Connect over polling first so the game still works when a proxy drops the
websocket upgrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 17:15:47 +02:00
co-authored by Claude Opus 5.5
parent 037b98f5e1
commit d1a9c7d0f7
7 changed files with 24 additions and 14 deletions
+1 -1
View File
@@ -186,7 +186,7 @@ restored on start. Players reconnect by themselves within a few seconds.
| `EACCES` during `npm ci`, build, or at start-up | Folder not owned by your user. Re-run the `chown` in step 2. | | `EACCES` during `npm ci`, build, or at start-up | Folder not owned by your user. Re-run the `chown` in step 2. |
| `EADDRINUSE` in `pm2 logs` | Port 3010 is taken. Change `PORT` in `ecosystem.config.cjs` and `localPort` in frpc, then `pm2 restart two-keys --update-env` and restart frpc. | | `EADDRINUSE` in `pm2 logs` | Port 3010 is taken. Change `PORT` in `ecosystem.config.cjs` and `localPort` in frpc, then `pm2 restart two-keys --update-env` and restart frpc. |
| Local `curl` works, domain gives frp's "page not found" | `customDomains` doesn't match the host name exactly, frpc wasn't restarted, or the front proxy rewrites `Host`. | | Local `curl` works, domain gives frp's "page not found" | `customDomains` doesn't match the host name exactly, frpc wasn't restarted, or the front proxy rewrites `Host`. |
| Site loads but "Can't reach the server" toasts, or games feel laggy | Websocket upgrade headers missing on the central server's HTTPS proxy. | | Site loads but games feel laggy, or `/socket.io` requests with `transport=websocket` fail in the browser's network tab | Websocket upgrade headers missing on the central server's HTTPS proxy. The game still works over polling. |
| Site loads but no games listed | `npm run build` or `validate-content` failed. Check `pm2 logs two-keys`. | | Site loads but no games listed | `npm run build` or `validate-content` failed. Check `pm2 logs two-keys`. |
Do not run more than one instance (no pm2 cluster mode, no second copy behind a load balancer): Do not run more than one instance (no pm2 cluster mode, no second copy behind a load balancer):
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@two-keys/client", "name": "@two-keys/client",
"version": "1.0.0", "version": "1.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+3 -1
View File
@@ -2,7 +2,9 @@ import { io } from 'socket.io-client';
import type { JoinAck } from '@two-keys/shared'; import type { JoinAck } from '@two-keys/shared';
import { pushToast } from './toasts'; import { pushToast } from './toasts';
export const socket = io({ transports: ['websocket', 'polling'] }); // Polling first, then upgrade: a websocket-first client never falls back, so a proxy that
// drops the Upgrade header would leave it unable to connect at all.
export const socket = io();
socket.on('toast', ({ message }: { message: string }) => pushToast(message)); socket.on('toast', ({ message }: { message: string }) => pushToast(message));
socket.on('error', ({ message }: { message: string }) => pushToast(message, 'error')); socket.on('error', ({ message }: { message: string }) => pushToast(message, 'error'));
+5 -5
View File
@@ -1,12 +1,12 @@
{ {
"name": "two-keys", "name": "two-keys",
"version": "1.0.0", "version": "1.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "two-keys", "name": "two-keys",
"version": "1.0.0", "version": "1.0.1",
"workspaces": [ "workspaces": [
"shared", "shared",
"server", "server",
@@ -23,7 +23,7 @@
}, },
"client": { "client": {
"name": "@two-keys/client", "name": "@two-keys/client",
"version": "1.0.0", "version": "1.0.1",
"dependencies": { "dependencies": {
"@two-keys/shared": "*", "@two-keys/shared": "*",
"framer-motion": "^11.11.0", "framer-motion": "^11.11.0",
@@ -6847,7 +6847,7 @@
}, },
"server": { "server": {
"name": "@two-keys/server", "name": "@two-keys/server",
"version": "1.0.0", "version": "1.0.1",
"dependencies": { "dependencies": {
"@two-keys/shared": "*", "@two-keys/shared": "*",
"express": "^4.21.0", "express": "^4.21.0",
@@ -7332,7 +7332,7 @@
}, },
"shared": { "shared": {
"name": "@two-keys/shared", "name": "@two-keys/shared",
"version": "1.0.0", "version": "1.0.1",
"devDependencies": { "devDependencies": {
"vitest": "^2.1.0" "vitest": "^2.1.0"
} }
+9 -3
View File
@@ -1,10 +1,16 @@
{ {
"name": "two-keys", "name": "two-keys",
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.1",
"description": "Co-op mystery games for couples", "description": "Co-op mystery games for couples",
"workspaces": ["shared", "server", "client"], "workspaces": [
"engines": { "node": ">=20" }, "shared",
"server",
"client"
],
"engines": {
"node": ">=20"
},
"scripts": { "scripts": {
"dev": "npm run validate-content && concurrently -n server,client \"npm -w server run dev\" \"npm -w client run dev\"", "dev": "npm run validate-content && concurrently -n server,client \"npm -w server run dev\" \"npm -w client run dev\"",
"build": "npm -w shared run build && npm -w client run build && npm -w server run build", "build": "npm -w shared run build && npm -w client run build && npm -w server run build",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@two-keys/server", "name": "@two-keys/server",
"version": "1.0.0", "version": "1.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+4 -2
View File
@@ -1,11 +1,13 @@
{ {
"name": "@two-keys/shared", "name": "@two-keys/shared",
"version": "1.0.0", "version": "1.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
"types": "./src/index.ts", "types": "./src/index.ts",
"exports": { ".": "./src/index.ts" }, "exports": {
".": "./src/index.ts"
},
"scripts": { "scripts": {
"build": "tsc --noEmit -p tsconfig.json", "build": "tsc --noEmit -p tsconfig.json",
"test": "vitest run" "test": "vitest run"