summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-11 14:55:52 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-11 14:55:52 +0200
commitcc90dc943fcd0e7bbf52674fb3f95ff097026f4a (patch)
tree6315a5e966c027c50b92d59e60c4b506a418143e /packages/meshbay-node/src/meshbay_node/transport
parentedde9e441fb6b84e9d56215d6e2a8d9338b8f962 (diff)
downloadmeshbay-cc90dc943fcd0e7bbf52674fb3f95ff097026f4a.tar.gz
feat: Phase 10b — Self-service UI (group create/join, upload, IndexedDB, search)
Six self-service features for the web SPA: - Group creation UI with GEK auto-generation (AES-256-GCM ECIES) - Member management + invite by username (GEK wrapping for invitee) - Open group self-join flow (POST /v1/groups/{id}/join) - File upload client→node (FILE_UPLOAD MNP type, .uploads/ staging) - IndexedDB caching of group file indexes (instant display on revisit) - Cross-group file search (SearchPage, pure client-side on cached indexes) 11 new tests (166 total): 8 group self-service + 3 AES GEK wrap/unwrap. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py44
1 files changed, 44 insertions, 0 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 7da6623..94d9b04 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -114,6 +114,8 @@ class WebRTCPeerSession:
self._do_chat_message(msg)
elif mtype == MNP.CHAT_HISTORY:
self._do_chat_history(msg)
+ elif mtype == MNP.FILE_UPLOAD:
+ self._do_file_upload(msg)
else:
log.warning("Unknown MNP message type on DataChannel: %s", mtype)
except Exception as e:
@@ -323,6 +325,48 @@ class WebRTCPeerSession:
],
})
+ def _do_file_upload(self, msg: dict) -> None:
+ ctx = self._group_ctx()
+ filename = msg.get("filename", "")
+ chunk_index = msg.get("chunk_index", 0)
+ total_chunks = msg.get("total_chunks", 1)
+ data = msg.get("data")
+
+ if not filename or data is None:
+ self._send({"type": "error", "detail": "Missing filename or data"})
+ return
+
+ shared_root = ctx.get("shared_root")
+ if not shared_root:
+ self._send({"type": "error", "detail": "No shared directory"})
+ return
+
+ upload_dir = shared_root / ".uploads"
+ upload_dir.mkdir(exist_ok=True)
+ safe_name = filename.replace("/", "_").replace("\\", "_").replace("..", "_")
+ tmp_path = upload_dir / f"{safe_name}.part"
+
+ if isinstance(data, str):
+ chunk_bytes = base64.b64decode(data)
+ else:
+ chunk_bytes = bytes(data)
+
+ mode = "ab" if chunk_index > 0 else "wb"
+ with open(tmp_path, mode) as f:
+ f.write(chunk_bytes)
+
+ self._send({
+ "type": MNP.FILE_UPLOAD_ACK,
+ "v": MNP_VERSION,
+ "chunk_index": chunk_index,
+ "filename": filename,
+ })
+
+ if chunk_index + 1 >= total_chunks:
+ final_path = shared_root / safe_name
+ tmp_path.rename(final_path)
+ log.info("Upload complete: %s (%d chunks)", safe_name, total_chunks)
+
def _send(self, obj: dict) -> None:
if self._channel and self._channel.readyState == "open":
self._channel.send(_pack(obj))