@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +517 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +666 -0
  5. package/dist/async-queue.d.ts +26 -0
  6. package/dist/async-queue.js +62 -0
  7. package/dist/credential.d.ts +70 -0
  8. package/dist/credential.js +95 -0
  9. package/dist/errors.d.ts +400 -0
  10. package/dist/errors.js +726 -0
  11. package/dist/events.d.ts +232 -0
  12. package/dist/events.js +298 -0
  13. package/dist/identity.d.ts +40 -0
  14. package/dist/identity.js +78 -0
  15. package/dist/index.bundle.js +2 -0
  16. package/dist/index.d.ts +38 -0
  17. package/dist/index.js +46 -0
  18. package/dist/leader/events.d.ts +111 -0
  19. package/dist/leader/events.js +116 -0
  20. package/dist/leader/index.d.ts +14 -0
  21. package/dist/leader/index.js +11 -0
  22. package/dist/leader/session.d.ts +539 -0
  23. package/dist/leader/session.js +770 -0
  24. package/dist/leader/wasm.d.ts +123 -0
  25. package/dist/leader/wasm.js +39 -0
  26. package/dist/lobby.d.ts +183 -0
  27. package/dist/lobby.js +504 -0
  28. package/dist/local.d.ts +80 -0
  29. package/dist/local.js +160 -0
  30. package/dist/net_leaf.d.ts +1494 -0
  31. package/dist/net_leaf.js +3753 -0
  32. package/dist/net_leaf_bg.wasm +0 -0
  33. package/dist/net_leaf_bg.wasm.d.ts +137 -0
  34. package/dist/netcode/client.d.ts +102 -0
  35. package/dist/netcode/client.js +296 -0
  36. package/dist/netcode/clock.d.ts +45 -0
  37. package/dist/netcode/clock.js +54 -0
  38. package/dist/netcode/host.d.ts +118 -0
  39. package/dist/netcode/host.js +276 -0
  40. package/dist/netcode/index.d.ts +19 -0
  41. package/dist/netcode/index.js +14 -0
  42. package/dist/netcode/interpolate.d.ts +50 -0
  43. package/dist/netcode/interpolate.js +100 -0
  44. package/dist/netcode/wire.d.ts +125 -0
  45. package/dist/netcode/wire.js +117 -0
  46. package/dist/node.d.ts +908 -0
  47. package/dist/node.js +1085 -0
  48. package/dist/org.d.ts +412 -0
  49. package/dist/org.js +567 -0
  50. package/dist/peer-driver.d.ts +117 -0
  51. package/dist/peer-driver.js +224 -0
  52. package/dist/store/assembly.d.ts +176 -0
  53. package/dist/store/assembly.js +325 -0
  54. package/dist/store/chunker.d.ts +83 -0
  55. package/dist/store/chunker.js +190 -0
  56. package/dist/store/core.d.ts +136 -0
  57. package/dist/store/core.js +477 -0
  58. package/dist/store/definition.d.ts +23 -0
  59. package/dist/store/definition.js +83 -0
  60. package/dist/store/errors.d.ts +77 -0
  61. package/dist/store/errors.js +94 -0
  62. package/dist/store/host.d.ts +247 -0
  63. package/dist/store/host.js +675 -0
  64. package/dist/store/index.d.ts +50 -0
  65. package/dist/store/index.js +40 -0
  66. package/dist/store/interest.d.ts +40 -0
  67. package/dist/store/interest.js +77 -0
  68. package/dist/store/inventory.d.ts +81 -0
  69. package/dist/store/inventory.js +140 -0
  70. package/dist/store/join.d.ts +118 -0
  71. package/dist/store/join.js +724 -0
  72. package/dist/store/json.d.ts +50 -0
  73. package/dist/store/json.js +283 -0
  74. package/dist/store/ledger.d.ts +187 -0
  75. package/dist/store/ledger.js +292 -0
  76. package/dist/store/owner.d.ts +616 -0
  77. package/dist/store/owner.js +1711 -0
  78. package/dist/store/patch.d.ts +70 -0
  79. package/dist/store/patch.js +166 -0
  80. package/dist/store/player.d.ts +65 -0
  81. package/dist/store/player.js +261 -0
  82. package/dist/store/replica.d.ts +187 -0
  83. package/dist/store/replica.js +740 -0
  84. package/dist/store/state.d.ts +42 -0
  85. package/dist/store/state.js +165 -0
  86. package/dist/store/types.d.ts +200 -0
  87. package/dist/store/types.js +25 -0
  88. package/dist/store/visibility.d.ts +118 -0
  89. package/dist/store/visibility.js +282 -0
  90. package/dist/store/wire.d.ts +309 -0
  91. package/dist/store/wire.js +834 -0
  92. package/dist/stream.d.ts +174 -0
  93. package/dist/stream.js +238 -0
  94. package/dist/three/index.d.ts +109 -0
  95. package/dist/three/index.js +130 -0
  96. package/dist/udp-probe.d.ts +159 -0
  97. package/dist/udp-probe.js +245 -0
  98. package/dist/wasm.d.ts +642 -0
  99. package/dist/wasm.js +98 -0
  100. package/dist/world/border.d.ts +84 -0
  101. package/dist/world/border.js +99 -0
  102. package/dist/world/handoff.d.ts +187 -0
  103. package/dist/world/handoff.js +222 -0
  104. package/dist/world/index.d.ts +13 -0
  105. package/dist/world/index.js +9 -0
  106. package/dist/world/link.d.ts +179 -0
  107. package/dist/world/link.js +476 -0
  108. package/dist/world/regions.d.ts +154 -0
  109. package/dist/world/regions.js +314 -0
  110. package/package.json +72 -0
