@fairfox/polly 0.53.0 → 0.55.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/dist/src/mesh.js CHANGED
@@ -1849,8 +1849,8 @@ import {
1849
1849
  } from "@automerge/automerge-repo/slim";
1850
1850
 
1851
1851
  // src/shared/lib/sync-fragment.ts
1852
- var SYNC_FRAGMENT_THRESHOLD = 64 * 1024;
1853
- var SYNC_FRAGMENT_CHUNK_SIZE = 64 * 1024;
1852
+ var SYNC_FRAGMENT_THRESHOLD = 60 * 1024;
1853
+ var SYNC_FRAGMENT_CHUNK_SIZE = 60 * 1024;
1854
1854
  function serialiseSyncFragment(header, data) {
1855
1855
  const headerBytes = new TextEncoder().encode(JSON.stringify(header));
1856
1856
  const size = 4 + headerBytes.length + data.length;
@@ -1933,6 +1933,132 @@ function findSubarray2(haystack, needle) {
1933
1933
  }
1934
1934
 
1935
1935
  // src/shared/lib/mesh-webrtc-adapter.ts
1936
+ async function collectTransportSnapshot(connection, lastDataChannelError) {
1937
+ const at = performance.now();
1938
+ let report;
1939
+ try {
1940
+ report = await connection.getStats();
1941
+ } catch {
1942
+ return emptyTransportSnapshot(lastDataChannelError, at);
1943
+ }
1944
+ const parsed = partitionStats(report);
1945
+ const selectedPair = selectActivePair(parsed);
1946
+ const selectedCandidatePair = selectedPair ? buildCandidatePairView(selectedPair, parsed.localCands, parsed.remoteCands) : undefined;
1947
+ return {
1948
+ selectedCandidatePair,
1949
+ retransmittedPacketsSent: parsed.retransmittedPacketsSent,
1950
+ retransmittedBytesSent: parsed.retransmittedBytesSent,
1951
+ lastDataChannelError,
1952
+ at
1953
+ };
1954
+ }
1955
+ function emptyTransportSnapshot(lastDataChannelError, at) {
1956
+ return {
1957
+ selectedCandidatePair: undefined,
1958
+ retransmittedPacketsSent: undefined,
1959
+ retransmittedBytesSent: undefined,
1960
+ lastDataChannelError,
1961
+ at
1962
+ };
1963
+ }
1964
+ function partitionStats(report) {
1965
+ const out = {
1966
+ localCands: new Map,
1967
+ remoteCands: new Map,
1968
+ pairs: new Map,
1969
+ selectedPairId: undefined,
1970
+ retransmittedPacketsSent: undefined,
1971
+ retransmittedBytesSent: undefined
1972
+ };
1973
+ const iter = report.values?.() ?? [];
1974
+ for (const raw of iter) {
1975
+ if (!raw || typeof raw !== "object")
1976
+ continue;
1977
+ const stat = raw;
1978
+ ingestStat(stat, out);
1979
+ }
1980
+ return out;
1981
+ }
1982
+ function ingestStat(stat, out) {
1983
+ const id = String(stat["id"]);
1984
+ switch (stat["type"]) {
1985
+ case "local-candidate":
1986
+ out.localCands.set(id, stat);
1987
+ return;
1988
+ case "remote-candidate":
1989
+ out.remoteCands.set(id, stat);
1990
+ return;
1991
+ case "candidate-pair":
1992
+ out.pairs.set(id, stat);
1993
+ return;
1994
+ case "transport":
1995
+ ingestTransport(stat, out);
1996
+ return;
1997
+ case "data-channel":
1998
+ ingestDataChannel(stat, out);
1999
+ return;
2000
+ }
2001
+ }
2002
+ function ingestTransport(stat, out) {
2003
+ const selectedId = stat["selectedCandidatePairId"];
2004
+ if (typeof selectedId === "string")
2005
+ out.selectedPairId = selectedId;
2006
+ const rp = stat["retransmittedPacketsSent"];
2007
+ const rb = stat["retransmittedBytesSent"];
2008
+ if (typeof rp === "number")
2009
+ out.retransmittedPacketsSent = rp;
2010
+ if (typeof rb === "number")
2011
+ out.retransmittedBytesSent = rb;
2012
+ }
2013
+ function ingestDataChannel(stat, out) {
2014
+ const rp = stat["retransmittedPacketsSent"];
2015
+ const rb = stat["retransmittedBytesSent"];
2016
+ if (out.retransmittedPacketsSent === undefined && typeof rp === "number") {
2017
+ out.retransmittedPacketsSent = rp;
2018
+ }
2019
+ if (out.retransmittedBytesSent === undefined && typeof rb === "number") {
2020
+ out.retransmittedBytesSent = rb;
2021
+ }
2022
+ }
2023
+ function selectActivePair(parsed) {
2024
+ if (parsed.selectedPairId) {
2025
+ const named = parsed.pairs.get(parsed.selectedPairId);
2026
+ if (named)
2027
+ return named;
2028
+ }
2029
+ for (const pair of parsed.pairs.values()) {
2030
+ if (pair["nominated"])
2031
+ return pair;
2032
+ }
2033
+ return;
2034
+ }
2035
+ function serialiseSlotView(slot) {
2036
+ return {
2037
+ signalingState: slot.connection.signalingState,
2038
+ iceConnectionState: slot.connection.iceConnectionState,
2039
+ connectionState: slot.connection.connectionState,
2040
+ dataChannelState: slot.channel?.readyState ?? "no-channel",
2041
+ pendingSendCount: slot.pendingSends.length,
2042
+ pendingRemoteIceCount: slot.pendingRemoteIce.length,
2043
+ inFlightSync: slot.inFlightSync ? { ...slot.inFlightSync } : undefined,
2044
+ transport: slot.transport ? {
2045
+ ...slot.transport,
2046
+ selectedCandidatePair: slot.transport.selectedCandidatePair ? { ...slot.transport.selectedCandidatePair } : undefined
2047
+ } : undefined
2048
+ };
2049
+ }
2050
+ function buildCandidatePairView(pair, localCands, remoteCands) {
2051
+ const local = localCands.get(String(pair["localCandidateId"]));
2052
+ const remote = remoteCands.get(String(pair["remoteCandidateId"]));
2053
+ return {
2054
+ localCandidateType: String(local?.["candidateType"] ?? "?"),
2055
+ remoteCandidateType: String(remote?.["candidateType"] ?? "?"),
2056
+ state: String(pair["state"] ?? ""),
2057
+ nominated: Boolean(pair["nominated"]),
2058
+ bytesSent: Number(pair["bytesSent"] ?? 0),
2059
+ bytesReceived: Number(pair["bytesReceived"] ?? 0)
2060
+ };
2061
+ }
1936
2062
  var DEFAULT_ICE_SERVERS = [
1937
2063
  { urls: "stun:stun.l.google.com:19302" },
1938
2064
  { urls: "stun:stun1.l.google.com:19302" }
@@ -1941,11 +2067,15 @@ var DEFAULT_ICE_SERVERS = [
1941
2067
  class MeshWebRTCAdapter extends NetworkAdapter2 {
1942
2068
  signaling;
1943
2069
  iceServers;
2070
+ iceTransportPolicy;
2071
+ iceRelayEnforcement;
1944
2072
  dataChannelLabel;
1945
2073
  knownPeers;
1946
2074
  keyringSource;
1947
2075
  knownPeersRefreshTimer;
1948
2076
  knownPeersRefreshIntervalMs;
2077
+ syncYieldEnabled;
2078
+ syncFragmentChunkSize;
1949
2079
  presentPeers = new Set;
1950
2080
  localPeerId;
1951
2081
  RTCPeerConnectionCtor;
@@ -1969,10 +2099,14 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
1969
2099
  super();
1970
2100
  this.signaling = options.signaling;
1971
2101
  this.iceServers = options.iceServers ?? DEFAULT_ICE_SERVERS;
2102
+ this.iceTransportPolicy = options.iceTransportPolicy;
2103
+ this.iceRelayEnforcement = options.iceRelayEnforcement ?? true;
1972
2104
  this.dataChannelLabel = options.dataChannelLabel ?? "polly-mesh";
1973
2105
  this.knownPeers = new Set(options.knownPeerIds ?? []);
1974
2106
  this.keyringSource = options.keyringSource;
1975
2107
  this.knownPeersRefreshIntervalMs = options.knownPeersRefreshIntervalMs ?? 2000;
2108
+ this.syncYieldEnabled = options.syncYieldEnabled ?? true;
2109
+ this.syncFragmentChunkSize = options.syncFragmentChunkSizeOverride ?? SYNC_FRAGMENT_CHUNK_SIZE;
1976
2110
  this.localPeerId = options.peerId;
1977
2111
  const PC = options.RTCPeerConnection ?? globalThis.RTCPeerConnection;
1978
2112
  if (typeof PC !== "function") {
@@ -2004,14 +2138,7 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2004
2138
  peerId,
2005
2139
  knownInKeyring: knownPeerSet.has(peerId),
2006
2140
  presentInSignalling: this.presentPeers.has(peerId),
2007
- slot: slot ? {
2008
- signalingState: slot.connection.signalingState,
2009
- iceConnectionState: slot.connection.iceConnectionState,
2010
- connectionState: slot.connection.connectionState,
2011
- dataChannelState: slot.channel?.readyState ?? "no-channel",
2012
- pendingSendCount: slot.pendingSends.length,
2013
- pendingRemoteIceCount: slot.pendingRemoteIce.length
2014
- } : undefined
2141
+ slot: slot ? serialiseSlotView(slot) : undefined
2015
2142
  });
2016
2143
  }
2017
2144
  return {
@@ -2133,15 +2260,29 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2133
2260
  slot.pendingSends.push(bytes);
2134
2261
  }
2135
2262
  }
2136
- sendBytesMaybeFragmented(channel, bytes) {
2263
+ static SEND_YIELD_EVERY_N_FRAGMENTS = 8;
2264
+ async sendBytesMaybeFragmented(channel, bytes) {
2137
2265
  if (bytes.length <= SYNC_FRAGMENT_THRESHOLD) {
2138
2266
  channel.send(bytes);
2139
2267
  return;
2140
2268
  }
2141
2269
  const id = crypto.randomUUID();
2142
- const fragments = chunkSyncMessage(bytes, id);
2270
+ const fragments = chunkSyncMessage(bytes, id, this.syncFragmentChunkSize);
2271
+ if (!this.syncYieldEnabled) {
2272
+ for (const fragment of fragments) {
2273
+ channel.send(fragment);
2274
+ }
2275
+ return;
2276
+ }
2277
+ let i = 0;
2143
2278
  for (const fragment of fragments) {
2144
2279
  channel.send(fragment);
2280
+ i += 1;
2281
+ if (i % MeshWebRTCAdapter.SEND_YIELD_EVERY_N_FRAGMENTS === 0 && i < fragments.length) {
2282
+ await new Promise((resolve) => {
2283
+ setTimeout(resolve, 0);
2284
+ });
2285
+ }
2145
2286
  }
2146
2287
  }
2147
2288
  handleSignal(fromPeerId, rawPayload) {
@@ -2161,15 +2302,71 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2161
2302
  return;
2162
2303
  }
2163
2304
  }
2305
+ buildRtcConfiguration() {
2306
+ const config = { iceServers: this.iceServers };
2307
+ if (this.iceTransportPolicy !== undefined) {
2308
+ config.iceTransportPolicy = this.iceTransportPolicy;
2309
+ }
2310
+ return config;
2311
+ }
2312
+ isRelayCandidateInit(init) {
2313
+ const candidateStr = init.candidate ?? "";
2314
+ const m = candidateStr.match(/\btyp\s+(\S+)/i);
2315
+ return m?.[1]?.toLowerCase() === "relay";
2316
+ }
2317
+ shouldSendCandidate(candidate) {
2318
+ if (!this.iceRelayEnforcement)
2319
+ return true;
2320
+ if (this.iceTransportPolicy !== "relay")
2321
+ return true;
2322
+ const typed = candidate.type;
2323
+ const match = candidate.candidate.match(/\btyp\s+(\S+)/i);
2324
+ const candidateType = (typed ?? match?.[1] ?? "").toLowerCase();
2325
+ return candidateType === "relay";
2326
+ }
2327
+ filterSdpCandidatesByPolicy(sdp) {
2328
+ if (!this.iceRelayEnforcement)
2329
+ return sdp;
2330
+ if (this.iceTransportPolicy !== "relay")
2331
+ return sdp;
2332
+ if (!sdp)
2333
+ return sdp;
2334
+ const lines = sdp.split(/\r?\n/);
2335
+ const filtered = [];
2336
+ for (const line of lines) {
2337
+ if (!line.startsWith("a=candidate:")) {
2338
+ filtered.push(line);
2339
+ continue;
2340
+ }
2341
+ const m = line.match(/\btyp\s+(\S+)/i);
2342
+ if (m?.[1]?.toLowerCase() === "relay")
2343
+ filtered.push(line);
2344
+ }
2345
+ return filtered.join(`\r
2346
+ `);
2347
+ }
2348
+ applySdpPolicyFilter(description) {
2349
+ if (!this.iceRelayEnforcement)
2350
+ return description;
2351
+ if (this.iceTransportPolicy !== "relay")
2352
+ return description;
2353
+ const filtered = this.filterSdpCandidatesByPolicy(description.sdp);
2354
+ if (filtered === description.sdp)
2355
+ return description;
2356
+ return { ...description, sdp: filtered };
2357
+ }
2164
2358
  createInitiatingSlot(targetId) {
2165
- const connection = new this.RTCPeerConnectionCtor({ iceServers: this.iceServers });
2359
+ const connection = new this.RTCPeerConnectionCtor(this.buildRtcConfiguration());
2166
2360
  const channel = connection.createDataChannel(this.dataChannelLabel, { ordered: true });
2167
2361
  const slot = {
2168
2362
  connection,
2169
2363
  channel,
2170
2364
  pendingSends: [],
2171
2365
  pendingFragments: new Map,
2172
- pendingRemoteIce: []
2366
+ pendingRemoteIce: [],
2367
+ inFlightSync: undefined,
2368
+ transport: undefined,
2369
+ lastDataChannelError: undefined
2173
2370
  };
2174
2371
  this.slots.set(targetId, slot);
2175
2372
  this.wireConnection(targetId, connection);
@@ -2180,7 +2377,15 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2180
2377
  async initiateOffer(targetId, connection) {
2181
2378
  const offer = await connection.createOffer();
2182
2379
  await connection.setLocalDescription(offer);
2183
- this.signaling.sendSignal(targetId, { kind: "offer", sdp: offer });
2380
+ const localOffer = connection.localDescription ?? offer;
2381
+ const sdpToSend = this.applySdpPolicyFilter({
2382
+ type: localOffer.type,
2383
+ sdp: localOffer.sdp ?? offer.sdp
2384
+ });
2385
+ this.signaling.sendSignal(targetId, {
2386
+ kind: "offer",
2387
+ sdp: sdpToSend
2388
+ });
2184
2389
  }
2185
2390
  async handleOffer(fromPeerId, sdp) {
2186
2391
  const existing = this.slots.get(fromPeerId);
@@ -2193,13 +2398,16 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2193
2398
  existing.connection.close();
2194
2399
  this.slots.delete(fromPeerId);
2195
2400
  }
2196
- const connection = new this.RTCPeerConnectionCtor({ iceServers: this.iceServers });
2401
+ const connection = new this.RTCPeerConnectionCtor(this.buildRtcConfiguration());
2197
2402
  const slot = {
2198
2403
  connection,
2199
2404
  channel: undefined,
2200
2405
  pendingSends: [],
2201
2406
  pendingFragments: new Map,
2202
- pendingRemoteIce: []
2407
+ pendingRemoteIce: [],
2408
+ inFlightSync: undefined,
2409
+ transport: undefined,
2410
+ lastDataChannelError: undefined
2203
2411
  };
2204
2412
  this.slots.set(fromPeerId, slot);
2205
2413
  this.wireConnection(fromPeerId, connection);
@@ -2207,13 +2415,18 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2207
2415
  slot.channel = event.channel;
2208
2416
  this.wireDataChannel(fromPeerId, event.channel);
2209
2417
  };
2210
- await connection.setRemoteDescription(sdp);
2418
+ await connection.setRemoteDescription(this.applySdpPolicyFilter(sdp));
2211
2419
  await this.flushPendingRemoteIce(slot);
2212
2420
  const answer = await connection.createAnswer();
2213
2421
  await connection.setLocalDescription(answer);
2422
+ const localAnswer = connection.localDescription ?? answer;
2423
+ const sdpToSend = this.applySdpPolicyFilter({
2424
+ type: localAnswer.type,
2425
+ sdp: localAnswer.sdp ?? answer.sdp
2426
+ });
2214
2427
  this.signaling.sendSignal(fromPeerId, {
2215
2428
  kind: "answer",
2216
- sdp: answer
2429
+ sdp: sdpToSend
2217
2430
  });
2218
2431
  }
