From 4b3e8c3b8b9d10c8ac333dd8db614a7569052472 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 10 Aug 2026 03:07:56 +0200 Subject: feat: Phase 7 — Node v2 (multi-group, Sender Keys, 0-RTT, chat, denylist) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements all 8 milestones (7.0-7.7): - 7.0: JWT carries `groups` claim; node verifies group membership at MNP handshake (QUIC + TCP+TLS). Resolves security review C2. - 7.1: QUIC 0-RTT session resumption via stored session tickets (17-21ms reconnect vs 47ms cold). - 7.2: Hub→node WebSocket signaling for NAT punch coordination (`client_incoming`/`punch_ready`) + jti denylist push. Denylist class blocks revoked users/jtis at handshake. - 7.3: Multi-group daemon — one QUIC port serves N groups with per-group GEK, shared_root, and index routing. - 7.4: HLS streaming via QUIC (STREAM_SEGMENT message type, ffmpeg segment extraction). - 7.5: Sender Keys protocol for group chat (Signal Groups approach). Each member has own sending chain key, HKDF chain ratchet, AES-256-GCM encryption, Ed25519 signing. Resolves security review C1. - 7.6: Chat store (SQLite via aiosqlite), CHAT_MESSAGE MNP wire type with peer broadcast, web UI with WebSocket push. - 7.7: Argon2id calibration CLI. First security review included (first-review.md). 109 tests, demo-v3 validated against meshbay.org production hub. Co-Authored-By: Claude Opus 4.6 --- packages/meshbay-node/src/meshbay_node/ui/app.py | 95 +++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-node/src/meshbay_node/ui') diff --git a/packages/meshbay-node/src/meshbay_node/ui/app.py b/packages/meshbay-node/src/meshbay_node/ui/app.py index f8978d1..a63e28f 100644 --- a/packages/meshbay-node/src/meshbay_node/ui/app.py +++ b/packages/meshbay-node/src/meshbay_node/ui/app.py @@ -10,15 +10,18 @@ Minimal FastAPI app providing: Served only on 127.0.0.1 — not exposed to the network. """ +import asyncio +import json import logging from typing import TYPE_CHECKING -from fastapi import FastAPI +from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import HTMLResponse from meshbay_node import __version__ if TYPE_CHECKING: + from meshbay_node.chat.store import ChatStore from meshbay_node.indexer import GroupIndex log = logging.getLogger(__name__) @@ -127,7 +130,95 @@ def create_ui_app(state: dict) -> FastAPI: {files_html}
MeshBay Node v{__version__} — JSON status - — JSON files + — JSON files — Chat + +""" + + # ── Chat endpoints ─────────────────────────────────────────────────────── + + _chat_subscribers: list[WebSocket] = [] + + @app.get("/api/chat/history") + async def chat_history(since: float = 0, limit: int = 100): + chat_store = state.get("chat_store") + if not chat_store: + return {"messages": []} + msgs = await chat_store.get_messages(since=since, limit=limit) + return { + "messages": [ + { + "id": m.id, + "sender_id": m.sender_id, + "iteration": m.iteration, + "timestamp": m.timestamp, + "thread_id": m.thread_id, + } + for m in msgs + ] + } + + @app.websocket("/ws/chat") + async def chat_websocket(ws: WebSocket): + """WebSocket for real-time chat push to the local UI.""" + await ws.accept() + _chat_subscribers.append(ws) + try: + while True: + await ws.receive_text() + except WebSocketDisconnect: + pass + finally: + _chat_subscribers.remove(ws) + + async def broadcast_chat_to_ui(msg: dict) -> None: + """Push a chat message to all connected UI WebSocket clients.""" + payload = json.dumps(msg) + dead = [] + for ws in _chat_subscribers: + try: + await ws.send_text(payload) + except Exception: + dead.append(ws) + for ws in dead: + _chat_subscribers.remove(ws) + + app.broadcast_chat = broadcast_chat_to_ui + + @app.get("/chat", response_class=HTMLResponse) + async def chat_page(): + return f""" + + + + MeshBay Chat + + + +

MeshBay Chat

+
+

Back to status

+ """ -- cgit v1.2.3