From 3d1486ef79ae0bfee9a34a4ddb0805fffd0e0048 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:59:46 -0400 Subject: [PATCH] Handle flexible auth provider IDs in cache --- docs/nostr-auth.md | 26 +++++++++------- js/state/cache.js | 55 +++++++++++++++++++++++---------- js/ui/profileModalController.js | 2 +- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/docs/nostr-auth.md b/docs/nostr-auth.md index c54554d4..25770132 100644 --- a/docs/nostr-auth.md +++ b/docs/nostr-auth.md @@ -9,7 +9,8 @@ updates can evolve it without breaking existing users. - `bitvid:profileCache:v1` – short-lived metadata cache for profile names and avatars. See `js/app.js` for TTL and eviction rules. - `bitvid:savedProfiles:v1` – persistent list of accounts the user authenticated - with in this browser. + with in this browser. The payload embeds a `version` field that is currently + `2`. Both keys live in the main origin scope. Clearing one should not affect the other. @@ -20,14 +21,14 @@ other. ```json { - "version": 1, + "version": 2, "entries": [ { "pubkey": "", "npub": "", "name": "", "picture": "", - "authType": "" + "authType": "" } ], "activePubkey": "" @@ -46,17 +47,18 @@ Notes: - `activePubkey` tracks the last account that successfully authenticated in this browser. It may be `null` when the user logs out but keeps saved entries. -### `authType` enum +### `authType` handling -`authType` describes how the profile authenticated: +`authType` describes how the profile authenticated and is stored exactly as the +provider reported it (after trimming whitespace). Bitvid does not attempt to +normalise these strings, so future auth integrations can safely persist custom +identifiers. -- `"nip07"` – Browser extension flow (current default). -- `"nsec"` – Reserved for future direct key import or signer integrations. New - auth strategies should pick a distinct string and document how migration works - alongside any recovery tooling. - -When the app reads stored entries it normalises unknown values back to -`"nip07"` but keeps recognised alternatives intact. +- Legacy payloads (version `1`) did not include the field, so the loader treats + those entries as `"nip07"` and rewrites the payload at version `2`. +- Newer providers may surface alternative IDs (for example, Wallet Connect + flows). UI components use provider metadata when available and otherwise fall + back to a generic label so unknown identifiers do not break rendering. ## Migration notes diff --git a/js/state/cache.js b/js/state/cache.js index 495ca93e..862759fb 100644 --- a/js/state/cache.js +++ b/js/state/cache.js @@ -9,7 +9,7 @@ const PROFILE_CACHE_VERSION = 1; const PROFILE_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes const SAVED_PROFILES_STORAGE_KEY = "bitvid:savedProfiles:v1"; -const SAVED_PROFILES_STORAGE_VERSION = 1; +const SAVED_PROFILES_STORAGE_VERSION = 2; const URL_HEALTH_TTL_MS = 45 * 60 * 1000; // 45 minutes const URL_HEALTH_TIMEOUT_RETRY_MS = 5 * 60 * 1000; // 5 minutes @@ -261,17 +261,19 @@ export function persistSavedProfiles({ persistActive = true } = {}) { const payload = { version: SAVED_PROFILES_STORAGE_VERSION, - entries: savedProfiles.map((entry) => ({ - pubkey: entry.pubkey, - npub: - typeof entry.npub === "string" && entry.npub.trim() ? entry.npub.trim() : null, - name: typeof entry.name === "string" ? entry.name : "", - picture: typeof entry.picture === "string" ? entry.picture : "", - authType: - typeof entry.authType === "string" && entry.authType.trim() - ? entry.authType.trim() - : "nip07", - })), + entries: savedProfiles.map((entry) => { + const sanitizedAuthType = + typeof entry.authType === "string" ? sanitizeProfileString(entry.authType) : ""; + + return { + pubkey: entry.pubkey, + npub: + typeof entry.npub === "string" && entry.npub.trim() ? entry.npub.trim() : null, + name: typeof entry.name === "string" ? entry.name : "", + picture: typeof entry.picture === "string" ? entry.picture : "", + authType: sanitizedAuthType || null, + }; + }), activePubkey: activePubkeyToPersist, }; @@ -294,7 +296,19 @@ export function loadSavedProfilesFromStorage() { let needsRewrite = false; try { const parsed = JSON.parse(raw); - if (parsed && typeof parsed === "object" && parsed.version === SAVED_PROFILES_STORAGE_VERSION) { + const parsedVersion = + parsed && typeof parsed === "object" && typeof parsed.version === "number" + ? parsed.version + : 1; + if ( + parsed && + typeof parsed === "object" && + parsedVersion <= SAVED_PROFILES_STORAGE_VERSION && + parsed.entries + ) { + if (parsedVersion !== SAVED_PROFILES_STORAGE_VERSION) { + needsRewrite = true; + } const entries = Array.isArray(parsed.entries) ? parsed.entries : []; const seenPubkeys = new Set(); for (const candidate of entries) { @@ -309,10 +323,13 @@ export function loadSavedProfilesFromStorage() { } seenPubkeys.add(normalizedPubkey); - const storedAuthType = - typeof candidate.authType === "string" && candidate.authType.trim() - ? candidate.authType.trim() - : "nip07"; + const storedAuthTypeRaw = + typeof candidate.authType === "string" ? sanitizeProfileString(candidate.authType) : ""; + const isLegacyEntry = !("authType" in candidate) || parsedVersion < 2; + const storedAuthType = storedAuthTypeRaw || (isLegacyEntry ? "nip07" : null); + if (isLegacyEntry && storedAuthType !== candidate.authType) { + needsRewrite = true; + } const npub = typeof candidate.npub === "string" && candidate.npub.trim() ? candidate.npub.trim() @@ -336,6 +353,10 @@ export function loadSavedProfilesFromStorage() { needsRewrite = true; } + if (storedAuthTypeRaw && storedAuthTypeRaw !== candidate.authType) { + needsRewrite = true; + } + savedProfiles.push(entry); } diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index ae917714..b135c064 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -39,7 +39,7 @@ function resolveAuthTypeLabel(authType) { return "Saved profile"; } - return trimmed; + return `Connected via ${trimmed}`; } const DEFAULT_INTERNAL_NWC_SETTINGS = Object.freeze({