fix: improve deep-link launch detection#431
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
decentraland-bot
left a comment
There was a problem hiding this comment.
Review Summary
PR: #431 — fix: improve deep-link launch detection
Files: 2 changed (+163 −16)
Branch: fix/deep-link-detection → main
Verdict: ✅ Approve
Clean, well-structured fix addressing a real UX issue. No P0 or P1 findings.
What this PR does
Extends deep-link detection from a single blur event within 500ms to three browser lifecycle signals (blur, visibilitychange, pagehide) within 5 seconds. This prevents a false "app not found" error when native-protocol confirmation dialogs take longer than 500ms to dismiss. The settle/cleanup pattern correctly prevents double-resolution of the Promise and ensures thorough resource cleanup (all listeners removed, timeout cleared, iframe removed).
Analysis
- Correctness: The
settledboolean guard is the standard idiom for one-shot promise resolution. The execution ordering (listeners → setTimeout → appendChild) is correct — listeners are attached before the iframe triggers the deep link, preventing a race where a fast app launch could fire blur before the handler is registered. - API contract: Unchanged
(url: string) => Promise<boolean>. Both consumers (ContinueInApp.tsx,MobileAuthSuccess.tsx) handletrue/falseoutcomes identically — no caller impact. - Browser compatibility:
visibilitychange(IE10+) andpagehide(all evergreen browsers, Safari included) have broad support. Together they cover casesblurmisses, particularly mobile WebViews. - Security: No new vulnerabilities. No secrets, XSS vectors, or auth changes. (Pre-existing: the
urlparameter has no protocol-scheme guard — not introduced here, but worth a follow-upurl.startsWith('decentraland://')check.) - Tests: 128-line test file covers all five main paths (blur, visibilitychange, pagehide, timeout, mobile). Proper teardown with
useRealTimers,resetAllMocks, and DOM cleanup.
P2 — Minor observations (non-blocking)
-
[Style]
timeoutIddeclaration ordering —cleanup()referencestimeoutIdbefore itsconstdeclaration (line 251). This is safe today becausecleanup()is only called asynchronously after assignment, but a future refactor could introduce a TDZReferenceError. Declaringlet timeoutId: ReturnType<typeof setTimeout>abovecleanupwould be more robust. -
[Test coverage] No double-signal guard test — No test fires two signals in sequence (e.g., blur then visibilitychange) to verify the promise resolves only once. The
settledflag handles this correctly, but an explicit test would harden the invariant. -
[Test coverage]
visibilitychangewithvisiblestate — No test confirms the handler is a no-op whenvisibilityStateis'visible'. Worth adding for completeness. -
[UX tradeoff] 5s timeout — Well justified in the code comment and PR description. If the desktop app is not installed, the user now waits 5s before seeing the failure state. This is a reasonable tradeoff given that 500ms caused false negatives.
-
[Heuristic limitation] False positive from manual tab switch — If the user switches tabs within 5s for reasons unrelated to the deep link,
visibilitychangewould resolvetrue. This is inherent to heuristic detection (the oldblur-only approach had the same class of issue) and is acceptable given the context.
CI Status
All checks passing (audit ✅, test ✅, CodeQL ✅, lint ✅, Vercel ✅). E2e tests were still in progress at review time.
Reviewed by Jarvis 🤖 · Requested by Lautaro Petaccio (<@U2MAY5BHL>) via Slack
Summary
Problem
The previous detector treated the absence of window blur within 500 ms as a launch failure. Native-protocol prompts can remain open longer than that, causing the page to show an application-opening error even though the app opens after the user accepts the prompt.
Validation