75 lines
1.9 KiB
TypeScript
75 lines
1.9 KiB
TypeScript
|
|
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<ChatEvent>();
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
}
|