From 1aee4f2d5b0480bf87c336effae4059e1c6600d3 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Thu, 14 May 2026 13:02:34 -0500 Subject: [PATCH] fix(socket): raise Engine.IO ping/pong + prefer WebSocket transport Connection-stability layer for issue #3. LG webOS WebKit (and other TV-grade clients) miss Engine.IO pongs under decode load with the Socket.IO defaults of 25s ping / 20s timeout, causing spurious transport drops and a connect/reconnect/evict/disconnect loop on the device. Default polling-first transport adds another fragility layer via the polling->WebSocket upgrade dance. - pingInterval / pingTimeout default to 30000 / 30000 (worst-case dead-socket detection 60s, up from ~45s). Both env-configurable via PING_INTERVAL / PING_TIMEOUT. - Player Socket.IO client: transports: ['websocket', 'polling']. Tries WebSocket first; falls back to polling on the same connect attempt if WebSocket fails. Polling fallback preserved for firewall-restricted networks. App-level heartbeat checker is unchanged and remains the safety net for clients that miss the transport-level ping/pong window. Tradeoffs documented in inline comments. README env table extended with PING_INTERVAL and PING_TIMEOUT rows. Refs #3 Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 ++ server/config.js | 6 ++++++ server/player/index.html | 8 ++++++++ server/server.js | 4 +++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b7b063..cf344c9 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ Schema migrations run automatically on first boot — no manual migration comman | `JWT_SECRET` | JWT signing key (auto-generated if not set) | _(auto)_ | | `SSL_CERT` | Path to SSL certificate | `server/certs/cert.pem` | | `SSL_KEY` | Path to SSL private key | `server/certs/key.pem` | +| `PING_INTERVAL` | Socket.IO Engine.IO ping interval (ms). Raise for slow TV WebKits that miss pongs under decode load. | `30000` | +| `PING_TIMEOUT` | Socket.IO Engine.IO pong wait (ms). Lower = faster dead-socket detection; higher = more forgiving of laggy clients. | `30000` | ### Optional Integrations diff --git a/server/config.js b/server/config.js index 5c541bf..946e72a 100644 --- a/server/config.js +++ b/server/config.js @@ -10,6 +10,12 @@ module.exports = { frontendDir: path.join(__dirname, '..', 'frontend'), heartbeatInterval: 10000, // Check every 10s heartbeatTimeout: 45000, // Offline after 45s (3 missed 15s beats) + // Engine.IO transport-level ping/pong. Raised from Socket.IO defaults + // (25000/20000) because TV WebKits (LG webOS, older Tizen) miss pongs + // under decode load - tighter values cause spurious transport drops. + // Worst-case dead-socket detection: pingInterval + pingTimeout = 60s. + pingInterval: parseInt(process.env.PING_INTERVAL) || 30000, + pingTimeout: parseInt(process.env.PING_TIMEOUT) || 30000, maxFileSize: 500 * 1024 * 1024, // 500MB thumbnailWidth: 320, screenshotQuality: 70, diff --git a/server/player/index.html b/server/player/index.html index 6ba350e..165b7ee 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -467,6 +467,14 @@ reconnectionDelay: 2000, reconnectionDelayMax: 10000, timeout: 20000, + // Prefer WebSocket but allow polling fallback. Socket.IO default is + // polling-first with an upgrade dance that's fragile on TV WebKits + // (LG webOS especially). Reversing the order opens a WebSocket directly; + // if that fails (rare - blocked by firewall), it falls back to polling + // on the same connect attempt. Tradeoff: WS-blocked networks add a few + // seconds to first connect while WS times out. Worth it for the common + // case where WS is fine but the upgrade dance was hanging the device. + transports: ['websocket', 'polling'], }); socket.on('connect', () => { diff --git a/server/server.js b/server/server.js index 2435a42..bc52464 100644 --- a/server/server.js +++ b/server/server.js @@ -43,7 +43,9 @@ const io = new Server(server, { origin: (origin, cb) => corsOriginCheck(origin, cb), credentials: true, }, - maxHttpBufferSize: 10 * 1024 * 1024 // 10MB for screenshot uploads + maxHttpBufferSize: 10 * 1024 * 1024, // 10MB for screenshot uploads + pingInterval: config.pingInterval, + pingTimeout: config.pingTimeout, }); // Middleware