aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
Commit message (Collapse)AuthorAgeFilesLines
* feat: open a group, a folder or a file from a #/name@owner linkChristophe Besson12 hours3-0/+413
| | | | | | | | | | | | | | | | | | | A group can now be reached by the handle shown under its name, and a path after it points inside the group: #/name@owner/root/dir/file downloads the file and opens Files on its folder; a folder opens Files there. The handle is resolved in the client against the account's own /v1/groups/mine, so no hub route answers for a name and nobody can probe for one. While a group is open the address shows the handle (replace, no history entry); a linked path is taken out of the address once acted on, so a reload does not download twice. Signing in no longer sends everyone home: the form stood in for the page the address named, and that is where a link opened signed out was going. group-link.js holds the parsing and lookups, executed whole by test_group_link.py; harness/group_link_probe.py drives the router in Chrome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: list as members only the accounts the node has admittedChristophe Besson13 hours1-2/+2
| | | | | | | | | | | | | | | | The Members list showed the hub's membership, which an account gains when it accepts the invitation or redeems a link, before it has presented its code to the node. The node's roster is the authority (MESHBAY_DESIGN.md §3.4), so the list now crosses the hub's members with the sealed group roster the node already sends every connected member. An account the node has not admitted yet is shown to the owner alone, as waiting for its code, with the Remove button; other members do not see it. When the roster cannot be read, the hub's list is shown as before. groupRoster() takes { fresh: true } so the page sees who joined since the connection opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: generate an HTTP API listing for the hub and the node control APIChristophe Besson13 hours1-0/+30
| | | | | | | | | | | | | | | | | | | docs/MESHBAY_HTTP_API.md lists every route of the hub (by domain, with the authentication each requires) and of the node's loopback control API. It is written by docs/generate_http_api.py from the routes and their docstrings; test_http_api_doc.py fails when the file drifts from the code or when a route has no docstring, so a new route must say what it does. 79 routes had no docstring and get a one-line description; a few whose first line did not describe the route get a summary line. The login page's developer docs gain an API link next to Design and Protocol, in every language. README, MESHBAY_DESIGN.md (§0.1, §6.7, §7) and CLAUDE.md point to the listing; README also points to examples/. The examples scripts with a shebang become executable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cast): music on the TV, the music bar as its remoteChristophe Besson22 hours4-262/+524
| | | | | | | | | | | | | | | | | A cast button in Music's toolbar and in the music bar. With a television chosen, each decrypted track goes to the relay with its cover — found as the album card finds it — and plays there as music with its title, artist and album; the bar's play, pause, seek, previous and next drive the receiver, its clock is the receiver's, and the end of a track there moves the queue on. A film or a photo taking the television pauses the bar; stopping the cast carries the track on locally. Photos and tracks now share one path: a whole file sent to the relay in pieces (binary frames on Android, written to disk there), served at /file with byte ranges and its cover at /cover, and loaded as what the relay says it is. cast:image is gone; cast:chromecast:seek is new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cast): photos on the TV, and a television chosen once for the sessionChristophe Besson23 hours3-1/+634
| | | | | | | | | | | | | | A cast button in Videos' toolbar, at the top of Photos, in an album's bar and in the lightbox, in a group and in Search alike. A television chosen there is kept for the session: a film opened plays on it with the player as its remote from the start, and a photo opened in the lightbox is shown on it, scaled to 1920x1080, upright, as JPEG. The lightbox gains a slideshow. The relay serves one photo at /image behind the stream's token, on the desktop and on Android; the shell, not the page, decides that the receiver loads it as a picture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): music keeps playing with the screen offChristophe Besson25 hours1-4/+6
| | | | | | | | While a track plays, the page asks the shell to stay awake (playback:keep-alive): on Android the cast's foreground service and visible WebView, with a notification; on desktop a power save blocker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Player: redesigned cast remoteChristophe Besson2 days1-2/+1
| | | | | | | Skip buttons with circular arrows, filled scrubber, large play/pause, device header; the remote is its own component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Player: remote control while castingChristophe Besson2 days2-2/+32
| | | | | | | Shows the receiver's position with play/pause, ±30 s and a scrubber; a seek restarts the relay where asked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(android): the cast relay spools a receiver's lead to diskChristophe Besson2 days1-1/+15
| | | | | | | | Fragments are 5-10 MB at a film's bitrate; dropped past 8 MB in memory, the TV froze for their length. Each receiver now reads from its own spool file, deleted with it; nothing is dropped short of a disk bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the local player is silent while castingChristophe Besson2 days1-0/+15
| | | | | | | It keeps playing to pace the relay, so it doubled the television's sound. The viewer's mute setting comes back when the cast ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): a seek's first segments are held while it lands, not droppedChristophe Besson2 days2-2/+70
| | | | | | | | They are the new stream, header first. Dropped, a cast relay restarted at the landing got no ftyp/moov and the receiver gave up; they are now replayed in order once reinitAt/resumeAt is done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): casting through a LAN relay and the platform cast SDKChristophe Besson2 days2-2/+122
| | | | | | | | A port of cast-relay.js (backlog also bounded in bytes), discovery and control with the default media receiver, relay calls kept in order, and a foreground service plus a WebView kept visible so a cast survives the screen going off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): downloads to disk and uploads through the system pickerChristophe Besson2 days1-0/+90
| | | | | | | | Native save over the Storage Access Framework and MediaStore, chunks sent as binary bridge messages, a chosen folder that has gone asks rather than redirects, unfinished files removed on abort and after a killed process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): a download written to disk no longer holds the whole fileChristophe Besson2 days1-0/+53
| | | | | | | pipelinedDownload kept every chunk's resolved promise until the end; a 2 GB download held 2 GB in the page. Each is released once read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): device key, bundle key and node identities held nativelyChristophe Besson2 days2-2/+77
| | | | | | | | Keystore-wrapped store, a Kotlin port of keyring.js and transcripts.js held to the shared vectors, the same keys/device/secrets bridge as the desktop, and a native confirmation before browser access is widened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the node setup welcome is for a build that has a nodeChristophe Besson2 days1-0/+11
| | | | | | | Gated on capabilities.nodeAdmin rather than on any bridge, so a phone with no groups sees its invitations and the join link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): client shell with the interface from the packageChristophe Besson2 days1-0/+210
| | | | | | | | WebView over the packaged UI (copied from hub/static at build time), the desktop CSP as a header, a bridge answering our top-level document only, hub calls from native to the signed-in hub. Keys stay in the page for now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(hub): shared keyring and transcript vectorsChristophe Besson2 days3-0/+716
| | | | | | | One file every bundle/transcript implementation must reproduce, generated from the desktop keyring; checked against it and against the specification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): no confirmation dialog for adding a folder or its flagsChristophe Besson4 days1-8/+4
| | | | | | | Removes confirmFolder (addRoot, attachGroup) and the writable confirmation added in e4f6177, with their two catalogue keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(mnp): sharing a folder is decided on the node's machine only (MNP 6.0)Christophe Besson4 days3-6/+51
| | | | | | | | | | root_add, root_update and group_attach leave MNP: adding a directory and switching writable/removable go through the loopback API (native dialog in the desktop app) or the CLI. The operator's Settings tab still lists the roots from any browser, read-only. The desktop app refuses to sign those ops; a loopback flag change now reaches open pages (publish_roots). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): list cast receivers as they answerChristophe Besson4 days1-0/+108
| | | | | | | | The scan still runs six seconds, but the picker polls what it has found and shows each receiver immediately. A rescan no longer has its timer cut short by the scan it replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(hub): wrap a docstring ruff flaggedChristophe Besson4 days1-3/+3
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): rotating a group key asks nothingChristophe Besson4 days1-3/+2
| | | | | | | Rotation narrows rather than widens: members still connected receive the new key, and nothing already shared changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a username is unique whatever its caseChristophe Besson4 days1-0/+24
| | | | | | | | Registration refuses a name that differs from an existing one only by case; accounts that already do keep their names, and a pending retry needs the exact name (F-26). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): what an offer or a node message costs the hub is boundedChristophe Besson4 days1-0/+51
| | | | | | | | | An offer's IP-log row (kept a year) was written before any check, for any string named as a node; it is written once the offer goes to a node. The ICE list is capped (64 candidates, 32 KiB). A node's update_groups, a database read each, is budgeted like chat_notify and claims at most 1000 groups (F-22). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the audit export never hands a spreadsheet a formulaChristophe Besson4 days1-0/+32
| | | | | | | A cell starting with = + - @ (or a tab or carriage return) gets a leading apostrophe; the export carries text members chose (F-29). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: an MBK2 bundle is opened once and stored again as MBK3Christophe Besson4 days2-2/+80
| | | | | | | | | | | | Transitional. The Argon2 run that makes M makes A, the key MBK2 bundles were sealed under; a session keeps it as a decrypt-only key (IndexedDB in a browser, the key storage in the desktop app). A client meeting an MBK2 bundle opens it — or its recovery copy — and stores the same identity as MBK3 once connected; the desktop app reseals or withdraws it as browser access says. A session without A asks for the passphrase once. Older formats stay refused by name. Replaces the unpin-and-reinvite step the 0.17 flag day required on every node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: downloads are marked and keep their extension; Explorer files are refusedChristophe Besson5 days1-0/+23
| | | | | | | | | | | The desktop app writes the Mark-of-the-Web on each file it saves on Windows, as a browser does. Bidirectional controls are reserved characters in a saved name (portable-name.js and paths.sanitize_for_download, and again in the main process), so a name cannot display one extension and carry another. The node refuses uploads of files Windows Explorer acts on by itself: desktop.ini, .lnk, .url, .scf, .library-ms, .searchConnector-ms (F-19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a stranger who knows your name locks only browsers you never usedChristophe Besson5 days2-4/+114
| | | | | | | | | | | A sign-in from a browser that presented no token is answered with one (known_browser, kept hashed, twenty per account); a later sign-in presenting it counts failures on its own row, which nobody else can spend. Passphrase checks inside an open session (change, e-mail, deletion, device, pepper) count on the account's own row, so a locked name no longer stops its owner there either; /me reports that row. Reset and erasure forget the browsers (F-15). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a group name fits its column and carries no control charactersChristophe Besson5 days1-0/+33
| | | | | | | | Over 128 characters was a 500 on PostgreSQL; line breaks, C0/C1 controls and bidi overrides are refused (joiners stay, for emoji). The creation form caps the field at 128 (F-13, what remains of it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: an identity signs a named kind, and a device approval answers a requestChristophe Besson5 days2-8/+102
| | | | | | | | | | | The desktop main process builds every transcript itself from fields (transcripts.js) and signs no raw bytes; the page's identity has the same contract (crypto.js transcriptFor). The keyring seals no bundle while browser access is off. On the node, device_add must redeem a pending request filed by the same keys, and device_revoke is signed under its own prefix (meshbay:device_revoke:v1), so a retirement signature admits nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): the pepper and a device key take the passphrase, not a tokenChristophe Besson5 days4-12/+67
| | | | | | | | | POST /me/bundle-pepper (was GET) and POST /users/devices require auth_key. A refreshed or lifted token could otherwise fetch the pepper, or register a device whose every sign-in carries it. Both callers have just been given the passphrase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: browser access, decided in the desktop applicationChristophe Besson5 days2-0/+37
| | | | | | | | | Off for an account made there: its identities stay on the device and nothing is left on nodes. Turned on from the Profile page behind a native confirmation; each node is settled when its group next opens. The hub keeps a mirror a browser reads to say why a group will not open; it grants nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): the desktop application keeps M and every node identity in its ↵Christophe Besson5 days2-0/+196
| | | | | | | | | | | | main process keyring.js derives, opens, mints, seals, signs and agrees there; the page gets public keys and a handle. Argon2 comes from the page's own WebAssembly build (Electron's crypto has none). Without OS key storage the page keeps its keys as a browser does. A node's bundle is settled after connecting, re-sealed when the key changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(hub): the transport holds an identity, never a private keyChristophe Besson5 days3-3/+53
| | | | | | | | Two public keys, sign() and shared(); the apps take transport.signFn. What holds the keys (this page, or the desktop main process) is the identity's business alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: bundles sealed per node under the passphrase and the hub's pepperChristophe Besson5 days10-276/+406
| | | | | | | | | The session key is M = HKDF(Argon2(passphrase) || pepper, account id); each node's bundle key and the playlist key derive from it. Bundles are MBK3, bound to account and node; MBK1/MBK2 are refused by name, never replaced silently. Playlists move to key v2 and are re-sealed over unreadable node copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): a bundle pepper per account, handed only to a proven sessionChristophe Besson5 days1-0/+181
| | | | | | | | Sealed at rest and bound to the account; returned by sign-in, device sign-in, a passphrase change and GET /me/bundle-pepper, never by a refresh, to a node token, in a token or in a log. Erasure clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a downloaded file opens in a tab only under a type that runs nothingChristophe Besson5 days2-4/+55
| | | | | | | Open is offered for PDFs, raster images, audio, video and plain text, typed from the name; HTML, SVG and the rest are not opened in the hub's origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: the page connects to its own origin and reCAPTCHA, nowhere elseChristophe Besson5 days1-7/+10
| | | | | | | | connect-src drops https: and wss: in both policies. Checked against Google's reCAPTCHA test keys: in Chrome widget, token and registration unchanged; in Firefox the widget loads; no violation reported in either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the page names node operations, and the app confirms what ↵Christophe Besson5 days3-7/+82
| | | | | | | | | | | widens the node node:call is replaced by named operations with checked arguments; hosting a group, sharing an unpicked folder, key rotation, denylist clearing and a change of node account are confirmed by a native dialog. Every channel checks its sender, secrets:get/set/clear are gone, node:start writes the app's own hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): the admin allow-list grants an account, not a usernameChristophe Besson5 days1-0/+121
| | | | | | | Each name in admin_usernames is pinned to the first active account seen holding it (admin_pins), so a name freed by a deletion grants nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: only the owner decides who hosts a group, and nobody is made a member ↵Christophe Besson6 days17-48/+787
| | | | | | | | | | | | | | | | | | | | | | | | | | unasked - hub: a node may host a group only if its account owns it or the owner approved that node (new `group_hosts`). Membership was the ceiling, and every member holds the group key, so any member's node could register as a host and be the one clients kept. A node claiming a group it may not host is recorded as a request; the owner is notified once and approves or refuses it (GET/POST/DELETE /v1/groups/{id}/hosts[/{node_id}]), which takes effect on a connected node at once. - hub: an owner adding a username creates an invitation (new `group_invitations`), accepted or declined by the invitee (/v1/groups/invitations, /{id}/invitation/accept|decline). Until then the group is not listed, not dialled, not searched and not in any token. Invitation links, open joins and group creation still make members directly: they are the account's own act. - hub: the MNP token names only the group it is minted for (group_id is now required), so a node operator no longer learns a member's other groups. - SPA: invitations on the home page; invited people and host requests in the group's settings; the transport sends group_id. Ten catalogues. - Browser probes for both screens, run in Chrome and Firefox. - Design §5.2, §7.2, §7.3, AV32, AV33; protocol §6.3; user guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: a member can no longer lock a node, crash it with a link, or stop hub ↵Christophe Besson6 days1-0/+103
| | | | | | | | | | | | | | | | | | | cleanup - node: only a wrong code counts towards the join lock, now per account (5) as well as node-wide (20), and it is consulted only when a code is tried. Every member reconnecting gets the group key through join_request, so a lock checked before recognition let one member refuse it to everyone. - node: link previews read the body as a stream and stop at the cap, counted on decoded bytes; a declared oversized image is not read; 15 s total deadline; image decoding off the loop. `client.get` had buffered the whole (decompressed) response before the caps looked at it. - hub: the daily purge of never-verified accounts detaches their IP-log rows (keeping the name) and clears every other reference first, and each cleanup step runs on its own. On PostgreSQL the bare DELETE violated the ip_logs foreign key and stopped every purge behind it for good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): search title, description, canonical and noscript in the shellChristophe Besson6 days2-1/+37
| | | | | | | | | A descriptive <title>, a two-line meta description apart from the short one messengers get, "/" canonical for every shell path, and a <noscript> pitch with the download and doc links for crawlers that do not run app.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): sitemap.xml, named by robots.txtChristophe Besson6 days1-1/+15
| | | | | | | Home, downloads, and the repository's about page and docs on git.meshbay.org, spelled as the welcome page links them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(hub): remove the closed relay registryChristophe Besson7 days2-91/+0
| | | | | | | Every /v1/relays route answered 503 and nothing called them; no TURN relay is needed. The proof-of-possession rule it carried stays as AV6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): save files under a name every platform can writeChristophe Besson7 days1-0/+42
| | | | | | | | | A node serves the name its disk gave a file; the client now makes it portable at save time (single file, zip entries, zip name) and says so on the transfer row. Same rule as paths.sanitize_for_download, held by a parity test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): reports from public-group members, decided by an administratorChristophe Besson7 days1-42/+163
| | | | | | | | | | A report needs a person's account at least a day old, membership of the public group, and fits a daily allowance per account. Past the threshold a hash is queued and administrators are notified; blocking without review is an instance setting, off by default. Report menu item in public groups, Reports tab and settings in the admin panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: nodes apply the content blocklist in their public groupsChristophe Besson7 days2-11/+94
| | | | | | | | | A node hosting a public group syncs the hub's blocklist on every connection (paged, node token only) and applies pushed changes. A blocked file leaves the index and is refused (content_blocked); private groups are untouched. The unused per-hash check route is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: remove the public-content swarmChristophe Besson7 days2-67/+3
| | | | | | | | Nodes registered the hashes of their public groups on the hub and nothing ever read them back. Routes, model and node registration removed; a migration drops swarm_sources. No node sends the hub a content hash now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>