Initial commit

This commit is contained in:
Jordan
2026-04-01 23:16:45 +01:00
commit 91cfdaee72
200 changed files with 25589 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
/**
* Redis Client for Session Persistence
*
* Provides persistent storage for user credentials across server restarts.
* Falls back to in-memory Map if Redis is unavailable.
*/
import { createClient } from 'redis';
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
const USER_CACHE_TTL = 30 * 60; // 30 minutes in seconds
let redisClient = null;
let redisAvailable = false;
// Fallback in-memory cache (used if Redis is unavailable)
const memoryCache = new Map();
/**
* Initialize Redis client
*/
export async function initRedis() {
try {
console.error('[Redis] Connecting to Redis at', REDIS_URL);
redisClient = createClient({
url: REDIS_URL,
socket: {
connectTimeout: 5000,
reconnectStrategy: (retries) => {
if (retries > 3) {
console.error('[Redis] Max reconnection attempts reached, falling back to memory cache');
redisAvailable = false;
return false; // Stop reconnecting
}
return Math.min(retries * 100, 3000);
}
}
});
redisClient.on('error', (err) => {
console.error('[Redis] Error:', err.message);
redisAvailable = false;
});
redisClient.on('connect', () => {
console.error('[Redis] Connected successfully');
redisAvailable = true;
});
redisClient.on('reconnecting', () => {
console.error('[Redis] Reconnecting...');
});
await redisClient.connect();
redisAvailable = true;
console.error('[Redis] Ready to use');
} catch (error) {
console.error('[Redis] Failed to initialize:', error.message);
console.error('[Redis] Falling back to in-memory cache');
redisAvailable = false;
}
}
/**
* Set user credentials in cache (Redis or memory)
*/
export async function setUserCredentials(userIdentifier, credentials) {
if (!userIdentifier) {
console.error('[Redis] Cannot set credentials: no userIdentifier');
return false;
}
const key = `user:creds:${userIdentifier}`;
const value = JSON.stringify({
...credentials,
lastUsed: Date.now()
});
if (redisAvailable && redisClient) {
try {
await redisClient.setEx(key, USER_CACHE_TTL, value);
console.error(`[Redis] Saved credentials for user ${userIdentifier} (TTL: ${USER_CACHE_TTL}s)`);
return true;
} catch (error) {
console.error('[Redis] Error saving to Redis:', error.message);
console.error('[Redis] Falling back to memory cache for this operation');
redisAvailable = false;
}
}
// Fallback to memory cache
memoryCache.set(key, {
value,
expiresAt: Date.now() + (USER_CACHE_TTL * 1000)
});
console.error(`[Redis] Saved credentials for user ${userIdentifier} to memory cache`);
return true;
}
/**
* Get user credentials from cache (Redis or memory)
*/
export async function getUserCredentials(userIdentifier) {
if (!userIdentifier) {
return null;
}
const key = `user:creds:${userIdentifier}`;
if (redisAvailable && redisClient) {
try {
const value = await redisClient.get(key);
if (value) {
console.error(`[Redis] Retrieved credentials for user ${userIdentifier} from Redis`);
const creds = JSON.parse(value);
// Update lastUsed timestamp
await setUserCredentials(userIdentifier, {
website: creds.website,
token: creds.token,
tokenHash: creds.tokenHash,
profileName: creds.profileName
});
return creds;
}
} catch (error) {
console.error('[Redis] Error reading from Redis:', error.message);
console.error('[Redis] Falling back to memory cache');
redisAvailable = false;
}
}
// Fallback to memory cache
const cached = memoryCache.get(key);
if (cached) {
if (Date.now() < cached.expiresAt) {
console.error(`[Redis] Retrieved credentials for user ${userIdentifier} from memory cache`);
const creds = JSON.parse(cached.value);
// Update expiration
memoryCache.set(key, {
value: cached.value,
expiresAt: Date.now() + (USER_CACHE_TTL * 1000)
});
return creds;
} else {
console.error(`[Redis] Memory cache expired for user ${userIdentifier}`);
memoryCache.delete(key);
}
}
return null;
}
/**
* Delete user credentials from cache
*/
export async function deleteUserCredentials(userIdentifier) {
if (!userIdentifier) {
return;
}
const key = `user:creds:${userIdentifier}`;
if (redisAvailable && redisClient) {
try {
await redisClient.del(key);
console.error(`[Redis] Deleted credentials for user ${userIdentifier} from Redis`);
} catch (error) {
console.error('[Redis] Error deleting from Redis:', error.message);
}
}
// Also delete from memory cache
memoryCache.delete(key);
console.error(`[Redis] Deleted credentials for user ${userIdentifier} from memory cache`);
}
/**
* Get Redis health status
*/
export function getRedisStatus() {
return {
available: redisAvailable,
connected: redisClient?.isOpen || false,
url: REDIS_URL,
fallbackCacheSize: memoryCache.size
};
}
/**
* Close Redis connection (for graceful shutdown)
*/
export async function closeRedis() {
if (redisClient) {
try {
await redisClient.quit();
console.error('[Redis] Connection closed');
} catch (error) {
console.error('[Redis] Error closing connection:', error.message);
}
}
}