Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ jobs:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_AUTH_HEADER: ${{ secrets.OTEL_AUTH_HEADER }}
API_KEY: ${{ secrets.API_KEY }}
NUM_WORKERS: ${{ vars.NUM_WORKERS }}
TURNSTILE_SITE_KEY: ${{ vars.TURNSTILE_SITE_KEY }}
Comment on lines +142 to +143
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if repository variables are configured
gh api repos/openfrontio/OpenFrontIO/actions/variables --jq '.variables[] | select(.name == "NUM_WORKERS" or .name == "TURNSTILE_SITE_KEY") | {name: .name, value: .value}'

Repository: openfrontio/OpenFrontIO

Length of output: 301


🏁 Script executed:

git ls-files '.github/workflows/*.yml' | head -20

Repository: openfrontio/OpenFrontIO

Length of output: 251


🏁 Script executed:

cat -n .github/workflows/deploy.yml | head -150

Repository: openfrontio/OpenFrontIO

Length of output: 7189


🏁 Script executed:

# Find the deploy.sh script
fd -t f -i 'deploy\.sh'

Repository: openfrontio/OpenFrontIO

Length of output: 93


🏁 Script executed:

cat -n deploy.sh

Repository: openfrontio/OpenFrontIO

Length of output: 6158


🏁 Script executed:

cat -n build-deploy.sh

Repository: openfrontio/OpenFrontIO

Length of output: 3070


Add validation for NUM_WORKERS and TURNSTILE_SITE_KEY in deploy.sh.

These variables are passed to the deployment script but are not validated before use. If vars.NUM_WORKERS or vars.TURNSTILE_SITE_KEY are not defined in repository settings, empty strings will be passed to the remote server, which may cause the application to fail at runtime even though the deployment appears to succeed.

Add validation checks similar to the existing GHCR_USERNAME check (line 58-60 in deploy.sh):

if [ -z "$NUM_WORKERS" ] || [ -z "$TURNSTILE_SITE_KEY" ]; then
    echo "Error: NUM_WORKERS or TURNSTILE_SITE_KEY not defined in repository variables or environment"
    exit 1
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 142 - 143, Add a validation block
in deploy.sh to check that NUM_WORKERS and TURNSTILE_SITE_KEY are set before
proceeding: locate the existing GHCR_USERNAME check example and insert a similar
conditional that tests [ -z "$NUM_WORKERS" ] || [ -z "$TURNSTILE_SITE_KEY" ] and
prints a clear error and exits non‑zero if either is empty so the deploy fails
fast when repository variables are missing.

SERVER_HOST_MASTERS: ${{ secrets.SERVER_HOST_MASTERS }}
SERVER_HOST_FALK2: ${{ secrets.SERVER_HOST_FALK2 }}
SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }}
Expand Down
6 changes: 6 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ else
GHCR_IMAGE="${GHCR_USERNAME}/${GHCR_REPO}:${VERSION_TAG}"
fi

# jwtAudience always matched DOMAIN in the old per-env configs.
JWT_AUDIENCE="$DOMAIN"
Comment on lines +69 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate DOMAIN before deriving JWT_AUDIENCE.

JWT_AUDIENCE is derived from $DOMAIN, but DOMAIN is not validated before use. If DOMAIN is missing from the environment files, JWT_AUDIENCE will be empty, causing runtime failures when the deployed application starts.

Following the existing pattern at lines 58-61 and 87-90, add a validation check for required variables.

🛡️ Suggested validation check
+# Validate DOMAIN is set before deriving JWT_AUDIENCE
+if [ -z "$DOMAIN" ]; then
+    echo "Error: DOMAIN not defined in .env file or environment"
+    exit 1
+fi
+
 # jwtAudience always matched DOMAIN in the old per-env configs.
 JWT_AUDIENCE="$DOMAIN"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# jwtAudience always matched DOMAIN in the old per-env configs.
