@vielhuber/wahelper 1.7.8 → 1.7.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielhuber/wahelper",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "Lightweight whatsapp integration layer.",
5
5
  "main": "wahelper.js",
6
6
  "files": [
@@ -27,9 +27,13 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "@prettier/plugin-php": "^0.25.0",
30
- "prettier": "^3.9.5"
30
+ "prettier": "^3.9.6"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=18"
34
+ },
35
+ "allowScripts": {
36
+ "baileys": true,
37
+ "protobufjs": true
34
38
  }
35
39
  }
@@ -39,6 +39,8 @@ export default class wahelperDaemon {
39
39
  this.isFirstRun = false;
40
40
  this.reconnectDelay = 1000;
41
41
  this.consecutiveFailures = 0;
42
+ this.connectionAttempt = null;
43
+ this.connectionTimeout = null;
42
44
  this.httpServer = null;
43
45
 
44
46
  if (this.args.device) {
@@ -627,23 +629,68 @@ export default class wahelperDaemon {
627
629
  return;
628
630
  }
629
631
  this.connecting = true;
632
+ let connectionAttempt = Symbol('connection');
633
+ let socket = null;
634
+ let staleSocket = null;
635
+ let connectionTimeoutError = new Error('connection attempt timed out');
636
+ this.connectionAttempt = connectionAttempt;
637
+ clearTimeout(this.connectionTimeout);
638
+ this.connectionTimeout = setTimeout(() => {
639
+ if (this.connectionAttempt !== connectionAttempt || this.connected) {
640
+ return;
641
+ }
642
+ this.connectionTimeout = null;
643
+ this.connected = false;
644
+ this.connecting = false;
645
+ this.lastError = { source: 'connect', message: connectionTimeoutError.message, at: Date.now() };
646
+ this.log('⛔ Connect error: ' + connectionTimeoutError.message);
647
+ console.log('⛔ Connect error: ' + connectionTimeoutError.message);
648
+ let socketToClose = socket || staleSocket || this.sock;
649
+ if (socketToClose) {
650
+ void socketToClose.end(connectionTimeoutError);
651
+ }
652
+ setTimeout(() => {
653
+ if (
654
+ this.connectionAttempt !== connectionAttempt ||
655
+ this.connected ||
656
+ this.connecting
657
+ ) {
658
+ return;
659
+ }
660
+ if (this.sock === socketToClose) {
661
+ this.sock = null;
662
+ }
663
+ this.connect();
664
+ }, this.reconnectDelay);
665
+ }, 60000);
630
666
  this.log('Connecting...');
631
667
  console.log('Connecting...');
632
668
 
633
669
  useMultiFileAuthState(this.dirname + '/' + this.authFolder)
634
670
  .then(async ({ state, saveCreds }) => {
671
+ if (this.connectionAttempt !== connectionAttempt) {
672
+ return;
673
+ }
635
674
  let { version } = await fetchLatestBaileysVersion();
675
+ if (this.connectionAttempt !== connectionAttempt) {
676
+ return;
677
+ }
636
678
  console.log('Baileys version: ' + version.join('.'));
637
679
 
638
680
  // close stale socket if present
639
- if (this.sock) {
681
+ staleSocket = this.sock;
682
+ this.sock = null;
683
+ if (staleSocket) {
640
684
  try {
641
- this.sock.end();
685
+ await staleSocket.end();
642
686
  } catch (_) {}
643
687
  }
688
+ if (this.connectionAttempt !== connectionAttempt) {
689
+ return;
690
+ }
644
691
 
645
692
  console.log('Creating WebSocket...');
646
- this.sock = makeWASocket({
693
+ socket = makeWASocket({
647
694
  auth: state,
648
695
  logger: P({ level: 'silent' }, P.destination(2)),
649
696
  // sync full history only during initial pairing and its required reconnect
@@ -651,25 +698,27 @@ export default class wahelperDaemon {
651
698
  version,
652
699
  browser: Browsers.windows('Desktop')
653
700
  });
701
+ this.sock = socket;
702
+ staleSocket = null;
654
703
 
655
- this.sock.ev.on('messaging-history.set', async obj => {
704
+ socket.ev.on('messaging-history.set', async obj => {
656
705
  this.log('messaging-history.set');
657
706
  await this.storeDataToDatabase(obj);
658
707
  // allow media downloads on all future syncs (reconnects)
659
708
  this.isFirstRun = false;
660
709
  });
661
710
 
662
- this.sock.ev.on('messages.upsert', async obj => {
711
+ socket.ev.on('messages.upsert', async obj => {
663
712
  this.log('messages.upsert');
664
713
  await this.storeDataToDatabase(obj);
665
714
  });
666
715
 
667
- this.sock.ev.on('chats.upsert', async obj => {
716
+ socket.ev.on('chats.upsert', async obj => {
668
717
  this.log('chats.upsert');
669
718
  await this.storeDataToDatabase(obj);
670
719
  });
671
720
 
672
- this.sock.ev.on('messages.update', async updates => {
721
+ socket.ev.on('messages.update', async updates => {
673
722
  try {
674
723
  await this.markMessagesRead(updates);
675
724
  } catch (error) {
@@ -677,7 +726,7 @@ export default class wahelperDaemon {
677
726
  }
678
727
  });
679
728
 
680
- this.sock.ev.on('chats.update', async updates => {
729
+ socket.ev.on('chats.update', async updates => {
681
730
  try {
682
731
  await this.markChatsRead(updates);
683
732
  } catch (error) {
@@ -685,9 +734,12 @@ export default class wahelperDaemon {
685
734
  }
686
735
  });
687
736
 
688
- this.sock.ev.on('creds.update', saveCreds);
737
+ socket.ev.on('creds.update', saveCreds);
689
738
 
690
- this.sock.ev.on('connection.update', async update => {
739
+ socket.ev.on('connection.update', async update => {
740
+ if (this.sock !== socket) {
741
+ return;
742
+ }
691
743
  let { connection, lastDisconnect, qr } = update;
692
744
  let statusCode = lastDisconnect?.error?.output?.statusCode;
693
745
  this.log(connection);
@@ -698,7 +750,7 @@ export default class wahelperDaemon {
698
750
  // request pairing code once per session
699
751
  if (!this.pairingCodeRequested && this.device) {
700
752
  this.pairingCodeRequested = true;
701
- this.sock
753
+ socket
702
754
  .requestPairingCode(this.device)
703
755
  .then(code => {
704
756
  this.pairingCode = code;
@@ -719,6 +771,9 @@ export default class wahelperDaemon {
719
771
  }
720
772
  } else {
721
773
  if (connection === 'close') {
774
+ clearTimeout(this.connectionTimeout);
775
+ this.connectionTimeout = null;
776
+ this.connectionAttempt = null;
722
777
  this.connected = false;
723
778
  this.connecting = false;
724
779
 
@@ -747,7 +802,13 @@ export default class wahelperDaemon {
747
802
  // the symptom — keep the more specific upstream
748
803
  // reason (e.g. "rate-overlimit") instead of burying
749
804
  // it under a generic "connectionLost"
750
- if (this.lastError?.source !== 'pairing') {
805
+ if (lastDisconnect?.error === connectionTimeoutError) {
806
+ this.lastError = {
807
+ source: 'connect',
808
+ message: connectionTimeoutError.message,
809
+ at: Date.now()
810
+ };
811
+ } else if (this.lastError?.source !== 'pairing') {
751
812
  let reason =
752
813
  DisconnectReason && statusCode
753
814
  ? Object.keys(DisconnectReason).find(k => DisconnectReason[k] === statusCode)
@@ -775,6 +836,9 @@ export default class wahelperDaemon {
775
836
  }
776
837
 
777
838
  if (connection === 'open') {
839
+ clearTimeout(this.connectionTimeout);
840
+ this.connectionTimeout = null;
841
+ this.connectionAttempt = null;
778
842
  this.connected = true;
779
843
  this.connecting = false;
780
844
  this.qr = null;
@@ -790,6 +854,12 @@ export default class wahelperDaemon {
790
854
  });
791
855
  })
792
856
  .catch(error => {
857
+ if (this.connectionAttempt !== connectionAttempt) {
858
+ return;
859
+ }
860
+ clearTimeout(this.connectionTimeout);
861
+ this.connectionTimeout = null;
862
+ this.connectionAttempt = null;
793
863
  this.connecting = false;
794
864
  this.lastError = { source: 'connect', message: error.message, at: Date.now() };
795
865
  this.log('⛔ Connect error: ' + error.message);
@@ -920,6 +990,9 @@ export default class wahelperDaemon {
920
990
 
921
991
  gracefulShutdown() {
922
992
  this.connected = false;
993
+ clearTimeout(this.connectionTimeout);
994
+ this.connectionAttempt = null;
995
+ this.connectionTimeout = null;
923
996
  if (this.sock) {
924
997
  try {
925
998
  this.sock.end();
package/wahelper.js CHANGED
@@ -84,61 +84,62 @@ export default class wahelper {
84
84
  this.resetFolder();
85
85
  }
86
86
  let response = null;
87
- let daemonStatus = await this.ensureDaemon();
88
- if (!daemonStatus.connected) {
89
- if (daemonStatus.message === 'daemon_not_running') {
90
- console.log(
91
- '⛔ Daemon not running. Start it with: npx wahelper-daemon --device ' + this.args.device
92
- );
93
- }
94
- if (daemonStatus.message === 'pairing_required') {
95
- console.log('\n⚠️ Pairing required. Enter this code in WhatsApp (Linked Devices), then retry.\n');
96
- console.log('Pairing code: ' + daemonStatus.pairingCode);
97
- }
98
- if (daemonStatus.message === 'qr_required') {
99
- console.log('\n⚠️ Pairing required. Scan the QR code with WhatsApp, then retry.\n');
100
- console.log(daemonStatus.qrString);
101
- }
102
- if (daemonStatus.message === 'daemon_error' || daemonStatus.message === 'daemon_timeout') {
103
- console.log('⛔ ' + (daemonStatus.public_message || daemonStatus.message));
87
+ if (this.args.action === 'fetch_messages') {
88
+ let filter = null;
89
+ if (typeof this.args.filter === 'string' && this.args.filter !== '') {
90
+ try {
91
+ filter = JSON.parse(this.args.filter);
92
+ } catch (_) {
93
+ filter = null;
94
+ }
95
+ } else if (this.args.filter && typeof this.args.filter === 'object') {
96
+ filter = this.args.filter;
104
97
  }
105
- this.write(
106
- {
107
- success: false,
108
- message: daemonStatus.message,
109
- public_message: daemonStatus.public_message || null,
110
- data: daemonStatus.data || daemonStatus.pairingCode || daemonStatus.qrString || null
111
- },
112
- true
98
+ response = await this.fetchMessages(
99
+ filter,
100
+ this.args.limit,
101
+ this.args.order,
102
+ this.args.exclude_body === true ||
103
+ this.args.exclude_body === 'true' ||
104
+ this.args.exclude_body === '1'
113
105
  );
114
- } else {
115
- if (this.args.action === 'fetch_messages') {
116
- let filter = null;
117
- if (typeof this.args.filter === 'string' && this.args.filter !== '') {
118
- try {
119
- filter = JSON.parse(this.args.filter);
120
- } catch (_) {
121
- filter = null;
122
- }
123
- } else if (this.args.filter && typeof this.args.filter === 'object') {
124
- filter = this.args.filter;
106
+ }
107
+ if (this.args.action === 'view_message') {
108
+ response = await this.viewMessage(this.args.id);
109
+ }
110
+ if (this.args.action === 'send_user' || this.args.action === 'send_group') {
111
+ let daemonStatus = await this.ensureDaemon();
112
+ if (!daemonStatus.connected) {
113
+ if (daemonStatus.message === 'daemon_not_running') {
114
+ console.log(
115
+ '⛔ Daemon not running. Start it with: npx wahelper-daemon --device ' + this.args.device
116
+ );
117
+ }
118
+ if (daemonStatus.message === 'pairing_required') {
119
+ console.log('\n⚠️ Pairing required. Enter this code in WhatsApp (Linked Devices), then retry.\n');
120
+ console.log('Pairing code: ' + daemonStatus.pairingCode);
125
121
  }
126
- response = await this.fetchMessages(
127
- filter,
128
- this.args.limit,
129
- this.args.order,
130
- this.args.exclude_body === true ||
131
- this.args.exclude_body === 'true' ||
132
- this.args.exclude_body === '1'
122
+ if (daemonStatus.message === 'qr_required') {
123
+ console.log('\n⚠️ Pairing required. Scan the QR code with WhatsApp, then retry.\n');
124
+ console.log(daemonStatus.qrString);
125
+ }
126
+ if (daemonStatus.message === 'daemon_error' || daemonStatus.message === 'daemon_timeout') {
127
+ console.log('⛔ ' + (daemonStatus.public_message || daemonStatus.message));
128
+ }
129
+ this.write(
130
+ {
131
+ success: false,
132
+ message: daemonStatus.message,
133
+ public_message: daemonStatus.public_message || null,
134
+ data: daemonStatus.data || daemonStatus.pairingCode || daemonStatus.qrString || null
135
+ },
136
+ true
133
137
  );
134
138
  }
135
- if (this.args.action === 'view_message') {
136
- response = await this.viewMessage(this.args.id);
137
- }
138
- if (this.args.action === 'send_user') {
139
+ if (daemonStatus.connected && this.args.action === 'send_user') {
139
140
  response = await this.sendMessageToUser(this.args.number, this.args.message, this.args.attachments);
140
141
  }
141
- if (this.args.action === 'send_group') {
142
+ if (daemonStatus.connected && this.args.action === 'send_group') {
142
143
  response = await this.sendMessageToGroup(this.args.name, this.args.message, this.args.attachments);
143
144
  }
144
145
  }