Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/proxy/cache/redis-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export class RedisCache implements TokenCache {
private readonly prefix: string;
private readonly url: string;
private readonly connectTimeout: number;
private readonly tls: boolean;
private readonly password?: string;
private readonly username?: string;
private hits = 0;
private misses = 0;
private connected = false;
Expand All @@ -24,6 +27,9 @@ export class RedisCache implements TokenCache {
this.url = options.url;
this.prefix = options.prefix ?? DEFAULT_PREFIX;
this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
this.tls = options.tls ?? options.url.startsWith("rediss://");
this.password = options.password;
this.username = options.username;
}

private key(k: string): string {
Expand Down Expand Up @@ -212,18 +218,24 @@ export class RedisCache implements TokenCache {
return withSpan("cache.redis.connect", async () => {
if (this.connected && this.client) return;

const client = createClient({
// deno-lint-ignore no-explicit-any
const clientOpts: Record<string, any> = {
url: this.url,
socket: {
connectTimeout: this.connectTimeout,
reconnectStrategy: (retries) => {
tls: this.tls || undefined,
reconnectStrategy: (retries: number) => {
if (retries > MAX_RECONNECT_RETRIES) {
return new Error("Max reconnection attempts reached");
}
return Math.min(retries * RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_MAX_MS);
},
},
});
};
if (this.password) clientOpts.password = this.password;
if (this.username) clientOpts.username = this.username;

const client = createClient(clientOpts);

client.on("error", (err) => {
logger.error("[RedisCache] Client error", {
Expand Down
3 changes: 3 additions & 0 deletions src/proxy/cache/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export interface RedisCacheOptions {
url: string;
prefix?: string;
connectTimeout?: number;
tls?: boolean;
password?: string;
username?: string;
}

export type CacheOptions =
Expand Down
36 changes: 32 additions & 4 deletions src/utils/redis-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ export interface RedisClient {
isOpen?: boolean;
}

interface RedisClientOptions {
export interface RedisClientOptions {
url?: string;
connectTimeout?: number;
autoReconnect?: boolean;
tls?: boolean;
password?: string;
username?: string;
}

let sharedClient: RedisClient | null = null;
Expand Down Expand Up @@ -64,12 +67,14 @@ export async function getRedisClient(options: RedisClientOptions = {}): Promise<
}

async function createClient(options: RedisClientOptions): Promise<RedisClient> {
let createClientFn: ((opts: { url?: string }) => RedisClient) | undefined;
// deno-lint-ignore no-explicit-any
let createClientFn: ((opts: Record<string, any>) => RedisClient) | undefined;

try {
const redisClientModule = "npm:@redis/client@1.5.8";
const mod = await import(redisClientModule);
createClientFn = mod.createClient as (opts: { url?: string }) => RedisClient;
// deno-lint-ignore no-explicit-any
createClientFn = mod.createClient as (opts: Record<string, any>) => RedisClient;
} catch (error) {
logger.debug("Failed to load @redis/client module", { error });
throw DEPENDENCY_MISSING.create({
Expand All @@ -78,7 +83,30 @@ async function createClient(options: RedisClientOptions): Promise<RedisClient> {
});
}

const client = createClientFn({ url: options.url ?? getEnv("REDIS_URL") });
const url = options.url ?? getEnv("REDIS_URL");
const useTls = options.tls ?? url?.startsWith("rediss://") ?? false;

if (!useTls && getEnv("NODE_ENV") === "production") {
logger.warn(
"Redis connection without TLS in production. Set REDIS_URL to rediss:// or pass tls: true.",
);
}

// deno-lint-ignore no-explicit-any
const clientOpts: Record<string, any> = { url };
if (useTls) {
clientOpts.socket = { tls: true };
}
const password = options.password ?? getEnv("REDIS_PASSWORD");
if (password) {
clientOpts.password = password;
}
const username = options.username ?? getEnv("REDIS_USERNAME");
if (username) {
clientOpts.username = username;
}

const client = createClientFn(clientOpts);

if (typeof client.on === "function") {
client.on("error", (err: unknown) => {
Expand Down
Loading