whalibmob 5.5.27 → 5.5.28
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 +9 -21
- package/lib/Registration.js +25 -1
- package/lib/messages/MessageSender.js +108 -21
- package/package.json +1 -1
package/lib/Client.js
CHANGED
|
@@ -134,7 +134,8 @@ class WhalibmobClient extends EventEmitter {
|
|
|
134
134
|
this._appStateVersions = {}; // collectionName → version (int)
|
|
135
135
|
this._sentMsgCache = new Map(); // msgId → {plaintext, toJid, msgId, mediaType, options, recipientPhone}
|
|
136
136
|
this._tcTokenStore = null; // TcTokenStore — loaded in init()
|
|
137
|
-
this._inFlightTcTokenIssuance = new Set(); // dedupe concurrent issuePrivacyTokens
|
|
137
|
+
this._inFlightTcTokenIssuance = new Set(); // dedupe concurrent proactive issuePrivacyTokens per JID
|
|
138
|
+
this._inFlight463Recoveries = new Set(); // dedupe concurrent 463-triggered token issuances per JID (separate from proactive)
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
get store() { return this._store; }
|
|
@@ -789,7 +790,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
789
790
|
}
|
|
790
791
|
|
|
791
792
|
const fromStr = attrs.from ? String(attrs.from) : '';
|
|
792
|
-
// Extract bare phone number: "
|
|
793
|
+
// Extract bare phone number: "407xxx@s.whatsapp.net" → "407xxxx"
|
|
793
794
|
const recipientPhone = fromStr.split('@')[0].split('.')[0];
|
|
794
795
|
|
|
795
796
|
process.stderr.write('[DBG] RETRY_RECV msgId=' + msgId + ' from=' + fromStr + ' — clearing session + resending\n');
|
|
@@ -832,25 +833,12 @@ class WhalibmobClient extends EventEmitter {
|
|
|
832
833
|
handler(attrs);
|
|
833
834
|
}
|
|
834
835
|
|
|
835
|
-
// ─── Error 463
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
const from = attrs.from ? String(attrs.from) : '';
|
|
842
|
-
process.stderr.write('[DBG] ACK_463 from=' + from + ' id=' + id + '\n');
|
|
843
|
-
if (from && this._tcTokenStore && !this._inFlightTcTokenIssuance.has(from)) {
|
|
844
|
-
this._inFlightTcTokenIssuance.add(from);
|
|
845
|
-
const issueTs = Math.floor(Date.now() / 1000);
|
|
846
|
-
const { normalizeJidForTcToken } = require('./messages/TcTokenStore');
|
|
847
|
-
const tcJid = normalizeJidForTcToken(from);
|
|
848
|
-
this._issuePrivacyTokens(from, issueTs)
|
|
849
|
-
.then(result => this._storeTcTokenFromIqResult(result, tcJid, issueTs))
|
|
850
|
-
.catch(() => {})
|
|
851
|
-
.finally(() => this._inFlightTcTokenIssuance.delete(from));
|
|
852
|
-
}
|
|
853
|
-
}
|
|
836
|
+
// ─── Error 463 ───────────────────────────────────────────────────────
|
|
837
|
+
// 463 recovery is fully handled inside MessageSender._dispatchAndAck:
|
|
838
|
+
// it issues the privacy token then automatically re-sends the message
|
|
839
|
+
// so the caller's promise resolves transparently (no loop).
|
|
840
|
+
// Nothing to do here — the pendingAck handler above already drove the
|
|
841
|
+
// recovery via _inFlight463Recoveries + _issuePrivacyTokens.
|
|
854
842
|
// ────────────────────────────────────────────────────────────────────
|
|
855
843
|
}
|
|
856
844
|
}
|
package/lib/Registration.js
CHANGED
|
@@ -590,13 +590,37 @@ async function assertRegistrationKeys(store, waVersion) {
|
|
|
590
590
|
return false;
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
async function requestSmsCode(store, method) {
|
|
593
|
+
async function requestSmsCode(store, method, opts) {
|
|
594
594
|
method = method || 'sms';
|
|
595
|
+
opts = opts || {};
|
|
595
596
|
const _device = getDeviceConfig();
|
|
596
597
|
const waVersion = await fetchWaVersion(_device);
|
|
597
598
|
store.version = waVersion;
|
|
598
599
|
store.device = _device;
|
|
599
600
|
|
|
601
|
+
// Email method: request code via email instead of SMS/voice.
|
|
602
|
+
// Sends method=email + email=<address> in the /code request.
|
|
603
|
+
// No auto-fallback for email — it either works or fails.
|
|
604
|
+
if (method === 'email') {
|
|
605
|
+
const emailAddr = opts.email || '';
|
|
606
|
+
if (!emailAddr) throw new Error('requestSmsCode: email method requires opts.email address');
|
|
607
|
+
const { cc: _regCc } = parsePhone(store.phoneNumber);
|
|
608
|
+
const _regMeta = getCountryMeta(_regCc);
|
|
609
|
+
const extra = [
|
|
610
|
+
'method', 'email',
|
|
611
|
+
'email', emailAddr,
|
|
612
|
+
'sim_mcc', _regMeta.mcc,
|
|
613
|
+
'sim_mnc', _regMeta.mnc,
|
|
614
|
+
'reason', '',
|
|
615
|
+
'cellular_strength', '1'
|
|
616
|
+
];
|
|
617
|
+
const result = await sendRequest('/code', store, waVersion, true, extra);
|
|
618
|
+
const status = result.status;
|
|
619
|
+
if (status === 'ok' || status === 'sent') return result;
|
|
620
|
+
const reason = result.reason || status || '';
|
|
621
|
+
throw new Error(`Registration email error: ${reason} — raw: ${JSON.stringify(result)}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
600
624
|
// Auto-fallback: if the primary method gets no_routes, try the alternate once.
|
|
601
625
|
// sms → wa_old, wa_old → sms, voice → sms
|
|
602
626
|
const fallbackMethod = method === 'wa_old' ? 'sms' : (method === 'sms' ? 'wa_old' : 'sms');
|
|
@@ -814,31 +814,118 @@ class MessageSender {
|
|
|
814
814
|
}
|
|
815
815
|
|
|
816
816
|
// ─── Dispatch and wait for ack ────────────────────────────────────────────
|
|
817
|
+
//
|
|
818
|
+
// Error 463 recovery (mirrors Baileys messages-recv.js inFlight463Recoveries):
|
|
819
|
+
// 1. 463 ack arrives → issue privacy token via _issuePrivacyTokens
|
|
820
|
+
// 2. Wait for token to be stored in _tcTokenStore
|
|
821
|
+
// 3. Re-send the SAME message node with a new msgId — the tctoken node
|
|
822
|
+
// was already attached by _sendDMMessage, so the re-dispatched node
|
|
823
|
+
// carries the same content; the token lookup on re-send will succeed.
|
|
824
|
+
// The re-send uses _isRetry=true to prevent recursive 463 handling.
|
|
825
|
+
// 4. Resolve/reject based on the re-send ack — transparent to the caller.
|
|
826
|
+
//
|
|
827
|
+
// Uses a SEPARATE _inFlight463Recoveries Set (not shared with the proactive
|
|
828
|
+
// _inFlightTcTokenIssuance) so 463 recovery and proactive issuance never
|
|
829
|
+
// block each other. Matches Baileys pattern exactly.
|
|
830
|
+
|
|
831
|
+
_dispatchAndAck(node, msgId, _isRetry) {
|
|
832
|
+
const self = this;
|
|
833
|
+
const client = this._client;
|
|
834
|
+
const socket = this._socket;
|
|
817
835
|
|
|
818
|
-
_dispatchAndAck(node, msgId) {
|
|
819
836
|
return new Promise((resolve, reject) => {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
837
|
+
let _timer = null;
|
|
838
|
+
|
|
839
|
+
const armTimer = (id) => {
|
|
840
|
+
_timer = setTimeout(() => {
|
|
841
|
+
client._pendingAcks.delete(id);
|
|
842
|
+
resolve({ id, status: 'sent' });
|
|
843
|
+
}, 20000);
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
const sendNode = (n, id) => {
|
|
847
|
+
armTimer(id);
|
|
848
|
+
client._pendingAcks.set(id, (ackAttrs) => {
|
|
849
|
+
clearTimeout(_timer);
|
|
850
|
+
_timer = null;
|
|
851
|
+
const error = ackAttrs && ackAttrs.error ? String(ackAttrs.error) : '';
|
|
852
|
+
|
|
853
|
+
// ─── Error 463: missing tcToken / reachout timelock ───────────
|
|
854
|
+
// Issue privacy token then automatically re-send once with the
|
|
855
|
+
// token attached. Uses a separate _inFlight463Recoveries Set so
|
|
856
|
+
// concurrent 463 recoveries and proactive issuances don't clash.
|
|
857
|
+
if (error === '463' && !_isRetry) {
|
|
858
|
+
const ackFrom = ackAttrs.from ? String(ackAttrs.from) : '';
|
|
859
|
+
const tcJid = normalizeJidForTcToken(
|
|
860
|
+
ackFrom || String(node.attrs && node.attrs.to || '')
|
|
861
|
+
);
|
|
862
|
+
process.stderr.write(
|
|
863
|
+
'[DBG] ACK_463 from=' + ackFrom + ' id=' + id +
|
|
864
|
+
' tcJid=' + tcJid + ' — issuing tctoken then retrying\n'
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
if (client._inFlight463Recoveries.has(tcJid)) {
|
|
868
|
+
// Another concurrent recovery in progress for this JID —
|
|
869
|
+
// wait ~3 s for it to finish then retry with whatever token
|
|
870
|
+
// is now stored (may or may not succeed).
|
|
871
|
+
process.stderr.write('[DBG] ACK_463 already in-flight for ' + tcJid + ' — retrying after delay\n');
|
|
872
|
+
setTimeout(() => {
|
|
873
|
+
const retryId = crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
874
|
+
const retryNode = new BinaryNode(
|
|
875
|
+
node.description,
|
|
876
|
+
Object.assign({}, node.attrs, { id: retryId }),
|
|
877
|
+
node.content
|
|
878
|
+
);
|
|
879
|
+
self._dispatchAndAck(retryNode, retryId, /* _isRetry */ true)
|
|
880
|
+
.then(r => resolve(r))
|
|
881
|
+
.catch(e => reject(e));
|
|
882
|
+
}, 3000);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
client._inFlight463Recoveries.add(tcJid);
|
|
887
|
+
const issueTs = Math.floor(Date.now() / 1000);
|
|
888
|
+
|
|
889
|
+
client._issuePrivacyTokens(ackFrom || tcJid, issueTs)
|
|
890
|
+
.then(result => client._storeTcTokenFromIqResult(result, tcJid, issueTs))
|
|
891
|
+
.then(() => {
|
|
892
|
+
process.stderr.write('[DBG] ACK_463 token stored for ' + tcJid + ' — retrying send\n');
|
|
893
|
+
const retryId = crypto.randomBytes(8).toString('hex').toUpperCase();
|
|
894
|
+
const retryNode = new BinaryNode(
|
|
895
|
+
node.description,
|
|
896
|
+
Object.assign({}, node.attrs, { id: retryId }),
|
|
897
|
+
node.content
|
|
898
|
+
);
|
|
899
|
+
return self._dispatchAndAck(retryNode, retryId, /* _isRetry */ true);
|
|
900
|
+
})
|
|
901
|
+
.then(r => resolve(r))
|
|
902
|
+
.catch(err => {
|
|
903
|
+
process.stderr.write('[DBG] ACK_463 recovery failed: ' + (err && err.message) + '\n');
|
|
904
|
+
reject(new Error('Send error 463: tctoken recovery failed — ' + (err && err.message)));
|
|
905
|
+
})
|
|
906
|
+
.finally(() => client._inFlight463Recoveries.delete(tcJid));
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
// ─────────────────────────────────────────────────────────────
|
|
910
|
+
|
|
911
|
+
if (error) {
|
|
912
|
+
reject(new Error('Send error ' + error + ' for ' + id));
|
|
913
|
+
} else {
|
|
914
|
+
resolve({ id, t: ackAttrs && ackAttrs.t, status: 'sent' });
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
socket.sendNode(n);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
clearTimeout(_timer);
|
|
922
|
+
_timer = null;
|
|
923
|
+
client._pendingAcks.delete(id);
|
|
924
|
+
reject(err);
|
|
832
925
|
}
|
|
833
|
-
}
|
|
926
|
+
};
|
|
834
927
|
|
|
835
|
-
|
|
836
|
-
this._socket.sendNode(node);
|
|
837
|
-
} catch (err) {
|
|
838
|
-
clearTimeout(timer);
|
|
839
|
-
this._client._pendingAcks.delete(msgId);
|
|
840
|
-
reject(err);
|
|
841
|
-
}
|
|
928
|
+
sendNode(node, msgId);
|
|
842
929
|
});
|
|
843
930
|
}
|
|
844
931
|
|