mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:10:42 +00:00
WSErrorSchema
This commit is contained in:
+30
-10
@@ -24,6 +24,8 @@ import {
|
||||
ServerMessage,
|
||||
ServerMessageSchema,
|
||||
Winner,
|
||||
WSError,
|
||||
WSErrorSchema,
|
||||
} from "../core/Schemas";
|
||||
import { replacer } from "../core/Util";
|
||||
import { getPlayToken } from "./Auth";
|
||||
@@ -178,6 +180,7 @@ export class SendStartGameEvent implements GameEvent {}
|
||||
|
||||
export class Transport {
|
||||
private socket: WebSocket | null = null;
|
||||
private disconnectWSError: WSError | null = null;
|
||||
|
||||
private localServer: LocalServer;
|
||||
|
||||
@@ -334,6 +337,7 @@ export class Transport {
|
||||
const workerPath = this.lobbyConfig.serverConfig.workerPath(
|
||||
this.lobbyConfig.gameID,
|
||||
);
|
||||
this.disconnectWSError = null;
|
||||
this.socket = new WebSocket(`${wsProtocol}//${wsHost}/${workerPath}`);
|
||||
this.onconnect = onconnect;
|
||||
this.onmessage = onmessage;
|
||||
@@ -359,6 +363,12 @@ export class Transport {
|
||||
const parsed = JSON.parse(event.data);
|
||||
const result = ServerMessageSchema.safeParse(parsed);
|
||||
if (!result.success) {
|
||||
const wsErrorResult = WSErrorSchema.safeParse(parsed);
|
||||
if (wsErrorResult.success) {
|
||||
this.disconnectWSError = wsErrorResult.data;
|
||||
return;
|
||||
}
|
||||
|
||||
const error = z.prettifyError(result.error);
|
||||
console.error("Error parsing server message", error);
|
||||
return;
|
||||
@@ -380,26 +390,36 @@ export class Transport {
|
||||
);
|
||||
if (event.code === 1002) {
|
||||
const connRefusedKey = `worker_error.connection_refused`;
|
||||
const errorKey = `worker_error.${event.reason}`;
|
||||
const translationKey = this.disconnectWSError
|
||||
? this.disconnectWSError.translationKey
|
||||
: event.reason;
|
||||
const args = this.disconnectWSError
|
||||
? this.disconnectWSError.args
|
||||
: undefined;
|
||||
|
||||
const errorKey = `worker_error.${translationKey}`;
|
||||
|
||||
let alertMsg = `${translateText(connRefusedKey)}: `;
|
||||
const translatedError = translateText(errorKey);
|
||||
const translatedError = translateText(errorKey, args);
|
||||
|
||||
if (translatedError === errorKey) {
|
||||
// No translation key in error.reason or no translation or default English found
|
||||
alertMsg += `${event.reason}`;
|
||||
// Raw string instead of translation key in disconnectWSError/error.reason,
|
||||
// or no user lang nor default English translation found with the key
|
||||
// Show the raw string or key as fallback. Eg. "WS_ERR_UNEXPECTED_RSV_1" or "ClientJoinMessageSchema"
|
||||
alertMsg += `${translationKey}`;
|
||||
} else {
|
||||
alertMsg += translatedError;
|
||||
|
||||
// Add tips if token invalid
|
||||
if (event.reason === "turnstile_invalid") {
|
||||
// Add tips if turnstile token invalid
|
||||
if (translationKey === "turnstile_invalid") {
|
||||
alertMsg += `\n${translateText("worker_error.turnstile_fix_tips")}`;
|
||||
}
|
||||
|
||||
// Append English translation if it differs
|
||||
const englishMsg = getEnglishText(errorKey);
|
||||
if (englishMsg !== errorKey && !alertMsg.includes(englishMsg)) {
|
||||
alertMsg += `\n\n--- English ---\n${getEnglishText(connRefusedKey)}: ${englishMsg}`;
|
||||
// Append English translation/key if it's not already there
|
||||
// Helps debugging if user shares screenshot
|
||||
const englishError = getEnglishText(errorKey, args);
|
||||
if (englishError !== errorKey && !alertMsg.includes(englishError)) {
|
||||
alertMsg += `\n\n--- English ---\n${getEnglishText(connRefusedKey)}: ${englishError}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -613,6 +613,12 @@ export const ServerErrorSchema = z.object({
|
||||
message: z.string().optional(),
|
||||
});
|
||||
|
||||
export const WSErrorSchema = z.object({
|
||||
translationKey: z.string(),
|
||||
args: z.record(z.string(), z.string()).optional(),
|
||||
});
|
||||
export type WSError = z.infer<typeof WSErrorSchema>;
|
||||
|
||||
export const ServerLobbyInfoMessageSchema = z.object({
|
||||
type: z.literal("lobby_info"),
|
||||
lobby: GameInfoSchema,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { createPartialGameRecord } from "../core/Util";
|
||||
import { archive, finalizeGameRecord } from "./Archive";
|
||||
import { Client } from "./Client";
|
||||
import { ClientMsgRateLimiter } from "./ClientMsgRateLimiter";
|
||||
import { sendErrorAndClose } from "./Worker";
|
||||
export enum GamePhase {
|
||||
Lobby = "LOBBY",
|
||||
Active = "ACTIVE",
|
||||
@@ -613,7 +614,9 @@ export class GameServer {
|
||||
});
|
||||
client.ws.on("error", (error: Error) => {
|
||||
if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") {
|
||||
client.ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1");
|
||||
sendErrorAndClose(client.ws, {
|
||||
translationKey: "WS_ERR_UNEXPECTED_RSV_1",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+21
-13
@@ -14,6 +14,7 @@ import {
|
||||
GameID,
|
||||
PartialGameRecordSchema,
|
||||
ServerErrorMessage,
|
||||
WSError,
|
||||
} from "../core/Schemas";
|
||||
import { generateID, replacer } from "../core/Util";
|
||||
import { CreateGameInputSchema } from "../core/WorkerSchemas";
|
||||
@@ -34,6 +35,13 @@ import { verifyTurnstileToken } from "./Turnstile";
|
||||
import { WorkerLobbyService } from "./WorkerLobbyService";
|
||||
import { initWorkerMetrics } from "./WorkerMetrics";
|
||||
|
||||
export function sendErrorAndClose(ws: WebSocket, error: WSError, code = 1002) {
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify(error));
|
||||
}
|
||||
ws.close(code);
|
||||
}
|
||||
|
||||
const config = getServerConfigFromServer();
|
||||
|
||||
const workerId = parseInt(process.env.WORKER_ID ?? "0");
|
||||
@@ -300,7 +308,7 @@ export async function startWorker() {
|
||||
error: error.toString(),
|
||||
} satisfies ServerErrorMessage),
|
||||
);
|
||||
ws.close(1002, "ClientJoinMessageSchema");
|
||||
sendErrorAndClose(ws, { translationKey: "ClientJoinMessageSchema" });
|
||||
return;
|
||||
}
|
||||
const clientMsg = parsed.data;
|
||||
@@ -330,13 +338,13 @@ export async function startWorker() {
|
||||
log.warn(`Invalid token: ${result.message}`, {
|
||||
gameID: clientMsg.gameID,
|
||||
});
|
||||
ws.close(1002, "turnstile_invalid");
|
||||
sendErrorAndClose(ws, { translationKey: "turnstile_invalid" });
|
||||
return;
|
||||
}
|
||||
const { persistentId, claims } = result;
|
||||
|
||||
if (claims?.role === "banned") {
|
||||
ws.close(1002, "account_banned");
|
||||
sendErrorAndClose(ws, { translationKey: "account_banned" });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -355,7 +363,7 @@ export async function startWorker() {
|
||||
log.warn(
|
||||
`game ${clientMsg.gameID} not found on worker ${workerId}`,
|
||||
);
|
||||
ws.close(1002, "game_not_found");
|
||||
sendErrorAndClose(ws, { translationKey: "game_not_found" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -385,7 +393,7 @@ export async function startWorker() {
|
||||
if (claims === null) {
|
||||
if (allowedFlares !== undefined) {
|
||||
log.warn("Unauthorized: Anonymous user attempted to join game");
|
||||
ws.close(1002, "unauthorized");
|
||||
sendErrorAndClose(ws, { translationKey: "unauthorized" });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -396,7 +404,7 @@ export async function startWorker() {
|
||||
persistentID: persistentId,
|
||||
gameID: clientMsg.gameID,
|
||||
});
|
||||
ws.close(1002, "user_me_fetch_failed");
|
||||
sendErrorAndClose(ws, { translationKey: "user_me_fetch_failed" });
|
||||
return;
|
||||
}
|
||||
flares = result.response.player.flares;
|
||||
@@ -409,7 +417,7 @@ export async function startWorker() {
|
||||
log.warn(
|
||||
"Forbidden: player without an allowed flare attempted to join game",
|
||||
);
|
||||
ws.close(1002, "forbidden");
|
||||
sendErrorAndClose(ws, { translationKey: "forbidden" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -424,7 +432,7 @@ export async function startWorker() {
|
||||
persistentID: persistentId,
|
||||
gameID: clientMsg.gameID,
|
||||
});
|
||||
ws.close(1002, cosmeticResult.reason);
|
||||
sendErrorAndClose(ws, { translationKey: cosmeticResult.reason });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -443,7 +451,7 @@ export async function startWorker() {
|
||||
gameID: clientMsg.gameID,
|
||||
reason: turnstileResult.reason,
|
||||
});
|
||||
ws.close(1002, "turnstile_invalid");
|
||||
sendErrorAndClose(ws, { translationKey: "turnstile_invalid" });
|
||||
return;
|
||||
case "error":
|
||||
// Fail open, allow the client to join.
|
||||
@@ -473,19 +481,19 @@ export async function startWorker() {
|
||||
|
||||
if (joinResult === "not_found") {
|
||||
log.info(`game ${clientMsg.gameID} not found on worker ${workerId}`);
|
||||
ws.close(1002, "game_not_found");
|
||||
sendErrorAndClose(ws, { translationKey: "game_not_found" });
|
||||
} else if (joinResult === "kicked") {
|
||||
log.warn(`kicked client tried to join game ${clientMsg.gameID}`, {
|
||||
gameID: clientMsg.gameID,
|
||||
workerId,
|
||||
});
|
||||
ws.close(1002, "cannot_join_game");
|
||||
sendErrorAndClose(ws, { translationKey: "cannot_join_game" });
|
||||
} else if (joinResult === "rejected") {
|
||||
log.info(`client rejected from game ${clientMsg.gameID}`, {
|
||||
gameID: clientMsg.gameID,
|
||||
workerId,
|
||||
});
|
||||
ws.close(1002, "lobby_full");
|
||||
sendErrorAndClose(ws, { translationKey: "lobby_full" });
|
||||
}
|
||||
|
||||
// Handle other message types
|
||||
@@ -502,7 +510,7 @@ export async function startWorker() {
|
||||
|
||||
ws.on("error", (error: Error) => {
|
||||
if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") {
|
||||
ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1");
|
||||
sendErrorAndClose(ws, { translationKey: "WS_ERR_UNEXPECTED_RSV_1" });
|
||||
}
|
||||
});
|
||||
ws.on("close", () => {
|
||||
|
||||
Reference in New Issue
Block a user