@fairfox/polly 0.64.0 → 0.66.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
|
@@ -2364,7 +2364,9 @@ function serialiseSlotView(slot) {
|
|
|
2364
2364
|
selectedCandidatePair: slot.transport.selectedCandidatePair ? { ...slot.transport.selectedCandidatePair } : undefined
|
|
2365
2365
|
} : undefined,
|
|
2366
2366
|
lastSyncHandshakeAttempt: { ...slot.lastSyncHandshakeAttempt },
|
|
2367
|
-
handles
|
|
2367
|
+
handles,
|
|
2368
|
+
createdAt: slot.createdAt,
|
|
2369
|
+
lastInboundAt: slot.lastInboundAt
|
|
2368
2370
|
};
|
|
2369
2371
|
}
|
|
2370
2372
|
function emptySyncHandshakeAttempt() {
|
|
@@ -2391,6 +2393,9 @@ var DEFAULT_ICE_SERVERS = [
|
|
|
2391
2393
|
{ urls: "stun:stun.l.google.com:19302" },
|
|
2392
2394
|
{ urls: "stun:stun1.l.google.com:19302" }
|
|
2393
2395
|
];
|
|
2396
|
+
var SLOT_NEVER_CONNECTED_TIMEOUT_MS = 30000;
|
|
2397
|
+
var SLOT_IDLE_TIMEOUT_MS = 120000;
|
|
2398
|
+
var SLOT_WATCHDOG_INTERVAL_MS = 5000;
|
|
2394
2399
|
function emptyHandleSyncSnapshot() {
|
|
2395
2400
|
return {
|
|
2396
2401
|
lastSyncMessageOutAt: undefined,
|
|
@@ -2421,6 +2426,10 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2421
2426
|
lastSlotInitiationDecisions = new Map;
|
|
2422
2427
|
sweepRunCount = 0;
|
|
2423
2428
|
lastSweepAt;
|
|
2429
|
+
slotWatchdogTimer;
|
|
2430
|
+
slotNeverConnectedTimeoutMs;
|
|
2431
|
+
slotIdleTimeoutMs;
|
|
2432
|
+
slotWatchdogIntervalMs;
|
|
2424
2433
|
get knownPeerIds() {
|
|
2425
2434
|
if (this.keyringSource !== undefined) {
|
|
2426
2435
|
return [...this.keyringSource().knownPeers.keys()].filter((id) => id !== this.localPeerId);
|
|
@@ -2446,6 +2455,9 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2446
2455
|
this.knownPeersRefreshIntervalMs = options.knownPeersRefreshIntervalMs ?? 2000;
|
|
2447
2456
|
this.syncYieldEnabled = options.syncYieldEnabled ?? true;
|
|
2448
2457
|
this.syncFragmentChunkSize = options.syncFragmentChunkSizeOverride ?? SYNC_FRAGMENT_CHUNK_SIZE;
|
|
2458
|
+
this.slotNeverConnectedTimeoutMs = options.slotNeverConnectedTimeoutMs ?? SLOT_NEVER_CONNECTED_TIMEOUT_MS;
|
|
2459
|
+
this.slotIdleTimeoutMs = options.slotIdleTimeoutMs ?? SLOT_IDLE_TIMEOUT_MS;
|
|
2460
|
+
this.slotWatchdogIntervalMs = options.slotWatchdogIntervalMs ?? SLOT_WATCHDOG_INTERVAL_MS;
|
|
2449
2461
|
this.localPeerId = options.peerId;
|
|
2450
2462
|
const PC = options.RTCPeerConnection ?? globalThis.RTCPeerConnection;
|
|
2451
2463
|
if (typeof PC !== "function") {
|
|
@@ -2603,9 +2615,11 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2603
2615
|
this.ready = true;
|
|
2604
2616
|
this.readyResolver?.();
|
|
2605
2617
|
this.startKnownPeersSweep();
|
|
2618
|
+
this.startSlotWatchdog();
|
|
2606
2619
|
}
|
|
2607
2620
|
disconnect() {
|
|
2608
2621
|
this.stopKnownPeersSweep();
|
|
2622
|
+
this.stopSlotWatchdog();
|
|
2609
2623
|
for (const slot of this.slots.values()) {
|
|
2610
2624
|
slot.channel?.close();
|
|
2611
2625
|
slot.connection.close();
|
|
@@ -2636,6 +2650,70 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2636
2650
|
clearInterval(this.knownPeersRefreshTimer);
|
|
2637
2651
|
this.knownPeersRefreshTimer = undefined;
|
|
2638
2652
|
}
|
|
2653
|
+
tearDownWedgedSlot(peerId) {
|
|
2654
|
+
const slot = this.slots.get(peerId);
|
|
2655
|
+
if (!slot)
|
|
2656
|
+
return;
|
|
2657
|
+
this.slots.delete(peerId);
|
|
2658
|
+
try {
|
|
2659
|
+
slot.channel?.close();
|
|
2660
|
+
} catch {}
|
|
2661
|
+
try {
|
|
2662
|
+
slot.connection.close();
|
|
2663
|
+
} catch {}
|
|
2664
|
+
this.emit("peer-disconnected", { peerId });
|
|
2665
|
+
}
|
|
2666
|
+
startSlotWatchdog() {
|
|
2667
|
+
if (this.slotWatchdogIntervalMs <= 0)
|
|
2668
|
+
return;
|
|
2669
|
+
if (this.slotWatchdogTimer !== undefined)
|
|
2670
|
+
return;
|
|
2671
|
+
this.slotWatchdogTimer = setInterval(() => {
|
|
2672
|
+
try {
|
|
2673
|
+
this.sweepWedgedSlots();
|
|
2674
|
+
} catch {}
|
|
2675
|
+
}, this.slotWatchdogIntervalMs);
|
|
2676
|
+
}
|
|
2677
|
+
stopSlotWatchdog() {
|
|
2678
|
+
if (this.slotWatchdogTimer === undefined)
|
|
2679
|
+
return;
|
|
2680
|
+
clearInterval(this.slotWatchdogTimer);
|
|
2681
|
+
this.slotWatchdogTimer = undefined;
|
|
2682
|
+
}
|
|
2683
|
+
sweepWedgedSlots() {
|
|
2684
|
+
const now = performance.now();
|
|
2685
|
+
const peerIds = [...this.slots.keys()];
|
|
2686
|
+
for (const peerId of peerIds) {
|
|
2687
|
+
const slot = this.slots.get(peerId);
|
|
2688
|
+
if (!slot)
|
|
2689
|
+
continue;
|
|
2690
|
+
const reason = this.classifyWedgedSlot(slot, now);
|
|
2691
|
+
if (!reason)
|
|
2692
|
+
continue;
|
|
2693
|
+
this.lastSlotInitiationDecisions.set(peerId, {
|
|
2694
|
+
decision: "rejected",
|
|
2695
|
+
reason: "fatal-error",
|
|
2696
|
+
error: reason,
|
|
2697
|
+
at: now
|
|
2698
|
+
});
|
|
2699
|
+
this.tearDownWedgedSlot(peerId);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
classifyWedgedSlot(slot, now) {
|
|
2703
|
+
const state = slot.connection.connectionState;
|
|
2704
|
+
const ageMs = now - slot.createdAt;
|
|
2705
|
+
if (this.slotNeverConnectedTimeoutMs > 0 && (state === "new" || state === "connecting") && ageMs > this.slotNeverConnectedTimeoutMs) {
|
|
2706
|
+
return `slot never reached connected (state=${state}) after ${Math.round(ageMs)}ms`;
|
|
2707
|
+
}
|
|
2708
|
+
if (this.slotIdleTimeoutMs > 0 && state === "connected" && slot.channel?.readyState === "open") {
|
|
2709
|
+
const lastInbound = slot.lastInboundAt ?? slot.createdAt;
|
|
2710
|
+
const idleMs = now - lastInbound;
|
|
2711
|
+
if (idleMs > this.slotIdleTimeoutMs) {
|
|
2712
|
+
return `slot idle: no inbound bytes for ${Math.round(idleMs)}ms (state=connected, dc=open)`;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2639
2717
|
send(message) {
|
|
2640
2718
|
const targetId = message.targetId;
|
|
2641
2719
|
const bytes = this.serialiseMessage(message);
|
|
@@ -2771,12 +2849,23 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2771
2849
|
transport: undefined,
|
|
2772
2850
|
lastDataChannelError: undefined,
|
|
2773
2851
|
lastSyncHandshakeAttempt: emptySyncHandshakeAttempt(),
|
|
2774
|
-
handles: new Map
|
|
2852
|
+
handles: new Map,
|
|
2853
|
+
createdAt: performance.now(),
|
|
2854
|
+
lastInboundAt: undefined
|
|
2775
2855
|
};
|
|
2776
2856
|
this.slots.set(targetId, slot);
|
|
2777
2857
|
this.wireConnection(targetId, connection);
|
|
2778
2858
|
this.wireDataChannel(targetId, channel);
|
|
2779
|
-
this.initiateOffer(targetId, connection)
|
|
2859
|
+
this.initiateOffer(targetId, connection).catch((err) => {
|
|
2860
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2861
|
+
this.lastSlotInitiationDecisions.set(targetId, {
|
|
2862
|
+
decision: "rejected",
|
|
2863
|
+
reason: "fatal-error",
|
|
2864
|
+
error: message,
|
|
2865
|
+
at: performance.now()
|
|
2866
|
+
});
|
|
2867
|
+
this.tearDownWedgedSlot(targetId);
|
|
2868
|
+
});
|
|
2780
2869
|
return slot;
|
|
2781
2870
|
}
|
|
2782
2871
|
async initiateOffer(targetId, connection) {
|
|
@@ -2814,7 +2903,9 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2814
2903
|
transport: undefined,
|
|
2815
2904
|
lastDataChannelError: undefined,
|
|
2816
2905
|
lastSyncHandshakeAttempt: emptySyncHandshakeAttempt(),
|
|
2817
|
-
handles: new Map
|
|
2906
|
+
handles: new Map,
|
|
2907
|
+
createdAt: performance.now(),
|
|
2908
|
+
lastInboundAt: undefined
|
|
2818
2909
|
};
|
|
2819
2910
|
this.slots.set(fromPeerId, slot);
|
|
2820
2911
|
this.wireConnection(fromPeerId, connection);
|
|
@@ -2886,6 +2977,8 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2886
2977
|
if (state === "connected") {
|
|
2887
2978
|
this.emitPeerCandidateOnce(peerId);
|
|
2888
2979
|
} else if (state === "disconnected" || state === "failed" || state === "closed") {
|
|
2980
|
+
if (!this.slots.has(peerId))
|
|
2981
|
+
return;
|
|
2889
2982
|
this.slots.delete(peerId);
|
|
2890
2983
|
this.emit("peer-disconnected", { peerId });
|
|
2891
2984
|
}
|
|
@@ -2916,6 +3009,9 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2916
3009
|
slot.pendingSends = [];
|
|
2917
3010
|
};
|
|
2918
3011
|
channel.onmessage = (event) => {
|
|
3012
|
+
const liveSlot = this.slots.get(peerId);
|
|
3013
|
+
if (liveSlot)
|
|
3014
|
+
liveSlot.lastInboundAt = performance.now();
|
|
2919
3015
|
const data = event.data;
|
|
2920
3016
|
if (data instanceof ArrayBuffer) {
|
|
2921
3017
|
this.dispatchMessage(peerId, new Uint8Array(data));
|
|
@@ -3165,14 +3261,49 @@ async function resolveIceServers(rtc) {
|
|
|
3165
3261
|
}
|
|
3166
3262
|
return rtc?.iceServers;
|
|
3167
3263
|
}
|
|
3168
|
-
function buildHandleEntry(state, wire) {
|
|
3264
|
+
function buildHandleEntry(state, wire, syncStateView) {
|
|
3169
3265
|
return {
|
|
3170
3266
|
state,
|
|
3171
3267
|
announcedToPeer: wire?.lastSyncMessageOutAt !== undefined,
|
|
3172
3268
|
lastSyncMessageOutAt: wire?.lastSyncMessageOutAt,
|
|
3173
3269
|
lastSyncMessageInAt: wire?.lastSyncMessageInAt,
|
|
3174
3270
|
lastSyncMessageOutSize: wire?.lastSyncMessageOutSize,
|
|
3175
|
-
lastSyncMessageOutType: wire?.lastSyncMessageOutType
|
|
3271
|
+
lastSyncMessageOutType: wire?.lastSyncMessageOutType,
|
|
3272
|
+
docSynchronizerExists: syncStateView.docSynchronizerExists,
|
|
3273
|
+
docSynchronizerKnowsPeer: syncStateView.docSynchronizerKnowsPeer,
|
|
3274
|
+
peerDocumentStatus: syncStateView.peerDocumentStatus
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
var EMPTY_SYNC_VIEW = {
|
|
3278
|
+
docSynchronizerExists: false,
|
|
3279
|
+
docSynchronizerKnowsPeer: undefined,
|
|
3280
|
+
peerDocumentStatus: undefined
|
|
3281
|
+
};
|
|
3282
|
+
function getCollectionSynchronizer(repo) {
|
|
3283
|
+
const sync = repo.synchronizer;
|
|
3284
|
+
return sync && typeof sync === "object" ? sync : undefined;
|
|
3285
|
+
}
|
|
3286
|
+
function buildSyncView(synchronizer, docId, peerId) {
|
|
3287
|
+
const docSync = synchronizer?.docSynchronizers?.[docId];
|
|
3288
|
+
if (!docSync)
|
|
3289
|
+
return EMPTY_SYNC_VIEW;
|
|
3290
|
+
let knowsPeer;
|
|
3291
|
+
try {
|
|
3292
|
+
knowsPeer = typeof docSync.hasPeer === "function" ? docSync.hasPeer(peerId) : undefined;
|
|
3293
|
+
} catch {
|
|
3294
|
+
knowsPeer = undefined;
|
|
3295
|
+
}
|
|
3296
|
+
let status;
|
|
3297
|
+
try {
|
|
3298
|
+
const states = docSync.peerStates;
|
|
3299
|
+
status = states && typeof states === "object" ? states[peerId] : undefined;
|
|
3300
|
+
} catch {
|
|
3301
|
+
status = undefined;
|
|
3302
|
+
}
|
|
3303
|
+
return {
|
|
3304
|
+
docSynchronizerExists: true,
|
|
3305
|
+
docSynchronizerKnowsPeer: knowsPeer,
|
|
3306
|
+
peerDocumentStatus: status
|
|
3176
3307
|
};
|
|
3177
3308
|
}
|
|
3178
3309
|
function stringifyHandleState(handle) {
|
|
@@ -3180,18 +3311,19 @@ function stringifyHandleState(handle) {
|
|
|
3180
3311
|
return "unknown";
|
|
3181
3312
|
return typeof handle.state === "string" ? handle.state : String(handle.state ?? "unknown");
|
|
3182
3313
|
}
|
|
3183
|
-
function enrichPeerSlot(peer, knownHandleIds, repoHandles) {
|
|
3314
|
+
function enrichPeerSlot(peer, knownHandleIds, repoHandles, synchronizer) {
|
|
3184
3315
|
if (!peer.slot) {
|
|
3185
3316
|
return { ...peer, slot: undefined };
|
|
3186
3317
|
}
|
|
3318
|
+
const peerIdString = peer.peerId;
|
|
3187
3319
|
const enriched = {};
|
|
3188
3320
|
for (const docId of knownHandleIds) {
|
|
3189
|
-
enriched[docId] = buildHandleEntry(stringifyHandleState(repoHandles[docId]), peer.slot.handles[docId]);
|
|
3321
|
+
enriched[docId] = buildHandleEntry(stringifyHandleState(repoHandles[docId]), peer.slot.handles[docId], buildSyncView(synchronizer, docId, peerIdString));
|
|
3190
3322
|
}
|
|
3191
3323
|
for (const docId of Object.keys(peer.slot.handles)) {
|
|
3192
3324
|
if (enriched[docId])
|
|
3193
3325
|
continue;
|
|
3194
|
-
enriched[docId] = buildHandleEntry("unknown", peer.slot.handles[docId]);
|
|
3326
|
+
enriched[docId] = buildHandleEntry("unknown", peer.slot.handles[docId], buildSyncView(synchronizer, docId, peerIdString));
|
|
3195
3327
|
}
|
|
3196
3328
|
return { ...peer, slot: { ...peer.slot, handles: enriched } };
|
|
3197
3329
|
}
|
|
@@ -3353,7 +3485,8 @@ async function createMeshClient(options) {
|
|
|
3353
3485
|
const base = webrtcAdapter.getPeerStateSnapshot();
|
|
3354
3486
|
const repoHandles = repo.handles;
|
|
3355
3487
|
const knownHandleIds = Object.keys(repoHandles);
|
|
3356
|
-
const
|
|
3488
|
+
const synchronizer = getCollectionSynchronizer(repo);
|
|
3489
|
+
const enrichedPeers = base.peers.map((peer) => enrichPeerSlot(peer, knownHandleIds, repoHandles, synchronizer));
|
|
3357
3490
|
const out = {
|
|
3358
3491
|
localPeerId: base.localPeerId,
|
|
3359
3492
|
knownPeerIds: base.knownPeerIds,
|
|
@@ -3807,4 +3940,4 @@ export {
|
|
|
3807
3940
|
$meshCounter
|
|
3808
3941
|
};
|
|
3809
3942
|
|
|
3810
|
-
//# debugId=
|
|
3943
|
+
//# debugId=E998FF56BA12701D64756E2164756E21
|