@net-mesh/browser 0.37.1 → 0.38.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 +48 -0
- package/dist/errors.d.ts +7 -1
- package/dist/errors.js +15 -7
- package/dist/events.d.ts +6 -0
- package/dist/events.js +13 -0
- package/dist/index.bundle.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/net_leaf.d.ts +10 -9
- package/dist/net_leaf.js +52 -39
- package/dist/net_leaf_bg.wasm +0 -0
- package/dist/net_leaf_bg.wasm.d.ts +10 -9
- package/dist/node.d.ts +12 -1
- package/dist/node.js +23 -7
- package/dist/udp-probe.d.ts +68 -0
- package/dist/udp-probe.js +89 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,54 @@ missing method at the call site rather than at install time. Unlike
|
|
|
14
14
|
`@net-mesh/sdk`, this package never depends on `@net-mesh/core` — see
|
|
15
15
|
the README on why it is a sibling package rather than a sub-path.
|
|
16
16
|
|
|
17
|
+
## 0.38.1 — dual-stack anchors
|
|
18
|
+
|
|
19
|
+
An anchor can now serve IPv4 and IPv6 players at once
|
|
20
|
+
(`net-mesh anchor serve --rtc-bind 0.0.0.0:7101 --rtc-bind '[::]:7101'`).
|
|
21
|
+
What a page sees:
|
|
22
|
+
|
|
23
|
+
- **The anchor needs a STUN endpoint in each family too**
|
|
24
|
+
(`--rtc-stun-bind` of each family). Without a media permission a
|
|
25
|
+
browser's only candidate on a network is the server-reflexive one from
|
|
26
|
+
a STUN server of that family. The default ICE servers now use every
|
|
27
|
+
STUN endpoint the anchor publishes (`stun_addrs`).
|
|
28
|
+
- **Firefox on a pure IPv6-only network (no IPv4 route) is unsupported.**
|
|
29
|
+
It gathers nothing there, and the connect fails `ice-timeout`, not
|
|
30
|
+
`udp-blocked`. On 464XLAT networks, as mobile IPv6-only networks are,
|
|
31
|
+
it connects. Chromium works on both.
|
|
32
|
+
- **A probe the engine failed before it gathered reads `notRun`**, not
|
|
33
|
+
`unanswered`, so it can never produce `udp-blocked`.
|
|
34
|
+
- **Public anchors.** An anchor started with `--open-games` issues a
|
|
35
|
+
credential for any game id from any page, keyed on the page's origin
|
|
36
|
+
and the id, so no operator setup is needed. `requestCredential({
|
|
37
|
+
anchorUrl, game })` is unchanged. Two sites naming their game alike
|
|
38
|
+
get two separate games and never see each other's lobbies.
|
|
39
|
+
- **Connecting needs no change.** The anchor's answer offers a host
|
|
40
|
+
candidate per family, and the browser pairs with whichever its network
|
|
41
|
+
reaches.
|
|
42
|
+
- **`udp-blocked` is stricter and says less.** The leaf now probes
|
|
43
|
+
**every** endpoint the anchor published, under one deadline, and
|
|
44
|
+
reports `udp-blocked` only when the anchor answered over HTTPS and
|
|
45
|
+
**every** probe went unanswered. One answered family, an unrun probe,
|
|
46
|
+
or no endpoints at all stays `ice-timeout`. So an IPv6-only player
|
|
47
|
+
whose IPv4 probe is silent is no longer told UDP is blocked.
|
|
48
|
+
- **The `udp-blocked` message states the observation, not a cause.** It
|
|
49
|
+
now reads `no UDP response from the anchor's advertised endpoints: its
|
|
50
|
+
HTTPS bootstrap succeeded but STUN bindings to … went unanswered`,
|
|
51
|
+
because blocked UDP, a stopped UDP listener, a wrong advertised address
|
|
52
|
+
and loss all look the same. The `kind` is unchanged. Match on `kind`,
|
|
53
|
+
never on the message.
|
|
54
|
+
- **`UdpBlockedEvidence.probedAll`** lists every endpoint probed; `probed`
|
|
55
|
+
is still the first.
|
|
56
|
+
- **`ConnectedEvent.rtcAddrs`** lists every endpoint the anchor published,
|
|
57
|
+
primary first. `rtcAddr` is still the primary.
|
|
58
|
+
- **The Rust and TypeScript probes agree** on what "answered" means: a
|
|
59
|
+
server-reflexive candidate, or a STUN error response (code below 700).
|
|
60
|
+
The wasm probe used to ignore error responses.
|
|
61
|
+
- **New exports:** `classifyRtcFailureAll`, `probeStunBindings`,
|
|
62
|
+
`probeEventAnswers`, `stunProbeAnswered`, `candidateLineIsReflexive`,
|
|
63
|
+
and the `EndpointProbe` and `ProbeEvent` types.
|
|
64
|
+
|
|
17
65
|
## 0.37.0 — the first npm release
|
|
18
66
|
|
|
19
67
|
Everything in this file ships in 0.37.0, the package's first release on npm.
|
package/dist/errors.d.ts
CHANGED
|
@@ -33,13 +33,19 @@ export interface UdpBlockedEvidence {
|
|
|
33
33
|
readonly stunProbeFailed: true;
|
|
34
34
|
/** The address the probe was aimed at, so the claim names its subject. */
|
|
35
35
|
readonly probed: string;
|
|
36
|
+
/**
|
|
37
|
+
* Every endpoint probed, `probed` first; each went unanswered. A
|
|
38
|
+
* dual-stack anchor publishes one per family, and one answered probe
|
|
39
|
+
* means UDP works for that family, which is never this evidence.
|
|
40
|
+
*/
|
|
41
|
+
readonly probedAll: readonly string[];
|
|
36
42
|
}
|
|
37
43
|
/**
|
|
38
44
|
* `UdpBlockedEvidence` only when both observations hold; `null`
|
|
39
45
|
* otherwise, which is the caller's instruction to stay with
|
|
40
46
|
* `ice-timeout`. Mirror of `UdpBlockedEvidence::new`.
|
|
41
47
|
*/
|
|
42
|
-
export declare function udpBlockedEvidence(bootstrapOk: boolean, stunProbeFailed: boolean, probed: string): UdpBlockedEvidence | null;
|
|
48
|
+
export declare function udpBlockedEvidence(bootstrapOk: boolean, stunProbeFailed: boolean, probed: string, probedAll?: readonly string[]): UdpBlockedEvidence | null;
|
|
43
49
|
/** Why an RTC attempt did not produce a DataChannel. */
|
|
44
50
|
export type RtcErrorFailure = {
|
|
45
51
|
readonly type: 'iceTimeout';
|
package/dist/errors.js
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
* otherwise, which is the caller's instruction to stay with
|
|
21
21
|
* `ice-timeout`. Mirror of `UdpBlockedEvidence::new`.
|
|
22
22
|
*/
|
|
23
|
-
export function udpBlockedEvidence(bootstrapOk, stunProbeFailed, probed) {
|
|
23
|
+
export function udpBlockedEvidence(bootstrapOk, stunProbeFailed, probed, probedAll = [probed]) {
|
|
24
24
|
if (!bootstrapOk || !stunProbeFailed || probed.length === 0)
|
|
25
25
|
return null;
|
|
26
|
-
|
|
26
|
+
if (probedAll.length === 0 || probedAll[0] !== probed || probedAll.some((addr) => addr.length === 0)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return { bootstrapOk: true, stunProbeFailed: true, probed, probedAll: [...probedAll] };
|
|
27
30
|
}
|
|
28
31
|
/** The base class of everything this package rejects with. */
|
|
29
32
|
export class LeafError extends Error {
|
|
@@ -564,8 +567,11 @@ function rtcDisplay(failure) {
|
|
|
564
567
|
case 'iceTimeout':
|
|
565
568
|
return ICE_TIMEOUT_DISPLAY;
|
|
566
569
|
case 'udpBlocked':
|
|
567
|
-
|
|
568
|
-
|
|
570
|
+
// An observation, not a cause: blocked UDP, a stopped UDP
|
|
571
|
+
// listener, a wrong advertised address or loss all look like
|
|
572
|
+
// this. Byte-identical to the Rust `Display`.
|
|
573
|
+
return ("no UDP response from the anchor's advertised endpoints: its HTTPS bootstrap succeeded " +
|
|
574
|
+
`but STUN bindings to ${failure.evidence.probedAll.join(', ')} went unanswered`);
|
|
569
575
|
case 'channelClosed':
|
|
570
576
|
return `the DataChannel closed: ${failure.detail}`;
|
|
571
577
|
case 'unsupported':
|
|
@@ -672,10 +678,12 @@ export function parseLeafError(message) {
|
|
|
672
678
|
export function parseRtcFailure(text) {
|
|
673
679
|
if (text === ICE_TIMEOUT_DISPLAY)
|
|
674
680
|
return { type: 'iceTimeout' };
|
|
675
|
-
const blocked = /^UDP
|
|
681
|
+
const blocked = /^no UDP response from the anchor's advertised endpoints: its HTTPS bootstrap succeeded but STUN bindings to (.+) went unanswered$/.exec(text);
|
|
676
682
|
if (blocked) {
|
|
677
|
-
|
|
678
|
-
const
|
|
683
|
+
// Endpoints are `host:port` or `[v6]:port`; neither holds ", ".
|
|
684
|
+
const probedAll = (blocked[1] ?? '').split(', ');
|
|
685
|
+
const probed = probedAll[0] ?? '';
|
|
686
|
+
const evidence = udpBlockedEvidence(true, true, probed, probedAll);
|
|
679
687
|
// The regex matched the message the Rust side only emits when it
|
|
680
688
|
// holds the evidence, so `probed` is non-empty and this is
|
|
681
689
|
// `UdpBlockedEvidence`; an empty subject is not evidence.
|
package/dist/events.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface ConnectedEvent {
|
|
|
42
42
|
* none.
|
|
43
43
|
*/
|
|
44
44
|
readonly rtcAddr: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* **Every** endpoint that anchor published, primary first: one per
|
|
47
|
+
* family on a dual-stack anchor, `[rtcAddr]` on any other, empty when
|
|
48
|
+
* it published none. The STUN probe aims at all of them.
|
|
49
|
+
*/
|
|
50
|
+
readonly rtcAddrs: readonly string[];
|
|
45
51
|
}
|
|
46
52
|
/** The session went away. */
|
|
47
53
|
export interface DisconnectedEvent {
|
package/dist/events.js
CHANGED
|
@@ -72,6 +72,7 @@ export function parseEvent(json) {
|
|
|
72
72
|
nodeIdHex: str(fields, 'node_id_hex'),
|
|
73
73
|
peerNode: str(fields, 'peer_node'),
|
|
74
74
|
rtcAddr: optionalStr(fields, 'rtc_addr'),
|
|
75
|
+
rtcAddrs: rtcAddrsOf(fields),
|
|
75
76
|
};
|
|
76
77
|
case 'disconnected':
|
|
77
78
|
return { type: 'disconnected', reason: str(fields, 'reason') };
|
|
@@ -247,6 +248,18 @@ function str(fields, key) {
|
|
|
247
248
|
return String(value);
|
|
248
249
|
return '';
|
|
249
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* `rtc_addrs` when the leaf sent a list (a dual-stack anchor), else
|
|
253
|
+
* `[rtc_addr]`, else empty: the rule `GET /rtc/anchor` documents.
|
|
254
|
+
*/
|
|
255
|
+
function rtcAddrsOf(fields) {
|
|
256
|
+
const list = fields.rtc_addrs;
|
|
257
|
+
if (Array.isArray(list)) {
|
|
258
|
+
return list.filter((addr) => typeof addr === 'string' && addr.length > 0);
|
|
259
|
+
}
|
|
260
|
+
const single = optionalStr(fields, 'rtc_addr');
|
|
261
|
+
return single === null ? [] : [single];
|
|
262
|
+
}
|
|
250
263
|
function optionalStr(fields, key) {
|
|
251
264
|
const value = fields[key];
|
|
252
265
|
return typeof value === 'string' && value.length > 0 ? value : null;
|