Files
tether/core/examples/prefer_direct.rs
Patrick Ecord 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

51 lines
1.9 KiB
Rust

//! Proves the relay carries content only until a direct path exists. A and B
//! join a room on the LIVE server and link directly (LAN/RTC). After that, A's
//! send must reach B but must NOT appear on the server bus.
//! cargo run --example prefer_direct -- <room>
//! (run an external SSE listener on the same room to confirm suppression)
use std::sync::mpsc;
use std::time::Duration;
use tethercore::{Engine, Message, MessageHandler};
struct Collector(mpsc::Sender<String>);
impl MessageHandler for Collector {
fn on_message(&self, m: Message) {
eprintln!(" B.on_message: kind={:?} text={:?} from={:?}", m.kind, m.text, m.from);
let _ = self.0.send(m.text);
}
fn on_status(&self, _c: bool) {}
fn on_file(&self, _name: String, _mime: String, _data: Vec<u8>) {}
}
fn main() {
let server = "https://tether.pecord.io".to_string();
let room = std::env::args().nth(1).unwrap_or_else(|| "prefer-direct-demo".into());
let (tx, rx) = mpsc::channel();
let a = Engine::new(server.clone(), room.clone(), "dev-a".into(), "macos".into(), "A".into(), "".into());
let b = Engine::new(server, room, "dev-b".into(), "ios".into(), "B".into(), "".into());
a.start(Box::new(Collector(mpsc::channel().0)));
b.start(Box::new(Collector(tx)));
// Let presence + the direct (LAN/RTC) link establish.
eprintln!("waiting ~12s for presence + direct link…");
std::thread::sleep(Duration::from_secs(12));
let secret = "DIRECT-ONLY-SECRET";
eprintln!("A sends — should go P2P, NOT via the relay");
a.send(secret.to_string());
let mut got = false;
while let Ok(t) = rx.recv_timeout(Duration::from_secs(4)) {
if t == secret { got = true; break; }
}
println!(
"{}",
if got { "✅ B received the secret over the direct path" } else { "❌ B never got the secret" }
);
a.stop();
b.stop();
}