import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { GameStreamEvent, ErrorEvent } from '../models/game.models'; const WS_CONNECT_TIMEOUT_MS = 3000; @Injectable({ providedIn: 'root' }) export class StreamHandlerService { createGameStream(wsUrl: string, gameId: string): Observable { return new Observable((observer) => { const ws = new WebSocket(wsUrl); let connected = false; const emitErrorEvent = (message: string): void => { const errorEvent: ErrorEvent = { type: 'error', error: { code: 'STREAM_ERROR', message } }; observer.next(errorEvent); }; const failAndComplete = (reason: string): void => { console.warn(`[StreamHandler] WebSocket failed for ${gameId}: ${reason}`); emitErrorEvent(reason); observer.complete(); }; const connectionTimeoutId = setTimeout(() => { if (!connected) { ws.close(); failAndComplete('WebSocket connection timed out — falling back to polling'); } }, WS_CONNECT_TIMEOUT_MS); ws.onopen = () => { connected = true; clearTimeout(connectionTimeoutId); console.log(`[StreamHandler] WebSocket connected for ${gameId}`); }; ws.onmessage = (message) => { const payload = typeof message.data === 'string' ? message.data : ''; if (!payload.trim()) return; try { const event = JSON.parse(payload) as GameStreamEvent; observer.next(event); } catch { // ignore malformed frames } }; ws.onerror = () => { clearTimeout(connectionTimeoutId); if (!connected) { failAndComplete('WebSocket connection error — falling back to polling'); } }; ws.onclose = () => { clearTimeout(connectionTimeoutId); if (connected) { observer.complete(); } }; return () => { ws.close(); }; }); } }