aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
diff options
context:
space:
mode:
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.py132
1 files changed, 73 insertions, 59 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 8a5bbff..e92ec13 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -25,8 +25,6 @@ Signaling flow (handled externally by the hub):
import asyncio
import base64
import contextvars
-import hashlib
-import hmac
import logging
import os
import re
@@ -38,63 +36,53 @@ from pathlib import Path
from typing import Any
import blake3
-import jwt
import msgpack
-from aiortc import RTCPeerConnection, RTCSessionDescription, RTCDataChannel
+from aiortc import RTCDataChannel, RTCPeerConnection, RTCSessionDescription
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
)
-
from meshbay_common import MNP_VERSION
-from meshbay_common.handshake import (
- MNP_MIN_SUPPORTED,
- NONCE_LEN,
- ROLE_CLIENT,
- ROLE_NODE,
- HandshakeError,
- authorize_token,
- check_version,
- handshake_transcript,
- make_proof,
- verify_proof,
- webrtc_binding,
-)
from meshbay_common.adminop import (
ADMIN_CHALLENGE_TTL,
+ OP_APP_DIRECTORIES,
+ OP_APPS_ENABLED,
+ OP_CHAT_DIRECTORY,
+ OP_CHAT_EPOCH,
+ OP_CHAT_LINK_PREVIEW,
OP_DIR_DELETE,
OP_FILE_DELETE,
+ OP_GEK_ROTATE,
+ OP_GROUP_ATTACH,
+ OP_GROUP_DETACH,
OP_INVITE_CREATE,
OP_MEMBER_REVOKE,
- OP_GEK_ROTATE,
OP_MEMBER_UNPIN,
- OP_APPS_ENABLED,
+ OP_MUSICBRAINZ_ENABLED,
+ OP_ROOT_ADD,
+ OP_ROOT_EJECT,
+ OP_ROOT_PLUG,
+ OP_ROOT_REMOVE,
+ OP_ROOT_UPDATE,
+ OP_SEARCH_LISTED,
OP_SET_SCAN_SETTINGS,
- OP_TRANSFER_LIMITS,
OP_TMDB_CONFIG,
OP_TMDB_ENABLED,
OP_TMDB_OVERRIDE,
OP_TMDB_REMATCH,
- OP_MUSICBRAINZ_ENABLED,
- OP_APP_DIRECTORIES,
- OP_CHAT_DIRECTORY,
- OP_CHAT_EPOCH,
- OP_CHAT_LINK_PREVIEW,
- OP_SEARCH_LISTED,
- OP_ROOT_ADD,
- OP_ROOT_REMOVE,
- OP_ROOT_UPDATE,
- OP_ROOT_EJECT,
- OP_ROOT_PLUG,
- OP_GROUP_ATTACH,
- OP_GROUP_DETACH,
+ OP_TRANSFER_LIMITS,
admin_transcript,
)
+from meshbay_common.chatbox import (
+ NONCE_LEN as CHAT_NONCE_LEN,
+)
+from meshbay_common.chatbox import (
+ SIG_LEN as CHAT_SIG_LEN,
+)
from meshbay_common.crypto import pk_to_b64, wrap_gek_aes
from meshbay_common.device import (
DEVICE_TTL,
device_add_transcript,
- device_code_hash,
device_hello_transcript,
device_request_transcript,
)
@@ -104,44 +92,62 @@ from meshbay_common.groupbox import (
PURPOSE_ROSTER,
seal,
)
+from meshbay_common.handshake import (
+ MNP_MIN_SUPPORTED,
+ NONCE_LEN,
+ ROLE_CLIENT,
+ ROLE_NODE,
+ HandshakeError,
+ authorize_token,
+ check_version,
+ handshake_transcript,
+ make_proof,
+ verify_proof,
+ webrtc_binding,
+)
from meshbay_common.join import (
JOIN_TTL,
ROLE_MEMBER,
ROLE_OPERATOR,
join_transcript,
)
-from meshbay_common.chatbox import (
- NONCE_LEN as CHAT_NONCE_LEN,
- SIG_LEN as CHAT_SIG_LEN,
-)
from meshbay_common.protocol import (
MNP,
+ UPLOAD_PROBE_INDEX,
chunk_ciphertext,
file_chunk_wire,
- UPLOAD_PROBE_INDEX,
file_upload_ack_wire,
file_upload_payload,
)
-from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage
-from meshbay_node.transport.wire import index_sync_message
-from meshbay_node.indexer import GroupIndex
-from meshbay_node.indexer.indexer import DirectoryIndexer
+
from meshbay_node import hwaccel, linkpreview, ops, platform
from meshbay_node import transfers as transfers_mod
from meshbay_node import uploads as uploads_mod
-from meshbay_node.transfers import TransferSlots
+from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage
+from meshbay_node.indexer import GroupIndex
+from meshbay_node.indexer.indexer import DirectoryIndexer
+
# Re-imported under its original name: every call site and existing test in
# this module still refers to it as `_probe_video`. The implementation lives
# in media_probe.py so the indexer package (imported just above) can call it
# too, for index-time enrichment, without a circular import.
from meshbay_node.media_probe import (
BROWSER_INCOMPATIBLE_VIDEO_CODECS,
+)
+from meshbay_node.media_probe import (
probe_video as _probe_video,
)
from meshbay_node.roots import (
- ROOT_NOT_SERVED, RootSet, entry_abs_path, off_disk, SAFE_UPLOAD_NAME, safe_subdir,
+ ROOT_NOT_SERVED,
+ SAFE_UPLOAD_NAME,
+ RootSet,
_free_name,
+ entry_abs_path,
+ off_disk,
+ safe_subdir,
)
+from meshbay_node.transfers import TransferSlots
+from meshbay_node.transport.wire import index_sync_message
log = logging.getLogger(__name__)
@@ -3924,7 +3930,8 @@ class WebRTCPeerSession:
if not entry:
thumb = await self._try_serve_thumbnail(file_id, chunk_index, ctx.get("gek"))
if thumb is not None:
- log.debug("file_req file_id=%s chunk=%s: served as thumbnail", file_id[:16], chunk_index)
+ log.debug("file_req file_id=%s chunk=%s: served as thumbnail",
+ file_id[:16], chunk_index)
self._send(thumb)
return
log.warning("File not found: %s", file_id[:16])
@@ -3992,7 +3999,8 @@ class WebRTCPeerSession:
self._leaseless.finish(str(file_id))
@staticmethod
- async def _fetch_and_cache_poster(media_cache, tmdb_client, poster_path: str | None) -> str | None:
+ 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/MESHBAY_DESIGN.md §9.7), and
@@ -4020,7 +4028,8 @@ class WebRTCPeerSession:
return thumb_hash
@staticmethod
- async def _fetch_and_cache_cover(media_cache, musicbrainz_client, mbid: str | None) -> str | None:
+ async def _fetch_and_cache_cover(media_cache, musicbrainz_client,
+ mbid: str | None) -> str | None:
"""
Music app equivalent of `_fetch_and_cache_poster` — a release's
Cover Art Archive image, fetched once per mbid and cached under its
@@ -4504,7 +4513,9 @@ class WebRTCPeerSession:
# itself.
if not fetched.get("overview"):
fallback = await tmdb_client.tv_season(tmdb_id, season, language="en-US") or {}
- fetched = {**fallback, **{k: v for k, v in fetched.items() if v not in (None, "", [])}}
+ fetched = {**fallback,
+ **{k: v for k, v in fetched.items()
+ if v not in (None, "", [])}}
await media_cache.set_season_meta(tmdb_id, season, fetched)
details = fetched
@@ -4833,8 +4844,10 @@ class WebRTCPeerSession:
# back to English per field rather than discarding an otherwise-good
# localized response over one empty one — mirrored here the same
# way, at field granularity, not by abandoning the whole response.
- if not details.get("overview") or not details.get("poster_path") or not details.get("genres"):
- fallback = (await tmdb_client.tv_details(tmdb_id, language="en-US") if media_type == "tv"
+ if (not details.get("overview") or not details.get("poster_path")
+ or not details.get("genres")):
+ fallback = (await tmdb_client.tv_details(tmdb_id, language="en-US")
+ if media_type == "tv"
else await tmdb_client.movie_details(tmdb_id, language="en-US")) or {}
details = {**fallback, **{k: v for k, v in details.items() if v not in (None, "", [])}}
credits = (await tmdb_client.tv_credits(tmdb_id) if media_type == "tv"
@@ -5909,7 +5922,8 @@ class WebRTCPeerSession:
transcript = admin_transcript(
op=pending["op"],
node_pk_b64=self._node_pk_b64(),
- group_id=pending["group_id"] if pending.get("group_id") is not None else (self._group_id or ""),
+ group_id=(pending["group_id"] if pending.get("group_id") is not None
+ else (self._group_id or "")),
subject=pending["subject"],
nonce=pending["nonce"],
ts=pending["ts"],
@@ -6160,7 +6174,7 @@ class WebRTCPeerSession:
# total budget is unchanged.
await asyncio.wait_for(self._stream_credit_evt.wait(),
timeout=STREAM_CREDIT_POLL)
- except asyncio.TimeoutError:
+ except TimeoutError:
silent = time.monotonic() - self._stream_heard_at
if silent >= STREAM_CREDIT_TIMEOUT:
log.info("Stream stalled: nothing from peer=%s for %.0fs",
@@ -6208,7 +6222,7 @@ class WebRTCPeerSession:
await asyncio.wait_for(asyncio.shield(prev), timeout=15)
log.info("stream: previous stream ended in %.1fs",
time.monotonic() - t0)
- except asyncio.TimeoutError:
+ except TimeoutError:
log.warning("stream: previous stream STILL RUNNING after 15s")
except Exception:
pass # it failed on its own; the slot is free either way
@@ -6851,7 +6865,7 @@ async def _transcode_audio_to_aac(file_path: Path) -> bytes:
try:
_, stderr = await asyncio.wait_for(
proc.communicate(), timeout=AUDIO_TRANSCODE_TIMEOUT_SECS)
- except asyncio.TimeoutError:
+ except TimeoutError:
proc.kill()
await proc.wait()
raise RuntimeError(f"ffmpeg timed out after {AUDIO_TRANSCODE_TIMEOUT_SECS}s")
@@ -6912,7 +6926,7 @@ async def _seek_lands_at(file_path: Path, t: float, map_args: list[str]) -> floa
)
stdout, _ = await asyncio.wait_for(
probe.communicate(), timeout=SEEK_PROBE_TIMEOUT_SECS)
- except (asyncio.TimeoutError, OSError) as e:
+ except (TimeoutError, OSError) as e:
log.warning("stream: seek probe failed at %.1fs: %r", t, e)
return None
finally:
@@ -6967,7 +6981,7 @@ async def _extract_subtitle_to_webvtt(file_path: Path, ordinal: int,
try:
_, stderr = await asyncio.wait_for(
proc.communicate(), timeout=timeout)
- except asyncio.TimeoutError:
+ except TimeoutError:
proc.kill()
await proc.wait()
raise RuntimeError(f"ffmpeg timed out after {timeout:.0f}s")
@@ -7152,7 +7166,7 @@ class WebRTCTransport:
Returns (answer_sdp, ice_candidates) to relay back via hub signaling.
ICE candidates are embedded in the SDP (aiortc gathers before returning).
"""
- from aiortc import RTCIceServer, RTCConfiguration
+ from aiortc import RTCConfiguration, RTCIceServer
# aiortc keeps only the first STUN entry it sees here; the actual
# multi-server fan-out is done by transport/stun_multi, which patches