@net-mesh/browser 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +517 -0
  2. package/LICENSE-APACHE +190 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +666 -0
  5. package/dist/async-queue.d.ts +26 -0
  6. package/dist/async-queue.js +62 -0
  7. package/dist/credential.d.ts +70 -0
  8. package/dist/credential.js +95 -0
  9. package/dist/errors.d.ts +400 -0
  10. package/dist/errors.js +726 -0
  11. package/dist/events.d.ts +232 -0
  12. package/dist/events.js +298 -0
  13. package/dist/identity.d.ts +40 -0
  14. package/dist/identity.js +78 -0
  15. package/dist/index.bundle.js +2 -0
  16. package/dist/index.d.ts +38 -0
  17. package/dist/index.js +46 -0
  18. package/dist/leader/events.d.ts +111 -0
  19. package/dist/leader/events.js +116 -0
  20. package/dist/leader/index.d.ts +14 -0
  21. package/dist/leader/index.js +11 -0
  22. package/dist/leader/session.d.ts +539 -0
  23. package/dist/leader/session.js +770 -0
  24. package/dist/leader/wasm.d.ts +123 -0
  25. package/dist/leader/wasm.js +39 -0
  26. package/dist/lobby.d.ts +183 -0
  27. package/dist/lobby.js +504 -0
  28. package/dist/local.d.ts +80 -0
  29. package/dist/local.js +160 -0
  30. package/dist/net_leaf.d.ts +1494 -0
  31. package/dist/net_leaf.js +3753 -0
  32. package/dist/net_leaf_bg.wasm +0 -0
  33. package/dist/net_leaf_bg.wasm.d.ts +137 -0
  34. package/dist/netcode/client.d.ts +102 -0
  35. package/dist/netcode/client.js +296 -0
  36. package/dist/netcode/clock.d.ts +45 -0
  37. package/dist/netcode/clock.js +54 -0
  38. package/dist/netcode/host.d.ts +118 -0
  39. package/dist/netcode/host.js +276 -0
  40. package/dist/netcode/index.d.ts +19 -0
  41. package/dist/netcode/index.js +14 -0
  42. package/dist/netcode/interpolate.d.ts +50 -0
  43. package/dist/netcode/interpolate.js +100 -0
  44. package/dist/netcode/wire.d.ts +125 -0
  45. package/dist/netcode/wire.js +117 -0
  46. package/dist/node.d.ts +908 -0
  47. package/dist/node.js +1085 -0
  48. package/dist/org.d.ts +412 -0
  49. package/dist/org.js +567 -0
  50. package/dist/peer-driver.d.ts +117 -0
  51. package/dist/peer-driver.js +224 -0
  52. package/dist/store/assembly.d.ts +176 -0
  53. package/dist/store/assembly.js +325 -0
  54. package/dist/store/chunker.d.ts +83 -0
  55. package/dist/store/chunker.js +190 -0
  56. package/dist/store/core.d.ts +136 -0
  57. package/dist/store/core.js +477 -0
  58. package/dist/store/definition.d.ts +23 -0
  59. package/dist/store/definition.js +83 -0
  60. package/dist/store/errors.d.ts +77 -0
  61. package/dist/store/errors.js +94 -0
  62. package/dist/store/host.d.ts +247 -0
  63. package/dist/store/host.js +675 -0
  64. package/dist/store/index.d.ts +50 -0
  65. package/dist/store/index.js +40 -0
  66. package/dist/store/interest.d.ts +40 -0
  67. package/dist/store/interest.js +77 -0
  68. package/dist/store/inventory.d.ts +81 -0
  69. package/dist/store/inventory.js +140 -0
  70. package/dist/store/join.d.ts +118 -0
  71. package/dist/store/join.js +724 -0
  72. package/dist/store/json.d.ts +50 -0
  73. package/dist/store/json.js +283 -0
  74. package/dist/store/ledger.d.ts +187 -0
  75. package/dist/store/ledger.js +292 -0
  76. package/dist/store/owner.d.ts +616 -0
  77. package/dist/store/owner.js +1711 -0
  78. package/dist/store/patch.d.ts +70 -0
  79. package/dist/store/patch.js +166 -0
  80. package/dist/store/player.d.ts +65 -0
  81. package/dist/store/player.js +261 -0
  82. package/dist/store/replica.d.ts +187 -0
  83. package/dist/store/replica.js +740 -0
  84. package/dist/store/state.d.ts +42 -0
  85. package/dist/store/state.js +165 -0
  86. package/dist/store/types.d.ts +200 -0
  87. package/dist/store/types.js +25 -0
  88. package/dist/store/visibility.d.ts +118 -0
  89. package/dist/store/visibility.js +282 -0
  90. package/dist/store/wire.d.ts +309 -0
  91. package/dist/store/wire.js +834 -0
  92. package/dist/stream.d.ts +174 -0
  93. package/dist/stream.js +238 -0
  94. package/dist/three/index.d.ts +109 -0
  95. package/dist/three/index.js +130 -0
  96. package/dist/udp-probe.d.ts +159 -0
  97. package/dist/udp-probe.js +245 -0
  98. package/dist/wasm.d.ts +642 -0
  99. package/dist/wasm.js +98 -0
  100. package/dist/world/border.d.ts +84 -0
  101. package/dist/world/border.js +99 -0
  102. package/dist/world/handoff.d.ts +187 -0
  103. package/dist/world/handoff.js +222 -0
  104. package/dist/world/index.d.ts +13 -0
  105. package/dist/world/index.js +9 -0
  106. package/dist/world/link.d.ts +179 -0
  107. package/dist/world/link.js +476 -0
  108. package/dist/world/regions.d.ts +154 -0
  109. package/dist/world/regions.js +314 -0
  110. package/package.json +72 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The failure-typing correction, implemented.
