Fix capacitorAmberSigner: correct named export and setPackageName API

- 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>
This commit is contained in:
shakespeare.diy 2026-07-09 12:25:25 +07:00
parent 387af2aa4f
commit 0f758d7b1f

View file

@ -2,35 +2,40 @@
* CapacitorAmberSigner * CapacitorAmberSigner
* *
* A NostrSigner implementation that delegates all cryptographic operations * A NostrSigner implementation that delegates all cryptographic operations
* to the nostr-signer-capacitor-plugin, which in turn communicates with * to the nostr-signer-capacitor-plugin, which communicates with Amber
* Amber (or any NIP-55 compatible signer app) via native Android intents. * (or any NIP-55 compatible signer app) via native Android intents.
* *
* This ONLY works inside a Capacitor native app. On web/PWA use AmberSigner * This ONLY works inside a Capacitor native app on Android.
* (the intent:// URL redirect approach) instead. * On web/PWA the AmberSigner (URL redirect) is used instead.
*
* Usage:
* const signer = new CapacitorAmberSigner(pubkeyHex);
* const event = await signer.signEvent({ kind: 1, content: 'hello', tags: [], created_at: ... });
*/ */
import type { NostrEvent, NostrSigner } from '@nostrify/nostrify'; import type { NostrEvent, NostrSigner } from '@nostrify/nostrify';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
// Amber's package name on Android — used to target the correct signer app. // Amber's package name on Android
const AMBER_PACKAGE_NAME = 'com.greenart7c3.nostrsigner'; const AMBER_PACKAGE_NAME = 'com.greenart7c3.nostrsigner';
/** /**
* Lazy-load the plugin and set the package name once. * Load the plugin using its correct named export: NostrSignerPlugin.
* Returns null if not running inside Capacitor (web/PWA fallback). * The plugin calls ensureAndroid() internally — if not on Android it throws
* with code ANDROID_ONLY. We catch that and return null so the web/PWA
* path is used instead.
*/ */
async function getPlugin() { async function getPlugin() {
try { try {
const mod = await import('nostr-signer-capacitor-plugin'); // Named export — NOT a default export
const plugin = mod.default ?? mod.NostrSignerPlugin; const { NostrSignerPlugin } = await import('nostr-signer-capacitor-plugin');
if (!plugin) return null; if (!NostrSignerPlugin) return null;
await plugin.setPackageName({ packageName: AMBER_PACKAGE_NAME }); // setPackageName takes a plain string, not { packageName: string }
return plugin; await NostrSignerPlugin.setPackageName(AMBER_PACKAGE_NAME);
} catch { return NostrSignerPlugin;
} catch (e: unknown) {
// ANDROID_ONLY error = not on Android (web/PWA) — silent fallback
// Any other error = plugin not loaded correctly — log it
const code = (e as { code?: string })?.code;
if (code !== 'ANDROID_ONLY') {
console.error('[CapacitorAmberSigner] getPlugin failed:', e);
}
return null; return null;
} }
} }
@ -49,11 +54,9 @@ export class CapacitorAmberSigner implements NostrSigner {
async signEvent(event: Omit<NostrEvent, 'id' | 'sig'>): Promise<NostrEvent> { async signEvent(event: Omit<NostrEvent, 'id' | 'sig'>): Promise<NostrEvent> {
const plugin = await getPlugin(); const plugin = await getPlugin();
if (!plugin) throw new Error('Capacitor Amber plugin not available'); if (!plugin) throw new Error('Capacitor Amber plugin not available');
const { event: signedJson } = await plugin.signEvent({ const { event: signedJson } = await plugin.signEvent({
eventJson: JSON.stringify(event), eventJson: JSON.stringify(event),
}); });
return JSON.parse(signedJson) as NostrEvent; return JSON.parse(signedJson) as NostrEvent;
} }
@ -88,24 +91,9 @@ export class CapacitorAmberSigner implements NostrSigner {
}; };
} }
/**
* Check if the Amber signer app is installed on the device.
* Returns false on web/PWA (plugin not available).
*/
export async function isAmberInstalled(): Promise<boolean> {
const plugin = await getPlugin();
if (!plugin) return false;
try {
const { installed } = await plugin.isExternalSignerInstalled();
return installed;
} catch {
return false;
}
}
/** /**
* Request the public key from Amber via the Capacitor plugin. * Request the public key from Amber via the Capacitor plugin.
* Returns the hex pubkey string, or throws on failure/cancel. * Returns hex pubkey string. Throws on failure or cancellation.
*/ */
export async function getAmberPublicKeyViaPlugin(): Promise<string> { export async function getAmberPublicKeyViaPlugin(): Promise<string> {
const plugin = await getPlugin(); const plugin = await getPlugin();
@ -118,3 +106,17 @@ export async function getAmberPublicKeyViaPlugin(): Promise<string> {
if (decoded.type !== 'npub') throw new Error('Unexpected key format from Amber plugin'); if (decoded.type !== 'npub') throw new Error('Unexpected key format from Amber plugin');
return decoded.data as string; return decoded.data as string;
} }
/**
* Check if Amber is installed. Returns false on web/PWA.
*/
export async function isAmberInstalled(): Promise<boolean> {
const plugin = await getPlugin();
if (!plugin) return false;
try {
const { installed } = await plugin.isExternalSignerInstalled(AMBER_PACKAGE_NAME);
return installed;
} catch {
return false;
}
}