Skip to content
minip2p
Esc
↑↓navigate↵open⌘Jpreview
On this page

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/certhash addresses dial. A /quic-v1 or /tcp candidate fails as a candidate, and there is no CA-certificate mode yet.
  • listenAddrs() is empty (a reservation is circuitAddress); knownPeers() is empty and reachability() stays unknown.
  • 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 driverFailed and later calls throw ClosedError. Create a new endpoint to carry on.

The browser example runs a Vite page against minip2p-peer.

Last updated on October 20, 2026

Was this page helpful?