Files
tiennm99 70ae09d16b refactor(web): type the wire instead of passing any across it
Use the generated ServerMessage/ClientMessage union everywhere a decoded
message crosses a function boundary, so a typo in a payload field is a build
error instead of undefined at runtime. Clears every jsdoc/reject-any-type
warning in src/ and all but one in tests/ — room-code.test.js keeps one to
deliberately call normalizeRoomCode(undefined) against its documented
string-only signature, which is the point of that test.

Also fixes the flaky e2e helper: playingPair now waits for the guest's seat
(joinRoomSeated) before readyAndStart, and readyAndStart asserts the guest's
own ready row before touching Start, since a SetReady send can silently drop
while the socket is not open and nothing retries it.
2026-09-21 16:07:02 +07:00

53 lines
1.9 KiB
JavaScript

/**
* A controllable break in a page's WebSocket.
*
* Offline emulation is not usable here: Chromium's network conditions do not
* apply to a loopback socket, so a test that "went offline" against a local
* server kept playing quite happily. Routing the socket cuts it for real.
*
* The server does not reliably see it, though: closing a routed connection is
* observed by the page but does not always reach the far end, so the grace
* window may never start. That makes this the right tool for what the client
* does about a dead socket, and the wrong one for what the server does about a
* missing player. For that, take the page away instead.
* @param {import('@playwright/test').Page} page
*/
export async function cuttableSocket(page) {
/** @type {import('@playwright/test').WebSocketRoute | null} */
let live = null;
/** @type {import('@playwright/test').WebSocketRoute | null} */
let upstream = null;
let blocked = false;
await page.routeWebSocket(/\/ws$/, (ws) => {
if (blocked) {
// Refuse without connecting, so the client keeps retrying and the
// outage lasts as long as the test wants it to.
ws.close({ code: 1006 });
return;
}
live = ws;
// With no message handlers registered, frames are proxied in both
// directions, so the game runs against the real server as usual.
upstream = ws.connectToServer();
});
return {
/** Drops the current connection, optionally refusing reconnects too. */
async cut({ sustained = false } = {}) {
blocked = sustained;
// Both halves. Closing only the page side leaves the server holding
// a connection it thinks is fine, so it never starts the grace
// window and the opponent is never told.
await upstream?.close({ code: 1006 });
await live?.close({ code: 1006 });
upstream = null;
live = null;
},
/** Lets the next reconnect through. */
restore() {
blocked = false;
}
};
}