johnandersen777.bsky.social/atlibs-atirc
atirc channels in a browser page: WebRTC to atirc mesh nodes, no server, no token
0
0
// ATIRC_BIN=<atirc> ATP_USERNAME=<handle> ATP_PASSWORD=<app password> deno run -A test/live.ts
//
// The account's real mesh: this host runs its own node (`atirc mesh run`, the bridge), headless Chrome
// (test/web/live.html) joins the owner's channel over WebRTC, asks the first agent it sees, reads the answer.
// The page's PDS writes go through an app-password session here; its records are deleted on exit.
const ROOT = new URL("..", import.meta.url).pathname;
const BIN = Deno.env.get("ATIRC_BIN") ?? "atirc";
const CHROME = Deno.env.get("CHROME") ?? (Deno.build.os === "darwin" ? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" : "google-chrome");
const HANDLE = Deno.env.get("ATP_USERNAME") ?? "";
const PASSWORD = Deno.env.get("ATP_PASSWORD") ?? "";
const ASK = Deno.env.get("ASK") ?? "agents: please run uname -m and reply with its output";
const CHANNEL = Deno.env.get("CHANNEL") ?? "";
// Identities of this account the node (and page) should dial: e.g. a VM agent whose owner graph roots elsewhere.
const PEERS = (Deno.env.get("PEERS") ?? "").split(",").filter(Boolean);
if (!HANDLE || !PASSWORD) throw new Error("set ATP_USERNAME and ATP_PASSWORD");
const json = async (response: Response) => {
if (!response.ok) throw new Error(`${response.url}: ${response.status} ${await response.text()}`);
return response.json();
};
const pdsCache = new Map<string, Promise<string>>();
const pdsOf = (did: string) => {
if (!pdsCache.has(did)) {
const url = did.startsWith("did:web:") ? `https://${did.slice(8)}/.well-known/did.json` : `https://plc.directory/${did}`;
pdsCache.set(did, fetch(url).then(json).then((doc) => doc.service.find((s: { id: string }) => s.id.endsWith("#atproto_pds")).serviceEndpoint));
}
return pdsCache.get(did)!;
};
const did = (await json(await fetch(`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${HANDLE}`))).did as string;
const pds = await pdsOf(did);
const session = await json(await fetch(`${pds}/xrpc/com.atproto.server.createSession`, {
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ identifier: HANDLE, password: PASSWORD }),
}));
const write = (method: string, body: Record<string, unknown>) =>
fetch(`${pds}/xrpc/com.atproto.repo.${method}`, {
method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${session.accessJwt}` }, body: JSON.stringify({ ...body, repo: did }),
});
const written = new Set<string>();
let resolveResult: (value: unknown) => void;
const result = new Promise((resolve) => (resolveResult = resolve));
const server = Deno.serve({ hostname: "127.0.0.1", port: 0, onListen: () => {} }, async (req) => {
const url = new URL(req.url);
if (url.pathname === "/xrpc/com.atproto.repo.getRecord") {
const repo = url.searchParams.get("repo") ?? "";
const response = await fetch(`${await pdsOf(repo)}/xrpc/com.atproto.repo.getRecord${url.search}`);
return new Response(response.body, { status: response.status, headers: { "content-type": "application/json" } });
}
if (url.pathname === "/xrpc/com.atproto.repo.putRecord" || url.pathname === "/xrpc/com.atproto.repo.deleteRecord") {
const body = await req.json();
const method = url.pathname.split(".").pop() as string;
if (method === "putRecord") written.add(`${body.collection}/${body.rkey}`);
else written.delete(`${body.collection}/${body.rkey}`);
const response = await write(method, body);
return new Response(response.body, { status: response.status, headers: { "content-type": "application/json" } });
}
if (url.pathname === "/log") {
console.error(`${new Date().toISOString()} page: ${await req.text()}`);
return new Response("{}");
}
if (url.pathname === "/result") {
resolveResult(await req.json());
return new Response("{}");
}
if (url.pathname.includes("..") || !/^\/(js|node_modules|test\/web)\//.test(url.pathname)) return new Response("not found", { status: 404 });
try {
const type = url.pathname.endsWith(".html") ? "text/html" : "text/javascript";
return new Response(await Deno.readFile(`${ROOT}${url.pathname.slice(1)}`), { headers: { "content-type": type } });
} catch {
return new Response("not found", { status: 404 });
}
});
const tmp = await Deno.makeTempDir({ prefix: "atlibs-atirc-live-" });
const children: Deno.ChildProcess[] = [];
const start = (args: string[], env: Record<string, string>, log: string) => {
const child = new Deno.Command(args[0], { args: args.slice(1), env, stdin: "null", stdout: "null", stderr: "piped" }).spawn();
children.push(child);
const file = Deno.openSync(`${tmp}/${log}`, { write: true, create: true });
(async () => {
for await (const chunk of child.stderr) file.writeSync(chunk);
})().catch(() => {});
return child;
};
let failed = false;
try {
// The owner's own node: publishes computer.socialweb.peer/<handle> + its listen record, bridges the page.
start([BIN, "mesh", "run", ...PEERS.flatMap((p) => ["--peer", p])], { ATIRC_CONFIG_DIR: `${tmp}/node`, ATP_USERNAME: HANDLE, ATP_PASSWORD: PASSWORD, RUST_LOG: Deno.env.get("RUST_LOG") ?? "info" }, "node.log");
for (let i = 0; i < 300 && !(await Deno.stat(`${tmp}/node/mesh.json`).then(() => true, () => false)); i++) await new Promise((r) => setTimeout(r, 100));
const page = `http://127.0.0.1:${server.addr.port}/test/web/live.html?${new URLSearchParams({ did, handle: HANDLE, ask: ASK, channel: CHANNEL, peers: PEERS.join(",") })}`;
start([CHROME, "--headless=new", `--user-data-dir=${tmp}/chrome`, "--no-first-run", "--no-default-browser-check", "--disable-gpu", page], {}, "chrome.log");
const outcome = await Promise.race([result, new Promise((_, reject) => setTimeout(() => reject(new Error("the page reported nothing in 660 s")), 660000))]) as
{ ok: boolean; error?: string };
if (!outcome.ok) throw new Error(`page: ${outcome.error}`);
console.log(`ok atlibs-atirc live: ${JSON.stringify(outcome)}`);
} catch (error) {
failed = true;
console.error(`FAIL atlibs-atirc live: ${error instanceof Error ? error.message : error}`);
console.error(`--- node.log\n${(await Deno.readTextFile(`${tmp}/node.log`).catch(() => "")).slice(-4000)}`);
} finally {
for (const child of children) {
try {
child.kill("SIGTERM");
} catch { /* exited */ }
}
await new Promise((r) => setTimeout(r, 1000));
// The node's records and any offer the page left.
for (const key of [...written, `computer.socialweb.peer/${HANDLE}`, `computer.socialweb.ingress.listen/${HANDLE}`]) {
const [collection, rkey] = key.split("/");
await write("deleteRecord", { collection, rkey }).catch(() => {});
}
await server.shutdown();
await Deno.remove(tmp, { recursive: true }).catch(() => {});
}
Deno.exit(failed ? 1 : 0);