Claude Opus 4.7 618d330682 v0.5: stateless room-based sessions with QR pairing
The single shared mesh is replaced by per-session rooms. Visit /
and the server mints a random 8-hex-char id, redirects to /r/<id>.
That URL IS the session — share the link (or scan the QR code now
shown on the page) on another device to join the same room.

Bus is now sharded per room. Rooms are created implicitly on first
subscribe and GC'd 5 minutes after the last subscriber leaves. No
accounts, no persistence, no server-side state beyond the in-memory
bus map.

Server:
- New endpoints: /, /r/<id>, /api/send?room=, /api/stream?room=
- Room manager with lazy creation + idle GC
- Metrics now labelled by room
- New gauge tether_active_rooms

Client (Go):
- -room flag (accepts bare id OR full /r/<id> URL — paste-friendly)
- All API calls now scope to the room
- The always-on ct210-rtc-peer systemd unit is disabled — sessions
  are user-initiated; the user runs tether-client with -room when
  they want their laptop in a particular session

Browser (HTML):
- Reads room from /r/<id> path
- Shows QR code + URL + "copy link" button at top
- "+ new session" link in header to start a fresh room

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:13:33 -05:00

tether

Phone ↔ laptop clipboard relay. v0.3 — WebRTC P2P working.

Today: an HTTP+SSE broadcast bus that also bootstraps WebRTC DataChannels between participants. Once paired, clipboard text flows direct peer-to-peer with DTLS encryption — the server never sees the payload.

The roadmap is what makes it interesting: symmetric presence chirps for self-healing mesh discovery, Sign in with Apple for cross-device identity, mDNS for zero-config same-LAN discovery, file drop, system tray UX.

phone (web UI)              tether-server                tether-client
─────────────               ─────────────                ──────────────
type/paste                  HTTP+SSE relay               Linux/Mac/Win
  │                              │                              │
  └─── POST /api/send ──────────▶│                              │
                                 ├──── event: clipboard ───────▶│
                                 │                              ▼
                                 │                          stdout / OS
                                 │                          clipboard
                                 │◀──── POST /api/send ─────────┘
                                 ▼
                          web UI shows it

Quick start

go run ./server
# in another terminal
go run ./client -server http://localhost:8765

# send a one-shot message from CLI:
go run ./client -server http://localhost:8765 -send "hello"

Then open http://localhost:8765/ on your phone (same network) and try the buttons.

Pieces

  • server/ — single Go binary. Embedded HTML page. Exposes:
    • GET / — phone UI
    • POST /api/send — accept a message
    • GET /api/stream — SSE feed of every published message
    • GET /healthz
  • client/ — CLI client. Subscribes to /api/stream, prints received messages to stdout. -send for one-shot send.
  • server/web/index.html — phone UI (paste, send, live feed of incoming).

Roadmap

Phase What Why
v0.1 HTTP+SSE relay, single broadcast bus Prove the shape end-to-end
v0.2 /metrics endpoint, server-side observability Latency/throughput visibility
v0.3 WebRTC DataChannel (Pion ↔ browser RTCPeerConnection), peer chirps offer until paired True P2P with mandatory DTLS encryption
v0.4 (next) Symmetric presence chirps — every participant (peer, browser, future Mac) broadcasts {type:"presence", from, role, capabilities} every 10-15s. Lets peers/browsers self-discover, auto-upgrade SSE→RTC, detect dead participants via heartbeat timeout. Pairs N-way (mesh, not star). Self-healing discovery without the "happen to be open at the right moment" timing trap of v0.3's single-direction chirp
v0.5 mDNS service advertisement + QR pairing Zero-config same-LAN discovery
v0.6 Sign in with Apple OAuth → stable sub ties multiple devices to one trust circle Identity without account/password
v0.7 OS clipboard hook on client (read + write) — phone copy → laptop paste appears automatically The actual Universal-Clipboard UX (current v0.3 has write on receive; missing the read on local change)
v0.8 File drop (large blob over WebRTC), encrypted at-rest history Snapdrop-like UX bundled in
v0.9 Mouse/keyboard handoff (Synergy-style) using the same authenticated peer set + DataChannel Universal Control for the rest of us
v1.0 macOS / Windows tray UX, push notifications when off-network, packaged installers Product

Note on v0.4 presence chirps

The current peer's "chirp every 5s while unpaired" only solves one-direction late-joining. The full pattern is symmetric:

Every participant emits:
    {type: "presence", from: <peerID>, role: "peer" | "browser",
     capabilities: ["clipboard", "filedrop", "cursor"], ts: …}
every 10-15s on the bus.

Maintains  Map<peerID, lastSeen> table on each side.
- New peer presence seen by browser → wait for offer.
- New browser presence seen by peer → post targeted offer.
- After RTC opens → drop chirp rate to 60s "alive" heartbeats.
- lastSeen > 30s → mark dead, restart discovery.

Once that's in, the system becomes properly mesh-shaped — N devices can all auto-pair, see each other in the UI, drop a file to any one, etc.

Why E2E by default

WebRTC data channels mandate DTLS. Once we move from SSE relay (v0.1) to P2P data channels (v0.3+), the server only ever sees encrypted bytes (and only during signaling — not for the data itself). That's free end-to-end encryption, modeled on Apple's Continuity but using standardized protocols.

Why Sign in with Apple

~80% of Windows users also own an iPhone. SiwA gives us a free, privacy-respecting identity provider that returns a stable per-app subject ID. Devices that authenticate to the same sub are in the same trust circle automatically. No passwords, no per-app account.

What this isn't (yet)

  • Not a Snapdrop clone — no file drop (v0.8 roadmap)
  • Not KDE Connect — no system-wide integration on the laptop side beyond clipboard (v0.7+)
  • Not Pushbullet — server never sees data, no persistence
  • Not Universal Control — but the mouse/keyboard handoff is on the roadmap (v0.9)

The foundation is right: bus → signaling → WebRTC → mesh → identity.

License

MIT

Description
phone ↔ laptop clipboard relay — MVP
Readme 137 KiB
Languages
Go 57.5%
HTML 42.5%