@leofcoin/peernet 1.1.96 → 1.1.98

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.
@@ -1,4 +1,4 @@
1
- import { L as LittlePubSub } from './peernet-BXonj_o8.js';
1
+ import { L as LittlePubSub } from './peernet-DzeROp0A.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -428,7 +428,11 @@ class Client {
428
428
  #peerId;
429
429
  #connections = {};
430
430
  #stars = {};
431
+ #starListeners = {};
432
+ #handlersSetup = false;
433
+ #reinitLock = null;
431
434
  #connectEvent = 'peer:connected';
435
+ #retryOptions = { retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 30000 };
432
436
  id;
433
437
  networkVersion;
434
438
  starsConfig;
@@ -467,21 +471,80 @@ class Client {
467
471
  this.version = version;
468
472
  this.#connectEvent = connectEvent;
469
473
  this.starsConfig = stars;
474
+ if (options?.retry)
475
+ this.#retryOptions = { ...this.#retryOptions, ...options.retry };
470
476
  this._init();
471
477
  }
472
- async _init() {
473
- // reconnectJob()
474
- if (!globalThis.RTCPeerConnection)
475
- globalThis.wrtc = (await import('./browser-Cjcx-T47.js').then(function (n) { return n.b; })).default;
476
- for (const star of this.starsConfig) {
478
+ /**
479
+ * Safely reinitialize the client (used after system resume/sleep).
480
+ * It closes existing connections and reconnects to configured stars.
481
+ */
482
+ async reinit() {
483
+ // avoid concurrent reinit runs
484
+ if (this.#reinitLock)
485
+ return this.#reinitLock;
486
+ this.#reinitLock = (async () => {
487
+ debug('reinit: start');
488
+ try {
489
+ await this.close();
490
+ // clear internal maps so setupStar starts fresh
491
+ this.#stars = {};
492
+ this.#connections = {};
493
+ for (const star of this.starsConfig) {
494
+ try {
495
+ await this.setupStar(star);
496
+ }
497
+ catch (e) {
498
+ // If last star fails and none connected, surface error
499
+ if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
500
+ Object.keys(this.#stars).length === 0)
501
+ throw new Error(`No star available to connect`);
502
+ }
503
+ }
504
+ }
505
+ finally {
506
+ debug('reinit: done');
507
+ this.#reinitLock = null;
508
+ }
509
+ })();
510
+ return this.#reinitLock;
511
+ }
512
+ async setupStar(star) {
513
+ const { retries, factor, minTimeout, maxTimeout } = this.#retryOptions;
514
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
515
+ let attempt = 0;
516
+ let lastErr;
517
+ while (attempt <= retries) {
477
518
  try {
478
519
  const client = new SocketRequestClient(star, this.networkVersion);
479
520
  this.#stars[star] = await client.init();
480
- this.setupStarListeners(this.#stars[star]);
521
+ this.setupStarListeners(this.#stars[star], star);
481
522
  this.#stars[star].send({
482
523
  url: 'join',
483
524
  params: { version: this.version, peerId: this.peerId }
484
525
  });
526
+ return this.#stars[star];
527
+ }
528
+ catch (e) {
529
+ lastErr = e;
530
+ attempt += 1;
531
+ if (attempt > retries)
532
+ break;
533
+ const delay = Math.min(maxTimeout, Math.round(minTimeout * Math.pow(factor, attempt - 1)));
534
+ debug(`setupStar ${star} failed, retrying in ${delay}ms (attempt ${attempt})`);
535
+ // eslint-disable-next-line no-await-in-loop
536
+ await sleep(delay);
537
+ }
538
+ }
539
+ throw lastErr;
540
+ }
541
+ async _init() {
542
+ // reconnectJob()
543
+ if (!globalThis.RTCPeerConnection)
544
+ globalThis.wrtc = (await import('./browser-Cjcx-T47.js').then(function (n) { return n.b; })).default;
545
+ for (const star of this.starsConfig) {
546
+ try {
547
+ await this.setupStar(star);
485
548
  }
486
549
  catch (e) {
487
550
  if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
@@ -499,13 +562,68 @@ class Client {
499
562
  else {
500
563
  globalThis.addEventListener('beforeunload', this.close.bind(this));
501
564
  }
565
+ // Setup resume/sleep detection so we can reinit connections after wake
566
+ this._setupResumeHandler();
567
+ }
568
+ setupStarListeners(starConnection, starId) {
569
+ // create stable references to handlers so we can unsubscribe later
570
+ const onPeerJoined = (id) => this.#peerJoined(id, starConnection);
571
+ const onPeerLeft = (id) => this.#peerLeft(id, starConnection);
572
+ const onStarJoined = this.#starJoined;
573
+ const onStarLeft = this.#starLeft;
574
+ const onSignal = (message) => this.#inComingSignal(message, starConnection);
575
+ starConnection.pubsub.subscribe('peer:joined', onPeerJoined);
576
+ starConnection.pubsub.subscribe('peer:left', onPeerLeft);
577
+ starConnection.pubsub.subscribe('star:joined', onStarJoined);
578
+ starConnection.pubsub.subscribe('star:left', onStarLeft);
579
+ starConnection.pubsub.subscribe('signal', onSignal);
580
+ this.#starListeners[starId] = [
581
+ { topic: 'peer:joined', handler: onPeerJoined },
582
+ { topic: 'peer:left', handler: onPeerLeft },
583
+ { topic: 'star:joined', handler: onStarJoined },
584
+ { topic: 'star:left', handler: onStarLeft },
585
+ { topic: 'signal', handler: onSignal }
586
+ ];
502
587
  }
503
- setupStarListeners(star) {
504
- star.pubsub.subscribe('peer:joined', (id) => this.#peerJoined(id, star));
505
- star.pubsub.subscribe('peer:left', (id) => this.#peerLeft(id, star));
506
- star.pubsub.subscribe('star:joined', this.#starJoined);
507
- star.pubsub.subscribe('star:left', this.#starLeft);
508
- star.pubsub.subscribe('signal', (message) => this.#inComingSignal(message, star));
588
+ _setupResumeHandler() {
589
+ if (this.#handlersSetup)
590
+ return;
591
+ this.#handlersSetup = true;
592
+ const THRESHOLD = 10 * 1000; // 10s gap indicates sleep/wake
593
+ let last = Date.now();
594
+ const check = () => {
595
+ const now = Date.now();
596
+ const delta = now - last;
597
+ last = now;
598
+ if (delta > THRESHOLD) {
599
+ debug(`resume detected (gap ${delta}ms)`);
600
+ this.reinit().catch((e) => debug('reinit error', e));
601
+ }
602
+ };
603
+ // Start interval checker
604
+ const iv = setInterval(check, 2000);
605
+ // Browser specific events
606
+ if (typeof document !== 'undefined' && document.addEventListener) {
607
+ document.addEventListener('visibilitychange', () => {
608
+ if (document.visibilityState === 'visible') {
609
+ // small delay to let timers update
610
+ setTimeout(() => check(), 50);
611
+ }
612
+ });
613
+ window.addEventListener('online', () => setTimeout(() => check(), 50));
614
+ }
615
+ // Node: listen for SIGCONT (process continued) as well
616
+ if (globalThis.process?.on) {
617
+ try {
618
+ process.on('SIGCONT', () => setTimeout(() => check(), 50));
619
+ }
620
+ catch (e) {
621
+ // ignore
622
+ }
623
+ }
624
+ // keep reference so it can be cleared on close
625
+ // @ts-ignore
626
+ this._resumeInterval = iv;
509
627
  }
510
628
  #starJoined = (id) => {
511
629
  if (this.#stars[id]) {
@@ -519,20 +637,16 @@ class Client {
519
637
  this.#stars[id].close(0);
520
638
  delete this.#stars[id];
521
639
  }
640
+ // if we lost all stars, try to reconnect to configured stars with backoff
522
641
  if (Object.keys(this.#stars).length === 0) {
523
642
  for (const star of this.starsConfig) {
524
643
  try {
525
- const socketClient = await new SocketRequestClient(star, this.networkVersion).init();
526
- if (!socketClient?.client?.OPEN)
527
- return;
528
- this.#stars[star] = socketClient;
529
- this.#stars[star].send({
530
- url: 'join',
531
- params: { peerId: this.peerId, version: this.version }
532
- });
533
- this.setupStarListeners(socketClient);
644
+ await this.setupStar(star);
645
+ // stop at first success
646
+ return;
534
647
  }
535
648
  catch (e) {
649
+ debug(`reconnect star ${star} failed: ${e.message || e}`);
536
650
  if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1)
537
651
  throw new Error(`No star available to connect`);
538
652
  }
@@ -611,7 +725,7 @@ class Client {
611
725
  // Destroy the existing peer connection
612
726
  // peer.destroy()
613
727
  // delete this.#connections[from]
614
- // // Create a new peer connection with the correct configuration
728
+ // // // Create a new peer connection with the correct configuration
615
729
  // this.#createRTCPeerConnection(from, star, version, false)
616
730
  // peer = this.#connections[from]
617
731
  }
@@ -685,15 +799,53 @@ class Client {
685
799
  peer.destroy();
686
800
  };
687
801
  async close() {
802
+ // clear resume interval if set
803
+ // @ts-ignore
804
+ if (this._resumeInterval) {
805
+ // @ts-ignore
806
+ clearInterval(this._resumeInterval);
807
+ // @ts-ignore
808
+ this._resumeInterval = null;
809
+ }
688
810
  for (const star in this.#stars) {
689
- if (this.#stars[star].connectionState() === 'open')
811
+ if (this.#stars[star].connectionState() === 'open') {
690
812
  await this.#stars[star].send({ url: 'leave', params: this.peerId });
813
+ // unsubscribe handlers we registered earlier
814
+ const listeners = this.#starListeners[star];
815
+ if (listeners && listeners.length) {
816
+ for (const { topic, handler } of listeners) {
817
+ try {
818
+ this.#stars[star].pubsub.unsubscribe(topic, handler);
819
+ }
820
+ catch (e) {
821
+ // ignore
822
+ }
823
+ }
824
+ }
825
+ }
691
826
  }
692
- const promises = [
693
- Object.values(this.#connections).map((connection) => connection.destroy()),
694
- Object.values(this.#stars).map((connection) => connection.close(0))
695
- ];
696
- return Promise.allSettled(promises);
827
+ // Ensure we wait for all peer and star close/destroy operations.
828
+ // Previous code passed an array of arrays to Promise.allSettled which
829
+ // resolves immediately; flatten into a single array of promises (or
830
+ // values) so we actually wait for async close operations.
831
+ const peerClosers = Object.values(this.#connections).map((connection) => {
832
+ try {
833
+ // destroy() may be sync or return a promise
834
+ return connection.destroy();
835
+ }
836
+ catch (e) {
837
+ return undefined;
838
+ }
839
+ });
840
+ const starClosers = Object.values(this.#stars).map((connection) => {
841
+ try {
842
+ return connection.close(0);
843
+ }
844
+ catch (e) {
845
+ return undefined;
846
+ }
847
+ });
848
+ return Promise.allSettled([...peerClosers, ...starClosers]);
697
849
  }
698
850
  }
699
851
 
@@ -1,4 +1,4 @@
1
- import { F as FormatInterface } from './peernet-BXonj_o8.js';
1
+ import { F as FormatInterface } from './peernet-DzeROp0A.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -7968,9 +7968,9 @@ const lastFetched = {
7968
7968
  const fetchedCoordinates = {};
7969
7969
  const getAddress = async () => {
7970
7970
  const { address } = lastFetched;
7971
- if (address) {
7972
- address.value = await fetch('https://icanhazip.com/');
7973
- address.value = await address.value.text();
7971
+ if (!address) {
7972
+ const value = await fetch('https://icanhazip.com/');
7973
+ address.value = await value.text();
7974
7974
  address.timestamp = Math.round(new Date().getTime() / 1000);
7975
7975
  lastFetched.address = address;
7976
7976
  }
@@ -8367,7 +8367,7 @@ class Peernet {
8367
8367
  this.root = options.root;
8368
8368
  const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile
8369
8369
  // FolderMessageResponse
8370
- } = await import(/* webpackChunkName: "messages" */ './messages-Y4l0mXGt.js');
8370
+ } = await import(/* webpackChunkName: "messages" */ './messages-CFqwXbrQ.js');
8371
8371
  /**
8372
8372
  * proto Object containing protos
8373
8373
  * @type {Object}
@@ -8461,7 +8461,7 @@ class Peernet {
8461
8461
  if (this.#starting || this.#started)
8462
8462
  return;
8463
8463
  this.#starting = true;
8464
- const importee = await import('./client-BlYwNU8G.js');
8464
+ const importee = await import('./client-UwSHOtRi.js');
8465
8465
  /**
8466
8466
  * @access public
8467
8467
  * @type {PeernetClient}
@@ -1,3 +1,3 @@
1
- export { P as default } from './peernet-BXonj_o8.js';
1
+ export { P as default } from './peernet-DzeROp0A.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
@@ -159,9 +159,9 @@ const lastFetched = {
159
159
  const fetchedCoordinates = {};
160
160
  const getAddress = async () => {
161
161
  const { address } = lastFetched;
162
- if (address) {
163
- address.value = await fetch('https://icanhazip.com/');
164
- address.value = await address.value.text();
162
+ if (!address) {
163
+ const value = await fetch('https://icanhazip.com/');
164
+ address.value = await value.text();
165
165
  address.timestamp = Math.round(new Date().getTime() / 1000);
166
166
  lastFetched.address = address;
167
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/peernet",
3
- "version": "1.1.96",
3
+ "version": "1.1.98",
4
4
  "description": "",
5
5
  "browser": "./exports/browser/peernet.js",
6
6
  "exports": {
@@ -37,13 +37,13 @@
37
37
  "@leofcoin/identity-utils": "^1.0.2",
38
38
  "@leofcoin/multi-wallet": "^3.1.8",
39
39
  "@leofcoin/storage": "^3.5.38",
40
- "@netpeer/swarm": "^0.8.21",
40
+ "@netpeer/swarm": "^0.8.24",
41
41
  "@vandeurenglenn/base32": "^1.2.4",
42
42
  "@vandeurenglenn/base58": "^1.1.9",
43
43
  "@vandeurenglenn/debug": "^1.2.6",
44
44
  "@vandeurenglenn/is-hex": "^1.1.1",
45
45
  "@vandeurenglenn/little-pubsub": "^1.5.1",
46
- "inquirer": "^12.9.0",
46
+ "inquirer": "^12.9.2",
47
47
  "multi-signature": "^1.3.1",
48
48
  "qr-scanner": "^1.4.2",
49
49
  "qrcode": "^1.5.4",
@@ -57,8 +57,8 @@
57
57
  "@rollup/plugin-node-resolve": "^16.0.1",
58
58
  "@rollup/plugin-typescript": "^12.1.4",
59
59
  "@rollup/plugin-wasm": "^6.2.2",
60
- "@types/bs58check": "^2.1.2",
61
- "@types/node": "^24.1.0",
60
+ "@types/bs58check": "^3.0.1",
61
+ "@types/node": "^24.3.0",
62
62
  "@types/qrcode": "^1.5.5",
63
63
  "@types/secp256k1": "^4.0.6",
64
64
  "@types/varint": "^6.0.3",
package/src/dht/dht.ts CHANGED
@@ -38,9 +38,9 @@ const fetchedCoordinates = {}
38
38
 
39
39
  export const getAddress = async () => {
40
40
  const { address } = lastFetched
41
- if (address) {
42
- address.value = await fetch('https://icanhazip.com/')
43
- address.value = await address.value.text()
41
+ if (!address) {
42
+ const value = await fetch('https://icanhazip.com/')
43
+ address.value = await value.text()
44
44
  address.timestamp = Math.round(new Date().getTime() / 1000)
45
45
  lastFetched.address = address
46
46
  }