fix: close race where updateIntent resolved before iframes applied new sdkAuthorization - #1738
fix: close race where updateIntent resolved before iframes applied new sdkAuthorization#1738aritro2002 wants to merge 4 commits into
Conversation
…w sdkAuthorization
|
Automated review findings (posted as PR-level comment because inline review-comment creation is unavailable in this run):
|
| headers: { | ||
| "Cache-Control": "must-revalidate", | ||
| }, | ||
| allowedHosts: "all", |
There was a problem hiding this comment.
whats the reason for this change?
There was a problem hiding this comment.
I had made this changes for cypress, so, basically ->
The application was running on localhost:9060 and was trying to load HyperLoader.js from localhost:9050, which was being blocked due to the different origins.
To fix this, I added allowedHosts: "all".
I will remove this changes, create a separate PR for it.
… cvc-widget-race-condition
c5d0c1f to
b7a2ab2
Compare
Type of Change
Description
updateIntent()could resolve before the widgets it just updated (CardCVCElement, ExpressCheckoutElement, etc.) had actually applied the newsdkAuthorization— so a confirm fired right afterupdateIntentcould go out with the old session and get a401from the backend ("API key not provided or invalid API key used").Root cause
paymentSession.updateIntenthardcodedshouldWaitForReady=false—it never waited for any iframe acknowledgment before resolving.
elements.updateIntentonly waited if aPaymentElementhappened tobe mounted (
paymentElementIframeRef->Array.length > 0); any otherwidget (a standalone CVC widget, headless, express checkout) got no
wait at all.
postMessage("ElementsUpdate", ...), which is fire-and-forget — thereceiving iframe applies it to its own local state asynchronously.
So
await updateIntent(...)could return while a mounted widget was still holding the old credentials, and a confirm triggered right after would race it.Fix
UpdateIntentHelpersNew.res: addedwaitForElementsUpdateAcks,which counts the mounted iframes and blocks
performUpdateIntentfrom resolving until it's received one
{elementsUpdateApplied: true}ack per iframe (bounded by a 5s timeout so a non-responsive/torn-down
iframe can't hang it forever). Runs unconditionally for every
updateIntentcall, regardless of entry point or widget mix.LoaderController.res: each content iframe now sends that ack from aReact.useEffectkeyed onkeys.sdkAuthorization, not synchronouslyinside the
"ElementsUpdate"message handler.setKeys/setConfigare React state setters — they don't apply immediately, so an ack sent
right after calling them doesn't actually guarantee the new value is
live yet. The effect only fires once React has committed the change,
which is the actual guarantee callers need. A ref skips the ack on
initial mount so it only fires on real updates.
How did you test it?
Reproduced the race with a dedicated test button (
paymentSession.updateIntentawaited, followed immediately byconfirmWithLastUsedPaymentMethod, zero delay) — reliably got401before this fix. After the fix, ran the same sequence 5x back-to-back with fresh page loads each time:/confirmreturned200every time.before:
https://github.com/user-attachments/assets/296635c5-96a2-421c-bb59-fc55485c8cfe
after:
https://github.com/user-attachments/assets/b49bb292-9ec9-4003-9923-95bfbeb00a03
Checklist
npm run re:build