Remove watch history metadata caching

This commit is contained in:
thePR0M3TH3AN committed 2026-01-03 09:11:26 -05:00
1 parent 7f244e7155
commit e9ce494ccd
5 files changed
+18 -625

No files matched your search

+1 -4
View File
@@ -210,10 +210,7 @@ function createWatchHistoryHydratorStage({
const resolver = metadataResolver || createWatchHistoryMetadataResolver();
return async function watchHistoryHydratorStage(items = [], context = {}) {
const shouldStoreMetadata =
typeof service?.shouldStoreMetadata === "function"
? service.shouldStoreMetadata() !== false
: true;
const shouldStoreMetadata = false;
const results = [];
+6 -43
View File
@@ -1450,10 +1450,7 @@ export function createWatchHistoryRenderer(config = {}) {
privacyDismissed: false,
metadataCache: new Map(),
feedMetadata: null,
metadataStorageEnabled:
typeof watchHistoryService.shouldStoreMetadata === "function"
? watchHistoryService.shouldStoreMetadata() !== false
: true,
metadataStorageEnabled: false,
sessionFallbackActive: false,
syncEnabled,
localSupported,
@@ -1681,20 +1678,8 @@ export function createWatchHistoryRenderer(config = {}) {
}
function subscribeToMetadataPreference() {
if (typeof watchHistoryService.subscribe !== "function") {
return;
}
const unsubscribe = watchHistoryService.subscribe(
"metadata-preference",
(payload) => {
const enabled = payload?.enabled !== false;
state.metadataStorageEnabled = enabled;
updateMetadataToggle();
}
);
if (typeof unsubscribe === "function") {
subscriptions.add(unsubscribe);
}
state.metadataStorageEnabled = false;
updateMetadataToggle();
}
function subscribeToRepublishEvents() {
@@ -2489,23 +2474,7 @@ export function createWatchHistoryRenderer(config = {}) {
}
boundMetadataToggleHandler = (event) => {
event.preventDefault();
const nextValue = !state.metadataStorageEnabled;
try {
if (typeof watchHistoryService.setMetadataPreference === "function") {
watchHistoryService.setMetadataPreference(nextValue);
}
} catch (error) {
if (isDevEnv) {
userLogger.warn(
"[historyView] Failed to set metadata preference:",
error
);
}
}
state.metadataStorageEnabled =
typeof watchHistoryService.shouldStoreMetadata === "function"
? watchHistoryService.shouldStoreMetadata() !== false
: nextValue;
state.metadataStorageEnabled = false;
updateMetadataToggle();
if (!state.metadataStorageEnabled) {
try {
@@ -2675,10 +2644,7 @@ export function createWatchHistoryRenderer(config = {}) {
subscribeToFingerprintUpdates();
subscribeToModerationEvents();
updateInfoCallout();
state.metadataStorageEnabled =
typeof watchHistoryService.shouldStoreMetadata === "function"
? watchHistoryService.shouldStoreMetadata() !== false
: true;
state.metadataStorageEnabled = false;
updateMetadataToggle();
const storedPreference = readPreference(
WATCH_HISTORY_METADATA_PREF_KEY,
@@ -2718,10 +2684,7 @@ export function createWatchHistoryRenderer(config = {}) {
return;
}
bindMetadataToggle();
state.metadataStorageEnabled =
typeof watchHistoryService.shouldStoreMetadata === "function"
? watchHistoryService.shouldStoreMetadata() !== false
: true;
state.metadataStorageEnabled = false;
updateMetadataToggle();
},
async refresh(options = {}) {
+6 -83
View File
@@ -72,7 +72,7 @@ export default class WatchHistoryTelemetry {
? Math.max(0, viewThresholdSeconds)
: DEFAULT_VIEW_THRESHOLD_SECONDS;
this.watchHistoryMetadataEnabled = null;
this.watchHistoryMetadataEnabled = false;
this.watchHistoryPreferenceUnsubscribe = null;
this.playbackTelemetryState = null;
this.loggedViewPointerKeys = new Set();
@@ -81,98 +81,21 @@ export default class WatchHistoryTelemetry {
destroy() {
this.resetPlaybackLoggingState();
this._clearPreferenceSubscription();
this.watchHistoryMetadataEnabled = null;
this.watchHistoryMetadataEnabled = false;
}
async initPreferenceSync() {
if (!this.watchHistoryService?.isEnabled?.()) {
this.watchHistoryMetadataEnabled = false;
return null;
}
this.refreshPreferenceSettings();
if (
typeof this.watchHistoryService.subscribe === "function" &&
!this.watchHistoryPreferenceUnsubscribe
) {
try {
const unsubscribe = this.watchHistoryService.subscribe(
"metadata-preference",
(payload) => {
const previous = this.watchHistoryMetadataEnabled;
const enabled = payload?.enabled !== false;
this.watchHistoryMetadataEnabled = enabled;
if (previous === true && enabled === false) {
try {
this.watchHistoryService?.clearLocalMetadata?.();
} catch (error) {
devLogger.warn(
"[watchHistoryTelemetry] Failed to clear cached metadata after toggle off:",
error,
);
}
}
},
);
if (typeof unsubscribe === "function") {
this.watchHistoryPreferenceUnsubscribe = unsubscribe;
}
} catch (error) {
devLogger.warn(
"[watchHistoryTelemetry] Failed to subscribe to metadata preference changes:",
error,
);
}
}
return this.watchHistoryPreferenceUnsubscribe || null;
this.watchHistoryMetadataEnabled = false;
return null;
}
refreshPreferenceSettings() {
if (!this.watchHistoryService?.isEnabled?.()) {
this.watchHistoryMetadataEnabled = false;
return this.watchHistoryMetadataEnabled;
}
const previous = this.watchHistoryMetadataEnabled;
let enabled = true;
try {
if (typeof this.watchHistoryService.getSettings === "function") {
const settings = this.watchHistoryService.getSettings();
enabled = settings?.metadata?.storeLocally !== false;
} else if (
typeof this.watchHistoryService.shouldStoreMetadata === "function"
) {
enabled = this.watchHistoryService.shouldStoreMetadata() !== false;
}
} catch (error) {
devLogger.warn(
"[watchHistoryTelemetry] Failed to read metadata settings:",
error,
);
enabled = true;
}
this.watchHistoryMetadataEnabled = enabled;
if (enabled === false && previous !== false) {
try {
this.watchHistoryService?.clearLocalMetadata?.();
} catch (error) {
devLogger.warn(
"[watchHistoryTelemetry] Failed to purge metadata cache while preference disabled:",
error,
);
}
}
this.watchHistoryMetadataEnabled = false;
return this.watchHistoryMetadataEnabled;
}
isMetadataPreferenceEnabled() {
return this.watchHistoryMetadataEnabled !== false;
return false;
}
persistMetadataForVideo(video, pointerInfo) {
+5 -393
View File
@@ -24,21 +24,12 @@ import { devLogger, userLogger } from "./utils/logger.js";
const SESSION_STORAGE_KEY = "bitvid:watch-history:session:v1";
const SESSION_STORAGE_VERSION = 1;
const POINTER_THROTTLE_MS = 60 * 1000;
const METADATA_STORAGE_KEY = "bitvid:watch-history:metadata-cache:v1";
const METADATA_STORAGE_VERSION = 1;
const METADATA_PREFERENCE_KEY = "bitvid:watch-history:metadata:store-locally";
const state = {
restored: false,
queues: new Map(),
inflightSnapshots: new Map(),
fingerprintCache: new Map(),
listeners: new Map(),
metadata: {
restored: false,
cache: new Map(),
preference: null,
},
};
function resolveFlagEnabled() {
@@ -234,345 +225,6 @@ function getLocalStorage() {
return null;
}
function ensureMetadataPreference() {
if (typeof state.metadata.preference === "boolean") {
return state.metadata.preference;
}
const storage = getLocalStorage();
if (!storage) {
state.metadata.preference = true;
return state.metadata.preference;
}
try {
const stored = storage.getItem(METADATA_PREFERENCE_KEY);
if (stored === null) {
state.metadata.preference = true;
} else {
state.metadata.preference = stored === "true";
}
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to read metadata preference:",
error,
);
state.metadata.preference = true;
}
return state.metadata.preference;
}
function persistMetadataPreference(value) {
const storage = getLocalStorage();
if (!storage) {
return;
}
try {
if (value === true) {
storage.setItem(METADATA_PREFERENCE_KEY, "true");
} else {
storage.setItem(METADATA_PREFERENCE_KEY, "false");
}
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to persist metadata preference:",
error,
);
}
}
function sanitizeVideoForStorage(video) {
if (!video || typeof video !== "object") {
return null;
}
const createdAt = Number.isFinite(video.created_at)
? Math.floor(Number(video.created_at))
: null;
return {
id: typeof video.id === "string" ? video.id : "",
title: typeof video.title === "string" ? video.title : "",
thumbnail: typeof video.thumbnail === "string" ? video.thumbnail : "",
url: typeof video.url === "string" ? video.url : "",
magnet: typeof video.magnet === "string" ? video.magnet : "",
pubkey: typeof video.pubkey === "string" ? video.pubkey : "",
created_at: createdAt,
infoHash: typeof video.infoHash === "string" ? video.infoHash : "",
legacyInfoHash:
typeof video.legacyInfoHash === "string" ? video.legacyInfoHash : "",
mode: typeof video.mode === "string" ? video.mode : "",
isPrivate: video?.isPrivate === true,
description:
typeof video.description === "string" ? video.description : "",
};
}
function sanitizeVideoForHistory(video) {
const sanitized = sanitizeVideoForStorage(video);
if (!sanitized) {
return null;
}
return {
id: sanitized.id,
title: sanitized.title,
thumbnail: sanitized.thumbnail,
pubkey: sanitized.pubkey,
created_at: sanitized.created_at,
url: sanitized.url,
magnet: sanitized.magnet,
infoHash: sanitized.infoHash,
legacyInfoHash:
typeof video?.legacyInfoHash === "string"
? video.legacyInfoHash
: typeof sanitized.legacyInfoHash === "string"
? sanitized.legacyInfoHash
: "",
};
}
function sanitizeProfileForStorage(profile) {
if (!profile || typeof profile !== "object") {
return null;
}
return {
pubkey: typeof profile.pubkey === "string" ? profile.pubkey : "",
name: typeof profile.name === "string" ? profile.name : "",
display_name:
typeof profile.display_name === "string" ? profile.display_name : "",
picture: typeof profile.picture === "string" ? profile.picture : "",
nip05: typeof profile.nip05 === "string" ? profile.nip05 : "",
};
}
function restoreMetadataCache() {
if (state.metadata.restored) {
return;
}
state.metadata.restored = true;
const storage = getLocalStorage();
if (!storage) {
return;
}
let raw = null;
try {
raw = storage.getItem(METADATA_STORAGE_KEY);
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to read metadata cache:",
error,
);
return;
}
if (!raw || typeof raw !== "string") {
return;
}
let parsed = null;
try {
parsed = JSON.parse(raw);
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to parse metadata cache:",
error,
);
try {
storage.removeItem(METADATA_STORAGE_KEY);
} catch (cleanupError) {
devLogger.warn(
"[watchHistoryService] Failed to clear corrupt metadata cache:",
cleanupError,
);
}
return;
}
if (!parsed || parsed.version !== METADATA_STORAGE_VERSION) {
try {
storage.removeItem(METADATA_STORAGE_KEY);
} catch (cleanupError) {
devLogger.warn(
"[watchHistoryService] Failed to clear outdated metadata cache:",
cleanupError,
);
}
return;
}
state.metadata.cache.clear();
const entries = parsed.entries && typeof parsed.entries === "object"
? parsed.entries
: {};
for (const [key, entry] of Object.entries(entries)) {
if (typeof key !== "string" || !key) {
continue;
}
const sanitizedVideo = sanitizeVideoForStorage(entry?.video);
const sanitizedProfile = sanitizeProfileForStorage(entry?.profile);
state.metadata.cache.set(key, {
video: sanitizedVideo,
profile: sanitizedProfile,
storedAt: Number.isFinite(entry?.storedAt) ? entry.storedAt : Date.now(),
});
}
}
function sanitizePointerMetadata(metadata) {
if (!metadata || typeof metadata !== "object") {
return null;
}
const pointerMetadata = {};
const video = sanitizeVideoForHistory(metadata.video);
if (video) {
pointerMetadata.video = video;
}
const profile = sanitizeProfileForStorage(metadata.profile);
if (profile) {
pointerMetadata.profile = profile;
}
const resumeCandidates = [
metadata.resumeAt,
metadata.resume,
metadata.resumeSeconds,
];
for (const candidate of resumeCandidates) {
if (Number.isFinite(candidate)) {
pointerMetadata.resumeAt = Math.max(0, Math.floor(candidate));
break;
}
}
if (metadata.completed === true) {
pointerMetadata.completed = true;
}
return Object.keys(pointerMetadata).length ? pointerMetadata : null;
}
function persistMetadataCache() {
const storage = getLocalStorage();
if (!storage) {
return;
}
const shouldStore = shouldStoreMetadataLocally();
if (!shouldStore || !state.metadata.cache.size) {
try {
storage.removeItem(METADATA_STORAGE_KEY);
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to clear metadata cache:",
error,
);
}
return;
}
const payload = { version: METADATA_STORAGE_VERSION, entries: {} };
for (const [key, entry] of state.metadata.cache.entries()) {
payload.entries[key] = {
video: sanitizeVideoForStorage(entry?.video) || null,
profile: sanitizeProfileForStorage(entry?.profile) || null,
storedAt: Number.isFinite(entry?.storedAt) ? entry.storedAt : Date.now(),
};
}
try {
storage.setItem(METADATA_STORAGE_KEY, JSON.stringify(payload));
} catch (error) {
devLogger.warn(
"[watchHistoryService] Failed to persist metadata cache:",
error,
);
}
}
function shouldStoreMetadataLocally() {
return ensureMetadataPreference() === true;
}
function setMetadataPreference(enabled) {
const normalized = enabled === false ? false : true;
const previous = ensureMetadataPreference();
state.metadata.preference = normalized;
persistMetadataPreference(normalized);
if (!normalized) {
state.metadata.cache.clear();
persistMetadataCache();
} else {
persistMetadataCache();
}
if (previous !== normalized) {
emit("metadata-preference", { enabled: normalized });
}
}
function getMetadataPreference() {
return ensureMetadataPreference();
}
function getLocalMetadata(pointerKeyValue) {
restoreMetadataCache();
const key = typeof pointerKeyValue === "string" ? pointerKeyValue : "";
if (!key) {
return null;
}
const stored = state.metadata.cache.get(key);
if (!stored) {
return null;
}
return {
video: stored.video ? { ...stored.video } : null,
profile: stored.profile ? { ...stored.profile } : null,
storedAt: stored.storedAt,
};
}
function setLocalMetadata(pointerKeyValue, metadata) {
if (!shouldStoreMetadataLocally()) {
return;
}
const key = typeof pointerKeyValue === "string" ? pointerKeyValue : "";
if (!key) {
return;
}
restoreMetadataCache();
if (!metadata || typeof metadata !== "object") {
state.metadata.cache.delete(key);
persistMetadataCache();
return;
}
const video = sanitizeVideoForStorage(metadata.video);
const profile = sanitizeProfileForStorage(metadata.profile);
state.metadata.cache.set(key, {
video,
profile,
storedAt: Date.now(),
});
persistMetadataCache();
}
function removeLocalMetadata(pointerKeyValue) {
const key = typeof pointerKeyValue === "string" ? pointerKeyValue : "";
if (!key) {
return;
}
restoreMetadataCache();
if (state.metadata.cache.delete(key)) {
persistMetadataCache();
}
}
function clearLocalMetadata() {
restoreMetadataCache();
if (!state.metadata.cache.size) {
return;
}
state.metadata.cache.clear();
persistMetadataCache();
}
function emit(eventName, payload) {
const listeners = state.listeners.get(eventName);
if (listeners && listeners.size) {
@@ -1133,32 +785,11 @@ async function publishView(pointerInput, createdAt, metadata = {}) {
viewResult?.event?.created_at
);
const pointerMetadata = sanitizePointerMetadata(metadata);
if (pointerMetadata) {
const existingMetadata =
typeof normalizedPointer.metadata === "object"
? normalizedPointer.metadata
: {};
normalizedPointer.metadata = { ...existingMetadata, ...pointerMetadata };
if (pointerMetadata.video) {
normalizedPointer.video = pointerMetadata.video;
}
if (pointerMetadata.profile) {
normalizedPointer.profile = pointerMetadata.profile;
}
if (
Number.isFinite(pointerMetadata.resumeAt) &&
!Number.isFinite(normalizedPointer.resumeAt)
) {
normalizedPointer.resumeAt = Math.max(
0,
Math.floor(pointerMetadata.resumeAt),
);
}
if (pointerMetadata.completed === true) {
normalizedPointer.completed = true;
}
}
delete normalizedPointer.metadata;
delete normalizedPointer.video;
delete normalizedPointer.profile;
delete normalizedPointer.resumeAt;
delete normalizedPointer.completed;
const key = pointerKey(normalizedPointer);
devLogger.info(
@@ -1608,17 +1239,6 @@ function getAllQueues() {
return summary;
}
function getSettings() {
const preference = getMetadataPreference();
return {
metadata: {
preference,
storeLocally: preference === true,
cacheSize: state.metadata.cache.size,
},
};
}
const watchHistoryService = {
isEnabled: isFeatureEnabled,
supportsLocalHistory,
@@ -1630,14 +1250,6 @@ const watchHistoryService = {
resetProgress,
getQueuedPointers,
getAllQueues,
getMetadataPreference,
setMetadataPreference,
shouldStoreMetadata: shouldStoreMetadataLocally,
getLocalMetadata,
setLocalMetadata,
removeLocalMetadata,
clearLocalMetadata,
getSettings,
subscribe,
};
-102
View File
@@ -1293,105 +1293,3 @@ test('history pane lazily initializes the watch history renderer', async () => {
assert.equal(controller.boundProfileHistoryVisibility, null);
});
test('history metadata toggle updates stored preference', async (t) => {
let storedPreference = true;
let clearCalls = 0;
const originalSetPreference = watchHistoryService.setMetadataPreference;
const originalShouldStore = watchHistoryService.shouldStoreMetadata;
const originalClearMetadata = watchHistoryService.clearLocalMetadata;
const originalSubscribe = watchHistoryService.subscribe;
const originalIsEnabled = watchHistoryService.isEnabled;
const originalSupportsLocal = watchHistoryService.supportsLocalHistory;
const originalIsLocalOnly = watchHistoryService.isLocalOnly;
watchHistoryService.setMetadataPreference = (value) => {
storedPreference = value !== false;
};
watchHistoryService.shouldStoreMetadata = () => storedPreference;
watchHistoryService.clearLocalMetadata = () => {
clearCalls += 1;
};
watchHistoryService.subscribe = () => () => {};
watchHistoryService.isEnabled = () => true;
watchHistoryService.supportsLocalHistory = () => true;
watchHistoryService.isLocalOnly = () => false;
t.after(() => {
watchHistoryService.setMetadataPreference = originalSetPreference;
watchHistoryService.shouldStoreMetadata = originalShouldStore;
watchHistoryService.clearLocalMetadata = originalClearMetadata;
watchHistoryService.subscribe = originalSubscribe;
watchHistoryService.isEnabled = originalIsEnabled;
watchHistoryService.supportsLocalHistory = originalSupportsLocal;
watchHistoryService.isLocalOnly = originalIsLocalOnly;
});
const controller = createController({
createWatchHistoryRenderer: (config) =>
createWatchHistoryRenderer({
...config,
fetchHistory: async () => ({ items: [], metadata: {} }),
snapshot: async () => ({}),
}),
services: {
nostrClient: { sessionActor: { pubkey: defaultActorHex } },
getCurrentUserNpub: () => defaultActorNpub,
},
});
await controller.load();
applyDesignSystemAttributes(document);
controller.refreshAdminPaneState = async () => {};
controller.populateProfileRelays = () => {};
controller.populateBlockedList = () => {};
controller.refreshWalletPaneState = () => {};
controller.setActivePubkey(defaultActorHex);
let cleanupRan = false;
const cleanup = () => {
try {
controller.hide({ silent: true });
} catch {}
cleanupRan = true;
};
try {
await controller.show('history');
await waitForAnimationFrame(window, 3);
const toggle = document.getElementById('profileHistoryMetadataToggle');
assert.ok(toggle);
assert.equal(toggle.getAttribute('aria-checked'), 'true');
assert.equal(toggle.getAttribute('data-enabled'), 'true');
toggle.dispatchEvent(
new window.MouseEvent('click', { bubbles: true, cancelable: true }),
);
await waitForAnimationFrame(window, 2);
assert.equal(storedPreference, false);
assert.equal(toggle.getAttribute('aria-checked'), 'false');
assert.equal(toggle.getAttribute('data-enabled'), 'false');
assert.equal(clearCalls, 1);
toggle.dispatchEvent(
new window.MouseEvent('click', { bubbles: true, cancelable: true }),
);
await waitForAnimationFrame(window, 2);
assert.equal(storedPreference, true);
assert.equal(toggle.getAttribute('aria-checked'), 'true');
assert.equal(toggle.getAttribute('data-enabled'), 'true');
} finally {
cleanup();
}
t.after(() => {
if (!cleanupRan) {
cleanup();
}
});
});