import { allServerEventsData, type ServerEventsData, type ServerEventsEvent } from '../serverEventsTypes' import { RedisGenericManager } from './redisGenericManager' export class RedisServerEvents extends RedisGenericManager { private readonly prefix = 'server_events:' /** * Broadcast an event to a user's server events listener. * * @param eventName - The event name to broadcast. * @param userId - The user ID to broadcast to. * @param data - The event to broadcast. */ async send( userId: number, eventName: T, data: ServerEventsData[T] ): Promise { const channel = `${this.prefix}${String(userId)}:${eventName}` as const await this.redisClient.publish(channel, JSON.stringify(data)) } /** * Subscribe to server events for a user. * * @param eventName - The event name to subscribe to. * @param userId - The user ID to subscribe to. * @param callback - The callback to call when the event is received. * @returns A cleanup function to unsubscribe. */ async addListener( eventName: T, userId: number, callback: (event: T extends 'all' ? ServerEventsEvent : Extract) => void ): Promise<() => Promise> { const subscriber = this.redisClient.duplicate() await subscriber.connect() const channel = eventName === 'all' ? allServerEventsData.map((eventName) => `${this.prefix}${String(userId)}:${eventName}` as const) : (`${this.prefix}${String(userId)}:${eventName}` as const) await subscriber.subscribe(channel, (message, channelKey) => { try { const data = JSON.parse(message) as ServerEventsData[T extends 'all' ? keyof ServerEventsData : T] const type = channelKey.split(':')[2] as T extends 'all' ? keyof ServerEventsData : T const event = { type, data } as T extends 'all' ? ServerEventsEvent : Extract callback(event) } catch (error) { console.error('Failed to parse notification stream event:', error) } }) return async () => { await subscriber.unsubscribe(channel) subscriber.destroy() } } } let redisServerEvents: RedisServerEvents | null = null export async function getRedisServerEvents() { redisServerEvents ??= await RedisServerEvents.createAndConnect({ expirationTime: 60 * 60 * 24, // 24 hours in seconds }) return redisServerEvents }