@net-mesh/browser 0.38.2 → 0.39.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 +35 -35
- package/dist/net_leaf.js +112 -112
- 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
|
@@ -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;
|