@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §8's lifecycle, as events.
|
|
3
|
+
*
|
|
4
|
+
* A session emits every {@link LeafEvent} the direct surface does,
|
|
5
|
+
* plus five tags of its own. They are here rather than in
|
|
6
|
+
* `src/events.ts` because they describe the *lifecycle*, not the node:
|
|
7
|
+
* a page using {@link BrowserNode} never sees them, and a page using a
|
|
8
|
+
* session cannot explain a leader change without them.
|
|
9
|
+
*
|
|
10
|
+
* `parseEvent` carries an unknown tag through intact, so a bundle
|
|
11
|
+
* whose leaf is newer than its wrapper stays legible; this module
|
|
12
|
+
* refines exactly the five it knows and leaves the rest as they
|
|
13
|
+
* arrived.
|
|
14
|
+
*/
|
|
15
|
+
import { parseEvent } from '../events.js';
|
|
16
|
+
/**
|
|
17
|
+
* Parse one event JSON string from a session.
|
|
18
|
+
*
|
|
19
|
+
* Never throws: this runs inside a callback the wasm module invokes,
|
|
20
|
+
* and an exception there would unwind through Rust. Anything that is
|
|
21
|
+
* not one of the five lifecycle tags goes to `parseEvent`, so the node
|
|
22
|
+
* events and the unknown-tag passthrough behave exactly as they do on
|
|
23
|
+
* the direct surface.
|
|
24
|
+
*/
|
|
25
|
+
export function parseSessionEvent(json) {
|
|
26
|
+
const base = parseEvent(json);
|
|
27
|
+
if (base.type !== 'unknown')
|
|
28
|
+
return base;
|
|
29
|
+
const refined = refine(base);
|
|
30
|
+
return refined ?? base;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `true` for the six tags a session adds, so a page can filter the
|
|
34
|
+
* lifecycle out of the event stream without listing them.
|
|
35
|
+
*/
|
|
36
|
+
export function isLifecycleEvent(event) {
|
|
37
|
+
switch (event.type) {
|
|
38
|
+
case 'leader_changed':
|
|
39
|
+
case 'subscription_restored':
|
|
40
|
+
case 'leader_lost':
|
|
41
|
+
case 'generation_fenced':
|
|
42
|
+
case 'not_leader':
|
|
43
|
+
case 'promotion_failed':
|
|
44
|
+
return true;
|
|
45
|
+
default:
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Refine an event `parseEvent` did not recognise.
|
|
51
|
+
*
|
|
52
|
+
* `raw` is whatever `parseJsonPreservingU64` produced, so the exact
|
|
53
|
+
* decimal strings for `generation`, `presented` and `current` are
|
|
54
|
+
* already intact — they are in `U64_EVENT_KEYS`, which is what keeps
|
|
55
|
+
* a large generation from being rounded into a fence that no longer
|
|
56
|
+
* fences.
|
|
57
|
+
*/
|
|
58
|
+
function refine(event) {
|
|
59
|
+
const raw = event.raw;
|
|
60
|
+
if (raw === null || typeof raw !== 'object')
|
|
61
|
+
return null;
|
|
62
|
+
const fields = { ...raw };
|
|
63
|
+
switch (event.tag) {
|
|
64
|
+
case 'leader_changed':
|
|
65
|
+
return { type: 'leader_changed', generation: exact(fields.generation) };
|
|
66
|
+
case 'subscription_restored':
|
|
67
|
+
return {
|
|
68
|
+
type: 'subscription_restored',
|
|
69
|
+
channel: typeof fields.channel === 'string' ? fields.channel : '',
|
|
70
|
+
};
|
|
71
|
+
case 'leader_lost': {
|
|
72
|
+
// `failed` is a `usize` count, not a `u64` id: a number is
|
|
73
|
+
// exact for it, and it is deliberately not in `U64_EVENT_KEYS`.
|
|
74
|
+
const failed = Number(fields.failed);
|
|
75
|
+
return {
|
|
76
|
+
type: 'leader_lost',
|
|
77
|
+
generation: exact(fields.generation),
|
|
78
|
+
failed: Number.isFinite(failed) ? failed : 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case 'generation_fenced':
|
|
82
|
+
return {
|
|
83
|
+
type: 'generation_fenced',
|
|
84
|
+
presented: exact(fields.presented),
|
|
85
|
+
current: exact(fields.current),
|
|
86
|
+
};
|
|
87
|
+
case 'not_leader':
|
|
88
|
+
return {
|
|
89
|
+
type: 'not_leader',
|
|
90
|
+
presented: exact(fields.presented),
|
|
91
|
+
current: exact(fields.current),
|
|
92
|
+
};
|
|
93
|
+
case 'promotion_failed':
|
|
94
|
+
return {
|
|
95
|
+
type: 'promotion_failed',
|
|
96
|
+
generation: exact(fields.generation),
|
|
97
|
+
detail: typeof fields.detail === 'string' ? fields.detail : '',
|
|
98
|
+
};
|
|
99
|
+
default:
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A `u64` field, kept exactly as it arrived.
|
|
105
|
+
*
|
|
106
|
+
* Five call sites that must agree: a generation read as a number
|
|
107
|
+
* anywhere is a generation that rounds above 2^53, and a rounded
|
|
108
|
+
* generation is a fence that silently stops fencing.
|
|
109
|
+
*/
|
|
110
|
+
function exact(value) {
|
|
111
|
+
if (typeof value === 'string')
|
|
112
|
+
return value;
|
|
113
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
114
|
+
return String(value);
|
|
115
|
+
return '0';
|
|
116
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §8's session surface: one node per origin, leader-elected.
|
|
3
|
+
*
|
|
4
|
+
* `connect()` gives a page this tab's node; `openSession()` gives it
|
|
5
|
+
* the origin's node, whichever tab is running it. See
|
|
6
|
+
* {@link MeshSession} for the three methods that are promises here and
|
|
7
|
+
* synchronous on the direct surface, and why.
|
|
8
|
+
*/
|
|
9
|
+
export { MeshSession, openSession } from './session.js';
|
|
10
|
+
export type { SessionOptions, SessionRole } from './session.js';
|
|
11
|
+
export { isLifecycleEvent, parseSessionEvent } from './events.js';
|
|
12
|
+
export type { GenerationFencedEvent, LeaderChangedEvent, LeaderLostEvent, NotLeaderEvent, SessionEvent, SessionEventOf, SessionLifecycleEvent, SubscriptionRestoredEvent, } from './events.js';
|
|
13
|
+
export { asSessionModule } from './wasm.js';
|
|
14
|
+
export type { LeafWasmSession, LeafWasmSessionModule, LeafWasmSessionOptions, } from './wasm.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §8's session surface: one node per origin, leader-elected.
|
|
3
|
+
*
|
|
4
|
+
* `connect()` gives a page this tab's node; `openSession()` gives it
|
|
5
|
+
* the origin's node, whichever tab is running it. See
|
|
6
|
+
* {@link MeshSession} for the three methods that are promises here and
|
|
7
|
+
* synchronous on the direct surface, and why.
|
|
8
|
+
*/
|
|
9
|
+
export { MeshSession, openSession } from './session.js';
|
|
10
|
+
export { isLifecycleEvent, parseSessionEvent } from './events.js';
|
|
11
|
+
export { asSessionModule } from './wasm.js';
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MeshSession` — one node per origin, whichever tab you opened.
|
|
3
|
+
*
|
|
4
|
+
* §8's decision is that a browser origin runs exactly one Net node:
|
|
5
|
+
* tabs contend for a Web Lock, the holder runs the node on the main
|
|
6
|
+
* thread, and the others attach over a `BroadcastChannel` and get the
|
|
7
|
+
* same API. This is that API in TypeScript.
|
|
8
|
+
*
|
|
9
|
+
* The difference from {@link BrowserNode} is one of scope, not of
|
|
10
|
+
* capability: `connect()` gives a page *this tab's* node, and
|
|
11
|
+
* `openSession()` gives it *the origin's* node. Everything a page can
|
|
12
|
+
* do with one it can do with the other — nRPC calls, channels,
|
|
13
|
+
* streams, announcements, queries, enrollment — and three methods are
|
|
14
|
+
* promises here that are synchronous there, because on a follower the
|
|
15
|
+
* answer lives in another tab:
|
|
16
|
+
*
|
|
17
|
+
* | | `BrowserNode` | `MeshSession` |
|
|
18
|
+
* |---|---|---|
|
|
19
|
+
* | `openStream` | synchronous | promise |
|
|
20
|
+
* | `counters` | synchronous | promise |
|
|
21
|
+
* | `isEnrolled` | synchronous | promise |
|
|
22
|
+
*
|
|
23
|
+
* Failures are the same taxonomy, re-typed by the same mapper, so a
|
|
24
|
+
* proxied refusal and a direct one arrive as the same class. The one
|
|
25
|
+
* addition is {@link NotLeaderError}: a tab that was superseded — a
|
|
26
|
+
* suspended tab that resumed, say — is refused rather than served, and
|
|
27
|
+
* that refusal is typed.
|
|
28
|
+
*/
|
|
29
|
+
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';
|
|
30
|
+
import { type ConnectOptions, type NodeDescriptor, type PeerConnectOutcome } from '../node.js';
|
|
31
|
+
import { LeafStream, type OpenStreamOptions } from '../stream.js';
|
|
32
|
+
import { type SessionEvent, type SessionEventOf, type SessionLifecycleEvent } from './events.js';
|
|
33
|
+
import { type Unsubscribe } from '../events.js';
|
|
34
|
+
/** Whether this tab runs the node or talks to the tab that does. */
|
|
35
|
+
export type SessionRole = 'leader' | 'follower';
|
|
36
|
+
/** {@link openSession}'s argument. */
|
|
37
|
+
export interface SessionOptions extends ConnectOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Capabilities to announce, and to re-announce whenever this tab
|
|
40
|
+
* becomes the leader.
|
|
41
|
+
*
|
|
42
|
+
* On the session rather than on `announce()` because a new leader
|
|
43
|
+
* has to re-publish them without being asked — D2's restoration
|
|
44
|
+
* step. Calling `announce()` as well is fine and additive.
|
|
45
|
+
*/
|
|
46
|
+
capabilities?: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Channels this tab depends on.
|
|
49
|
+
*
|
|
50
|
+
* Declared up front so a *new* leader can restore them: the leader
|
|
51
|
+
* keeps the union of every follower's declaration subscribed, and a
|
|
52
|
+
* tab that only called `subscribe()` after a handoff would have a
|
|
53
|
+
* window where its channel was not subscribed by anybody.
|
|
54
|
+
*/
|
|
55
|
+
subscriptions?: readonly string[];
|
|
56
|
+
/** The IndexedDB database holding the identity. Defaults to `net-mesh-leaf`. */
|
|
57
|
+
dbName?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Override the Web Lock and `BroadcastChannel` name. Defaults to
|
|
60
|
+
* `net-mesh/<origin>/<identity-fingerprint>`.
|
|
61
|
+
*
|
|
62
|
+
* For tests that need two independent elections inside one origin.
|
|
63
|
+
* A page should not set it: two scopes on one identity is two nodes
|
|
64
|
+
* on one identity, which is the eviction §8 exists to prevent.
|
|
65
|
+
*/
|
|
66
|
+
lockScope?: string;
|
|
67
|
+
}
|
|
68
|
+
/** The origin's Net node, as this tab sees it. */
|
|
69
|
+
export declare class MeshSession {
|
|
70
|
+
private readonly inner;
|
|
71
|
+
/**
|
|
72
|
+
* The same fan-out the direct surface uses, instantiated over the
|
|
73
|
+
* wider event union. One implementation, so a throwing listener,
|
|
74
|
+
* the never-unwind-into-Rust rule and the iterator buffering behave
|
|
75
|
+
* identically on both surfaces.
|
|
76
|
+
*/
|
|
77
|
+
private readonly hub;
|
|
78
|
+
/**
|
|
79
|
+
* Streams this session handed out, so the ones still open can be
|
|
80
|
+
* ended when leadership moves.
|
|
81
|
+
*
|
|
82
|
+
* A stream is session-scoped and is **not** restored across a
|
|
83
|
+
* leader change, which the Rust side enforces by binding each
|
|
84
|
+
* handle to its opening generation. That enforcement makes a stale
|
|
85
|
+
* `send` reject — but it says nothing about a consumer sitting in
|
|
86
|
+
* `await stream[Symbol.asyncIterator]().next()`, which would simply
|
|
87
|
+
* never settle: the wasm stream stops emitting and nothing tells
|
|
88
|
+
* the queue to end. So the session owns the set and closes it,
|
|
89
|
+
* which is what turns "no more data, ever" into the end of the
|
|
90
|
+
* iteration a page is already written to handle.
|
|
91
|
+
*/
|
|
92
|
+
private readonly streams;
|
|
93
|
+
/**
|
|
94
|
+
* The generation the streams in that set were opened under.
|
|
95
|
+
*
|
|
96
|
+
* Tracked because the generation moving is the *only* signal an
|
|
97
|
+
* abrupt leader disappearance gives. A page that crashes or
|
|
98
|
+
* navigates away never sends its `leader_lost` announcement: the
|
|
99
|
+
* promoted follower fails its own pending requests and reports
|
|
100
|
+
* `leader_changed`, and so does every other surviving follower
|
|
101
|
+
* when the successor's Leadership arrives. The Rust handle then
|
|
102
|
+
* correctly suppresses the old stream's bytes by its opening
|
|
103
|
+
* generation — which is precisely what leaves a consumer sitting
|
|
104
|
+
* in `await iterator.next()` forever, waiting on a stream that is
|
|
105
|
+
* now guaranteed never to emit again.
|
|
106
|
+
*/
|
|
107
|
+
private streamGeneration;
|
|
108
|
+
/**
|
|
109
|
+
* The live org handles (plan §4.5), retired when leadership moves
|
|
110
|
+
* or the session closes: pending calls fail typed and each dropped
|
|
111
|
+
* caller handle emits exactly one CANCEL.
|
|
112
|
+
*/
|
|
113
|
+
private readonly orgHandles;
|
|
114
|
+
private closed;
|
|
115
|
+
/** `'leader'` if this tab runs the node, `'follower'` if another does. */
|
|
116
|
+
role(): SessionRole;
|
|
117
|
+
/**
|
|
118
|
+
* The generation in force for this tab, as an exact decimal string.
|
|
119
|
+
*
|
|
120
|
+
* A string because it is a `u64`: a number would round above 2^53,
|
|
121
|
+
* and a rounded generation is a fence that has stopped fencing.
|
|
122
|
+
*/
|
|
123
|
+
generation(): string;
|
|
124
|
+
/** The node's id, 16 lowercase hex digits, or `null` before one is known. */
|
|
125
|
+
nodeIdHex(): string | null;
|
|
126
|
+
/** The identity fingerprint the Web Lock is named after. */
|
|
127
|
+
fingerprint(): string;
|
|
128
|
+
/** The Web Lock and `BroadcastChannel` name. */
|
|
129
|
+
scope(): string;
|
|
130
|
+
/**
|
|
131
|
+
* How long this tab took to take over, in milliseconds, or `null` if
|
|
132
|
+
* it was the origin's first leader.
|
|
133
|
+
*
|
|
134
|
+
* Measured from the lock coming free to the node being re-bootstrapped
|
|
135
|
+
* and the subscriptions and announcement restored. It is a
|
|
136
|
+
* measurement, not a promise: the interruption a *peer* observes is
|
|
137
|
+
* bounded by Net's own failure detection, which is a different and
|
|
138
|
+
* much larger number.
|
|
139
|
+
*/
|
|
140
|
+
interruptionMs(): number | null;
|
|
141
|
+
/**
|
|
142
|
+
* Call an nRPC service.
|
|
143
|
+
*
|
|
144
|
+
* Rejects with {@link RpcError} — including `leader-lost` when the
|
|
145
|
+
* tab running the node was replaced while the call was in flight.
|
|
146
|
+
* Never a silent retry: §8's rule is that the caller is told and
|
|
147
|
+
* decides.
|
|
148
|
+
*
|
|
149
|
+
* On a follower the deadline is armed here as well as on the
|
|
150
|
+
* leader, and that is true whether or not `timeoutMs` was given:
|
|
151
|
+
* omitting it takes the leaf's own 30 s default rather than
|
|
152
|
+
* leaving the promise to a leader that may be frozen. Either way
|
|
153
|
+
* expiry is `rpc-indeterminate` — the work may already have been
|
|
154
|
+
* admitted in the other tab, and nothing here re-issues it.
|
|
155
|
+
*/
|
|
156
|
+
call(service: string, payload: Uint8Array, timeoutMs?: number): Promise<Uint8Array>;
|
|
157
|
+
/**
|
|
158
|
+
* Subscribe to a channel.
|
|
159
|
+
*
|
|
160
|
+
* Also adds it to what a future leader restores, so a handoff does
|
|
161
|
+
* not silently drop it.
|
|
162
|
+
*/
|
|
163
|
+
subscribe(channel: string): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Release this tab's claim on a channel.
|
|
166
|
+
*
|
|
167
|
+
* Not `subscribe` run backwards. The origin runs one node and the
|
|
168
|
+
* leader holds **one** membership for the union of every tab's
|
|
169
|
+
* declarations, so this drops this tab's claim and the membership
|
|
170
|
+
* itself is given up only when no other tab still wants the
|
|
171
|
+
* channel. One tab closing a subscription therefore cannot stop
|
|
172
|
+
* another tab that is still reading the same channel.
|
|
173
|
+
*
|
|
174
|
+
* Also removes it from what a future leader restores, so a handoff
|
|
175
|
+
* does not silently bring back a channel this tab released.
|
|
176
|
+
*/
|
|
177
|
+
unsubscribe(channel: string): Promise<void>;
|
|
178
|
+
/** Publish one payload to a channel. */
|
|
179
|
+
publish(channel: string, payload: Uint8Array): Promise<void>;
|
|
180
|
+
/** Publish this node's capability announcement. */
|
|
181
|
+
announce(capabilities: readonly string[]): Promise<void>;
|
|
182
|
+
/** Find the nodes offering a capability. */
|
|
183
|
+
query(capability: string): Promise<NodeDescriptor[]>;
|
|
184
|
+
/** Every leaf counter. Values stay strings because they are `u64`. */
|
|
185
|
+
counters(): Promise<Record<string, string>>;
|
|
186
|
+
/**
|
|
187
|
+
* Run the enrollment exchange if the node is not enrolled.
|
|
188
|
+
*
|
|
189
|
+
* Works on a follower too: one node per origin means one enrollment
|
|
190
|
+
* per origin, so a follower asking to enroll is asking the only
|
|
191
|
+
* session there is.
|
|
192
|
+
*/
|
|
193
|
+
enroll(): Promise<void>;
|
|
194
|
+
/** Whether the anchor has admitted this node. */
|
|
195
|
+
isEnrolled(): Promise<boolean>;
|
|
196
|
+
/**
|
|
197
|
+
* Sign and send a `0x0D02` signalling envelope — the
|
|
198
|
+
* session-independent path, so no session with `peer` is needed.
|
|
199
|
+
*/
|
|
200
|
+
signal(peerHex: string, dialogHex: string, kind: string, payload: Uint8Array): Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* Connect directly to `nodeIdHex` (§9), wherever the node is.
|
|
203
|
+
*
|
|
204
|
+
* The same drive loop `BrowserNode.connectPeer` runs — literally
|
|
205
|
+
* the same function, from `peer-driver.ts`, with this session's
|
|
206
|
+
* primitives instead of the direct node's. One loop means the
|
|
207
|
+
* offerer and the answerer cannot disagree about what supersession
|
|
208
|
+
* or a terminal reading looks like, which is a disagreement neither
|
|
209
|
+
* side could detect.
|
|
210
|
+
*
|
|
211
|
+
* On a follower every step is a proxy round trip and the attempt
|
|
212
|
+
* runs on the leader tab's node. A step whose attempt was replaced
|
|
213
|
+
* while the request was in flight is refused by the leaf before it
|
|
214
|
+
* touches the replacement, and arrives here as `superseded`.
|
|
215
|
+
*
|
|
216
|
+
* **Idempotent on a healthy pair**, as `BrowserNode.connectPeer` is: an
|
|
217
|
+
* already-direct, open pair resolves `direct` with its live dialog and
|
|
218
|
+
* nothing is offered — a second offer would replace the working link
|
|
219
|
+
* and close it under the peer. A leader too old to answer that reading
|
|
220
|
+
* is asked for an offer, as before.
|
|
221
|
+
*/
|
|
222
|
+
connectPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
223
|
+
/**
|
|
224
|
+
* Answer the offer `nodeIdHex` filed — {@link connectPeer}'s
|
|
225
|
+
* counterpart, on whichever tab holds the lock.
|
|
226
|
+
*/
|
|
227
|
+
acceptPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
228
|
+
/**
|
|
229
|
+
* The proxied primitives, each re-typed by the same mapper every
|
|
230
|
+
* other method here uses, so a proxied refusal is the same class a
|
|
231
|
+
* direct one is — which is what lets one drive loop classify both.
|
|
232
|
+
*/
|
|
233
|
+
private peerPrimitives;
|
|
234
|
+
/**
|
|
235
|
+
* Open a reliable or fire-and-forget stream.
|
|
236
|
+
*
|
|
237
|
+
* A promise where {@link BrowserNode.openStream} is synchronous: on
|
|
238
|
+
* a follower the stream is opened by the tab that owns the
|
|
239
|
+
* DataChannel. The object it resolves to is the same
|
|
240
|
+
* {@link LeafStream}, so `await stream.send(bytes)` and
|
|
241
|
+
* `for await (const payload of stream)` read the same either way.
|
|
242
|
+
*
|
|
243
|
+
* A stream is **not** restored across a leader change — it is
|
|
244
|
+
* session-scoped, and pretending otherwise would hide a real
|
|
245
|
+
* interruption. A page that wants one back opens one. What this
|
|
246
|
+
* session does guarantee is that the interruption is *observable*:
|
|
247
|
+
* the handle is retained here and ended when leadership moves, so
|
|
248
|
+
* `for await (const payload of stream)` finishes instead of hanging
|
|
249
|
+
* on a node that is gone.
|
|
250
|
+
*
|
|
251
|
+
* That guarantee has to cover the open itself. On a follower this
|
|
252
|
+
* is a proxy round trip, so a result can arrive *after* the
|
|
253
|
+
* leadership-loss notification that already drained the retained
|
|
254
|
+
* set — and the handle it carries was stamped by Rust with the
|
|
255
|
+
* generation the request was issued under, so it is stale on
|
|
256
|
+
* arrival. Registering it would put a permanently silent stream
|
|
257
|
+
* into a set nothing will drain again. The generation is therefore
|
|
258
|
+
* captured before the await and compared after it, and a crossing
|
|
259
|
+
* result is ended exactly as a consumer a microtask later would
|
|
260
|
+
* have seen it.
|
|
261
|
+
*/
|
|
262
|
+
openStream(options: OpenStreamOptions): Promise<LeafStream>;
|
|
263
|
+
/**
|
|
264
|
+
* Call an org-protected unary service.
|
|
265
|
+
*
|
|
266
|
+
* Rejects with the §4.3 typed error: an **opening denial** is
|
|
267
|
+
* {@link OrgAdmissionDeniedError} carrying the coarse reason and
|
|
268
|
+
* nothing finer — a precise remote reason would be a credential
|
|
269
|
+
* oracle. Never a silent retry: the caller is told and decides.
|
|
270
|
+
*
|
|
271
|
+
* **Leader replacement.** A pending proxied call fails typed
|
|
272
|
+
* `LeaderLost` on a generation move and is NEVER resumed; a
|
|
273
|
+
* successor generation's calls carry fresh correlation and fresh
|
|
274
|
+
* proofs.
|
|
275
|
+
*
|
|
276
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
277
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
278
|
+
* deadline passes while its tab is suspended retires with its
|
|
279
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
280
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
281
|
+
* executing in another tab. Nothing resumes automatically: a
|
|
282
|
+
* retired call is never transparently re-opened, because re-opening
|
|
283
|
+
* is a fresh call with a fresh proof and MAY repeat effects. Node
|
|
284
|
+
* or tab close retires ownership — pending calls fail typed, and
|
|
285
|
+
* each dropped caller handle emits exactly one CANCEL.
|
|
286
|
+
*/
|
|
287
|
+
callOrg(service: string, payload: Uint8Array, options: OrgCallOptions): Promise<Uint8Array>;
|
|
288
|
+
/**
|
|
289
|
+
* Call an org-protected server-streaming service; resolve to the
|
|
290
|
+
* response stream.
|
|
291
|
+
*
|
|
292
|
+
* An **opening denial** rejects here with
|
|
293
|
+
* {@link OrgAdmissionDeniedError} (coarse) — a denial is a
|
|
294
|
+
* rejection, never a terminal item. Midstream, the stream's FINAL
|
|
295
|
+
* error item carries the §4.3 outcome: revocation is
|
|
296
|
+
* `AdmissionDenied('denied')` ({@link OrgRevokedError} where the
|
|
297
|
+
* cause is known), deadline/cancel retirement is
|
|
298
|
+
* {@link OrgTimeoutError}/{@link OrgCancelledError}. The async
|
|
299
|
+
* iterator throws that typed error at the terminal error item.
|
|
300
|
+
*
|
|
301
|
+
* **Leader replacement.** A pending proxied call fails typed
|
|
302
|
+
* `LeaderLost` on a generation move and is NEVER resumed; a
|
|
303
|
+
* successor generation's calls carry fresh correlation and fresh
|
|
304
|
+
* proofs. An open that lands after the move is retired on arrival
|
|
305
|
+
* rather than handed out live.
|
|
306
|
+
*
|
|
307
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
308
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
309
|
+
* deadline passes while its tab is suspended retires with its
|
|
310
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
311
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
312
|
+
* executing in another tab. Nothing resumes automatically: a
|
|
313
|
+
* retired call is never transparently re-opened, because re-opening
|
|
314
|
+
* is a fresh call with a fresh proof and MAY repeat effects. Node
|
|
315
|
+
* or tab close retires ownership — pending calls fail typed, and
|
|
316
|
+
* each dropped caller handle emits exactly one CANCEL.
|
|
317
|
+
*/
|
|
318
|
+
callOrgStreaming(service: string, payload: Uint8Array, options: OrgCallOptions): Promise<OrgByteStream>;
|
|
319
|
+
/**
|
|
320
|
+
* Open an org-protected client-streaming upload. `finish()` is the
|
|
321
|
+
* half-close and the reply await, and it rejects with the typed
|
|
322
|
+
* terminal error (§4.3).
|
|
323
|
+
*
|
|
324
|
+
* An **opening denial** rejects here with
|
|
325
|
+
* {@link OrgAdmissionDeniedError} (coarse).
|
|
326
|
+
*
|
|
327
|
+
* **Leader replacement.** A pending proxied call fails typed
|
|
328
|
+
* `LeaderLost` on a generation move and is NEVER resumed; a
|
|
329
|
+
* successor generation's calls carry fresh correlation and fresh
|
|
330
|
+
* proofs. An open that lands after the move is retired on arrival
|
|
331
|
+
* rather than handed out live.
|
|
332
|
+
*
|
|
333
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
334
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
335
|
+
* deadline passes while its tab is suspended retires with its
|
|
336
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
337
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
338
|
+
* executing in another tab. Nothing resumes automatically: a
|
|
339
|
+
* retired call is never transparently re-opened, because re-opening
|
|
340
|
+
* is a fresh call with a fresh proof and MAY repeat effects. Node
|
|
341
|
+
* or tab close retires ownership — pending calls fail typed, and
|
|
342
|
+
* each dropped caller handle emits exactly one CANCEL.
|
|
343
|
+
*/
|
|
344
|
+
callOrgClientStream(service: string, options: OrgCallOptions): Promise<OrgUploadCall>;
|
|
345
|
+
/**
|
|
346
|
+
* Open an org-protected duplex call: an upload sink and an
|
|
347
|
+
* independent response stream.
|
|
348
|
+
*
|
|
349
|
+
* An **opening denial** rejects here with
|
|
350
|
+
* {@link OrgAdmissionDeniedError} (coarse). Midstream outcomes are
|
|
351
|
+
* the §4.3 terminals on each half.
|
|
352
|
+
*
|
|
353
|
+
* **Leader replacement.** A pending proxied call fails typed
|
|
354
|
+
* `LeaderLost` on a generation move and is NEVER resumed; a
|
|
355
|
+
* successor generation's calls carry fresh correlation and fresh
|
|
356
|
+
* proofs. An open that lands after the move is retired on arrival
|
|
357
|
+
* rather than handed out live.
|
|
358
|
+
*
|
|
359
|
+
* **Suspension and closure (plan §4.5).** Deadlines are absolute: a
|
|
360
|
+
* frozen tab extends no lease and no window, so a call whose
|
|
361
|
+
* deadline passes while its tab is suspended retires with its
|
|
362
|
+
* deadline terminal on wake — {@link OrgTimeoutError}, or
|
|
363
|
+
* {@link OrgIndeterminateError} where the work may already be
|
|
364
|
+
* executing in another tab. Nothing resumes automatically: a
|
|
365
|
+
* retired call is never transparently re-opened, because re-opening
|
|
366
|
+
* is a fresh call with a fresh proof and MAY repeat effects. Node
|
|
367
|
+
* or tab close retires ownership — pending calls fail typed, and
|
|
368
|
+
* each dropped caller handle emits exactly one CANCEL.
|
|
369
|
+
*/
|
|
370
|
+
callOrgDuplex(service: string, options: OrgCallOptions): Promise<OrgDuplexHandles>;
|
|
371
|
+
/**
|
|
372
|
+
* Serve an org-protected unary service. `access` is `'same-org'`
|
|
373
|
+
* (owner-org traffic only) or `'granted'` (capability grants too);
|
|
374
|
+
* anything else is refused loudly at registration.
|
|
375
|
+
*
|
|
376
|
+
* On a follower the registration rides the proxy registry and
|
|
377
|
+
* inbound calls dispatch to the registering follower; teardown
|
|
378
|
+
* follows the retire discipline — a generation move fences the
|
|
379
|
+
* registration's calls and streams, never resurrected.
|
|
380
|
+
*
|
|
381
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
382
|
+
* handler future without a final poll — cancellation is observed
|
|
383
|
+
* through the retirement observables (the terminal item, the sink's
|
|
384
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
385
|
+
* handler-side event; a detached observer holding `retired`
|
|
386
|
+
* observes the signal. After retirement a handler sees typed
|
|
387
|
+
* refusals and any return value it produces is discarded.
|
|
388
|
+
*
|
|
389
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
390
|
+
* protected split).** Closing the returned handle retires its live
|
|
391
|
+
* protected calls — each with its exact retirement terminal, the
|
|
392
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
393
|
+
* on the service. Deliberately NOT the public path's
|
|
394
|
+
* let-existing-calls-finish behavior.
|
|
395
|
+
*/
|
|
396
|
+
serveOrg(service: string, access: OrgAccess, handler: OrgUnaryHandler, options: OrgServeOptions): OrgServeHandle;
|
|
397
|
+
/**
|
|
398
|
+
* Serve an org-protected server-streaming service: one request in,
|
|
399
|
+
* items out through the {@link OrgResponseSink}.
|
|
400
|
+
*
|
|
401
|
+
* On a follower the registration rides the proxy registry and
|
|
402
|
+
* inbound calls dispatch to the registering follower; teardown
|
|
403
|
+
* follows the retire discipline — a generation move fences the
|
|
404
|
+
* registration's calls and streams, never resurrected.
|
|
405
|
+
*
|
|
406
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
407
|
+
* handler future without a final poll — cancellation is observed
|
|
408
|
+
* through the retirement observables (the terminal item, the sink's
|
|
409
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
410
|
+
* handler-side event; a detached observer holding `retired`
|
|
411
|
+
* observes the signal. After retirement a handler sees typed
|
|
412
|
+
* refusals and any return value it produces is discarded.
|
|
413
|
+
*
|
|
414
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
415
|
+
* protected split).** Closing the returned handle retires its live
|
|
416
|
+
* protected calls — each with its exact retirement terminal, the
|
|
417
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
418
|
+
* on the service. Deliberately NOT the public path's
|
|
419
|
+
* let-existing-calls-finish behavior.
|
|
420
|
+
*/
|
|
421
|
+
serveOrgStreaming(service: string, access: OrgAccess, handler: OrgStreamingHandler, options: OrgServeOptions): OrgServeHandle;
|
|
422
|
+
/**
|
|
423
|
+
* Serve an org-protected client-streaming service: requests in
|
|
424
|
+
* through the {@link OrgRequestStream}, one reply out.
|
|
425
|
+
*
|
|
426
|
+
* On a follower the registration rides the proxy registry and
|
|
427
|
+
* inbound calls dispatch to the registering follower; teardown
|
|
428
|
+
* follows the retire discipline — a generation move fences the
|
|
429
|
+
* registration's calls and streams, never resurrected.
|
|
430
|
+
*
|
|
431
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
432
|
+
* handler future without a final poll — cancellation is observed
|
|
433
|
+
* through the retirement observables (the terminal item, the sink's
|
|
434
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
435
|
+
* handler-side event; a detached observer holding `retired`
|
|
436
|
+
* observes the signal. After retirement a handler sees typed
|
|
437
|
+
* refusals and any return value it produces is discarded.
|
|
438
|
+
*
|
|
439
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
440
|
+
* protected split).** Closing the returned handle retires its live
|
|
441
|
+
* protected calls — each with its exact retirement terminal, the
|
|
442
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
443
|
+
* on the service. Deliberately NOT the public path's
|
|
444
|
+
* let-existing-calls-finish behavior.
|
|
445
|
+
*/
|
|
446
|
+
serveOrgClientStream(service: string, access: OrgAccess, handler: OrgClientStreamHandler, options: OrgServeOptions): OrgServeHandle;
|
|
447
|
+
/**
|
|
448
|
+
* Serve an org-protected duplex service: requests in and items out,
|
|
449
|
+
* independently, through the request stream and the sink.
|
|
450
|
+
*
|
|
451
|
+
* On a follower the registration rides the proxy registry and
|
|
452
|
+
* inbound calls dispatch to the registering follower; teardown
|
|
453
|
+
* follows the retire discipline — a generation move fences the
|
|
454
|
+
* registration's calls and streams, never resurrected.
|
|
455
|
+
*
|
|
456
|
+
* **Handler drop (F-S3.1-2).** The retire supervisor may drop the
|
|
457
|
+
* handler future without a final poll — cancellation is observed
|
|
458
|
+
* through the retirement observables (the terminal item, the sink's
|
|
459
|
+
* typed closed refusal, and `retired`), never assumed as a
|
|
460
|
+
* handler-side event; a detached observer holding `retired`
|
|
461
|
+
* observes the signal. After retirement a handler sees typed
|
|
462
|
+
* refusals and any return value it produces is discarded.
|
|
463
|
+
*
|
|
464
|
+
* **`close()` is a retirement, not a graceful unregister (C9's
|
|
465
|
+
* protected split).** Closing the returned handle retires its live
|
|
466
|
+
* protected calls — each with its exact retirement terminal, the
|
|
467
|
+
* deadline/cancel/revocation vocabulary — and refuses new openings
|
|
468
|
+
* on the service. Deliberately NOT the public path's
|
|
469
|
+
* let-existing-calls-finish behavior.
|
|
470
|
+
*/
|
|
471
|
+
serveOrgDuplex(service: string, access: OrgAccess, handler: OrgDuplexHandler, options: OrgServeOptions): OrgServeHandle;
|
|
472
|
+
/** Listen for one event tag. Returns a cancel handle. */
|
|
473
|
+
on<T extends SessionEvent['type']>(type: T, handler: (event: SessionEventOf<T>) => void): Unsubscribe;
|
|
474
|
+
/** Listen for every event. Returns a cancel handle. */
|
|
475
|
+
onEvent(handler: (event: SessionEvent) => void): Unsubscribe;
|
|
476
|
+
/**
|
|
477
|
+
* Listen for the five lifecycle tags only — `leader_changed`,
|
|
478
|
+
* `subscription_restored`, `leader_lost`, `generation_fenced`,
|
|
479
|
+
* `not_leader`.
|
|
480
|
+
*
|
|
481
|
+
* A page that wants to render "reconnecting" wants exactly these and
|
|
482
|
+
* none of the node traffic.
|
|
483
|
+
*/
|
|
484
|
+
onLifecycle(handler: (event: SessionLifecycleEvent) => void): Unsubscribe;
|
|
485
|
+
/** Consume events as an async iterable. */
|
|
486
|
+
events(): AsyncIterableIterator<SessionEvent>;
|
|
487
|
+
/**
|
|
488
|
+
* Stand down and release the lock.
|
|
489
|
+
*
|
|
490
|
+
* On a leader this is what lets a follower promote, so a page that
|
|
491
|
+
* navigates away without calling it leaves the handoff to the
|
|
492
|
+
* browser's own lock release.
|
|
493
|
+
*
|
|
494
|
+
* Every stream this session handed out is ended first, in that
|
|
495
|
+
* order: a consumer awaiting the next payload has to be settled by
|
|
496
|
+
* something, and after `inner.close()` nothing will ever emit
|
|
497
|
+
* again.
|
|
498
|
+
*/
|
|
499
|
+
close(): void;
|
|
500
|
+
/**
|
|
501
|
+
* End every stream this session handed out.
|
|
502
|
+
*
|
|
503
|
+
* `LeafStream.close` ends each waiting consumer and clears the
|
|
504
|
+
* listeners, which is precisely the disposition D2 specifies for a
|
|
505
|
+
* stream across a leader change: failed and observable, never
|
|
506
|
+
* resurrected and never left pending. Called on explicit
|
|
507
|
+
* leadership loss, on a generation that moved under this tab, and
|
|
508
|
+
* on close; idempotent, because a page may have closed a stream
|
|
509
|
+
* itself.
|
|
510
|
+
*/
|
|
511
|
+
private endStreams;
|
|
512
|
+
/**
|
|
513
|
+
* Re-type whatever the boundary threw.
|
|
514
|
+
*
|
|
515
|
+
* One wrapper for every method because they all cross the same
|
|
516
|
+
* boundary and a proxied failure must arrive as the same class a
|
|
517
|
+
* direct one does — that is the whole point of the failure text
|
|
518
|
+
* being carried verbatim through the proxy instead of re-parsed on
|
|
519
|
+
* the Rust side.
|
|
520
|
+
*/
|
|
521
|
+
private guard;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Join the origin's Net node, electing a leader if there is none.
|
|
525
|
+
*
|
|
526
|
+
* Resolves as soon as this tab knows its role: the leader once it has
|
|
527
|
+
* bootstrapped, a follower once it has attached. A follower keeps
|
|
528
|
+
* queueing for the lock in the background, so when the leader goes
|
|
529
|
+
* away it promotes itself, re-bootstraps with the same identity and
|
|
530
|
+
* restores the subscriptions — without the page doing anything.
|
|
531
|
+
*
|
|
532
|
+
* Rejects with a typed {@link LeafError}. {@link IdentityError} covers
|
|
533
|
+
* the two refusals worth knowing by name: a context with no Web Locks
|
|
534
|
+
* API (where one-node-per-origin cannot be enforced, so the leaf
|
|
535
|
+
* refuses rather than let two tabs evict each other) and a context
|
|
536
|
+
* with no IndexedDB (where an identity cannot be stored, so the page
|
|
537
|
+
* must inject one).
|
|
538
|
+
*/
|
|
539
|
+
export declare function openSession(options: SessionOptions): Promise<MeshSession>;
|