feat: add cancelPairingCode() and requestPairingCode() on QR clients#22449
feat: add cancelPairingCode() and requestPairingCode() on QR clients#22449BenyFilho merged 6 commits intowwebjs:mainfrom
Conversation
6f49eb4 to
c866e50
Compare
312c35a to
29fead3
Compare
|
Hey @purpshell @BenyFilho, just checking in on this PR 🙂 It adds cancelPairingCode() and lets you call requestPairingCode() on clients that were initialized with QR. Pretty useful for apps that want to let users switch between QR and phone number. Any chance you could take a look? Happy to make changes or jump on Discord if needed! |
Need user tests to validate it, and update your changes with branch main |
29fead3 to
d4b22ac
Compare
|
Hey @BenyFilho, just rebased onto the latest main so it's up to date and CI is green. I keep running into conflicts because other PRs get merged while mine stay open, and I have to keep rebasing them. I'd really love to get these merged so I can stop chasing conflicts 😅 You mentioned user tests, could you clarify what exactly you're looking for? I'd be happy to add whatever is needed to move this forward. |
Need user tests to validate it, and update your changes with branch main |
d4b22ac to
1a2694e
Compare
- 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
1a2694e to
d2784c1
Compare
|
Hey @BenyFilho, thanks for looking at this! Rebased onto latest main and CI is green. This feature lets users switch between QR and pairing code without destroying the client, which is pretty useful for apps with a linking UI. I've been using it in my own app and tested the full round trip (QR -> pairing code -> cancel -> QR again) multiple times. If anyone here needs this functionality or wants to give it a spin, I'd love your feedback. I really need my open PRs merged so if anyone can help review or test them, I'm happy to pay for your time. Just ping me! |
Hi, This is a community project, so changes need to be reviewed and validated by other users. They should be useful for the community in general and must not break existing functionality. I understand that these changes are important for your use case, but they also need to make sense for the community as a whole. The approval decision is based on technical and community impact, not on financial aspects. Thanks for your understanding. |
|
Hey @BenyFilho, just want to clarify - I absolutely did not mean paying for approvals. I meant compensating someone for their time reviewing and discussing the best approach, even if the conclusion is that my implementation needs rework. I share the same philosophy I've seen you promote - if you have a problem, fix it yourself. That's why I've put in the work and opened multiple PRs. Unfortunately it's been hard to get people to test and review them. I truly believe in this project and would rather contribute back than maintain my own fork with all the fixes. So yes, I'm genuinely willing to pay for help and support to move these fixes forward - for the benefit of the whole community. Thanks! 🙏 |
|
@Adi1231234 Cool idea, switching between QR and pairing code without recreating the client would be really useful. I spent some time looking at the WA Web internals to understand the flow, and I think cancelPairingCode() has a problem that would make the QR it emits unscannable. The issue is with how window.getQR works. It's a closure created during initialize() that captures the ADV secret key once. But by the time cancelPairingCode() runs, that key is no longer valid - initializeAltDeviceLinking() (called during requestPairingCode) deletes it from IndexedDB, and then initializeQRLinking() (called during cancel) generates a completely new random one. So the closure still has the original key, but IndexedDB has a different one. This actually causes a logout, not just a broken QR. When a phone scans the code, the server uses the key from the QR string to compute an HMAC, and the web client validates by reading the current key from IndexedDB. Since they don't match, logoutAfterValidationFail() kicks in. I looked at how WA Web's own QR component handles this - it reads the key fresh from IndexedDB on every render instead of caching it, which avoids this entirely. A couple more things I noticed: initializeQRLinking() is async (it writes to IndexedDB) but it's called without await, so the QR gets emitted before the state reset is done. Also, WA Web's internal resetLinkDeviceState always calls require('WAWebLaunchSocketUtils').refreshQR() before switching modes - this restarts the WebSocket and gets fresh refs from the server. Without it Conn.ref is probably expired since refs only live for ~60 seconds. You're right that Store.Cmd.refreshQR() is gone, but WAWebLaunchSocketUtils.refreshQR() is still there and that's what the internal code uses. The exposeFunctionIfAbsent part in requestPairingCode looks good though, that makes sense. |
25c26f6 to
0c2b592
Compare
|
@BenyFilho Thanks! @sofi-ans Good catch, you were right. I tested it and the QR scan was actually failing. Fixed it - getQR now reads the key fresh instead of using the cached one, and cancelPairingCode follows the same flow WA Web uses internally (refreshQR + initializeQRLinking). Works now. |
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.
0c2b592 to
154d702
Compare
- PR wwebjs#22449: dynamic ADV key via WAWebUserPrefsMultiDevice (closure bug fix), cancelPairingCode uses WAWebLaunchSocketUtils + WAWebAltDeviceLinkingApi - PR wwebjs#201653: remove unnecessary try/catch around obj.off() and framenavigated - PR wwebjs#182677: batch ciphertext PDO requests via pendingResend Set, remove try/catch from isPlaceholderMessageResendEnabled, add guard check in failTimer before emitting ciphertext_failed - upstream: rename WAWebGroupQueryJob to WAWebGroupInviteJob in acceptInvite
- PR wwebjs#22449: dynamic ADV key via WAWebUserPrefsMultiDevice (closure bug fix), cancelPairingCode uses WAWebLaunchSocketUtils + WAWebAltDeviceLinkingApi - PR wwebjs#201653: remove unnecessary try/catch around obj.off() - PR wwebjs#182677: remove try/catch from isPlaceholderMessageResendEnabled - upstream: rename WAWebGroupQueryJob to WAWebGroupInviteJob in acceptInvite
|
@Adi1231234 @BenyFilho Works good 👍 |
Summary
Add
cancelPairingCode()method and allowrequestPairingCode()to work on QR-initialized clients (not just clients initialized withpairWithPhoneNumberoption).Changes
requestPairingCode()- allow on QR-initialized clientsWhen called on a client that was initialized in QR mode,
onCodeReceivedEventmay not be exposed yet (it is only set up ininitialize()whenpairWithPhoneNumberis configured). AddedexposeFunctionIfAbsentcall at the beginning ofrequestPairingCode()to ensure the callback is available regardless of initialization mode.cancelPairingCode()- new methodCancels an active pairing code session and returns to QR code mode:
window.codeIntervalrefresh timerPairingCodeLinkUtils.initializeQRLinking()to switch WhatsApp Web back to QR modeonQRChangedEventusing the cachedgetQRhelperwindow.getQR- cached QR builderThe
getQRfunction (which builds the full QR string from registration keys) is now saved onwindowduringinitialize(). This is necessary because:Store.Cmd.refreshQR()is no longer available in current WhatsApp Web versionsgetADVSecretKey()returns null aftersetPairingType("ALT_DEVICE_LINKING")is calledchange:reflistener registered ininitialize()already uses this closure, so caching it onwindowallowscancelPairingCode()to reuse the same keysTypeScript definition
Added
cancelPairingCode(): Promise<void>toindex.d.ts.Use case
Allows switching between QR code and pairing code authentication without destroying and recreating the client. This enables UIs where users can toggle between QR scanning and phone number pairing on the same screen.
Testing
Tested full round-trip flow via E2E tests:
All transitions work without destroying the client.
Closes #22390