fix: make inject() resilient to page navigation during initialization#127083
fix: make inject() resilient to page navigation during initialization#127083Adi1231234 wants to merge 2 commits intowwebjs:mainfrom
Conversation
|
Hey @purpshell @BenyFilho, friendly bump on this one 🙂 This fixes inject() crashing after the machine goes to sleep and wakes up. Happens because the page navigates during sleep and the execution context gets destroyed. Let me know if you'd like me to change anything, happy to discuss on Discord too! |
Need user tests to validate it, and update your changes with branch main |
6cc0468 to
c4b1202
Compare
|
Hey @BenyFilho, just rebased onto the latest main, resolved the conflicts and CI is passing. These PRs keep getting conflicts because other things get merged while mine are waiting for review. I'd really appreciate getting them merged so I don't have to keep rebasing 😅 You mentioned user tests, could you explain what you mean exactly? Happy to add whatever helps move this forward. |
Need user tests to validate it, and update your changes with branch main |
Replace manual evaluate-based polling loops with waitForFunction, which natively survives execution context destruction caused by page navigation (e.g. Chrome's internal IndexedDB recovery after system sleep/resume). Also move the framenavigated listener registration before the initial inject() call, so navigation events during inject are handled by the existing listener. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c4b1202 to
929c81c
Compare
929c81c to
b199c22
Compare
|
Hey @BenyFilho, thanks! Just rebased onto main again and resolved conflicts, CI is passing. This one is important for anyone running whatsapp-web.js on machines that go to sleep/resume (laptops, servers with power management). I've tested it thoroughly in my own setup and it's been solid. If anyone else has hit this crash and wants to validate, please give it a try! I really need these fixes merged, they're all critical for my use case. If anyone wants to help review or test, I'd be more than happy to pay for your time. Feel free to reach out. |
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! 🙏 |
Integrate changes from upstream PR wwebjs#127083: - Replace manual while polling loops with page.waitForFunction() which natively survives execution context destruction - Move framenavigated listener registration before inject() in initialize() so recovery is in place from the start
|
Closing in favor of #201653, which consolidates this PR together with #127090 into a single, more comprehensive fix. The new PR includes all the changes from both PRs plus additional fixes (listener cleanup, concurrency guard, atomic hasSynced check, qrRetries reset) - all validated with A/B diagnostic testing across 5 real-world scenarios. Thanks everyone for the feedback and reviews here - it really helped shape the final solution! |
…127083 - RemoteAuth: pass session name directly to store.save instead of full path (fixes PR wwebjs#201660 - path.join was passing the full filesystem path instead of just the session identifier) - Client: register framenavigated listener before calling inject() so any page navigation that occurs during initialization is captured (fixes PR wwebjs#127083 ordering issue)
…127083 (#65) - RemoteAuth: pass session name directly to store.save instead of full path (fixes PR wwebjs#201660 - path.join was passing the full filesystem path instead of just the session identifier) - Client: register framenavigated listener before calling inject() so any page navigation that occurs during initialization is captured (fixes PR wwebjs#127083 ordering issue)
…127083 - RemoteAuth: pass session name directly to store.save instead of full path (fixes PR wwebjs#201660 - path.join was passing the full filesystem path instead of just the session identifier) - Client: register framenavigated listener before calling inject() so any page navigation that occurs during initialization is captured (fixes PR wwebjs#127083 ordering issue)
Summary
Fixes #127082
inject()usespage.evaluate()in polling loops to wait forwindow.Debug?.VERSIONandwindow.Store. After sleep/resume, Chrome may perform an internal page navigation (IndexedDB recovery) that destroys the execution context, causingpage.evaluate()to throw"Execution context was destroyed".This PR:
page.evaluate()polling loops withpage.waitForFunction(), which natively survives execution context destructionframenavigatedlistener registration to beforeinject()ininitialize(), so the recovery mechanism is in place from the startNo retry logic, no try/catch wrapping, no error swallowing. Uses the correct Puppeteer API that handles execution context lifecycle internally.
Changes
src/Client.jsFirst polling loop (auth timeout): Replace manual
whileloop withpage.evaluate()->page.waitForFunction('window.Debug?.VERSION != undefined', { timeout })Second polling loop (ready timeout): Replace manual
whileloop withpage.evaluate()->page.waitForFunction('window.Store != undefined', { timeout: 30000 })initialize()ordering: Movethis.pupPage.on('framenavigated', ...)registration to beforeawait this.inject()so that if inject fails, the framenavigated handler is already in place for recoveryWhy
waitForFunctionworksPuppeteer's
WaitTasktreats"Execution context was destroyed"as a non-fatal error (getBadError()returnsundefined). When a new context is created after navigation,IsolatedWorldcallstaskManager.rerunAll()to re-evaluate all waiting tasks in the new context. This is the intended Puppeteer mechanism for handling navigation during waits.Verified in both Puppeteer 18.x (used by wwjs) and 24.x.
Test plan
page.evaluate()throws during navigation (the bug)page.waitForFunction()survives navigation (the fix)framenavigated+waitForFunction