Refactor feed loading logic into shared loadFeedVideos method

- Introduce `loadFeedVideos(feedType, forceFetch)` in `js/app/feedCoordinator.js` to consolidate video loading logic.
- Update `loadForYouVideos`, `loadKidsVideos`, `loadExploreVideos`, and `loadVideos` to use the shared method.
- Parameterize feed-specific configuration (telemetry context, tags, loading messages, relay health checks) based on feed type.
- Add `tests/app/feedCoordinator.test.mjs` to verify the new logic.

Signed-off-by: Jules <[email protected]>

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-07 23:15:56 +00:00
1 parent c539fef553
commit afd64ba1ce
2 files changed
+183 -182

No files matched your search

+94 -182
View File
@@ -1085,6 +1085,96 @@ export function createFeedCoordinator(deps) {
return true;
},
/**
* Unified loader for all feed types.
*/
async loadFeedVideos(feedType, forceFetch = false) {
devLogger.log(`Starting loadFeedVideos(${feedType})... (forceFetch =`, forceFetch, ")");
this.setFeedTelemetryContext(feedType);
let includeTags = false;
let loadingMessage = "Fetching videos\u2026";
let refreshMethod = null;
let shouldCheckRelayHealth = false;
switch (feedType) {
case "recent":
includeTags = true;
loadingMessage = "Fetching recent videos\u2026";
refreshMethod = (opts) => this.refreshRecentFeed(opts);
shouldCheckRelayHealth = true;
break;
case FEED_TYPES.FOR_YOU:
includeTags = false;
loadingMessage = "Fetching for-you videos\u2026";
refreshMethod = (opts) => this.refreshForYouFeed(opts);
shouldCheckRelayHealth = true;
break;
case FEED_TYPES.KIDS:
includeTags = true;
loadingMessage = "Fetching kids videos\u2026";
refreshMethod = (opts) => this.refreshKidsFeed(opts);
shouldCheckRelayHealth = false;
break;
case FEED_TYPES.EXPLORE:
includeTags = false;
loadingMessage = "Fetching explore videos\u2026";
refreshMethod = (opts) => this.refreshExploreFeed(opts);
shouldCheckRelayHealth = false;
break;
default:
devLogger.warn(`[Application] Unknown feed type for loadFeedVideos: ${feedType}`);
return;
}
if (shouldCheckRelayHealth) {
this.checkRelayHealthWarning();
}
const container = this.mountVideoListView({ includeTags });
const hasCachedVideos =
this.nostrService &&
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
this.nostrService.getFilteredActiveVideos().length > 0;
if (!hasCachedVideos) {
if (this.videoListView && container) {
this.videoListView.showLoading(loadingMessage);
} else if (container) {
container.innerHTML = getSidebarLoadingMarkup(loadingMessage);
}
}
let initialRefreshPromise = null;
const videos = await this.nostrService.loadVideos({
forceFetch,
blacklistedEventIds: this.blacklistedEventIds,
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
onVideos: (payload, detail = {}) => {
const promise = refreshMethod({
reason: detail?.reason,
fallbackVideos: payload,
});
if (!initialRefreshPromise) {
initialRefreshPromise = promise;
}
},
});
if (initialRefreshPromise) {
await initialRefreshPromise;
} else if (!Array.isArray(videos) || videos.length === 0) {
await refreshMethod({ reason: "initial", fallbackVideos: [] });
}
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
this.videosMap = this.nostrService.getVideosMap();
if (this.videoListView) {
this.videoListView.state.videosMap = this.videosMap;
}
},
/**
* Subscribe to videos (older + new) and render them as they come in.
*/
@@ -1102,52 +1192,7 @@ export function createFeedCoordinator(deps) {
this.lastLoadVideosTime = now;
this.loadVideosPromise = (async () => {
devLogger.log("Starting loadVideos... (forceFetch =", forceFetch, ")");
this.setFeedTelemetryContext("recent");
this.checkRelayHealthWarning();
const container = this.mountVideoListView();
const hasCachedVideos =
this.nostrService &&
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
this.nostrService.getFilteredActiveVideos().length > 0;
if (!hasCachedVideos) {
if (this.videoListView && container) {
this.videoListView.showLoading("Fetching recent videos\u2026");
} else if (container) {
container.innerHTML = getSidebarLoadingMarkup("Fetching recent videos\u2026");
}
}
let initialRefreshPromise = null;
const videos = await this.nostrService.loadVideos({
forceFetch,
blacklistedEventIds: this.blacklistedEventIds,
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
onVideos: (payload, detail = {}) => {
const promise = this.refreshRecentFeed({
reason: detail?.reason,
fallbackVideos: payload,
});
if (!initialRefreshPromise) {
initialRefreshPromise = promise;
}
},
});
if (initialRefreshPromise) {
await initialRefreshPromise;
} else if (!Array.isArray(videos) || videos.length === 0) {
await this.refreshRecentFeed({ reason: "initial", fallbackVideos: [] });
}
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
this.videosMap = this.nostrService.getVideosMap();
if (this.videoListView) {
this.videoListView.state.videosMap = this.videosMap;
}
await this.loadFeedVideos("recent", forceFetch);
})();
try {
@@ -1158,148 +1203,15 @@ export function createFeedCoordinator(deps) {
},
async loadForYouVideos(forceFetch = false) {
devLogger.log("Starting loadForYouVideos... (forceFetch =", forceFetch, ")");
this.setFeedTelemetryContext(FEED_TYPES.FOR_YOU);
this.checkRelayHealthWarning();
const container = this.mountVideoListView({ includeTags: false });
const hasCachedVideos =
this.nostrService &&
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
this.nostrService.getFilteredActiveVideos().length > 0;
if (!hasCachedVideos) {
if (this.videoListView && container) {
this.videoListView.showLoading("Fetching for-you videos\u2026");
} else if (container) {
container.innerHTML = getSidebarLoadingMarkup("Fetching for-you videos\u2026");
}
}
let initialRefreshPromise = null;
const videos = await this.nostrService.loadVideos({
forceFetch,
blacklistedEventIds: this.blacklistedEventIds,
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
onVideos: (payload, detail = {}) => {
const promise = this.refreshForYouFeed({
reason: detail?.reason,
fallbackVideos: payload,
});
if (!initialRefreshPromise) {
initialRefreshPromise = promise;
}
},
});
if (initialRefreshPromise) {
await initialRefreshPromise;
} else if (!Array.isArray(videos) || videos.length === 0) {
await this.refreshForYouFeed({ reason: "initial", fallbackVideos: [] });
}
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
this.videosMap = this.nostrService.getVideosMap();
if (this.videoListView) {
this.videoListView.state.videosMap = this.videosMap;
}
return this.loadFeedVideos(FEED_TYPES.FOR_YOU, forceFetch);
},
async loadKidsVideos(forceFetch = false) {
devLogger.log("Starting loadKidsVideos... (forceFetch =", forceFetch, ")");
this.setFeedTelemetryContext(FEED_TYPES.KIDS);
const container = this.mountVideoListView({ includeTags: true });
const hasCachedVideos =
this.nostrService &&
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
this.nostrService.getFilteredActiveVideos().length > 0;
if (!hasCachedVideos) {
if (this.videoListView && container) {
this.videoListView.showLoading("Fetching kids videos\u2026");
} else if (container) {
container.innerHTML = getSidebarLoadingMarkup("Fetching kids videos\u2026");
}
}
let initialRefreshPromise = null;
const videos = await this.nostrService.loadVideos({
forceFetch,
blacklistedEventIds: this.blacklistedEventIds,
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
onVideos: (payload, detail = {}) => {
const promise = this.refreshKidsFeed({
reason: detail?.reason,
fallbackVideos: payload,
});
if (!initialRefreshPromise) {
initialRefreshPromise = promise;
}
},
});
if (initialRefreshPromise) {
await initialRefreshPromise;
} else if (!Array.isArray(videos) || videos.length === 0) {
await this.refreshKidsFeed({ reason: "initial", fallbackVideos: [] });
}
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
this.videosMap = this.nostrService.getVideosMap();
if (this.videoListView) {
this.videoListView.state.videosMap = this.videosMap;
}
return this.loadFeedVideos(FEED_TYPES.KIDS, forceFetch);
},
async loadExploreVideos(forceFetch = false) {
devLogger.log("Starting loadExploreVideos... (forceFetch =", forceFetch, ")");
this.setFeedTelemetryContext(FEED_TYPES.EXPLORE);
const container = this.mountVideoListView({ includeTags: false });
const hasCachedVideos =
this.nostrService &&
Array.isArray(this.nostrService.getFilteredActiveVideos()) &&
this.nostrService.getFilteredActiveVideos().length > 0;
if (!hasCachedVideos) {
if (this.videoListView && container) {
this.videoListView.showLoading("Fetching explore videos\u2026");
} else if (container) {
container.innerHTML = getSidebarLoadingMarkup("Fetching explore videos\u2026");
}
}
let initialRefreshPromise = null;
const videos = await this.nostrService.loadVideos({
forceFetch,
blacklistedEventIds: this.blacklistedEventIds,
isAuthorBlocked: (pubkey) => this.isAuthorBlocked(pubkey),
onVideos: (payload, detail = {}) => {
const promise = this.refreshExploreFeed({
reason: detail?.reason,
fallbackVideos: payload,
});
if (!initialRefreshPromise) {
initialRefreshPromise = promise;
}
},
});
if (initialRefreshPromise) {
await initialRefreshPromise;
} else if (!Array.isArray(videos) || videos.length === 0) {
await this.refreshExploreFeed({ reason: "initial", fallbackVideos: [] });
}
this.videoSubscription = this.nostrService.getVideoSubscription() || null;
this.videosMap = this.nostrService.getVideosMap();
if (this.videoListView) {
this.videoListView.state.videosMap = this.videosMap;
}
return this.loadFeedVideos(FEED_TYPES.EXPLORE, forceFetch);
},
async loadOlderVideos(lastTimestamp) {
+89
View File
@@ -0,0 +1,89 @@
import { test, mock } from 'node:test';
import assert from 'node:assert/strict';
import { createFeedCoordinator } from '../../js/app/feedCoordinator.js';
import { FEED_TYPES } from '../../js/constants.js';
test('createFeedCoordinator - loadForYouVideos', async (t) => {
const mockDeps = {
devLogger: { log: mock.fn(), warn: mock.fn() },
userLogger: { warn: mock.fn() },
nostrClient: {},
getSidebarLoadingMarkup: mock.fn(() => '<div>Loading...</div>'),
watchHistoryService: {},
subscriptions: {},
createActiveNostrSource: mock.fn(),
createBlacklistFilterStage: mock.fn(),
createDisinterestFilterStage: mock.fn(),
createDedupeByRootStage: mock.fn(),
createExploreDiversitySorter: mock.fn(),
createExploreScorerStage: mock.fn(),
createKidsAudienceFilterStage: mock.fn(),
createKidsScorerStage: mock.fn(),
createKidsScoreSorter: mock.fn(),
createModerationStage: mock.fn(),
createResolvePostedAtStage: mock.fn(),
createTagPreferenceFilterStage: mock.fn(),
createWatchHistorySuppressionStage: mock.fn(),
createChronologicalSorter: mock.fn(),
createSubscriptionAuthorsSource: mock.fn(),
registerWatchHistoryFeed: mock.fn(),
};
const coordinator = createFeedCoordinator(mockDeps);
// Mock application context
const app = {
...coordinator,
nostrService: {
getFilteredActiveVideos: mock.fn(() => []), // Simulate no cached videos
loadVideos: mock.fn(async ({ onVideos }) => {
// Simulate fetch completing with videos
onVideos([], { reason: 'test-reason' });
return [];
}),
getVideoSubscription: mock.fn(() => 'mock-subscription'),
getVideosMap: mock.fn(() => new Map()),
},
videoListView: {
showLoading: mock.fn(),
state: {},
},
mountVideoListView: mock.fn(() => ({ innerHTML: '' })),
checkRelayHealthWarning: mock.fn(),
setFeedTelemetryContext: mock.fn(),
isAuthorBlocked: mock.fn(() => false),
refreshForYouFeed: mock.fn(async () => {}),
refreshRecentFeed: mock.fn(async () => {}),
refreshKidsFeed: mock.fn(async () => {}),
refreshExploreFeed: mock.fn(async () => {}),
blacklistedEventIds: new Set(),
};
// Bind methods to app context
const loadForYouVideos = app.loadForYouVideos.bind(app);
await t.test('loadForYouVideos executes successfully', async () => {
await loadForYouVideos(false);
// Verify devLogger was called
assert.strictEqual(mockDeps.devLogger.log.mock.callCount() > 0, true, 'devLogger.log should be called');
// Verify telemetry context set
assert.strictEqual(app.setFeedTelemetryContext.mock.calls[0].arguments[0], FEED_TYPES.FOR_YOU, 'Should set telemetry context to for-you');
// Verify mountVideoListView called with includeTags: false
assert.deepStrictEqual(app.mountVideoListView.mock.calls[0].arguments[0], { includeTags: false }, 'Should mount list view with includeTags: false');
// Verify checkRelayHealthWarning called
assert.strictEqual(app.checkRelayHealthWarning.mock.callCount(), 1, 'Should check relay health warning');
// Verify nostrService.loadVideos called
assert.strictEqual(app.nostrService.loadVideos.mock.callCount(), 1, 'Should call nostrService.loadVideos');
// Verify refreshForYouFeed called via callback
assert.strictEqual(app.refreshForYouFeed.mock.callCount(), 1, 'Should call refreshForYouFeed via callback');
// Verify subscription updated
assert.strictEqual(app.videoSubscription, 'mock-subscription', 'Should update videoSubscription');
});
});