@leofcoin/peernet 1.1.97 → 1.1.99

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-O_tySEFY.js';
1
+ import { L as LittlePubSub } from './peernet-BQUef8u4.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -132,10 +132,9 @@ class Api {
132
132
  throw e;
133
133
  }
134
134
  },
135
- peers: async (params) => {
135
+ peers: async () => {
136
136
  try {
137
- params.peers = true;
138
- const requested = { url: 'peernet', params };
137
+ const requested = { url: 'peernet', params: { peers: true } };
139
138
  const { result, id, handler } = await this.request(client, requested);
140
139
  this.unsubscribe(id, handler);
141
140
  return result;
@@ -206,7 +205,7 @@ class SocketRequestClient {
206
205
  clientConnection;
207
206
  #tries = 0;
208
207
  #retry = false;
209
- #timeout = 10000;
208
+ #timeout = 10_000;
210
209
  #times = 10;
211
210
  #options;
212
211
  #protocol;
@@ -342,16 +341,16 @@ class Peer extends SimplePeer {
342
341
  channelName;
343
342
  version;
344
343
  bw = { up: 0, down: 0 };
345
- get connected() {
346
- return super.connected;
347
- }
348
344
  constructor(options) {
349
- const { from, to, initiator, trickle, config, version } = options;
350
- const channelName = initiator ? `${from}:${to}` : `${to}:${from}`;
345
+ const { from, to, trickle, config, version } = options;
346
+ // Canonical initiator and channel label based on sorted IDs
347
+ const [a, b] = [from, to].sort();
348
+ const channelName = `${a}:${b}`;
349
+ const initiator = from === a;
351
350
  super({
352
351
  channelName,
353
352
  initiator,
354
- trickle: trickle || true,
353
+ trickle: trickle ?? true,
355
354
  config: { iceServers, ...config },
356
355
  wrtc: globalThis.wrtc
357
356
  });
@@ -428,7 +427,11 @@ class Client {
428
427
  #peerId;
429
428
  #connections = {};
430
429
  #stars = {};
430
+ #starListeners = {};
431
+ #handlersSetup = false;
432
+ #reinitLock = null;
431
433
  #connectEvent = 'peer:connected';
434
+ #retryOptions = { retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 30000 };
432
435
  id;
433
436
  networkVersion;
434
437
  starsConfig;
@@ -467,21 +470,80 @@ class Client {
467
470
  this.version = version;
468
471
  this.#connectEvent = connectEvent;
469
472
  this.starsConfig = stars;
473
+ if (options?.retry)
474
+ this.#retryOptions = { ...this.#retryOptions, ...options.retry };
470
475
  this._init();
471
476
  }
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) {
477
+ /**
478
+ * Safely reinitialize the client (used after system resume/sleep).
479
+ * It closes existing connections and reconnects to configured stars.
480
+ */
481
+ async reinit() {
482
+ // avoid concurrent reinit runs
483
+ if (this.#reinitLock)
484
+ return this.#reinitLock;
485
+ this.#reinitLock = (async () => {
486
+ debug('reinit: start');
487
+ try {
488
+ await this.close();
489
+ // clear internal maps so setupStar starts fresh
490
+ this.#stars = {};
491
+ this.#connections = {};
492
+ for (const star of this.starsConfig) {
493
+ try {
494
+ await this.setupStar(star);
495
+ }
496
+ catch (e) {
497
+ // If last star fails and none connected, surface error
498
+ if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
499
+ Object.keys(this.#stars).length === 0)
500
+ throw new Error(`No star available to connect`);
501
+ }
502
+ }
503
+ }
504
+ finally {
505
+ debug('reinit: done');
506
+ this.#reinitLock = null;
507
+ }
508
+ })();
509
+ return this.#reinitLock;
510
+ }
511
+ async setupStar(star) {
512
+ const { retries, factor, minTimeout, maxTimeout } = this.#retryOptions;
513
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
514
+ let attempt = 0;
515
+ let lastErr;
516
+ while (attempt <= retries) {
477
517
  try {
478
518
  const client = new SocketRequestClient(star, this.networkVersion);
479
519
  this.#stars[star] = await client.init();
480
- this.setupStarListeners(this.#stars[star]);
520
+ this.setupStarListeners(this.#stars[star], star);
481
521
  this.#stars[star].send({
482
522
  url: 'join',
483
523
  params: { version: this.version, peerId: this.peerId }
484
524
  });
525
+ return this.#stars[star];
526
+ }
527
+ catch (e) {
528
+ lastErr = e;
529
+ attempt += 1;
530
+ if (attempt > retries)
531
+ break;
532
+ const delay = Math.min(maxTimeout, Math.round(minTimeout * Math.pow(factor, attempt - 1)));
533
+ debug(`setupStar ${star} failed, retrying in ${delay}ms (attempt ${attempt})`);
534
+ // eslint-disable-next-line no-await-in-loop
535
+ await sleep(delay);
536
+ }
537
+ }
538
+ throw lastErr;
539
+ }
540
+ async _init() {
541
+ // reconnectJob()
542
+ if (!globalThis.RTCPeerConnection)
543
+ globalThis.wrtc = (await import('./browser-Cjcx-T47.js').then(function (n) { return n.b; })).default;
544
+ for (const star of this.starsConfig) {
545
+ try {
546
+ await this.setupStar(star);
485
547
  }
486
548
  catch (e) {
487
549
  if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 &&
@@ -499,13 +561,68 @@ class Client {
499
561
  else {
500
562
  globalThis.addEventListener('beforeunload', this.close.bind(this));
501
563
  }
564
+ // Setup resume/sleep detection so we can reinit connections after wake
565
+ this._setupResumeHandler();
566
+ }
567
+ setupStarListeners(starConnection, starId) {
568
+ // create stable references to handlers so we can unsubscribe later
569
+ const onPeerJoined = (id) => this.#peerJoined(id, starConnection);
570
+ const onPeerLeft = (id) => this.#peerLeft(id, starConnection);
571
+ const onStarJoined = this.#starJoined;
572
+ const onStarLeft = this.#starLeft;
573
+ const onSignal = (message) => this.#inComingSignal(message, starConnection);
574
+ starConnection.pubsub.subscribe('peer:joined', onPeerJoined);
575
+ starConnection.pubsub.subscribe('peer:left', onPeerLeft);
576
+ starConnection.pubsub.subscribe('star:joined', onStarJoined);
577
+ starConnection.pubsub.subscribe('star:left', onStarLeft);
578
+ starConnection.pubsub.subscribe('signal', onSignal);
579
+ this.#starListeners[starId] = [
580
+ { topic: 'peer:joined', handler: onPeerJoined },
581
+ { topic: 'peer:left', handler: onPeerLeft },
582
+ { topic: 'star:joined', handler: onStarJoined },
583
+ { topic: 'star:left', handler: onStarLeft },
584
+ { topic: 'signal', handler: onSignal }
585
+ ];
502
586
  }
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));
587
+ _setupResumeHandler() {
588
+ if (this.#handlersSetup)
589
+ return;
590
+ this.#handlersSetup = true;
591
+ const THRESHOLD = 10 * 1000; // 10s gap indicates sleep/wake
592
+ let last = Date.now();
593
+ const check = () => {
594
+ const now = Date.now();
595
+ const delta = now - last;
596
+ last = now;
597
+ if (delta > THRESHOLD) {
598
+ debug(`resume detected (gap ${delta}ms)`);
599
+ this.reinit().catch((e) => debug('reinit error', e));
600
+ }
601
+ };
602
+ // Start interval checker
603
+ const iv = setInterval(check, 2000);
604
+ // Browser specific events
605
+ if (typeof document !== 'undefined' && document.addEventListener) {
606
+ document.addEventListener('visibilitychange', () => {
607
+ if (document.visibilityState === 'visible') {
608
+ // small delay to let timers update
609
+ setTimeout(() => check(), 50);
610
+ }
611
+ });
612
+ window.addEventListener('online', () => setTimeout(() => check(), 50));
613
+ }
614
+ // Node: listen for SIGCONT (process continued) as well
615
+ if (globalThis.process?.on) {
616
+ try {
617
+ process.on('SIGCONT', () => setTimeout(() => check(), 50));
618
+ }
619
+ catch (e) {
620
+ // ignore
621
+ }
622
+ }
623
+ // keep reference so it can be cleared on close
624
+ // @ts-ignore
625
+ this._resumeInterval = iv;
509
626
  }
510
627
  #starJoined = (id) => {
511
628
  if (this.#stars[id]) {
@@ -519,20 +636,16 @@ class Client {
519
636
  this.#stars[id].close(0);
520
637
  delete this.#stars[id];
521
638
  }
639
+ // if we lost all stars, try to reconnect to configured stars with backoff
522
640
  if (Object.keys(this.#stars).length === 0) {
523
641
  for (const star of this.starsConfig) {
524
642
  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);
643
+ await this.setupStar(star);
644
+ // stop at first success
645
+ return;
534
646
  }
535
647
  catch (e) {
648
+ debug(`reconnect star ${star} failed: ${e.message || e}`);
536
649
  if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1)
537
650
  throw new Error(`No star available to connect`);
538
651
  }
@@ -548,7 +661,7 @@ class Client {
548
661
  }
549
662
  debug(`peer ${id} left`);
550
663
  };
551
- connect(peerId, star, initiator = true) {
664
+ connect(peerId, star) {
552
665
  if (this.#connections[peerId]) {
553
666
  debug(`peer ${peerId} already connected`);
554
667
  return;
@@ -557,16 +670,15 @@ class Client {
557
670
  console.warn(`Star ${star} is not connected, cannot reconnect to peer ${peerId}`);
558
671
  return;
559
672
  }
560
- this.#createRTCPeerConnection(peerId, star, this.version, initiator);
673
+ this.#createRTCPeerConnection(peerId, star, this.version);
561
674
  }
562
- reconnect(peerId, star, initiator = true) {
675
+ reconnect(peerId, star) {
563
676
  delete this.#connections[peerId];
564
677
  debug(`reconnecting to peer ${peerId}`);
565
- return this.connect(peerId, star, initiator);
678
+ return this.connect(peerId, star);
566
679
  }
567
- #createRTCPeerConnection = (peerId, star, version, initiator = false) => {
680
+ #createRTCPeerConnection = (peerId, star, version) => {
568
681
  const peer = new Peer({
569
- initiator: initiator,
570
682
  from: this.peerId,
571
683
  to: peerId,
572
684
  version
@@ -585,7 +697,7 @@ class Client {
585
697
  delete this.#connections[peerId];
586
698
  }
587
699
  if (this.peerId !== peerId)
588
- this.#createRTCPeerConnection(peerId, star, version, true);
700
+ this.#createRTCPeerConnection(peerId, star, version);
589
701
  debug(`peer ${peerId} joined`);
590
702
  };
591
703
  #inComingSignal = async ({ from, signal, channelName, version }, star) => {
@@ -611,9 +723,10 @@ class Client {
611
723
  // Destroy the existing peer connection
612
724
  // peer.destroy()
613
725
  // delete this.#connections[from]
614
- // // Create a new peer connection with the correct configuration
726
+ // // // Create a new peer connection with the correct configuration
615
727
  // this.#createRTCPeerConnection(from, star, version, false)
616
728
  // peer = this.#connections[from]
729
+ return;
617
730
  }
618
731
  peer.signal(signal);
619
732
  };
@@ -685,15 +798,53 @@ class Client {
685
798
  peer.destroy();
686
799
  };
687
800
  async close() {
801
+ // clear resume interval if set
802
+ // @ts-ignore
803
+ if (this._resumeInterval) {
804
+ // @ts-ignore
805
+ clearInterval(this._resumeInterval);
806
+ // @ts-ignore
807
+ this._resumeInterval = null;
808
+ }
688
809
  for (const star in this.#stars) {
689
- if (this.#stars[star].connectionState() === 'open')
810
+ if (this.#stars[star].connectionState() === 'open') {
690
811
  await this.#stars[star].send({ url: 'leave', params: this.peerId });
812
+ // unsubscribe handlers we registered earlier
813
+ const listeners = this.#starListeners[star];
814
+ if (listeners && listeners.length) {
815
+ for (const { topic, handler } of listeners) {
816
+ try {
817
+ this.#stars[star].pubsub.unsubscribe(topic, handler);
818
+ }
819
+ catch (e) {
820
+ // ignore
821
+ }
822
+ }
823
+ }
824
+ }
691
825
  }
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);
826
+ // Ensure we wait for all peer and star close/destroy operations.
827
+ // Previous code passed an array of arrays to Promise.allSettled which
828
+ // resolves immediately; flatten into a single array of promises (or
829
+ // values) so we actually wait for async close operations.
830
+ const peerClosers = Object.values(this.#connections).map((connection) => {
831
+ try {
832
+ // destroy() may be sync or return a promise
833
+ return connection.destroy();
834
+ }
835
+ catch (e) {
836
+ return undefined;
837
+ }
838
+ });
839
+ const starClosers = Object.values(this.#stars).map((connection) => {
840
+ try {
841
+ return connection.close(0);
842
+ }
843
+ catch (e) {
844
+ return undefined;
845
+ }
846
+ });
847
+ return Promise.allSettled([...peerClosers, ...starClosers]);
697
848
  }
698
849
  }
699
850
 
@@ -1,4 +1,4 @@
1
- import { F as FormatInterface } from './peernet-O_tySEFY.js';
1
+ import { F as FormatInterface } from './peernet-BQUef8u4.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -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-DDEOxfJp.js');
8370
+ } = await import(/* webpackChunkName: "messages" */ './messages-B2WVXv2a.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-BDXkFVRR.js');
8464
+ const importee = await import('./client-BfWOZz0W.js');
8465
8465
  /**
8466
8466
  * @access public
8467
8467
  * @type {PeernetClient}
@@ -1,3 +1,3 @@
1
- export { P as default } from './peernet-O_tySEFY.js';
1
+ export { P as default } from './peernet-BQUef8u4.js';
2
2
  import './identity-Cn0iQbY3.js';
3
3
  import './value-C3vAp-wb.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/peernet",
3
- "version": "1.1.97",
3
+ "version": "1.1.99",
4
4
  "description": "",
5
5
  "browser": "./exports/browser/peernet.js",
6
6
  "exports": {
@@ -37,34 +37,34 @@
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.29",
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.10.0",
47
47
  "multi-signature": "^1.3.1",
48
48
  "qr-scanner": "^1.4.2",
49
49
  "qrcode": "^1.5.4",
50
- "socket-request-client": "^2.1.0",
51
- "socket-request-server": "^1.7.1"
50
+ "socket-request-client": "^2.1.2",
51
+ "socket-request-server": "^1.7.2"
52
52
  },
53
53
  "devDependencies": {
54
- "@jest/globals": "^30.0.5",
55
- "@rollup/plugin-commonjs": "^28.0.6",
54
+ "@jest/globals": "^30.2.0",
55
+ "@rollup/plugin-commonjs": "^28.0.8",
56
56
  "@rollup/plugin-json": "^6.1.0",
57
- "@rollup/plugin-node-resolve": "^16.0.1",
57
+ "@rollup/plugin-node-resolve": "^16.0.3",
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.9.1",
62
62
  "@types/qrcode": "^1.5.5",
63
- "@types/secp256k1": "^4.0.6",
63
+ "@types/secp256k1": "^4.0.7",
64
64
  "@types/varint": "^6.0.3",
65
- "chai": "^5.2.1",
66
- "cross-env": "^10.0.0",
67
- "rollup": "^4.46.2",
65
+ "chai": "^6.2.0",
66
+ "cross-env": "^10.1.0",
67
+ "rollup": "^4.52.5",
68
68
  "sinon": "^21.0.0"
69
69
  }
70
70
  }