Browser quickstart
Install @minip2p/web, dial a native node over WebTransport, and measure a Ping RTT.
@minip2p/web runs minip2p in the browser as WebAssembly. It dials native minip2p nodes over libp2p WebTransport and has the same API as @minip2p/node. Chrome and Firefox are supported; Safari 26.4+ is best-effort.
Install
npm install @minip2p/web
The default entry loads its .wasm with top-level await, resolved from import.meta.url. Bundlers emit it as an asset; give them an es2022 target. With Vite, also keep the package out of dependency pre-bundling:
import { defineConfig } from "vite";
export default defineConfig({
build: { target: "es2022" },
optimizeDeps: { exclude: ["@minip2p/web"] },
});
To serve the .wasm yourself (a CDN, a strict CSP), import @minip2p/web/manual instead. It loads nothing at import; await initMinip2p({ wasm }) with a URL, a Response, the bytes or a compiled module before Minip2p.create.
Give the browser a node to dial
A native node serves browsers on a /webtransport listen address. With @minip2p/node:
import { Minip2p, generateSecretKey } from "@minip2p/node";
const server = Minip2p.create({
secretKey: generateSecretKey(),
listen: ["/ip4/127.0.0.1/udp/4001/quic-v1/webtransport"],
});
console.log(server.listenAddrs()[0]);
It prints an address carrying the hashes of the node’s self-signed certificates, which the browser trusts instead of a certificate authority:
/ip4/127.0.0.1/udp/4001/quic-v1/webtransport/certhash/uEi…/certhash/uEi…/p2p/12D3KooW…
The hashes derive from the node’s key and a 13-day certificate period, so keep the key to keep the address across restarts. An address stays dialable for at least 13 days; listenAddressesChanged fires when the node rotates to the next period. A Rust node does the same with listen_on("/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"), and the relay server with --webtransport.
Connect from the page
import { Minip2p, generateSecretKey } from "@minip2p/web";
const endpoint = Minip2p.create({ secretKey: generateSecretKey() });
const path = await endpoint.connect(address, { timeoutMs: 10_000 });
await endpoint.waitPeerReady(path.peerId, { timeoutMs: 10_000 });
console.log(`ping: ${await endpoint.ping(path.peerId)} ms`);
From here, connections and streams work as on Node.js: register protocols with protocols, open streams, read with backpressure.
Serve the page from a secure context, HTTPS or localhost: WebTransport is unavailable otherwise. A page on a public origin that dials a private address may trigger Chrome’s Local Network Access prompt; production nodes are dialed on public addresses.
Relays
With relays (WebTransport addresses with certhashes) the browser holds a reservation on the relay and is reachable at its /p2p-circuit address (node.circuitAddress), from other browsers and from native nodes. Connecting by peer ID goes through the relay; connecting to a native peer’s WebTransport address races the direct dial against the relay leg, so the path is direct or relayed, never punched: browsers never hole punch. forceRelay: true skips the direct dial. activeReservation() reports the reservation.
Browser limits
Minip2p.create throws InvalidConfigError, with the reason, for what a browser cannot do:
| Option | Why |
|---|---|
listen |
Browsers cannot listen. |
mdns |
Browsers have no multicast sockets. |
discovery |
Signed discovery is not in the browser build. |
autonatServers |
Browsers cannot be dialed back. |
- Only
/webtransport/certhashaddresses dial. A/quic-v1or/tcpcandidate fails as a candidate, and there is no CA-certificate mode yet. listenAddrs()is empty (a reservation iscircuitAddress);knownPeers()is empty andreachability()staysunknown.close()closes every session at once with WebTransport’s close capsule, so its timeout bounds nothing.- If the wasm module traps (a Rust panic), the endpoint reports
driverFailedand later calls throwClosedError. Create a new endpoint to carry on.
The browser example runs a Vite page against minip2p-peer.