diff options
| -rw-r--r-- | src/app.js | 35 | ||||
| -rw-r--r-- | src/features/messages.js | 25 | ||||
| -rw-r--r-- | src/i18n.js | 4 |
3 files changed, 53 insertions, 11 deletions
@@ -1495,14 +1495,17 @@ function identityPkOf(a) { // Both are read from the account itself; the key remembered on the record // (saveDirectory) and in the durable directory by xpub is only the fallback, // for a watch-only entry whose seed isn't here to ask. +// One key per person: a seed's Spending and Savings accounts share it. +const seedKeyOf = (a) => a.type === 'watch' ? 'w:' + a.xpub + : a.mnemonic ? 'm:' + a.mnemonic + '\n' + (a.passphrase || '') + '\n' + (a.deriveIndex || 0) + : 'x:' + (a.xprv || a.xpub || a.id); + function identities() { const pkOf = identityPkOf; const bySeed = new Map(); for (const a of accounts) { if (a.provisional) continue; - const key = a.type === 'watch' ? 'w:' + a.xpub - : a.mnemonic ? 'm:' + a.mnemonic + '\n' + (a.passphrase || '') + '\n' + (a.deriveIndex || 0) - : 'x:' + (a.xprv || a.xpub || a.id); + const key = seedKeyOf(a); const active = a.id === activeId; const pk = pkOf(a); const cur = bySeed.get(key); @@ -1527,6 +1530,30 @@ function clearFeatureNav() { ui.chatOpen = false; ui.userSearch = null; ui.noteThread = null; ui.zapSetup = null; } +// Sign out of the CURRENT identity only. "Log out" locks every account on +// the device, which is the wrong size when two people (or two of your own +// keys) are signed in and just one should leave. This removes that +// identity's accounts — session, directory, cache, vault — and leaves the +// others exactly as they were, landing on the next one. A nostr login can +// sign straight back in (its seed lives on the relays, encrypted to its +// key); a bare seed wallet needs its recovery phrase, and the confirmation +// says which. +function signOutIdentity() { + const me = activeAccount(); + if (!me) return; + const key = seedKeyOf(me); + const mine = accounts.filter((a) => seedKeyOf(a) === key).map((a) => a.id); + clearFeatureNav(); + // the active one last: wiping it is what hands off to the next identity + for (const id of mine.filter((id) => id !== me.id)) wipeAccount(id); + wipeAccount(me.id); + if (!activeAccount()) { ui.screen = 'unlock'; ui.unlockTab = 'create'; ui.onb = { step: 'welcome' }; } + render(); +} +// Whether the current identity can come back without a recovery phrase: +// a nostr login (extension, bunker, passkey, Google) re-derives its wallet. +const identityRecoverable = () => !!loginPkOf(activeAccount() || {}); + // Switch to another identity already signed in on this device: the wallet // it opens lands on its own history, out of any profile or chat the previous // identity had open. @@ -5282,7 +5309,7 @@ const ctx = { // this runs — wipe now and land on the front door. // identity switching lives on the profile page (the Accounts screen is // about Spending/Savings faces, not who you are) - signInAnother, identities, switchIdentity, + signInAnother, identities, switchIdentity, signOutIdentity, identityRecoverable, logoutForget: () => { ui.profilePk = null; ui.profEdit = null; ui.profEditFilled = false; ui.chatOpen = false; ui.pubProf = null; diff --git a/src/features/messages.js b/src/features/messages.js index 127dc08..e2d09b6 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4045,19 +4045,30 @@ export function messagesFeature(ctx) { onClick: () => { ui.logoutConfirm = null; ctx.logoutForget(); }, }, t('clearAll')), h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.logoutConfirm = true; render(); } }, t('back'))) - : h('div', { class: 'card col confirm-pop', style: 'gap:10px' }, - h('h3', { style: 'margin:0' }, t('logout') + '?'), - h('p', { class: 'small muted', style: 'margin:0' }, t('logoutPopBlurb')), - h('button', { class: 'btn-primary btn-block', onClick: () => { + : (() => { + // With other identities signed in, logging out means THIS one + // leaves and the rest stay put; logging out of everything is + // the second, explicit choice. Alone on the device, it's the + // plain log-out it always was. + const others = ctx.identities ? ctx.identities().filter((x) => !x.active) : []; + const one = others.length > 0 && !!ctx.signOutIdentity; + const leave = (fn) => () => { ui.logoutConfirm = null; ui.profilePk = null; ui.profEdit = null; ui.profEditFilled = false; - ctx.logout && ctx.logout(); - } }, t('logout')), + fn && fn(); + }; + return h('div', { class: 'card col confirm-pop', style: 'gap:10px' }, + h('h3', { style: 'margin:0' }, (one ? t('logoutOne', { name: displayName(pk) }) : t('logout')) + '?'), + h('p', { class: 'small muted', style: 'margin:0' }, + one ? t(ctx.identityRecoverable && ctx.identityRecoverable() ? 'logoutOneBlurbLogin' : 'logoutOneBlurbSeed') : t('logoutPopBlurb')), + h('button', { class: 'btn-primary btn-block', onClick: leave(one ? ctx.signOutIdentity : ctx.logout) }, t('logout')), + one ? h('button', { class: 'btn-block', onClick: leave(ctx.logout) }, t('logoutAll')) : null, ctx.logoutForget ? h('button', { class: 'btn-block', style: 'color:var(--red,#c0392b)', onClick: () => { ui.logoutConfirm = 'forget'; render(); }, }, t('logoutForget')) : null, - h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.logoutConfirm = null; render(); } }, t('back')))); + h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.logoutConfirm = null; render(); } }, t('back'))); + })()); // The identity switcher: every account signed in on this device as an // avatar + name row (tap = switch, nothing logged out), and the door to // add a new one. diff --git a/src/i18n.js b/src/i18n.js index 7948dfc..bc3cf14 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -57,6 +57,10 @@ const STR = { entropyWarn: '⚠ Anyone who can guess this text can take your money. Use plenty of real-world randomness — 100+ dice rolls, or the order of a fully shuffled deck of cards.', entropyPlaceholder: '4 2 6 6 1 3 5 2 …', logoutPopBlurb: 'Your accounts stay saved on this device.', + logoutOne: 'Log out of {name}', + logoutOneBlurbLogin: 'Only this account leaves — the others stay signed in. You can sign back in with the same key anytime.', + logoutOneBlurbSeed: 'Only this account leaves — the others stay signed in. It is removed from this device, so make sure you have its recovery phrase.', + logoutAll: 'Log out of all accounts', clearAllWarn: '⚠ This removes every account from this device. Any account whose recovery phrase or key you have not backed up will be lost for good.', removeWalletTitle: 'Remove account', removeWalletWarn: '⚠ Remove “{name}” from this device? It will be gone from this browser — make sure you have its recovery phrase. Other accounts aren’t affected.', |
