import type {Server, ServerWebSocket} from 'bun'; import {LoggerFactory, type ILogger} from '@techniker-me/logger'; import {MessageKindMapping} from './messaging/MessageKind'; export default class SignalingServer { private readonly _logger: ILogger = LoggerFactory.getLogger('SignalingServer'); private readonly _port: number; private readonly _hostname: string; private readonly _development: boolean; private readonly _clients: Set> = new Set(); constructor(port: number, hostname: string = '0.0.0.0', development: boolean = false) { this._port = port; this._hostname = hostname; this._development = development; } get port(): number { return this._port; } get hostname(): string { return this._hostname; } get development(): boolean { return this._development; } get websocket() { return { open: this.handleWebSocketOpen.bind(this), message: this.handleWebSocketMessage.bind(this), close: this.handleWebSocketClose.bind(this), drain: this.handleWebSocketDrain.bind(this), error: this.handleWebSocketError.bind(this), perMessageDeflate: true, maxPayloadLength: 10 * 1024 }; } get fetch() { return (req: Request, server: Server) => { this._logger.info(`Fetch request received [${req.url}] from [${server.requestIP(req)?.address}:${server.requestIP(req)?.port}]`); const url = new URL(req.url); if (url.pathname.endsWith('/ws')) { this._logger.info('Upgrading to WebSocket'); server.upgrade(req); return; } return new Response('Hello World'); }; } private handleWebSocketOpen(ws: ServerWebSocket): void { this._logger.info('WebSocket opened'); this._clients.add(ws); } private handleWebSocketMessage(ws: ServerWebSocket, message: string | Buffer): void { const messageString = typeof message === 'string' ? message : message.toString(); const jsonMessage = JSON.parse(messageString); this._logger.info(`WebSocket message received [${MessageKindMapping.convertMessageKindToMessageType(jsonMessage.type)}]`); // Forward message to all other clients (following sequence diagram) // This allows the signaling server to relay offers/answers between caller and callee this._clients.forEach(client => { if (client !== ws && client.readyState === 1) { // 1 = OPEN client.send(messageString); } }); } private handleWebSocketClose(ws: ServerWebSocket): void { this._logger.info('WebSocket closed'); this._clients.delete(ws); } private handleWebSocketError(ws: ServerWebSocket, error: Error): void { this._logger.error('WebSocket error', error); } private handleWebSocketDrain(ws: ServerWebSocket): void { this._logger.info('WebSocket drained'); } }