whalibmob 5.5.60 → 5.5.62

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/.env.example ADDED
@@ -0,0 +1,78 @@
1
+ # whalibmob — example environment configuration
2
+ #
3
+ # Copy this file to `.env` in your project root and uncomment what you need:
4
+ #
5
+ # cp node_modules/whalibmob/.env.example .env
6
+ #
7
+ # The CLI loads `.env` from the current working directory automatically.
8
+ # If you use the library programmatically, call `require('dotenv').config()`
9
+ # yourself before creating a client.
10
+ #
11
+ # Every variable below is OPTIONAL — with an empty .env the library runs with
12
+ # the defaults shown next to each entry.
13
+
14
+
15
+ # ─── Device profile ──────────────────────────────────────────────────────────
16
+ # Which platform to identify as during registration and connection.
17
+ # Values: ios | android
18
+ # WA_OS=ios
19
+
20
+ # Named predefined device profile. Takes priority over the individual
21
+ # WA_DEVICE_* variables below.
22
+ #
23
+ # ios iphone15pro, iphone15, iphone14, iphone14pro, iphone13, iphone12
24
+ # android samsung-s24-ultra, samsung-s24, samsung-s23, samsung-s23-ultra,
25
+ # samsung-a55, pixel8pro, pixel8, pixel7, pixel7a, xiaomi14,
26
+ # xiaomi13, oneplus12, oneplus11, oppo-find-x7, realme-gt5
27
+ #
28
+ # Default: iphone15pro when WA_OS=ios, samsungs24ultra when WA_OS=android
29
+ # WA_DEVICE=iphone15pro
30
+
31
+
32
+ # ─── Custom device profile ───────────────────────────────────────────────────
33
+ # Only used when WA_DEVICE does not match a predefined profile. Any value left
34
+ # unset falls back to the default profile for the selected WA_OS.
35
+ # The defaults shown here are the iOS ones (iPhone 15 Pro).
36
+
37
+ # Device display name — e.g. "iPhone 15 Pro", "Samsung Galaxy S24 Ultra"
38
+ # WA_DEVICE_MODEL=iPhone 15 Pro
39
+
40
+ # Apple | Samsung | Google | Xiaomi | OnePlus …
41
+ # WA_DEVICE_MANUFACTURER=Apple
42
+
43
+ # OS version string — e.g. 17.4.1 for iOS, 14 for Android
44
+ # WA_DEVICE_OS_VERSION=17.4.1
45
+
46
+ # OS build number — e.g. 21E236 for iOS, UP1A.231005.007 for Android
47
+ # WA_DEVICE_BUILD=21E236
48
+
49
+ # Internal model identifier — e.g. iPhone16,1 for iOS, SM-S928B for Android
50
+ # WA_DEVICE_MODEL_ID=iPhone16,1
51
+
52
+
53
+ # ─── WhatsApp version ────────────────────────────────────────────────────────
54
+ # Pin the WhatsApp client version. When set, the live version lookup is skipped
55
+ # entirely. Leave unset to fetch the current version automatically (recommended).
56
+ # WA_VERSION=2.24.10.75
57
+
58
+ # Override the static registration token. Leave unset to use the built-in token
59
+ # for the selected platform — only change this if you know what you are doing.
60
+ # WA_STATIC_TOKEN=
61
+
62
+
63
+ # ─── Proxy ───────────────────────────────────────────────────────────────────
64
+ # Route registration traffic through a SOCKS5 proxy, given as host:port.
65
+ # TOR_PROXY takes precedence over SOCKS_PROXY when both are set.
66
+ # Default Tor SOCKS port is 9050.
67
+ # TOR_PROXY=127.0.0.1:9050
68
+ # SOCKS_PROXY=127.0.0.1:1080
69
+
70
+
71
+ # ─── Frida attestation bridge ────────────────────────────────────────────────
72
+ # Host running the Frida attestation helper (see the frida/ directory).
73
+ # Leave unset to disable the bridge.
74
+ # WA_FRIDA_HOST=127.0.0.1
75
+
76
+ # Port the helper listens on.
77
+ # Default: 1119 (WhatsApp consumer; the Business build listens on 1120)
78
+ # WA_FRIDA_PORT=1119
package/cli.js CHANGED
@@ -37,7 +37,12 @@ const {
37
37
 
38
38
  const { assertMeId, initAuthCreds } = require('./lib/auth-utils');
39
39
 
40
- const VERSION = '5.5.59';
40
+ // Read the version straight from package.json so it can never drift out of sync
41
+ // with the published package. npm always ships package.json in the tarball,
42
+ // regardless of the "files" list, so this resolves for installed users too.
43
+ const VERSION = (() => {
44
+ try { return require('./package.json').version; } catch (_) { return 'unknown'; }
45
+ })();
41
46
 
42
47
  // ─── output helpers ───────────────────────────────────────────────────────────
43
48
 
package/lib/Client.js CHANGED
@@ -762,7 +762,17 @@ class WhalibmobClient extends EventEmitter {
762
762
  this._sendReadReceipt(id, fromRaw, partRaw);
763
763
  })
