diff options
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/messages.js | 40 | ||||
| -rw-r--r-- | src/features/nostrlogin.js | 41 |
2 files changed, 71 insertions, 10 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 06371cf..597cb23 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -2898,11 +2898,28 @@ export function messagesFeature(ctx) { // "nothing to wait for": counting those passes gave every wrap sealed to // the login npub up after ~40 s with only the wallet key tried, and the // wrap log then hid it from every later session (a phone kept 9 of 186 - // messages). An interactive signer (Amber) is live from the moment it - // resumes and never decrypts here, so it counts as complete. + // messages). An interactive signer (Amber by intent) never decrypts here + // either, so it is incomplete too: counted as complete, every wrap sealed + // to the login npub was given up on the wallet key and logged as opened + // — an Amber user's threads held only what that device had sent. function decryptorsComplete() { const login = hook('nostrLoginIdentity'); - return !login || !!(login.signer && (login.signer.interactive || login.signer.decryptFrom)); + return !login || !!(login.signer && !login.signer.interactive && login.signer.decryptFrom); + } + const interactiveLogin = () => { const l = hook('nostrLoginIdentity'); return !!(l && l.signer && l.signer.interactive); }; + // 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 + // every wrap the relays still hold. + function noteSignerGap() { + const s = st(); + if (interactiveLogin()) { if (!s.dmSignerGap) { s.dmSignerGap = true; save(s); } return; } + if (!s.dmSignerGap || !decryptorsComplete()) return; + delete s.dmSignerGap; + s.dmHeal = 0; + save(s); + wrapLog = null; healed.clear(); seenWraps.clear(); + if (dmStarted) resubscribeStreams(); } // Raw-key unwraps (seed-derived / pasted nsec — the common case) run in @@ -3055,6 +3072,7 @@ export function messagesFeature(ctx) { const pks = dmSubKeys(); if (!pks.length) return; dmStarted = true; + if (interactiveLogin()) noteSignerGap(); loadSeenWraps(); // warm threads from the local cache. A self-thread of our own messages is // the residue of the old sent-copy misthreading (each welcome DM landed @@ -12140,6 +12158,17 @@ export function messagesFeature(ctx) { }, ui.msgReconnecting ? h('span', { class: 'spinner sm' }) : t('msgReconnect'))); } + // Amber by intent signs, but each decrypt would be a trip through the app, + // so nothing sent TO the login npub is opened here — a thread shows only + // what this device sent. Amber also speaks NIP-46; connected that way it + // decrypts quietly, and the messages held back open. + function amberDmNotice() { + if (wallet.watchOnly || !interactiveLogin()) return null; + return h('div', { class: 'row gap6 chat-signer-off', style: 'align-items:center' }, + h('span', { class: 'small muted grow' }, t('msgAmberDms')), + h('button', { class: 'btn-sm', onClick: () => hook('nostrSignerUpgrade') }, t('msgAmberConnect'))); + } + // Enter sends; Shift+Enter (the textarea's native edit) and Ctrl+J insert // a newline. Height follows the text as it's typed — imperatively, since // typing doesn't re-render — with rows as the re-render fallback. @@ -12402,6 +12431,8 @@ export function messagesFeature(ctx) { // that quietly won't open are listed. const lockedRow = signerNotice(); if (lockedRow && wallet.watchOnly) kids.push(lockedRow); + const amberRow = amberDmNotice(); + if (amberRow) kids.push(amberRow); if (pendingLink && pendingLink.where !== 'communities') kids.push(linkInviteCard()); for (const [rid, inv] of pendingDirect) kids.push(directInviteCard(rid, inv)); @@ -13319,6 +13350,7 @@ export function messagesFeature(ctx) { h('div', { class: 'chat-time' }, timeLabel(m.rumor.created_at * 1000))))) : [h('div', { class: 'muted small', style: 'text-align:center;padding:24px 0' }, t('msgNoDmsYet'))])), dmReplyBar(), + amberDmNotice(), composer(t('msgDmPlaceholder'), () => sendDM(peer), null, 'dm:' + peer, (f) => sendDMFile(peer, f)), ui.msgSheet ? dmSheet(peer) : null, choice && ui.msgRoutePick ? routeSheet(peer, choice) : null); @@ -13796,7 +13828,7 @@ export function messagesFeature(ctx) { }, // nostr-login just connected or resumed a signer: wraps the backfill // couldn't open a moment ago can be opened now — drain right away. - nostrSignerLive() { scheduleDrain(0); }, + nostrSignerLive() { noteSignerGap(); scheduleDrain(0); }, // Anyone (ark's history, other features) can open a profile or render a // small clickable identity chip. showProfile(pk) { openProfile(pk); return true; }, diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index 306a12f..2023931 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -278,6 +278,16 @@ export function nostrLoginFeature(ctx) { // approval and the login finishes on its own. Deferred a beat so the boot // restore has decided whether this tab is even logged out. setTimeout(() => { + // the same, for a wallet that was reconnecting its own account (Amber + // moving to NIP-46 so messages can be read): back to that screen + const again = !nc && wallet.mnemonic && load().pubkey && nostrConnectPending(); + if (again) { + ui.nostrReconnect = 'upgrade'; + ui.nostrConnectOpen = true; + startNostrConnect(reconnectRun, again); + render(); + return; + } const pending = !nc && !wallet.mnemonic && ui.screen === 'unlock' && nostrConnectPending(); if (!pending) return; if (ui.onb && ['welcome', 'signin'].includes(ui.onb.step)) ui.onb = { step: 'signin' }; @@ -676,6 +686,11 @@ export function nostrLoginFeature(ctx) { linkSection()); } + const reconnectRun = (makeSigner) => reconnectSigner(makeSigner).then(() => { + // an upgrade is done only once the new signer can decrypt + if (liveSigner() && !(ui.nostrReconnect === 'upgrade' && live.interactive)) { ui.nostrReconnect = null; ui.nostrConnectOpen = false; render(); } + }); + // The reconnect takeover: opened by any feature whose action just found // the signer missing (a reload dropped it; Amethyst and friends can't // reattach silently). Same guarded buttons as the settings card — an @@ -683,15 +698,18 @@ export function nostrLoginFeature(ctx) { // refuses a different account. function reconnectScreen() { const st = load(); - const run = (makeSigner) => reconnectSigner(makeSigner).then(() => { - if (liveSigner()) { ui.nostrReconnect = null; render(); } - }); + // 'upgrade': Amber by intent is live but can't read messages — offer + // only its NIP-46 door (nostrconnect), same account enforced + const upgrade = ui.nostrReconnect === 'upgrade'; + const run = reconnectRun; + // straight to the link: the screen exists for this one door + if (upgrade && ui.nostrUpgradeStart) { ui.nostrUpgradeStart = false; ui.nostrConnectOpen = true; startNostrConnect(run); } return h('div', { class: 'col', style: 'gap:16px' }, ctx.brandHeader(false), h('div', { class: 'card col', style: 'gap:12px' }, - h('h3', { style: 'margin:0' }, t('nlReconnectTitle')), - h('p', { class: 'small muted', style: 'margin:0' }, t('nlReconnectBody', { npub: (npubOf(st.pubkey) || '').slice(0, 12) })), - signerButtons(run, { reconnectVia: reconnectViaOf(st) }), + h('h3', { style: 'margin:0' }, t(upgrade ? 'nlAmberUpgradeTitle' : 'nlReconnectTitle')), + h('p', { class: 'small muted', style: 'margin:0' }, upgrade ? t('nlAmberUpgradeBody') : t('nlReconnectBody', { npub: (npubOf(st.pubkey) || '').slice(0, 12) })), + upgrade ? nostrConnectSection(run) : signerButtons(run, { reconnectVia: reconnectViaOf(st) }), ui.nostrLoginError ? h('div', { class: 'notice err' }, ui.nostrLoginError) : null), h('button', { class: 'btn-ghost btn-block', onClick: () => { stopNostrConnect(); @@ -710,6 +728,17 @@ export function nostrLoginFeature(ctx) { // An action needed the login signer and it isn't there: open the // reconnect screen (and try the silent extension reattach in the // background — it closes the screen by itself when it lands). + // Messages found an Amber-by-intent login that can't decrypt: offer to + // reconnect the same account over NIP-46. + nostrSignerUpgrade() { + const st = load(); + if (!st.pubkey) return false; + ui.nostrReconnect = 'upgrade'; + ui.nostrUpgradeStart = true; + ui.nostrLoginError = ''; + render(); + return true; + }, nostrReconnectPrompt() { const st = load(); if (!st.pubkey || liveSigner()) return false; |
