mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2217 from PR0M3TH3AN/refactor-feed-refresh-logic-11328377621655034485
Refactor duplicate feed refresh logic
This commit is contained in:
3 files changed
+99
-234
No files matched your search
@@ -22,6 +22,7 @@ import {
|
||||
DEFAULT_BLUR_THRESHOLD,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
FEED_TYPES,
|
||||
} from "./constants.js";
|
||||
import { attachHealthBadges } from "./gridHealth.js";
|
||||
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
|
||||
@@ -3996,22 +3997,22 @@ class Application {
|
||||
|
||||
async refreshForYouFeed(...args) {
|
||||
this._initCoordinators();
|
||||
return this._feed.refreshForYouFeed(...args);
|
||||
return this._feed.refreshFeed(FEED_TYPES.FOR_YOU, ...args);
|
||||
}
|
||||
|
||||
refreshKidsFeed(...args) {
|
||||
this._initCoordinators();
|
||||
return this._feed.refreshKidsFeed(...args);
|
||||
return this._feed.refreshFeed(FEED_TYPES.KIDS, ...args);
|
||||
}
|
||||
|
||||
refreshExploreFeed(...args) {
|
||||
this._initCoordinators();
|
||||
return this._feed.refreshExploreFeed(...args);
|
||||
return this._feed.refreshFeed(FEED_TYPES.EXPLORE, ...args);
|
||||
}
|
||||
|
||||
refreshRecentFeed(...args) {
|
||||
this._initCoordinators();
|
||||
return this._feed.refreshRecentFeed(...args);
|
||||
return this._feed.refreshFeed("recent", ...args);
|
||||
}
|
||||
|
||||
checkRelayHealthWarning(...args) {
|
||||
|
||||
+90
-224
@@ -785,48 +785,92 @@ export function createFeedCoordinator(deps) {
|
||||
};
|
||||
},
|
||||
|
||||
async refreshForYouFeed({ reason, fallbackVideos } = {}) {
|
||||
let watchHistoryItems = [];
|
||||
async refreshFeed(feedType, { reason, fallbackVideos } = {}) {
|
||||
const normalizedReason = typeof reason === "string" ? reason : undefined;
|
||||
const fallback = Array.isArray(fallbackVideos) ? fallbackVideos : [];
|
||||
let runtime = {};
|
||||
let metadataModifier = (m) => m;
|
||||
|
||||
// 1. Prepare Runtime
|
||||
try {
|
||||
watchHistoryItems = await watchHistoryService.loadLatest(undefined, {
|
||||
allowStale: true,
|
||||
});
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to preload watch history for For You feed:",
|
||||
error,
|
||||
switch (feedType) {
|
||||
case FEED_TYPES.FOR_YOU: {
|
||||
let watchHistoryItems = [];
|
||||
try {
|
||||
watchHistoryItems = await watchHistoryService.loadLatest(
|
||||
undefined,
|
||||
{ allowStale: true },
|
||||
);
|
||||
} catch (err) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to preload watch history for For You feed:",
|
||||
err,
|
||||
);
|
||||
}
|
||||
runtime = this.buildForYouFeedRuntime({ watchHistoryItems });
|
||||
break;
|
||||
}
|
||||
case FEED_TYPES.KIDS:
|
||||
runtime = this.buildKidsFeedRuntime();
|
||||
metadataModifier = (m) => {
|
||||
if (runtime?.ageGroup && !m.ageGroup)
|
||||
m.ageGroup = runtime.ageGroup;
|
||||
return m;
|
||||
};
|
||||
break;
|
||||
case FEED_TYPES.EXPLORE:
|
||||
runtime = this.buildExploreFeedRuntime();
|
||||
metadataModifier = (m) => {
|
||||
const next = { ...m };
|
||||
if (!next.sortOrder) next.sortOrder = FEED_TYPES.EXPLORE;
|
||||
next.preserveOrder = true;
|
||||
return next;
|
||||
};
|
||||
break;
|
||||
case "recent":
|
||||
runtime = this.buildRecentFeedRuntime();
|
||||
break;
|
||||
default:
|
||||
devLogger.warn(`[Application] Unknown feed type: ${feedType}`);
|
||||
return Promise.resolve({
|
||||
videos: fallback,
|
||||
metadata: { reason: normalizedReason, engine: "unknown" },
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.error(
|
||||
`[Application] Failed to build runtime for ${feedType}:`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
const runtime = this.buildForYouFeedRuntime({ watchHistoryItems });
|
||||
const normalizedReason = typeof reason === "string" ? reason : undefined;
|
||||
const fallback = Array.isArray(fallbackVideos) ? fallbackVideos : [];
|
||||
|
||||
// 2. Check Engine
|
||||
if (!this.feedEngine || typeof this.feedEngine.run !== "function") {
|
||||
const metadata = {
|
||||
const metadata = metadataModifier({
|
||||
reason: normalizedReason,
|
||||
engine: "unavailable",
|
||||
};
|
||||
});
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.updateForYouTelemetryMetadata([], metadata);
|
||||
// Telemetry update for unavailable engine?
|
||||
if (feedType === FEED_TYPES.FOR_YOU || feedType === FEED_TYPES.KIDS) {
|
||||
this.updateFeedTelemetryMetadata(feedType, [], metadata);
|
||||
}
|
||||
this.renderVideoList({ videos: fallback, metadata });
|
||||
return Promise.resolve({ videos: fallback, metadata });
|
||||
}
|
||||
|
||||
// 3. Run Engine
|
||||
return this.feedEngine
|
||||
.run(FEED_TYPES.FOR_YOU, { runtime })
|
||||
.run(feedType, { runtime })
|
||||
.then((result) => {
|
||||
const videos = Array.isArray(result?.videos) ? result.videos : [];
|
||||
const metadata = {
|
||||
...(result?.metadata || {}),
|
||||
};
|
||||
if (normalizedReason) {
|
||||
metadata.reason = normalizedReason;
|
||||
}
|
||||
let metadata = { ...(result?.metadata || {}) };
|
||||
if (normalizedReason) metadata.reason = normalizedReason;
|
||||
metadata = metadataModifier(metadata);
|
||||
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
@@ -835,141 +879,8 @@ export function createFeedCoordinator(deps) {
|
||||
}
|
||||
|
||||
const items = Array.isArray(result?.items) ? result.items : [];
|
||||
this.updateForYouTelemetryMetadata(items, metadata);
|
||||
|
||||
const payload = { videos, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.error("[Application] Failed to run for-you feed:", error);
|
||||
const metadata = {
|
||||
reason: normalizedReason || "error:for-you-feed",
|
||||
error: true,
|
||||
};
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.updateForYouTelemetryMetadata([], metadata);
|
||||
const payload = { videos: fallback, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
});
|
||||
},
|
||||
|
||||
refreshKidsFeed({ reason, fallbackVideos } = {}) {
|
||||
const runtime = this.buildKidsFeedRuntime();
|
||||
const normalizedReason = typeof reason === "string" ? reason : undefined;
|
||||
const fallback = Array.isArray(fallbackVideos) ? fallbackVideos : [];
|
||||
|
||||
if (!this.feedEngine || typeof this.feedEngine.run !== "function") {
|
||||
const metadata = {
|
||||
reason: normalizedReason,
|
||||
engine: "unavailable",
|
||||
};
|
||||
if (runtime?.ageGroup && !metadata.ageGroup) {
|
||||
metadata.ageGroup = runtime.ageGroup;
|
||||
}
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, [], metadata);
|
||||
this.renderVideoList({ videos: fallback, metadata });
|
||||
return Promise.resolve({ videos: fallback, metadata });
|
||||
}
|
||||
|
||||
return this.feedEngine
|
||||
.run(FEED_TYPES.KIDS, { runtime })
|
||||
.then((result) => {
|
||||
const videos = Array.isArray(result?.videos) ? result.videos : [];
|
||||
const metadata = {
|
||||
...(result?.metadata || {}),
|
||||
};
|
||||
if (normalizedReason) {
|
||||
metadata.reason = normalizedReason;
|
||||
}
|
||||
if (runtime?.ageGroup && !metadata.ageGroup) {
|
||||
metadata.ageGroup = runtime.ageGroup;
|
||||
}
|
||||
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
|
||||
const items = Array.isArray(result?.items) ? result.items : [];
|
||||
this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, items, metadata);
|
||||
|
||||
const payload = { videos, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.error("[Application] Failed to run kids feed:", error);
|
||||
const metadata = {
|
||||
reason: normalizedReason || "error:kids-feed",
|
||||
error: true,
|
||||
};
|
||||
if (runtime?.ageGroup && !metadata.ageGroup) {
|
||||
metadata.ageGroup = runtime.ageGroup;
|
||||
}
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.updateFeedTelemetryMetadata(FEED_TYPES.KIDS, [], metadata);
|
||||
const payload = { videos: fallback, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
});
|
||||
},
|
||||
|
||||
refreshExploreFeed({ reason, fallbackVideos } = {}) {
|
||||
const runtime = this.buildExploreFeedRuntime();
|
||||
const normalizedReason = typeof reason === "string" ? reason : undefined;
|
||||
const fallback = Array.isArray(fallbackVideos) ? fallbackVideos : [];
|
||||
const applyExploreOrderingMetadata = (source) => {
|
||||
const next = source && typeof source === "object" ? { ...source } : {};
|
||||
if (!next.sortOrder) {
|
||||
next.sortOrder = FEED_TYPES.EXPLORE;
|
||||
}
|
||||
next.preserveOrder = true;
|
||||
return next;
|
||||
};
|
||||
|
||||
if (!this.feedEngine || typeof this.feedEngine.run !== "function") {
|
||||
const metadata = applyExploreOrderingMetadata({
|
||||
reason: normalizedReason,
|
||||
engine: "unavailable",
|
||||
});
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.renderVideoList({ videos: fallback, metadata });
|
||||
return Promise.resolve({ videos: fallback, metadata });
|
||||
}
|
||||
|
||||
return this.feedEngine
|
||||
.run(FEED_TYPES.EXPLORE, { runtime })
|
||||
.then((result) => {
|
||||
const videos = Array.isArray(result?.videos) ? result.videos : [];
|
||||
const metadata = applyExploreOrderingMetadata({
|
||||
...(result?.metadata || {}),
|
||||
...(normalizedReason ? { reason: normalizedReason } : {}),
|
||||
});
|
||||
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
if (feedType === FEED_TYPES.FOR_YOU || feedType === FEED_TYPES.KIDS) {
|
||||
this.updateFeedTelemetryMetadata(feedType, items, metadata);
|
||||
}
|
||||
|
||||
const payload = { videos, metadata };
|
||||
@@ -977,73 +888,26 @@ export function createFeedCoordinator(deps) {
|
||||
return payload;
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.error("[Application] Failed to run explore feed:", error);
|
||||
const metadata = applyExploreOrderingMetadata({
|
||||
reason: normalizedReason || "error:explore-feed",
|
||||
error: true,
|
||||
});
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
const payload = { videos: fallback, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
});
|
||||
},
|
||||
|
||||
refreshRecentFeed({ reason, fallbackVideos } = {}) {
|
||||
const runtime = this.buildRecentFeedRuntime();
|
||||
const normalizedReason = typeof reason === "string" ? reason : undefined;
|
||||
const fallback = Array.isArray(fallbackVideos) ? fallbackVideos : [];
|
||||
|
||||
if (!this.feedEngine || typeof this.feedEngine.run !== "function") {
|
||||
const metadata = {
|
||||
reason: normalizedReason,
|
||||
engine: "unavailable",
|
||||
};
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
this.renderVideoList({ videos: fallback, metadata });
|
||||
return Promise.resolve({ videos: fallback, metadata });
|
||||
}
|
||||
|
||||
return this.feedEngine
|
||||
.run("recent", { runtime })
|
||||
.then((result) => {
|
||||
const videos = Array.isArray(result?.videos) ? result.videos : [];
|
||||
const metadata = {
|
||||
...(result?.metadata || {}),
|
||||
};
|
||||
if (normalizedReason) {
|
||||
metadata.reason = normalizedReason;
|
||||
}
|
||||
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
|
||||
const payload = { videos, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.error("[Application] Failed to run recent feed:", error);
|
||||
const metadata = {
|
||||
reason: normalizedReason || "error:recent-feed",
|
||||
devLogger.error(
|
||||
`[Application] Failed to run ${feedType} feed:`,
|
||||
error,
|
||||
);
|
||||
let metadata = {
|
||||
reason: normalizedReason || `error:${feedType}-feed`,
|
||||
error: true,
|
||||
};
|
||||
metadata = metadataModifier(metadata);
|
||||
|
||||
this.latestFeedMetadata = metadata;
|
||||
this.videosMap = this.nostrService.getVideosMap();
|
||||
if (this.videoListView) {
|
||||
this.videoListView.state.videosMap = this.videosMap;
|
||||
}
|
||||
|
||||
if (feedType === FEED_TYPES.FOR_YOU || feedType === FEED_TYPES.KIDS) {
|
||||
this.updateFeedTelemetryMetadata(feedType, [], metadata);
|
||||
}
|
||||
|
||||
const payload = { videos: fallback, metadata };
|
||||
this.renderVideoList(payload);
|
||||
return payload;
|
||||
@@ -1101,29 +965,31 @@ export function createFeedCoordinator(deps) {
|
||||
case "recent":
|
||||
includeTags = true;
|
||||
loadingMessage = "Fetching recent videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshRecentFeed(opts);
|
||||
refreshMethod = (opts) => this.refreshFeed("recent", opts);
|
||||
shouldCheckRelayHealth = true;
|
||||
break;
|
||||
case FEED_TYPES.FOR_YOU:
|
||||
includeTags = false;
|
||||
loadingMessage = "Fetching for-you videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshForYouFeed(opts);
|
||||
refreshMethod = (opts) => this.refreshFeed(FEED_TYPES.FOR_YOU, opts);
|
||||
shouldCheckRelayHealth = true;
|
||||
break;
|
||||
case FEED_TYPES.KIDS:
|
||||
includeTags = true;
|
||||
loadingMessage = "Fetching kids videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshKidsFeed(opts);
|
||||
refreshMethod = (opts) => this.refreshFeed(FEED_TYPES.KIDS, opts);
|
||||
shouldCheckRelayHealth = false;
|
||||
break;
|
||||
case FEED_TYPES.EXPLORE:
|
||||
includeTags = false;
|
||||
loadingMessage = "Fetching explore videos\u2026";
|
||||
refreshMethod = (opts) => this.refreshExploreFeed(opts);
|
||||
refreshMethod = (opts) => this.refreshFeed(FEED_TYPES.EXPLORE, opts);
|
||||
shouldCheckRelayHealth = false;
|
||||
break;
|
||||
default:
|
||||
devLogger.warn(`[Application] Unknown feed type for loadFeedVideos: ${feedType}`);
|
||||
devLogger.warn(
|
||||
`[Application] Unknown feed type for loadFeedVideos: ${feedType}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1225,7 +1091,7 @@ export function createFeedCoordinator(deps) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.refreshRecentFeed({ reason: "older-fetch" });
|
||||
await this.refreshFeed("recent", { reason: "older-fetch" });
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,10 +52,7 @@ test('createFeedCoordinator - loadForYouVideos', async (t) => {
|
||||
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 () => {}),
|
||||
refreshFeed: mock.fn(async () => {}),
|
||||
blacklistedEventIds: new Set(),
|
||||
};
|
||||
|
||||
@@ -80,8 +77,9 @@ test('createFeedCoordinator - loadForYouVideos', async (t) => {
|
||||
// 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 refreshFeed called via callback
|
||||
assert.strictEqual(app.refreshFeed.mock.callCount(), 1, 'Should call refreshFeed via callback');
|
||||
assert.strictEqual(app.refreshFeed.mock.calls[0].arguments[0], FEED_TYPES.FOR_YOU, 'Should call refreshFeed with FOR_YOU');
|
||||
|
||||
// Verify subscription updated
|
||||
assert.strictEqual(app.videoSubscription, 'mock-subscription', 'Should update videoSubscription');
|
||||
|
||||
Reference in new issue
Block a user