2219
2432
  async handleAnswer(fromPeerId, sdp) {
@@ -2222,13 +2435,15 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2222
2435
  return;
2223
2436
  if (slot.connection.signalingState !== "have-local-offer")
2224
2437
  return;
2225
- await slot.connection.setRemoteDescription(sdp);
2438
+ await slot.connection.setRemoteDescription(this.applySdpPolicyFilter(sdp));
2226
2439
  await this.flushPendingRemoteIce(slot);
2227
2440
  }
2228
2441
  async handleIceCandidate(fromPeerId, candidate) {
2229
2442
  const slot = this.slots.get(fromPeerId);
2230
2443
  if (!slot)
2231
2444
  return;
2445
+ if (this.iceRelayEnforcement && this.iceTransportPolicy === "relay" && !this.isRelayCandidateInit(candidate))
2446
+ return;
2232
2447
  if (slot.connection.remoteDescription === null) {
2233
2448
  slot.pendingRemoteIce.push(candidate);
2234
2449
  return;
@@ -2250,12 +2465,14 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2250
2465
  }
2251
2466
  wireConnection(peerId, connection) {
2252
2467
  connection.onicecandidate = (event) => {
2253
- if (event.candidate) {
2254
- this.signaling.sendSignal(peerId, {
2255
- kind: "ice",
2256
- candidate: event.candidate.toJSON()
2257
- });
2258
- }
2468
+ if (!event.candidate)
2469
+ return;
2470
+ if (!this.shouldSendCandidate(event.candidate))
2471
+ return;
2472
+ this.signaling.sendSignal(peerId, {
2473
+ kind: "ice",
2474
+ candidate: event.candidate.toJSON()
2475
+ });
2259
2476
  };
2260
2477
  connection.onconnectionstatechange = () => {
2261
2478
  const state = connection.connectionState;
@@ -2294,6 +2511,34 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2294
2511
  slot.channel = undefined;
2295
2512
  }
2296
2513
  };
2514
+ channel.onerror = (event) => {
2515
+ const slot = this.slots.get(peerId);
2516
+ if (!slot)
2517
+ return;
2518
+ const ev = event;
2519
+ const message = typeof ev.error?.message === "string" ? ev.error.message : typeof ev.message === "string" ? ev.message : "unknown data channel error";
2520
+ slot.lastDataChannelError = message;
2521
+ };
2522
+ }
2523
+ async refreshTransportStats(peerId) {
2524
+ const slot = this.slots.get(peerId);
2525
+ if (!slot)
2526
+ return;
2527
+ const snapshot = await collectTransportSnapshot(slot.connection, slot.lastDataChannelError);
2528
+ slot.transport = snapshot;
2529
+ return snapshot;
2530
+ }
2531
+ async refreshAllTransportStats() {
2532
+ const out = new Map;
2533
+ const peerIds = [...this.slots.keys()];
2534
+ const results = await Promise.all(peerIds.map((id) => this.refreshTransportStats(id)));
2535
+ for (let i = 0;i < peerIds.length; i += 1) {
2536
+ const r = results[i];
2537
+ const id = peerIds[i];
2538
+ if (r && id !== undefined)
2539
+ out.set(id, r);
2540
+ }
2541
+ return out;
2297
2542
  }
