@fairfox/polly 0.52.0 → 0.53.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.
@@ -45,6 +45,7 @@
45
45
  * signalling client.
46
46
  */
47
47
  import { type Message, NetworkAdapter, type PeerId, type PeerMetadata } from "@automerge/automerge-repo/slim";
48
+ import type { MeshKeyring } from "./mesh-network-adapter";
48
49
  import type { MeshSignalingClient } from "./mesh-signaling-client";
49
50
  /** Standard STUN servers for NAT traversal. In production, callers who
50
51
  * need TURN fallback for peers behind symmetric NATs should replace this
@@ -64,8 +65,41 @@ export interface MeshWebRTCAdapterOptions {
64
65
  * one by sending an SDP offer through the signalling channel. Peers
65
66
  * not in this list can still connect by sending an offer *to* this
66
67
  * adapter. The natural source for this list is the keyring's
67
- * knownPeers map keys. */
68
+ * knownPeers map keys.
69
+ *
70
+ * Used only when {@link keyringSource} is not supplied. With
71
+ * `keyringSource` set the adapter reads `knownPeers` live from the
72
+ * keyring on every initiation decision, which is the shape
73
+ * {@link createMeshClient} wires up so post-construction
74
+ * {@link applyPairingToken} calls take effect without the consumer
75
+ * having to call {@link MeshWebRTCAdapter.addKnownPeer} by hand. */
68
76
  knownPeerIds?: string[];
77
+ /** Live keyring source. When supplied, the adapter reads
78
+ * `knownPeers` from `keyringSource()` on every initiation decision
79
+ * instead of a Set captured at construction. Combined with the
80
+ * periodic re-sweep started in {@link MeshWebRTCAdapter.connect},
81
+ * this makes mutations to `keyring.knownPeers` — including the ones
82
+ * produced by {@link applyPairingToken} after the mesh client is up
83
+ * — visible to the dial path within at most one sweep interval. The
84
+ * crypto layer already re-reads the keyring on every send and
85
+ * receive; this option closes the same loop for the WebRTC adapter.
86
+ *
87
+ * Polly issue #103: without this, a long-lived daemon that pairs a
88
+ * new device after its mesh client is constructed never dials the
89
+ * new peer — the adapter's captured Set stays stale even though the
90
+ * keyring contains the new entry, and the lex-tie-break rule in
91
+ * {@link MeshWebRTCAdapter.shouldInitiateTo} can leave both peers
92
+ * waiting for the other to offer indefinitely. */
93
+ keyringSource?: () => MeshKeyring;
94
+ /** How often the adapter re-evaluates whether to dial peers in the
95
+ * signalling roster. The sweep is what catches peers that became
96
+ * authorised in the keyring after their `peer-joined` notification
97
+ * already fired — the adapter has no other event to hang the retry
98
+ * on, so it polls. Cheap: one Map lookup per present peer, fired at
99
+ * most once per interval. Defaults to 2000ms; tests override to
100
+ * shorten the failure budget. Set to 0 to disable the sweep
101
+ * (the captured-set behaviour, kept only for migration). */
102
+ knownPeersRefreshIntervalMs?: number;
69
103
  /** Optional ICE server list override. Defaults to {@link DEFAULT_ICE_SERVERS}. */
70
104
  iceServers?: RTCIceServer[];
71
105
  /** Optional data channel label. Defaults to "polly-mesh". Applications
@@ -90,8 +124,24 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
90
124
  /** Peers this adapter is willing to dial. Mutable so callers that pair
91
125
  * a new device after construction (e.g. a CLI `add-device` process whose
92
126
  * mesh client is long-lived across the pair ceremony) can register the
93
- * new peer with {@link addKnownPeer} without restarting the client. */
127
+ * new peer with {@link addKnownPeer} without restarting the client.
128
+ *
129
+ * Used only in the captured-set fallback path — when no
130
+ * {@link keyringSource} is supplied. With `keyringSource` set, the
131
+ * authoritative source is the live keyring and this Set is unused. */
94
132
  private readonly knownPeers;
