Skip to content

fix: fix widget retry logic#7636

Open
Danziger wants to merge 5 commits into
developfrom
feat/fix-widget-retry-logic
Open

fix: fix widget retry logic#7636
Danziger wants to merge 5 commits into
developfrom
feat/fix-widget-retry-logic

Conversation

@Danziger

@Danziger Danziger commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Clicking "Retry" will now show "Loading..." in the button. If the retry fails, the same error page is displayed. Before this fix, the default browser error page will be shown instead in this scenario.

To Test

  1. Go to the widget configurator preview in this PR.
  2. Set baseURL to an incorrect (non-existent) page.
  3. Verify the error page appears.
  4. Click "Retry" and wait. Notice how the button text changes to "Loading..."
  5. Wait and verify how the button text goes back to "Retry", instead of seeing the default browser error page.

Summary by CodeRabbit

Release Notes

  • New Features

    • Widget retry mechanism: Failed widgets now display an actionable error message, enabling users to retry loading without a full page refresh.
  • Bug Fixes

    • Enhanced widget readiness detection for improved accuracy in tracking load status.
    • Strengthened error recovery handling for widgets experiencing timeout or initialization failures.

@Danziger Danziger requested review from a team and shoom3301 June 9, 2026 14:47
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 16, 2026 5:01pm
explorer-dev Ready Ready Preview Jun 16, 2026 5:01pm
storybook Ready Ready Preview Jun 16, 2026 5:01pm
swap-dev Ready Ready Preview Jun 16, 2026 5:01pm
widget-configurator Ready Ready Preview Jun 16, 2026 5:01pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 16, 2026 5:01pm
sdk-tools Ignored Ignored Preview Jun 16, 2026 5:01pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The widgetIframeLoading module was refactored to replace a simple timeout-and-error-document pattern with a sophisticated probe-based detection system. The new flow uses hidden probe iframes to verify widget readiness, validates cross-window retry messages, and regenerates error documents with optional custom styling and inline retry logic.

Changes

Widget iframe probe loading and retry flow

Layer / File(s) Summary
Message validation and transport configuration
libs/widget-lib/src/widgetIframeLoading.ts (lines 1–27)
Added message transport keys, type guard isWidgetLoadRetryMessage to validate retry payloads, and WindowListener type contract for postMessage handlers. Replaces prior static error-document constants with dynamic configuration.
Core loading orchestration with probe detection and state management
libs/widget-lib/src/widgetIframeLoading.ts (lines 29–175)
Refactored main function with cancellation/loading state, separate primary and probe-readiness timeouts, hidden probe iframe lifecycle management, READY message listener via widgetIframeTransport, and postMessage listener that re-checks widget readiness when user clicks retry in the error UI. Cleanup now detaches message handler and removes probe iframe.
Error document generation with sanitized custom styling
libs/widget-lib/src/widgetIframeLoading.ts (lines 177–183, 193–287)
Added sanitizeInlineStyle helper to escape < characters and prevent CSS injection. Replaced static ERROR_DOCUMENT with buildErrorDocument function that conditionally embeds sanitized custom styles, renders a retry button, and includes inline DOMContentLoaded script that disables the button and posts WIDGET_LOAD_RETRY messages to the parent on click.

Sequence Diagram

sequenceDiagram
  participant Parent
  participant MainIframe
  participant ProbeIframe
  participant ErrorDocument
  Parent->>MainIframe: Start load timeout
  MainIframe->>MainIframe: Timeout fires
  Parent->>ErrorDocument: Replace iframe srcdoc with error UI
  Parent->>Parent: User clicks retry button
  ErrorDocument->>Parent: Post WIDGET_LOAD_RETRY message
  Parent->>ProbeIframe: Create hidden probe iframe
  ProbeIframe->>ProbeIframe: Navigate to widget URL
  ProbeIframe->>Parent: Send READY message via widgetIframeTransport
  Parent->>MainIframe: Clear srcdoc, reset src
  Parent->>MainIframe: Restart load timeout
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • cowprotocol/cowswap#7574: Modifies the same file to implement iframe load-time error handling with srcdoc error UI and postMessage-based retry mechanism.

Suggested reviewers

  • alfetopito
  • fairlighteth

Poem

