@net-mesh/browser 0.37.0
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/CHANGELOG.md +517 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +666 -0
- package/dist/async-queue.d.ts +26 -0
- package/dist/async-queue.js +62 -0
- package/dist/credential.d.ts +70 -0
- package/dist/credential.js +95 -0
- package/dist/errors.d.ts +400 -0
- package/dist/errors.js +726 -0
- package/dist/events.d.ts +232 -0
- package/dist/events.js +298 -0
- package/dist/identity.d.ts +40 -0
- package/dist/identity.js +78 -0
- package/dist/index.bundle.js +2 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +46 -0
- package/dist/leader/events.d.ts +111 -0
- package/dist/leader/events.js +116 -0
- package/dist/leader/index.d.ts +14 -0
- package/dist/leader/index.js +11 -0
- package/dist/leader/session.d.ts +539 -0
- package/dist/leader/session.js +770 -0
- package/dist/leader/wasm.d.ts +123 -0
- package/dist/leader/wasm.js +39 -0
- package/dist/lobby.d.ts +183 -0
- package/dist/lobby.js +504 -0
- package/dist/local.d.ts +80 -0
- package/dist/local.js +160 -0
- package/dist/net_leaf.d.ts +1494 -0
- package/dist/net_leaf.js +3753 -0
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +137 -0
- package/dist/netcode/client.d.ts +102 -0
- package/dist/netcode/client.js +296 -0
- package/dist/netcode/clock.d.ts +45 -0
- package/dist/netcode/clock.js +54 -0
- package/dist/netcode/host.d.ts +118 -0
- package/dist/netcode/host.js +276 -0
- package/dist/netcode/index.d.ts +19 -0
- package/dist/netcode/index.js +14 -0
- package/dist/netcode/interpolate.d.ts +50 -0
- package/dist/netcode/interpolate.js +100 -0
- package/dist/netcode/wire.d.ts +125 -0
- package/dist/netcode/wire.js +117 -0
- package/dist/node.d.ts +908 -0
- package/dist/node.js +1085 -0
- package/dist/org.d.ts +412 -0
- package/dist/org.js +567 -0
- package/dist/peer-driver.d.ts +117 -0
- package/dist/peer-driver.js +224 -0
- package/dist/store/assembly.d.ts +176 -0
- package/dist/store/assembly.js +325 -0
- package/dist/store/chunker.d.ts +83 -0
- package/dist/store/chunker.js +190 -0
- package/dist/store/core.d.ts +136 -0
- package/dist/store/core.js +477 -0
- package/dist/store/definition.d.ts +23 -0
- package/dist/store/definition.js +83 -0
- package/dist/store/errors.d.ts +77 -0
- package/dist/store/errors.js +94 -0
- package/dist/store/host.d.ts +247 -0
- package/dist/store/host.js +675 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.js +40 -0
- package/dist/store/interest.d.ts +40 -0
- package/dist/store/interest.js +77 -0
- package/dist/store/inventory.d.ts +81 -0
- package/dist/store/inventory.js +140 -0
- package/dist/store/join.d.ts +118 -0
- package/dist/store/join.js +724 -0
- package/dist/store/json.d.ts +50 -0
- package/dist/store/json.js +283 -0
- package/dist/store/ledger.d.ts +187 -0
- package/dist/store/ledger.js +292 -0
- package/dist/store/owner.d.ts +616 -0
- package/dist/store/owner.js +1711 -0
- package/dist/store/patch.d.ts +70 -0
- package/dist/store/patch.js +166 -0
- package/dist/store/player.d.ts +65 -0
- package/dist/store/player.js +261 -0
- package/dist/store/replica.d.ts +187 -0
- package/dist/store/replica.js +740 -0
- package/dist/store/state.d.ts +42 -0
- package/dist/store/state.js +165 -0
- package/dist/store/types.d.ts +200 -0
- package/dist/store/types.js +25 -0
- package/dist/store/visibility.d.ts +118 -0
- package/dist/store/visibility.js +282 -0
- package/dist/store/wire.d.ts +309 -0
- package/dist/store/wire.js +834 -0
- package/dist/stream.d.ts +174 -0
- package/dist/stream.js +238 -0
- package/dist/three/index.d.ts +109 -0
- package/dist/three/index.js +130 -0
- package/dist/udp-probe.d.ts +159 -0
- package/dist/udp-probe.js +245 -0
- package/dist/wasm.d.ts +642 -0
- package/dist/wasm.js +98 -0
- package/dist/world/border.d.ts +84 -0
- package/dist/world/border.js +99 -0
- package/dist/world/handoff.d.ts +187 -0
- package/dist/world/handoff.js +222 -0
- package/dist/world/index.d.ts +13 -0
- package/dist/world/index.js +9 -0
- package/dist/world/link.d.ts +179 -0
- package/dist/world/link.js +476 -0
- package/dist/world/regions.d.ts +154 -0
- package/dist/world/regions.js +314 -0
- package/package.json +72 -0
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BrowserNode` — the surface a page actually uses.
|
|
3
|
+
*
|
|
4
|
+
* A thin, typed, camelCased layer over the wasm node: it owns the one
|
|
5
|
+
* `on_event` registration, re-types everything the boundary throws,
|
|
6
|
+
* parses `query`'s JSON without rounding 64-bit ids, and applies the
|
|
7
|
+
* Stage 5 failure-typing correction to an ICE failure when — and only
|
|
8
|
+
* when — the distinguishing evidence can be obtained.
|
|
9
|
+
*/
|
|
10
|
+
import { type LeafEvent, type LeafEventOf, type Unsubscribe } from './events.js';
|
|
11
|
+
import { type LeafError } from './errors.js';
|
|
12
|
+
import { type OrgAccess, type OrgByteStream, type OrgCallOptions, type OrgClientStreamHandler, type OrgDuplexHandler, type OrgDuplexHandles, type OrgServeHandle, type OrgServeOptions, type OrgStreamingHandler, type OrgUnaryHandler, type OrgUploadCall } from './org.js';
|
|
13
|
+
import { LeafStream, type OpenStreamOptions } from './stream.js';
|
|
14
|
+
import { type BootstrapProbeOptions, type StunProbeOptions } from './udp-probe.js';
|
|
15
|
+
import { type LeafWasmConnectOptions, type WasmSource } from './wasm.js';
|
|
16
|
+
/**
|
|
17
|
+
* One node the mesh knows about, from {@link BrowserNode.query}.
|
|
18
|
+
*
|
|
19
|
+
* **`nodeId` and the peer methods do not take the same spelling.**
|
|
20
|
+
* `nodeId` is the exact DECIMAL `u64` the announcement carries;
|
|
21
|
+
* {@link BrowserNode.connectPeer} and its siblings take 16
|
|
22
|
+
* lowercase HEX digits. The two are different strings for the same
|
|
23
|
+
* number, and a decimal string can even be 16 digits long — so
|
|
24
|
+
* `connectPeer(descriptor.nodeId)` addresses a different node, or is
|
|
25
|
+
* refused, rather than doing what it reads like. Neither value is
|
|
26
|
+
* silently normalised into the other: {@link peerIdHex} is the
|
|
27
|
+
* conversion, and it is also pre-computed here as
|
|
28
|
+
* {@link NodeDescriptor.peerIdHex}.
|
|
29
|
+
*/
|
|
30
|
+
export interface NodeDescriptor {
|
|
31
|
+
/**
|
|
32
|
+
* The node's mesh id, exact decimal — as the announcement spells
|
|
33
|
+
* it. NOT what the peer methods take; see
|
|
34
|
+
* {@link NodeDescriptor.peerIdHex}.
|
|
35
|
+
*/
|
|
36
|
+
readonly nodeId: string;
|
|
37
|
+
/**
|
|
38
|
+
* The same id in the spelling every peer method takes: 16
|
|
39
|
+
* lowercase hex digits.
|
|
40
|
+
*
|
|
41
|
+
* Derived from {@link NodeDescriptor.nodeId} by {@link peerIdHex},
|
|
42
|
+
* and carried beside it rather than instead of it — the decimal
|
|
43
|
+
* literal is what the announcement signed and what
|
|
44
|
+
* {@link BrowserNode.query}'s consumers may be matching on.
|
|
45
|
+
*/
|
|
46
|
+
readonly peerIdHex: string;
|
|
47
|
+
/** Its Ed25519 entity id, hex — the identity the announcement signed. */
|
|
48
|
+
readonly entityId: string | null;
|
|
49
|
+
readonly capabilities: readonly string[];
|
|
50
|
+
/** The RTC address it published, when it published one. */
|
|
51
|
+
readonly rtcAddr: string | null;
|
|
52
|
+
/** Its Noise static public key, hex, when the announcement carried one. */
|
|
53
|
+
readonly noisePubkey: string | null;
|
|
54
|
+
/** The announcement's version counter, exact decimal. */
|
|
55
|
+
readonly version: string | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert a decimal `u64` node id — {@link NodeDescriptor.nodeId},
|
|
59
|
+
* or any other decimal mesh id — into the 16-lowercase-hex-digit
|
|
60
|
+
* spelling the peer methods require.
|
|
61
|
+
*
|
|
62
|
+
* Explicit, because the alternative is worse in both directions. The
|
|
63
|
+
* leaf refuses a decimal peer id deliberately (one contract: `0009`
|
|
64
|
+
* must not name two different nodes depending on which method read
|
|
65
|
+
* it), and a wrapper that guessed which spelling it had been handed
|
|
66
|
+
* would dial whichever node its guess produced. So the caller says
|
|
67
|
+
* which conversion it means.
|
|
68
|
+
*
|
|
69
|
+
* `BigInt` rather than `Number`: a mesh id is a full `u64` and
|
|
70
|
+
* `parseInt` would round it.
|
|
71
|
+
*
|
|
72
|
+
* @throws TypeError if `nodeId` is not a decimal integer inside
|
|
73
|
+
* `u64`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function peerIdHex(nodeId: string): string;
|
|
76
|
+
/** How hard to work at classifying an ICE failure. */
|
|
77
|
+
export interface FailureTypingOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Run the STUN probe when an attempt fails with an ICE timeout.
|
|
80
|
+
* Default `true`. Turning it off does not change which failures are
|
|
81
|
+
* possible — it only means an ICE timeout always stays
|
|
82
|
+
* `ice-timeout`, never `udp-blocked`.
|
|
83
|
+
*/
|
|
84
|
+
readonly probeOnIceTimeout?: boolean;
|
|
85
|
+
/** Passed to the STUN probe. */
|
|
86
|
+
readonly stun?: StunProbeOptions;
|
|
87
|
+
/** Passed to the HTTPS reachability probe. */
|
|
88
|
+
readonly bootstrap?: BootstrapProbeOptions;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Where one browser ↔ browser attempt ended (plan §9).
|
|
92
|
+
*
|
|
93
|
+
* A **typed result**, not a rejection: every member below is a
|
|
94
|
+
* disposition a drive loop acts on rather than an exception. `direct`
|
|
95
|
+
* is the only success; the rest are the four the stage brief names,
|
|
96
|
+
* plus `udpBlocked`, which is `iceTimeout` with the evidence that
|
|
97
|
+
* narrows it actually established (`UdpBlockedEvidence` — never
|
|
98
|
+
* claimed without it).
|
|
99
|
+
*
|
|
100
|
+
* `dialog` is the attempt's id, 16 lowercase hex digits, the same
|
|
101
|
+
* value both sides number the exchange with.
|
|
102
|
+
*/
|
|
103
|
+
export type PeerConnectOutcome =
|
|
104
|
+
/** A direct session is installed and the pair is off the relay. */
|
|
105
|
+
{
|
|
106
|
+
readonly type: 'direct';
|
|
107
|
+
readonly peer: string;
|
|
108
|
+
readonly dialog: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* ICE did not connect inside the attempt's deadline. The routed
|
|
112
|
+
* session was never replaced (§9 step 6), which is a relayed
|
|
113
|
+
* session and not a failure.
|
|
114
|
+
*/
|
|
115
|
+
| {
|
|
116
|
+
readonly type: 'iceTimeout';
|
|
117
|
+
readonly peer: string;
|
|
118
|
+
readonly dialog: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* As `iceTimeout`, and the STUN probe against the address the
|
|
122
|
+
* anchor published also went unanswered — so this browser's UDP is
|
|
123
|
+
* blocked, with the two observations that establish it.
|
|
124
|
+
*/
|
|
125
|
+
| {
|
|
126
|
+
readonly type: 'udpBlocked';
|
|
127
|
+
readonly peer: string;
|
|
128
|
+
readonly dialog: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* This node holds no verified announcement for the peer, so there
|
|
132
|
+
* is no key to handshake against. Discover it by capability query
|
|
133
|
+
* first: a peer's Noise key comes from its signed announcement and
|
|
134
|
+
* never from a caller.
|
|
135
|
+
*/
|
|
136
|
+
| {
|
|
137
|
+
readonly type: 'noAnnouncement';
|
|
138
|
+
readonly peer: string;
|
|
139
|
+
readonly detail: string;
|
|
140
|
+
}
|
|
141
|
+
/** The DataChannel opened and the Noise handshake did not install. */
|
|
142
|
+
| {
|
|
143
|
+
readonly type: 'handshakeFailed';
|
|
144
|
+
readonly peer: string;
|
|
145
|
+
readonly dialog: string;
|
|
146
|
+
readonly detail: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A newer attempt with the same peer replaced this one. `liveDialog`
|
|
150
|
+
* is the attempt that now holds the peer, or `null` when the
|
|
151
|
+
* attempt simply ended.
|
|
152
|
+
*/
|
|
153
|
+
| {
|
|
154
|
+
readonly type: 'superseded';
|
|
155
|
+
readonly peer: string;
|
|
156
|
+
readonly dialog: string;
|
|
157
|
+
readonly liveDialog: string | null;
|
|
158
|
+
};
|
|
159
|
+
/** One `peer_candidate` reading, as the leaf reports it. */
|
|
160
|
+
export interface PeerAttemptStatus {
|
|
161
|
+
/** The attempt CURRENTLY live for the peer. */
|
|
162
|
+
readonly dialog: string;
|
|
163
|
+
/**
|
|
164
|
+
* Where the attempt stands.
|
|
165
|
+
*
|
|
166
|
+
* `gathering` and `open` are about ICE. The other three are the
|
|
167
|
+
* attempt's terminal transition, which spans ICE, Noise AND
|
|
168
|
+
* installation: `iceTimeout` and `udpBlocked` are a deadline with
|
|
169
|
+
* no channel, and `failed` is everything else that ended it — a
|
|
170
|
+
* channel that opened and a session that never installed, a
|
|
171
|
+
* responder step that failed. A terminal reading is final; the
|
|
172
|
+
* leaf neither drives nor re-settles the attempt after it.
|
|
173
|
+
*/
|
|
174
|
+
readonly state: 'gathering' | 'open' | 'iceTimeout' | 'udpBlocked' | 'failed';
|
|
175
|
+
/** Signed `Candidate` envelopes sent by this call. */
|
|
176
|
+
readonly sent: number;
|
|
177
|
+
/** Remote candidates the browser accepted on this call. */
|
|
178
|
+
readonly applied: number;
|
|
179
|
+
/** Whether the peer's answer was applied on this call. */
|
|
180
|
+
readonly answered: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Whether a direct session is installed and the relay is gone — a
|
|
183
|
+
* different fact from `state === 'open'`, which is only the
|
|
184
|
+
* DataChannel.
|
|
185
|
+
*/
|
|
186
|
+
readonly direct: boolean;
|
|
187
|
+
/** Milliseconds left on the attempt's ICE deadline, exact decimal. */
|
|
188
|
+
readonly remainingMs: string;
|
|
189
|
+
/**
|
|
190
|
+
* The engine's own text for the first trickled candidate it
|
|
191
|
+
* refused on this call, or `null` when it accepted every one.
|
|
192
|
+
*
|
|
193
|
+
* Machine-readable on purpose. "ICE is working on it" and "ICE has
|
|
194
|
+
* nothing to work with" are different states and a console warning
|
|
195
|
+
* is not a value a page, a witness or a diagnostic can read.
|
|
196
|
+
*/
|
|
197
|
+
readonly candidateError: string | null;
|
|
198
|
+
}
|
|
199
|
+
/** {@link connect}'s argument. */
|
|
200
|
+
export interface ConnectOptions extends WasmSource {
|
|
201
|
+
/** The bootstrap credential, base64. */
|
|
202
|
+
credentialB64?: string;
|
|
203
|
+
/** The bootstrap credential as bytes, or base64 — either is fine. */
|
|
204
|
+
credential?: Uint8Array | string;
|
|
205
|
+
/**
|
|
206
|
+
* The anchor's HTTPS bootstrap endpoint. Optional — the credential
|
|
207
|
+
* carries one, and this overrides it. Naming it also gives the
|
|
208
|
+
* failure classifier its HTTPS reachability probe before the first
|
|
209
|
+
* `connected` event.
|
|
210
|
+
*/
|
|
211
|
+
bootstrapUrl?: string;
|
|
212
|
+
/**
|
|
213
|
+
* The origin this node's identity belongs to. Defaults to
|
|
214
|
+
* `location.origin`, which is the trust boundary the identity is
|
|
215
|
+
* stored under.
|
|
216
|
+
*/
|
|
217
|
+
origin?: string;
|
|
218
|
+
/**
|
|
219
|
+
* The ICE servers this node's connections gather against.
|
|
220
|
+
*
|
|
221
|
+
* Optional, and **defaulted for you**: omitted, the leaf uses the
|
|
222
|
+
* STUN endpoint the anchor announces as `stun_addr` on
|
|
223
|
+
* `GET /rtc/anchor`, so the advertised configuration works without
|
|
224
|
+
* a page choosing a STUN service. An anchor that announces none
|
|
225
|
+
* leaves the connection with no ICE servers, which is host
|
|
226
|
+
* candidates only.
|
|
227
|
+
*
|
|
228
|
+
* **Absence, not emptiness, triggers the default.** An explicit
|
|
229
|
+
* `iceServers: []` is a caller saying *none*, and is honoured as
|
|
230
|
+
* given; only an omitted option takes the announced endpoint. A
|
|
231
|
+
* default that also fired on `[]` would override an intent rather
|
|
232
|
+
* than supply a missing one, and the only symptom would be a
|
|
233
|
+
* connection that gathered more than the page asked for.
|
|
234
|
+
*
|
|
235
|
+
* Supplied, it is honoured verbatim — with one refusal. An entry
|
|
236
|
+
* naming this connection's peer RTC endpoint as its STUN server is
|
|
237
|
+
* rejected with {@link IceServerConflictError} before any ICE
|
|
238
|
+
* work, because a peer cannot be its own STUN server. In
|
|
239
|
+
* particular, do not build an entry out of
|
|
240
|
+
* {@link diagnosticStunUrl}(anchorRtcAddr): that helper is for the
|
|
241
|
+
* throwaway UDP probe.
|
|
242
|
+
*/
|
|
243
|
+
iceServers?: readonly RTCIceServer[];
|
|
244
|
+
/**
|
|
245
|
+
* Custodial identity: the Ed25519 entity secret as 32 bytes of hex.
|
|
246
|
+
*
|
|
247
|
+
* Supplied, the leaf builds its identity from this instead of
|
|
248
|
+
* generating one, so two tabs (or two pages) handed the same secret
|
|
249
|
+
* are the same node id. Absent, the leaf generates from the platform
|
|
250
|
+
* CSPRNG.
|
|
251
|
+
*/
|
|
252
|
+
entitySecretHex?: string;
|
|
253
|
+
/**
|
|
254
|
+
* The Noise X25519 static secret as 32 bytes of hex. Only read when
|
|
255
|
+
* {@link ConnectOptions.entitySecretHex} is also given — the leaf
|
|
256
|
+
* generates this half otherwise, which would leave two tabs sharing
|
|
257
|
+
* an entity but not a static key.
|
|
258
|
+
*/
|
|
259
|
+
noiseSecretHex?: string;
|
|
260
|
+
/**
|
|
261
|
+
* The `rtc_addr` the anchor publishes, supplied out of band.
|
|
262
|
+
*
|
|
263
|
+
* The STUN probe needs a subject, and before a `connected` event
|
|
264
|
+
* arrives this package has not been told one. A page that already
|
|
265
|
+
* knows the anchor's address (a test harness, a pinned deployment)
|
|
266
|
+
* passes it here and gets `udp-blocked` classified on the very first
|
|
267
|
+
* failed attempt; without it, and before the first `connected`, an
|
|
268
|
+
* ICE timeout correctly stays `ice-timeout`.
|
|
269
|
+
*/
|
|
270
|
+
anchorRtcAddr?: string;
|
|
271
|
+
/** Failure-typing knobs. */
|
|
272
|
+
failureTyping?: FailureTypingOptions;
|
|
273
|
+
}
|
|
274
|
+
/** A browser-native Net node. */
|
|
275
|
+
export declare class BrowserNode {
|
|
276
|
+
private readonly inner;
|
|
277
|
+
private readonly bootstrapUrl;
|
|
278
|
+
private readonly failureTyping;
|
|
279
|
+
private readonly hub;
|
|
280
|
+
private readonly nodeId;
|
|
281
|
+
private closed;
|
|
282
|
+
/**
|
|
283
|
+
* Streams this node handed out and that are still open, so closing
|
|
284
|
+
* the node can end them.
|
|
285
|
+
*
|
|
286
|
+
* The direct surface's half of the same guarantee `MeshSession`
|
|
287
|
+
* gives across a leader change: after `close()` the wasm node
|
|
288
|
+
* delivers nothing and no stream will ever emit again, so a
|
|
289
|
+
* consumer sitting in `await iterator.next()` would never settle.
|
|
290
|
+
* Nothing else can end it — the queue is fed only by the stream's
|
|
291
|
+
* `on_message` callback, and a closed node does not call it. So
|
|
292
|
+
* the node owns the set and drains it, which turns "no more data,
|
|
293
|
+
* ever" into the end of the iteration a page is already written to
|
|
294
|
+
* handle.
|
|
295
|
+
*/
|
|
296
|
+
private readonly streams;
|
|
297
|
+
/**
|
|
298
|
+
* The live org handles (plan §4.5), so {@link BrowserNode.close}
|
|
299
|
+
* can retire ownership: pending calls fail typed and each dropped
|
|
300
|
+
* caller handle emits exactly one CANCEL.
|
|
301
|
+
*/
|
|
302
|
+
private readonly orgHandles;
|
|
303
|
+
/** The last `rtc_addr` an anchor told us about; the probe's subject. */
|
|
304
|
+
private anchorRtcAddr;
|
|
305
|
+
/** Set once the leaf reports a session — a bootstrap that demonstrably worked. */
|
|
306
|
+
private bootstrapObserved;
|
|
307
|
+
/** This node's mesh id, hex. */
|
|
308
|
+
nodeIdHex(): string;
|
|
309
|
+
/** The anchor's mesh id, hex. */
|
|
310
|
+
anchorIdHex(): string;
|
|
311
|
+
/**
|
|
312
|
+
* This node's origin hash, hex.
|
|
313
|
+
*
|
|
314
|
+
* Distinct from {@link BrowserNode.nodeIdHex} and not a synonym:
|
|
315
|
+
* the origin hash rides every packet header this node seals, names
|
|
316
|
+
* its nRPC reply channels (`<service>.replies.<origin>`), and is
|
|
317
|
+
* what a receiver compares an event payload's
|
|
318
|
+
* `EventMeta.origin_hash` against — a direct peer whose header
|
|
319
|
+
* origin and payload origin disagree has the frame dropped before
|
|
320
|
+
* admission. A page that hands this node a pre-encoded event
|
|
321
|
+
* payload to send must build it with THIS value.
|
|
322
|
+
*/
|
|
323
|
+
originHashHex(): string;
|
|
324
|
+
/**
|
|
325
|
+
* Run the enrollment exchange.
|
|
326
|
+
*
|
|
327
|
+
* {@link connect} already awaits it, so a page needs this only to
|
|
328
|
+
* drive or observe the step — a harness proving that an unenrolled
|
|
329
|
+
* leaf is exactly what a provisional peer looks like, for instance.
|
|
330
|
+
* Until a leaf is enrolled the anchor keeps its peer provisional and
|
|
331
|
+
* §12 refuses everything above the transport, which surfaces here as
|
|
332
|
+
* `rpc-timeout` on calls rather than as a transport failure.
|
|
333
|
+
*/
|
|
334
|
+
enroll(): Promise<void>;
|
|
335
|
+
/**
|
|
336
|
+
* Whether the anchor has admitted this leaf. `false` means the
|
|
337
|
+
* session is still §12-provisional, so a call will die on its
|
|
338
|
+
* deadline — check this before blaming a `rpc-timeout` on a slow
|
|
339
|
+
* anchor.
|
|
340
|
+
*/
|
|
341
|
+
isEnrolled(): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Call an nRPC service and resolve its reply.
|
|
344
|
+
*
|
|
345
|
+
* Rejects with {@link RpcError} (`rpc-refused`, `rpc-timeout`,
|
|
346
|
+
* `session-lost`, `leader-lost`, `rpc-malformed`) — never with a
|
|
347
|
+
* silent retry, which is §8's rule for a call whose leader or
|
|
348
|
+
* session went away.
|
|
349
|
+
*/
|
|
350
|
+
call(service: string, payload: Uint8Array, timeoutMs?: number): Promise<Uint8Array>;
|
|
351
|
+
/**
|
|
352
|
+
* Subscribe to a channel. Messages arrive as `channel_message`
|
|
353
|
+
* events; the leaf only delivers channels this node subscribed to,
|
|
354
|
+
* and `channelHash` is its hash of the name.
|
|
355
|
+
*/
|
|
356
|
+
subscribe(channel: string): Promise<void>;
|
|
357
|
+
/** Publish one payload to a channel. */
|
|
358
|
+
publish(channel: string, payload: Uint8Array): Promise<void>;
|
|
359
|
+
/**
|
|
360
|
+
* Open a reliable or fire-and-forget stream.
|
|
361
|
+
*
|
|
362
|
+
* The stream is retained until it or the node closes, so
|
|
363
|
+
* {@link BrowserNode.close} can end the consumers it handed out.
|
|
364
|
+
*
|
|
365
|
+
* On a **closed** node this is a typed refusal, not a dead handle:
|
|
366
|
+
* the leaf's own fence (`Inner::admit`) rejects every outbound
|
|
367
|
+
* operation on a node that no longer holds the origin's identity,
|
|
368
|
+
* and that is re-typed here as `SessionError` (`kind: 'session'`)
|
|
369
|
+
* like every other boundary failure. There is deliberately no
|
|
370
|
+
* second check in TypeScript — one fence, on the side that owns
|
|
371
|
+
* the node's lifetime.
|
|
372
|
+
*/
|
|
373
|
+
openStream(options: OpenStreamOptions): LeafStream;
|
|
374
|
+
/**
|
|
375
|
+
* Call an org-protected unary service.
|
|
376
|
+
*
|
|
377
|
+
* Rejects with the §4.3 typed error: an **opening denial** is
|
|
378
|
+
* {@link OrgAdmissionDeniedError} carrying the coarse reason and
|
|
379
|
+
* nothing finer — a precise remote reason would be a credential
|
|
380
|
+
* oracle. Never a silent retry: the caller is told and decides.
|
|
381
|
+
*
|
|
382
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
383
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
384
|
+
* deadline passes while its tab is suspended retires with its
|
|
385
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
386
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
387
|
+
* executing elsewhere. Nothing resumes automatically: a retired
|
|
388
|
+
* call is never transparently re-opened, because re-opening is a
|
|
389
|
+
* fresh call with a fresh proof and MAY repeat effects. Node or tab
|
|
390
|
+
* close retires ownership — pending calls fail typed, and each
|
|
391
|
+
* dropped caller handle emits exactly one CANCEL.
|
|
392
|
+
*/
|
|
393
|
+
callOrg(service: string, payload: Uint8Array, options: OrgCallOptions): Promise<Uint8Array>;
|
|
394
|
+
/**
|
|
395
|
+
* Call an org-protected server-streaming service; resolve to the
|
|
396
|
+
* response stream.
|
|
397
|
+
*
|
|
398
|
+
* An **opening denial** rejects here with
|
|
399
|
+
* {@link OrgAdmissionDeniedError} (coarse) — a denial is a
|
|
400
|
+
* rejection, never a terminal item. Midstream, the stream's FINAL
|
|
401
|
+
* error item carries the §4.3 outcome: revocation is
|
|
402
|
+
* `AdmissionDenied('denied')` ({@link OrgRevokedError} where the
|
|
403
|
+
* cause is known), deadline/cancel retirement is
|
|
404
|
+
* {@link OrgTimeoutError}/{@link OrgCancelledError}. The async
|
|
405
|
+
* iterator throws that typed error at the terminal error item.
|
|
406
|
+
*
|
|
407
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
408
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
409
|
+
* deadline passes while its tab is suspended retires with its
|
|
410
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
411
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
412
|
+
* executing elsewhere. Nothing resumes automatically: a retired
|
|
413
|
+
* call is never transparently re-opened, because re-opening is a
|
|
414
|
+
* fresh call with a fresh proof and MAY repeat effects. Node or tab
|
|
415
|
+
* close retires ownership — pending calls fail typed, and each
|
|
416
|
+
* dropped caller handle emits exactly one CANCEL.
|
|
417
|
+
*/
|
|
418
|
+
callOrgStreaming(service: string, payload: Uint8Array, options: OrgCallOptions): Promise<OrgByteStream>;
|
|
419
|
+
/**
|
|
420
|
+
* Open an org-protected client-streaming upload. `finish()` is the
|
|
421
|
+
* half-close and the reply await, and it rejects with the typed
|
|
422
|
+
* terminal error (§4.3).
|
|
423
|
+
*
|
|
424
|
+
* An **opening denial** rejects here with
|
|
425
|
+
* {@link OrgAdmissionDeniedError} (coarse).
|
|
426
|
+
*
|
|
427
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
428
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
429
|
+
* deadline passes while its tab is suspended retires with its
|
|
430
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
431
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
432
|
+
* executing elsewhere. Nothing resumes automatically: a retired
|
|
433
|
+
* call is never transparently re-opened, because re-opening is a
|
|
434
|
+
* fresh call with a fresh proof and MAY repeat effects. Node or tab
|
|
435
|
+
* close retires ownership — pending calls fail typed, and each
|
|
436
|
+
* dropped caller handle emits exactly one CANCEL.
|
|
437
|
+
*/
|
|
438
|
+
callOrgClientStream(service: string, options: OrgCallOptions): Promise<OrgUploadCall>;
|
|
439
|
+
/**
|
|
440
|
+
* Open an org-protected duplex call: an upload sink and an
|
|
441
|
+
* independent response stream.
|
|
442
|
+
*
|
|
443
|
+
* An **opening denial** rejects here with
|
|
444
|
+
* {@link OrgAdmissionDeniedError} (coarse). Midstream outcomes are
|
|
445
|
+
* the §4.3 terminals on each half.
|
|
446
|
+
*
|
|
447
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
448
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
449
|
+
* deadline passes while its tab is suspended retires with its
|
|
450
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
451
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
452
|
+
* executing elsewhere. Nothing resumes automatically: a retired
|
|
453
|
+
* call is never transparently re-opened, because re-opening is a
|
|
454
|
+
* fresh call with a fresh proof and MAY repeat effects. Node or tab
|
|
455
|
+
* close retires ownership — pending calls fail typed, and each
|
|
456
|
+
* dropped caller handle emits exactly one CANCEL.
|
|
457
|
+
*/
|
|
458
|
+
callOrgDuplex(service: string, options: OrgCallOptions): Promise<OrgDuplexHandles>;
|
|
459
|
+
/**
|
|
460
|
+
* Serve an org-protected unary service. `access` is `'same-org'`
|
|
461
|
+
* (owner-org traffic only) or `'granted'` (capability grants too);
|
|
462
|
+
* anything else is refused loudly at registration.
|
|
463
|
+
*
|
|
464
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
465
|
+
* handler future without a final poll — cancellation is observed
|
|
466
|
+
* through the retirement observables (the terminal item, the sink's
|
|
467
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
468
|
+
* handler-side event; a detached observer holding `retired`
|
|
469
|
+
* observes the signal. After retirement a handler sees typed
|
|
470
|
+
* refusals and any return value it produces is discarded.
|
|
471
|
+
*
|
|
472
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
473
|
+
* protected split).** Closing the returned handle retires its live
|
|
474
|
+
* protected calls — each with its exact retirement terminal, the
|
|
475
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
476
|
+
* on the service. Deliberately NOT the public path's
|
|
477
|
+
* let-existing-calls-finish behavior.
|
|
478
|
+
*/
|
|
479
|
+
serveOrg(service: string, access: OrgAccess, handler: OrgUnaryHandler, options: OrgServeOptions): OrgServeHandle;
|
|
480
|
+
/**
|
|
481
|
+
* Serve an org-protected server-streaming service: one request in,
|
|
482
|
+
* items out through the {@link OrgResponseSink}.
|
|
483
|
+
*
|
|
484
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
485
|
+
* handler future without a final poll — cancellation is observed
|
|
486
|
+
* through the retirement observables (the terminal item, the sink's
|
|
487
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
488
|
+
* handler-side event; a detached observer holding `retired`
|
|
489
|
+
* observes the signal. After retirement a handler sees typed
|
|
490
|
+
* refusals and any return value it produces is discarded.
|
|
491
|
+
*
|
|
492
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
493
|
+
* protected split).** Closing the returned handle retires its live
|
|
494
|
+
* protected calls — each with its exact retirement terminal, the
|
|
495
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
496
|
+
* on the service. Deliberately NOT the public path's
|
|
497
|
+
* let-existing-calls-finish behavior.
|
|
498
|
+
*/
|
|
499
|
+
serveOrgStreaming(service: string, access: OrgAccess, handler: OrgStreamingHandler, options: OrgServeOptions): OrgServeHandle;
|
|
500
|
+
/**
|
|
501
|
+
* Serve an org-protected client-streaming service: requests in
|
|
502
|
+
* through the {@link OrgRequestStream}, one reply out.
|
|
503
|
+
*
|
|
504
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
505
|
+
* handler future without a final poll — cancellation is observed
|
|
506
|
+
* through the retirement observables (the terminal item, the sink's
|
|
507
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
508
|
+
* handler-side event; a detached observer holding `retired`
|
|
509
|
+
* observes the signal. After retirement a handler sees typed
|
|
510
|
+
* refusals and any return value it produces is discarded.
|
|
511
|
+
*
|
|
512
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
513
|
+
* protected split).** Closing the returned handle retires its live
|
|
514
|
+
* protected calls — each with its exact retirement terminal, the
|
|
515
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
516
|
+
* on the service. Deliberately NOT the public path's
|
|
517
|
+
* let-existing-calls-finish behavior.
|
|
518
|
+
*/
|
|
519
|
+
serveOrgClientStream(service: string, access: OrgAccess, handler: OrgClientStreamHandler, options: OrgServeOptions): OrgServeHandle;
|
|
520
|
+
/**
|
|
521
|
+
* Serve an org-protected duplex service: requests in and items out,
|
|
522
|
+
* independently, through the request stream and the sink.
|
|
523
|
+
*
|
|
524
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
525
|
+
* handler future without a final poll — cancellation is observed
|
|
526
|
+
* through the retirement observables (the terminal item, the sink's
|
|
527
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
528
|
+
* handler-side event; a detached observer holding `retired`
|
|
529
|
+
* observes the signal. After retirement a handler sees typed
|
|
530
|
+
* refusals and any return value it produces is discarded.
|
|
531
|
+
*
|
|
532
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
533
|
+
* protected split).** Closing the returned handle retires its live
|
|
534
|
+
* protected calls — each with its exact retirement terminal, the
|
|
535
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
536
|
+
* on the service. Deliberately NOT the public path's
|
|
537
|
+
* let-existing-calls-finish behavior.
|
|
538
|
+
*/
|
|
539
|
+
serveOrgDuplex(service: string, access: OrgAccess, handler: OrgDuplexHandler, options: OrgServeOptions): OrgServeHandle;
|
|
540
|
+
/**
|
|
541
|
+
* Sign and send a `0x0D02` signalling envelope to `peer` — the
|
|
542
|
+
* session-independent path, so no session with `peer` is needed.
|
|
543
|
+
*
|
|
544
|
+
* `dialogHex` is the attempt's id, 16 lowercase hex digits — the
|
|
545
|
+
* same value {@link BrowserNode.offerPeer} resolves to. Never a
|
|
546
|
+
* number: a `u64` dialog through a JS number comes back rounded,
|
|
547
|
+
* and an envelope signed for a dialog that names no attempt kills
|
|
548
|
+
* the attempt at its ICE deadline.
|
|
549
|
+
*/
|
|
550
|
+
signal(peerHex: string, dialogHex: string, kind: string, payload: Uint8Array): Promise<void>;
|
|
551
|
+
/**
|
|
552
|
+
* Connect **directly** to another browser node — plan §9, from a
|
|
553
|
+
* page.
|
|
554
|
+
*
|
|
555
|
+
* The whole sequence, driven here so a page does not have to:
|
|
556
|
+
* bring up the relayed session through the anchor if there is not
|
|
557
|
+
* one, offer, trickle candidates both ways as signed envelopes,
|
|
558
|
+
* and run the Noise handshake over the DataChannel that results.
|
|
559
|
+
* The direct session then **replaces** the relayed one rather than
|
|
560
|
+
* joining it (§9 step 4), through the node's own session-table
|
|
561
|
+
* fence.
|
|
562
|
+
*
|
|
563
|
+
* `nodeIdHex` is the peer's mesh id in the spelling every peer
|
|
564
|
+
* method takes: 16 lowercase HEX digits —
|
|
565
|
+
* {@link BrowserNode.nodeIdHex} on the other tab, or
|
|
566
|
+
* {@link NodeDescriptor.peerIdHex} from a
|
|
567
|
+
* {@link BrowserNode.query} descriptor. It is **not**
|
|
568
|
+
* {@link NodeDescriptor.nodeId}, which is that id's decimal
|
|
569
|
+
* spelling; pass {@link peerIdHex} over it, or the decimal digits
|
|
570
|
+
* name a different node.
|
|
571
|
+
*
|
|
572
|
+
* **Nothing else is passed, and nothing else can be.** No SDP, no
|
|
573
|
+
* candidate, no key: the peer's Noise static comes from its
|
|
574
|
+
* signature-verified announcement and the pre-shared key from the
|
|
575
|
+
* credential this node connected with. A page that could supply a
|
|
576
|
+
* key could supply any key.
|
|
577
|
+
*
|
|
578
|
+
* **Idempotent on a healthy pair.** When the pair is already direct
|
|
579
|
+
* and its channel open, this resolves `direct` with the live dialog
|
|
580
|
+
* and sends nothing: a second offer would replace the working link
|
|
581
|
+
* and close it under the other side. So a library may call it "to be
|
|
582
|
+
* sure" before opening a stream.
|
|
583
|
+
*
|
|
584
|
+
* Returns a {@link PeerConnectOutcome}. It **rejects** only for
|
|
585
|
+
* something that is not a disposition of the attempt — a closed
|
|
586
|
+
* node, a peer that answered `Reject`, a malformed peer id.
|
|
587
|
+
*/
|
|
588
|
+
connectPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
589
|
+
/**
|
|
590
|
+
* The four primitives the shared drive loop calls, bound to this
|
|
591
|
+
* tab's own node.
|
|
592
|
+
*
|
|
593
|
+
* The dialog-named `*_in` forms, not the peer-only ones: one
|
|
594
|
+
* primitive set for both surfaces means one classification, and a
|
|
595
|
+
* direct caller loses nothing by naming the dialog it already
|
|
596
|
+
* holds.
|
|
597
|
+
*/
|
|
598
|
+
private peerPrimitives;
|
|
599
|
+
/**
|
|
600
|
+
* Run the offerer's Noise handshake on an attempt whose
|
|
601
|
+
* DataChannel is already open, and type the result the way
|
|
602
|
+
* {@link BrowserNode.connectPeer} types it.
|
|
603
|
+
*
|
|
604
|
+
* `connectPeer` is the drive loop plus exactly this call, so there
|
|
605
|
+
* is one classifier and not two. It is public because the four
|
|
606
|
+
* `#[wasm_bindgen]` methods are a page surface in their own right:
|
|
607
|
+
* a page that drove `peer_offer`, `peer_accept_offer` and
|
|
608
|
+
* `peer_candidate` itself — because it wanted to observe or
|
|
609
|
+
* intervene between them — would otherwise have to re-derive the
|
|
610
|
+
* typing of the last step, and a second classifier is a second
|
|
611
|
+
* set of bugs.
|
|
612
|
+
*
|
|
613
|
+
* `dialog` is the id `peer_offer` returned, and it is CHECKED
|
|
614
|
+
* rather than assumed: `peer_handshake` resolves to the dialog it
|
|
615
|
+
* actually ran for, and any mismatch is this caller's attempt
|
|
616
|
+
* having been replaced underneath it. Reporting `direct` with the
|
|
617
|
+
* caller's own dialog on any success is how a stale caller was
|
|
618
|
+
* told its superseded attempt had connected — while the leaf had
|
|
619
|
+
* handshaken the attempt that replaced it.
|
|
620
|
+
*/
|
|
621
|
+
handshakePeer(nodeIdHex: string, dialog: string): Promise<PeerConnectOutcome>;
|
|
622
|
+
/**
|
|
623
|
+
* Accept the offer `nodeIdHex` sent, and drive the attempt to a
|
|
624
|
+
* direct session — {@link BrowserNode.connectPeer}'s counterpart.
|
|
625
|
+
*
|
|
626
|
+
* Call it when a `signal` event arrives from that peer: the offer
|
|
627
|
+
* it answers is the **verified envelope** the leaf already holds,
|
|
628
|
+
* so the page neither sees nor supplies the SDP.
|
|
629
|
+
*
|
|
630
|
+
* The answerer does not run the handshake — §9 step 4 puts Noise
|
|
631
|
+
* in the offerer's role, so this waits for the direct session to
|
|
632
|
+
* install from the inbound message instead. Same
|
|
633
|
+
* {@link PeerConnectOutcome}, same meanings.
|
|
634
|
+
*/
|
|
635
|
+
acceptPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
636
|
+
/**
|
|
637
|
+
* Read one `peer_candidate` status for a live attempt.
|
|
638
|
+
*
|
|
639
|
+
* Exposed because it is the only way to observe an attempt without
|
|
640
|
+
* driving it to a conclusion — which is what a witness asserting
|
|
641
|
+
* the shape of the exchange needs, and what a page showing
|
|
642
|
+
* connection progress wants.
|
|
643
|
+
*/
|
|
644
|
+
peerAttempt(nodeIdHex: string): Promise<PeerAttemptStatus>;
|
|
645
|
+
/**
|
|
646
|
+
* Every leaf counter, including each drop reason. Values are exact
|
|
647
|
+
* decimal strings because they are `u64` on the Rust side.
|
|
648
|
+
*/
|
|
649
|
+
counters(): Record<string, string>;
|
|
650
|
+
/**
|
|
651
|
+
* The RTC transport's `RtcStats`, in the **same field names** the
|
|
652
|
+
* native `RtcStats` uses — plan §10's telemetry surface on the
|
|
653
|
+
* leaf.
|
|
654
|
+
*
|
|
655
|
+
* Values are exact decimal strings, for the reason
|
|
656
|
+
* {@link BrowserNode.counters} gives. `notApplicable` is not a
|
|
657
|
+
* counter: it maps each native field a leaf has no meaning for to
|
|
658
|
+
* the reason it has none, because a field frozen at `0` reads as
|
|
659
|
+
* an observation ("no ingress overflow", "no STUN requests
|
|
660
|
+
* answered") and a browser leaf is not entitled to those claims.
|
|
661
|
+
* Native makes the same choice in the other direction — it
|
|
662
|
+
* carries no `udpBlocked`/`udp_blocked` field at all — so
|
|
663
|
+
* `udp_blocked` is the one term here with no native counterpart.
|
|
664
|
+
*
|
|
665
|
+
* `ice_pending` is absent on both sides on purpose: it is
|
|
666
|
+
* `ice_attempted - (direct + relayed + failed + udp_blocked)`,
|
|
667
|
+
* the attempts still in flight, and the §10 identity is exact
|
|
668
|
+
* only where that residual is zero. Derive it; do not expect it.
|
|
669
|
+
*/
|
|
670
|
+
rtcStats(): RtcStatsReading;
|
|
671
|
+
/**
|
|
672
|
+
* Arm the network-change re-attempt trigger: `online` events and
|
|
673
|
+
* an ICE `disconnected` → `failed` transition drive **one**
|
|
674
|
+
* bounded re-attempt per network change, per peer.
|
|
675
|
+
*
|
|
676
|
+
* Opt-in, and that is a decision rather than an omission.
|
|
677
|
+
* Re-dialling is policy and policy is the application's: a page
|
|
678
|
+
* may be holding a pair on the routed path deliberately, may be
|
|
679
|
+
* tearing down, may be on a metered link. §9 step 6 makes the
|
|
680
|
+
* routed session a supported disposition rather than a degraded
|
|
681
|
+
* one, so "this pair is relayed" is not a fault the library may
|
|
682
|
+
* assume it should fix.
|
|
683
|
+
*
|
|
684
|
+
* Idempotent — a second call installs no second listener. Only
|
|
685
|
+
* pairs this node took direct **as the offerer** are
|
|
686
|
+
* re-attempted: `connectPeer`'s side owns the repair, and both
|
|
687
|
+
* ends re-offering one network change would be two attempts per
|
|
688
|
+
* pair plus a supersession race.
|
|
689
|
+
*
|
|
690
|
+
* Once armed, {@link BrowserNode.retryReport} is how a page
|
|
691
|
+
* observes it.
|
|
692
|
+
*/
|
|
693
|
+
enableNetworkRetry(): void;
|
|
694
|
+
/**
|
|
695
|
+
* What the re-attempt owner has seen and done.
|
|
696
|
+
*
|
|
697
|
+
* `started` is the assertion that matters: one network change, one
|
|
698
|
+
* re-attempt, however many observations of it the browser
|
|
699
|
+
* delivered. `triggers` is how many arrived and `coalesced` is
|
|
700
|
+
* the difference the owner absorbed — reported rather than
|
|
701
|
+
* hidden, because "the trigger never fired" and "it fired and was
|
|
702
|
+
* absorbed" are different facts and only one of them is a defect.
|
|
703
|
+
*/
|
|
704
|
+
retryReport(): RetryReport;
|
|
705
|
+
/** Publish this node's capabilities as a signed fold announcement. */
|
|
706
|
+
announce(capabilities: readonly string[]): Promise<void>;
|
|
707
|
+
/** Find the nodes offering a capability. */
|
|
708
|
+
query(capability: string): Promise<NodeDescriptor[]>;
|
|
709
|
+
/** Listen for one event tag. Returns a cancel handle. */
|
|
710
|
+
on<T extends LeafEvent['type']>(type: T, handler: (event: LeafEventOf<T>) => void): Unsubscribe;
|
|
711
|
+
/** Listen for every event. Returns a cancel handle. */
|
|
712
|
+
onEvent(handler: (event: LeafEvent) => void): Unsubscribe;
|
|
713
|
+
/** Consume events as an async iterable. */
|
|
714
|
+
events(): AsyncIterableIterator<LeafEvent>;
|
|
715
|
+
/**
|
|
716
|
+
* Narrow an ICE failure to `udp-blocked` when the evidence is there.
|
|
717
|
+
*
|
|
718
|
+
* Anything that is not an `ice-timeout` is returned untouched. For an
|
|
719
|
+
* `ice-timeout` it gathers the two observations the correction
|
|
720
|
+
* requires — the anchor answering over HTTPS, and a STUN binding to
|
|
721
|
+
* the address that anchor published going unanswered — and returns
|
|
722
|
+
* the classification {@link classifyRtcError} makes of them. With no
|
|
723
|
+
* address to probe there is no evidence, and the error comes back
|
|
724
|
+
* unchanged.
|
|
725
|
+
*
|
|
726
|
+
* Public because a page holding an `rtc_failure` event's error, or a
|
|
727
|
+
* Playwright witness holding a rejection, wants the same narrowing
|
|
728
|
+
* the connect path gets.
|
|
729
|
+
*/
|
|
730
|
+
refineIceFailure(error: LeafError): Promise<LeafError>;
|
|
731
|
+
/**
|
|
732
|
+
* Close the node, its streams and its event surface.
|
|
733
|
+
*
|
|
734
|
+
* **Every stream this node handed out is ended first**, and that
|
|
735
|
+
* is a behaviour change: an iterator parked in `for await`
|
|
736
|
+
* completes here rather than hanging forever on a node that will
|
|
737
|
+
* never emit again, and `onMessage` listeners are dropped. It is
|
|
738
|
+
* the disposition the leader-proxied surface already gave its
|
|
739
|
+
* streams on a generation change (`MeshSession.endStreams`), so
|
|
740
|
+
* the two surfaces now dispose of a stream the same way; the
|
|
741
|
+
* direct one was the outlier.
|
|
742
|
+
*
|
|
743
|
+
* The order is load-bearing: the streams go before
|
|
744
|
+
* `inner.close()`, because a wasm `LeafStream.close` retires its
|
|
745
|
+
* handle *through the node* and a node already closed would
|
|
746
|
+
* report that as a failed close instead of performing it.
|
|
747
|
+
*
|
|
748
|
+
* **Every one of those closes is attempted.** `close` latches and
|
|
749
|
+
* empties its set before the loop, so there is no second chance at
|
|
750
|
+
* any of it: a child whose `close` threw out of the loop left the
|
|
751
|
+
* next child open with its iterator parked forever, the hub
|
|
752
|
+
* dispatching, the leaf never closed — and the retry a page then
|
|
753
|
+
* makes returns immediately, because the latch is set. So each
|
|
754
|
+
* obligation is discharged independently and the failures are
|
|
755
|
+
* collected, which is the only reading under which the latch is
|
|
756
|
+
* honest.
|
|
757
|
+
*
|
|
758
|
+
* Reported, not swallowed: if anything threw, this throws an
|
|
759
|
+
* `AggregateError` whose `errors` are the {@link LeafError}s in
|
|
760
|
+
* teardown order — always an aggregate, however many failed,
|
|
761
|
+
* because "how many parts of the close failed" is not a shape a
|
|
762
|
+
* caller should have to branch on to find out that one did. A
|
|
763
|
+
* host-supplied {@link LeafWasmStreamLike} is the surface that
|
|
764
|
+
* makes this reachable; the leaf's own `close` does not throw.
|
|
765
|
+
*/
|
|
766
|
+
close(): void;
|
|
767
|
+
/**
|
|
768
|
+
* The one `on_event` callback. Two events teach the probe something:
|
|
769
|
+
* `connected` proves the HTTPS bootstrap worked and carries the
|
|
770
|
+
* `rtc_addr` the anchor published, which is the only address this
|
|
771
|
+
* package may aim a STUN probe at.
|
|
772
|
+
*/
|
|
773
|
+
private ingest;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Connect to the mesh through an anchor.
|
|
777
|
+
*
|
|
778
|
+
* Loads and instantiates the leaf wasm (once per page, whatever the
|
|
779
|
+
* number of `connect()` calls), hands it the credential, and resolves a
|
|
780
|
+
* {@link BrowserNode}. Rejects with a typed {@link LeafError}; an ICE
|
|
781
|
+
* failure is classified per {@link BrowserNode.refineIceFailure}.
|
|
782
|
+
*/
|
|
783
|
+
export declare function connect(options: ConnectOptions): Promise<BrowserNode>;
|
|
784
|
+
/**
|
|
785
|
+
* Build exactly the options object `LeafNode.connect` reads, credential
|
|
786
|
+
* resolved, origin defaulted, custodial identity validated.
|
|
787
|
+
*
|
|
788
|
+
* Exported because `src/leader/` builds `MeshSession.open`'s options on
|
|
789
|
+
* top of it: the two surfaces take the same connect keys, and a key
|
|
790
|
+
* added here must not be silently missing there. A dropped option is
|
|
791
|
+
* invisible from a page — it has already cost one witness — so the two
|
|
792
|
+
* share the builder rather than each listing the keys.
|
|
793
|
+
*/
|
|
794
|
+
export declare function buildConnectRequest(options: ConnectOptions): LeafWasmConnectOptions;
|
|
795
|
+
/**
|
|
796
|
+
* Parse a `counters_json()` payload. Values stay strings because they
|
|
797
|
+
* are `u64` on the Rust side and a JS number would round the large
|
|
798
|
+
* ones. Exported for the session surface, whose `counters_json` is a
|
|
799
|
+
* promise but whose payload is identical.
|
|
800
|
+
*/
|
|
801
|
+
export declare function parseCounters(json: string): Record<string, string>;
|
|
802
|
+
/**
|
|
803
|
+
* Parse one `peer_candidate` reading.
|
|
804
|
+
*
|
|
805
|
+
* `remainingMs` stays a string for the reason every other `u64` on
|
|
806
|
+
* this boundary does, and `state` is validated rather than cast: an
|
|
807
|
+
* unrecognised state would otherwise fall through both the
|
|
808
|
+
* `iceTimeout` and the `open` arm of a drive loop and spin until the
|
|
809
|
+
* leaf's own deadline, which is the slowest possible way to report a
|
|
810
|
+
* boundary that changed shape.
|
|
811
|
+
*
|
|
812
|
+
* `candidateError` is carried through as a value. The leaf warns it
|
|
813
|
+
* to the console as well, but a console line is not something a
|
|
814
|
+
* page, a witness or a diagnostic can read — and this parser used to
|
|
815
|
+
* drop the field, which made the leaf's machine-readable diagnostic
|
|
816
|
+
* unreachable from TypeScript.
|
|
817
|
+
*
|
|
818
|
+
* Exported for the unit tests, which drive it with fixed payloads.
|
|
819
|
+
*/
|
|
820
|
+
export declare function parseAttemptStatus(json: string): PeerAttemptStatus;
|
|
821
|
+
/**
|
|
822
|
+
* One `rtc_stats_json()` reading — plan §10's `RtcStats` on the leaf.
|
|
823
|
+
*
|
|
824
|
+
* `counters` are exact decimal strings under the NATIVE field names;
|
|
825
|
+
* `notApplicable` maps each native field a leaf has no meaning for
|
|
826
|
+
* to the reason it has none. Kept apart in the type because they are
|
|
827
|
+
* different kinds of thing: one is measurement, the other is the
|
|
828
|
+
* refusal to pretend to measure.
|
|
829
|
+
*/
|
|
830
|
+
export interface RtcStatsReading {
|
|
831
|
+
readonly counters: Record<string, string>;
|
|
832
|
+
readonly notApplicable: Record<string, string>;
|
|
833
|
+
}
|
|
834
|
+
/** What {@link BrowserNode.retryReport} reports. */
|
|
835
|
+
export interface RetryReport {
|
|
836
|
+
/** Whether {@link BrowserNode.enableNetworkRetry} has been called. */
|
|
837
|
+
readonly armed: boolean;
|
|
838
|
+
/** Triggers filed by the `online` listener. */
|
|
839
|
+
readonly online: string;
|
|
840
|
+
/** Triggers filed by the ICE `disconnected` → `failed` watcher. */
|
|
841
|
+
readonly iceFailed: string;
|
|
842
|
+
/** Every trigger seen, whatever the owner decided. */
|
|
843
|
+
readonly triggers: string;
|
|
844
|
+
/** Re-attempts STARTED. One per network change, per peer. */
|
|
845
|
+
readonly started: string;
|
|
846
|
+
/** Triggers absorbed into an episode that already had its attempt. */
|
|
847
|
+
readonly coalesced: string;
|
|
848
|
+
/** Triggers for a peer with nothing to repair. */
|
|
849
|
+
readonly notEligible: string;
|
|
850
|
+
/** Episodes open right now. */
|
|
851
|
+
readonly openEpisodes: number;
|
|
852
|
+
/** Pairs this node took direct as the offerer, i.e. the eligible set. */
|
|
853
|
+
readonly owned: number;
|
|
854
|
+
/**
|
|
855
|
+
* The last re-attempt's disposition, in
|
|
856
|
+
* {@link PeerConnectOutcome}'s vocabulary, or `null` before the
|
|
857
|
+
* first one. `offerRefused` is the one name not in that union: it
|
|
858
|
+
* is a repair that could not even re-offer, which `connectPeer`
|
|
859
|
+
* surfaces as a thrown error rather than an outcome.
|
|
860
|
+
*/
|
|
861
|
+
readonly last: string | null;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Parse an `rtc_stats_json()` payload.
|
|
865
|
+
*
|
|
866
|
+
* `not_applicable` is lifted out rather than left among the
|
|
867
|
+
* counters: a consumer iterating the reading must not find a field
|
|
868
|
+
* whose value is a sentence where every other value is a number.
|
|
869
|
+
*
|
|
870
|
+
* Exported for the unit tests, which drive it with fixed payloads.
|
|
871
|
+
*/
|
|
872
|
+
export declare function parseRtcStats(json: string): RtcStatsReading;
|
|
873
|
+
/**
|
|
874
|
+
* Parse a `retry_report()` payload.
|
|
875
|
+
*
|
|
876
|
+
* Counts stay strings because they are `u64`, for the reason
|
|
877
|
+
* {@link parseCounters} gives. Exported for the unit tests.
|
|
878
|
+
*/
|
|
879
|
+
export declare function parseRetryReport(json: string): RetryReport;
|
|
880
|
+
/** What {@link refineIceFailure} is allowed to know. */
|
|
881
|
+
interface RefineContext {
|
|
882
|
+
/** `null` when the credential carried the endpoint and the page never named it. */
|
|
883
|
+
readonly bootstrapUrl: string | null;
|
|
884
|
+
/** The leaf already demonstrated a working bootstrap this session. */
|
|
885
|
+
readonly bootstrapObserved: boolean;
|
|
886
|
+
readonly anchorRtcAddr: string | null;
|
|
887
|
+
readonly failureTyping: FailureTypingOptions;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* The orchestration half of the correction: gather the observations,
|
|
891
|
+
* then let the pure classifier decide. Exported for the unit tests,
|
|
892
|
+
* which drive it with fake probes.
|
|
893
|
+
*/
|
|
894
|
+
export declare function refineIceFailure(error: LeafError, context: RefineContext): Promise<LeafError>;
|
|
895
|
+
/**
|
|
896
|
+
* Parse `query`'s JSON array — `{ node_id, entity_id, capabilities,
|
|
897
|
+
* rtc_addr, noise_pubkey, version }` per entry, with `node_id` and
|
|
898
|
+
* `version` kept as exact decimal strings. Exported for the unit tests.
|
|
899
|
+
*
|
|
900
|
+
* `peerIdHex` is derived beside the decimal `nodeId`, never instead
|
|
901
|
+
* of it: the peer methods take hex and the announcement carries
|
|
902
|
+
* decimal, and a consumer that cannot see both cannot tell which it
|
|
903
|
+
* is holding. A `node_id` that is not a decimal `u64` is a boundary
|
|
904
|
+
* that changed shape and is refused rather than turned into an
|
|
905
|
+
* address.
|
|
906
|
+
*/
|
|
907
|
+
export declare function parseDescriptors(json: string): NodeDescriptor[];
|
|
908
|
+
export {};
|