From c6372742a9bc00433198aa6c4601b77ffe872fda Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 27 Aug 2026 14:00:39 +0700 Subject: [PATCH] Fix Amber native login in APK: reliable Capacitor detection (v1.1.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: LoginDialog and useCurrentUser detected the native app via window.Capacitor, which is unreliable inside the WebView (false at mount / not attached in some builds). So the ZapStore APK fell into the web intent:// redirect flow and never opened Amber ("Waiting for Amber…" hang). Fix: add isNativeAmberAvailable() in capacitorAmberSigner.ts using the imported @capacitor/core Capacitor + native plugin presence, and use it (evaluated at click time) in LoginDialog.handleAmberLogin and in useCurrentUser's signer selection. The APK now calls getAmberPublicKeyViaPlugin() → native intent → Amber opens directly, no redirects. Bump version to 1.1.7. Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/auth/LoginDialog.tsx | 11 ++++++----- src/hooks/useCurrentUser.ts | 6 +++--- src/lib/capacitorAmberSigner.ts | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index f28722e..b12f797 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.1.6", + "version": "1.1.7", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index 33dee42..9a5e803 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -16,7 +16,7 @@ import { useLoginActions } from '@/hooks/useLoginActions'; import { useToast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; import { openAmberForLogin, subscribeToAmberReturn } from '@/lib/amber'; -import { getAmberPublicKeyViaPlugin } from '@/lib/capacitorAmberSigner'; +import { getAmberPublicKeyViaPlugin, isNativeAmberAvailable } from '@/lib/capacitorAmberSigner'; interface LoginDialogProps { @@ -45,9 +45,6 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog const isAndroid = /Android/i.test(navigator.userAgent); - // Detect if running inside a Capacitor native app - const isCapacitor = !!(window as unknown as { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor?.isNativePlatform?.(); - // Step 1: open Amber // - On Capacitor (ZapStore APK): use the native plugin — direct Promise, no redirects needed // - On web/PWA: use intent:// URL with callbackUrl so Amber redirects back @@ -55,7 +52,11 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog setAmberStep('loading'); setAmberError(''); - if (isCapacitor) { + // Decide native-vs-web at CLICK TIME using the reliable Capacitor check from + // the signer module (imported @capacitor/core), NOT window.Capacitor which + // can be false at mount inside the WebView — that mismatch was causing the + // APK to fall into the web redirect flow and never actually open Amber. + if (isNativeAmberAvailable()) { // ── Native path (ZapStore/Capacitor APK) ──────────────────────────────── // Attempt login directly — skip isExternalSignerInstalled() check because // Android 11+ package visibility rules can make it return false even when diff --git a/src/hooks/useCurrentUser.ts b/src/hooks/useCurrentUser.ts index d057a4a..a8b6a75 100644 --- a/src/hooks/useCurrentUser.ts +++ b/src/hooks/useCurrentUser.ts @@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react'; import { useAuthor } from './useAuthor.ts'; import { AmberSigner } from '@/lib/amber'; -import { CapacitorAmberSigner } from '@/lib/capacitorAmberSigner'; +import { CapacitorAmberSigner, isNativeAmberAvailable } from '@/lib/capacitorAmberSigner'; import type { AmberLoginType } from './useLoginActions'; /** Extended login type that includes Amber */ @@ -27,8 +27,8 @@ export function useCurrentUser() { // On Capacitor (ZapStore APK) use the native plugin signer so all // signing operations go through Amber via native intents. // On web/PWA fall back to the URL-redirect based AmberSigner. - const isCapacitor = !!(window as unknown as { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor?.isNativePlatform?.(); - const signer = isCapacitor + // Use the reliable module-level Capacitor check (not window.Capacitor). + const signer = isNativeAmberAvailable() ? new CapacitorAmberSigner(amberLogin.pubkey) : new AmberSigner(amberLogin.pubkey); return new NUser('x-amber', amberLogin.pubkey, signer); diff --git a/src/lib/capacitorAmberSigner.ts b/src/lib/capacitorAmberSigner.ts index bc9bd25..dab4359 100644 --- a/src/lib/capacitorAmberSigner.ts +++ b/src/lib/capacitorAmberSigner.ts @@ -257,6 +257,21 @@ export class CapacitorAmberSigner implements NostrSigner { }; } +/** + * True when we're running inside the native Capacitor app AND the native + * NostrSigner bridge is usable. This is the ONLY reliable native check — + * reading `window.Capacitor` from a component can be false at mount time or in + * builds where Capacitor doesn't attach itself to `window`. Always call this at + * the moment of the action (e.g. inside the login click handler), not at render. + */ +export function isNativeAmberAvailable(): boolean { + try { + return !!Capacitor?.isNativePlatform?.() && !!getNativeSigner(); + } catch { + return false; + } +} + /** * Is an external signer (Amber) installed on this device? */