Files
tether/core/examples/receipt_demo.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

41 lines
1.4 KiB
Rust

//! Proves delivery receipts: A sends a clipboard, B receives it and auto-acks,
//! and A.receipts() shows "seen by <B's name>" — no asking.
//! cargo run --example receipt_demo
use std::time::Duration;
use tethercore::{Engine, Message, MessageHandler};
struct Noop;
impl MessageHandler for Noop {
fn on_message(&self, _m: Message) {}
fn on_status(&self, _c: bool) {}
fn on_file(&self, _name: String, _mime: String, _data: Vec<u8>) {}
}
fn main() {
let dead = "http://127.0.0.1:1".to_string(); // LAN-only
let room = "receipt-room".to_string();
let a = Engine::new(dead.clone(), room.clone(), "dev-a".into(), "macos".into(), "Patrick's Mac".into(), "".into());
let b = Engine::new(dead, room, "dev-b".into(), "ios".into(), "Patrick's iPhone".into(), "".into());
a.start(Box::new(Noop));
b.start(Box::new(Noop));
eprintln!("waiting for LAN link…");
std::thread::sleep(Duration::from_secs(5));
eprintln!("A sends a clipboard…");
a.send("important-link".to_string());
std::thread::sleep(Duration::from_secs(2)); // let B receive + ack, A record
let r = a.receipts();
if let Some(rec) = r.iter().find(|r| r.name == "Patrick's iPhone") {
println!("✅ A sees its clipboard was delivered → seen by {:?} [{}]", rec.name, rec.source);
} else {
println!("❌ no receipt recorded ({} total)", r.len());
std::process::exit(1);
}
a.stop();
b.stop();
}