2298
2543
  dispatchMessage(fromPeerId, bytes) {
2299
2544
  try {
@@ -2310,9 +2555,56 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2310
2555
  return;
2311
2556
  }
2312
2557
  const message = this.deserialiseMessage(bytes);
2313
- this.emit("message", message);
2558
+ this.scheduleEmitMessage(fromPeerId, message, false);
2314
2559
  } catch {}
2315
2560
  }
2561
+ scheduleEmitMessage(fromPeerId, message, viaFragmentPath) {
2562
+ if (!this.syncYieldEnabled) {
2563
+ this.emit("message", message);
2564
+ if (viaFragmentPath) {
2565
+ this.finishInFlightSyncApply(fromPeerId);
2566
+ }
2567
+ return;
2568
+ }
2569
+ if (viaFragmentPath) {
2570
+ const slot = this.slots.get(fromPeerId);
2571
+ if (slot?.inFlightSync) {
2572
+ slot.inFlightSync.applyBacklog += 1;
2573
+ }
2574
+ }
2575
+ setTimeout(() => {
2576
+ try {
2577
+ this.emit("message", message);
2578
+ } finally {
2579
+ if (viaFragmentPath) {
2580
+ this.finishInFlightSyncApply(fromPeerId);
2581
+ }
2582
+ }
2583
+ }, 0);
2584
+ }
2585
+ finishInFlightSyncApply(fromPeerId) {
2586
+ const slot = this.slots.get(fromPeerId);
2587
+ if (!slot?.inFlightSync)
2588
+ return;
2589
+ slot.inFlightSync.applyBacklog = Math.max(0, slot.inFlightSync.applyBacklog - 1);
2590
+ this.emitSyncProgress(fromPeerId, "dispatch-applied", 0);
2591
+ if (slot.inFlightSync.applyBacklog === 0 && slot.pendingFragments.size === 0) {
2592
+ slot.inFlightSync = undefined;
2593
+ }
2594
+ }
2595
+ emitSyncProgress(fromPeerId, kind, bytesDelta) {
2596
+ const slot = this.slots.get(fromPeerId);
2597
+ const inFlightSync = slot?.inFlightSync;
2598
+ this.emit("sync-progress", {
2599
+ peerId: fromPeerId,
2600
+ kind,
2601
+ bytesDelta,
2602
+ chunksReceived: inFlightSync?.chunksReceived ?? 0,
2603
+ bytesReceived: inFlightSync?.bytesReceived ?? 0,
2604
+ applyBacklog: inFlightSync?.applyBacklog ?? 0,
2605
+ at: performance.now()
2606
+ });
2607
+ }
2316
2608
  handleSyncFragment(fromPeerId, bytes) {
2317
2609
  const parsed = parseSyncFragment(bytes);
2318
2610
  if (!parsed)
@@ -2327,11 +2619,46 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2327
2619
  slot.pendingFragments.set(header.id, entry);
2328
2620
  }
