Files
road-monitoring-ui/src/services/sse/sse.service.ts

67 lines
1.7 KiB
TypeScript

import { ENV_CONSTANT } from '@/constants/secrect.constant';
export type SseEventHandler<T = unknown> = (
data: T,
event: MessageEvent,
) => void;
export type SseEventMap<TEvents extends Record<string, unknown>> = {
[EventName in keyof TEvents]: SseEventHandler<TEvents[EventName]>;
};
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: <TEvents extends Record<string, unknown>>(
path: string,
events: SseEventMap<TEvents>,
): 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();
},
};
},
};