From aae2ea7d16a311d9a95b3f1ae6e195edffe34454 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:15:13 +0000 Subject: [PATCH] perf: use batch fetch for profile refresh Replaces the sequential loop in `forceRefreshAllProfiles` with `batchFetchProfiles` to reduce network requests. Updates `batchFetchProfilesFromRelays` to support `forceRefresh`, ensuring fresh data is requested while maintaining optimistic UI updates. Adds unit tests for `forceRefresh` behavior. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/app.js | 7 +-- js/utils/profileBatchFetcher.js | 4 ++ tests/app-batch-fetch-profiles.test.mjs | 76 +++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/js/app.js b/js/app.js index e0c363fa..12920b3f 100644 --- a/js/app.js +++ b/js/app.js @@ -718,9 +718,7 @@ class Application { const uniqueAuthors = new Set(activeVideos.map((v) => v.pubkey)); // 3) For each author, fetchAndRenderProfile with forceRefresh = true - for (const authorPubkey of uniqueAuthors) { - this.authService.fetchAndRenderProfile(authorPubkey, true); - } + this.batchFetchProfiles(uniqueAuthors, { forceRefresh: true }); } getCurrentUserNpub() { @@ -2661,9 +2659,10 @@ class Application { return this.authService.fetchAndRenderProfile(pubkey, forceRefresh); } - async batchFetchProfiles(authorSet) { + async batchFetchProfiles(authorSet, { forceRefresh = false } = {}) { return batchFetchProfilesFromRelays({ authorSet, + forceRefresh, getProfileCacheEntry: (pubkey) => this.getProfileCacheEntry(pubkey), setProfileCacheEntry: (pubkey, profile) => this.setProfileCacheEntry(pubkey, profile), diff --git a/js/utils/profileBatchFetcher.js b/js/utils/profileBatchFetcher.js index 1c3b20e1..b8a0f98e 100644 --- a/js/utils/profileBatchFetcher.js +++ b/js/utils/profileBatchFetcher.js @@ -10,6 +10,7 @@ const DEFAULT_PROFILE_IMAGE = "assets/svg/default-profile.svg"; export async function batchFetchProfilesFromRelays({ authorSet, + forceRefresh = false, getProfileCacheEntry, setProfileCacheEntry, updateProfileInDOM, @@ -50,6 +51,9 @@ export async function batchFetchProfilesFromRelays({ const cacheEntry = getProfileCacheEntry(normalizedPubkey); if (cacheEntry) { updateProfileInDOM(normalizedPubkey, cacheEntry.profile); + if (forceRefresh) { + toFetch.push(normalizedPubkey); + } } else { toFetch.push(normalizedPubkey); } diff --git a/tests/app-batch-fetch-profiles.test.mjs b/tests/app-batch-fetch-profiles.test.mjs index 361a46f7..2e916946 100644 --- a/tests/app-batch-fetch-profiles.test.mjs +++ b/tests/app-batch-fetch-profiles.test.mjs @@ -133,3 +133,79 @@ test("batchFetchProfiles handles fast and failing relays", async () => { { pubkey: VALID_PUBKEY, profile: { name: "Fast", picture: "fast.png" } }, ]); }); + +test("batchFetchProfiles respects forceRefresh", async () => { + const cache = new Map(); + const updates = []; + const setCalls = []; + const authorSet = new Set([ + CACHED_PUBKEY, + ]); + + const getProfileCacheEntry = (pubkey) => { + if (pubkey === CACHED_PUBKEY) { + return { profile: { name: "Cached", picture: "cached.png" } }; + } + return null; + }; + + const setProfileCacheEntry = (pubkey, profile) => { + cache.set(pubkey, { profile }); + setCalls.push({ pubkey, profile }); + }; + + const updateProfileInDOM = (pubkey, profile) => { + updates.push({ pubkey, profile }); + }; + + const originalPool = nostrClient.pool; + const poolListCalls = []; + + nostrClient.pool = { + list: (relays, filters) => { + poolListCalls.push({ relays, filters }); + return Promise.resolve([ + createProfileEvent({ + pubkey: CACHED_PUBKEY, + createdAt: 300, + content: { name: "Refreshed", picture: "refreshed.png" }, + }), + ]); + }, + }; + + try { + await batchFetchProfilesFromRelays({ + authorSet, + forceRefresh: true, + getProfileCacheEntry, + setProfileCacheEntry, + updateProfileInDOM, + }); + } finally { + nostrClient.pool = originalPool; + } + + // Should fetch despite cache presence + assert.ok(poolListCalls.length > 0, "should query relays when forceRefresh is true"); + assert.deepEqual( + poolListCalls[0].filters[0].authors, + [CACHED_PUBKEY], + "should include cached pubkey in fetch list" + ); + + // Should still optimistic update from cache first + assert.deepEqual( + updates[0], + { pubkey: CACHED_PUBKEY, profile: { name: "Cached", picture: "cached.png" } }, + "cached profiles should hydrate immediately even with forceRefresh" + ); + + // Then update with fresh data + const lastUpdate = updates[updates.length - 1]; + assert.deepEqual( + lastUpdate, + { pubkey: CACHED_PUBKEY, profile: { name: "Refreshed", picture: "refreshed.png" } }, + "should update with refreshed data" + ); +});