import { assertUnreachable, ReadOnlySubject, Subject } from '@techniker-me/tools'; import type { ISignalingMessage, IWebRTCClient } from '../interfaces/IWebRTCClient.ts'; export enum WebSocketClientStatus { Offline = 0, Connecting = 1, Online = 2, Reconnecting = 3, Error = 4, Closed = 5, } export type WebSocketClientStatusType = 'offline' | 'connecting' | 'online' | 'reconnecting' | 'error' | 'closed'; export class WebSocketClientStatusMapping { public static convertWebSocketClientStatusToWebSocketClientStatusType(status: WebSocketClientStatus): WebSocketClientStatusType { switch (status) { case WebSocketClientStatus.Offline: return 'offline'; case WebSocketClientStatus.Connecting: return 'connecting'; case WebSocketClientStatus.Online: return 'online'; case WebSocketClientStatus.Reconnecting: return 'reconnecting'; case WebSocketClientStatus.Error: return 'error'; case WebSocketClientStatus.Closed: return 'closed'; default: assertUnreachable(status); } } public static convertWebSocketClientStatusTypeToWebSocketClientStatus(status: WebSocketClientStatusType): WebSocketClientStatus { switch (status) { case 'offline': return WebSocketClientStatus.Offline; case 'connecting': return WebSocketClientStatus.Connecting; case 'online': return WebSocketClientStatus.Online; case 'reconnecting': return WebSocketClientStatus.Reconnecting; case 'error': return WebSocketClientStatus.Error; case 'closed': return WebSocketClientStatus.Closed; default: assertUnreachable(status); } } } export class WebSocketClient implements IWebRTCClient { private readonly _status: Subject = new Subject(WebSocketClientStatus.Offline); private readonly _readOnlyStatus: ReadOnlySubject = new ReadOnlySubject(this._status); private ws: WebSocket | null = null; private role: 'publisher' | 'subscriber'; private messageHandlers: Map void> = new Map(); constructor(role: 'publisher' | 'subscriber') { this.role = role; } get status(): ReadOnlySubject { return this._readOnlyStatus; } async connect(): Promise { this._status.value = WebSocketClientStatus.Online; return new Promise((resolve, reject) => { const wsUrl = `ws://localhost:3000/ws?role=${this.role}`; this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { this._status.value = WebSocketClientStatus.Online; resolve(); }; this.ws.onerror = (error) => { this._status.value = WebSocketClientStatus.Error; reject(error); }; this.ws.onmessage = (event) => { try { const message: ISignalingMessage = JSON.parse(event.data); this.handleMessage(message); } catch (error) { console.error('Failed to parse WebSocket message:', error); } }; this.ws.onclose = () => { this._status.value = WebSocketClientStatus.Closed; this.ws = null; }; }); } disconnect(): void { if (this.ws) { this.ws.close(); this.ws = null; } } sendMessage(message: ISignalingMessage): void { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(message)); } } isConnected(): boolean { return this.ws !== null && this.ws.readyState === WebSocket.OPEN; } onMessage(type: string, handler: (message: ISignalingMessage) => void): void { this.messageHandlers.set(type, handler); } private handleMessage(message: ISignalingMessage): void { const handler = this.messageHandlers.get(message.type); if (handler) { handler(message); } } }