diff --git a/js/app.js b/js/app.js index 211150f2..f33c9653 100644 --- a/js/app.js +++ b/js/app.js @@ -6749,10 +6749,19 @@ class bitvidApp { this.resetViewLoggingState(); } + const trimmedPubkey = + typeof pubkey === "string" && pubkey.trim() ? pubkey.trim() : ""; + if (normalizedPubkey) { this.pubkey = normalizedPubkey; + if (nostrClient && typeof nostrClient === "object") { + nostrClient.pubkey = normalizedPubkey; + } } else { this.pubkey = pubkey; + if (nostrClient && typeof nostrClient === "object") { + nostrClient.pubkey = trimmedPubkey ? trimmedPubkey.toLowerCase() : ""; + } } this.currentUserNpub = this.safeEncodeNpub(this.pubkey); @@ -7587,8 +7596,52 @@ class bitvidApp { try { const canUseWatchHistoryService = typeof watchHistoryService?.publishView === "function"; + const resolveWatchActor = () => { + const normalizedUser = this.normalizeHexPubkey(this.pubkey); + if (normalizedUser) { + return normalizedUser; + } + + const normalizedClient = this.normalizeHexPubkey( + nostrClient?.pubkey + ); + if (normalizedClient) { + return normalizedClient; + } + + const normalizedSession = this.normalizeHexPubkey( + nostrClient?.sessionActor?.pubkey + ); + if (normalizedSession) { + return normalizedSession; + } + + if ( + typeof nostrClient?.pubkey === "string" && + nostrClient.pubkey.trim() + ) { + return nostrClient.pubkey.trim().toLowerCase(); + } + + if ( + typeof nostrClient?.sessionActor?.pubkey === "string" && + nostrClient.sessionActor.pubkey.trim() + ) { + return nostrClient.sessionActor.pubkey.trim().toLowerCase(); + } + + return ""; + }; + + const activeWatchActor = resolveWatchActor(); + const watchMetadata = activeWatchActor ? { actor: activeWatchActor } : undefined; + if (canUseWatchHistoryService) { - viewResult = await watchHistoryService.publishView(thresholdPointer); + viewResult = await watchHistoryService.publishView( + thresholdPointer, + undefined, + watchMetadata + ); } else if (typeof nostrClient?.recordVideoView === "function") { viewResult = await nostrClient.recordVideoView(thresholdPointer); } else { @@ -11640,5 +11693,11 @@ function dedupeToNewestByRoot(videos) { export const app = new bitvidApp(); export const appReady = app.init(); -window.app = app; -window.appReady = appReady; + +if (typeof window !== "undefined") { + window.app = app; + window.appReady = appReady; + window.bitvid = window.bitvid || {}; + window.bitvid.app = app; + window.bitvid.appReady = appReady; +} diff --git a/js/watchHistoryService.js b/js/watchHistoryService.js index 6e0ca51c..94002c05 100644 --- a/js/watchHistoryService.js +++ b/js/watchHistoryService.js @@ -53,7 +53,43 @@ function resolveFlagEnabled() { } function getLoggedInActorKey() { - return normalizeActorKey(nostrClient?.pubkey); + const direct = normalizeActorKey(nostrClient?.pubkey); + if (direct) { + return direct; + } + + if (typeof window !== "undefined") { + const appCandidate = + (window.bitvid && window.bitvid.app) || window.app || 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) { + if (isDevMode) { + console.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() { diff --git a/tests/watch-history.test.mjs b/tests/watch-history.test.mjs index d8bff039..b9fae06a 100644 --- a/tests/watch-history.test.mjs +++ b/tests/watch-history.test.mjs @@ -1148,6 +1148,52 @@ async function testWatchHistorySyncEnabledForLoggedInUsers() { } } +async function testWatchHistoryAppLoginFallback() { + console.log("Running watch history app login fallback test..."); + + const actor = "f".repeat(64); + const originalPub = nostrClient.pubkey; + const originalSession = nostrClient.sessionActor; + const originalApp = typeof window.app === "undefined" ? undefined : window.app; + + try { + setWatchHistoryV2Enabled(false); + localStorage.clear(); + watchHistoryService.resetProgress(); + nostrClient.pubkey = ""; + nostrClient.sessionActor = null; + window.app = { + pubkey: actor, + normalizeHexPubkey(value) { + if (typeof value === "string" && value.trim()) { + return value.trim().toLowerCase(); + } + return null; + }, + }; + + const enabled = + typeof watchHistoryService.isEnabled === "function" + ? watchHistoryService.isEnabled(actor) + : false; + assert.equal( + enabled, + true, + "sync should be enabled when the app reports a logged-in pubkey", + ); + } finally { + setWatchHistoryV2Enabled(true); + watchHistoryService.resetProgress(); + nostrClient.pubkey = originalPub; + nostrClient.sessionActor = originalSession; + if (typeof originalApp === "undefined") { + delete window.app; + } else { + window.app = originalApp; + } + } +} + await testPublishSnapshotCanonicalizationAndChunking(); await testPublishSnapshotUsesExtensionCrypto(); await testPublishSnapshotFailureRetry(); @@ -1156,6 +1202,7 @@ await testResolveWatchHistoryBatchingWindow(); await testWatchHistoryServiceIntegration(); await testWatchHistoryLocalFallbackWhenDisabled(); await testWatchHistorySyncEnabledForLoggedInUsers(); +await testWatchHistoryAppLoginFallback(); console.log("watch-history.test.mjs completed successfully");