whalibmob 5.5.26 → 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/cli.js CHANGED
@@ -196,9 +196,10 @@ const HELP = `
196
196
  /biz <phone|jid> query business profile
197
197
 
198
198
  Registration
199
- /reg check <phone> check if number has WhatsApp
200
- /reg code <phone> [sms|voice|wa_old] request verification code
201
- /reg confirm <phone> <code> complete registration
199
+ /reg check <phone> check if number has WhatsApp
200
+ /reg code <phone> [sms|voice|wa_old] request verification code
201
+ /reg code <phone> email <address> request code via email
202
+ /reg confirm <phone> <code> complete registration
202
203
 
203
204
  Connection
204
205
  /connect <phone> connect to WhatsApp
@@ -1153,8 +1154,14 @@ async function handleLine(line) {
1153
1154
  }
1154
1155
  else if (sub === 'code') {
1155
1156
  const ph = normalizePhone(p[2]);
1156
- const method = p[3] || 'sms';
1157
- if (!ph) { fail('usage: /reg code <phone> [sms|voice|wa_old]'); break; }
1157
+ const method = (p[3] || 'sms').toLowerCase();
1158
+ // email method: /reg code <phone> email <address>
1159
+ const emailAddr = method === 'email' ? (p[4] || '') : '';
1160
+ if (!ph) { fail('usage: /reg code <phone> [sms|voice|wa_old|email <address>]'); break; }
1161
+ if (method === 'email' && !emailAddr) {
1162
+ fail('email method requires an address — usage: /reg code <phone> email <address>');
1163
+ break;
1164
+ }
1158
1165
  if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
1159
1166
  const sessFile = path.join(_sessDir, `${ph}.json`);
1160
1167
  let store = loadStore(sessFile);
@@ -1173,8 +1180,10 @@ async function handleLine(line) {
1173
1180
  out(' new keys saved — proceed with code below');
1174
1181
  }
1175
1182
  }
1176
- out('requesting ' + method + ' code for +' + ph + '...');
1177
- const r = await requestSmsCode(store, method);
1183
+ const methodLabel = method === 'email' ? ('email ' + emailAddr) : method;
1184
+ out('requesting ' + methodLabel + ' code for +' + ph + '...');
1185
+ const codeOpts = method === 'email' ? { email: emailAddr } : {};
1186
+ const r = await requestSmsCode(store, method, codeOpts);
1178
1187
  store.codePending = true;
1179
1188
  saveStore(store, sessFile);
1180
1189
  out(' status ' + (r && r.status));
@@ -1427,7 +1436,8 @@ usage:
1427
1436
 
1428
1437
  options:
1429
1438
  --session <dir> session directory (default: ~/.waSession)
1430
- --method sms | voice | wa_old (default: sms)
1439
+ --method sms | voice | wa_old | email (default: sms)
1440
+ --email <address> email address (required when --method email)
1431
1441
 
1432
1442
  after connecting, type /help for all available commands.
1433
1443
  `.trim();
@@ -1475,9 +1485,14 @@ async function main() {
1475
1485
  }
1476
1486
 
1477
1487
  if (flags['request-code'] !== undefined) {
1478
- const ph = phone || normalizePhone(pos[0] || '');
1479
- const method = flags.method || 'sms';
1488
+ const ph = phone || normalizePhone(pos[0] || '');
1489
+ const method = (flags.method || 'sms').toLowerCase();
1490
+ const emailAddr = flags.email || '';
1480
1491
  if (!ph) { fail('phone number required'); process.exit(1); }
1492
+ if (method === 'email' && !emailAddr) {
1493
+ fail('--method email requires --email <address>');
1494
+ process.exit(1);
1495
+ }
1481
1496
  if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
1482
1497
  const sessFile = path.join(_sessDir, `${ph}.json`);
1483
1498
  let store = loadStore(sessFile);
@@ -1500,9 +1515,11 @@ async function main() {
1500
1515
  }
1501
1516
  // If store.codePending === true, keys were already accepted by WhatsApp in a
1502
1517
  // prior /code request — reuse the exact same store without any /exist call.
1503
- out('requesting ' + method + ' code for +' + ph + '...');
1518
+ const methodLabel = method === 'email' ? ('email ' + emailAddr) : method;
1519
+ out('requesting ' + methodLabel + ' code for +' + ph + '...');
1504
1520
  try {
1505
- const r = await requestSmsCode(store, method);
1521
+ const codeOpts = method === 'email' ? { email: emailAddr } : {};
1522
+ const r = await requestSmsCode(store, method, codeOpts);
1506
1523
  store.codePending = true;
1507
1524
  saveStore(store, sessFile);
1508
1525
  out(' status ' + (r && r.status));
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.26",
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",