@abraca/dabra 2.8.0 → 2.9.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/index.d.ts CHANGED
@@ -2212,7 +2212,19 @@ declare class AbracadabraWS extends EventEmitter {
2212
2212
  resolve: (value?: any) => void;
2213
2213
  reject: (reason?: any) => void;
2214
2214
  } | null;
2215
+ private onlineListener;
2216
+ private offlineListener;
2215
2217
  constructor(configuration: AbracadabraWSConfiguration);
2218
+ /**
2219
+ * Whether the device currently believes it has network connectivity.
2220
+ *
2221
+ * Treats "unknown" as online: in Node and other non-browser environments
2222
+ * `navigator` (or `navigator.onLine`) is absent, and we must not gate
2223
+ * reconnection there — only the browser exposes a trustworthy signal.
2224
+ */
2225
+ get isOnline(): boolean;
2226
+ handleOnline(): void;
2227
+ handleOffline(): void;
2216
2228
  receivedOnOpenPayload?: Event | undefined;
2217
2229
  onOpen(event: Event): Promise<void>;
2218
2230
  attach(provider: AbracadabraBaseProvider): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -41,7 +41,7 @@
41
41
  "yjs": "^13.6.8"
42
42
  },
43
43
  "devDependencies": {
44
- "@abraca/schema": "2.8.0"
44
+ "@abraca/schema": "2.9.0"
45
45
  },
46
46
  "scripts": {
47
47
  "test": "node --no-warnings --conditions=source --experimental-transform-types --test 'tests/*.test.ts'"
@@ -182,6 +182,12 @@ export class AbracadabraWS extends EventEmitter {
182
182
  reject: (reason?: any) => void;
183
183
  } | null = null;
184
184
 
185
+ // Bound `online`/`offline` listeners, retained so we can detach them in
186
+ // destroy(). Only registered in browser-like environments (see constructor).
187
+ private onlineListener: (() => void) | null = null;
188
+
189
+ private offlineListener: (() => void) | null = null;
190
+
185
191
  constructor(configuration: AbracadabraWSConfiguration) {
186
192
  super();
187
193
  this.setConfiguration(configuration);
@@ -210,11 +216,57 @@ export class AbracadabraWS extends EventEmitter {
210
216
  this.configuration.messageReconnectTimeout / 10,
211
217
  );
212
218
 
219
+ // Offline-first reconnect gating. When the device reports it is offline we
220
+ // stop hammering the socket (failed attempts just flip status
221
+ // disconnected↔connecting and light up every connection indicator) and
222
+ // wait for the `online` event to resume immediately — no backoff wait.
223
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
224
+ this.onlineListener = this.handleOnline.bind(this);
225
+ this.offlineListener = this.handleOffline.bind(this);
226
+ window.addEventListener("online", this.onlineListener);
227
+ window.addEventListener("offline", this.offlineListener);
228
+ }
229
+
213
230
  if (this.shouldConnect) {
214
231
  this.connect();
215
232
  }
216
233
  }
217
234
 
235
+ /**
236
+ * Whether the device currently believes it has network connectivity.
237
+ *
238
+ * Treats "unknown" as online: in Node and other non-browser environments
239
+ * `navigator` (or `navigator.onLine`) is absent, and we must not gate
240
+ * reconnection there — only the browser exposes a trustworthy signal.
241
+ */
242
+ get isOnline(): boolean {
243
+ return typeof navigator === "undefined" || navigator.onLine !== false;
244
+ }
245
+
246
+ handleOnline() {
247
+ // Network came back — resume immediately if we still want a connection
248
+ // and aren't already connected. Bypasses the backoff that a pending
249
+ // retryer would otherwise wait out.
250
+ if (this.shouldConnect && this.status !== WebSocketStatus.Connected) {
251
+ this.connect();
252
+ }
253
+ }
254
+
255
+ handleOffline() {
256
+ // Stop attempting while offline, but preserve `shouldConnect` so intent
257
+ // survives — `handleOnline`/attach will resume once connectivity returns.
258
+ if (this.cancelWebsocketRetry) {
259
+ this.cancelWebsocketRetry();
260
+ this.cancelWebsocketRetry = undefined;
261
+ }
262
+ try {
263
+ this.webSocket?.close();
264
+ this.messageQueue = [];
265
+ } catch (e) {
266
+ console.error(e);
267
+ }
268
+ }
269
+
218
270
  receivedOnOpenPayload?: Event | undefined = undefined;
219
271
 
220
272
  async onOpen(event: Event) {
@@ -271,6 +323,14 @@ export class AbracadabraWS extends EventEmitter {
271
323
  return;
272
324
  }
273
325
 
326
+ // Don't attempt while the device is offline — record the intent and let
327
+ // the `online` event drive the resume. Avoids the reconnect storm of
328
+ // instantly-failing socket attempts against a known-dead network.
329
+ if (!this.isOnline) {
330
+ this.shouldConnect = true;
331
+ return;
332
+ }
333
+
274
334
  // Always cancel any previously initiated connection retryer instances
275
335
  if (this.cancelWebsocketRetry) {
276
336
  this.cancelWebsocketRetry();
@@ -548,8 +608,11 @@ export class AbracadabraWS extends EventEmitter {
548
608
  this.emit("rateLimited");
549
609
  }
550
610
 
551
- // trigger connect if no retry is running and we want to have a connection
552
- if (!this.cancelWebsocketRetry && this.shouldConnect) {
611
+ // trigger connect if no retry is running and we want to have a connection.
612
+ // Skip scheduling entirely while offline — `handleOnline` resumes the
613
+ // moment connectivity returns, so there's no point burning a timer (and a
614
+ // failed attempt) against a dead network.
615
+ if (!this.cancelWebsocketRetry && this.shouldConnect && this.isOnline) {
553
616
  // Apply a much longer delay for rate-limited closes to let the server window reset.
554
617
  const delay = isRateLimited ? 60_000 : this.configuration.delay;
555
618
  setTimeout(() => {
@@ -565,6 +628,13 @@ export class AbracadabraWS extends EventEmitter {
565
628
 
566
629
  this.emit("destroy");
567
630
 
631
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
632
+ if (this.onlineListener) window.removeEventListener("online", this.onlineListener);
633
+ if (this.offlineListener) window.removeEventListener("offline", this.offlineListener);
634
+ }
635
+ this.onlineListener = null;
636
+ this.offlineListener = null;
637
+
568
638
  clearInterval(this.intervals.connectionChecker);
569
639
 
570
640
  // If there is still a connection attempt outstanding then we should stop