From c7ca457c4514ad0a6f4a53c5355832fec0a163b5 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 18 Jul 2025 20:10:07 +0200 Subject: [PATCH] Fix type errors in /server related to typescript strict mode (#1468) ## Description: Task: #1075 Enable typescript strict mode First (out of many) PR in order to enable typescript Strict mode in the project. This PR fixes all type issues present under the `/server` path. ## Specifics Most changes are just basic type fixes, however here are some further explanation for some of the more complex changes: 1. `PatternDecoder.ts` used to accept `Uint8Array` as an input, however this was never used, so to simplify the typing and avoid casting in various places, I just removed support for it. 2. `MapPlaylist.ts` has a `frequency` object with map names, used to specify how often each map should appear in the playlist. However this list is not in sync with the actual map list, so some maps were missing from that list. I fixed that while adding stronger typing for the future. Also removed an un-necessary call to `parseInt`. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I have read and accepted the CLA aggreement (only required once). ## Please put your Discord username so you can be contacted if a bug or regression is found: azlod --- src/core/PatternDecoder.ts | 5 +-- src/core/game/Game.ts | 2 ++ src/server/Archive.ts | 68 +++++++++++++++++++++++++++---------- src/server/GameServer.ts | 9 +++-- src/server/Logger.ts | 2 +- src/server/MapPlaylist.ts | 10 +++--- src/server/WorkerMetrics.ts | 2 +- 7 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/core/PatternDecoder.ts b/src/core/PatternDecoder.ts index 1311aec44..c749b1615 100644 --- a/src/core/PatternDecoder.ts +++ b/src/core/PatternDecoder.ts @@ -5,10 +5,7 @@ export class PatternDecoder { readonly width: number; readonly scale: number; - constructor( - base64: string, - base64urlDecode: (input: Uint8Array | string) => Uint8Array, - ) { + constructor(base64: string, base64urlDecode: (input: string) => Uint8Array) { this.bytes = base64urlDecode(base64); if (this.bytes.length < 3) { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 5c50d07c3..88babd8a2 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -84,6 +84,8 @@ export enum GameMapType { Italia = "Italia", } +export type GameMapName = keyof typeof GameMapType; + export const mapCategories: Record = { continental: [ GameMapType.World, diff --git a/src/server/Archive.ts b/src/server/Archive.ts index 9f5cfc6b5..6b3675d94 100644 --- a/src/server/Archive.ts +++ b/src/server/Archive.ts @@ -35,11 +35,20 @@ export async function archive(gameRecord: GameRecord) { ); await archiveFullGameToR2(gameRecord); } - } catch (error) { + } catch (error: unknown) { + // If the error is not an instance of Error, log it as a string + if (!(error instanceof Error)) { + log.error( + `${gameRecord.info.gameID}: Final archive error. Non-Error type: ${String(error)}`, + ); + return; + } + + const { message, stack, name } = error; log.error(`${gameRecord.info.gameID}: Final archive error: ${error}`, { - message: error?.message ?? error, - stack: error?.stack, - name: error?.name, + message: message, + stack: stack, + name: name, ...(error && typeof error === "object" ? error : {}), }); } @@ -68,11 +77,20 @@ async function archiveAnalyticsToR2(gameRecord: GameRecord) { }); log.info(`${info.gameID}: successfully wrote game analytics to R2`); - } catch (error) { + } catch (error: unknown) { + // If the error is not an instance of Error, log it as a string + if (!(error instanceof Error)) { + log.error( + `${gameRecord.info.gameID}: Error writing game analytics to R2. Non-Error type: ${String(error)}`, + ); + return; + } + + const { message, stack, name } = error; log.error(`${info.gameID}: Error writing game analytics to R2: ${error}`, { - message: error?.message ?? error, - stack: error?.stack, - name: error?.name, + message: message, + stack: stack, + name: name, ...(error && typeof error === "object" ? error : {}), }); throw error; @@ -116,12 +134,20 @@ export async function readGameRecord( if (response.Body === undefined) return null; const bodyContents = await response.Body.transformToString(); return JSON.parse(bodyContents) as GameRecord; - } catch (error) { + } catch (error: unknown) { + // If the error is not an instance of Error, log it as a string + if (!(error instanceof Error)) { + log.error( + `${gameId}: Error reading game record from R2. Non-Error type: ${String(error)}`, + ); + return null; + } + const { message, stack, name } = error; // Log the error for monitoring purposes log.error(`${gameId}: Error reading game record from R2: ${error}`, { - message: error?.message ?? error, - stack: error?.stack, - name: error?.name, + message: message, + stack: stack, + name: name, ...(error && typeof error === "object" ? error : {}), }); @@ -137,14 +163,22 @@ export async function gameRecordExists(gameId: GameID): Promise { Key: `${gameFolder}/${gameId}`, // Fixed - needed to include gameFolder }); return true; - } catch (error) { - if (error.name === "NotFound") { + } catch (error: unknown) { + // If the error is not an instance of Error, log it as a string + if (!(error instanceof Error)) { + log.error( + `${gameId}: Error checking archive existence. Non-Error type: ${String(error)}`, + ); + return false; + } + const { message, stack, name } = error; + if (name === "NotFound") { return false; } log.error(`${gameId}: Error checking archive existence: ${error}`, { - message: error?.message ?? error, - stack: error?.stack, - name: error?.name, + message: message, + stack: stack, + name: name, ...(error && typeof error === "object" ? error : {}), }); return false; diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 76e7615b0..1f4956b19 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -46,13 +46,14 @@ export class GameServer { private _hasStarted = false; private _startTime: number | null = null; - private endTurnIntervalID; + private endTurnIntervalID: ReturnType | undefined; private lastPingUpdate = 0; private winner: ClientSendWinnerMessage | null = null; - private gameStartInfo: GameStartInfo; + // Note: This can be undefined if accessed before the game starts. + private gameStartInfo!: GameStartInfo; private log: Logger; @@ -402,7 +403,9 @@ export class GameServer { async end() { // Close all WebSocket connections - clearInterval(this.endTurnIntervalID); + if (this.endTurnIntervalID) { + clearInterval(this.endTurnIntervalID); + } this.allClients.forEach((client) => { client.ws.removeAllListeners("message"); if (client.ws.readyState === WebSocket.OPEN) { diff --git a/src/server/Logger.ts b/src/server/Logger.ts index 94e508fbb..16c3496b2 100644 --- a/src/server/Logger.ts +++ b/src/server/Logger.ts @@ -24,7 +24,7 @@ const loggerProvider = new LoggerProvider({ if (config.env() === GameEnv.Prod && config.otelEnabled()) { console.log("OTEL enabled"); // Configure OpenTelemetry endpoint with basic auth (if provided) - const headers = {}; + const headers: Record = {}; if (config.otelUsername() && config.otelPassword()) { headers["Authorization"] = "Basic " + diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts index 49c832f9c..66ab8cacb 100644 --- a/src/server/MapPlaylist.ts +++ b/src/server/MapPlaylist.ts @@ -2,6 +2,7 @@ import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; import { Difficulty, Duos, + GameMapName, GameMapType, GameMode, GameType, @@ -17,7 +18,9 @@ const log = logger.child({}); const config = getServerConfigFromServer(); -const frequency = { +// How many times each map should appear in the playlist. +// Note: The Partial should eventually be removed for better type safety. +const frequency: Partial> = { World: 3, Europe: 2, Africa: 2, @@ -27,7 +30,6 @@ const frequency = { GatewayToTheAtlantic: 1, Iceland: 1, SouthAmerica: 1, - KnownWorld: 1, DeglaciatedAntarctica: 1, EuropeClassic: 1, Mena: 1, @@ -109,8 +111,8 @@ export class MapPlaylist { private shuffleMapsPlaylist(): boolean { const maps: GameMapType[] = []; - Object.keys(GameMapType).forEach((key) => { - for (let i = 0; i < parseInt(frequency[key]); i++) { + (Object.keys(GameMapType) as GameMapName[]).forEach((key) => { + for (let i = 0; i < (frequency[key] ?? 0); i++) { maps.push(GameMapType[key]); } }); diff --git a/src/server/WorkerMetrics.ts b/src/server/WorkerMetrics.ts index 1576b0f73..9c2071bb9 100644 --- a/src/server/WorkerMetrics.ts +++ b/src/server/WorkerMetrics.ts @@ -18,7 +18,7 @@ export function initWorkerMetrics(gameManager: GameManager): void { const resource = getOtelResource(); // Configure auth headers - const headers = {}; + const headers: Record = {}; if (config.otelEnabled()) { headers["Authorization"] = "Basic " +