screentinker/server
ScreenTinker cbf81a05a3 fix(#146): crash-hardening — one device's handler throw can't take down the fleet
Found in the alpha load test: client-chosen pairing codes collide by birthday
paradox, the provisioning INSERT hit UNIQUE(devices.pairing_code), the SqliteError
threw out of the (synchronous) socket handler -> uncaughtException -> logFatalAndExit
-> the WHOLE server exited and every device dropped. The colliding flood crash-LOOPED
the container (2 restarts).

Two layers, same "one device can't take down the fleet" theme as #142/#143/#144:

1. Narrow (deviceSocket.js): wrap the device:register provisioning INSERT in
   try/catch — a UNIQUE pairing_code collision (or ANY db error) rejects THAT
   registration (device:auth-error -> client retries) instead of throwing.
   currentDeviceId/authenticated now set only AFTER the row exists (no half-auth
   socket on failure).

2. Broader (lib/safe-socket.js): protectSocket() overrides socket.on per connection
   so any handler throw is caught, logged (event + id + stack), the socket told, and
   DISCONNECTED — per-CONNECTION fail-fast, not whole-PROCESS. We don't keep serving a
   connection from possibly-half-mutated state (honors the existing fail-fast intent),
   we just contain it to "one device reconnects" (a non-event after beta5). Wired into
   both the /device and /dashboard connection handlers; auto-covers future handlers.
   Audited first: no handler throws as control flow, so blanket-wrapping is safe.

Tests (mutation-verified, fail without their fix):
- register-insert-crash.test.js: a pairing_code collision AND a general bind error
  each reject-one-device with no uncaughtException; server keeps serving.
- socket-handler-isolation.test.js: a throwing handler disconnects only that socket;
  the server + other sockets stay alive.
Full suite 243/243.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 23:38:11 -05:00
..
config PiP overlay MVP: push image/web overlays to a device or group (#109) (#127) 2026-06-18 14:54:44 -05:00
db fix(#146): reconnect/heartbeat storm containment (beta5) 2026-06-29 22:12:13 -05:00
lib fix(#146): crash-hardening — one device's handler throw can't take down the fleet 2026-06-29 23:38:11 -05:00
middleware feat(api): per-agency-token auto-publish (#73) 2026-06-14 13:48:17 -05:00
player fix(player): composite multi-zone layouts in screenshot/stream capture 2026-06-22 23:22:12 -05:00
routes feat(#142): event-loop lag telemetry (perf_hooks) + bounded storage 2026-06-27 19:01:08 -05:00
scripts feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
services fix(#146): reconnect/heartbeat storm containment (beta5) 2026-06-29 22:12:13 -05:00
test fix(#146): crash-hardening — one device's handler throw can't take down the fleet 2026-06-29 23:38:11 -05:00
ws fix(#146): crash-hardening — one device's handler throw can't take down the fleet 2026-06-29 23:38:11 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js fix(#146): reconnect/heartbeat storm containment (beta5) 2026-06-29 22:12:13 -05:00
package-lock.json chore(release): v1.9.2-beta5 2026-06-29 22:12:57 -05:00
package.json chore(release): v1.9.2-beta5 2026-06-29 22:12:57 -05:00
server.js fix(#146): reconnect/heartbeat storm containment (beta5) 2026-06-29 22:12:13 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00