From 2338345063b0f1c816469401264b0cd0d7b7636b Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 10 Oct 2026 08:35:36 -0700 Subject: Messages: the Amber notice no longer waits for Amber to wake. "Messages sent to you can't be opened here ยท Connect Amber" showed only once the Amber signer had resumed, and the DM drain is what resumes it โ€” on a phone whose every wrap the old code had already logged as opened, nothing is pending, so nothing might ever ask. nostrLoginIdentity now says how the account came in (`via`), and an Amber account counts as one whether or not its signer is live yet (the notice, and the flag that re-opens written-off wraps once a decrypting signer takes over). tools/amber-dm-upgrade-test.js runs that case with ALL_LOGGED=1. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3 --- src/features/messages.js | 8 +++++++- src/features/nostrlogin.js | 6 ++++-- tools/amber-dm-upgrade-test.js | 13 ++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/features/messages.js b/src/features/messages.js index 28e20f1..be5f785 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -2950,7 +2950,13 @@ export function messagesFeature(ctx) { const login = hook('nostrLoginIdentity'); return !login || !!(login.signer && !login.signer.interactive && login.signer.decryptFrom); } - const interactiveLogin = () => { const l = hook('nostrLoginIdentity'); return !!(l && l.signer && l.signer.interactive); }; + // an Amber-by-intent account, whether or not its signer has woken yet: + // a wallet whose every wrap was already logged never wakes it, and then + // never said why its messages were missing + const interactiveLogin = () => { + const l = hook('nostrLoginIdentity'); + return !!l && (l.signer ? !!l.signer.interactive : l.via === 'amber'); + }; // Wraps logged while an interactive signer stood in for the login npub // may be messages no key here ever tried. Once a signer that can decrypt // takes over (Amber reconnected over NIP-46), the heal pass re-opens diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index 2023931..4f1f0de 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -763,10 +763,12 @@ export function nostrLoginFeature(ctx) { // A just-connected signer counts immediately โ€” the persisted link lands // only after the wallet opens, and the header avatar shouldn't show the // wallet key's face in the gap. - if (liveSigner()) return { pubkey: live.pubkey, npub: npubOf(live.pubkey), signer: live }; + // `via`: how the account came in, known before its signer wakes (an + // Amber account is one whether or not Amber has been asked anything yet) const st = load(); + if (liveSigner()) return { pubkey: live.pubkey, npub: npubOf(live.pubkey), signer: live, via: st.pubkey === live.pubkey ? reconnectViaOf(st) : viaOf(live) }; if (!st.pubkey) return null; - return { pubkey: st.pubkey, npub: npubOf(st.pubkey), signer: live }; + return { pubkey: st.pubkey, npub: npubOf(st.pubkey), signer: live, via: reconnectViaOf(st) }; }, // A page reload loses the signer. A stored passkey key or an installed // extension reattaches without prompting, which is what lets the payment diff --git a/tools/amber-dm-upgrade-test.js b/tools/amber-dm-upgrade-test.js index 4e34a94..9a5070a 100644 --- a/tools/amber-dm-upgrade-test.js +++ b/tools/amber-dm-upgrade-test.js @@ -6,7 +6,7 @@ // the same account over NIP-46 (nostrconnect), after which every wrap the // relays hold is opened again โ€” including ones the old code wrote off. // The remote signer is played by this script over a shimmed relay. -// Run: bun tools/amber-dm-upgrade-test.js +// Run: bun tools/amber-dm-upgrade-test.js (and ALL_LOGGED=1 bun tools/amber-dm-upgrade-test.js) import puppeteer from './puppeteer.js'; import { cacheKeyFor } from '../src/wallet.js'; import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure'; @@ -20,6 +20,9 @@ const SK = generateSecretKey(); const PK = getPublicKey(SK); // the Amber accoun const PEER_SK = generateSecretKey(); const PEER = getPublicKey(PEER_SK); const OLD = wrapEvent(PEER_SK, { publicKey: PK }, 'an old message the wrap log wrote off'); const NEW = wrapEvent(PEER_SK, { publicKey: PK }, 'a message from before the upgrade'); +// ALL_LOGGED=1: every wrap already written off by the old code, so nothing +// is pending and the Amber signer is never woken (isolabellart's phone) +const ALL = process.env.ALL_LOGGED === '1'; const html = await buildHtml({ minify: true, pwa: false }); const server = Bun.serve({ port: 5311, fetch: (req) => { const p = new URL(req.url).pathname; if (p === '/verify-worker.js' || p === '/nip46.js' || p.startsWith('/punks')) return new Response(Bun.file('dist' + p)); return new Response(html, { headers: { 'content-type': 'text/html' } }); } }); @@ -106,12 +109,12 @@ try { const base = cacheKeyFor(mn + '\n'); // an Amber login, and a wrap log in which the old code had already // written OLD off as opened - await page.evaluate(([k, pk, oldId]) => { + await page.evaluate(([k, pk, oldId, newId]) => { localStorage.setItem(k + ':nostrlogin', JSON.stringify({ pubkey: pk, session: { amber: pk }, via: 'amber' })); const m = JSON.parse(localStorage.getItem(k + ':messages') || '{}'); - m.wraps = [oldId.slice(0, 16)]; m.dmHeal = 2; + m.wraps = [oldId.slice(0, 16)].concat(newId ? [newId.slice(0, 16)] : []); m.dmHeal = 2; localStorage.setItem(k + ':messages', JSON.stringify(m)); - }, [base, PK, OLD.id]); + }, [base, PK, OLD.id, ALL ? NEW.id : null]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Messages"]')?.click()); @@ -121,7 +124,7 @@ try { await sleep(4000); check('no decrypt intent went to Amber for an arriving wrap', !(await page.evaluate(() => window.__amberUrls.some((u) => /nip44_decrypt/.test(u))))); check('neither message shows', !(await text()).includes('a message from before the upgrade') && !(await text()).includes('wrote off')); - check('the arriving wrap is not logged as opened', await page.evaluate(([k, id]) => !(JSON.parse(localStorage.getItem(k + ':messages') || '{}').wraps || []).includes(id.slice(0, 16)), [base, NEW.id])); + if (!ALL) check('the arriving wrap is not logged as opened', await page.evaluate(([k, id]) => !(JSON.parse(localStorage.getItem(k + ':messages') || '{}').wraps || []).includes(id.slice(0, 16)), [base, NEW.id])); console.log('\n[Connect Amber: the same account over NIP-46]'); await click('connect amber'); await sleep(300); -- cgit v1.2.3