From 90bbcf0ad4940a00b3ffdf38f2749ed98ab30c32 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:32:53 -0400 Subject: [PATCH 1/2] Ensure video grid refresh on auth changes --- js/app.js | 266 +++++++++++++++++++++++++++++++- js/ui/profileModalController.js | 9 +- 2 files changed, 262 insertions(+), 13 deletions(-) diff --git a/js/app.js b/js/app.js index a7c7c678..63959ea3 100644 --- a/js/app.js +++ b/js/app.js @@ -1307,12 +1307,22 @@ class Application { }, onRequestSwitchProfile: (payload) => this.handleProfileSwitchRequest(payload), + onRelayOperation: (payload) => + this.handleProfileRelayOperation(payload), + onRelayModeToggle: (payload) => + this.handleProfileRelayModeToggle(payload), + onRelayRestore: (payload) => + this.handleProfileRelayRestore(payload), + onBlocklistMutation: (payload) => + this.handleProfileBlocklistMutation(payload), onWalletPersist: (payload) => this.handleProfileWalletPersist(payload), onWalletTestRequest: (payload) => this.handleProfileWalletTest(payload), onWalletDisconnectRequest: (payload) => this.handleProfileWalletDisconnect(payload), + onAdminMutation: (payload) => + this.handleProfileAdminMutation(payload), onAdminNotifyError: (payload) => this.handleProfileAdminNotifyError(payload), onHistoryReady: (payload) => @@ -2309,13 +2319,247 @@ class Application { throw new Error("Missing pubkey for profile switch request."); } - if (!this.profileModalController) { - throw new Error("Profile modal controller unavailable for switch."); + const result = await this.authService.switchProfile(pubkey); + + if (result?.switched) { + try { + await this.loadVideos(true); + } catch (error) { + console.error("Failed to refresh videos after switching profiles:", error); + } } - await this.profileModalController.switchProfile(pubkey); - await this.loadVideos(true); - return true; + return result; + } + + async handleProfileRelayOperation({ + action, + url, + activePubkey, + skipPublishIfUnchanged = true, + } = {}) { + const context = { + action, + url, + ok: false, + changed: false, + reason: null, + error: null, + publishResult: null, + operationResult: null, + }; + + if (!activePubkey) { + context.reason = "no-active-pubkey"; + return context; + } + + const previous = relayManager.snapshot(); + + let operationResult; + try { + switch (action) { + case "add": + operationResult = relayManager.addRelay(url); + break; + case "remove": + operationResult = relayManager.removeRelay(url); + break; + case "restore": + operationResult = relayManager.restoreDefaults(); + break; + case "mode-toggle": + operationResult = relayManager.cycleRelayMode(url); + break; + default: { + const error = Object.assign(new Error("Unknown relay operation."), { + code: "invalid-operation", + }); + throw error; + } + } + } catch (error) { + context.reason = error?.code || "operation-error"; + context.error = error; + return context; + } + + context.operationResult = operationResult; + context.changed = Boolean(operationResult?.changed); + + if (!context.changed && skipPublishIfUnchanged) { + context.reason = operationResult?.reason || "unchanged"; + return context; + } + + try { + const publishResult = await relayManager.publishRelayList(activePubkey); + if (!publishResult?.ok) { + throw Object.assign(new Error("No relays accepted the update."), { + code: "publish-failed", + }); + } + context.ok = true; + context.publishResult = publishResult; + + const refreshReason = `relay-${action || "update"}`; + try { + await this.onVideosShouldRefresh({ reason: refreshReason }); + } catch (refreshError) { + if (isDevMode) { + console.warn( + "[Profile] Failed to refresh videos after relay update:", + refreshError, + ); + } + } + + return context; + } catch (error) { + context.reason = error?.code || "publish-failed"; + context.error = error; + try { + if (Array.isArray(previous)) { + relayManager.setEntries(previous, { allowEmpty: false }); + } + } catch (restoreError) { + if (isDevMode) { + console.warn( + "[Profile] Failed to restore relay preferences after publish error:", + restoreError, + ); + } + } + return context; + } + } + + handleProfileRelayModeToggle(payload = {}) { + return payload?.context || null; + } + + handleProfileRelayRestore(payload = {}) { + return payload?.context || null; + } + + async handleProfileBlocklistMutation({ + action, + actorHex, + targetHex, + } = {}) { + const context = { ok: false, reason: null, error: null }; + + if (!actorHex || !targetHex) { + context.reason = "invalid-target"; + return context; + } + + try { + await userBlocks.ensureLoaded(actorHex); + const isBlocked = userBlocks.isBlocked(targetHex); + + if (action === "add") { + if (isBlocked) { + context.reason = "already-blocked"; + return context; + } + await userBlocks.addBlock(targetHex, actorHex); + context.ok = true; + context.reason = "blocked"; + } else if (action === "remove") { + if (!isBlocked) { + context.reason = "not-blocked"; + return context; + } + await userBlocks.removeBlock(targetHex, actorHex); + context.ok = true; + context.reason = "unblocked"; + } else { + context.reason = "invalid-action"; + return context; + } + + if (context.ok) { + try { + await this.onVideosShouldRefresh({ reason: `blocklist-${action}` }); + } catch (refreshError) { + console.error( + "Failed to refresh videos after blocklist mutation:", + refreshError, + ); + } + } + + return context; + } catch (error) { + context.error = error; + context.reason = error?.code || "service-error"; + return context; + } + } + + async handleProfileAdminMutation(payload = {}) { + const action = payload?.action; + const context = { ok: false, error: null, result: null }; + + try { + switch (action) { + case "ensure-ready": + await accessControl.ensureReady(); + context.ok = true; + break; + case "add-moderator": + context.result = await accessControl.addModerator( + payload.actorNpub, + payload.targetNpub, + ); + context.ok = !!context.result?.ok; + break; + case "remove-moderator": + context.result = await accessControl.removeModerator( + payload.actorNpub, + payload.targetNpub, + ); + context.ok = !!context.result?.ok; + break; + case "list-mutation": + if (payload.listType === "whitelist") { + context.result = + payload.mode === "add" + ? await accessControl.addToWhitelist( + payload.actorNpub, + payload.targetNpub, + ) + : await accessControl.removeFromWhitelist( + payload.actorNpub, + payload.targetNpub, + ); + } else { + context.result = + payload.mode === "add" + ? await accessControl.addToBlacklist( + payload.actorNpub, + payload.targetNpub, + ) + : await accessControl.removeFromBlacklist( + payload.actorNpub, + payload.targetNpub, + ); + } + context.ok = !!context.result?.ok; + break; + default: + context.error = Object.assign( + new Error("Unknown admin mutation."), + { code: "invalid-action" }, + ); + } + } catch (error) { + context.error = error; + return context; + } + + return context; } async handleProfileWalletPersist({ @@ -2611,7 +2855,11 @@ class Application { this.updateActiveProfileUI(activePubkey, detail.postLogin.profile); } - await this.loadVideos(); + try { + await this.loadVideos(true); + } catch (error) { + console.error("Failed to refresh videos after login:", error); + } this.forceRefreshAllProfiles(); if (this.uploadModal?.refreshCloudflareBucketPreview) { await this.uploadModal.refreshCloudflareBucketPreview(); @@ -2679,7 +2927,11 @@ class Application { this.subscriptionsLink.classList.add("hidden"); } - await this.loadVideos(); + try { + await this.loadVideos(true); + } catch (error) { + console.error("Failed to refresh videos after logout:", error); + } this.forceRefreshAllProfiles(); if (this.uploadModal?.refreshCloudflareBucketPreview) { await this.uploadModal.refreshCloudflareBucketPreview(); diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index 04c75856..d597db32 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -1184,10 +1184,7 @@ export class ProfileModalController { button.setAttribute("aria-busy", "true"); try { - await this.requestSwitchProfile({ - entry, - pubkey: entry.pubkey, - }); + await this.switchProfile(entry.pubkey, { entry }); } catch (error) { console.error("Failed to switch profile:", error); } finally { @@ -3829,14 +3826,14 @@ export class ProfileModalController { } } - async switchProfile(pubkey) { + async switchProfile(pubkey, { entry } = {}) { if (!pubkey) { return { switched: false, reason: "missing-pubkey" }; } let result; try { - result = await this.services.switchProfile(pubkey); + result = await this.requestSwitchProfile({ pubkey, entry }); } catch (error) { const message = error && typeof error.message === "string" && error.message.trim() From 1db10f7f81c0a6f28c4ecade175961e94ae8a3eb Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:55:24 -0400 Subject: [PATCH 2/2] Fix channel playback and speed up profile fetches --- js/channelProfile.js | 52 +++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/js/channelProfile.js b/js/channelProfile.js index ff8b8f38..d20db4cf 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -1601,13 +1601,41 @@ async function loadUserVideos(pubkey) { // 2) Collect raw events from all relays const events = []; - for (const url of nostrClient.relays) { + const relayList = Array.isArray(nostrClient.relays) + ? nostrClient.relays + : Array.from(nostrClient.relays || []); + + if (relayList.length === 0) { try { - const result = await nostrClient.pool.list([url], [filter]); - events.push(...result); - } catch (relayErr) { - console.error(`Relay error (${url}):`, relayErr); + const fallbackEvents = await nostrClient.pool.list( + nostrClient.relays, + [filter] + ); + if (Array.isArray(fallbackEvents)) { + events.push(...fallbackEvents); + } + } catch (error) { + console.error("Relay error (default pool):", error); } + } else { + const relayPromises = relayList.map((url) => + nostrClient.pool.list([url], [filter]) + ); + + const settled = await Promise.allSettled(relayPromises); + settled.forEach((result, index) => { + const relayUrl = relayList[index]; + + if (result.status === "fulfilled") { + const relayEvents = Array.isArray(result.value) ? result.value : []; + events.push(...relayEvents); + return; + } + + if (result.reason) { + console.error(`Relay error (${relayUrl}):`, result.reason); + } + }); } // 3) Convert to "video" objects and keep everything (including tombstones) @@ -1964,7 +1992,7 @@ async function loadUserVideos(pubkey) { app?.attachMoreMenuHandlers?.(container); if (!container.dataset.playHandlerBound) { - container.addEventListener("click", (event) => { + container.addEventListener("click", async (event) => { if (!event || !(event.target instanceof HTMLElement)) { return; } @@ -2010,15 +2038,9 @@ async function loadUserVideos(pubkey) { if (videoId && typeof app?.playVideoByEventId === "function") { try { - const maybePromise = app.playVideoByEventId(videoId); - if (maybePromise && typeof maybePromise.then === "function") { - maybePromise.catch((error) => { - console.error( - "Failed to play via event id from channel grid:", - error - ); - playWithUrlAndMagnet(); - }); + const result = await app.playVideoByEventId(videoId); + if (!result || result.error) { + playWithUrlAndMagnet(); } } catch (error) { console.error(