import {inject, Injectable, OnDestroy} from '@angular/core'; import {Subject} from 'rxjs'; import {AuthService} from '@services/auth.service'; import {EventSourcePolyfill} from 'event-source-polyfill'; export interface ChatEvent { type: string; data: any; } interface SsePayloadEvent { data: string; } @Injectable({providedIn: 'root'}) export class ChatService implements OnDestroy { private eventSource?: EventSourcePolyfill; private events$ = new Subject(); private readonly authService: AuthService = inject(AuthService) connect() { if (this.eventSource) { return; // already connected } const jwt = this.authService.getJwt(); if (!jwt) { //TODO [Stijn] [2026-07-18]: Error when no JWT available (log in?) return; } const source = new EventSourcePolyfill('/api/chat/read/subscribe', { headers: { Authorization: `Bearer ${jwt}` }, heartbeatTimeout: 60000 }); this.eventSource = source; this.on(source, 'connected', (event) => { console.log('SSE connected:', event.data); }); this.on(source, 'chat-message', (event) => { this.events$.next({type: 'chat-message', data: JSON.parse(event.data)}); }); source.onerror = (err) => { console.error('SSE error, polyfill will auto-reconnect:', err); }; } // single, deliberate escape hatch from the broken DOM/polyfill type overlap — // everything else in this file stays fully typed private on(source: EventSourcePolyfill, eventName: string, handler: (event: SsePayloadEvent) => void): void { (source as unknown as { addEventListener: (type: string, listener: (event: SsePayloadEvent) => void) => void }) .addEventListener(eventName, handler); } onEvent() { return this.events$.asObservable(); } disconnect(): void { this.eventSource?.close(); this.eventSource = undefined; } ngOnDestroy(): void { this.disconnect(); } }