diff options
| -rw-r--r-- | names/server.js | 5 | ||||
| -rw-r--r-- | src/features/names.js | 40 |
2 files changed, 41 insertions, 4 deletions
diff --git a/names/server.js b/names/server.js index df823f9..3021621 100644 --- a/names/server.js +++ b/names/server.js @@ -665,8 +665,11 @@ Bun.serve({ // username again without the user retyping it. Public data (DNS is public). const pm = url.pathname.match(/^\/pubkey\/([0-9a-f]{64})$/); if (pm && req.method === 'GET') { + // optional domain filter, so a staging claim can't shadow a mainnet + // name (or vice versa) when a wallet asks who it is + const domFilter = (url.searchParams.get('domain') || '').toLowerCase(); const mine = Object.entries(state.names) - .filter(([, r]) => r.pubkey === pm[1]) + .filter(([k, r]) => r.pubkey === pm[1] && (!domFilter || k.endsWith('@' + domFilter))) .sort((a, b) => (b[1].updated || 0) - (a[1].updated || 0)); if (!mine.length) return json({}); const [key, r] = mine[0]; diff --git a/src/features/names.js b/src/features/names.js index d3971a4..cf2aa3c 100644 --- a/src/features/names.js +++ b/src/features/names.js @@ -55,8 +55,25 @@ export function namesFeature(ctx) { return j; } - const load = () => wallet.loadFeatureState('names', {}); - const save = (st) => wallet.saveFeatureState('names', st); + // One name PER DOMAIN: mainnet and staging claims live side by side in the + // wallet's state instead of overwriting each other on a network switch. + // Legacy flat state ({name, domain, …}) reads/migrates into its domain slot. + const loadAll = () => wallet.loadFeatureState('names', {}); + const load = () => { + const all = loadAll(); + if (all.byDomain) return all.byDomain[DOMAIN()] || {}; + if (all.name) return (all.domain || 'coinos.io') === DOMAIN() ? all : {}; + return {}; + }; + const save = (st) => { + const all = loadAll(); + const byDomain = all.byDomain || {}; + if (!all.byDomain && all.name) { + byDomain[all.domain || 'coinos.io'] = { name: all.name, domain: all.domain || 'coinos.io', uri: all.uri, offerPk: all.offerPk, updated: all.updated }; + } + byDomain[DOMAIN()] = st; + wallet.saveFeatureState('names', { byDomain }); + }; // Mainnet and mutinynet: staging's play network gets the full payment- // address experience (claims already registered fine — the record simply @@ -113,7 +130,7 @@ export function namesFeature(ctx) { const pk = wallet.nostrPubkey && wallet.nostrPubkey(); if (!pk) return; const r = await withTimeout( - fetch(`${REGISTRAR}/pubkey/${pk}`).then((x) => x.json()), 12000, 'registrar'); + fetch(`${REGISTRAR}/pubkey/${pk}?domain=${encodeURIComponent(DOMAIN())}`).then((x) => x.json()), 12000, 'registrar'); if (r && r.name) save({ ...load(), name: r.name, domain: r.domain || DOMAIN(), uri: r.uri }); } catch (e) { console.warn('names: lookup failed —', e.message); } } @@ -143,6 +160,23 @@ export function namesFeature(ctx) { } let st = load(); if (!st.name) { await lookupMine(); st = load(); } + // A custom name claimed on ANOTHER DEVICE by the login identity beats a + // local npub placeholder: the username people know the user by should + // show everywhere they sign in, not only where they typed it. + if (!st.name || /^npub1/.test(st.name)) { + const login = hook('nostrLoginIdentity'); + if (login && login.pubkey) { + try { + const r = await withTimeout( + fetch(`${REGISTRAR}/pubkey/${login.pubkey}?domain=${encodeURIComponent(DOMAIN())}`).then((x) => x.json()), + 8000, 'registrar'); + if (r && r.name && !/^npub1/.test(r.name)) { + save({ ...st, name: r.name, domain: r.domain || DOMAIN(), uri: r.uri }); + st = load(); + } + } catch {} + } + } if (!st.name) { // The address people see should be the identity they know the user // by: their real npub when a nostr account is linked, else the |
