From 0c87fa5827fa516b26dd3528b91001f8e38ff599 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Wed, 17 Jun 2026 22:46:39 +0700 Subject: [PATCH] Fix Amber login not completing on GrapheneOS new-tab callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: on GrapheneOS, Amber opens /amber-callback in a BRAND NEW TAB. login.amber() was being called in that new tab's React instance, which has no effect on the original tab where the user is waiting. Fix: - AmberCallbackPage now writes pubkey to localStorage('amber_pending_pubkey') instead of calling login.amber() — a cross-tab signal - LoginDialog polls for 'amber_pending_pubkey' every 400ms and calls login.amber() itself in the correct React context - Manual "I approved" button also reads the same key - Removed reliance on getAmberLogin() which checked the wrong localStorage key Co-authored-by: shakespeare.diy --- src/components/auth/LoginDialog.tsx | 52 +++++++++++++++++++---------- src/pages/AmberCallbackPage.tsx | 21 ++++++------ 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index 20ffce5..9c0c22d 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -11,7 +11,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert'; import { useLoginActions } from '@/hooks/useLoginActions'; import { useToast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; -import { subscribeToAmberReturn, getAmberLogin } from '@/lib/amber'; +import { subscribeToAmberReturn } from '@/lib/amber'; interface LoginDialogProps { isOpen: boolean; @@ -95,20 +95,30 @@ const LoginDialog: React.FC = ({ isOpen, onClose, onLogin, onS // Stamp the time so we know this is a fresh attempt localStorage.setItem('amber_login_attempt_ts', String(Date.now())); - // Poll localStorage every 500ms — on Android, the page navigates away and - // AmberCallbackPage completes the login in a new page load. When user comes - // back here, polling will detect the stored amber_login pubkey and complete. + // Poll localStorage every 400ms for 'amber_pending_pubkey'. + // AmberCallbackPage writes this key when Amber approves — even if it runs + // in a separate tab (GrapheneOS opens callback in new tab). This tab picks + // it up, calls login.amber() here where React state is alive, and completes. if (pollRef.current) clearInterval(pollRef.current); pollRef.current = setInterval(() => { - const storedPubkey = getAmberLogin(); - if (storedPubkey) { + const pendingPubkey = localStorage.getItem('amber_pending_pubkey'); + if (pendingPubkey) { + // Consume it immediately so we don't process twice + localStorage.removeItem('amber_pending_pubkey'); clearInterval(pollRef.current!); pollRef.current = null; - setAmberStep('success'); - toast({ title: '🛹 Logged in', description: 'Welcome back via Amber!' }); - setTimeout(() => { onLogin(); onClose(); }, 600); + try { + login.amber(pendingPubkey); + setAmberStep('success'); + toast({ title: '🛹 Logged in', description: 'Welcome back via Amber!' }); + setTimeout(() => { onLogin(); onClose(); }, 600); + } catch (err) { + console.error('[Amber] login.amber failed in poll:', err); + setErrors(prev => ({ ...prev, amber: 'Login failed. Please try again.' })); + setAmberStep('initial'); + } } - }, 500); + }, 400); // Also try visibilitychange for browsers that keep the tab alive unsubAmberRef.current = subscribeToAmberReturn(() => { @@ -369,16 +379,22 @@ const LoginDialog: React.FC = ({ isOpen, onClose, onLogin, onS