diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-11 04:13:53 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-11 04:13:53 +0200 |
| commit | e23e33adeaf8ee7439187d4451c856b37816a51f (patch) | |
| tree | a41eef1fba34cdd642d25576395b4ad484a748ad /packages/meshbay-hub/src/meshbay_hub/static/transport.js | |
| parent | 60c4570e72e36c2a9720593c8baec74ee2ab52d6 (diff) | |
| download | meshbay-e23e33adeaf8ee7439187d4451c856b37816a51f.tar.gz | |
feat: Phase 9 — Web client SPA with WebRTC P2P transport
Complete browser-based client: Preact SPA with login, group file browser,
encrypted download, video playback, group chat, i18n, and dark/light theme.
Browser connects P2P to nodes behind residential NAT via WebRTC DataChannel
(aiortc). Hub handles signaling only — all data flows E2E.
Performance: pipelined downloads (8-chunk sliding window), binary msgpack
wire format (no base64), redundant I/O elimination. Large file downloads
stream to disk via File System Access API (showSaveFilePicker).
Validated on SFR + Orange residential NATs, Chrome + Firefox, IPv4/IPv6.
132 tests passing. Deployed to meshbay.org + Orange node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 47 |
1 files changed, 41 insertions, 6 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 9112734..de93b2d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -41,21 +41,36 @@ class MeshBayTransport { this._channel = this._pc.createDataChannel('mnp', { ordered: true }); this._channel.binaryType = 'arraybuffer'; + let channelReject = null; const channelReady = new Promise((resolve, reject) => { + channelReject = reject; const timeout = setTimeout(() => reject(new Error('DataChannel open timeout')), 30000); this._channel.onopen = () => { clearTimeout(timeout); this._connected = true; resolve(); }; - this._channel.onerror = (e) => { - clearTimeout(timeout); - reject(new Error('DataChannel error: ' + e.message)); - }; }); this._channel.onmessage = (event) => this._onMessage(event.data); - this._channel.onclose = () => { this._connected = false; }; + this._channel.onclose = (ev) => { + console.warn('[MeshBay] DataChannel closed', this._channel?.readyState, ev); + this._connected = false; + if (channelReject) channelReject(new Error('DataChannel closed')); + for (const [, p] of this._pending) p.reject(new Error('DataChannel closed')); + this._pending.clear(); + }; + this._channel.onerror = (ev) => { + console.error('[MeshBay] DataChannel error', ev); + if (channelReject) channelReject(new Error('DataChannel error')); + }; + + this._pc.onconnectionstatechange = () => { + console.log('[MeshBay] PC state:', this._pc.connectionState); + }; + this._pc.oniceconnectionstatechange = () => { + console.log('[MeshBay] ICE state:', this._pc.iceConnectionState); + }; const offer = await this._pc.createOffer(); await this._pc.setLocalDescription(offer); @@ -106,7 +121,13 @@ class MeshBayTransport { async fetchIndex() { const msg = await this._sendAndWait({ type: 'index_sync', v: '0.1' }); if (msg.type === 'error') throw new Error(msg.detail); - return _b64decode(msg.index_b64); + return msg; + } + + async fetchGEK() { + const msg = await this._sendAndWait({ type: 'gek_req', v: '0.1' }); + if (msg.type === 'error') throw new Error(msg.detail); + return msg.gek_b64; } async fetchChunk(fileId, chunkIndex) { @@ -132,6 +153,17 @@ class MeshBayTransport { return _b64decode(msg.data_b64); } + async fetchChatHistory(since, limit) { + const msg = await this._sendAndWait({ + type: 'chat_hist', + v: '0.1', + since: since || 0, + limit: limit || 100, + }); + if (msg.type === 'error') throw new Error(msg.detail); + return msg.messages || []; + } + async sendChat(payload, iteration, threadId) { const msg = await this._sendAndWait({ type: 'chat_msg', @@ -169,6 +201,9 @@ class MeshBayTransport { } _send(obj) { + if (!this._channel || this._channel.readyState !== 'open') { + throw new Error(`DataChannel not open (state: ${this._channel?.readyState})`); + } const encoded = msgpack_encode(obj); const header = new Uint8Array(4); new DataView(header.buffer).setUint32(0, encoded.byteLength, false); |