fix(rn-window, wallets): auto-recover on sendAction timeout and retry TEE requests#1610
fix(rn-window, wallets): auto-recover on sendAction timeout and retry TEE requests#1610
Conversation
… TEE requests WebViewParent.sendAction now verifies handshake before sending and retries once after reloading the WebView on timeout. TEE event options add intervalMs (3s) for periodic retries. initializeWebView waits for handshake completion instead of just ref existence. Fixes Stellar wallet.approve 30s timeout on React Native (Pylon #5822). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: afe21ec The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| if (!this.isConnected) { | ||
| await this.ensureConnected(); | ||
| } | ||
|
|
||
| if (this.isRecoverableError(response)) { | ||
| console.info(`[WebViewParent] Recoverable error (code: ${response.code}), reloading and retrying`); | ||
| await this.reloadAndHandshake(); | ||
| return await super.sendAction(args); | ||
| try { | ||
| const response = await super.sendAction(args); | ||
|
|
||
| if (this.isRecoverableError(response)) { | ||
| console.info( | ||
| `[WebViewParent] Recoverable error (code: ${(response as any).code}), reloading and retrying` | ||
| ); | ||
| await this.reloadAndHandshake(); | ||
| return await super.sendAction(args); | ||
| } | ||
|
|
||
| return response; | ||
| } catch (error) { | ||
| if (typeof error === "string" && error.includes("Timed out")) { | ||
| console.info("[WebViewParent] sendAction timed out, reloading WebView and retrying"); | ||
| await this.reloadAndHandshake(); | ||
| return await super.sendAction(args); | ||
| } | ||
| throw error; | ||
| } | ||
| } |
There was a problem hiding this comment.
Chained recovery can triple the reload wait time
When the WebView is fully unresponsive, the error handling paths can chain up to 3 reload+handshake cycles:
ensureConnected()→handshakeWithChild()times out (~30s)ensureConnected()catches →reloadAndHandshake()→ handshake times out again (~30s), throws- The thrown timeout string error propagates to the
catchblock at line 124, which matches"Timed out"→ triggers anotherreloadAndHandshake()+super.sendAction()(~30s+)
This means worst-case latency before final failure is ~90s+, which is significant for a fix targeting a 30s timeout on low-end devices. Consider either:
- Tracking whether
ensureConnectedalready attempted a reload, so the timeout catch doesn't reload a third time - Re-throwing the error from
ensureConnectedwithout entering the timeout catch path (e.g. wrapping it in anErrorobject to distinguish it fromsendActiontimeouts)
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/client/rn-window/src/rn-webview/Parent.ts
Line: 107-131
Comment:
**Chained recovery can triple the reload wait time**
When the WebView is fully unresponsive, the error handling paths can chain up to 3 reload+handshake cycles:
1. `ensureConnected()` → `handshakeWithChild()` times out (~30s)
2. `ensureConnected()` catches → `reloadAndHandshake()` → handshake times out again (~30s), throws
3. The thrown timeout string error propagates to the `catch` block at line 124, which matches `"Timed out"` → triggers _another_ `reloadAndHandshake()` + `super.sendAction()` (~30s+)
This means worst-case latency before final failure is ~90s+, which is significant for a fix targeting a 30s timeout on low-end devices. Consider either:
- Tracking whether `ensureConnected` already attempted a reload, so the timeout catch doesn't reload a third time
- Re-throwing the error from `ensureConnected` without entering the timeout catch path (e.g. wrapping it in an `Error` object to distinguish it from `sendAction` timeouts)
How can I resolve this? If you propose a fix, please make it concise.
🔥 Smoke Test Results✅ Status: Passed Statistics
✅ All smoke tests passed!All critical flows are working correctly. This is a non-blocking smoke test. Full regression tests run separately. |
…ptions, add init recovery - Parent.ts: extract sendActionWithTimeoutRecovery so recoverable-error retry is outside the try-catch, preventing double-retry (max 1 reload per recovery path) - ncs-signer.ts: split into DEFAULT_EVENT_OPTIONS (timeout only) and POLLING_EVENT_OPTIONS (timeout + intervalMs) — only get-status uses polling; side-effectful ops (OTP, sign, export) are never retried - CrossmintWalletProvider.tsx: initializeWebView now reloads WebView and re-polls if first handshake fails, instead of throwing immediately - Add tests for WebViewParent recovery (5 cases) and signer options (3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Closing - this PR addresses too many concerns at once. Will break it into smaller, focused PRs starting with the isConnected gate fix. |
Summary
Fixes the Stellar
wallet.approve30s timeout on React Native (Pylon #5822 — Marshall Islands + SpotPay). The open-signer frame fails to initialize/respond on low-end devices, and the SDK had no recovery mechanism on RN.Three complementary fixes:
rn-window/Parent.ts): ChecksisConnectedbefore sending — if disconnected, awaits the existing handshake (single-flight) or reloads the WebView. On timeout, reloads and retries once. Addresses PR feat(rn-window): auto-recover on sendAction when WebView handshake failed #1599 feedback from Greptile (race condition) and Devin (recovery gating).ncs-signer.ts): AddsintervalMs: 3_000toDEFAULT_EVENT_OPTIONSsoget-status,sign, etc. retry every 3s within the 30s window instead of sending once.CrossmintWalletProvider.tsx): Now polls forisConnected === true(not just ref existence) with a 30s max wait to match the handshake timeout.Supersedes #1599 — includes its
isConnectedpre-check plus timeout recovery and the other two fixes.Test plan
@crossmint/client-sdk-rn-windowtests pass (9/9)@crossmint/wallets-sdktests pass (194/194)🤖 Generated with Claude Code