@fairfox/polly 0.55.0 → 0.56.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.
@@ -29,7 +29,7 @@ export type { MeshSignalingClientOptions, SignalingMessage as MeshSignalingMessa
29
29
  export { MeshSignalingClient } from "./shared/lib/mesh-signaling-client";
30
30
  export type { MeshStateOptions } from "./shared/lib/mesh-state";
31
31
  export { $meshCounter, $meshList, $meshState, $meshText, configureMeshState, resetMeshState, } from "./shared/lib/mesh-state";
32
- export type { MeshWebRTCAdapterOptions } from "./shared/lib/mesh-webrtc-adapter";
32
+ export type { InFlightSyncSnapshot, MeshWebRTCAdapterOptions, SlotInitiationDecision, SlotInitiationRejectionReason, SweepSnapshot, SyncHandshakeAttemptSnapshot, SyncProgressEvent, TransportSnapshot, } from "./shared/lib/mesh-webrtc-adapter";
33
33
  export { DEFAULT_ICE_SERVERS, MeshWebRTCAdapter } from "./shared/lib/mesh-webrtc-adapter";
34
34
  export type { CreatePairingTokenOptions, PairingToken, } from "./shared/lib/pairing";
35
35
  export { applyPairingToken, createPairingToken, createPairingTokenWithFreshIdentity, DEFAULT_PAIRING_TTL_MS, decodePairingToken, encodePairingToken, isPairingTokenExpired, PAIRING_NONCE_BYTES, PAIRING_TOKEN_VERSION, PairingError, parsePairingToken, serialisePairingToken, } from "./shared/lib/pairing";
package/dist/src/mesh.js CHANGED
@@ -2044,7 +2044,16 @@ function serialiseSlotView(slot) {
2044
2044
  transport: slot.transport ? {
2045
2045
  ...slot.transport,
2046
2046
  selectedCandidatePair: slot.transport.selectedCandidatePair ? { ...slot.transport.selectedCandidatePair } : undefined
2047
- } : undefined
2047
+ } : undefined,
2048
+ lastSyncHandshakeAttempt: { ...slot.lastSyncHandshakeAttempt }
2049
+ };
2050
+ }
2051
+ function emptySyncHandshakeAttempt() {
2052
+ return {
2053
+ dataChannelOpenedAt: undefined,
2054
+ peerCandidateEmittedAt: undefined,
2055
+ firstOutboundSendAt: undefined,
2056
+ firstInboundMessageAt: undefined
2048
2057
  };
2049
2058
  }
2050
2059
  function buildCandidatePairView(pair, localCands, remoteCands) {
@@ -2082,6 +2091,9 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2082
2091
  slots = new Map;
2083
2092
  ready = false;
2084
2093
  readyResolver;
2094
+ lastSlotInitiationDecisions = new Map;
2095
+ sweepRunCount = 0;
2096
+ lastSweepAt;
2085
2097
  get knownPeerIds() {
2086
2098
  if (this.keyringSource !== undefined) {
2087
2099
  return [...this.keyringSource().knownPeers.keys()].filter((id) => id !== this.localPeerId);
@@ -2134,10 +2146,13 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2134
2146
  const peers = [];
2135
2147
  for (const peerId of allPeers) {
2136
2148
  const slot = this.slots.get(peerId);
2149
+ const decision = this.snapshotInitiationDecision(peerId);
2137
2150
  peers.push({
2138
2151
  peerId,
2139
2152
  knownInKeyring: knownPeerSet.has(peerId),
2140
2153
  presentInSignalling: this.presentPeers.has(peerId),
2154
+ slotInitiationRejectionReason: decision.reason,
2155
+ slotInitiationDecision: decision,
2141
2156
  slot: slot ? serialiseSlotView(slot) : undefined
2142
2157
  });
2143
2158
  }
@@ -2145,6 +2160,12 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2145
2160
  localPeerId: this.localPeerId,
2146
2161
  knownPeerIds,
2147
2162
  presentPeerIds,
2163
+ sweep: {
2164
+ enabled: this.knownPeersRefreshTimer !== undefined,
2165
+ intervalMs: this.knownPeersRefreshIntervalMs,
2166
+ runCount: this.sweepRunCount,
2167
+ lastRunAt: this.lastSweepAt
2168
+ },
2148
2169
  peers
2149
2170
  };
2150
2171
  }
@@ -2152,14 +2173,27 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2152
2173
  this.presentPeers.add(remotePeerId);
2153
2174
  if (!this.shouldInitiateTo(remotePeerId))
2154
2175
  return;
2155
- this.createInitiatingSlot(remotePeerId);
2176
+ this.tryCreateInitiatingSlot(remotePeerId);
2156
2177
  }