🐰 A probe now pings where timeouts once slept,
Messages between windows are kept.
When widgets won't load, an error appears—
Retry with style, custom-styled fears!
Cross-frame chats with sanitized flair,
Loading flows float through the iframe air.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change - fixing widget retry logic and improving the retry user experience by showing loading state and custom error handling.
Description check ✅ Passed The description covers the Summary and To Test sections as required by the template, clearly explaining what was fixed and providing concrete testing steps. The Background section is optional and not included.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fix-widget-retry-logic

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 158a172
Status: ✅  Deploy successful!
Preview URL: https://2b4e1fe5.explorer-dev-dxz.pages.dev
Branch Preview URL: https://feat-fix-widget-retry-logic.explorer-dev-dxz.pages.dev

View logs

background: #fff5f5;
color: #d32f2f;

border: 1px solid #f5c2c7;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether the border and border radius work in the integrators page depends on how the iframe is being rendered/placed in the page, so I've removed them just to be safe.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
libs/widget-lib/src/widgetIframeLoading.ts (1)

163-174: 💤 Low value

Consider removing the message listener in onWidgetReady.

The onRetryMessage listener (added on line 159) is only removed in cancelWidgetLoading, not in onWidgetReady. After the widget loads successfully, this listener remains attached and fires on every window message event, returning early due to the isLoaded check. While functionally harmless, it adds unnecessary overhead.

