Fix Amber login not completing on GrapheneOS new-tab callback
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 <assistant@shakespeare.diy>
This commit is contained in:
parent
67850348e6
commit
0c87fa5827
2 changed files with 44 additions and 29 deletions
|
|
@ -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<LoginDialogProps> = ({ 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<LoginDialogProps> = ({ isOpen, onClose, onLogin, onS
|
|||
<Button
|
||||
className="w-full rounded-full bg-[#ccff00]/20 border border-[#ccff00]/50 text-[#ccff00] hover:bg-[#ccff00]/30 font-bold text-sm h-auto py-3"
|
||||
onClick={() => {
|
||||
// Check localStorage right now — AmberCallbackPage may
|
||||
// have already stored the pubkey during the redirect
|
||||
const storedPubkey = getAmberLogin();
|
||||
if (storedPubkey) {
|
||||
// Check for pending pubkey written by AmberCallbackPage
|
||||
const pendingPubkey = localStorage.getItem('amber_pending_pubkey');
|
||||
if (pendingPubkey) {
|
||||
localStorage.removeItem('amber_pending_pubkey');
|
||||
if (pollRef.current) { 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 {
|
||||
setErrors(prev => ({ ...prev, amber: 'Login failed. Please try again.' }));
|
||||
setAmberStep('initial');
|
||||
}
|
||||
} else {
|
||||
setErrors(prev => ({ ...prev, amber: 'Not logged in yet — make sure you approved in Amber first.' }));
|
||||
setErrors(prev => ({ ...prev, amber: 'Not approved yet — go back to Amber and approve first.' }));
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -56,19 +56,18 @@ export default function AmberCallbackPage() {
|
|||
const isLoginOp = !pendingOp || pendingOp.type === 'get_public_key';
|
||||
if (isLoginOp) {
|
||||
if (pubkeyRegex.test(result)) {
|
||||
// Store pubkey under a SEPARATE key so the original tab's polling
|
||||
// detects it and calls login.amber() itself — this works even when
|
||||
// Amber opens the callback in a brand new tab (GrapheneOS behaviour)
|
||||
localStorage.setItem('amber_pending_pubkey', result.toLowerCase());
|
||||
clearAmberPendingOperation();
|
||||
setStatus('success');
|
||||
setMessage('Approved! You can close this tab and return to SkateSpots.');
|
||||
// Also try to complete login in THIS tab in case it is the same tab
|
||||
try {
|
||||
login.amber(result.toLowerCase());
|
||||
clearAmberPendingOperation();
|
||||
setStatus('success');
|
||||
setMessage('Logged in! Returning to SkateSpots...');
|
||||
toast({ title: '🛹 Logged in', description: 'Welcome back via Amber!' });
|
||||
redirectBack(true);
|
||||
} catch (err) {
|
||||
console.error('[AmberCallback] login.amber failed:', err);
|
||||
setStatus('error');
|
||||
setMessage('Login failed. Please try again.');
|
||||
clearAmberPendingOperation();
|
||||
}
|
||||
} catch { /* ignore — may fail if called twice */ }
|
||||
redirectBack(true);
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage('Invalid response from Amber. Please try again.');
|
||||
|
|
|
|||
Loading…
Reference in a new issue