2329
2621
  entry.chunks.set(header.index, data.slice());
2622
+ if (!slot.inFlightSync) {
2623
+ slot.inFlightSync = {
2624
+ chunksReceived: 0,
2625
+ bytesReceived: 0,
2626
+ lastChunkAt: performance.now(),
2627
+ applyBacklog: 0
2628
+ };
2629
+ }
2630
+ slot.inFlightSync.chunksReceived += 1;
2631
+ slot.inFlightSync.bytesReceived += data.byteLength;
2632
+ slot.inFlightSync.lastChunkAt = performance.now();
2633
+ this.emitSyncProgress(fromPeerId, "fragment-received", data.byteLength);
2330
2634
  if (entry.chunks.size < entry.total)
2331
2635
  return;
2332
2636
  slot.pendingFragments.delete(header.id);
2333
2637
  const reassembled = reassembleSyncFragments(entry.chunks, entry.total);
2334
- this.dispatchMessage(fromPeerId, reassembled);
2638
+ if (!this.syncYieldEnabled) {
2639
+ this.dispatchReassembled(fromPeerId, reassembled);
2640
+ return;
2641
+ }
2642
+ setTimeout(() => {
2643
+ this.dispatchReassembled(fromPeerId, reassembled);
2644
+ }, 0);
2645
+ }
2646
+ dispatchReassembled(fromPeerId, bytes) {
2647
+ try {
2648
+ if (this.onBlobMessage && isBlobMessageType(bytes)) {
2649
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2650
+ const headerLen = view.getUint32(0, false);
2651
+ const header = JSON.parse(new TextDecoder().decode(bytes.subarray(4, 4 + headerLen)));
2652
+ const data = bytes.subarray(4 + headerLen);
2653
+ this.onBlobMessage(fromPeerId, header, data);
2654
+ this.finishInFlightSyncApply(fromPeerId);
2655
+ return;
2656
+ }
2657
+ const message = this.deserialiseMessage(bytes);
2658
+ this.scheduleEmitMessage(fromPeerId, message, true);
2659
+ } catch {
2660
+ this.finishInFlightSyncApply(fromPeerId);
2661
+ }
2335
2662
  }
