From 0c4fbf70adbeeab9d8c132c01e4083ff38767225 Mon Sep 17 00:00:00 2001 From: Ticruz Date: Tue, 5 Nov 2024 17:51:26 +0100 Subject: [PATCH] remove group related utilities --- packages/app/src/router.ts | 217 ++++++++++++++++++----------------- packages/util/src/Address.ts | 11 +- packages/util/src/Tags.ts | 51 ++++---- 3 files changed, 135 insertions(+), 144 deletions(-) diff --git a/packages/app/src/router.ts b/packages/app/src/router.ts index f9ec073..6b4eda4 100644 --- a/packages/app/src/router.ts +++ b/packages/app/src/router.ts @@ -1,24 +1,53 @@ import { - intersection, first, switcher, throttleWithValue, clamp, last, splitAt, identity, sortBy, uniq, shuffle, - pushToMapKey, now, assoc, ctx, sample, -} from '@welshman/lib' + intersection, + first, + switcher, + throttleWithValue, + clamp, + last, + splitAt, + identity, + sortBy, + uniq, + shuffle, + pushToMapKey, + now, + assoc, + ctx, + sample, +} from "@welshman/lib" import { - Tags, getFilterId, unionFilters, isShareableRelayUrl, isCommunityAddress, isGroupAddress, isContextAddress, - PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS, LOCAL_RELAY_URL, WRAP, -} from '@welshman/util' -import type {TrustedEvent, Filter} from '@welshman/util' -import {ConnectionStatus, AuthStatus} from '@welshman/net' -import type {RelaysAndFilters} from '@welshman/net' -import {pubkey} from './session' -import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, getReadRelayUrls, getWriteRelayUrls, getRelayUrls} from './relaySelections' -import {relays, relaysByUrl} from './relays' + Tags, + getFilterId, + unionFilters, + isShareableRelayUrl, + isContextAddress, + PROFILE, + RELAYS, + INBOX_RELAYS, + FOLLOWS, + LOCAL_RELAY_URL, + WRAP, +} from "@welshman/util" +import type {TrustedEvent, Filter} from "@welshman/util" +import {ConnectionStatus, AuthStatus} from "@welshman/net" +import type {RelaysAndFilters} from "@welshman/net" +import {pubkey} from "./session" +import { + relaySelectionsByPubkey, + inboxRelaySelectionsByPubkey, + getReadRelayUrls, + getWriteRelayUrls, + getRelayUrls, +} from "./relaySelections" +import {relays, relaysByUrl} from "./relays" export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS] export enum RelayMode { Read = "read", Write = "write", - Inbox = "inbox" + Inbox = "inbox", } export type RouterOptions = { @@ -28,20 +57,6 @@ export type RouterOptions = { */ getUserPubkey?: () => string | null - /** - * Retrieves group relays for the specified community. - * @param address - The address to retrieve group relays for. - * @returns An array of group relay URLs as strings. - */ - getGroupRelays?: (address: string) => string[] - - /** - * Retrieves relays for the specified community. - * @param address - The address to retrieve community relays for. - * @returns An array of community relay URLs as strings. - */ - getCommunityRelays?: (address: string) => string[] - /** * Retrieves relays for the specified public key and mode. * @param pubkey - The public key to retrieve relays for. @@ -107,7 +122,7 @@ export type FallbackPolicy = (count: number, limit: number) => number export const addNoFallbacks = (count: number, redundancy: number) => 0 -export const addMinimalFallbacks = (count: number, redundancy: number) => count > 0 ? 0 : 1 +export const addMinimalFallbacks = (count: number, redundancy: number) => (count > 0 ? 0 : 1) export const addMaximalFallbacks = (count: number, redundancy: number) => redundancy - count @@ -125,13 +140,6 @@ export class Router { getUserSelections = (mode?: RelayMode) => this.getPubkeySelections([this.options.getUserPubkey?.()].filter(identity) as string[], mode) - getContextSelections = (tags: Tags) => { - return [ - ...tags.communities().mapTo(t => this.selection(t.value(), this.options.getCommunityRelays?.(t.value()) || [])).valueOf(), - ...tags.groups().mapTo(t => this.selection(t.value(), this.options.getGroupRelays?.(t.value()) || [])).valueOf(), - ] - } - // Utilities for creating ValueRelays selection = (value: string, relays: Iterable) => ({value, relays: Array.from(relays)}) @@ -147,8 +155,10 @@ export class Router { relaySelectionsFromMap = (valuesByRelay: ValuesByRelay) => sortBy( ({values}) => -values.length, - Array.from(valuesByRelay) - .map(([relay, values]: [string, string[]]) => ({relay, values: uniq(values)})) + Array.from(valuesByRelay).map(([relay, values]: [string, string[]]) => ({ + relay, + values: uniq(values), + })) ) scoreRelaySelection = ({values, relay}: RelayValues) => @@ -172,8 +182,7 @@ export class Router { merge = (scenarios: RouterScenario[]) => this.scenario(scenarios.flatMap((scenario: RouterScenario) => scenario.selections)) - product = (values: string[], relays: string[]) => - this.scenario(this.selections(values, relays)) + product = (values: string[], relays: string[]) => this.scenario(this.selections(values, relays)) fromRelays = (relays: string[]) => this.scenario([this.selection("", relays)]) @@ -200,28 +209,22 @@ export class Router { ]).policy(addMinimalFallbacks) Event = (event: TrustedEvent) => - this.scenario(this.forceValue(event.id, [ - this.getPubkeySelection(event.pubkey, RelayMode.Write), - ...this.getContextSelections(Tags.fromEvent(event).context()), - ])) + this.scenario( + this.forceValue(event.id, [this.getPubkeySelection(event.pubkey, RelayMode.Write)]) + ) EventChildren = (event: TrustedEvent) => - this.scenario(this.forceValue(event.id, [ - this.getPubkeySelection(event.pubkey, RelayMode.Read), - ...this.getContextSelections(Tags.fromEvent(event).context()), - ])) + this.scenario( + this.forceValue(event.id, [this.getPubkeySelection(event.pubkey, RelayMode.Read)]) + ) EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => { const tags = Tags.fromEvent(event) const ancestors = tags.ancestors()[type] const pubkeys = tags.values("p").valueOf() - const communities = tags.communities().values().valueOf() - const groups = tags.groups().values().valueOf() const relays = uniq([ - ...this.options.getPubkeyRelays?.(event.pubkey, RelayMode.Read) || [], + ...(this.options.getPubkeyRelays?.(event.pubkey, RelayMode.Read) || []), ...pubkeys.flatMap((k: string) => this.options.getPubkeyRelays?.(k, RelayMode.Write) || []), - ...communities.flatMap((a: string) => this.options.getCommunityRelays?.(a) || []), - ...groups.flatMap((a: string) => this.options.getGroupRelays?.(a) || []), ...ancestors.relays().valueOf(), ]) @@ -238,18 +241,12 @@ export class Router { const tags = Tags.fromEvent(event) const mentions = tags.values("p").valueOf() - // If we're publishing to private groups, only publish to those groups' relays - if (tags.groups().exists()) { - return this - .scenario(this.getContextSelections(tags.groups())) - .policy(addNoFallbacks) - } - - return this.scenario(this.forceValue(event.id, [ - this.getPubkeySelection(event.pubkey, RelayMode.Write), - ...this.getContextSelections(tags.context()), - ...this.getPubkeySelections(mentions, RelayMode.Read), - ])) + return this.scenario( + this.forceValue(event.id, [ + this.getPubkeySelection(event.pubkey, RelayMode.Write), + ...this.getPubkeySelections(mentions, RelayMode.Read), + ]) + ) } FromPubkeys = (pubkeys: string[]) => @@ -258,29 +255,6 @@ export class Router { ForPubkeys = (pubkeys: string[]) => this.scenario(this.getPubkeySelections(pubkeys, RelayMode.Read)) - WithinGroup = (address: string, relays?: string) => - this - .scenario(this.getContextSelections(Tags.wrap([["a", address]]))) - .policy(addNoFallbacks) - - WithinCommunity = (address: string) => - this.scenario(this.getContextSelections(Tags.wrap([["a", address]]))) - - WithinContext = (address: string) => { - if (isGroupAddress(address)) { - return this.WithinGroup(address) - } - - if (isCommunityAddress(address)) { - return this.WithinCommunity(address) - } - - throw new Error(`Unknown context ${address}`) - } - - WithinMultipleContexts = (addresses: string[]) => - this.merge(addresses.map(this.WithinContext)) - Search = (term: string, relays: string[] = []) => this.product([term], uniq(relays.concat(this.options.getSearchRelays?.() || []))) } @@ -294,16 +268,28 @@ export type RouterScenarioOptions = { } export class RouterScenario { - constructor(readonly router: Router, readonly selections: ValueRelays[], readonly options: RouterScenarioOptions = {}) {} + constructor( + readonly router: Router, + readonly selections: ValueRelays[], + readonly options: RouterScenarioOptions = {} + ) {} clone = (options: RouterScenarioOptions) => new RouterScenario(this.router, this.selections, {...this.options, ...options}) filter = (f: (selection: ValueRelays) => boolean) => - new RouterScenario(this.router, this.selections.filter(selection => f(selection)), this.options) + new RouterScenario( + this.router, + this.selections.filter(selection => f(selection)), + this.options + ) update = (f: (selection: ValueRelays) => ValueRelays) => - new RouterScenario(this.router, this.selections.map(selection => f(selection)), this.options) + new RouterScenario( + this.router, + this.selections.map(selection => f(selection)), + this.options + ) redundancy = (redundancy: number) => this.clone({redundancy}) @@ -334,7 +320,10 @@ export class RouterScenario { // are we're less tolerant of failure. Add more redundancy to fill our relay limit. const limit = this.getLimit() const redundancy = this.getRedundancy() - const adjustedRedundancy = Math.max(redundancy, redundancy * (limit / (allValues.size * redundancy))) + const adjustedRedundancy = Math.max( + redundancy, + redundancy * (limit / (allValues.size * redundancy)) + ) const seen = new Map() const result: ValuesByRelay = new Map() @@ -371,7 +360,9 @@ export class RouterScenario { const [keep, discard] = splitAt(limit, this.router.relaySelectionsFromMap(result)) for (const target of keep.slice(0, redundancy)) { - target.values = uniq(discard.concat(target).flatMap((selection: RelayValues) => selection.values)) + target.values = uniq( + discard.concat(target).flatMap((selection: RelayValues) => selection.values) + ) } return keep @@ -437,23 +428,30 @@ export const getPubkeyRelays = (pubkey: string, mode?: string) => { const $inboxSelections = inboxRelaySelectionsByPubkey.get() switch (mode) { - case RelayMode.Read: return getReadRelayUrls($relaySelections.get(pubkey)) - case RelayMode.Write: return getWriteRelayUrls($relaySelections.get(pubkey)) - case RelayMode.Inbox: return getRelayUrls($inboxSelections.get(pubkey)) - default: return getRelayUrls($relaySelections.get(pubkey)) + case RelayMode.Read: + return getReadRelayUrls($relaySelections.get(pubkey)) + case RelayMode.Write: + return getWriteRelayUrls($relaySelections.get(pubkey)) + case RelayMode.Inbox: + return getRelayUrls($inboxSelections.get(pubkey)) + default: + return getRelayUrls($relaySelections.get(pubkey)) } } export const getIndexerRelays = () => ctx.app.indexerRelays || getFallbackRelays() export const getFallbackRelays = throttleWithValue(300, () => - sortBy(r => -getRelayQuality(r.url), relays.get()).slice(0, 30).map(r => r.url) + sortBy(r => -getRelayQuality(r.url), relays.get()) + .slice(0, 30) + .map(r => r.url) ) export const getSearchRelays = throttleWithValue(300, () => sortBy(r => -getRelayQuality(r.url), relays.get()) .filter(r => r.profile?.supported_nips?.includes(50)) - .slice(0, 30).map(r => r.url) + .slice(0, 30) + .map(r => r.url) ) export const makeRouter = (options: Partial = {}) => @@ -472,20 +470,23 @@ export const makeRouter = (options: Partial = {}) => // Infer relay selections from filters export type FilterSelection = { - id: string, - filter: Filter, + id: string + filter: Filter scenario: RouterScenario } type FilterSelectionRuleState = { - filter: Filter, + filter: Filter selections: FilterSelection[] } type FilterSelectionRule = (state: FilterSelectionRuleState) => boolean -export const makeFilterSelection = (id: string, filter: Filter, scenario: RouterScenario) => - ({id, filter, scenario}) +export const makeFilterSelection = (id: string, filter: Filter, scenario: RouterScenario) => ({ + id, + filter, + scenario, +}) export const getFilterSelectionsForLocalRelay = (state: FilterSelectionRuleState) => { const id = getFilterId(state.filter) @@ -530,7 +531,7 @@ export const getFilterSelectionsForWraps = (state: FilterSelectionRuleState) => if (!state.filter.kinds?.includes(WRAP) || state.filter.authors) return false const id = getFilterId({...state.filter, kinds: [WRAP]}) - const scenario = ctx.app.router.InboxRelays().update(assoc('value', id)) + const scenario = ctx.app.router.InboxRelays().update(assoc("value", id)) state.selections.push(makeFilterSelection(id, state.filter, scenario)) @@ -557,7 +558,7 @@ export const getFilterSelectionsForAuthors = (state: FilterSelectionRuleState) = const id = getFilterId(state.filter) const pubkeys = sample(50, state.filter.authors!) - const scenario = ctx.app.router.FromPubkeys(pubkeys).update(assoc('value', id)) + const scenario = ctx.app.router.FromPubkeys(pubkeys).update(assoc("value", id)) state.selections.push(makeFilterSelection(id, state.filter, scenario)) @@ -584,7 +585,10 @@ export const defaultFilterSelectionRules = [ getFilterSelectionsForUser, ] -export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRule[] = defaultFilterSelectionRules): RelaysAndFilters[] => { +export const getFilterSelections = ( + filters: Filter[], + rules: FilterSelectionRule[] = defaultFilterSelectionRules +): RelaysAndFilters[] => { const scenarios: RouterScenario[] = [] const filtersById = new Map() @@ -605,7 +609,6 @@ export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRul } } - // Use low redundancy because filters will be very low cardinality const selections = ctx.app.router .merge(scenarios) diff --git a/packages/util/src/Address.ts b/packages/util/src/Address.ts index f5be6be..8c95d9f 100644 --- a/packages/util/src/Address.ts +++ b/packages/util/src/Address.ts @@ -1,5 +1,4 @@ -import {nip19} from 'nostr-tools' -import {GROUP, COMMUNITY} from './Kinds' +import {nip19} from "nostr-tools" // Define this locally to avoid circular dependencies type AddressableEvent = { @@ -30,7 +29,7 @@ export class Address { let type let data = {} as any try { - ({type, data} = nip19.decode(naddr) as { + ;({type, data} = nip19.decode(naddr) as { type: "naddr" data: any }) @@ -59,9 +58,3 @@ export class Address { // Utils export const getAddress = (e: AddressableEvent) => Address.fromEvent(e).toString() - -export const isGroupAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && Address.from(a).kind === GROUP - -export const isCommunityAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && Address.from(a).kind === COMMUNITY - -export const isContextAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && [GROUP, COMMUNITY].includes(Address.from(a).kind) diff --git a/packages/util/src/Tags.ts b/packages/util/src/Tags.ts index 60439bc..5eec40b 100644 --- a/packages/util/src/Tags.ts +++ b/packages/util/src/Tags.ts @@ -1,10 +1,9 @@ -import type {OmitStatics} from '@welshman/lib' -import {Fluent, uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from '@welshman/lib' -import {isRelayUrl, isShareableRelayUrl, normalizeRelayUrl} from './Relay' -import {Address, isContextAddress} from './Address' -import {GROUP, COMMUNITY} from './Kinds' +import type {OmitStatics} from "@welshman/lib" +import {Fluent, uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib" +import {isRelayUrl, isShareableRelayUrl, normalizeRelayUrl} from "./Relay" +import {Address} from "./Address" -export class Tag extends (Fluent as OmitStatics, 'from'>) { +export class Tag extends (Fluent as OmitStatics, "from">) { static from = (xs: Iterable) => new Tag(Array.from(xs)) static fromId = (id: string) => new Tag(["e", id]) @@ -28,15 +27,9 @@ export class Tag extends (Fluent as OmitStatics, ' setValue = (v: string) => this.set(1, v) isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`) - - isGroup = () => this.isAddress(GROUP) - - isCommunity = () => this.isAddress(COMMUNITY) - - isContext = () => this.isAddress(GROUP) || this.isAddress(COMMUNITY) } -export class Tags extends (Fluent as OmitStatics, 'from'>) { +export class Tags extends (Fluent as OmitStatics, "from">) { static from = (p: Iterable) => new Tags(Array.from(p)) static wrap = (p: Iterable) => new Tags(Array.from(p).map(Tag.from)) @@ -70,9 +63,18 @@ export class Tags extends (Fluent as OmitStatics, 'from' entries = () => this.mapTo(t => t.entry()) - relays = () => this.flatMap((t: Tag) => t.valueOf().filter(isRelayUrl).map(url => normalizeRelayUrl(url))).uniq() + relays = () => + this.flatMap((t: Tag) => + t + .valueOf() + .filter(isRelayUrl) + .map(url => normalizeRelayUrl(url)) + ).uniq() - topics = () => this.whereKey("t").values().map((t: string) => t.replace(/^#/, "")) + topics = () => + this.whereKey("t") + .values() + .map((t: string) => t.replace(/^#/, "")) ancestors = (x?: boolean) => { const {roots, replies, mentions} = getAncestorTags(this.unwrap()) @@ -114,10 +116,6 @@ export class Tags extends (Fluent as OmitStatics, 'from' return parents.get("e") || parents.get("a") } - groups = () => this.whereKey("a").filter(t => t.isGroup()) - - communities = () => this.whereKey("a").filter(t => t.isCommunity()) - context = () => this.whereKey("a").filter(t => t.isContext()) asObject = () => { @@ -161,7 +159,7 @@ export class Tags extends (Fluent as OmitStatics, 'from' addImages = (imeta: Tags[]) => this.concat(imeta.map(tags => Tag.from(["image", tags.get("url").value()]))) - removeImages = () => this.rejectByKey(['image']) + removeImages = () => this.rejectByKey(["image"]) setImages = (imeta: Tags[]) => this.removeImages().addImages(imeta) @@ -170,7 +168,7 @@ export class Tags extends (Fluent as OmitStatics, 'from' addIMeta = (imeta: Tags[]) => this.concat(imeta.map(tags => Tag.from(["imeta", ...tags.valueOf().map(xs => xs.join(" "))]))) - removeIMeta = () => this.rejectByKey(['imeta']) + removeIMeta = () => this.rejectByKey(["imeta"]) setIMeta = (imeta: Tags[]) => this.removeIMeta().addIMeta(imeta) } @@ -196,9 +194,6 @@ export const getAddressTags = (tags: string[][]) => export const getAddressTagValues = (tags: string[][]) => getAddressTags(tags).map(nth(1)) -export const getContextTagValues = (tags: string[][]) => - getAddressTagValues(tags).filter(isContextAddress) - export const getPubkeyTags = (tags: string[][]) => tags.filter(t => ["p"].includes(t[0]) && t[1].length === 64) @@ -224,7 +219,7 @@ export const getKindTags = (tags: string[][]) => export const getKindTagValues = (tags: string[][]) => getKindTags(tags).map(t => parseInt(t[1])) export const getAncestorTags = (tags: string[][]) => { - const validTags = tags.filter(t => ["a", "e", "q"].includes(t[0]) && !isContextAddress(t[1])) + const validTags = tags.filter(t => ["a", "e", "q"].includes(t[0])) const mentionTags = validTags.filter(nthEq(0, "q")) const roots: string[][] = [] const replies: string[][] = [] @@ -232,15 +227,15 @@ export const getAncestorTags = (tags: string[][]) => { const dispatchTags = (thisTags: string[][]) => thisTags.forEach((t: string[], i: number) => { - if (t[3] === 'root') { + if (t[3] === "root") { if (validTags.filter(nthEq(3, "reply")).length === 0) { replies.push(t) } else { roots.push(t) } - } else if (t[3] === 'reply') { + } else if (t[3] === "reply") { replies.push(t) - } else if (t[3] === 'mention') { + } else if (t[3] === "mention") { mentions.push(t) } else if (i === thisTags.length - 1) { replies.push(t)