whalibmob 5.6.8 → 5.6.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/cli.js +17 -0
- package/lib/Client.js +104 -19
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -580,6 +580,23 @@ function attachEvents(client) {
|
|
|
580
580
|
notConnected();
|
|
581
581
|
});
|
|
582
582
|
|
|
583
|
+
// Someone else opened a session for this number. Reconnecting would fight
|
|
584
|
+
// them for it, so the client stands down and says so.
|
|
585
|
+
client.on('connection_replaced', (r) => {
|
|
586
|
+
_rl && _rl.pause();
|
|
587
|
+
fail('connection replaced');
|
|
588
|
+
out(' ' + r.message);
|
|
589
|
+
_rl && _rl.resume();
|
|
590
|
+
notConnected();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// A stanza whose shape we mishandled. Not fatal — the connection kept going.
|
|
594
|
+
client.on('node_error', (e) => {
|
|
595
|
+
_rl && _rl.pause();
|
|
596
|
+
fail('could not handle a <' + e.tag + '>: ' + (e.err && e.err.message));
|
|
597
|
+
_rl && (_rl.resume(), _rl.prompt(true));
|
|
598
|
+
});
|
|
599
|
+
|
|
583
600
|
// The server declined the client, not the session. Nothing to re-register.
|
|
584
601
|
client.on('client_rejected', (r) => {
|
|
585
602
|
_rl && _rl.pause();
|
package/lib/Client.js
CHANGED
|
@@ -1019,6 +1019,29 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1019
1019
|
// We do NOT decrypt the patches — that requires the full LTHASH machinery.
|
|
1020
1020
|
// We DO update our version counters from the server response so that we
|
|
1021
1021
|
// never re-request the same snapshot twice.
|
|
1022
|
+
/**
|
|
1023
|
+
* Acknowledge a dirty bit so the server stops announcing it.
|
|
1024
|
+
*
|
|
1025
|
+
* `<ib><dirty type="..."/></ib>` is the server saying something changed while
|
|
1026
|
+
* we were away. Whatever the client does about it, the bit has to be cleared
|
|
1027
|
+
* or the same announcement arrives on every connection.
|
|
1028
|
+
*
|
|
1029
|
+
* @param {string} type the dirty type, e.g. 'groups'
|
|
1030
|
+
* @param {number|string} [fromTs] the timestamp the announcement carried
|
|
1031
|
+
*/
|
|
1032
|
+
_cleanDirtyBits(type, fromTs) {
|
|
1033
|
+
if (!this._socket || !this._connected) return;
|
|
1034
|
+
const attrs = { type: String(type) };
|
|
1035
|
+
if (fromTs) attrs.timestamp = String(fromTs);
|
|
1036
|
+
_whaDbg('[DBG] CLEAN_DIRTY type=' + type + (fromTs ? ' t=' + fromTs : ''));
|
|
1037
|
+
this._socket.sendNode(new BinaryNode('iq', {
|
|
1038
|
+
id: this._genMsgId(),
|
|
1039
|
+
to: 's.whatsapp.net',
|
|
1040
|
+
type: 'set',
|
|
1041
|
+
xmlns: 'urn:xmpp:whatsapp:dirty'
|
|
1042
|
+
}, [new BinaryNode('clean', attrs, null)]));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1022
1045
|
_sendAppStateSyncForTypes(dirtyTypes) {
|
|
1023
1046
|
if (!this._socket || !this._connected) return;
|
|
1024
1047
|
|
|
@@ -1037,7 +1060,11 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1037
1060
|
for (const type of dirtyTypes) {
|
|
1038
1061
|
const mapped = COLLECTION_MAP[type];
|
|
1039
1062
|
if (!mapped) {
|
|
1040
|
-
|
|
1063
|
+
// "groups" is not an app-state collection, but it is still a dirty bit
|
|
1064
|
+
// the server expects to be cleared. Ignoring it meant the server
|
|
1065
|
+
// re-announced it on every single connection, forever.
|
|
1066
|
+
_whaDbg('[DBG] DIRTY type=' + type + ' — not an app-state collection, clearing the bit');
|
|
1067
|
+
this._cleanDirtyBits(type);
|
|
1041
1068
|
continue;
|
|
1042
1069
|
}
|
|
1043
1070
|
for (const c of mapped) collections.add(c);
|
|
@@ -1134,18 +1161,31 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1134
1161
|
try { _whaDbg('[DBG] IQ_ERROR content=' + JSON.stringify(node.content)); } catch(_) {}
|
|
1135
1162
|
}
|
|
1136
1163
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1164
|
+
// One malformed stanza must not take the connection down with it.
|
|
1165
|
+
//
|
|
1166
|
+
// A throw here propagates out through the socket's frame reader, which
|
|
1167
|
+
// reports it as a socket error — so a single unhandled shape in one
|
|
1168
|
+
// notification looked like the transport failing, and every stanza queued
|
|
1169
|
+
// behind it in the same offline batch was lost. Contain it: log the stanza
|
|
1170
|
+
// that did it, tell the caller, carry on reading.
|
|
1171
|
+
try {
|
|
1172
|
+
if (tag === 'iq') this._handleIq(node);
|
|
1173
|
+
else if (tag === 'message') this._handleMessage(node);
|
|
1174
|
+
else if (tag === 'receipt') this._handleReceipt(node);
|
|
1175
|
+
else if (tag === 'ack') this._handleAck(node);
|
|
1176
|
+
else if (tag === 'notification') this._handleNotification(node);
|
|
1177
|
+
else if (tag === 'presence') this._handlePresence(node);
|
|
1178
|
+
else if (tag === 'call') this._handleCall(node);
|
|
1179
|
+
else if (tag === 'ib') this._handleIb(node);
|
|
1180
|
+
else if (tag === 'success') this.emit('session_refresh', { node }); // late success (re-auth)
|
|
1181
|
+
else if (tag === 'failure') this._handleFailure(node);
|
|
1182
|
+
else if (tag === 'stream:error') this._handleStreamError(node);
|
|
1183
|
+
else this.emit('node', node);
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
_whaDbg('[DBG] NODE_HANDLER_ERR tag=' + tag + ' err=' + (err && err.message) +
|
|
1186
|
+
' attrs=' + JSON.stringify(node.attrs || {}));
|
|
1187
|
+
this.emit('node_error', { tag, node, err });
|
|
1188
|
+
}
|
|
1149
1189
|
}
|
|
1150
1190
|
|
|
1151
1191
|
// A stream error ends the connection; the reconnect in _onClose picks it up.
|
|
@@ -1157,16 +1197,55 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1157
1197
|
_handleStreamError(node) {
|
|
1158
1198
|
const attrs = (node && node.attrs) || {};
|
|
1159
1199
|
const code = attrs.code ? String(attrs.code) : null;
|
|
1160
|
-
const reason = attrs.reason || code || 'unknown';
|
|
1161
1200
|
|
|
1162
1201
|
if (code === '515') {
|
|
1163
1202
|
_whaDbg('[DBG] STREAM_ERROR 515 — restart required after pairing, reconnecting');
|
|
1164
|
-
this.emit('restart_required', { reason });
|
|
1203
|
+
this.emit('restart_required', { reason: '515' });
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Most stream errors carry no code at all — the reason is a child element,
|
|
1208
|
+
// and for <conflict> the type attribute on it says which kind. Reading only
|
|
1209
|
+
// the attributes reported every one of these as "code=null reason=unknown",
|
|
1210
|
+
// which is the same message for a session someone else took over and a
|
|
1211
|
+
// session that was deleted.
|
|
1212
|
+
const child = Array.isArray(node.content) && node.content[0] ? node.content[0] : null;
|
|
1213
|
+
const childTag = child && child.description ? child.description : null;
|
|
1214
|
+
const conflictType = childTag === 'conflict' && child.attrs
|
|
1215
|
+
? String(child.attrs.type || '') : null;
|
|
1216
|
+
|
|
1217
|
+
if (conflictType === 'replaced') {
|
|
1218
|
+
// Another client opened a session for this account and the server handed
|
|
1219
|
+
// it over. Reconnecting immediately just takes it back, and the two ends
|
|
1220
|
+
// trade the session until one of them is stopped — so this one stands
|
|
1221
|
+
// down and says why.
|
|
1222
|
+
_whaDbg('[DBG] STREAM_ERROR conflict/replaced — another client took the session');
|
|
1223
|
+
this._fatal = true;
|
|
1224
|
+
this.emit('connection_replaced', {
|
|
1225
|
+
message: 'Another client connected with this number and took over the ' +
|
|
1226
|
+
'session. Reconnecting would take it back and the two would ' +
|
|
1227
|
+
'keep displacing each other, so this connection has stopped. ' +
|
|
1228
|
+
'Close the other client, then connect again.'
|
|
1229
|
+
});
|
|
1230
|
+
this.disconnect();
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (conflictType === 'device_removed') {
|
|
1235
|
+
// The companion was unlinked from the phone. Same meaning as a 401.
|
|
1236
|
+
_whaDbg('[DBG] STREAM_ERROR conflict/device_removed — unlinked');
|
|
1237
|
+
this._fatal = true;
|
|
1238
|
+
this.emit('auth_failure', {
|
|
1239
|
+
reason: '401', location: null, loggedOut: true, node
|
|
1240
|
+
});
|
|
1241
|
+
this.disconnect();
|
|
1165
1242
|
return;
|
|
1166
1243
|
}
|
|
1167
1244
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1245
|
+
const reason = attrs.reason || conflictType || childTag || code || 'unknown';
|
|
1246
|
+
_whaDbg('[DBG] STREAM_ERROR code=' + code + ' child=' + childTag +
|
|
1247
|
+
' conflict=' + conflictType + ' reason=' + reason);
|
|
1248
|
+
this.emit('stream_error', { reason, code, conflictType, childTag, node });
|
|
1170
1249
|
}
|
|
1171
1250
|
|
|
1172
1251
|
// ─── IQ handling ──────────────────────────────────────────────────────────
|
|
@@ -1956,7 +2035,13 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1956
2035
|
// Server push: a contact's linked device list changed (they linked or
|
|
1957
2036
|
// unlinked a tablet, desktop, etc.). Invalidate that phone's cache entry
|
|
1958
2037
|
// so the next send triggers a fresh usync IQ and reaches all their devices.
|
|
1959
|
-
|
|
2038
|
+
// BinaryNode decodes an AD-encoded JID into an object, not a string, so
|
|
2039
|
+
// this has to be stringified before it can be split. It was not, and a
|
|
2040
|
+
// <notification type="devices"> from a LID-addressed contact threw
|
|
2041
|
+
// "fromJid.split is not a function" out of the node handler — taking the
|
|
2042
|
+
// whole dispatch down with it, so every notification behind it in the
|
|
2043
|
+
// offline queue was dropped too.
|
|
2044
|
+
const fromJid = String(attrs.from || '');
|
|
1960
2045
|
const fromPhone = fromJid.split('@')[0].split(':')[0];
|
|
1961
2046
|
if (fromPhone && this._devMgr) {
|
|
1962
2047
|
this._devMgr._dcDel([fromPhone]);
|
|
@@ -1980,7 +2065,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1980
2065
|
// Server push: a contact re-registered WhatsApp (new identity key / new phone).
|
|
1981
2066
|
// Their old Signal sessions are no longer valid — clear them and the device
|
|
1982
2067
|
// cache so the next send builds a fresh pkmsg session from scratch.
|
|
1983
|
-
const fromJid = attrs.from || '';
|
|
2068
|
+
const fromJid = String(attrs.from || '');
|
|
1984
2069
|
const fromPhone = fromJid.split('@')[0].split(':')[0];
|
|
1985
2070
|
if (fromPhone && this._devMgr) {
|
|
1986
2071
|
this._devMgr._dcDel([fromPhone]);
|