2157
2178
  handlePeersPresent(peerIds) {
2158
2179
  for (const remotePeerId of peerIds) {
2159
2180
  this.presentPeers.add(remotePeerId);
2160
2181
  if (!this.shouldInitiateTo(remotePeerId))
2161
2182
  continue;
2183
+ this.tryCreateInitiatingSlot(remotePeerId);
2184
+ }
2185
+ }
2186
+ tryCreateInitiatingSlot(remotePeerId) {
2187
+ try {
2162
2188
  this.createInitiatingSlot(remotePeerId);
2189
+ } catch (err) {
2190
+ const message = err instanceof Error ? err.message : String(err);
2191
+ this.lastSlotInitiationDecisions.set(remotePeerId, {
2192
+ decision: "rejected",
2193
+ reason: "fatal-error",
2194
+ error: message,
2195
+ at: performance.now()
2196
+ });
2163
2197
  }
2164
2198
  }
2165
2199
  handlePeerLeft(remotePeerId) {
@@ -2183,25 +2217,49 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2183
2217
  return;
2184
2218
  if (!this.shouldInitiateTo(remotePeerId))
2185
2219
  return;
2186
- this.createInitiatingSlot(remotePeerId);
2220
+ this.tryCreateInitiatingSlot(remotePeerId);
2187
2221
  }
2188
2222
  refreshKnownPeers() {
2189
2223
  for (const remotePeerId of this.presentPeers) {
2190
2224
  if (!this.shouldInitiateTo(remotePeerId))
2191
2225
  continue;
2192
- this.createInitiatingSlot(remotePeerId);
2226
+ this.tryCreateInitiatingSlot(remotePeerId);
2193
2227
  }
2194
2228
  }
2195
2229
  shouldInitiateTo(remotePeerId) {
2230
+ const reason = this.evaluateInitiation(remotePeerId);
2231
+ this.lastSlotInitiationDecisions.set(remotePeerId, {
2232
+ decision: reason === undefined ? "accepted" : "rejected",
2233
+ reason,
2234
+ error: undefined,
2235
+ at: performance.now()
2236
+ });
2237
+ return reason === undefined;
2238
+ }
2239
+ evaluateInitiation(remotePeerId) {
2196
2240
  if (remotePeerId === this.localPeerId)
2197
- return false;
2241
+ return "self";
2198
2242
  if (!this.hasKnownPeer(remotePeerId))
2199
- return false;
2243
+ return "not-in-keyring";
2244
+ if (!this.presentPeers.has(remotePeerId))
2245
+ return "not-present";
2200
2246
  if (this.slots.has(remotePeerId))
2201
- return false;
2247
+ return "slot-already-exists";
2202
2248
  if (this.localPeerId <= remotePeerId)
2203
- return false;
2204
- return true;
2249
+ return "tie-break-other-side";
2250
+ return;
2251
+ }
2252
+ snapshotInitiationDecision(remotePeerId) {
2253
+ const cached = this.lastSlotInitiationDecisions.get(remotePeerId);
2254
+ if (cached?.reason === "fatal-error")
2255
+ return cached;
2256
+ const reason = this.evaluateInitiation(remotePeerId);
2257
+ return {
2258
+ decision: reason === undefined ? "accepted" : "rejected",
2259
+ reason,
2260
+ error: undefined,
2261
+ at: performance.now()
2262
+ };
2205
2263
  }
2206
2264
  whenReady() {
2207
2265
  if (this.ready)
@@ -2238,7 +2296,11 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2238
2296
  if (this.knownPeersRefreshTimer !== undefined)
2239
2297
  return;
2240
2298
  this.knownPeersRefreshTimer = setInterval(() => {
2241
- this.refreshKnownPeers();
2299
+ this.sweepRunCount += 1;
2300
+ this.lastSweepAt = performance.now();
2301
+ try {
2302
+ this.refreshKnownPeers();
2303
+ } catch {}
2242
2304
  }, this.knownPeersRefreshIntervalMs);
2243
2305
  }