764
764
  .catch(err => {
765
+ _whaDbg('[DBG] GROUP_ERR id=' + id + ' from=' + from +
766
+ ' participant=' + participant + ' err=' + (err && err.message));
765
767
  this.emit('decrypt_error', { id, from, participant, err });
768
+ // Ask the sender to re-send with a fresh SenderKey distribution.
769
+ // Without this a group we joined after the sender distributed its key
770
+ // — or any group whose SKDM we missed — stays permanently
771
+ // undecryptable, failing every message with "No session found to
772
+ // decrypt message". The DM path already does this; groups need it
773
+ // just as much, and the retry must be addressed to the participant
774
+ // that actually encrypted the message, not to the group JID.
775
+ this._sendRetryRequest(id, node);
766
776
  });
767
777
  }
768
778
 
@@ -1244,6 +1254,15 @@ class WhalibmobClient extends EventEmitter {
1244
1254
  if (!this._socket || !this._connected) return;
1245
1255
  const mcId = this._genMsgId();
1246
1256
  _whaDbg('[DBG] SEND media_conn IQ id=' + mcId);
1257
+ // Match the reply by request id. The server echoes back only `from`, `id`
1258
+ // and `type='result'` — it does NOT repeat the `xmlns` of the request, so
1259
+ // keying off xmlns alone would never fire and every upload would stall
1260
+ // until the 15 s "Timeout waiting for media connection".
1261
+ this._pendingIqs.set(mcId, (resp) => {
1262
+ this._onMediaConnectionResult(resp);
1263
+ });
1264
+ // Don't leak the handler if the server never answers.
1265
+ setTimeout(() => this._pendingIqs.delete(mcId), 20000);
1247
1266
  this._socket.sendNode(new BinaryNode('iq', {
1248
1267
  id: mcId,
1249
1268
  to: 's.whatsapp.net',
@@ -1254,7 +1273,17 @@ class WhalibmobClient extends EventEmitter {
1254
1273
 
1255
1274
  _onMediaConnectionResult(node) {
1256
1275
  const connNode = findChild(node, 'media_conn');
1257
- if (!connNode) return;
1276
+ if (!connNode) {
1277
+ // Server answered but refused (or the IQ handler timed out and passed
1278
+ // nothing). Surface it so the waiter fails fast with a real reason
1279
+ // instead of sitting out the full media-connection timeout.
1280
+ const reason = (node && node.attrs && node.attrs.type === 'error')
1281
+ ? ('server returned IQ error' + (node.attrs.code ? ' ' + node.attrs.code : ''))
1282
+ : 'no media_conn node in server reply';
1283
+ _whaDbg('[DBG] media_conn failed: ' + reason);
1284
+ this.emit('media_conn_error', new Error('Media connection refused: ' + reason));
1285
+ return;
1286
+ }
1258
1287
  const auth = (connNode.attrs && connNode.attrs.auth) || '';
1259
1288
  const ttl = connNode.attrs && connNode.attrs.ttl ? parseInt(connNode.attrs.ttl, 10) : 300;
1260
1289
  const hosts = [];
@@ -1473,15 +1502,25 @@ class WhalibmobClient extends EventEmitter {
1473
1502
  ]);
1474
1503
 
1475
1504
  const response = await this._sendIq(node);
1476
- if (!response) return [];
1477
-
1478
- // Response content is a list of <group> nodes
1479
- const children = Array.isArray(response.content) ? response.content : [];
1480
- const groups = children
1505
+ if (!response) {
1506
+ throw new Error('fetchAllGroups: no reply from server (IQ timed out)');
1507
+ }
1508
+ if (response.attrs && response.attrs.type === 'error') {
1509
+ const errNode = findChild(response, 'error');
1510
+ const code = errNode && errNode.attrs && errNode.attrs.code;
1511
+ throw new Error('fetchAllGroups: server returned error' + (code ? ' ' + code : ''));
1512
+ }
1513
+
1514
+ // The server wraps the list in a <groups> node: iq > groups > group[].
1515
+ // Fall back to scanning the iq body directly in case a server build ever
1516
+ // returns them unwrapped.
1517
+ const groupsNode = findChild(response, 'groups');
1518
+ const container = groupsNode || response;
1519
+ const children = Array.isArray(container.content) ? container.content : [];
1520
+ return children
1481
1521
  .filter(n => n && n.description === 'group')
1482
1522
  .map(n => this._parseGroupNode(n))
1483
1523
  .filter(Boolean);
1484
- return groups;
1485
1524
  }
1486
1525
 
1487
1526
  // ─── Group member helpers ─────────────────────────────────────────────────
@@ -813,11 +813,28 @@ class MessageSender {
813
813
  // Auto-fetch group metadata if member cache is empty (first send to this group)
814
814
  let members = this._client._getGroupMembers(groupJid);
815
815
  if (members.length === 0 && typeof this._client.getGroupMetadata === 'function') {
816
+ _whaDbg('[DBG] GROUP_SEND auto-fetch metadata for ' + groupJid);
817
+ let metaErr = null;
816
818
  try {
817
- _whaDbg('[DBG] GROUP_SEND auto-fetch metadata for ' + groupJid);
818
819
  await this._client.getGroupMetadata(groupJid);
819
820
  members = this._client._getGroupMembers(groupJid);
820
- } catch (_) {}
821
+ } catch (err) {
822
+ metaErr = err;
823
+ _whaDbg('[DBG] GROUP_SEND metadata fetch failed: ' + (err && err.message));
824
+ }
825
+ // Without the participant list we cannot establish Signal sessions with
826
+ // the members, so the SenderKey distribution would reach nobody and the
827
+ // stanza would carry an <enc type='skmsg'> that no one can decrypt. The
828
+ // server rejects exactly that shape with ack error 479. Fail here with a
829
+ // reason the caller can act on instead of emitting a doomed stanza.
830
+ if (members.length === 0) {
831
+ throw new Error(
832
+ 'Cannot send to ' + groupJid + ': group metadata unavailable' +
833
+ (metaErr ? ' (' + metaErr.message + ')' : '') +
834
+ ' — no Signal session can be established with the members. ' +
835
+ 'Check that the account is still a participant of this group.'
836
+ );
837
+ }
821
838
  }
822
839
 
823
840
  // Determine addressing mode for this group (lid = modern groups, pn = legacy)
@@ -1153,20 +1170,39 @@ class MessageSender {
1153
1170
  }
1154
1171
 
1155
1172
  async _fetchMediaConn() {
1173
+ if (!this._client || !this._client._connected) {
1174
+ throw new Error('Cannot fetch media connection: not connected');
1175
+ }
1156
1176
  return new Promise((resolve, reject) => {
1157
- const timeout = setTimeout(() => {
1177
+ const cleanup = () => {
1178
+ clearTimeout(timeout);
1158
1179
  this._client.removeListener('media_conn', onConn);
1180
+ this._client.removeListener('media_conn_error', onErr);
1181
+ };
1182
+ const timeout = setTimeout(() => {
1183
+ cleanup();
1159
1184
  reject(new Error('Timeout waiting for media connection'));
1160
1185
  }, 15000);
1161
1186
  const onConn = ({ hosts, auth, ttl }) => {
1162
- clearTimeout(timeout);
1163
- if (hosts && hosts.length) {
1164
- this.setMediaConnection(hosts, auth, ttl, Date.now());
1187
+ cleanup();
1188
+ if (!hosts || !hosts.length) {
1189
+ return reject(new Error('Media connection returned no upload hosts'));
1165
1190
  }
1191
+ this.setMediaConnection(hosts, auth, ttl, Date.now());
1166
1192
  resolve();
1167
1193
  };
1194
+ const onErr = (err) => {
1195
+ cleanup();
1196
+ reject(err instanceof Error ? err : new Error(String(err)));
1197
+ };
1168
1198
  this._client.once('media_conn', onConn);
1169
- try { this._client._requestMediaConnection(); } catch (_) {}
1199
+ this._client.once('media_conn_error', onErr);
1200
+ try {
1201
+ this._client._requestMediaConnection();
1202
+ } catch (err) {
1203
+ cleanup();
1204
+ reject(err);
1205
+ }
1170
1206
  });
1171
1207
  }
1172
1208
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { decrypt, encrypt } = require('libsignal/src/crypto');
3
+ const { decrypt, encrypt } = require('../libsignal/crypto');
4
4
  const { SenderKeyMessage } = require('./sender-key-message');
5
5
  const { SenderKeyRecord } = require('./sender-key-record');
6
6
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
- const { generateKeyPair } = require('libsignal/src/curve');
4
+ const { generateKeyPair } = require('../libsignal/curve');
5
5
 
6
6
  function generateSenderKey() {
7
7
  return crypto.randomBytes(32);
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { calculateMAC } = require('libsignal/src/crypto');
3
+ const { calculateMAC } = require('../libsignal/crypto');
4
4
  const { SenderMessageKey } = require('./sender-message-key');
5
5
 
6
6
  class SenderChainKey {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { calculateSignature, verifySignature } = require('libsignal/src/curve');
3
+ const { calculateSignature, verifySignature } = require('../libsignal/curve');
4
4
  const { CiphertextMessage } = require('./ciphertext-message');
5
5
 
6
6
  // ─── Minimal protobuf encode/decode for SenderKeyMessage ─────────────────────
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { deriveSecrets } = require('libsignal/src/crypto');
3
+ const { deriveSecrets } = require('../libsignal/crypto');
4
4
 
5
5
  class SenderMessageKey {
6
6
  constructor(iteration, seed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.5.60",
3
+ "version": "5.5.62",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto50",
6
6
  "main": "index.js",