Root cause found in the plugin's native getPublicKey (NostrSignerPlugin.java):
when a packageName is set, it FIRST tries Amber's ContentProvider (background,
no-UI). On GrapheneOS that cross-app ContentResolver query is blocked and never
returns, so it never falls through to startActivityForResult(nostrsigner:) —
the branch that actually opens Amber's approval UI. Result: eternal
"Waiting for Amber".
Fix: getAmberPublicKeyViaPlugin no longer calls setPackageName and no longer
passes packageName; it passes only the get_public_key permission. With no
package name the plugin skips the content-resolver branch and launches the
intent directly, opening Amber's UI. Amber returns both pubkey and its package,
so later signing still works. Paired with the auto-injected <queries> manifest
block so the OS can resolve the intent in the first place.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Two concrete root causes for the eternal "Waiting for Amber…":
1. setPackageName could hang forever on GrapheneOS. getPlugin() awaited it with
no timeout, so getPublicKey() (and its 90s timeout) was never even reached.
Now setPackageName races a 3s timeout and continues regardless — the Amber
package is passed explicitly on every call anyway, so it's non-essential.
2. AndroidManifest.xml had no <queries> block. On Android 11+/GrapheneOS the OS
silently blocks the nostrsigner intent without it, so Amber never opens. Added
scripts/inject-amber-queries.mjs (idempotent) and wired it into android:init /
android:sync / android:build so the block is guaranteed present in every APK,
surviving cap sync regeneration.
Bump version to 1.1.9.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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 <assistant@shakespeare.diy>
Symptom: crew logo upload spins forever, and crew creation appears to do
nothing. Both go through Amber signEvent. Login (getPublicKey) works because
it uses Amber's silent content-resolver path, but signEvent must launch
Amber's approval Activity and wait for the result — and if that result never
comes back (buggy plugin build, user backs out, missing permission) the
bridge promise never settles, so the UI hangs with no error.
Changes:
- withTimeout() wraps every Amber bridge call (sign/getPublicKey/nip04/nip44)
with a 90s cap. A hung round-trip now rejects with a clear, actionable
message instead of spinning forever, which also unblocks the CrewForm
(isUploading/isPublishing finally resolve).
- setPackageName is now called ONCE per session (cached promise) instead of
before every op — repeated calls are needless bridge chatter and can wedge
on some builds.
NOTE (root cause, fixed on the build machine): package.json requests the
GitHub `main` build of nostr-signer-capacitor-plugin, but package-lock.json
still pins the old npm 0.0.5 (which the APK bundles). 0.0.5 is the version
prone to the signEvent activity-result hang. Regenerate the lockfile with
`npm install` so the `main` build is used — see chat for steps.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Root cause (same class as the camera bug): capacitorAmberSigner.ts used
`await import('nostr-signer-capacitor-plugin')`, but that package is marked
external in vite.config.ts and the native build uses inlineDynamicImports, so
the bare specifier survives in the APK bundle and the Android WebView can't
resolve it at runtime. The import threw, getPlugin() returned null, and every
Amber signing op — including the Blossom upload auth event that a Crew logo
upload needs — failed with "Capacitor Amber plugin not available".
Fix: access the native signer via Capacitor's registerPlugin('NostrSignerPlugin')
from the bundled @capacitor/core, and reimplement the plugin's thin JS wrapper
logic directly against the native bridge (options-object API per the plugin's
main source). This removes the broken dynamic import entirely and is version-
independent as long as the native plugin is registered. Covers both native
Amber login (getAmberPublicKeyViaPlugin) and all signing/encryption paths.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- 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>
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>
- 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>