mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Optimize subscription feed retrieval and rendering
This commit is contained in:
1 parent
a8cea0b147
commit
e49483d34d
4 files changed
+139
-11
No files matched your search
@@ -92,12 +92,19 @@ export function createSubscriptionAuthorsSource({ service } = {}) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const limitCandidate = Number(context?.runtime?.limit);
|
||||
const limit =
|
||||
Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
|
||||
const options = {
|
||||
blacklistedEventIds: toSet(context?.runtime?.blacklistedEventIds),
|
||||
isAuthorBlocked:
|
||||
typeof context?.runtime?.isAuthorBlocked === "function"
|
||||
? context.runtime.isAuthorBlocked
|
||||
: () => false,
|
||||
limit,
|
||||
};
|
||||
|
||||
const authorList = Array.from(authors);
|
||||
@@ -129,7 +136,15 @@ export function createSubscriptionAuthorsSource({ service } = {}) {
|
||||
return author && authors.has(author);
|
||||
});
|
||||
|
||||
return filtered.map((video) => ({
|
||||
const sorted = filtered
|
||||
.slice()
|
||||
.sort(
|
||||
(a, b) => (Number(b?.created_at) || 0) - (Number(a?.created_at) || 0)
|
||||
);
|
||||
|
||||
const limited = limit ? sorted.slice(0, limit) : sorted;
|
||||
|
||||
return limited.map((video) => ({
|
||||
video,
|
||||
metadata: {
|
||||
source: "nostr:subscriptions",
|
||||
|
||||
@@ -297,6 +297,24 @@ export class NostrService {
|
||||
index.get(author).push(video);
|
||||
}
|
||||
|
||||
for (const [author, videos] of index.entries()) {
|
||||
if (!Array.isArray(videos) || videos.length <= 1) {
|
||||
continue;
|
||||
}
|
||||
videos.sort((a, b) => {
|
||||
const aCreatedCandidate = Number(a?.created_at);
|
||||
const bCreatedCandidate = Number(b?.created_at);
|
||||
const aCreated = Number.isFinite(aCreatedCandidate)
|
||||
? aCreatedCandidate
|
||||
: 0;
|
||||
const bCreated = Number.isFinite(bCreatedCandidate)
|
||||
? bCreatedCandidate
|
||||
: 0;
|
||||
return bCreated - aCreated;
|
||||
});
|
||||
index.set(author, videos);
|
||||
}
|
||||
|
||||
this.videosByAuthorIndex = index;
|
||||
this.authorIndexDirty = false;
|
||||
return this.videosByAuthorIndex;
|
||||
@@ -400,13 +418,25 @@ export class NostrService {
|
||||
const index = this.ensureVideosByAuthorIndex();
|
||||
const collected = [];
|
||||
const seen = new Set();
|
||||
const limitCandidate = Number(options?.limit);
|
||||
const limit =
|
||||
Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
const perAuthorLimit = limit
|
||||
? Math.max(limit * 2, limit + 5)
|
||||
: null;
|
||||
|
||||
for (const author of normalizedAuthors) {
|
||||
const entries = index.get(author);
|
||||
if (!Array.isArray(entries)) {
|
||||
if (!Array.isArray(entries) || entries.length === 0) {
|
||||
continue;
|
||||
}
|
||||
for (const video of entries) {
|
||||
const sliceCount = perAuthorLimit
|
||||
? Math.min(perAuthorLimit, entries.length)
|
||||
: entries.length;
|
||||
for (let idx = 0; idx < sliceCount; idx += 1) {
|
||||
const video = entries[idx];
|
||||
if (!video || typeof video !== "object") {
|
||||
continue;
|
||||
}
|
||||
@@ -421,13 +451,19 @@ export class NostrService {
|
||||
|
||||
const filtered = this.filterVideos(collected, options);
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
const sorted = filtered.sort((a, b) => {
|
||||
const aCreatedCandidate = Number(a?.created_at);
|
||||
const bCreatedCandidate = Number(b?.created_at);
|
||||
const aCreated = Number.isFinite(aCreatedCandidate) ? aCreatedCandidate : 0;
|
||||
const bCreated = Number.isFinite(bCreatedCandidate) ? bCreatedCandidate : 0;
|
||||
return bCreated - aCreated;
|
||||
});
|
||||
|
||||
if (limit) {
|
||||
return sorted.slice(0, limit);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
async loadVideos({
|
||||
|
||||
+27
-7
@@ -373,7 +373,11 @@ class SubscriptionsManager {
|
||||
}
|
||||
|
||||
const app = getApp();
|
||||
const runtime = this.buildFeedRuntime({ app, authors: channelHexes });
|
||||
const runtime = this.buildFeedRuntime({
|
||||
app,
|
||||
authors: channelHexes,
|
||||
limit,
|
||||
});
|
||||
const runOptions = {
|
||||
actorPubkey: userPubkey,
|
||||
limit,
|
||||
@@ -410,11 +414,20 @@ class SubscriptionsManager {
|
||||
"[SubscriptionsManager] Failed to run subscriptions feed:",
|
||||
error
|
||||
);
|
||||
container.innerHTML =
|
||||
"<p class='text-muted-strong'>Unable to load subscriptions right now.</p>";
|
||||
this.lastResult = null;
|
||||
if (container && this.lastResult) {
|
||||
const fallbackReason = reason
|
||||
? `${reason}:cached`
|
||||
: "cached-result";
|
||||
this.renderSameGridStyle(this.lastResult, containerId, {
|
||||
limit,
|
||||
reason: fallbackReason,
|
||||
});
|
||||
} else if (container) {
|
||||
container.innerHTML =
|
||||
"<p class='text-muted-strong'>Unable to load subscriptions right now.</p>";
|
||||
}
|
||||
this.hasRenderedOnce = Boolean(container);
|
||||
return null;
|
||||
return this.lastResult;
|
||||
} finally {
|
||||
this.isRunningFeed = false;
|
||||
this.processScheduledRefresh();
|
||||
@@ -500,7 +513,7 @@ class SubscriptionsManager {
|
||||
return app?.feedEngine || null;
|
||||
}
|
||||
|
||||
buildFeedRuntime({ app, authors = [] } = {}) {
|
||||
buildFeedRuntime({ app, authors = [], limit = null } = {}) {
|
||||
const normalizedAuthors = Array.isArray(authors)
|
||||
? authors.filter((author) => typeof author === "string" && author)
|
||||
: [];
|
||||
@@ -515,11 +528,18 @@ class SubscriptionsManager {
|
||||
? (pubkey) => app.isAuthorBlocked(pubkey)
|
||||
: () => false;
|
||||
|
||||
const limitCandidate = Number(limit);
|
||||
const normalizedLimit =
|
||||
Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
|
||||
return {
|
||||
subscriptionAuthors: normalizedAuthors,
|
||||
authors: normalizedAuthors,
|
||||
blacklistedEventIds: blacklist,
|
||||
isAuthorBlocked
|
||||
isAuthorBlocked,
|
||||
limit: normalizedLimit
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -199,8 +199,65 @@ async function testNoAuthorsSkipsLookup() {
|
||||
assert.deepEqual(result.videos, [], "no authors should yield an empty feed");
|
||||
}
|
||||
|
||||
async function testLimitApplied() {
|
||||
const engine = createFeedEngine();
|
||||
const feedName = "subscriptions-limit";
|
||||
|
||||
const videos = [
|
||||
{ id: "video-1", pubkey: "pub1", created_at: 100 },
|
||||
{ id: "video-2", pubkey: "pub1", created_at: 200 },
|
||||
{ id: "video-3", pubkey: "pub1", created_at: 300 },
|
||||
{ id: "video-4", pubkey: "pub1", created_at: 400 },
|
||||
{ id: "video-5", pubkey: "pub1", created_at: 500 },
|
||||
];
|
||||
|
||||
const calls = [];
|
||||
const service = {
|
||||
async getActiveVideosByAuthors(authors, options) {
|
||||
calls.push({ authors, options });
|
||||
return videos;
|
||||
},
|
||||
async getFilteredActiveVideos() {
|
||||
throw new Error("limit run should not use fallback lookup");
|
||||
},
|
||||
};
|
||||
|
||||
engine.registerFeed(feedName, {
|
||||
source: createSubscriptionAuthorsSource({ service }),
|
||||
stages: [
|
||||
createBlacklistFilterStage({ shouldIncludeVideo: () => true }),
|
||||
createDedupeByRootStage(),
|
||||
],
|
||||
sorter: createChronologicalSorter(),
|
||||
});
|
||||
|
||||
const result = await engine.runFeed(feedName, {
|
||||
runtime: {
|
||||
subscriptionAuthors: ["pub1"],
|
||||
authors: ["pub1"],
|
||||
blacklistedEventIds: new Set(),
|
||||
isAuthorBlocked: () => false,
|
||||
limit: 3,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(calls.length, 1, "limit run should request targeted lookup once");
|
||||
assert.equal(
|
||||
calls[0]?.options?.limit,
|
||||
3,
|
||||
"limit option should be forwarded to the service lookup",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
result.videos.map((video) => video.id),
|
||||
["video-5", "video-4", "video-3"],
|
||||
"feed should only include the newest videos within the limit",
|
||||
);
|
||||
}
|
||||
|
||||
await testBlockedCreatorsFiltered();
|
||||
await testDuplicateRootsFiltered();
|
||||
await testNoAuthorsSkipsLookup();
|
||||
await testLimitApplied();
|
||||
|
||||
console.log("subscriptions-feed tests passed");
|
||||
Reference in new issue
Block a user