♻️ Suggested fix
     onWidgetReady() {
       isLoaded = true
       clearTimeout(loadingTimeoutID)
+      window.removeEventListener('message', onRetryMessage)
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/widget-lib/src/widgetIframeLoading.ts` around lines 163 - 174, The
onRetryMessage listener is only removed in cancelWidgetLoading, so after a
successful load the window 'message' listener remains attached; update the
onWidgetReady handler to also remove the listener (call
window.removeEventListener('message', onRetryMessage)) and perform the same
cleanup you do on cancel (e.g., clearTimeout(loadingTimeoutID) and optionally
call cleanUpLoadCheck()) so the listener and timers are cleared when the widget
loads successfully.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@libs/widget-lib/src/widgetIframeLoading.ts`:
- Around line 163-174: The onRetryMessage listener is only removed in
cancelWidgetLoading, so after a successful load the window 'message' listener
remains attached; update the onWidgetReady handler to also remove the listener
(call window.removeEventListener('message', onRetryMessage)) and perform the
same cleanup you do on cancel (e.g., clearTimeout(loadingTimeoutID) and
optionally call cleanUpLoadCheck()) so the listener and timers are cleared when
the widget loads successfully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a60e98c5-e0a9-4e70-8f00-e57db5161294

📥 Commits

Reviewing files that changed from the base of the PR and between 6af03be and 2476c83.

📒 Files selected for processing (1)
  • libs/widget-lib/src/widgetIframeLoading.ts

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 158a172
Status: ✅  Deploy successful!
Preview URL: https://609475b4.swap-dev-5u6.pages.dev
Branch Preview URL: https://feat-fix-widget-retry-logic.swap-dev-5u6.pages.dev

View logs

@shoom3301

Copy link
Copy Markdown
Collaborator

@Danziger the button doesn't work to me.
When I click it, it just does nothing.

kkklk.mov

@Danziger

Copy link
Copy Markdown
Contributor Author

@shoom3301 The widget configurator preview sets a strict CSP (script-src 'self'), and srcdoc iframes inherit the parent page's policy. I could fix it adding unsafe-inline, but that will only fix it for the configurator. Once an integrator adds it to their site, it will work or not working depending on their CSP policy.

I'd park this until the widget configurator revamp PR is merged, as I have some changes there that show a loader while the iframe loads (added from the app (widget configurator app in this case) context). I'll try this change in there later and re-assess the fix and/or whether we need/want to fix it.

@elena-zh elena-zh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Danziger , thank you. I can see the 'Retry' button now.
But it does nothing when I click on it, and I don't see 'loading' state after :(
image

@elena-zh

Copy link
Copy Markdown
Contributor

Hey @Danziger , I've reported one more related issue #7653
Could you please try addressing it here if you have time/capacity?

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ AI Review (Codex GPT-5, worked 2m): retry probe can be swallowed in local widget origins

Finding: Hidden retry probe can mark the real widget as ready before reload

  • Location: libs/widget-lib/src/widgetIframeLoading.ts:129
  • The retry check creates a hidden iframe that emits the normal cowSwapWidget READY message. In local widget origins, IframeTransport.listenToMessageFromWindow() intentionally accepts source mismatches when the origin is local, so the existing listenToReady(iframeWindow, ...) listener for the real iframe can consume the probe iframe's READY first.
  • That calls onWidgetReady(), sets isLoaded = true, and then the probe callback returns early in completeCleanUpLoadCheck(true). The visible iframe keeps the error srcdoc and the button can stay on Loading... instead of retrying the real widget.
  • This is separate from the existing open thread, which only discusses iframe error-page border styling.

Suggested fix

  • Do not let the probe reuse the same parent-level READY channel in a way that existing widget listeners can consume, or make the probe completion path reload the visible iframe before/independent of onWidgetReady() being set by the probe message.
  • Add focused coverage for the local-origin source-mismatch case: dispatch a READY message from a non-visible/probe iframe window while the visible iframe is still showing srcdoc, and assert retry removes srcdoc and restores originalSrc.
Review scope and related context

Checked current PR head 167870f17075866e709b440e0432d67f6dfda071, the PR description, the single existing review thread, and the changed widget loading flow. I did not find other non-duplicate actionable comments.

🤖 Prompt for AI agents
Verify this finding against current PR head before changing code.

Context:
- `widgetIframeLoading.ts` creates a hidden probe iframe and listens for `WidgetMethodsEmit.READY`.
- `cowSwapWidget.ts` already has a parent `READY` listener for the real iframe.
- `IframeTransport.listenToMessageFromWindow()` allows source mismatches for local origins.
- If the probe READY reaches the older real-iframe READY listener first, `onWidgetReady()` can set `isLoaded = true` before `completeCleanUpLoadCheck(true)` reloads the visible iframe.

Expected fix:
- Ensure probe READY cannot be consumed as real-widget readiness, or ensure successful probe completion still reloads the visible iframe.
- Add targeted widget-lib test coverage for local-origin source mismatch during retry.

@fairlighteth

fairlighteth commented Jun 16, 2026

Copy link
Copy Markdown
Contributor
Automated QA failed: advertised widget retry flow still does not show loading state

Outcome

  • Error page appears after setting baseUrl to a non-existent URL.
  • Clicking Retry did not change the button text to Loading....
  • After waiting, the iframe still showed the same error page and the button still read Retry.
  • Console shows the retry document inline script was blocked by the configurator CSP: Refused to execute inline script because it violates ... "script-src 'self'".

Run details

  • Source: PR widget configurator preview, current PR head observed from GitHub: 158a172c
  • Preview: https://widget-configurator-git-feat-fix-widget-retry-logic-cowswap-dev.vercel.app/
  • Browser: Chromium 140.0.7339.16
  • OS: macOS
  • Wallet: disconnected; wallet state is not relevant to this retry flow.
  • Browser QA was executed with Playwright; this was not a manual human QA pass.
  • Results were visually inspected from captured screenshots/video.
  • AI assistance: Codex GPT-5 drove the Playwright run and drafted this summary from the recorded artifacts.

How to retest

  • Open: https://widget-configurator-git-feat-fix-widget-retry-logic-cowswap-dev.vercel.app/
  • Set Raw JSON params to:
    {"baseUrl":"https://cow-widget-missing.invalid"}
  • Click Update widget.
  • Wait for the iframe error page.
  • Click Retry.

Expected per PR description:

  1. Error page appears.
  2. Clicking Retry changes the button text to Loading....
  3. After the retry fails, the same error page returns with the button text back to Retry.

Observed:

  1. Error page appears.
  2. Clicking Retry leaves the button text as Retry; no Loading... state is visible.
  3. The same error page remains.

Artifacts

Full MP4 recording: https://github.com/user-attachments/assets/c4ed31b4-2f35-424b-a178-5b988ae87bd5

1s after clicking Retry:
PR 7636 Chromium QA, one second after clicking Retry, button still reads Retry

After waiting post-retry:
PR 7636 Chromium QA, after waiting post-retry, error page remains and button still reads Retry

Commands + setup
  • Browser QA was run with Playwright against the deployed widget configurator preview.
  • Video recording was enabled for the whole run.
  • The WebM Playwright recording was converted to MP4 for GitHub playback.
  • The MP4 recording is linked above; screenshots are embedded inline for quick review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants