mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-16 21:22:20 +00:00
## What Implements the close-code contract from the matchmaking API handoff (API PR #419) in the matchmaking modal, plus two runnable integration harnesses. Previously `onclose` only logged, so an API deploy while queued left the player on the "searching" spinner forever (the queue is in-memory on the API worker). | Close | Behavior | | --- | --- | | 1008 `Invalid session` | Reconnect and re-send `join` — `getPlayToken()` refreshes an expired token internally, so the rejoin carries a fresh JWT | | 1000 `Replaced by newer connection` | Another tab/window took the queue slot: show a message (new `matchmaking_modal.replaced` string) and stop. No retry | | Any other close before assignment | Server restart/deploy: reconnect and re-send `join`, with exponential backoff (1s doubling to a 15s cap) | Intentional closes (user backs out of the modal, assignment received) don't reconnect. A pending 2s join timer from a previous socket is cleared before each reconnect so it can't fire on the new socket. ## Test harnesses (`tests/matchmaking/`) - **`npm run test:matchmaking`** (contained): drives the real modal in a headless browser against a fake in-process matchmaking server speaking the documented protocol; covers the whole close-code table over real WebSockets. Needs only `npm run dev`. - **`npm run test:matchmaking:e2e`**: real integration against the API worker on `localhost:8787` — two browser players join the real queue, the dev game server receives the checkin assignment, and both clients end up in the same created game. ## Why now This is required by the matchmaking API's client contract independent of the upcoming 2v2 work ("clients must already handle unexpected close → reconnect and rejoin"). The rest of the 2v2 integration is planned for the next version. ## Verification - Contained harness: 8/8 checks pass (join, 1006 reconnect+rejoin, 1008 rejoin with fresh token, assignment, replaced → message + no retry, intentional close → no retry/no message). - E2E harness against a local worker: 3/3 — both players matched into the same game, game created via checkin and joinable by both. - `npm test` (all 2,046 pass), `npx tsc --noEmit`, ESLint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
66 lines
1.7 KiB
JavaScript
66 lines
1.7 KiB
JavaScript
import { includeIgnoreFile } from "@eslint/compat";
|
|
import pluginJs from "@eslint/js";
|
|
import eslintConfigPrettier from "eslint-config-prettier/flat";
|
|
import globals from "globals";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import tseslint from "typescript-eslint";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const gitignorePath = path.resolve(__dirname, ".gitignore");
|
|
|
|
/** @type {import('eslint').Linter.Config[]} */
|
|
export default [
|
|
includeIgnoreFile(gitignorePath),
|
|
{
|
|
ignores: [
|
|
"src/server/gatekeeper/**",
|
|
"tests/pathfinding/playground/**",
|
|
".claude/**",
|
|
],
|
|
},
|
|
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
|
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
|
|
pluginJs.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
eslintConfigPrettier,
|
|
{
|
|
languageOptions: {
|
|
parserOptions: {
|
|
projectService: {
|
|
allowDefaultProject: [
|
|
"__mocks__/fileMock.js",
|
|
"eslint.config.js",
|
|
"scripts/sync-assets.mjs",
|
|
"tests/matchmaking/*.mjs",
|
|
],
|
|
},
|
|
tsconfigRootDir: import.meta.dirname,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
rules: {
|
|
// Disable rules that would fail. The failures should be fixed, and the entries here removed.
|
|
"@typescript-eslint/no-explicit-any": "off",
|
|
"no-unused-vars": "off",
|
|
},
|
|
},
|
|
{
|
|
rules: {
|
|
// Enable rules
|
|
"@typescript-eslint/prefer-nullish-coalescing": "error",
|
|
eqeqeq: "error",
|
|
"no-case-declarations": "error",
|
|
"@typescript-eslint/no-unused-vars": [
|
|
"error",
|
|
{
|
|
args: "none",
|
|
caughtErrors: "none",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
];
|