@net-mesh/browser 0.38.2 → 0.40.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 +40 -0
- package/dist/index.bundle.js +2 -2
- package/dist/leader/session.d.ts +10 -0
- package/dist/leader/session.js +22 -14
- package/dist/local.js +6 -1
- package/dist/net_leaf.d.ts +10 -10
- package/dist/net_leaf.js +33 -33
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +10 -10
- package/dist/node.d.ts +11 -0
- package/dist/node.js +26 -9
- package/dist/peer-driver.d.ts +48 -0
- package/dist/peer-driver.js +104 -0
- package/package.json +1 -1
package/dist/leader/session.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ 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}. */
|
|
94
|
+
private readonly attempts;
|
|
93
95
|
/**
|
|
94
96
|
* The generation the streams in that set were opened under.
|
|
95
97
|
*
|
|
@@ -218,6 +220,14 @@ export declare class MeshSession {
|
|
|
218
220
|
* nothing is offered — a second offer would replace the working link
|
|
219
221
|
* and close it under the peer. A leader too old to answer that reading
|
|
220
222
|
* is asked for an offer, as before.
|
|
223
|
+
*
|
|
224
|
+
* **And on a pair still connecting**, as `BrowserNode.connectPeer` is:
|
|
225
|
+
* a call while this session's own `connectPeer` or `acceptPeer` for the
|
|
226
|
+
* peer is under way is answered by it rather than by a second offer.
|
|
227
|
+
* The healthy-pair reading runs inside that gate, just before any
|
|
228
|
+
* offer: it is a proxy round trip here, and read ahead of the gate it
|
|
229
|
+
* could be stale by the time the offer went out. See
|
|
230
|
+
* {@link PeerAttempts}.
|
|
221
231
|
*/
|
|
222
232
|
connectPeer(nodeIdHex: string): Promise<PeerConnectOutcome>;
|
|
223
233
|
/**
|
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, } from '../peer-driver.js';
|
|
32
|
+
import { acceptPeer as driveAccept, connectPeer as driveConnect, 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,6 +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
65
|
/**
|
|
64
66
|
* The generation the streams in that set were opened under.
|
|
65
67
|
*
|
|
@@ -255,27 +257,33 @@ export class MeshSession {
|
|
|
255
257
|
* nothing is offered — a second offer would replace the working link
|
|
256
258
|
* and close it under the peer. A leader too old to answer that reading
|
|
257
259
|
* is asked for an offer, as before.
|
|
260
|
+
*
|
|
261
|
+
* **And on a pair still connecting**, as `BrowserNode.connectPeer` is:
|
|
262
|
+
* a call while this session's own `connectPeer` or `acceptPeer` for the
|
|
263
|
+
* peer is under way is answered by it rather than by a second offer.
|
|
264
|
+
* The healthy-pair reading runs inside that gate, just before any
|
|
265
|
+
* offer: it is a proxy round trip here, and read ahead of the gate it
|
|
266
|
+
* could be stale by the time the offer went out. See
|
|
267
|
+
* {@link PeerAttempts}.
|
|
258
268
|
*/
|
|
259
269
|
async connectPeer(nodeIdHex) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return { type: 'direct', peer: nodeIdHex, dialog: settled };
|
|
271
|
-
return driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus);
|
|
270
|
+
return this.attempts.connect(nodeIdHex, async () => {
|
|
271
|
+
try {
|
|
272
|
+
return (await this.inner.peer_direct_dialog(nodeIdHex)) ?? undefined;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// A closed session is refused by the offer below, typed; an
|
|
276
|
+
// older leader simply has no such reading.
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
272
280
|
}
|
|
273
281
|
/**
|
|
274
282
|
* Answer the offer `nodeIdHex` filed — {@link connectPeer}'s
|
|
275
283
|
* counterpart, on whichever tab holds the lock.
|
|
276
284
|
*/
|
|
277
285
|
async acceptPeer(nodeIdHex) {
|
|
278
|
-
return driveAccept(nodeIdHex, this.peerPrimitives(), parseAttemptStatus);
|
|
286
|
+
return this.attempts.accept(nodeIdHex, () => driveAccept(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
279
287
|
}
|
|
280
288
|
/**
|
|
281
289
|
* The proxied primitives, each re-typed by the same mapper every
|
package/dist/local.js
CHANGED
|
@@ -61,11 +61,16 @@ export function createLocalMesh(options = {}) {
|
|
|
61
61
|
const targets = listeners.get(to);
|
|
62
62
|
if (targets === undefined)
|
|
63
63
|
throw new Error(`no local node ${to} on this mesh`);
|
|
64
|
+
// Exact decimal, as the real node's `stream_data` carries it. Hex
|
|
65
|
+
// here was wrong in a way that hid itself: a hex id with no letters
|
|
66
|
+
// in it (one random id in ~1,800) reads as a DIFFERENT node's
|
|
67
|
+
// decimal, so the store answered a joiner at an id not on the mesh.
|
|
68
|
+
const peerNode = BigInt(`0x${from}`).toString(10);
|
|
64
69
|
for (const handler of [...targets]) {
|
|
65
70
|
// A copy per receiver, as a network would hand each its own
|
|
66
71
|
// bytes: a receiver that mutates its payload must not change
|
|
67
72
|
// what another receiver — or the sender — holds.
|
|
68
|
-
handler({ type: 'stream_data', streamId, peerNode
|
|
73
|
+
handler({ type: 'stream_data', streamId, peerNode, payload: payload.slice() });
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
function node(requested) {
|
package/dist/net_leaf.d.ts
CHANGED
|
@@ -1453,16 +1453,16 @@ 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
|
|
1458
|
-
readonly
|
|
1459
|
-
readonly
|
|
1460
|
-
readonly
|
|
1461
|
-
readonly
|
|
1462
|
-
readonly
|
|
1463
|
-
readonly
|
|
1464
|
-
readonly
|
|
1465
|
-
readonly
|
|
1456
|
+
readonly __wasm_bindgen_func_elem_6509: (a: number, b: number, c: number, d: number) => void;
|
|
1457
|
+
readonly __wasm_bindgen_func_elem_6511: (a: number, b: number, c: number, d: number) => void;
|
|
1458
|
+
readonly __wasm_bindgen_func_elem_2365: (a: number, b: number, c: number) => number;
|
|
1459
|
+
readonly __wasm_bindgen_func_elem_2366: (a: number, b: number, c: number) => void;
|
|
1460
|
+
readonly __wasm_bindgen_func_elem_2366_112: (a: number, b: number, c: number) => void;
|
|
1461
|
+
readonly __wasm_bindgen_func_elem_2366_113: (a: number, b: number, c: number) => void;
|
|
1462
|
+
readonly __wasm_bindgen_func_elem_2366_114: (a: number, b: number, c: number) => void;
|
|
1463
|
+
readonly __wasm_bindgen_func_elem_2366_115: (a: number, b: number, c: number) => void;
|
|
1464
|
+
readonly __wasm_bindgen_func_elem_2366_116: (a: number, b: number, c: number) => void;
|
|
1465
|
+
readonly __wasm_bindgen_func_elem_2366_117: (a: number, b: number, c: number) => void;
|
|
1466
1466
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
1467
1467
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
1468
1468
|
readonly __wbindgen_export3: (a: number) => void;
|
package/dist/net_leaf.js
CHANGED
|
@@ -2722,7 +2722,7 @@ function __wbg_get_imports() {
|
|
|
2722
2722
|
const ret = getObject(arg0).error;
|
|
2723
2723
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
2724
2724
|
}, arguments); },
|
|
2725
|
-
|
|
2725
|
+
__wbg_error_c7a46bea9bd49ca0: function(arg0, arg1) {
|
|
2726
2726
|
console.error(getStringFromWasm0(arg0, arg1));
|
|
2727
2727
|
},
|
|
2728
2728
|
__wbg_fetch_4178962c570137ca: function(arg0, arg1, arg2) {
|
|
@@ -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_6511(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_6511(a, state0.b, arg0, arg1);
|
|
2960
2960
|
} finally {
|
|
2961
2961
|
state0.a = a;
|
|
2962
2962
|
}
|
|
@@ -3266,47 +3266,47 @@ function __wbg_get_imports() {
|
|
|
3266
3266
|
},
|
|
3267
3267
|
__wbindgen_generic_0000000000000001: function(arg0, arg1) {
|
|
3268
3268
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 34, ret: Externref, inner_ret: Some(Externref) }, mutable: true }) -> Externref`.
|
|
3269
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3269
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2365);
|
|
3270
3270
|
return addHeapObject(ret);
|
|
3271
3271
|
},
|
|
3272
3272
|
__wbindgen_generic_0000000000000002: function(arg0, arg1) {
|
|
3273
3273
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3274
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3274
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366);
|
|
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: 868, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3279
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_6509);
|
|
3280
3280
|
return addHeapObject(ret);
|
|
3281
3281
|
},
|
|
3282
3282
|
__wbindgen_generic_0000000000000004: function(arg0, arg1) {
|
|
3283
3283
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("CloseEvent")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3284
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3284
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_112);
|
|
3285
3285
|
return addHeapObject(ret);
|
|
3286
3286
|
},
|
|
3287
3287
|
__wbindgen_generic_0000000000000005: function(arg0, arg1) {
|
|
3288
3288
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3289
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3289
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_113);
|
|
3290
3290
|
return addHeapObject(ret);
|
|
3291
3291
|
},
|
|
3292
3292
|
__wbindgen_generic_0000000000000006: function(arg0, arg1) {
|
|
3293
3293
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3294
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3294
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_114);
|
|
3295
3295
|
return addHeapObject(ret);
|
|
3296
3296
|
},
|
|
3297
3297
|
__wbindgen_generic_0000000000000007: function(arg0, arg1) {
|
|
3298
3298
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("RTCDataChannelEvent")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3299
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3299
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_115);
|
|
3300
3300
|
return addHeapObject(ret);
|
|
3301
3301
|
},
|
|
3302
3302
|
__wbindgen_generic_0000000000000008: function(arg0, arg1) {
|
|
3303
3303
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("RTCPeerConnectionIceErrorEvent")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3304
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3304
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_116);
|
|
3305
3305
|
return addHeapObject(ret);
|
|
3306
3306
|
},
|
|
3307
3307
|
__wbindgen_generic_0000000000000009: function(arg0, arg1) {
|
|
3308
3308
|
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("RTCPeerConnectionIceEvent")], shim_idx: 35, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3309
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3309
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_2366_117);
|
|
3310
3310
|
return addHeapObject(ret);
|
|
3311
3311
|
},
|
|
3312
3312
|
__wbindgen_generic_000000000000000a: function(arg0) {
|
|
@@ -3333,47 +3333,47 @@ function __wbg_get_imports() {
|
|
|
3333
3333
|
};
|
|
3334
3334
|
}
|
|
3335
3335
|
|
|
3336
|
-
function
|
|
3337
|
-
wasm.
|
|
3336
|
+
function __wasm_bindgen_func_elem_2366(arg0, arg1, arg2) {
|
|
3337
|
+
wasm.__wasm_bindgen_func_elem_2366(arg0, arg1, addHeapObject(arg2));
|
|
3338
3338
|
}
|
|
3339
3339
|
|
|
3340
|
-
function
|
|
3341
|
-
wasm.
|
|
3340
|
+
function __wasm_bindgen_func_elem_2366_112(arg0, arg1, arg2) {
|
|
3341
|
+
wasm.__wasm_bindgen_func_elem_2366_112(arg0, arg1, addHeapObject(arg2));
|
|
3342
3342
|
}
|
|
3343
3343
|
|
|
3344
|
-
function
|
|
3345
|
-
wasm.
|
|
3344
|
+
function __wasm_bindgen_func_elem_2366_113(arg0, arg1, arg2) {
|
|
3345
|
+
wasm.__wasm_bindgen_func_elem_2366_113(arg0, arg1, addHeapObject(arg2));
|
|
3346
3346
|
}
|
|
3347
3347
|
|
|
3348
|
-
function
|
|
3349
|
-
wasm.
|
|
3348
|
+
function __wasm_bindgen_func_elem_2366_114(arg0, arg1, arg2) {
|
|
3349
|
+
wasm.__wasm_bindgen_func_elem_2366_114(arg0, arg1, addHeapObject(arg2));
|
|
3350
3350
|
}
|
|
3351
3351
|
|
|
3352
|
-
function
|
|
3353
|
-
wasm.
|
|
3352
|
+
function __wasm_bindgen_func_elem_2366_115(arg0, arg1, arg2) {
|
|
3353
|
+
wasm.__wasm_bindgen_func_elem_2366_115(arg0, arg1, addHeapObject(arg2));
|
|
3354
3354
|
}
|
|
3355
3355
|
|
|
3356
|
-
function
|
|
3357
|
-
wasm.
|
|
3356
|
+
function __wasm_bindgen_func_elem_2366_116(arg0, arg1, arg2) {
|
|
3357
|
+
wasm.__wasm_bindgen_func_elem_2366_116(arg0, arg1, addHeapObject(arg2));
|
|
3358
3358
|
}
|
|
3359
3359
|
|
|
3360
|
-
function
|
|
3361
|
-
wasm.
|
|
3360
|
+
function __wasm_bindgen_func_elem_2366_117(arg0, arg1, arg2) {
|
|
3361
|
+
wasm.__wasm_bindgen_func_elem_2366_117(arg0, arg1, addHeapObject(arg2));
|
|
3362
3362
|
}
|
|
3363
3363
|
|
|
3364
|
-
function
|
|
3365
|
-
const ret = wasm.
|
|
3364
|
+
function __wasm_bindgen_func_elem_2365(arg0, arg1, arg2) {
|
|
3365
|
+
const ret = wasm.__wasm_bindgen_func_elem_2365(arg0, arg1, addHeapObject(arg2));
|
|
3366
3366
|
return takeObject(ret);
|
|
3367
3367
|
}
|
|
3368
3368
|
|
|
3369
|
-
function
|
|
3370
|
-
wasm.
|
|
3369
|
+
function __wasm_bindgen_func_elem_6511(arg0, arg1, arg2, arg3) {
|
|
3370
|
+
wasm.__wasm_bindgen_func_elem_6511(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
3371
3371
|
}
|
|
3372
3372
|
|
|
3373
|
-
function
|
|
3373
|
+
function __wasm_bindgen_func_elem_6509(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_6509(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,16 +119,16 @@ 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
|
|
124
|
-
export const
|
|
125
|
-
export const
|
|
126
|
-
export const
|
|
127
|
-
export const
|
|
128
|
-
export const
|
|
129
|
-
export const
|
|
130
|
-
export const
|
|
131
|
-
export const
|
|
122
|
+
export const __wasm_bindgen_func_elem_6509: (a: number, b: number, c: number, d: number) => void;
|
|
123
|
+
export const __wasm_bindgen_func_elem_6511: (a: number, b: number, c: number, d: number) => void;
|
|
124
|
+
export const __wasm_bindgen_func_elem_2365: (a: number, b: number, c: number) => number;
|
|
125
|
+
export const __wasm_bindgen_func_elem_2366: (a: number, b: number, c: number) => void;
|
|
126
|
+
export const __wasm_bindgen_func_elem_2366_112: (a: number, b: number, c: number) => void;
|
|
127
|
+
export const __wasm_bindgen_func_elem_2366_113: (a: number, b: number, c: number) => void;
|
|
128
|
+
export const __wasm_bindgen_func_elem_2366_114: (a: number, b: number, c: number) => void;
|
|
129
|
+
export const __wasm_bindgen_func_elem_2366_115: (a: number, b: number, c: number) => void;
|
|
130
|
+
export const __wasm_bindgen_func_elem_2366_116: (a: number, b: number, c: number) => void;
|
|
131
|
+
export const __wasm_bindgen_func_elem_2366_117: (a: number, b: number, c: number) => void;
|
|
132
132
|
export const __wbindgen_export: (a: number, b: number) => number;
|
|
133
133
|
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
134
134
|
export const __wbindgen_export3: (a: number) => void;
|
package/dist/node.d.ts
CHANGED
|
@@ -294,6 +294,8 @@ 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}. */
|
|
298
|
+
private readonly attempts;
|
|
297
299
|
/**
|
|
298
300
|
* The live org handles (plan §4.5), so {@link BrowserNode.close}
|
|
299
301
|
* can retire ownership: pending calls fail typed and each dropped
|
|
@@ -587,6 +589,15 @@ export declare class BrowserNode {
|
|
|
587
589
|
* and close it under the other side. So a library may call it "to be
|
|
588
590
|
* sure" before opening a stream.
|
|
589
591
|
*
|
|
592
|
+
* **And on a pair that is still connecting.** A call while another
|
|
593
|
+
* `connectPeer` for the same peer is under way resolves with that
|
|
594
|
+
* attempt's outcome instead of offering again (a new offer would cancel
|
|
595
|
+
* it), and a call while an {@link acceptPeer} for the peer is under way
|
|
596
|
+
* waits for it and takes its outcome when that settles the pair
|
|
597
|
+
* (`direct`, `iceTimeout`, `udpBlocked`, or `superseded` by a newer
|
|
598
|
+
* attempt), offering only after an inconclusive one. See
|
|
599
|
+
* {@link PeerAttempts}.
|
|
600
|
+
*
|
|
590
601
|
* Returns a {@link PeerConnectOutcome}. It **rejects** only for
|
|
591
602
|
* something that is not a disposition of the attempt — a closed
|
|
592
603
|
* node, a peer that answered `Reject`, a malformed peer id.
|
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, } from './peer-driver.js';
|
|
14
|
+
import { acceptPeer as driveAccept, connectPeer as driveConnect, handshakePeer as driveHandshake, 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,6 +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
70
|
/**
|
|
69
71
|
* The live org handles (plan §4.5), so {@link BrowserNode.close}
|
|
70
72
|
* can retire ownership: pending calls fail typed and each dropped
|
|
@@ -503,23 +505,38 @@ export class BrowserNode {
|
|
|
503
505
|
* and close it under the other side. So a library may call it "to be
|
|
504
506
|
* sure" before opening a stream.
|
|
505
507
|
*
|
|
508
|
+
* **And on a pair that is still connecting.** A call while another
|
|
509
|
+
* `connectPeer` for the same peer is under way resolves with that
|
|
510
|
+
* attempt's outcome instead of offering again (a new offer would cancel
|
|
511
|
+
* it), and a call while an {@link acceptPeer} for the peer is under way
|
|
512
|
+
* waits for it and takes its outcome when that settles the pair
|
|
513
|
+
* (`direct`, `iceTimeout`, `udpBlocked`, or `superseded` by a newer
|
|
514
|
+
* attempt), offering only after an inconclusive one. See
|
|
515
|
+
* {@link PeerAttempts}.
|
|
516
|
+
*
|
|
506
517
|
* Returns a {@link PeerConnectOutcome}. It **rejects** only for
|
|
507
518
|
* something that is not a disposition of the attempt — a closed
|
|
508
519
|
* node, a peer that answered `Reject`, a malformed peer id.
|
|
509
520
|
*/
|
|
510
521
|
async connectPeer(nodeIdHex) {
|
|
511
|
-
|
|
512
|
-
// replace the working link and close it under the other side.
|
|
513
|
-
let settled;
|
|
522
|
+
let peer;
|
|
514
523
|
try {
|
|
515
|
-
|
|
524
|
+
peer = idArg(nodeIdHex, 'peer');
|
|
516
525
|
}
|
|
517
526
|
catch (error) {
|
|
518
527
|
throw fromWasmError(error);
|
|
519
528
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
529
|
+
return this.attempts.connect(nodeIdHex,
|
|
530
|
+
// Already direct and open: the pair it has. A new offer would
|
|
531
|
+
// replace the working link and close it under the other side.
|
|
532
|
+
() => {
|
|
533
|
+
try {
|
|
534
|
+
return this.inner.peer_direct_dialog(peer);
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
throw fromWasmError(error);
|
|
538
|
+
}
|
|
539
|
+
}, () => driveConnect(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
523
540
|
}
|
|
524
541
|
/**
|
|
525
542
|
* The four primitives the shared drive loop calls, bound to this
|
|
@@ -577,7 +594,7 @@ export class BrowserNode {
|
|
|
577
594
|
* {@link PeerConnectOutcome}, same meanings.
|
|
578
595
|
*/
|
|
579
596
|
async acceptPeer(nodeIdHex) {
|
|
580
|
-
return driveAccept(nodeIdHex, this.peerPrimitives(), parseAttemptStatus);
|
|
597
|
+
return this.attempts.accept(nodeIdHex, () => driveAccept(nodeIdHex, this.peerPrimitives(), parseAttemptStatus));
|
|
581
598
|
}
|
|
582
599
|
/**
|
|
583
600
|
* Read one `peer_candidate` status for a live attempt.
|
package/dist/peer-driver.d.ts
CHANGED
|
@@ -77,6 +77,54 @@ 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
|
+
* The pair's live dialog when it is already direct and open, `undefined`
|
|
82
|
+
* (or `null`) otherwise: the healthy-pair reading a surface's
|
|
83
|
+
* `connectPeer` must not offer over.
|
|
84
|
+
*/
|
|
85
|
+
export type HealthyDialog = () => Promise<string | null | undefined> | string | null | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* One attempt per peer at a time, per surface.
|
|
88
|
+
*
|
|
89
|
+
* **Every offer replaces the pair's link.** The leaf retires whatever
|
|
90
|
+
* attempt is live for the peer and replaces its DataChannel, so a
|
|
91
|
+
* second `connectPeer` while the first is still under way cancels it,
|
|
92
|
+
* and both callers end on the relay or in `iceTimeout`. Nothing about
|
|
93
|
+
* that is exotic: `joinLobby` reaches the host and then the store it
|
|
94
|
+
* joins reaches it again before its first stream, and netcode beside a
|
|
95
|
+
* store does the same. The healthy-direct check in
|
|
96
|
+
* `BrowserNode.connectPeer` covers a pair that has already arrived; this
|
|
97
|
+
* covers one that is still arriving.
|
|
98
|
+
*
|
|
99
|
+
* So, on one surface:
|
|
100
|
+
*
|
|
101
|
+
* - a `connectPeer` while another for the same peer is in flight is
|
|
102
|
+
* answered by that one's outcome, not by a second offer;
|
|
103
|
+
* - a `connectPeer` while an `acceptPeer` for the peer is in flight
|
|
104
|
+
* waits for it (offering over it would cancel the attempt it is
|
|
105
|
+
* answering), and takes its outcome when that settles the pair
|
|
106
|
+
* ({@link answerSettles}): it offers only after an inconclusive one;
|
|
107
|
+
* - concurrent `acceptPeer`s share one answer.
|
|
108
|
+
*
|
|
109
|
+
* Settled attempts are forgotten: the next call starts fresh. Another
|
|
110
|
+
* tab's node is outside this gate, as it is outside this surface.
|
|
111
|
+
*/
|
|
112
|
+
export declare class PeerAttempts {
|
|
113
|
+
#private;
|
|
114
|
+
/**
|
|
115
|
+
* Run `drive` as this peer's offer, unless one is already under way,
|
|
116
|
+
* the pair is already direct, or an answer under way settles it.
|
|
117
|
+
*
|
|
118
|
+
* `healthy` is read HERE, inside the gate and just before the offer,
|
|
119
|
+
* never by the caller ahead of it: on a proxied surface the reading is
|
|
120
|
+
* a round trip, and an `acceptPeer` could take the pair direct and
|
|
121
|
+
* leave the gate between a caller's early reading and its offer, which
|
|
122
|
+
* would then replace the healthy link.
|
|
123
|
+
*/
|
|
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
|
+
accept(peer: string, drive: () => Promise<PeerConnectOutcome>): Promise<PeerConnectOutcome>;
|
|
127
|
+
}
|
|
80
128
|
/** Offer, drive to an open channel, then handshake (§9 steps 3-4). */
|
|
81
129
|
export declare function connectPeer(peer: string, primitives: PeerPrimitives, parse: (json: string) => PeerAttemptStatus): Promise<PeerConnectOutcome>;
|
|
82
130
|
/**
|
package/dist/peer-driver.js
CHANGED
|
@@ -58,6 +58,110 @@ export const PEER_TICK_MS = 50;
|
|
|
58
58
|
* Two anchor hops and one pump, so it is generous rather than tight.
|
|
59
59
|
*/
|
|
60
60
|
export const PEER_OFFER_WAIT_MS = 5_000;
|
|
61
|
+
/**
|
|
62
|
+
* Whether an answer's outcome settles the pair for a `connectPeer` that
|
|
63
|
+
* waited on it, rather than calling for an offer of its own.
|
|
64
|
+
*
|
|
65
|
+
* - `direct`: the pair has its link.
|
|
66
|
+
* - `iceTimeout`, `udpBlocked`: this network could not go direct just
|
|
67
|
+
* now, and the routed session the answer ran over is still in place.
|
|
68
|
+
* An offer straight after would almost surely end the same way, a whole
|
|
69
|
+
* ICE deadline later, so the caller gets the answer's verdict instead.
|
|
70
|
+
* - `superseded` naming a successor: a newer attempt is live, and an
|
|
71
|
+
* offer would cancel it.
|
|
72
|
+
*
|
|
73
|
+
* Anything else (`handshakeFailed`, `noAnnouncement`, a `superseded`
|
|
74
|
+
* with no successor) says nothing about the pair, so the waiter offers.
|
|
75
|
+
*/
|
|
76
|
+
function answerSettles(outcome) {
|
|
77
|
+
switch (outcome.type) {
|
|
78
|
+
case 'direct':
|
|
79
|
+
case 'iceTimeout':
|
|
80
|
+
case 'udpBlocked':
|
|
81
|
+
return true;
|
|
82
|
+
case 'superseded':
|
|
83
|
+
return outcome.liveDialog !== null;
|
|
84
|
+
default:
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* One attempt per peer at a time, per surface.
|
|
90
|
+
*
|
|
91
|
+
* **Every offer replaces the pair's link.** The leaf retires whatever
|
|
92
|
+
* attempt is live for the peer and replaces its DataChannel, so a
|
|
93
|
+
* second `connectPeer` while the first is still under way cancels it,
|
|
94
|
+
* and both callers end on the relay or in `iceTimeout`. Nothing about
|
|
95
|
+
* that is exotic: `joinLobby` reaches the host and then the store it
|
|
96
|
+
* joins reaches it again before its first stream, and netcode beside a
|
|
97
|
+
* store does the same. The healthy-direct check in
|
|
98
|
+
* `BrowserNode.connectPeer` covers a pair that has already arrived; this
|
|
99
|
+
* covers one that is still arriving.
|
|
100
|
+
*
|
|
101
|
+
* So, on one surface:
|
|
102
|
+
*
|
|
103
|
+
* - a `connectPeer` while another for the same peer is in flight is
|
|
104
|
+
* answered by that one's outcome, not by a second offer;
|
|
105
|
+
* - a `connectPeer` while an `acceptPeer` for the peer is in flight
|
|
106
|
+
* waits for it (offering over it would cancel the attempt it is
|
|
107
|
+
* answering), and takes its outcome when that settles the pair
|
|
108
|
+
* ({@link answerSettles}): it offers only after an inconclusive one;
|
|
109
|
+
* - concurrent `acceptPeer`s share one answer.
|
|
110
|
+
*
|
|
111
|
+
* Settled attempts are forgotten: the next call starts fresh. Another
|
|
112
|
+
* tab's node is outside this gate, as it is outside this surface.
|
|
113
|
+
*/
|
|
114
|
+
export class PeerAttempts {
|
|
115
|
+
#connecting = new Map();
|
|
116
|
+
#accepting = new Map();
|
|
117
|
+
/**
|
|
118
|
+
* Run `drive` as this peer's offer, unless one is already under way,
|
|
119
|
+
* the pair is already direct, or an answer under way settles it.
|
|
120
|
+
*
|
|
121
|
+
* `healthy` is read HERE, inside the gate and just before the offer,
|
|
122
|
+
* never by the caller ahead of it: on a proxied surface the reading is
|
|
123
|
+
* a round trip, and an `acceptPeer` could take the pair direct and
|
|
124
|
+
* leave the gate between a caller's early reading and its offer, which
|
|
125
|
+
* would then replace the healthy link.
|
|
126
|
+
*/
|
|
127
|
+
connect(peer, healthy, drive) {
|
|
128
|
+
const key = peer.toLowerCase();
|
|
129
|
+
const joining = this.#connecting.get(key);
|
|
130
|
+
if (joining !== undefined)
|
|
131
|
+
return joining;
|
|
132
|
+
const answering = this.#accepting.get(key);
|
|
133
|
+
const started = (async () => {
|
|
134
|
+
if (answering !== undefined) {
|
|
135
|
+
const answered = await answering.catch(() => null);
|
|
136
|
+
if (answered !== null && answerSettles(answered))
|
|
137
|
+
return answered;
|
|
138
|
+
}
|
|
139
|
+
const dialog = await healthy();
|
|
140
|
+
if (dialog !== undefined && dialog !== null)
|
|
141
|
+
return { type: 'direct', peer, dialog };
|
|
142
|
+
return drive();
|
|
143
|
+
})();
|
|
144
|
+
return PeerAttempts.#track(this.#connecting, key, started);
|
|
145
|
+
}
|
|
146
|
+
/** Run `drive` as this peer's answer, unless one is already under way. */
|
|
147
|
+
accept(peer, drive) {
|
|
148
|
+
const key = peer.toLowerCase();
|
|
149
|
+
const joining = this.#accepting.get(key);
|
|
150
|
+
if (joining !== undefined)
|
|
151
|
+
return joining;
|
|
152
|
+
return PeerAttempts.#track(this.#accepting, key, drive());
|
|
153
|
+
}
|
|
154
|
+
static #track(map, key, started) {
|
|
155
|
+
map.set(key, started);
|
|
156
|
+
// Forget it once settled, and only if no newer one took its place.
|
|
157
|
+
const forget = () => {
|
|
158
|
+
if (map.get(key) === started)
|
|
159
|
+
map.delete(key);
|
|
160
|
+
};
|
|
161
|
+
started.then(forget, forget);
|
|
162
|
+
return started;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
61
165
|
/** Offer, drive to an open channel, then handshake (§9 steps 3-4). */
|
|
62
166
|
export async function connectPeer(peer, primitives, parse) {
|
|
63
167
|
let dialog;
|