aboutsummaryrefslogtreecommitdiffstats
path: root/packages
Commit message (Collapse)AuthorAgeFilesLines
* test(hub): read the search code wherever it is splitChristophe Besson11 hours8-9/+50
| | | | | | | | The Node harnesses take one file or several, and the tests that lift connectToGroup and the pool out of search-page.js as text take them from spa_source, which also reads connection-pool.js once it exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): split ops.py into the ops packageChristophe Besson20 hours16-2060/+2285
| | | | | | | | | Each section of ops.py becomes a module of meshbay_node/ops/ (core, node_toml, members, chat, groups, roots, files, settings, apps), cut as text; ops/__init__.py keeps the docstring and re-exports every name, so `ops.<name>` is unchanged for every caller. Logger name unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): read ops wherever it is splitChristophe Besson20 hours4-10/+44
| | | | | | | | The tests that read ops.py for an absence (fastapi, a TOML path without as_posix, the reference app's name) and the one counting operations by module now take every file of ops.py or the ops package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move per-app enrichment out of NodeDaemonChristophe Besson27 hours7-296/+307
| | | | | | | | The _enrich_*/_reenrich_* methods and _on_enriched become EnrichmentMixin in meshbay_node/enrichment.py, with the two directory helpers only they use. APP_DIR_KEYS stays on NodeDaemon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the CLI out of daemon.py into cli/Christophe Besson27 hours17-1427/+1600
| | | | | | | | | | Each `if args.command == ...` branch of main() becomes a function in cli/ (one module per family of verbs); the parser, the process setup and a VERBS table go to cli/parser.py and cli/dispatch.py. daemon.main runs the verb or starts the daemon. Tests patch the CLI through conftest.patch_cli; the CLI golden is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): record what every CLI verb printsChristophe Besson28 hours2-0/+1001
| | | | | | | | A golden master of `meshbay-node`: help text, every verb, the prompts answered "no", each verb's usage fallback. Exit code, output, loopback calls with their bodies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): keep the CLI dispatch test off the developer's nodeChristophe Besson28 hours1-0/+8
| | | | | | | | The stub left HOME and config.DEFAULT_CONFIG_PATH real: every run wrote TEST-CODE over ~/.local/share/meshbay/invite-code and pair-code, and `status` read the real keystore and asked the running node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): read the daemon's source wherever it is splitChristophe Besson28 hours9-59/+138
| | | | | | | | Tests that read daemon.py now take their text from node_source (daemon.py, NodeDaemon's bases, the cli package once it exists). The CLI tests that sliced main() run the CLI instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): Peers tab answered 500 after the webrtc splitChristophe Besson28 hours3-1/+70
| | | | | | | | /api/peers imported _get_remote_ip, inside the function, from webrtc_server, which no longer has it. A test now resolves every meshbay import in the node's source, function bodies included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): run signed operations through a table of executorsChristophe Besson29 hours1-85/+37
| | | | | | | | _do_admin_response looked each operation up in a 28-branch elif, every branch the same call. The admin cases of the dispatch golden are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): dispatch MNP messages through a tableChristophe Besson29 hours3-196/+166
| | | | | | | | | | The pre-authentication guards stay explicit code, in the same order and text. After the handshake, a table maps each type to its handler and to whether it runs as a task, the choice each branch made; the three inline blocks become StreamingMixin methods, unchanged. The dispatch golden is identical, including types that are not strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): the dispatch golden covers a type field that is not a stringChristophe Besson29 hours2-0/+258
| | | | | | | | Missing, None, an int, a list, a dict, bytes: recorded on the elif chain before it becomes a table, since a dict lookup would raise on the unhashable ones where the chain found no match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move _dispatch_message out of webrtc_server, unchangedChristophe Besson29 hours3-244/+254
| | | | | | | | DispatchMixin in transport/webrtc/dispatch.py, with the pre-proof fetch bound it enforces. The elif chain moves as it is; turning it into a table is the next commit, on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the session core out of webrtc_serverChristophe Besson29 hours3-322/+342
| | | | | | | | SessionCore in transport/webrtc/core.py, last among the bases: state, the data channel, task ownership, the peer registry, sending and teardown. The facade keeps _dispatch_message and WebRTCTransport. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move operator authority and the admin response out of ↵Christophe Besson29 hours3-309/+320
| | | | | | | | | webrtc_server AdminMixin in transport/webrtc/admin.py: who the operator is, the signed challenge, signature checks, and _do_admin_response moved unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the operator's group controls out of webrtc_serverChristophe Besson29 hours4-369/+384
| | | | | | | | GroupOpsMixin in transport/webrtc/group_ops.py: member revocation and unpinning, the group key rotation, apps and their directories with the allow-list, and Search listing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the operator's node controls out of webrtc_serverChristophe Besson29 hours2-563/+588
| | | | | | | | NodeOpsMixin in transport/webrtc/node_ops.py: status and settings, roster and denylist, roots, hosted groups, reload, scan pacing and transfer limits, with their class-level bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): section headers follow the code they introduceChristophe Besson29 hours3-21/+16
| | | | | | | | Three headers stayed in webrtc_server when their sections moved; they now sit above the same methods in blobs.py and admission.py. Two that were only a title over what is now a whole module are dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the MNP handshake out of webrtc_serverChristophe Besson29 hours4-356/+374
| | | | | | | HandshakeMixin in transport/webrtc/handshake.py: challenge, proof, channel binding, and the sealed configuration a peer receives once admitted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(node): two stale comments in webrtc_serverChristophe Besson29 hours1-6/+2
| | | | | | | | The peer-session ceiling no longer quotes the hub's old per-account number, and the note on a single uploads/ directory goes: its constant left with f3fb449, when an upload started landing in the folder it was sent to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move invitations, joins and device linking out of webrtc_serverChristophe Besson29 hours2-847/+864
| | | | | | | AdmissionMixin in transport/webrtc/admission.py, with the invite handle pattern and the join attempt bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move uploads out of webrtc_serverChristophe Besson29 hours7-416/+422
| | | | | | | UploadMixin in transport/webrtc/upload_handlers.py, with the upload cap; _upload_chunk moved unchanged, _append_chunk joins webrtc/disk.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move transfer slots and leases out of webrtc_serverChristophe Besson29 hours3-262/+285
| | | | | | | TransferMixin in transport/webrtc/transfer_handlers.py, with the sweep interval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move file browsing and folder/file ops out of webrtc_serverChristophe Besson29 hours8-474/+503
| | | | | | | FilesMixin in transport/webrtc/files.py; the blocking disk helpers join _locate in webrtc/disk.py, and the lease states go to webrtc/limits.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move group chat out of webrtc_serverChristophe Besson29 hours4-654/+670
| | | | | | | | ChatMixin in transport/webrtc/chat.py: sealed messages, history, epoch keys, link previews with their cache and rate bounds, and the operator's chat ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move per-account blobs and key bundles out of webrtc_serverChristophe Besson29 hours3-244/+262
| | | | | | | BlobsMixin in transport/webrtc/blobs.py, with the blob caps and the kind pattern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move video streaming out of webrtc_serverChristophe Besson29 hours12-652/+663
| | | | | | | StreamingMixin in transport/webrtc/apps/streaming.py: stream credit, handover, the transcode slots and _stream_video_inner, moved unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the Videos catalogue handlers out of webrtc_serverChristophe Besson29 hours4-721/+744
| | | | | | | | VideoMetaMixin in transport/webrtc/apps/video_meta.py: media and season meta, TMDB search and its per-member bound, posters, and the operator's TMDB ops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the MusicBrainz switch to the Music mixinChristophe Besson29 hours3-39/+41
| | | | | | An operator op that exists for one app lives with that app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the Music handlers out of webrtc_serverChristophe Besson29 hours4-193/+213
| | | | | | | MusicMixin in transport/webrtc/apps/music.py: tags and cover art, and the audio transcode with the extension list that gates it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move the subtitle handler out of webrtc_serverChristophe Besson29 hours5-176/+199
| | | | | | | SubtitlesMixin in transport/webrtc/apps/subtitles.py; _locate, which it shares with the files, music and streaming handlers, in webrtc/disk.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): move framing, shared limits and ffmpeg jobs out of webrtc_serverChristophe Besson29 hours6-343/+383
| | | | | | | transport/webrtc/channel.py, limits.py and media_tools.py, cut from webrtc_server.py as text; the facade imports them back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): record what the session does with every message it can be sentChristophe Besson29 hours2-0/+24943
| | | | | | | | A characterisation test over every MNP type, peer state and message shape, and every signed operation's answer: the reply, the audit, the handler started. It pins dispatch order and the admin table so they can be moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): a patched module attribute must be read where it is patchedChristophe Besson29 hours1-0/+118
| | | | | | | A function reads its globals from the module that defines it, so patching a name a module only re-exports changes nothing and the test passes anyway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: read the WebRTC transport's source as a set of filesChristophe Besson29 hours17-125/+231
| | | | | | | | | Source-reading tests take their text from node_source (node) and node_tree (hub): webrtc_server.py plus anything under transport/webrtc/, so a check for something's absence keeps reading the code it guards if that code moves. test_node_source_scope holds the boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a connection that ends leaves its group's peer setChristophe Besson29 hours2-0/+70
| | | | | | | | | | connectionstatechange dropped a closed or failed session from the transport and stopped its tasks, but only close() took it out of the group's peer set, and nothing on that path called it. Every broadcast then went to closed channels, and each reconnect left a dead session held until restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): Search reaches each group with one offer, and a busy hub is not a ↵0.15Christophe Besson45 hours9-100/+776
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | dead node The other half of the 4G failure. The page negotiated every group twice: the sweep opened a connection, read the index and closed it, then the warm-up opened the same group again. The sweep, the warm-up and the tiles each had a concurrency ceiling of their own, and together they went past what the hub admits per account. Whatever the hub refused was then reported as "node unreachable" and remembered as down, which put that group last next time. - Every connection goes through ConnectionPool, which holds the page's one ceiling (six at once, sized for twenty groups on a phone) and keeps what the sweep opened for the tiles. A visit costs one offer per group. A refresh costs none for a connection that answers a four-second ping, and a connection that died while the phone slept is replaced, not waited on. - A connection whose index is being read is held against eviction. With more groups than the pool keeps, it was otherwise the least recently used one. - Negotiations still under way when the page closes close what they get, and a sweep cut short that way remembers nobody as down. - transport.js sends an offer again on 429, 502 or 503, honouring Retry-After, with jittered waits of about twenty seconds at worst. Search counts each retry as progress. A 404, 403 or 504 still fails at once, so a dead node costs no time. The fan-out tests assumed a ceiling of three and were re-measured: four dead groups of twelve now hold nothing back, even on a first visit. The pool and the retry run as shipped code, lifted as text, against a fake clock. Each guard was checked by removing it and seeing its test fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): offer ceilings that bound a member without failing an ordinary oneChristophe Besson45 hours3-5/+236
| | | | | | | | | | | | | | | | | | | | | | | | | | | A phone on 4G found one of five groups missing from Search on half its visits, and all but one on a fifth of them. Every offer that reached the node was answered in about a second; the hub refused the others with 429 — 93 of them in half an hour on meshbay.org, all from that phone, all to nodes that were up. Two ceilings did it. Three pending offers per account was exactly what one Search page dialled at once, so the first tile or reconnection beside the sweep was refused. Thirty offers a minute per address and per node was spent by four or five reloads of a page whose groups share one node. Sized now against an account with twenty groups on three devices: - 32 offers pending per account, across devices and nodes. - A budget per account and per node, a burst of 120 refilled at two a second. This is what bounds a member's cost to one machine (H6), and it leaves their other nodes alone. Counted by account, because a mobile carrier puts many subscribers behind one IPv4 address. - 600 a minute per address and per node, as a coarse guard in front of authentication only. Both refusals carry Retry-After, which the browser now honours. The node's own ceiling on peer sessions is unchanged; its comment no longer quotes the old per-account number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a redeemed invitation link leaves the owner's listChristophe Besson2 days13-28/+57
| | | | | | | | | | | | | | | | | | | | The list under "Invite by link" answered every ticket the group had ever minted, so a link that somebody had already used sat there saying "used by <name>" for the thirty days of KEEP_REDEEMED — beside the member row it had just produced, and above the links that still wait for somebody, which are the only ones there is anything to do about. The node's own `member list` had never shown them: it selects `used_at IS NULL`. The listing now selects `redeemed_by IS NULL`, and drops the `redeemed` status and the `redeemed_by` field with it. The row itself still lives for KEEP_REDEEMED, which is what lets a reload or a second tab of the invitation page be answered rather than refused; its comment says that now instead of naming a list it is no longer in. The SPA filters too, because the desktop client's copy of this interface can be newer than the hub it is signed into. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(client): let Copy buttons write the clipboard in the desktop appChristophe Besson2 days2-3/+18
| | | | | | | | Only fullscreen was granted, so navigator.clipboard.writeText was refused and every Copy button did nothing. Grant clipboard-sanitized-write; reading stays refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): an invitation no longer holds back its invitee's sign-up codeChristophe Besson2 days2-12/+66
| | | | | | | | | The per-recipient cooldown was shared by every purpose, so the code a person asked for by registering within two minutes of an invitation link was refused, silently. The cooldown is now per family (invitations vs the account's own steps); the daily cap still counts everything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): member invite --link and member cancel in the CLIChristophe Besson2 days7-6/+284
| | | | | | | | | The CLI makes both halves itself — the node's code, then the hub's ticket bound to the address — and prints the link; a refused ticket takes the code back, and cancel takes back both. The CLI never asks the hub to mail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): open, create and join invitation links in the interfaceChristophe Besson2 days22-10/+1360
| | | | | | | | | | | #/invite takes the link out of the address on load and keeps it in the tab through registration and sign-in; joining is one click, only the ticket goes to the hub, and the code goes only to the node the link names once it has signed its challenge. Members tab gains "Invite by link" (shared e-mail box, pending list, cancel both halves); home page takes a pasted link. Browser probe drives the real app, signed out and in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): invitation-link tickets bound to a verified addressChristophe Besson2 days22-9/+847
| | | | | | | | | | | group_invite_links holds sha256(ticket) and the invitee's address blind index; redeeming grants membership to that account only. Owner-only create/list/cancel (a node token may create, never mail), 20 outstanding per group, optional mail written by the hub itself and capped at 10 per sender per day (mail.invite_link_daily_cap). MESHBAY_DESIGN.md §3.4 now carries the whole link design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: drop the invitation-link plan from the treeChristophe Besson2 days6-6/+6
| | | | | | | | The plan was a working document; what it decided lives in MESHBAY_DESIGN.md and MESHBAY_NODE_PROTOCOL.md. Code and tests now cite those instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): invitation links — a code bound to no account until redeemedChristophe Besson2 days10-25/+710
| | | | | | | | | | New invite kind "link": member of one group, once, never operator, not spendable by an active member, capped at 20 per group, cancellable by handle. Signed ops invite_link_create / invite_cancel, loopback routes, and the known-device join path now accepts a link code. Adds the plan, docs/invite-links.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: the node signs its handshake challenge (MNP 3.4)Christophe Besson2 days11-12/+345
| | | | | | | | | node_pk in handshake_challenge is now signed over the channel binding and both nonces, so a client can check the node key before a join rather than only at the ack. Both transports; the browser and the QUIC client refuse a wrong signature and treat an absent one as an older node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): make mailing an invitation a remembered choiceChristophe Besson2 days13-17/+132
| | | | | | | | A "Send the invitation by e-mail" box under the Invite member field, checked by default and stored as the invite_email preference. Unchecked, invite-notify is never called and the hub never sees the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): right-click menu in the Files tabChristophe Besson2 days14-65/+351
| | | | | | | | The toolbar's actions on the row under the pointer, sharing one action list with the toolbar — which keeps showing what does not apply, disabled, while the menu leaves it out. A count only where more than one item is concerned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: ask in the page instead of native confirm/alertChristophe Besson4 days26-58/+200
| | | | | | | | A native confirm() or alert() leaves the desktop client unable to type until the window is refocused. ask.js draws both in the page; the SPA test now bans all three browser dialogs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>