2244
2306
  stopKnownPeersSweep() {
@@ -2254,6 +2316,9 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2254
2316
  if (!slot) {
2255
2317
  slot = this.createInitiatingSlot(targetId);
2256
2318
  }
2319
+ if (slot.lastSyncHandshakeAttempt.firstOutboundSendAt === undefined) {
2320
+ slot.lastSyncHandshakeAttempt.firstOutboundSendAt = performance.now();
2321
+ }
2257
2322
  if (slot.channel && slot.channel.readyState === "open") {
2258
2323
  this.sendBytesMaybeFragmented(slot.channel, bytes);
2259
2324
  } else {
@@ -2366,7 +2431,8 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2366
2431
  pendingRemoteIce: [],
2367
2432
  inFlightSync: undefined,
2368
2433
  transport: undefined,
2369
- lastDataChannelError: undefined
2434
+ lastDataChannelError: undefined,
2435
+ lastSyncHandshakeAttempt: emptySyncHandshakeAttempt()
2370
2436
  };
2371
2437
  this.slots.set(targetId, slot);
2372
2438
  this.wireConnection(targetId, connection);
@@ -2407,7 +2473,8 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2407
2473
  pendingRemoteIce: [],
2408
2474
  inFlightSync: undefined,
2409
2475
  transport: undefined,
2410
- lastDataChannelError: undefined
2476
+ lastDataChannelError: undefined,
2477
+ lastSyncHandshakeAttempt: emptySyncHandshakeAttempt()
2411
2478
  };
2412
2479
  this.slots.set(fromPeerId, slot);
2413
2480
  this.wireConnection(fromPeerId, connection);
@@ -2477,21 +2544,32 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2477
2544
  connection.onconnectionstatechange = () => {
2478
2545
  const state = connection.connectionState;
2479
2546
  if (state === "connected") {
2480
- this.emit("peer-candidate", {
2481
- peerId,
2482
- peerMetadata: {}
2483
- });
2547
+ this.emitPeerCandidateOnce(peerId);
2484
2548
  } else if (state === "disconnected" || state === "failed" || state === "closed") {
2485
2549
  this.slots.delete(peerId);
2486
2550
  this.emit("peer-disconnected", { peerId });
2487
2551
  }
2488
2552
  };
2489
2553
  }
2554
+ emitPeerCandidateOnce(peerId) {
2555
+ const slot = this.slots.get(peerId);
2556
+ if (!slot)
2557
+ return;
2558
+ if (slot.lastSyncHandshakeAttempt.peerCandidateEmittedAt !== undefined)
2559
+ return;
2560
+ slot.lastSyncHandshakeAttempt.peerCandidateEmittedAt = performance.now();
2561
+ this.emit("peer-candidate", {
2562
+ peerId,
2563
+ peerMetadata: {}
2564
+ });
2565
+ }
2490
2566
  wireDataChannel(peerId, channel) {
2491
2567
  channel.onopen = () => {
2492
2568
  const slot = this.slots.get(peerId);
2493
2569
  if (!slot)
2494
2570
  return;
2571
+ slot.lastSyncHandshakeAttempt.dataChannelOpenedAt = performance.now();
2572
+ this.emitPeerCandidateOnce(peerId);
2495
2573
  for (const bytes of slot.pendingSends) {
2496
2574
  this.sendBytesMaybeFragmented(channel, bytes);
2497
2575
  }
@@ -2559,6 +2637,7 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2559
2637
  } catch {}
2560
2638
  }
2561
2639
  scheduleEmitMessage(fromPeerId, message, viaFragmentPath) {
2640
+ this.stampFirstInboundMessage(fromPeerId);
2562
2641
  if (!this.syncYieldEnabled) {
2563
2642
  this.emit("message", message);
2564
2643
  if (viaFragmentPath) {
@@ -2582,6 +2661,14 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
2582
2661
  }
2583
2662
  }, 0);
2584
2663
  }
2664
+ stampFirstInboundMessage(fromPeerId) {
2665
+ const slot = this.slots.get(fromPeerId);
2666
+ if (!slot)
2667
+ return;
2668
+ if (slot.lastSyncHandshakeAttempt.firstInboundMessageAt !== undefined)
2669
+ return;
2670
+ slot.lastSyncHandshakeAttempt.firstInboundMessageAt = performance.now();
2671
+ }
2585
2672
  finishInFlightSyncApply(fromPeerId) {
2586
2673
  const slot = this.slots.get(fromPeerId);
2587
2674
  if (!slot?.inFlightSync)
@@ -2813,6 +2900,12 @@ async function createMeshClient(options) {
2813
2900
  localPeerId: options.signaling.peerId,
2814
2901
  knownPeerIds: [],
2815
2902
  presentPeerIds: [],
2903
+ sweep: {
2904
+ enabled: false,
2905
+ intervalMs: 0,
2906
+ runCount: 0,
2907
+ lastRunAt: undefined
2908
+ },
2816
2909
  peers: []
2817
2910
  };
2818
2911
  }
@@ -3237,4 +3330,4 @@ export {
3237
3330
  $meshCounter
3238
3331
  };
3239
3332
 
3240
- //# debugId=15751E988ED47F0264756E2164756E21
3333
+ //# debugId=27D9577FC215E14564756E2164756E21