feat: Switch between QR or phone number pairing method (#3180)

* fix(webpack-exodus): Added the new patch

* fix(webpack-exodus): Fixed v2.3000.x and v2.24

* chore(webpack-exodus): eslint

* fix(webpack-exodus): Fixed Store

* fix(webpack-exodus): slight store fix

* chore(webpack-exodus): fix example so that I don't crash on every load

* chore: eslint fix

* fix(webpack-exodus): prevent confusion with build systems

* fix(webpack-exodus): Add proper groups module

* fix(webpack-exodus): Fix order queries

* fix(webpack-exodus): Bad export name

* feat(webpack-exodus): Re-injection, No more selectors, Pairing Code auth

* fix(webpack-exodus): Eslint and better login/logout handling

* fix(webpack-exodus): 2.24 compatibility

* revert testing options

* chore(webpack-exodus): ESLint

* chore(webpack-exodus): final eslint tweak (did not commit with last commit for some reason)

* style: fix broken link in readme file

* fix(webpack-exodus): Fix forwarding messages

* fix(webpack-exodus): finish forwarding function

* Making the ESLint god happy

* Fix window.Store.ProfilePic.profilePicFind is not a function error (#3001)

* Change window.Store.ProfilePic.profilePicFind to window.Store.ProfilePic.requestProfilePicFromServer

* Prevent breaking v2.2x

* refactor: make it more readable

---------

Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com>

* Sanitize, improves and fixes

* make ESList god happy

* fix getInviteCode for group (#3007) (#3029)

* fix getInviteCode for group (#3007)

* move changes to proper getInviteCode method.

* style fix

* refactor: add try-catch in a case the user is not a group admin

* refactor: handle promise properly

---------

Co-authored-by: Sven Neumann <s.neumann@websix.de>
Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com>

* fixes

* Update LocalWebCache.js

* fix delete on 2.3000 (#3048)

* fix delete on 2.3000

* fix compareWwebVersions

* fix: use proper comparison operator

---------

Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com>

* Implement bot invoking capabilities (#3009)

* Implement bot invoking capabilities

* Update index.d.ts

* Eslint

* Webpack exodus fix message_reaction event (#3099)

* exposed WAWebAddonReactionTableMode

* capture reaction using WAWebAddonReactionTableMode

* move compareWwebVersions and check version before trying to expose new store

* message_reaction on example.js (#3102)

* handle message_reaction event on example.js

* log wweb version on ready

* fix reactions on pvt chats (#3104)

* New group function

* new group settings

* Update Client.js

* Switch pairing method between QR and phone number

* Remove existing pairing code interval

* Modify example.js

* Update docs

* Small improvements in inject function

* Fix ESLint

* example.js correction

* docs: fix docs for 'requestPairingCode' method

Co-authored-by: Leandro Matilla <30844484+wis-dev@users.noreply.github.com>

* Update Utils.js getChatModel because Meta chat object affected Group Chats

* Ajust to set isGroup as false as default

* Remove the need for custom interval

* Fix typo

* Fix intermittent response bug #9864

Co-authored-by: Mob Code 100 <66469454+MobCode100@users.noreply.github.com>

* fix: eslint fix

* Revert "Remove the need for custom interval"

This reverts commit c5d5e5626684742cadbf58f28739c1c2c9dcc582.

* Add wait for PairingCodeLinkUtils on requestPairingCode

* Remove unused vars WhatsWebStaticURL

Co-authored-by: Mob Code 100 <66469454+MobCode100@users.noreply.github.com>

* style: format code

---------

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
Co-authored-by: alechkos <93551621+alechkos@users.noreply.github.com>
Co-authored-by: tuyuribr <45042245+tuyuribr@users.noreply.github.com>
Co-authored-by: Seow Zhen Jun <56989187+seowzhenjun0126@users.noreply.github.com>
Co-authored-by: Sven <sven2neumann@gmail.com>
Co-authored-by: Sven Neumann <s.neumann@websix.de>
Co-authored-by: Jorge Rocha Gualtieri <jorge@jrg.com.br>
Co-authored-by: Mateus Mercer <accounts@matbm.net>
Co-authored-by: Leandro Matilla <30844484+wis-dev@users.noreply.github.com>
Co-authored-by: BenyFilho <168232825+BenyFilho@users.noreply.github.com>
Co-authored-by: Shir Serlui <70711723+shirser121@users.noreply.github.com>
This commit is contained in:
Mob Code 100 2025-08-09 05:37:25 +08:00 • committed by GitHub
commit b5adf47664
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 103 additions and 50 deletions

View file

@ -96,8 +96,8 @@ class Client extends EventEmitter {
*/
async inject() {
await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
await this.setDeviceName(this.options.deviceName, this.options.browserName);
const pairWithPhoneNumber = this.options.pairWithPhoneNumber;
const version = await this.getWWebVersion();
const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;
@ -143,41 +143,55 @@ class Client extends EventEmitter {
return;
}
// Register qr events
let qrRetries = 0;
await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
/**
* Emitted when a QR code is received
* @event Client#qr
* @param {string} qr QR Code
*/
this.emit(Events.QR_RECEIVED, qr);
if (this.options.qrMaxRetries > 0) {
qrRetries++;
if (qrRetries > this.options.qrMaxRetries) {
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
await this.destroy();
// Register qr/code events
if (pairWithPhoneNumber.phoneNumber) {
await exposeFunctionIfAbsent(this.pupPage, 'onCodeReceivedEvent', async (code) => {
/**
* Emitted when a pairing code is received
* @event Client#code
* @param {string} code Code
* @returns {string} Code that was just received
*/
this.emit(Events.CODE_RECEIVED, code);
return code;
});
this.requestPairingCode(pairWithPhoneNumber.phoneNumber, pairWithPhoneNumber.showNotification, pairWithPhoneNumber.intervalMs);
} else {
let qrRetries = 0;
await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
/**
* Emitted when a QR code is received
* @event Client#qr
* @param {string} qr QR Code
*/
this.emit(Events.QR_RECEIVED, qr);
if (this.options.qrMaxRetries > 0) {
qrRetries++;
if (qrRetries > this.options.qrMaxRetries) {
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
await this.destroy();
}
}
}
});
});
await this.pupPage.evaluate(async () => {
const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
});
await this.pupPage.evaluate(async () => {
const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
});
}
}
await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => {
if (state == 'UNPAIRED_IDLE') {
if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) {
// refresh qr code
window.Store.Cmd.refreshQR();
}
@ -346,15 +360,32 @@ class Client extends EventEmitter {
/**
* Request authentication via pairing code instead of QR code
* @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
* @param {boolean} showNotification - Show notification to pair on phone number
* @param {boolean} [showNotification = true] - Show notification to pair on phone number
* @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes)
* @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
*/
async requestPairingCode(phoneNumber, showNotification = true) {
return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
}, phoneNumber, showNotification);
async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) {
return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => {
const getCode = async () => {
while (!window.AuthStore.PairingCodeLinkUtils) {
await new Promise(resolve => setTimeout(resolve, 250));
}
window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
};
if (window.codeInterval) {
clearInterval(window.codeInterval); // remove existing interval
}
window.codeInterval = setInterval(async () => {
if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') {
clearInterval(window.codeInterval);
return;
}
window.onCodeReceivedEvent(await getCode());
}, intervalMs);
return window.onCodeReceivedEvent(await getCode());
}, phoneNumber, showNotification, intervalMs);
}
/**