133
+ /** Live keyring source, or undefined when the adapter is operating in
134
+ * the captured-set fallback shape. See the JSDoc on
135
+ * {@link MeshWebRTCAdapterOptions.keyringSource}. */
136
+ private readonly keyringSource;
137
+ /** Interval handle for the periodic re-sweep that catches peers
138
+ * already in the signalling roster when the keyring grew. Cleared in
139
+ * {@link MeshWebRTCAdapter.disconnect}. */
140
+ private knownPeersRefreshTimer;
141
+ /** Sweep interval. Resolved from
142
+ * {@link MeshWebRTCAdapterOptions.knownPeersRefreshIntervalMs} at
143
+ * construction. Defaults to 2000ms; tests override to 100–250ms. */
144
+ private readonly knownPeersRefreshIntervalMs;
95
145
  /** Peers currently visible in the signalling roster — populated by
96
146
  * {@link handlePeersPresent} / {@link handlePeerJoined} and pruned by
97
147
  * {@link handlePeerLeft}. Read by {@link addKnownPeer} to decide
@@ -109,10 +159,18 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
109
159
  private ready;
110
160
  private readyResolver;
111
161
  /** The peers this adapter will dial. Backward-compatible read accessor
112
- * for callers that previously iterated the `knownPeerIds` array; the
113
- * underlying storage is now a Set so mutations through
114
- * {@link addKnownPeer} are O(1) and survive without re-allocation. */
162
+ * for callers that previously iterated the `knownPeerIds` array. With
163
+ * a {@link MeshWebRTCAdapterOptions.keyringSource} configured, the
164
+ * value is read live from the keyring; otherwise it falls back to the
165
+ * captured Set populated through the constructor and
166
+ * {@link addKnownPeer}. */
115
167
  get knownPeerIds(): string[];
168
+ /** True iff `remotePeerId` is currently in the authoritative
169
+ * knownPeers source — the live keyring when one was supplied, or
170
+ * the captured Set otherwise. Centralises the membership check so
171
+ * {@link shouldInitiateTo} and the JSDoc on
172
+ * {@link MeshWebRTCAdapterOptions.keyringSource} agree on the rule. */
173
+ private hasKnownPeer;
116
174
  /** Callback for incoming blob messages. Set by the blob store.
117
175
  * Called with the sender's peer ID, the raw header object, and the
118
176
  * binary payload (chunk data). */
@@ -124,6 +182,43 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
124
182
  * RTCPeerConnection and data channel. Exposed for tests that assert
125
183
  * "exactly one channel per pair" after discovery settles. */
126
184
  peerSlotCount(): number;
185
+ /** Snapshot of the adapter's per-peer state at the moment of the
186
+ * call. Returned values are plain data — strings and booleans only —
187
+ * so consumers can log, assert on, or render them without retaining
188
+ * references into the adapter's internals.
189
+ *
190
+ * Polly issue #103 asked for an inspection surface so a consumer
191
+ * harness can answer "is the mesh layer in a known good state" from
192
+ * outside polly. This method is that surface. The fields mirror the
193
+ * three layers of the WebRTC connection lifecycle so a "stuck"
194
+ * connection can be diagnosed without reaching for the browser
195
+ * devtools: the SDP signalling state, the ICE checking state, and
196
+ * the unified RTCPeerConnection connection state. A `dataChannel`
197
+ * field reports whether the data channel — the thing the mesh
198
+ * actually carries bytes over — is open.
199
+ *
200
+ * Peers visible in the signalling roster but not yet dialled appear
201
+ * with `slot: undefined`, so a consumer can tell "we know about this
202
+ * peer in signalling but the WebRTC adapter has not started an
203
+ * offer yet" from "we have a slot in some negotiation state". */
204
+ getPeerStateSnapshot(): {
205
+ localPeerId: string;
206
+ knownPeerIds: string[];
207
+ presentPeerIds: string[];
208
+ peers: Array<{
209
+ peerId: string;
210
+ knownInKeyring: boolean;
211
+ presentInSignalling: boolean;
212
+ slot: undefined | {
213
+ signalingState: string;
214
+ iceConnectionState: string;
215
+ connectionState: string;
216
+ dataChannelState: string;
217
+ pendingSendCount: number;
218
+ pendingRemoteIceCount: number;
219
+ };
220
+ }>;
221
+ };
127
222
  /** Handle the signalling server's `peer-joined` notification: a new
128
223
  * peer has appeared on the relay. If the peer is in `knownPeerIds`
129
224
  * and we do not already have a slot for it, and the tie-break rule
@@ -152,8 +247,22 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
152
247
  * new entry. Calling this method propagates that into the adapter's
153
248
  * own allowlist; if the peer is already in the signalling roster and
154
249
  * the tie-break rule names us as the initiator, an SDP offer fires
155
- * immediately. No-op if the peer is already known. */
250
+ * immediately. No-op if the peer is already known.
251
+ *
252
+ * When a {@link MeshWebRTCAdapterOptions.keyringSource} is configured
253
+ * the adapter already reads `knownPeers` live and the periodic sweep
254
+ * picks up the new entry within
255
+ * {@link MeshWebRTCAdapterOptions.knownPeersRefreshIntervalMs} on its
256
+ * own, so explicit calls to this method are not required for
257
+ * correctness — but remain supported for callers that want the
258
+ * "dial now if present" prompt without waiting for the next sweep. */
156
259
  addKnownPeer(remotePeerId: string): void;
