@minip2p/web 0.0.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +107 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +480 -0
- package/dist/io.js.map +1 -0
- package/dist/manual.d.ts +10 -0
- package/dist/manual.d.ts.map +1 -0
- package/dist/manual.js +10 -0
- package/dist/manual.js.map +1 -0
- package/dist/sdk.d.ts +35 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +191 -0
- package/dist/sdk.js.map +1 -0
- package/dist/wasm.d.ts +18 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +37 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +75 -4
- package/src/index.ts +19 -0
- package/src/io.ts +601 -0
- package/src/manual.ts +17 -0
- package/src/sdk.ts +237 -0
- package/src/wasm.ts +48 -0
- package/wasm/minip2p_web.d.ts +241 -0
- package/wasm/minip2p_web.js +1100 -0
- package/wasm/minip2p_web_bg.wasm +0 -0
- package/wasm/minip2p_web_bg.wasm.d.ts +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deepso
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @minip2p/web
|
|
2
|
+
|
|
3
|
+
The browser package for [minip2p](https://minip2p.com): minip2p compiled to WebAssembly, dialing native nodes over libp2p WebTransport, with the same `@minip2p/core` API as `@minip2p/node`. It is ESM-only.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Minip2p, generateSecretKey } from "@minip2p/web";
|
|
7
|
+
|
|
8
|
+
const endpoint = Minip2p.create({
|
|
9
|
+
protocols: ["/minip2p/echo/1"],
|
|
10
|
+
secretKey: generateSecretKey(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// A native node's WebTransport address, certhashes and peer ID included.
|
|
14
|
+
const path = await endpoint.connect(
|
|
15
|
+
"/ip4/192.0.2.1/udp/4001/quic-v1/webtransport/certhash/uEi…/certhash/uEi…/p2p/12D3KooW…"
|
|
16
|
+
);
|
|
17
|
+
console.log(await endpoint.ping(path.peerId));
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A native node serves browsers by listening on a `/…/quic-v1/webtransport` address beside (or instead of) its `/quic-v1` one; its `listenAddrs()` then include the full address to hand to the browser. The [browser example](https://github.com/deepso7/minip2p/tree/main/examples/web) runs this against `minip2p-peer`.
|
|
21
|
+
|
|
22
|
+
## Loading the wasm
|
|
23
|
+
|
|
24
|
+
- **`@minip2p/web`** loads its `.wasm` (resolved from `import.meta.url`, so bundlers emit it as an asset) with top-level `await`. Once the import resolves, `Minip2p.create` is synchronous, as in Node. Bundlers need an `es2022` target for the top-level `await`; Vite also needs `optimizeDeps.exclude: ["@minip2p/web"]` so pre-bundling keeps the package next to its `.wasm`.
|
|
25
|
+
- **`@minip2p/web/manual`** exports the same API plus `initMinip2p({ wasm })` and fetches nothing at import: serve the `.wasm` yourself (a CDN, a strict CSP) and pass a URL, a `Response`, its bytes or a compiled `WebAssembly.Module`. `Minip2p.create` throws until `initMinip2p` has resolved.
|
|
26
|
+
|
|
27
|
+
## What works in a browser
|
|
28
|
+
|
|
29
|
+
Dialing native nodes at `/webtransport/certhash` addresses, Identify, ping, custom protocol streams with backpressure, gossipsub, and relay reservations with circuits to and from other browsers and native nodes. The page must be a secure context (HTTPS or `localhost`). Chrome and Firefox are supported; Safari 26.4+ is best-effort.
|
|
30
|
+
|
|
31
|
+
`Minip2p.create` throws `InvalidConfigError`, with the reason, for what a browser cannot do:
|
|
32
|
+
|
|
33
|
+
| Option | Why |
|
|
34
|
+
| ---------------- | --------------------------------------------- |
|
|
35
|
+
| `listen` | Browsers cannot listen. |
|
|
36
|
+
| `mdns` | Browsers have no multicast sockets. |
|
|
37
|
+
| `discovery` | Signed discovery is not in the browser build. |
|
|
38
|
+
| `autonatServers` | Browsers cannot be dialed back. |
|
|
39
|
+
|
|
40
|
+
Other limits:
|
|
41
|
+
|
|
42
|
+
- Only WebTransport addresses with certhashes dial; a `/quic-v1` or `/tcp` candidate fails as a candidate. There is no CA-certificate mode.
|
|
43
|
+
- `listenAddrs()` is always empty (a reservation is `circuitAddress`), `knownPeers()` is empty and `reachability()` stays `unknown`.
|
|
44
|
+
- `close()` closes every session at once with WebTransport's close capsule; its timeout is ignored. `setActive()` is ignored as well: the page's own timers drive the endpoint.
|
|
45
|
+
- A page on a public origin that dials a private address may trigger Chrome's Local Network Access prompt.
|
|
46
|
+
- A trapped wasm module (a Rust panic) fails the endpoint with `DriverFailedError`; its WebTransport sessions are closed from TypeScript, `close()` still resolves (`Failed`), and later calls throw `ClosedError`. Create a new endpoint to carry on.
|
|
47
|
+
|
|
48
|
+
## Relays
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
rustup target add wasm32-unknown-unknown
|
|
56
|
+
cargo install wasm-bindgen-cli --version 0.2.128
|
|
57
|
+
pnpm wasm:build # crates/web → wasm/
|
|
58
|
+
pnpm test # unit tests (runs wasm:build first)
|
|
59
|
+
pnpm test:e2e # Playwright: Chromium and Firefox against native nodes
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`wasm:build` regenerates `wasm/minip2p_web.js` and its `.d.ts`, the checked-in wasm-bindgen glue; commit them, CI fails when they are stale. The `.wasm` is a build output. The e2e suite needs `pnpm build:bindings`, the Node addon (`pnpm --filter @minip2p/node native:build`), the relay-server example (`cargo build -p minip2p-relay-server-example`) and `npx playwright install chromium firefox`.
|
|
63
|
+
|
|
64
|
+
`src/io.ts` is `JsIo`, the `WebTransportIo` seam the Rust client drives over the browser's `WebTransport`; [`crates/web`](https://github.com/deepso7/minip2p/tree/main/crates/web) is the Rust side.
|
|
65
|
+
|
|
66
|
+
The release workflow publishes the package with npm trusted publishing. Before the first release that includes it, an owner of the `@minip2p` scope runs `scripts/npm-trusted-publisher-web.sh` once to register the publisher.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@minip2p/web`: minip2p for the browser, with the same API as
|
|
3
|
+
* `@minip2p/node`. Importing it loads the wasm module (the `.wasm` beside
|
|
4
|
+
* the package, resolved from `import.meta.url`) with top-level `await`, so
|
|
5
|
+
* `Minip2p.create` is ready to call. Use `@minip2p/web/manual` to load the
|
|
6
|
+
* `.wasm` yourself.
|
|
7
|
+
*/
|
|
8
|
+
export * from "@minip2p/core";
|
|
9
|
+
export { Minip2p, circuitAddress, generateSecretKey, peerIdFromSecretKey, } from "./sdk.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,cAAc,eAAe,CAAC;AAC9B,OAAO,EACL,OAAO,EACP,cAAc,EACd,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@minip2p/web`: minip2p for the browser, with the same API as
|
|
3
|
+
* `@minip2p/node`. Importing it loads the wasm module (the `.wasm` beside
|
|
4
|
+
* the package, resolved from `import.meta.url`) with top-level `await`, so
|
|
5
|
+
* `Minip2p.create` is ready to call. Use `@minip2p/web/manual` to load the
|
|
6
|
+
* `.wasm` yourself.
|
|
7
|
+
*/
|
|
8
|
+
import { initMinip2p } from "./sdk.js";
|
|
9
|
+
await initMinip2p();
|
|
10
|
+
export * from "@minip2p/core";
|
|
11
|
+
export { Minip2p, circuitAddress, generateSecretKey, peerIdFromSecretKey, } from "./sdk.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,EAAE,CAAC;AAEpB,cAAc,eAAe,CAAC;AAC9B,OAAO,EACL,OAAO,EACP,cAAc,EACd,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,UAAU,CAAC"}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `JsIo`: the `WebTransportIo` seam of `minip2p-webtransport` over the
|
|
3
|
+
* browser's `WebTransport` API. Rust owns the protocol and calls these
|
|
4
|
+
* methods synchronously; every outcome the browser learns later (a session
|
|
5
|
+
* coming up, a read, a write resolving, a reset) is queued as an
|
|
6
|
+
* {@link IoEvent} and handed back by {@link JsIo.pollEvent}, and `wake`
|
|
7
|
+
* asks the host to pump.
|
|
8
|
+
*
|
|
9
|
+
* The rules Rust relies on:
|
|
10
|
+
*
|
|
11
|
+
* - `write` copies what it accepts before returning, so a queued browser
|
|
12
|
+
* write never views wasm memory, and caps each stream's in-flight bytes;
|
|
13
|
+
* each accepted write reports exactly one `writeCompleted` with its own
|
|
14
|
+
* length, and a write the browser rejects reports nothing.
|
|
15
|
+
* - Reads are pulled: `grantRead` is additive, at most one read is
|
|
16
|
+
* outstanding per stream, and `data` never exceeds what was granted.
|
|
17
|
+
* - Handles are never reused, and events for a reset stream or an ended
|
|
18
|
+
* session are dropped. A stream whose read side ended and whose write
|
|
19
|
+
* side finished (every accepted write reported first) or was stopped is
|
|
20
|
+
* retired, so finished streams hold nothing.
|
|
21
|
+
* - Stream error codes are WebTransport application codes cut to `u8`
|
|
22
|
+
* (minip2p only sends 0); a missing code is 0.
|
|
23
|
+
* - Only `connect`, `openStream` and `write` may throw (Rust catches those
|
|
24
|
+
* as `IoError`s). An exception escaping any other method would unwind
|
|
25
|
+
* through Rust and leave the `WebEndpoint` borrowed for good, so they
|
|
26
|
+
* swallow everything.
|
|
27
|
+
*/
|
|
28
|
+
/** In-flight write bytes each stream may hold in the browser. */
|
|
29
|
+
export declare const MAX_IN_FLIGHT_BYTES: number;
|
|
30
|
+
/** One outcome for Rust, the shape `crates/web/src/io.rs` decodes. */
|
|
31
|
+
export type IoEvent = {
|
|
32
|
+
readonly kind: "sessionReady";
|
|
33
|
+
readonly session: number;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "sessionClosed";
|
|
36
|
+
readonly session: number;
|
|
37
|
+
readonly code: number;
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: "sessionFailed";
|
|
41
|
+
readonly session: number;
|
|
42
|
+
readonly reason: string;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "streamOpened";
|
|
45
|
+
readonly stream: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: "incomingStream";
|
|
48
|
+
readonly session: number;
|
|
49
|
+
readonly stream: number;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: "data";
|
|
52
|
+
readonly stream: number;
|
|
53
|
+
readonly bytes: Uint8Array;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "fin";
|
|
56
|
+
readonly stream: number;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "reset";
|
|
59
|
+
readonly stream: number;
|
|
60
|
+
readonly code: number;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "stopSending";
|
|
63
|
+
readonly stream: number;
|
|
64
|
+
readonly code: number;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: "writeCompleted";
|
|
67
|
+
readonly stream: number;
|
|
68
|
+
readonly bytes: number;
|
|
69
|
+
};
|
|
70
|
+
/** The part of a browser `WebTransport` that `JsIo` uses. */
|
|
71
|
+
export type WebTransportSession = Pick<WebTransport, "close" | "closed" | "createBidirectionalStream" | "incomingBidirectionalStreams" | "ready">;
|
|
72
|
+
/** Opens one session; the browser's `new WebTransport(url, options)`. */
|
|
73
|
+
export type OpenSession = (url: string, options: WebTransportOptions) => WebTransportSession;
|
|
74
|
+
/** The browser `WebTransportIo` behind one `WebEndpoint`. */
|
|
75
|
+
export declare class JsIo {
|
|
76
|
+
#private;
|
|
77
|
+
/**
|
|
78
|
+
* `wake` runs whenever an event is queued; `openSession` defaults to the
|
|
79
|
+
* global `WebTransport`.
|
|
80
|
+
*/
|
|
81
|
+
constructor(wake: () => void, openSession?: OpenSession | undefined);
|
|
82
|
+
/** Starts a session trusting exactly the certificates `certhashes` name. */
|
|
83
|
+
connect(url: string, certhashes: Uint8Array<ArrayBuffer>[]): number;
|
|
84
|
+
/** Opens a bidirectional stream; `streamOpened` follows. */
|
|
85
|
+
openStream(sessionId: number): number;
|
|
86
|
+
/** Copies and queues a prefix of `data`; a short write is backpressure. */
|
|
87
|
+
write(streamId: number, data: Uint8Array): number;
|
|
88
|
+
/** Finishes the write side after every accepted write. */
|
|
89
|
+
closeWrite(streamId: number): void;
|
|
90
|
+
/** Resets the stream both ways (`abort()` and `cancel()`). */
|
|
91
|
+
reset(streamId: number, code: number): void;
|
|
92
|
+
/** Asks the peer to stop sending (`cancel()` only). */
|
|
93
|
+
stop(streamId: number, code: number): void;
|
|
94
|
+
/** Lets the stream deliver up to `bytes` more. */
|
|
95
|
+
grantRead(streamId: number, bytes: number): void;
|
|
96
|
+
/** Closes the session with a close capsule, as `WebTransport.close()` does. */
|
|
97
|
+
close(sessionId: number, code: number, reason: string): void;
|
|
98
|
+
/** The next queued event, if any. */
|
|
99
|
+
pollEvent(): IoEvent | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Closes every session and forgets everything, for an owner that can no
|
|
102
|
+
* longer drive Rust (a trapped wasm module): no later event or wake, and
|
|
103
|
+
* `connect` throws. Idempotent; called from TypeScript, not Rust.
|
|
104
|
+
*/
|
|
105
|
+
dispose(): void;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"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"}
|