Updated dependencies + A few other things (!150)
Initially this was going to be an update to dependencies but it seems i got a little carried away!
Anyways this PR removes 2 unused dependencies (`jshint` and `utf-8-validate`), and 2 other, `del` and `fs-extra` that were replaced by the built-in `fs/promises`.
It also renames all `tsconfig` and `Dockerfile` files, in a way that when viewed in a file tree sorted alphabetically they will be next to each other.
It also updates the typescript target to `ES2022`, and changes moduleResolution from `Node` to `Node10` (this isn't an update, they are the same thing but `Node` is now deprecated).
It also adds the `node:` discriminator to every import from built-in modules.
It also has major changes to the build script, `del` and `fs-extra` were only being used in the build script, it's now using `fs/promises` instead, cleaned up the code from some functions, adds better documentation to a few functions, and renames some gulp tasks and npm scripts to better represent what they actually do.
And finally it updates dependencies, except for `atomically` which can't be updated unless the project switches to ESM.
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/150
Co-authored-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
Co-committed-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
2023-10-14 09:01:41 +00:00
|
|
|
import http, { IncomingMessage } from "node:http";
|
2023-03-03 15:23:46 +00:00
|
|
|
import { inject, injectable } from "tsyringe";
|
|
|
|
import WebSocket from "ws";
|
|
|
|
|
2023-10-19 17:21:17 +00:00
|
|
|
import { HttpServerHelper } from "@spt-aki/helpers/HttpServerHelper";
|
|
|
|
import { INotification, NotificationType } from "@spt-aki/models/eft/notifier/INotifier";
|
|
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
|
|
import { IHttpConfig } from "@spt-aki/models/spt/config/IHttpConfig";
|
|
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
|
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
|
|
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
|
|
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
@injectable()
|
2023-11-16 21:42:06 +00:00
|
|
|
export class WebSocketServer
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
constructor(
|
|
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
2023-08-09 10:49:45 +00:00
|
|
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
2023-11-16 21:42:06 +00:00
|
|
|
@inject("HttpServerHelper") protected httpServerHelper: HttpServerHelper,
|
|
|
|
)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected httpConfig: IHttpConfig;
|
2023-11-16 21:42:06 +00:00
|
|
|
protected defaultNotification: INotification = { type: NotificationType.PING, eventId: "ping" };
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
protected webSockets: Record<string, WebSocket.WebSocket> = {};
|
|
|
|
protected websocketPingHandler = null;
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
public setupWebSocket(httpServer: http.Server): void
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
const webSocketServer = new WebSocket.Server({ server: httpServer });
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
webSocketServer.addListener("listening", () =>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
this.logger.success(
|
|
|
|
this.localisationService.getText("websocket-started", this.httpServerHelper.getWebsocketUrl()),
|
|
|
|
);
|
|
|
|
this.logger.success(
|
|
|
|
`${this.localisationService.getText("server_running")}, ${this.getRandomisedMessage()}!`,
|
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
webSocketServer.addListener("connection", this.wsOnConnection.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
public sendMessage(sessionID: string, output: INotification): void
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (this.isConnectionWebSocket(sessionID))
|
|
|
|
{
|
2023-08-09 10:49:45 +00:00
|
|
|
this.webSockets[sessionID].send(this.jsonUtil.serialize(output));
|
2023-03-03 15:23:46 +00:00
|
|
|
this.logger.debug(this.localisationService.getText("websocket-message_sent"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this.logger.debug(this.localisationService.getText("websocket-not_ready_message_not_sent", sessionID));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (err)
|
|
|
|
{
|
|
|
|
this.logger.error(this.localisationService.getText("websocket-message_send_failed_with_error", err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
protected getRandomisedMessage(): string
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-11-16 21:42:06 +00:00
|
|
|
if (this.randomUtil.getInt(1, 1000) > 999)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-08-09 12:07:31 +01:00
|
|
|
return this.localisationService.getRandomTextThatMatchesPartialKey("server_start_meme_");
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (globalThis.G_RELEASE_CONFIGURATION)
|
|
|
|
? `${this.localisationService.getText("server_start_success")}!`
|
|
|
|
: this.localisationService.getText("server_start_success");
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
public isConnectionWebSocket(sessionID: string): boolean
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
return this.webSockets[sessionID] !== undefined && this.webSockets[sessionID].readyState === WebSocket.OPEN;
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
protected wsOnConnection(ws: WebSocket.WebSocket, req: IncomingMessage): void
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
// Strip request and break it into sections
|
|
|
|
const splitUrl = req.url.substring(0, req.url.indexOf("?")).split("/");
|
|
|
|
const sessionID = splitUrl.pop();
|
|
|
|
|
|
|
|
this.logger.info(this.localisationService.getText("websocket-player_connected", sessionID));
|
|
|
|
|
|
|
|
const logger = this.logger;
|
|
|
|
const msgToLog = this.localisationService.getText("websocket-received_message", sessionID);
|
2023-11-16 21:42:06 +00:00
|
|
|
ws.on("message", function message(msg)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
logger.info(`${msgToLog} ${msg}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.webSockets[sessionID] = ws;
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
if (this.websocketPingHandler)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
clearInterval(this.websocketPingHandler);
|
|
|
|
}
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
this.websocketPingHandler = setInterval(() =>
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
this.logger.debug(this.localisationService.getText("websocket-pinging_player", sessionID));
|
|
|
|
|
2023-11-16 21:42:06 +00:00
|
|
|
if (ws.readyState === WebSocket.OPEN)
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
2023-08-09 10:49:45 +00:00
|
|
|
ws.send(this.jsonUtil.serialize(this.defaultNotification));
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
else
|
2023-03-03 15:23:46 +00:00
|
|
|
{
|
|
|
|
this.logger.debug(this.localisationService.getText("websocket-socket_lost_deleting_handle"));
|
|
|
|
clearInterval(this.websocketPingHandler);
|
|
|
|
delete this.webSockets[sessionID];
|
|
|
|
}
|
|
|
|
}, this.httpConfig.webSocketPingDelayMs);
|
|
|
|
}
|
2023-11-16 21:42:06 +00:00
|
|
|
}
|