johnandersen777.bsky.social/atlibs-atirc
atirc channels in a browser page: WebRTC to atirc mesh nodes, no server, no token
0
0
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>atlibs-atirc live</title>
<!-- The account's page against its real mesh (test/live.ts): find an agent on the owner's channel, ask it, read the answer. -->
<script type="importmap">
{
"imports": {
"age-encryption": "https://esm.sh/age-encryption@0.3.1",
"@publicdomainrelay/atlibs-webrtc": "/node_modules/@publicdomainrelay/atlibs-webrtc/js/index.js",
"@publicdomainrelay/atlibs-atirc": "/js/index.js"
}
}
</script>
<script type="module">
import * as age from "age-encryption";
import { atirc, channelOf } from "@publicdomainrelay/atlibs-atirc";
import { WEBRTC_STATE, WebrtcHost } from "@publicdomainrelay/atlibs-webrtc";
const q = new URLSearchParams(location.search);
const [did, handle, ask] = ["did", "handle", "ask"].map((k) => q.get(k));
// The mesh owner's channel (an agent account's owner is another handle).
const channel = q.get("channel") || channelOf(handle);
const post = (method, body) => fetch(`/xrpc/com.atproto.repo.${method}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
const repo = {
did,
async getRecord({ repo, collection, rkey }) {
const response = await fetch(`/xrpc/com.atproto.repo.getRecord?${new URLSearchParams({ repo, collection, rkey })}`);
return response.ok ? response.json() : null;
},
putRecord: (input) => post("putRecord", input),
deleteRecord: (input) => post("deleteRecord", input),
};
const log = (text) => fetch("/log", { method: "POST", body: text });
const until = async (check, what, ms) => {
for (const end = Date.now() + ms; Date.now() < end; await new Promise((r) => setTimeout(r, 500))) { const v = await check(); if (v) return v; }
throw new Error(`timed out waiting for ${what}`);
};
try {
// Each connection's phases, to the harness log.
const host = (options) => new WebrtcHost({ ...options, store: new (class extends EventTarget {
state = WEBRTC_STATE;
emit(patch) {
this.state = { ...this.state, ...patch };
if (patch.phase) log(`webrtc ${patch.phase}${this.state.error ? `: ${this.state.error}` : ""}`);
this.dispatchEvent(new Event("change"));
}
})() });
const peers = (q.get("peers") || "").split(",").filter(Boolean).map((subhandle) => ({ did, subhandle }));
const chat = atirc({ repo, handle, age, host, peers });
const got = [];
chat.onMessage((m) => (got.push(m), log(`${m.channel} ${m.identity}: ${m.text}`)));
chat.onPresence((p) => log(`presence ${p.event} ${p.channel} ${p.identity}`));
log("page up");
const t0 = Date.now();
await chat.join(channel);
log(`joined ${channel} in ${Date.now() - t0} ms`);
const agent = await until(async () => (await chat.peers(channel)).peers.find((p) => p.subhandle !== handle && p.subhandle.endsWith(`.${q.get("agentsOf") || handle}`)), "an agent on the channel", 300000);
const asked = Date.now();
// No nick prefix: an agent's monitor may drop lines naming itself as its own echoes.
await chat.post(channel, ask);
const answer = await until(() => got.find((m) => m.identity === agent.subhandle && Date.parse(m.ts ?? 0) >= asked - 5000 && !/online/.test(m.text)), "the agent's answer", 300000);
await fetch("/result", { method: "POST", body: JSON.stringify({ ok: true, agent: agent.subhandle, answer: answer.text, seconds: (Date.now() - asked) / 1000, peers: (await chat.peers(channel)).peers.map((p) => p.subhandle) }) });
chat.close();
} catch (error) {
await fetch("/result", { method: "POST", body: JSON.stringify({ ok: false, error: String(error?.stack ?? error) }) });
}
</script>
</head>
<body></body>
</html>