Guard watch-history renderer feed fallback path

This commit is contained in:
thePR0M3TH3AN committed 2026-02-08 14:48:19 -05:00
1 parent ffae61ca75
commit 7e5d1c7d63
2 files changed
+93 -16

No files matched your search

+32 -16
View File
@@ -11,7 +11,7 @@ import {
WATCH_HISTORY_BATCH_PAGE_SIZE
} from "./config.js";
import { getApplication } from "./applicationContext.js";
import { userLogger } from "./utils/logger.js";
import { devLogger, userLogger } from "./utils/logger.js";
import {
normalizeVideoModerationContext,
applyModerationContextDatasets,
@@ -1391,6 +1391,7 @@ export function buildHistoryCard({ item, video, profile, variant }) {
}
export function createWatchHistoryRenderer(config = {}) {
let hasLoggedFeedRegistrationFallback = false;
const {
container = typeof document !== "undefined" ? document : null,
fetchHistory = async (
@@ -1400,25 +1401,40 @@ export function createWatchHistoryRenderer(config = {}) {
const app = getAppInstance();
const engine = app?.feedEngine;
if (engine && typeof engine.run === "function") {
let hasFeedDefinition =
typeof engine.getFeedDefinition === "function"
? Boolean(engine.getFeedDefinition("watch-history"))
: true;
// Ensure the watch-history feed is registered. It may not be if the
// feed engine was ready but registration was silently skipped during
// bootstrap (e.g. timing edge cases).
if (
typeof engine.getFeedDefinition === "function" &&
!engine.getFeedDefinition("watch-history") &&
typeof app.registerWatchHistoryFeed === "function"
) {
app.registerWatchHistoryFeed();
if (!hasFeedDefinition && typeof app.registerWatchHistoryFeed === "function") {
try {
app.registerWatchHistoryFeed();
} catch (registerError) {
// Fall through to service fallback if registration fails.
}
hasFeedDefinition =
typeof engine.getFeedDefinition === "function"
? Boolean(engine.getFeedDefinition("watch-history"))
: hasFeedDefinition;
}
try {
const runtime = buildWatchHistoryFeedRuntime({
actor: actorInput,
cursor,
forceRefresh,
});
return await engine.run("watch-history", { runtime });
} catch (feedError) {
// Fall through to service fallback if feed is still not registered
if (hasFeedDefinition) {
try {
const runtime = buildWatchHistoryFeedRuntime({
actor: actorInput,
cursor,
forceRefresh,
});
return await engine.run("watch-history", { runtime });
} catch (feedError) {
// Fall through to service fallback if feed is still not registered
}
} else if (!hasLoggedFeedRegistrationFallback) {
hasLoggedFeedRegistrationFallback = true;
devLogger.info(
"[historyView] watch-history feed unavailable after registration attempt; using service fallback.",
);
}
}
const items = await watchHistoryService.loadLatest(actorInput, {
+61
View File
@@ -2392,6 +2392,66 @@ async function testWatchHistoryLocalFallbackWhenDisabled() {
}
}
async function testWatchHistoryRendererFeedRegistrationFallback() {
console.log("Running watch history renderer feed registration fallback test...");
const actor = "watch-history-renderer-fallback";
const originalApp = getApplication();
const originalLoadLatest = watchHistoryService.loadLatest;
const originalDocument = globalThis.document;
const originalHTMLElement = globalThis.HTMLElement;
let runCalled = false;
let loadLatestCalls = 0;
try {
globalThis.document = { querySelector: () => null };
globalThis.HTMLElement = class HTMLElement {};
watchHistoryService.loadLatest = async (actorInput) => {
loadLatestCalls += 1;
assert.equal(actorInput, actor, "fallback loader should target provided actor");
return [{ type: "e", value: "service-fallback-entry", watchedAt: 1_700_700_000 }];
};
setApplication({
feedEngine: {
run() {
runCalled = true;
throw new Error("not registered");
},
getFeedDefinition() {
return null;
},
},
registerWatchHistoryFeed() {
throw new Error("registration failed");
},
isAuthorBlocked: () => false,
getHashtagPreferences: () => ({ interests: [], disinterests: [] }),
});
const renderer = createWatchHistoryRenderer({
container: { querySelector: () => null },
getActor: async () => actor,
});
await renderer.init({ actor, force: true });
const state = renderer.getState();
assert.equal(runCalled, false, "renderer should not run unregistered feed");
assert.equal(loadLatestCalls, 1, "renderer should use service fallback once");
assert.equal(state.lastError, null, "fallback path should avoid surfacing feed registration errors");
assert.equal(state.items.length, 1, "fallback path should populate items from service");
assert.equal(state.items[0]?.pointer?.value, "service-fallback-entry");
} finally {
watchHistoryService.loadLatest = originalLoadLatest;
globalThis.document = originalDocument;
globalThis.HTMLElement = originalHTMLElement;
setApplication(originalApp || null);
}
}
async function testWatchHistorySyncEnabledForLoggedInUsers() {
console.log("Running watch history logged-in sync override test...");
@@ -2636,6 +2696,7 @@ await testWatchHistoryStaleCacheRefresh();
await testWatchHistoryLocalFallbackWhenDisabled();
await testWatchHistorySyncEnabledForLoggedInUsers();
await testWatchHistoryAppLoginFallback();
await testWatchHistoryRendererFeedRegistrationFallback();
await testNormalizeActorKeyShortCircuit();
// await testNormalizeActorKeyManualFallback(); // Flaky in env without robust nostr-tools