whalibmob 5.5.61 → 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/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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.5.61",
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",