Commit Graph

7 Commits

Author SHA1 Message Date
d3dd785921 core: file/image transfer over the RTC data channel (chunked, P2P)
Add Engine.send_file(name, mime, data) and MessageHandler.on_file. Files stream
over each open RTC data channel as binary frames — M<json meta> · C<16-byte
id><16KB chunk>… · E<id> — and reassemble on the receiver in arrival order
(the channel is reliable+ordered). The data channel now distinguishes string
(clipboard) from binary (file) messages. Files are direct-only — never relayed,
matching prefer-direct.

Transport trait gains start(...files) + a default-noop send_file (only RTC
implements it). The agent saves received files to ~/Downloads; the apps stub
on_file (receive UI is a follow-up). All MessageHandler impls updated.

Proven by examples/file_demo.rs: A sends a 50KB blob (4 chunks), B reassembles
it to identical bytes over RTC.

Follow-ups: file transfer over LAN (same-network without RTC); attach/preview UI;
progress + size cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:54:29 -05:00
d1750a95d6 core: account-derived rooms — same account → same room everywhere
Add room_for_account(account) = sha256(account)[..4] hex, exported via UniFFI so
every client derives the IDENTICAL room from a shared identity. Clients now
default their room to the account-derived id (falling back to a device id only
when signed out). With the prefilled account, all our devices land in one room
across LAN, the relay, and RTC — so "just us" works cross-network, not only on
the same Wi-Fi. The single canonical Rust impl guarantees the ids match across
Swift/Kotlin/agent (a hand-rolled per-platform hash would risk drift).

Verified: agent and the live Mac app both derive room 360309c8 from
"pecord@gmail.com"; the Mac app publishes there on the public relay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:55:28 -05:00
f7bc8eed4b clients: prefill a shared account so our devices auto-pair ("just us")
Default the account to a shared identity (overridable, persisted in
UserDefaults/prefs; agent via --account) instead of empty. With it prefilled,
every one of our devices on the same network auto-pairs over the LAN regardless
of room — no setup. Swapped in for the real Sign-in-with-Apple `sub` later.

Verified with the live Mac app: it and an agent in a totally different room
LAN-paired purely via the shared account.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:49:35 -05:00
c3b9b7940f core: account identity — same-account devices auto-pair on the LAN
Add an `account` dimension (Apple/Google `sub`, or any shared id) advertised
over mDNS alongside the device name. The LAN transport now connects to a peer
when they share a room OR a non-empty account — so your own devices find each
other and sync on the network regardless of room, no pairing step. Peer gains
`account` so the UI can mark "your devices".

Engine::new gains `account` (6th arg, ""=signed out); all callers updated.
Apps pass "" until sign-in lands; agent takes --account.

Proven by examples/account_pair.rs: two devices in DIFFERENT rooms with the
same account sync over LAN with no server.

Next: derive the account from Sign in with Apple / Google so this is automatic
(the cross-network version = account-derived shared server room).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:41:00 -05:00
6e24d19a58 core: nearby-device discovery (AirDrop-style) — friendly names + Engine.nearby()
Each device now advertises a friendly name over mDNS ("Patrick's MacBook"),
and the LAN transport records every tether device on the network — regardless
of room — into a discovered table. New Engine.nearby() -> [Peer] exposes that
list for an AirDrop-style picker; entries prune after 30s unseen. Peer carries
{id, name, source, room} so the UI can offer "pair" (join that device's room).

Engine::new gains a `name` param (5th); all callers updated (apps pass
UIDevice.name / Host.localizedName / Build.MODEL, agent passes hostname or
--name). Auto-connect stays room-scoped; only the *visibility* is broadened.

Proven by examples/nearby.rs: two engines in different rooms discover each
other by name over mDNS — and it picks up real devices on the LAN too.

iOS caveat unchanged: mDNS needs the multicast entitlement, so nearby() is
empty there until that lands. Desktop/Android work today.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:09:56 -05:00
e52e20c2c5 clients: default server URL → https://tether.pecord.io
Replace the dead tether.lan / localhost / emulator-host placeholders with the
live public relay across all clients (iOS, macOS, Android, agent), so a fresh
install connects out of the box. All remain overridable (UI field / --server).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:01:58 -05:00
3581c1e86c android: Compose client scaffold on tethercore (Kotlin/UniFFI)
Add an Android app that drives the same tethercore engine (SSE + RTC) as the
iOS/macOS apps via generated Kotlin/UniFFI bindings — proving the shared core
reaches a fourth language with no protocol reimplementation. Engine surface is
identical to Swift: Engine(server,room,from,source) / start / send / stop.

Includes the Gradle/Compose project, a TetherStore that bridges the engine to
ClipboardManager (auto-send via OnPrimaryClipChangedListener — Android allows
foreground clipboard observation, unlike iOS), per-install RoomIdentity, and
core/build-android.sh (cargo-ndk → jniLibs + Kotlin bindgen).

Not built here: this machine has no Android SDK/NDK, so the native .so and
Kotlin binding are generated (git-ignored), not committed. README documents the
NDK install + build steps. The binding itself was confirmed to generate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:42:36 -05:00