mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:40:43 +00:00
Inject server env vars into index.html, including instance id (#2888)
## Description: Should fix the broken 1v1 on staging. The issue was that we had multiple staging environments, and the matchmaker would often route a player to a game on a different staging server, so the client couldn't find the game. So now each deployment has a unique id, and the matchmaker only connects players & servers that have the same instance id. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"html.validate.scripts": false
|
||||
}
|
||||
@@ -56,6 +56,12 @@
|
||||
/>
|
||||
<meta property="og:type" content="game" />
|
||||
|
||||
<!-- Injected from Server env -->
|
||||
<script>
|
||||
window.GIT_COMMIT = <%- gitCommit %>;
|
||||
window.INSTANCE_ID = <%- instanceId %>;
|
||||
</script>
|
||||
|
||||
<!-- CrazyGames SDK -->
|
||||
<script
|
||||
src="https://sdk.crazygames.com/crazygames-sdk-v3.js"
|
||||
|
||||
Generated
+1
-7
@@ -22,6 +22,7 @@
|
||||
"compression": "^1.8.1",
|
||||
"dompurify": "^3.1.7",
|
||||
"dotenv": "^16.5.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.22.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fastpriorityqueue": "^0.7.5",
|
||||
@@ -5389,7 +5390,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
@@ -5557,7 +5557,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@@ -7061,7 +7060,6 @@
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
@@ -7765,7 +7763,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
@@ -7775,7 +7772,6 @@
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
@@ -8537,7 +8533,6 @@
|
||||
"version": "10.9.4",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
@@ -10156,7 +10151,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
"compression": "^1.8.1",
|
||||
"dompurify": "^3.1.7",
|
||||
"dotenv": "^16.5.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.22.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fastpriorityqueue": "^0.7.5",
|
||||
|
||||
@@ -524,22 +524,12 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
private async checkArchivedGame(
|
||||
lobbyId: string,
|
||||
): Promise<"success" | "not_found" | "version_mismatch" | "error"> {
|
||||
const archivePromise = fetch(`${getApiBase()}/game/${lobbyId}`, {
|
||||
const archiveResponse = await fetch(`${getApiBase()}/game/${lobbyId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const gitCommitPromise = fetch(`/commit.txt`, {
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
cache: "no-cache",
|
||||
});
|
||||
|
||||
const [archiveResponse, gitCommitResponse] = await Promise.all([
|
||||
archivePromise,
|
||||
gitCommitPromise,
|
||||
]);
|
||||
|
||||
if (archiveResponse.status === 404) {
|
||||
return "not_found";
|
||||
@@ -554,19 +544,11 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
return "version_mismatch";
|
||||
}
|
||||
|
||||
let myGitCommit = "";
|
||||
if (gitCommitResponse.status === 404) {
|
||||
// commit.txt is not found when running locally
|
||||
myGitCommit = "DEV";
|
||||
} else if (gitCommitResponse.status === 200) {
|
||||
myGitCommit = (await gitCommitResponse.text()).trim();
|
||||
} else {
|
||||
console.error("Error getting git commit:", gitCommitResponse.status);
|
||||
return "error";
|
||||
}
|
||||
|
||||
// Allow DEV to join games created with a different version for debugging.
|
||||
if (myGitCommit !== "DEV" && parsed.data.gitCommit !== myGitCommit) {
|
||||
if (
|
||||
window.GIT_COMMIT !== "DEV" &&
|
||||
parsed.data.gitCommit !== window.GIT_COMMIT
|
||||
) {
|
||||
const safeLobbyId = this.sanitizeForLog(lobbyId);
|
||||
console.warn(
|
||||
`Git commit hash mismatch for game ${safeLobbyId}`,
|
||||
|
||||
@@ -160,6 +160,8 @@ function updateAccountNavButton(userMeResponse: UserMeResponse | false) {
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
GIT_COMMIT: string;
|
||||
INSTANCE_ID: string;
|
||||
turnstile: any;
|
||||
enableAds: boolean;
|
||||
PageOS: {
|
||||
|
||||
@@ -120,7 +120,9 @@ export class MatchmakingModal extends BaseModal {
|
||||
private async connect() {
|
||||
const config = await getServerConfigFromClient();
|
||||
|
||||
this.socket = new WebSocket(`${config.jwtIssuer()}/matchmaking/join`);
|
||||
this.socket = new WebSocket(
|
||||
`${config.jwtIssuer()}/matchmaking/join?instance_id=${window.INSTANCE_ID}`,
|
||||
);
|
||||
this.socket.onopen = async () => {
|
||||
console.log("Connected to matchmaking server");
|
||||
setTimeout(() => {
|
||||
|
||||
+60
-14
@@ -1,11 +1,14 @@
|
||||
import cluster from "cluster";
|
||||
import crypto from "crypto";
|
||||
import ejs from "ejs";
|
||||
import express from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import fs from "fs/promises";
|
||||
import http from "http";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { GameEnv } from "../core/configuration/Config";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { GameInfo } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
@@ -23,23 +26,29 @@ const log = logger.child({ comp: "m" });
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Middleware to handle HTML files with EJS templating
|
||||
app.use(async (req, res, next) => {
|
||||
if (req.path === "/") {
|
||||
try {
|
||||
await renderHtml(res, path.join(__dirname, "../../static/index.html"));
|
||||
} catch (error) {
|
||||
log.error("Error rendering index.html:", error);
|
||||
res.status(500).send("Internal Server Error");
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(
|
||||
express.static(path.join(__dirname, "../../static"), {
|
||||
maxAge: "1y", // Set max-age to 1 year for all static assets
|
||||
setHeaders: (res, path) => {
|
||||
// You can conditionally set different cache times based on file types
|
||||
if (path.endsWith(".html")) {
|
||||
// Set HTML files to no-cache to ensure Express doesn't send 304s
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
"no-store, no-cache, must-revalidate, proxy-revalidate",
|
||||
);
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Expires", "0");
|
||||
// Prevent conditional requests
|
||||
res.setHeader("ETag", "");
|
||||
} else if (path.match(/\.(js|css|svg)$/)) {
|
||||
if (path.match(/\.(js|css|svg)$/)) {
|
||||
// JS, CSS, SVG get long cache with immutable
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||
} else if (path.match(/\.(bin|dat|exe|dll|so|dylib)$/)) {
|
||||
@@ -50,7 +59,6 @@ app.use(
|
||||
},
|
||||
}),
|
||||
);
|
||||
app.use(express.json());
|
||||
|
||||
app.set("trust proxy", 3);
|
||||
app.use(
|
||||
@@ -133,11 +141,20 @@ export async function startMaster() {
|
||||
const ADMIN_TOKEN = crypto.randomBytes(16).toString("hex");
|
||||
process.env.ADMIN_TOKEN = ADMIN_TOKEN;
|
||||
|
||||
const INSTANCE_ID =
|
||||
config.env() === GameEnv.Dev
|
||||
? "DEV_ID"
|
||||
: crypto.randomBytes(4).toString("hex");
|
||||
process.env.INSTANCE_ID = INSTANCE_ID;
|
||||
|
||||
log.info(`Instance ID: ${INSTANCE_ID}`);
|
||||
|
||||
// Fork workers
|
||||
for (let i = 0; i < config.numWorkers(); i++) {
|
||||
const worker = cluster.fork({
|
||||
WORKER_ID: i,
|
||||
ADMIN_TOKEN,
|
||||
INSTANCE_ID,
|
||||
});
|
||||
|
||||
log.info(`Started worker ${i} (PID: ${worker.process.pid})`);
|
||||
@@ -190,6 +207,7 @@ export async function startMaster() {
|
||||
const newWorker = cluster.fork({
|
||||
WORKER_ID: workerId,
|
||||
ADMIN_TOKEN,
|
||||
INSTANCE_ID,
|
||||
});
|
||||
|
||||
log.info(
|
||||
@@ -321,6 +339,34 @@ async function schedulePublicGame(playlist: MapPlaylist) {
|
||||
}
|
||||
|
||||
// SPA fallback route
|
||||
app.get("*", function (req, res) {
|
||||
res.sendFile(path.join(__dirname, "../../static/index.html"));
|
||||
app.get("*", async function (_req, res) {
|
||||
try {
|
||||
const htmlPath = path.join(__dirname, "../../static/index.html");
|
||||
await renderHtml(res, htmlPath);
|
||||
} catch (error) {
|
||||
log.error("Error rendering SPA fallback:", error);
|
||||
res.status(500).send("Internal Server Error");
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to render HTML with EJS templating
|
||||
async function renderHtml(
|
||||
res: express.Response,
|
||||
htmlPath: string,
|
||||
): Promise<void> {
|
||||
const htmlContent = await fs.readFile(htmlPath, "utf-8");
|
||||
const rendered = ejs.render(htmlContent, {
|
||||
gitCommit: JSON.stringify(process.env.GIT_COMMIT ?? "undefined"),
|
||||
instanceId: JSON.stringify(process.env.INSTANCE_ID ?? "undefined"),
|
||||
});
|
||||
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
"no-store, no-cache, must-revalidate, proxy-revalidate",
|
||||
);
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Expires", "0");
|
||||
res.setHeader("ETag", "");
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.send(rendered);
|
||||
}
|
||||
|
||||
@@ -479,6 +479,7 @@ async function pollLobby(gm: GameManager) {
|
||||
id: workerId,
|
||||
gameId: gameId,
|
||||
ccu: gm.activeClients(),
|
||||
instanceId: process.env.INSTANCE_ID,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
+15
-25
@@ -1,5 +1,4 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
@@ -11,19 +10,6 @@ import tsconfigPaths from "vite-tsconfig-paths";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
let gitCommit = process.env.GIT_COMMIT;
|
||||
|
||||
if (!gitCommit) {
|
||||
try {
|
||||
gitCommit = execSync("git rev-parse HEAD").toString().trim();
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn("Unable to determine git commit:", error.message);
|
||||
}
|
||||
gitCommit = "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
const isProduction = mode === "production";
|
||||
@@ -50,16 +36,21 @@ export default defineConfig(({ mode }) => {
|
||||
|
||||
plugins: [
|
||||
tsconfigPaths(),
|
||||
createHtmlPlugin({
|
||||
minify: isProduction,
|
||||
entry: "/src/client/Main.ts",
|
||||
template: "index.html",
|
||||
inject: {
|
||||
data: {
|
||||
// In case we need to inject variables into HTML
|
||||
},
|
||||
},
|
||||
}),
|
||||
...(isProduction
|
||||
? []
|
||||
: [
|
||||
createHtmlPlugin({
|
||||
minify: false,
|
||||
entry: "/src/client/Main.ts",
|
||||
template: "index.html",
|
||||
inject: {
|
||||
data: {
|
||||
gitCommit: JSON.stringify("DEV"),
|
||||
instanceId: JSON.stringify("DEV_ID"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
@@ -76,7 +67,6 @@ export default defineConfig(({ mode }) => {
|
||||
isProduction ? "" : "localhost:3000",
|
||||
),
|
||||
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
|
||||
"process.env.GIT_COMMIT": JSON.stringify(gitCommit),
|
||||
"process.env.STRIPE_PUBLISHABLE_KEY": JSON.stringify(
|
||||
env.STRIPE_PUBLISHABLE_KEY,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user