wasm: tethercore compiles for wasm32 — runtime Spawner + n0-future timers
The web client is real: `tethercore` with the full iroh stack now builds for wasm32-unknown-unknown, producing tethercore.wasm. - Runtime abstraction: a `Spawner` replaces the bare tokio runtime Handle across the Transport trait + Engine + all transports — tokio Handle on native, the browser event loop (wasm-bindgen-futures::spawn_local) on wasm. The engine no longer builds a tokio multi-thread runtime on wasm (there are no OS threads). - IrohTransport's internal tasks (re-register loop, blob fetch) route through the Spawner; its 30s timer uses n0-future (iroh's cross-platform async) instead of tokio::time, which isn't available on wasm. - iroh on wasm: default-features=false + tls-ring (ring builds for wasm with a wasm-capable clang) + iroh-gossip net; getrandom uses its wasm_js backend. - build-web.sh documents the toolchain (Homebrew LLVM clang for ring's C, the getrandom rustflag) and produces the .wasm. Native (default + iroh + all clients) is byte-unaffected and verified clean throughout. Remaining for a running web app: wasm-bindgen JS bindings + a browser shim (relay-only in-browser). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1
core/Cargo.lock
generated
1
core/Cargo.lock
generated
@@ -4858,6 +4858,7 @@ dependencies = [
|
|||||||
"iroh-blobs",
|
"iroh-blobs",
|
||||||
"iroh-gossip",
|
"iroh-gossip",
|
||||||
"mdns-sd",
|
"mdns-sd",
|
||||||
|
"n0-future",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -25,9 +25,13 @@ bytes = "1.12.0"
|
|||||||
chacha20poly1305 = "0.10.1"
|
chacha20poly1305 = "0.10.1"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
|
||||||
|
# n0-future: iroh's cross-platform async (works native + wasm) — used for the
|
||||||
|
# IrohTransport's timers so it doesn't depend on tokio's time on wasm.
|
||||||
|
n0-future = { version = "0.3", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
iroh-transport = ["dep:iroh", "dep:iroh-gossip", "dep:iroh-blobs"]
|
iroh-transport = ["dep:iroh", "dep:iroh-gossip", "dep:iroh-blobs", "dep:n0-future"]
|
||||||
|
|
||||||
# ── Native (desktop/mobile): legacy transports + UniFFI + full tokio ──
|
# ── Native (desktop/mobile): legacy transports + UniFFI + full tokio ──
|
||||||
# The hand-rolled SSE/RTC/LAN stack and its OS-socket deps live here; none of it
|
# The hand-rolled SSE/RTC/LAN stack and its OS-socket deps live here; none of it
|
||||||
|
|||||||
24
core/build-web.sh
Executable file
24
core/build-web.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Builds tethercore for the browser (wasm32). The core compiles to wasm with the
|
||||||
|
# iroh Mesh (relay-only in-browser). The NEXT step — wasm-bindgen JS bindings +
|
||||||
|
# a browser shim for the web app — is still TODO; this just produces the .wasm.
|
||||||
|
#
|
||||||
|
# Requires:
|
||||||
|
# rustup target add wasm32-unknown-unknown
|
||||||
|
# Homebrew LLVM (`brew install llvm`) for a wasm-capable clang — ring's build
|
||||||
|
# script compiles C for wasm and Apple's clang has no wasm backend.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# A clang/llvm-ar that can target wasm (override with LLVM_PREFIX if elsewhere).
|
||||||
|
LLVM="${LLVM_PREFIX:-/opt/homebrew/opt/llvm}"
|
||||||
|
export CC_wasm32_unknown_unknown="$LLVM/bin/clang"
|
||||||
|
export AR_wasm32_unknown_unknown="$LLVM/bin/llvm-ar"
|
||||||
|
# getrandom has no OS rng in the browser — use its wasm_js backend.
|
||||||
|
export RUSTFLAGS="${RUSTFLAGS:-} --cfg getrandom_backend=\"wasm_js\""
|
||||||
|
|
||||||
|
rustup target add wasm32-unknown-unknown >/dev/null 2>&1 || true
|
||||||
|
echo "▸ building tethercore for wasm32 (iroh, relay-only in-browser)…"
|
||||||
|
cargo build --target wasm32-unknown-unknown --features iroh-transport --lib "$@"
|
||||||
|
echo "✅ target/wasm32-unknown-unknown/debug/tethercore.wasm"
|
||||||
|
echo " next: wasm-bindgen bindings + a JS shim for the browser app."
|
||||||
@@ -24,9 +24,7 @@ use iroh_gossip::{
|
|||||||
proto::TopicId,
|
proto::TopicId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::runtime::Handle;
|
use crate::{Config, EventTx, MeshEvent, Message, Spawner, Transport};
|
||||||
|
|
||||||
use crate::{Config, EventTx, MeshEvent, Message, Transport};
|
|
||||||
|
|
||||||
/// Blob handles needed by `send_file` / blob downloads, set once online.
|
/// Blob handles needed by `send_file` / blob downloads, set once online.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -169,8 +167,9 @@ impl Transport for IrohTransport {
|
|||||||
true // iroh chooses direct-vs-relay itself; treat as a direct peer channel
|
true // iroh chooses direct-vs-relay itself; treat as a direct peer channel
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self: Arc<Self>, rt: Handle, running: Arc<AtomicBool>) {
|
fn start(self: Arc<Self>, rt: Spawner, running: Arc<AtomicBool>) {
|
||||||
let this = self;
|
let this = self;
|
||||||
|
let spawner = rt.clone(); // for tasks spawned inside the transport loop
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
let endpoint = match Endpoint::bind(presets::N0).await {
|
let endpoint = match Endpoint::bind(presets::N0).await {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
@@ -210,9 +209,9 @@ impl Transport for IrohTransport {
|
|||||||
let room = this.cfg.room.clone();
|
let room = this.cfg.room.clone();
|
||||||
let id = my_id.clone();
|
let id = my_id.clone();
|
||||||
let running = running.clone();
|
let running = running.clone();
|
||||||
tokio::spawn(async move {
|
spawner.spawn(async move {
|
||||||
while running.load(Ordering::SeqCst) {
|
while running.load(Ordering::SeqCst) {
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
n0_future::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||||
let _ = rendezvous_register(&server, &room, &id).await;
|
let _ = rendezvous_register(&server, &room, &id).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -245,7 +244,7 @@ impl Transport for IrohTransport {
|
|||||||
if let Ok(ann) = serde_json::from_str::<BlobAnnounce>(&m.text) {
|
if let Ok(ann) = serde_json::from_str::<BlobAnnounce>(&m.text) {
|
||||||
if let Some(blobs) = this.blobs.lock().unwrap().clone() {
|
if let Some(blobs) = this.blobs.lock().unwrap().clone() {
|
||||||
let events = this.events.clone();
|
let events = this.events.clone();
|
||||||
tokio::spawn(Self::fetch_blob(blobs, events, ann));
|
spawner.spawn(Self::fetch_blob(blobs, events, ann));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -270,7 +269,7 @@ impl Transport for IrohTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish(&self, rt: &Handle, msg: Message) {
|
fn publish(&self, rt: &Spawner, msg: Message) {
|
||||||
// Clipboard + receipts ride gossip; other kinds are local-only.
|
// Clipboard + receipts ride gossip; other kinds are local-only.
|
||||||
if !(msg.kind.is_empty() || msg.kind == "clipboard" || msg.kind == "receipt") {
|
if !(msg.kind.is_empty() || msg.kind == "clipboard" || msg.kind == "receipt") {
|
||||||
return;
|
return;
|
||||||
@@ -289,7 +288,7 @@ impl Transport for IrohTransport {
|
|||||||
|
|
||||||
/// Add the (already-sealed) bytes to the local blob store and broadcast a
|
/// Add the (already-sealed) bytes to the local blob store and broadcast a
|
||||||
/// blob announcement over gossip; peers fetch the bytes P2P by hash.
|
/// blob announcement over gossip; peers fetch the bytes P2P by hash.
|
||||||
fn send_file(&self, rt: &Handle, _id: String, name: String, mime: String, data: Vec<u8>) {
|
fn send_file(&self, rt: &Spawner, _id: String, name: String, mime: String, data: Vec<u8>) {
|
||||||
let blobs = self.blobs.lock().unwrap().clone();
|
let blobs = self.blobs.lock().unwrap().clone();
|
||||||
let sender = self.sender.lock().unwrap().clone();
|
let sender = self.sender.lock().unwrap().clone();
|
||||||
let cfg = self.cfg.clone();
|
let cfg = self.cfg.clone();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use serde_json::{json, Value};
|
|||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::runtime::Handle;
|
use crate::Spawner;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{Config, EventTx, MeshEvent, Message, Peer, Transport};
|
use crate::{Config, EventTx, MeshEvent, Message, Peer, Transport};
|
||||||
@@ -77,7 +77,7 @@ impl Transport for LanTransport {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self: Arc<Self>, rt: Handle, running: Arc<AtomicBool>) {
|
fn start(self: Arc<Self>, rt: Spawner, running: Arc<AtomicBool>) {
|
||||||
let this = self;
|
let this = self;
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
if let Err(e) = this.run(running).await {
|
if let Err(e) = this.run(running).await {
|
||||||
@@ -86,7 +86,7 @@ impl Transport for LanTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish(&self, rt: &Handle, msg: Message) {
|
fn publish(&self, rt: &Spawner, msg: Message) {
|
||||||
let peers = self.peers.clone();
|
let peers = self.peers.clone();
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
let body = match serde_json::to_vec(&msg) {
|
let body = match serde_json::to_vec(&msg) {
|
||||||
@@ -101,7 +101,7 @@ impl Transport for LanTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_file(&self, rt: &Handle, id: String, name: String, mime: String, data: Vec<u8>) {
|
fn send_file(&self, rt: &Spawner, id: String, name: String, mime: String, data: Vec<u8>) {
|
||||||
let peers = self.peers.clone();
|
let peers = self.peers.clone();
|
||||||
let events = self.events.clone();
|
let events = self.events.clone();
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
|
|||||||
@@ -24,11 +24,41 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use futures_util::StreamExt; // only the native SSE stream uses this
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::runtime::Handle;
|
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
|
/// Cross-target task spawner. Native wraps a tokio runtime `Handle`; wasm uses
|
||||||
|
/// the browser event loop via `spawn_local`. This is what lets the engine +
|
||||||
|
/// transports compile on `wasm32` (no OS threads / tokio runtime there) — call
|
||||||
|
/// `rt.spawn(fut)` the same way on both. Futures are `Send` on native (thread
|
||||||
|
/// pool) and `?Send` on wasm (single JS thread).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Spawner(tokio::runtime::Handle);
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl Spawner {
|
||||||
|
pub(crate) fn spawn<F>(&self, fut: F)
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = ()> + Send + 'static,
|
||||||
|
{
|
||||||
|
self.0.spawn(fut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Spawner;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
impl Spawner {
|
||||||
|
pub(crate) fn spawn<F>(&self, fut: F)
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = ()> + 'static,
|
||||||
|
{
|
||||||
|
wasm_bindgen_futures::spawn_local(fut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod crypto;
|
mod crypto;
|
||||||
#[cfg(feature = "iroh-transport")]
|
#[cfg(feature = "iroh-transport")]
|
||||||
mod iroh;
|
mod iroh;
|
||||||
@@ -319,17 +349,19 @@ trait Transport: Send + Sync {
|
|||||||
}
|
}
|
||||||
/// Spawn the inbound loop on `rt`; emit traffic/link/file events off the
|
/// Spawn the inbound loop on `rt`; emit traffic/link/file events off the
|
||||||
/// `EventTx` held since construction. Runs until `running` clears.
|
/// `EventTx` held since construction. Runs until `running` clears.
|
||||||
fn start(self: Arc<Self>, rt: Handle, running: Arc<AtomicBool>);
|
fn start(self: Arc<Self>, rt: Spawner, running: Arc<AtomicBool>);
|
||||||
/// Publish an outbound message. Fire-and-forget.
|
/// Publish an outbound message. Fire-and-forget.
|
||||||
fn publish(&self, rt: &Handle, msg: Message);
|
fn publish(&self, rt: &Spawner, msg: Message);
|
||||||
/// Send a file/image. Only direct transports implement this; others no-op
|
/// Send a file/image. Only direct transports implement this; others no-op
|
||||||
/// (files are never relayed).
|
/// (files are never relayed).
|
||||||
fn send_file(&self, _rt: &Handle, _id: String, _name: String, _mime: String, _data: Vec<u8>) {}
|
fn send_file(&self, _rt: &Spawner, _id: String, _name: String, _mime: String, _data: Vec<u8>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Object))]
|
#[cfg_attr(not(target_arch = "wasm32"), derive(uniffi::Object))]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
|
// Native owns a tokio runtime; wasm uses the browser event loop (no field).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
rt: Arc<tokio::runtime::Runtime>,
|
rt: Arc<tokio::runtime::Runtime>,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
transports: Vec<Arc<dyn Transport>>,
|
transports: Vec<Arc<dyn Transport>>,
|
||||||
@@ -356,6 +388,19 @@ pub struct Engine {
|
|||||||
iroh_id: Arc<Mutex<Option<String>>>,
|
iroh_id: Arc<Mutex<Option<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal (non-FFI) helpers — kept out of the uniffi::export impl so the
|
||||||
|
// non-FFI `Spawner` return type doesn't leak into the bindings.
|
||||||
|
impl Engine {
|
||||||
|
/// A task spawner for this engine — tokio runtime on native, the JS event
|
||||||
|
/// loop on wasm.
|
||||||
|
fn spawner(&self) -> Spawner {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
return Spawner(self.rt.handle().clone());
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
return Spawner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)]
|
#[cfg_attr(not(target_arch = "wasm32"), uniffi::export)]
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// `server` is the bus base URL, e.g. "http://192.168.11.233:8765".
|
/// `server` is the bus base URL, e.g. "http://192.168.11.233:8765".
|
||||||
@@ -373,6 +418,8 @@ impl Engine {
|
|||||||
name: String,
|
name: String,
|
||||||
account: String,
|
account: String,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
|
// Native runs the engine on a tokio runtime; wasm uses the JS event loop.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(2)
|
.worker_threads(2)
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -411,6 +458,7 @@ impl Engine {
|
|||||||
];
|
];
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
cfg,
|
cfg,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
rt: Arc::new(rt),
|
rt: Arc::new(rt),
|
||||||
running: Arc::new(AtomicBool::new(false)),
|
running: Arc::new(AtomicBool::new(false)),
|
||||||
transports,
|
transports,
|
||||||
@@ -446,11 +494,11 @@ impl Engine {
|
|||||||
let n = self.file_counter.fetch_add(1, Ordering::SeqCst);
|
let n = self.file_counter.fetch_add(1, Ordering::SeqCst);
|
||||||
let id = format!("{:016x}", n);
|
let id = format!("{:016x}", n);
|
||||||
let data = self.crypto.seal(&data); // E2E: chunks carry ciphertext
|
let data = self.crypto.seal(&data); // E2E: chunks carry ciphertext
|
||||||
let rt = self.rt.handle();
|
let rt = self.spawner();
|
||||||
for t in &self.transports {
|
for t in &self.transports {
|
||||||
// Direct transports only — files are never relayed over SSE.
|
// Direct transports only — files are never relayed over SSE.
|
||||||
if t.direct() {
|
if t.direct() {
|
||||||
t.send_file(rt, id.clone(), name.clone(), mime.clone(), data.clone());
|
t.send_file(&rt, id.clone(), name.clone(), mime.clone(), data.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,13 +555,13 @@ impl Engine {
|
|||||||
let receipts = self.receipts.clone();
|
let receipts = self.receipts.clone();
|
||||||
let cfg = self.cfg.clone();
|
let cfg = self.cfg.clone();
|
||||||
let transports = self.transports.clone();
|
let transports = self.transports.clone();
|
||||||
let rt = self.rt.clone();
|
let spawner = self.spawner();
|
||||||
let direct = self.direct.clone();
|
let direct = self.direct.clone();
|
||||||
let present = self.present.clone();
|
let present = self.present.clone();
|
||||||
let discovered = self.discovered.clone();
|
let discovered = self.discovered.clone();
|
||||||
let transfers = self.transfers.clone();
|
let transfers = self.transfers.clone();
|
||||||
let crypto = self.crypto.clone();
|
let crypto = self.crypto.clone();
|
||||||
self.rt.spawn(async move {
|
self.spawner().spawn(async move {
|
||||||
// File de-dup: the same file can arrive over both RTC and LAN.
|
// File de-dup: the same file can arrive over both RTC and LAN.
|
||||||
let mut file_seen: VecDeque<(String, Instant)> = VecDeque::new();
|
let mut file_seen: VecDeque<(String, Instant)> = VecDeque::new();
|
||||||
while let Some(ev) = rx.recv().await {
|
while let Some(ev) = rx.recv().await {
|
||||||
@@ -585,12 +633,11 @@ impl Engine {
|
|||||||
ts: 0,
|
ts: 0,
|
||||||
};
|
};
|
||||||
let covered = directly_covered(&direct, &present);
|
let covered = directly_covered(&direct, &present);
|
||||||
let h = rt.handle();
|
|
||||||
for t in &transports {
|
for t in &transports {
|
||||||
if covered && t.is_relay() {
|
if covered && t.is_relay() {
|
||||||
continue; // keep the ack off the relay when P2P covers
|
continue; // keep the ack off the relay when P2P covers
|
||||||
}
|
}
|
||||||
t.publish(h, ack.clone());
|
t.publish(&spawner, ack.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let h = handler.lock().unwrap().clone();
|
let h = handler.lock().unwrap().clone();
|
||||||
@@ -627,7 +674,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for t in &self.transports {
|
for t in &self.transports {
|
||||||
t.clone().start(self.rt.handle().clone(), self.running.clone());
|
t.clone().start(self.spawner(), self.running.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,12 +694,12 @@ impl Engine {
|
|||||||
ts: 0, // server stamps authoritative ts
|
ts: 0, // server stamps authoritative ts
|
||||||
};
|
};
|
||||||
let covered = self.directly_covered();
|
let covered = self.directly_covered();
|
||||||
let rt = self.rt.handle();
|
let rt = self.spawner();
|
||||||
for t in &self.transports {
|
for t in &self.transports {
|
||||||
if covered && t.is_relay() {
|
if covered && t.is_relay() {
|
||||||
continue; // direct channels reach everyone → keep content off the relay
|
continue; // direct channels reach everyone → keep content off the relay
|
||||||
}
|
}
|
||||||
t.publish(rt, msg.clone());
|
t.publish(&rt, msg.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,7 +775,7 @@ impl Transport for SseTransport {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self: Arc<Self>, rt: Handle, running: Arc<AtomicBool>) {
|
fn start(self: Arc<Self>, rt: Spawner, running: Arc<AtomicBool>) {
|
||||||
let this = self;
|
let this = self;
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
let min = Duration::from_millis(500);
|
let min = Duration::from_millis(500);
|
||||||
@@ -759,7 +806,7 @@ impl Transport for SseTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish(&self, rt: &Handle, msg: Message) {
|
fn publish(&self, rt: &Spawner, msg: Message) {
|
||||||
let http = self.http.clone();
|
let http = self.http.clone();
|
||||||
let cfg = self.cfg.clone();
|
let cfg = self.cfg.clone();
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use std::time::Duration;
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tokio::runtime::Handle;
|
use crate::Spawner;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use webrtc::api::media_engine::MediaEngine;
|
use webrtc::api::media_engine::MediaEngine;
|
||||||
@@ -485,7 +485,7 @@ impl Transport for RtcTransport {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self: Arc<Self>, rt: Handle, running: Arc<AtomicBool>) {
|
fn start(self: Arc<Self>, rt: Spawner, running: Arc<AtomicBool>) {
|
||||||
// Presence chirp — announce ourselves so peers initiate.
|
// Presence chirp — announce ourselves so peers initiate.
|
||||||
{
|
{
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
@@ -531,7 +531,7 @@ impl Transport for RtcTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publish(&self, rt: &Handle, msg: Message) {
|
fn publish(&self, rt: &Spawner, msg: Message) {
|
||||||
// The "tether" data channel carries raw clipboard text only; receipts
|
// The "tether" data channel carries raw clipboard text only; receipts
|
||||||
// and other typed messages go over SSE/LAN.
|
// and other typed messages go over SSE/LAN.
|
||||||
if !(msg.kind.is_empty() || msg.kind == "clipboard") {
|
if !(msg.kind.is_empty() || msg.kind == "clipboard") {
|
||||||
@@ -551,7 +551,7 @@ impl Transport for RtcTransport {
|
|||||||
/// Stream a file over each open data channel as binary frames:
|
/// Stream a file over each open data channel as binary frames:
|
||||||
/// M<json meta> · C<16-byte id><chunk>… · E<16-byte id>
|
/// M<json meta> · C<16-byte id><chunk>… · E<16-byte id>
|
||||||
/// The channel is reliable+ordered, so chunks reassemble in arrival order.
|
/// The channel is reliable+ordered, so chunks reassemble in arrival order.
|
||||||
fn send_file(&self, rt: &Handle, id: String, name: String, mime: String, data: Vec<u8>) {
|
fn send_file(&self, rt: &Spawner, id: String, name: String, mime: String, data: Vec<u8>) {
|
||||||
let peers = self.peers.clone();
|
let peers = self.peers.clone();
|
||||||
let events = self.events.clone();
|
let events = self.events.clone();
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
|
|||||||
Reference in New Issue
Block a user