diff --git a/packages/app/src/plugins/relayLists.ts b/packages/app/src/plugins/relayLists.ts index 9472aab..bc41568 100644 --- a/packages/app/src/plugins/relayLists.ts +++ b/packages/app/src/plugins/relayLists.ts @@ -55,7 +55,10 @@ export class RelayLists extends DerivedPlugin { return this.app.use(Thunks).publishToOutbox({event}) } - addRelay = (url: string, mode: RelayMode) => this.update(builder => builder.addUrl(url, mode)) + addRelay = (url: string, mode: RelayMode) => + this.update(builder => + mode === RelayMode.Read ? builder.addReadUrl(url) : builder.addWriteUrl(url), + ) setReadRelays = (urls: string[]) => this.update(builder => builder.setReadUrls(urls)) @@ -64,7 +67,9 @@ export class RelayLists extends DerivedPlugin { removeRelay = async (url: string, mode: RelayMode) => { const user = User.require(this.app) const builder = new RelayListBuilder(await this.forceLoad(user.pubkey)) - const event = await builder.removeUrl(url, mode).toTemplate(user.signer) + const event = await ( + mode === RelayMode.Read ? builder.removeReadUrl(url) : builder.removeWriteUrl(url) + ).toTemplate(user.signer) // publishToOutbox is outbox-only, so build relays here to also notify the // removed relay of its removal diff --git a/packages/domain/__tests__/RelayList.test.ts b/packages/domain/__tests__/RelayList.test.ts index d24e9cd..9949f0c 100644 --- a/packages/domain/__tests__/RelayList.test.ts +++ b/packages/domain/__tests__/RelayList.test.ts @@ -60,10 +60,10 @@ describe("RelayList", () => { it("adds modeless and single-mode relays via a fresh builder", async () => { const tmpl = await new RelayListBuilder() - .addUrl(read, RelayMode.Read) - .addUrl(write, RelayMode.Write) - .addUrl(both, RelayMode.Read) - .addUrl(both, RelayMode.Write) + .addReadUrl(read) + .addWriteUrl(write) + .addReadUrl(both) + .addWriteUrl(both) .toTemplate(signer) expect(tmpl.kind).toBe(RELAYS) @@ -75,9 +75,9 @@ describe("RelayList", () => { it("downgrades a modeless relay when one mode is removed", async () => { const tmpl = await new RelayListBuilder() - .addUrl(both, RelayMode.Read) - .addUrl(both, RelayMode.Write) - .removeUrl(both, RelayMode.Read) + .addReadUrl(both) + .addWriteUrl(both) + .removeReadUrl(both) .toTemplate(signer) expect(tmpl.tags).toContainEqual(["r", both, RelayMode.Write]) diff --git a/packages/domain/src/kinds/RelayList.ts b/packages/domain/src/kinds/RelayList.ts index bea623b..f84e8e7 100644 --- a/packages/domain/src/kinds/RelayList.ts +++ b/packages/domain/src/kinds/RelayList.ts @@ -1,32 +1,30 @@ -import {uniq, uniqBy} from "@welshman/lib" -import {RELAYS, RelayMode, getRelayTags, getRelayTagValues, normalizeRelayUrl} from "@welshman/util" +import {nth, uniq, uniqBy, remove} from "@welshman/lib" +import {RELAYS, getRelayTags, normalizeRelayUrl} from "@welshman/util" import {ListReader} from "../ListReader.js" import {ListBuilder} from "../ListBuilder.js" +const getUrls = (tags: string[][], mode?: string) => + uniqBy( + normalizeRelayUrl, + getRelayTags(tags) + .filter(t => !mode || !t[2] || t[2] === mode) + .map(nth(1)), + ) + // NIP-65 kind-10002 relay list. export class RelayList extends ListReader { readonly kind = RELAYS urls() { - return uniqBy(normalizeRelayUrl, getRelayTagValues(this.tags())) + return getUrls(this.tags()) } readUrls() { - return uniqBy( - normalizeRelayUrl, - getRelayTags(this.tags()) - .filter(t => !t[2] || t[2] === RelayMode.Read) - .map(t => t[1]), - ) + return getUrls(this.tags(), "read") } writeUrls() { - return uniqBy( - normalizeRelayUrl, - getRelayTags(this.tags()) - .filter(t => !t[2] || t[2] === RelayMode.Write) - .map(t => t[1]), - ) + return getUrls(this.tags(), "write") } builder() { @@ -37,73 +35,62 @@ export class RelayList extends ListReader { export class RelayListBuilder extends ListBuilder { readonly kind = RELAYS - readUrls() { - return uniqBy( - normalizeRelayUrl, - getRelayTags(this.publicTags) - .filter(t => !t[2] || t[2] === RelayMode.Read) - .map(t => t[1]), - ) + addReadUrl(url: string) { + return this.addUrlForMode(url, "read") } - writeUrls() { - return uniqBy( - normalizeRelayUrl, - getRelayTags(this.publicTags) - .filter(t => !t[2] || t[2] === RelayMode.Write) - .map(t => t[1]), - ) + addWriteUrl(url: string) { + return this.addUrlForMode(url, "write") } - addUrl(url: string, mode: RelayMode) { + removeReadUrl(url: string) { + return this.removeUrlForMode(url, "read") + } + + removeWriteUrl(url: string) { + return this.removeUrlForMode(url, "write") + } + + private findUrlTag(url: string) { const normalized = normalizeRelayUrl(url) - const existing = getRelayTags(this.publicTags).filter( - t => normalizeRelayUrl(t[1]) === normalized, - ) - const priorModes = new Set( - existing.map(t => t[2] as RelayMode | undefined), - ) + return this.publicTags.find(t => t[0] === "r" && normalizeRelayUrl(t[1]) === normalized) + } - const alt = mode === RelayMode.Read ? RelayMode.Write : RelayMode.Read - const coversAlt = priorModes.has(undefined) || priorModes.has(alt) + private addUrlForMode(url: string, mode: "read" | "write") { + const existing = this.findUrlTag(url) + const alt = mode === "read" ? "write" : "read" - this.publicTags = this.publicTags.filter( - t => !(t[0] === "r" && normalizeRelayUrl(t[1]) === normalized), - ) - - this.publicTags.push(coversAlt ? ["r", url] : ["r", url, mode]) + if (!existing) { + this.publicTags.push(["r", normalizeRelayUrl(url), mode]) + } else if (existing[2] === alt) { + existing.splice(2) + } return this } - removeUrl(url: string, mode: RelayMode) { - const normalized = normalizeRelayUrl(url) - const existing = getRelayTags(this.publicTags).filter( - t => normalizeRelayUrl(t[1]) === normalized, - ) + private removeUrlForMode(url: string, mode: "read" | "write") { + const existing = this.findUrlTag(url) + const alt = mode === "read" ? "write" : "read" - const alt = mode === RelayMode.Read ? RelayMode.Write : RelayMode.Read - - const keepAlt = existing.some(t => !t[2] || t[2] === alt) - - this.publicTags = this.publicTags.filter( - t => !(t[0] === "r" && normalizeRelayUrl(t[1]) === normalized), - ) - - if (keepAlt) { - this.publicTags.push(["r", url, alt]) + if (existing) { + if (!existing[2]) { + existing[2] = alt + } else if (existing[2] === mode) { + this.publicTags = remove(existing, this.publicTags) + } } return this } setReadUrls(urls: string[]) { - return this.setUrlsForModes(urls, this.writeUrls()) + return this.setUrlsForModes(urls, getUrls(this.publicTags, "write")) } setWriteUrls(urls: string[]) { - return this.setUrlsForModes(this.readUrls(), urls) + return this.setUrlsForModes(getUrls(this.publicTags, "read"), urls) } private setUrlsForModes(readUrls: string[], writeUrls: string[]) { @@ -114,8 +101,8 @@ export class RelayListBuilder extends ListBuilder { read.has(url) && write.has(url) ? ["r", url] : read.has(url) - ? ["r", url, RelayMode.Read] - : ["r", url, RelayMode.Write], + ? ["r", url, "read"] + : ["r", url, "write"], ) this.publicTags = [...otherTags, ...relayTags]