@formstr/signer 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -7
- package/dist/index.cjs +113 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +113 -5
- package/dist/index.js.map +1 -1
- package/dist/{signer-DiKj4PR7.d.cts → signer-BepGdtJs.d.cts} +55 -1
- package/dist/{signer-DiKj4PR7.d.ts → signer-BepGdtJs.d.ts} +55 -1
- package/dist/ui/index.d.cts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,14 +58,38 @@ The single most important thing to understand about this package: **after a fres
|
|
|
58
58
|
- `getActiveAccount()` returns the account that was active before reload.
|
|
59
59
|
- `getActiveSigner()` returns **`null`**, regardless of method.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
### `unlock()` — silent rehydration
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
For every method except `ncryptsec`, the package has enough persisted state to rebuild the runtime signer without prompting anyone — `unlock()` is the way to actually do that on cold start:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const signer = createSigner({ androidSignerPlugin, /* ... */ });
|
|
67
|
+
const active = await signer.unlock({ pool }); // pool only needed for nip46
|
|
68
|
+
if (active) {
|
|
69
|
+
// signed-in user — proceed
|
|
70
|
+
} else {
|
|
71
|
+
// either no active account, or method='ncryptsec' (drive the passphrase prompt yourself)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Per-method behavior:
|
|
76
|
+
|
|
77
|
+
| Method | What `unlock()` does | Prompt on cold start? |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `extension` | constructs `ExtensionSigner` (stateless wrapper around `window.nostr`) | no |
|
|
80
|
+
| `nip46` | reuses persisted `clientSecretKey` + `remoteSignerPubkey` + `relays` to attach a `BunkerSigner` — **skips the `connect` request**, which is what triggers a fresh approval prompt every reload | no |
|
|
81
|
+
| `android` | builds the `AndroidSigner` directly from cached `pubkey` + `npub` + `androidPackageName`, **bypassing the plugin's `getPublicKey` content-provider call** | no |
|
|
82
|
+
| `ncryptsec` | returns `null` — the passphrase is not (and must not be) persisted; caller drives the prompt and calls `loginWithNcryptsec(account.ncryptsec, passphrase)` | n/a (by design) |
|
|
83
|
+
|
|
84
|
+
`unlock()` returns `null` (without emitting an event) when there is no active account, when the account is missing fields it needs to resume, when method is `nip46` but no `pool` was supplied, or when method is `android` but no plugin is configured. On success it emits the same `login`/`switch` event the corresponding `loginWith*` would.
|
|
85
|
+
|
|
86
|
+
`getPublicKey()` after a successful nip46 unlock is a memory read, not a bunker roundtrip — the cached pubkey is wired into the `BunkerSigner` wrapper so a subsequent call needs no network.
|
|
87
|
+
|
|
88
|
+
### Why not just re-call `loginWith*`?
|
|
89
|
+
|
|
90
|
+
You still can. The difference is that the `loginWith*` methods are *first-time pairing* flows — `loginWithBunkerUri` re-sends the `connect` request, `loginWithAndroidSigner` re-queries the external signer for the pubkey — and on signer apps like Amber both of those surface as a permission prompt the user has to approve again. `unlock()` is the resume path: same end state (an active `ActiveSigner`), without re-pairing.
|
|
91
|
+
|
|
92
|
+
For ncryptsec, `unlock()` returning `null` is the signal to drive the passphrase prompt and call `loginWithNcryptsec(account.ncryptsec, passphrase)` — that's the only method with no silent path, by design.
|
|
69
93
|
|
|
70
94
|
The pattern in the UI is: **always render off `getActiveAccount()`, gate signing on `getActiveSigner()`**. Show "logged in as @alice" plus an "Unlock" button when the signer is null.
|
|
71
95
|
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
41
41
|
|
|
42
42
|
// src/core/signer.ts
|
|
43
43
|
var import_nostr_tools5 = require("nostr-tools");
|
|
44
|
+
var import_nip462 = require("nostr-tools/nip46");
|
|
44
45
|
|
|
45
46
|
// src/core/storage.ts
|
|
46
47
|
var DEFAULT_PREFIX = "@formstr/signer:";
|
|
@@ -165,10 +166,15 @@ var import_nostr_tools3 = require("nostr-tools");
|
|
|
165
166
|
var import_nip46 = require("nostr-tools/nip46");
|
|
166
167
|
var BunkerSigner = class {
|
|
167
168
|
#delegate;
|
|
168
|
-
|
|
169
|
+
#cachedUserPubkey;
|
|
170
|
+
constructor(delegate, cachedUserPubkey) {
|
|
169
171
|
this.#delegate = delegate;
|
|
172
|
+
this.#cachedUserPubkey = cachedUserPubkey ?? null;
|
|
170
173
|
}
|
|
171
174
|
getPublicKey() {
|
|
175
|
+
if (this.#cachedUserPubkey !== null) {
|
|
176
|
+
return Promise.resolve(this.#cachedUserPubkey);
|
|
177
|
+
}
|
|
172
178
|
return this.#delegate.getPublicKey();
|
|
173
179
|
}
|
|
174
180
|
signEvent(event) {
|
|
@@ -244,7 +250,7 @@ async function connectWithBunkerUri(uri, options = {}) {
|
|
|
244
250
|
options.onRelayMismatch
|
|
245
251
|
);
|
|
246
252
|
return {
|
|
247
|
-
signer: new BunkerSigner(tools),
|
|
253
|
+
signer: new BunkerSigner(tools, pubkey),
|
|
248
254
|
pubkey,
|
|
249
255
|
pointer: { ...pointer, relays: resolvedRelays },
|
|
250
256
|
clientSecretKey
|
|
@@ -280,7 +286,7 @@ function initiateNostrConnect(options) {
|
|
|
280
286
|
options.onRelayMismatch
|
|
281
287
|
);
|
|
282
288
|
return {
|
|
283
|
-
signer: new BunkerSigner(tools),
|
|
289
|
+
signer: new BunkerSigner(tools, pubkey),
|
|
284
290
|
pubkey,
|
|
285
291
|
pointer: { ...tools.bp, relays: resolvedRelays },
|
|
286
292
|
clientSecretKey
|
|
@@ -371,6 +377,17 @@ var AndroidSigner = class {
|
|
|
371
377
|
return result;
|
|
372
378
|
}
|
|
373
379
|
};
|
|
380
|
+
function describeIdentifier(value) {
|
|
381
|
+
if (value === null) return "null";
|
|
382
|
+
if (value === void 0) return "undefined";
|
|
383
|
+
if (typeof value !== "string") {
|
|
384
|
+
return `<${typeof value}>`;
|
|
385
|
+
}
|
|
386
|
+
if (value.length === 0) return "empty string";
|
|
387
|
+
const prefix = value.slice(0, 12);
|
|
388
|
+
const suffix = value.length > 12 ? "\u2026" : "";
|
|
389
|
+
return `"${prefix}${suffix}" (length=${value.length})`;
|
|
390
|
+
}
|
|
374
391
|
async function loginWithAndroidSigner(plugin, packageName) {
|
|
375
392
|
if (packageName) {
|
|
376
393
|
await plugin.setPackageName(packageName);
|
|
@@ -382,9 +399,18 @@ async function loginWithAndroidSigner(plugin, packageName) {
|
|
|
382
399
|
"@formstr/signer: android signer did not return a package name and none was supplied"
|
|
383
400
|
);
|
|
384
401
|
}
|
|
385
|
-
|
|
402
|
+
let decoded;
|
|
403
|
+
try {
|
|
404
|
+
decoded = import_nostr_tools4.nip19.decode(npub);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
`@formstr/signer: android signer returned an undecodable identifier (got ${describeIdentifier(npub)}): ${e.message}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
386
410
|
if (decoded.type !== "npub") {
|
|
387
|
-
throw new Error(
|
|
411
|
+
throw new Error(
|
|
412
|
+
`@formstr/signer: android signer returned a non-npub identifier (type=${decoded.type}, got ${describeIdentifier(npub)})`
|
|
413
|
+
);
|
|
388
414
|
}
|
|
389
415
|
return {
|
|
390
416
|
signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),
|
|
@@ -657,6 +683,88 @@ var Signer = class {
|
|
|
657
683
|
getActiveSigner() {
|
|
658
684
|
return this.#activeSigner;
|
|
659
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Silently unlock the active account from persisted state — no user
|
|
688
|
+
* prompt, no fresh pairing. The package already keeps everything it
|
|
689
|
+
* needs to reconstruct the runtime signer on disk; this method is the
|
|
690
|
+
* way to actually use that on cold start instead of re-running each
|
|
691
|
+
* method's first-time login flow.
|
|
692
|
+
*
|
|
693
|
+
* Behavior by method:
|
|
694
|
+
*
|
|
695
|
+
* - `extension`: constructs an {@link ExtensionSigner}, which just
|
|
696
|
+
* proxies to `window.nostr`. No setup roundtrip — individual
|
|
697
|
+
* operations may still prompt depending on the extension's own
|
|
698
|
+
* permission state, but unlock itself does not.
|
|
699
|
+
*
|
|
700
|
+
* - `nip46`: reuses the stored `clientSecretKey` to construct a
|
|
701
|
+
* {@link BunkerSigner} against the stored bunker pubkey + relays
|
|
702
|
+
* via `BunkerSigner.fromBunker`. Deliberately skips the `connect`
|
|
703
|
+
* request — the remote signer (Amber etc.) approved this client
|
|
704
|
+
* pubkey on first pairing and re-sending `connect` is what surfaces
|
|
705
|
+
* a fresh approval prompt every cold start. Requires `options.pool`
|
|
706
|
+
* so the BunkerSigner has somewhere to listen for responses.
|
|
707
|
+
* The cached user pubkey is fed into the wrapper so a follow-up
|
|
708
|
+
* `getPublicKey()` is a memory read, not a relay request.
|
|
709
|
+
*
|
|
710
|
+
* - `android`: constructs an {@link AndroidSigner} directly from the
|
|
711
|
+
* stored `androidPackageName` + `pubkey` + `npub`. Skips the
|
|
712
|
+
* `getPublicKey` content-provider roundtrip that
|
|
713
|
+
* {@link loginWithAndroidSigner} performs and that — on Amber —
|
|
714
|
+
* surfaces as a permission prompt every cold start.
|
|
715
|
+
*
|
|
716
|
+
* - `ncryptsec`: returns `null`. There is no silent path — the user's
|
|
717
|
+
* passphrase isn't (and shouldn't be) persisted. The caller must
|
|
718
|
+
* drive the passphrase prompt and call {@link loginWithNcryptsec}.
|
|
719
|
+
*
|
|
720
|
+
* Returns `null` (without emitting any event or mutating state) when
|
|
721
|
+
* there is no active account, when the account is missing fields
|
|
722
|
+
* required to unlock, when `nip46` is the method but no `pool` was
|
|
723
|
+
* supplied, or when `android` is the method but no plugin is
|
|
724
|
+
* configured. On success emits the same `login` / `switch` event the
|
|
725
|
+
* corresponding `loginWith*` would.
|
|
726
|
+
*/
|
|
727
|
+
async unlock(options = {}) {
|
|
728
|
+
const account = this.getActiveAccount();
|
|
729
|
+
if (!account) return null;
|
|
730
|
+
switch (account.method) {
|
|
731
|
+
case "extension": {
|
|
732
|
+
const signer = new ExtensionSigner();
|
|
733
|
+
this.#setActive(account, signer);
|
|
734
|
+
return signer;
|
|
735
|
+
}
|
|
736
|
+
case "nip46": {
|
|
737
|
+
if (!account.nip46) return null;
|
|
738
|
+
if (!options.pool) return null;
|
|
739
|
+
const { remoteSignerPubkey, relays, clientSecretKey } = account.nip46;
|
|
740
|
+
if (!remoteSignerPubkey || !relays.length || !clientSecretKey) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const tools = import_nip462.BunkerSigner.fromBunker(
|
|
744
|
+
hexToBytes(clientSecretKey),
|
|
745
|
+
{ pubkey: remoteSignerPubkey, relays, secret: null },
|
|
746
|
+
{ pool: options.pool }
|
|
747
|
+
);
|
|
748
|
+
const signer = new BunkerSigner(tools, account.pubkey);
|
|
749
|
+
this.#setActive(account, signer);
|
|
750
|
+
return signer;
|
|
751
|
+
}
|
|
752
|
+
case "android": {
|
|
753
|
+
if (!account.androidPackageName) return null;
|
|
754
|
+
if (!this.#defaultAndroidPlugin) return null;
|
|
755
|
+
const signer = new AndroidSigner(
|
|
756
|
+
this.#defaultAndroidPlugin,
|
|
757
|
+
account.androidPackageName,
|
|
758
|
+
account.npub,
|
|
759
|
+
account.pubkey
|
|
760
|
+
);
|
|
761
|
+
this.#setActive(account, signer);
|
|
762
|
+
return signer;
|
|
763
|
+
}
|
|
764
|
+
case "ncryptsec":
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
660
768
|
/**
|
|
661
769
|
* Make `pubkey` the active account. Clears the in-memory signer —
|
|
662
770
|
* the new account starts **locked** even if it was previously
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/signer.ts","../src/core/storage.ts","../src/core/localSigner.ts","../src/nip49.ts","../src/nip07.ts","../src/nip46.ts","../src/nip55.ts"],"sourcesContent":["export { Signer, createSigner } from './core/signer.js';\nexport { LocalSigner } from './core/localSigner.js';\nexport { localStorageAdapter } from './core/storage.js';\nexport type { StorageAdapter } from './core/storage.js';\nexport type {\n SignerConfig,\n ActiveSigner,\n StoredAccount,\n LoginMethod,\n SignerEvent,\n NostrConnectOptions,\n BunkerLoginOptions,\n RelayMismatchInfo,\n RelayMismatchHandler,\n} from './core/types.js';\nexport {\n encryptSecretKey,\n decryptNcryptsec,\n generateAccount,\n type GeneratedAccount,\n} from './nip49.js';\nexport { ExtensionSigner, getWindowNostr, type WindowNostr } from './nip07.js';\nexport {\n AndroidSigner,\n loginWithAndroidSigner,\n type AndroidSignerPlugin,\n type AndroidSignerAppInfo,\n type AndroidLoginOptions,\n type AndroidLoginResult,\n} from './nip55.js';\nexport {\n BunkerSigner,\n connectWithBunkerUri,\n initiateNostrConnect,\n bytesToHex,\n hexToBytes,\n type BunkerPointer,\n type BunkerLoginOptions as BunkerConnectOptions,\n type BunkerConnectResult,\n type NostrConnectInitOptions,\n type NostrConnectInitiation,\n} from './nip46.js';\n","import { getPublicKey, nip19 } from 'nostr-tools';\nimport type {\n ActiveSigner,\n BunkerLoginOptions,\n NostrConnectOptions,\n SignerConfig,\n SignerEvent,\n StoredAccount,\n} from './types.js';\nimport { localStorageAdapter, type StorageAdapter } from './storage.js';\nimport { LocalSigner } from './localSigner.js';\nimport { decryptNcryptsec, generateAccount } from '../nip49.js';\nimport { ExtensionSigner } from '../nip07.js';\nimport { bytesToHex, connectWithBunkerUri, initiateNostrConnect } from '../nip46.js';\nimport {\n loginWithAndroidSigner as connectWithAndroidSigner,\n type AndroidLoginOptions,\n type AndroidSignerAppInfo,\n type AndroidSignerPlugin,\n} from '../nip55.js';\n\nconst ACCOUNTS_KEY = 'accounts';\nconst ACTIVE_KEY = 'active-pubkey';\n\n/**\n * Multi-account Nostr signer with persistence.\n *\n * **Hydration.** The constructor reads previously-saved accounts from\n * the configured storage adapter. Every hydrated account starts\n * **locked**: present in `listAccounts()` and (if it was the active one\n * before) reachable via `getActiveAccount()`, but `getActiveSigner()`\n * returns `null` until the user re-authenticates. The matching\n * `loginWith*` method unlocks the active account.\n *\n * **Locked vs unlocked.** Use `getActiveAccount()` to render UI (\"logged\n * in as @alice\") and `getActiveSigner()` to decide whether the user can\n * actually sign. The pattern is \"show the account always, gate signing\n * on the signer.\"\n *\n * **Events.** Subscribe via `onChange()` to re-render when an account\n * is added, switched, or removed. See {@link SignerEvent}.\n */\nexport class Signer {\n readonly #storage: StorageAdapter;\n readonly #defaultAndroidPlugin: AndroidSignerPlugin | undefined;\n readonly #appMetadata: { name?: string; url?: string; image?: string };\n #accounts: StoredAccount[] = [];\n #activePubkey: string | null = null;\n #activeSigner: ActiveSigner | null = null;\n #listeners = new Set<(event: SignerEvent) => void>();\n\n constructor(config: SignerConfig = {}) {\n this.#storage = config.storage ?? localStorageAdapter(config.storageKeyPrefix);\n this.#defaultAndroidPlugin = config.androidSignerPlugin;\n this.#appMetadata = {\n name: config.appName,\n url: config.appUrl,\n image: config.appImage,\n };\n this.#hydrate();\n }\n\n #hydrate(): void {\n try {\n const raw = this.#storage.get(ACCOUNTS_KEY);\n if (raw) {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) this.#accounts = parsed as StoredAccount[];\n }\n this.#activePubkey = this.#storage.get(ACTIVE_KEY);\n } catch {\n this.#accounts = [];\n this.#activePubkey = null;\n }\n }\n\n #persistAccounts(): void {\n this.#storage.set(ACCOUNTS_KEY, JSON.stringify(this.#accounts));\n }\n\n #persistActive(): void {\n if (this.#activePubkey) this.#storage.set(ACTIVE_KEY, this.#activePubkey);\n else this.#storage.remove(ACTIVE_KEY);\n }\n\n #upsertAccount(account: StoredAccount): void {\n const idx = this.#accounts.findIndex(a => a.pubkey === account.pubkey);\n if (idx >= 0) this.#accounts[idx] = account;\n else this.#accounts.push(account);\n this.#persistAccounts();\n }\n\n #setActive(account: StoredAccount, signer: ActiveSigner): void {\n const wasDifferent = this.#activePubkey !== null && this.#activePubkey !== account.pubkey;\n this.#activePubkey = account.pubkey;\n this.#activeSigner = signer;\n this.#persistActive();\n this.#emit({ type: wasDifferent ? 'switch' : 'login', account });\n }\n\n #emit(event: SignerEvent): void {\n for (const cb of this.#listeners) {\n try {\n cb(event);\n } catch {\n // listener errors are swallowed so one bad listener can't break others\n }\n }\n }\n\n /**\n * Generate a brand-new nsec, encrypt it with `passphrase` (NIP-49),\n * persist the resulting `ncryptsec` account, and activate it. Returns\n * the new account's `npub` and `ncryptsec` — the caller must surface\n * the `ncryptsec` to the user **immediately** since it is the only way\n * back into the account on a fresh device.\n *\n * @throws if `passphrase` is empty.\n */\n async createAccount(passphrase: string): Promise<{ npub: string; ncryptsec: string }> {\n if (!passphrase) throw new Error('createAccount: passphrase required');\n const { secretKey, pubkey, npub, ncryptsec } = generateAccount(passphrase);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return { npub, ncryptsec };\n }\n\n /**\n * Decrypt an ncryptsec with the user's passphrase, persist the account\n * (overwriting any previous entry for the same pubkey), and activate it.\n *\n * @throws if either argument is empty, or if the passphrase doesn't\n * decrypt the ncryptsec.\n */\n async loginWithNcryptsec(ncryptsec: string, passphrase: string): Promise<StoredAccount> {\n if (!ncryptsec) throw new Error('loginWithNcryptsec: ncryptsec required');\n if (!passphrase) throw new Error('loginWithNcryptsec: passphrase required');\n const secretKey = decryptNcryptsec(ncryptsec, passphrase);\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return account;\n }\n\n /**\n * Connect via the NIP-07 browser extension exposed at `window.nostr`.\n * The extension prompts the user for permission on first use.\n *\n * @throws if no extension is installed or the user denies the request.\n */\n async loginWithExtension(): Promise<StoredAccount> {\n const extension = new ExtensionSigner();\n const pubkey = await extension.getPublicKey();\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'extension' };\n this.#upsertAccount(account);\n this.#setActive(account, extension);\n return account;\n }\n\n /**\n * Connect to a NIP-46 remote signer via a `bunker://` URI. Relays are\n * read from the URI itself — no hardcoded fallbacks. Pass a `pool`\n * to reuse an existing relay connection; pass `clientSecretKey` to\n * resume a previous session (the hex from `StoredAccount.nip46`).\n *\n * @throws if the URI is malformed, no relay is reachable, or the\n * remote signer rejects pairing within the implementation's timeout.\n */\n async loginWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n ): Promise<StoredAccount> {\n const result = await connectWithBunkerUri(uri, options);\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Initiate a NIP-46 `nostrconnect://` pairing. Generates a client\n * keypair, publishes a connect request to the supplied `relays`, and\n * waits for a remote signer to pair. Call `options.onUri(uri)` to\n * render the URI as a QR code; the returned promise resolves once\n * pairing completes. Cancel by aborting `options.signal`.\n *\n * @throws if `relays` is empty, the user aborts, the pairing times\n * out, or no signer responds.\n */\n async loginWithNostrConnect(options: NostrConnectOptions): Promise<StoredAccount> {\n if (options.relays.length === 0) {\n throw new Error('loginWithNostrConnect: at least one relay required');\n }\n // Merge per-call metadata over SignerConfig defaults (appName/url/image).\n // `name` is required — Amber (and likely other NIP-55 signer apps) gate\n // the consent UI on having a recognizable client identity in the URI.\n // Without one they will receive the request but never surface\n // approve/deny buttons, leaving the pairing silently stuck.\n const metadata = {\n name: options.metadata?.name ?? this.#appMetadata.name,\n url: options.metadata?.url ?? this.#appMetadata.url,\n image: options.metadata?.image ?? this.#appMetadata.image,\n };\n if (!metadata.name) {\n throw new Error(\n '@formstr/signer: loginWithNostrConnect requires an app name. Set `appName` in createSigner() or pass `metadata.name` to loginWithNostrConnect().',\n );\n }\n const init = initiateNostrConnect({\n relays: options.relays,\n metadata,\n perms: options.perms,\n pool: options.pool,\n onAuth: options.onAuth,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n onRelayMismatch: options.onRelayMismatch,\n });\n options.onUri(init.uri);\n const result = await init.complete;\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri: init.uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Enumerate NIP-55 signer apps installed on the device, via the\n * configured Android plugin (or `plugin` if supplied). Useful for\n * rendering a \"pick your signer\" list — the built-in UI does this\n * automatically when the Android tab is selected.\n *\n * Only meaningful inside a Capacitor Android shell. On web/iOS the\n * configured plugin is typically absent and this throws.\n *\n * @throws if no plugin is configured and none is passed in.\n */\n async listAndroidSignerApps(\n plugin?: AndroidSignerPlugin,\n ): Promise<AndroidSignerAppInfo[]> {\n const p = plugin ?? this.#defaultAndroidPlugin;\n if (!p) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to listAndroidSignerApps)',\n );\n }\n const { apps } = await p.getInstalledSignerApps();\n return apps;\n }\n\n /**\n * Sign in via a NIP-55 Android external signer (Amber, etc). If\n * `options.packageName` is given, that specific signer app is invoked;\n * otherwise the plugin picks a default (typically the only installed\n * signer, or an OS chooser). Pass `options.plugin` to override the\n * configured default for this call.\n *\n * @throws if no plugin is configured, the signer app cannot be\n * resolved to a package name, or the user denies the request.\n */\n async loginWithAndroidSigner(options: AndroidLoginOptions = {}): Promise<StoredAccount> {\n const plugin = options.plugin ?? this.#defaultAndroidPlugin;\n if (!plugin) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to loginWithAndroidSigner)',\n );\n }\n const result = await connectWithAndroidSigner(plugin, options.packageName);\n const account: StoredAccount = {\n npub: result.npub,\n pubkey: result.pubkey,\n method: 'android',\n androidPackageName: result.packageName,\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /** Snapshot of every persisted account, in insertion order. */\n listAccounts(): StoredAccount[] {\n return [...this.#accounts];\n }\n\n /**\n * The currently selected account, or `null` if none. Present even when\n * the account is locked (no active signer yet). Use this to render\n * \"logged in as @alice\" — pair with {@link getActiveSigner} to decide\n * whether signing is actually available.\n */\n getActiveAccount(): StoredAccount | null {\n if (!this.#activePubkey) return null;\n return this.#accounts.find(a => a.pubkey === this.#activePubkey) ?? null;\n }\n\n /**\n * The unlocked signer for the active account, or `null` if locked.\n * After a fresh page load this is `null` for every account type\n * (passphrase / extension grant / signer-app handshake all need to\n * be redone). Calling the matching `loginWith*` method unlocks it.\n */\n getActiveSigner(): ActiveSigner | null {\n return this.#activeSigner;\n }\n\n /**\n * Make `pubkey` the active account. Clears the in-memory signer —\n * the new account starts **locked** even if it was previously\n * unlocked in this session.\n *\n * @throws if `pubkey` does not match any persisted account.\n */\n async switchAccount(pubkey: string): Promise<void> {\n const account = this.#accounts.find(a => a.pubkey === pubkey);\n if (!account) throw new Error(`switchAccount: no account for pubkey ${pubkey}`);\n this.#activePubkey = pubkey;\n this.#activeSigner = null;\n this.#persistActive();\n this.#emit({ type: 'switch', account });\n }\n\n /**\n * Remove an account from storage. `pubkey` defaults to the active\n * account. If the active account is removed, the in-memory signer is\n * cleared. No-op if there is nothing to remove.\n */\n async logout(pubkey?: string): Promise<void> {\n const target = pubkey ?? this.#activePubkey;\n if (!target) return;\n this.#accounts = this.#accounts.filter(a => a.pubkey !== target);\n this.#persistAccounts();\n if (this.#activePubkey === target) {\n this.#activePubkey = null;\n this.#activeSigner = null;\n this.#persistActive();\n }\n this.#emit({ type: 'logout', pubkey: target });\n }\n\n /**\n * Subscribe to account-state changes. Returns an unsubscribe function.\n * Listener errors are swallowed so one bad listener can't break others.\n * See {@link SignerEvent} for the variants.\n */\n onChange(cb: (event: SignerEvent) => void): () => void {\n this.#listeners.add(cb);\n return () => {\n this.#listeners.delete(cb);\n };\n }\n}\n\n/** Convenience wrapper around `new Signer(config)`. */\nexport function createSigner(config: SignerConfig = {}): Signer {\n return new Signer(config);\n}\n","export interface StorageAdapter {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nconst DEFAULT_PREFIX = '@formstr/signer:';\n\nexport function localStorageAdapter(prefix: string = DEFAULT_PREFIX): StorageAdapter {\n const ls = (): Storage | null => {\n try {\n return typeof globalThis !== 'undefined' && globalThis.localStorage\n ? globalThis.localStorage\n : null;\n } catch {\n return null;\n }\n };\n return {\n get(key) {\n try {\n return ls()?.getItem(prefix + key) ?? null;\n } catch {\n return null;\n }\n },\n set(key, value) {\n try {\n ls()?.setItem(prefix + key, value);\n } catch {\n // swallow quota / privacy-mode errors\n }\n },\n remove(key) {\n try {\n ls()?.removeItem(prefix + key);\n } catch {\n // swallow\n }\n },\n };\n}\n","import {\n finalizeEvent,\n getPublicKey,\n nip04,\n nip44,\n type Event as NostrEvent,\n type EventTemplate,\n} from 'nostr-tools';\nimport type { ActiveSigner } from './types.js';\n\n/**\n * ActiveSigner backed by a raw secret key held in memory.\n * The secret key never leaves this object — there is no getter for it.\n */\nexport class LocalSigner implements ActiveSigner {\n readonly #secretKey: Uint8Array;\n\n constructor(secretKey: Uint8Array) {\n this.#secretKey = secretKey;\n }\n\n async getPublicKey(): Promise<string> {\n return getPublicKey(this.#secretKey);\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return finalizeEvent(event, this.#secretKey);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return nip04.encrypt(this.#secretKey, peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return nip04.decrypt(this.#secretKey, peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.encrypt(plaintext, key);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.decrypt(ciphertext, key);\n }\n}\n","import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';\nimport { encrypt as nip49Encrypt, decrypt as nip49Decrypt } from 'nostr-tools/nip49';\n\nexport function encryptSecretKey(secretKey: Uint8Array, passphrase: string): string {\n return nip49Encrypt(secretKey, passphrase);\n}\n\nexport function decryptNcryptsec(ncryptsec: string, passphrase: string): Uint8Array {\n return nip49Decrypt(ncryptsec, passphrase);\n}\n\nexport interface GeneratedAccount {\n secretKey: Uint8Array;\n pubkey: string;\n npub: string;\n ncryptsec: string;\n}\n\nexport function generateAccount(passphrase: string): GeneratedAccount {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const ncryptsec = nip49Encrypt(secretKey, passphrase);\n return { secretKey, pubkey, npub, ncryptsec };\n}\n","import type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\nexport interface WindowNostr {\n getPublicKey(): Promise<string>;\n signEvent(event: EventTemplate): Promise<NostrEvent>;\n getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>;\n nip04?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n nip44?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n}\n\nexport function getWindowNostr(): WindowNostr {\n const nostr = (globalThis as { nostr?: WindowNostr }).nostr;\n if (!nostr) {\n throw new Error(\n '@formstr/signer: NIP-07 extension not found (globalThis.nostr is undefined)',\n );\n }\n return nostr;\n}\n\nexport class ExtensionSigner implements ActiveSigner {\n async getPublicKey(): Promise<string> {\n return getWindowNostr().getPublicKey();\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return getWindowNostr().signEvent(event);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.encrypt(peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.decrypt(peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.encrypt(peerPubkey, plaintext);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.decrypt(peerPubkey, ciphertext);\n }\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools';\nimport {\n BunkerSigner as ToolsBunkerSigner,\n createNostrConnectURI,\n parseBunkerInput,\n type BunkerPointer,\n} from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner, RelayMismatchHandler } from './core/types.js';\n\nexport type { BunkerPointer };\n\n/**\n * Thin wrapper around nostr-tools' BunkerSigner that exposes only the\n * ActiveSigner surface. We keep this layer so callers depend on a stable\n * interface even if we ever swap the underlying implementation.\n */\nexport class BunkerSigner implements ActiveSigner {\n readonly #delegate: ToolsBunkerSigner;\n\n constructor(delegate: ToolsBunkerSigner) {\n this.#delegate = delegate;\n }\n\n getPublicKey(): Promise<string> {\n return this.#delegate.getPublicKey();\n }\n signEvent(event: EventTemplate): Promise<NostrEvent> {\n return this.#delegate.signEvent(event);\n }\n nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip04Encrypt(peerPubkey, plaintext);\n }\n nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip04Decrypt(peerPubkey, ciphertext);\n }\n nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip44Encrypt(peerPubkey, plaintext);\n }\n nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip44Decrypt(peerPubkey, ciphertext);\n }\n async close(): Promise<void> {\n return this.#delegate.close();\n }\n}\n\nexport interface BunkerLoginOptions {\n /** Custom pool, e.g. for tests. Defaults to a new SimplePool inside nostr-tools. */\n pool?: AbstractSimplePool;\n /** Called when the remote signer needs the user to visit an auth URL. */\n onAuth?: (url: string) => void;\n /** Optional client session keypair (hex bytes). Auto-generated if omitted. */\n clientSecretKey?: Uint8Array;\n /** Notified when the bunker's preferred relays differ from the URI's. */\n onRelayMismatch?: RelayMismatchHandler;\n /**\n * NIP-46 permissions to request as the 3rd `connect` param\n * (e.g. `['sign_event:1', 'nip44_encrypt']`). When omitted, the\n * connect request carries no perms — bunker UIs may then skip the\n * approval prompt entirely, leaving the user with nothing to tap.\n */\n perms?: string[];\n}\n\nasync function fetchBunkerRelays(tools: ToolsBunkerSigner): Promise<string[] | null> {\n try {\n const resp = await tools.sendRequest('get_relays', []);\n const parsed = JSON.parse(resp) as unknown;\n if (Array.isArray(parsed)) {\n return parsed.filter((r): r is string => typeof r === 'string');\n }\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.keys(parsed as Record<string, unknown>);\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction relayListsMatch(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sa = [...a].sort();\n const sb = [...b].sort();\n for (let i = 0; i < sa.length; i++) if (sa[i] !== sb[i]) return false;\n return true;\n}\n\nasync function resolveRelayChoice(\n tools: ToolsBunkerSigner,\n userRelays: string[],\n onRelayMismatch: RelayMismatchHandler | undefined,\n): Promise<string[]> {\n if (!onRelayMismatch) return userRelays;\n const bunkerRelays = await fetchBunkerRelays(tools);\n if (!bunkerRelays || relayListsMatch(userRelays, bunkerRelays)) return userRelays;\n const accept = await onRelayMismatch({ userRelays, bunkerRelays });\n return accept ? bunkerRelays : userRelays;\n}\n\nexport interface BunkerConnectResult {\n signer: BunkerSigner;\n pubkey: string;\n pointer: BunkerPointer;\n clientSecretKey: Uint8Array;\n}\n\n/**\n * Connect to a remote signer via a bunker:// URI (or a NIP-05 identifier).\n * Relays come from the URI — there is no fallback default list.\n */\nexport async function connectWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n): Promise<BunkerConnectResult> {\n const pointer = await parseBunkerInput(uri);\n if (!pointer) {\n throw new Error('@formstr/signer: invalid bunker URI');\n }\n if (!pointer.relays?.length) {\n throw new Error('@formstr/signer: bunker URI must include at least one relay');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const tools = ToolsBunkerSigner.fromBunker(clientSecretKey, pointer, {\n pool: options.pool,\n onauth: options.onAuth,\n });\n // nostr-tools' BunkerSigner.connect() hardcodes only [pubkey, secret],\n // dropping the optional 3rd `perms` arg defined by NIP-46. Without it\n // bunker UIs (Amber, etc.) have no permissions to authorize and may\n // skip the approval prompt entirely. We send the request directly.\n await tools.sendRequest('connect', [\n pointer.pubkey,\n pointer.secret ?? '',\n (options.perms ?? []).join(','),\n ]);\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n pointer.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools),\n pubkey,\n pointer: { ...pointer, relays: resolvedRelays },\n clientSecretKey,\n };\n}\n\nexport interface NostrConnectInitOptions {\n /** User-supplied relays. The whole point of the strict-relay rule. */\n relays: string[];\n metadata?: { name?: string; url?: string; image?: string };\n /** Permissions to request (NIP-46 perms list, e.g. [\"sign_event:1\",\"nip44_encrypt\"]). */\n perms?: string[];\n pool?: AbstractSimplePool;\n onAuth?: (url: string) => void;\n /** Override the auto-generated client session keypair. */\n clientSecretKey?: Uint8Array;\n /** Override the auto-generated URI secret. */\n secret?: string;\n /** Abort the pairing wait. */\n signal?: AbortSignal;\n /** Max wait for pairing in ms (default 5 minutes). */\n timeoutMs?: number;\n /** Notified when the bunker's preferred relays differ from the user's. */\n onRelayMismatch?: RelayMismatchHandler;\n}\n\nexport interface NostrConnectInitiation {\n uri: string;\n clientPubkey: string;\n complete: Promise<BunkerConnectResult>;\n}\n\n/**\n * Generate a nostrconnect:// URI and wait for the remote signer to pair.\n * The caller displays the URI (typically as a QR code), and the returned\n * `complete` promise resolves once the signer connects back.\n */\nexport function initiateNostrConnect(options: NostrConnectInitOptions): NostrConnectInitiation {\n if (options.relays.length === 0) {\n throw new Error('@formstr/signer: at least one relay is required for nostrconnect');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const secret = options.secret ?? Math.random().toString(36).slice(2);\n const uri = createNostrConnectURI({\n clientPubkey,\n relays: options.relays,\n secret,\n perms: options.perms,\n name: options.metadata?.name,\n url: options.metadata?.url,\n image: options.metadata?.image,\n });\n const maxWaitOrAbort: number | AbortSignal =\n options.signal ?? options.timeoutMs ?? 300_000;\n // skipSwitchRelays:true — keep the caller-supplied relays authoritative,\n // never silently swap to whatever the bunker prefers.\n const complete = ToolsBunkerSigner.fromURI(\n clientSecretKey,\n uri,\n { pool: options.pool, onauth: options.onAuth, skipSwitchRelays: true },\n maxWaitOrAbort,\n ).then(async (tools) => {\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n options.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools),\n pubkey,\n pointer: { ...tools.bp, relays: resolvedRelays },\n clientSecretKey,\n };\n });\n return { uri, clientPubkey, complete };\n}\n\nconst hexAlphabet = '0123456789abcdef';\n\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += hexAlphabet[b >> 4] + hexAlphabet[b & 0xf];\n return s;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexToBytes: odd-length hex string');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { getEventHash, nip19, type Event as NostrEvent, type EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\n/**\n * Subset of `nostr-signer-capacitor-plugin`'s exported `NostrSignerPlugin`\n * that we depend on. Signatures intentionally mirror that library\n * (positional args, per-call `packageName`) so the real plugin is\n * structurally assignable here — and any mock written against this\n * interface is a faithful stand-in. The conformance is enforced by a\n * compile-time guard in `tests/helpers/mockAndroidPlugin.ts`.\n */\nexport interface AndroidSignerAppInfo {\n name: string;\n packageName: string;\n iconUrl?: string;\n}\n\nexport interface AndroidSignerPlugin {\n setPackageName(packageName: string): Promise<void>;\n getInstalledSignerApps(): Promise<{ apps: AndroidSignerAppInfo[] }>;\n getPublicKey(\n packageName?: string,\n permissions?: string,\n ): Promise<{ npub: string; package: string }>;\n signEvent(\n packageName: string,\n eventJson: string,\n id: string,\n npub: string,\n ): Promise<{ signature: string; id: string; event: string }>;\n nip04Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip04Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n}\n\nexport interface AndroidLoginOptions {\n /** The Android package name of the external signer app (e.g. com.greenart7c3.nostrsigner). */\n packageName?: string;\n /** Override the plugin for this call. Falls back to SignerConfig.androidSignerPlugin. */\n plugin?: AndroidSignerPlugin;\n}\n\nexport class AndroidSigner implements ActiveSigner {\n readonly #plugin: AndroidSignerPlugin;\n readonly #packageName: string;\n readonly #npub: string;\n readonly #pubkey: string;\n\n constructor(\n plugin: AndroidSignerPlugin,\n packageName: string,\n npub: string,\n pubkey: string,\n ) {\n this.#plugin = plugin;\n this.#packageName = packageName;\n this.#npub = npub;\n this.#pubkey = pubkey;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#pubkey;\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n const unsigned = { ...event, pubkey: this.#pubkey };\n const eventId = getEventHash(unsigned);\n const result = await this.#plugin.signEvent(\n this.#packageName,\n JSON.stringify(unsigned),\n eventId,\n this.#npub,\n );\n return JSON.parse(result.event) as NostrEvent;\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n}\n\nexport interface AndroidLoginResult {\n signer: AndroidSigner;\n pubkey: string;\n npub: string;\n packageName: string;\n}\n\nexport async function loginWithAndroidSigner(\n plugin: AndroidSignerPlugin,\n packageName?: string,\n): Promise<AndroidLoginResult> {\n if (packageName) {\n await plugin.setPackageName(packageName);\n }\n const { npub, package: pluginPackage } = await plugin.getPublicKey(packageName);\n const resolvedPackage = pluginPackage || packageName;\n if (!resolvedPackage) {\n throw new Error(\n '@formstr/signer: android signer did not return a package name and none was supplied',\n );\n }\n const decoded = nip19.decode(npub);\n if (decoded.type !== 'npub') {\n throw new Error('@formstr/signer: android signer returned a non-npub identifier');\n }\n return {\n signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),\n pubkey: decoded.data,\n npub,\n packageName: resolvedPackage,\n };\n}\n"],"mappings":"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","names":["import_nostr_tools","import_nostr_tools","nip49Encrypt","nip49Decrypt","import_nostr_tools","ToolsBunkerSigner","import_nostr_tools"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/signer.ts","../src/core/storage.ts","../src/core/localSigner.ts","../src/nip49.ts","../src/nip07.ts","../src/nip46.ts","../src/nip55.ts"],"sourcesContent":["export { Signer, createSigner } from './core/signer.js';\nexport { LocalSigner } from './core/localSigner.js';\nexport { localStorageAdapter } from './core/storage.js';\nexport type { StorageAdapter } from './core/storage.js';\nexport type {\n SignerConfig,\n ActiveSigner,\n StoredAccount,\n LoginMethod,\n SignerEvent,\n NostrConnectOptions,\n BunkerLoginOptions,\n RelayMismatchInfo,\n RelayMismatchHandler,\n UnlockOptions,\n} from './core/types.js';\nexport {\n encryptSecretKey,\n decryptNcryptsec,\n generateAccount,\n type GeneratedAccount,\n} from './nip49.js';\nexport { ExtensionSigner, getWindowNostr, type WindowNostr } from './nip07.js';\nexport {\n AndroidSigner,\n loginWithAndroidSigner,\n type AndroidSignerPlugin,\n type AndroidSignerAppInfo,\n type AndroidLoginOptions,\n type AndroidLoginResult,\n} from './nip55.js';\nexport {\n BunkerSigner,\n connectWithBunkerUri,\n initiateNostrConnect,\n bytesToHex,\n hexToBytes,\n type BunkerPointer,\n type BunkerLoginOptions as BunkerConnectOptions,\n type BunkerConnectResult,\n type NostrConnectInitOptions,\n type NostrConnectInitiation,\n} from './nip46.js';\n","import { getPublicKey, nip19 } from 'nostr-tools';\nimport { BunkerSigner as ToolsBunkerSigner } from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type {\n ActiveSigner,\n BunkerLoginOptions,\n NostrConnectOptions,\n SignerConfig,\n SignerEvent,\n StoredAccount,\n UnlockOptions,\n} from './types.js';\nimport { localStorageAdapter, type StorageAdapter } from './storage.js';\nimport { LocalSigner } from './localSigner.js';\nimport { decryptNcryptsec, generateAccount } from '../nip49.js';\nimport { ExtensionSigner } from '../nip07.js';\nimport {\n BunkerSigner,\n bytesToHex,\n connectWithBunkerUri,\n hexToBytes,\n initiateNostrConnect,\n} from '../nip46.js';\nimport {\n AndroidSigner,\n loginWithAndroidSigner as connectWithAndroidSigner,\n type AndroidLoginOptions,\n type AndroidSignerAppInfo,\n type AndroidSignerPlugin,\n} from '../nip55.js';\n\nconst ACCOUNTS_KEY = 'accounts';\nconst ACTIVE_KEY = 'active-pubkey';\n\n/**\n * Multi-account Nostr signer with persistence.\n *\n * **Hydration.** The constructor reads previously-saved accounts from\n * the configured storage adapter. Every hydrated account starts\n * **locked**: present in `listAccounts()` and (if it was the active one\n * before) reachable via `getActiveAccount()`, but `getActiveSigner()`\n * returns `null` until the user re-authenticates. The matching\n * `loginWith*` method unlocks the active account.\n *\n * **Locked vs unlocked.** Use `getActiveAccount()` to render UI (\"logged\n * in as @alice\") and `getActiveSigner()` to decide whether the user can\n * actually sign. The pattern is \"show the account always, gate signing\n * on the signer.\"\n *\n * **Events.** Subscribe via `onChange()` to re-render when an account\n * is added, switched, or removed. See {@link SignerEvent}.\n */\nexport class Signer {\n readonly #storage: StorageAdapter;\n readonly #defaultAndroidPlugin: AndroidSignerPlugin | undefined;\n readonly #appMetadata: { name?: string; url?: string; image?: string };\n #accounts: StoredAccount[] = [];\n #activePubkey: string | null = null;\n #activeSigner: ActiveSigner | null = null;\n #listeners = new Set<(event: SignerEvent) => void>();\n\n constructor(config: SignerConfig = {}) {\n this.#storage = config.storage ?? localStorageAdapter(config.storageKeyPrefix);\n this.#defaultAndroidPlugin = config.androidSignerPlugin;\n this.#appMetadata = {\n name: config.appName,\n url: config.appUrl,\n image: config.appImage,\n };\n this.#hydrate();\n }\n\n #hydrate(): void {\n try {\n const raw = this.#storage.get(ACCOUNTS_KEY);\n if (raw) {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) this.#accounts = parsed as StoredAccount[];\n }\n this.#activePubkey = this.#storage.get(ACTIVE_KEY);\n } catch {\n this.#accounts = [];\n this.#activePubkey = null;\n }\n }\n\n #persistAccounts(): void {\n this.#storage.set(ACCOUNTS_KEY, JSON.stringify(this.#accounts));\n }\n\n #persistActive(): void {\n if (this.#activePubkey) this.#storage.set(ACTIVE_KEY, this.#activePubkey);\n else this.#storage.remove(ACTIVE_KEY);\n }\n\n #upsertAccount(account: StoredAccount): void {\n const idx = this.#accounts.findIndex(a => a.pubkey === account.pubkey);\n if (idx >= 0) this.#accounts[idx] = account;\n else this.#accounts.push(account);\n this.#persistAccounts();\n }\n\n #setActive(account: StoredAccount, signer: ActiveSigner): void {\n const wasDifferent = this.#activePubkey !== null && this.#activePubkey !== account.pubkey;\n this.#activePubkey = account.pubkey;\n this.#activeSigner = signer;\n this.#persistActive();\n this.#emit({ type: wasDifferent ? 'switch' : 'login', account });\n }\n\n #emit(event: SignerEvent): void {\n for (const cb of this.#listeners) {\n try {\n cb(event);\n } catch {\n // listener errors are swallowed so one bad listener can't break others\n }\n }\n }\n\n /**\n * Generate a brand-new nsec, encrypt it with `passphrase` (NIP-49),\n * persist the resulting `ncryptsec` account, and activate it. Returns\n * the new account's `npub` and `ncryptsec` — the caller must surface\n * the `ncryptsec` to the user **immediately** since it is the only way\n * back into the account on a fresh device.\n *\n * @throws if `passphrase` is empty.\n */\n async createAccount(passphrase: string): Promise<{ npub: string; ncryptsec: string }> {\n if (!passphrase) throw new Error('createAccount: passphrase required');\n const { secretKey, pubkey, npub, ncryptsec } = generateAccount(passphrase);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return { npub, ncryptsec };\n }\n\n /**\n * Decrypt an ncryptsec with the user's passphrase, persist the account\n * (overwriting any previous entry for the same pubkey), and activate it.\n *\n * @throws if either argument is empty, or if the passphrase doesn't\n * decrypt the ncryptsec.\n */\n async loginWithNcryptsec(ncryptsec: string, passphrase: string): Promise<StoredAccount> {\n if (!ncryptsec) throw new Error('loginWithNcryptsec: ncryptsec required');\n if (!passphrase) throw new Error('loginWithNcryptsec: passphrase required');\n const secretKey = decryptNcryptsec(ncryptsec, passphrase);\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return account;\n }\n\n /**\n * Connect via the NIP-07 browser extension exposed at `window.nostr`.\n * The extension prompts the user for permission on first use.\n *\n * @throws if no extension is installed or the user denies the request.\n */\n async loginWithExtension(): Promise<StoredAccount> {\n const extension = new ExtensionSigner();\n const pubkey = await extension.getPublicKey();\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'extension' };\n this.#upsertAccount(account);\n this.#setActive(account, extension);\n return account;\n }\n\n /**\n * Connect to a NIP-46 remote signer via a `bunker://` URI. Relays are\n * read from the URI itself — no hardcoded fallbacks. Pass a `pool`\n * to reuse an existing relay connection; pass `clientSecretKey` to\n * resume a previous session (the hex from `StoredAccount.nip46`).\n *\n * @throws if the URI is malformed, no relay is reachable, or the\n * remote signer rejects pairing within the implementation's timeout.\n */\n async loginWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n ): Promise<StoredAccount> {\n const result = await connectWithBunkerUri(uri, options);\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Initiate a NIP-46 `nostrconnect://` pairing. Generates a client\n * keypair, publishes a connect request to the supplied `relays`, and\n * waits for a remote signer to pair. Call `options.onUri(uri)` to\n * render the URI as a QR code; the returned promise resolves once\n * pairing completes. Cancel by aborting `options.signal`.\n *\n * @throws if `relays` is empty, the user aborts, the pairing times\n * out, or no signer responds.\n */\n async loginWithNostrConnect(options: NostrConnectOptions): Promise<StoredAccount> {\n if (options.relays.length === 0) {\n throw new Error('loginWithNostrConnect: at least one relay required');\n }\n // Merge per-call metadata over SignerConfig defaults (appName/url/image).\n // `name` is required — Amber (and likely other NIP-55 signer apps) gate\n // the consent UI on having a recognizable client identity in the URI.\n // Without one they will receive the request but never surface\n // approve/deny buttons, leaving the pairing silently stuck.\n const metadata = {\n name: options.metadata?.name ?? this.#appMetadata.name,\n url: options.metadata?.url ?? this.#appMetadata.url,\n image: options.metadata?.image ?? this.#appMetadata.image,\n };\n if (!metadata.name) {\n throw new Error(\n '@formstr/signer: loginWithNostrConnect requires an app name. Set `appName` in createSigner() or pass `metadata.name` to loginWithNostrConnect().',\n );\n }\n const init = initiateNostrConnect({\n relays: options.relays,\n metadata,\n perms: options.perms,\n pool: options.pool,\n onAuth: options.onAuth,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n onRelayMismatch: options.onRelayMismatch,\n });\n options.onUri(init.uri);\n const result = await init.complete;\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri: init.uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Enumerate NIP-55 signer apps installed on the device, via the\n * configured Android plugin (or `plugin` if supplied). Useful for\n * rendering a \"pick your signer\" list — the built-in UI does this\n * automatically when the Android tab is selected.\n *\n * Only meaningful inside a Capacitor Android shell. On web/iOS the\n * configured plugin is typically absent and this throws.\n *\n * @throws if no plugin is configured and none is passed in.\n */\n async listAndroidSignerApps(\n plugin?: AndroidSignerPlugin,\n ): Promise<AndroidSignerAppInfo[]> {\n const p = plugin ?? this.#defaultAndroidPlugin;\n if (!p) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to listAndroidSignerApps)',\n );\n }\n const { apps } = await p.getInstalledSignerApps();\n return apps;\n }\n\n /**\n * Sign in via a NIP-55 Android external signer (Amber, etc). If\n * `options.packageName` is given, that specific signer app is invoked;\n * otherwise the plugin picks a default (typically the only installed\n * signer, or an OS chooser). Pass `options.plugin` to override the\n * configured default for this call.\n *\n * @throws if no plugin is configured, the signer app cannot be\n * resolved to a package name, or the user denies the request.\n */\n async loginWithAndroidSigner(options: AndroidLoginOptions = {}): Promise<StoredAccount> {\n const plugin = options.plugin ?? this.#defaultAndroidPlugin;\n if (!plugin) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to loginWithAndroidSigner)',\n );\n }\n const result = await connectWithAndroidSigner(plugin, options.packageName);\n const account: StoredAccount = {\n npub: result.npub,\n pubkey: result.pubkey,\n method: 'android',\n androidPackageName: result.packageName,\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /** Snapshot of every persisted account, in insertion order. */\n listAccounts(): StoredAccount[] {\n return [...this.#accounts];\n }\n\n /**\n * The currently selected account, or `null` if none. Present even when\n * the account is locked (no active signer yet). Use this to render\n * \"logged in as @alice\" — pair with {@link getActiveSigner} to decide\n * whether signing is actually available.\n */\n getActiveAccount(): StoredAccount | null {\n if (!this.#activePubkey) return null;\n return this.#accounts.find(a => a.pubkey === this.#activePubkey) ?? null;\n }\n\n /**\n * The unlocked signer for the active account, or `null` if locked.\n * After a fresh page load this is `null` for every account type\n * (passphrase / extension grant / signer-app handshake all need to\n * be redone). Calling the matching `loginWith*` method unlocks it.\n */\n getActiveSigner(): ActiveSigner | null {\n return this.#activeSigner;\n }\n\n /**\n * Silently unlock the active account from persisted state — no user\n * prompt, no fresh pairing. The package already keeps everything it\n * needs to reconstruct the runtime signer on disk; this method is the\n * way to actually use that on cold start instead of re-running each\n * method's first-time login flow.\n *\n * Behavior by method:\n *\n * - `extension`: constructs an {@link ExtensionSigner}, which just\n * proxies to `window.nostr`. No setup roundtrip — individual\n * operations may still prompt depending on the extension's own\n * permission state, but unlock itself does not.\n *\n * - `nip46`: reuses the stored `clientSecretKey` to construct a\n * {@link BunkerSigner} against the stored bunker pubkey + relays\n * via `BunkerSigner.fromBunker`. Deliberately skips the `connect`\n * request — the remote signer (Amber etc.) approved this client\n * pubkey on first pairing and re-sending `connect` is what surfaces\n * a fresh approval prompt every cold start. Requires `options.pool`\n * so the BunkerSigner has somewhere to listen for responses.\n * The cached user pubkey is fed into the wrapper so a follow-up\n * `getPublicKey()` is a memory read, not a relay request.\n *\n * - `android`: constructs an {@link AndroidSigner} directly from the\n * stored `androidPackageName` + `pubkey` + `npub`. Skips the\n * `getPublicKey` content-provider roundtrip that\n * {@link loginWithAndroidSigner} performs and that — on Amber —\n * surfaces as a permission prompt every cold start.\n *\n * - `ncryptsec`: returns `null`. There is no silent path — the user's\n * passphrase isn't (and shouldn't be) persisted. The caller must\n * drive the passphrase prompt and call {@link loginWithNcryptsec}.\n *\n * Returns `null` (without emitting any event or mutating state) when\n * there is no active account, when the account is missing fields\n * required to unlock, when `nip46` is the method but no `pool` was\n * supplied, or when `android` is the method but no plugin is\n * configured. On success emits the same `login` / `switch` event the\n * corresponding `loginWith*` would.\n */\n async unlock(options: UnlockOptions = {}): Promise<ActiveSigner | null> {\n const account = this.getActiveAccount();\n if (!account) return null;\n\n switch (account.method) {\n case 'extension': {\n const signer = new ExtensionSigner();\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'nip46': {\n if (!account.nip46) return null;\n if (!options.pool) return null;\n const { remoteSignerPubkey, relays, clientSecretKey } = account.nip46;\n if (!remoteSignerPubkey || !relays.length || !clientSecretKey) {\n return null;\n }\n const tools = ToolsBunkerSigner.fromBunker(\n hexToBytes(clientSecretKey),\n { pubkey: remoteSignerPubkey, relays, secret: null },\n { pool: options.pool },\n );\n // No tools.connect() — the bunker already approved this client\n // on first pairing; re-sending `connect` is what triggers the\n // cold-start approval prompt we are trying to avoid.\n const signer = new BunkerSigner(tools, account.pubkey);\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'android': {\n if (!account.androidPackageName) return null;\n if (!this.#defaultAndroidPlugin) return null;\n const signer = new AndroidSigner(\n this.#defaultAndroidPlugin,\n account.androidPackageName,\n account.npub,\n account.pubkey,\n );\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'ncryptsec':\n // No silent path — passphrase is not (and must not be) persisted.\n return null;\n }\n }\n\n /**\n * Make `pubkey` the active account. Clears the in-memory signer —\n * the new account starts **locked** even if it was previously\n * unlocked in this session.\n *\n * @throws if `pubkey` does not match any persisted account.\n */\n async switchAccount(pubkey: string): Promise<void> {\n const account = this.#accounts.find(a => a.pubkey === pubkey);\n if (!account) throw new Error(`switchAccount: no account for pubkey ${pubkey}`);\n this.#activePubkey = pubkey;\n this.#activeSigner = null;\n this.#persistActive();\n this.#emit({ type: 'switch', account });\n }\n\n /**\n * Remove an account from storage. `pubkey` defaults to the active\n * account. If the active account is removed, the in-memory signer is\n * cleared. No-op if there is nothing to remove.\n */\n async logout(pubkey?: string): Promise<void> {\n const target = pubkey ?? this.#activePubkey;\n if (!target) return;\n this.#accounts = this.#accounts.filter(a => a.pubkey !== target);\n this.#persistAccounts();\n if (this.#activePubkey === target) {\n this.#activePubkey = null;\n this.#activeSigner = null;\n this.#persistActive();\n }\n this.#emit({ type: 'logout', pubkey: target });\n }\n\n /**\n * Subscribe to account-state changes. Returns an unsubscribe function.\n * Listener errors are swallowed so one bad listener can't break others.\n * See {@link SignerEvent} for the variants.\n */\n onChange(cb: (event: SignerEvent) => void): () => void {\n this.#listeners.add(cb);\n return () => {\n this.#listeners.delete(cb);\n };\n }\n}\n\n/** Convenience wrapper around `new Signer(config)`. */\nexport function createSigner(config: SignerConfig = {}): Signer {\n return new Signer(config);\n}\n","export interface StorageAdapter {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nconst DEFAULT_PREFIX = '@formstr/signer:';\n\nexport function localStorageAdapter(prefix: string = DEFAULT_PREFIX): StorageAdapter {\n const ls = (): Storage | null => {\n try {\n return typeof globalThis !== 'undefined' && globalThis.localStorage\n ? globalThis.localStorage\n : null;\n } catch {\n return null;\n }\n };\n return {\n get(key) {\n try {\n return ls()?.getItem(prefix + key) ?? null;\n } catch {\n return null;\n }\n },\n set(key, value) {\n try {\n ls()?.setItem(prefix + key, value);\n } catch {\n // swallow quota / privacy-mode errors\n }\n },\n remove(key) {\n try {\n ls()?.removeItem(prefix + key);\n } catch {\n // swallow\n }\n },\n };\n}\n","import {\n finalizeEvent,\n getPublicKey,\n nip04,\n nip44,\n type Event as NostrEvent,\n type EventTemplate,\n} from 'nostr-tools';\nimport type { ActiveSigner } from './types.js';\n\n/**\n * ActiveSigner backed by a raw secret key held in memory.\n * The secret key never leaves this object — there is no getter for it.\n */\nexport class LocalSigner implements ActiveSigner {\n readonly #secretKey: Uint8Array;\n\n constructor(secretKey: Uint8Array) {\n this.#secretKey = secretKey;\n }\n\n async getPublicKey(): Promise<string> {\n return getPublicKey(this.#secretKey);\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return finalizeEvent(event, this.#secretKey);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return nip04.encrypt(this.#secretKey, peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return nip04.decrypt(this.#secretKey, peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.encrypt(plaintext, key);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.decrypt(ciphertext, key);\n }\n}\n","import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';\nimport { encrypt as nip49Encrypt, decrypt as nip49Decrypt } from 'nostr-tools/nip49';\n\nexport function encryptSecretKey(secretKey: Uint8Array, passphrase: string): string {\n return nip49Encrypt(secretKey, passphrase);\n}\n\nexport function decryptNcryptsec(ncryptsec: string, passphrase: string): Uint8Array {\n return nip49Decrypt(ncryptsec, passphrase);\n}\n\nexport interface GeneratedAccount {\n secretKey: Uint8Array;\n pubkey: string;\n npub: string;\n ncryptsec: string;\n}\n\nexport function generateAccount(passphrase: string): GeneratedAccount {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const ncryptsec = nip49Encrypt(secretKey, passphrase);\n return { secretKey, pubkey, npub, ncryptsec };\n}\n","import type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\nexport interface WindowNostr {\n getPublicKey(): Promise<string>;\n signEvent(event: EventTemplate): Promise<NostrEvent>;\n getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>;\n nip04?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n nip44?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n}\n\nexport function getWindowNostr(): WindowNostr {\n const nostr = (globalThis as { nostr?: WindowNostr }).nostr;\n if (!nostr) {\n throw new Error(\n '@formstr/signer: NIP-07 extension not found (globalThis.nostr is undefined)',\n );\n }\n return nostr;\n}\n\nexport class ExtensionSigner implements ActiveSigner {\n async getPublicKey(): Promise<string> {\n return getWindowNostr().getPublicKey();\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return getWindowNostr().signEvent(event);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.encrypt(peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.decrypt(peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.encrypt(peerPubkey, plaintext);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.decrypt(peerPubkey, ciphertext);\n }\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools';\nimport {\n BunkerSigner as ToolsBunkerSigner,\n createNostrConnectURI,\n parseBunkerInput,\n type BunkerPointer,\n} from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner, RelayMismatchHandler } from './core/types.js';\n\nexport type { BunkerPointer };\n\n/**\n * Thin wrapper around nostr-tools' BunkerSigner that exposes only the\n * ActiveSigner surface. We keep this layer so callers depend on a stable\n * interface even if we ever swap the underlying implementation.\n *\n * Optionally accepts a `cachedUserPubkey`. When supplied, {@link getPublicKey}\n * returns it without a bunker roundtrip. The user's signer pubkey is fixed\n * for a given paired account, so caching it after the initial `connect` —\n * or feeding it back in from persisted storage on unlock — avoids both a\n * network hop and a potential approval prompt on every cold start. Without\n * a cached value we fall back to asking the bunker, matching the prior\n * behavior.\n */\nexport class BunkerSigner implements ActiveSigner {\n readonly #delegate: ToolsBunkerSigner;\n readonly #cachedUserPubkey: string | null;\n\n constructor(delegate: ToolsBunkerSigner, cachedUserPubkey?: string) {\n this.#delegate = delegate;\n this.#cachedUserPubkey = cachedUserPubkey ?? null;\n }\n\n getPublicKey(): Promise<string> {\n if (this.#cachedUserPubkey !== null) {\n return Promise.resolve(this.#cachedUserPubkey);\n }\n return this.#delegate.getPublicKey();\n }\n signEvent(event: EventTemplate): Promise<NostrEvent> {\n return this.#delegate.signEvent(event);\n }\n nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip04Encrypt(peerPubkey, plaintext);\n }\n nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip04Decrypt(peerPubkey, ciphertext);\n }\n nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip44Encrypt(peerPubkey, plaintext);\n }\n nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip44Decrypt(peerPubkey, ciphertext);\n }\n async close(): Promise<void> {\n return this.#delegate.close();\n }\n}\n\nexport interface BunkerLoginOptions {\n /** Custom pool, e.g. for tests. Defaults to a new SimplePool inside nostr-tools. */\n pool?: AbstractSimplePool;\n /** Called when the remote signer needs the user to visit an auth URL. */\n onAuth?: (url: string) => void;\n /** Optional client session keypair (hex bytes). Auto-generated if omitted. */\n clientSecretKey?: Uint8Array;\n /** Notified when the bunker's preferred relays differ from the URI's. */\n onRelayMismatch?: RelayMismatchHandler;\n /**\n * NIP-46 permissions to request as the 3rd `connect` param\n * (e.g. `['sign_event:1', 'nip44_encrypt']`). When omitted, the\n * connect request carries no perms — bunker UIs may then skip the\n * approval prompt entirely, leaving the user with nothing to tap.\n */\n perms?: string[];\n}\n\nasync function fetchBunkerRelays(tools: ToolsBunkerSigner): Promise<string[] | null> {\n try {\n const resp = await tools.sendRequest('get_relays', []);\n const parsed = JSON.parse(resp) as unknown;\n if (Array.isArray(parsed)) {\n return parsed.filter((r): r is string => typeof r === 'string');\n }\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.keys(parsed as Record<string, unknown>);\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction relayListsMatch(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sa = [...a].sort();\n const sb = [...b].sort();\n for (let i = 0; i < sa.length; i++) if (sa[i] !== sb[i]) return false;\n return true;\n}\n\nasync function resolveRelayChoice(\n tools: ToolsBunkerSigner,\n userRelays: string[],\n onRelayMismatch: RelayMismatchHandler | undefined,\n): Promise<string[]> {\n if (!onRelayMismatch) return userRelays;\n const bunkerRelays = await fetchBunkerRelays(tools);\n if (!bunkerRelays || relayListsMatch(userRelays, bunkerRelays)) return userRelays;\n const accept = await onRelayMismatch({ userRelays, bunkerRelays });\n return accept ? bunkerRelays : userRelays;\n}\n\nexport interface BunkerConnectResult {\n signer: BunkerSigner;\n pubkey: string;\n pointer: BunkerPointer;\n clientSecretKey: Uint8Array;\n}\n\n/**\n * Connect to a remote signer via a bunker:// URI (or a NIP-05 identifier).\n * Relays come from the URI — there is no fallback default list.\n */\nexport async function connectWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n): Promise<BunkerConnectResult> {\n const pointer = await parseBunkerInput(uri);\n if (!pointer) {\n throw new Error('@formstr/signer: invalid bunker URI');\n }\n if (!pointer.relays?.length) {\n throw new Error('@formstr/signer: bunker URI must include at least one relay');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const tools = ToolsBunkerSigner.fromBunker(clientSecretKey, pointer, {\n pool: options.pool,\n onauth: options.onAuth,\n });\n // nostr-tools' BunkerSigner.connect() hardcodes only [pubkey, secret],\n // dropping the optional 3rd `perms` arg defined by NIP-46. Without it\n // bunker UIs (Amber, etc.) have no permissions to authorize and may\n // skip the approval prompt entirely. We send the request directly.\n await tools.sendRequest('connect', [\n pointer.pubkey,\n pointer.secret ?? '',\n (options.perms ?? []).join(','),\n ]);\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n pointer.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools, pubkey),\n pubkey,\n pointer: { ...pointer, relays: resolvedRelays },\n clientSecretKey,\n };\n}\n\nexport interface NostrConnectInitOptions {\n /** User-supplied relays. The whole point of the strict-relay rule. */\n relays: string[];\n metadata?: { name?: string; url?: string; image?: string };\n /** Permissions to request (NIP-46 perms list, e.g. [\"sign_event:1\",\"nip44_encrypt\"]). */\n perms?: string[];\n pool?: AbstractSimplePool;\n onAuth?: (url: string) => void;\n /** Override the auto-generated client session keypair. */\n clientSecretKey?: Uint8Array;\n /** Override the auto-generated URI secret. */\n secret?: string;\n /** Abort the pairing wait. */\n signal?: AbortSignal;\n /** Max wait for pairing in ms (default 5 minutes). */\n timeoutMs?: number;\n /** Notified when the bunker's preferred relays differ from the user's. */\n onRelayMismatch?: RelayMismatchHandler;\n}\n\nexport interface NostrConnectInitiation {\n uri: string;\n clientPubkey: string;\n complete: Promise<BunkerConnectResult>;\n}\n\n/**\n * Generate a nostrconnect:// URI and wait for the remote signer to pair.\n * The caller displays the URI (typically as a QR code), and the returned\n * `complete` promise resolves once the signer connects back.\n */\nexport function initiateNostrConnect(options: NostrConnectInitOptions): NostrConnectInitiation {\n if (options.relays.length === 0) {\n throw new Error('@formstr/signer: at least one relay is required for nostrconnect');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const secret = options.secret ?? Math.random().toString(36).slice(2);\n const uri = createNostrConnectURI({\n clientPubkey,\n relays: options.relays,\n secret,\n perms: options.perms,\n name: options.metadata?.name,\n url: options.metadata?.url,\n image: options.metadata?.image,\n });\n const maxWaitOrAbort: number | AbortSignal =\n options.signal ?? options.timeoutMs ?? 300_000;\n // skipSwitchRelays:true — keep the caller-supplied relays authoritative,\n // never silently swap to whatever the bunker prefers.\n const complete = ToolsBunkerSigner.fromURI(\n clientSecretKey,\n uri,\n { pool: options.pool, onauth: options.onAuth, skipSwitchRelays: true },\n maxWaitOrAbort,\n ).then(async (tools) => {\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n options.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools, pubkey),\n pubkey,\n pointer: { ...tools.bp, relays: resolvedRelays },\n clientSecretKey,\n };\n });\n return { uri, clientPubkey, complete };\n}\n\nconst hexAlphabet = '0123456789abcdef';\n\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += hexAlphabet[b >> 4] + hexAlphabet[b & 0xf];\n return s;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexToBytes: odd-length hex string');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { getEventHash, nip19, type Event as NostrEvent, type EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\n/**\n * Subset of `nostr-signer-capacitor-plugin`'s exported `NostrSignerPlugin`\n * that we depend on. Signatures intentionally mirror that library\n * (positional args, per-call `packageName`) so the real plugin is\n * structurally assignable here — and any mock written against this\n * interface is a faithful stand-in. The conformance is enforced by a\n * compile-time guard in `tests/helpers/mockAndroidPlugin.ts`.\n */\nexport interface AndroidSignerAppInfo {\n name: string;\n packageName: string;\n iconUrl?: string;\n}\n\nexport interface AndroidSignerPlugin {\n setPackageName(packageName: string): Promise<void>;\n getInstalledSignerApps(): Promise<{ apps: AndroidSignerAppInfo[] }>;\n getPublicKey(\n packageName?: string,\n permissions?: string,\n ): Promise<{ npub: string; package: string }>;\n signEvent(\n packageName: string,\n eventJson: string,\n id: string,\n npub: string,\n ): Promise<{ signature: string; id: string; event: string }>;\n nip04Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip04Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n}\n\nexport interface AndroidLoginOptions {\n /** The Android package name of the external signer app (e.g. com.greenart7c3.nostrsigner). */\n packageName?: string;\n /** Override the plugin for this call. Falls back to SignerConfig.androidSignerPlugin. */\n plugin?: AndroidSignerPlugin;\n}\n\nexport class AndroidSigner implements ActiveSigner {\n readonly #plugin: AndroidSignerPlugin;\n readonly #packageName: string;\n readonly #npub: string;\n readonly #pubkey: string;\n\n constructor(\n plugin: AndroidSignerPlugin,\n packageName: string,\n npub: string,\n pubkey: string,\n ) {\n this.#plugin = plugin;\n this.#packageName = packageName;\n this.#npub = npub;\n this.#pubkey = pubkey;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#pubkey;\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n const unsigned = { ...event, pubkey: this.#pubkey };\n const eventId = getEventHash(unsigned);\n const result = await this.#plugin.signEvent(\n this.#packageName,\n JSON.stringify(unsigned),\n eventId,\n this.#npub,\n );\n return JSON.parse(result.event) as NostrEvent;\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n}\n\nexport interface AndroidLoginResult {\n signer: AndroidSigner;\n pubkey: string;\n npub: string;\n packageName: string;\n}\n\n/**\n * Render a debuggable summary of what the Android signer plugin returned\n * where an npub was expected. Includes type and length, plus a truncated\n * prefix that preserves the bech32 HRP (so callers can tell `nsec1…` /\n * `nprofile1…` / a raw hex pubkey apart) without leaking the full secret\n * material that an erroneous `nsec` response would carry.\n */\nfunction describeIdentifier(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value !== 'string') {\n return `<${typeof value}>`;\n }\n if (value.length === 0) return 'empty string';\n const prefix = value.slice(0, 12);\n const suffix = value.length > 12 ? '…' : '';\n return `\"${prefix}${suffix}\" (length=${value.length})`;\n}\n\nexport async function loginWithAndroidSigner(\n plugin: AndroidSignerPlugin,\n packageName?: string,\n): Promise<AndroidLoginResult> {\n if (packageName) {\n await plugin.setPackageName(packageName);\n }\n const { npub, package: pluginPackage } = await plugin.getPublicKey(packageName);\n const resolvedPackage = pluginPackage || packageName;\n if (!resolvedPackage) {\n throw new Error(\n '@formstr/signer: android signer did not return a package name and none was supplied',\n );\n }\n // Wrap nip19.decode so a bech32 failure (\"Data must be at least 6\n // characters long\", \"Invalid checksum\", ...) surfaces what the plugin\n // actually returned. Without this, callers see an opaque bech32 crash\n // and can't tell whether Amber sent back an empty string, a hex\n // pubkey, an nsec, or something else entirely.\n let decoded: ReturnType<typeof nip19.decode>;\n try {\n decoded = nip19.decode(npub);\n } catch (e) {\n // nostr-tools' nip19 decoder always throws Error instances on bech32\n // failures (\"Data must be at least 6 characters long\", \"Invalid\n // checksum\", \"Unknown prefix\", ...). Pass the message straight through.\n throw new Error(\n `@formstr/signer: android signer returned an undecodable identifier (got ${describeIdentifier(npub)}): ${(e as Error).message}`,\n );\n }\n if (decoded.type !== 'npub') {\n throw new Error(\n `@formstr/signer: android signer returned a non-npub identifier (type=${decoded.type}, got ${describeIdentifier(npub)})`,\n );\n }\n return {\n signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),\n pubkey: decoded.data,\n npub,\n packageName: resolvedPackage,\n };\n}\n"],"mappings":"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","names":["import_nostr_tools","import_nip46","import_nostr_tools","nip49Encrypt","nip49Decrypt","import_nostr_tools","ToolsBunkerSigner","import_nostr_tools","ToolsBunkerSigner"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as ActiveSigner, R as RelayMismatchHandler } from './signer-
|
|
2
|
-
export { a as AndroidLoginOptions, b as AndroidLoginResult, c as AndroidSigner, d as AndroidSignerAppInfo, e as AndroidSignerPlugin, B as BunkerLoginOptions, L as LoginMethod, N as NostrConnectOptions, f as RelayMismatchInfo, S as Signer, g as SignerConfig, h as SignerEvent, i as StorageAdapter, j as StoredAccount, k as createSigner, l as localStorageAdapter, m as loginWithAndroidSigner } from './signer-
|
|
1
|
+
import { A as ActiveSigner, R as RelayMismatchHandler } from './signer-BepGdtJs.cjs';
|
|
2
|
+
export { a as AndroidLoginOptions, b as AndroidLoginResult, c as AndroidSigner, d as AndroidSignerAppInfo, e as AndroidSignerPlugin, B as BunkerLoginOptions, L as LoginMethod, N as NostrConnectOptions, f as RelayMismatchInfo, S as Signer, g as SignerConfig, h as SignerEvent, i as StorageAdapter, j as StoredAccount, U as UnlockOptions, k as createSigner, l as localStorageAdapter, m as loginWithAndroidSigner } from './signer-BepGdtJs.cjs';
|
|
3
3
|
import { EventTemplate, Event } from 'nostr-tools';
|
|
4
4
|
import { BunkerSigner as BunkerSigner$1, BunkerPointer } from 'nostr-tools/nip46';
|
|
5
5
|
export { BunkerPointer } from 'nostr-tools/nip46';
|
|
@@ -60,10 +60,18 @@ declare class ExtensionSigner implements ActiveSigner {
|
|
|
60
60
|
* Thin wrapper around nostr-tools' BunkerSigner that exposes only the
|
|
61
61
|
* ActiveSigner surface. We keep this layer so callers depend on a stable
|
|
62
62
|
* interface even if we ever swap the underlying implementation.
|
|
63
|
+
*
|
|
64
|
+
* Optionally accepts a `cachedUserPubkey`. When supplied, {@link getPublicKey}
|
|
65
|
+
* returns it without a bunker roundtrip. The user's signer pubkey is fixed
|
|
66
|
+
* for a given paired account, so caching it after the initial `connect` —
|
|
67
|
+
* or feeding it back in from persisted storage on unlock — avoids both a
|
|
68
|
+
* network hop and a potential approval prompt on every cold start. Without
|
|
69
|
+
* a cached value we fall back to asking the bunker, matching the prior
|
|
70
|
+
* behavior.
|
|
63
71
|
*/
|
|
64
72
|
declare class BunkerSigner implements ActiveSigner {
|
|
65
73
|
#private;
|
|
66
|
-
constructor(delegate: BunkerSigner$1);
|
|
74
|
+
constructor(delegate: BunkerSigner$1, cachedUserPubkey?: string);
|
|
67
75
|
getPublicKey(): Promise<string>;
|
|
68
76
|
signEvent(event: EventTemplate): Promise<Event>;
|
|
69
77
|
nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as ActiveSigner, R as RelayMismatchHandler } from './signer-
|
|
2
|
-
export { a as AndroidLoginOptions, b as AndroidLoginResult, c as AndroidSigner, d as AndroidSignerAppInfo, e as AndroidSignerPlugin, B as BunkerLoginOptions, L as LoginMethod, N as NostrConnectOptions, f as RelayMismatchInfo, S as Signer, g as SignerConfig, h as SignerEvent, i as StorageAdapter, j as StoredAccount, k as createSigner, l as localStorageAdapter, m as loginWithAndroidSigner } from './signer-
|
|
1
|
+
import { A as ActiveSigner, R as RelayMismatchHandler } from './signer-BepGdtJs.js';
|
|
2
|
+
export { a as AndroidLoginOptions, b as AndroidLoginResult, c as AndroidSigner, d as AndroidSignerAppInfo, e as AndroidSignerPlugin, B as BunkerLoginOptions, L as LoginMethod, N as NostrConnectOptions, f as RelayMismatchInfo, S as Signer, g as SignerConfig, h as SignerEvent, i as StorageAdapter, j as StoredAccount, U as UnlockOptions, k as createSigner, l as localStorageAdapter, m as loginWithAndroidSigner } from './signer-BepGdtJs.js';
|
|
3
3
|
import { EventTemplate, Event } from 'nostr-tools';
|
|
4
4
|
import { BunkerSigner as BunkerSigner$1, BunkerPointer } from 'nostr-tools/nip46';
|
|
5
5
|
export { BunkerPointer } from 'nostr-tools/nip46';
|
|
@@ -60,10 +60,18 @@ declare class ExtensionSigner implements ActiveSigner {
|
|
|
60
60
|
* Thin wrapper around nostr-tools' BunkerSigner that exposes only the
|
|
61
61
|
* ActiveSigner surface. We keep this layer so callers depend on a stable
|
|
62
62
|
* interface even if we ever swap the underlying implementation.
|
|
63
|
+
*
|
|
64
|
+
* Optionally accepts a `cachedUserPubkey`. When supplied, {@link getPublicKey}
|
|
65
|
+
* returns it without a bunker roundtrip. The user's signer pubkey is fixed
|
|
66
|
+
* for a given paired account, so caching it after the initial `connect` —
|
|
67
|
+
* or feeding it back in from persisted storage on unlock — avoids both a
|
|
68
|
+
* network hop and a potential approval prompt on every cold start. Without
|
|
69
|
+
* a cached value we fall back to asking the bunker, matching the prior
|
|
70
|
+
* behavior.
|
|
63
71
|
*/
|
|
64
72
|
declare class BunkerSigner implements ActiveSigner {
|
|
65
73
|
#private;
|
|
66
|
-
constructor(delegate: BunkerSigner$1);
|
|
74
|
+
constructor(delegate: BunkerSigner$1, cachedUserPubkey?: string);
|
|
67
75
|
getPublicKey(): Promise<string>;
|
|
68
76
|
signEvent(event: EventTemplate): Promise<Event>;
|
|
69
77
|
nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/core/signer.ts
|
|
2
2
|
import { getPublicKey as getPublicKey4, nip19 as nip193 } from "nostr-tools";
|
|
3
|
+
import { BunkerSigner as ToolsBunkerSigner2 } from "nostr-tools/nip46";
|
|
3
4
|
|
|
4
5
|
// src/core/storage.ts
|
|
5
6
|
var DEFAULT_PREFIX = "@formstr/signer:";
|
|
@@ -133,10 +134,15 @@ import {
|
|
|
133
134
|
} from "nostr-tools/nip46";
|
|
134
135
|
var BunkerSigner = class {
|
|
135
136
|
#delegate;
|
|
136
|
-
|
|
137
|
+
#cachedUserPubkey;
|
|
138
|
+
constructor(delegate, cachedUserPubkey) {
|
|
137
139
|
this.#delegate = delegate;
|
|
140
|
+
this.#cachedUserPubkey = cachedUserPubkey ?? null;
|
|
138
141
|
}
|
|
139
142
|
getPublicKey() {
|
|
143
|
+
if (this.#cachedUserPubkey !== null) {
|
|
144
|
+
return Promise.resolve(this.#cachedUserPubkey);
|
|
145
|
+
}
|
|
140
146
|
return this.#delegate.getPublicKey();
|
|
141
147
|
}
|
|
142
148
|
signEvent(event) {
|
|
@@ -212,7 +218,7 @@ async function connectWithBunkerUri(uri, options = {}) {
|
|
|
212
218
|
options.onRelayMismatch
|
|
213
219
|
);
|
|
214
220
|
return {
|
|
215
|
-
signer: new BunkerSigner(tools),
|
|
221
|
+
signer: new BunkerSigner(tools, pubkey),
|
|
216
222
|
pubkey,
|
|
217
223
|
pointer: { ...pointer, relays: resolvedRelays },
|
|
218
224
|
clientSecretKey
|
|
@@ -248,7 +254,7 @@ function initiateNostrConnect(options) {
|
|
|
248
254
|
options.onRelayMismatch
|
|
249
255
|
);
|
|
250
256
|
return {
|
|
251
|
-
signer: new BunkerSigner(tools),
|
|
257
|
+
signer: new BunkerSigner(tools, pubkey),
|
|
252
258
|
pubkey,
|
|
253
259
|
pointer: { ...tools.bp, relays: resolvedRelays },
|
|
254
260
|
clientSecretKey
|
|
@@ -339,6 +345,17 @@ var AndroidSigner = class {
|
|
|
339
345
|
return result;
|
|
340
346
|
}
|
|
341
347
|
};
|
|
348
|
+
function describeIdentifier(value) {
|
|
349
|
+
if (value === null) return "null";
|
|
350
|
+
if (value === void 0) return "undefined";
|
|
351
|
+
if (typeof value !== "string") {
|
|
352
|
+
return `<${typeof value}>`;
|
|
353
|
+
}
|
|
354
|
+
if (value.length === 0) return "empty string";
|
|
355
|
+
const prefix = value.slice(0, 12);
|
|
356
|
+
const suffix = value.length > 12 ? "\u2026" : "";
|
|
357
|
+
return `"${prefix}${suffix}" (length=${value.length})`;
|
|
358
|
+
}
|
|
342
359
|
async function loginWithAndroidSigner(plugin, packageName) {
|
|
343
360
|
if (packageName) {
|
|
344
361
|
await plugin.setPackageName(packageName);
|
|
@@ -350,9 +367,18 @@ async function loginWithAndroidSigner(plugin, packageName) {
|
|
|
350
367
|
"@formstr/signer: android signer did not return a package name and none was supplied"
|
|
351
368
|
);
|
|
352
369
|
}
|
|
353
|
-
|
|
370
|
+
let decoded;
|
|
371
|
+
try {
|
|
372
|
+
decoded = nip192.decode(npub);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`@formstr/signer: android signer returned an undecodable identifier (got ${describeIdentifier(npub)}): ${e.message}`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
354
378
|
if (decoded.type !== "npub") {
|
|
355
|
-
throw new Error(
|
|
379
|
+
throw new Error(
|
|
380
|
+
`@formstr/signer: android signer returned a non-npub identifier (type=${decoded.type}, got ${describeIdentifier(npub)})`
|
|
381
|
+
);
|
|
356
382
|
}
|
|
357
383
|
return {
|
|
358
384
|
signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),
|
|
@@ -625,6 +651,88 @@ var Signer = class {
|
|
|
625
651
|
getActiveSigner() {
|
|
626
652
|
return this.#activeSigner;
|
|
627
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Silently unlock the active account from persisted state — no user
|
|
656
|
+
* prompt, no fresh pairing. The package already keeps everything it
|
|
657
|
+
* needs to reconstruct the runtime signer on disk; this method is the
|
|
658
|
+
* way to actually use that on cold start instead of re-running each
|
|
659
|
+
* method's first-time login flow.
|
|
660
|
+
*
|
|
661
|
+
* Behavior by method:
|
|
662
|
+
*
|
|
663
|
+
* - `extension`: constructs an {@link ExtensionSigner}, which just
|
|
664
|
+
* proxies to `window.nostr`. No setup roundtrip — individual
|
|
665
|
+
* operations may still prompt depending on the extension's own
|
|
666
|
+
* permission state, but unlock itself does not.
|
|
667
|
+
*
|
|
668
|
+
* - `nip46`: reuses the stored `clientSecretKey` to construct a
|
|
669
|
+
* {@link BunkerSigner} against the stored bunker pubkey + relays
|
|
670
|
+
* via `BunkerSigner.fromBunker`. Deliberately skips the `connect`
|
|
671
|
+
* request — the remote signer (Amber etc.) approved this client
|
|
672
|
+
* pubkey on first pairing and re-sending `connect` is what surfaces
|
|
673
|
+
* a fresh approval prompt every cold start. Requires `options.pool`
|
|
674
|
+
* so the BunkerSigner has somewhere to listen for responses.
|
|
675
|
+
* The cached user pubkey is fed into the wrapper so a follow-up
|
|
676
|
+
* `getPublicKey()` is a memory read, not a relay request.
|
|
677
|
+
*
|
|
678
|
+
* - `android`: constructs an {@link AndroidSigner} directly from the
|
|
679
|
+
* stored `androidPackageName` + `pubkey` + `npub`. Skips the
|
|
680
|
+
* `getPublicKey` content-provider roundtrip that
|
|
681
|
+
* {@link loginWithAndroidSigner} performs and that — on Amber —
|
|
682
|
+
* surfaces as a permission prompt every cold start.
|
|
683
|
+
*
|
|
684
|
+
* - `ncryptsec`: returns `null`. There is no silent path — the user's
|
|
685
|
+
* passphrase isn't (and shouldn't be) persisted. The caller must
|
|
686
|
+
* drive the passphrase prompt and call {@link loginWithNcryptsec}.
|
|
687
|
+
*
|
|
688
|
+
* Returns `null` (without emitting any event or mutating state) when
|
|
689
|
+
* there is no active account, when the account is missing fields
|
|
690
|
+
* required to unlock, when `nip46` is the method but no `pool` was
|
|
691
|
+
* supplied, or when `android` is the method but no plugin is
|
|
692
|
+
* configured. On success emits the same `login` / `switch` event the
|
|
693
|
+
* corresponding `loginWith*` would.
|
|
694
|
+
*/
|
|
695
|
+
async unlock(options = {}) {
|
|
696
|
+
const account = this.getActiveAccount();
|
|
697
|
+
if (!account) return null;
|
|
698
|
+
switch (account.method) {
|
|
699
|
+
case "extension": {
|
|
700
|
+
const signer = new ExtensionSigner();
|
|
701
|
+
this.#setActive(account, signer);
|
|
702
|
+
return signer;
|
|
703
|
+
}
|
|
704
|
+
case "nip46": {
|
|
705
|
+
if (!account.nip46) return null;
|
|
706
|
+
if (!options.pool) return null;
|
|
707
|
+
const { remoteSignerPubkey, relays, clientSecretKey } = account.nip46;
|
|
708
|
+
if (!remoteSignerPubkey || !relays.length || !clientSecretKey) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
const tools = ToolsBunkerSigner2.fromBunker(
|
|
712
|
+
hexToBytes(clientSecretKey),
|
|
713
|
+
{ pubkey: remoteSignerPubkey, relays, secret: null },
|
|
714
|
+
{ pool: options.pool }
|
|
715
|
+
);
|
|
716
|
+
const signer = new BunkerSigner(tools, account.pubkey);
|
|
717
|
+
this.#setActive(account, signer);
|
|
718
|
+
return signer;
|
|
719
|
+
}
|
|
720
|
+
case "android": {
|
|
721
|
+
if (!account.androidPackageName) return null;
|
|
722
|
+
if (!this.#defaultAndroidPlugin) return null;
|
|
723
|
+
const signer = new AndroidSigner(
|
|
724
|
+
this.#defaultAndroidPlugin,
|
|
725
|
+
account.androidPackageName,
|
|
726
|
+
account.npub,
|
|
727
|
+
account.pubkey
|
|
728
|
+
);
|
|
729
|
+
this.#setActive(account, signer);
|
|
730
|
+
return signer;
|
|
731
|
+
}
|
|
732
|
+
case "ncryptsec":
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
628
736
|
/**
|
|
629
737
|
* Make `pubkey` the active account. Clears the in-memory signer —
|
|
630
738
|
* the new account starts **locked** even if it was previously
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/signer.ts","../src/core/storage.ts","../src/core/localSigner.ts","../src/nip49.ts","../src/nip07.ts","../src/nip46.ts","../src/nip55.ts"],"sourcesContent":["import { getPublicKey, nip19 } from 'nostr-tools';\nimport type {\n ActiveSigner,\n BunkerLoginOptions,\n NostrConnectOptions,\n SignerConfig,\n SignerEvent,\n StoredAccount,\n} from './types.js';\nimport { localStorageAdapter, type StorageAdapter } from './storage.js';\nimport { LocalSigner } from './localSigner.js';\nimport { decryptNcryptsec, generateAccount } from '../nip49.js';\nimport { ExtensionSigner } from '../nip07.js';\nimport { bytesToHex, connectWithBunkerUri, initiateNostrConnect } from '../nip46.js';\nimport {\n loginWithAndroidSigner as connectWithAndroidSigner,\n type AndroidLoginOptions,\n type AndroidSignerAppInfo,\n type AndroidSignerPlugin,\n} from '../nip55.js';\n\nconst ACCOUNTS_KEY = 'accounts';\nconst ACTIVE_KEY = 'active-pubkey';\n\n/**\n * Multi-account Nostr signer with persistence.\n *\n * **Hydration.** The constructor reads previously-saved accounts from\n * the configured storage adapter. Every hydrated account starts\n * **locked**: present in `listAccounts()` and (if it was the active one\n * before) reachable via `getActiveAccount()`, but `getActiveSigner()`\n * returns `null` until the user re-authenticates. The matching\n * `loginWith*` method unlocks the active account.\n *\n * **Locked vs unlocked.** Use `getActiveAccount()` to render UI (\"logged\n * in as @alice\") and `getActiveSigner()` to decide whether the user can\n * actually sign. The pattern is \"show the account always, gate signing\n * on the signer.\"\n *\n * **Events.** Subscribe via `onChange()` to re-render when an account\n * is added, switched, or removed. See {@link SignerEvent}.\n */\nexport class Signer {\n readonly #storage: StorageAdapter;\n readonly #defaultAndroidPlugin: AndroidSignerPlugin | undefined;\n readonly #appMetadata: { name?: string; url?: string; image?: string };\n #accounts: StoredAccount[] = [];\n #activePubkey: string | null = null;\n #activeSigner: ActiveSigner | null = null;\n #listeners = new Set<(event: SignerEvent) => void>();\n\n constructor(config: SignerConfig = {}) {\n this.#storage = config.storage ?? localStorageAdapter(config.storageKeyPrefix);\n this.#defaultAndroidPlugin = config.androidSignerPlugin;\n this.#appMetadata = {\n name: config.appName,\n url: config.appUrl,\n image: config.appImage,\n };\n this.#hydrate();\n }\n\n #hydrate(): void {\n try {\n const raw = this.#storage.get(ACCOUNTS_KEY);\n if (raw) {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) this.#accounts = parsed as StoredAccount[];\n }\n this.#activePubkey = this.#storage.get(ACTIVE_KEY);\n } catch {\n this.#accounts = [];\n this.#activePubkey = null;\n }\n }\n\n #persistAccounts(): void {\n this.#storage.set(ACCOUNTS_KEY, JSON.stringify(this.#accounts));\n }\n\n #persistActive(): void {\n if (this.#activePubkey) this.#storage.set(ACTIVE_KEY, this.#activePubkey);\n else this.#storage.remove(ACTIVE_KEY);\n }\n\n #upsertAccount(account: StoredAccount): void {\n const idx = this.#accounts.findIndex(a => a.pubkey === account.pubkey);\n if (idx >= 0) this.#accounts[idx] = account;\n else this.#accounts.push(account);\n this.#persistAccounts();\n }\n\n #setActive(account: StoredAccount, signer: ActiveSigner): void {\n const wasDifferent = this.#activePubkey !== null && this.#activePubkey !== account.pubkey;\n this.#activePubkey = account.pubkey;\n this.#activeSigner = signer;\n this.#persistActive();\n this.#emit({ type: wasDifferent ? 'switch' : 'login', account });\n }\n\n #emit(event: SignerEvent): void {\n for (const cb of this.#listeners) {\n try {\n cb(event);\n } catch {\n // listener errors are swallowed so one bad listener can't break others\n }\n }\n }\n\n /**\n * Generate a brand-new nsec, encrypt it with `passphrase` (NIP-49),\n * persist the resulting `ncryptsec` account, and activate it. Returns\n * the new account's `npub` and `ncryptsec` — the caller must surface\n * the `ncryptsec` to the user **immediately** since it is the only way\n * back into the account on a fresh device.\n *\n * @throws if `passphrase` is empty.\n */\n async createAccount(passphrase: string): Promise<{ npub: string; ncryptsec: string }> {\n if (!passphrase) throw new Error('createAccount: passphrase required');\n const { secretKey, pubkey, npub, ncryptsec } = generateAccount(passphrase);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return { npub, ncryptsec };\n }\n\n /**\n * Decrypt an ncryptsec with the user's passphrase, persist the account\n * (overwriting any previous entry for the same pubkey), and activate it.\n *\n * @throws if either argument is empty, or if the passphrase doesn't\n * decrypt the ncryptsec.\n */\n async loginWithNcryptsec(ncryptsec: string, passphrase: string): Promise<StoredAccount> {\n if (!ncryptsec) throw new Error('loginWithNcryptsec: ncryptsec required');\n if (!passphrase) throw new Error('loginWithNcryptsec: passphrase required');\n const secretKey = decryptNcryptsec(ncryptsec, passphrase);\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return account;\n }\n\n /**\n * Connect via the NIP-07 browser extension exposed at `window.nostr`.\n * The extension prompts the user for permission on first use.\n *\n * @throws if no extension is installed or the user denies the request.\n */\n async loginWithExtension(): Promise<StoredAccount> {\n const extension = new ExtensionSigner();\n const pubkey = await extension.getPublicKey();\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'extension' };\n this.#upsertAccount(account);\n this.#setActive(account, extension);\n return account;\n }\n\n /**\n * Connect to a NIP-46 remote signer via a `bunker://` URI. Relays are\n * read from the URI itself — no hardcoded fallbacks. Pass a `pool`\n * to reuse an existing relay connection; pass `clientSecretKey` to\n * resume a previous session (the hex from `StoredAccount.nip46`).\n *\n * @throws if the URI is malformed, no relay is reachable, or the\n * remote signer rejects pairing within the implementation's timeout.\n */\n async loginWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n ): Promise<StoredAccount> {\n const result = await connectWithBunkerUri(uri, options);\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Initiate a NIP-46 `nostrconnect://` pairing. Generates a client\n * keypair, publishes a connect request to the supplied `relays`, and\n * waits for a remote signer to pair. Call `options.onUri(uri)` to\n * render the URI as a QR code; the returned promise resolves once\n * pairing completes. Cancel by aborting `options.signal`.\n *\n * @throws if `relays` is empty, the user aborts, the pairing times\n * out, or no signer responds.\n */\n async loginWithNostrConnect(options: NostrConnectOptions): Promise<StoredAccount> {\n if (options.relays.length === 0) {\n throw new Error('loginWithNostrConnect: at least one relay required');\n }\n // Merge per-call metadata over SignerConfig defaults (appName/url/image).\n // `name` is required — Amber (and likely other NIP-55 signer apps) gate\n // the consent UI on having a recognizable client identity in the URI.\n // Without one they will receive the request but never surface\n // approve/deny buttons, leaving the pairing silently stuck.\n const metadata = {\n name: options.metadata?.name ?? this.#appMetadata.name,\n url: options.metadata?.url ?? this.#appMetadata.url,\n image: options.metadata?.image ?? this.#appMetadata.image,\n };\n if (!metadata.name) {\n throw new Error(\n '@formstr/signer: loginWithNostrConnect requires an app name. Set `appName` in createSigner() or pass `metadata.name` to loginWithNostrConnect().',\n );\n }\n const init = initiateNostrConnect({\n relays: options.relays,\n metadata,\n perms: options.perms,\n pool: options.pool,\n onAuth: options.onAuth,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n onRelayMismatch: options.onRelayMismatch,\n });\n options.onUri(init.uri);\n const result = await init.complete;\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri: init.uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Enumerate NIP-55 signer apps installed on the device, via the\n * configured Android plugin (or `plugin` if supplied). Useful for\n * rendering a \"pick your signer\" list — the built-in UI does this\n * automatically when the Android tab is selected.\n *\n * Only meaningful inside a Capacitor Android shell. On web/iOS the\n * configured plugin is typically absent and this throws.\n *\n * @throws if no plugin is configured and none is passed in.\n */\n async listAndroidSignerApps(\n plugin?: AndroidSignerPlugin,\n ): Promise<AndroidSignerAppInfo[]> {\n const p = plugin ?? this.#defaultAndroidPlugin;\n if (!p) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to listAndroidSignerApps)',\n );\n }\n const { apps } = await p.getInstalledSignerApps();\n return apps;\n }\n\n /**\n * Sign in via a NIP-55 Android external signer (Amber, etc). If\n * `options.packageName` is given, that specific signer app is invoked;\n * otherwise the plugin picks a default (typically the only installed\n * signer, or an OS chooser). Pass `options.plugin` to override the\n * configured default for this call.\n *\n * @throws if no plugin is configured, the signer app cannot be\n * resolved to a package name, or the user denies the request.\n */\n async loginWithAndroidSigner(options: AndroidLoginOptions = {}): Promise<StoredAccount> {\n const plugin = options.plugin ?? this.#defaultAndroidPlugin;\n if (!plugin) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to loginWithAndroidSigner)',\n );\n }\n const result = await connectWithAndroidSigner(plugin, options.packageName);\n const account: StoredAccount = {\n npub: result.npub,\n pubkey: result.pubkey,\n method: 'android',\n androidPackageName: result.packageName,\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /** Snapshot of every persisted account, in insertion order. */\n listAccounts(): StoredAccount[] {\n return [...this.#accounts];\n }\n\n /**\n * The currently selected account, or `null` if none. Present even when\n * the account is locked (no active signer yet). Use this to render\n * \"logged in as @alice\" — pair with {@link getActiveSigner} to decide\n * whether signing is actually available.\n */\n getActiveAccount(): StoredAccount | null {\n if (!this.#activePubkey) return null;\n return this.#accounts.find(a => a.pubkey === this.#activePubkey) ?? null;\n }\n\n /**\n * The unlocked signer for the active account, or `null` if locked.\n * After a fresh page load this is `null` for every account type\n * (passphrase / extension grant / signer-app handshake all need to\n * be redone). Calling the matching `loginWith*` method unlocks it.\n */\n getActiveSigner(): ActiveSigner | null {\n return this.#activeSigner;\n }\n\n /**\n * Make `pubkey` the active account. Clears the in-memory signer —\n * the new account starts **locked** even if it was previously\n * unlocked in this session.\n *\n * @throws if `pubkey` does not match any persisted account.\n */\n async switchAccount(pubkey: string): Promise<void> {\n const account = this.#accounts.find(a => a.pubkey === pubkey);\n if (!account) throw new Error(`switchAccount: no account for pubkey ${pubkey}`);\n this.#activePubkey = pubkey;\n this.#activeSigner = null;\n this.#persistActive();\n this.#emit({ type: 'switch', account });\n }\n\n /**\n * Remove an account from storage. `pubkey` defaults to the active\n * account. If the active account is removed, the in-memory signer is\n * cleared. No-op if there is nothing to remove.\n */\n async logout(pubkey?: string): Promise<void> {\n const target = pubkey ?? this.#activePubkey;\n if (!target) return;\n this.#accounts = this.#accounts.filter(a => a.pubkey !== target);\n this.#persistAccounts();\n if (this.#activePubkey === target) {\n this.#activePubkey = null;\n this.#activeSigner = null;\n this.#persistActive();\n }\n this.#emit({ type: 'logout', pubkey: target });\n }\n\n /**\n * Subscribe to account-state changes. Returns an unsubscribe function.\n * Listener errors are swallowed so one bad listener can't break others.\n * See {@link SignerEvent} for the variants.\n */\n onChange(cb: (event: SignerEvent) => void): () => void {\n this.#listeners.add(cb);\n return () => {\n this.#listeners.delete(cb);\n };\n }\n}\n\n/** Convenience wrapper around `new Signer(config)`. */\nexport function createSigner(config: SignerConfig = {}): Signer {\n return new Signer(config);\n}\n","export interface StorageAdapter {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nconst DEFAULT_PREFIX = '@formstr/signer:';\n\nexport function localStorageAdapter(prefix: string = DEFAULT_PREFIX): StorageAdapter {\n const ls = (): Storage | null => {\n try {\n return typeof globalThis !== 'undefined' && globalThis.localStorage\n ? globalThis.localStorage\n : null;\n } catch {\n return null;\n }\n };\n return {\n get(key) {\n try {\n return ls()?.getItem(prefix + key) ?? null;\n } catch {\n return null;\n }\n },\n set(key, value) {\n try {\n ls()?.setItem(prefix + key, value);\n } catch {\n // swallow quota / privacy-mode errors\n }\n },\n remove(key) {\n try {\n ls()?.removeItem(prefix + key);\n } catch {\n // swallow\n }\n },\n };\n}\n","import {\n finalizeEvent,\n getPublicKey,\n nip04,\n nip44,\n type Event as NostrEvent,\n type EventTemplate,\n} from 'nostr-tools';\nimport type { ActiveSigner } from './types.js';\n\n/**\n * ActiveSigner backed by a raw secret key held in memory.\n * The secret key never leaves this object — there is no getter for it.\n */\nexport class LocalSigner implements ActiveSigner {\n readonly #secretKey: Uint8Array;\n\n constructor(secretKey: Uint8Array) {\n this.#secretKey = secretKey;\n }\n\n async getPublicKey(): Promise<string> {\n return getPublicKey(this.#secretKey);\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return finalizeEvent(event, this.#secretKey);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return nip04.encrypt(this.#secretKey, peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return nip04.decrypt(this.#secretKey, peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.encrypt(plaintext, key);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.decrypt(ciphertext, key);\n }\n}\n","import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';\nimport { encrypt as nip49Encrypt, decrypt as nip49Decrypt } from 'nostr-tools/nip49';\n\nexport function encryptSecretKey(secretKey: Uint8Array, passphrase: string): string {\n return nip49Encrypt(secretKey, passphrase);\n}\n\nexport function decryptNcryptsec(ncryptsec: string, passphrase: string): Uint8Array {\n return nip49Decrypt(ncryptsec, passphrase);\n}\n\nexport interface GeneratedAccount {\n secretKey: Uint8Array;\n pubkey: string;\n npub: string;\n ncryptsec: string;\n}\n\nexport function generateAccount(passphrase: string): GeneratedAccount {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const ncryptsec = nip49Encrypt(secretKey, passphrase);\n return { secretKey, pubkey, npub, ncryptsec };\n}\n","import type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\nexport interface WindowNostr {\n getPublicKey(): Promise<string>;\n signEvent(event: EventTemplate): Promise<NostrEvent>;\n getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>;\n nip04?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n nip44?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n}\n\nexport function getWindowNostr(): WindowNostr {\n const nostr = (globalThis as { nostr?: WindowNostr }).nostr;\n if (!nostr) {\n throw new Error(\n '@formstr/signer: NIP-07 extension not found (globalThis.nostr is undefined)',\n );\n }\n return nostr;\n}\n\nexport class ExtensionSigner implements ActiveSigner {\n async getPublicKey(): Promise<string> {\n return getWindowNostr().getPublicKey();\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return getWindowNostr().signEvent(event);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.encrypt(peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.decrypt(peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.encrypt(peerPubkey, plaintext);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.decrypt(peerPubkey, ciphertext);\n }\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools';\nimport {\n BunkerSigner as ToolsBunkerSigner,\n createNostrConnectURI,\n parseBunkerInput,\n type BunkerPointer,\n} from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner, RelayMismatchHandler } from './core/types.js';\n\nexport type { BunkerPointer };\n\n/**\n * Thin wrapper around nostr-tools' BunkerSigner that exposes only the\n * ActiveSigner surface. We keep this layer so callers depend on a stable\n * interface even if we ever swap the underlying implementation.\n */\nexport class BunkerSigner implements ActiveSigner {\n readonly #delegate: ToolsBunkerSigner;\n\n constructor(delegate: ToolsBunkerSigner) {\n this.#delegate = delegate;\n }\n\n getPublicKey(): Promise<string> {\n return this.#delegate.getPublicKey();\n }\n signEvent(event: EventTemplate): Promise<NostrEvent> {\n return this.#delegate.signEvent(event);\n }\n nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip04Encrypt(peerPubkey, plaintext);\n }\n nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip04Decrypt(peerPubkey, ciphertext);\n }\n nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip44Encrypt(peerPubkey, plaintext);\n }\n nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip44Decrypt(peerPubkey, ciphertext);\n }\n async close(): Promise<void> {\n return this.#delegate.close();\n }\n}\n\nexport interface BunkerLoginOptions {\n /** Custom pool, e.g. for tests. Defaults to a new SimplePool inside nostr-tools. */\n pool?: AbstractSimplePool;\n /** Called when the remote signer needs the user to visit an auth URL. */\n onAuth?: (url: string) => void;\n /** Optional client session keypair (hex bytes). Auto-generated if omitted. */\n clientSecretKey?: Uint8Array;\n /** Notified when the bunker's preferred relays differ from the URI's. */\n onRelayMismatch?: RelayMismatchHandler;\n /**\n * NIP-46 permissions to request as the 3rd `connect` param\n * (e.g. `['sign_event:1', 'nip44_encrypt']`). When omitted, the\n * connect request carries no perms — bunker UIs may then skip the\n * approval prompt entirely, leaving the user with nothing to tap.\n */\n perms?: string[];\n}\n\nasync function fetchBunkerRelays(tools: ToolsBunkerSigner): Promise<string[] | null> {\n try {\n const resp = await tools.sendRequest('get_relays', []);\n const parsed = JSON.parse(resp) as unknown;\n if (Array.isArray(parsed)) {\n return parsed.filter((r): r is string => typeof r === 'string');\n }\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.keys(parsed as Record<string, unknown>);\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction relayListsMatch(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sa = [...a].sort();\n const sb = [...b].sort();\n for (let i = 0; i < sa.length; i++) if (sa[i] !== sb[i]) return false;\n return true;\n}\n\nasync function resolveRelayChoice(\n tools: ToolsBunkerSigner,\n userRelays: string[],\n onRelayMismatch: RelayMismatchHandler | undefined,\n): Promise<string[]> {\n if (!onRelayMismatch) return userRelays;\n const bunkerRelays = await fetchBunkerRelays(tools);\n if (!bunkerRelays || relayListsMatch(userRelays, bunkerRelays)) return userRelays;\n const accept = await onRelayMismatch({ userRelays, bunkerRelays });\n return accept ? bunkerRelays : userRelays;\n}\n\nexport interface BunkerConnectResult {\n signer: BunkerSigner;\n pubkey: string;\n pointer: BunkerPointer;\n clientSecretKey: Uint8Array;\n}\n\n/**\n * Connect to a remote signer via a bunker:// URI (or a NIP-05 identifier).\n * Relays come from the URI — there is no fallback default list.\n */\nexport async function connectWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n): Promise<BunkerConnectResult> {\n const pointer = await parseBunkerInput(uri);\n if (!pointer) {\n throw new Error('@formstr/signer: invalid bunker URI');\n }\n if (!pointer.relays?.length) {\n throw new Error('@formstr/signer: bunker URI must include at least one relay');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const tools = ToolsBunkerSigner.fromBunker(clientSecretKey, pointer, {\n pool: options.pool,\n onauth: options.onAuth,\n });\n // nostr-tools' BunkerSigner.connect() hardcodes only [pubkey, secret],\n // dropping the optional 3rd `perms` arg defined by NIP-46. Without it\n // bunker UIs (Amber, etc.) have no permissions to authorize and may\n // skip the approval prompt entirely. We send the request directly.\n await tools.sendRequest('connect', [\n pointer.pubkey,\n pointer.secret ?? '',\n (options.perms ?? []).join(','),\n ]);\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n pointer.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools),\n pubkey,\n pointer: { ...pointer, relays: resolvedRelays },\n clientSecretKey,\n };\n}\n\nexport interface NostrConnectInitOptions {\n /** User-supplied relays. The whole point of the strict-relay rule. */\n relays: string[];\n metadata?: { name?: string; url?: string; image?: string };\n /** Permissions to request (NIP-46 perms list, e.g. [\"sign_event:1\",\"nip44_encrypt\"]). */\n perms?: string[];\n pool?: AbstractSimplePool;\n onAuth?: (url: string) => void;\n /** Override the auto-generated client session keypair. */\n clientSecretKey?: Uint8Array;\n /** Override the auto-generated URI secret. */\n secret?: string;\n /** Abort the pairing wait. */\n signal?: AbortSignal;\n /** Max wait for pairing in ms (default 5 minutes). */\n timeoutMs?: number;\n /** Notified when the bunker's preferred relays differ from the user's. */\n onRelayMismatch?: RelayMismatchHandler;\n}\n\nexport interface NostrConnectInitiation {\n uri: string;\n clientPubkey: string;\n complete: Promise<BunkerConnectResult>;\n}\n\n/**\n * Generate a nostrconnect:// URI and wait for the remote signer to pair.\n * The caller displays the URI (typically as a QR code), and the returned\n * `complete` promise resolves once the signer connects back.\n */\nexport function initiateNostrConnect(options: NostrConnectInitOptions): NostrConnectInitiation {\n if (options.relays.length === 0) {\n throw new Error('@formstr/signer: at least one relay is required for nostrconnect');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const secret = options.secret ?? Math.random().toString(36).slice(2);\n const uri = createNostrConnectURI({\n clientPubkey,\n relays: options.relays,\n secret,\n perms: options.perms,\n name: options.metadata?.name,\n url: options.metadata?.url,\n image: options.metadata?.image,\n });\n const maxWaitOrAbort: number | AbortSignal =\n options.signal ?? options.timeoutMs ?? 300_000;\n // skipSwitchRelays:true — keep the caller-supplied relays authoritative,\n // never silently swap to whatever the bunker prefers.\n const complete = ToolsBunkerSigner.fromURI(\n clientSecretKey,\n uri,\n { pool: options.pool, onauth: options.onAuth, skipSwitchRelays: true },\n maxWaitOrAbort,\n ).then(async (tools) => {\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n options.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools),\n pubkey,\n pointer: { ...tools.bp, relays: resolvedRelays },\n clientSecretKey,\n };\n });\n return { uri, clientPubkey, complete };\n}\n\nconst hexAlphabet = '0123456789abcdef';\n\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += hexAlphabet[b >> 4] + hexAlphabet[b & 0xf];\n return s;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexToBytes: odd-length hex string');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { getEventHash, nip19, type Event as NostrEvent, type EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\n/**\n * Subset of `nostr-signer-capacitor-plugin`'s exported `NostrSignerPlugin`\n * that we depend on. Signatures intentionally mirror that library\n * (positional args, per-call `packageName`) so the real plugin is\n * structurally assignable here — and any mock written against this\n * interface is a faithful stand-in. The conformance is enforced by a\n * compile-time guard in `tests/helpers/mockAndroidPlugin.ts`.\n */\nexport interface AndroidSignerAppInfo {\n name: string;\n packageName: string;\n iconUrl?: string;\n}\n\nexport interface AndroidSignerPlugin {\n setPackageName(packageName: string): Promise<void>;\n getInstalledSignerApps(): Promise<{ apps: AndroidSignerAppInfo[] }>;\n getPublicKey(\n packageName?: string,\n permissions?: string,\n ): Promise<{ npub: string; package: string }>;\n signEvent(\n packageName: string,\n eventJson: string,\n id: string,\n npub: string,\n ): Promise<{ signature: string; id: string; event: string }>;\n nip04Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip04Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n}\n\nexport interface AndroidLoginOptions {\n /** The Android package name of the external signer app (e.g. com.greenart7c3.nostrsigner). */\n packageName?: string;\n /** Override the plugin for this call. Falls back to SignerConfig.androidSignerPlugin. */\n plugin?: AndroidSignerPlugin;\n}\n\nexport class AndroidSigner implements ActiveSigner {\n readonly #plugin: AndroidSignerPlugin;\n readonly #packageName: string;\n readonly #npub: string;\n readonly #pubkey: string;\n\n constructor(\n plugin: AndroidSignerPlugin,\n packageName: string,\n npub: string,\n pubkey: string,\n ) {\n this.#plugin = plugin;\n this.#packageName = packageName;\n this.#npub = npub;\n this.#pubkey = pubkey;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#pubkey;\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n const unsigned = { ...event, pubkey: this.#pubkey };\n const eventId = getEventHash(unsigned);\n const result = await this.#plugin.signEvent(\n this.#packageName,\n JSON.stringify(unsigned),\n eventId,\n this.#npub,\n );\n return JSON.parse(result.event) as NostrEvent;\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n}\n\nexport interface AndroidLoginResult {\n signer: AndroidSigner;\n pubkey: string;\n npub: string;\n packageName: string;\n}\n\nexport async function loginWithAndroidSigner(\n plugin: AndroidSignerPlugin,\n packageName?: string,\n): Promise<AndroidLoginResult> {\n if (packageName) {\n await plugin.setPackageName(packageName);\n }\n const { npub, package: pluginPackage } = await plugin.getPublicKey(packageName);\n const resolvedPackage = pluginPackage || packageName;\n if (!resolvedPackage) {\n throw new Error(\n '@formstr/signer: android signer did not return a package name and none was supplied',\n );\n }\n const decoded = nip19.decode(npub);\n if (decoded.type !== 'npub') {\n throw new Error('@formstr/signer: android signer returned a non-npub identifier');\n }\n return {\n signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),\n pubkey: decoded.data,\n npub,\n packageName: resolvedPackage,\n };\n}\n"],"mappings":"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","names":["getPublicKey","nip19","getPublicKey","generateSecretKey","getPublicKey","nip19","getPublicKey","nip19"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/signer.ts","../src/core/storage.ts","../src/core/localSigner.ts","../src/nip49.ts","../src/nip07.ts","../src/nip46.ts","../src/nip55.ts"],"sourcesContent":["import { getPublicKey, nip19 } from 'nostr-tools';\nimport { BunkerSigner as ToolsBunkerSigner } from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type {\n ActiveSigner,\n BunkerLoginOptions,\n NostrConnectOptions,\n SignerConfig,\n SignerEvent,\n StoredAccount,\n UnlockOptions,\n} from './types.js';\nimport { localStorageAdapter, type StorageAdapter } from './storage.js';\nimport { LocalSigner } from './localSigner.js';\nimport { decryptNcryptsec, generateAccount } from '../nip49.js';\nimport { ExtensionSigner } from '../nip07.js';\nimport {\n BunkerSigner,\n bytesToHex,\n connectWithBunkerUri,\n hexToBytes,\n initiateNostrConnect,\n} from '../nip46.js';\nimport {\n AndroidSigner,\n loginWithAndroidSigner as connectWithAndroidSigner,\n type AndroidLoginOptions,\n type AndroidSignerAppInfo,\n type AndroidSignerPlugin,\n} from '../nip55.js';\n\nconst ACCOUNTS_KEY = 'accounts';\nconst ACTIVE_KEY = 'active-pubkey';\n\n/**\n * Multi-account Nostr signer with persistence.\n *\n * **Hydration.** The constructor reads previously-saved accounts from\n * the configured storage adapter. Every hydrated account starts\n * **locked**: present in `listAccounts()` and (if it was the active one\n * before) reachable via `getActiveAccount()`, but `getActiveSigner()`\n * returns `null` until the user re-authenticates. The matching\n * `loginWith*` method unlocks the active account.\n *\n * **Locked vs unlocked.** Use `getActiveAccount()` to render UI (\"logged\n * in as @alice\") and `getActiveSigner()` to decide whether the user can\n * actually sign. The pattern is \"show the account always, gate signing\n * on the signer.\"\n *\n * **Events.** Subscribe via `onChange()` to re-render when an account\n * is added, switched, or removed. See {@link SignerEvent}.\n */\nexport class Signer {\n readonly #storage: StorageAdapter;\n readonly #defaultAndroidPlugin: AndroidSignerPlugin | undefined;\n readonly #appMetadata: { name?: string; url?: string; image?: string };\n #accounts: StoredAccount[] = [];\n #activePubkey: string | null = null;\n #activeSigner: ActiveSigner | null = null;\n #listeners = new Set<(event: SignerEvent) => void>();\n\n constructor(config: SignerConfig = {}) {\n this.#storage = config.storage ?? localStorageAdapter(config.storageKeyPrefix);\n this.#defaultAndroidPlugin = config.androidSignerPlugin;\n this.#appMetadata = {\n name: config.appName,\n url: config.appUrl,\n image: config.appImage,\n };\n this.#hydrate();\n }\n\n #hydrate(): void {\n try {\n const raw = this.#storage.get(ACCOUNTS_KEY);\n if (raw) {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) this.#accounts = parsed as StoredAccount[];\n }\n this.#activePubkey = this.#storage.get(ACTIVE_KEY);\n } catch {\n this.#accounts = [];\n this.#activePubkey = null;\n }\n }\n\n #persistAccounts(): void {\n this.#storage.set(ACCOUNTS_KEY, JSON.stringify(this.#accounts));\n }\n\n #persistActive(): void {\n if (this.#activePubkey) this.#storage.set(ACTIVE_KEY, this.#activePubkey);\n else this.#storage.remove(ACTIVE_KEY);\n }\n\n #upsertAccount(account: StoredAccount): void {\n const idx = this.#accounts.findIndex(a => a.pubkey === account.pubkey);\n if (idx >= 0) this.#accounts[idx] = account;\n else this.#accounts.push(account);\n this.#persistAccounts();\n }\n\n #setActive(account: StoredAccount, signer: ActiveSigner): void {\n const wasDifferent = this.#activePubkey !== null && this.#activePubkey !== account.pubkey;\n this.#activePubkey = account.pubkey;\n this.#activeSigner = signer;\n this.#persistActive();\n this.#emit({ type: wasDifferent ? 'switch' : 'login', account });\n }\n\n #emit(event: SignerEvent): void {\n for (const cb of this.#listeners) {\n try {\n cb(event);\n } catch {\n // listener errors are swallowed so one bad listener can't break others\n }\n }\n }\n\n /**\n * Generate a brand-new nsec, encrypt it with `passphrase` (NIP-49),\n * persist the resulting `ncryptsec` account, and activate it. Returns\n * the new account's `npub` and `ncryptsec` — the caller must surface\n * the `ncryptsec` to the user **immediately** since it is the only way\n * back into the account on a fresh device.\n *\n * @throws if `passphrase` is empty.\n */\n async createAccount(passphrase: string): Promise<{ npub: string; ncryptsec: string }> {\n if (!passphrase) throw new Error('createAccount: passphrase required');\n const { secretKey, pubkey, npub, ncryptsec } = generateAccount(passphrase);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return { npub, ncryptsec };\n }\n\n /**\n * Decrypt an ncryptsec with the user's passphrase, persist the account\n * (overwriting any previous entry for the same pubkey), and activate it.\n *\n * @throws if either argument is empty, or if the passphrase doesn't\n * decrypt the ncryptsec.\n */\n async loginWithNcryptsec(ncryptsec: string, passphrase: string): Promise<StoredAccount> {\n if (!ncryptsec) throw new Error('loginWithNcryptsec: ncryptsec required');\n if (!passphrase) throw new Error('loginWithNcryptsec: passphrase required');\n const secretKey = decryptNcryptsec(ncryptsec, passphrase);\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'ncryptsec', ncryptsec };\n this.#upsertAccount(account);\n this.#setActive(account, new LocalSigner(secretKey));\n return account;\n }\n\n /**\n * Connect via the NIP-07 browser extension exposed at `window.nostr`.\n * The extension prompts the user for permission on first use.\n *\n * @throws if no extension is installed or the user denies the request.\n */\n async loginWithExtension(): Promise<StoredAccount> {\n const extension = new ExtensionSigner();\n const pubkey = await extension.getPublicKey();\n const npub = nip19.npubEncode(pubkey);\n const account: StoredAccount = { npub, pubkey, method: 'extension' };\n this.#upsertAccount(account);\n this.#setActive(account, extension);\n return account;\n }\n\n /**\n * Connect to a NIP-46 remote signer via a `bunker://` URI. Relays are\n * read from the URI itself — no hardcoded fallbacks. Pass a `pool`\n * to reuse an existing relay connection; pass `clientSecretKey` to\n * resume a previous session (the hex from `StoredAccount.nip46`).\n *\n * @throws if the URI is malformed, no relay is reachable, or the\n * remote signer rejects pairing within the implementation's timeout.\n */\n async loginWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n ): Promise<StoredAccount> {\n const result = await connectWithBunkerUri(uri, options);\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Initiate a NIP-46 `nostrconnect://` pairing. Generates a client\n * keypair, publishes a connect request to the supplied `relays`, and\n * waits for a remote signer to pair. Call `options.onUri(uri)` to\n * render the URI as a QR code; the returned promise resolves once\n * pairing completes. Cancel by aborting `options.signal`.\n *\n * @throws if `relays` is empty, the user aborts, the pairing times\n * out, or no signer responds.\n */\n async loginWithNostrConnect(options: NostrConnectOptions): Promise<StoredAccount> {\n if (options.relays.length === 0) {\n throw new Error('loginWithNostrConnect: at least one relay required');\n }\n // Merge per-call metadata over SignerConfig defaults (appName/url/image).\n // `name` is required — Amber (and likely other NIP-55 signer apps) gate\n // the consent UI on having a recognizable client identity in the URI.\n // Without one they will receive the request but never surface\n // approve/deny buttons, leaving the pairing silently stuck.\n const metadata = {\n name: options.metadata?.name ?? this.#appMetadata.name,\n url: options.metadata?.url ?? this.#appMetadata.url,\n image: options.metadata?.image ?? this.#appMetadata.image,\n };\n if (!metadata.name) {\n throw new Error(\n '@formstr/signer: loginWithNostrConnect requires an app name. Set `appName` in createSigner() or pass `metadata.name` to loginWithNostrConnect().',\n );\n }\n const init = initiateNostrConnect({\n relays: options.relays,\n metadata,\n perms: options.perms,\n pool: options.pool,\n onAuth: options.onAuth,\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n onRelayMismatch: options.onRelayMismatch,\n });\n options.onUri(init.uri);\n const result = await init.complete;\n const npub = nip19.npubEncode(result.pubkey);\n const account: StoredAccount = {\n npub,\n pubkey: result.pubkey,\n method: 'nip46',\n nip46: {\n uri: init.uri,\n remoteSignerPubkey: result.pointer.pubkey,\n relays: result.pointer.relays,\n clientSecretKey: bytesToHex(result.clientSecretKey),\n },\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /**\n * Enumerate NIP-55 signer apps installed on the device, via the\n * configured Android plugin (or `plugin` if supplied). Useful for\n * rendering a \"pick your signer\" list — the built-in UI does this\n * automatically when the Android tab is selected.\n *\n * Only meaningful inside a Capacitor Android shell. On web/iOS the\n * configured plugin is typically absent and this throws.\n *\n * @throws if no plugin is configured and none is passed in.\n */\n async listAndroidSignerApps(\n plugin?: AndroidSignerPlugin,\n ): Promise<AndroidSignerAppInfo[]> {\n const p = plugin ?? this.#defaultAndroidPlugin;\n if (!p) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to listAndroidSignerApps)',\n );\n }\n const { apps } = await p.getInstalledSignerApps();\n return apps;\n }\n\n /**\n * Sign in via a NIP-55 Android external signer (Amber, etc). If\n * `options.packageName` is given, that specific signer app is invoked;\n * otherwise the plugin picks a default (typically the only installed\n * signer, or an OS chooser). Pass `options.plugin` to override the\n * configured default for this call.\n *\n * @throws if no plugin is configured, the signer app cannot be\n * resolved to a package name, or the user denies the request.\n */\n async loginWithAndroidSigner(options: AndroidLoginOptions = {}): Promise<StoredAccount> {\n const plugin = options.plugin ?? this.#defaultAndroidPlugin;\n if (!plugin) {\n throw new Error(\n '@formstr/signer: no Android signer plugin configured (pass `androidSignerPlugin` to createSigner or `plugin` to loginWithAndroidSigner)',\n );\n }\n const result = await connectWithAndroidSigner(plugin, options.packageName);\n const account: StoredAccount = {\n npub: result.npub,\n pubkey: result.pubkey,\n method: 'android',\n androidPackageName: result.packageName,\n };\n this.#upsertAccount(account);\n this.#setActive(account, result.signer);\n return account;\n }\n\n /** Snapshot of every persisted account, in insertion order. */\n listAccounts(): StoredAccount[] {\n return [...this.#accounts];\n }\n\n /**\n * The currently selected account, or `null` if none. Present even when\n * the account is locked (no active signer yet). Use this to render\n * \"logged in as @alice\" — pair with {@link getActiveSigner} to decide\n * whether signing is actually available.\n */\n getActiveAccount(): StoredAccount | null {\n if (!this.#activePubkey) return null;\n return this.#accounts.find(a => a.pubkey === this.#activePubkey) ?? null;\n }\n\n /**\n * The unlocked signer for the active account, or `null` if locked.\n * After a fresh page load this is `null` for every account type\n * (passphrase / extension grant / signer-app handshake all need to\n * be redone). Calling the matching `loginWith*` method unlocks it.\n */\n getActiveSigner(): ActiveSigner | null {\n return this.#activeSigner;\n }\n\n /**\n * Silently unlock the active account from persisted state — no user\n * prompt, no fresh pairing. The package already keeps everything it\n * needs to reconstruct the runtime signer on disk; this method is the\n * way to actually use that on cold start instead of re-running each\n * method's first-time login flow.\n *\n * Behavior by method:\n *\n * - `extension`: constructs an {@link ExtensionSigner}, which just\n * proxies to `window.nostr`. No setup roundtrip — individual\n * operations may still prompt depending on the extension's own\n * permission state, but unlock itself does not.\n *\n * - `nip46`: reuses the stored `clientSecretKey` to construct a\n * {@link BunkerSigner} against the stored bunker pubkey + relays\n * via `BunkerSigner.fromBunker`. Deliberately skips the `connect`\n * request — the remote signer (Amber etc.) approved this client\n * pubkey on first pairing and re-sending `connect` is what surfaces\n * a fresh approval prompt every cold start. Requires `options.pool`\n * so the BunkerSigner has somewhere to listen for responses.\n * The cached user pubkey is fed into the wrapper so a follow-up\n * `getPublicKey()` is a memory read, not a relay request.\n *\n * - `android`: constructs an {@link AndroidSigner} directly from the\n * stored `androidPackageName` + `pubkey` + `npub`. Skips the\n * `getPublicKey` content-provider roundtrip that\n * {@link loginWithAndroidSigner} performs and that — on Amber —\n * surfaces as a permission prompt every cold start.\n *\n * - `ncryptsec`: returns `null`. There is no silent path — the user's\n * passphrase isn't (and shouldn't be) persisted. The caller must\n * drive the passphrase prompt and call {@link loginWithNcryptsec}.\n *\n * Returns `null` (without emitting any event or mutating state) when\n * there is no active account, when the account is missing fields\n * required to unlock, when `nip46` is the method but no `pool` was\n * supplied, or when `android` is the method but no plugin is\n * configured. On success emits the same `login` / `switch` event the\n * corresponding `loginWith*` would.\n */\n async unlock(options: UnlockOptions = {}): Promise<ActiveSigner | null> {\n const account = this.getActiveAccount();\n if (!account) return null;\n\n switch (account.method) {\n case 'extension': {\n const signer = new ExtensionSigner();\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'nip46': {\n if (!account.nip46) return null;\n if (!options.pool) return null;\n const { remoteSignerPubkey, relays, clientSecretKey } = account.nip46;\n if (!remoteSignerPubkey || !relays.length || !clientSecretKey) {\n return null;\n }\n const tools = ToolsBunkerSigner.fromBunker(\n hexToBytes(clientSecretKey),\n { pubkey: remoteSignerPubkey, relays, secret: null },\n { pool: options.pool },\n );\n // No tools.connect() — the bunker already approved this client\n // on first pairing; re-sending `connect` is what triggers the\n // cold-start approval prompt we are trying to avoid.\n const signer = new BunkerSigner(tools, account.pubkey);\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'android': {\n if (!account.androidPackageName) return null;\n if (!this.#defaultAndroidPlugin) return null;\n const signer = new AndroidSigner(\n this.#defaultAndroidPlugin,\n account.androidPackageName,\n account.npub,\n account.pubkey,\n );\n this.#setActive(account, signer);\n return signer;\n }\n\n case 'ncryptsec':\n // No silent path — passphrase is not (and must not be) persisted.\n return null;\n }\n }\n\n /**\n * Make `pubkey` the active account. Clears the in-memory signer —\n * the new account starts **locked** even if it was previously\n * unlocked in this session.\n *\n * @throws if `pubkey` does not match any persisted account.\n */\n async switchAccount(pubkey: string): Promise<void> {\n const account = this.#accounts.find(a => a.pubkey === pubkey);\n if (!account) throw new Error(`switchAccount: no account for pubkey ${pubkey}`);\n this.#activePubkey = pubkey;\n this.#activeSigner = null;\n this.#persistActive();\n this.#emit({ type: 'switch', account });\n }\n\n /**\n * Remove an account from storage. `pubkey` defaults to the active\n * account. If the active account is removed, the in-memory signer is\n * cleared. No-op if there is nothing to remove.\n */\n async logout(pubkey?: string): Promise<void> {\n const target = pubkey ?? this.#activePubkey;\n if (!target) return;\n this.#accounts = this.#accounts.filter(a => a.pubkey !== target);\n this.#persistAccounts();\n if (this.#activePubkey === target) {\n this.#activePubkey = null;\n this.#activeSigner = null;\n this.#persistActive();\n }\n this.#emit({ type: 'logout', pubkey: target });\n }\n\n /**\n * Subscribe to account-state changes. Returns an unsubscribe function.\n * Listener errors are swallowed so one bad listener can't break others.\n * See {@link SignerEvent} for the variants.\n */\n onChange(cb: (event: SignerEvent) => void): () => void {\n this.#listeners.add(cb);\n return () => {\n this.#listeners.delete(cb);\n };\n }\n}\n\n/** Convenience wrapper around `new Signer(config)`. */\nexport function createSigner(config: SignerConfig = {}): Signer {\n return new Signer(config);\n}\n","export interface StorageAdapter {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\nconst DEFAULT_PREFIX = '@formstr/signer:';\n\nexport function localStorageAdapter(prefix: string = DEFAULT_PREFIX): StorageAdapter {\n const ls = (): Storage | null => {\n try {\n return typeof globalThis !== 'undefined' && globalThis.localStorage\n ? globalThis.localStorage\n : null;\n } catch {\n return null;\n }\n };\n return {\n get(key) {\n try {\n return ls()?.getItem(prefix + key) ?? null;\n } catch {\n return null;\n }\n },\n set(key, value) {\n try {\n ls()?.setItem(prefix + key, value);\n } catch {\n // swallow quota / privacy-mode errors\n }\n },\n remove(key) {\n try {\n ls()?.removeItem(prefix + key);\n } catch {\n // swallow\n }\n },\n };\n}\n","import {\n finalizeEvent,\n getPublicKey,\n nip04,\n nip44,\n type Event as NostrEvent,\n type EventTemplate,\n} from 'nostr-tools';\nimport type { ActiveSigner } from './types.js';\n\n/**\n * ActiveSigner backed by a raw secret key held in memory.\n * The secret key never leaves this object — there is no getter for it.\n */\nexport class LocalSigner implements ActiveSigner {\n readonly #secretKey: Uint8Array;\n\n constructor(secretKey: Uint8Array) {\n this.#secretKey = secretKey;\n }\n\n async getPublicKey(): Promise<string> {\n return getPublicKey(this.#secretKey);\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return finalizeEvent(event, this.#secretKey);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return nip04.encrypt(this.#secretKey, peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return nip04.decrypt(this.#secretKey, peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.encrypt(plaintext, key);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const key = nip44.v2.utils.getConversationKey(this.#secretKey, peerPubkey);\n return nip44.v2.decrypt(ciphertext, key);\n }\n}\n","import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';\nimport { encrypt as nip49Encrypt, decrypt as nip49Decrypt } from 'nostr-tools/nip49';\n\nexport function encryptSecretKey(secretKey: Uint8Array, passphrase: string): string {\n return nip49Encrypt(secretKey, passphrase);\n}\n\nexport function decryptNcryptsec(ncryptsec: string, passphrase: string): Uint8Array {\n return nip49Decrypt(ncryptsec, passphrase);\n}\n\nexport interface GeneratedAccount {\n secretKey: Uint8Array;\n pubkey: string;\n npub: string;\n ncryptsec: string;\n}\n\nexport function generateAccount(passphrase: string): GeneratedAccount {\n const secretKey = generateSecretKey();\n const pubkey = getPublicKey(secretKey);\n const npub = nip19.npubEncode(pubkey);\n const ncryptsec = nip49Encrypt(secretKey, passphrase);\n return { secretKey, pubkey, npub, ncryptsec };\n}\n","import type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\nexport interface WindowNostr {\n getPublicKey(): Promise<string>;\n signEvent(event: EventTemplate): Promise<NostrEvent>;\n getRelays?(): Promise<Record<string, { read: boolean; write: boolean }>>;\n nip04?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n nip44?: {\n encrypt(peerPubkey: string, plaintext: string): Promise<string>;\n decrypt(peerPubkey: string, ciphertext: string): Promise<string>;\n };\n}\n\nexport function getWindowNostr(): WindowNostr {\n const nostr = (globalThis as { nostr?: WindowNostr }).nostr;\n if (!nostr) {\n throw new Error(\n '@formstr/signer: NIP-07 extension not found (globalThis.nostr is undefined)',\n );\n }\n return nostr;\n}\n\nexport class ExtensionSigner implements ActiveSigner {\n async getPublicKey(): Promise<string> {\n return getWindowNostr().getPublicKey();\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n return getWindowNostr().signEvent(event);\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.encrypt(peerPubkey, plaintext);\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip04) throw new Error('NIP-07 extension does not expose nip04');\n return ext.nip04.decrypt(peerPubkey, ciphertext);\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.encrypt(peerPubkey, plaintext);\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const ext = getWindowNostr();\n if (!ext.nip44) throw new Error('NIP-07 extension does not expose nip44');\n return ext.nip44.decrypt(peerPubkey, ciphertext);\n }\n}\n","import { generateSecretKey, getPublicKey } from 'nostr-tools';\nimport {\n BunkerSigner as ToolsBunkerSigner,\n createNostrConnectURI,\n parseBunkerInput,\n type BunkerPointer,\n} from 'nostr-tools/nip46';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Event as NostrEvent, EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner, RelayMismatchHandler } from './core/types.js';\n\nexport type { BunkerPointer };\n\n/**\n * Thin wrapper around nostr-tools' BunkerSigner that exposes only the\n * ActiveSigner surface. We keep this layer so callers depend on a stable\n * interface even if we ever swap the underlying implementation.\n *\n * Optionally accepts a `cachedUserPubkey`. When supplied, {@link getPublicKey}\n * returns it without a bunker roundtrip. The user's signer pubkey is fixed\n * for a given paired account, so caching it after the initial `connect` —\n * or feeding it back in from persisted storage on unlock — avoids both a\n * network hop and a potential approval prompt on every cold start. Without\n * a cached value we fall back to asking the bunker, matching the prior\n * behavior.\n */\nexport class BunkerSigner implements ActiveSigner {\n readonly #delegate: ToolsBunkerSigner;\n readonly #cachedUserPubkey: string | null;\n\n constructor(delegate: ToolsBunkerSigner, cachedUserPubkey?: string) {\n this.#delegate = delegate;\n this.#cachedUserPubkey = cachedUserPubkey ?? null;\n }\n\n getPublicKey(): Promise<string> {\n if (this.#cachedUserPubkey !== null) {\n return Promise.resolve(this.#cachedUserPubkey);\n }\n return this.#delegate.getPublicKey();\n }\n signEvent(event: EventTemplate): Promise<NostrEvent> {\n return this.#delegate.signEvent(event);\n }\n nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip04Encrypt(peerPubkey, plaintext);\n }\n nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip04Decrypt(peerPubkey, ciphertext);\n }\n nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n return this.#delegate.nip44Encrypt(peerPubkey, plaintext);\n }\n nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n return this.#delegate.nip44Decrypt(peerPubkey, ciphertext);\n }\n async close(): Promise<void> {\n return this.#delegate.close();\n }\n}\n\nexport interface BunkerLoginOptions {\n /** Custom pool, e.g. for tests. Defaults to a new SimplePool inside nostr-tools. */\n pool?: AbstractSimplePool;\n /** Called when the remote signer needs the user to visit an auth URL. */\n onAuth?: (url: string) => void;\n /** Optional client session keypair (hex bytes). Auto-generated if omitted. */\n clientSecretKey?: Uint8Array;\n /** Notified when the bunker's preferred relays differ from the URI's. */\n onRelayMismatch?: RelayMismatchHandler;\n /**\n * NIP-46 permissions to request as the 3rd `connect` param\n * (e.g. `['sign_event:1', 'nip44_encrypt']`). When omitted, the\n * connect request carries no perms — bunker UIs may then skip the\n * approval prompt entirely, leaving the user with nothing to tap.\n */\n perms?: string[];\n}\n\nasync function fetchBunkerRelays(tools: ToolsBunkerSigner): Promise<string[] | null> {\n try {\n const resp = await tools.sendRequest('get_relays', []);\n const parsed = JSON.parse(resp) as unknown;\n if (Array.isArray(parsed)) {\n return parsed.filter((r): r is string => typeof r === 'string');\n }\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.keys(parsed as Record<string, unknown>);\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction relayListsMatch(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const sa = [...a].sort();\n const sb = [...b].sort();\n for (let i = 0; i < sa.length; i++) if (sa[i] !== sb[i]) return false;\n return true;\n}\n\nasync function resolveRelayChoice(\n tools: ToolsBunkerSigner,\n userRelays: string[],\n onRelayMismatch: RelayMismatchHandler | undefined,\n): Promise<string[]> {\n if (!onRelayMismatch) return userRelays;\n const bunkerRelays = await fetchBunkerRelays(tools);\n if (!bunkerRelays || relayListsMatch(userRelays, bunkerRelays)) return userRelays;\n const accept = await onRelayMismatch({ userRelays, bunkerRelays });\n return accept ? bunkerRelays : userRelays;\n}\n\nexport interface BunkerConnectResult {\n signer: BunkerSigner;\n pubkey: string;\n pointer: BunkerPointer;\n clientSecretKey: Uint8Array;\n}\n\n/**\n * Connect to a remote signer via a bunker:// URI (or a NIP-05 identifier).\n * Relays come from the URI — there is no fallback default list.\n */\nexport async function connectWithBunkerUri(\n uri: string,\n options: BunkerLoginOptions = {},\n): Promise<BunkerConnectResult> {\n const pointer = await parseBunkerInput(uri);\n if (!pointer) {\n throw new Error('@formstr/signer: invalid bunker URI');\n }\n if (!pointer.relays?.length) {\n throw new Error('@formstr/signer: bunker URI must include at least one relay');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const tools = ToolsBunkerSigner.fromBunker(clientSecretKey, pointer, {\n pool: options.pool,\n onauth: options.onAuth,\n });\n // nostr-tools' BunkerSigner.connect() hardcodes only [pubkey, secret],\n // dropping the optional 3rd `perms` arg defined by NIP-46. Without it\n // bunker UIs (Amber, etc.) have no permissions to authorize and may\n // skip the approval prompt entirely. We send the request directly.\n await tools.sendRequest('connect', [\n pointer.pubkey,\n pointer.secret ?? '',\n (options.perms ?? []).join(','),\n ]);\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n pointer.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools, pubkey),\n pubkey,\n pointer: { ...pointer, relays: resolvedRelays },\n clientSecretKey,\n };\n}\n\nexport interface NostrConnectInitOptions {\n /** User-supplied relays. The whole point of the strict-relay rule. */\n relays: string[];\n metadata?: { name?: string; url?: string; image?: string };\n /** Permissions to request (NIP-46 perms list, e.g. [\"sign_event:1\",\"nip44_encrypt\"]). */\n perms?: string[];\n pool?: AbstractSimplePool;\n onAuth?: (url: string) => void;\n /** Override the auto-generated client session keypair. */\n clientSecretKey?: Uint8Array;\n /** Override the auto-generated URI secret. */\n secret?: string;\n /** Abort the pairing wait. */\n signal?: AbortSignal;\n /** Max wait for pairing in ms (default 5 minutes). */\n timeoutMs?: number;\n /** Notified when the bunker's preferred relays differ from the user's. */\n onRelayMismatch?: RelayMismatchHandler;\n}\n\nexport interface NostrConnectInitiation {\n uri: string;\n clientPubkey: string;\n complete: Promise<BunkerConnectResult>;\n}\n\n/**\n * Generate a nostrconnect:// URI and wait for the remote signer to pair.\n * The caller displays the URI (typically as a QR code), and the returned\n * `complete` promise resolves once the signer connects back.\n */\nexport function initiateNostrConnect(options: NostrConnectInitOptions): NostrConnectInitiation {\n if (options.relays.length === 0) {\n throw new Error('@formstr/signer: at least one relay is required for nostrconnect');\n }\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const secret = options.secret ?? Math.random().toString(36).slice(2);\n const uri = createNostrConnectURI({\n clientPubkey,\n relays: options.relays,\n secret,\n perms: options.perms,\n name: options.metadata?.name,\n url: options.metadata?.url,\n image: options.metadata?.image,\n });\n const maxWaitOrAbort: number | AbortSignal =\n options.signal ?? options.timeoutMs ?? 300_000;\n // skipSwitchRelays:true — keep the caller-supplied relays authoritative,\n // never silently swap to whatever the bunker prefers.\n const complete = ToolsBunkerSigner.fromURI(\n clientSecretKey,\n uri,\n { pool: options.pool, onauth: options.onAuth, skipSwitchRelays: true },\n maxWaitOrAbort,\n ).then(async (tools) => {\n const pubkey = await tools.getPublicKey();\n const resolvedRelays = await resolveRelayChoice(\n tools,\n options.relays,\n options.onRelayMismatch,\n );\n return {\n signer: new BunkerSigner(tools, pubkey),\n pubkey,\n pointer: { ...tools.bp, relays: resolvedRelays },\n clientSecretKey,\n };\n });\n return { uri, clientPubkey, complete };\n}\n\nconst hexAlphabet = '0123456789abcdef';\n\nexport function bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += hexAlphabet[b >> 4] + hexAlphabet[b & 0xf];\n return s;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('hexToBytes: odd-length hex string');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { getEventHash, nip19, type Event as NostrEvent, type EventTemplate } from 'nostr-tools';\nimport type { ActiveSigner } from './core/types.js';\n\n/**\n * Subset of `nostr-signer-capacitor-plugin`'s exported `NostrSignerPlugin`\n * that we depend on. Signatures intentionally mirror that library\n * (positional args, per-call `packageName`) so the real plugin is\n * structurally assignable here — and any mock written against this\n * interface is a faithful stand-in. The conformance is enforced by a\n * compile-time guard in `tests/helpers/mockAndroidPlugin.ts`.\n */\nexport interface AndroidSignerAppInfo {\n name: string;\n packageName: string;\n iconUrl?: string;\n}\n\nexport interface AndroidSignerPlugin {\n setPackageName(packageName: string): Promise<void>;\n getInstalledSignerApps(): Promise<{ apps: AndroidSignerAppInfo[] }>;\n getPublicKey(\n packageName?: string,\n permissions?: string,\n ): Promise<{ npub: string; package: string }>;\n signEvent(\n packageName: string,\n eventJson: string,\n id: string,\n npub: string,\n ): Promise<{ signature: string; id: string; event: string }>;\n nip04Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip04Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Encrypt(\n packageName: string,\n plainText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n nip44Decrypt(\n packageName: string,\n encryptedText: string,\n id: string,\n pubKey: string,\n npub: string,\n ): Promise<{ result: string; id: string }>;\n}\n\nexport interface AndroidLoginOptions {\n /** The Android package name of the external signer app (e.g. com.greenart7c3.nostrsigner). */\n packageName?: string;\n /** Override the plugin for this call. Falls back to SignerConfig.androidSignerPlugin. */\n plugin?: AndroidSignerPlugin;\n}\n\nexport class AndroidSigner implements ActiveSigner {\n readonly #plugin: AndroidSignerPlugin;\n readonly #packageName: string;\n readonly #npub: string;\n readonly #pubkey: string;\n\n constructor(\n plugin: AndroidSignerPlugin,\n packageName: string,\n npub: string,\n pubkey: string,\n ) {\n this.#plugin = plugin;\n this.#packageName = packageName;\n this.#npub = npub;\n this.#pubkey = pubkey;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#pubkey;\n }\n\n async signEvent(event: EventTemplate): Promise<NostrEvent> {\n const unsigned = { ...event, pubkey: this.#pubkey };\n const eventId = getEventHash(unsigned);\n const result = await this.#plugin.signEvent(\n this.#packageName,\n JSON.stringify(unsigned),\n eventId,\n this.#npub,\n );\n return JSON.parse(result.event) as NostrEvent;\n }\n\n async nip04Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip04Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip04Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Encrypt(peerPubkey: string, plaintext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Encrypt(\n this.#packageName,\n plaintext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n\n async nip44Decrypt(peerPubkey: string, ciphertext: string): Promise<string> {\n const { result } = await this.#plugin.nip44Decrypt(\n this.#packageName,\n ciphertext,\n '',\n peerPubkey,\n this.#npub,\n );\n return result;\n }\n}\n\nexport interface AndroidLoginResult {\n signer: AndroidSigner;\n pubkey: string;\n npub: string;\n packageName: string;\n}\n\n/**\n * Render a debuggable summary of what the Android signer plugin returned\n * where an npub was expected. Includes type and length, plus a truncated\n * prefix that preserves the bech32 HRP (so callers can tell `nsec1…` /\n * `nprofile1…` / a raw hex pubkey apart) without leaking the full secret\n * material that an erroneous `nsec` response would carry.\n */\nfunction describeIdentifier(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value !== 'string') {\n return `<${typeof value}>`;\n }\n if (value.length === 0) return 'empty string';\n const prefix = value.slice(0, 12);\n const suffix = value.length > 12 ? '…' : '';\n return `\"${prefix}${suffix}\" (length=${value.length})`;\n}\n\nexport async function loginWithAndroidSigner(\n plugin: AndroidSignerPlugin,\n packageName?: string,\n): Promise<AndroidLoginResult> {\n if (packageName) {\n await plugin.setPackageName(packageName);\n }\n const { npub, package: pluginPackage } = await plugin.getPublicKey(packageName);\n const resolvedPackage = pluginPackage || packageName;\n if (!resolvedPackage) {\n throw new Error(\n '@formstr/signer: android signer did not return a package name and none was supplied',\n );\n }\n // Wrap nip19.decode so a bech32 failure (\"Data must be at least 6\n // characters long\", \"Invalid checksum\", ...) surfaces what the plugin\n // actually returned. Without this, callers see an opaque bech32 crash\n // and can't tell whether Amber sent back an empty string, a hex\n // pubkey, an nsec, or something else entirely.\n let decoded: ReturnType<typeof nip19.decode>;\n try {\n decoded = nip19.decode(npub);\n } catch (e) {\n // nostr-tools' nip19 decoder always throws Error instances on bech32\n // failures (\"Data must be at least 6 characters long\", \"Invalid\n // checksum\", \"Unknown prefix\", ...). Pass the message straight through.\n throw new Error(\n `@formstr/signer: android signer returned an undecodable identifier (got ${describeIdentifier(npub)}): ${(e as Error).message}`,\n );\n }\n if (decoded.type !== 'npub') {\n throw new Error(\n `@formstr/signer: android signer returned a non-npub identifier (type=${decoded.type}, got ${describeIdentifier(npub)})`,\n );\n }\n return {\n signer: new AndroidSigner(plugin, resolvedPackage, npub, decoded.data),\n pubkey: decoded.data,\n npub,\n packageName: resolvedPackage,\n };\n}\n"],"mappings":"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","names":["getPublicKey","nip19","ToolsBunkerSigner","getPublicKey","generateSecretKey","getPublicKey","nip19","getPublicKey","nip19","ToolsBunkerSigner"]}
|
|
@@ -170,6 +170,18 @@ interface BunkerLoginOptions {
|
|
|
170
170
|
*/
|
|
171
171
|
perms?: string[];
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Options for {@link Signer.unlock}. Only `nip46` needs `pool`; the other
|
|
175
|
+
* methods reconstruct their signer from purely local state.
|
|
176
|
+
*/
|
|
177
|
+
interface UnlockOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Relay pool used when silently re-attaching a NIP-46 bunker session.
|
|
180
|
+
* The BunkerSigner needs somewhere to subscribe for incoming responses;
|
|
181
|
+
* without a pool the nip46 branch returns `null`.
|
|
182
|
+
*/
|
|
183
|
+
pool?: AbstractSimplePool;
|
|
184
|
+
}
|
|
173
185
|
interface NostrConnectOptions {
|
|
174
186
|
relays: string[];
|
|
175
187
|
metadata?: {
|
|
@@ -350,6 +362,48 @@ declare class Signer {
|
|
|
350
362
|
* be redone). Calling the matching `loginWith*` method unlocks it.
|
|
351
363
|
*/
|
|
352
364
|
getActiveSigner(): ActiveSigner | null;
|
|
365
|
+
/**
|
|
366
|
+
* Silently unlock the active account from persisted state — no user
|
|
367
|
+
* prompt, no fresh pairing. The package already keeps everything it
|
|
368
|
+
* needs to reconstruct the runtime signer on disk; this method is the
|
|
369
|
+
* way to actually use that on cold start instead of re-running each
|
|
370
|
+
* method's first-time login flow.
|
|
371
|
+
*
|
|
372
|
+
* Behavior by method:
|
|
373
|
+
*
|
|
374
|
+
* - `extension`: constructs an {@link ExtensionSigner}, which just
|
|
375
|
+
* proxies to `window.nostr`. No setup roundtrip — individual
|
|
376
|
+
* operations may still prompt depending on the extension's own
|
|
377
|
+
* permission state, but unlock itself does not.
|
|
378
|
+
*
|
|
379
|
+
* - `nip46`: reuses the stored `clientSecretKey` to construct a
|
|
380
|
+
* {@link BunkerSigner} against the stored bunker pubkey + relays
|
|
381
|
+
* via `BunkerSigner.fromBunker`. Deliberately skips the `connect`
|
|
382
|
+
* request — the remote signer (Amber etc.) approved this client
|
|
383
|
+
* pubkey on first pairing and re-sending `connect` is what surfaces
|
|
384
|
+
* a fresh approval prompt every cold start. Requires `options.pool`
|
|
385
|
+
* so the BunkerSigner has somewhere to listen for responses.
|
|
386
|
+
* The cached user pubkey is fed into the wrapper so a follow-up
|
|
387
|
+
* `getPublicKey()` is a memory read, not a relay request.
|
|
388
|
+
*
|
|
389
|
+
* - `android`: constructs an {@link AndroidSigner} directly from the
|
|
390
|
+
* stored `androidPackageName` + `pubkey` + `npub`. Skips the
|
|
391
|
+
* `getPublicKey` content-provider roundtrip that
|
|
392
|
+
* {@link loginWithAndroidSigner} performs and that — on Amber —
|
|
393
|
+
* surfaces as a permission prompt every cold start.
|
|
394
|
+
*
|
|
395
|
+
* - `ncryptsec`: returns `null`. There is no silent path — the user's
|
|
396
|
+
* passphrase isn't (and shouldn't be) persisted. The caller must
|
|
397
|
+
* drive the passphrase prompt and call {@link loginWithNcryptsec}.
|
|
398
|
+
*
|
|
399
|
+
* Returns `null` (without emitting any event or mutating state) when
|
|
400
|
+
* there is no active account, when the account is missing fields
|
|
401
|
+
* required to unlock, when `nip46` is the method but no `pool` was
|
|
402
|
+
* supplied, or when `android` is the method but no plugin is
|
|
403
|
+
* configured. On success emits the same `login` / `switch` event the
|
|
404
|
+
* corresponding `loginWith*` would.
|
|
405
|
+
*/
|
|
406
|
+
unlock(options?: UnlockOptions): Promise<ActiveSigner | null>;
|
|
353
407
|
/**
|
|
354
408
|
* Make `pubkey` the active account. Clears the in-memory signer —
|
|
355
409
|
* the new account starts **locked** even if it was previously
|
|
@@ -374,4 +428,4 @@ declare class Signer {
|
|
|
374
428
|
/** Convenience wrapper around `new Signer(config)`. */
|
|
375
429
|
declare function createSigner(config?: SignerConfig): Signer;
|
|
376
430
|
|
|
377
|
-
export { type ActiveSigner as A, type BunkerLoginOptions as B, type LoginMethod as L, type NostrConnectOptions as N, type RelayMismatchHandler as R, Signer as S, type AndroidLoginOptions as a, type AndroidLoginResult as b, AndroidSigner as c, type AndroidSignerAppInfo as d, type AndroidSignerPlugin as e, type RelayMismatchInfo as f, type SignerConfig as g, type SignerEvent as h, type StorageAdapter as i, type StoredAccount as j, createSigner as k, localStorageAdapter as l, loginWithAndroidSigner as m };
|
|
431
|
+
export { type ActiveSigner as A, type BunkerLoginOptions as B, type LoginMethod as L, type NostrConnectOptions as N, type RelayMismatchHandler as R, Signer as S, type UnlockOptions as U, type AndroidLoginOptions as a, type AndroidLoginResult as b, AndroidSigner as c, type AndroidSignerAppInfo as d, type AndroidSignerPlugin as e, type RelayMismatchInfo as f, type SignerConfig as g, type SignerEvent as h, type StorageAdapter as i, type StoredAccount as j, createSigner as k, localStorageAdapter as l, loginWithAndroidSigner as m };
|
|
@@ -170,6 +170,18 @@ interface BunkerLoginOptions {
|
|
|
170
170
|
*/
|
|
171
171
|
perms?: string[];
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Options for {@link Signer.unlock}. Only `nip46` needs `pool`; the other
|
|
175
|
+
* methods reconstruct their signer from purely local state.
|
|
176
|
+
*/
|
|
177
|
+
interface UnlockOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Relay pool used when silently re-attaching a NIP-46 bunker session.
|
|
180
|
+
* The BunkerSigner needs somewhere to subscribe for incoming responses;
|
|
181
|
+
* without a pool the nip46 branch returns `null`.
|
|
182
|
+
*/
|
|
183
|
+
pool?: AbstractSimplePool;
|
|
184
|
+
}
|
|
173
185
|
interface NostrConnectOptions {
|
|
174
186
|
relays: string[];
|
|
175
187
|
metadata?: {
|
|
@@ -350,6 +362,48 @@ declare class Signer {
|
|
|
350
362
|
* be redone). Calling the matching `loginWith*` method unlocks it.
|
|
351
363
|
*/
|
|
352
364
|
getActiveSigner(): ActiveSigner | null;
|
|
365
|
+
/**
|
|
366
|
+
* Silently unlock the active account from persisted state — no user
|
|
367
|
+
* prompt, no fresh pairing. The package already keeps everything it
|
|
368
|
+
* needs to reconstruct the runtime signer on disk; this method is the
|
|
369
|
+
* way to actually use that on cold start instead of re-running each
|
|
370
|
+
* method's first-time login flow.
|
|
371
|
+
*
|
|
372
|
+
* Behavior by method:
|
|
373
|
+
*
|
|
374
|
+
* - `extension`: constructs an {@link ExtensionSigner}, which just
|
|
375
|
+
* proxies to `window.nostr`. No setup roundtrip — individual
|
|
376
|
+
* operations may still prompt depending on the extension's own
|
|
377
|
+
* permission state, but unlock itself does not.
|
|
378
|
+
*
|
|
379
|
+
* - `nip46`: reuses the stored `clientSecretKey` to construct a
|
|
380
|
+
* {@link BunkerSigner} against the stored bunker pubkey + relays
|
|
381
|
+
* via `BunkerSigner.fromBunker`. Deliberately skips the `connect`
|
|
382
|
+
* request — the remote signer (Amber etc.) approved this client
|
|
383
|
+
* pubkey on first pairing and re-sending `connect` is what surfaces
|
|
384
|
+
* a fresh approval prompt every cold start. Requires `options.pool`
|
|
385
|
+
* so the BunkerSigner has somewhere to listen for responses.
|
|
386
|
+
* The cached user pubkey is fed into the wrapper so a follow-up
|
|
387
|
+
* `getPublicKey()` is a memory read, not a relay request.
|
|
388
|
+
*
|
|
389
|
+
* - `android`: constructs an {@link AndroidSigner} directly from the
|
|
390
|
+
* stored `androidPackageName` + `pubkey` + `npub`. Skips the
|
|
391
|
+
* `getPublicKey` content-provider roundtrip that
|
|
392
|
+
* {@link loginWithAndroidSigner} performs and that — on Amber —
|
|
393
|
+
* surfaces as a permission prompt every cold start.
|
|
394
|
+
*
|
|
395
|
+
* - `ncryptsec`: returns `null`. There is no silent path — the user's
|
|
396
|
+
* passphrase isn't (and shouldn't be) persisted. The caller must
|
|
397
|
+
* drive the passphrase prompt and call {@link loginWithNcryptsec}.
|
|
398
|
+
*
|
|
399
|
+
* Returns `null` (without emitting any event or mutating state) when
|
|
400
|
+
* there is no active account, when the account is missing fields
|
|
401
|
+
* required to unlock, when `nip46` is the method but no `pool` was
|
|
402
|
+
* supplied, or when `android` is the method but no plugin is
|
|
403
|
+
* configured. On success emits the same `login` / `switch` event the
|
|
404
|
+
* corresponding `loginWith*` would.
|
|
405
|
+
*/
|
|
406
|
+
unlock(options?: UnlockOptions): Promise<ActiveSigner | null>;
|
|
353
407
|
/**
|
|
354
408
|
* Make `pubkey` the active account. Clears the in-memory signer —
|
|
355
409
|
* the new account starts **locked** even if it was previously
|
|
@@ -374,4 +428,4 @@ declare class Signer {
|
|
|
374
428
|
/** Convenience wrapper around `new Signer(config)`. */
|
|
375
429
|
declare function createSigner(config?: SignerConfig): Signer;
|
|
376
430
|
|
|
377
|
-
export { type ActiveSigner as A, type BunkerLoginOptions as B, type LoginMethod as L, type NostrConnectOptions as N, type RelayMismatchHandler as R, Signer as S, type AndroidLoginOptions as a, type AndroidLoginResult as b, AndroidSigner as c, type AndroidSignerAppInfo as d, type AndroidSignerPlugin as e, type RelayMismatchInfo as f, type SignerConfig as g, type SignerEvent as h, type StorageAdapter as i, type StoredAccount as j, createSigner as k, localStorageAdapter as l, loginWithAndroidSigner as m };
|
|
431
|
+
export { type ActiveSigner as A, type BunkerLoginOptions as B, type LoginMethod as L, type NostrConnectOptions as N, type RelayMismatchHandler as R, Signer as S, type UnlockOptions as U, type AndroidLoginOptions as a, type AndroidLoginResult as b, AndroidSigner as c, type AndroidSignerAppInfo as d, type AndroidSignerPlugin as e, type RelayMismatchInfo as f, type SignerConfig as g, type SignerEvent as h, type StorageAdapter as i, type StoredAccount as j, createSigner as k, localStorageAdapter as l, loginWithAndroidSigner as m };
|
package/dist/ui/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractSimplePool } from 'nostr-tools/abstract-pool';
|
|
2
|
-
import { j as StoredAccount, R as RelayMismatchHandler, S as Signer } from '../signer-
|
|
2
|
+
import { j as StoredAccount, R as RelayMismatchHandler, S as Signer } from '../signer-BepGdtJs.cjs';
|
|
3
3
|
import 'nostr-tools';
|
|
4
4
|
|
|
5
5
|
type LoginTab = 'create' | 'ncryptsec' | 'extension' | 'bunker' | 'nostrconnect' | 'android';
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractSimplePool } from 'nostr-tools/abstract-pool';
|
|
2
|
-
import { j as StoredAccount, R as RelayMismatchHandler, S as Signer } from '../signer-
|
|
2
|
+
import { j as StoredAccount, R as RelayMismatchHandler, S as Signer } from '../signer-BepGdtJs.js';
|
|
3
3
|
import 'nostr-tools';
|
|
4
4
|
|
|
5
5
|
type LoginTab = 'create' | 'ncryptsec' | 'extension' | 'bunker' | 'nostrconnect' | 'android';
|
package/package.json
CHANGED