fix: Amber login callback URL for HashRouter

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 <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-03 13:58:41 +07:00
parent 4f7852fec7
commit 59eda93641
2 changed files with 24 additions and 2 deletions

View file

@ -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);

View file

@ -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=<result>
// 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)}`;