From 37a675392f9920fbe4d19d205725c5293bdea5cd Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:33:35 -0400 Subject: [PATCH] refactor(watch-history): extract actor helpers from watchHistoryService (4/10) File-size decomposition (no behavior change). Move getLoggedInActorKey, getSessionActorKey, and ensureWatchHistoryExtensionPermissions (which depend only on imported singletons, not module state) into js/watchHistoryActorHelpers.js, and drop the now-unused getActiveSigner/requestDefaultExtensionPermissions/getApplication imports from the service. watchHistoryService.js: 1491 -> 1388 lines (below its original 1395 baseline; re-recorded to 1386 to lock the reduction). Build + full unit suite green. Co-Authored-By: Claude Opus 4.8 --- js/watchHistoryActorHelpers.js | 119 +++++++++++++++++++++++++++++++++ js/watchHistoryService.js | 115 ++----------------------------- scripts/check-file-size.mjs | 2 +- 3 files changed, 126 insertions(+), 110 deletions(-) create mode 100644 js/watchHistoryActorHelpers.js diff --git a/js/watchHistoryActorHelpers.js b/js/watchHistoryActorHelpers.js new file mode 100644 index 00000000..a550455d --- /dev/null +++ b/js/watchHistoryActorHelpers.js @@ -0,0 +1,119 @@ +// js/watchHistoryActorHelpers.js +// +// Actor-key resolution + extension-permission helpers extracted from +// watchHistoryService.js to keep that module under the file-size budget. These +// depend only on imported singletons (no watchHistoryService module state), so +// they move cleanly. No behavior change. + +import { + getActiveSigner, + nostrClient, + requestDefaultExtensionPermissions, +} from "./nostrClientFacade.js"; +import { normalizeActorKey } from "./nostr/watchHistory.js"; +import { getApplication } from "./applicationContext.js"; +import { devLogger, userLogger } from "./utils/logger.js"; + +export function getLoggedInActorKey() { + const direct = normalizeActorKey(nostrClient?.pubkey); + if (direct) { + return direct; + } + + if (typeof window !== "undefined") { + const appCandidate = + getApplication() || null; + if (appCandidate && typeof appCandidate === "object") { + if (typeof appCandidate.normalizeHexPubkey === "function") { + try { + const normalized = appCandidate.normalizeHexPubkey( + appCandidate.pubkey + ); + if (normalized) { + return normalizeActorKey(normalized); + } + } catch (error) { + devLogger.warn( + "[watchHistoryService] Failed to normalize app login pubkey:", + error + ); + } + } + + if (typeof appCandidate.pubkey === "string" && appCandidate.pubkey) { + const fallback = normalizeActorKey(appCandidate.pubkey); + if (fallback) { + return fallback; + } + } + } + } + + return ""; +} + +export function getSessionActorKey() { + const logged = getLoggedInActorKey(); + if (logged) { + return ""; + } + return normalizeActorKey(nostrClient?.sessionActor?.pubkey); +} + +export async function ensureWatchHistoryExtensionPermissions(actorKey, options = {}) { + const normalizedActor = normalizeActorKey(actorKey); + if (!normalizedActor) { + return { ok: true }; + } + + const loggedActor = normalizeActorKey(nostrClient?.pubkey); + if (!loggedActor || loggedActor !== normalizedActor) { + return { ok: true }; + } + + const allowPermissionPrompt = options?.allowPermissionPrompt !== false; + + // FIX: Always attempt to resolve the signer regardless of allowPermissionPrompt. + // ensureActiveSignerForPubkey does not prompt the user — it only resolves an + // already-injected extension or returns the existing signer. + let signer = getActiveSigner(); + if ( + !signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function" + ) { + signer = await nostrClient.ensureActiveSignerForPubkey(normalizedActor); + } + + const canSign = typeof signer?.canSign === "function" + ? signer.canSign() + : typeof signer?.signEvent === "function"; + // FIX: NIP-07 adapters use type "nip07", not "extension". Both types + // represent browser extension signers that need permission pre-granting. + if (!canSign || (signer?.type !== "extension" && signer?.type !== "nip07")) { + return { ok: true }; + } + + if (!allowPermissionPrompt) { + return { ok: true }; + } + + const permissionResult = await requestDefaultExtensionPermissions(); + if (permissionResult.ok) { + return { ok: true }; + } + + const message = + "Approve your NIP-07 extension to sync 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 }; +} diff --git a/js/watchHistoryService.js b/js/watchHistoryService.js index 1b052db0..fc41faa6 100644 --- a/js/watchHistoryService.js +++ b/js/watchHistoryService.js @@ -1,10 +1,6 @@ // js/watchHistoryService.js -import { - getActiveSigner, - nostrClient, - requestDefaultExtensionPermissions, -} from "./nostrClientFacade.js"; +import { nostrClient } from "./nostrClientFacade.js"; import { normalizePointerInput, pointerKey, @@ -14,9 +10,13 @@ import { WATCH_HISTORY_CACHE_TTL_MS, WATCH_HISTORY_MAX_ITEMS, } from "./config.js"; -import { getApplication } from "./applicationContext.js"; import { devLogger, userLogger } from "./utils/logger.js"; import { ONE_MINUTE_MS } from "./constants.js"; +import { + getLoggedInActorKey, + getSessionActorKey, + ensureWatchHistoryExtensionPermissions, +} from "./watchHistoryActorHelpers.js"; const LOCAL_STORAGE_QUEUE_KEY = "bitvid:watch-history:queue:v1"; const SESSION_STORAGE_VERSION = 1; @@ -38,109 +38,6 @@ const state = { chunkBackfills: new Set(), }; -function getLoggedInActorKey() { - const direct = normalizeActorKey(nostrClient?.pubkey); - if (direct) { - return direct; - } - - if (typeof window !== "undefined") { - const appCandidate = - getApplication() || null; - if (appCandidate && typeof appCandidate === "object") { - if (typeof appCandidate.normalizeHexPubkey === "function") { - try { - const normalized = appCandidate.normalizeHexPubkey( - appCandidate.pubkey - ); - if (normalized) { - return normalizeActorKey(normalized); - } - } catch (error) { - devLogger.warn( - "[watchHistoryService] Failed to normalize app login pubkey:", - error - ); - } - } - - if (typeof appCandidate.pubkey === "string" && appCandidate.pubkey) { - const fallback = normalizeActorKey(appCandidate.pubkey); - if (fallback) { - return fallback; - } - } - } - } - - return ""; -} - -function getSessionActorKey() { - const logged = getLoggedInActorKey(); - if (logged) { - return ""; - } - return normalizeActorKey(nostrClient?.sessionActor?.pubkey); -} - -async function ensureWatchHistoryExtensionPermissions(actorKey, options = {}) { - const normalizedActor = normalizeActorKey(actorKey); - if (!normalizedActor) { - return { ok: true }; - } - - const loggedActor = normalizeActorKey(nostrClient?.pubkey); - if (!loggedActor || loggedActor !== normalizedActor) { - return { ok: true }; - } - - const allowPermissionPrompt = options?.allowPermissionPrompt !== false; - - // FIX: Always attempt to resolve the signer regardless of allowPermissionPrompt. - // ensureActiveSignerForPubkey does not prompt the user — it only resolves an - // already-injected extension or returns the existing signer. - let signer = getActiveSigner(); - if ( - !signer && typeof nostrClient?.ensureActiveSignerForPubkey === "function" - ) { - signer = await nostrClient.ensureActiveSignerForPubkey(normalizedActor); - } - - const canSign = typeof signer?.canSign === "function" - ? signer.canSign() - : typeof signer?.signEvent === "function"; - // FIX: NIP-07 adapters use type "nip07", not "extension". Both types - // represent browser extension signers that need permission pre-granting. - if (!canSign || (signer?.type !== "extension" && signer?.type !== "nip07")) { - return { ok: true }; - } - - if (!allowPermissionPrompt) { - return { ok: true }; - } - - const permissionResult = await requestDefaultExtensionPermissions(); - if (permissionResult.ok) { - return { ok: true }; - } - - const message = - "Approve your NIP-07 extension to sync 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 = diff --git a/scripts/check-file-size.mjs b/scripts/check-file-size.mjs index 267ffaf0..6afb975b 100644 --- a/scripts/check-file-size.mjs +++ b/scripts/check-file-size.mjs @@ -52,7 +52,7 @@ const GRANDFATHERED = { "js/services/hashtagPreferencesService.js": 1434, "js/nostr/publishHelpers.js": 1415, "js/index.js": 1412, - "js/watchHistoryService.js": 1395, + "js/watchHistoryService.js": 1386, "js/app/playbackCoordinator.js": 1376, "js/feedEngine/stages.js": 1370, "js/ui/views/VideoListView.js": 1359,