diff --git a/src/webserver.ts b/src/webserver.ts index c17bbfb..60aaeed 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -9,7 +9,6 @@ import express, { type Response, } from "express"; import helmet from "helmet"; -import { WebSocketServer } from "ws"; import { config } from "./config"; import { AppError } from "./errors"; import { createChildLogger, logger } from "./logger"; @@ -37,7 +36,7 @@ import { exposePcmBroadcastGlobal, exposeVideoBroadcastGlobal, } from "./ws/broadcastGlobals"; -import { createVoiceAudioBridge } from "./ws/voiceAudioBridge"; +import { startWebSocketServer } from "./ws/server"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -61,8 +60,6 @@ export async function startWebserver( const server = http.createServer(app); const wsPath = "/ws"; - const wss = new WebSocketServer({ server, path: wsPath }); - wsLogger.info({ port, wsPath }, "WebSocket server listening"); // Create broadcaster instance const broadcaster = createBroadcaster(); @@ -205,40 +202,15 @@ export async function startWebserver( exposeVideoBroadcastGlobal(() => broadcaster.getClients(), wsLogger); exposeActiveUserGlobal(activeUsers, broadcastUserState); - const voiceAudioBridge = createVoiceAudioBridge(wsLogger); - - wss.on("connection", (ws) => { - wsLogger.info({ port, wsPath }, "New WebSocket connection"); - broadcaster.addClient(ws); - - ws.send( - JSON.stringify({ - type: "user_state", - users: Array.from(activeUsers.entries()).map(([id, data]) => ({ - id, - ...data, - })), - }), - ); - ws.send(JSON.stringify({ type: "ui_state", state: getSharedUIState() })); - ws.send( - JSON.stringify({ - type: "media_state", - state: mediaController.getState(), - }), - ); - - ws.on("message", (data: Buffer | ArrayBuffer | Buffer[]) => { - if (!Buffer.isBuffer(data)) return; - voiceAudioBridge.handleBrowserAudio(data); - }); - - ws.on("close", () => { - broadcaster.removeClient(ws); - }); - ws.on("error", () => { - broadcaster.removeClient(ws); - }); + startWebSocketServer({ + server, + port, + wsPath, + broadcaster, + activeUsers, + getSharedUIState, + mediaController, + logger: wsLogger, }); app.use( diff --git a/src/ws/server.ts b/src/ws/server.ts new file mode 100644 index 0000000..4229ff1 --- /dev/null +++ b/src/ws/server.ts @@ -0,0 +1,78 @@ +import type { Server as HttpServer } from "node:http"; +import { WebSocketServer } from "ws"; +import type { createChildLogger } from "../logger"; +import type { MediaController } from "../media/mediaController"; +import type { ModerationBroadcaster } from "../moderation/types"; +import { createVoiceAudioBridge } from "./voiceAudioBridge"; + +type Logger = ReturnType; + +type ActiveUsers = Map< + string, + { username: string; avatar: string; speaking: boolean } +>; + +export interface WebSocketServerOptions { + server: HttpServer; + port: number; + wsPath: string; + broadcaster: ModerationBroadcaster; + activeUsers: ActiveUsers; + getSharedUIState: () => unknown; + mediaController: MediaController; + logger: Logger; +} + +export function startWebSocketServer(options: WebSocketServerOptions) { + const wss = new WebSocketServer({ + server: options.server, + path: options.wsPath, + }); + const voiceAudioBridge = createVoiceAudioBridge(options.logger); + + options.logger.info( + { port: options.port, wsPath: options.wsPath }, + "WebSocket server listening", + ); + + wss.on("connection", (ws) => { + options.logger.info( + { port: options.port, wsPath: options.wsPath }, + "New WebSocket connection", + ); + options.broadcaster.addClient(ws); + + ws.send( + JSON.stringify({ + type: "user_state", + users: Array.from(options.activeUsers.entries()).map(([id, data]) => ({ + id, + ...data, + })), + }), + ); + ws.send( + JSON.stringify({ type: "ui_state", state: options.getSharedUIState() }), + ); + ws.send( + JSON.stringify({ + type: "media_state", + state: options.mediaController.getState(), + }), + ); + + ws.on("message", (data: Buffer | ArrayBuffer | Buffer[]) => { + if (!Buffer.isBuffer(data)) return; + voiceAudioBridge.handleBrowserAudio(data); + }); + + ws.on("close", () => { + options.broadcaster.removeClient(ws); + }); + ws.on("error", () => { + options.broadcaster.removeClient(ws); + }); + }); + + return wss; +}