diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-28 18:22:07 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-28 18:22:07 +0200 |
| commit | f63104b82da24ff3f406c53346300bd50788796f (patch) | |
| tree | 1bd4466385e4298a2e2936d188b01047a4a7a8a6 /packages | |
| parent | a4b36e5fe31cb671a4cbbdaad75746cd68b601fe (diff) | |
| download | meshbay-f63104b82da24ff3f406c53346300bd50788796f.tar.gz | |
refactor: remove dead code across packages
Unused modules, functions, constants and client helpers with no caller,
the unreachable hub:probe IPC handler, and the CSAM hash matching.
Behaviour unchanged; the dispatch golden loses only the two removed
message types.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
23 files changed, 10 insertions, 784 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 9116d1a..f8bb3f4 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -850,16 +850,6 @@ function registerBridge() { return out; }); - ipcMain.handle('hub:probe', async (_e, url) => { - const target = String(url || config.hubBase || '').replace(/\/+$/, ''); - if (!target) return null; - try { - const r = await fetch(`${target}/v1/hub/version`, - { signal: AbortSignal.timeout(10000) }); - return r.ok ? await r.json() : null; - } catch { return null; } - }); - // Hide, never close: `window-all-closed` quits the app, and closing here would // make "minimise to tray" mean "exit". A hidden window keeps the session, the // transfers and the node connection exactly as they were. diff --git a/packages/meshbay-common/src/meshbay_common/protocol.py b/packages/meshbay-common/src/meshbay_common/protocol.py index 5e666e9..af2d93b 100644 --- a/packages/meshbay-common/src/meshbay_common/protocol.py +++ b/packages/meshbay-common/src/meshbay_common/protocol.py @@ -30,7 +30,6 @@ number — it is a label chosen by the peer, and the only thing it decides is which local promise a reply belongs to. """ -import os from dataclasses import dataclass, field # The wire versions live in meshbay_common/__init__.py — one source, because a @@ -75,7 +74,6 @@ class MNP: # plaintext form on the wire (`chatbox.py`, docs/MESHBAY_DESIGN.md §4.5); # `format` distinguishes a *stored* pre-2.0 row, which is still served. CHAT_MESSAGE = "chat_msg" # one chat message, sealed and signed - CHAT_ATTACHMENT = "chat_attach" # attachment metadata CHAT_HISTORY = "chat_hist" # request message history (newest, or before a cursor) CHAT_HISTORY_RESPONSE = "chat_hist_resp" # history response with messages # Link unfurl: the node fetches a URL a member pasted and returns an @@ -123,7 +121,6 @@ class MNP: STREAM_END = "stream_end" # node signals end of stream STREAM_MORE = "stream_more" # client → node: room for N more segments STREAM_STOP = "stream_stop" # client → node: nobody is watching any more - EPHEMERAL_STREAM = "ephemeral_stream" # reserved — mobile live push HANDSHAKE_CHALLENGE = "handshake_challenge" # node → client: GEK proof nonce HANDSHAKE_RESPONSE = "handshake_response" # client → node: HMAC(GEK, nonce) ADMIN_CHALLENGE = "admin_challenge" # node → client: Ed25519 sign challenge @@ -458,11 +455,6 @@ def file_chunk_plaintext( UPLOAD_ID_LEN = 16 # 128 bits of client-chosen correlation, hex on the wire -def new_upload_id() -> str: - """A fresh correlation id for one upload.""" - return os.urandom(UPLOAD_ID_LEN).hex() - - def file_upload_wire( gek: bytes, group_id: str, diff --git a/packages/meshbay-hub/src/meshbay_hub/api/groups.py b/packages/meshbay-hub/src/meshbay_hub/api/groups.py index 3a11345..52d9f60 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/groups.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/groups.py @@ -255,10 +255,6 @@ async def swarm_register( Availability: the endpoint is a port, not an address, and the number of hashes one account may claim is bounded. See the two constants above. """ - from meshbay_hub.csam import check_content_hash - if check_content_hash(body.content_hash): - raise HTTPException(status_code=451, detail="Content blocked") - m = _SWARM_ENDPOINT.match(body.endpoint or "") if not m or not (0 < int(m.group(2)) < 65536): raise HTTPException( diff --git a/packages/meshbay-hub/src/meshbay_hub/api/signaling.py b/packages/meshbay-hub/src/meshbay_hub/api/signaling.py index fc40204..6c9699b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/signaling.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/signaling.py @@ -171,7 +171,7 @@ async def webrtc_offer( Finding H4: it also ignored group status, so "suspend a group" did not stop new connections from being brokered to nodes hosting it. """ - from meshbay_hub.api.revocation import _connected_nodes, _node_groups + from meshbay_hub.api.revocation import _connected_nodes if len(body.sdp) > MAX_SDP_BYTES: raise HTTPException(status_code=413, detail="SDP too large") diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py index 7ccf598..cca1e6b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/app.py +++ b/packages/meshbay-hub/src/meshbay_hub/app.py @@ -41,7 +41,6 @@ from meshbay_hub.api.webapp import configure as webapp_configure from meshbay_hub.api.webapp import router as webapp_router from meshbay_hub.auth import generate_hub_keypair, load_hub_keypair from meshbay_hub.config import HubConfig -from meshbay_hub.csam import csam_router from meshbay_hub.db.engine import close_db, init_db @@ -129,9 +128,6 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI: if cfg.identity.admin_usernames: await _sync_admin_roles(cfg.identity.admin_usernames) - from meshbay_hub.csam import get_csam_checker - get_csam_checker().load() - from meshbay_hub.db.engine import get_session_factory from meshbay_hub.tasks.cleanup import cleanup_loop cleanup_task = asyncio.create_task(cleanup_loop(get_session_factory())) @@ -207,7 +203,6 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI: app.include_router(revocation_router) app.include_router(moderation_router) app.include_router(federation_router) - app.include_router(csam_router) app.include_router(health_router) app.include_router(relay_router) app.include_router(signaling_router) diff --git a/packages/meshbay-hub/src/meshbay_hub/csam.py b/packages/meshbay-hub/src/meshbay_hub/csam.py deleted file mode 100644 index b8e8d04..0000000 --- a/packages/meshbay-hub/src/meshbay_hub/csam.py +++ /dev/null @@ -1,158 +0,0 @@ -""" -MeshBay Hub — CSAM hash matching. - -Checks public content hashes against known CSAM (Child Sexual Abuse Material) -hash databases before allowing content to be registered or served publicly. - -Production integration: - - NCMEC (National Center for Missing & Exploited Children): PhotoDNA hash database - Access requires formal application: https://www.missingkids.org/gethelpnow/cybertipline - - IWF (Internet Watch Foundation): URL and hash list (UK-based) - Access via IWF membership: https://www.iwf.org.uk/our-technology/our-products/hash-list/ - -This module provides: - 1. A local CSAM hash database (SQLite file, populated from official sources) - 2. A check function used before content registration - 3. An admin endpoint to update the hash list - -IMPORTANT: Never log matched hashes or file contents. CSAM detection -must be reported to NCMEC (US law) or relevant authority immediately. -""" - -import logging -from pathlib import Path - -from fastapi import APIRouter, Depends, HTTPException - -from meshbay_hub.api.deps import require_admin -from meshbay_hub.db.models import User - -log = logging.getLogger(__name__) - -# Default path for the CSAM hash database (blake3 hex hashes, one per line) -DEFAULT_CSAM_DB_PATH = Path("/var/lib/meshbay/hub/csam_hashes.txt") - - -class CSAMChecker: - """ - Checks content hashes against a known CSAM hash database. - - Usage: - checker = CSAMChecker() - checker.load() - if checker.is_known_csam(blake3_hex): - # refuse to serve, report to authority - pass - """ - - def __init__(self, db_path: Path = DEFAULT_CSAM_DB_PATH): - self._db_path = db_path - self._hashes: set[str] = set() - self._loaded = False - - def load(self, db_path: Path | None = None) -> int: - """ - Load CSAM hashes from the hash database file. - Returns the number of hashes loaded. - - File format: one blake3 hex hash per line (64 chars), comments with #. - """ - path = db_path or self._db_path - if not path.exists(): - log.warning("CSAM hash database not found: %s. " - "Contact NCMEC (US) or IWF (EU) for access.", path) - self._loaded = True - return 0 - - count = 0 - with open(path) as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and len(line) == 64: - self._hashes.add(line.lower()) - count += 1 - - self._loaded = True - log.info("CSAM hash database loaded: %d hashes from %s", count, path) - return count - - def is_known_csam(self, content_hash_hex: str) -> bool: - """ - Return True if the hash matches a known CSAM hash. - NEVER logs the hash or any file information. - """ - if not self._loaded: - self.load() - return content_hash_hex.lower() in self._hashes - - @property - def hash_count(self) -> int: - return len(self._hashes) - - def add_hash(self, hash_hex: str) -> None: - """Add a hash to the in-memory set (and optionally persist).""" - self._hashes.add(hash_hex.lower()) - - def update_from_file(self, new_db_path: Path) -> int: - """Hot-reload from a new hash database file.""" - old_count = len(self._hashes) - self._hashes.clear() - count = self.load(new_db_path) - log.info("CSAM database updated: %d → %d hashes", old_count, count) - return count - - -# Module-level singleton (initialised in hub lifespan) -_checker = CSAMChecker() - - -def get_csam_checker() -> CSAMChecker: - return _checker - - -def check_content_hash(blake3_hex: str) -> bool: - """ - Check a content hash against the CSAM database. - Returns True if the content is KNOWN CSAM — block immediately. - - Callers MUST: - 1. Refuse to serve the content - 2. Log the event (without the hash) for legal audit purposes - 3. Report to NCMEC CyberTipline if operating in the US: - https://www.missingkids.org/gethelpnow/cybertipline - """ - return _checker.is_known_csam(blake3_hex) - - -# ── Hub API integration ─────────────────────────────────────────────────────── - -csam_router = APIRouter(prefix="/v1/admin/csam", tags=["csam"]) - - -@csam_router.get("/status") -async def csam_status(current_user: User = Depends(require_admin)): - """Return CSAM checker status (hash count, database path).""" - return { - "hash_count": _checker.hash_count, - "db_path": str(_checker._db_path), - "loaded": _checker._loaded, - "note": "Contact NCMEC or IWF for hash database access.", - } - - -@csam_router.post("/check") -async def check_hash( - body: dict, - current_user: User = Depends(require_admin), -): - """ - Check a single hash. Admin use only. - Returns True/False WITHOUT logging the hash (legal requirement). - """ - hash_hex = body.get("hash", "") - if len(hash_hex) != 64: - raise HTTPException(status_code=422, detail="hash must be 64 hex chars") - matched = check_content_hash(hash_hex) - # Do NOT log whether a match was found — only log the check attempt - log.info("CSAM check performed by admin %s", current_user.username) - return {"matched": matched} diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 3101be7..bcd5999 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -3,7 +3,7 @@ import { clearPending, loadPending } from './invite-link.js'; import { html, render, useState, useEffect, useLayoutEffect, useCallback, useRef, - createContext, useContext, + createContext, } from './vendor/htm-preact.js'; import { t, getLocale, setLocale, initLocale, LOCALES } from './i18n.js'; import { ZipStream, entriesUnder } from './zipstream.js'; @@ -72,7 +72,6 @@ function useRoute() { // ── Context ────────────────────────────────────────────────────────────────── const AuthContext = createContext(null); -function useAuth() { return useContext(AuthContext); } // The M of the wordmark is a picture; the rest is text. Resolved from this // module's own URL so the hub's fingerprinted path and the application's @@ -645,10 +644,6 @@ function LazyCreateGroupPage(props) { return html`<${_CreateGroupPage} ...${props} />`; } -// ── Settings Page ─────────────────────────────────────────────────────────── - -const THEME_OPTIONS = ['light', 'dark', 'system']; - // ── Profile Page ──────────────────────────────────────────────────────────── // // ── Lazy-loaded Admin page (admin/moderator only) ───────────────────────── diff --git a/packages/meshbay-hub/src/meshbay_hub/static/crypto.js b/packages/meshbay-hub/src/meshbay_hub/static/crypto.js index a3680ce..b74732c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/crypto.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/crypto.js @@ -1,20 +1,12 @@ /** - * MeshBay Browser Crypto — AES-256-GCM private group decryption. - * Uses WebCrypto SubtleCrypto API (available in all modern browsers). - * - * Handles groups with cipher="aes-256-gcm" (browser-accessible groups). - * ChaCha20-Poly1305 groups (cipher="chacha20-poly1305") require the - * native client (node) for decryption — not supported in browser. + * MeshBay Browser Crypto — AES-256-GCM, through WebCrypto's SubtleCrypto API. + * The one content cipher, for every client (meshbay_common/webcrypto.py). * * Usage: * const gek = await importGEK(gekB64); * const plaintext = await decryptChunkBin(gek, fileHashHex, chunkIndex, nonce, ct); */ -const CIPHER_INFO_PREFIX = new TextEncoder().encode('file:'); -const CIPHER_INFO_SUFFIX_AES = new TextEncoder().encode(':aes'); - - // ── Key derivation ──────────────────────────────────────────────────────────── /** @@ -284,38 +276,7 @@ async function verifyChatSignature(deviceRaw, groupId, epoch, nonce, ct, sig) { } -// ── GEK generation + ECIES wrapping ────────────────────────────────────────── - -function generateGEK() { - return crypto.getRandomValues(new Uint8Array(32)); -} - -async function wrapGEK(gek, pkXRaw) { - const skEph = await crypto.subtle.generateKey({ name: 'X25519' }, true, ['deriveBits']); - const pkEphRaw = new Uint8Array(await crypto.subtle.exportKey('raw', skEph.publicKey)); - - const pkRecip = await crypto.subtle.importKey('raw', pkXRaw, { name: 'X25519' }, false, []); - const sharedBits = await crypto.subtle.deriveBits( - { name: 'X25519', public: pkRecip }, skEph.privateKey, 256); - - const sharedKey = await crypto.subtle.importKey( - 'raw', sharedBits, 'HKDF', false, ['deriveKey']); - const wrapKey = await crypto.subtle.deriveKey( - { name: 'HKDF', hash: 'SHA-256', salt: pkEphRaw, - info: new TextEncoder().encode('meshbay:gek_wrap:v1:aes') }, - sharedKey, - { name: 'AES-GCM', length: 256 }, false, ['encrypt']); - - const nonce = crypto.getRandomValues(new Uint8Array(12)); - const ct = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: nonce, additionalData: pkXRaw }, wrapKey, gek); - - return { - pk_eph_b64: btoa(String.fromCharCode(...pkEphRaw)), - nonce_b64: btoa(String.fromCharCode(...nonce)), - wrapped_b64: btoa(String.fromCharCode(...new Uint8Array(ct))), - }; -} +// ── GEK unwrapping (ECIES) ───────────────────────────────────────────────────── async function unwrapGEK(bundle, skXPkcs8, pkXRaw) { const pkEphRaw = b64decode(bundle.pk_eph_b64); @@ -342,16 +303,6 @@ async function unwrapGEK(bundle, skXPkcs8, pkXRaw) { return new Uint8Array(plain); } -// ── Chunk encryption (for upload) ──────────────────────────────────────────── - -async function encryptChunk(gek, fileHashHex, chunkIndex, plaintext) { - const chunkKey = await deriveChunkKey(gek, fileHashHex, chunkIndex); - const nonce = crypto.getRandomValues(new Uint8Array(12)); - const ct = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: nonce }, chunkKey, plaintext); - return { nonce, ct: new Uint8Array(ct) }; -} - function b64encode(bytes) { return btoa(String.fromCharCode(...bytes)); } @@ -583,7 +534,7 @@ async function verifyNodeSignature(nodePkB64, sigB64, transcript) { window.MeshBayCrypto = { importGEK, deriveChunkKey, decryptChunkBin, openGroup, sealGroup, - generateGEK, wrapGEK, unwrapGEK, encryptChunk, b64encode, b64decode, + unwrapGEK, b64encode, b64decode, adminTranscript, handshakeTranscript, handshakeProof, webrtcBinding, challengeTranscript, joinTranscript, verifyNodeSignature, constantTimeEqual, deviceRequestTranscript, deviceAddTranscript, deviceHelloTranscript, diff --git a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js index 9c23d3c..50bca46 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js @@ -193,13 +193,6 @@ export async function openTarget(filename) { }; } -/** - * Below this, a download with no granted folder and no service worker is - * collected in memory and handed to the browser. Above it that would mean - * holding gigabytes in a tab, so it is worth one Save As dialog instead. - */ -export const BLOB_LIMIT = 512 * 1024 * 1024; - // ── Streaming to disk without the File System Access API ──────────────────── const SW_PATH = '/sw.js'; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js index ac978e2..775e2e3 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js @@ -893,7 +893,6 @@ function FilesPanel({ // ── File Preview (text, images) ───────────────────────────────────────── -const TEXT_EXTS = /\.(txt|md|json|csv|log|xml|yaml|yml|ini|conf|py|js|html|css|sh|c|h|java|rs|go|rb|toml)$/i; const IMAGE_EXTS = /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)$/i; function FilePreview({ entry, transportRef, gekRef, onClose, onDownload }) { diff --git a/packages/meshbay-hub/src/meshbay_hub/static/i18n.js b/packages/meshbay-hub/src/meshbay_hub/static/i18n.js index 56d35f7..bc78266 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/i18n.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/i18n.js @@ -131,10 +131,6 @@ export function setLocale(code) { return true; } -export function addLocale(code, strings) { - _strings[code] = strings; -} - function _pluralRules(locale) { if (!_plurals[locale]) _plurals[locale] = new Intl.PluralRules(locale); return _plurals[locale]; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js index 879f56f..33b1cf2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js @@ -256,15 +256,9 @@ async function deriveRecoveryKey(R, username) { // ── Bundle encryption ───────────────────────────────────────────────────────── /** - * Encrypt the keypair bundle with the password-derived AES key. - * Bundle format: JSON { skEd: base64(pkcs8), skX: base64(pkcs8) } + * Encrypt the keypair bundle with a bundle key derived at sign-in. Always + * writes v2. Bundle format: JSON { skEd: base64(pkcs8), skX: base64(pkcs8) } */ -async function encryptBundle(skEdRaw, skXRaw, password, username) { - const aesKey = await deriveEncryptionKey(password, username); - return encryptBundleWithKey(skEdRaw, skXRaw, aesKey); -} - -/** Same, when the key was already derived at sign-in. Always writes v2. */ async function encryptBundleWithKey(skEdRaw, skXRaw, aesKey) { const nonce = crypto.getRandomValues(new Uint8Array(12)); const data = new TextEncoder().encode(JSON.stringify({ @@ -289,16 +283,6 @@ function bundleVersion(bundleB64) { } catch { return 1; } } -/** - * Decrypt a keypair bundle. Throws if password is wrong. - */ -async function decryptBundle(bundleB64, password, username) { - const key = bundleVersion(bundleB64) === 2 - ? await deriveEncryptionKey(password, username) - : await deriveEncryptionKeyV1(password, username); - return decryptBundleWithKey(bundleB64, key); -} - // ── Registration ────────────────────────────────────────────────────────────── /** diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transfers.js b/packages/meshbay-hub/src/meshbay_hub/static/transfers.js index 9a1455a..bde382f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transfers.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transfers.js @@ -29,13 +29,6 @@ function _live(status) { || status === 'paused'; } -/** Raised by `run` when it stopped because the transfer was paused. */ -function _pausedError() { - const err = new Error('Paused'); - err.name = 'PausedError'; - return err; -} - function _abortError() { const err = new Error('Cancelled'); err.name = 'AbortError'; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport-chat.js b/packages/meshbay-hub/src/meshbay_hub/static/transport-chat.js index d6d733f..270c76e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport-chat.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport-chat.js @@ -212,7 +212,6 @@ extendTransport(class { if (msg.epoch) this.chatEpoch = msg.epoch; this._chatKeys = null; this._chatKeysInFlight = null; - if (this._onChatEpoch) this._onChatEpoch(this.chatEpoch); } /** diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 97b2293..0f3f3b8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -571,9 +571,6 @@ class MeshBayTransport { set onIndexDelta(fn) { this._onIndexDelta = fn; } set onRootsChanged(fn) { this._onRootsChanged = fn; } - /** The MNP version the connected node declared, or '' before a handshake. */ - get nodeVersion() { return this._nodeVersion || ''; } - /** This member's own caps in this group, or null when the node said nothing. */ get transferLimits() { return this._transferLimits; } @@ -582,7 +579,6 @@ class MeshBayTransport { set onChatDirectory(fn) { this._onChatDirectory = fn; } set onChatLinkPreview(fn) { this._onChatLinkPreview = fn; } set onSearchListed(fn) { this._onSearchListed = fn; } - set onChatEpoch(fn) { this._onChatEpoch = fn; } set onTmdbConfig(fn) { this._onTmdbConfig = fn; } set onTmdbEnabled(fn) { this._onTmdbEnabled = fn; } set onMusicbrainzEnabled(fn) { this._onMusicbrainzEnabled = fn; } @@ -941,12 +937,9 @@ class MeshBayTransport { if (reply.type === 'handshake_challenge') { // The node's half of the range. Checked before anything else in this // block, because everything below — the join, the proof, the sealed ack - // — assumes both sides mean the same thing by each message. + // — assumes both sides mean the same thing by each message. Nothing else + // reads the node's version: a peer this admits speaks every message here. _checkNodeVersion(reply); - // Kept for diagnostics only. Nothing branches on it: the range check - // above is what decides whether these two can talk at all, and a peer it - // admits speaks every message in this file. - this._nodeVersion = String(reply.v || ''); if (!window.MeshBayCrypto) { throw new Error('Node requires GEK proof but no crypto available'); } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js index 2b274b5..fa10d15 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js @@ -270,12 +270,6 @@ function reconnectPlan(playhead, range, ended, castActive) { return { mode: 'seek', at: playhead }; } -function _mseSupported(codec) { - if (!window.MediaSource) return false; - const mime = `video/mp4; codecs="${codec}"`; - return MediaSource.isTypeSupported(mime); -} - /** Seconds as h:mm:ss, or m:ss under an hour. */ function formatClock(seconds) { const s = Math.max(0, Math.floor(seconds || 0)); diff --git a/packages/meshbay-hub/tests/test_memory_ceiling.py b/packages/meshbay-hub/tests/test_memory_ceiling.py index 9ea6ad3..48aac1a 100644 --- a/packages/meshbay-hub/tests/test_memory_ceiling.py +++ b/packages/meshbay-hub/tests/test_memory_ceiling.py @@ -84,7 +84,6 @@ const platform = {{ bridgeMessage: (e) => String(e), }}; const downloads = {{ - BLOB_LIMIT: 512 * 1024 * 1024, // Called by the refusal to name why the streamed path declined -- absent // from this stub, the error constructor threw TypeError and the test saw the // wrong failure entirely. diff --git a/packages/meshbay-node/src/meshbay_node/audit.py b/packages/meshbay-node/src/meshbay_node/audit.py index b382107..81ee600 100644 --- a/packages/meshbay-node/src/meshbay_node/audit.py +++ b/packages/meshbay-node/src/meshbay_node/audit.py @@ -33,20 +33,6 @@ CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id); CREATE INDEX IF NOT EXISTS idx_audit_event ON audit_log(event); """ -EVENTS = { - "connect", - "disconnect", - "handshake", - "file_download", - "file_upload", - "file_delete", - "stream_video", - "chat_message", - "chat_history", - "index_sync", - "auth_failed", -} - RETENTION_DAYS = 365 diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py index 563e395..2cd8a8f 100644 --- a/packages/meshbay-node/src/meshbay_node/config.py +++ b/packages/meshbay-node/src/meshbay_node/config.py @@ -505,10 +505,3 @@ def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config: "MESHBAY_MAX_CONCURRENT_STREAMS") return cfg - - -def write_example_config(path: Path = DEFAULT_CONFIG_PATH) -> None: - """Write an example config file if none exists.""" - if not path.exists(): - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(EXAMPLE_CONFIG, encoding="utf-8", newline="\n") diff --git a/packages/meshbay-node/src/meshbay_node/modules/__init__.py b/packages/meshbay-node/src/meshbay_node/modules/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/packages/meshbay-node/src/meshbay_node/modules/__init__.py +++ /dev/null diff --git a/packages/meshbay-node/src/meshbay_node/musicbrainz.py b/packages/meshbay-node/src/meshbay_node/musicbrainz.py index ce2d233..e3681ea 100644 --- a/packages/meshbay-node/src/meshbay_node/musicbrainz.py +++ b/packages/meshbay-node/src/meshbay_node/musicbrainz.py @@ -183,10 +183,6 @@ class MusicBrainzClient: results = (data or {}).get("releases", []) return _best_match_release(artist, album, results) - async def release_details(self, mbid: str) -> dict | None: - """Full release details, including recordings (tracklist).""" - return await self._get(_BASE_URL + f"release/{mbid}", {"inc": "recordings+artist-credits"}) - async def fetch_cover_art(self, mbid: str) -> bytes | None: """ The release's front cover, or None if Cover Art Archive has nothing diff --git a/packages/meshbay-node/src/meshbay_node/replication.py b/packages/meshbay-node/src/meshbay_node/replication.py deleted file mode 100644 index 297ed0b..0000000 --- a/packages/meshbay-node/src/meshbay_node/replication.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -MeshBay Node — content replication (node-to-node, admin-authorized). - -A replication node downloads files from a source node and stores them -locally, then registers itself as an additional swarm source in the hub. -This provides redundancy and improves availability for public content. - -Only public content is replicated (no GEK needed). -Private content replication requires the GEK and is admin-controlled. - -Usage: - replicator = ContentReplicator( - hub_url=..., access_token=..., source_endpoint=..., - local_dir=Path("/data/replicated"), node_pk_b64=..., - ) - await replicator.replicate_file(file_id, file_name, file_size) -""" - -import logging -from pathlib import Path - -import blake3 -import httpx - -log = logging.getLogger(__name__) - -CHUNK_SIZE = 1024 * 1024 # 1 MB - - -class ContentReplicator: - """ - Downloads public files from a source node and registers as swarm source. - """ - - def __init__( - self, - hub_url: str, - access_token: str, - source_endpoint: str, # "http://ip:port" of source node HTTP API - local_dir: Path, - node_pk_b64: str, - ): - self._hub_url = hub_url.rstrip("/") - self._access_token = access_token - self._source_endpoint = source_endpoint.rstrip("/") - self._local_dir = local_dir - self._node_pk_b64 = node_pk_b64 - self._local_dir.mkdir(parents=True, exist_ok=True) - - @property - def _auth_headers(self) -> dict: - return {"Authorization": f"Bearer {self._access_token}"} - - async def fetch_index(self) -> list[dict]: - """Fetch the public Mesh Group Index from the source node.""" - async with httpx.AsyncClient(timeout=30) as c: - r = await c.get(f"{self._source_endpoint}/index") - r.raise_for_status() - return r.json()["entries"] - - async def replicate_file( - self, - file_id: str, - file_name: str, - file_size: int, - progress_cb=None, - ) -> Path: - """ - Download a public file from the source node, verify integrity, - save locally, and register as swarm source in the hub. - Returns the local file path. - """ - local_path = self._local_dir / file_name - if local_path.exists(): - # Verify hash - existing_hash = blake3.blake3(local_path.read_bytes()).hexdigest() - if existing_hash == file_id: - log.info("Already have %s, skipping", file_name) - await self._register_swarm(file_id, local_path) - return local_path - - log.info("Replicating %s (%d bytes) from %s", file_name, file_size, self._source_endpoint) - - # Stream download chunk by chunk - n_chunks = max(1, (file_size + CHUNK_SIZE - 1) // CHUNK_SIZE) - with open(local_path, "wb") as f: - async with httpx.AsyncClient(timeout=60) as c: - for chunk_idx in range(n_chunks): - # Download full file (simpler for public content) - if chunk_idx == 0: - r = await c.get( - f"{self._source_endpoint}/file/{file_id}", - headers=self._auth_headers, - ) - r.raise_for_status() - f.write(r.content) - if progress_cb: - progress_cb(len(r.content), file_size) - break # full file downloaded in one request - - # Verify hash - actual_hash = blake3.blake3(local_path.read_bytes()).hexdigest() - if actual_hash != file_id: - local_path.unlink(missing_ok=True) - raise ValueError(f"Hash mismatch: expected {file_id[:16]}, got {actual_hash[:16]}") - - log.info("Replicated %s (hash OK)", file_name) - await self._register_swarm(file_id, local_path) - return local_path - - async def _register_swarm(self, content_hash: str, local_path: Path) -> None: - """Register this node as a swarm source for the content hash in the hub.""" - try: - async with httpx.AsyncClient(timeout=10) as c: - r = await c.post( - f"{self._hub_url}/v1/swarm/register", - json={"content_hash": content_hash, "endpoint": self._source_endpoint}, - headers=self._auth_headers, - ) - r.raise_for_status() - log.debug("Registered as swarm source for %s", content_hash[:16]) - except Exception as e: - log.warning("Failed to register swarm source: %s", e) - - async def replicate_all(self, progress_cb=None) -> list[Path]: - """Replicate all public files from the source node.""" - entries = await self.fetch_index() - results = [] - for entry in entries: - try: - path = await self.replicate_file( - file_id=entry["id"], - file_name=entry["name"], - file_size=entry["size"], - progress_cb=progress_cb, - ) - results.append(path) - except Exception as e: - log.error("Failed to replicate %s: %s", entry["name"], e) - return results diff --git a/packages/meshbay-node/tests/golden/dispatch.json b/packages/meshbay-node/tests/golden/dispatch.json index 5d15057..4767996 100644 --- a/packages/meshbay-node/tests/golden/dispatch.json +++ b/packages/meshbay-node/tests/golden/dispatch.json @@ -1874,166 +1874,6 @@ "sent": [], "spawned": [] }, - "chat_attach | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "chat_attach | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "chat_attach | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "chat_directory | challenged | bare": { "audit": [], "log": [], @@ -7229,166 +7069,6 @@ "sent": [], "spawned": [] }, - "ephemeral_stream | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "ephemeral_stream | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "ephemeral_stream | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "file_chunk | challenged | bare": { "audit": [], "log": [], |