diff --git a/js/relayManager.js b/js/relayManager.js index f8d9db56..c1a88756 100644 --- a/js/relayManager.js +++ b/js/relayManager.js @@ -222,6 +222,12 @@ class RelayPreferencesManager { }); } else if (nostrClient) { nostrClient.relays = this.getAllRelayUrls(); + const writeUrls = this.getWriteRelayUrls(); + if (Array.isArray(writeUrls) && writeUrls.length) { + nostrClient.writeRelays = [...writeUrls]; + } else { + nostrClient.writeRelays = this.getAllRelayUrls(); + } } } diff --git a/js/subscriptions.js b/js/subscriptions.js index 4b32767f..16114dd6 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -3,6 +3,7 @@ import { nostrClient, convertEventToVideo as sharedConvertEventToVideo, requestDefaultExtensionPermissions, + DEFAULT_RELAY_URLS, } from "./nostr.js"; import { buildSubscriptionListEvent, @@ -413,9 +414,24 @@ class SubscriptionsManager { throw signErr; } + const sanitizeRelayList = (candidate) => + Array.isArray(candidate) + ? candidate + .map((url) => (typeof url === "string" ? url.trim() : "")) + .filter(Boolean) + : []; + + const writeRelays = sanitizeRelayList(nostrClient.writeRelays); + const fallbackRelays = writeRelays.length + ? writeRelays + : sanitizeRelayList(nostrClient.relays); + const targetRelays = fallbackRelays.length + ? fallbackRelays + : Array.from(DEFAULT_RELAY_URLS); + const publishResults = await publishEventToRelays( nostrClient.pool, - nostrClient.relays, + targetRelays, signedEvent ); diff --git a/tests/app-batch-fetch-profiles.test.mjs b/tests/app-batch-fetch-profiles.test.mjs index 2dceb811..4a3680d2 100644 --- a/tests/app-batch-fetch-profiles.test.mjs +++ b/tests/app-batch-fetch-profiles.test.mjs @@ -49,11 +49,15 @@ test("batchFetchProfiles handles fast and failing relays", async () => { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : []; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalPool = nostrClient.pool; const poolListCalls = []; nostrClient.relays = [FAST_RELAY, FAIL_RELAY]; + nostrClient.writeRelays = [FAST_RELAY, FAIL_RELAY]; nostrClient.pool = { list: (relays, filters) => { poolListCalls.push({ relays, filters }); @@ -88,6 +92,7 @@ test("batchFetchProfiles handles fast and failing relays", async () => { }); } finally { nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.pool = originalPool; } diff --git a/tests/subscriptions-manager.test.mjs b/tests/subscriptions-manager.test.mjs index 5024132f..cb9f4e2a 100644 --- a/tests/subscriptions-manager.test.mjs +++ b/tests/subscriptions-manager.test.mjs @@ -16,6 +16,9 @@ test("loadSubscriptions aggregates relay results when one rejects", async () => const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalPool = nostrClient.pool; const originalEnsurePermissions = nostrClient.ensureExtensionPermissions; @@ -31,6 +34,7 @@ test("loadSubscriptions aggregates relay results when one rejects", async () => "wss://relay-c.example", ]; nostrClient.relays = relayUrls; + nostrClient.writeRelays = relayUrls; const eventsByRelay = { "wss://relay-a.example": [ @@ -112,6 +116,7 @@ test("loadSubscriptions aggregates relay results when one rejects", async () => } } finally { nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.pool = originalPool; nostrClient.ensureExtensionPermissions = originalEnsurePermissions; if (typeof originalWindowNostr === "undefined") { @@ -132,6 +137,9 @@ test("loadSubscriptions falls back to nip44 when hinted", async () => { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalPool = nostrClient.pool; const originalEnsurePermissions = nostrClient.ensureExtensionPermissions; @@ -143,6 +151,7 @@ test("loadSubscriptions falls back to nip44 when hinted", async () => { const relayUrls = ["wss://relay-nip44.example"]; nostrClient.relays = relayUrls; + nostrClient.writeRelays = relayUrls; const event = { id: "event-nip44", @@ -197,6 +206,7 @@ test("loadSubscriptions falls back to nip44 when hinted", async () => { ); } finally { nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.pool = originalPool; nostrClient.ensureExtensionPermissions = originalEnsurePermissions; if (typeof originalWindowNostr === "undefined") { @@ -217,6 +227,9 @@ test("loadSubscriptions handles nip44.v2 decryptors", async () => { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalPool = nostrClient.pool; const originalEnsurePermissions = nostrClient.ensureExtensionPermissions; @@ -228,6 +241,7 @@ test("loadSubscriptions handles nip44.v2 decryptors", async () => { const relayUrls = ["wss://relay-nip44-v2.example"]; nostrClient.relays = relayUrls; + nostrClient.writeRelays = relayUrls; const event = { id: "event-nip44-v2", @@ -283,6 +297,7 @@ test("loadSubscriptions handles nip44.v2 decryptors", async () => { ); } finally { nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.pool = originalPool; nostrClient.ensureExtensionPermissions = originalEnsurePermissions; if (typeof originalWindowNostr === "undefined") { diff --git a/tests/user-blocks.test.mjs b/tests/user-blocks.test.mjs index ecc6371a..65ddefbd 100644 --- a/tests/user-blocks.test.mjs +++ b/tests/user-blocks.test.mjs @@ -22,6 +22,9 @@ await (async () => { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalBlocked = new Set(userBlocks.blockedPubkeys); const originalBlockEventId = userBlocks.blockEventId; const originalLoaded = userBlocks.loaded; @@ -54,6 +57,7 @@ await (async () => { nostrClient.pool = pool; nostrClient.relays = relays; + nostrClient.writeRelays = relays; assert.equal( typeof nostrClient.pool?.list, @@ -118,6 +122,7 @@ await (async () => { userBlocks.loaded = originalLoaded; nostrClient.pool = originalPool; nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; window.nostr = originalNostr; } })(); @@ -138,6 +143,9 @@ await (async () => { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalBlocked = new Set(userBlocks.blockedPubkeys); const originalBlockEventId = userBlocks.blockEventId; const originalBlockEventCreatedAt = userBlocks.blockEventCreatedAt; @@ -171,6 +179,7 @@ await (async () => { nostrClient.pool = pool; nostrClient.relays = relays; + nostrClient.writeRelays = relays; assert.equal( typeof nostrClient.pool?.list, @@ -266,6 +275,7 @@ await (async () => { userBlocks.loaded = originalLoaded; nostrClient.pool = originalPool; nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; window.nostr = originalNostr; } })(); diff --git a/tests/view-counter.test.mjs b/tests/view-counter.test.mjs index 788895fe..d0f68408 100644 --- a/tests/view-counter.test.mjs +++ b/tests/view-counter.test.mjs @@ -608,6 +608,9 @@ async function testRecordVideoViewEmitsJsonPayload() { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : nostrClient.relays; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalEnsureSessionActor = nostrClient.ensureSessionActor; const originalSessionActor = nostrClient.sessionActor; const originalPubkey = nostrClient.pubkey; @@ -653,6 +656,7 @@ async function testRecordVideoViewEmitsJsonPayload() { }, }; nostrClient.relays = ["wss://relay.example"]; + nostrClient.writeRelays = ["wss://relay.example"]; nostrClient.ensureSessionActor = async () => { nostrClient.sessionActor = { pubkey: "pub-record-json", @@ -705,6 +709,7 @@ async function testRecordVideoViewEmitsJsonPayload() { } finally { nostrClient.pool = originalPool; nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.ensureSessionActor = originalEnsureSessionActor; nostrClient.sessionActor = originalSessionActor; nostrClient.pubkey = originalPubkey; @@ -726,6 +731,7 @@ async function testSignAndPublishFallbackUsesSessionActor() { const originalExtension = window.nostr; const originalPool = nostrClient.pool; const originalRelays = nostrClient.relays; + const originalWriteRelays = nostrClient.writeRelays; const originalPubkey = nostrClient.pubkey; const originalSessionActor = nostrClient.sessionActor; const originalEnsureSessionActor = nostrClient.ensureSessionActor; @@ -749,6 +755,7 @@ async function testSignAndPublishFallbackUsesSessionActor() { }, }; nostrClient.relays = ["wss://relay.fallback"]; + nostrClient.writeRelays = ["wss://relay.fallback"]; nostrClient.pubkey = "logged-user"; nostrClient.sessionActor = { pubkey: "session-actor", @@ -809,6 +816,7 @@ async function testSignAndPublishFallbackUsesSessionActor() { } nostrClient.pool = originalPool; nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.pubkey = originalPubkey; nostrClient.sessionActor = originalSessionActor; nostrClient.ensureSessionActor = originalEnsureSessionActor; @@ -876,6 +884,9 @@ async function testHydrateHistoryPrefersRootEvent() { const originalRelays = Array.isArray(nostrClient.relays) ? [...nostrClient.relays] : []; + const originalWriteRelays = Array.isArray(nostrClient.writeRelays) + ? [...nostrClient.writeRelays] + : nostrClient.writeRelays; const originalPopulate = nostrClient.populateNip71MetadataForVideos; const originalAllEvents = nostrClient.allEvents; const originalActiveMap = nostrClient.activeMap; @@ -920,6 +931,7 @@ async function testHydrateHistoryPrefersRootEvent() { list: async () => [], }; nostrClient.relays = ["wss://relay.example"]; + nostrClient.writeRelays = ["wss://relay.example"]; nostrClient.populateNip71MetadataForVideos = async () => {}; nostrClient.allEvents = new Map([[latestVideo.id, latestVideo]]); nostrClient.activeMap = new Map([[`ROOT:${rootId}`, latestVideo]]); @@ -954,6 +966,7 @@ async function testHydrateHistoryPrefersRootEvent() { } finally { nostrClient.pool = originalPool; nostrClient.relays = originalRelays; + nostrClient.writeRelays = originalWriteRelays; nostrClient.populateNip71MetadataForVideos = originalPopulate; nostrClient.allEvents = originalAllEvents; nostrClient.activeMap = originalActiveMap;