Iroh 1.0 transports for Cap'n Web. The packages let Node.js and browser peers run Cap'n Web RPC sessions over direct, encrypted Iroh QUIC connections.
| Package | Purpose |
|---|---|
@cbj/iroh-capnweb-js-core |
Shared ALPN and Cap'n Web session helper |
@cbj/iroh-capnweb-js-node |
Transport for native @number0/iroh connections |
@cbj/iroh-capnweb-js-browser |
Browser endpoint and transport backed by WebAssembly |
The Node and browser packages re-export every API from core, including ALPN and createIrohRpcSession. The packages are ESM-only and use capnweb 0.10 from npm. The Node transport requires Node.js 20.3 or newer; repository development uses Node.js 22.18 or newer.
Choose the transport for each peer:
npx jsr add @cbj/iroh-capnweb-js-node
npx jsr add @cbj/iroh-capnweb-js-browserWith Deno or package managers that support JSR natively, use deno add jsr:@cbj/iroh-capnweb-js-browser, pnpm add jsr:@cbj/iroh-capnweb-js-browser, or the equivalent. The Node transport uses the npm package @number0/iroh 1.0; declare it directly when your application imports Iroh classes, as the examples below do.
Connect to an endpoint ticket and create a typed remote stub:
import { createIrohRpcSession, IrohTransport } from "@cbj/iroh-capnweb-js-node";
import { ALPN } from "@cbj/iroh-capnweb-js-node";
import { Endpoint, EndpointTicket } from "@number0/iroh";
const endpoint = await Endpoint.bind({ alpns: [ALPN] });
const address = EndpointTicket.fromString(ticket).endpointAddr();
const connection = await endpoint.connect(address, ALPN);
using remote = createIrohRpcSession<RemoteApi>(new IrohTransport(connection));
await remote.ping();
await endpoint.close();The same transport handles an accepted server connection. listen() owns the endpoint accept loop and begins accepting when its first connection listener is attached:
import { ALPN, createIrohRpcSession, IrohTransport, listen } from "@cbj/iroh-capnweb-js-node";
import { Endpoint } from "@number0/iroh";
const endpoint = await Endpoint.bind({ alpns: [ALPN] });
await using server = listen(endpoint);
server.addEventListener("connection", ({ connection }) => {
createIrohRpcSession(new IrohTransport(connection), new Api());
});Disposing the server stops its accept loop and closes the endpoint and active connections.
The browser package creates its own Iroh endpoint. WebAssembly initialization is lazy and happens on the first API call:
import {
createCapnWebEndpoint,
createIrohRpcSession,
getAddressFromString,
IrohTransport,
} from "@cbj/iroh-capnweb-js-browser";
const endpoint = await createCapnWebEndpoint();
const address = await getAddressFromString(ticket);
const connection = await endpoint.connect(address);
using remote = createIrohRpcSession<RemoteApi>(new IrohTransport(connection));
await remote.ping();
await endpoint.close();The generated ESM resolves its .wasm file with import.meta.url; modern bundlers should copy it as an asset automatically. init() is available for applications that want to preload the module.
createCapnWebEndpoint() uses Iroh's production relay configuration by default. It also accepts staging, custom relay URLs, and a stable 32-byte secret key:
await createCapnWebEndpoint({ relay: "staging" });
await createCapnWebEndpoint({ relay: { urls: ["https://relay.example.com"] } });
await createCapnWebEndpoint({ secretKey });Browser endpoints can call accept() directly or use the endpoint-owning listen() API shown in the Node example. endpoint.ticket() returns a dialable endpoint ticket.
Pass an RpcTarget explicitly when the remote API needs to call back:
using camera = createIrohRpcSession<CameraApi>(new IrohTransport(connection));
const viewer = new ViewerTarget("local-viewer");
await camera.takePhoto("hello", viewer);Cap'n Web converts the target into a remote stub for that call. Disposing the remote main stub shuts down the session and aborts the transport.
- ALPN:
capnweb/iroh/1 - One bidirectional QUIC stream per RPC session
- UTF-8 Cap'n Web JSON messages
- Four-byte, unsigned, big-endian payload length
- Maximum message size: 32 MiB
Both peers must use the same ALPN and framing version.
The browser build requires the wasm32-unknown-unknown Rust target, wasm-pack 0.15.0, and Clang with a WebAssembly backend. On macOS, brew install llvm supplies a compatible compiler.
vp install
rustup target add wasm32-unknown-unknown
cargo install wasm-pack --version 0.15.0 --locked
vp exec playwright install chromium
vp run readyvp pack owns the browser package build: its prepare hook runs wasm-pack, then copies the generated .wasm asset into dist. The end-to-end suite uses real Iroh endpoints and relay networking for Node-to-Node, Browser-to-Browser, and Browser-to-Node coverage.
See RELEASING.md for the JSR release checklist.
Licensed under either the Apache License 2.0 or the MIT License, at your option.