Daemon:
- Per-network download directories (-download-dir flag, set_download_dir IPC)
- File transfer resume after disconnection: .tmp.meta sidecars survive
interruption; resume_offset in file-accept lets sender seek and continue
- set_download_dir IPC command; download_dir reported in state_snapshot
Protocol:
- PeerMessage.ResumeOffset (EXT-006) for file transfer resume
- IpcMessage.ShareNetworks json tag changed from "networks" to "network_ids"
to fix duplicate json tag collision with Networks []NetworkInfo
Desktop app (cmd/app):
- Wails v2 shell embedding daemon logic directly (no subprocess)
- System tray on Linux/Windows via getlantern/systray; macOS hides to Dock
- OS notifications for message_received and file_complete via Wails events
- notray build tag for headless/CI builds without GTK tray headers
- build-app.sh: builds web frontend, copies dist, runs wails build
Web / PWA:
- manifest.json + Apple touch icon meta tags for mobile "Add to Home Screen"
- PNG icons (192px, 512px, 180px) generated from SVG
- Wails EventsOn("notify") hook in App.tsx for native OS notifications
CI:
- .gitea/workflows/build.yml: server binaries cross-compiled for 5 platforms,
desktop app for Linux amd64, release artifacts published on v* tags
Docs:
- README: download dir, file transfer resume, desktop app, PWA, CI sections
- EXTENSIONS.md: EXT-004 daemon mode marked shipped; EXT-006 resume added
- FUTURE.md: roadmap updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.2 KiB
waste-go Protocol Extensions
These are additive extensions to YAW/2 implemented by waste-go. They do not break compatibility — YAW/2-only peers silently ignore all new fields. Where a waste-go peer connects to a YAW/2-only peer, the extension simply has no effect on that peer.
EXT-001 — Signed Invites
Status: implemented
Affects: waste: invite format, hello DataChannel message
Motivation
The base YAW/2 network model is open to anyone who knows the anchor URL and
network name (or hash). This extension adds opt-in cryptographic membership
gating: invites are signed by an existing peer, and peers that enforce
RequireInvite reject hellos that carry no valid signed invite.
Invite format changes
The waste: invite payload (base64-encoded JSON) gains two optional fields:
{
"anchor": "wss://...",
"network": "friends",
"net": "<64-hex SHA-256(yaw2-net:name)>",
"inviter": "<64-hex Ed25519 pubkey of signing peer>",
"sig": "<hex Ed25519 signature>"
}
The signature covers the following bytes (null-separated):
anchor \x00 network \x00 net \x00 inviter
Unsigned invites (inviter/sig absent) remain valid for backward compat.
Hello message extension
The YAW/2 §6 hello message gains one optional field:
{
"type": "hello",
"id": "<hex pubkey>",
"nick": "alice",
"caps": ["chat", "file"],
"sig": "<DTLS binding sig>",
"invite": "waste:eyJ..."
}
invite carries the full waste: string the connecting peer used to join.
YAW/2-only peers ignore this field.
Enforcement
Per-network flag RequireInvite (set via join_network IPC command).
When enabled:
- A peer that presents no
invitein hello is disconnected immediately. - A peer that presents an invite with no signature is disconnected.
- A peer whose invite signature is invalid is disconnected.
- A peer whose invite was signed by an unknown peer ID (not in the store or currently connected) is disconnected.
The inviter's key must be a known peer — i.e. previously connected and stored in the per-network SQLite store, or currently connected. This forms a chain of trust: Alice (founder) invites Bob; Bob's key is now known; Bob can invite Carol, whose invite Alice will also accept.
Default: off. Networks opt in. Existing networks with no RequireInvite behave exactly as before.
EXT-002 — Hash-based Hang Link
Status: implemented
Affects: web UI URL handling only, no wire changes
Motivation
A shareable URL that pre-fills the join form without conveying cryptographic membership. Suitable for public announcements ("come hang out here"). The fragment is never sent to the server, keeping the network name opaque to server logs and HTTP intermediaries.
Format
https://host/#waste:eyJ...
The fragment payload is the standard waste: base64 JSON with only network
and anchor fields — no inviter, no sig. This does not grant
membership on networks with RequireInvite enabled; it only pre-fills the
join form.
The web UI generates hang links via the 🔗 button in the Networks sidebar section. Arriving users see the join form pre-populated and still need a proper signed invite (if the network enforces it) to be accepted by peers.
EXT-003 — Multi-Share Configuration
Status: implemented
Affects: IPC protocol only, no peer-to-peer wire changes
New IPC commands
{"type":"add_share","path":"/home/alice/Music"} // global
{"type":"add_share","path":"/home/alice/Docs","network_ids":["abc123"]} // scoped
{"type":"remove_share","path":"/home/alice/Music"}
{"type":"list_shares"}
New IPC event
{"type":"shares_list","shares":[{"path":"...","networks":["*"]}]}
Persistence
shares.json in the data directory (next to identity.json). Each entry:
{ "path": "/absolute/path", "networks": ["*"] }
networks: ["*"] = global (all networks). Specific network IDs = scoped.
Coexists with the legacy set_share_dir single-dir mechanism.
File listings returned by get_file_list and MsgFileListReq include
entries from all applicable share roots, with relative path fields
(e.g. "path": "docs/report.pdf").
EXT-004 — TURN Relay (browser mode)
Status: implemented (browser mode + daemon mode)
Affects: ICE server configuration only, no wire changes
The browser adapter reads WASTE_CONFIG.turnURL and WASTE_CONFIG.turnSecret
and adds a TURN server to the WebRTC ICEServers list. Credentials are
generated using HMAC-SHA1 of the username (coturn use-auth-secret scheme).
YAW/2 §0 explicitly declines TURN ("No relay (TURN)"). This extension is opt-in via server configuration and does not affect peers that omit it.
EXT-005 — Per-Network Path in FileEntry
Status: implemented
Affects: MsgFileListResp wire message (additive field)
FileEntry gains an optional path field carrying the file's relative path
within its share root (e.g. "docs/report.pdf"). Peers that don't understand
this field continue to use name for display and download requests.
MsgFileListReq / get requests use path as the lookup key when present,
falling back to name for backward compat with peers that don't send path.
EXT-006 — File Transfer Resume
Status: implemented (daemon mode)
Affects: file-accept wire message (additive field)
Motivation
A transfer interrupted mid-stream (peer disconnect, network drop) can be continued from where it left off on the next offer of the same file, avoiding a full re-download.
Protocol change
file-accept gains one optional field:
{ "type": "file-accept", "xid": "...", "resume_offset": 65536 }
resume_offset is the number of bytes the receiver already has on disk.
When non-zero, the sender seeks to that byte position before streaming.
Peers that don't understand this field ignore it and send from the start —
the receiver detects this by comparing incoming data to expected offset and
will still verify the final SHA-256, but the partial bytes from the interrupted
session will be overwritten (YAW/2-only interop degrades gracefully to a full
re-download, not corruption).
Receiver behaviour
- On
file-offer, the receiver scans its download directory for a.tmp.metasidecar whosesha256matches the offer. - If found, the corresponding
.tmpfile's size is the resume offset. This is sent back infile-accept. - On DC open, the receiver opens the existing
.tmpin append mode and re-hashes its existing bytes to restore the SHA-256 state. - On DC close with all bytes received, SHA-256 is verified. Success → sidecar removed, file renamed to final path. Hash mismatch → both files removed.
- On DC close with fewer bytes than expected (interrupted again) → both files kept for the next resume attempt.
Sidecar format
Each in-progress .tmp file has a corresponding .tmp.meta JSON sidecar:
{ "name": "archive.zip", "sha256": "abc...", "from": "<peer-id>", "size": 1048576 }
The sidecar is written when the transfer starts and removed on completion or corruption. Interrupted transfers keep the sidecar indefinitely.