@fairfox/polly 0.54.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.
|
@@ -75,6 +75,21 @@ export interface CreateMeshClientOptions {
|
|
|
75
75
|
* the credential window closes.
|
|
76
76
|
*/
|
|
77
77
|
iceCredentialResolver?: () => Promise<RTCIceServer[]>;
|
|
78
|
+
/** Forward of {@link MeshWebRTCAdapterOptions.iceTransportPolicy}.
|
|
79
|
+
* Set to `"relay"` to force every candidate pair through TURN; the
|
|
80
|
+
* default leaves the underlying {@link RTCPeerConnection}
|
|
81
|
+
* implementation's default in place. The
|
|
82
|
+
* `examples/mesh-large-initial-sync-turn` harness uses `"relay"` to
|
|
83
|
+
* exercise the polly#105 real-transport contract. */
|
|
84
|
+
iceTransportPolicy?: MeshWebRTCAdapterOptions["iceTransportPolicy"];
|
|
85
|
+
/** Forward of {@link MeshWebRTCAdapterOptions.iceRelayEnforcement}.
|
|
86
|
+
* Defaults to `true`. Set to `false` to bypass the polly#105
|
|
87
|
+
* relay-only enforcement layer; the
|
|
88
|
+
* `examples/mesh-large-initial-sync-turn` falsification path
|
|
89
|
+
* (`POLLY_105_DISABLE_TURN_FIX=1`) does this to reproduce the
|
|
90
|
+
* pre-#105 candidate-leak shape. Production callers should leave
|
|
91
|
+
* this at the default. */
|
|
92
|
+
iceRelayEnforcement?: MeshWebRTCAdapterOptions["iceRelayEnforcement"];
|
|
78
93
|
dataChannelLabel?: string;
|
|
79
94
|
/** How often the mesh client re-evaluates whether to dial peers
|
|
80
95
|
* already present in the signalling roster against the live
|
|
@@ -186,6 +201,14 @@ export interface MeshClient {
|
|
|
186
201
|
* harness can answer "is the mesh layer in a known good state"
|
|
187
202
|
* without instrumenting polly internals. Polly issue #103 item 7. */
|
|
188
203
|
getPeerStateSnapshot(): ReturnType<MeshWebRTCAdapter["getPeerStateSnapshot"]>;
|
|
204
|
+
/** Refresh every active peer slot's transport-level summary —
|
|
205
|
+
* selected ICE candidate pair, SCTP retransmission counters, last
|
|
206
|
+
* data-channel error — and populate it into the next
|
|
207
|
+
* {@link getPeerStateSnapshot}. Walks {@link RTCPeerConnection.getStats}
|
|
208
|
+
* once per peer, so it isn't free; consumers that want continuous
|
|
209
|
+
* visibility should call this on a polling cadence the cost can
|
|
210
|
+
* absorb. Polly issue #105 item 7. */
|
|
211
|
+
refreshTransportStats(): Promise<void>;
|
|
189
212
|
/** Close the signalling WebSocket, tear down every RTCPeerConnection,
|
|
190
213
|
* and shut the Repo cleanly. Idempotent. */
|
|
191
214
|
close(): Promise<void>;
|
|
@@ -102,6 +102,27 @@ export interface MeshWebRTCAdapterOptions {
|
|
|
102
102
|
knownPeersRefreshIntervalMs?: number;
|
|
103
103
|
/** Optional ICE server list override. Defaults to {@link DEFAULT_ICE_SERVERS}. */
|
|
104
104
|
iceServers?: RTCIceServer[];
|
|
105
|
+
/** Optional ICE transport policy. Defaults to the
|
|
106
|
+
* {@link RTCPeerConnection} implementation's own default (`"all"` in
|
|
107
|
+
* Chrome, Firefox, and werift). Set to `"relay"` to force every
|
|
108
|
+
* candidate pair through a TURN relay — the shape the polly#105
|
|
109
|
+
* falsification harness needs to exercise the real-transport contract
|
|
110
|
+
* the polly#104 in-process throttle could not reach. */
|
|
111
|
+
iceTransportPolicy?: RTCIceTransportPolicy;
|
|
112
|
+
/** When `true` (the default), polly enforces
|
|
113
|
+
* {@link iceTransportPolicy} `"relay"` on its side of the signalling
|
|
114
|
+
* channel by filtering non-relay ICE candidates out of both the SDP
|
|
115
|
+
* description it forwards and the trickle ICE stream it emits. This
|
|
116
|
+
* closes the gap exposed by polly#105: Chrome's implementation
|
|
117
|
+
* already filters at the source, but werift only filters its own
|
|
118
|
+
* outgoing connectivity checks and still advertises host and srflx
|
|
119
|
+
* candidates upstream — so a relay-only peer on werift will, against
|
|
120
|
+
* a peer with the default policy, still pair through a non-relay
|
|
121
|
+
* candidate (or against a host-derived peer-reflexive remote).
|
|
122
|
+
* Setting this option to `false` reverts the enforcement and is the
|
|
123
|
+
* shape used by the `POLLY_105_DISABLE_TURN_FIX=1` falsification
|
|
124
|
+
* path. Production callers should leave this at the default. */
|
|
125
|
+
iceRelayEnforcement?: boolean;
|
|
105
126
|
/** Optional data channel label. Defaults to "polly-mesh". Applications
|
|
106
127
|
* that share a signalling server between multiple meshes may want
|
|
107
128
|
* distinct labels per mesh. */
|
|
@@ -161,6 +182,138 @@ export interface SyncProgressEvent {
|
|
|
161
182
|
/** `performance.now()` at event emission. */
|
|
162
183
|
at: number;
|
|
163
184
|
}
|
|
185
|
+
/** Last-seen transport-level summary for a peer slot. Populated
|
|
186
|
+
* lazily by {@link MeshWebRTCAdapter.refreshTransportStats}, which
|
|
187
|
+
* the consumer calls (typically from a polling loop in a debugging
|
|
188
|
+
* harness) so the cost of {@link RTCPeerConnection.getStats} is
|
|
189
|
+
* incurred only on demand. Polly issue #105 item 7 — exposes the
|
|
190
|
+
* dimension of the transport that {@link InFlightSyncSnapshot}
|
|
191
|
+
* doesn't reach: the negotiated ICE candidate pair, the SCTP
|
|
192
|
+
* retransmission counters, and the last data-channel-level error if
|
|
193
|
+
* one has been observed.
|
|
194
|
+
*
|
|
195
|
+
* Field names mirror the W3C stats spec (`selectedCandidatePairId`,
|
|
196
|
+
* `localCandidateType`, etc.) so consumers can correlate with the
|
|
197
|
+
* raw `RTCStatsReport`. werift exposes a stats shape close to the
|
|
198
|
+
* spec; Chrome exposes the spec itself; this view is implementation-
|
|
199
|
+
* agnostic. */
|
|
200
|
+
export interface TransportSnapshot {
|
|
201
|
+
/** ICE-level summary of the pair currently carrying data. */
|
|
202
|
+
selectedCandidatePair: {
|
|
203
|
+
localCandidateType: string;
|
|
204
|
+
remoteCandidateType: string;
|
|
205
|
+
state: string;
|
|
206
|
+
nominated: boolean;
|
|
207
|
+
bytesSent: number;
|
|
208
|
+
bytesReceived: number;
|
|
209
|
+
} | undefined;
|
|
210
|
+
/** SCTP / data-channel retransmission counters. Some implementations
|
|
211
|
+
* surface these on the transport stat, others on the data-channel
|
|
212
|
+
* stat — we expose the values we found, leaving the field undefined
|
|
213
|
+
* when the underlying impl doesn't surface them. */
|
|
214
|
+
retransmittedPacketsSent: number | undefined;
|
|
215
|
+
retransmittedBytesSent: number | undefined;
|
|
216
|
+
/** Most-recent data-channel error message, if `error` ever fired on
|
|
217
|
+
* the channel for this peer. Cleared only when the slot is replaced. */
|
|
218
|
+
lastDataChannelError: string | undefined;
|
|
219
|
+
/** `performance.now()` at the time the stats were last refreshed. */
|
|
220
|
+
at: number;
|
|
221
|
+
}
|
|
222
|
+
/** Reasons the adapter declined to construct an RTC slot for a peer
|
|
223
|
+
* that appeared in the signalling roster or the keyring. Recorded
|
|
224
|
+
* per-peer in {@link MeshWebRTCAdapter.getPeerStateSnapshot} so a
|
|
225
|
+
* consumer harness observing "(no slot)" can tell which gate inside
|
|
226
|
+
* the adapter stopped construction without having to log-correlate
|
|
227
|
+
* through three layers of timing. Polly issue #106 item 7.
|
|
228
|
+
*
|
|
229
|
+
* - `self`: the peerId equals the local peerId.
|
|
230
|
+
* - `not-in-keyring`: the live keyring (or captured Set) does not
|
|
231
|
+
* currently authorise this peer.
|
|
232
|
+
* - `not-present`: the peer is not in the signalling roster. The
|
|
233
|
+
* adapter only dials peers it has heard about through
|
|
234
|
+
* `peers-present` or `peer-joined`; keyring entries that have not
|
|
235
|
+
* appeared on signalling are quietly held without a slot.
|
|
236
|
+
* - `tie-break-other-side`: the lex-tie-break designates the remote
|
|
237
|
+
* peer as the initiator; we wait for their offer.
|
|
238
|
+
* - `slot-already-exists`: a slot exists already, possibly in any
|
|
239
|
+
* negotiation state.
|
|
240
|
+
* - `fatal-error`: an exception was thrown while attempting to build
|
|
241
|
+
* the slot. The accompanying {@link SlotInitiationDecision.error}
|
|
242
|
+
* string carries the message.
|
|
243
|
+
*/
|
|
244
|
+
export type SlotInitiationRejectionReason = "self" | "not-in-keyring" | "not-present" | "tie-break-other-side" | "slot-already-exists" | "fatal-error";
|
|
245
|
+
/** Most-recent slot-initiation decision for a peer. Computed at
|
|
246
|
+
* snapshot time so the view always reflects the current state of the
|
|
247
|
+
* relevant gates; a `decision === "accepted"` value paired with a
|
|
248
|
+
* `slot === undefined` view on the same snapshot is the load-bearing
|
|
249
|
+
* signal for "the adapter wants to dial but isn't" — the failure
|
|
250
|
+
* shape polly#106 documents. */
|
|
251
|
+
export interface SlotInitiationDecision {
|
|
252
|
+
/** "accepted" means every gate in `shouldInitiateTo` would pass right
|
|
253
|
+
* now and a sweep tick would construct a slot. "rejected" means at
|
|
254
|
+
* least one gate failed; the `reason` names it. */
|
|
255
|
+
decision: "accepted" | "rejected";
|
|
256
|
+
/** Populated only on `rejected` decisions. */
|
|
257
|
+
reason: SlotInitiationRejectionReason | undefined;
|
|
258
|
+
/** If the previous sweep tick caught a synchronous throw while
|
|
259
|
+
* building this peer's slot, the message is preserved here for the
|
|
260
|
+
* next snapshot. The reason will be `fatal-error`. */
|
|
261
|
+
error: string | undefined;
|
|
262
|
+
/** `performance.now()` at the time the decision was computed. */
|
|
263
|
+
at: number;
|
|
264
|
+
}
|
|
265
|
+
/** Most-recent sync-handshake timeline for a peer slot. Each timestamp
|
|
266
|
+
* is `performance.now()` of the corresponding event the first time it
|
|
267
|
+
* fired on the current slot — slots that get evicted and rebuilt start
|
|
268
|
+
* over. The four fields together describe whether the adapter and the
|
|
269
|
+
* receiver downstream of it have done their part in initiating sync:
|
|
270
|
+
*
|
|
271
|
+
* - `dataChannelOpenedAt`: when the wire is ready to carry bytes.
|
|
272
|
+
* - `peerCandidateEmittedAt`: when polly emitted `peer-candidate`
|
|
273
|
+
* upward; Automerge's network subsystem hooks this event to add the
|
|
274
|
+
* peer to its known set and kick off the per-document sync exchange.
|
|
275
|
+
* If this is `undefined` long after the data channel has opened,
|
|
276
|
+
* polly never signalled "ready" to Automerge — that's a failure in
|
|
277
|
+
* the adapter.
|
|
278
|
+
* - `firstOutboundSendAt`: when polly first dispatched bytes through
|
|
279
|
+
* {@link MeshWebRTCAdapter.send} for this peer. If
|
|
280
|
+
* `peerCandidateEmittedAt` is set but this is still `undefined`, the
|
|
281
|
+
* wrapper above polly (Automerge's NetworkSubsystem, MeshNetworkAdapter)
|
|
282
|
+
* never asked the adapter to send anything; that's a failure
|
|
283
|
+
* upstream of polly.
|
|
284
|
+
* - `firstInboundMessageAt`: when polly first emitted a `message` event
|
|
285
|
+
* for this peer. If `peerCandidateEmittedAt` is set on the remote and
|
|
286
|
+
* `firstOutboundSendAt` is set on the remote but this is `undefined`
|
|
287
|
+
* locally, bytes were sent across the wire but never decoded — that
|
|
288
|
+
* points at the crypto envelope or the wire fragmenter.
|
|
289
|
+
*
|
|
290
|
+
* Polly issue #106 item 7. */
|
|
291
|
+
export interface SyncHandshakeAttemptSnapshot {
|
|
292
|
+
dataChannelOpenedAt: number | undefined;
|
|
293
|
+
peerCandidateEmittedAt: number | undefined;
|
|
294
|
+
firstOutboundSendAt: number | undefined;
|
|
295
|
+
firstInboundMessageAt: number | undefined;
|
|
296
|
+
}
|
|
297
|
+
/** Sweep-loop observability for the periodic dial re-evaluation. The
|
|
298
|
+
* sweep is what catches peers that were not in the keyring at the time
|
|
299
|
+
* of their `peer-joined` notification (polly#103) AND peers whose
|
|
300
|
+
* previous slot failed and got evicted (polly#106). Exposing its tick
|
|
301
|
+
* counter lets a consumer distinguish "sweep is running but
|
|
302
|
+
* `shouldInitiateTo` rejected the peer" from "sweep is broken and
|
|
303
|
+
* never fires" without instrumenting polly internals. */
|
|
304
|
+
export interface SweepSnapshot {
|
|
305
|
+
/** True iff the sweep timer is currently scheduled — false on the
|
|
306
|
+
* captured-set fallback path (no keyringSource) or when the interval
|
|
307
|
+
* is configured to 0. */
|
|
308
|
+
enabled: boolean;
|
|
309
|
+
/** Configured interval in milliseconds. 0 means disabled. */
|
|
310
|
+
intervalMs: number;
|
|
311
|
+
/** How many times the sweep callback has fired since `connect()`. */
|
|
312
|
+
runCount: number;
|
|
313
|
+
/** `performance.now()` at the last tick. `undefined` until the
|
|
314
|
+
* first tick fires. */
|
|
315
|
+
lastRunAt: number | undefined;
|
|
316
|
+
}
|
|
164
317
|
/** Per-peer view of an in-flight initial sync. Populated by
|
|
165
318
|
* {@link MeshWebRTCAdapter.handleSyncFragment} as fragments of a
|
|
166
319
|
* single reassembly arrive, and reset to `undefined` once the
|
|
@@ -192,6 +345,8 @@ export interface InFlightSyncSnapshot {
|
|
|
192
345
|
export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
193
346
|
readonly signaling: MeshSignalingClient;
|
|
194
347
|
readonly iceServers: RTCIceServer[];
|
|
348
|
+
readonly iceTransportPolicy: RTCIceTransportPolicy | undefined;
|
|
349
|
+
private readonly iceRelayEnforcement;
|
|
195
350
|
readonly dataChannelLabel: string;
|
|
196
351
|
/** Peers this adapter is willing to dial. Mutable so callers that pair
|
|
197
352
|
* a new device after construction (e.g. a CLI `add-device` process whose
|
|
@@ -241,6 +396,21 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
241
396
|
private readonly slots;
|
|
242
397
|
private ready;
|
|
243
398
|
private readyResolver;
|
|
399
|
+
/** Sticky cache of the most recent {@link SlotInitiationDecision}
|
|
400
|
+
* per peer. Updated on every `shouldInitiateTo` call and on every
|
|
401
|
+
* caught throw inside the sweep loop, so a snapshot taken at any
|
|
402
|
+
* moment can answer "why is there no slot for this peer right
|
|
403
|
+
* now?". Polly issue #106 item 7. */
|
|
404
|
+
private readonly lastSlotInitiationDecisions;
|
|
405
|
+
/** Tick count of the periodic sweep — incremented inside the
|
|
406
|
+
* `setInterval` callback, exposed via {@link getPeerStateSnapshot}.
|
|
407
|
+
* Lets a consumer rule out "sweep is dead" before chasing the
|
|
408
|
+
* shouldInitiateTo gates. */
|
|
409
|
+
private sweepRunCount;
|
|
410
|
+
/** `performance.now()` at the last sweep tick. Paired with
|
|
411
|
+
* {@link sweepRunCount} so a stalled sweep is visible at a glance
|
|
412
|
+
* via the snapshot's `sweep` block. */
|
|
413
|
+
private lastSweepAt;
|
|
244
414
|
/** The peers this adapter will dial. Backward-compatible read accessor
|
|
245
415
|
* for callers that previously iterated the `knownPeerIds` array. With
|
|
246
416
|
* a {@link MeshWebRTCAdapterOptions.keyringSource} configured, the
|
|
@@ -288,10 +458,13 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
288
458
|
localPeerId: string;
|
|
289
459
|
knownPeerIds: string[];
|
|
290
460
|
presentPeerIds: string[];
|
|
461
|
+
sweep: SweepSnapshot;
|
|
291
462
|
peers: Array<{
|
|
292
463
|
peerId: string;
|
|
293
464
|
knownInKeyring: boolean;
|
|
294
465
|
presentInSignalling: boolean;
|
|
466
|
+
slotInitiationRejectionReason: SlotInitiationRejectionReason | undefined;
|
|
467
|
+
slotInitiationDecision: SlotInitiationDecision;
|
|
295
468
|
slot: undefined | {
|
|
296
469
|
signalingState: string;
|
|
297
470
|
iceConnectionState: string;
|
|
@@ -300,6 +473,8 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
300
473
|
pendingSendCount: number;
|
|
301
474
|
pendingRemoteIceCount: number;
|
|
302
475
|
inFlightSync: InFlightSyncSnapshot | undefined;
|
|
476
|
+
transport: TransportSnapshot | undefined;
|
|
477
|
+
lastSyncHandshakeAttempt: SyncHandshakeAttemptSnapshot;
|
|
303
478
|
};
|
|
304
479
|
}>;
|
|
305
480
|
};
|
|
@@ -317,6 +492,17 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
317
492
|
* every listed peer, so a device joining into an established lobby
|
|
318
493
|
* dials every knownPeer it is meant to initiate to in one pass. */
|
|
319
494
|
handlePeersPresent(peerIds: string[]): void;
|
|
495
|
+
/** Construct an initiating slot inside a per-peer try/catch and
|
|
496
|
+
* record any throw as a `fatal-error` rejection on the per-peer
|
|
497
|
+
* decision map so the snapshot surface names it. Every dial entry
|
|
498
|
+
* point ({@link handlePeerJoined}, {@link handlePeersPresent},
|
|
499
|
+
* {@link addKnownPeer}, {@link refreshKnownPeers}) routes through
|
|
500
|
+
* here so a single peer's broken construction can never take down a
|
|
501
|
+
* batch of peers — pre-#106 a thrown `new RTCPeerConnection()`
|
|
502
|
+
* inside `handlePeersPresent` would skip every later peer in the
|
|
503
|
+
* same batch with no observable trace because the signalling
|
|
504
|
+
* client's frame dispatch swallowed the rejection. */
|
|
505
|
+
private tryCreateInitiatingSlot;
|
|
320
506
|
/** Handle the signalling server's `peer-left` notification: a
|
|
321
507
|
* previously joined peer has closed its socket. Evict any slot we
|
|
322
508
|
* hold for that peer so a subsequent `peer-joined` from the same
|
|
@@ -345,9 +531,41 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
345
531
|
* dial the ones the keyring authorises that we do not already have
|
|
346
532
|
* a slot for. The periodic sweep started in {@link connect} calls
|
|
347
533
|
* this; consumers can call it manually to skip the wait after they
|
|
348
|
-
* apply a fresh pairing token. Idempotent.
|
|
534
|
+
* apply a fresh pairing token. Idempotent.
|
|
535
|
+
*
|
|
536
|
+
* A throw from {@link createInitiatingSlot} for one peer must not
|
|
537
|
+
* prevent the sweep from continuing to the next one — pre-#106 a
|
|
538
|
+
* synchronous throw inside `new RTCPeerConnection()` (a real risk
|
|
539
|
+
* once the page has built dozens of connections and Chrome's
|
|
540
|
+
* per-page cap is in play) skipped every later peer in the same
|
|
541
|
+
* sweep, with no observable trace because `setInterval` swallows
|
|
542
|
+
* the rejection silently. {@link tryCreateInitiatingSlot} caches
|
|
543
|
+
* the error onto the snapshot's slotInitiationRejectionReason as
|
|
544
|
+
* `fatal-error` so the failing peer is named instead of disguised
|
|
545
|
+
* as "(no slot)". */
|
|
349
546
|
refreshKnownPeers(): void;
|
|
350
547
|
private shouldInitiateTo;
|
|
548
|
+
/** Pure-function form of the gate cascade behind {@link shouldInitiateTo}.
|
|
549
|
+
* Returns `undefined` when every gate passes (the slot would be
|
|
550
|
+
* built); otherwise returns the named reason the dial was declined.
|
|
551
|
+
* Pulling the gates out of the boolean wrapper lets the snapshot
|
|
552
|
+
* surface name *which* gate stopped construction without the caller
|
|
553
|
+
* having to re-implement the check. Polly issue #106 item 7.
|
|
554
|
+
*
|
|
555
|
+
* The "not-present" gate is checked here even though the inbound
|
|
556
|
+
* call sites (`handlePeerJoined`, `handlePeersPresent`,
|
|
557
|
+
* `refreshKnownPeers`, `addKnownPeer`) only invoke `shouldInitiateTo`
|
|
558
|
+
* for peers in the signalling roster — so on those paths the gate
|
|
559
|
+
* never fires. It's load-bearing on the snapshot path, where the
|
|
560
|
+
* reason is computed for every peer the caller might ask about
|
|
561
|
+
* (including keyring entries that aren't currently signalling). */
|
|
562
|
+
private evaluateInitiation;
|
|
563
|
+
/** Compute the latest initiation decision for a peer at snapshot
|
|
564
|
+
* time. Prefers the cached decision when a sweep tick fixed the
|
|
565
|
+
* outcome to `fatal-error` (a thrown construction is sticky until
|
|
566
|
+
* the next successful sweep clears it); otherwise re-evaluates the
|
|
567
|
+
* gates against current state. Pure read; never mutates the map. */
|
|
568
|
+
private snapshotInitiationDecision;
|
|
351
569
|
whenReady(): Promise<void>;
|
|
352
570
|
/**
|
|
353
571
|
* Start the adapter. Marks the adapter ready so Automerge's
|
|
@@ -367,7 +585,15 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
367
585
|
* keyring after their `peer-joined` notification has already fired.
|
|
368
586
|
* No-op when no keyringSource was supplied — the captured-set
|
|
369
587
|
* fallback has no live source to re-read, so the sweep would be
|
|
370
|
-
* useless. No-op when the interval is configured to 0.
|
|
588
|
+
* useless. No-op when the interval is configured to 0.
|
|
589
|
+
*
|
|
590
|
+
* Each tick increments {@link sweepRunCount} and stamps
|
|
591
|
+
* {@link lastSweepAt} *before* dispatching to
|
|
592
|
+
* {@link refreshKnownPeers} so a snapshot can distinguish "sweep is
|
|
593
|
+
* running but every peer is rejected" from "sweep is dead". An
|
|
594
|
+
* outer try/catch keeps the timer alive even if the per-peer
|
|
595
|
+
* try/catch inside `refreshKnownPeers` somehow leaks. Polly issue
|
|
596
|
+
* #106 item 7. */
|
|
371
597
|
private startKnownPeersSweep;
|
|
372
598
|
private stopKnownPeersSweep;
|
|
373
599
|
/**
|
|
@@ -411,6 +637,44 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
411
637
|
* method.
|
|
412
638
|
*/
|
|
413
639
|
handleSignal(fromPeerId: string, rawPayload: unknown): void;
|
|
640
|
+
/** Assemble the {@link RTCConfiguration} every new
|
|
641
|
+
* {@link RTCPeerConnection} is built with. Centralised so every slot
|
|
642
|
+
* (initiator and answerer) honours the same iceTransportPolicy. */
|
|
643
|
+
private buildRtcConfiguration;
|
|
644
|
+
/** Decide whether a local ICE candidate should be relayed through
|
|
645
|
+
* the signalling channel to the remote peer. Chrome's iceTransport
|
|
646
|
+
* Policy = "relay" implementation filters non-relay candidates at
|
|
647
|
+
* the source so the remote peer never sees them; werift's only
|
|
648
|
+
* filters its own outgoing connectivity checks and still emits host
|
|
649
|
+
* and srflx candidates upstream, so a remote peer with policy "all"
|
|
650
|
+
* can pair against them — making relay-only enforcement leaky in
|
|
651
|
+
* the mixed-implementation case the polly#105 falsification harness
|
|
652
|
+
* exposes. Mirroring Chrome's filter at this layer gives a uniform
|
|
653
|
+
* contract on every RTCPeerConnection implementation polly supports.
|
|
654
|
+
*
|
|
655
|
+
* The candidate `type` field is the SDP-spec form; we additionally
|
|
656
|
+
* parse it out of the legacy `candidate` string for implementations
|
|
657
|
+
* that don't surface the typed field directly (werift exposes the
|
|
658
|
+
* SDP string only). */
|
|
659
|
+
private isRelayCandidateInit;
|
|
660
|
+
private shouldSendCandidate;
|
|
661
|
+
/** Strip non-relay `a=candidate:` lines from an SDP when
|
|
662
|
+
* iceTransportPolicy is `"relay"`. Some RTCPeerConnection
|
|
663
|
+
* implementations (werift, notably) embed every gathered candidate
|
|
664
|
+
* in the SDP regardless of policy, and a remote peer parsing those
|
|
665
|
+
* via `setRemoteDescription` will pair against them — bypassing the
|
|
666
|
+
* relay-only contract. Chrome already filters in-SDP candidates by
|
|
667
|
+
* policy, so this is only load-bearing for werift consumers, but
|
|
668
|
+
* the SDP rewrite is implementation-agnostic and idempotent. */
|
|
669
|
+
private filterSdpCandidatesByPolicy;
|
|
670
|
+
/** Apply {@link filterSdpCandidatesByPolicy} to an SDP
|
|
671
|
+
* description ahead of `setLocalDescription` (filter outgoing) or
|
|
672
|
+
* `setRemoteDescription` (filter incoming). The defensive
|
|
673
|
+
* receive-side filter exists because we cannot trust the remote
|
|
674
|
+
* peer's adapter to have stripped its non-relay candidates first —
|
|
675
|
+
* polly might be talking to a pre-#105 polly, or to a non-polly
|
|
676
|
+
* peer whose policy enforcement is different. */
|
|
677
|
+
private applySdpPolicyFilter;
|
|
414
678
|
private createInitiatingSlot;
|
|
415
679
|
private initiateOffer;
|
|
416
680
|
private handleOffer;
|
|
@@ -422,7 +686,35 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
422
686
|
* them — a single bad candidate must not stall the connection. */
|
|
423
687
|
private flushPendingRemoteIce;
|
|
424
688
|
private wireConnection;
|
|
689
|
+
/** Emit `peer-candidate` upward exactly once per slot lifetime,
|
|
690
|
+
* stamping the slot's `lastSyncHandshakeAttempt.peerCandidateEmittedAt`
|
|
691
|
+
* with the moment of emission. The "once" semantics protect Automerge's
|
|
692
|
+
* NetworkSubsystem from a double-add when both the
|
|
693
|
+
* `connectionstatechange = connected` event AND the
|
|
694
|
+
* `dataChannel.onopen` event fire on the same slot — under werift
|
|
695
|
+
* the former is sometimes flaky, under Chrome the latter sometimes
|
|
696
|
+
* fires first; pre-#106 only the connection-state path emitted, so a
|
|
697
|
+
* werift slot whose data channel opened cleanly but whose connection
|
|
698
|
+
* state never advanced past `connecting` would never signal "ready"
|
|
699
|
+
* upward. Polly issue #106 Failure B item — closing one named
|
|
700
|
+
* mechanism for "data channel open, sync never started". */
|
|
701
|
+
private emitPeerCandidateOnce;
|
|
425
702
|
private wireDataChannel;
|
|
703
|
+
/** Refresh the per-peer {@link TransportSnapshot} for one peer by
|
|
704
|
+
* pulling {@link RTCPeerConnection.getStats} and distilling the
|
|
705
|
+
* result. Cheap-ish but not free — getStats walks the underlying
|
|
706
|
+
* stats graph — so this is opt-in: callers (typically a debugging
|
|
707
|
+
* harness or a periodic observability loop) invoke it explicitly
|
|
708
|
+
* and the result lives on the slot for the next
|
|
709
|
+
* {@link getPeerStateSnapshot}. Returns the refreshed snapshot, or
|
|
710
|
+
* `undefined` if there is no slot for the peer or stats are
|
|
711
|
+
* unavailable. Polly issue #105 item 7. */
|
|
712
|
+
refreshTransportStats(peerId: string): Promise<TransportSnapshot | undefined>;
|
|
713
|
+
/** Refresh transport stats for every active peer slot in one shot.
|
|
714
|
+
* Returns a map keyed by peerId so a caller can render the result
|
|
715
|
+
* without re-walking {@link getPeerStateSnapshot}. The underlying
|
|
716
|
+
* `getStats` calls run concurrently. */
|
|
717
|
+
refreshAllTransportStats(): Promise<Map<string, TransportSnapshot>>;
|
|
426
718
|
private dispatchMessage;
|
|
427
719
|
/** Hand a deserialised Automerge message off to whoever is listening
|
|
428
720
|
* on this adapter's `"message"` event. When
|
|
@@ -439,6 +731,11 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
|
|
|
439
731
|
* `inFlightSync` reassembly state worth bookkeeping. Small
|
|
440
732
|
* single-message dispatches yield but don't touch inFlightSync. */
|
|
441
733
|
private scheduleEmitMessage;
|
|
734
|
+
/** Stamp the slot's `firstInboundMessageAt` the first time a
|
|
735
|
+
* dispatched (non-fragment, non-blob) message lands for a peer. Pure
|
|
736
|
+
* observability for {@link SyncHandshakeAttemptSnapshot}; does not
|
|
737
|
+
* affect dispatch. */
|
|
738
|
+
private stampFirstInboundMessage;
|
|
442
739
|
private finishInFlightSyncApply;
|
|
443
740
|
private emitSyncProgress;
|
|
444
741
|
private handleSyncFragment;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fairfox/polly",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.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",
|