@net-mesh/browser 0.40.0 → 0.41.1
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 +54 -0
- package/README.md +5 -3
- package/dist/index.bundle.js +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/leader/index.d.ts +1 -1
- package/dist/leader/session.d.ts +8 -1
- package/dist/leader/session.js +12 -4
- package/dist/lobby.js +24 -10
- package/dist/net_leaf.d.ts +2 -2
- package/dist/net_leaf.js +11 -11
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +2 -2
- package/dist/netcode/index.d.ts +3 -0
- package/dist/netcode/index.js +4 -0
- package/dist/node.d.ts +13 -1
- package/dist/node.js +28 -7
- package/dist/peer-driver.d.ts +77 -2
- package/dist/peer-driver.js +163 -3
- package/dist/store/host.d.ts +19 -3
- package/dist/store/host.js +21 -7
- package/dist/store/index.d.ts +23 -0
- package/dist/store/index.js +16 -0
- package/dist/store/join.d.ts +6 -1
- package/dist/store/join.js +14 -8
- package/dist/world/link.js +3 -3
- package/dist/world/regions.js +3 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -36,3 +36,12 @@ export { rememberedIdentity, DEFAULT_IDENTITY_KEY } from './identity.js';
|
|
|
36
36
|
export type { IdentitySecrets } from './identity.js';
|
|
37
37
|
export { requestCredential, CredentialRequestError } from './credential.js';
|
|
38
38
|
export type { AnchorCredential, CredentialRequestErrorKind, RequestCredentialOptions, } from './credential.js';
|
|
39
|
+
export { ORG_SINK_BUDGET_REFUSAL, ORG_UPLOAD_SINK_BUDGET_REFUSAL, ORG_UPLOAD_SINK_CLOSED_REFUSAL, } from './errors.js';
|
|
40
|
+
export { clientStreamOrgTrampoline, duplexOrgTrampoline, streamingOrgTrampoline, unaryOrgTrampoline, } from './org.js';
|
|
41
|
+
export { acceptPeer, connectPeer, driveAttempt, handshakePeer, NO_ANNOUNCEMENT_PREFIX, NO_LIVE_ATTEMPT_PREFIX, NO_OFFER_PREFIX, PEER_OFFER_WAIT_MS, PEER_TICK_MS, PeerAttempts, REPLACED_ATTEMPT_MARKER, } from './peer-driver.js';
|
|
42
|
+
export type { HealthyDialog, PeerPrimitives, } from './peer-driver.js';
|
|
43
|
+
export { StreamIdentityError, } from './stream.js';
|
|
44
|
+
export type { StreamIdentity, } from './stream.js';
|
|
45
|
+
export { STUN_NO_RESPONSE_CODE, } from './udp-probe.js';
|
|
46
|
+
export { idArg, } from './wasm.js';
|
|
47
|
+
export type { LeafWasmOrgAccess, LeafWasmOrgByteItem, LeafWasmOrgByteStreamHandle, LeafWasmOrgCaller, LeafWasmOrgCallOptions, LeafWasmOrgClientStreamHandler, LeafWasmOrgCredentials, LeafWasmOrgDuplexCallHandle, LeafWasmOrgDuplexHandler, LeafWasmOrgRequestItem, LeafWasmOrgRequestStreamHandle, LeafWasmOrgResponseSinkHandle, LeafWasmOrgServeHandle, LeafWasmOrgServeOptions, LeafWasmOrgStreamingHandler, LeafWasmOrgTerminalError, LeafWasmOrgUnaryHandler, LeafWasmOrgUploadCallHandle, } from './wasm.js';
|
package/dist/index.js
CHANGED
|
@@ -44,3 +44,12 @@ export * from './lobby.js';
|
|
|
44
44
|
export { rememberedIdentity, DEFAULT_IDENTITY_KEY } from './identity.js';
|
|
45
45
|
// Anonymous visitor credentials from a game anchor.
|
|
46
46
|
export { requestCredential, CredentialRequestError } from './credential.js';
|
|
47
|
+
// The rest of the public module surface: lower-level building blocks
|
|
48
|
+
// the high-level verbs above are made of, exported so callers can
|
|
49
|
+
// compose their own.
|
|
50
|
+
export { ORG_SINK_BUDGET_REFUSAL, ORG_UPLOAD_SINK_BUDGET_REFUSAL, ORG_UPLOAD_SINK_CLOSED_REFUSAL, } from './errors.js';
|
|
51
|
+
export { clientStreamOrgTrampoline, duplexOrgTrampoline, streamingOrgTrampoline, unaryOrgTrampoline, } from './org.js';
|
|
52
|
+
export { acceptPeer, connectPeer, driveAttempt, handshakePeer, NO_ANNOUNCEMENT_PREFIX, NO_LIVE_ATTEMPT_PREFIX, NO_OFFER_PREFIX, PEER_OFFER_WAIT_MS, PEER_TICK_MS, PeerAttempts, REPLACED_ATTEMPT_MARKER, } from './peer-driver.js';
|
|
53
|
+
export { StreamIdentityError, } from './stream.js';
|
|
54
|
+
export { STUN_NO_RESPONSE_CODE, } from './udp-probe.js';
|
|
55
|
+
export { idArg, } from './wasm.js';
|
package/dist/leader/index.d.ts
CHANGED
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
export { MeshSession, openSession } from './session.js';
|
|
10
10
|
export type { SessionOptions, SessionRole } from './session.js';
|
|
11
11
|
export { isLifecycleEvent, parseSessionEvent } from './events.js';
|
|
12
|
-
export type { GenerationFencedEvent, LeaderChangedEvent, LeaderLostEvent, NotLeaderEvent, SessionEvent, SessionEventOf, SessionLifecycleEvent, SubscriptionRestoredEvent, } from './events.js';
|
|
12
|
+
export type { GenerationFencedEvent, LeaderChangedEvent, LeaderLostEvent, NotLeaderEvent, PromotionFailedEvent, SessionEvent, SessionEventOf, SessionLifecycleEvent, SubscriptionRestoredEvent, } from './events.js';
|
|
13
13
|
export { asSessionModule } from './wasm.js';
|
|
14
14
|
export type { LeafWasmSession, LeafWasmSessionModule, LeafWasmSessionOptions, } from './wasm.js';
|
package/dist/leader/session.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export declare class MeshSession {
|
|
|
90
90
|
* iteration a page is already written to handle.
|
|
91
91
|
*/
|
|
92
92
|
private readonly streams;
|
|
93
|
-
/** One attempt per peer at a time: see {@link PeerAttempts}. */
|
|
93
|
+
/** One attempt per peer at a time, and one offerer per pair: see {@link PeerAttempts}. */
|
|
94
94
|
private readonly attempts;
|
|
95
95
|
/**
|
|
96
96
|
* The generation the streams in that set were opened under.
|
|
@@ -228,6 +228,13 @@ export declare class MeshSession {
|
|
|
228
228
|
* offer: it is a proxy round trip here, and read ahead of the gate it
|
|
229
229
|
* could be stale by the time the offer went out. See
|
|
230
230
|
* {@link PeerAttempts}.
|
|
231
|
+
*
|
|
232
|
+
* **And between two nodes, the lower id offers.** When this node's id
|
|
233
|
+
* is the higher of the pair, an offer the peer sent in the last
|
|
234
|
+
* `PEER_OFFER_FRESH_MS` (10 s) — or one that crosses this call's own —
|
|
235
|
+
* is answered instead of offered over, so two pages reaching each
|
|
236
|
+
* other at once (glare) converge rather than cancel. See
|
|
237
|
+
* {@link PeerAttempts}.
|
|
231
238
|
*/
|
|
232
239
|
connectPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
233
240
|
/**
|
package/dist/leader/session.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
import { fromWasmError, OrgCancelledError, OrgLeaderLostError, } from '../errors.js';
|
|
30
30
|
import { clientStreamOrgTrampoline, duplexOrgTrampoline, OrgDuplex, OrgHandleRegistry, OrgServe, OrgStream, OrgUpload, streamingOrgTrampoline, toWasmOrgCallOptions, toWasmOrgServeOptions, unaryOrgTrampoline, } from '../org.js';
|
|
31
31
|
import { buildConnectRequest, parseAttemptStatus, parseCounters, parseDescriptors, } from '../node.js';
|
|
32
|
-
import { acceptPeer as driveAccept, connectPeer as driveConnect, PeerAttempts, } from '../peer-driver.js';
|
|
32
|
+
import { acceptPeer as driveAccept, answerWaitingOffer, connectPeer as driveConnect, noteOffer, PeerAttempts, } from '../peer-driver.js';
|
|
33
33
|
import { LeafStream } from '../stream.js';
|
|
34
34
|
import { idArg, loadLeafWasm } from '../wasm.js';
|
|
35
35
|
import { isLifecycleEvent, parseSessionEvent, } from './events.js';
|
|
@@ -60,8 +60,8 @@ export class MeshSession {
|
|
|
60
60
|
* iteration a page is already written to handle.
|
|
61
61
|
*/
|
|
62
62
|
streams = new Set();
|
|
63
|
-
/** One attempt per peer at a time: see {@link PeerAttempts}. */
|
|
64
|
-
attempts = new PeerAttempts();
|
|
63
|
+
/** One attempt per peer at a time, and one offerer per pair: see {@link PeerAttempts}. */
|
|
64
|
+
attempts = new PeerAttempts(() => this.inner.node_id_hex() ?? null);
|
|
65
65
|
/**
|
|
66
66
|
* The generation the streams in that set were opened under.
|
|
67
67
|
*
|
|
@@ -89,6 +89,7 @@ export class MeshSession {
|
|
|
89
89
|
this.inner = inner;
|
|
90
90
|
this.streamGeneration = inner.generation();
|
|
91
91
|
inner.on_event((json) => this.hub.deliver(json, parseSessionEvent));
|
|
92
|
+
this.hub.onAny((event) => noteOffer(this.attempts, event));
|
|
92
93
|
this.hub.onAny((event) => {
|
|
93
94
|
// `leader_lost` is this tab losing the leader it was talking
|
|
94
95
|
// to; `not_leader` is this tab discovering it *was* the leader
|
|
@@ -265,6 +266,13 @@ export class MeshSession {
|
|
|
265
266
|
* offer: it is a proxy round trip here, and read ahead of the gate it
|
|
266
267
|
* could be stale by the time the offer went out. See
|
|
267
268
|
* {@link PeerAttempts}.
|
|
269
|
+
*
|
|
270
|
+
* **And between two nodes, the lower id offers.** When this node's id
|
|
271
|
+
* is the higher of the pair, an offer the peer sent in the last
|
|
272
|
+
* `PEER_OFFER_FRESH_MS` (10 s) — or one that crosses this call's own —
|
|
273
|
+
* is answered instead of offered over, so two pages reaching each
|
|
274
|
+
* other at once (glare) converge rather than cancel. See
|
|
275
|
+
* {@link PeerAttempts}.
|
|
268
276
|
*/
|
|
269
277
|
async connectPeer(nodeIdHex) {
|
|
270
278
|
return this.attempts.connect(nodeIdHex, async () => {
|
|
@@ -276,7 +284,7 @@ export class MeshSession {
|
|
|
276
284
|
// older leader simply has no such reading.
|
|
277
285
|
return undefined;
|
|
278
286
|
}
|
|
279
|
-
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
287
|
+
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus), () => answerWaitingOffer(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
280
288
|
}
|
|
281
289
|
/**
|
|
282
290
|
* Answer the offer `nodeIdHex` filed — {@link connectPeer}'s
|
package/dist/lobby.js
CHANGED
|
@@ -435,19 +435,32 @@ export async function joinLobby(options) {
|
|
|
435
435
|
const node = options.node;
|
|
436
436
|
const keep = [...(options.tags ?? [])];
|
|
437
437
|
const seeking = [...keep, seekTag(options.game, host)];
|
|
438
|
-
let stopped =
|
|
438
|
+
let stopped = null;
|
|
439
|
+
// The seeking announcements not yet settled: the withdrawal waits for
|
|
440
|
+
// them, or one landing after it would put the seek tag back — over a
|
|
441
|
+
// lobby this node created meanwhile.
|
|
442
|
+
const seekingInFlight = new Set();
|
|
439
443
|
const announce = () => {
|
|
440
|
-
if (
|
|
441
|
-
|
|
444
|
+
if (stopped !== null)
|
|
445
|
+
return;
|
|
446
|
+
const sent = node.announce(seeking).catch(() => { });
|
|
447
|
+
seekingInFlight.add(sent);
|
|
448
|
+
void sent.finally(() => seekingInFlight.delete(sent));
|
|
442
449
|
};
|
|
443
450
|
announce();
|
|
444
451
|
const timer = setInterval(announce, LOBBY_ANNOUNCE_MS);
|
|
452
|
+
// Withdraws the seek tag, after any seeking announcement still in
|
|
453
|
+
// flight. Settles once the withdrawal has been attempted (a failure is
|
|
454
|
+
// swallowed, as every announcement here is), so a `close()` that awaits
|
|
455
|
+
// it is an ordering barrier: an announcement replaces the node's whole
|
|
456
|
+
// tag set, and one still in flight when the page goes on to
|
|
457
|
+
// `createLobby` would land after the new listing and erase it.
|
|
445
458
|
const stop = () => {
|
|
446
|
-
if (stopped)
|
|
447
|
-
return;
|
|
448
|
-
stopped = true;
|
|
459
|
+
if (stopped !== null)
|
|
460
|
+
return stopped;
|
|
449
461
|
clearInterval(timer);
|
|
450
|
-
node.announce(keep).catch(() => { });
|
|
462
|
+
stopped = Promise.all(seekingInFlight).then(() => node.announce(keep)).catch(() => { });
|
|
463
|
+
return stopped;
|
|
451
464
|
};
|
|
452
465
|
// Reach the host before joining. Until this player's announcement has
|
|
453
466
|
// reached the host, the host refuses the handshake — so a refusal here
|
|
@@ -462,7 +475,7 @@ export async function joinLobby(options) {
|
|
|
462
475
|
}
|
|
463
476
|
catch (error) {
|
|
464
477
|
if (Date.now() >= deadline) {
|
|
465
|
-
stop();
|
|
478
|
+
void stop();
|
|
466
479
|
throw new LobbyError('not-found', `could not reach the lobby's host ${host}: ${String(error?.message ?? error)}`);
|
|
467
480
|
}
|
|
468
481
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
@@ -482,13 +495,14 @@ export async function joinLobby(options) {
|
|
|
482
495
|
});
|
|
483
496
|
}
|
|
484
497
|
catch (error) {
|
|
485
|
-
stop();
|
|
498
|
+
void stop();
|
|
486
499
|
throw error;
|
|
487
500
|
}
|
|
488
501
|
const close = joined.close.bind(joined);
|
|
489
502
|
joined.close = async () => {
|
|
490
|
-
stop();
|
|
503
|
+
const withdrawn = stop();
|
|
491
504
|
await close();
|
|
505
|
+
await withdrawn;
|
|
492
506
|
};
|
|
493
507
|
return joined;
|
|
494
508
|
}
|
package/dist/net_leaf.d.ts
CHANGED
|
@@ -1453,8 +1453,8 @@ export interface InitOutput {
|
|
|
1453
1453
|
readonly proxystream_send: (a: number, b: number) => number;
|
|
1454
1454
|
readonly proxystream_stream_id_hex: (a: number, b: number) => void;
|
|
1455
1455
|
readonly start: () => void;
|
|
1456
|
-
readonly
|
|
1457
|
-
readonly
|
|
1456
|
+
readonly __wasm_bindgen_func_elem_6504: (a: number, b: number, c: number, d: number) => void;
|
|
1457
|
+
readonly __wasm_bindgen_func_elem_6506: (a: number, b: number, c: number, d: number) => void;
|
|
1458
1458
|
readonly __wasm_bindgen_func_elem_2365: (a: number, b: number, c: number) => number;
|
|
1459
1459
|
readonly __wasm_bindgen_func_elem_2366: (a: number, b: number, c: number) => void;
|
|
1460
1460
|
readonly __wasm_bindgen_func_elem_2366_112: (a: number, b: number, c: number) => void;
|
package/dist/net_leaf.js
CHANGED
|
@@ -2718,13 +2718,13 @@ function __wbg_get_imports() {
|
|
|
2718
2718
|
const ret = getObject(arg0).error;
|
|
2719
2719
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
2720
2720
|
},
|
|
2721
|
+
__wbg_error_4c50581549efd2db: function(arg0, arg1) {
|
|
2722
|
+
console.error(getStringFromWasm0(arg0, arg1));
|
|
2723
|
+
},
|
|
2721
2724
|
__wbg_error_6a23b57453026a47: function() { return handleError(function (arg0) {
|
|
2722
2725
|
const ret = getObject(arg0).error;
|
|
2723
2726
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
2724
2727
|
}, arguments); },
|
|
2725
|
-
__wbg_error_c7a46bea9bd49ca0: function(arg0, arg1) {
|
|
2726
|
-
console.error(getStringFromWasm0(arg0, arg1));
|
|
2727
|
-
},
|
|
2728
2728
|
__wbg_fetch_4178962c570137ca: function(arg0, arg1, arg2) {
|
|
2729
2729
|
const ret = getObject(arg0).fetch(getStringFromWasm0(arg1, arg2));
|
|
2730
2730
|
return addHeapObject(ret);
|
|
@@ -2914,7 +2914,7 @@ function __wbg_get_imports() {
|
|
|
2914
2914
|
const a = state0.a;
|
|
2915
2915
|
state0.a = 0;
|
|
2916
2916
|
try {
|
|
2917
|
-
return
|
|
2917
|
+
return __wasm_bindgen_func_elem_6506(a, state0.b, arg0, arg1);
|
|
2918
2918
|
} finally {
|
|
2919
2919
|
state0.a = a;
|
|
2920
2920
|
}
|
|
@@ -2956,7 +2956,7 @@ function __wbg_get_imports() {
|
|
|
2956
2956
|
const a = state0.a;
|
|
2957
2957
|
state0.a = 0;
|
|
2958
2958
|
try {
|
|
2959
|
-
return
|
|
2959
|
+
return __wasm_bindgen_func_elem_6506(a, state0.b, arg0, arg1);
|
|
2960
2960
|
} finally {
|
|
2961
2961
|
state0.a = a;
|
|
2962
2962
|
}
|
|
@@ -3275,8 +3275,8 @@ function __wbg_get_imports() {
|
|
|
3275
3275
|
return addHeapObject(ret);
|
|
3276
3276
|
},
|
|
3277
3277
|
__wbindgen_generic_0000000000000003: function(arg0, arg1) {
|
|
3278
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
3279
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3278
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 867, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3279
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_6504);
|
|
3280
3280
|
return addHeapObject(ret);
|
|
3281
3281
|
},
|
|
3282
3282
|
__wbindgen_generic_0000000000000004: function(arg0, arg1) {
|
|
@@ -3366,14 +3366,14 @@ function __wasm_bindgen_func_elem_2365(arg0, arg1, arg2) {
|
|
|
3366
3366
|
return takeObject(ret);
|
|
3367
3367
|
}
|
|
3368
3368
|
|
|
3369
|
-
function
|
|
3370
|
-
wasm.
|
|
3369
|
+
function __wasm_bindgen_func_elem_6506(arg0, arg1, arg2, arg3) {
|
|
3370
|
+
wasm.__wasm_bindgen_func_elem_6506(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
3371
3371
|
}
|
|
3372
3372
|
|
|
3373
|
-
function
|
|
3373
|
+
function __wasm_bindgen_func_elem_6504(arg0, arg1, arg2) {
|
|
3374
3374
|
try {
|
|
3375
3375
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3376
|
-
wasm.
|
|
3376
|
+
wasm.__wasm_bindgen_func_elem_6504(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3377
3377
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3378
3378
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3379
3379
|
if (r1) {
|
package/dist/net_leaf_bg.wasm
CHANGED
|
Binary file
|
|
@@ -119,8 +119,8 @@ export const proxystream_peer_node_hex: (a: number, b: number) => void;
|
|
|
119
119
|
export const proxystream_send: (a: number, b: number) => number;
|
|
120
120
|
export const proxystream_stream_id_hex: (a: number, b: number) => void;
|
|
121
121
|
export const start: () => void;
|
|
122
|
-
export const
|
|
123
|
-
export const
|
|
122
|
+
export const __wasm_bindgen_func_elem_6504: (a: number, b: number, c: number, d: number) => void;
|
|
123
|
+
export const __wasm_bindgen_func_elem_6506: (a: number, b: number, c: number, d: number) => void;
|
|
124
124
|
export const __wasm_bindgen_func_elem_2365: (a: number, b: number, c: number) => number;
|
|
125
125
|
export const __wasm_bindgen_func_elem_2366: (a: number, b: number, c: number) => void;
|
|
126
126
|
export const __wasm_bindgen_func_elem_2366_112: (a: number, b: number, c: number) => void;
|
package/dist/netcode/index.d.ts
CHANGED
|
@@ -17,3 +17,6 @@ export type { ClockEstimate, Millis } from './clock.js';
|
|
|
17
17
|
export { SnapshotBuffer, lerpNumbers, SNAPSHOT_BUFFER } from './interpolate.js';
|
|
18
18
|
export type { Interpolator, TimedSnapshot } from './interpolate.js';
|
|
19
19
|
export type { NetcodeStream, NetcodeTransport } from './wire.js';
|
|
20
|
+
export { snapshotFrames, } from './host.js';
|
|
21
|
+
export { chunkOf, decodeFrame, defaultNow, encodeFrame, eventPeer, MAX_NETCODE_INTEREST_KEY_LENGTH, MAX_NETCODE_INTEREST_KEYS, MAX_SNAPSHOT_CHUNKS, peerHex, } from './wire.js';
|
|
22
|
+
export type { Frame, Now, WireInput, } from './wire.js';
|
package/dist/netcode/index.js
CHANGED
|
@@ -12,3 +12,7 @@ export { hostNetcode } from './host.js';
|
|
|
12
12
|
export { joinNetcode } from './client.js';
|
|
13
13
|
export { ClockEstimator, CLOCK_WINDOW } from './clock.js';
|
|
14
14
|
export { SnapshotBuffer, lerpNumbers, SNAPSHOT_BUFFER } from './interpolate.js';
|
|
15
|
+
// The netcode wire format and its helpers, exported so callers can
|
|
16
|
+
// build their own transports and tooling on the same frames.
|
|
17
|
+
export { snapshotFrames, } from './host.js';
|
|
18
|
+
export { chunkOf, decodeFrame, defaultNow, encodeFrame, eventPeer, MAX_NETCODE_INTEREST_KEY_LENGTH, MAX_NETCODE_INTEREST_KEYS, MAX_SNAPSHOT_CHUNKS, peerHex, } from './wire.js';
|
package/dist/node.d.ts
CHANGED
|
@@ -294,7 +294,7 @@ export declare class BrowserNode {
|
|
|
294
294
|
* handle.
|
|
295
295
|
*/
|
|
296
296
|
private readonly streams;
|
|
297
|
-
/** One attempt per peer at a time: see {@link PeerAttempts}. */
|
|
297
|
+
/** One attempt per peer at a time, and one offerer per pair: see {@link PeerAttempts}. */
|
|
298
298
|
private readonly attempts;
|
|
299
299
|
/**
|
|
300
300
|
* The live org handles (plan §4.5), so {@link BrowserNode.close}
|
|
@@ -598,6 +598,13 @@ export declare class BrowserNode {
|
|
|
598
598
|
* attempt), offering only after an inconclusive one. See
|
|
599
599
|
* {@link PeerAttempts}.
|
|
600
600
|
*
|
|
601
|
+
* **And between two nodes, the lower id offers.** When this node's id
|
|
602
|
+
* is the higher of the pair, an offer the peer sent in the last
|
|
603
|
+
* `PEER_OFFER_FRESH_MS` (10 s) — or one that crosses this call's own —
|
|
604
|
+
* is answered instead of offered over, so two pages reaching each
|
|
605
|
+
* other at once (glare) converge rather than cancel. See
|
|
606
|
+
* {@link PeerAttempts}.
|
|
607
|
+
*
|
|
601
608
|
* Returns a {@link PeerConnectOutcome}. It **rejects** only for
|
|
602
609
|
* something that is not a disposition of the attempt — a closed
|
|
603
610
|
* node, a peer that answered `Reject`, a malformed peer id.
|
|
@@ -814,6 +821,11 @@ export declare function buildConnectRequest(options: ConnectOptions): LeafWasmCo
|
|
|
814
821
|
* are `u64` on the Rust side and a JS number would round the large
|
|
815
822
|
* ones. Exported for the session surface, whose `counters_json` is a
|
|
816
823
|
* promise but whose payload is identical.
|
|
824
|
+
*
|
|
825
|
+
* A nested group is flattened into dotted keys: the leaf's per-reason
|
|
826
|
+
* `drops` object becomes `drops.<reason>`, one counter each. Stringified
|
|
827
|
+
* whole, it read `"[object Object]"` — every drop reason lost behind
|
|
828
|
+
* the doc comment's promise of "each drop reason".
|
|
817
829
|
*/
|
|
818
830
|
export declare function parseCounters(json: string): Record<string, string>;
|
|
819
831
|
/**
|
package/dist/node.js
CHANGED
|
@@ -11,7 +11,7 @@ import { EventHub, parseEvent, parseJsonPreservingU64, toBase64, } from './event
|
|
|
11
11
|
import { fromWasmError, IdentityError, OrgCancelledError, RtcError } from './errors.js';
|
|
12
12
|
import { clientStreamOrgTrampoline, duplexOrgTrampoline, OrgDuplex, OrgHandleRegistry, OrgServe, OrgStream, OrgUpload, streamingOrgTrampoline, toWasmOrgCallOptions, toWasmOrgServeOptions, unaryOrgTrampoline, } from './org.js';
|
|
13
13
|
import { LeafStream } from './stream.js';
|
|
14
|
-
import { acceptPeer as driveAccept, connectPeer as driveConnect, handshakePeer as driveHandshake, PeerAttempts, } from './peer-driver.js';
|
|
14
|
+
import { acceptPeer as driveAccept, answerWaitingOffer, connectPeer as driveConnect, handshakePeer as driveHandshake, noteOffer, PeerAttempts, } from './peer-driver.js';
|
|
15
15
|
import { classifyRtcFailureAll, probeBootstrapReachable, probeStunBindings, } from './udp-probe.js';
|
|
16
16
|
import { idArg, loadLeafWasm, } from './wasm.js';
|
|
17
17
|
/**
|
|
@@ -65,8 +65,8 @@ export class BrowserNode {
|
|
|
65
65
|
* handle.
|
|
66
66
|
*/
|
|
67
67
|
streams = new Set();
|
|
68
|
-
/** One attempt per peer at a time: see {@link PeerAttempts}. */
|
|
69
|
-
attempts = new PeerAttempts();
|
|
68
|
+
/** One attempt per peer at a time, and one offerer per pair: see {@link PeerAttempts}. */
|
|
69
|
+
attempts = new PeerAttempts(() => this.nodeId);
|
|
70
70
|
/**
|
|
71
71
|
* The live org handles (plan §4.5), so {@link BrowserNode.close}
|
|
72
72
|
* can retire ownership: pending calls fail typed and each dropped
|
|
@@ -514,6 +514,13 @@ export class BrowserNode {
|
|
|
514
514
|
* attempt), offering only after an inconclusive one. See
|
|
515
515
|
* {@link PeerAttempts}.
|
|
516
516
|
*
|
|
517
|
+
* **And between two nodes, the lower id offers.** When this node's id
|
|
518
|
+
* is the higher of the pair, an offer the peer sent in the last
|
|
519
|
+
* `PEER_OFFER_FRESH_MS` (10 s) — or one that crosses this call's own —
|
|
520
|
+
* is answered instead of offered over, so two pages reaching each
|
|
521
|
+
* other at once (glare) converge rather than cancel. See
|
|
522
|
+
* {@link PeerAttempts}.
|
|
523
|
+
*
|
|
517
524
|
* Returns a {@link PeerConnectOutcome}. It **rejects** only for
|
|
518
525
|
* something that is not a disposition of the attempt — a closed
|
|
519
526
|
* node, a peer that answered `Reject`, a malformed peer id.
|
|
@@ -536,7 +543,7 @@ export class BrowserNode {
|
|
|
536
543
|
catch (error) {
|
|
537
544
|
throw fromWasmError(error);
|
|
538
545
|
}
|
|
539
|
-
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
546
|
+
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus), () => answerWaitingOffer(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
540
547
|
}
|
|
541
548
|
/**
|
|
542
549
|
* The four primitives the shared drive loop calls, bound to this
|
|
@@ -828,6 +835,7 @@ export class BrowserNode {
|
|
|
828
835
|
if (event.rtcAddrs.length > 0)
|
|
829
836
|
this.anchorRtcAddrs = event.rtcAddrs;
|
|
830
837
|
}
|
|
838
|
+
noteOffer(this.attempts, event);
|
|
831
839
|
this.hub.dispatch(event);
|
|
832
840
|
}
|
|
833
841
|
}
|
|
@@ -880,15 +888,28 @@ export function buildConnectRequest(options) {
|
|
|
880
888
|
* are `u64` on the Rust side and a JS number would round the large
|
|
881
889
|
* ones. Exported for the session surface, whose `counters_json` is a
|
|
882
890
|
* promise but whose payload is identical.
|
|
891
|
+
*
|
|
892
|
+
* A nested group is flattened into dotted keys: the leaf's per-reason
|
|
893
|
+
* `drops` object becomes `drops.<reason>`, one counter each. Stringified
|
|
894
|
+
* whole, it read `"[object Object]"` — every drop reason lost behind
|
|
895
|
+
* the doc comment's promise of "each drop reason".
|
|
883
896
|
*/
|
|
884
897
|
export function parseCounters(json) {
|
|
885
898
|
const parsed = JSON.parse(json);
|
|
886
899
|
if (parsed === null || typeof parsed !== 'object')
|
|
887
900
|
return {};
|
|
888
901
|
const out = {};
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
902
|
+
const flatten = (group, prefix) => {
|
|
903
|
+
for (const [key, value] of Object.entries(group)) {
|
|
904
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
905
|
+
flatten(value, `${prefix}${key}.`);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
out[`${prefix}${key}`] = typeof value === 'string' ? value : String(value);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
flatten(parsed, '');
|
|
892
913
|
return out;
|
|
893
914
|
}
|
|
894
915
|
/**
|
package/dist/peer-driver.d.ts
CHANGED
|
@@ -77,6 +77,16 @@ export declare const PEER_TICK_MS = 50;
|
|
|
77
77
|
* Two anchor hops and one pump, so it is generous rather than tight.
|
|
78
78
|
*/
|
|
79
79
|
export declare const PEER_OFFER_WAIT_MS = 5000;
|
|
80
|
+
/**
|
|
81
|
+
* How long an offer that arrived stays worth answering in place of
|
|
82
|
+
* offering back: the offerer's own ICE deadline (`wasm.rs`'s
|
|
83
|
+
* `PEER_ICE_DEADLINE_MS`). An older offer's attempt has ended at its
|
|
84
|
+
* sender, so answering it would only retire this side's own offer for
|
|
85
|
+
* nothing — and the leaf holds an unanswered offer until something
|
|
86
|
+
* takes it, so without a bound a stale one would hijack every later
|
|
87
|
+
* `connectPeer`.
|
|
88
|
+
*/
|
|
89
|
+
export declare const PEER_OFFER_FRESH_MS = 10000;
|
|
80
90
|
/**
|
|
81
91
|
* The pair's live dialog when it is already direct and open, `undefined`
|
|
82
92
|
* (or `null`) otherwise: the healthy-pair reading a surface's
|
|
@@ -108,9 +118,44 @@ export type HealthyDialog = () => Promise<string | null | undefined> | string |
|
|
|
108
118
|
*
|
|
109
119
|
* Settled attempts are forgotten: the next call starts fresh. Another
|
|
110
120
|
* tab's node is outside this gate, as it is outside this surface.
|
|
121
|
+
*
|
|
122
|
+
* **Between two nodes, the lower id offers (glare).** The gate above
|
|
123
|
+
* cannot stop the *peer's* offer cancelling this node's: when both
|
|
124
|
+
* ends call `connectPeer` at once — two pages reaching each other, or
|
|
125
|
+
* both re-trying after an attempt ended — each answer retires the
|
|
126
|
+
* answerer's own offer, both run out their ICE deadline, and the pair
|
|
127
|
+
* stays relayed. So when this node's id is the higher of the pair:
|
|
128
|
+
*
|
|
129
|
+
* - its `connectPeer` answers an offer from the peer that arrived
|
|
130
|
+
* within {@link PEER_OFFER_FRESH_MS}, instead of offering back;
|
|
131
|
+
* - while its own offer is under way, an offer arriving from the peer
|
|
132
|
+
* is answered, and the answer's outcome is the call's.
|
|
133
|
+
*
|
|
134
|
+
* And on either side, an `acceptPeer` while this surface's own
|
|
135
|
+
* `connectPeer` for the peer is under way takes that call's outcome:
|
|
136
|
+
* on the lower id the crossing offer is the one to ignore, and on the
|
|
137
|
+
* higher id the `connectPeer` already answers it.
|
|
138
|
+
*
|
|
139
|
+
* Offers are learned from the surface's `signal` events
|
|
140
|
+
* ({@link noteOffer}); with none noted, or this node's id unknown,
|
|
141
|
+
* `connectPeer` offers exactly as before.
|
|
111
142
|
*/
|
|
112
143
|
export declare class PeerAttempts {
|
|
113
144
|
#private;
|
|
145
|
+
private readonly self;
|
|
146
|
+
private readonly now;
|
|
147
|
+
/**
|
|
148
|
+
* @param self This node's id, 16 hex, or `null` while unknown (no
|
|
149
|
+
* tie-break then).
|
|
150
|
+
* @param now The clock offer arrivals are stamped with.
|
|
151
|
+
*/
|
|
152
|
+
constructor(self?: () => string | null, now?: () => number);
|
|
153
|
+
/**
|
|
154
|
+
* A verified offer from `peer` (16 hex) arrived. Arrivals past
|
|
155
|
+
* {@link PEER_OFFER_FRESH_MS} are dropped here, so the map holds only
|
|
156
|
+
* peers heard from lately rather than every sender for the node's life.
|
|
157
|
+
*/
|
|
158
|
+
noteOffer(peer: string): void;
|
|
114
159
|
/**
|
|
115
160
|
* Run `drive` as this peer's offer, unless one is already under way,
|
|
116
161
|
* the pair is already direct, or an answer under way settles it.
|
|
@@ -120,11 +165,34 @@ export declare class PeerAttempts {
|
|
|
120
165
|
* a round trip, and an `acceptPeer` could take the pair direct and
|
|
121
166
|
* leave the gate between a caller's early reading and its offer, which
|
|
122
167
|
* would then replace the healthy link.
|
|
168
|
+
*
|
|
169
|
+
* `answer` answers the offer the peer filed, if one is waiting
|
|
170
|
+
* (`null` when none is): how the higher id of a pair yields to the
|
|
171
|
+
* lower id's offer rather than crossing it.
|
|
172
|
+
*/
|
|
173
|
+
connect(peer: string, healthy: HealthyDialog, drive: () => Promise<PeerConnectOutcome>, answer?: () => Promise<PeerConnectOutcome | null>): Promise<PeerConnectOutcome>;
|
|
174
|
+
/**
|
|
175
|
+
* Run `drive` as this peer's answer, unless one is already under way,
|
|
176
|
+
* or this surface's own `connectPeer` for the peer is (see the class
|
|
177
|
+
* comment).
|
|
123
178
|
*/
|
|
124
|
-
connect(peer: string, healthy: HealthyDialog, drive: () => Promise<PeerConnectOutcome>): Promise<PeerConnectOutcome>;
|
|
125
|
-
/** Run `drive` as this peer's answer, unless one is already under way. */
|
|
126
179
|
accept(peer: string, drive: () => Promise<PeerConnectOutcome>): Promise<PeerConnectOutcome>;
|
|
127
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The `kind` a `signal` event carries for an offer: `SignalKind::tag`
|
|
183
|
+
* in `control_plane.rs`, which the leaf writes as a bare number.
|
|
184
|
+
*/
|
|
185
|
+
export declare const SIGNAL_KIND_OFFER = "1";
|
|
186
|
+
/**
|
|
187
|
+
* Tell `attempts` about an offer, when `event` is a verified one (a
|
|
188
|
+
* `signal` event of kind {@link SIGNAL_KIND_OFFER}). Every other event
|
|
189
|
+
* is ignored, as is a sender id that is not an exact decimal u64.
|
|
190
|
+
*/
|
|
191
|
+
export declare function noteOffer(attempts: PeerAttempts, event: {
|
|
192
|
+
readonly type: string;
|
|
193
|
+
readonly kind?: string;
|
|
194
|
+
readonly from?: string;
|
|
195
|
+
}): void;
|
|
128
196
|
/** Offer, drive to an open channel, then handshake (§9 steps 3-4). */
|
|
129
197
|
export declare function connectPeer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;
|
|
130
198
|
/**
|
|
@@ -137,6 +205,13 @@ export declare function connectPeer(peer: string, primitives: PeerPrimitives, pa
|
|
|
137
205
|
* thrown at once, because retrying those would only hide them.
|
|
138
206
|
*/
|
|
139
207
|
export declare function acceptPeer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;
|
|
208
|
+
/**
|
|
209
|
+
* Answer the offer `peer` filed if one is waiting now, without waiting
|
|
210
|
+
* for one: `null` when none is, or when the leaf refuses the answer —
|
|
211
|
+
* the caller ({@link PeerAttempts.connect}, yielding to the lower id)
|
|
212
|
+
* then offers as it would have.
|
|
213
|
+
*/
|
|
214
|
+
export declare function answerWaitingOffer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome | null>;
|
|
140
215
|
/**
|
|
141
216
|
* Run the offerer's handshake on an attempt whose channel is open.
|
|
142
217
|
*
|