diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 72 |
1 files changed, 38 insertions, 34 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index 3bb0df7..4540f2f 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -174,7 +174,8 @@ USER_BLOB_ACCOUNT_MAX = 8 * 1024 * 1024 _USER_BLOB_KIND_RE = re.compile( r"^(playlists|playlist:[A-Za-z0-9_-]{1,64})$") -# Chat link-preview results, kept in memory only (draft-v6 §2.7: the node +# Chat link-preview results, kept in memory only (docs/MESHBAY_DESIGN.md §6.5: +# the node # produces enrichment on demand and keeps nothing durable — the asking device # caches). Bounded and time-limited so a busy group cannot grow it without end # and a page that changed its card is picked up within the hour. @@ -998,7 +999,7 @@ class WebRTCPeerSession: self._ctx.get("daemon_state", {}).get("tmdb_token_customized", False)), "tmdb_language": str( self._ctx.get("daemon_state", {}).get("tmdb_language") or ""), - # Music app (docs/musicbay.md §6) — same shape as the TMDB + # Music app (docs/MESHBAY_DESIGN.md §9.8) — same shape as the TMDB # fields above. No language field: MusicBrainz search doesn't # take one the way TMDB does. "musicbrainz_enabled": bool(self._group_ctx().get("musicbrainz_enabled", True)), @@ -1174,7 +1175,7 @@ class WebRTCPeerSession: } # The recovery-wrapped copy (MNP 0.14) rides along when present, so a # client holding the recovery key can re-wrap it under a new - # passphrase — docs/auth-confirm.md §4.5. + # passphrase — docs/MESHBAY_DESIGN.md §3.6. if kp.get("bundle_enc_recovery"): resp["bundle_enc_recovery"] = kp["bundle_enc_recovery"] self._send(resp) @@ -1558,7 +1559,7 @@ class WebRTCPeerSession: # A person may hold several devices on one node. The authority admitting a # new one is a key the node already pinned — never the hub, which has stored # no user keys since 2026-08-14 and therefore cannot countersign anything. - # See docs/desktop-client-v1.md §4. + # See docs/MESHBAY_DESIGN.md §3.3. async def _do_device_request(self, msg: dict) -> None: """ @@ -1719,7 +1720,7 @@ class WebRTCPeerSession: # # This is what lets another member check for themselves that this device # belongs to an account whose earlier device they have already pinned, - # instead of taking the node's word (Tier 2, desktop-client-v1.md §4.8). + # instead of taking the node's word (Tier 2, docs/MESHBAY_DESIGN.md §3.3). await roster.pin_identity( user_id=self._user_id, username=self._username or "", pk_ed25519=pk_ed_b64, pk_x25519=pk_x_b64, via="device", @@ -1746,7 +1747,7 @@ class WebRTCPeerSession: What is checked, in order: the key is a live device *of this account* in the node's own roster (never a token claim — that is - `per-node-identity-v1.md`'s rule), the timestamp is fresh, and the + `docs/MESHBAY_DESIGN.md` §3.2's rule), the timestamp is fresh, and the signature verifies over a transcript naming this node, this group and this connection's nonce. A key that is merely well-formed proves nothing. @@ -1964,7 +1965,7 @@ class WebRTCPeerSession: Admission policy for a group, read from the node's own configuration. Never from the hub: a hub that could declare a group open would be handed - the key to it (§3.4 of docs/invite-pairing-v1.md). + the key to it (docs/MESHBAY_DESIGN.md §3.4). """ gctx = (self._ctx.get("groups") or {}).get(group_id) or {} return gctx.get("join_policy", "invite") @@ -2290,9 +2291,9 @@ class WebRTCPeerSession: # include "video" or "music" — both can make outbound third-party # network calls (TMDB, MusicBrainz) once enabled, so an operator opts a # group in explicitly rather than getting it for free - # (docs/mediacenter.md §5.6, docs/musicbay.md §4.4). - # `helloworld` is the reference implementation (docs/refactor-groups.md - # §4.1), hidden client-side behind `?dev=1`. It is here because the + # (docs/MESHBAY_DESIGN.md §9.7, §9.8). + # `helloworld` is the reference implementation (docs/MESHBAY_DESIGN.md + # §9.4), hidden client-side behind `?dev=1`. It is here because the # allow-list is server-side enforcement — a client that names an app this # node does not know is refused — and an app the node refused could not # demonstrate anything. This entry and the client's registry line are the @@ -2363,7 +2364,7 @@ class WebRTCPeerSession: per-group concern (see _do_tmdb_enabled for the per-group on/off switch). Signed like the rest: this changes outbound third-party network traffic the node did not have before the Videos app - (docs/mediacenter.md §5.5, §8) — an unsigned change would let any + (docs/MESHBAY_DESIGN.md §9.7, §6.5) — an unsigned change would let any member alter egress the operator never agreed to. """ token = msg.get("token") @@ -2746,7 +2747,7 @@ class WebRTCPeerSession: def _do_musicbrainz_enabled(self, msg: dict) -> None: """ Whether MusicBrainz lookups run for this group at all. Per-group - from the start (docs/musicbay.md §3.2/§6) — signed like + from the start (docs/MESHBAY_DESIGN.md §9.8) — signed like tmdb_enabled: it decides whether this group's members' Music tab ever makes outbound MusicBrainz traffic. """ @@ -3813,13 +3814,13 @@ class WebRTCPeerSession: self, thumb_hash: str, chunk_index: int, gek: bytes | None, ) -> dict | None: """ - docs/mediacenter.md §5.3: a thumbnail is served through the same + docs/MESHBAY_DESIGN.md §6.5: a thumbnail is served through the same chunked file_req path as a real file, resolved against the media cache instead of the index when the id doesn't match a file. Sliced by `chunk_index` like a real file's chunks, not just handed back whole: a thumbnail/poster/cover never approached CHUNK_SIZE so this used to be equivalent to "only chunk 0 exists", but an audio - transcode result (docs/musicbay.md, the WMA/Musepack exception) is + transcode result (docs/MESHBAY_DESIGN.md §9.8, the WMA/Musepack exception) is cached in the same media_cache blob store and can be several MB — genuinely multi-chunk, same as a file read straight off disk. """ @@ -3939,7 +3940,7 @@ class WebRTCPeerSession: async def _fetch_and_cache_poster(media_cache, tmdb_client, poster_path: str | None) -> str | None: """ Downloads a TMDB poster/backdrop once, caches it under its own - blake3 like a video thumbnail (docs/mediacenter.md §5.4), and + blake3 like a video thumbnail (docs/MESHBAY_DESIGN.md §9.7), and returns the hash a client then fetches via the normal file_req/ chunk path (§5.3) — no client ever contacts image.tmdb.org directly. @@ -3988,7 +3989,7 @@ class WebRTCPeerSession: async def _do_audio_transcode_request(self, msg: dict) -> None: """ - docs/musicbay.md's one exception to "no node-side transcode pool": + docs/MESHBAY_DESIGN.md §9.8's one exception to "no node-side transcode pool": WMA and Musepack tag/cover fine (enrich_audio.py) but decode in no mainstream browser's <audio> element at all. Transcoded to AAC/M4A once and cached under its own content hash — same "computed once, @@ -4195,7 +4196,7 @@ class WebRTCPeerSession: async def _do_music_meta_request(self, msg: dict) -> None: """ - docs/musicbay.md §4.3: MusicBrainz metadata for one track, resolved + docs/MESHBAY_DESIGN.md §9.8: MusicBrainz metadata for one track, resolved from the group's index by its content id. Album-level (release), the direct analogue of Videos' show-level TMDB caching: one search per (artist, album) pair serves cover art and canonical naming to every @@ -4275,7 +4276,7 @@ class WebRTCPeerSession: async def _do_media_meta_request(self, msg: dict) -> None: """ - docs/mediacenter.md §5.4: TMDB metadata for one file, resolved from + docs/MESHBAY_DESIGN.md §9.7: TMDB metadata for one file, resolved from the group's index by its content id (root+relpath the client already knows from index_sync/index_delta identify the entry; its own `id` is what actually names one file — never a raw filesystem path off @@ -4300,7 +4301,7 @@ class WebRTCPeerSession: media_cache = self._ctx.get("media_cache") tmdb_client = self._ctx.get("tmdb_client") - # Per-group, not node-wide (docs/mediacenter.md §5.5, 2026-08-24): + # Per-group, not node-wide (docs/MESHBAY_DESIGN.md §9.7, 2026-08-24): # treated exactly like "no client configured" — same silent, no-error # degradation, since a member's Videos tab already has to handle "no # TMDB match" as the ordinary case. @@ -4409,7 +4410,7 @@ class WebRTCPeerSession: return media_cache = self._ctx.get("media_cache") tmdb_client = self._ctx.get("tmdb_client") - # Per-group, not node-wide (docs/mediacenter.md §5.5, 2026-08-24) — + # Per-group, not node-wide (docs/MESHBAY_DESIGN.md §9.7, 2026-08-24) — # same silent zero-confidence degradation as "no client configured". if (media_cache is None or tmdb_client is None or not self._group_ctx().get("tmdb_enabled", True)): @@ -4447,7 +4448,7 @@ class WebRTCPeerSession: async def _do_tmdb_search_request(self, msg: dict) -> None: """ Candidate TMDB matches for an operator correcting a wrong automatic - match (docs/mediacenter.md, §V-whatever this becomes) — a plain + match (docs/MESHBAY_DESIGN.md §9.7, §V-whatever this becomes) — a plain lookup, not a mutation, so unlike `tmdb_override` this needs no admin authority: any member can see what TMDB itself would offer, the same as the automatic search already silently does on their @@ -4460,7 +4461,7 @@ class WebRTCPeerSession: return media_cache = self._ctx.get("media_cache") tmdb_client = self._ctx.get("tmdb_client") - # Per-group, not node-wide (docs/mediacenter.md §5.5, 2026-08-24) — + # Per-group, not node-wide (docs/MESHBAY_DESIGN.md §9.7, 2026-08-24) — # same silent empty-results degradation as "no client configured": # a member with TMDB off for this group sees the same "type it in # yourself" affordance either way, never an error. @@ -4593,7 +4594,7 @@ class WebRTCPeerSession: def _do_tmdb_rematch(self, msg: dict) -> None: """ An operator dropping one file's cached TMDB match so it re-resolves - with the current matcher (§10.1/V13) — the one-click alternative to + with the current matcher (V13) — the one-click alternative to the full search-and-pick "Fix match" flow, and reachable without SSH (`meshbay-node video rematch` clears a whole group). Signed like `tmdb_override`: `media_cache` is shared node-wide. @@ -4635,7 +4636,8 @@ class WebRTCPeerSession: async def _tmdb_search(self, tmdb_client, entry, is_show: bool): """ - §3.3's retry ladder — same shape for movies and shows (§10.1/V8). + docs/MESHBAY_DESIGN.md §9.7's scored ladder — same shape for movies + and shows (V8). TMDB's own top result is still trusted per query (§3.3's last row — no local re-ranking of *its* list); what the ladder adds is that it *scores every candidate query* and keeps the best, instead of @@ -4681,7 +4683,7 @@ class WebRTCPeerSession: specific than a punctuation-normalised restatement of `primary` (an alternative_title, a sequel variant); when it does not and the primary hit is already decent, the remaining calls are skipped - (§10.1/V11 — they almost never win and cost a round trip each). + (V11 — they almost never win and cost a round trip each). """ def _year_of(res: dict) -> int | None: d = str(res.get("release_date") or res.get("first_air_date") or "") @@ -5124,7 +5126,8 @@ class WebRTCPeerSession: async def _do_link_preview_request(self, msg: dict) -> None: """ - Unfurl a URL a member pasted into chat (draft-v6 §2.7 enrichment rule: + Unfurl a URL a member pasted into chat (docs/MESHBAY_DESIGN.md §6.5's + enrichment rule: the client asks, the node produces on demand, the asking device caches — nothing durable here). @@ -5895,13 +5898,14 @@ class WebRTCPeerSession: # anything failing loudly: a file uploaded from a phone could not be # deleted from the same person's laptop, and the only symptom was # "Signature verification failed" on their own file - # (docs/desktop-client-v1.md §4.8 A). + # (docs/MESHBAY_DESIGN.md §3.3). # # `uploader_pk` is kept, and stops being the authorization key: it is # now the audit record of *which device* did it. Authorization is by # account, through the roster — never through a token claim, which is - # the protection `per-node-identity-v1.md` added and which a lookup by - # `uploader_id` in the hub's world would give straight back. + # the protection per-node identity keys give (docs/MESHBAY_DESIGN.md + # §3.2) and which a lookup by `uploader_id` in the hub's world would + # give straight back. if not (await self._verify_admin_sig(transcript, sig) or await self._verify_uploader_sig(entry, transcript, sig)): self._send({"type": "error", "detail": "Signature verification failed"}) @@ -5915,7 +5919,7 @@ class WebRTCPeerSession: ) -> None: # Node operator only. A group admin who does not run the node has no # authority over who this node admits (deny by default). Delegation is - # designed but deferred — see §6.2 of docs/invite-pairing-v1.md. + # designed but deferred — see docs/MESHBAY_DESIGN.md §3.4. if not await self._verify_admin_sig(transcript, sig): self._send({"type": "error", "detail": "Signature verification failed"}) self._audit("admin_auth_failed", f"invite_create:{pending['subject'][:16]}") @@ -6220,7 +6224,7 @@ class WebRTCPeerSession: # node's answer to it, since ffmpeg re-encodes these in real time on # any machine that can run this daemon. Reported live against an # Xvid/MP3 .avi. `transcode_incompatible_video`'s own documentation - # (draft-v6 §2.11) already said "HEVC *and other browser- + # (docs/MESHBAY_DESIGN.md §6.8) already said "HEVC *and other browser- # incompatible video codecs*"; only HEVC was ever wired up. can_copy = (bool(codec_str) and raw_video_codec not in BROWSER_INCOMPATIBLE_VIDEO_CODECS) @@ -6906,9 +6910,9 @@ class WebRTCTransport: `ctx["_transcode_sem"]`, and `hasattr(webrtc, "_stream_sem")` is always False. So the hot-swap was a no-op and **`max_concurrent_streams` has never taken effect from the Node page without a restart**, contrary to - draft-v6 §2.11. This is the one implementation, on the object that owns - the state, so the next two caps do not each grow their own copy of the - mistake. + docs/MESHBAY_DESIGN.md §6.8. This is the one implementation, on the + object that owns the state, so the next two caps do not each grow their + own copy of the mistake. What resizing means, stated because it is a decision and not a detail: **the new cap governs new streams; the ones already running are |