From 59eda936413c91b77e351635501459c073ef0cfb Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Fri, 3 Jul 2026 13:58:41 +0700 Subject: [PATCH] fix: Amber login callback URL for HashRouter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG #1 (critical): buildCallbackUrl() was returning https://skate-spots.org/amber-callback?event= but the app uses HashRouter, so React Router never matched /amber-callback. Amber would open the callback URL in a new tab, land on the homepage, and AmberCallbackPage would never render — login was silently broken. Fixed to: https://skate-spots.org/#/amber-callback?event= BUG #2: AmberCallbackHandler (global, in App.tsx) was competing with AmberCallbackPage on the /amber-callback route, causing a race condition where the global handler could consume the pubkey before the page component. Fixed by detecting the hash path and early-returning when on /amber-callback. BONUS: openAmberForLogin() now persists amber_return_path from the current hash path so AmberCallbackPage can navigate back to where the user was. Co-authored-by: shakespeare.diy --- src/components/auth/AmberCallbackHandler.tsx | 8 ++++++++ src/lib/amber.ts | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/components/auth/AmberCallbackHandler.tsx b/src/components/auth/AmberCallbackHandler.tsx index c1580bc..e7c36fd 100644 --- a/src/components/auth/AmberCallbackHandler.tsx +++ b/src/components/auth/AmberCallbackHandler.tsx @@ -40,6 +40,14 @@ export function AmberCallbackHandler() { console.log('[AmberCallback] Search:', window.location.search); console.log('[AmberCallback] Pre-captured event:', sessionStorage.getItem('amber_callback_event')); + // If we're already on the /amber-callback route, AmberCallbackPage handles + // the processing — this global handler must stay silent to avoid a race. + const hashPath = window.location.hash.replace(/^#/, '').split('?')[0]; + if (hashPath === '/amber-callback') { + console.log('[AmberCallback] On /amber-callback route — deferring to AmberCallbackPage'); + return; + } + // Check if this is an Amber callback const isCallback = isAmberCallback(); console.log('[AmberCallback] isAmberCallback():', isCallback); diff --git a/src/lib/amber.ts b/src/lib/amber.ts index ce0311a..68b7898 100644 --- a/src/lib/amber.ts +++ b/src/lib/amber.ts @@ -270,9 +270,15 @@ function generateOperationId(): string { /** * Build the dedicated Amber callback URL. * + * This app uses HashRouter, so ALL in-app routes live inside the hash fragment: + * https://skate-spots.org/#/amber-callback + * + * Amber will open the callbackUrl in the browser after the user approves. + * The URL must therefore use the hash-based path so React Router can match + * the /amber-callback route and render AmberCallbackPage. + * * In the Capacitor APK we must use the app's custom URI scheme so Android fires * an intent back into the app (not a browser navigation). - * Web/PWA builds use the normal https origin. * * The app ID is `org.skatespots.app` — Capacitor registers this as a URI scheme * automatically via the `appId` in capacitor.config.ts. @@ -285,7 +291,10 @@ function buildCallbackUrl(): string { // correct route inside the HashRouter. return 'org.skatespots.app://amber-callback?event='; } - return `${window.location.origin}/amber-callback?event=`; + // HashRouter: routes live in the # fragment, so the callback URL must be: + // https://skate-spots.org/#/amber-callback?event= + // This is what Amber will open in the browser tab after user approves. + return `${window.location.origin}/#/amber-callback?event=`; } /** @@ -336,6 +345,11 @@ export function openAmberForLogin(): void { setAmberPendingOperation(pendingOp); + // Store the current hash path so AmberCallbackPage can navigate back here + // after login (e.g. user was on /map and tapped Login → returns to /map). + const currentPath = window.location.hash.replace(/^#/, '') || '/'; + localStorage.setItem('amber_return_path', currentPath); + // NIP-55 nostrsigner: URI — Amber opens, user approves, Amber calls callbackUrl const amberUrl = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=${encodeURIComponent(callbackUrl)}`;