From ec83b1222feef0cdf81637bef9543431de66ab30 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:55:40 -0400 Subject: [PATCH] Refine login flow and profile storage --- js/app.js | 523 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 391 insertions(+), 132 deletions(-) diff --git a/js/app.js b/js/app.js index 8bf1fad4..e5e16c48 100644 --- a/js/app.js +++ b/js/app.js @@ -509,6 +509,7 @@ class bitvidApp { this.profileLogoutBtn = null; this.profileModalAvatar = null; this.profileModalName = null; + this.profileModalNpub = null; this.profileChannelLink = null; this.profileNavButtons = { account: null, @@ -549,6 +550,7 @@ class bitvidApp { this.boundProfileModalKeydown = null; this.boundProfileModalFocusIn = null; this.profileModalFocusables = []; + this.profileSwitcherList = null; this.currentUserNpub = null; // Upload modal elements @@ -678,7 +680,58 @@ class bitvidApp { this.profileCache = new Map(); this.savedProfiles = []; this.activeProfilePubkey = null; - this.persistSavedProfiles = () => { + this.readSavedProfilesPayloadFromStorage = () => { + if (typeof localStorage === "undefined") { + return null; + } + + const raw = localStorage.getItem(SAVED_PROFILES_STORAGE_KEY); + if (!raw) { + return null; + } + + try { + const parsed = JSON.parse(raw); + if (!parsed || typeof parsed !== "object") { + return null; + } + return parsed; + } catch (err) { + console.warn("[readSavedProfilesPayloadFromStorage] Failed to parse payload:", err); + return null; + } + }; + this.writeSavedProfilesPayloadToStorage = (payload) => { + if (typeof localStorage === "undefined") { + return; + } + + try { + localStorage.setItem( + SAVED_PROFILES_STORAGE_KEY, + JSON.stringify(payload) + ); + } catch (err) { + const isQuotaError = + err && + (err.name === "QuotaExceededError" || + err.name === "NS_ERROR_DOM_QUOTA_REACHED" || + err.code === 22 || + err.code === 1014); + if (isQuotaError) { + console.warn( + "[writeSavedProfilesPayloadToStorage] Storage quota exceeded while saving profiles; keeping in-memory copy only.", + err + ); + } else { + console.warn( + "[writeSavedProfilesPayloadToStorage] Failed to persist saved profiles:", + err + ); + } + } + }; + this.persistSavedProfiles = ({ persistActive = true } = {}) => { if (typeof localStorage === "undefined") { return; } @@ -692,6 +745,25 @@ class bitvidApp { return; } + let activePubkeyToPersist = this.activeProfilePubkey || null; + if (!persistActive) { + const storedPayload = this.readSavedProfilesPayloadFromStorage(); + if (storedPayload && typeof storedPayload === "object") { + const candidate = + typeof storedPayload.activePubkey === "string" + ? storedPayload.activePubkey + : typeof storedPayload.activePubKey === "string" + ? storedPayload.activePubKey + : null; + const normalizedStored = this.normalizeHexPubkey(candidate); + if (normalizedStored) { + activePubkeyToPersist = normalizedStored; + } else if (candidate === null) { + activePubkeyToPersist = null; + } + } + } + const payload = { version: SAVED_PROFILES_STORAGE_VERSION, entries: this.savedProfiles.map((entry) => ({ @@ -705,37 +777,19 @@ class bitvidApp { typeof entry.picture === "string" ? entry.picture : "", authType: entry.authType === "nsec" ? "nsec" : "nip07", })), - activePubkey: this.activeProfilePubkey || null, + activePubkey: activePubkeyToPersist, }; - try { - localStorage.setItem( - SAVED_PROFILES_STORAGE_KEY, - JSON.stringify(payload) - ); - try { - localStorage.removeItem("userPubKey"); - } catch (legacyErr) { - console.warn( - "[persistSavedProfiles] Failed to remove legacy userPubKey entry:", - legacyErr - ); - } - } catch (err) { - const isQuotaError = - err && - (err.name === "QuotaExceededError" || - err.name === "NS_ERROR_DOM_QUOTA_REACHED" || - err.code === 22 || - err.code === 1014); - if (isQuotaError) { - console.warn( - "[persistSavedProfiles] Storage quota exceeded while saving profiles; keeping in-memory copy only.", - err - ); - } else { - console.warn("[persistSavedProfiles] Failed to persist saved profiles:", err); - } + this.writeSavedProfilesPayloadToStorage(payload); + }; + this.persistActiveProfileSelection = ( + pubkey, + { persist = true } = {} + ) => { + const normalized = this.normalizeHexPubkey(pubkey); + this.activeProfilePubkey = normalized; + if (persist) { + this.persistSavedProfiles({ persistActive: true }); } }; this.lastRenderedVideoSignature = null; @@ -867,24 +921,6 @@ class bitvidApp { needsRewrite = true; } - if (!this.savedProfiles.length) { - const legacyPubkey = localStorage.getItem("userPubKey"); - const normalizedLegacy = this.normalizeHexPubkey(legacyPubkey); - if (normalizedLegacy) { - const legacyEntry = { - pubkey: normalizedLegacy, - npub: this.safeEncodeNpub(normalizedLegacy), - name: "", - picture: "", - authType: "nip07", - }; - this.savedProfiles = [legacyEntry]; - this.activeProfilePubkey = normalizedLegacy; - this.persistSavedProfiles(); - return; - } - } - if (!this.activeProfilePubkey && this.savedProfiles.length) { this.activeProfilePubkey = this.savedProfiles[0].pubkey; needsRewrite = true; @@ -893,6 +929,51 @@ class bitvidApp { if (needsRewrite) { this.persistSavedProfiles(); } + + this.renderSavedProfilesList(); + } + + syncSavedProfileFromCache(pubkey, { persist = false } = {}) { + const normalized = this.normalizeHexPubkey(pubkey); + if (!normalized) { + return false; + } + + const cacheEntry = this.profileCache.get(normalized); + if (!cacheEntry || typeof cacheEntry !== "object") { + return false; + } + + const index = this.savedProfiles.findIndex( + (entry) => entry && entry.pubkey === normalized + ); + if (index < 0) { + return false; + } + + const existing = this.savedProfiles[index] || {}; + const profile = cacheEntry.profile || {}; + const nextEntry = { + ...existing, + name: profile.name || existing.name || "", + picture: profile.picture || existing.picture || "", + }; + + const changed = + existing.name !== nextEntry.name || existing.picture !== nextEntry.picture; + + if (!changed) { + return false; + } + + this.savedProfiles[index] = nextEntry; + + if (persist) { + this.persistSavedProfiles({ persistActive: false }); + } + + this.renderSavedProfilesList(); + return true; } loadProfileCacheFromStorage() { @@ -1034,6 +1115,12 @@ class bitvidApp { this.profileCache.set(pubkey, entry); this.persistProfileCacheToStorage(); + const didUpdateSavedProfile = this.syncSavedProfileFromCache(pubkey, { + persist: true, + }); + if (!didUpdateSavedProfile) { + this.renderSavedProfilesList(); + } return entry; } @@ -1114,12 +1201,11 @@ class bitvidApp { // Grab the "Subscriptions" link by its id in the sidebar this.subscriptionsLink = document.getElementById("subscriptionsLink"); - const savedPubKey = - this.activeProfilePubkey || localStorage.getItem("userPubKey"); + const savedPubKey = this.activeProfilePubkey; if (savedPubKey) { // Auto-login if a pubkey was saved try { - await this.login(savedPubKey, false); + await this.login(savedPubKey, { persistActive: false }); } catch (error) { console.error("Auto-login failed:", error); } @@ -3462,6 +3548,156 @@ class bitvidApp { } } + renderSavedProfilesList() { + const normalizedActive = this.normalizeHexPubkey( + this.activeProfilePubkey + ); + const fallbackAvatar = "assets/svg/default-profile.svg"; + + const resolveMeta = (entry) => { + if (!entry || typeof entry !== "object") { + return { + name: "", + picture: fallbackAvatar, + npub: null, + }; + } + + const cacheEntry = this.profileCache.get(entry.pubkey); + const cachedProfile = cacheEntry?.profile || {}; + const resolvedName = cachedProfile.name || entry.name || "Unknown"; + const resolvedPicture = + cachedProfile.picture || entry.picture || fallbackAvatar; + let resolvedNpub = + typeof entry.npub === "string" && entry.npub.trim() + ? entry.npub.trim() + : null; + if (!resolvedNpub && entry.pubkey) { + resolvedNpub = this.safeEncodeNpub(entry.pubkey); + } + + return { + name: resolvedName, + picture: resolvedPicture, + npub: resolvedNpub, + }; + }; + + const savedEntries = Array.isArray(this.savedProfiles) + ? this.savedProfiles + : []; + const activeEntry = normalizedActive + ? savedEntries.find((entry) => entry.pubkey === normalizedActive) + : null; + const activeMeta = activeEntry ? resolveMeta(activeEntry) : null; + + if (this.profileModalName) { + this.profileModalName.textContent = activeMeta + ? activeMeta.name || "Unnamed profile" + : "No active profile"; + } + + if (this.profileModalAvatar) { + const avatarSrc = activeMeta?.picture || fallbackAvatar; + if (this.profileModalAvatar.src !== avatarSrc) { + this.profileModalAvatar.src = avatarSrc; + } + this.profileModalAvatar.alt = activeMeta + ? `${activeMeta.name || "Saved profile"} avatar` + : "Inactive profile avatar"; + } + + if (this.profileModalNpub) { + if (activeMeta?.npub) { + this.profileModalNpub.textContent = truncateMiddle( + activeMeta.npub, + 48 + ); + } else { + this.profileModalNpub.textContent = "Not signed in"; + } + } + + if (this.profileChannelLink) { + if (activeMeta?.npub) { + this.profileChannelLink.href = `#view=channel-profile&npub=${encodeURIComponent( + activeMeta.npub + )}`; + this.profileChannelLink.dataset.targetNpub = activeMeta.npub; + this.profileChannelLink.classList.remove("hidden"); + } else { + this.profileChannelLink.classList.add("hidden"); + this.profileChannelLink.removeAttribute("href"); + delete this.profileChannelLink.dataset.targetNpub; + } + } + + const listEl = this.profileSwitcherList; + if (!(listEl instanceof HTMLElement)) { + return; + } + + const otherEntries = savedEntries.filter( + (entry) => entry && entry.pubkey && entry.pubkey !== normalizedActive + ); + + if (!otherEntries.length) { + listEl.innerHTML = ` +