260
+ /** Re-evaluate every peer currently in the signalling roster and
261
+ * dial the ones the keyring authorises that we do not already have
262
+ * a slot for. The periodic sweep started in {@link connect} calls
263
+ * this; consumers can call it manually to skip the wait after they
264
+ * apply a fresh pairing token. Idempotent. */
265
+ refreshKnownPeers(): void;
157
266
  private shouldInitiateTo;
158
267
  whenReady(): Promise<void>;
159
268
  /**
@@ -170,6 +279,13 @@ export declare class MeshWebRTCAdapter extends NetworkAdapter {
170
279
  */
171
280
  connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
172
281
  disconnect(): void;
282
+ /** Start the periodic re-sweep that catches peers added to the
283
+ * keyring after their `peer-joined` notification has already fired.
284
+ * No-op when no keyringSource was supplied — the captured-set
285
+ * fallback has no live source to re-read, so the sweep would be
286
+ * useless. No-op when the interval is configured to 0. */
287
+ private startKnownPeersSweep;
288
+ private stopKnownPeersSweep;
173
289
  /**
174
290
  * Send a sync message to a specific remote peer. If no RTCPeerConnection
175
291
  * exists yet, the adapter initiates one by producing an SDP offer and
@@ -6135,12 +6135,23 @@ __export(exports_assert, {
6135
6135
  CallTracker: () => CallTracker,
6136
6136
  AssertionError: () => AssertionError
6137
6137
  });
6138
- var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
6138
+ function __accessProp2(key) {
6139
+ return this[key];
6140
+ }
6141
+ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
6142
+ var canCache = mod != null && typeof mod === "object";
6143
+ if (canCache) {
6144
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap, cached = cache.get(mod);
6145
+ if (cached)
6146
+ return cached;
6147
+ }
6139
6148
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
6140
6149
  let to = isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target;
6141
6150
  for (let key of __getOwnPropNames2(mod))
6142
6151
  if (!__hasOwnProp2.call(to, key))
6143
- __defProp2(to, key, { get: () => mod[key], enumerable: true });
6152
+ __defProp2(to, key, { get: __accessProp2.bind(mod, key), enumerable: true });
6153
+ if (canCache)
6154
+ cache.set(mod, to);
6144
6155
  return to;
6145
6156
  }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), require_shams, require_shams2, require_es_object_atoms, require_es_errors, require_eval, require_range, require_ref, require_syntax, require_type, require_uri, require_abs, require_floor, require_max, require_min, require_pow, require_round, require_isNaN, require_sign, require_gOPD, require_gopd, require_es_define_property, require_has_symbols, require_Reflect_getPrototypeOf, require_Object_getPrototypeOf, require_implementation, require_function_bind, require_functionCall, require_functionApply, require_reflectApply, require_actualApply, require_call_bind_apply_helpers, require_get, require_get_proto, require_hasown, require_get_intrinsic, require_call_bound, require_is_arguments, require_is_regex, require_safe_regex_test, require_generator_function, require_is_generator_function, require_is_callable, require_for_each, require_possible_typed_array_names, require_available_typed_arrays, require_define_data_property, require_has_property_descriptors, require_set_function_length, require_applyBind, require_call_bind, require_which_typed_array, require_is_typed_array, require_types, require_isBuffer, require_inherits_browser, require_inherits, require_util, require_errors, require_assertion_error, require_isArguments, require_implementation2, require_object_keys, require_implementation3, require_polyfill, require_implementation4, require_polyfill2, require_callBound, require_define_properties, require_shim, require_object_is, require_implementation5, require_polyfill3, require_shim2, require_is_nan, require_comparisons, require_assert, assert, AssertionError, CallTracker, deepEqual, deepStrictEqual, doesNotMatch, doesNotReject, doesNotThrow, equal, fail, ifError, match, notDeepEqual, notDeepStrictEqual, notEqual, notStrictEqual, ok, rejects, strict, strictEqual, throws, assert_default;
6146
6157
  var init_assert = __esm(() => {
@@ -9108,26 +9119,41 @@ var exports_zlib = {};
9108
9119
  __export(exports_zlib, {
9109
9120
  default: () => export_default
9110
9121
  });
9122
+ function __accessProp3(key) {
9123
+ return this[key];
9124
+ }
9125
+ function __exportSetter2(name, newValue) {
9126
+ this[name] = __returnValue2.bind(null, newValue);
9127
+ }
9111
9128
  var __create3, __getProtoOf3, __defProp3, __getOwnPropNames3, __hasOwnProp3, __reExport = (target, mod, secondTarget) => {
9112
- for (let key of __getOwnPropNames3(mod))
9129
+ var keys = __getOwnPropNames3(mod);
9130
+ for (let key of keys)
9113
9131
  if (!__hasOwnProp3.call(target, key) && key !== "default")
9114
- __defProp3(target, key, { get: () => mod[key], enumerable: true });
9132
+ __defProp3(target, key, { get: __accessProp3.bind(mod, key), enumerable: true });
9115
9133
  if (secondTarget) {
9116
- for (let key of __getOwnPropNames3(mod))
9134
+ for (let key of keys)
9117
9135
  if (!__hasOwnProp3.call(secondTarget, key) && key !== "default")
9118
- __defProp3(secondTarget, key, { get: () => mod[key], enumerable: true });
9136
+ __defProp3(secondTarget, key, { get: __accessProp3.bind(mod, key), enumerable: true });
9119
9137
  return secondTarget;
9120
9138
  }
9121
- }, __toESM3 = (mod, isNodeMode, target) => {
9139
+ }, __toESMCache_node3, __toESMCache_esm3, __toESM3 = (mod, isNodeMode, target) => {
9140
+ var canCache = mod != null && typeof mod === "object";
9141
+ if (canCache) {
9142
+ var cache = isNodeMode ? __toESMCache_node3 ??= new WeakMap : __toESMCache_esm3 ??= new WeakMap, cached = cache.get(mod);
9143
+ if (cached)
9144
+ return cached;
9145
+ }
9122
9146
  target = mod != null ? __create3(__getProtoOf3(mod)) : {};
9123
9147
  let to = isNodeMode || !mod || !mod.__esModule ? __defProp3(target, "default", { value: mod, enumerable: true }) : target;
9124
9148
  for (let key of __getOwnPropNames3(mod))
9125
9149
  if (!__hasOwnProp3.call(to, key))
9126
- __defProp3(to, key, { get: () => mod[key], enumerable: true });
9150
+ __defProp3(to, key, { get: __accessProp3.bind(mod, key), enumerable: true });
9151
+ if (canCache)
9152
+ cache.set(mod, to);
9127
9153
  return to;
9128
- }, __commonJS3 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __export2 = (target, all) => {
9154
+ }, __commonJS3 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue2 = (v) => v, __export2 = (target, all) => {
9129
9155
  for (var name in all)
9130
- __defProp3(target, name, { get: all[name], enumerable: true, configurable: true, set: (newValue) => all[name] = () => newValue });
9156
+ __defProp3(target, name, { get: all[name], enumerable: true, configurable: true, set: __exportSetter2.bind(all, name) });
9131
9157
  }, require_zstream, require_common, require_trees, require_adler32, require_crc32, require_messages, require_deflate, require_inffast, require_inftrees, require_inflate, require_constants, require_binding, require_lib, exports_zlib2, import_browserify_zlib, export_default;
9132
9158
  var init_zlib = __esm(() => {
9133
9159
  __create3 = Object.create;
@@ -13991,4 +14017,4 @@ export {
13991
14017
  assertSafeUpdateMode
13992
14018
  };
13993
14019
 
13994
- //# debugId=A8F0CA1BEDC12C8F64756E2164756E21
14020
+ //# debugId=51DC7226BD2C4BE464756E2164756E21