- JSObject from Capacitor needs JSON.parse(JSON.stringify()) to be
accessible as plain JS object with property access
- Restore AMBER_PACKAGE as argument (null was causing broken URI)
- Remove debug toast from LoginDialog
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Android 11+ package visibility rules cause isExternalSignerInstalled() to
return false even when Amber is installed, unless <queries> is declared in
AndroidManifest.xml. Skip the pre-check and attempt login directly instead,
with better error messages for cancel vs not-found vs other errors.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add CapacitorAmberSigner: a full NostrSigner implementation backed by
the native nostr-signer-capacitor-plugin (login, signEvent, nip04,
nip44 all go through Amber via native Android intents)
- LoginDialog: on Capacitor, call getAmberPublicKeyViaPlugin() directly
instead of the intent:// URL redirect — returns a Promise, no
callback URL or polling needed. Shows clear error if Amber not installed.
- useCurrentUser: on Capacitor amber logins use CapacitorAmberSigner
so every publish/sign goes through the plugin, not window.location.href
- Web/PWA path unchanged — still uses URL redirect + AmberSigner
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- LoginDialog now uses openAmberForLogin() which sends the proper
intent:// URL with a callbackUrl instead of the old nostrsigner:
scheme that only copies to clipboard (broken on most setups)
- Add polling loop (500ms, 3min timeout) for amber_pending_pubkey
written by AmberCallbackPage — works even when Amber opens the
callback in a new browser tab (GrapheneOS/Android behaviour)
- visibilitychange listener now waits for app to regain focus before
attempting clipboard read, fixing race condition
- DeepLinkHandler pre-captures ?event= param into sessionStorage
when Amber fires appUrlOpen deep link in Capacitor, so
AmberCallbackPage can always find the result
- Add 'paste' to amberStep union type (was missing, caused TS error)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The chebizarro npm version has a different API to the Coracle fork that
Welshman/Coracle actually use in production. Now using:
github:coracle-social/nostr-signer-capacitor-plugin#main
Correct API (from Welshman nip55.js source):
NostrSignerPlugin.setPackageName(packageName: string) // string not object
NostrSignerPlugin.getPublicKey(packageName?: string) // packageName as arg
NostrSignerPlugin.isExternalSignerInstalled(packageName?)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
All previous URL-scheme approaches (nostrsigner:, intent:) were fundamentally
wrong for a Capacitor app. JavaScript in a WebView cannot reliably dispatch
Android intents via URL navigation.
The correct solution — used by Coracle and other Capacitor-based Nostr apps —
is the nostr-signer-capacitor-plugin which communicates with Amber via native
Android intents through the Capacitor bridge:
NostrSignerPlugin.setPackageName({ packageName: 'com.greenart7c3.nostrsigner' })
NostrSignerPlugin.isExternalSignerInstalled()
NostrSignerPlugin.getPublicKey() → { npub }
This is synchronous from the JS side, handles the entire Android intent
roundtrip natively, and returns the pubkey directly. No URL encoding,
no callback URLs, no redirects, no malformed request errors.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
GrapheneOS Vanadium browser blocks intent: URLs from web pages as a
security hardening measure. This is why every URL format we tried
caused "malformed request" — Vanadium was blocking the intent before
Amber ever saw it.
New flow that works on ALL Android browsers including GrapheneOS:
1. Tap "Open Amber" → navigates to nostrsigner: URL with no callbackUrl
(Amber copies pubkey to clipboard when no callbackUrl is given)
2. User approves in Amber, comes back to app
3. App tries navigator.clipboard.readText() automatically on visibilitychange
4. If clipboard permission denied → shows paste input for hex pubkey
5. User pastes pubkey → logged in
No intent: scheme, no callback URL, no redirect loop. Simple and bulletproof.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The entire nostrconnect relay approach was wrong for a native Android/Capacitor app.
Amber on Android uses NIP-55 intents (nostrsigner: URI scheme), not NIP-46 relays.
New flow (same as Amethyst):
1. openAmberForLogin() fires nostrsigner:?type=get_public_key&callbackUrl=...
2. Amber opens, user taps approve
3. Amber calls back to org.skatespots.app://amber-callback?event=<pubkey>
4. AmberCallbackPage writes pubkey to localStorage('amber_pending_pubkey')
5. LoginDialog polls localStorage every 500ms and calls login.amber(pubkey)
6. Done — no relays, no waiting, no spinning
Also added login.amber(pubkey) method to useLoginActions.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The nostrconnect flow was incorrectly creating a bunker login and trying
to do NIP-46 key exchange. Amber signs as the user's own key directly,
so the pubkey from the connect response IS the user pubkey. Now stores
a proper amber login which uses AmberSigner via useCurrentUser.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Replace one-shot visibility handler with pollRelay() called immediately on foreground + every 3s
- Add elapsed time counter (M:SS) so user can see the app is still trying
- Add 'Check Now' button for manual relay poll without generating a new URI
- Poll interval auto-clears on success, timeout, or cancel
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Replace wss://relay.primal.net with wss://nos.lol (more reliable for NIP-46)
- Shorten timeout from 10 minutes to 20 seconds
- Fix stale closure bug: track amberStep in amberStepRef so timeout can correctly reset UI
- Add 'error' step with red UI and 'Try Again' button instead of endless spinner
- Extend since filter from 5s to 10s in the past
- Add console logs for URI, relays, incoming events, decryption result, secret matching
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
1. AboutPage: add Credits section (Zapstore, ContextVM, Angor, Geyser, MKStack,
Shakespeare, Nostr), add Zapstore download button, fix powered-by text overflow
on mobile (wrap-friendly flex layout)
2. SasquatchGuide: make minimized bubble draggable on all devices
- pointer events for mouse, touch, and stylus
- saves position to localStorage, persists across sessions
- tap still opens, drag moves (5px threshold to distinguish)
3. Layout (burger menu): add overflow-y-auto + pb-8 to SheetContent so
mobile menu is scrollable when content exceeds screen height
4. CashuWalletCard (wallet): fix input+button rows overflowing on mobile
- flex-col on small screens, flex-row on xs+ (stacked on phones)
- whitespace-nowrap on buttons to prevent text wrapping
5. LabPage: compact action buttons on mobile (h-8, text-[10px], px-2)
so all 4 buttons fit in 2x2 grid without overflow/truncation
6. LoginDialog (Amber): improve visibility-change handler - use fresh
SimplePool subscription on each foreground return instead of querySync
(which doesn't exist in nostr-tools v2.13)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add visibilitychange listener: when app returns to foreground after Amber,
does a one-shot relay query to catch Amber's connect response
- Store pending nostrconnect session in localStorage so it survives app suspend
- Keep live relay subscription as fallback for web/PWA
- Add nsec login tab as reliable fallback option
- Add proper step states: idle -> connecting -> waiting -> success
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Replace NRelay1 async iterator (unreliable) with nostr-tools SimplePool.subscribeMany()
using onevent callback — same pattern as Ditto/Coracle/Nostrudel.
- Switch to relay.nsec.app, relay.damus.io, relay.primal.net (support ephemeral events)
- Use nip04.decrypt() from nostr-tools for Amber's connect response decryption
- Proper cleanup of pool and subscription on abort/close
- Fall back to signerPubkey as userPubkey if NConnectSigner.getPublicKey() fails
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Completely replaces the broken NIP-55 intent approach with NIP-46:
- Amber tab: generates nostrconnect:// URI, opens Amber, waits for
Amber to connect back via relay using NLogin.fromNostrConnect()
(same pattern as Ditto/soapbox-pub)
- Bunker tab: paste bunker:// URI from Amber or nsec.app
- Extension tab: NIP-07 browser extension (desktop)
- useLoginActions: updated to match Ditto's implementation with
nostrconnect(), generateNostrConnectParams, generateNostrConnectURI
- Clean mobile-first UI — no clipboard hacks, no intent tricks
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
NIP-55 (nostrsigner: intents) is fundamentally broken from Capacitor
WebView — no combination of window.open, window.location.href, or
anchor clicks works reliably across all Android WebView versions.
Switch to NIP-46 (bunker://) which works everywhere via WebSockets:
- User opens Amber → Connect apps → Add app → copies bunker:// URI
- Pastes it into the login dialog
- NLogin.fromBunker() completes the handshake over a relay
- Login works in browser, PWA, and Capacitor APK identically
This is exactly how Ditto (soapbox-pub) implements Amber login.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Capacitor native apps fail with "malformed nostrsigner request" when
using callbackUrl because Amber treats custom URI schemes as invalid.
Fix: detect window.Capacitor.isNativePlatform() and use the no-callback
clipboard flow (openAmberForLoginNoCallback) instead of the callbackUrl
flow. Amber copies the pubkey to clipboard, app reads it on
visibilitychange when user returns.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Amber login (critical):
- Replace window.location.href with window.open(_system) in Capacitor WebViews
- Capacitor's WebView throws "malformed nostrsigner request" on href navigation
- New openAmberUrl() helper detects Capacitor via window.Capacitor.isNativePlatform()
- Add openAmberForLoginNoCallback() for PWA/Capacitor clipboard flow
- Fix all 6 Amber URL opens in amber.ts + LoginDialog.tsx
Mobile nav:
- Newsletter and Sasquatch AI (/pyramid) added to hamburger menu
- Sasquatch floating guide now shows on mobile (removed hidden md:block)
Lab page mobile:
- Action buttons (Offer Coaching/Filming/Editing/Get Card) now 2-col grid on mobile
- Service type filter (Coaches/Filmers/Editors) now 3-col grid, full width
- Reduced top padding on mobile (py-6 → py-12 on md+)
- Text truncates cleanly, icons don't overflow
Wallet page mobile:
- Reduced padding (py-6 md:py-8)
- Tab triggers use h-auto + py-2.5 to avoid overflow
- Smaller header text on mobile
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
PWA standalone mode is single-tab. Opening nostrsigner: with a callbackUrl
sends the callback to the browser (a different app), not back to the PWA.
The PWA tab is suspended and never sees the result.
Fix:
- Detect PWA standalone mode via matchMedia('(display-mode: standalone)')
- In PWA mode: open Amber WITHOUT a callbackUrl — Amber copies pubkey to
clipboard automatically when no callback is given
- Use visibilitychange to detect when user returns to PWA after approving
- Auto-read clipboard on return and complete login silently
- Manual "I approved" button now tries localStorage first (browser), then
falls back to clipboard read (PWA) — covers both contexts with one button
- Browser mode unchanged: localStorage cross-tab polling still works
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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>
Root cause: window.location.href navigates the tab away entirely on Android,
destroying sessionStorage. When Amber calls back to /amber-callback the login
completes there, but returning to the dialog the state is lost.
Fixes:
- Switch from sessionStorage to localStorage for amber_return_path and
amber_login_attempt_ts — survives tab navigation on Android
- Add 500ms polling loop in handleAmberLogin that checks getAmberLogin()
every 500ms — detects when AmberCallbackPage stored the pubkey and
auto-completes login without needing visibilitychange to fire
- Add "I approved — complete login" manual button in waiting state as
explicit fallback for stuck users (reads localStorage right away)
- AmberCallbackPage now uses localStorage for all persistence
- Clean up poll interval on dialog close and unmount
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add dedicated /amber-callback route so Amber always has a stable return URL
- Handles get_public_key (login), sign_event, nip44/nip04 encrypt/decrypt
- Shows animated processing screen while completing, then redirects back
- Add subscribeToAmberReturn() helper in amber.ts using visibilitychange
- Auto-detects when user returns to the tab after cancelling in Amber
- Resets login UI to initial state gracefully
- Update buildCallbackUrl() to use /amber-callback instead of current page URL
- Rewrite Amber tab in LoginDialog:
- Remove 2-step clipboard paste flow entirely
- Single tap opens Amber, login completes automatically on return
- Waiting state shows animated dots + cancel button
- No manual pubkey entry needed
- Save return path in sessionStorage so AmberCallbackPage redirects user back
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.
Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)
Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
Renamed from src/src/components/auth/LoginDialog.tsx (Browse further)