From b02d275bdb2c264fe66169f98579b46fa86c0eaa Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:45:18 -0400 Subject: [PATCH] Parallelize block list relay fetches --- js/userBlocks.js | 29 ++++++++-- tests/user-blocks.test.mjs | 115 +++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 tests/user-blocks.test.mjs diff --git a/js/userBlocks.js b/js/userBlocks.js index 30228d81..3cb4b115 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -87,14 +87,33 @@ class UserBlockListManager { }; const events = []; - for (const relay of nostrClient.relays) { - try { - const res = await nostrClient.pool.list([relay], [filter]); + const relays = Array.isArray(nostrClient.relays) + ? nostrClient.relays + : []; + + const results = await Promise.allSettled( + relays.map((relay) => + Promise.resolve() + .then(() => nostrClient.pool.list([relay], [filter])) + .then((res) => ({ relay, res })) + .catch((error) => { + throw { relay, error }; + }) + ) + ); + + for (const outcome of results) { + if (outcome.status === "fulfilled") { + const { res } = outcome.value || {}; if (Array.isArray(res) && res.length) { events.push(...res); } - } catch (err) { - console.error(`[UserBlockList] Relay error at ${relay}:`, err); + } else { + const { relay, error } = outcome.reason || {}; + console.error( + `[UserBlockList] Relay error at ${relay}:`, + error ?? outcome.reason + ); } } diff --git a/tests/user-blocks.test.mjs b/tests/user-blocks.test.mjs new file mode 100644 index 00000000..6c28daa9 --- /dev/null +++ b/tests/user-blocks.test.mjs @@ -0,0 +1,115 @@ +import "./test-helpers/setup-localstorage.mjs"; +import assert from "node:assert/strict"; + +const { userBlocks } = await import("../js/userBlocks.js"); +const { nostrClient } = await import("../js/nostr.js"); + +if (typeof globalThis.window === "undefined") { + globalThis.window = {}; +} + +await (async () => { + const actor = "f".repeat(64); + const blocked = "e".repeat(64); + const relays = [ + "wss://relay-one.example", + "wss://relay-two.example", + "wss://relay-three.example", + ]; + + const originalNostr = window.nostr; + const originalPool = nostrClient.pool; + const originalRelays = Array.isArray(nostrClient.relays) + ? [...nostrClient.relays] + : nostrClient.relays; + const originalBlocked = new Set(userBlocks.blockedPubkeys); + const originalBlockEventId = userBlocks.blockEventId; + const originalLoaded = userBlocks.loaded; + + const calls = []; + const resolvers = []; + + window.nostr = { + ...(originalNostr || {}), + nip04: { + ...((originalNostr && originalNostr.nip04) || {}), + decrypt: async () => + JSON.stringify({ + blockedPubkeys: [blocked], + }), + }, + }; + + const pool = originalPool ? { ...originalPool } : {}; + pool.list = (targets) => { + const relay = Array.isArray(targets) ? targets[0] : undefined; + calls.push(relay); + let resolve; + const promise = new Promise((res) => { + resolve = res; + }); + resolvers.push({ relay, resolve }); + return promise; + }; + + nostrClient.pool = pool; + nostrClient.relays = relays; + + userBlocks.blockedPubkeys = new Set(); + userBlocks.blockEventId = null; + userBlocks.loaded = false; + + try { + const loadPromise = userBlocks.loadBlocks(actor); + + await Promise.resolve(); + + assert.deepEqual( + calls, + relays, + "loadBlocks should initiate queries for all relays concurrently" + ); + + const responses = { + [relays[0]]: [ + { + id: "event-old", + created_at: 1700, + content: "ciphertext-old", + }, + ], + [relays[1]]: [ + { + id: "event-new", + created_at: 1800, + content: "ciphertext-new", + }, + ], + [relays[2]]: [], + }; + + for (const { relay, resolve } of resolvers) { + resolve(responses[relay]); + } + + await loadPromise; + + assert.equal( + userBlocks.blockEventId, + "event-new", + "latest event should win after aggregating relay responses" + ); + assert.deepEqual( + userBlocks.getBlockedPubkeys(), + [blocked], + "decrypted block list should hydrate from newest event" + ); + } finally { + userBlocks.blockedPubkeys = originalBlocked; + userBlocks.blockEventId = originalBlockEventId; + userBlocks.loaded = originalLoaded; + nostrClient.pool = originalPool; + nostrClient.relays = originalRelays; + window.nostr = originalNostr; + } +})();