@fairfox/polly 0.53.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.
|
@@ -89,6 +89,20 @@ export interface CreateMeshClientOptions {
|
|
|
89
89
|
* `examples/mesh-recovery-pair`). Forwarded straight to
|
|
90
90
|
* {@link MeshWebRTCAdapterOptions.knownPeersRefreshIntervalMs}. */
|
|
91
91
|
knownPeersRefreshIntervalMs?: MeshWebRTCAdapterOptions["knownPeersRefreshIntervalMs"];
|
|
92
|
+
/** Forward of {@link MeshWebRTCAdapterOptions.syncYieldEnabled}.
|
|
93
|
+
* Defaults to `true`. The `examples/mesh-large-initial-sync`
|
|
94
|
+
* example flips this to `false` when `POLLY_104_DISABLE_FIX=1` is
|
|
95
|
+
* set, to demonstrate the pre-#104 tight-loop behaviour against
|
|
96
|
+
* post-fix polly. Production callers should leave this at the
|
|
97
|
+
* default. */
|
|
98
|
+
syncYieldEnabled?: MeshWebRTCAdapterOptions["syncYieldEnabled"];
|
|
99
|
+
/** Forward of
|
|
100
|
+
* {@link MeshWebRTCAdapterOptions.syncFragmentChunkSizeOverride}.
|
|
101
|
+
* Production callers should leave this undefined. The
|
|
102
|
+
* `examples/mesh-large-initial-sync` example passes 64 KiB when
|
|
103
|
+
* `POLLY_104_DISABLE_FIX=1` to recreate the pre-#104
|
|
104
|
+
* fragmentation bug. */
|
|
105
|
+
syncFragmentChunkSizeOverride?: MeshWebRTCAdapterOptions["syncFragmentChunkSizeOverride"];
|
|
92
106
|
};
|
|
93
107
|
/** The local peer's keyring — one of three shapes:
|
|
94
108
|
*
|
|
@@ -111,6 +111,78 @@ export interface MeshWebRTCAdapterOptions {
|
|
|
111
111
|
* (e.g. `werift` or `@roamhq/wrtc`) when running outside a browser, or
|
|
112
112
|
* to use a custom subclass for tests or instrumentation. */
|
|
113
113
|
RTCPeerConnection?: typeof RTCPeerConnection;
|
|
114
|
+
/** When `true` (the default), the adapter yields to the event loop
|
|
115
|
+
* between the points where a large initial sync would otherwise hold
|
|
116
|
+
* the main thread for tens of seconds: between each batch of
|
|
117
|
+
* fragmented `RTCDataChannel.send` calls on the sender side, and at
|
|
118
|
+
* the boundary between reassembling a sync message and dispatching
|
|
119
|
+
* it (deserialise → MeshNetworkAdapter unwrap → Automerge
|
|
120
|
+
* `applyChanges`) on the receiver side. Set to `false` to recover
|
|
121
|
+
* the pre-#104 tight-loop behaviour; this is the configuration the
|
|
122
|
+
* `POLLY_104_DISABLE_FIX=1` falsification path in
|
|
123
|
+
* `examples/mesh-large-initial-sync` uses to demonstrate the bug
|
|
124
|
+
* against post-fix polly. Production callers should leave this at
|
|
125
|
+
* the default. */
|
|
126
|
+
syncYieldEnabled?: boolean;
|
|
127
|
+
/** Override the sync fragment chunk size. Defaults to
|
|
128
|
+
* {@link SYNC_FRAGMENT_CHUNK_SIZE} (60 KiB), which leaves header
|
|
129
|
+
* overhead inside werift's hard 64 KiB max-message-size cap.
|
|
130
|
+
* Setting this to 64 KiB recreates the pre-#104 fragmentation bug,
|
|
131
|
+
* where peer A's outbound fragments overshoot the cap and werift
|
|
132
|
+
* rejects them silently — sync stalls forever. Used by the
|
|
133
|
+
* `POLLY_104_DISABLE_FIX=1` falsification path. Production callers
|
|
134
|
+
* should leave this at the default. */
|
|
135
|
+
syncFragmentChunkSizeOverride?: number;
|
|
136
|
+
}
|
|
137
|
+
/** Payload of the polly-specific `"sync-progress"` event emitted by
|
|
138
|
+
* {@link MeshWebRTCAdapter}. Consumers can subscribe via the adapter's
|
|
139
|
+
* standard `.on()` surface (the same one that carries `peer-candidate`
|
|
140
|
+
* and `peer-disconnected`) to observe fragment receive and dispatch
|
|
141
|
+
* activity in real time, without polling
|
|
142
|
+
* {@link MeshWebRTCAdapter.getPeerStateSnapshot}. Polly issue #104
|
|
143
|
+
* item 7. */
|
|
144
|
+
export interface SyncProgressEvent {
|
|
145
|
+
/** Remote peer the fragment or dispatch is for. */
|
|
146
|
+
peerId: string;
|
|
147
|
+
/** Lifecycle stage. `fragment-received` fires for each chunk that
|
|
148
|
+
* arrives during reassembly; `dispatch-applied` fires once the
|
|
149
|
+
* reassembled message has been emitted upward to Automerge. */
|
|
150
|
+
kind: "fragment-received" | "dispatch-applied";
|
|
151
|
+
/** Bytes carried by the chunk that triggered the event. Zero for
|
|
152
|
+
* `dispatch-applied`. */
|
|
153
|
+
bytesDelta: number;
|
|
154
|
+
/** Running total of fragments received for the current reassembly. */
|
|
155
|
+
chunksReceived: number;
|
|
156
|
+
/** Running total of bytes received for the current reassembly. */
|
|
157
|
+
bytesReceived: number;
|
|
158
|
+
/** Number of reassembled messages whose dispatch has been scheduled
|
|
159
|
+
* but not yet emitted upward to Automerge. */
|
|
160
|
+
applyBacklog: number;
|
|
161
|
+
/** `performance.now()` at event emission. */
|
|
162
|
+
at: number;
|
|
163
|
+
}
|
|
164
|
+
/** Per-peer view of an in-flight initial sync. Populated by
|
|
165
|
+
* {@link MeshWebRTCAdapter.handleSyncFragment} as fragments of a
|
|
166
|
+
* single reassembly arrive, and reset to `undefined` once the
|
|
167
|
+
* reassembled message has been dispatched. Exposed verbatim through
|
|
168
|
+
* {@link MeshWebRTCAdapter.getPeerStateSnapshot} so a consumer
|
|
169
|
+
* harness can observe progress mid-stream. Polly issue #104 item 7. */
|
|
170
|
+
export interface InFlightSyncSnapshot {
|
|
171
|
+
/** Fragments received for the current reassembly. Cleared once
|
|
172
|
+
* reassembly completes. */
|
|
173
|
+
chunksReceived: number;
|
|
174
|
+
/** Bytes received across the fragments of the current reassembly.
|
|
175
|
+
* The reassembled message will be slightly smaller than this sum
|
|
176
|
+
* because each fragment carries a small header. */
|
|
177
|
+
bytesReceived: number;
|
|
178
|
+
/** `performance.now()` value at the last fragment arrival. */
|
|
179
|
+
lastChunkAt: number;
|
|
180
|
+
/** Count of reassembled messages whose dispatch has been
|
|
181
|
+
* scheduled but not yet run. With the receiver-side `setTimeout(0)`
|
|
182
|
+
* yield enabled, this is normally 0 or 1; with the yield
|
|
183
|
+
* disabled (the falsification path) dispatch runs synchronously
|
|
184
|
+
* and this stays 0. */
|
|
185
|
+
applyBacklog: number;
|
|
114
186
|
}
|
|
115
187
|
/**
|
|
116
188
|
* Automerge-Repo NetworkAdapter backed by real WebRTC data channels.
|
|
@@ -142,6 +214,17 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
142
214
|
* {@link MeshWebRTCAdapterOptions.knownPeersRefreshIntervalMs} at
|
|
143
215
|
* construction. Defaults to 2000ms; tests override to 100–250ms. */
|
|
144
216
|
private readonly knownPeersRefreshIntervalMs;
|
|
217
|
+
/** When `true`, the sender side awaits between batches of fragment
|
|
218
|
+
* sends and the receiver side schedules dispatch via `setTimeout(0)`
|
|
219
|
+
* so the JS event loop can drain timers (including the consumer's
|
|
220
|
+
* own setInterval-based liveness probes) between large-message
|
|
221
|
+
* apply calls. Defaults to `true`; set to `false` only by the
|
|
222
|
+
* `POLLY_104_DISABLE_FIX` falsification path. */
|
|
223
|
+
private readonly syncYieldEnabled;
|
|
224
|
+
/** Resolved chunk size for fragmenting oversized messages.
|
|
225
|
+
* Defaults to {@link SYNC_FRAGMENT_CHUNK_SIZE}; can be overridden
|
|
226
|
+
* via {@link MeshWebRTCAdapterOptions.syncFragmentChunkSizeOverride}. */
|
|
227
|
+
private readonly syncFragmentChunkSize;
|
|
145
228
|
/** Peers currently visible in the signalling roster — populated by
|
|
146
229
|
* {@link handlePeersPresent} / {@link handlePeerJoined} and pruned by
|
|
147
230
|
* {@link handlePeerLeft}. Read by {@link addKnownPeer} to decide
|
|
@@ -216,6 +299,7 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
216
299
|
dataChannelState: string;
|
|
217
300
|
pendingSendCount: number;
|
|
218
301
|
pendingRemoteIceCount: number;
|
|
302
|
+
inFlightSync: InFlightSyncSnapshot | undefined;
|
|
219
303
|
};
|
|
220
304
|
}>;
|
|
221
305
|
};
|
|
@@ -293,13 +377,30 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
293
377
|
* queued until the data channel is open.
|
|
294
378
|
*/
|
|
295
379
|
send(message: Message): void;
|
|
380
|
+
/** Number of consecutive fragment sends after which the sender
|
|
381
|
+
* yields to the macrotask queue when {@link syncYieldEnabled} is on.
|
|
382
|
+
* 8 × 64 KiB = 512 KiB of bytes between yields — small enough that
|
|
383
|
+
* a 5 MB sync produces many yield points (and the JS event loop
|
|
384
|
+
* drains the consumer's `setInterval` liveness probes between
|
|
385
|
+
* them), large enough that the per-yield overhead does not dominate
|
|
386
|
+
* the wire cost. */
|
|
387
|
+
private static readonly SEND_YIELD_EVERY_N_FRAGMENTS;
|
|
296
388
|
/** Send raw wire bytes, fragmenting if they exceed the SCTP maxMessageSize
|
|
297
389
|
* cap. The default RTCDataChannel limit is 256 KiB in current Chrome and
|
|
298
390
|
* werift; oversized sends either throw, drop silently, or stall the
|
|
299
391
|
* channel, none of which surface as an error to the caller. Fragments
|
|
300
392
|
* use the same length-prefixed JSON header wire format as ordinary
|
|
301
393
|
* messages but carry a `sync-fragment` type that the receive path
|
|
302
|
-
* detects and reassembles before deserialising.
|
|
394
|
+
* detects and reassembles before deserialising.
|
|
395
|
+
*
|
|
396
|
+
* When {@link MeshWebRTCAdapterOptions.syncYieldEnabled} is true (the
|
|
397
|
+
* default), the loop awaits the macrotask queue every
|
|
398
|
+
* {@link SEND_YIELD_EVERY_N_FRAGMENTS} fragments so the JS event
|
|
399
|
+
* loop drains between batches — without this, a 5–8 MB initial
|
|
400
|
+
* sync produces 78–125 back-to-back `RTCDataChannel.send` calls in
|
|
401
|
+
* a tight loop, starving anything else on the main thread (polly
|
|
402
|
+
* issue #104, sender side). When the option is false the legacy
|
|
403
|
+
* tight-loop shape is preserved for the falsification path. */
|
|
303
404
|
private sendBytesMaybeFragmented;
|
|
304
405
|
/**
|
|
305
406
|
* Entry point the signalling client calls when it receives a signal
|
|
@@ -323,7 +424,31 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
323
424
|
private wireConnection;
|
|
324
425
|
private wireDataChannel;
|
|
325
426
|
private dispatchMessage;
|
|
427
|
+
/** Hand a deserialised Automerge message off to whoever is listening
|
|
428
|
+
* on this adapter's `"message"` event. When
|
|
429
|
+
* {@link MeshWebRTCAdapterOptions.syncYieldEnabled} is true (the
|
|
430
|
+
* default), the emit runs on a fresh macrotask so the crypto-unwrap
|
|
431
|
+
* and Automerge `applyChanges` chain downstream of this method does
|
|
432
|
+
* not sit on the same JS stack frame as the wire `onmessage` callback
|
|
433
|
+
* — that's the receiver-side starvation site polly issue #104
|
|
434
|
+
* documents. When the option is false the emit is synchronous,
|
|
435
|
+
* recovering the pre-fix shape used by the falsification path.
|
|
436
|
+
*
|
|
437
|
+
* The `viaFragmentPath` argument tags whether this dispatch came out
|
|
438
|
+
* of a reassembled fragment chain; only those carry an
|
|
439
|
+
* `inFlightSync` reassembly state worth bookkeeping. Small
|
|
440
|
+
* single-message dispatches yield but don't touch inFlightSync. */
|
|
441
|
+
private scheduleEmitMessage;
|
|
442
|
+
private finishInFlightSyncApply;
|
|
443
|
+
private emitSyncProgress;
|
|
326
444
|
private handleSyncFragment;
|
|
445
|
+
/** Dispatch a reassembled fragment payload back through
|
|
446
|
+
* {@link dispatchMessage}, but tagged so the
|
|
447
|
+
* {@link scheduleEmitMessage} path knows it owes a
|
|
448
|
+
* `finishInFlightSyncApply` afterwards. Synchronous re-entry into
|
|
449
|
+
* `dispatchMessage` would lose that signal, so the post-fragment
|
|
450
|
+
* deserialise+emit is inlined here. */
|
|
451
|
+
private dispatchReassembled;
|
|
327
452
|
/** Peer IDs with an open data channel, suitable for blob requests. */
|
|
328
453
|
get connectedPeerIds(): string[];
|
|
329
454
|
/** Send a pre-serialised blob message to a specific peer. Returns false
|
|
@@ -17,12 +17,23 @@
|
|
|
17
17
|
* does not mistake a sync fragment for a blob chunk.
|
|
18
18
|
*/
|
|
19
19
|
/** Maximum bytes a single channel.send may carry without fragmentation.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Werift (the node-side WebRTC implementation polly recommends for
|
|
21
|
+
* CLI/daemon use) enforces a hard 64 KiB (65536 bytes) maxMessageSize
|
|
22
|
+
* on its RTCDataChannel — anything larger is rejected with a
|
|
23
|
+
* `max-message-size exceeded` error and silently drops the channel
|
|
24
|
+
* for that send. Chrome's SCTP cap is 256 KiB and would tolerate
|
|
25
|
+
* larger frames, but the threshold is chosen to fit inside werift's
|
|
26
|
+
* cap WITH per-fragment header overhead included so a single mesh
|
|
27
|
+
* deployment works on both transports. Matches the blob-transfer
|
|
28
|
+
* chunk size so the two transports have a consistent per-message
|
|
29
|
+
* footprint on the data channel. */
|
|
24
30
|
export declare const SYNC_FRAGMENT_THRESHOLD: number;
|
|
25
|
-
/** Chunk size used when a message exceeds the threshold.
|
|
31
|
+
/** Chunk size used when a message exceeds the threshold. Left at the
|
|
32
|
+
* same value as {@link SYNC_FRAGMENT_THRESHOLD} so the framing
|
|
33
|
+
* header (a JSON-encoded `SyncFragmentHeader` of ~90 bytes plus a
|
|
34
|
+
* 4-byte length prefix) does not push any fragment over werift's
|
|
35
|
+
* 64 KiB wire limit — see polly issue #104 for the failure mode
|
|
36
|
+
* this guards against. */
|
|
26
37
|
export declare const SYNC_FRAGMENT_CHUNK_SIZE: number;
|
|
27
38
|
export interface SyncFragmentHeader {
|
|
28
39
|
type: "sync-fragment";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairfox/polly",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Multi-execution-context framework with reactive state and cross-context messaging for Chrome extensions, PWAs, and worker-based applications",
|