clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

release/0.5.0
lofi-node / tests / tunnel_iroh_test.ts
tunnel_iroh_test.ts 3.7K
1// Integration: a WebSocket carried end-to-end over a real iroh connection2// between two in-process endpoints (vendored iroh-js addon). Skips with a3// visible marker when the addon is not built. Sanitizers off: the addon's4// tokio runtime and QUIC endpoints outlive the test body.56import { assertEquals } from "@std/assert";7import { loadIrohAddon } from "../src/native/addon.ts";8import { IrohNode } from "../src/iroh/node.ts";9import { resolveIrohLib } from "../src/native/loader.ts";10import { runTunnelAcceptor, startTunnelListener } from "../src/tunnel.ts";1112const available = resolveIrohLib().status === "ok";1314Deno.test({15 name: "ws echo through the iroh tunnel (dialer ws → iroh → acceptor ws)",16 ignore: !available,17 sanitizeOps: false,18 sanitizeResources: false,19 fn: async () => {20 const addon = await loadIrohAddon();2122 // Stand-in for the acceptor's local JazzServer: a WS echo server.23 const echo = Deno.serve({ hostname: "127.0.0.1", port: 0, onListen: () => {} }, (req) => {24 const { socket, response } = Deno.upgradeWebSocket(req);25 socket.addEventListener("message", (ev) => socket.send(ev.data));26 return response;27 });28 const echoUrl = `ws://127.0.0.1:${(echo.addr as Deno.NetAddr).port}`;2930 const acceptorNode = await IrohNode.open(addon, crypto.getRandomValues(new Uint8Array(32)));31 const dialerNode = await IrohNode.open(addon, crypto.getRandomValues(new Uint8Array(32)));32 const acceptor = runTunnelAcceptor(acceptorNode, echoUrl);3334 const ticket = await acceptorNode.ticket();35 const listener = startTunnelListener(dialerNode, ticket);3637 const client = new WebSocket(`ws://127.0.0.1:${listener.port}/sync?key=test`);38 client.binaryType = "arraybuffer";39 const received: (string | Uint8Array)[] = [];40 const gotBoth = new Promise<void>((resolve, reject) => {41 const timer = setTimeout(() => reject(new Error("echo timeout (15s)")), 15_000);42 client.addEventListener("message", (ev) => {43 received.push(44 typeof ev.data === "string" ? ev.data : new Uint8Array(ev.data as ArrayBuffer),45 );46 if (received.length === 2) {47 clearTimeout(timer);48 resolve();49 }50 });51 client.addEventListener("error", () => {52 clearTimeout(timer);53 reject(new Error("client ws error"));54 });55 });56 await new Promise<void>((resolve, reject) => {57 client.addEventListener("open", () => resolve());58 client.addEventListener("close", () => reject(new Error("closed before open")));59 });60 client.send("ping over iroh");61 client.send(new Uint8Array([1, 2, 3, 4]).buffer);62 await gotBoth;6364 assertEquals(received[0], "ping over iroh");65 assertEquals(received[1], new Uint8Array([1, 2, 3, 4]));6667 client.close(1000, "done");68 await new Promise((r) => setTimeout(r, 300));69 acceptor.close();70 await listener.close();71 await echo.shutdown();72 await dialerNode.close();73 await acceptorNode.close();74 },75});7677Deno.test("release artifacts manifest matches deno.json version and prebuilt digests", async () => {78 const { RELEASE_ARTIFACTS } = await import("../src/native/artifacts.ts");79 const denoJson = JSON.parse(await Deno.readTextFile(new URL("../deno.json", import.meta.url)));80 assertEquals(81 RELEASE_ARTIFACTS.version,82 denoJson.version,83 "run `deno task release:artifacts` after a version bump",84 );85 for (const [platform, asset] of Object.entries(RELEASE_ARTIFACTS.assets)) {86 assertEquals(asset.sha256.length, 64, `${platform} digest is sha256 hex`);87 assertEquals(88 /^libnumber0_iroh-[a-z0-9_]+-[a-z-]+\.(dylib|so)$/.test(asset.file),89 true,90 `${platform} asset name shape: ${asset.file}`,91 );92 }93});