@formstr/signer 0.2.1 → 0.3.0
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 +81 -4
- package/dist/index.cjs +381 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +374 -12
- package/dist/index.js.map +1 -1
- package/dist/{signer-BepGdtJs.d.cts → signer-oU50RBqS.d.cts} +232 -2
- package/dist/{signer-BepGdtJs.d.ts → signer-oU50RBqS.d.ts} +232 -2
- package/dist/ui/index.cjs +31 -2
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +21 -4
- package/dist/ui/index.d.ts +21 -4
- package/dist/ui/index.js +31 -2
- package/dist/ui/index.js.map +1 -1
- package/package.json +3 -2
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ui/index.ts"],"sourcesContent":["import QRCode from 'qrcode';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Signer } from '../core/signer.js';\nimport type { RelayMismatchHandler, StoredAccount } from '../core/types.js';\n\nexport type LoginTab = 'create' | 'ncryptsec' | 'extension' | 'bunker' | 'nostrconnect' | 'android';\n\nexport interface LoginUiHandlers {\n onLogin?: (account: StoredAccount) => void;\n onCancel?: () => void;\n onError?: (error: Error) => void;\n /** Optional: passed through to NIP-46 login methods. */\n pool?: AbstractSimplePool;\n /** Optional: passed through to NIP-46 login methods. */\n onRelayMismatch?: RelayMismatchHandler;\n /** Initial tab to show. Defaults to 'create'. */\n defaultTab?: LoginTab;\n}\n\nexport interface LoginUiBinding {\n detach(): void;\n selectTab(tab: LoginTab): void;\n}\n\nconst TABS: ReadonlyArray<{ id: LoginTab; label: string }> = [\n { id: 'create', label: 'Create' },\n { id: 'ncryptsec', label: 'Existing key' },\n { id: 'extension', label: 'Extension' },\n { id: 'bunker', label: 'Bunker URI' },\n { id: 'nostrconnect', label: 'Remote (QR)' },\n { id: 'android', label: 'Android' },\n];\n\nexport function renderLoginHtml(): string {\n const tabs = TABS.map(\n (t) =>\n `<button class=\"nostr-signer__tab nostr-signer__tab--${t.id}\" type=\"button\" data-tab=\"${t.id}\">${t.label}</button>`,\n ).join('');\n\n return `<div class=\"nostr-signer__root\">\n <div class=\"nostr-signer__modal\">\n <header class=\"nostr-signer__header\">\n <h2 class=\"nostr-signer__title\">Sign in</h2>\n <button class=\"nostr-signer__close\" type=\"button\" data-action=\"cancel\" aria-label=\"Close\">×</button>\n </header>\n <nav class=\"nostr-signer__tabs\" role=\"tablist\">${tabs}</nav>\n <main class=\"nostr-signer__body\">\n <section class=\"nostr-signer__panel nostr-signer__panel--create\" data-panel=\"create\">\n <p class=\"nostr-signer__hint\">Generate a new nsec encrypted with your passphrase (NIP-49). Back up the ncryptsec on the next step — it's the only way back in.</p>\n <form class=\"nostr-signer__form\" data-form=\"create\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-create-passphrase\">Passphrase</label>\n <input class=\"nostr-signer__input nostr-signer__input--passphrase\" id=\"nostr-signer-create-passphrase\" type=\"password\" name=\"passphrase\" autocomplete=\"new-password\" required>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Create account</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--ncryptsec\" data-panel=\"ncryptsec\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"ncryptsec\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-ncryptsec\">ncryptsec1...</label>\n <textarea class=\"nostr-signer__input nostr-signer__input--ncryptsec\" id=\"nostr-signer-ncryptsec\" name=\"ncryptsec\" required></textarea>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-ncryptsec-passphrase\">Passphrase</label>\n <input class=\"nostr-signer__input nostr-signer__input--passphrase\" id=\"nostr-signer-ncryptsec-passphrase\" type=\"password\" name=\"passphrase\" autocomplete=\"current-password\" required>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Sign in</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--extension\" data-panel=\"extension\" hidden>\n <p class=\"nostr-signer__hint\">Requires a NIP-07 browser extension (nos2x, Alby, etc.).</p>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"button\" data-action=\"extension-login\">Sign in with extension</button>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--bunker\" data-panel=\"bunker\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"bunker\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-bunker-uri\">bunker:// URI</label>\n <textarea class=\"nostr-signer__input nostr-signer__input--bunker-uri\" id=\"nostr-signer-bunker-uri\" name=\"uri\" placeholder=\"bunker://<pubkey>?relay=wss://...\" required></textarea>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-bunker-perms\">Permissions (optional, comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--perms\" id=\"nostr-signer-bunker-perms\" name=\"perms\" placeholder=\"sign_event:1, nip44_encrypt\">\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Connect</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--nostrconnect\" data-panel=\"nostrconnect\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"nostrconnect\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-nc-relays\">Relays (comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--relays\" id=\"nostr-signer-nc-relays\" name=\"relays\" placeholder=\"wss://relay.example, wss://other\" required>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-nc-perms\">Permissions (optional, comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--perms\" id=\"nostr-signer-nc-perms\" name=\"perms\" placeholder=\"sign_event:1, nip44_encrypt\">\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Generate URI</button>\n </form>\n <div class=\"nostr-signer__qr\" data-region=\"nostrconnect-qr\" hidden>\n <p class=\"nostr-signer__hint\">Scan or paste this into your remote signer:</p>\n <div class=\"nostr-signer__qr-image\" data-region=\"nostrconnect-qr-image\" aria-hidden=\"true\"></div>\n <pre class=\"nostr-signer__qr-uri\" data-region=\"nostrconnect-uri\"></pre>\n <p class=\"nostr-signer__status\" data-region=\"nostrconnect-status\">Waiting for remote signer to pair...</p>\n <button class=\"nostr-signer__button nostr-signer__button--secondary\" type=\"button\" data-action=\"nostrconnect-cancel\">Cancel</button>\n </div>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--android\" data-panel=\"android\" hidden>\n <p class=\"nostr-signer__hint\">Sign in with an Android external signer app (NIP-55) such as Amber.</p>\n <p class=\"nostr-signer__status\" data-region=\"android-status\">Loading installed signers…</p>\n <ul class=\"nostr-signer__android-apps\" data-region=\"android-apps\" hidden></ul>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--created\" data-panel=\"created\" hidden>\n <p class=\"nostr-signer__hint\">Account created. Back up this encrypted secret key — without it (and your passphrase) you cannot sign in again.</p>\n <pre class=\"nostr-signer__ncryptsec-display\" data-region=\"created-ncryptsec\"></pre>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"button\" data-action=\"created-ack\">I've backed it up</button>\n </section>\n </main>\n <div class=\"nostr-signer__error\" data-region=\"error\" hidden></div>\n </div>\n</div>`;\n}\n\nexport function attachLoginListeners(\n rootEl: HTMLElement,\n signer: Signer,\n handlers: LoginUiHandlers = {},\n): LoginUiBinding {\n const detachers: Array<() => void> = [];\n let nostrConnectAbort: AbortController | null = null;\n\n const on = <K extends keyof HTMLElementEventMap>(\n el: Element,\n event: K,\n handler: (ev: HTMLElementEventMap[K]) => void,\n ): void => {\n const wrapped = handler as EventListener;\n el.addEventListener(event, wrapped);\n detachers.push(() => el.removeEventListener(event, wrapped));\n };\n\n const q = <T extends Element = Element>(selector: string): T =>\n rootEl.querySelector(selector) as T;\n\n const errorEl = q<HTMLDivElement>('[data-region=\"error\"]');\n const showError = (err: Error | string): void => {\n const msg = typeof err === 'string' ? err : err.message;\n errorEl.hidden = false;\n errorEl.textContent = msg;\n if (typeof err !== 'string') handlers.onError?.(err);\n };\n const clearError = (): void => {\n errorEl.hidden = true;\n errorEl.textContent = '';\n };\n\n // ---- android (NIP-55) — declared before selectTab so it can be invoked\n // when the user opens with defaultTab: 'android'.\n const androidStatus = q<HTMLElement>('[data-region=\"android-status\"]');\n const androidList = q<HTMLUListElement>('[data-region=\"android-apps\"]');\n let androidFetchToken = 0;\n\n const refreshAndroidApps = async (): Promise<void> => {\n const token = ++androidFetchToken;\n androidList.hidden = true;\n androidList.innerHTML = '';\n androidStatus.hidden = false;\n androidStatus.textContent = 'Loading installed signers…';\n try {\n const apps = await signer.listAndroidSignerApps();\n if (token !== androidFetchToken) return;\n if (apps.length === 0) {\n androidStatus.textContent =\n 'No NIP-55 signer apps installed. Install Amber (or similar) and try again.';\n return;\n }\n androidStatus.hidden = true;\n for (const app of apps) {\n const li = document.createElement('li');\n li.className = 'nostr-signer__android-app';\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'nostr-signer__button nostr-signer__button--primary';\n btn.dataset.packageName = app.packageName;\n btn.textContent = `${app.name} (${app.packageName})`;\n on(btn, 'click', async () => {\n clearError();\n btn.disabled = true;\n try {\n const account = await signer.loginWithAndroidSigner({\n packageName: app.packageName,\n });\n handlers.onLogin?.(account);\n } catch (err) {\n btn.disabled = false;\n showError(err as Error);\n }\n });\n li.appendChild(btn);\n androidList.appendChild(li);\n }\n androidList.hidden = false;\n } catch (err) {\n if (token !== androidFetchToken) return;\n androidStatus.textContent = (err as Error).message;\n }\n };\n\n const selectTab = (tab: LoginTab): void => {\n rootEl.querySelectorAll<HTMLElement>('[data-panel]').forEach((panel) => {\n panel.hidden = panel.dataset.panel !== tab;\n });\n rootEl.querySelectorAll<HTMLElement>('[data-tab]').forEach((btn) => {\n btn.classList.toggle('nostr-signer__tab--active', btn.dataset.tab === tab);\n });\n clearError();\n if (tab === 'android') void refreshAndroidApps();\n };\n\n selectTab(handlers.defaultTab ?? 'create');\n\n rootEl.querySelectorAll<HTMLButtonElement>('[data-tab]').forEach((btn) => {\n on(btn, 'click', () => selectTab(btn.dataset.tab as LoginTab));\n });\n\n on(q('[data-action=\"cancel\"]'), 'click', () => handlers.onCancel?.());\n\n // ---- create ----\n const createForm = q<HTMLFormElement>('[data-form=\"create\"]');\n on(createForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const passphrase = (createForm.elements.namedItem('passphrase') as HTMLInputElement).value;\n try {\n const { ncryptsec } = await signer.createAccount(passphrase);\n q<HTMLElement>('[data-region=\"created-ncryptsec\"]').textContent = ncryptsec;\n rootEl.querySelectorAll<HTMLElement>('[data-panel]').forEach((p) => {\n p.hidden = p.dataset.panel !== 'created';\n });\n } catch (err) {\n showError(err as Error);\n }\n });\n\n on(q('[data-action=\"created-ack\"]'), 'click', () => {\n const active = signer.getActiveAccount();\n if (active) handlers.onLogin?.(active);\n });\n\n // ---- ncryptsec ----\n const ncryptsecForm = q<HTMLFormElement>('[data-form=\"ncryptsec\"]');\n on(ncryptsecForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const ncryptsec = (\n ncryptsecForm.elements.namedItem('ncryptsec') as HTMLTextAreaElement\n ).value.trim();\n const passphrase = (\n ncryptsecForm.elements.namedItem('passphrase') as HTMLInputElement\n ).value;\n try {\n const account = await signer.loginWithNcryptsec(ncryptsec, passphrase);\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- extension ----\n on(q('[data-action=\"extension-login\"]'), 'click', async () => {\n clearError();\n try {\n const account = await signer.loginWithExtension();\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- bunker URI ----\n const bunkerForm = q<HTMLFormElement>('[data-form=\"bunker\"]');\n on(bunkerForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const uri = (\n bunkerForm.elements.namedItem('uri') as HTMLTextAreaElement\n ).value.trim();\n const perms = (bunkerForm.elements.namedItem('perms') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n try {\n const account = await signer.loginWithBunkerUri(uri, {\n pool: handlers.pool,\n onRelayMismatch: handlers.onRelayMismatch,\n perms: perms.length > 0 ? perms : undefined,\n });\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- nostrconnect ----\n const ncForm = q<HTMLFormElement>('[data-form=\"nostrconnect\"]');\n const ncQrRegion = q<HTMLDivElement>('[data-region=\"nostrconnect-qr\"]');\n const ncQrUri = q<HTMLElement>('[data-region=\"nostrconnect-uri\"]');\n const ncQrImage = q<HTMLDivElement>('[data-region=\"nostrconnect-qr-image\"]');\n const ncStatus = q<HTMLElement>('[data-region=\"nostrconnect-status\"]');\n let ncQrToken = 0;\n\n const resetNostrConnect = (): void => {\n ncForm.hidden = false;\n ncQrRegion.hidden = true;\n ncQrUri.textContent = '';\n ncQrImage.innerHTML = '';\n ncStatus.textContent = 'Waiting for remote signer to pair...';\n ncQrToken++;\n };\n\n const renderNostrConnectQr = async (uri: string): Promise<void> => {\n const token = ++ncQrToken;\n try {\n const svg = await QRCode.toString(uri, {\n type: 'svg',\n margin: 1,\n errorCorrectionLevel: 'M',\n });\n if (token === ncQrToken) ncQrImage.innerHTML = svg;\n } catch {\n // QR rendering is a nice-to-have — the URI text remains usable.\n }\n };\n\n on(ncForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const relays = (ncForm.elements.namedItem('relays') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (relays.length === 0) {\n showError('At least one relay is required.');\n return;\n }\n const perms = (ncForm.elements.namedItem('perms') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n ncForm.hidden = true;\n ncQrRegion.hidden = false;\n nostrConnectAbort = new AbortController();\n try {\n const account = await signer.loginWithNostrConnect({\n relays,\n perms: perms.length > 0 ? perms : undefined,\n pool: handlers.pool,\n onRelayMismatch: handlers.onRelayMismatch,\n signal: nostrConnectAbort.signal,\n onUri: (uri) => {\n ncQrUri.textContent = uri;\n void renderNostrConnectQr(uri);\n },\n });\n handlers.onLogin?.(account);\n } catch (err) {\n resetNostrConnect();\n if ((err as Error).name !== 'AbortError') showError(err as Error);\n } finally {\n nostrConnectAbort = null;\n }\n });\n\n on(q('[data-action=\"nostrconnect-cancel\"]'), 'click', () => {\n nostrConnectAbort?.abort();\n resetNostrConnect();\n });\n\n return {\n detach: () => {\n nostrConnectAbort?.abort();\n for (const d of detachers) d();\n },\n selectTab,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/ui/index.ts"],"sourcesContent":["import QRCode from 'qrcode';\nimport type { AbstractSimplePool } from 'nostr-tools/abstract-pool';\nimport type { Signer } from '../core/signer.js';\nimport type { RelayMismatchHandler, StoredAccount } from '../core/types.js';\n\nexport type LoginTab =\n | 'create'\n | 'ncryptsec'\n | 'extension'\n | 'bunker'\n | 'nostrconnect'\n | 'nip55web'\n | 'android';\n\nexport interface LoginUiHandlers {\n onLogin?: (account: StoredAccount) => void;\n onCancel?: () => void;\n onError?: (error: Error) => void;\n /** Optional: passed through to NIP-46 login methods. */\n pool?: AbstractSimplePool;\n /** Optional: passed through to NIP-46 login methods. */\n onRelayMismatch?: RelayMismatchHandler;\n /** Initial tab to show. Defaults to 'create'. */\n defaultTab?: LoginTab;\n /**\n * Optional diagnostic stream for the browser NIP-55 (intent/clipboard)\n * flow. Wire it to an on-screen log when testing on a phone — the flow\n * navigates away, so its behaviour is otherwise hard to observe.\n */\n onNip55WebDebug?: (message: string) => void;\n}\n\nexport interface LoginUiBinding {\n detach(): void;\n selectTab(tab: LoginTab): void;\n}\n\nconst TABS: ReadonlyArray<{ id: LoginTab; label: string }> = [\n { id: 'create', label: 'Create' },\n { id: 'ncryptsec', label: 'Existing key' },\n { id: 'extension', label: 'Extension' },\n { id: 'bunker', label: 'Bunker URI' },\n { id: 'nostrconnect', label: 'Remote (QR)' },\n { id: 'nip55web', label: 'Signer app' },\n { id: 'android', label: 'Android' },\n];\n\nexport interface RenderLoginOptions {\n /**\n * Include the \"Signer app\" tab (browser NIP-55). Defaults to `true`.\n *\n * Pass `signer.supportsNip55Web()` to hide it where it cannot work —\n * notably inside a Capacitor native shell, where\n * `loginWithAndroidSigner` is the right path. Omit it in a native build\n * and the tab would only ever error.\n */\n includeNip55Web?: boolean;\n}\n\nexport function renderLoginHtml(options: RenderLoginOptions = {}): string {\n const includeNip55Web = options.includeNip55Web ?? true;\n const tabs = TABS.filter((t) => t.id !== 'nip55web' || includeNip55Web)\n .map(\n (t) =>\n `<button class=\"nostr-signer__tab nostr-signer__tab--${t.id}\" type=\"button\" data-tab=\"${t.id}\">${t.label}</button>`,\n )\n .join('');\n\n return `<div class=\"nostr-signer__root\">\n <div class=\"nostr-signer__modal\">\n <header class=\"nostr-signer__header\">\n <h2 class=\"nostr-signer__title\">Sign in</h2>\n <button class=\"nostr-signer__close\" type=\"button\" data-action=\"cancel\" aria-label=\"Close\">×</button>\n </header>\n <nav class=\"nostr-signer__tabs\" role=\"tablist\">${tabs}</nav>\n <main class=\"nostr-signer__body\">\n <section class=\"nostr-signer__panel nostr-signer__panel--create\" data-panel=\"create\">\n <p class=\"nostr-signer__hint\">Generate a new nsec encrypted with your passphrase (NIP-49). Back up the ncryptsec on the next step — it's the only way back in.</p>\n <form class=\"nostr-signer__form\" data-form=\"create\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-create-passphrase\">Passphrase</label>\n <input class=\"nostr-signer__input nostr-signer__input--passphrase\" id=\"nostr-signer-create-passphrase\" type=\"password\" name=\"passphrase\" autocomplete=\"new-password\" required>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Create account</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--ncryptsec\" data-panel=\"ncryptsec\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"ncryptsec\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-ncryptsec\">ncryptsec1...</label>\n <textarea class=\"nostr-signer__input nostr-signer__input--ncryptsec\" id=\"nostr-signer-ncryptsec\" name=\"ncryptsec\" required></textarea>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-ncryptsec-passphrase\">Passphrase</label>\n <input class=\"nostr-signer__input nostr-signer__input--passphrase\" id=\"nostr-signer-ncryptsec-passphrase\" type=\"password\" name=\"passphrase\" autocomplete=\"current-password\" required>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Sign in</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--extension\" data-panel=\"extension\" hidden>\n <p class=\"nostr-signer__hint\">Requires a NIP-07 browser extension (nos2x, Alby, etc.).</p>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"button\" data-action=\"extension-login\">Sign in with extension</button>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--bunker\" data-panel=\"bunker\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"bunker\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-bunker-uri\">bunker:// URI</label>\n <textarea class=\"nostr-signer__input nostr-signer__input--bunker-uri\" id=\"nostr-signer-bunker-uri\" name=\"uri\" placeholder=\"bunker://<pubkey>?relay=wss://...\" required></textarea>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-bunker-perms\">Permissions (optional, comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--perms\" id=\"nostr-signer-bunker-perms\" name=\"perms\" placeholder=\"sign_event:1, nip44_encrypt\">\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Connect</button>\n </form>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--nostrconnect\" data-panel=\"nostrconnect\" hidden>\n <form class=\"nostr-signer__form\" data-form=\"nostrconnect\">\n <label class=\"nostr-signer__label\" for=\"nostr-signer-nc-relays\">Relays (comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--relays\" id=\"nostr-signer-nc-relays\" name=\"relays\" placeholder=\"wss://relay.example, wss://other\" required>\n <label class=\"nostr-signer__label\" for=\"nostr-signer-nc-perms\">Permissions (optional, comma-separated)</label>\n <input class=\"nostr-signer__input nostr-signer__input--perms\" id=\"nostr-signer-nc-perms\" name=\"perms\" placeholder=\"sign_event:1, nip44_encrypt\">\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"submit\">Generate URI</button>\n </form>\n <div class=\"nostr-signer__qr\" data-region=\"nostrconnect-qr\" hidden>\n <p class=\"nostr-signer__hint\">Scan or paste this into your remote signer:</p>\n <div class=\"nostr-signer__qr-image\" data-region=\"nostrconnect-qr-image\" aria-hidden=\"true\"></div>\n <pre class=\"nostr-signer__qr-uri\" data-region=\"nostrconnect-uri\"></pre>\n <p class=\"nostr-signer__status\" data-region=\"nostrconnect-status\">Waiting for remote signer to pair...</p>\n <button class=\"nostr-signer__button nostr-signer__button--secondary\" type=\"button\" data-action=\"nostrconnect-cancel\">Cancel</button>\n </div>\n </section>\n ${includeNip55Web ? `<section class=\"nostr-signer__panel nostr-signer__panel--nip55web\" data-panel=\"nip55web\" hidden>\n <p class=\"nostr-signer__hint\">Sign in with a signer app installed on this Android device, using the browser (NIP-55). You’ll be asked to approve each signature in the app.</p>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"button\" data-action=\"nip55web-login\">Open signer app</button>\n </section>` : ''}\n <section class=\"nostr-signer__panel nostr-signer__panel--android\" data-panel=\"android\" hidden>\n <p class=\"nostr-signer__hint\">Sign in with an Android external signer app (NIP-55) such as Amber.</p>\n <p class=\"nostr-signer__status\" data-region=\"android-status\">Loading installed signers…</p>\n <ul class=\"nostr-signer__android-apps\" data-region=\"android-apps\" hidden></ul>\n </section>\n <section class=\"nostr-signer__panel nostr-signer__panel--created\" data-panel=\"created\" hidden>\n <p class=\"nostr-signer__hint\">Account created. Back up this encrypted secret key — without it (and your passphrase) you cannot sign in again.</p>\n <pre class=\"nostr-signer__ncryptsec-display\" data-region=\"created-ncryptsec\"></pre>\n <button class=\"nostr-signer__button nostr-signer__button--primary\" type=\"button\" data-action=\"created-ack\">I've backed it up</button>\n </section>\n </main>\n <div class=\"nostr-signer__error\" data-region=\"error\" hidden></div>\n </div>\n</div>`;\n}\n\nexport function attachLoginListeners(\n rootEl: HTMLElement,\n signer: Signer,\n handlers: LoginUiHandlers = {},\n): LoginUiBinding {\n const detachers: Array<() => void> = [];\n let nostrConnectAbort: AbortController | null = null;\n let nip55WebAbort: AbortController | null = null;\n\n const on = <K extends keyof HTMLElementEventMap>(\n el: Element,\n event: K,\n handler: (ev: HTMLElementEventMap[K]) => void,\n ): void => {\n const wrapped = handler as EventListener;\n el.addEventListener(event, wrapped);\n detachers.push(() => el.removeEventListener(event, wrapped));\n };\n\n const q = <T extends Element = Element>(selector: string): T =>\n rootEl.querySelector(selector) as T;\n\n const errorEl = q<HTMLDivElement>('[data-region=\"error\"]');\n const showError = (err: Error | string): void => {\n const msg = typeof err === 'string' ? err : err.message;\n errorEl.hidden = false;\n errorEl.textContent = msg;\n if (typeof err !== 'string') handlers.onError?.(err);\n };\n const clearError = (): void => {\n errorEl.hidden = true;\n errorEl.textContent = '';\n };\n\n // ---- android (NIP-55) — declared before selectTab so it can be invoked\n // when the user opens with defaultTab: 'android'.\n const androidStatus = q<HTMLElement>('[data-region=\"android-status\"]');\n const androidList = q<HTMLUListElement>('[data-region=\"android-apps\"]');\n let androidFetchToken = 0;\n\n const refreshAndroidApps = async (): Promise<void> => {\n const token = ++androidFetchToken;\n androidList.hidden = true;\n androidList.innerHTML = '';\n androidStatus.hidden = false;\n androidStatus.textContent = 'Loading installed signers…';\n try {\n const apps = await signer.listAndroidSignerApps();\n if (token !== androidFetchToken) return;\n if (apps.length === 0) {\n androidStatus.textContent =\n 'No NIP-55 signer apps installed. Install Amber (or similar) and try again.';\n return;\n }\n androidStatus.hidden = true;\n for (const app of apps) {\n const li = document.createElement('li');\n li.className = 'nostr-signer__android-app';\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'nostr-signer__button nostr-signer__button--primary';\n btn.dataset.packageName = app.packageName;\n btn.textContent = `${app.name} (${app.packageName})`;\n on(btn, 'click', async () => {\n clearError();\n btn.disabled = true;\n try {\n const account = await signer.loginWithAndroidSigner({\n packageName: app.packageName,\n });\n handlers.onLogin?.(account);\n } catch (err) {\n btn.disabled = false;\n showError(err as Error);\n }\n });\n li.appendChild(btn);\n androidList.appendChild(li);\n }\n androidList.hidden = false;\n } catch (err) {\n if (token !== androidFetchToken) return;\n androidStatus.textContent = (err as Error).message;\n }\n };\n\n const selectTab = (tab: LoginTab): void => {\n rootEl.querySelectorAll<HTMLElement>('[data-panel]').forEach((panel) => {\n panel.hidden = panel.dataset.panel !== tab;\n });\n rootEl.querySelectorAll<HTMLElement>('[data-tab]').forEach((btn) => {\n btn.classList.toggle('nostr-signer__tab--active', btn.dataset.tab === tab);\n });\n clearError();\n if (tab === 'android') void refreshAndroidApps();\n };\n\n selectTab(handlers.defaultTab ?? 'create');\n\n rootEl.querySelectorAll<HTMLButtonElement>('[data-tab]').forEach((btn) => {\n on(btn, 'click', () => selectTab(btn.dataset.tab as LoginTab));\n });\n\n on(q('[data-action=\"cancel\"]'), 'click', () => handlers.onCancel?.());\n\n // ---- create ----\n const createForm = q<HTMLFormElement>('[data-form=\"create\"]');\n on(createForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const passphrase = (createForm.elements.namedItem('passphrase') as HTMLInputElement).value;\n try {\n const { ncryptsec } = await signer.createAccount(passphrase);\n q<HTMLElement>('[data-region=\"created-ncryptsec\"]').textContent = ncryptsec;\n rootEl.querySelectorAll<HTMLElement>('[data-panel]').forEach((p) => {\n p.hidden = p.dataset.panel !== 'created';\n });\n } catch (err) {\n showError(err as Error);\n }\n });\n\n on(q('[data-action=\"created-ack\"]'), 'click', () => {\n const active = signer.getActiveAccount();\n if (active) handlers.onLogin?.(active);\n });\n\n // ---- ncryptsec ----\n const ncryptsecForm = q<HTMLFormElement>('[data-form=\"ncryptsec\"]');\n on(ncryptsecForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const ncryptsec = (\n ncryptsecForm.elements.namedItem('ncryptsec') as HTMLTextAreaElement\n ).value.trim();\n const passphrase = (\n ncryptsecForm.elements.namedItem('passphrase') as HTMLInputElement\n ).value;\n try {\n const account = await signer.loginWithNcryptsec(ncryptsec, passphrase);\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- extension ----\n on(q('[data-action=\"extension-login\"]'), 'click', async () => {\n clearError();\n try {\n const account = await signer.loginWithExtension();\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- signer app (NIP-55 over the web) ----\n // Absent when renderLoginHtml({ includeNip55Web: false }) omitted the tab.\n const nip55WebBtn = rootEl.querySelector<HTMLButtonElement>(\n '[data-action=\"nip55web-login\"]',\n );\n if (nip55WebBtn) on(nip55WebBtn, 'click', async () => {\n clearError();\n const btn = nip55WebBtn;\n btn.disabled = true;\n nip55WebAbort = new AbortController();\n try {\n const account = await signer.loginWithNip55Web({\n signal: nip55WebAbort.signal,\n debug: handlers.onNip55WebDebug,\n });\n handlers.onLogin?.(account);\n } catch (err) {\n btn.disabled = false;\n // A user cancelling (detach) is not an error worth surfacing.\n if ((err as Error).name !== 'AbortError') showError(err as Error);\n } finally {\n nip55WebAbort = null;\n }\n });\n\n // ---- bunker URI ----\n const bunkerForm = q<HTMLFormElement>('[data-form=\"bunker\"]');\n on(bunkerForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const uri = (\n bunkerForm.elements.namedItem('uri') as HTMLTextAreaElement\n ).value.trim();\n const perms = (bunkerForm.elements.namedItem('perms') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n try {\n const account = await signer.loginWithBunkerUri(uri, {\n pool: handlers.pool,\n onRelayMismatch: handlers.onRelayMismatch,\n perms: perms.length > 0 ? perms : undefined,\n });\n handlers.onLogin?.(account);\n } catch (err) {\n showError(err as Error);\n }\n });\n\n // ---- nostrconnect ----\n const ncForm = q<HTMLFormElement>('[data-form=\"nostrconnect\"]');\n const ncQrRegion = q<HTMLDivElement>('[data-region=\"nostrconnect-qr\"]');\n const ncQrUri = q<HTMLElement>('[data-region=\"nostrconnect-uri\"]');\n const ncQrImage = q<HTMLDivElement>('[data-region=\"nostrconnect-qr-image\"]');\n const ncStatus = q<HTMLElement>('[data-region=\"nostrconnect-status\"]');\n let ncQrToken = 0;\n\n const resetNostrConnect = (): void => {\n ncForm.hidden = false;\n ncQrRegion.hidden = true;\n ncQrUri.textContent = '';\n ncQrImage.innerHTML = '';\n ncStatus.textContent = 'Waiting for remote signer to pair...';\n ncQrToken++;\n };\n\n const renderNostrConnectQr = async (uri: string): Promise<void> => {\n const token = ++ncQrToken;\n try {\n const svg = await QRCode.toString(uri, {\n type: 'svg',\n margin: 1,\n errorCorrectionLevel: 'M',\n });\n if (token === ncQrToken) ncQrImage.innerHTML = svg;\n } catch {\n // QR rendering is a nice-to-have — the URI text remains usable.\n }\n };\n\n on(ncForm, 'submit', async (ev) => {\n ev.preventDefault();\n clearError();\n const relays = (ncForm.elements.namedItem('relays') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (relays.length === 0) {\n showError('At least one relay is required.');\n return;\n }\n const perms = (ncForm.elements.namedItem('perms') as HTMLInputElement).value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n ncForm.hidden = true;\n ncQrRegion.hidden = false;\n nostrConnectAbort = new AbortController();\n try {\n const account = await signer.loginWithNostrConnect({\n relays,\n perms: perms.length > 0 ? perms : undefined,\n pool: handlers.pool,\n onRelayMismatch: handlers.onRelayMismatch,\n signal: nostrConnectAbort.signal,\n onUri: (uri) => {\n ncQrUri.textContent = uri;\n void renderNostrConnectQr(uri);\n },\n });\n handlers.onLogin?.(account);\n } catch (err) {\n resetNostrConnect();\n if ((err as Error).name !== 'AbortError') showError(err as Error);\n } finally {\n nostrConnectAbort = null;\n }\n });\n\n on(q('[data-action=\"nostrconnect-cancel\"]'), 'click', () => {\n nostrConnectAbort?.abort();\n resetNostrConnect();\n });\n\n return {\n detach: () => {\n nostrConnectAbort?.abort();\n nip55WebAbort?.abort();\n for (const d of detachers) d();\n },\n selectTab,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formstr/signer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nostr signer with login UI for NIP-07, NIP-46, NIP-49 (ncryptsec), and NIP-55",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Nostr signer with login UI for NIP-07, NIP-46, NIP-49 (ncryptsec), and NIP-55 (Android apps, native or browser)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Abhay <toabhayraizada@gmail.com>",
|
|
7
7
|
"homepage": "https://github.com/formstr-hq/common-packages/tree/main/packages/signer#readme",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"bunker",
|
|
25
25
|
"nostr-connect",
|
|
26
26
|
"amber",
|
|
27
|
+
"nostrsigner",
|
|
27
28
|
"formstr"
|
|
28
29
|
],
|
|
29
30
|
"publishConfig": {
|