WebSocket connections take longer to establish on first load in
Capacitor. Increase timeout from 8s to 15s and retries from 3 to 5.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The Content Resolver silent path returns a cached/wrong key.
Passing null skips it and always opens Amber UI so the user
explicitly selects their key and the correct pubkey is returned.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
NostrSigner.npubToHex() is called before putting result in "npub" field,
so the value is already a 64-char hex pubkey. Skip nip19.decode() and
use it directly. This was causing the "data must be at least 8 chars" error.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The actual plugin source uses positional args, not object params:
- getPublicKey(packageName)
- signEvent(packageName, eventJson, id, npub)
- nip04Encrypt/Decrypt(packageName, text, id, pubKey, npub)
- nip44Encrypt/Decrypt(packageName, text, id, pubKey, npub)
- isExternalSignerInstalled(packageName)
Previous implementation was based on wrong README docs.
Now based on actual dist/esm/index.js source.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use named export { NostrSignerPlugin } not mod.default
- setPackageName takes a plain string not { packageName: string }
- isExternalSignerInstalled takes packageName as argument
- These were the exact bugs causing "plugin not available" error
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Plugin is now properly installed and registered via cap sync.
It must be bundled by Rollup, not treated as external.
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>
- vite.config.ts: mark nostr-signer-capacitor-plugin as external so
Rollup doesn't try to bundle it (it's resolved at runtime by Capacitor)
- en.ts / es.ts: remove duplicate lab key that was causing esbuild warnings
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>
Three compounding bugs fixed:
1. WRONG FORMAT: Was using nostrsigner: directly, then intent: with encoded
values. Amber README shows intent: with raw unencoded S.key=value params.
2. # IN CALLBACKURL: Our callbackUrl was https://.../#/amber-callback which
contains a # — but intent: uses # as its own delimiter (#Intent;...;end).
Having a # inside the callbackUrl value breaks the entire intent string.
Fixed: callbackUrl is now https://.../amber-callback?event= (no hash).
3. HASHROUTER MISMATCH: Amber opens /amber-callback (no hash) but HashRouter
only handles /#/route paths. Fixed in main.tsx: pre-React IIFE detects
/amber-callback path, saves ?event= to sessionStorage, then redirects to
/#/amber-callback so React Router matches AmberCallbackPage correctly.
Final correct intent URL format:
intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;S.callbackUrl=https://skate-spots.org/amber-callback?event=;end
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Root cause of "malformed request":
- buildIntentUrl() was calling encodeURIComponent() on ALL values including
callbackUrl, which was ALREADY a full URL with special chars
- This double-encoded the callbackUrl, arriving at Amber as garbage
- The intent: wrapper format was also unnecessary
Fix: back to clean nostrsigner: format per NIP-55 spec
- Only callbackUrl gets encodeURIComponent (once)
- All other params are plain query string values
- Matches exactly what NIP-55 spec shows in its example code
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The nostrsigner: scheme used directly caused "malformed request" in Amber
when opened from a browser context. The correct format per Amber's README
for web clients is the Android intent: URL format:
intent:<payload>#Intent;scheme=nostrsigner;S.type=...;S.callbackUrl=...;end
Updated all Amber operations (login, sign, nip04/nip44 encrypt/decrypt)
to use buildIntentUrl() helper instead of raw nostrsigner: URLs.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
window.open(..., '_blank') opens a new browser tab instead of firing
the Android intent — causing Amber's "malformed request" error.
Per NIP-55 spec the correct method is window.location.href = url,
which triggers the nostrsigner: intent that Amber is registered to handle.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- release_notes must be a file path for zsp, not inline YAML text
- add release-notes.txt with v1.0.26 changelog
- sync package.json version to 1.0.26 to match APK versionName
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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>
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>
- Index.tsx: remove Nostr.com button from homepage footer (next to FAQ/About)
- SupportersPage: change 'View on GitHub' → 'View on NostrHub' with correct naddr URL
- Layout: remove 'Map & Earn' (/bounties) from desktop More dropdown menu
- Layout: remove 'Map & Earn' (/bounties) from mobile slide-out nav
- Layout: clean up unused Gift icon import and stale isActive check
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Partners: removed all entries, now shows 4 placeholder slots
- Friends: added No Teeth Skateboarding with red transparent logo
linking to instagram.com/noteethskateboarding
- Friends grid: always shows real entries + fills remaining slots to 5
with "Coming soon" placeholders
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>