Files
tether/core/web/index.html
Patrick Ecord 3d4af095b1 wasm: browser↔native clipboard sync works over iroh
Two root causes kept the wasm web client from syncing with native peers,
both fixed here:

1. Gossip island — a node that registered with the rendezvous first joined
   its gossip topic alone and never pulled later-joining peers into its
   active view (its single subscribe_and_join was done, so a browser that
   appeared afterwards had a live magicsock path but no gossip neighbor).
   The re-register loop now seeds peer relay addrs into the MemoryLookup and
   calls GossipSender::join_peers each round, so neighbors form both ways.

2. Engine::send panicked on wasm — directly_covered() calls Instant::now(),
   which traps ("time not implemented") on wasm32-unknown-unknown, so send
   threw before ever reaching the gossip broadcast (0 Broadcast commands).
   web-time backs Instant with Performance.now() on wasm; native keeps
   std::time and is unaffected (web-time isn't linked there).

Supporting connectivity: the rendezvous now stores + returns each peer's
iroh relay URL (PeerInfo{id,relay}) so a browser — relay-only, can't resolve
id→addr via discovery — can dial natives via a seeded MemoryLookup; CORS
added so the browser's fetch to the rendezvous is allowed.

Verified live, cross-relay: text typed in a browser tab lands on the Mac
clipboard and vice-versa, with the wasm engine running as a relay-only iroh
peer. Browser-console tracing capped at INFO; demo send wrapped in try/catch.

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

81 lines
2.9 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>tether — web (wasm)</title>
<style>
body { font: 15px/1.5 system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
input, button, textarea { font: inherit; padding: .4rem .5rem; }
.row { display: flex; gap: .5rem; margin: .5rem 0; }
.row input { flex: 1; }
#feed { width: 100%; height: 12rem; }
#status { color: #666; font-size: 13px; }
.dot { display:inline-block;width:10px;height:10px;border-radius:50%;background:#bbb;vertical-align:middle }
.dot.on { background:#2ecc40 }
</style>
</head>
<body>
<h1>tether <small>· web</small> <span id="dot" class="dot"></span></h1>
<p>The same Rust core as the native apps, compiled to wasm — a relay-only iroh
peer in the browser. Point it at your rendezvous and account.</p>
<div class="row"><input id="server" value="https://tether.pecord.io" placeholder="rendezvous URL" /></div>
<div class="row">
<input id="account" value="you@example.com" placeholder="account" />
<button id="connect">Connect</button>
</div>
<div id="status">not connected</div>
<textarea id="feed" readonly placeholder="inbound clipboards appear here…"></textarea>
<div class="row">
<input id="msg" placeholder="type and Send to the room…" />
<button id="send" disabled>Send</button>
</div>
<script type="module">
import init, { WasmEngine, roomForAccount } from "./pkg/tethercore.js";
const $ = (id) => document.getElementById(id);
let engine = null;
$("connect").onclick = async () => {
await init();
const server = $("server").value.trim();
const account = $("account").value.trim();
const room = roomForAccount(account);
const from = "web-" + Math.random().toString(36).slice(2, 10);
engine = new WasmEngine(server, room, from, account);
window.engine = engine; // expose for debugging
engine.start((text) => {
$("feed").value += "← " + text + "\n";
$("feed").scrollTop = $("feed").scrollHeight;
});
$("status").textContent = `connecting… room=${room} from=${from}`;
$("dot").classList.add("on");
$("send").disabled = false;
// surface the iroh id once online
const tick = setInterval(() => {
const id = engine.irohId();
if (id) { $("status").textContent = `online · room=${room} · id=${id.slice(0,16)}`; clearInterval(tick); }
}, 500);
};
$("send").onclick = () => {
const text = $("msg").value;
if (engine && text) {
try {
engine.send(text);
$("feed").value += "→ " + text + "\n"; $("msg").value = "";
} catch (e) {
console.error("send failed:", e);
}
}
};
$("msg").addEventListener("keydown", (e) => { if (e.key === "Enter") $("send").click(); });
</script>
</body>
</html>