| 139 | * so commands are queued and executed once connected. No manual connection checks needed. |
| 140 | */ |
| 141 | export function getRedisClient(): Redis | null { |
| 142 | if (typeof window !== 'undefined') return null |
| 143 | if (!redisUrl) return null |
| 144 | if (state.client) return state.client |
| 145 | |
| 146 | // Outside the try/catch so config errors aren't silently swallowed. |
| 147 | const defaults = getRedisConnectionDefaults(redisUrl) |
| 148 | |
| 149 | try { |
| 150 | logger.info('Initializing Redis client') |
| 151 | |
| 152 | state.client = new Redis(redisUrl, { |
| 153 | ...defaults, |
| 154 | commandTimeout: 5000, |
| 155 | maxRetriesPerRequest: 5, |
| 156 | |
| 157 | retryStrategy: (times) => { |
| 158 | if (times > 10) { |
| 159 | logger.error(`Redis reconnection attempt ${times}`, { nextRetryMs: 30000 }) |
| 160 | return 30000 |
| 161 | } |
| 162 | const base = Math.min(1000 * 2 ** (times - 1), 10000) |
| 163 | const jitter = randomFloat() * base * 0.3 |
| 164 | const delay = Math.round(base + jitter) |
| 165 | logger.warn('Redis reconnecting', { attempt: times, nextRetryMs: delay }) |
| 166 | return delay |
| 167 | }, |
| 168 | |
| 169 | reconnectOnError: (err) => { |
| 170 | const targetErrors = ['READONLY', 'ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED'] |
| 171 | return targetErrors.some((e) => err.message.includes(e)) |
| 172 | }, |
| 173 | }) |
| 174 | |
| 175 | state.client.on('connect', () => logger.info('Redis connected')) |
| 176 | state.client.on('ready', () => logger.info('Redis ready')) |
| 177 | state.client.on('error', (err: Error) => { |
| 178 | logger.error('Redis error', { error: err.message, code: (err as any).code }) |
| 179 | }) |
| 180 | state.client.on('close', () => logger.warn('Redis connection closed')) |
| 181 | state.client.on('end', () => logger.error('Redis connection ended')) |
| 182 | |
| 183 | startPingHealthCheck(state.client) |
| 184 | |
| 185 | return state.client |
| 186 | } catch (error) { |
| 187 | logger.error('Failed to initialize Redis client', { error }) |
| 188 | return null |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | /** |
| 193 | * Lua script for safe lock release. |