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 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 calls per JID
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: "40756469325@s.whatsapp.net" → "40756469325"
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: reachout timelock / missing tcToken ─────────────────
836
- // The server returns error 463 when a DM is sent without a tctoken node
837
- // and the account has hit the "reaching out" rate limit.
838
- // Recovery: immediately issue a privacy token to the sender and store it
839
- // so future messages carry the <tctoken> node automatically.
840
- if (error === '463') {
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
  }
@@ -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
- const timer = setTimeout(() => {
821
- this._client._pendingAcks.delete(msgId);
822
- // Resolve anyway WA often doesn't ack immediately
823
- resolve({ id: msgId, status: 'sent' });
824
- }, 20000);
825
-
826
- this._client._pendingAcks.set(msgId, (ackAttrs) => {
827
- clearTimeout(timer);
828
- if (ackAttrs && ackAttrs.error) {
829
- reject(new Error('Send error ' + ackAttrs.error + ' for ' + msgId));
830
- } else {
831
- resolve({ id: msgId, t: ackAttrs && ackAttrs.t, status: 'sent' });
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
- try {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.5.27",
3
+ "version": "5.5.28",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",