2336
2663
  get connectedPeerIds() {
2337
2664
  const ids = [];
@@ -2411,6 +2738,12 @@ async function createMeshClient(options) {
2411
2738
  knownPeerIds,
2412
2739
  keyringSource,
2413
2740
  ...resolvedIceServers !== undefined && { iceServers: resolvedIceServers },
2741
+ ...options.rtc?.iceTransportPolicy !== undefined && {
2742
+ iceTransportPolicy: options.rtc.iceTransportPolicy
2743
+ },
2744
+ ...options.rtc?.iceRelayEnforcement !== undefined && {
2745
+ iceRelayEnforcement: options.rtc.iceRelayEnforcement
2746
+ },
2414
2747
  ...options.rtc?.dataChannelLabel !== undefined && {
2415
2748
  dataChannelLabel: options.rtc.dataChannelLabel
2416
2749
  },
@@ -2419,6 +2752,12 @@ async function createMeshClient(options) {
2419
2752
  },
2420
2753
  ...options.rtc?.knownPeersRefreshIntervalMs !== undefined && {
2421
2754
  knownPeersRefreshIntervalMs: options.rtc.knownPeersRefreshIntervalMs
2755
+ },
2756
+ ...options.rtc?.syncYieldEnabled !== undefined && {
2757
+ syncYieldEnabled: options.rtc.syncYieldEnabled
2758
+ },
2759
+ ...options.rtc?.syncFragmentChunkSizeOverride !== undefined && {
2760
+ syncFragmentChunkSizeOverride: options.rtc.syncFragmentChunkSizeOverride
2422
2761
  }
2423
2762
  };
2424
2763
  let webrtcAdapter;
@@ -2479,6 +2818,11 @@ async function createMeshClient(options) {
2479
2818
  }
2480
2819
  return webrtcAdapter.getPeerStateSnapshot();
2481
2820
  },
2821
+ refreshTransportStats: async () => {
2822
+ if (!webrtcAdapter)
2823
+ return;
2824
+ await webrtcAdapter.refreshAllTransportStats();
2825
+ },
2482
2826
  close: async () => {
2483
2827
  signaling.close();
2484
2828
  webrtcAdapter?.disconnect();
@@ -2893,4 +3237,4 @@ export {
2893
3237
  $meshCounter
2894
3238
  };
2895
3239
 
2896
- //# debugId=51457A626E5A2F2B64756E2164756E21
3240
+ //# debugId=15751E988ED47F0264756E2164756E21