From 95e24c8583da3cc2bcc708fe61b0b833efcb81ce Mon Sep 17 00:00:00 2001 From: Archangel Date: Fri, 27 Dec 2024 15:17:40 +0100 Subject: [PATCH] Begin WS implementation --- project/src/servers/HttpServer.ts | 68 ++++++++++++++++++++-- project/src/servers/WebSocketServer.ts | 49 +--------------- project/src/servers/ws/SPTWebsocketData.ts | 6 ++ 3 files changed, 70 insertions(+), 53 deletions(-) create mode 100644 project/src/servers/ws/SPTWebsocketData.ts diff --git a/project/src/servers/HttpServer.ts b/project/src/servers/HttpServer.ts index 36793f628..28f119cda 100644 --- a/project/src/servers/HttpServer.ts +++ b/project/src/servers/HttpServer.ts @@ -5,25 +5,31 @@ import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import type { IHttpConfig } from "@spt/models/spt/config/IHttpConfig"; import type { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; -import { WebSocketServer } from "@spt/servers/WebSocketServer"; import type { IHttpListener } from "@spt/servers/http/IHttpListener"; import { LocalisationService } from "@spt/services/LocalisationService"; import { type ErrorLike, type Server, serve } from "bun"; import { inject, injectAll, injectable } from "tsyringe"; +import type { JsonUtil } from "../utils/JsonUtil"; +import type { RandomUtil } from "../utils/RandomUtil"; +import type { IWebSocketConnectionHandler } from "./ws/IWebSocketConnectionHandler"; +import type { SPTWebsocketData } from "./ws/SPTWebsocketData"; @injectable() export class HttpServer { + protected Server: Server; protected httpConfig: IHttpConfig; protected started = false; constructor( @inject("PrimaryLogger") protected logger: ILogger, + @inject("RandomUtil") protected randomUtil: RandomUtil, + @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @injectAll("HttpListener") protected httpListeners: IHttpListener[], @inject("ConfigServer") protected configServer: ConfigServer, @inject("ApplicationContext") protected applicationContext: ApplicationContext, - @inject("WebSocketServer") protected webSocketServer: WebSocketServer, + @injectAll("WebSocketConnectionHandler") protected webSocketConnectionHandlers: IWebSocketConnectionHandler[], ) { this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP); } @@ -33,28 +39,68 @@ export class HttpServer { */ public load(): void { /* create server */ - const httpServer: Server = serve({ + this.Server = serve({ port: this.httpConfig.port, fetch: async (req: Request) => { + /* Handle WebSocket upgrades */ + if ( + this.Server.upgrade(req, { + data: new SPTWebsocketData(req.headers, new URL(req.url.replace("http://", "ws://"))), + }) + ) { + return; + } + return await this.handleRequest(req); }, + error: (e: ErrorLike) => { let errorMessage: string; /* server is already running or program using privileged port without root */ if ( process.platform === "linux" && !(process.getuid && process.getuid() === 0) && - this.httpConfig.port < 1024 + this.Server.port < 1024 ) { errorMessage = this.localisationService.getText("linux_use_priviledged_port_non_root"); } else { - errorMessage = this.localisationService.getText("port_already_in_use", this.httpConfig.port); + errorMessage = this.localisationService.getText("port_already_in_use", this.Server.port); } this.logger.error(`${errorMessage} [${e.message}] ${e.stack}`); return new Response(errorMessage, { status: 500 }); }, + websocket: { + message: async (ws, sentData) => { + const req: SPTWebsocketData = ws.data as SPTWebsocketData; + this.logger.error(req.url.host); + + const socketHandlers = this.webSocketConnectionHandlers.filter((wsh) => + req.url.pathname.includes(wsh.getHookUrl()), + ); + if ((socketHandlers?.length ?? 0) === 0) { + const message = `Socket connection received for url ${req.url.pathname}, but there is no websocket handler configured for it`; + this.logger.warning(message); + ws.send(this.jsonUtil.serialize({ error: message })); + ws.close(); + return; + } + socketHandlers.forEach((wsh) => { + this.logger.info(`WebSocketHandler "${wsh.getSocketId()}" connected`); + await wsh.onConnection(ws, req); + }); + }, + }, }); - this.started = true; + if (this.Server) { + this.started = true; + + this.logger.success( + this.localisationService.getText("websocket-started", this.httpServerHelper.getWebsocketUrl()), + ); + this.logger.success( + `${this.localisationService.getText("server_running")}, ${this.getRandomisedStartMessage()}!`, + ); + } // Setting up websocket //this.webSocketServer.setupWebSocket(httpServer); @@ -128,6 +174,16 @@ export class HttpServer { return found; } + protected getRandomisedStartMessage(): string { + if (this.randomUtil.getInt(1, 1000) > 999) { + return this.localisationService.getRandomTextThatMatchesPartialKey("server_start_meme_"); + } + + return globalThis.G_RELEASE_CONFIGURATION + ? `${this.localisationService.getText("server_start_success")}!` + : this.localisationService.getText("server_start_success"); + } + public isStarted(): boolean { return this.started; } diff --git a/project/src/servers/WebSocketServer.ts b/project/src/servers/WebSocketServer.ts index 1de5f8e02..60cf1f64b 100644 --- a/project/src/servers/WebSocketServer.ts +++ b/project/src/servers/WebSocketServer.ts @@ -1,17 +1,14 @@ -import http, { IncomingMessage } from "node:http"; import { HttpServerHelper } from "@spt/helpers/HttpServerHelper"; import type { ILogger } from "@spt/models/spt/utils/ILogger"; import type { IWebSocketConnectionHandler } from "@spt/servers/ws/IWebSocketConnectionHandler"; import { LocalisationService } from "@spt/services/LocalisationService"; import { JsonUtil } from "@spt/utils/JsonUtil"; import { RandomUtil } from "@spt/utils/RandomUtil"; +import type { ServerWebSocket } from "bun"; import { inject, injectAll, injectable } from "tsyringe"; -import { WebSocketServer as WSServer, WebSocket } from "ws"; @injectable() export class WebSocketServer { - protected webSocketServer: WSServer; - constructor( @inject("PrimaryLogger") protected logger: ILogger, @inject("RandomUtil") protected randomUtil: RandomUtil, @@ -21,47 +18,5 @@ export class WebSocketServer { @injectAll("WebSocketConnectionHandler") protected webSocketConnectionHandlers: IWebSocketConnectionHandler[], ) {} - public getWebSocketServer(): WSServer { - return this.webSocketServer; - } - - public setupWebSocket(httpServer: http.Server): void { - this.webSocketServer = new WSServer({ server: httpServer }); - - this.webSocketServer.addListener("listening", () => { - this.logger.success( - this.localisationService.getText("websocket-started", this.httpServerHelper.getWebsocketUrl()), - ); - this.logger.success( - `${this.localisationService.getText("server_running")}, ${this.getRandomisedMessage()}!`, - ); - }); - - this.webSocketServer.addListener("connection", this.wsOnConnection.bind(this)); - } - - protected getRandomisedMessage(): string { - if (this.randomUtil.getInt(1, 1000) > 999) { - return this.localisationService.getRandomTextThatMatchesPartialKey("server_start_meme_"); - } - - return globalThis.G_RELEASE_CONFIGURATION - ? `${this.localisationService.getText("server_start_success")}!` - : this.localisationService.getText("server_start_success"); - } - - protected wsOnConnection(ws: WebSocket, req: IncomingMessage): void { - const socketHandlers = this.webSocketConnectionHandlers.filter((wsh) => req.url.includes(wsh.getHookUrl())); - if ((socketHandlers?.length ?? 0) === 0) { - const message = `Socket connection received for url ${req.url}, but there is not websocket handler configured for it`; - this.logger.warning(message); - ws.send(this.jsonUtil.serialize({ error: message })); - ws.close(); - return; - } - socketHandlers.forEach((wsh) => { - wsh.onConnection(ws, req); - this.logger.info(`WebSocketHandler "${wsh.getSocketId()}" connected`); - }); - } + public wsOnConnection(ws: ServerWebSocket, req: string | Buffer): Promise {} } diff --git a/project/src/servers/ws/SPTWebsocketData.ts b/project/src/servers/ws/SPTWebsocketData.ts new file mode 100644 index 000000000..5d09c68a2 --- /dev/null +++ b/project/src/servers/ws/SPTWebsocketData.ts @@ -0,0 +1,6 @@ +export class SPTWebsocketData { + constructor( + public headers: Headers, + public url: URL, + ) {} +}