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

55 lines
1.9 KiB
Rust

//! Proves the engine round-trips against a live tether server, no FFI involved.
//! cargo run --example roundtrip -- http://192.168.11.233:8765
//! Subscriber engine (from=rx) listens; sender engine (from=tx) publishes;
//! the message must come back through on_message.
use std::sync::mpsc;
use std::time::Duration;
use tethercore::{Engine, Message, MessageHandler};
struct Collector(mpsc::Sender<Message>);
impl MessageHandler for Collector {
fn on_message(&self, msg: Message) {
let _ = self.0.send(msg);
}
fn on_status(&self, connected: bool) {
eprintln!("[rx] connected={connected}");
}
fn on_file(&self, _name: String, _mime: String, _data: Vec<u8>) {}
}
fn main() {
let server = std::env::args()
.nth(1)
.unwrap_or_else(|| "http://192.168.11.233:8765".into());
let room = "rust-spike".to_string();
let (tx, rx) = mpsc::channel();
let sub = Engine::new(server.clone(), room.clone(), "rx".into(), "rust-rx".into(), "Rust RX".into(), "".into());
sub.start(Box::new(Collector(tx)));
// Let the SSE subscription establish before publishing.
std::thread::sleep(Duration::from_millis(800));
let payload = "hello-from-rust-core";
let pubr = Engine::new(server, room, "tx".into(), "rust-tx".into(), "Rust TX".into(), "".into());
pubr.send(payload.to_string());
match rx.recv_timeout(Duration::from_secs(5)) {
Ok(m) if m.text == payload => {
println!("✅ round-trip OK: kind={} from={} source={} ts={}\n text={:?}",
m.kind, m.from, m.source, m.ts, m.text);
}
Ok(m) => {
println!("⚠️ received unexpected message: {m:?}");
std::process::exit(1);
}
Err(_) => {
println!("❌ timed out — no message received (is the server up at the URL?)");
std::process::exit(1);
}
}
sub.stop();
}