aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js35
-rw-r--r--src/features/messages.js25
-rw-r--r--src/i18n.js4
3 files changed, 53 insertions, 11 deletions
diff --git a/src/app.js b/src/app.js
index e970167..c92f901 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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.',