@statewalker/httpeers-member 0.1.4
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 +203 -0
- package/dist/browser-platform.d.ts +30 -0
- package/dist/browser-platform.d.ts.map +1 -0
- package/dist/browser-platform.js +30 -0
- package/dist/browser-profile.d.ts +85 -0
- package/dist/browser-profile.d.ts.map +1 -0
- package/dist/browser-profile.js +120 -0
- package/dist/browser.d.ts +48 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +51 -0
- package/dist/connection-kind.d.ts +36 -0
- package/dist/connection-kind.d.ts.map +1 -0
- package/dist/connection-kind.js +52 -0
- package/dist/describe-error.d.ts +18 -0
- package/dist/describe-error.d.ts.map +1 -0
- package/dist/describe-error.js +72 -0
- package/dist/edge-control.d.ts +50 -0
- package/dist/edge-control.d.ts.map +1 -0
- package/dist/edge-control.js +69 -0
- package/dist/edge-dispatch.d.ts +188 -0
- package/dist/edge-dispatch.d.ts.map +1 -0
- package/dist/edge-dispatch.js +142 -0
- package/dist/edge-guard.d.ts +45 -0
- package/dist/edge-guard.d.ts.map +1 -0
- package/dist/edge-guard.js +62 -0
- package/dist/edge.d.ts +97 -0
- package/dist/edge.d.ts.map +1 -0
- package/dist/edge.js +104 -0
- package/dist/gateway.d.ts +49 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +102 -0
- package/dist/identity.d.ts +78 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +117 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/join-blob.d.ts +125 -0
- package/dist/join-blob.d.ts.map +1 -0
- package/dist/join-blob.js +195 -0
- package/dist/join.d.ts +343 -0
- package/dist/join.d.ts.map +1 -0
- package/dist/join.js +409 -0
- package/dist/kv.d.ts +20 -0
- package/dist/kv.d.ts.map +1 -0
- package/dist/kv.js +10 -0
- package/dist/mesh-config.d.ts +26 -0
- package/dist/mesh-config.d.ts.map +1 -0
- package/dist/mesh-config.js +39 -0
- package/dist/mesh-memory.d.ts +55 -0
- package/dist/mesh-memory.d.ts.map +1 -0
- package/dist/mesh-memory.js +74 -0
- package/dist/node.d.ts +29 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +42 -0
- package/dist/page-wake.d.ts +24 -0
- package/dist/page-wake.d.ts.map +1 -0
- package/dist/page-wake.js +31 -0
- package/dist/peer-request.d.ts +29 -0
- package/dist/peer-request.d.ts.map +1 -0
- package/dist/peer-request.js +11 -0
- package/dist/reset.d.ts +56 -0
- package/dist/reset.d.ts.map +1 -0
- package/dist/reset.js +103 -0
- package/dist/session.d.ts +214 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +403 -0
- package/dist/start-member.d.ts +176 -0
- package/dist/start-member.d.ts.map +1 -0
- package/dist/start-member.js +307 -0
- package/package.json +120 -0
- package/src/browser-platform.ts +53 -0
- package/src/browser-profile.ts +179 -0
- package/src/browser.ts +103 -0
- package/src/connection-kind.ts +53 -0
- package/src/describe-error.ts +76 -0
- package/src/edge-control.ts +94 -0
- package/src/edge-dispatch.ts +295 -0
- package/src/edge-guard.ts +67 -0
- package/src/edge.ts +175 -0
- package/src/gateway.ts +141 -0
- package/src/identity.ts +146 -0
- package/src/index.ts +95 -0
- package/src/join-blob.ts +229 -0
- package/src/join.ts +632 -0
- package/src/kv.ts +21 -0
- package/src/mesh-config.ts +41 -0
- package/src/mesh-memory.ts +104 -0
- package/src/node.ts +49 -0
- package/src/page-wake.ts +59 -0
- package/src/peer-request.ts +39 -0
- package/src/reset.ts +127 -0
- package/src/session.ts +579 -0
- package/src/start-member.ts +476 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikhail Kotelnikov
|
|
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,203 @@
|
|
|
1
|
+
# @statewalker/httpeers-member
|
|
2
|
+
|
|
3
|
+
Everything a participant does: hold an identity, redeem or resume, connect,
|
|
4
|
+
keep the link, advertise, discover, call, and serve.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { startMember } from "@statewalker/httpeers-member";
|
|
8
|
+
import { nodePlatform } from "@statewalker/httpeers-member/node";
|
|
9
|
+
|
|
10
|
+
const member = await startMember({
|
|
11
|
+
key: "peers",
|
|
12
|
+
mounts, // what this peer serves
|
|
13
|
+
rules, // the mesh's policy
|
|
14
|
+
config: { relayAddrs, hubPeerId },
|
|
15
|
+
platform: nodePlatform,
|
|
16
|
+
invitationId, // omit to resume an existing membership
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await member.fetch(new Request(`http://local/peers/${otherPeer}/hello`));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## One lifecycle, and a platform object with three methods
|
|
23
|
+
|
|
24
|
+
The prototype had **two** lifecycles — a 644-line `startBrowserPeer` and a Node
|
|
25
|
+
equivalent — that agreed on every step and diverged on three. `startMember` is
|
|
26
|
+
both of them, with the three differences injected:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
interface MemberPlatform {
|
|
30
|
+
createNode(init): Promise<Libp2p>; // required
|
|
31
|
+
mountEdge?(init): Promise<MemberEdge>; // a page only
|
|
32
|
+
watchWake?(onWake): () => void; // a page only
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That is the extraction's central claim, so `browser/peer-runtime.ts` was
|
|
37
|
+
deliberately **not ported**: porting it would have walked the duplication back
|
|
38
|
+
in through the front door. There is no browser peer runtime in this package,
|
|
39
|
+
only `browserPlatform()`.
|
|
40
|
+
|
|
41
|
+
| Import | Holds |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `.` | `startMember`, the session, the join blob, the gateway, the edge dispatch, identity, mesh memory |
|
|
44
|
+
| `./node` | `nodePlatform` — a value, because a Node member has nothing per-instance to build |
|
|
45
|
+
| `./browser` | `browserPlatform()`, `createSession()`, `mountEdge`, `watchPageWake`, the IndexedDB backends |
|
|
46
|
+
|
|
47
|
+
## `MemberHandle.fetch` IS the edge, on both platforms
|
|
48
|
+
|
|
49
|
+
A Node caller writes `member.fetch(...)`; a page writes
|
|
50
|
+
`fetch(`${baseUrl}${peer}/x`)` and the ServiceWorker hands it to the same
|
|
51
|
+
handler. `baseUrl` is the browser-only extra, which is why it is optional.
|
|
52
|
+
|
|
53
|
+
**The edge is a local ingress and strips the proven-peer header.** A page
|
|
54
|
+
builds the requests that enter its own edge, so a header there is a claim, not
|
|
55
|
+
a fact. `createEdgeDispatch` used to branch on "does this already have a
|
|
56
|
+
binding" to mean "it arrived from the network" — safe while that binding was a
|
|
57
|
+
WeakMap no page could write, and a forgery hole the moment it became a header.
|
|
58
|
+
The branch is gone: a network request never reaches the edge, because
|
|
59
|
+
`serveTransport` dispatches to `peer.dispatch` directly.
|
|
60
|
+
|
|
61
|
+
**A member must be told it may route its own traffic.** The edge hands
|
|
62
|
+
`/{peerId}/{path}` to the peer's router, and that router refuses to forward
|
|
63
|
+
unless asked — relaying is its own capability, deny by default. `startMember`
|
|
64
|
+
passes `forwardLocalOnly` (from `httpeers-core`), which allows what originated
|
|
65
|
+
at this edge and refuses anything that arrived from the network. Omitting it
|
|
66
|
+
does not fail to compile; it fails at runtime with `403 this peer does not
|
|
67
|
+
relay for you`, from the **caller's own** router. That was a real defect here,
|
|
68
|
+
found by standing up a live mesh.
|
|
69
|
+
|
|
70
|
+
## `mountEdge` never waits for a controller that cannot come
|
|
71
|
+
|
|
72
|
+
The edge is `webrun-http-browser`'s `SwHttpAdapter` (`^0.5.0`). A **hard
|
|
73
|
+
reload** (Ctrl+Shift+R; Firefox's `location.reload(true)`) loads the page
|
|
74
|
+
bypassing its ServiceWorker, while that worker is already active and claimed its
|
|
75
|
+
clients long ago — so no `controllerchange` ever comes. Up to 0.4 `start()`
|
|
76
|
+
then waited forever, and llm-chat's `mesh.html` shipped sitting at "Joining…
|
|
77
|
+
(mounting-edge)" for good after one.
|
|
78
|
+
|
|
79
|
+
Since 0.5 the library recovers it, and `mountEdge` relies on that:
|
|
80
|
+
|
|
81
|
+
- uncontrolled under an active worker → `start()` sends a `CLAIM` request, the
|
|
82
|
+
stock `sw-worker` (every app's `/sw.js`) answers with `clients.claim()`, and
|
|
83
|
+
the page is taken over **in place, without a reload**;
|
|
84
|
+
- a worker that does not answer `CLAIM` (an older `/sw.js` still installed in a
|
|
85
|
+
returning visitor's browser, or a custom `serviceWorkerUrl`) → after the
|
|
86
|
+
bound, **one reload** (`reloadIfUncontrolled`, kept as the last resort; the
|
|
87
|
+
library guards it with a `sessionStorage` marker, so it cannot loop);
|
|
88
|
+
- still uncontrolled after that, or no usable `sessionStorage` → throw
|
|
89
|
+
`UncontrolledPageError` ("close the tab and reopen it").
|
|
90
|
+
|
|
91
|
+
Every wait is bounded (`controlTimeoutMs`, default `DEFAULT_CONTROL_TIMEOUT_MS`
|
|
92
|
+
= 30 s, passed to the adapter as its `timeout`); a worker that never activates
|
|
93
|
+
or never answers fails with an `Error` that says what to do. `edge-control.ts`
|
|
94
|
+
maps the library's `ServiceWorkerControlError` onto those errors, matching on
|
|
95
|
+
`name`/`reason` rather than `instanceof`. `tests/edge-control.test.ts` pins the
|
|
96
|
+
mapping; `httpeers-browser-conformance`'s `pnpm run test:reload` runs first
|
|
97
|
+
visit, reload, a hard reload (asserting it recovers with no extra navigation) and
|
|
98
|
+
a second tab, then — against a worker that ignores `CLAIM` — the fallback reload
|
|
99
|
+
and a spent guard, in real Chromium and Firefox.
|
|
100
|
+
|
|
101
|
+
## Direct or relay: how a member reaches its hub
|
|
102
|
+
|
|
103
|
+
`MemberHandle.hubLink()` is `"direct"` or `"relay"`, decided once per join.
|
|
104
|
+
**Direct** is a WebRTC upgrade to the hub plus a reservation on it, which is
|
|
105
|
+
what lets other members reach this one. **Relay** is a kept, limited circuit
|
|
106
|
+
through the public relay: the member calls the hub over it and nothing else
|
|
107
|
+
can reach the member. A member lands in relay mode when
|
|
108
|
+
|
|
109
|
+
- the WebRTC upgrade fails (a hub in Docker on a bridge network), or
|
|
110
|
+
- the upgrade works but the hub **refuses the reservation for want of capacity**
|
|
111
|
+
(`RESERVATION_REFUSED`, `RESOURCE_LIMIT_EXCEEDED`) or does not answer. The
|
|
112
|
+
WebRTC link is then closed and the member continues exactly as in the first
|
|
113
|
+
case. `MemberHandle.hubLinkNote` says why, and the session shows it as the
|
|
114
|
+
live phase's note (the join widget: `Connected (relay)` plus the reason).
|
|
115
|
+
|
|
116
|
+
Before this, a full reservation store on the hub failed the whole join while a
|
|
117
|
+
relay-mode member worked. Any other refusal — `PERMISSION_DENIED` from a hub
|
|
118
|
+
that has just accepted this peer, a hub with no relay service — still fails the
|
|
119
|
+
join: those are faults to report, not conditions to route around
|
|
120
|
+
(`fallsBackToRelay` in `start-member.ts`). Relay mode does not retry the
|
|
121
|
+
upgrade; a reconnect decides again.
|
|
122
|
+
|
|
123
|
+
## `createGateway` — the mesh as ordinary HTTP
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const gateway = createGateway({ source: member, basePath: "", edgeKey: "peers" });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A `FetchHandler` dispatching `/{peerId}/{path}`, so a client with no peer
|
|
130
|
+
object, no token and no knowledge of libp2p can `fetch()` a mesh resource.
|
|
131
|
+
`GatewaySource` is structurally a subset of `MemberHandle`, so **a member is
|
|
132
|
+
one** — no adapter.
|
|
133
|
+
|
|
134
|
+
## Joining: a blob, a bare id, and one rule that must not bend
|
|
135
|
+
|
|
136
|
+
| What someone supplies | Which mesh |
|
|
137
|
+
|---|---|
|
|
138
|
+
| a join **blob** (`eyJ…`) | the one the blob names — a hub page's |
|
|
139
|
+
| a **bare invitation id** | the one the deployment's `httpeers.json` names |
|
|
140
|
+
| nothing (a resume) | the one this origin remembers, else the deployment's |
|
|
141
|
+
|
|
142
|
+
Only a **resume** consults the remembered mesh. A bare id still means the
|
|
143
|
+
deployment's, because that is the contract a Node-hub invitation travels under;
|
|
144
|
+
reversing it would silently point those joins at whatever mesh the page last
|
|
145
|
+
saw. `tests/session.test.ts` pins all three.
|
|
146
|
+
|
|
147
|
+
`invitationFromQrText` lives here rather than in `@statewalker/httpeers-qr`,
|
|
148
|
+
because it is entirely about the join format — a QR package that knew it could
|
|
149
|
+
not be used for anything else. It is a **predicate, never a parser**: it cannot
|
|
150
|
+
throw, because a live scanner that died on a malformed URL would die on the
|
|
151
|
+
first poster.
|
|
152
|
+
|
|
153
|
+
## The session, and why it is isomorphic
|
|
154
|
+
|
|
155
|
+
`createPeerSession` holds the four operator controls — join, disconnect,
|
|
156
|
+
reconnect, reset identity — and the state a page renders. It has **no DOM and
|
|
157
|
+
no browser imports**, so all fifteen of its branches are tested under Node with
|
|
158
|
+
no browser, no relay and no ServiceWorker.
|
|
159
|
+
|
|
160
|
+
That is load-bearing rather than tidy: `browserPlatform` reaches
|
|
161
|
+
`@libp2p/webrtc`, whose Node build loads a native module, so a browser default
|
|
162
|
+
inside the session would have made importing it a browser-only act. The
|
|
163
|
+
defaults live in `./browser`'s `createSession` instead.
|
|
164
|
+
|
|
165
|
+
**Disconnect is not leaving.** `stop()` drops presence and keeps membership, so
|
|
166
|
+
reconnecting needs no new invitation. `resetIdentity` is the destructive one,
|
|
167
|
+
named separately and documented with what it costs.
|
|
168
|
+
|
|
169
|
+
## The two "cannot resume" states are different, and say so
|
|
170
|
+
|
|
171
|
+
A page with no saved identity has never joined; a page whose identity the hub
|
|
172
|
+
does not recognise has had its membership revoked, or is talking to a hub that
|
|
173
|
+
was reset. Both would otherwise render as "please paste an invitation", and an
|
|
174
|
+
operator could not tell which had happened. When a resume fails *and* the
|
|
175
|
+
deployment now names a different hub, the session says so — that is almost
|
|
176
|
+
always a re-run of `pnpm bootstrap`, and saying it is the difference between a
|
|
177
|
+
two-minute fix and an hour.
|
|
178
|
+
|
|
179
|
+
## Isomorphism, measured by reachability
|
|
180
|
+
|
|
181
|
+
`tests/boundary.test.ts` walks the import closure of `index.ts` and fails if it
|
|
182
|
+
reaches `node:`, `@libp2p/tcp`, `@libp2p/webrtc`, `idb-keyval`, a ServiceWorker
|
|
183
|
+
adapter or a DOM-only global. It does **not** exempt filenames: `edge.ts` and
|
|
184
|
+
`page-wake.ts` are browser-only by construction and simply are not reachable
|
|
185
|
+
from the root, which the test asserts explicitly. Verified non-vacuous by
|
|
186
|
+
re-exporting `mountEdge` from `index.ts` and watching three named tests fail.
|
|
187
|
+
|
|
188
|
+
## Tests
|
|
189
|
+
|
|
190
|
+
**116.** `tests/edge-control.test.ts` pins the error `mountEdge` throws when
|
|
191
|
+
its ServiceWorker cannot take control. `tests/reservation-fallback.test.ts` pins which refused reservations
|
|
192
|
+
drop a join to relay mode and which fail it. `tests/consumer.test.ts` compiles a real dependent against all three
|
|
193
|
+
entry points under three tsconfig shapes — the `NodeNext` row is the only one
|
|
194
|
+
that honours the `exports` map, and for a subpath there is no legacy `types`
|
|
195
|
+
field to fall back to.
|
|
196
|
+
|
|
197
|
+
A member is also exercised for real in `@statewalker/httpeers-conformance`:
|
|
198
|
+
two live members redeem invitations from a live hub over a relay, call each
|
|
199
|
+
other, and the far side reports back a subject read out of a verified Biscuit
|
|
200
|
+
that matches the peer its handshake proved. `member-relay-fallback.test.ts` and
|
|
201
|
+
`member-reservation-fallback.test.ts` there run the two ways into relay mode
|
|
202
|
+
against a live hub: an upgrade that fails, and a hub whose reservation store is
|
|
203
|
+
full.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three things a page does differently, as one `MemberPlatform`.
|
|
3
|
+
*
|
|
4
|
+
* THIS IS WHAT THE EXTRACTION IS FOR. The prototype had two lifecycles —
|
|
5
|
+
* `startBrowserPeer` (644 lines) and a Node equivalent — that agreed on every
|
|
6
|
+
* step and diverged on three: how a node is built, whether an edge is mounted,
|
|
7
|
+
* and whether the process gets wake events. Rung 01 replaced both with
|
|
8
|
+
* `startMember` plus an injected platform, and porting `peer-runtime.ts` here
|
|
9
|
+
* would have brought the duplication back through the front door.
|
|
10
|
+
*
|
|
11
|
+
* So there is no browser peer runtime in this package. There is one lifecycle
|
|
12
|
+
* and this object.
|
|
13
|
+
*/
|
|
14
|
+
import type { MemberPlatform } from "./start-member.js";
|
|
15
|
+
export interface BrowserPlatformInit {
|
|
16
|
+
/**
|
|
17
|
+
* Where the ServiceWorker script lives. Omitting it is not an option the
|
|
18
|
+
* edge survives — see `mountEdge`.
|
|
19
|
+
*/
|
|
20
|
+
serviceWorkerUrl?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Relax libp2p's dial gater for loopback and private addresses.
|
|
23
|
+
*
|
|
24
|
+
* A page dialling `127.0.0.1` is a development setup, and the default gater
|
|
25
|
+
* refuses it silently. Off in production, where refusing is right.
|
|
26
|
+
*/
|
|
27
|
+
dev?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function browserPlatform(init?: BrowserPlatformInit): MemberPlatform;
|
|
30
|
+
//# sourceMappingURL=browser-platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-platform.d.ts","sourceRoot":"","sources":["../src/browser-platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,mBAAwB,GAAG,cAAc,CAiB9E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three things a page does differently, as one `MemberPlatform`.
|
|
3
|
+
*
|
|
4
|
+
* THIS IS WHAT THE EXTRACTION IS FOR. The prototype had two lifecycles —
|
|
5
|
+
* `startBrowserPeer` (644 lines) and a Node equivalent — that agreed on every
|
|
6
|
+
* step and diverged on three: how a node is built, whether an edge is mounted,
|
|
7
|
+
* and whether the process gets wake events. Rung 01 replaced both with
|
|
8
|
+
* `startMember` plus an injected platform, and porting `peer-runtime.ts` here
|
|
9
|
+
* would have brought the duplication back through the front door.
|
|
10
|
+
*
|
|
11
|
+
* So there is no browser peer runtime in this package. There is one lifecycle
|
|
12
|
+
* and this object.
|
|
13
|
+
*/
|
|
14
|
+
import { createBrowserNode } from "./browser-profile.js";
|
|
15
|
+
import { mountEdge } from "./edge.js";
|
|
16
|
+
import { watchPageWake } from "./page-wake.js";
|
|
17
|
+
export function browserPlatform(init = {}) {
|
|
18
|
+
return {
|
|
19
|
+
createNode: async ({ privateKey }) => createBrowserNode({ privateKey, dev: init.dev ?? false }),
|
|
20
|
+
// A PAGE ONLY. `MemberHandle.fetch` is the edge on both platforms; what a
|
|
21
|
+
// page additionally gets is a same-origin URL a plain `fetch()` reaches it
|
|
22
|
+
// through, which is what `baseUrl` is and why it is optional on the handle.
|
|
23
|
+
mountEdge: async ({ key, dispatch }) => mountEdge({ key, dispatch, serviceWorkerUrl: init.serviceWorkerUrl }),
|
|
24
|
+
// A PAGE ONLY. A Node process gets no wake events, so there the
|
|
25
|
+
// supervisor's backstop timer is the whole story. A tab that was suspended
|
|
26
|
+
// and is now visible again should re-check its hub link immediately rather
|
|
27
|
+
// than wait out a backoff the user can feel.
|
|
28
|
+
watchWake: (onWake) => watchPageWake(onWake),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Ed25519PrivateKey, Libp2p } from "@libp2p/interface";
|
|
2
|
+
import { type IsMember } from "@statewalker/httpeers-libp2p";
|
|
3
|
+
/**
|
|
4
|
+
* Re-exported from `./identity.ts`, which is where they live now -- see
|
|
5
|
+
* this module's own note on the move. A page that imports
|
|
6
|
+
* `IDENTITY_STORAGE_KEY` from here keeps working.
|
|
7
|
+
*/
|
|
8
|
+
export { clearIdentity, decodeIdentity, encodeIdentity, IDENTITY_STORAGE_KEY, loadOrCreateIdentity, peerIdOf, } from "./identity.js";
|
|
9
|
+
/**
|
|
10
|
+
* Does dialing this address need the permissive gater?
|
|
11
|
+
*
|
|
12
|
+
* libp2p's browser default refuses to dial loopback and private-range
|
|
13
|
+
* addresses. `dev` used to be derived from the PAGE's hostname
|
|
14
|
+
* (`localhost`/`127.0.0.1`), which asks the wrong question: what the gater
|
|
15
|
+
* objects to is the address being DIALLED, not where the page came from. Open
|
|
16
|
+
* the same page at `http://192.168.1.5:5177` and the hostname test says
|
|
17
|
+
* "production" while `httpeers.json` still names a loopback relay -- so every
|
|
18
|
+
* address in the dial is denied and the failure surfaces as
|
|
19
|
+
* `DialDeniedError`, whose message blames the relay for being unreachable
|
|
20
|
+
* when the relay is running perfectly well.
|
|
21
|
+
*
|
|
22
|
+
* Asking it of the address fixes both that case and the LAN one (a page on
|
|
23
|
+
* `192.168.1.5` dialling a relay on `192.168.1.5`, also a private address,
|
|
24
|
+
* also denied). A public relay address returns false and the gater stays at
|
|
25
|
+
* its default, which is the behaviour a deployment wants.
|
|
26
|
+
*/
|
|
27
|
+
export declare function dialNeedsPermissiveGater(addr: string): boolean;
|
|
28
|
+
export interface CreateBrowserNodeInit {
|
|
29
|
+
/**
|
|
30
|
+
* Relaxes the libp2p connection gater to allow insecure (`ws://`) and
|
|
31
|
+
* private/loopback dials. libp2p 3.x's browser-only default gater denies
|
|
32
|
+
* BOTH on its own (`libp2p/dist/src/config/connection-gater.browser.js`)
|
|
33
|
+
* -- fine in production, where a real deployment's relay is `wss://` on
|
|
34
|
+
* a real DNS name (`../setup/main.ts`'s `relayAddrFamily`), but fatal in
|
|
35
|
+
* local development, where the relay is `ws://127.0.0.1:<port>/ws`: with
|
|
36
|
+
* the default gater EVERY dial this node makes -- to the relay itself,
|
|
37
|
+
* and to every derived `/p2p-circuit/webrtc/p2p/...` peer address -- is
|
|
38
|
+
* denied before any handshake even starts. Precedent:
|
|
39
|
+
* `workspaces/webrun-wire/apps/p2p-demo/lib/browser-node.ts`, which
|
|
40
|
+
* applies exactly this relaxation gated on `import.meta.env.DEV`.
|
|
41
|
+
* Required here (no default), rather than silently inferred from
|
|
42
|
+
* `import.meta.env.DEV`, because THIS file has no bundler-injected `env`
|
|
43
|
+
* of its own to read -- inferring it here would mean guessing at a value
|
|
44
|
+
* only the caller (a Vite-built page, see `../static-server/main.ts`'s
|
|
45
|
+
* "DIST DIRECTORY LAYOUT" note) actually knows.
|
|
46
|
+
*/
|
|
47
|
+
dev: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* This node's signing key. Defaults to this origin's persisted identity
|
|
50
|
+
* (`./identity.ts`'s `loadOrCreateIdentity`) -- which is what both
|
|
51
|
+
* ordinary pages want and neither has to say.
|
|
52
|
+
*
|
|
53
|
+
* SUPPLIED BY A CALLER THAT NEEDS THE KEY FOR SOMETHING ELSE TOO, which
|
|
54
|
+
* today means the hub page: `createLibp2p` never gives a key back out,
|
|
55
|
+
* and the hub must hand the SAME key to `createPeer` (`mintToken` signs
|
|
56
|
+
* with it, and a mesh whose hub node and hub signer were different keys
|
|
57
|
+
* would mint tokens no peer could match to the hub it is talking to).
|
|
58
|
+
* Loading it once and passing it here is the only way to guarantee they
|
|
59
|
+
* are the same key rather than two reads that happen to agree.
|
|
60
|
+
*/
|
|
61
|
+
privateKey: Ed25519PrivateKey;
|
|
62
|
+
/**
|
|
63
|
+
* Makes this node a HUB: it relays signalling for the peers this answers
|
|
64
|
+
* true for, and for nobody else. See `../hub-relay.ts`. Only the hub page
|
|
65
|
+
* passes it.
|
|
66
|
+
*/
|
|
67
|
+
isMember?: IsMember;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build this origin's browser libp2p node -- identity loaded/generated,
|
|
71
|
+
* transports and encryption wired, NOT yet dialled anywhere. See
|
|
72
|
+
* `dialRelay` / `waitForCircuitReservation` for the next two steps, kept
|
|
73
|
+
* separate so a caller (`peer-runtime.ts`) can report distinct lifecycle
|
|
74
|
+
* states for "connecting to the relay" and "waiting for the reservation"
|
|
75
|
+
* rather than one opaque await.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createBrowserNode(init: CreateBrowserNodeInit): Promise<Libp2p>;
|
|
78
|
+
/**
|
|
79
|
+
* Re-exported from `../reservation.ts`, which is where they live now -- see
|
|
80
|
+
* this module's own note on the move. A page that imports them from here
|
|
81
|
+
* keeps working, and a reader looking for "what happens after
|
|
82
|
+
* `createBrowserNode`" still finds the answer named in this file.
|
|
83
|
+
*/
|
|
84
|
+
export { dialRelay, RESERVATION_POLL_ATTEMPTS, RESERVATION_POLL_INTERVAL_MS, type WaitForCircuitReservationInit, waitForCircuitReservation, } from "@statewalker/httpeers-libp2p";
|
|
85
|
+
//# sourceMappingURL=browser-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-profile.d.ts","sourceRoot":"","sources":["../src/browser-profile.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGnE,OAAO,EAAmB,KAAK,QAAQ,EAAmB,MAAM,8BAA8B,CAAC;AAG/F;;;;GAIG;AACH,OAAO,EACL,aAAa,EACb,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,QAAQ,GACT,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO9D;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,GAAG,EAAE,OAAO,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBpF;AASD;;;;;GAKG;AACH,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,4BAA4B,EAC5B,KAAK,6BAA6B,EAClC,yBAAyB,GAC1B,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser libp2p profile.
|
|
3
|
+
*
|
|
4
|
+
* A browser cannot listen for inbound TCP the way `../hub/main.ts` /
|
|
5
|
+
* `../relay/main.ts` do (`transport-duplex.ts`'s `createNode`, TCP-only,
|
|
6
|
+
* is Node-side and stays Node-side). Instead this node reserves a relayed
|
|
7
|
+
* address on the deployment's relay and accepts a WebRTC upgrade brokered
|
|
8
|
+
* over it -- WebSockets to dial the relay in the first place,
|
|
9
|
+
* Circuit-Relay-v2 for the reservation and for dialling other peers'
|
|
10
|
+
* relayed addresses, WebRTC for the peer-to-peer upgrade once a relayed
|
|
11
|
+
* connection exists. `addresses.listen: ['/p2p-circuit', '/webrtc']` and
|
|
12
|
+
* this transport list are the exact configuration design note 17 §4 (the
|
|
13
|
+
* relay prototype) and the validated browser-WebRTC prototype (repo notes,
|
|
14
|
+
* `httpeers-plan/prototypes/21-browser-webrtc-prototype-validated/app.ts`)
|
|
15
|
+
* both landed on -- not a guess assembled from the libp2p docs.
|
|
16
|
+
*
|
|
17
|
+
* DIALING THE RELAY AND WAITING FOR THE RESERVATION LIVE IN
|
|
18
|
+
* `../reservation.ts`, re-exported below so this module still reads as the
|
|
19
|
+
* one place a page asks "how do I get a browser node onto the mesh". They
|
|
20
|
+
* moved there in Task 20 because they touch nothing transport-specific and
|
|
21
|
+
* three callers now need them -- this file, `../hub/main.ts`, and
|
|
22
|
+
* `tests/e2e/harness.ts`, the last of which had hand-copied the poll loop
|
|
23
|
+
* rather than import it through this module's `@libp2p/webrtc` dependency.
|
|
24
|
+
*
|
|
25
|
+
* IDENTITY PERSISTS PER ORIGIN, IN INDEXEDDB -- and it lives in
|
|
26
|
+
* `./identity.ts` now, re-exported below. It moved there in Task 24
|
|
27
|
+
* because the hub page needs the key itself, not just a node built from
|
|
28
|
+
* it: `createLibp2p` never hands a generated key back out, and the hub must
|
|
29
|
+
* pass the very same key to `createPeer` so its `mintToken` closure signs
|
|
30
|
+
* as the mesh. See that module for the format contract and for why
|
|
31
|
+
* per-origin identities are deliberate.
|
|
32
|
+
*/
|
|
33
|
+
import { noise } from "@chainsafe/libp2p-noise";
|
|
34
|
+
import { yamux } from "@chainsafe/libp2p-yamux";
|
|
35
|
+
import { circuitRelayTransport } from "@libp2p/circuit-relay-v2";
|
|
36
|
+
import { identify } from "@libp2p/identify";
|
|
37
|
+
import { webRTC } from "@libp2p/webrtc";
|
|
38
|
+
import { webSockets } from "@libp2p/websockets";
|
|
39
|
+
import { hubRelayService, membershipGater } from "@statewalker/httpeers-libp2p";
|
|
40
|
+
import { createLibp2p } from "libp2p";
|
|
41
|
+
/**
|
|
42
|
+
* Re-exported from `./identity.ts`, which is where they live now -- see
|
|
43
|
+
* this module's own note on the move. A page that imports
|
|
44
|
+
* `IDENTITY_STORAGE_KEY` from here keeps working.
|
|
45
|
+
*/
|
|
46
|
+
export { clearIdentity, decodeIdentity, encodeIdentity, IDENTITY_STORAGE_KEY, loadOrCreateIdentity, peerIdOf, } from "./identity.js";
|
|
47
|
+
/**
|
|
48
|
+
* Does dialing this address need the permissive gater?
|
|
49
|
+
*
|
|
50
|
+
* libp2p's browser default refuses to dial loopback and private-range
|
|
51
|
+
* addresses. `dev` used to be derived from the PAGE's hostname
|
|
52
|
+
* (`localhost`/`127.0.0.1`), which asks the wrong question: what the gater
|
|
53
|
+
* objects to is the address being DIALLED, not where the page came from. Open
|
|
54
|
+
* the same page at `http://192.168.1.5:5177` and the hostname test says
|
|
55
|
+
* "production" while `httpeers.json` still names a loopback relay -- so every
|
|
56
|
+
* address in the dial is denied and the failure surfaces as
|
|
57
|
+
* `DialDeniedError`, whose message blames the relay for being unreachable
|
|
58
|
+
* when the relay is running perfectly well.
|
|
59
|
+
*
|
|
60
|
+
* Asking it of the address fixes both that case and the LAN one (a page on
|
|
61
|
+
* `192.168.1.5` dialling a relay on `192.168.1.5`, also a private address,
|
|
62
|
+
* also denied). A public relay address returns false and the gater stays at
|
|
63
|
+
* its default, which is the behaviour a deployment wants.
|
|
64
|
+
*/
|
|
65
|
+
export function dialNeedsPermissiveGater(addr) {
|
|
66
|
+
const ip = /\/ip4\/([0-9.]+)/.exec(addr)?.[1];
|
|
67
|
+
if (ip == null)
|
|
68
|
+
return /\/dns4?\/localhost(\/|$)/.test(addr);
|
|
69
|
+
if (ip === "127.0.0.1" || ip.startsWith("127."))
|
|
70
|
+
return true;
|
|
71
|
+
if (ip.startsWith("10.") || ip.startsWith("192.168."))
|
|
72
|
+
return true;
|
|
73
|
+
const m = /^172\.(\d+)\./.exec(ip);
|
|
74
|
+
return m != null && Number(m[1]) >= 16 && Number(m[1]) <= 31;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build this origin's browser libp2p node -- identity loaded/generated,
|
|
78
|
+
* transports and encryption wired, NOT yet dialled anywhere. See
|
|
79
|
+
* `dialRelay` / `waitForCircuitReservation` for the next two steps, kept
|
|
80
|
+
* separate so a caller (`peer-runtime.ts`) can report distinct lifecycle
|
|
81
|
+
* states for "connecting to the relay" and "waiting for the reservation"
|
|
82
|
+
* rather than one opaque await.
|
|
83
|
+
*/
|
|
84
|
+
export async function createBrowserNode(init) {
|
|
85
|
+
// REQUIRED now. It used to fall back to `loadOrCreateIdentity()`, which
|
|
86
|
+
// reached for IndexedDB from a function a Node member also calls — see
|
|
87
|
+
// `./kv.js`. The caller holds the key; this only starts a node with it.
|
|
88
|
+
const privateKey = init.privateKey;
|
|
89
|
+
return createLibp2p({
|
|
90
|
+
privateKey,
|
|
91
|
+
// ONLY A HUB RESERVES ON THE PUBLIC RELAY. A member is reached through
|
|
92
|
+
// its hub (`../hub-link.ts`), so it listens for WebRTC upgrades and
|
|
93
|
+
// nothing else; a `/p2p-circuit` entry here would make libp2p reserve on
|
|
94
|
+
// the first relay it meets -- the public one -- and spend its capacity.
|
|
95
|
+
addresses: { listen: init.isMember != null ? ["/p2p-circuit", "/webrtc"] : ["/webrtc"] },
|
|
96
|
+
transports: [webSockets(), webRTC(), circuitRelayTransport()],
|
|
97
|
+
connectionEncrypters: [noise()],
|
|
98
|
+
streamMuxers: [yamux()],
|
|
99
|
+
connectionGater: {
|
|
100
|
+
...(init.dev ? { denyDialMultiaddr: async () => false } : {}),
|
|
101
|
+
// The thunk is read at DECISION time, so it closes the ordering cycle:
|
|
102
|
+
// this node must exist before the hub that answers `isMember` does.
|
|
103
|
+
...(init.isMember != null ? membershipGater(() => init.isMember) : {}),
|
|
104
|
+
},
|
|
105
|
+
services: nodeServices(init.isMember),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** The relay service only on a hub, and only behind its membership test. */
|
|
109
|
+
function nodeServices(isMember) {
|
|
110
|
+
return isMember != null
|
|
111
|
+
? { identify: identify(), relay: hubRelayService() }
|
|
112
|
+
: { identify: identify() };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Re-exported from `../reservation.ts`, which is where they live now -- see
|
|
116
|
+
* this module's own note on the move. A page that imports them from here
|
|
117
|
+
* keeps working, and a reader looking for "what happens after
|
|
118
|
+
* `createBrowserNode`" still finds the answer named in this file.
|
|
119
|
+
*/
|
|
120
|
+
export { dialRelay, RESERVATION_POLL_ATTEMPTS, RESERVATION_POLL_INTERVAL_MS, waitForCircuitReservation, } from "@statewalker/httpeers-libp2p";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BROWSER half of a member: the platform object, the two IndexedDB
|
|
3
|
+
* stores, and one function that assembles a page session out of them.
|
|
4
|
+
*
|
|
5
|
+
* EVERYTHING BROWSER-BOUND IS BEHIND THIS ENTRY, and that is a load-bearing
|
|
6
|
+
* arrangement rather than tidiness. `idb-keyval` needs a real IndexedDB;
|
|
7
|
+
* `@libp2p/webrtc`'s Node build loads a native module and throws on import;
|
|
8
|
+
* `@statewalker/webrun-http-browser/sw` is a ServiceWorker adapter. Any of
|
|
9
|
+
* them reachable from `./index.ts` would make a Node member fail at import
|
|
10
|
+
* time, which `tests/boundary.test.ts` now measures by reachability.
|
|
11
|
+
*
|
|
12
|
+
* `createSession` IS THE ONLY THING HERE THAT IS NOT A RE-EXPORT. It is the
|
|
13
|
+
* defaults `session.ts` deliberately does not carry: the platform, the two
|
|
14
|
+
* stores, `location.search` and the reload. A page that wants to substitute
|
|
15
|
+
* any of them passes it and this steps aside.
|
|
16
|
+
*/
|
|
17
|
+
import type { AsyncBytesBackend, AsyncKeyValueBackend } from "./kv.js";
|
|
18
|
+
import type { PeerSession, PeerSessionInit } from "./session.js";
|
|
19
|
+
export { type BrowserPlatformInit, browserPlatform } from "./browser-platform.js";
|
|
20
|
+
export { type CreateBrowserNodeInit, createBrowserNode, dialNeedsPermissiveGater, } from "./browser-profile.js";
|
|
21
|
+
export { DEFAULT_CONTROL_TIMEOUT_MS, DEFAULT_SERVICE_WORKER_URL, type MountEdgeInit, mountEdge, UncontrolledPageError, } from "./edge.js";
|
|
22
|
+
export { type WakeDocument, type WakeWindow, type WatchPageWakeInit, watchPageWake, } from "./page-wake.js";
|
|
23
|
+
/** The real thing: the same IndexedDB store `@statewalker/webrun-http-browser` already uses. */
|
|
24
|
+
export declare function idbBackend(): AsyncKeyValueBackend;
|
|
25
|
+
export declare function idbBytesBackend(): AsyncBytesBackend;
|
|
26
|
+
/**
|
|
27
|
+
* `PeerSessionInit` with every browser-supplied part made optional.
|
|
28
|
+
*
|
|
29
|
+
* The four that become optional are exactly the four `session.ts` refuses to
|
|
30
|
+
* default, and they are listed there with the reason. `serviceWorkerUrl` and
|
|
31
|
+
* `dev` are here rather than there because they are `browserPlatform`'s
|
|
32
|
+
* arguments, and a page that passes its own `platform` has no use for them.
|
|
33
|
+
*/
|
|
34
|
+
export interface CreateSessionInit extends Omit<PeerSessionInit, "platform" | "identity" | "meshMemory" | "reload" | "search"> {
|
|
35
|
+
platform?: PeerSessionInit["platform"];
|
|
36
|
+
identity?: PeerSessionInit["identity"];
|
|
37
|
+
meshMemory?: PeerSessionInit["meshMemory"];
|
|
38
|
+
reload?: () => void;
|
|
39
|
+
/** Defaults to `location.search`, read once at `start()`. */
|
|
40
|
+
search?: string;
|
|
41
|
+
/** Where the ServiceWorker script lives — see `mountEdge`. */
|
|
42
|
+
serviceWorkerUrl?: string;
|
|
43
|
+
/** Relax libp2p's dial gater for loopback and private addresses. Off in production. */
|
|
44
|
+
dev?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function createSession(init: CreateSessionInit): PeerSession;
|
|
47
|
+
export { type ResetResult, resetBrowserState } from "./reset.js";
|
|
48
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGjE,OAAO,EAAE,KAAK,mBAAmB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,KAAK,qBAAqB,EAC1B,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,KAAK,aAAa,EAClB,SAAS,EACT,qBAAqB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,aAAa,GACd,MAAM,gBAAgB,CAAC;AASxB,gGAAgG;AAChG,wBAAgB,UAAU,IAAI,oBAAoB,CAMjD;AAED,wBAAgB,eAAe,IAAI,iBAAiB,CAMnD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBACf,SAAQ,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3F,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IACvC,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,WAAW,CAWlE;AACD,OAAO,EAAE,KAAK,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BROWSER half of a member: the platform object, the two IndexedDB
|
|
3
|
+
* stores, and one function that assembles a page session out of them.
|
|
4
|
+
*
|
|
5
|
+
* EVERYTHING BROWSER-BOUND IS BEHIND THIS ENTRY, and that is a load-bearing
|
|
6
|
+
* arrangement rather than tidiness. `idb-keyval` needs a real IndexedDB;
|
|
7
|
+
* `@libp2p/webrtc`'s Node build loads a native module and throws on import;
|
|
8
|
+
* `@statewalker/webrun-http-browser/sw` is a ServiceWorker adapter. Any of
|
|
9
|
+
* them reachable from `./index.ts` would make a Node member fail at import
|
|
10
|
+
* time, which `tests/boundary.test.ts` now measures by reachability.
|
|
11
|
+
*
|
|
12
|
+
* `createSession` IS THE ONLY THING HERE THAT IS NOT A RE-EXPORT. It is the
|
|
13
|
+
* defaults `session.ts` deliberately does not carry: the platform, the two
|
|
14
|
+
* stores, `location.search` and the reload. A page that wants to substitute
|
|
15
|
+
* any of them passes it and this steps aside.
|
|
16
|
+
*/
|
|
17
|
+
import { del, get, set } from "idb-keyval";
|
|
18
|
+
import { browserPlatform } from "./browser-platform.js";
|
|
19
|
+
import { createMeshMemory } from "./mesh-memory.js";
|
|
20
|
+
import { createIdentityStore, createPeerSession } from "./session.js";
|
|
21
|
+
export { browserPlatform } from "./browser-platform.js";
|
|
22
|
+
export { createBrowserNode, dialNeedsPermissiveGater, } from "./browser-profile.js";
|
|
23
|
+
export { DEFAULT_CONTROL_TIMEOUT_MS, DEFAULT_SERVICE_WORKER_URL, mountEdge, UncontrolledPageError, } from "./edge.js";
|
|
24
|
+
export { watchPageWake, } from "./page-wake.js";
|
|
25
|
+
/** The real thing: the same IndexedDB store `@statewalker/webrun-http-browser` already uses. */
|
|
26
|
+
export function idbBackend() {
|
|
27
|
+
return {
|
|
28
|
+
get: async (key) => (await get(key)) ?? undefined,
|
|
29
|
+
set: async (key, value) => await set(key, value),
|
|
30
|
+
delete: async (key) => await del(key),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function idbBytesBackend() {
|
|
34
|
+
return {
|
|
35
|
+
get: async (key) => (await get(key)) ?? undefined,
|
|
36
|
+
set: async (key, value) => await set(key, value),
|
|
37
|
+
delete: async (key) => await del(key),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createSession(init) {
|
|
41
|
+
return createPeerSession({
|
|
42
|
+
...init,
|
|
43
|
+
search: init.search ?? location.search,
|
|
44
|
+
platform: init.platform ??
|
|
45
|
+
browserPlatform({ serviceWorkerUrl: init.serviceWorkerUrl, dev: init.dev ?? false }),
|
|
46
|
+
identity: init.identity ?? createIdentityStore({ backend: idbBytesBackend() }),
|
|
47
|
+
meshMemory: init.meshMemory ?? createMeshMemory({ backend: idbBackend() }),
|
|
48
|
+
reload: init.reload ?? (() => location.reload()),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export { resetBrowserState } from "./reset.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is this peer reached directly, or through the relay?
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. libp2p does not upgrade a circuit to WebRTC on its own:
|
|
5
|
+
* `join.ts`'s `preDialPeer` dials `<relay>/p2p-circuit/webrtc/p2p/<peer>`
|
|
6
|
+
* explicitly, and the circuit's only job there is to carry the SDP exchange.
|
|
7
|
+
* When that dial fails -- symmetric NAT on a mobile carrier, a VPN, no reachable
|
|
8
|
+
* STUN -- `edge-dispatch.ts` SWALLOWS the failure by design and the call is
|
|
9
|
+
* attempted anyway. `remote()` then dials by peer id, which resolves through the
|
|
10
|
+
* peerStore to a bare `/p2p-circuit` address, and every byte crosses the relay.
|
|
11
|
+
*
|
|
12
|
+
* That fallback works. What it does not do is announce itself, and the silence
|
|
13
|
+
* is the problem: it is 10-100x slower, it spends the relay operator's
|
|
14
|
+
* bandwidth instead of going peer-to-peer, and while the relay applied a
|
|
15
|
+
* per-connection data cap it truncated transfers mid-stream. The visible
|
|
16
|
+
* symptom was broken images in a gallery, with one of them loading fine in a
|
|
17
|
+
* fresh tab -- which reads as random corruption rather than as a transport
|
|
18
|
+
* that quietly changed underneath.
|
|
19
|
+
*
|
|
20
|
+
* Reading the open connection's own address is the whole diagnosis, and it is
|
|
21
|
+
* one call. Strings in, so this module needs no libp2p import and is testable
|
|
22
|
+
* under plain Node.
|
|
23
|
+
*/
|
|
24
|
+
export type ConnectionKind = "direct" | "relayed" | "none";
|
|
25
|
+
/**
|
|
26
|
+
* Classify the connections open to one peer, from their multiaddrs.
|
|
27
|
+
*
|
|
28
|
+
* `/webrtc` present means the WebRTC upgrade completed and the relay is no
|
|
29
|
+
* longer in the data path. A `/p2p-circuit` WITHOUT it means it did not, and
|
|
30
|
+
* traffic is being relayed. Direct wins when both are open, because that is the
|
|
31
|
+
* one libp2p will actually use.
|
|
32
|
+
*/
|
|
33
|
+
export declare function classifyConnection(remoteAddrs: readonly string[]): ConnectionKind;
|
|
34
|
+
/** One line a page can render unconditionally, saying what the kind means rather than naming it. */
|
|
35
|
+
export declare function describeConnection(kind: ConnectionKind): string;
|
|
36
|
+
//# sourceMappingURL=connection-kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-kind.d.ts","sourceRoot":"","sources":["../src/connection-kind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAMjF;AAED,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAS/D"}
|