Enable runtime watch history debug logging

This commit is contained in:
thePR0M3TH3AN committed 2025-10-09 16:18:31 -04:00
1 parent 3792b218a9
commit 614ff3b1f1
4 files changed
+396 -20

No files matched your search

+33 -1
View File
@@ -42,6 +42,7 @@ import WatchHistoryController from "./ui/watchHistoryController.js";
import WatchHistoryTelemetry from "./services/watchHistoryTelemetry.js";
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
import { subscriptions } from "./subscriptions.js";
import { isWatchHistoryDebugEnabled } from "./watchHistoryDebug.js";
import {
initViewCounter,
subscribeToVideoViewCount,
@@ -186,7 +187,38 @@ class Application {
this.nostrService = services.nostrService || nostrService;
this.r2Service = services.r2Service || r2Service;
this.feedEngine = services.feedEngine || createFeedEngine();
const feedEngineLogger = (...args) => {
if (!isWatchHistoryDebugEnabled()) {
return;
}
if (!Array.isArray(args) || args.length === 0) {
return;
}
const [prefix, ...rest] = args;
if (typeof prefix === "string" && !prefix.includes("watch-history")) {
return;
}
try {
if (rest.length) {
console.info(prefix, ...rest);
} else {
console.info(prefix);
}
} catch (error) {
if (rest.length) {
console.log(prefix, ...rest);
} else {
console.log(prefix);
}
}
};
this.feedEngine =
services.feedEngine ||
createFeedEngine({
logger: feedEngineLogger,
});
this.payments = services.payments || null;
this.splitAndZap =
(services.payments && services.payments.splitAndZap) ||
+157
View File
@@ -4,12 +4,26 @@ import { pointerKey, normalizePointerInput } from "../nostr.js";
import watchHistoryService from "../watchHistoryService.js";
import nostrService from "../services/nostrService.js";
import { createWatchHistoryMetadataResolver } from "../watchHistoryMetadata.js";
import {
isWatchHistoryDebugEnabled,
logWatchHistoryDebug,
} from "../watchHistoryDebug.js";
import {
createBlacklistFilterStage,
createWatchHistorySuppressionStage,
} from "./stages.js";
import { isPlainObject } from "./utils.js";
const WATCH_HISTORY_FEED_LOG_NAMESPACE = "watchHistoryFeed";
function debugInfo(message, details) {
logWatchHistoryDebug(WATCH_HISTORY_FEED_LOG_NAMESPACE, "info", message, details);
}
function debugWarn(message, details) {
logWatchHistoryDebug(WATCH_HISTORY_FEED_LOG_NAMESPACE, "warn", message, details);
}
function normalizeActorCandidate(...values) {
for (const value of values) {
if (typeof value === "string") {
@@ -213,6 +227,11 @@ function createWatchHistoryHydratorStage({
? item.pointerKey
: pointerKey(item.pointer);
const pointerType =
typeof item?.pointer?.type === "string" ? item.pointer.type : null;
const pointerValue =
typeof item?.pointer?.value === "string" ? item.pointer.value : null;
const watchedAtValue = Number.isFinite(item?.watchedAt)
? Math.max(0, Math.floor(Number(item.watchedAt)))
: Number.isFinite(item?.metadata?.watchedAt)
@@ -222,40 +241,133 @@ function createWatchHistoryHydratorStage({
let video = item.video || null;
let profile = item.metadata?.profile || null;
if (isWatchHistoryDebugEnabled()) {
debugInfo("Hydrating watch history item.", {
pointerKey: pointerKeyValue || null,
pointerType,
pointerValue,
hasInitialVideo: Boolean(video),
hasInitialProfile: Boolean(profile),
watchedAt: watchedAtValue || null,
});
}
if (pointerKeyValue && typeof service?.getLocalMetadata === "function") {
try {
const stored = service.getLocalMetadata(pointerKeyValue);
if (stored) {
const hadVideo = Boolean(video);
const hadProfile = Boolean(profile);
video = video || stored.video || null;
profile = profile || stored.profile || null;
if (isWatchHistoryDebugEnabled()) {
debugInfo("Loaded cached metadata for pointer.", {
pointerKey: pointerKeyValue,
hadVideo,
hadProfile,
cachedVideo: Boolean(stored.video),
cachedProfile: Boolean(stored.profile),
resultingVideo: Boolean(video),
resultingProfile: Boolean(profile),
});
}
}
} catch (error) {
context?.log?.(
"[watch-history-feed] Failed to read cached metadata",
error,
);
if (isWatchHistoryDebugEnabled()) {
debugWarn("Failed to read cached metadata for pointer.", {
pointerKey: pointerKeyValue || null,
error,
});
}
}
}
if (!video && resolver?.resolveVideo) {
if (isWatchHistoryDebugEnabled()) {
debugInfo("Resolving video via metadata resolver.", {
pointerKey: pointerKeyValue || null,
pointerType,
pointerValue,
});
}
try {
video = await resolver.resolveVideo(item.pointer);
if (isWatchHistoryDebugEnabled()) {
if (video) {
debugInfo("Metadata resolver returned video candidate.", {
pointerKey: pointerKeyValue || null,
videoId: typeof video.id === "string" ? video.id : null,
title:
typeof video.title === "string" && video.title.trim()
? video.title
: null,
invalid: Boolean(video.invalid),
});
} else {
debugWarn("Metadata resolver returned no video candidate.", {
pointerKey: pointerKeyValue || null,
});
}
}
} catch (error) {
context?.log?.(
"[watch-history-feed] Failed to resolve video from pointer",
error,
);
if (isWatchHistoryDebugEnabled()) {
debugWarn("Metadata resolver threw while resolving video.", {
pointerKey: pointerKeyValue || null,
error,
});
}
}
}
if (video?.pubkey && !profile && resolver?.resolveProfile) {
if (isWatchHistoryDebugEnabled()) {
debugInfo("Resolving profile for video author.", {
pointerKey: pointerKeyValue || null,
pubkey: video.pubkey,
});
}
try {
profile = resolver.resolveProfile(video.pubkey) || null;
if (isWatchHistoryDebugEnabled()) {
if (profile) {
const label =
profile.display_name ||
profile.name ||
profile.username ||
profile.nip05 ||
null;
debugInfo("Resolved profile for video author.", {
pointerKey: pointerKeyValue || null,
pubkey: video.pubkey,
label: label || null,
});
} else {
debugWarn("Resolver returned no profile for video author.", {
pointerKey: pointerKeyValue || null,
pubkey: video.pubkey,
});
}
}
} catch (error) {
context?.log?.(
"[watch-history-feed] Failed to resolve profile for pointer",
error,
);
if (isWatchHistoryDebugEnabled()) {
debugWarn("Profile resolver threw while hydrating metadata.", {
pointerKey: pointerKeyValue || null,
pubkey: video.pubkey,
error,
});
}
}
}
@@ -270,6 +382,27 @@ function createWatchHistoryHydratorStage({
profile: profile || null,
};
if (isWatchHistoryDebugEnabled()) {
const summary = {
pointerKey: pointerKeyValue || null,
pointerType,
pointerValue,
watchedAt: watchedAtValue || null,
videoId: typeof video?.id === "string" ? video.id : null,
title:
typeof video?.title === "string" && video.title?.trim()
? video.title
: null,
invalid: Boolean(video?.invalid),
hasProfile: Boolean(profile),
};
if (video) {
debugInfo("Hydrated watch history entry metadata.", summary);
} else {
debugWarn("Hydrated entry is missing resolved video metadata.", summary);
}
}
const nextItem = {
...item,
pointerKey: pointerKeyValue,
@@ -286,20 +419,44 @@ function createWatchHistoryHydratorStage({
video: metadata.video,
profile: metadata.profile,
});
if (isWatchHistoryDebugEnabled()) {
debugInfo("Stored metadata cache entry for pointer.", {
pointerKey: pointerKeyValue,
cachedVideo: Boolean(metadata.video),
cachedProfile: Boolean(metadata.profile),
});
}
} catch (error) {
context?.log?.(
"[watch-history-feed] Failed to persist metadata cache",
error,
);
if (isWatchHistoryDebugEnabled()) {
debugWarn("Failed to persist metadata cache entry.", {
pointerKey: pointerKeyValue,
error,
});
}
}
} else if (typeof service?.removeLocalMetadata === "function") {
try {
service.removeLocalMetadata(pointerKeyValue);
if (isWatchHistoryDebugEnabled()) {
debugInfo("Cleared metadata cache entry for pointer.", {
pointerKey: pointerKeyValue,
});
}
} catch (error) {
context?.log?.(
"[watch-history-feed] Failed to clear cached metadata",
error,
);
if (isWatchHistoryDebugEnabled()) {
debugWarn("Failed to clear cached metadata entry.", {
pointerKey: pointerKeyValue,
error,
});
}
}
}
}
+203
View File
@@ -0,0 +1,203 @@
// js/watchHistoryDebug.js
const WATCH_HISTORY_DEBUG_STORAGE_KEY =
"bitvid:debug:watch-history";
const globalScope =
typeof globalThis !== "undefined"
? globalThis
: typeof window !== "undefined"
? window
: typeof self !== "undefined"
? self
: null;
const TRUTHY_VALUES = new Set([
"1",
"true",
"yes",
"on",
"enable",
"enabled",
"debug",
"verbose",
"watch",
]);
const FALSY_VALUES = new Set([
"0",
"false",
"no",
"off",
"disable",
"disabled",
"quiet",
"silent",
]);
function parseFlagValue(value) {
if (value === null || value === undefined) {
return true;
}
if (typeof value === "boolean") {
return value;
}
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().toLowerCase();
if (!normalized) {
return true;
}
if (TRUTHY_VALUES.has(normalized)) {
return true;
}
if (FALSY_VALUES.has(normalized)) {
return false;
}
return null;
}
function safeReadStorageFlag(storage) {
if (!storage || typeof storage.getItem !== "function") {
return null;
}
try {
const raw = storage.getItem(WATCH_HISTORY_DEBUG_STORAGE_KEY);
if (raw === null || raw === undefined) {
return null;
}
return parseFlagValue(String(raw));
} catch (error) {
return null;
}
}
function safeWriteStorageFlag(storage, value) {
if (!storage || typeof storage.setItem !== "function") {
return;
}
try {
storage.setItem(WATCH_HISTORY_DEBUG_STORAGE_KEY, value ? "true" : "false");
} catch (error) {
// Ignore storage errors (e.g., quota exceeded, private mode restrictions).
}
}
function persistFlag(scope, value) {
if (value === null) {
return;
}
safeWriteStorageFlag(scope?.sessionStorage, value);
safeWriteStorageFlag(scope?.localStorage, value);
}
function readFlagFromUrl(scope) {
if (!scope || typeof scope.location?.search !== "string") {
return null;
}
const search = scope.location.search;
if (!search) {
return null;
}
try {
const params = new URLSearchParams(search);
if (!params.has("watchHistoryDebug")) {
return null;
}
const parsed = parseFlagValue(params.get("watchHistoryDebug"));
if (parsed === null) {
return null;
}
persistFlag(scope, parsed);
return parsed;
} catch (error) {
return null;
}
}
function resolveExplicitFlag(scope) {
if (!scope) {
return null;
}
const runtimeFlag = scope.__BITVID_ENABLE_WATCH_HISTORY_DEBUG__;
if (runtimeFlag === true || runtimeFlag === false) {
return Boolean(runtimeFlag);
}
const altRuntimeFlag = scope.BITVID_WATCH_HISTORY_DEBUG;
if (altRuntimeFlag === true || altRuntimeFlag === false) {
return Boolean(altRuntimeFlag);
}
const urlFlag = readFlagFromUrl(scope);
if (urlFlag !== null) {
return urlFlag;
}
const sessionFlag = safeReadStorageFlag(scope.sessionStorage);
if (sessionFlag !== null) {
return sessionFlag;
}
const localFlag = safeReadStorageFlag(scope.localStorage);
if (localFlag !== null) {
return localFlag;
}
return null;
}
export function isWatchHistoryDebugEnabled() {
const scope = globalScope;
const explicitFlag = resolveExplicitFlag(scope);
if (explicitFlag !== null) {
return explicitFlag;
}
if (typeof process !== "undefined" && process?.env?.NODE_ENV) {
return process.env.NODE_ENV !== "production";
}
return false;
}
export function logWatchHistoryDebug(namespace, level, message, details) {
if (!isWatchHistoryDebugEnabled()) {
return;
}
const scopeConsole =
(globalScope && globalScope.console) || (typeof console !== "undefined" ? console : null);
if (!scopeConsole) {
return;
}
const methodName =
typeof level === "string" && level && typeof scopeConsole[level] === "function"
? level
: typeof scopeConsole.info === "function"
? "info"
: "log";
const prefix = namespace ? `[${namespace}] ${message}` : message;
try {
if (details && typeof details === "object") {
scopeConsole[methodName](prefix, details);
} else {
scopeConsole[methodName](prefix);
}
} catch (error) {
try {
if (details && typeof details === "object") {
scopeConsole.log(prefix, details);
} else {
scopeConsole.log(prefix);
}
} catch (fallbackError) {
// Swallow logging errors entirely.
}
}
}
+3 -19
View File
@@ -3,30 +3,14 @@
import { getApplication } from "./applicationContext.js";
import nostrService from "./services/nostrService.js";
import { nostrClient } from "./nostr.js";
const isDevEnv =
typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
import { logWatchHistoryDebug } from "./watchHistoryDebug.js";
function logInfo(message, details) {
if (!isDevEnv) {
return;
}
if (details && typeof details === "object") {
console.info(`[watchHistoryMetadata] ${message}`, details);
} else {
console.info(`[watchHistoryMetadata] ${message}`);
}
logWatchHistoryDebug("watchHistoryMetadata", "info", message, details);
}
function logWarn(message, details) {
if (!isDevEnv) {
return;
}
if (details && typeof details === "object") {
console.warn(`[watchHistoryMetadata] ${message}`, details);
} else {
console.warn(`[watchHistoryMetadata] ${message}`);
}
logWatchHistoryDebug("watchHistoryMetadata", "warn", message, details);
}
function normalizeVideoCandidate(candidate) {