JWT_AUDIENCE="$DOMAIN"
# Validate DOMAIN is set before deriving JWT_AUDIENCE
if [ -z "$DOMAIN" ]; then
echo "Error: DOMAIN not defined in .env file or environment"
exit 1
fi
# jwtAudience always matched DOMAIN in the old per-env configs.
JWT_AUDIENCE="$DOMAIN"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy.sh` around lines 69 - 70, Add a validation that ensures DOMAIN is set
before deriving JWT_AUDIENCE: check if the DOMAIN environment variable is
empty/null and if so print a descriptive error and exit with non-zero status
(same pattern used for other required vars), and only then set
JWT_AUDIENCE="$DOMAIN"; reference the JWT_AUDIENCE and DOMAIN variables and
follow the existing validation pattern used elsewhere in the script.


if [ "$HOST" == "staging" ]; then
print_header "DEPLOYING TO STAGING HOST"
SERVER_HOST=$SERVER_HOST_STAGING
Expand Down Expand Up @@ -138,6 +141,9 @@ API_KEY=$API_KEY
DOMAIN=$DOMAIN
SUBDOMAIN=$SUBDOMAIN
CDN_BASE=$CDN_BASE
JWT_AUDIENCE=$JWT_AUDIENCE
NUM_WORKERS=$NUM_WORKERS
TURNSTILE_SITE_KEY=$TURNSTILE_SITE_KEY
Comment on lines +144 to +146
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate required variables before writing to remote environment.

NUM_WORKERS and TURNSTILE_SITE_KEY are written to the remote environment file without validation. According to the AI summary, these are required by ServerEnv.ts. If these variables are missing or empty, the deployment will succeed but the application will fail at runtime.

Add validation checks before line 133 to fail fast with a clear error message.

🛡️ Suggested validation checks
 print_header "EXECUTING UPDATE SCRIPT ON SERVER"
 
+# Validate required environment variables
+if [ -z "$JWT_AUDIENCE" ]; then
+    echo "Error: JWT_AUDIENCE not defined"
+    exit 1
+fi
+
+if [ -z "$NUM_WORKERS" ]; then
+    echo "Error: NUM_WORKERS not defined in environment"
+    exit 1
+fi
+
+if [ -z "$TURNSTILE_SITE_KEY" ]; then
+    echo "Error: TURNSTILE_SITE_KEY not defined in environment"
+    exit 1
+fi
+
 ssh -i $SSH_KEY $REMOTE_USER@$SERVER_HOST "chmod +x $REMOTE_UPDATE_SCRIPT && \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy.sh` around lines 144 - 146, The deployment script writes NUM_WORKERS
