diff --git a/js/nostr/client.js b/js/nostr/client.js index e1db73b2..a6279725 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -7750,6 +7750,7 @@ export class NostrClient { const toProcess = eventBuffer; eventBuffer = []; + const updatedVideos = []; for (const evt of toProcess) { try { @@ -7805,25 +7806,32 @@ export class NostrClient { const prevActive = this.activeMap.get(activeKey); if (!prevActive || video.created_at > prevActive.created_at) { this.activeMap.set(activeKey, video); - onVideo(video); // Trigger the callback that re-renders - - // Fetch NIP-71 metadata (categorization tags) in the background - this.populateNip71MetadataForVideos([video]) - .then(() => { - this.applyRootCreatedAt(video); - }) - .catch((error) => { - devLogger.warn( - "[nostr] Failed to hydrate NIP-71 metadata for live video:", - error - ); - }); + updatedVideos.push(video); } } catch (err) { devLogger.error("[subscribeVideos] Error processing event:", err); } } + if (updatedVideos.length > 0) { + // Trigger the callback once per batch to avoid UI thrashing + onVideo(updatedVideos); + + // Fetch NIP-71 metadata (categorization tags) in the background for the whole batch + this.populateNip71MetadataForVideos(updatedVideos) + .then(() => { + for (const video of updatedVideos) { + this.applyRootCreatedAt(video); + } + }) + .catch((error) => { + devLogger.warn( + "[nostr] Failed to hydrate NIP-71 metadata for live video batch:", + error + ); + }); + } + // Persist processed events after each flush so reloads warm quickly. this.saveLocalData("subscribeVideos:flush"); }; diff --git a/tests/nostr-delete-flow.test.mjs b/tests/nostr-delete-flow.test.mjs index 6731ec66..dce4325d 100644 --- a/tests/nostr-delete-flow.test.mjs +++ b/tests/nostr-delete-flow.test.mjs @@ -196,8 +196,10 @@ await (async function testDeleteFlowPublishesDeletionFlag() { }, }; - client.subscribeVideos((videoUpdate) => { - seenUpdates.push(videoUpdate); + client.subscribeVideos((videos) => { + if (Array.isArray(videos)) { + seenUpdates.push(...videos); + } }); const deleteEvent = { @@ -300,8 +302,10 @@ await (async function testSubscribeVideosSkipsOlderThanTombstone() { }, }; - client.subscribeVideos((videoUpdate) => { - seenUpdates.push(videoUpdate); + client.subscribeVideos((videos) => { + if (Array.isArray(videos)) { + seenUpdates.push(...videos); + } }); const pubkey = "PUBKEY789"; diff --git a/tests/nostr/client.test.mjs b/tests/nostr/client.test.mjs index 97e84cbd..6e15cccc 100644 --- a/tests/nostr/client.test.mjs +++ b/tests/nostr/client.test.mjs @@ -148,8 +148,11 @@ describe("NostrClient", () => { client.pool.sub.mock.mockImplementation(() => subMock); let capturedVideo = null; - const sub = client.subscribeVideos((video) => { - capturedVideo = video; + const sub = client.subscribeVideos((videos) => { + // Callback receives an array of updates + if (Array.isArray(videos) && videos.length > 0) { + capturedVideo = videos[0]; + } }); assert.ok(sub);