3
+ *
4
+ * An ICE timeout is not evidence that UDP is blocked: an anchor that
5
+ * is down, wrong or saturated produces exactly the same symptom. The
6
+ * leaf therefore surfaces `rtc: ICE did not connect…` by default, and
7
+ * only the two observations below may narrow it:
8
+ *
9
+ * 1. the HTTPS bootstrap to *that anchor* succeeded — it is up and
10
+ * addressable; and
11
+ * 2. a STUN binding to the `rtc_addr` *that same anchor published*
12
+ * went unanswered.
13
+ *
14
+ * {@link classifyRtcFailure} is a pure function of those two
15
+ * observations, so the rule is unit-testable without a browser, and
16
+ * {@link probeStunBinding} / {@link probeBootstrapReachable} are the
17
+ * two impure halves that produce them. Nothing else in this package
18
+ * may construct a `udp-blocked` error.
19
+ *
20
+ * **What the STUN probe actually keys on** (measured in headless
21
+ * Chromium, not assumed — see `docs/internal/spikes/S5_REPORT.md`):
22
+ * host candidates are gathered whatever the network does, so their
23
+ * presence or absence proves nothing and they are ignored. The signals
24
+ * that mean something are
25
+ *
26
+ * - a **server-reflexive** candidate — the STUN binding was answered,
27
+ * so UDP to the anchor works and the ICE failure is something else;
28
+ * - an `icecandidateerror` with a STUN **error code** below 700 — an
29
+ * error *response* came back, so packets reach the anchor;
30
+ * - an `icecandidateerror` with code 701, or gathering completing with
31
+ * no reflexive candidate at all — nothing came back.
32
+ */
33
+ import { RtcError, type UdpBlockedEvidence } from './errors.js';
34
+ /** What a STUN binding attempt against the anchor's `rtc_addr` did. */
35
+ export type StunProbeOutcome =
36
+ /** A reflexive candidate came back: the binding was answered. */
37
+ {
38
+ readonly type: 'reflexive';
39
+ readonly address: string;
40
+ }
41
+ /**
42
+ * A STUN **error response** came back (code < 700). The anchor's ICE
43
+ * agent refusing an unauthenticated binding still proves the packet
44
+ * arrived and the reply got home, so this is *not* blocked UDP.
45
+ */
46
+ | {
47
+ readonly type: 'stunError';
48
+ readonly code: number;
49
+ readonly detail: string;
50
+ }
51
+ /** Nothing came back inside the deadline. */
52
+ | {
53
+ readonly type: 'unanswered';
54
+ readonly detail: string;
55
+ }
56
+ /** The probe was not run — no address to aim at, or it was disabled. */
57
+ | {
58
+ readonly type: 'notRun';
59
+ readonly reason: string;
60
+ }
61
+ /** This engine could not run the probe at all. */
62
+ | {
63
+ readonly type: 'unsupported';
64
+ readonly detail: string;
65
+ };
66
+ /** Everything the classification is allowed to look at. */
67
+ export interface RtcFailureObservations {
68
+ /** Did the HTTPS bootstrap to this anchor succeed? */
69
+ readonly bootstrapOk: boolean;
70
+ /** What the STUN probe saw. */
71
+ readonly stunProbe: StunProbeOutcome;
72
+ /** The address the probe was aimed at, so the claim names its subject. */
73
+ readonly probed: string | null;
74
+ }
75
+ /** The only two classifications an ICE failure may end up with. */
76
+ export type IceFailureClassification = {
77
+ readonly type: 'iceTimeout';
78
+ } | {
79
+ readonly type: 'udpBlocked';
80
+ readonly evidence: UdpBlockedEvidence;
81
+ };
82
+ /**
83
+ * The rule, pure.
84
+ *
85
+ * `udpBlocked` requires a named subject, a successful bootstrap and a
86
+ * STUN probe that went unanswered. Every other combination — including
87
+ * "we did not probe", "the probe is unsupported here" and "an error
88
+ * response came back" — stays `iceTimeout`.
89
+ */
90
+ export declare function classifyRtcFailure(observations: RtcFailureObservations): IceFailureClassification;
91
+ /** {@link classifyRtcFailure}, as the error a caller will be rejected with. */
92
+ export declare function classifyRtcError(observations: RtcFailureObservations): RtcError;
93
+ /** Injection seam for the probe, so tests never need a real engine. */
94
+ export interface StunProbeOptions {
95
+ /** How long to wait for an answer. Default 2500 ms. */
96
+ readonly timeoutMs?: number;
97
+ /**
98
+ * Builds the `RTCPeerConnection` used for the probe. Defaults to the
99
+ * global constructor; tests pass a fake.
100
+ */
101
+ readonly peerConnectionFactory?: (config: RTCConfiguration) => RTCPeerConnection;
102
+ }
103
+ /**
104
+ * Aim a STUN binding at `rtcAddr` and report what came back.
105
+ *
106
+ * Uses an `RTCPeerConnection` whose *only* ICE server is that address:
107
+ * the browser then sends a STUN binding request to it as part of
108
+ * gathering, and surfaces the outcome as either a reflexive candidate
109
+ * or an `icecandidateerror`. The probe connection is never used for
110
+ * media and is always closed.
111
+ */
112
+ export declare function probeStunBinding(rtcAddr: string, options?: StunProbeOptions): Promise<StunProbeOutcome>;
113
+ /** Injection seam for the bootstrap observation. */
114
+ export interface BootstrapProbeOptions {
115
+ /** How long to wait for the anchor to answer. Default 3000 ms. */
116
+ readonly timeoutMs?: number;
117
+ /** Defaults to the global `fetch`; tests pass a fake. */
118
+ readonly fetchImpl?: typeof fetch;
119
+ }
120
+ /**
121
+ * Did the anchor answer over HTTPS?
122
+ *
123
+ * Any response counts, including 4xx and 5xx: the question is whether
124
+ * the anchor is up and addressable, not whether it liked this request.
125
+ * The request is `no-cors` on purpose — a cross-origin anchor without
126
+ * CORS headers would otherwise reject a perfectly delivered request
127
+ * and we would under-report reachability — and `no-store`, so a cached
128
+ * response cannot stand in for a live one.
129
+ */
130
+ export declare function probeBootstrapReachable(bootstrapUrl: string, options?: BootstrapProbeOptions): Promise<boolean>;
131
+ /**
132
+ * `host:port` (or `[v6]:port`, or a bare host) to a `stun:` URL for
133
+ * the **throwaway diagnostic probe**, or `null` when it is not an
134
+ * address at all. A missing port means the IANA STUN port, which is
135
+ * what an anchor publishing a bare host would mean.
136
+ *
137
+ * This is for {@link probeStunBinding} and nothing else. It must
138
+ * **NOT** be used to build the anchor connection's `iceServers`:
139
+ * `rtc_addr` is that connection's ICE peer, and a peer cannot be its
140
+ * own STUN server — configuring it that way gathers no reflexive
141
+ * candidate and buys an ICE deadline instead of a connection. The
142
+ * leaf refuses it outright ({@link IceServerConflictError}).
143
+ *
144
+ * The endpoint an anchor connection should gather against is the one
145
+ * the anchor announces separately, as `stun_addr` on
146
+ * `GET /rtc/anchor`; `connect()` uses it by default, so a page needs
147
+ * no URL-building at all.
148
+ */
149
+ export declare function diagnosticStunUrl(rtcAddr: string): string | null;
150
+ /**
151
+ * The reflexive address of a candidate, or `null` if it is not a
152
+ * reflexive one.
153
+ *
154
+ * `RTCIceCandidate.type`/`.address` are the modern surface; the SDP
155
+ * line is parsed as well because the fields are still nullable in
156
+ * engines that predate them, and a missed `srflx` would be read as
157
+ * blocked UDP — the exact mis-typing this module exists to prevent.
158
+ */
159
+ export declare function reflexiveAddress(candidate: RTCIceCandidate): string | null;
@@ -0,0 +1,245 @@
1
+ /**
2
+ * The failure-typing correction, implemented.
3
+ *
4
+ * An ICE timeout is not evidence that UDP is blocked: an anchor that
5
+ * is down, wrong or saturated produces exactly the same symptom. The
6
+ * leaf therefore surfaces `rtc: ICE did not connect…` by default, and
7
+ * only the two observations below may narrow it:
8
+ *
9
+ * 1. the HTTPS bootstrap to *that anchor* succeeded — it is up and
10
+ * addressable; and
11
+ * 2. a STUN binding to the `rtc_addr` *that same anchor published*
12
+ * went unanswered.
13
+ *
14
+ * {@link classifyRtcFailure} is a pure function of those two
15
+ * observations, so the rule is unit-testable without a browser, and
16
+ * {@link probeStunBinding} / {@link probeBootstrapReachable} are the
17
+ * two impure halves that produce them. Nothing else in this package
18
+ * may construct a `udp-blocked` error.
19
+ *
20
+ * **What the STUN probe actually keys on** (measured in headless
21
+ * Chromium, not assumed — see `docs/internal/spikes/S5_REPORT.md`):
22
+ * host candidates are gathered whatever the network does, so their
23
+ * presence or absence proves nothing and they are ignored. The signals
24
+ * that mean something are
25
+ *
26
+ * - a **server-reflexive** candidate — the STUN binding was answered,
27
+ * so UDP to the anchor works and the ICE failure is something else;
28
+ * - an `icecandidateerror` with a STUN **error code** below 700 — an
29
+ * error *response* came back, so packets reach the anchor;
30
+ * - an `icecandidateerror` with code 701, or gathering completing with
31
+ * no reflexive candidate at all — nothing came back.
32
+ */
33
+ import { RtcError, udpBlockedEvidence } from './errors.js';
34
+ /**
35
+ * The rule, pure.
36
+ *
37
+ * `udpBlocked` requires a named subject, a successful bootstrap and a
38
+ * STUN probe that went unanswered. Every other combination — including
39
+ * "we did not probe", "the probe is unsupported here" and "an error
40
+ * response came back" — stays `iceTimeout`.
41
+ */
42
+ export function classifyRtcFailure(observations) {
43
+ const { bootstrapOk, stunProbe, probed } = observations;
44
+ if (stunProbe.type !== 'unanswered')
45
+ return { type: 'iceTimeout' };
46
+ const evidence = udpBlockedEvidence(bootstrapOk, true, probed ?? '');
47
+ return evidence === null ? { type: 'iceTimeout' } : { type: 'udpBlocked', evidence };
48
+ }
49
+ /** {@link classifyRtcFailure}, as the error a caller will be rejected with. */
50
+ export function classifyRtcError(observations) {
51
+ return new RtcError(classifyRtcFailure(observations));
52
+ }
53
+ const DEFAULT_PROBE_TIMEOUT_MS = 2500;
54
+ /** Anything at or above this `errorCode` means "no response arrived". */
55
+ const STUN_NO_RESPONSE_CODE = 700;
56
+ const DEFAULT_STUN_PORT = 3478;
57
+ /**
58
+ * Aim a STUN binding at `rtcAddr` and report what came back.
59
+ *
60
+ * Uses an `RTCPeerConnection` whose *only* ICE server is that address:
61
+ * the browser then sends a STUN binding request to it as part of
62
+ * gathering, and surfaces the outcome as either a reflexive candidate
63
+ * or an `icecandidateerror`. The probe connection is never used for
64
+ * media and is always closed.
65
+ */
66
+ export async function probeStunBinding(rtcAddr, options = {}) {
67
+ const url = diagnosticStunUrl(rtcAddr);
68
+ if (url === null)
69
+ return { type: 'notRun', reason: `not an address: ${rtcAddr}` };
70
+ const factory = options.peerConnectionFactory ?? defaultPeerConnectionFactory();
71
+ if (factory === null) {
72
+ return { type: 'unsupported', detail: 'RTCPeerConnection is not available in this context' };
73
+ }
74
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
75
+ const { promise, resolve } = Promise.withResolvers();
76
+ let pc;
77
+ try {
78
+ pc = factory({ iceServers: [{ urls: url }], iceCandidatePoolSize: 0 });
79
+ }
80
+ catch (error) {
81
+ return { type: 'unsupported', detail: String(error) };
82
+ }
83
+ let settled = false;
84
+ const settle = (outcome) => {
85
+ if (settled)
86
+ return;
87
+ settled = true;
88
+ resolve(outcome);
89
+ };
90
+ // A "no response" report from the engine, when it makes one. Chromium
91
+ // does not always: against a black-holed address it emits no error and
92
+ // never completes gathering, which is why the deadline below is
93
+ // load-bearing rather than a safety net.
94
+ let noResponse = null;
95
+ const timer = setTimeout(() => settle({ type: 'unanswered', detail: noResponse ?? 'no answer inside the probe deadline' }), timeoutMs);
96
+ const gatheringDone = () => {
97
+ settle({
98
+ type: 'unanswered',
99
+ detail: noResponse ?? 'gathering completed with no reflexive candidate',
100
+ });
101
+ };
102
+ pc.onicecandidate = (event) => {
103
+ const candidate = event.candidate;
104
+ if (candidate === null) {
105
+ gatheringDone();
106
+ return;
107
+ }
108
+ // Host candidates are gathered regardless of what the network does
109
+ // to UDP — Chromium even hides them behind an mDNS `.local` name —
110
+ // so only a reflexive one is evidence.
111
+ const reflexive = reflexiveAddress(candidate);
112
+ if (reflexive !== null)
113
+ settle({ type: 'reflexive', address: reflexive });
114
+ };
115
+ pc.onicecandidateerror = (event) => {
116
+ const code = event.errorCode;
117
+ const detail = `${code}: ${event.errorText} (${event.url})`;
118
+ // A STUN *error response* is still a response: the packet reached
119
+ // the anchor and the reply got home, so UDP is not blocked. (An
120
+ // anchor's ICE agent refusing an unauthenticated binding lands
121
+ // here; Chromium reports the STUN error number, e.g. `1` for a 401.)
122
+ if (code < STUN_NO_RESPONSE_CODE) {
123
+ settle({ type: 'stunError', code, detail });
124
+ return;
125
+ }
126
+ noResponse = detail;
127
+ };
128
+ pc.onicegatheringstatechange = () => {
129
+ if (pc.iceGatheringState === 'complete')
130
+ gatheringDone();
131
+ };
132
+ try {
133
+ // A data section is required or there is nothing to gather for.
134
+ pc.createDataChannel('net-udp-probe');
135
+ await pc.setLocalDescription(await pc.createOffer());
136
+ }
137
+ catch (error) {
138
+ settle({ type: 'unsupported', detail: String(error) });
139
+ }
140
+ try {
141
+ return await promise;
142
+ }
143
+ finally {
144
+ clearTimeout(timer);
145
+ pc.onicecandidate = null;
146
+ pc.onicecandidateerror = null;
147
+ pc.onicegatheringstatechange = null;
148
+ pc.close();
149
+ }
150
+ }
151
+ const DEFAULT_BOOTSTRAP_TIMEOUT_MS = 3000;
152
+ /**
153
+ * Did the anchor answer over HTTPS?
154
+ *
155
+ * Any response counts, including 4xx and 5xx: the question is whether
156
+ * the anchor is up and addressable, not whether it liked this request.
157
+ * The request is `no-cors` on purpose — a cross-origin anchor without
158
+ * CORS headers would otherwise reject a perfectly delivered request
159
+ * and we would under-report reachability — and `no-store`, so a cached
160
+ * response cannot stand in for a live one.
161
+ */
162
+ export async function probeBootstrapReachable(bootstrapUrl, options = {}) {
163
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
164
+ if (typeof fetchImpl !== 'function')
165
+ return false;
166
+ try {
167
+ await fetchImpl(bootstrapUrl, {
168
+ method: 'GET',
169
+ mode: 'no-cors',
170
+ cache: 'no-store',
171
+ redirect: 'follow',
172
+ signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_BOOTSTRAP_TIMEOUT_MS),
173
+ });
174
+ return true;
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ /**
181
+ * `host:port` (or `[v6]:port`, or a bare host) to a `stun:` URL for
182
+ * the **throwaway diagnostic probe**, or `null` when it is not an
183
+ * address at all. A missing port means the IANA STUN port, which is
184
+ * what an anchor publishing a bare host would mean.
185
+ *
186
+ * This is for {@link probeStunBinding} and nothing else. It must
187
+ * **NOT** be used to build the anchor connection's `iceServers`:
188
+ * `rtc_addr` is that connection's ICE peer, and a peer cannot be its
189
+ * own STUN server — configuring it that way gathers no reflexive
190
+ * candidate and buys an ICE deadline instead of a connection. The
191
+ * leaf refuses it outright ({@link IceServerConflictError}).
192
+ *
193
+ * The endpoint an anchor connection should gather against is the one
194
+ * the anchor announces separately, as `stun_addr` on
195
+ * `GET /rtc/anchor`; `connect()` uses it by default, so a page needs
196
+ * no URL-building at all.
197
+ */
198
+ export function diagnosticStunUrl(rtcAddr) {
199
+ const trimmed = rtcAddr.trim();
200
+ if (trimmed.length === 0)
201
+ return null;
202
+ if (trimmed.startsWith('stun:') || trimmed.startsWith('stuns:'))
203
+ return trimmed;
204
+ const bracketed = /^\[([0-9a-fA-F:.]+)\](?::(\d+))?$/.exec(trimmed);
205
+ if (bracketed) {
206
+ const host = bracketed[1] ?? '';
207
+ return `stun:[${host}]:${bracketed[2] ?? DEFAULT_STUN_PORT}`;
208
+ }
209
+ // A bare IPv6 literal has more than one colon and no brackets.
210
+ if ((trimmed.match(/:/g)?.length ?? 0) > 1)
211
+ return `stun:[${trimmed}]:${DEFAULT_STUN_PORT}`;
212
+ const hostPort = /^([A-Za-z0-9._-]+)(?::(\d+))?$/.exec(trimmed);
213
+ if (hostPort === null)
214
+ return null;
215
+ const host = hostPort[1] ?? '';
216
+ return `stun:${host}:${hostPort[2] ?? DEFAULT_STUN_PORT}`;
217
+ }
218
+ /**
219
+ * The reflexive address of a candidate, or `null` if it is not a
220
+ * reflexive one.
221
+ *
222
+ * `RTCIceCandidate.type`/`.address` are the modern surface; the SDP
223
+ * line is parsed as well because the fields are still nullable in
224
+ * engines that predate them, and a missed `srflx` would be read as
225
+ * blocked UDP — the exact mis-typing this module exists to prevent.
226
+ */
227
+ export function reflexiveAddress(candidate) {
228
+ if (candidate.type === 'srflx')
229
+ return candidate.address ?? sdpAddress(candidate.candidate);
230
+ if (candidate.type !== null)
231
+ return null;
232
+ return / typ srflx /.test(candidate.candidate) ? sdpAddress(candidate.candidate) : null;
233
+ }
234
+ function sdpAddress(line) {
235
+ // candidate:<foundation> <component> <transport> <priority> <ip> <port> typ …
236
+ const fields = line.split(' ');
237
+ const ip = fields[4] ?? '';
238
+ const port = fields[5] ?? '';
239
+ return port.length > 0 ? `${ip}:${port}` : ip;
240
+ }
241
+ function defaultPeerConnectionFactory() {
242
+ if (typeof RTCPeerConnection === 'undefined')
243
+ return null;
244
+ return (config) => new RTCPeerConnection(config);
245
+ }