mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:10:42 +00:00
Split runtime and game logic env loading
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
UserMeResponse,
|
||||
} from "../core/ApiSchemas";
|
||||
import { assetUrl } from "../core/AssetUrls";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { fetchPlayerById, getUserMe } from "./Api";
|
||||
import { discordLogin, logOut, sendMagicLink } from "./Auth";
|
||||
import "./components/baseComponents/stats/DiscordUserHeader";
|
||||
@@ -217,7 +217,7 @@ export class AccountModal extends BaseModal {
|
||||
|
||||
private async viewGame(gameId: string): Promise<void> {
|
||||
this.close();
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const encodedGameId = encodeURIComponent(gameId);
|
||||
const newUrl = `/${config.workerPath(gameId)}/game/${encodedGameId}`;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "../core/Schemas";
|
||||
import { createPartialGameRecord, findClosestBy, replacer } from "../core/Util";
|
||||
import { ServerConfig } from "../core/configuration/Config";
|
||||
import { getConfig } from "../core/configuration/ConfigLoader";
|
||||
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";
|
||||
@@ -214,7 +214,7 @@ async function createClientGame(
|
||||
if (lobbyConfig.gameStartInfo === undefined) {
|
||||
throw new Error("missing gameStartInfo");
|
||||
}
|
||||
const config = await getConfig(
|
||||
const config = await getGameLogicConfig(
|
||||
lobbyConfig.gameStartInfo.config,
|
||||
userSettings,
|
||||
lobbyConfig.gameRecord !== undefined,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { html, LitElement, nothing, type TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { getServerConfigFromClient } from "src/core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "src/core/configuration/ConfigLoader";
|
||||
import {
|
||||
Duos,
|
||||
GameMapType,
|
||||
@@ -58,7 +58,7 @@ export class GameModeSelector extends LitElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.lobbySocket.start();
|
||||
getServerConfigFromClient().then((config) => {
|
||||
getRuntimeClientServerConfig().then((config) => {
|
||||
this.defaultLobbyTime = config.gameCreationRate() / 1000;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { translateText } from "../client/Utils";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import {
|
||||
Difficulty,
|
||||
@@ -113,7 +113,7 @@ export class HostLobbyModal extends BaseModal {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
return `${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}?lobby&s=${encodeURIComponent(this.lobbyUrlSuffix)}`;
|
||||
}
|
||||
|
||||
@@ -823,7 +823,7 @@ export class HostLobbyModal extends BaseModal {
|
||||
// If the modal closes as part of starting the game, do not leave the lobby
|
||||
this.leaveLobbyOnClose = false;
|
||||
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const response = await fetch(
|
||||
`${window.location.origin}/${config.workerPath(this.lobbyId)}/api/start_game/${this.lobbyId}`,
|
||||
{
|
||||
@@ -871,7 +871,7 @@ export class HostLobbyModal extends BaseModal {
|
||||
}
|
||||
|
||||
async function createLobby(gameID: string): Promise<GameInfo> {
|
||||
const config = await getServerConfigFromClient();
|
||||
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();
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
LobbyInfoEvent,
|
||||
PublicGameInfo,
|
||||
} from "../core/Schemas";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import {
|
||||
Difficulty,
|
||||
GameMapSize,
|
||||
@@ -897,7 +897,7 @@ export class JoinLobbyModal extends BaseModal {
|
||||
}
|
||||
|
||||
private async checkActiveLobby(lobbyId: string): Promise<boolean> {
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const url = `/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { PublicGames, PublicGamesSchema } from "../core/Schemas";
|
||||
|
||||
interface LobbySocketOptions {
|
||||
@@ -35,7 +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 getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
this.workerPath = getRandomWorkerPath(config.numWorkers());
|
||||
this.connectWebSocket();
|
||||
}
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ import {
|
||||
PublicGameInfo,
|
||||
} from "../core/Schemas";
|
||||
import { GameEnv } from "../core/configuration/Config";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import "./AccountModal";
|
||||
@@ -749,7 +749,7 @@ class Client {
|
||||
if (lobby.source === "public") {
|
||||
this.joinModal?.open(lobby.gameID, lobby.publicLobbyInfo);
|
||||
}
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
// Only update URL immediately for private lobbies, not public ones
|
||||
if (lobby.source !== "public") {
|
||||
this.updateJoinUrlForShare(lobby.gameID, config);
|
||||
@@ -857,7 +857,7 @@ class Client {
|
||||
|
||||
private updateJoinUrlForShare(
|
||||
lobbyId: string,
|
||||
config: Awaited<ReturnType<typeof getServerConfigFromClient>>,
|
||||
config: Awaited<ReturnType<typeof getRuntimeClientServerConfig>>,
|
||||
) {
|
||||
const lobbyIdHidden = !this.userSettings.lobbyIdVisibility();
|
||||
const targetUrl = lobbyIdHidden
|
||||
@@ -930,7 +930,7 @@ class Client {
|
||||
private async getTurnstileToken(
|
||||
lobby: JoinLobbyEvent,
|
||||
): Promise<string | null> {
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
if (
|
||||
config.env() === GameEnv.Dev ||
|
||||
lobby.gameStartInfo?.config.gameType === GameType.Singleplayer
|
||||
@@ -1008,7 +1008,7 @@ async function getTurnstileToken(): Promise<{
|
||||
throw new Error("Failed to load Turnstile script");
|
||||
}
|
||||
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const widgetId = window.turnstile.render("#turnstile-container", {
|
||||
sitekey: config.turnstileSiteKey(),
|
||||
size: "normal",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../core/configuration/ConfigLoader";
|
||||
import { getUserMe, hasLinkedAccount } from "./Api";
|
||||
import { getPlayToken } from "./Auth";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
@@ -87,7 +87,7 @@ export class MatchmakingModal extends BaseModal {
|
||||
}
|
||||
|
||||
private async connect() {
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const instanceId = await MatchmakingModal.getInstanceId();
|
||||
|
||||
this.socket = new WebSocket(
|
||||
@@ -210,7 +210,7 @@ export class MatchmakingModal extends BaseModal {
|
||||
if (this.gameID === null) {
|
||||
return;
|
||||
}
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
const url = `/${config.workerPath(this.gameID)}/api/game/${this.gameID}/exists`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { getServerConfigFromClient } from "../../core/configuration/ConfigLoader";
|
||||
import { getRuntimeClientServerConfig } from "../../core/configuration/ConfigLoader";
|
||||
import { UserSettings } from "../../core/game/UserSettings";
|
||||
import { crazyGamesSDK } from "../CrazyGamesSDK";
|
||||
import { copyToClipboard, translateText } from "../Utils";
|
||||
@@ -66,7 +66,7 @@ export class CopyButton extends LitElement {
|
||||
}
|
||||
|
||||
private async buildCopyUrl(): Promise<string> {
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
let url = `${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}`;
|
||||
if (this.includeLobbyQuery) {
|
||||
url += `?lobby&s=${encodeURIComponent(this.lobbySuffix)}`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { placeName } from "../client/graphics/NameBoxCalculator";
|
||||
import { getConfig } from "./configuration/ConfigLoader";
|
||||
import { getGameLogicConfig } from "./configuration/ConfigLoader";
|
||||
import { Executor } from "./execution/ExecutionManager";
|
||||
import { RecomputeRailClusterExecution } from "./execution/RecomputeRailClusterExecution";
|
||||
import { WinCheckExecution } from "./execution/WinCheckExecution";
|
||||
@@ -35,7 +35,7 @@ export async function createGameRunner(
|
||||
mapLoader: GameMapLoader,
|
||||
callBack: (gu: GameUpdateViewData | ErrorUpdate) => void,
|
||||
): Promise<GameRunner> {
|
||||
const config = await getConfig(gameStart.config, null);
|
||||
const config = await getGameLogicConfig(gameStart.config, null);
|
||||
const gameMap = await loadGameMap(
|
||||
gameStart.config.gameMap,
|
||||
gameStart.config.gameMapSize,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { UserSettings } from "../game/UserSettings";
|
||||
import { GameConfig } from "../Schemas";
|
||||
import { Config, GameEnv, ServerConfig } from "./Config";
|
||||
import { Config, ServerConfig } from "./Config";
|
||||
import { DefaultConfig } from "./DefaultConfig";
|
||||
import { DevConfig, DevServerConfig } from "./DevConfig";
|
||||
import { Env } from "./Env";
|
||||
import { preprodConfig } from "./PreprodConfig";
|
||||
import { prodConfig } from "./ProdConfig";
|
||||
|
||||
export let cachedSC: ServerConfig | null = null;
|
||||
export enum GameLogicEnv {
|
||||
Dev = "dev",
|
||||
Default = "default",
|
||||
}
|
||||
|
||||
export let cachedRuntimeClientServerConfig: ServerConfig | null = null;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -17,35 +22,77 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getConfig(
|
||||
export async function getGameLogicConfig(
|
||||
gameConfig: GameConfig,
|
||||
userSettings: UserSettings | null,
|
||||
isReplay: boolean = false,
|
||||
): Promise<Config> {
|
||||
const sc = await getServerConfigFromClient();
|
||||
switch (sc.env()) {
|
||||
case GameEnv.Dev:
|
||||
return new DevConfig(sc, gameConfig, userSettings, isReplay);
|
||||
case GameEnv.Preprod:
|
||||
case GameEnv.Prod:
|
||||
console.log("using prod config");
|
||||
return new DefaultConfig(sc, gameConfig, userSettings, isReplay);
|
||||
const gameLogicEnv = getBuildTimeGameLogicEnv();
|
||||
const serverConfig = getServerConfigForGameLogicEnv(gameLogicEnv);
|
||||
|
||||
switch (gameLogicEnv) {
|
||||
case GameLogicEnv.Dev:
|
||||
return new DevConfig(serverConfig, gameConfig, userSettings, isReplay);
|
||||
case GameLogicEnv.Default:
|
||||
return new DefaultConfig(
|
||||
serverConfig,
|
||||
gameConfig,
|
||||
userSettings,
|
||||
isReplay,
|
||||
);
|
||||
default:
|
||||
throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
|
||||
throw Error(`unsupported game logic environment: ${gameLogicEnv}`);
|
||||
}
|
||||
}
|
||||
export async function getServerConfigFromClient(): Promise<ServerConfig> {
|
||||
if (cachedSC) {
|
||||
return cachedSC;
|
||||
|
||||
export function getBuildTimeGameLogicEnv(): GameLogicEnv {
|
||||
const bundledGameEnv = process.env.GAME_ENV;
|
||||
|
||||
switch (bundledGameEnv) {
|
||||
case "dev":
|
||||
return GameLogicEnv.Dev;
|
||||
case "staging":
|
||||
case "prod":
|
||||
return GameLogicEnv.Default;
|
||||
case undefined:
|
||||
throw new Error("Missing bundled game logic env");
|
||||
default:
|
||||
throw Error(`unsupported bundled game logic env: ${bundledGameEnv}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getServerConfigForGameLogicEnv(
|
||||
gameLogicEnv: GameLogicEnv,
|
||||
): ServerConfig {
|
||||
switch (gameLogicEnv) {
|
||||
case GameLogicEnv.Dev:
|
||||
return new DevServerConfig();
|
||||
case GameLogicEnv.Default:
|
||||
console.log("using default game logic config");
|
||||
return prodConfig;
|
||||
default:
|
||||
throw Error(`unsupported game logic environment: ${gameLogicEnv}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRuntimeClientServerConfig(): Promise<ServerConfig> {
|
||||
if (cachedRuntimeClientServerConfig) {
|
||||
return cachedRuntimeClientServerConfig;
|
||||
}
|
||||
|
||||
const bootstrapGameEnv = window.BOOTSTRAP_CONFIG?.gameEnv;
|
||||
if (!bootstrapGameEnv) {
|
||||
throw new Error("Missing bootstrap server config");
|
||||
if (typeof window === "undefined") {
|
||||
throw new Error(
|
||||
"Runtime client server config is only available on the browser main thread",
|
||||
);
|
||||
}
|
||||
|
||||
cachedSC = getServerConfig(bootstrapGameEnv);
|
||||
return cachedSC;
|
||||
const runtimeClientEnv = window.BOOTSTRAP_CONFIG?.gameEnv;
|
||||
if (!runtimeClientEnv) {
|
||||
throw new Error("Missing runtime client server config");
|
||||
}
|
||||
|
||||
cachedRuntimeClientServerConfig = getServerConfig(runtimeClientEnv);
|
||||
return cachedRuntimeClientServerConfig;
|
||||
}
|
||||
export function getServerConfigFromServer(): ServerConfig {
|
||||
const gameEnv = Env.GAME_ENV;
|
||||
@@ -67,6 +114,6 @@ export function getServerConfig(gameEnv: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function clearCachedServerConfig(): void {
|
||||
cachedSC = null;
|
||||
export function clearCachedRuntimeClientServerConfig(): void {
|
||||
cachedRuntimeClientServerConfig = null;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { GameEnv } from "../../../src/core/configuration/Config";
|
||||
import {
|
||||
clearCachedServerConfig,
|
||||
getServerConfigFromClient,
|
||||
clearCachedRuntimeClientServerConfig,
|
||||
GameLogicEnv,
|
||||
getBuildTimeGameLogicEnv,
|
||||
getGameLogicConfig,
|
||||
getRuntimeClientServerConfig,
|
||||
getServerConfigForGameLogicEnv,
|
||||
} from "../../../src/core/configuration/ConfigLoader";
|
||||
|
||||
describe("ConfigLoader", () => {
|
||||
const originalGameEnv = process.env.GAME_ENV;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
window.BOOTSTRAP_CONFIG = undefined;
|
||||
clearCachedServerConfig();
|
||||
process.env.GAME_ENV = originalGameEnv;
|
||||
clearCachedRuntimeClientServerConfig();
|
||||
});
|
||||
|
||||
test("uses bootstrap config without fetching /api/env", async () => {
|
||||
window.BOOTSTRAP_CONFIG = { gameEnv: "prod" };
|
||||
test("uses runtime bootstrap config without fetching /api/env", async () => {
|
||||
window.BOOTSTRAP_CONFIG = { gameEnv: "staging" };
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch");
|
||||
|
||||
const config = await getServerConfigFromClient();
|
||||
const config = await getRuntimeClientServerConfig();
|
||||
|
||||
expect(config.env()).toBe(GameEnv.Prod);
|
||||
expect(config.env()).toBe(GameEnv.Preprod);
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("maps staging builds to the default game logic config", async () => {
|
||||
process.env.GAME_ENV = "staging";
|
||||
|
||||
expect(getBuildTimeGameLogicEnv()).toBe(GameLogicEnv.Default);
|
||||
expect(getServerConfigForGameLogicEnv(GameLogicEnv.Default).env()).toBe(
|
||||
GameEnv.Prod,
|
||||
);
|
||||
|
||||
const config = await getGameLogicConfig({} as any, null);
|
||||
|
||||
expect(config.serverConfig().env()).toBe(GameEnv.Prod);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user