diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/concord.js | 4 | ||||
| -rw-r--r-- | src/features/nostrlogin.js | 19 | ||||
| -rw-r--r-- | src/nip46-global.js | 9 | ||||
| -rw-r--r-- | src/nostr-login.js | 23 |
4 files changed, 48 insertions, 7 deletions
diff --git a/src/concord.js b/src/concord.js index 9f6803d..74c1313 100644 --- a/src/concord.js +++ b/src/concord.js @@ -9,7 +9,9 @@ import { hkdf } from "@noble/hashes/hkdf"; import { schnorr, secp256k1 } from "@noble/curves/secp256k1"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; import { finalizeEvent, generateSecretKey, getPublicKey, getEventHash, verifyEvent } from "nostr-tools/pure"; -import { nip44 } from "nostr-tools"; +// subpath, NOT the "nostr-tools" barrel — the barrel drags every NIP (and a +// second copy of the noble crypto stack) into the bundle for one import +import * as nip44 from "nostr-tools/nip44"; import * as nip19 from "nostr-tools/nip19"; import { base64urlnopad } from "@scure/base"; diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index 5195778..9c4156f 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -162,11 +162,19 @@ export function nostrLoginFeature(ctx) { // from another device); when the app connects, login continues by itself. let nc = null; let ncRun = null; - function startNostrConnect(run) { + let ncToken = null; // guards the async gap while the nip46 module loads + async function startNostrConnect(run) { if (nc) nc.cancel(); ncRun = run; - nc = nostrConnect(); - const mine = nc; + const token = (ncToken = {}); + let mine; + try { mine = await nostrConnect(); } catch { + if (token === ncToken && ui.nostrConnectOpen) { ui.nostrConnectOpen = false; render(); } + return; + } + if (token !== ncToken || !ncRun) { mine.cancel(); return; } + nc = mine; + render(); mine.ready.then((signer) => { if (nc !== mine || !ncRun) return; ui.nostrConnectOpen = false; @@ -179,6 +187,7 @@ export function nostrLoginFeature(ctx) { function stopNostrConnect() { if (nc) { nc.cancel(); nc = null; } ncRun = null; + ncToken = null; } function nostrConnectSection(run) { @@ -188,7 +197,9 @@ export function nostrLoginFeature(ctx) { onClick: () => { ui.nostrConnectOpen = true; startNostrConnect(run); render(); }, }, t('nlConnectApp')); } - if (!nc) return null; + // the nip46 module is still fetching — hold the spot with a spinner + if (!nc) return h('div', { class: 'row gap6', style: 'align-items:center;justify-content:center;padding:8px' }, + h('span', { class: 'spinner sm' })); return h('div', { class: 'col', style: 'gap:10px;align-items:center' }, h('a', { class: 'btn btn-block btn-primary', href: nc.uri, diff --git a/src/nip46-global.js b/src/nip46-global.js new file mode 100644 index 0000000..a841905 --- /dev/null +++ b/src/nip46-global.js @@ -0,0 +1,9 @@ +// Standalone bundle for the NIP-46 remote-signer client (bunker:// and +// nostrconnect:// logins) — emitted as a separate dist/nip46.js and loaded on +// demand by nostr-login.js, the first time a bunker interaction happens. +// It's the heaviest, rarest corner of the login stack (and drags a second +// copy of the noble crypto stack via nostr-tools' own pins), so it stays out +// of the main bundle and off the initial load. +import * as nip46 from 'nostr-tools/nip46'; + +window.__nip46 = nip46; diff --git a/src/nostr-login.js b/src/nostr-login.js index 301bbeb..6958da0 100644 --- a/src/nostr-login.js +++ b/src/nostr-login.js @@ -38,9 +38,25 @@ import { entropyToMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; import { getPublicKey, finalizeEvent, nip44 } from './nostr.js'; import { SimplePool } from 'nostr-tools/pool'; -import { BunkerSigner, parseBunkerInput, createNostrConnectURI } from 'nostr-tools/nip46'; import { generateSecretKey } from 'nostr-tools/pure'; +// NIP-46 loads lazily from dist/nip46.js (same pattern as the QR decoder): +// bunker logins are rare, and the module is heavy. Tests running outside a +// browser preset globalThis.__nip46 with the real module instead. +let _nip46 = null; +function loadNip46() { + if (_nip46) return _nip46; + if (typeof globalThis !== 'undefined' && globalThis.__nip46) return (_nip46 = Promise.resolve(globalThis.__nip46)); + _nip46 = new Promise((resolve, reject) => { + const s = document.createElement('script'); + s.src = 'nip46.js'; + s.onload = () => (window.__nip46 ? resolve(window.__nip46) : reject(new Error('signer module unavailable'))); + s.onerror = () => { _nip46 = null; reject(new Error('could not load the remote-signer module')); }; + document.head.appendChild(s); + }); + return _nip46; +} + const SEED_DTAG = 'coinos:wallet:v1'; const BACKUP_KIND = 30078; const DERIVE_TAG = 'coinos:wallet-seed:v1'; @@ -91,6 +107,7 @@ export async function extensionSigner() { } export async function bunkerSigner(uri, { onAuth } = {}) { + const { BunkerSigner, parseBunkerInput } = await loadNip46(); const bp = await parseBunkerInput(String(uri || '').trim()); if (!bp) throw new Error('not a bunker:// address'); if (!bp.relays || !bp.relays.length) throw new Error('that bunker address lists no relays'); @@ -111,6 +128,7 @@ export async function bunkerSigner(uri, { onAuth } = {}) { // prompting the user again. export async function resumeBunker(session, { onAuth, timeoutMs = 8000 } = {}) { if (!session || !session.local || !session.bp || !session.bp.pubkey) return null; + const { BunkerSigner } = await loadNip46(); const local = hex.decode(session.local); const signer = BunkerSigner.fromBunker(local, session.bp, { onauth: (url) => { if (onAuth) onAuth(url); }, @@ -147,7 +165,8 @@ function bunkerAdapter(signer, pubkey, local) { // relay — no bunker URL to copy. Returns the URI (render it as a deep link // and a QR), a promise that resolves to a signer adapter when the app // answers, and a cancel. -export function nostrConnect({ relays = ['wss://relay.coinos.io', 'wss://nos.lol'], timeoutMs = 180_000 } = {}) { +export async function nostrConnect({ relays = ['wss://relay.coinos.io', 'wss://nos.lol'], timeoutMs = 180_000 } = {}) { + const { BunkerSigner, createNostrConnectURI } = await loadNip46(); const local = generateSecretKey(); const secret = hex.encode(crypto.getRandomValues(new Uint8Array(16))); const uri = createNostrConnectURI({ |
