File size: 2,705 Bytes
87cb242 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | import Redis from 'ioredis';
import { CONFIG } from '../config';
import { logger } from '../utils/logger';
/**
* Redis Bucket 1 β Presence
*
* Key schema:
* presence:uid:{uid} β socket_id (TTL = PRESENCE_TTL_S)
* status:uid:{uid} β "online" (TTL = PRESENCE_TTL_S)
*
* Separate client instance so Bucket 1 errors never affect Bucket 2.
*/
const presenceClient = new Redis(CONFIG.REDIS_PRESENCE_URL, {
maxRetriesPerRequest: 2,
connectTimeout: 5000,
lazyConnect: false,
enableReadyCheck: true,
// Keep-alive so the HF container connection stays warm
keepAlive: 10000,
});
presenceClient.on('connect', () => logger.info('Presence Redis connected'));
presenceClient.on('error', (err: Error) =>
logger.error('Presence Redis error', { error: err.message })
);
presenceClient.on('reconnecting', () => logger.warn('Presence Redis reconnecting'));
export { presenceClient };
// ββ Key builders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const presenceKey = (uid: string) => `presence:uid:${uid}`;
const statusKey = (uid: string) => `status:uid:${uid}`;
// ββ Operations βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/** Map uid β socketId, mark user online. Called on WS connect. */
export async function setOnline(uid: string, socketId: string): Promise<void> {
const ttl = CONFIG.PRESENCE_TTL_S;
// MULTI/EXEC keeps it atomic
await presenceClient
.multi()
.set(presenceKey(uid), socketId, 'EX', ttl)
.set(statusKey(uid), 'online', 'EX', ttl)
.exec();
}
/** Refresh TTL on heartbeat. Does not overwrite the socket id. */
export async function refreshPresence(uid: string): Promise<void> {
const ttl = CONFIG.PRESENCE_TTL_S;
await presenceClient
.multi()
.expire(presenceKey(uid), ttl)
.expire(statusKey(uid), ttl)
.exec();
}
/** Remove user from presence. Called on WS close. */
export async function setOffline(uid: string): Promise<void> {
await presenceClient
.multi()
.del(presenceKey(uid))
.del(statusKey(uid))
.exec();
}
/** Returns the socket id for a uid, or null if offline. */
export async function getSocketId(uid: string): Promise<string | null> {
return presenceClient.get(presenceKey(uid));
}
/** Returns true if the user is online. */
export async function isOnline(uid: string): Promise<boolean> {
const status = await presenceClient.get(statusKey(uid));
return status === 'online';
}
|