feat: add cancelPairingCode() and requestPairingCode() on QR clients (#22449)

* feat: add cancelPairingCode() and allow requestPairingCode()

- requestPairingCode() now exposes onCodeReceivedEvent if needed,
  so it works on clients initialized in QR mode
- Add cancelPairingCode() method to stop pairing and return to QR
- Update TypeScript definitions

* fix(auth): dynamic ADV key in getQR + structural cancelPairingCode

the getQR closure captured advSecretKey once during initialize(), but
requestpairingcode/cancelPairingCode changes the key in IndexedDB.
the stale closure key caused QR scans to fail with HMAC validation error.

fix: read key dynamically via WAWebUserPrefsMultiDevice.getADVSecretKey()
(synchronous in-memory cache) on every getQR call, matching how WhatsApp
web's internal QR component works.

cancelpairingcode now mirrors WhatsApp Web's resetLinkDeviceState:
refreshqr() restarts WebSocket for fresh refs, then
initializeqrlinking() generates a new key. QR emission happens
automatically via the existing change:ref listener.

---------

Co-authored-by: BenyFilho <168232825+BenyFilho@users.noreply.github.com>
This commit is contained in:
Adi1231234 2026-03-24 15:01:31 +02:00 • committed by GitHub
commit 3e810b6913
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 4 deletions

3
index.d.ts vendored
View file

@ -197,6 +197,9 @@ declare namespace WAWebJS {
intervalMs?: number, intervalMs?: number,
): Promise<string>; ): Promise<string>;
/** Cancels an active pairing code session and returns to QR code mode */
cancelPairingCode(): Promise<void>
/** Force reset of connection state for the client */ /** Force reset of connection state for the client */
resetState(): Promise<void>; resetState(): Promise<void>;

View file

@ -255,8 +255,6 @@ class Client extends EventEmitter {
window.AuthStore.Base64Tools.encodeB64( window.AuthStore.Base64Tools.encodeB64(
registrationInfo.identityKeyPair.pubKey, registrationInfo.identityKeyPair.pubKey,
); );
const advSecretKey =
await window.AuthStore.RegistrationUtils.getADVSecretKey();
const platform = const platform =
window.AuthStore.RegistrationUtils.DEVICE_PLATFORM; window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
const getQR = (ref) => const getQR = (ref) =>
@ -266,10 +264,11 @@ class Client extends EventEmitter {
',' + ',' +
identityKeyB64 + identityKeyB64 +
',' + ',' +
advSecretKey + window
.require('WAWebUserPrefsMultiDevice')
.getADVSecretKey() +
',' + ',' +
platform; platform;
window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
window.AuthStore.Conn.on('change:ref', (_, ref) => { window.AuthStore.Conn.on('change:ref', (_, ref) => {
window.onQRChangedEvent(getQR(ref)); window.onQRChangedEvent(getQR(ref));
@ -516,6 +515,14 @@ class Client extends EventEmitter {
showNotification = true, showNotification = true,
intervalMs = 180000, intervalMs = 180000,
) { ) {
await exposeFunctionIfAbsent(
this.pupPage,
'onCodeReceivedEvent',
async (code) => {
this.emit(Events.CODE_RECEIVED, code);
return code;
},
);
return await this.pupPage.evaluate( return await this.pupPage.evaluate(
async (phoneNumber, showNotification, intervalMs) => { async (phoneNumber, showNotification, intervalMs) => {
const getCode = async () => { const getCode = async () => {
@ -553,6 +560,22 @@ class Client extends EventEmitter {
); );
} }
/**
* Cancels an active pairing code session and returns to QR code mode
*/
async cancelPairingCode() {
await this.pupPage.evaluate(async () => {
if (window.codeInterval) {
clearInterval(window.codeInterval);
window.codeInterval = undefined;
}
window.require('WAWebLaunchSocketUtils').refreshQR();
await window
.require('WAWebAltDeviceLinkingApi')
.initializeQRLinking();
});
}
/** /**
* Attach event listeners to WA Web * Attach event listeners to WA Web
* Private function * Private function