From 72334660300ccd835a52683ad64988977ae8f3db Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Wed, 5 Aug 2026 12:18:44 -0500 Subject: [PATCH] A stale bridge must not kill the heartbeat, and rc3 must invalidate the shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caught on hardware immediately after deploying rc3 to alpha: the player kept playing content while reporting nothing at all, throwing every 15 seconds. Uncaught TypeError: BS.telemetrySnapshot is not a function The page was rc3 and the bridge it ran was older. Two causes, both fixed. CACHE_NAME stayed at rd-player-v19 across a release that changed both the service worker's fetch strategy and the shipped /player assets. The activate handler deletes every cache whose name does not match, so keeping the name kept the previous shell cache alive — including a stale st-bridge.js. Bumped to v20. Content lives in its own cache, so this costs a small shell re-download and never re-fetches a playlist. The deeper defect is that the call site treated an optional bridge method as guaranteed. It was the ONLY unguarded BS.* call in the player; every other one checks or wraps. The bridge and the page are halves of one contract but are fetched separately, so version skew is a normal condition, not an anomaly — it must degrade, not throw. Now guarded on typeof, so a skewed pair reports the fields it can and keeps heartbeating. Worth naming the failure shape: the display looked perfectly healthy. Content played, the socket connected, the device showed online — and telemetry silently stopped. Anything that reports health through the same path it is breaking will fail this way. 1056 pass. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL --- server/player/index.html | 6 +++++- server/player/sw.js | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/server/player/index.html b/server/player/index.html index 0368d04..601bcae 100644 --- a/server/player/index.html +++ b/server/player/index.html @@ -1718,7 +1718,11 @@ device_utc: Date.now(), // Real values where the platform has them (temperature, storage quota). Spread LAST so // it overrides the nulls above, and empty off-platform so nothing else changes. - ...(BS ? BS.telemetrySnapshot() : {}), + // typeof, not truthiness: the bridge and this page are halves of one contract but are + // fetched separately, so a player can run a NEW page against a CACHED older bridge. + // Calling a method that version lacks threw here every 15s and took the whole heartbeat + // with it — the display kept playing while silently reporting nothing at all. + ...(BS && typeof BS.telemetrySnapshot === 'function' ? BS.telemetrySnapshot() : {}), } }); }, HEARTBEAT_INTERVAL); diff --git a/server/player/sw.js b/server/player/sw.js index 0178414..188f61f 100644 --- a/server/player/sw.js +++ b/server/player/sw.js @@ -1,4 +1,9 @@ -const CACHE_NAME = 'rd-player-v19'; +// v20: rc3 changed the fetch strategy AND the shipped player assets. The activate handler deletes +// every cache whose name does not match, so leaving this at v19 kept the previous shell cache alive +// — a player then ran a new index.html against a stale st-bridge.js and threw on every heartbeat. +// Bump whenever a shipped /player asset changes shape; content lives in its own cache, so this +// costs a small re-download and never re-fetches the playlist. +const CACHE_NAME = 'rd-player-v20'; // Content lives in its own cache so the shell can be re-versioned (the activate handler deletes // every cache that is not CACHE_NAME) WITHOUT throwing away megabytes of media that are still // perfectly valid. Rolling the shell used to mean a player re-downloaded its entire playlist.