@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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-consumer async queue behind every async iterable in this
|
|
3
|
+
* package (`node.events()`, `stream`).
|
|
4
|
+
*
|
|
5
|
+
* Items that arrive while the consumer is not awaiting are buffered in
|
|
6
|
+
* arrival order; buffering stops the moment the loop is left (`break`,
|
|
7
|
+
* `return`, or an exception), which is what calls the `detach` hook, so
|
|
8
|
+
* a page that stops consuming stops paying. A page that starts a loop
|
|
9
|
+
* and then never awaits it will grow the buffer — leave the loop
|
|
10
|
+
* instead of abandoning it.
|
|
11
|
+
*/
|
|
12
|
+
export class AsyncQueue {
|
|
13
|
+
detach;
|
|
14
|
+
buffered = [];
|
|
15
|
+
waiting = null;
|
|
16
|
+
ended = false;
|
|
17
|
+
/** @param detach called once when the consumer stops iterating. */
|
|
18
|
+
constructor(detach = () => { }) {
|
|
19
|
+
this.detach = detach;
|
|
20
|
+
}
|
|
21
|
+
/** Hand an item to the consumer, or buffer it. */
|
|
22
|
+
push(item) {
|
|
23
|
+
if (this.ended)
|
|
24
|
+
return;
|
|
25
|
+
const waiting = this.waiting;
|
|
26
|
+
if (waiting) {
|
|
27
|
+
this.waiting = null;
|
|
28
|
+
waiting({ value: item, done: false });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.buffered.push(item);
|
|
32
|
+
}
|
|
33
|
+
/** End the iteration once the buffer drains. */
|
|
34
|
+
end() {
|
|
35
|
+
if (this.ended)
|
|
36
|
+
return;
|
|
37
|
+
this.ended = true;
|
|
38
|
+
const waiting = this.waiting;
|
|
39
|
+
if (waiting) {
|
|
40
|
+
this.waiting = null;
|
|
41
|
+
waiting({ value: undefined, done: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
next() {
|
|
45
|
+
const buffered = this.buffered.shift();
|
|
46
|
+
if (buffered !== undefined)
|
|
47
|
+
return Promise.resolve({ value: buffered, done: false });
|
|
48
|
+
if (this.ended)
|
|
49
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
50
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
51
|
+
this.waiting = resolve;
|
|
52
|
+
return promise;
|
|
53
|
+
}
|
|
54
|
+
return() {
|
|
55
|
+
this.detach();
|
|
56
|
+
this.end();
|
|
57
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
58
|
+
}
|
|
59
|
+
[Symbol.asyncIterator]() {
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `requestCredential` — ask a game anchor for an anonymous visitor
|
|
3
|
+
* credential, so a page connects with no hand-minted secret.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { openSession, requestCredential } from '@net-mesh/browser';
|
|
7
|
+
*
|
|
8
|
+
* const { credentialB64, bootstrapUrl } = await requestCredential({
|
|
9
|
+
* anchorUrl: 'https://anchor.example.com',
|
|
10
|
+
* game: 'my-game',
|
|
11
|
+
* });
|
|
12
|
+
* const session = await openSession({ credentialB64, bootstrapUrl });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The anchor must admit the game (`net-mesh anchor serve --game my-game`).
|
|
16
|
+
* Each call is a new visitor seat: the credential binds to the first
|
|
17
|
+
* browser identity that enrolls with it, and that identity may reconnect
|
|
18
|
+
* with it for the credential's lifetime (a promoted leader tab does).
|
|
19
|
+
*
|
|
20
|
+
* **Who the player is.** A game connects with `connect()` (stores, lobbies
|
|
21
|
+
* and the lossy channel need it) and keeps its player with
|
|
22
|
+
* `rememberedIdentity()`, which stores the identity in this origin's
|
|
23
|
+
* `localStorage`: one player per **browser profile**. Clearing site data
|
|
24
|
+
* makes a new player, and the same person on two devices is two players.
|
|
25
|
+
* A bare `connect()` without it makes a new identity every page load.
|
|
26
|
+
*/
|
|
27
|
+
/** {@link requestCredential}'s argument. */
|
|
28
|
+
export interface RequestCredentialOptions {
|
|
29
|
+
/**
|
|
30
|
+
* The anchor's bootstrap URL — what the operator passed as
|
|
31
|
+
* `anchor serve --url`, e.g. `https://anchor.example.com`.
|
|
32
|
+
*/
|
|
33
|
+
readonly anchorUrl: string;
|
|
34
|
+
/** The game id the anchor admits (`--game`). */
|
|
35
|
+
readonly game: string;
|
|
36
|
+
/** Cancels the request. */
|
|
37
|
+
readonly signal?: AbortSignal;
|
|
38
|
+
/** A `fetch` to use instead of the global one (tests, workers). */
|
|
39
|
+
readonly fetch?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
/** What {@link requestCredential} returns — exactly what `connect()` / `openSession()` take. */
|
|
42
|
+
export interface AnchorCredential {
|
|
43
|
+
readonly credentialB64: string;
|
|
44
|
+
readonly bootstrapUrl: string;
|
|
45
|
+
readonly game: string;
|
|
46
|
+
}
|
|
47
|
+
/** Why {@link requestCredential} failed. */
|
|
48
|
+
export type CredentialRequestErrorKind =
|
|
49
|
+
/** The anchor does not admit that game. */
|
|
50
|
+
'unknown-game'
|
|
51
|
+
/** Too many requests from this address, or for this game; retry shortly. */
|
|
52
|
+
| 'rate-limited'
|
|
53
|
+
/** The anchor did not understand the request. */
|
|
54
|
+
| 'malformed-request'
|
|
55
|
+
/** The anchor could not be reached, or it issues no credentials. */
|
|
56
|
+
| 'unreachable'
|
|
57
|
+
/** The anchor answered something this package does not recognise. */
|
|
58
|
+
| 'unexpected';
|
|
59
|
+
/** A typed {@link requestCredential} failure. */
|
|
60
|
+
export declare class CredentialRequestError extends Error {
|
|
61
|
+
readonly kind: CredentialRequestErrorKind;
|
|
62
|
+
/** The HTTP status, when the anchor answered. */
|
|
63
|
+
readonly status: number | null;
|
|
64
|
+
readonly name = "CredentialRequestError";
|
|
65
|
+
constructor(kind: CredentialRequestErrorKind, message: string,
|
|
66
|
+
/** The HTTP status, when the anchor answered. */
|
|
67
|
+
status?: number | null);
|
|
68
|
+
}
|
|
69
|
+
/** Ask `anchorUrl` for a fresh anonymous credential for `game`. */
|
|
70
|
+
export declare function requestCredential(options: RequestCredentialOptions): Promise<AnchorCredential>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `requestCredential` — ask a game anchor for an anonymous visitor
|
|
3
|
+
* credential, so a page connects with no hand-minted secret.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { openSession, requestCredential } from '@net-mesh/browser';
|
|
7
|
+
*
|
|
8
|
+
* const { credentialB64, bootstrapUrl } = await requestCredential({
|
|
9
|
+
* anchorUrl: 'https://anchor.example.com',
|
|
10
|
+
* game: 'my-game',
|
|
11
|
+
* });
|
|
12
|
+
* const session = await openSession({ credentialB64, bootstrapUrl });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The anchor must admit the game (`net-mesh anchor serve --game my-game`).
|
|
16
|
+
* Each call is a new visitor seat: the credential binds to the first
|
|
17
|
+
* browser identity that enrolls with it, and that identity may reconnect
|
|
18
|
+
* with it for the credential's lifetime (a promoted leader tab does).
|
|
19
|
+
*
|
|
20
|
+
* **Who the player is.** A game connects with `connect()` (stores, lobbies
|
|
21
|
+
* and the lossy channel need it) and keeps its player with
|
|
22
|
+
* `rememberedIdentity()`, which stores the identity in this origin's
|
|
23
|
+
* `localStorage`: one player per **browser profile**. Clearing site data
|
|
24
|
+
* makes a new player, and the same person on two devices is two players.
|
|
25
|
+
* A bare `connect()` without it makes a new identity every page load.
|
|
26
|
+
*/
|
|
27
|
+
/** A typed {@link requestCredential} failure. */
|
|
28
|
+
export class CredentialRequestError extends Error {
|
|
29
|
+
kind;
|
|
30
|
+
status;
|
|
31
|
+
name = 'CredentialRequestError';
|
|
32
|
+
constructor(kind, message,
|
|
33
|
+
/** The HTTP status, when the anchor answered. */
|
|
34
|
+
status = null) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.kind = kind;
|
|
37
|
+
this.status = status;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const REFUSALS = {
|
|
41
|
+
unknown_game: 'unknown-game',
|
|
42
|
+
rate_limited: 'rate-limited',
|
|
43
|
+
malformed_request: 'malformed-request',
|
|
44
|
+
};
|
|
45
|
+
/** Ask `anchorUrl` for a fresh anonymous credential for `game`. */
|
|
46
|
+
export async function requestCredential(options) {
|
|
47
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
48
|
+
if (typeof doFetch !== 'function') {
|
|
49
|
+
throw new CredentialRequestError('unreachable', 'requestCredential needs fetch');
|
|
50
|
+
}
|
|
51
|
+
const endpoint = `${options.anchorUrl.replace(/\/+$/, '')}/credential`;
|
|
52
|
+
let response;
|
|
53
|
+
try {
|
|
54
|
+
response = await doFetch(endpoint, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'content-type': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ game: options.game }),
|
|
58
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (options.signal?.aborted)
|
|
63
|
+
throw error;
|
|
64
|
+
throw new CredentialRequestError('unreachable', `could not reach ${endpoint}: ${String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
let body = null;
|
|
67
|
+
try {
|
|
68
|
+
body = await response.json();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Not JSON: an anchor without `--game` answers its router's bare 404.
|
|
72
|
+
}
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const refusal = body?.refusal;
|
|
75
|
+
const message = body?.message;
|
|
76
|
+
const kind = typeof refusal === 'string' && refusal in REFUSALS
|
|
77
|
+
? REFUSALS[refusal]
|
|
78
|
+
: response.status === 404
|
|
79
|
+
? 'unreachable'
|
|
80
|
+
: 'unexpected';
|
|
81
|
+
const detail = kind === 'unreachable'
|
|
82
|
+
? `${endpoint} issues no credentials (is the anchor started with --game?)`
|
|
83
|
+
: typeof message === 'string'
|
|
84
|
+
? message
|
|
85
|
+
: `the anchor answered ${response.status}`;
|
|
86
|
+
throw new CredentialRequestError(kind, detail, response.status);
|
|
87
|
+
}
|
|
88
|
+
const reply = body;
|
|
89
|
+
if (typeof reply?.credentialB64 !== 'string' ||
|
|
90
|
+
typeof reply.bootstrapUrl !== 'string' ||
|
|
91
|
+
typeof reply.game !== 'string') {
|
|
92
|
+
throw new CredentialRequestError('unexpected', 'the anchor answered without a credential', response.status);
|
|
93
|
+
}
|
|
94
|
+
return { credentialB64: reply.credentialB64, bootstrapUrl: reply.bootstrapUrl, game: reply.game };
|
|
95
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The leaf's error taxonomy, mirrored in TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* One class per variant of `net_leaf::error::LeafError`, with the two
|
|
5
|
+
* nested enums (`RtcError`, `RpcError`) flattened into `kind` so a
|
|
6
|
+
* caller — or a Playwright assertion — can branch on one stable
|
|
7
|
+
* string. `message` is verbatim the Rust `Display` text that crossed
|
|
8
|
+
* the wasm boundary, so nothing is lost in the re-typing;
|
|
9
|
+
* `errors.test.ts` pins that round-trip.
|
|
10
|
+
*
|
|
11
|
+
* **The failure-typing correction lives in this file's types.** There
|
|
12
|
+
* is no way to build a {@link RtcErrorFailure} of type `udpBlocked`
|
|
13
|
+
* without a {@link UdpBlockedEvidence}, and `udpBlockedEvidence()` —
|
|
14
|
+
* the mirror of Rust's `UdpBlockedEvidence::new` — returns `null`
|
|
15
|
+
* unless both observations hold. An ICE timeout on its own therefore
|
|
16
|
+
* cannot become `udp-blocked` anywhere in this package.
|
|
17
|
+
*/
|
|
18
|
+
/** Every error kind the leaf can surface, one per Rust variant. */
|
|
19
|
+
export type LeafErrorKind = 'wire' | 'session' | 'control-plane' | 'identity' | 'not-leader' | 'ice-timeout' | 'udp-blocked' | 'channel-closed' | 'rtc-unsupported' | 'rpc-refused' | 'rpc-timeout' | 'session-lost' | 'leader-lost' | 'rpc-indeterminate' | 'rpc-malformed' | 'ice-server-conflict' | 'org-admission-denied' | 'org-revoked' | 'org-timeout' | 'org-cancelled' | 'org-leader-lost' | 'org-session-lost' | 'org-indeterminate' | 'org-refused' | 'org-internal' | 'org-malformed' | 'unknown';
|
|
20
|
+
/**
|
|
21
|
+
* The two observations that, together, distinguish blocked UDP from
|
|
22
|
+
* an anchor that simply is not there. Mirrors
|
|
23
|
+
* `net_leaf::error::UdpBlockedEvidence`.
|
|
24
|
+
*
|
|
25
|
+
* The field types are the literal `true`, not `boolean`: a value of
|
|
26
|
+
* this type is itself the proof, so a caller cannot hand one over
|
|
27
|
+
* with `bootstrapOk: false`.
|
|
28
|
+
*/
|
|
29
|
+
export interface UdpBlockedEvidence {
|
|
30
|
+
/** The anchor answered over HTTPS — it is up and addressable. */
|
|
31
|
+
readonly bootstrapOk: true;
|
|
32
|
+
/** A STUN binding to the `rtc_addr` that anchor published went unanswered. */
|
|
33
|
+
readonly stunProbeFailed: true;
|
|
34
|
+
/** The address the probe was aimed at, so the claim names its subject. */
|
|
35
|
+
readonly probed: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `UdpBlockedEvidence` only when both observations hold; `null`
|
|
39
|
+
* otherwise, which is the caller's instruction to stay with
|
|
40
|
+
* `ice-timeout`. Mirror of `UdpBlockedEvidence::new`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function udpBlockedEvidence(bootstrapOk: boolean, stunProbeFailed: boolean, probed: string): UdpBlockedEvidence | null;
|
|
43
|
+
/** Why an RTC attempt did not produce a DataChannel. */
|
|
44
|
+
export type RtcErrorFailure = {
|
|
45
|
+
readonly type: 'iceTimeout';
|
|
46
|
+
} | {
|
|
47
|
+
readonly type: 'udpBlocked';
|
|
48
|
+
readonly evidence: UdpBlockedEvidence;
|
|
49
|
+
} | {
|
|
50
|
+
readonly type: 'channelClosed';
|
|
51
|
+
readonly detail: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: 'unsupported';
|
|
54
|
+
readonly detail: string;
|
|
55
|
+
};
|
|
56
|
+
/** How an nRPC call ended when it did not return a reply. */
|
|
57
|
+
export type RpcErrorFailure = {
|
|
58
|
+
readonly type: 'refused';
|
|
59
|
+
readonly status: number;
|
|
60
|
+
readonly message: string;
|
|
61
|
+
} | {
|
|
62
|
+
readonly type: 'timeout';
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: 'sessionLost';
|
|
65
|
+
} | {
|
|
66
|
+
readonly type: 'leaderLost';
|
|
67
|
+
readonly generation: number;
|
|
68
|
+
} | {
|
|
69
|
+
readonly type: 'indeterminate';
|
|
70
|
+
readonly deadlineMs: number;
|
|
71
|
+
} | {
|
|
72
|
+
readonly type: 'malformed';
|
|
73
|
+
readonly detail: string;
|
|
74
|
+
};
|
|
75
|
+
/** The base class of everything this package rejects with. */
|
|
76
|
+
export declare abstract class LeafError extends Error {
|
|
77
|
+
/** The flat, stable discriminant. */
|
|
78
|
+
abstract readonly kind: LeafErrorKind;
|
|
79
|
+
protected constructor(message: string);
|
|
80
|
+
}
|
|
81
|
+
/** The wire layer refused a packet: framing, AEAD, replay window. */
|
|
82
|
+
export declare class WireError extends LeafError {
|
|
83
|
+
readonly detail: string;
|
|
84
|
+
readonly kind: "wire";
|
|
85
|
+
constructor(detail: string);
|
|
86
|
+
}
|
|
87
|
+
/** No session for a peer, an incomplete handshake, a replaced session. */
|
|
88
|
+
export declare class SessionError extends LeafError {
|
|
89
|
+
readonly detail: string;
|
|
90
|
+
readonly kind: "session";
|
|
91
|
+
constructor(detail: string);
|
|
92
|
+
}
|
|
93
|
+
/** The control plane could not carry what the leaf asked it to. */
|
|
94
|
+
export declare class ControlPlaneError extends LeafError {
|
|
95
|
+
readonly detail: string;
|
|
96
|
+
readonly kind: "control-plane";
|
|
97
|
+
constructor(detail: string);
|
|
98
|
+
}
|
|
99
|
+
/** Identity storage, generation or custody. */
|
|
100
|
+
export declare class IdentityError extends LeafError {
|
|
101
|
+
readonly detail: string;
|
|
102
|
+
readonly kind: "identity";
|
|
103
|
+
constructor(detail: string);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A supplied `iceServers` entry named **this connection's own peer**
|
|
107
|
+
* as its STUN server.
|
|
108
|
+
*
|
|
109
|
+
* The mirror of Rust's `LeafError::IceServerConflictsWithPeer`. An
|
|
110
|
+
* anchor's `rtc_addr` is the ICE peer of a connection with that
|
|
111
|
+
* anchor, not a STUN server for it: the connection gathers no
|
|
112
|
+
* server-reflexive candidate and times out. The leaf refuses this
|
|
113
|
+
* **before any ICE work**, and refuses rather than silently
|
|
114
|
+
* stripping the entry — stripping would turn an explicit
|
|
115
|
+
* NAT-traversal configuration into a host-candidate-only attempt
|
|
116
|
+
* while appearing to have accepted it.
|
|
117
|
+
*
|
|
118
|
+
* The fix is to omit `iceServers` and let `connect()` use the
|
|
119
|
+
* endpoint the anchor announces as `stun_addr` on
|
|
120
|
+
* `GET /rtc/anchor`, or to name a STUN server that is not this
|
|
121
|
+
* peer. {@link diagnosticStunUrl} exists for the throwaway UDP
|
|
122
|
+
* probe and is not a source of `iceServers`.
|
|
123
|
+
*
|
|
124
|
+
* **Detection is endpoint equality only**, after default-port
|
|
125
|
+
* normalisation. A URL naming a DNS alias that resolves to the peer
|
|
126
|
+
* is not detected: the leaf resolves no names. The announced
|
|
127
|
+
* endpoint is what makes detection unnecessary for the configuration
|
|
128
|
+
* Net supplies.
|
|
129
|
+
*/
|
|
130
|
+
export declare class IceServerConflictError extends LeafError {
|
|
131
|
+
readonly entry: string;
|
|
132
|
+
readonly peerRtcAddr: string;
|
|
133
|
+
readonly kind: "ice-server-conflict";
|
|
134
|
+
constructor(entry: string, peerRtcAddr: string);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* This tab is not the leader for the origin and the operation needs
|
|
138
|
+
* the node. Carries the generation the caller presented so a stale
|
|
139
|
+
* follower's failure is legible.
|
|
140
|
+
*/
|
|
141
|
+
export declare class NotLeaderError extends LeafError {
|
|
142
|
+
readonly presented: number;
|
|
143
|
+
readonly current: number | null;
|
|
144
|
+
readonly kind: "not-leader";
|
|
145
|
+
constructor(presented: number, current?: number | null);
|
|
146
|
+
}
|
|
147
|
+
/** The RTC transport itself. `kind` is the flattened failure. */
|
|
148
|
+
export declare class RtcError extends LeafError {
|
|
149
|
+
readonly failure: RtcErrorFailure;
|
|
150
|
+
readonly kind: 'ice-timeout' | 'udp-blocked' | 'channel-closed' | 'rtc-unsupported';
|
|
151
|
+
constructor(failure: RtcErrorFailure);
|
|
152
|
+
}
|
|
153
|
+
/** An nRPC call failed or was disposed of. `kind` is the flattened failure. */
|
|
154
|
+
export declare class RpcError extends LeafError {
|
|
155
|
+
readonly failure: RpcErrorFailure;
|
|
156
|
+
readonly kind: 'rpc-refused' | 'rpc-timeout' | 'session-lost' | 'leader-lost' | 'rpc-indeterminate' | 'rpc-malformed';
|
|
157
|
+
constructor(failure: RpcErrorFailure);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Something crossed the boundary that this taxonomy does not
|
|
161
|
+
* recognise — a browser exception, or a leaf `Display` string this
|
|
162
|
+
* version of the package predates.
|
|
163
|
+
*
|
|
164
|
+
* Deliberately not folded into a neighbouring variant: mis-typing a
|
|
165
|
+
* failure is the exact mistake the Stage 5 correction exists to
|
|
166
|
+
* prevent, so an unrecognised message stays unrecognised.
|
|
167
|
+
*/
|
|
168
|
+
export declare class UnknownLeafError extends LeafError {
|
|
169
|
+
/** The value as it crossed the boundary, for diagnosis. */
|
|
170
|
+
readonly cause?: unknown | undefined;
|
|
171
|
+
readonly kind: "unknown";
|
|
172
|
+
constructor(message: string,
|
|
173
|
+
/** The value as it crossed the boundary, for diagnosis. */
|
|
174
|
+
cause?: unknown | undefined);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The frozen coarse vocabulary of an admission denial — the whole of
|
|
178
|
+
* what a remote provider ever says about why. A precise remote reason
|
|
179
|
+
* would be a credential oracle, so the wire carries one of these three
|
|
180
|
+
* bytes and the detailed refusal stays provider-side audit only.
|
|
181
|
+
*/
|
|
182
|
+
export type CoarseAdmissionReason = 'denied' | 'not-supported' | 'unavailable';
|
|
183
|
+
/** Every org terminal kind, one per the leaf's frozen terminal vocabulary. */
|
|
184
|
+
export type OrgErrorKind = 'org-admission-denied' | 'org-revoked' | 'org-timeout' | 'org-cancelled' | 'org-leader-lost' | 'org-session-lost' | 'org-indeterminate' | 'org-refused' | 'org-internal' | 'org-malformed';
|
|
185
|
+
/**
|
|
186
|
+
* The base class of the org terminal vocabulary — what
|
|
187
|
+
* {@link OrgByteStream}'s terminal error item carries and what the org
|
|
188
|
+
* verbs reject with. `message` is the boundary's own text, verbatim,
|
|
189
|
+
* exactly as the rest of this taxonomy keeps it.
|
|
190
|
+
*/
|
|
191
|
+
export declare abstract class OrgStreamError extends LeafError {
|
|
192
|
+
abstract readonly kind: OrgErrorKind;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The provider's admission engine refused the call (`RpcStatus 0x0009`).
|
|
196
|
+
* `coarse` is the frozen three-bucket reason and nothing finer.
|
|
197
|
+
*/
|
|
198
|
+
export declare class OrgAdmissionDeniedError extends OrgStreamError {
|
|
199
|
+
readonly coarse: CoarseAdmissionReason;
|
|
200
|
+
readonly kind: OrgErrorKind;
|
|
201
|
+
constructor(coarse: CoarseAdmissionReason, message?: string);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Credentials were revoked mid-call. **Is** an admission denial:
|
|
205
|
+
* `coarse` is fixed to `'denied'` (the frozen Revoked → Denied coarse
|
|
206
|
+
* map), so `instanceof OrgAdmissionDeniedError` and
|
|
207
|
+
* `coarse === 'denied'` hold — `instanceof OrgRevokedError` is how a
|
|
208
|
+
* page that wants the cause gets it.
|
|
209
|
+
*/
|
|
210
|
+
export declare class OrgRevokedError extends OrgAdmissionDeniedError {
|
|
211
|
+
readonly kind: OrgErrorKind;
|
|
212
|
+
constructor(message?: string);
|
|
213
|
+
}
|
|
214
|
+
/** The call's deadline elapsed. Nothing is re-issued. */
|
|
215
|
+
export declare class OrgTimeoutError extends OrgStreamError {
|
|
216
|
+
readonly kind: "org-timeout";
|
|
217
|
+
constructor(message?: string);
|
|
218
|
+
}
|
|
219
|
+
/** The call was cancelled — by its owner, or by a teardown that owns it. */
|
|
220
|
+
export declare class OrgCancelledError extends OrgStreamError {
|
|
221
|
+
readonly kind: "org-cancelled";
|
|
222
|
+
constructor(message?: string);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The generation holding the call was replaced (the shared-session
|
|
226
|
+
* surface's typed `LeaderLost`). `generation` is the generation that
|
|
227
|
+
* owned the call, as an exact decimal string — the u64 never becomes a
|
|
228
|
+
* JS number here. Never resumed: a successor generation's calls are
|
|
229
|
+
* fresh calls with fresh correlation and fresh proofs.
|
|
230
|
+
*/
|
|
231
|
+
export declare class OrgLeaderLostError extends OrgStreamError {
|
|
232
|
+
readonly generation: string;
|
|
233
|
+
readonly kind: "org-leader-lost";
|
|
234
|
+
constructor(generation: string, message?: string);
|
|
235
|
+
}
|
|
236
|
+
/** The session carrying the call went away. */
|
|
237
|
+
export declare class OrgSessionLostError extends OrgStreamError {
|
|
238
|
+
readonly kind: "org-session-lost";
|
|
239
|
+
constructor(message?: string);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The caller's own deadline elapsed before an answer arrived; the
|
|
243
|
+
* remote operation may still have executed and was not retried.
|
|
244
|
+
*/
|
|
245
|
+
export declare class OrgIndeterminateError extends OrgStreamError {
|
|
246
|
+
readonly deadlineMs: number;
|
|
247
|
+
readonly kind: "org-indeterminate";
|
|
248
|
+
constructor(deadlineMs: number, message?: string);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The application refused the call. `status` is its status code.
|
|
252
|
+
*
|
|
253
|
+
* Also the frozen wire kinds `server_error` (a server-answered failure
|
|
254
|
+
* status — the vocabulary's second name for this same condition) and
|
|
255
|
+
* `capability_denied` (the capability gate's refusal of the call): a
|
|
256
|
+
* re-typed wire string carries no recoverable `status` (`0`) and its
|
|
257
|
+
* `message` keeps the detail verbatim.
|
|
258
|
+
*/
|
|
259
|
+
export declare class OrgRefusedError extends OrgStreamError {
|
|
260
|
+
readonly status: number;
|
|
261
|
+
readonly kind: "org-refused";
|
|
262
|
+
constructor(status: number, message?: string);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The boundary failed internally. Also what a terminal item whose
|
|
266
|
+
* `kind` this build does not know becomes: mis-typing a failure is the
|
|
267
|
+
* exact mistake this taxonomy exists to prevent, so an unknown kind is
|
|
268
|
+
* named internal rather than guessed into a merits bucket. The frozen
|
|
269
|
+
* wire kinds `no_route` and `transport` land here for the same reason:
|
|
270
|
+
* they carry no merits verdict (nobody refused the call).
|
|
271
|
+
*/
|
|
272
|
+
export declare class OrgInternalError extends OrgStreamError {
|
|
273
|
+
readonly kind: "org-internal";
|
|
274
|
+
constructor(message?: string);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The reply or item did not decode. Also the frozen wire kinds
|
|
278
|
+
* `codec_encode` / `codec_decode` — the one codec class, both
|
|
279
|
+
* directions: an item that did not survive the codec is malformed
|
|
280
|
+
* whichever side failed.
|
|
281
|
+
*/
|
|
282
|
+
export declare class OrgMalformedError extends OrgStreamError {
|
|
283
|
+
readonly kind: "org-malformed";
|
|
284
|
+
constructor(message?: string);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Why a call, stream, sink or request stream retired — the exact
|
|
288
|
+
* string the retirement observables resolve with, kept verbatim.
|
|
289
|
+
*
|
|
290
|
+
* `node-closed` and `replaced` are teardown verdicts rather than call
|
|
291
|
+
* outcomes; both arrive at the terminal vocabulary as `cancelled`
|
|
292
|
+
* (the frozen retire → terminal map), and the raw string is what tells
|
|
293
|
+
* them apart.
|
|
294
|
+
*
|
|
295
|
+
* `resource-exhausted` is the leaf's byte-budget retirement
|
|
296
|
+
* (`RetireReason::ResourceExhausted`'s spelling). Its precise
|
|
297
|
+
* observable is the `admission-denied` / `unavailable` denial — the
|
|
298
|
+
* same class the byte-budget send refusal on that call re-types as
|
|
299
|
+
* ({@link ORG_SINK_BUDGET_REFUSAL}) — never a cancel and never a
|
|
300
|
+
* teardown verdict.
|
|
301
|
+
*/
|
|
302
|
+
export type OrgRetireReason = 'timeout' | 'cancelled' | 'revoked' | 'session-lost' | 'leader-lost' | 'node-closed' | 'replaced' | 'resource-exhausted';
|
|
303
|
+
/**
|
|
304
|
+
* THE typed closed refusal of a handler-side sink after retirement —
|
|
305
|
+
* one text whatever the verdict, so the wrapper's belt (its own
|
|
306
|
+
* short-circuit) and the leaf's hardening (its rejection) are the
|
|
307
|
+
* same observable. The verdict itself is what `retired` reports.
|
|
308
|
+
*/
|
|
309
|
+
export declare const ORG_SINK_CLOSED_REFUSAL = "org: the response sink is closed: the call was retired";
|
|
310
|
+
/**
|
|
311
|
+
* The upload sink's spelling of the SAME typed closed refusal —
|
|
312
|
+
* `sink_error(SinkError::Closed, "the upload sink")` at the leaf —
|
|
313
|
+
* pinned verbatim like {@link ORG_SINK_CLOSED_REFUSAL}. One text per
|
|
314
|
+
* sink, one text per verdict: what the leaf's `sink_error` spells is
|
|
315
|
+
* exactly this family, and anything outside it is not a closed refusal.
|
|
316
|
+
*/
|
|
317
|
+
export declare const ORG_UPLOAD_SINK_CLOSED_REFUSAL = "org: the upload sink is closed: the call was retired";
|
|
318
|
+
/**
|
|
319
|
+
* The byte-budget closed refusals — `sink_error(SinkError::ResourceExhausted, ..)`
|
|
320
|
+
* for the response sink and the upload sink. The item overran the
|
|
321
|
+
* call's byte budget and the call retired as `ResourceExhausted`,
|
|
322
|
+
* whose precise observable is the `admission-denied` / `unavailable`
|
|
323
|
+
* denial (the leaf's frozen map; C4, coarse `Unavailable`) — so these
|
|
324
|
+
* re-type as exactly that, never as a cancel.
|
|
325
|
+
*/
|
|
326
|
+
export declare const ORG_SINK_BUDGET_REFUSAL = "org: the response sink is closed: the call's byte budget refused the item and retired the call";
|
|
327
|
+
export declare const ORG_UPLOAD_SINK_BUDGET_REFUSAL = "org: the upload sink is closed: the call's byte budget refused the item and retired the call";
|
|
328
|
+
/**
|
|
329
|
+
* Parse one wire token — or the one-byte coarse body a `0x0009`
|
|
330
|
+
* refusal carries — into the coarse bucket. Anything undecodable is
|
|
331
|
+
* the least-informative bucket (`denied`), never an error about an
|
|
332
|
+
* error: the caller still learns it was denied.
|
|
333
|
+
*/
|
|
334
|
+
export declare function coarseAdmissionReason(token: string | undefined): CoarseAdmissionReason;
|
|
335
|
+
/** The terminal error a retirement verdict maps to (the frozen map). */
|
|
336
|
+
export declare function orgRetireError(reason: OrgRetireReason): OrgStreamError;
|
|
337
|
+
/**
|
|
338
|
+
* Type one terminal item from the boundary — the frozen `kind`
|
|
339
|
+
* vocabulary plus its fields — as the class §4.3 names.
|
|
340
|
+
*
|
|
341
|
+
* `message` is kept verbatim as the error's message.
|
|
342
|
+
*/
|
|
343
|
+
export declare function orgTerminalError(item: {
|
|
344
|
+
kind: string;
|
|
345
|
+
message?: string;
|
|
346
|
+
coarse?: string;
|
|
347
|
+
generation?: string;
|
|
348
|
+
status?: number;
|
|
349
|
+
deadlineMs?: number;
|
|
350
|
+
}): OrgStreamError;
|
|
351
|
+
/**
|
|
352
|
+
* Type one retirement verdict coming back from the boundary's
|
|
353
|
+
* `retired()`. A string this build does not know is a boundary
|
|
354
|
+
* disagreement — named `replaced`-as-cancelled would lie about which
|
|
355
|
+
* teardown ran, so it becomes the cancelled terminal and the raw
|
|
356
|
+
* string stays in `OrgRetireReason`'s consumers' hands unchanged.
|
|
357
|
+
*/
|
|
358
|
+
export declare function orgRetireReason(raw: string): OrgRetireReason;
|
|
359
|
+
/**
|
|
360
|
+
* Parse one `org:` wire string (`org:<domain>:<kind>[:detail]`, the
|
|
361
|
+
* `OrgSdkError::to_wire` vocabulary) or a `0x0009` admission refusal,
|
|
362
|
+
* or `null` if the message is not one.
|
|
363
|
+
*
|
|
364
|
+
* The domains and kind tokens mirror `classifyOrgError` from the Node
|
|
365
|
+
* binding where names coincide; the `rpc` domain reuses the frozen
|
|
366
|
+
* nRPC kind vocabulary rather than minting second names for the same
|
|
367
|
+
* conditions. Every frozen `org:rpc:` kind classifies (the frozen
|
|
368
|
+
* wire-kind map at the `rpc` switch) — a binding that returned `null`
|
|
369
|
+
* there forced the caller into `UnknownLeafError`, which is the
|
|
370
|
+
* cross-language drift this closes.
|
|
371
|
+
*/
|
|
372
|
+
export declare function parseOrgError(message: string): OrgStreamError | null;
|
|
373
|
+
/**
|
|
374
|
+
* Re-type whatever the wasm boundary threw.
|
|
375
|
+
*
|
|
376
|
+
* `wasm-bindgen` hands us a `JsError` whose `message` is the Rust
|
|
377
|
+
* `Display` of `LeafError`, so the taxonomy is recovered by parsing
|
|
378
|
+
* exactly those prefixes. Anything else — a `DOMException` from
|
|
379
|
+
* `RTCPeerConnection`, a string, a plain object — becomes
|
|
380
|
+
* {@link UnknownLeafError} with the original value as `cause`.
|
|
381
|
+
*/
|
|
382
|
+
export declare function fromWasmError(thrown: unknown): LeafError;
|
|
383
|
+
/**
|
|
384
|
+
* Parse one `LeafError` `Display` string, or `null` if it is not one.
|
|
385
|
+
*
|
|
386
|
+
* Exported because it is the whole of the wasm→TS error contract and
|
|
387
|
+
* deserves to be unit-testable without a wasm module.
|
|
388
|
+
*/
|
|
389
|
+
export declare function parseLeafError(message: string): LeafError | null;
|
|
390
|
+
/** Parse the nested `RtcError` `Display`, or `null`. */
|
|
391
|
+
export declare function parseRtcFailure(text: string): RtcErrorFailure | null;
|
|
392
|
+
/** Parse the nested `RpcError` `Display`, or `null`. */
|
|
393
|
+
export declare function parseRpcFailure(text: string): RpcErrorFailure | null;
|
|
394
|
+
/** `true` when this error carries the UDP-blocked evidence. */
|
|
395
|
+
export declare function isUdpBlocked(error: unknown): error is RtcError & {
|
|
396
|
+
failure: {
|
|
397
|
+
type: 'udpBlocked';
|
|
398
|
+
evidence: UdpBlockedEvidence;
|
|
399
|
+
};
|
|
400
|
+
};
|