diff --git a/js/ui/watchHistoryController.js b/js/ui/watchHistoryController.js index 252128c9..610d8a2a 100644 --- a/js/ui/watchHistoryController.js +++ b/js/ui/watchHistoryController.js @@ -285,7 +285,11 @@ export default class WatchHistoryController { ? latest.map(normalizeEntry).filter(Boolean) : []; } catch (error) { - this.showError("Failed to load watch history. Please try again."); + const message = + error?.code === "watch-history-extension-permission-denied" + ? error.message + : "Failed to load watch history. Please try again."; + this.showError(message); if (error && typeof error === "object") { error.handled = true; } diff --git a/js/watchHistoryService.js b/js/watchHistoryService.js index 6b8d67fc..d8c02d2d 100644 --- a/js/watchHistoryService.js +++ b/js/watchHistoryService.js @@ -5,6 +5,7 @@ import { normalizePointerInput, pointerKey, normalizeActorKey, + requestDefaultExtensionPermissions, } from "./nostr.js"; import { WATCH_HISTORY_CACHE_TTL_MS, @@ -98,6 +99,56 @@ function getSessionActorKey() { return normalizeActorKey(nostrClient?.sessionActor?.pubkey); } +function shouldUseExtensionForHistory(actorKey) { + const normalizedActor = normalizeActorKey(actorKey); + if (!normalizedActor) { + return false; + } + + const loggedActor = normalizeActorKey(nostrClient?.pubkey); + if (!loggedActor || loggedActor !== normalizedActor) { + return false; + } + + if (typeof window === "undefined") { + return false; + } + + const extension = window.nostr; + if (!extension || !extension.nip04) { + return false; + } + + return typeof extension.nip04.decrypt === "function"; +} + +async function ensureWatchHistoryExtensionPermissions(actorKey) { + if (!shouldUseExtensionForHistory(actorKey)) { + return { ok: true }; + } + + const permissionResult = await requestDefaultExtensionPermissions(); + if (permissionResult.ok) { + return { ok: true }; + } + + const message = + "Approve your NIP-07 extension to decrypt encrypted watch history."; + const error = new Error(message); + error.code = "watch-history-extension-permission-denied"; + error.cause = permissionResult.error; + + userLogger.warn( + "[watchHistoryService] Extension denied decrypt permission required for watch history.", + { + actor: normalizeActorKey(actorKey) || null, + error: permissionResult.error, + }, + ); + + return { ok: false, error }; +} + function resolveEffectiveActorKey(actorInput) { const supplied = typeof actorInput === "string" && actorInput.trim() @@ -1252,8 +1303,16 @@ function scheduleWatchHistoryRefresh(actorKey, cacheEntry = {}) { return cacheEntry.promise; } - const promise = nostrClient - .resolveWatchHistory(actorKey, { forceRefresh: true }) + const promise = Promise.resolve() + .then(async () => { + const permissionResult = await ensureWatchHistoryExtensionPermissions( + actorKey, + ); + if (!permissionResult.ok) { + throw permissionResult.error; + } + return nostrClient.resolveWatchHistory(actorKey, { forceRefresh: true }); + }) .then((resolvedItems) => updateFingerprintCache(actorKey, resolvedItems)) .then(() => { const latest = state.fingerprintCache.get(actorKey);