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 +46 -7
- package/lib/messages/MessageSender.js +43 -7
- package/package.json +1 -1
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)
|
|
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)
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
|
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
|
-
|
|
1163
|
-
if (hosts
|
|
1164
|
-
|
|
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
|
-
|
|
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
|
}
|