@vielhuber/wahelper 1.8.7 → 1.8.8

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/README.md CHANGED
@@ -53,6 +53,9 @@ npx wahelper \
53
53
  --device "xxxxxxxxxxxx" \
54
54
  ...
55
55
 
56
+ # check the live connection
57
+ --action "get_status"
58
+
56
59
  # fetch messages
57
60
  --action "fetch_messages" \
58
61
  --filter '{"from":"491234567890","to":"491234567890","message":"meeting","date_from":"2026-01-01","date_until":"2026-12-31"}' \
@@ -85,6 +88,9 @@ use vielhuber\wahelper\wahelper;
85
88
 
86
89
  $wahelper = new wahelper();
87
90
 
91
+ // success is true only when connected
92
+ $status = $wahelper->getStatus(device: 'xxxxxxxxxxxx');
93
+
88
94
  // fetch messages
89
95
  $wahelper->fetchMessages(
90
96
  device: 'xxxxxxxxxxxx',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielhuber/wahelper",
3
- "version": "1.8.7",
3
+ "version": "1.8.8",
4
4
  "description": "Lightweight whatsapp integration layer.",
5
5
  "main": "wahelper.js",
6
6
  "files": [
@@ -36,6 +36,7 @@ export default class wahelperDaemon {
36
36
  this.dbLock = false;
37
37
  this.connected = false;
38
38
  this.connecting = false;
39
+ this.loggedOut = false;
39
40
  this.qr = null;
40
41
  this.pairingCode = null;
41
42
  this.pairingCodeRequested = false;
@@ -698,8 +699,8 @@ export default class wahelperDaemon {
698
699
  }
699
700
 
700
701
  async sendMessageToUser(number = null, message = null, attachments = null) {
701
- if (!this.connected || !this.sock) {
702
- throw new Error('not_connected');
702
+ if (!this.getStatus().connected) {
703
+ throw new Error(this.loggedOut ? 'logged_out' : 'not_connected');
703
704
  }
704
705
  // validate all attachments exist before sending anything
705
706
  if (attachments !== null && attachments.length > 0) {
@@ -724,8 +725,8 @@ export default class wahelperDaemon {
724
725
  }
725
726
 
726
727
  async sendMessageToGroup(name = null, message = null, attachments = null) {
727
- if (!this.connected || !this.sock) {
728
- throw new Error('not_connected');
728
+ if (!this.getStatus().connected) {
729
+ throw new Error(this.loggedOut ? 'logged_out' : 'not_connected');
729
730
  }
730
731
  // validate all attachments exist before sending anything
731
732
  if (attachments !== null && attachments.length > 0) {
@@ -756,7 +757,7 @@ export default class wahelperDaemon {
756
757
  }
757
758
 
758
759
  connect() {
759
- if (this.connecting) {
760
+ if (this.connecting || this.loggedOut) {
760
761
  return;
761
762
  }
762
763
  this.connecting = true;
@@ -885,10 +886,16 @@ export default class wahelperDaemon {
885
886
  socket
886
887
  .requestPairingCode(this.device)
887
888
  .then(code => {
889
+ if (this.sock !== socket) {
890
+ return;
891
+ }
888
892
  this.pairingCode = code;
889
893
  console.log('\nPairing code: ' + code);
890
894
  })
891
895
  .catch(err => {
896
+ if (this.sock !== socket) {
897
+ return;
898
+ }
892
899
  this.lastError = { source: 'pairing', message: err.message, at: Date.now() };
893
900
  this.log('Pairing code request failed: ' + err.message);
894
901
  });
@@ -917,16 +924,19 @@ export default class wahelperDaemon {
917
924
  }
918
925
 
919
926
  if (statusCode === DisconnectReason.loggedOut) {
920
- // logged out — delete auth and reconnect
927
+ this.loggedOut = true;
928
+ this.sock = null;
921
929
  this.qr = null;
922
930
  this.pairingCode = null;
923
931
  this.pairingCodeRequested = false;
924
- this.log('Logged out, removing auth folder');
925
- console.log('Logged out, removing auth folder...');
926
- if (fs.existsSync(this.dirname + '/' + this.authFolder)) {
927
- fs.rmSync(this.dirname + '/' + this.authFolder, { recursive: true, force: true });
928
- }
929
- setTimeout(() => this.connect(), 1000);
932
+ this.lastError = {
933
+ source: 'disconnect',
934
+ message: 'Logged out. Automatic reconnect stopped; manual device linking required.',
935
+ statusCode,
936
+ at: Date.now()
937
+ };
938
+ this.log(this.lastError);
939
+ console.error(this.lastError.message);
930
940
  return;
931
941
  }
932
942
 
@@ -949,10 +959,16 @@ export default class wahelperDaemon {
949
959
  source: 'disconnect',
950
960
  message:
951
961
  'connection closed' +
952
- (reason ? ' (' + reason + ')' : statusCode ? ' (statusCode=' + statusCode + ')' : ''),
962
+ (reason
963
+ ? ' (' + reason + ')'
964
+ : statusCode
965
+ ? ' (statusCode=' + statusCode + ')'
966
+ : ''),
967
+ statusCode,
953
968
  at: Date.now()
954
969
  };
955
970
  }
971
+ this.log(this.lastError);
956
972
  this.consecutiveFailures++;
957
973
  // first 3 attempts recover network blips fast (1s/2s/4s),
958
974
  // after that back off to 15min so a persistent failure
@@ -1000,6 +1016,19 @@ export default class wahelperDaemon {
1000
1016
  });
1001
1017
  }
1002
1018
 
1019
+ getStatus() {
1020
+ return {
1021
+ success: true,
1022
+ connected: this.connected && this.sock?.ws?.isOpen === true,
1023
+ connecting: this.connecting,
1024
+ loggedOut: this.loggedOut,
1025
+ device: this.device,
1026
+ qr: this.qr,
1027
+ pairingCode: this.pairingCode,
1028
+ lastError: this.lastError
1029
+ };
1030
+ }
1031
+
1003
1032
  startHttpServer() {
1004
1033
  this.httpServer = http.createServer(async (req, res) => {
1005
1034
  // bound to 127.0.0.1 below — every request additionally has to
@@ -1025,14 +1054,7 @@ export default class wahelperDaemon {
1025
1054
  this.sendJsonResponse(res, 403, { success: false, message: 'forbidden' });
1026
1055
  return;
1027
1056
  }
1028
- this.sendJsonResponse(res, 200, {
1029
- success: true,
1030
- connected: this.connected,
1031
- device: this.device,
1032
- qr: this.qr,
1033
- pairingCode: this.pairingCode,
1034
- lastError: this.lastError
1035
- });
1057
+ this.sendJsonResponse(res, 200, this.getStatus());
1036
1058
  return;
1037
1059
  }
1038
1060
 
package/wahelper.js CHANGED
@@ -53,6 +53,28 @@ export default class wahelper {
53
53
  }
54
54
 
55
55
  async init() {
56
+ if (this.args.action === 'get_status') {
57
+ let status = this.args.device
58
+ ? await this.callDaemon('GET', '/status')
59
+ : { success: false, message: 'missing_device' };
60
+ let connected = status.connected === true;
61
+ let response = {
62
+ success: connected,
63
+ message: connected ? 'connected' : status.loggedOut ? 'logged_out' : status.message || 'not_connected',
64
+ public_message: status.lastError?.message ?? null,
65
+ data: {
66
+ connected,
67
+ connecting: status.connecting === true,
68
+ loggedOut: status.loggedOut === true,
69
+ pairingRequired: status.loggedOut === true || Boolean(status.pairingCode || status.qr),
70
+ lastError: status.lastError ?? null
71
+ }
72
+ };
73
+ this.write(response, false);
74
+ console.log(JSON.stringify(response));
75
+ process.exitCode = connected ? 0 : 1;
76
+ return;
77
+ }
56
78
  await this.awaitLock('init', true);
57
79
  this.setLock('init', true);
58
80
  this.write({ success: false, message: 'loading_state', data: null }, false);
@@ -127,7 +149,11 @@ export default class wahelper {
127
149
  console.log('\n⚠️ Pairing required. Scan the QR code with WhatsApp, then retry.\n');
128
150
  console.log(daemonStatus.qrString);
129
151
  }
130
- if (daemonStatus.message === 'daemon_error' || daemonStatus.message === 'daemon_timeout') {
152
+ if (
153
+ daemonStatus.message === 'daemon_error' ||
154
+ daemonStatus.message === 'daemon_timeout' ||
155
+ daemonStatus.message === 'logged_out'
156
+ ) {
131
157
  console.log('⛔ ' + (daemonStatus.public_message || daemonStatus.message));
132
158
  }
133
159
  this.write(
@@ -316,26 +342,25 @@ export default class wahelper {
316
342
  }
317
343
 
318
344
  async ensureDaemon() {
319
- // check current status
320
- let status = await this.callDaemon('GET', '/status');
321
-
322
- // daemon not reachable — return error immediately, daemon must be started manually
323
- if (status.message === 'daemon_not_reachable') {
324
- return { connected: false, message: 'daemon_not_running' };
325
- }
326
-
327
- if (status.connected) {
328
- return status;
329
- }
330
-
331
- // poll up to 30s — return immediately when pairing code appears
332
- console.log('Waiting for daemon to connect...');
333
- for (let i = 0; i < 30; i++) {
334
- await new Promise(resolve => setTimeout(resolve, 1000));
335
- status = await this.callDaemon('GET', '/status');
345
+ for (let i = 0; i <= 30; i++) {
346
+ let status = await this.callDaemon('GET', '/status');
347
+ if (status.success === false) {
348
+ return {
349
+ connected: false,
350
+ message: status.message === 'daemon_not_reachable' ? 'daemon_not_running' : status.message
351
+ };
352
+ }
336
353
  if (status.connected) {
337
354
  return status;
338
355
  }
356
+ if (status.loggedOut) {
357
+ return {
358
+ connected: false,
359
+ message: 'logged_out',
360
+ public_message: status.lastError?.message,
361
+ data: status.lastError
362
+ };
363
+ }
339
364
  if (status.pairingCode) {
340
365
  return { connected: false, message: 'pairing_required', pairingCode: status.pairingCode };
341
366
  }
@@ -353,6 +378,12 @@ export default class wahelper {
353
378
  data: status.lastError
354
379
  };
355
380
  }
381
+ if (i === 0) {
382
+ console.log('Waiting for daemon to connect...');
383
+ }
384
+ if (i < 30) {
385
+ await new Promise(resolve => setTimeout(resolve, 1000));
386
+ }
356
387
  }
357
388
  return { connected: false, message: 'daemon_timeout' };
358
389
  }
@@ -400,7 +431,7 @@ export default class wahelper {
400
431
  }
401
432
  });
402
433
  });
403
- req.setTimeout(30000, () => {
434
+ req.setTimeout(path === '/status' ? 5000 : 30000, () => {
404
435
  req.destroy();
405
436
  this.log('⛔ Daemon request timeout');
406
437
  resolve({ success: false, message: 'daemon_timeout' });
@@ -642,6 +673,6 @@ if (
642
673
  ) {
643
674
  let wa = new wahelper();
644
675
  wa.init()
645
- .then(() => process.exit(0))
676
+ .then(() => process.exit(process.exitCode ?? 0))
646
677
  .catch(() => process.exit(1));
647
678
  }