and TURNSTILE_SITE_KEY into the remote env unvalidated, which can cause runtime
failures in ServerEnv.ts; add pre-write validation in deploy.sh to check that
NUM_WORKERS and TURNSTILE_SITE_KEY are non-empty (and optionally that
NUM_WORKERS is a positive integer) and if not, exit with a clear error message
(e.g., "Missing required env: NUM_WORKERS" or "Missing required env:
TURNSTILE_SITE_KEY") before proceeding to write the remote environment file.

OTEL_EXPORTER_OTLP_ENDPOINT=$OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_AUTH_HEADER=$OTEL_AUTH_HEADER
EOL
Expand Down
10 changes: 7 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@

<!-- Injected from Server env -->
<script>
window.GIT_COMMIT = <%- gitCommit %>;
window.ASSET_MANIFEST = <%- assetManifest %>;
window.CDN_BASE = <%- cdnBase %>;
window.BOOTSTRAP_CONFIG = {
gitCommit: <%- gitCommit %>,
assetManifest: <%- assetManifest %>,
cdnBase: <%- cdnBase %>,
gameEnv: <%- gameEnv %>,
numWorkers: <%- numWorkers %>,
turnstileSiteKey: <%- turnstileSiteKey %>,
jwtAudience: <%- jwtAudience %>,
instanceId: <%- instanceId %>,
};
document.documentElement.style.setProperty(
"--background-image-url",
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"build-prod": "concurrently --kill-others-on-fail \"tsc --noEmit\" \"vite build\"",
"start:client": "vite",
"start:server": "tsx src/server/Server.ts",
"start:server-dev": "cross-env GAME_ENV=dev tsx src/server/Server.ts",
"start:server-dev": "cross-env GAME_ENV=dev NUM_WORKERS=2 TURNSTILE_SITE_KEY=1x00000000000000000000AA JWT_AUDIENCE=localhost API_KEY=WARNING_DEV_API_KEY_DO_NOT_USE_IN_PRODUCTION DOMAIN=localhost GIT_COMMIT=DEV tsx src/server/Server.ts",
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:staging": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:prod": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.io concurrently \"npm run start:client\" \"npm run start:server-dev\"",
Expand Down
5 changes: 2 additions & 3 deletions src/client/AccountModal.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { html, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import {
PlayerGame,
PlayerStatsTree,
UserMeResponse,
} from "../core/ApiSchemas";
import { assetUrl } from "../core/AssetUrls";
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
import { fetchPlayerById, getUserMe } from "./Api";
import { discordLogin, logOut, sendMagicLink } from "./Auth";
import "./components/baseComponents/stats/DiscordUserHeader";
Expand Down Expand Up @@ -229,9 +229,8 @@ export class AccountModal extends BaseModal {

private async viewGame(gameId: string): Promise<void> {
this.close();
const config = await getRuntimeClientServerConfig();
const encodedGameId = encodeURIComponent(gameId);
const newUrl = `/${config.workerPath(gameId)}/game/${encodedGameId}`;
const newUrl = `/${ClientEnv.workerPath(gameId)}/game/${encodedGameId}`;

history.pushState({ join: gameId }, "", newUrl);
window.dispatchEvent(
Expand Down
112 changes: 112 additions & 0 deletions src/client/ClientEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { JWK } from "jose";
import { z } from "zod";
import { GameID } from "../core/Schemas";
import { simpleHash } from "../core/Util";
import {
GameEnv,
JwksSchema,
parseGameEnv,
} from "../core/configuration/Config";

export class ClientEnv {
private static values: ClientEnvValues | null = null;
private static publicKey: JWK | null = null;

/** Test-only. */
static reset(): void {
ClientEnv.values = null;
ClientEnv.publicKey = null;
}

private static get(): ClientEnvValues {
if (ClientEnv.values) return ClientEnv.values;
if (typeof window === "undefined") {
throw new Error("ClientEnv is only available on the browser main thread");
}
const bc = window.BOOTSTRAP_CONFIG;
if (
!bc ||
bc.gameEnv === undefined ||
bc.numWorkers === undefined ||
bc.turnstileSiteKey === undefined ||
bc.jwtAudience === undefined
) {
throw new Error("Missing BOOTSTRAP_CONFIG");
}
if (bc.instanceId === undefined) {
throw new Error("Missing BOOTSTRAP_CONFIG");
}
ClientEnv.values = {
gameEnv: parseGameEnv(bc.gameEnv),
numWorkers: bc.numWorkers,
turnstileSiteKey: bc.turnstileSiteKey,
jwtAudience: bc.jwtAudience,
instanceId: bc.instanceId,
};
Comment on lines +27 to +45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate numWorkers before using it for sharding.

Right now 0, negatives, or non-integers will flow into workerIndex() and produce NaN or wrong worker paths. That breaks create/join routing for the whole client. Reject bad bootstrap values up front.

Suggested fix
     if (
       !bc ||
       bc.gameEnv === undefined ||
       bc.numWorkers === undefined ||
       bc.turnstileSiteKey === undefined ||
       bc.jwtAudience === undefined
     ) {
       throw new Error("Missing BOOTSTRAP_CONFIG");
     }
+    if (!Number.isInteger(bc.numWorkers) || bc.numWorkers < 1) {
+      throw new Error(`Invalid BOOTSTRAP_CONFIG.numWorkers: ${bc.numWorkers}`);
+    }
     if (bc.instanceId === undefined) {
       throw new Error("Missing BOOTSTRAP_CONFIG");
     }

Also applies to: 94-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ClientEnv.ts` around lines 27 - 45, The bootstrap validation
currently allows invalid numWorkers (0, negatives, non-integers) which breaks
workerIndex() sharding; update the check in ClientEnv (the block that sets
ClientEnv.values using parseGameEnv) to validate bc.numWorkers is a positive
integer (e.g., Number.isInteger(bc.numWorkers) && bc.numWorkers > 0) and throw a
descriptive error if not, and apply the same validation in the other
bootstrap-check site around lines 94-99 where numWorkers is used; ensure you
reference and protect any code paths that call workerIndex() so they only
receive a validated positive integer.

return ClientEnv.values;
}

// TODO: the following methods are duplicated on ServerEnv. The two classes
// read from different sources (window.BOOTSTRAP_CONFIG vs process.env) but
// the derived logic is identical. Consolidate into a shared helper that
// takes a source so we don't have to keep them in sync by hand.
static env(): GameEnv {
return ClientEnv.get().gameEnv;
}
static numWorkers(): number {
return ClientEnv.get().numWorkers;
}
static turnstileSiteKey(): string {
return ClientEnv.get().turnstileSiteKey;
}
static jwtAudience(): string {
return ClientEnv.get().jwtAudience;
}
static instanceId(): string {
return ClientEnv.get().instanceId;
}
static jwtIssuer(): string {
const audience = ClientEnv.jwtAudience();
return audience === "localhost"
? "http://localhost:8787"
: `https://api.${audience}`;
}
static async jwkPublicKey(): Promise<JWK> {
if (ClientEnv.publicKey) return ClientEnv.publicKey;
const jwksUrl = ClientEnv.jwtIssuer() + "/.well-known/jwks.json";
console.log(`Fetching JWKS from ${jwksUrl}`);
const response = await fetch(jwksUrl);
const result = JwksSchema.safeParse(await response.json());
if (!result.success) {
const error = z.prettifyError(result.error);
console.error("Error parsing JWKS", error);
throw new Error("Invalid JWKS");
}
ClientEnv.publicKey = result.data.keys[0];
return ClientEnv.publicKey;
}
static turnIntervalMs(): number {
return 100;
}
static gameCreationRate(): number {
return ClientEnv.env() === GameEnv.Dev ? 5 * 1000 : 2 * 60 * 1000;
}
static workerIndex(gameID: GameID): number {
return simpleHash(gameID) % ClientEnv.numWorkers();
}
static workerPath(gameID: GameID): string {
return `w${ClientEnv.workerIndex(gameID)}`;
}
}
/**
* Values that flow from server → client via index.html. Set on the server from
* process.env, then re-hydrated on the client from window.BOOTSTRAP_CONFIG.
*/

export interface ClientEnvValues {
gameEnv: GameEnv;
numWorkers: number;
turnstileSiteKey: string;
jwtAudience: string;
instanceId: string;
}
6 changes: 2 additions & 4 deletions src/client/ClientGameRunner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Config } from "src/core/configuration/Config";
import { translateText } from "../client/Utils";
import { EventBus } from "../core/EventBus";
import {
Expand All @@ -11,8 +12,6 @@ import {
ServerMessage,
} from "../core/Schemas";
import { createPartialGameRecord, findClosestBy, replacer } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getGameLogicConfig } from "../core/configuration/ConfigLoader";
import { BuildableUnit, Structures, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { GameMapLoader } from "../core/game/GameMapLoader";
Expand Down Expand Up @@ -57,7 +56,6 @@ import { GoToPlayerEvent } from "./graphics/TransformHandler";
import { SoundManager } from "./sound/SoundManager";

export interface LobbyConfig {
serverConfig: ServerConfig;
cosmetics: PlayerCosmeticRefs;
playerName: string;
playerClanTag: string | null;
Expand Down Expand Up @@ -232,7 +230,7 @@ async function createClientGame(
if (lobbyConfig.gameStartInfo === undefined) {
throw new Error("missing gameStartInfo");
}
const config = await getGameLogicConfig(
const config = new Config(
lobbyConfig.gameStartInfo.config,
userSettings,
lobbyConfig.gameRecord !== undefined,
Expand Down
6 changes: 2 additions & 4 deletions src/client/GameModeSelector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { html, LitElement, nothing, type TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { getRuntimeClientServerConfig } from "src/core/configuration/ConfigLoader";
import { ClientEnv } from "src/client/ClientEnv";
import {
Duos,
GameMapType,
Expand Down Expand Up @@ -59,9 +59,7 @@ export class GameModeSelector extends LitElement {
connectedCallback() {
super.connectedCallback();
this.lobbySocket.start();
getRuntimeClientServerConfig().then((config) => {
this.defaultLobbyTime = config.gameCreationRate() / 1000;
});
this.defaultLobbyTime = ClientEnv.gameCreationRate() / 1000;
}

disconnectedCallback() {
Expand Down
8 changes: 3 additions & 5 deletions src/client/HostLobbyModal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { translateText } from "../client/Utils";
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
import { EventBus } from "../core/EventBus";
import {
Difficulty,
Expand Down Expand Up @@ -121,8 +121,7 @@ export class HostLobbyModal extends BaseModal {
return link;
}
}
const config = await getRuntimeClientServerConfig();
return `${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}?lobby&s=${encodeURIComponent(this.lobbyUrlSuffix)}`;
return `${window.location.origin}/${ClientEnv.workerPath(this.lobbyId)}/game/${this.lobbyId}?lobby&s=${encodeURIComponent(this.lobbyUrlSuffix)}`;
}

private async constructUrl(): Promise<string> {
Expand Down Expand Up @@ -1050,13 +1049,12 @@ export class HostLobbyModal extends BaseModal {
}

async function createLobby(gameID: string): Promise<GameInfo> {
const config = await getRuntimeClientServerConfig();
// Send JWT token for creator identification - server extracts persistentID from it
// persistentID should never be exposed to other clients
const token = await getPlayToken();
try {
const response = await fetch(
`/${config.workerPath(gameID)}/api/create_game/${gameID}`,
`/${ClientEnv.workerPath(gameID)}/api/create_game/${gameID}`,
{
method: "POST",
headers: {
Expand Down
11 changes: 6 additions & 5 deletions src/client/JoinLobbyModal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { html, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import {
calculateServerTimeOffset,
getMapName,
Expand All @@ -19,7 +20,6 @@ import {
LobbyInfoEvent,
PublicGameInfo,
} from "../core/Schemas";
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
import {
Difficulty,
GameMapSize,
Expand Down Expand Up @@ -967,8 +967,7 @@ export class JoinLobbyModal extends BaseModal {
}

private async checkActiveLobby(lobbyId: string): Promise<boolean> {
const config = await getRuntimeClientServerConfig();
const url = `/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;
const url = `/${ClientEnv.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;

const response = await fetch(url, {
method: "GET",
Expand Down Expand Up @@ -1037,9 +1036,11 @@ export class JoinLobbyModal extends BaseModal {
return "version_mismatch";
}

const gitCommit = window.BOOTSTRAP_CONFIG?.gitCommit;
if (
window.GIT_COMMIT !== "DEV" &&
parsed.data.gitCommit !== window.GIT_COMMIT
gitCommit !== "DEV" &&
gitCommit !== undefined &&
parsed.data.gitCommit !== gitCommit
) {
const safeLobbyId = this.sanitizeForLog(lobbyId);
console.warn(
Expand Down
5 changes: 2 additions & 3 deletions src/client/LobbySocket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
import { ClientEnv } from "src/client/ClientEnv";
import { PublicGames, PublicGamesSchema } from "../core/Schemas";

interface LobbySocketOptions {
Expand Down Expand Up @@ -35,8 +35,7 @@ export class PublicLobbySocket {
this.stopped = false;
this.wsConnectionAttempts = 0;
// Get config to determine number of workers, then pick a random one
const config = await getRuntimeClientServerConfig();
this.workerPath = getRandomWorkerPath(config.numWorkers());
this.workerPath = getRandomWorkerPath(ClientEnv.numWorkers());
this.connectWebSocket();
}

Expand Down
6 changes: 3 additions & 3 deletions src/client/LocalServer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ClientEnv } from "src/client/ClientEnv";
import { z } from "zod";
import { EventBus } from "../core/EventBus";
import {
Expand Down Expand Up @@ -81,8 +82,7 @@ export class LocalServer {
console.log("local server starting");
this.turnCheckInterval = setInterval(() => {
const turnIntervalMs =
this.lobbyConfig.serverConfig.turnIntervalMs() *
this.replaySpeedMultiplier;
ClientEnv.turnIntervalMs() * this.replaySpeedMultiplier;
const backlog = Math.max(0, this.turns.length - this.turnsExecuted);
const allowReplayBacklog =
this.replaySpeedMultiplier === ReplaySpeedMultiplier.fastest &&
Expand Down Expand Up @@ -297,7 +297,7 @@ export class LocalServer {
console.error("Error parsing game record", error);
return;
}
const workerPath = this.lobbyConfig.serverConfig.workerPath(
const workerPath = ClientEnv.workerPath(
this.lobbyConfig.gameStartInfo.gameID,
);

Expand Down
Loading
Loading