fix(client): WHATWG URL polyfill + native-intent for Android OAuth return #298
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!298
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "297-android-oauth-url-polyfill"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Android OAuth sign-in/link returned to a bare deep link
carol:///and Expo Router showed its "Unmatched Route" 404; sign-in never completed.Root cause
React Native's built-in global
URL(Libraries/Blob/URL.js) hardcodeshttps?://in its host/pathname regexes (lines 131, 158). Noreact-native-url-polyfillwas installed and SDK 56's winter runtime is absent, so RN's implementation is authoritative. On-device:That empties the return deep link and breaks both consumers:
`${host}${pathname}`(lib/auth/oauthDeepLinkParse.ts:32-33) →""≠"auth/oauth/complete"→ null, so the awaiting native flow never gets{init, code}and never callsPOST /api/auth/token.Unmatched, whose label iscreateURL("/")=carol:///.Masked in CI because vitest runs on Node's spec-compliant
URL.Fix
react-native-url-polyfill@^2.0.0; install it on native only (web keeps the browser's native URL) at the top ofapp/_layout.tsx, before any deep-link code runs — fixes the parsers and router extraction.app/+native-intent.ts(redirectSystemPath) to redirect the non-routable return deep links to real screens (carol://auth/oauth/complete…→/login,carol://account/identities…→/account), matched on the raw URL string. TheLinkinglistener still drains the queue and completes the exchange.Verification
pnpm -F @carol/client typecheck— passespnpm -F @carol/client lint— passes/notes; account linking →/account. Nocarol:///.Known follow-up (separate ticket)
True Android cold-start completion (app killed during the browser handoff) still won't finish the exchange — the in-memory waiter is gone, so the queued
{init, code}is never drained; the user lands cleanly on/loginbut must retry. Out of scope here.Closes #297
🤖 Generated with Claude Code
React Native's built-in global URL (Libraries/Blob/URL.js) hardcodes `https?://` in its host/pathname regexes, so on-device `new URL("carol://auth/oauth/complete?…")` yields host="" pathname="/". That empties the OAuth/link return deep links, which (1) makes the deep-link parsers return null so the native token exchange never runs, and (2) makes expo-router extract an empty route and render its Unmatched 404 (the `carol:///` screen). CI missed it because vitest runs on Node's spec-compliant URL. Install react-native-url-polyfill on native (web keeps the browser URL) at the top of app/_layout.tsx before any deep-link code runs, and add app/+native-intent.ts so expo-router redirects the non-routable return deep links to real screens (oauth complete → /login, link complete → /account) instead of 404ing. The Linking listener still drains the queue and completes the exchange. Cold-start completion (app killed during the browser handoff) remains a known follow-up. Closes #297 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.