@fairfox/polly 0.52.0 → 0.54.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 +217 -13
- package/dist/src/mesh.js.map +5 -5
- package/dist/src/polly-ui/markdown.js +2 -3
- package/dist/src/polly-ui/markdown.js.map +2 -2
- package/dist/src/shared/lib/mesh-client.d.ts +43 -0
- package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +248 -7
- package/dist/src/shared/lib/sync-fragment.d.ts +16 -5
- package/dist/tools/test/src/visual/index.js +37 -11
- package/dist/tools/test/src/visual/index.js.map +4 -4
- package/package.json +1 -1
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 =
|
|
1853
|
-
var SYNC_FRAGMENT_CHUNK_SIZE =
|
|
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;
|
|
@@ -1943,6 +1943,11 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
1943
1943
|
iceServers;
|
|
1944
1944
|
dataChannelLabel;
|
|
1945
1945
|
knownPeers;
|
|
1946
|
+
keyringSource;
|
|
1947
|
+
knownPeersRefreshTimer;
|
|
1948
|
+
knownPeersRefreshIntervalMs;
|
|
1949
|
+
syncYieldEnabled;
|
|
1950
|
+
syncFragmentChunkSize;
|
|
1946
1951
|
presentPeers = new Set;
|
|
1947
1952
|
localPeerId;
|
|
1948
1953
|
RTCPeerConnectionCtor;
|
|
@@ -1950,8 +1955,17 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
1950
1955
|
ready = false;
|
|
1951
1956
|
readyResolver;
|
|
1952
1957
|
get knownPeerIds() {
|
|
1958
|
+
if (this.keyringSource !== undefined) {
|
|
1959
|
+
return [...this.keyringSource().knownPeers.keys()].filter((id) => id !== this.localPeerId);
|
|
1960
|
+
}
|
|
1953
1961
|
return [...this.knownPeers];
|
|
1954
1962
|
}
|
|
1963
|
+
hasKnownPeer(remotePeerId) {
|
|
1964
|
+
if (this.keyringSource !== undefined) {
|
|
1965
|
+
return this.keyringSource().knownPeers.has(remotePeerId);
|
|
1966
|
+
}
|
|
1967
|
+
return this.knownPeers.has(remotePeerId);
|
|
1968
|
+
}
|
|
1955
1969
|
onBlobMessage;
|
|
1956
1970
|
constructor(options) {
|
|
1957
1971
|
super();
|
|
@@ -1959,6 +1973,10 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
1959
1973
|
this.iceServers = options.iceServers ?? DEFAULT_ICE_SERVERS;
|
|
1960
1974
|
this.dataChannelLabel = options.dataChannelLabel ?? "polly-mesh";
|
|
1961
1975
|
this.knownPeers = new Set(options.knownPeerIds ?? []);
|
|
1976
|
+
this.keyringSource = options.keyringSource;
|
|
1977
|
+
this.knownPeersRefreshIntervalMs = options.knownPeersRefreshIntervalMs ?? 2000;
|
|
1978
|
+
this.syncYieldEnabled = options.syncYieldEnabled ?? true;
|
|
1979
|
+
this.syncFragmentChunkSize = options.syncFragmentChunkSizeOverride ?? SYNC_FRAGMENT_CHUNK_SIZE;
|
|
1962
1980
|
this.localPeerId = options.peerId;
|
|
1963
1981
|
const PC = options.RTCPeerConnection ?? globalThis.RTCPeerConnection;
|
|
1964
1982
|
if (typeof PC !== "function") {
|
|
@@ -1972,6 +1990,42 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
1972
1990
|
peerSlotCount() {
|
|
1973
1991
|
return this.slots.size;
|
|
1974
1992
|
}
|
|
1993
|
+
getPeerStateSnapshot() {
|
|
1994
|
+
const knownPeerIds = this.knownPeerIds;
|
|
1995
|
+
const presentPeerIds = [...this.presentPeers];
|
|
1996
|
+
const knownPeerSet = new Set(knownPeerIds);
|
|
1997
|
+
const allPeers = new Set;
|
|
1998
|
+
for (const id of knownPeerIds)
|
|
1999
|
+
allPeers.add(id);
|
|
2000
|
+
for (const id of presentPeerIds)
|
|
2001
|
+
allPeers.add(id);
|
|
2002
|
+
for (const id of this.slots.keys())
|
|
2003
|
+
allPeers.add(id);
|
|
2004
|
+
const peers = [];
|
|
2005
|
+
for (const peerId of allPeers) {
|
|
2006
|
+
const slot = this.slots.get(peerId);
|
|
2007
|
+
peers.push({
|
|
2008
|
+
peerId,
|
|
2009
|
+
knownInKeyring: knownPeerSet.has(peerId),
|
|
2010
|
+
presentInSignalling: this.presentPeers.has(peerId),
|
|
2011
|
+
slot: slot ? {
|
|
2012
|
+
signalingState: slot.connection.signalingState,
|
|
2013
|
+
iceConnectionState: slot.connection.iceConnectionState,
|
|
2014
|
+
connectionState: slot.connection.connectionState,
|
|
2015
|
+
dataChannelState: slot.channel?.readyState ?? "no-channel",
|
|
2016
|
+
pendingSendCount: slot.pendingSends.length,
|
|
2017
|
+
pendingRemoteIceCount: slot.pendingRemoteIce.length,
|
|
2018
|
+
inFlightSync: slot.inFlightSync ? { ...slot.inFlightSync } : undefined
|
|
2019
|
+
} : undefined
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
return {
|
|
2023
|
+
localPeerId: this.localPeerId,
|
|
2024
|
+
knownPeerIds,
|
|
2025
|
+
presentPeerIds,
|
|
2026
|
+
peers
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
1975
2029
|
handlePeerJoined(remotePeerId) {
|
|
1976
2030
|
this.presentPeers.add(remotePeerId);
|
|
1977
2031
|
if (!this.shouldInitiateTo(remotePeerId))
|
|
@@ -1998,19 +2052,28 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
1998
2052
|
addKnownPeer(remotePeerId) {
|
|
1999
2053
|
if (remotePeerId === this.localPeerId)
|
|
2000
2054
|
return;
|
|
2001
|
-
if (this.
|
|
2002
|
-
|
|
2003
|
-
|
|
2055
|
+
if (this.keyringSource === undefined) {
|
|
2056
|
+
if (this.knownPeers.has(remotePeerId))
|
|
2057
|
+
return;
|
|
2058
|
+
this.knownPeers.add(remotePeerId);
|
|
2059
|
+
}
|
|
2004
2060
|
if (!this.presentPeers.has(remotePeerId))
|
|
2005
2061
|
return;
|
|
2006
2062
|
if (!this.shouldInitiateTo(remotePeerId))
|
|
2007
2063
|
return;
|
|
2008
2064
|
this.createInitiatingSlot(remotePeerId);
|
|
2009
2065
|
}
|
|
2066
|
+
refreshKnownPeers() {
|
|
2067
|
+
for (const remotePeerId of this.presentPeers) {
|
|
2068
|
+
if (!this.shouldInitiateTo(remotePeerId))
|
|
2069
|
+
continue;
|
|
2070
|
+
this.createInitiatingSlot(remotePeerId);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2010
2073
|
shouldInitiateTo(remotePeerId) {
|
|
2011
2074
|
if (remotePeerId === this.localPeerId)
|
|
2012
2075
|
return false;
|
|
2013
|
-
if (!this.
|
|
2076
|
+
if (!this.hasKnownPeer(remotePeerId))
|
|
2014
2077
|
return false;
|
|
2015
2078
|
if (this.slots.has(remotePeerId))
|
|
2016
2079
|
return false;
|
|
@@ -2032,8 +2095,10 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2032
2095
|
}
|
|
2033
2096
|
this.ready = true;
|
|
2034
2097
|
this.readyResolver?.();
|
|
2098
|
+
this.startKnownPeersSweep();
|
|
2035
2099
|
}
|
|
2036
2100
|
disconnect() {
|
|
2101
|
+
this.stopKnownPeersSweep();
|
|
2037
2102
|
for (const slot of this.slots.values()) {
|
|
2038
2103
|
slot.channel?.close();
|
|
2039
2104
|
slot.connection.close();
|
|
@@ -2043,6 +2108,23 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2043
2108
|
this.ready = false;
|
|
2044
2109
|
this.emit("close");
|
|
2045
2110
|
}
|
|
2111
|
+
startKnownPeersSweep() {
|
|
2112
|
+
if (this.keyringSource === undefined)
|
|
2113
|
+
return;
|
|
2114
|
+
if (this.knownPeersRefreshIntervalMs <= 0)
|
|
2115
|
+
return;
|
|
2116
|
+
if (this.knownPeersRefreshTimer !== undefined)
|
|
2117
|
+
return;
|
|
2118
|
+
this.knownPeersRefreshTimer = setInterval(() => {
|
|
2119
|
+
this.refreshKnownPeers();
|
|
2120
|
+
}, this.knownPeersRefreshIntervalMs);
|
|
2121
|
+
}
|
|
2122
|
+
stopKnownPeersSweep() {
|
|
2123
|
+
if (this.knownPeersRefreshTimer === undefined)
|
|
2124
|
+
return;
|
|
2125
|
+
clearInterval(this.knownPeersRefreshTimer);
|
|
2126
|
+
this.knownPeersRefreshTimer = undefined;
|
|
2127
|
+
}
|
|
2046
2128
|
send(message) {
|
|
2047
2129
|
const targetId = message.targetId;
|
|
2048
2130
|
const bytes = this.serialiseMessage(message);
|
|
@@ -2056,15 +2138,29 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2056
2138
|
slot.pendingSends.push(bytes);
|
|
2057
2139
|
}
|
|
2058
2140
|
}
|
|
2059
|
-
|
|
2141
|
+
static SEND_YIELD_EVERY_N_FRAGMENTS = 8;
|
|
2142
|
+
async sendBytesMaybeFragmented(channel, bytes) {
|
|
2060
2143
|
if (bytes.length <= SYNC_FRAGMENT_THRESHOLD) {
|
|
2061
2144
|
channel.send(bytes);
|
|
2062
2145
|
return;
|
|
2063
2146
|
}
|
|
2064
2147
|
const id = crypto.randomUUID();
|
|
2065
|
-
const fragments = chunkSyncMessage(bytes, id);
|
|
2148
|
+
const fragments = chunkSyncMessage(bytes, id, this.syncFragmentChunkSize);
|
|
2149
|
+
if (!this.syncYieldEnabled) {
|
|
2150
|
+
for (const fragment of fragments) {
|
|
2151
|
+
channel.send(fragment);
|
|
2152
|
+
}
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
let i = 0;
|
|
2066
2156
|
for (const fragment of fragments) {
|
|
2067
2157
|
channel.send(fragment);
|
|
2158
|
+
i += 1;
|
|
2159
|
+
if (i % MeshWebRTCAdapter.SEND_YIELD_EVERY_N_FRAGMENTS === 0 && i < fragments.length) {
|
|
2160
|
+
await new Promise((resolve) => {
|
|
2161
|
+
setTimeout(resolve, 0);
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2068
2164
|
}
|
|
2069
2165
|
}
|
|
2070
2166
|
handleSignal(fromPeerId, rawPayload) {
|
|
@@ -2092,7 +2188,8 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2092
2188
|
channel,
|
|
2093
2189
|
pendingSends: [],
|
|
2094
2190
|
pendingFragments: new Map,
|
|
2095
|
-
pendingRemoteIce: []
|
|
2191
|
+
pendingRemoteIce: [],
|
|
2192
|
+
inFlightSync: undefined
|
|
2096
2193
|
};
|
|
2097
2194
|
this.slots.set(targetId, slot);
|
|
2098
2195
|
this.wireConnection(targetId, connection);
|
|
@@ -2122,7 +2219,8 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2122
2219
|
channel: undefined,
|
|
2123
2220
|
pendingSends: [],
|
|
2124
2221
|
pendingFragments: new Map,
|
|
2125
|
-
pendingRemoteIce: []
|
|
2222
|
+
pendingRemoteIce: [],
|
|
2223
|
+
inFlightSync: undefined
|
|
2126
2224
|
};
|
|
2127
2225
|
this.slots.set(fromPeerId, slot);
|
|
2128
2226
|
this.wireConnection(fromPeerId, connection);
|
|
@@ -2233,9 +2331,56 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2233
2331
|
return;
|
|
2234
2332
|
}
|
|
2235
2333
|
const message = this.deserialiseMessage(bytes);
|
|
2236
|
-
this.
|
|
2334
|
+
this.scheduleEmitMessage(fromPeerId, message, false);
|
|
2237
2335
|
} catch {}
|
|
2238
2336
|
}
|
|
2337
|
+
scheduleEmitMessage(fromPeerId, message, viaFragmentPath) {
|
|
2338
|
+
if (!this.syncYieldEnabled) {
|
|
2339
|
+
this.emit("message", message);
|
|
2340
|
+
if (viaFragmentPath) {
|
|
2341
|
+
this.finishInFlightSyncApply(fromPeerId);
|
|
2342
|
+
}
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
if (viaFragmentPath) {
|
|
2346
|
+
const slot = this.slots.get(fromPeerId);
|
|
2347
|
+
if (slot?.inFlightSync) {
|
|
2348
|
+
slot.inFlightSync.applyBacklog += 1;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
setTimeout(() => {
|
|
2352
|
+
try {
|
|
2353
|
+
this.emit("message", message);
|
|
2354
|
+
} finally {
|
|
2355
|
+
if (viaFragmentPath) {
|
|
2356
|
+
this.finishInFlightSyncApply(fromPeerId);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}, 0);
|
|
2360
|
+
}
|
|
2361
|
+
finishInFlightSyncApply(fromPeerId) {
|
|
2362
|
+
const slot = this.slots.get(fromPeerId);
|
|
2363
|
+
if (!slot?.inFlightSync)
|
|
2364
|
+
return;
|
|
2365
|
+
slot.inFlightSync.applyBacklog = Math.max(0, slot.inFlightSync.applyBacklog - 1);
|
|
2366
|
+
this.emitSyncProgress(fromPeerId, "dispatch-applied", 0);
|
|
2367
|
+
if (slot.inFlightSync.applyBacklog === 0 && slot.pendingFragments.size === 0) {
|
|
2368
|
+
slot.inFlightSync = undefined;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
emitSyncProgress(fromPeerId, kind, bytesDelta) {
|
|
2372
|
+
const slot = this.slots.get(fromPeerId);
|
|
2373
|
+
const inFlightSync = slot?.inFlightSync;
|
|
2374
|
+
this.emit("sync-progress", {
|
|
2375
|
+
peerId: fromPeerId,
|
|
2376
|
+
kind,
|
|
2377
|
+
bytesDelta,
|
|
2378
|
+
chunksReceived: inFlightSync?.chunksReceived ?? 0,
|
|
2379
|
+
bytesReceived: inFlightSync?.bytesReceived ?? 0,
|
|
2380
|
+
applyBacklog: inFlightSync?.applyBacklog ?? 0,
|
|
2381
|
+
at: performance.now()
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2239
2384
|
handleSyncFragment(fromPeerId, bytes) {
|
|
2240
2385
|
const parsed = parseSyncFragment(bytes);
|
|
2241
2386
|
if (!parsed)
|
|
@@ -2250,11 +2395,46 @@ class MeshWebRTCAdapter extends NetworkAdapter2 {
|
|
|
2250
2395
|
slot.pendingFragments.set(header.id, entry);
|
|
2251
2396
|
}
|
|
2252
2397
|
entry.chunks.set(header.index, data.slice());
|
|
2398
|
+
if (!slot.inFlightSync) {
|
|
2399
|
+
slot.inFlightSync = {
|
|
2400
|
+
chunksReceived: 0,
|
|
2401
|
+
bytesReceived: 0,
|
|
2402
|
+
lastChunkAt: performance.now(),
|
|
2403
|
+
applyBacklog: 0
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
slot.inFlightSync.chunksReceived += 1;
|
|
2407
|
+
slot.inFlightSync.bytesReceived += data.byteLength;
|
|
2408
|
+
slot.inFlightSync.lastChunkAt = performance.now();
|
|
2409
|
+
this.emitSyncProgress(fromPeerId, "fragment-received", data.byteLength);
|
|
2253
2410
|
if (entry.chunks.size < entry.total)
|
|
2254
2411
|
return;
|
|
2255
2412
|
slot.pendingFragments.delete(header.id);
|
|
2256
2413
|
const reassembled = reassembleSyncFragments(entry.chunks, entry.total);
|
|
2257
|
-
this.
|
|
2414
|
+
if (!this.syncYieldEnabled) {
|
|
2415
|
+
this.dispatchReassembled(fromPeerId, reassembled);
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
setTimeout(() => {
|
|
2419
|
+
this.dispatchReassembled(fromPeerId, reassembled);
|
|
2420
|
+
}, 0);
|
|
2421
|
+
}
|
|
2422
|
+
dispatchReassembled(fromPeerId, bytes) {
|
|
2423
|
+
try {
|
|
2424
|
+
if (this.onBlobMessage && isBlobMessageType(bytes)) {
|
|
2425
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2426
|
+
const headerLen = view.getUint32(0, false);
|
|
2427
|
+
const header = JSON.parse(new TextDecoder().decode(bytes.subarray(4, 4 + headerLen)));
|
|
2428
|
+
const data = bytes.subarray(4 + headerLen);
|
|
2429
|
+
this.onBlobMessage(fromPeerId, header, data);
|
|
2430
|
+
this.finishInFlightSyncApply(fromPeerId);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const message = this.deserialiseMessage(bytes);
|
|
2434
|
+
this.scheduleEmitMessage(fromPeerId, message, true);
|
|
2435
|
+
} catch {
|
|
2436
|
+
this.finishInFlightSyncApply(fromPeerId);
|
|
2437
|
+
}
|
|
2258
2438
|
}
|
|
2259
2439
|
get connectedPeerIds() {
|
|
2260
2440
|
const ids = [];
|
|
@@ -2332,12 +2512,22 @@ async function createMeshClient(options) {
|
|
|
2332
2512
|
signaling: undefined,
|
|
2333
2513
|
peerId: options.signaling.peerId,
|
|
2334
2514
|
knownPeerIds,
|
|
2515
|
+
keyringSource,
|
|
2335
2516
|
...resolvedIceServers !== undefined && { iceServers: resolvedIceServers },
|
|
2336
2517
|
...options.rtc?.dataChannelLabel !== undefined && {
|
|
2337
2518
|
dataChannelLabel: options.rtc.dataChannelLabel
|
|
2338
2519
|
},
|
|
2339
2520
|
...options.rtc?.RTCPeerConnection !== undefined && {
|
|
2340
2521
|
RTCPeerConnection: options.rtc.RTCPeerConnection
|
|
2522
|
+
},
|
|
2523
|
+
...options.rtc?.knownPeersRefreshIntervalMs !== undefined && {
|
|
2524
|
+
knownPeersRefreshIntervalMs: options.rtc.knownPeersRefreshIntervalMs
|
|
2525
|
+
},
|
|
2526
|
+
...options.rtc?.syncYieldEnabled !== undefined && {
|
|
2527
|
+
syncYieldEnabled: options.rtc.syncYieldEnabled
|
|
2528
|
+
},
|
|
2529
|
+
...options.rtc?.syncFragmentChunkSizeOverride !== undefined && {
|
|
2530
|
+
syncFragmentChunkSizeOverride: options.rtc.syncFragmentChunkSizeOverride
|
|
2341
2531
|
}
|
|
2342
2532
|
};
|
|
2343
2533
|
let webrtcAdapter;
|
|
@@ -2384,6 +2574,20 @@ async function createMeshClient(options) {
|
|
|
2384
2574
|
signaling,
|
|
2385
2575
|
networkAdapter,
|
|
2386
2576
|
webrtcAdapter,
|
|
2577
|
+
refreshKnownPeers: () => {
|
|
2578
|
+
webrtcAdapter?.refreshKnownPeers();
|
|
2579
|
+
},
|
|
2580
|
+
getPeerStateSnapshot: () => {
|
|
2581
|
+
if (!webrtcAdapter) {
|
|
2582
|
+
return {
|
|
2583
|
+
localPeerId: options.signaling.peerId,
|
|
2584
|
+
knownPeerIds: [],
|
|
2585
|
+
presentPeerIds: [],
|
|
2586
|
+
peers: []
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
return webrtcAdapter.getPeerStateSnapshot();
|
|
2590
|
+
},
|
|
2387
2591
|
close: async () => {
|
|
2388
2592
|
signaling.close();
|
|
2389
2593
|
webrtcAdapter?.disconnect();
|
|
@@ -2798,4 +3002,4 @@ export {
|
|
|
2798
3002
|
$meshCounter
|
|
2799
3003
|
};
|
|
2800
3004
|
|
|
2801
|
-
//# debugId=
|
|
3005
|
+
//# debugId=18409B674B254AA864756E2164756E21
|