aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-26 15:09:20 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-26 15:09:20 +0200
commitdb7f81fd08742847f3ebea061c75530b7b31b934 (patch)
tree14b308686860eea0a9c12c47c39f8a68a1bba376 /packages/meshbay-hub/src/meshbay_hub/api
parent7126fd3c265ba75d77b449bfd0f83f5f3e584b74 (diff)
parent59d9f50bf41b2b38b7c95f8da52b698dff923c2d (diff)
downloadmeshbay-db7f81fd08742847f3ebea061c75530b7b31b934.tar.gz
Merge branch 'debug/webrtc-lock-resume'
WebRTC transport dies silently after an extended mobile screen lock (confirmed live via client-side trace + node logs): ICE goes disconnected -> failed within ~10s of each other on both ends, but the DataChannel's readyState stays "open" throughout, so nothing failed fast — every request just sat out its own timeout, matching the reported symptom (poster spinners, blocked chat, dead new streams, stuck music). - Automatic reconnect on WebRTC "failed": capped exponential backoff, redoes the full signaling handshake, wakes immediately on visibilitychange instead of waiting out a throttled backoff timer. - Fixed two real bugs the reconnect work exposed: the signaling POST to the hub kept using the token captured at construction, never the fresh one fetched per reconnect attempt (401 loop, no possible recovery); and connect() re-armed a diagnostic listener/interval on every attempt without disposing of the previous one. - pipelinedDownload retries a lost chunk instead of aborting the whole transfer — covers Files downloads, video poster/thumbnail fetches, and music-player.js's blob-based track download. - music-player.js: don't throw "Transport not connected" while a reconnect is already landing (waitForReconnect); prefetch depth now adapts to network type (5 tracks ahead on Wi-Fi, 3 on cellular or unrecognized — Firefox/Safari included, where the detection API is simply absent). - video-player.js: onReconnected reissues the existing seek-to-current-time path, so a mid-stream reconnect looks like an ordinary seek rather than a dead player; holds a Screen Wake Lock unconditionally while open. - New opt-in (off by default) user preference: keep the screen on during audio playback, for whoever wants to trade battery for sidestepping the screen-lock gap entirely — off by default because the ordinary expectation (matching Spotify/Deezer) is that the phone locks on its own while listening. - hub: /app and / now serve Cache-Control: no-store — the SPA shell had no cache header at all, so a browser that cached it heuristically could keep re-serving an old build (old ASSET_V, old JS) through any number of reloads or pull-to-refreshes. Verified against real production use across many rounds (demo groups, actual mobile screen-lock testing) rather than synthetic reproduction alone. 430 hub tests + 650 node tests passing throughout.
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/users.py1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/webapp.py18
2 files changed, 16 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/users.py b/packages/meshbay-hub/src/meshbay_hub/api/users.py
index a50a2d7..b1489ec 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/users.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/users.py
@@ -536,6 +536,7 @@ async def update_profile(
ALLOWED_PREF_KEYS = frozenset([
"notifications_disabled",
"default_tab",
+ "music_keep_screen_on",
])
def _valid_pref_key(key: str) -> bool:
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
index 8aff952..6a96602 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
@@ -60,19 +60,31 @@ def _asset_version() -> str:
ASSET_V = _asset_version()
+# No Cache-Control here meant no explicit signal either way, and a browser
+# left to its own heuristics can decide this is fresh enough without asking
+# — which nothing about a subsequent reload, pull-to-refresh included,
+# is guaranteed to override. `{v}` only reaches the browser at all if this
+# shell itself is refetched; a heuristically-cached copy of it re-serves the
+# OLD hash and therefore the old JS forever, indistinguishable from a fix not
+# working. `no-store` forces every navigation here to hit the network, which
+# is the only way `{v}` can ever change what a browser holding an old page
+# actually asks for next.
+_NO_STORE = {"Cache-Control": "no-store"}
+
+
@router.get("/app", response_class=HTMLResponse)
async def app_root():
- return HTMLResponse(_HTML)
+ return HTMLResponse(_HTML, headers=_NO_STORE)
@router.get("/app/{path:path}", response_class=HTMLResponse)
async def app_catchall(path: str):
- return HTMLResponse(_HTML)
+ return HTMLResponse(_HTML, headers=_NO_STORE)
@router.get("/", response_class=HTMLResponse)
async def index():
- return HTMLResponse(_HTML)
+ return HTMLResponse(_HTML, headers=_NO_STORE)
_HTML = """\