package/dist/org.js ADDED
@@ -0,0 +1,567 @@
1
+ /**
2
+ * Organization-scoped streaming, as a page wants it (plan §4.5).
3
+ *
4
+ * The eight org verbs — {@link OrgUnaryHandler call}, streaming,
5
+ * client-streaming and duplex, each calling and serving — share one
6
+ * credential/option vocabulary and one terminal error taxonomy
7
+ * (`errors.ts`'s `OrgStreamError` family). The same eight names exist
8
+ * on {@link BrowserNode} and on {@link MeshSession}; the wrappers here
9
+ * serve both, so the two surfaces cannot drift into two spellings of
10
+ * one contract.
11
+ *
12
+ * **Suspension and closure (plan §4.5).** Deadlines are absolute: a
13
+ * frozen tab extends no lease and no window, so a call whose deadline
14
+ * passes while its tab is suspended retires with its deadline
15
+ * terminal on wake — {@link OrgTimeoutError}, or
16
+ * {@link OrgIndeterminateError} where the work may already be
17
+ * executing in another tab. Nothing resumes automatically: a retired
18
+ * call is never transparently re-opened, because re-opening is a
19
+ * fresh call with a fresh proof and MAY repeat effects. Node or tab
20
+ * close retires ownership — pending calls fail typed, and each
21
+ * dropped caller handle emits exactly one CANCEL.
22
+ *
23
+ * **Handler drop (F-S3.1-2), verbatim.** The retire supervisor may
24
+ * drop the handler future without a final poll — cancellation is
25
+ * observed through the retirement observables (the terminal item, the
26
+ * sink's typed closed refusal, and `retired`), never assumed as a
27
+ * handler-side event; a detached observer holding `retired` observes
28
+ * the signal. After retirement a handler sees typed refusals from
29
+ * `send`/`close`, and any return value it produces is discarded.
30
+ */
31
+ import { fromWasmError, orgRetireReason, orgTerminalError, OrgCancelledError, ORG_SINK_CLOSED_REFUSAL, UnknownLeafError, } from './errors.js';
32
+ /**
33
+ * The live org handles one node or session handed out, so its
34
+ * teardown can retire them: node close retires ownership — pending
35
+ * calls fail typed, one CANCEL per dropped caller handle.
36
+ */
37
+ export class OrgHandleRegistry {
38
+ live = new Set();
39
+ add(handle) {
40
+ this.live.add(handle);
41
+ }
42
+ delete(handle) {
43
+ this.live.delete(handle);
44
+ }
45
+ /**
46
+ * Retire every live handle with one typed terminal. Drains first,
47
+ * so a handle's own teardown cannot re-enter this loop. Every
48
+ * handle is attempted; the raw failures come back rather than being
49
+ * swallowed, for the owner's teardown ledger.
50
+ */
51
+ retireAll(error) {
52
+ const open = [...this.live];
53
+ this.live.clear();
54
+ const failures = [];
55
+ for (const handle of open) {
56
+ try {
57
+ handle.retire(error);
58
+ }
59
+ catch (failure) {
60
+ failures.push(failure);
61
+ }
62
+ }
63
+ return failures;
64
+ }
65
+ }
66
+ /**
67
+ * The response half of an org call, wrapped: terminal items become
68
+ * the §4.3 typed errors, and the async iterator throws them.
69
+ */
70
+ export class OrgStream {
71
+ inner;
72
+ onCancel;
73
+ onDone;
74
+ terminal = null;
75
+ /** A boundary rejection, latched so a second pull sees the same typed failure. */
76
+ failure = null;
77
+ handleDropped = false;
78
+ waiters = [];
79
+ /**
80
+ * @param `onCancel` drops the underlying handle — at most once, so
81
+ * one CANCEL leaves however many halves initiate the drop.
82
+ */
83
+ constructor(inner, onCancel, onDone) {
84
+ this.inner = inner;
85
+ this.onCancel = onCancel;
86
+ this.onDone = onDone;
87
+ }
88
+ next() {
89
+ if (this.failure !== null)
90
+ return Promise.reject(this.failure);
91
+ if (this.terminal !== null)
92
+ return Promise.resolve(this.terminal);
93
+ const { promise, resolve, reject } = Promise.withResolvers();
94
+ const waiter = { resolve, reject };
95
+ this.waiters.push(waiter);
96
+ void this.inner.next().then((raw) => {
97
+ // BROWSER-4: this pull's waiter leaves the list the moment the
98
+ // pull settles — a completed pull retains nothing to the
99
+ // terminal.
100
+ spliceWaiter(this.waiters, waiter);
101
+ const item = byteItem(raw);
102
+ if (item.done)
103
+ this.complete(item);
104
+ // With-resolvers settlement is first-wins, so a pull a
105
+ // retirement already settled simply keeps that outcome.
106
+ resolve(item);
107
+ }, (error) => {
108
+ // A boundary rejection propagates as its classified self —
109
+ // a proxied `LeaderLost` is the existing `RpcError` kind,
110
+ // re-typed by the one mapper — and is latched for the next
111
+ // pull. Not folded into an error item: the item vocabulary
112
+ // is the §4.3 terminal set, and inventing a translation for
113
+ // a throw this package cannot name is how a taxonomy turns
114
+ // into guessing.
115
+ spliceWaiter(this.waiters, waiter);
116
+ const failure = fromWasmError(error);
117
+ // `fail` settles every still-parked pull and latches the
118
+ // failure for the next one; this pull settles itself.
119
+ this.fail(failure);
120
+ reject(failure);
121
+ });
122
+ return promise;
123
+ }
124
+ cancel() {
125
+ this.retire(new OrgCancelledError());
126
+ }
127
+ /**
128
+ * Retire this stream: one CANCEL at most, every parked and future
129
+ * `next()` settled with `error` as the terminal item.
130
+ */
131
+ retire(error) {
132
+ if (this.terminal !== null || this.failure !== null)
133
+ return;
134
+ this.dropHandle();
135
+ this.complete({ done: true, error });
136
+ }
137
+ [Symbol.asyncIterator]() {
138
+ // A terminal's final body is yielded BEFORE the end (BROWSER-1):
139
+ // one done item carrying bytes is one yield plus one end, so the
140
+ // end is this iterator's own latch, not the item's `done`.
141
+ let ended = false;
142
+ return {
143
+ next: async () => {
144
+ if (ended)
145
+ return { done: true, value: undefined };
146
+ const item = await this.next();
147
+ // THROWS the typed error at the terminal error item — the
148
+ // idiomatic `for await` shape, so `catch` sees the §4.3 class.
149
+ if (item.error !== undefined)
150
+ throw item.error;
151
+ if (item.value === undefined) {
152
+ ended = true;
153
+ return { done: true, value: undefined };
154
+ }
155
+ if (item.done)
156
+ ended = true;
157
+ return { done: false, value: item.value };
158
+ },
159
+ return: async () => {
160
+ ended = true;
161
+ this.cancel();
162
+ return { done: true, value: undefined };
163
+ },
164
+ };
165
+ }
166
+ complete(item) {
167
+ if (this.terminal !== null)
168
+ return;
169
+ this.terminal = item;
170
+ this.onDone?.();
171
+ const waiters = [...this.waiters];
172
+ this.waiters.length = 0;
173
+ for (const waiter of waiters)
174
+ waiter.resolve(item);
175
+ }
176
+ fail(error) {
177
+ if (this.terminal !== null || this.failure !== null)
178
+ return;
179
+ this.failure = error;
180
+ this.onDone?.();
181
+ const waiters = [...this.waiters];
182
+ this.waiters.length = 0;
183
+ for (const waiter of waiters)
184
+ waiter.reject(error);
185
+ }
186
+ dropHandle() {
187
+ if (this.handleDropped)
188
+ return;
189
+ this.handleDropped = true;
190
+ try {
191
+ this.onCancel();
192
+ }
193
+ catch {
194
+ // A handle that refuses its own drop is already retired at the
195
+ // boundary; the typed terminal below is what the consumer sees.
196
+ }
197
+ }
198
+ }
199
+ /** A client-streaming upload, wrapped. */
200
+ export class OrgUpload {
201
+ inner;
202
+ onDone;
203
+ failure = null;
204
+ handleDropped = false;
205
+ waiters = [];
206
+ constructor(inner, onDone) {
207
+ this.inner = inner;
208
+ this.onDone = onDone;
209
+ }
210
+ async send(payload) {
211
+ if (this.failure !== null)
212
+ throw this.failure;
213
+ try {
214
+ await this.inner.send(payload);
215
+ }
216
+ catch (error) {
217
+ throw fromWasmError(error);
218
+ }
219
+ }
220
+ finish() {
221
+ if (this.failure !== null)
222
+ return Promise.reject(this.failure);
223
+ const { promise, resolve, reject } = Promise.withResolvers();
224
+ const waiter = { reject };
225
+ this.waiters.push(waiter);
226
+ void this.inner.finish().then((body) => {
227
+ spliceWaiter(this.waiters, waiter);
228
+ this.onDone?.();
229
+ resolve(body);
230
+ }, (error) => {
231
+ spliceWaiter(this.waiters, waiter);
232
+ this.onDone?.();
233
+ // finish() rejects with the typed terminal error (§4.3).
234
+ reject(fromWasmError(error));
235
+ });
236
+ return promise;
237
+ }
238
+ cancel() {
239
+ this.retire(new OrgCancelledError());
240
+ }
241
+ /** Retire this upload: one CANCEL at most; parked `finish()` rejects typed. */
242
+ retire(error) {
243
+ if (this.failure !== null)
244
+ return;
245
+ this.failure = error;
246
+ if (!this.handleDropped) {
247
+ this.handleDropped = true;
248
+ try {
249
+ this.inner.cancel();
250
+ }
251
+ catch {
252
+ // Already retired at the boundary; `error` is the outcome.
253
+ }
254
+ }
255
+ this.onDone?.();
256
+ const waiters = [...this.waiters];
257
+ this.waiters.length = 0;
258
+ for (const waiter of waiters)
259
+ waiter.reject(error);
260
+ }
261
+ }
262
+ /** A duplex call, wrapped: one call-level CANCEL however both halves end. */
263
+ export class OrgDuplex {
264
+ inner;
265
+ onDone;
266
+ /** The two halves share one drop, so one CANCEL leaves per call. */
267
+ handleDropped = false;
268
+ failure = null;
269
+ sink;
270
+ stream;
271
+ constructor(inner, onDone) {
272
+ this.inner = inner;
273
+ this.onDone = onDone;
274
+ this.stream = new OrgStream(inner.stream(), () => this.dropHandle(), () => this.onDone?.());
275
+ this.sink = {
276
+ send: async (payload) => {
277
+ if (this.failure !== null)
278
+ throw this.failure;
279
+ try {
280
+ await this.inner.send(payload);
281
+ }
282
+ catch (error) {
283
+ throw fromWasmError(error);
284
+ }
285
+ },
286
+ finishSending: async () => {
287
+ if (this.failure !== null)
288
+ throw this.failure;
289
+ try {
290
+ await this.inner.finish_sending();
291
+ }
292
+ catch (error) {
293
+ throw fromWasmError(error);
294
+ }
295
+ },
296
+ cancel: () => {
297
+ this.retire(new OrgCancelledError());
298
+ },
299
+ };
300
+ }
301
+ /** Retire the whole call: one CANCEL, both halves settled typed. */
302
+ retire(error) {
303
+ this.failure ??= error;
304
+ this.dropHandle();
305
+ // Idempotent one level down: a stream that already reached its
306
+ // own terminal keeps that outcome.
307
+ this.stream.retire(error);
308
+ this.onDone?.();
309
+ }
310
+ dropHandle() {
311
+ if (this.handleDropped)
312
+ return;
313
+ this.handleDropped = true;
314
+ try {
315
+ this.inner.cancel();
316
+ }
317
+ catch {
318
+ // Already retired at the boundary; `error` is the outcome.
319
+ }
320
+ }
321
+ }
322
+ /**
323
+ * The response sink a handler receives, wrapped. `retired` is
324
+ * memoized at construction — the boundary's `retired()` is called
325
+ * exactly once and the one signal is what a detached observer holds.
326
+ */
327
+ export class OrgSink {
328
+ inner;
329
+ retired;
330
+ verdict = null;
331
+ constructor(inner) {
332
+ this.inner = inner;
333
+ this.retired = inner.retired().then((raw) => {
334
+ this.verdict ??= orgRetireReason(raw);
335
+ return this.verdict;
336
+ }, () => {
337
+ // A `retired()` that itself fails is reported as a teardown
338
+ // verdict rather than left unsettled — an observer must get
339
+ // ONE signal, never a hang and never a rejection.
340
+ this.verdict ??= 'replaced';
341
+ return this.verdict;
342
+ });
343
+ }
344
+ async send(payload) {
345
+ if (this.verdict !== null)
346
+ throw new OrgCancelledError(ORG_SINK_CLOSED_REFUSAL);
347
+ try {
348
+ await this.inner.send(payload);
349
+ }
350
+ catch (error) {
351
+ throw fromWasmError(error);
352
+ }
353
+ }
354
+ /** Half-close the response direction: the call's normal ending. */
355
+ async close() {
356
+ if (this.verdict !== null)
357
+ throw new OrgCancelledError(ORG_SINK_CLOSED_REFUSAL);
358
+ try {
359
+ await this.inner.close();
360
+ }
361
+ catch (error) {
362
+ throw fromWasmError(error);
363
+ }
364
+ }
365
+ }
366
+ /**
367
+ * The request stream a handler receives, wrapped. Its terminal is the
368
+ * `done` item; the abnormal record is `retired`, memoized once as
369
+ * {@link OrgResponseSink.retired} is.
370
+ */
371
+ export class OrgRequests {
372
+ inner;
373
+ retired;
374
+ verdict = null;
375
+ waiters = [];
376
+ constructor(inner) {
377
+ this.inner = inner;
378
+ this.retired = inner.retired().then((raw) => {
379
+ this.settle(orgRetireReason(raw));
380
+ return this.verdict;
381
+ }, () => {
382
+ this.settle('replaced');
383
+ return this.verdict;
384
+ });
385
+ }
386
+ next() {
387
+ if (this.verdict !== null)
388
+ return Promise.resolve({ done: true });
389
+ const { promise, resolve } = Promise.withResolvers();
390
+ this.waiters.push(resolve);
391
+ void this.inner.next().then((raw) => {
392
+ spliceWaiter(this.waiters, resolve);
393
+ resolve(requestItem(raw));
394
+ }, () => {
395
+ // A boundary throw here ends the iteration: the request
396
+ // direction is over one way or the other, and `retired` is
397
+ // where a handler reads which.
398
+ spliceWaiter(this.waiters, resolve);
399
+ resolve({ done: true });
400
+ });
401
+ return promise;
402
+ }
403
+ [Symbol.asyncIterator]() {
404
+ return {
405
+ next: async () => {
406
+ const item = await this.next();
407
+ if (item.done || item.value === undefined)
408
+ return { done: true, value: undefined };
409
+ return { done: false, value: item.value };
410
+ },
411
+ return: async () => ({ done: true, value: undefined }),
412
+ };
413
+ }
414
+ settle(verdict) {
415
+ if (this.verdict !== null)
416
+ return;
417
+ this.verdict = verdict;
418
+ const waiters = [...this.waiters];
419
+ this.waiters.length = 0;
420
+ for (const waiter of waiters)
421
+ waiter({ done: true });
422
+ }
423
+ }
424
+ /**
425
+ * One serve registration, wrapped. `close()` is C9's protected-split
426
+ * retirement: it retires the registration's live protected calls with
427
+ * their exact terminals and refuses new openings on the service.
428
+ */
429
+ export class OrgServe {
430
+ inner;
431
+ onDone;
432
+ closed = false;
433
+ constructor(inner, onDone) {
434
+ this.inner = inner;
435
+ this.onDone = onDone;
436
+ }
437
+ get service() {
438
+ return this.inner.service();
439
+ }
440
+ /**
441
+ * **A retirement, not a graceful unregister (C9's protected
442
+ * split).** Every live protected call this registration admitted is
443
+ * retired with its exact retirement terminal — the
444
+ * deadline/cancel/revocation vocabulary — and new openings on the
445
+ * service are refused. Deliberately NOT the public path's
446
+ * let-existing-calls-finish behavior.
447
+ */
448
+ close() {
449
+ if (this.closed)
450
+ return;
451
+ this.closed = true;
452
+ this.onDone?.();
453
+ this.inner.close();
454
+ }
455
+ /** The registry's teardown verb: for a registration it is `close()`. */
456
+ retire(_error) {
457
+ this.close();
458
+ }
459
+ }
460
+ /**
461
+ * Build exactly the options object `call_org*` reads.
462
+ *
463
+ * A fresh object, so a caller mutating its own afterwards cannot
464
+ * change a call already in flight. The byte fields are forwarded as
465
+ * the `Uint8Array`s they must be — the leaf refuses anything else
466
+ * loudly, and a silent re-encode here would be a second codec.
467
+ */
468
+ export function toWasmOrgCallOptions(options) {
469
+ const credentials = {
470
+ membership: options.credentials.membership,
471
+ dispatcher: options.credentials.dispatcher,
472
+ ...(options.credentials.capabilityGrant === undefined
473
+ ? {}
474
+ : { capabilityGrant: options.credentials.capabilityGrant }),
475
+ actingOrg: options.credentials.actingOrg,
476
+ providerOwnerOrg: options.credentials.providerOwnerOrg,
477
+ provider: options.credentials.provider,
478
+ ...(options.credentials.proofTtlSecs === undefined
479
+ ? {}
480
+ : { proofTtlSecs: options.credentials.proofTtlSecs }),
481
+ };
482
+ return {
483
+ credentials,
484
+ ...(options.deadlineMs === undefined ? {} : { deadlineMs: options.deadlineMs }),
485
+ ...(options.streamWindowInitial === undefined
486
+ ? {}
487
+ : { streamWindowInitial: options.streamWindowInitial }),
488
+ ...(options.requestWindowInitial === undefined
489
+ ? {}
490
+ : { requestWindowInitial: options.requestWindowInitial }),
491
+ };
492
+ }
493
+ /** Build exactly the options object `serve_org*` reads. */
494
+ export function toWasmOrgServeOptions(options) {
495
+ return { ownerOrg: options.ownerOrg };
496
+ }
497
+ /**
498
+ * Read one handler invocation's caller attribution.
499
+ *
500
+ * The boundary hands the crate's `to_json` JSON **string** (the same
501
+ * convention `on_event` uses), camelCase keys verbatim
502
+ * {@link OrgCaller}'s; anything that does not parse to an object is
503
+ * refused rather than served with an attribution this build cannot
504
+ * vouch for.
505
+ */
506
+ export function parseOrgCaller(raw) {
507
+ const value = JSON.parse(raw);
508
+ if (value === null || typeof value !== 'object') {
509
+ throw new UnknownLeafError('the caller attribution is not an OrgCaller object');
510
+ }
511
+ const record = value;
512
+ return {
513
+ entity: String(record.entity ?? ''),
514
+ actingOrg: String(record.actingOrg ?? ''),
515
+ providerOrg: String(record.providerOrg ?? ''),
516
+ provider: String(record.provider ?? ''),
517
+ capability: String(record.capability ?? ''),
518
+ isSameOrg: record.isSameOrg === true,
519
+ };
520
+ }
521
+ /** Adapt a unary handler to the boundary's `(caller, request)` trampoline. */
522
+ export function unaryOrgTrampoline(handler) {
523
+ return async (caller, request) => await handler(parseOrgCaller(caller), request);
524
+ }
525
+ /**
526
+ * Adapt a streaming handler to `(caller, request, sink)`. The sink is
527
+ * wrapped once per invocation; its `retired` is the one signal the
528
+ * F-S3.1-2 level names.
529
+ */
530
+ export function streamingOrgTrampoline(handler) {
531
+ return async (caller, request, sink) => {
532
+ await handler(parseOrgCaller(caller), request, new OrgSink(sink));
533
+ };
534
+ }
535
+ /** Adapt a client-streaming handler to `(caller, requests)`. */
536
+ export function clientStreamOrgTrampoline(handler) {
537
+ return async (caller, requests) => await handler(parseOrgCaller(caller), new OrgRequests(requests));
538
+ }
539
+ /** Adapt a duplex handler to `(caller, requests, sink)`. */
540
+ export function duplexOrgTrampoline(handler) {
541
+ return async (caller, requests, sink) => {
542
+ await handler(parseOrgCaller(caller), new OrgRequests(requests), new OrgSink(sink));
543
+ };
544
+ }
545
+ /**
546
+ * One waiter leaves its list the moment its pull settles (BROWSER-4):
547
+ * a resolved pull retains no closure until the terminal.
548
+ */
549
+ function spliceWaiter(waiters, waiter) {
550
+ const index = waiters.indexOf(waiter);
551
+ if (index >= 0)
552
+ waiters.splice(index, 1);
553
+ }
554
+ /** The boundary's byte item, typed through the §4.3 vocabulary. */
555
+ function byteItem(raw) {
556
+ if (!raw.done)
557
+ return { done: false, value: raw.value };
558
+ if (raw.error !== undefined)
559
+ return { done: true, error: orgTerminalError(raw.error) };
560
+ // A terminal that carries a final body keeps it (BROWSER-1): the
561
+ // item is `{ done: true, value }`, never a flattened end.
562
+ return raw.value === undefined ? { done: true } : { done: true, value: raw.value };
563
+ }
564
+ /** The boundary's request item: no error arm, by contract. */
565
+ function requestItem(raw) {
566
+ return raw.done ? { done: true } : { done: false, value: raw.value };
567
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The peer-attempt drive loop, once, for both surfaces.
3
+ *
4
+ * `BrowserNode` drives an attempt on this tab's own node;
5
+ * `MeshSession` drives one on whichever tab holds the lock. The steps
6
+ * and the classification are identical, and they must stay identical:
7
+ * the offerer and the answerer disagreeing about what supersession or
8
+ * a terminal reading looks like is a defect neither side can detect.
9
+ * So the loop lives here and each surface supplies only the four
10
+ * primitives ({@link PeerPrimitives}).
11
+ *
12
+ * **Supersession now has two sources, and that is the reason this
13
+ * file exists rather than a second copy of the loop.**
14
+ *
15
+ * 1. *Client-side*, as before: the reading names the dialog that is
16
+ * live, so a reading for a different dialog means this attempt was
17
+ * replaced.
18
+ * 2. *Server-side*, new with the proxy: a request that named a dialog
19
+ * is refused by the leaf **before** it services anything, because
20
+ * the attempt it was issued for is no longer the live one. A
21
+ * proxied request crosses a channel and is served later, so this is
22
+ * the common case for a follower rather than an edge.
23
+ *
24
+ * Both are the same disposition to a caller, and both are classified
25
+ * here. A direct caller sees (1) almost always and (2) never; a
26
+ * proxied caller sees either.
27
+ */
28
+ import type { PeerAttemptStatus, PeerConnectOutcome } from './node.js';
29
+ /**
30
+ * The four steps an attempt needs, as whichever surface provides
31
+ * them.
32
+ *
33
+ * `candidate` and `handshake` take the dialog because the leaf
34
+ * requires it: a peer-only poll is resolved against whatever attempt
35
+ * is live when it runs, which for a request that crossed a channel is
36
+ * how a superseded request drives its own replacement.
37
+ */
38
+ export interface PeerPrimitives {
39
+ /** Mint an offer and start an attempt; resolves to its dialog. */
40
+ offer(peer: string): Promise<string>;
41
+ /** Answer the offer already filed; resolves to its dialog. */
42
+ acceptOffer(peer: string): Promise<string>;
43
+ /** Service one polling step of `dialog`; resolves to the reading JSON. */
44
+ candidate(peer: string, dialog: string): Promise<string>;
45
+ /** Run the Noise handshake for `dialog`; resolves to the dialog it ran for. */
46
+ handshake(peer: string, dialog: string): Promise<string>;
47
+ }
48
+ /**
49
+ * The exact prefix the leaf's "peer not discovered" refusal carries
50
+ * (`wasm.rs`'s `NO_ANNOUNCEMENT_PREFIX`).
51
+ *
52
+ * Pinned because the wasm boundary carries messages, so a message is
53
+ * a contract: a reworded refusal demotes a typed disposition to a
54
+ * thrown error with nothing red.
55
+ */
56
+ export declare const NO_ANNOUNCEMENT_PREFIX = "no verified announcement for";
57
+ /** `wasm.rs`'s `NO_LIVE_ATTEMPT_PREFIX`. */
58
+ export declare const NO_LIVE_ATTEMPT_PREFIX = "no live attempt with";
59
+ /** `wasm.rs`'s refusal when no verified offer is waiting yet. */
60
+ export declare const NO_OFFER_PREFIX = "no verified offer from";
61
+ /**
62
+ * `wasm.rs`'s `attempt_for` refusal: the named dialog is not the one
63
+ * now live.
64
+ *
65
+ * Distinct from {@link NO_LIVE_ATTEMPT_PREFIX} on purpose — "your
66
+ * attempt was replaced" and "there is no attempt" are different
67
+ * facts, and the leaf says which. Both classify as `superseded`, but
68
+ * only this one can name the replacement.
69
+ */
70
+ export declare const REPLACED_ATTEMPT_MARKER = "is not the live attempt";
71
+ /** How long the drive loop waits between candidate steps. */
72
+ export declare const PEER_TICK_MS = 50;
73
+ /**
74
+ * How long {@link acceptPeer} waits for the offer envelope to arrive
75
+ * before it treats "no offer" as the answer.
76
+ *
77
+ * Two anchor hops and one pump, so it is generous rather than tight.
78
+ */
79
+ export declare const PEER_OFFER_WAIT_MS = 5000;
80
+ /** Offer, drive to an open channel, then handshake (§9 steps 3-4). */
81
+ export declare function connectPeer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;
82
+ /**
83
+ * Answer an offer and drive to a direct session.
84
+ *
85
+ * The accept is retried while the leaf reports no offer yet: the
86
+ * envelope crosses A → anchor → B, so a page reacting to "the peer is
87
+ * here" a moment before it lands would be told there is no offer,
88
+ * which is true and not an answer. Every other refusal is returned or
89
+ * thrown at once, because retrying those would only hide them.
90
+ */
91
+ export declare function acceptPeer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;
92
+ /**
93
+ * Run the offerer's handshake on an attempt whose channel is open.
94
+ *
95
+ * The dialog is **checked** rather than assumed: the handshake
96
+ * resolves to the dialog it actually ran for, and a mismatch is this
97
+ * caller's attempt having been replaced underneath it. Reporting
98
+ * `direct` with the caller's own dialog on any success is how a stale
99
+ * caller was once told its superseded attempt had connected, while
100
+ * the leaf had handshaken the attempt that replaced it.
101
+ */
102
+ export declare function handshakePeer(peer: string, dialog: string, primitives: PeerPrimitives): Promise<PeerConnectOutcome>;
103
+ /**
104
+ * Pump one attempt until `done`, its terminal transition, or a newer
105
+ * attempt replaces it.
106
+ *
107
+ * `{ type: 'direct' }` here means only "`done` answered true" — the
108
+ * caller decides what remains.
109
+ *
110
+ * **Every terminal state ends the loop**, not only the two ICE ones.
111
+ * An answerer waits for `direct`, and a channel that opened with a
112
+ * session that never installed satisfies neither `direct` nor
113
+ * `iceTimeout`: the leaf reports `failed`, which is this loop's exit.
114
+ * The bound is the leaf's, which owns the attempt's deadline — a
115
+ * timeout added here would be a second clock disagreeing with it.
116
+ */
117
+ export declare function driveAttempt(peer: string, dialog: string, done: (status: PeerAttemptStatus) => boolean, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;