import { ENV_CONSTANT } from '@/constants/secrect.constant'; export type SseEventHandler = ( data: T, event: MessageEvent, ) => void; export type SseEventMap> = { [EventName in keyof TEvents]: SseEventHandler; }; export interface SseConnection { source: EventSource; close: () => void; } function getSseUrl(path: string) { if (/^https?:\/\//i.test(path)) return path; const baseUrl = ENV_CONSTANT.BASE_API_URL ?? ''; const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; const normalizedPath = path.startsWith('/') ? path : `/${path}`; return `${normalizedBase}${normalizedPath}`; } function parseEventData(event: MessageEvent) { if (!event.data) return null; try { return JSON.parse(event.data); } catch { return event.data; } } export const sseService = { connect: >( path: string, events: SseEventMap, ): SseConnection => { const source = new EventSource(getSseUrl(path)); const cleanups: Array<() => void> = []; Object.entries(events).forEach(([eventName, handler]) => { const listener = (event: Event) => { const messageEvent = event as MessageEvent; (handler as SseEventHandler)( parseEventData(messageEvent), messageEvent, ); }; source.addEventListener(eventName, listener); cleanups.push(() => source.removeEventListener(eventName, listener)); }); return { source, close: () => { cleanups.forEach((cleanup) => cleanup()); source.close(); }, }; }, };