Remove router singleton
This commit is contained in:
@@ -2,6 +2,7 @@ import {Scope, FeedController} from "@welshman/feeds"
|
|||||||
import type {FeedControllerOptions, Feed} from "@welshman/feeds"
|
import type {FeedControllerOptions, Feed} from "@welshman/feeds"
|
||||||
import type {AdapterContext} from "@welshman/net"
|
import type {AdapterContext} from "@welshman/net"
|
||||||
import type {IClient} from "./client.js"
|
import type {IClient} from "./client.js"
|
||||||
|
import {Router} from "./router.js"
|
||||||
import {Wot} from "./wot.js"
|
import {Wot} from "./wot.js"
|
||||||
|
|
||||||
export type MakeFeedControllerOptions = Partial<Omit<FeedControllerOptions, "feed">> & {feed: Feed}
|
export type MakeFeedControllerOptions = Partial<Omit<FeedControllerOptions, "feed">> & {feed: Feed}
|
||||||
@@ -58,6 +59,7 @@ export class Feeds {
|
|||||||
|
|
||||||
makeFeedController = (options: MakeFeedControllerOptions) =>
|
makeFeedController = (options: MakeFeedControllerOptions) =>
|
||||||
new FeedController({
|
new FeedController({
|
||||||
|
router: this.ctx.use(Router),
|
||||||
getPubkeysForScope: this.getPubkeysForScope,
|
getPubkeysForScope: this.getPubkeysForScope,
|
||||||
getPubkeysForWOTRange: this.getPubkeysForWOTRange,
|
getPubkeysForWOTRange: this.getPubkeysForWOTRange,
|
||||||
signer: this.ctx.user?.signer,
|
signer: this.ctx.user?.signer,
|
||||||
|
|||||||
+23
-253
@@ -1,259 +1,29 @@
|
|||||||
import {nth, uniq, first, sortBy, shuffle, inc, add, take} from "@welshman/lib"
|
import {Router as BaseRouter} from "@welshman/router"
|
||||||
import {
|
|
||||||
isRelayUrl,
|
|
||||||
isOnionUrl,
|
|
||||||
isLocalUrl,
|
|
||||||
isShareableRelayUrl,
|
|
||||||
getPubkeyTagValues,
|
|
||||||
normalizeRelayUrl,
|
|
||||||
getAncestorTags,
|
|
||||||
getPubkeyTags,
|
|
||||||
RelayMode,
|
|
||||||
} from "@welshman/util"
|
|
||||||
import type {TrustedEvent, Filter} from "@welshman/util"
|
|
||||||
import type {IClient} from "./client.js"
|
|
||||||
import {RelayLists} from "./relayLists.js"
|
import {RelayLists} from "./relayLists.js"
|
||||||
import {RelayStats} from "./relayStats.js"
|
import {RelayStats} from "./relayStats.js"
|
||||||
|
import type {IClient} from "./client.js"
|
||||||
|
|
||||||
export type RelaysAndFilters = {
|
// Re-export the upstream router surface (scenarios, fallback policies,
|
||||||
relays: string[]
|
// makeSelection, getFilterSelections, types). The local `Router` below shadows
|
||||||
filters: Filter[]
|
// the upstream `Router` in this re-export.
|
||||||
}
|
export * from "@welshman/router"
|
||||||
|
|
||||||
export type Selection = {
|
/**
|
||||||
weight: number
|
* The upstream `@welshman/router` Router, wired to this client: relay lists come
|
||||||
relays: string[]
|
* from the `RelayLists` collection, quality from `RelayStats`, and the user
|
||||||
}
|
* pubkey + relay-getters from the client (via `ctx.config`). Reach it via
|
||||||
|
* `client.use(Router)`. This replaces the old forked copy — one source of truth,
|
||||||
export const makeSelection = (relays: string[], weight = 1): Selection => ({
|
* no global `routerContext`/`Router.get()`.
|
||||||
relays: relays.filter(isRelayUrl).map(normalizeRelayUrl),
|
*/
|
||||||
weight,
|
export class Router extends BaseRouter {
|
||||||
})
|
constructor(ctx: IClient) {
|
||||||
|
super({
|
||||||
// Fallback policies
|
getUserPubkey: () => ctx.user?.pubkey,
|
||||||
|
getPubkeyRelays: (pubkey, mode) => ctx.use(RelayLists).getRelaysForPubkey(pubkey, mode),
|
||||||
export type FallbackPolicy = (count: number, limit: number) => number
|
getRelayQuality: url => ctx.use(RelayStats).getQuality(url),
|
||||||
|
getDefaultRelays: ctx.config.getDefaultRelays,
|
||||||
export const addNoFallbacks = (count: number, limit: number) => 0
|
getIndexerRelays: ctx.config.getIndexerRelays,
|
||||||
|
getSearchRelays: ctx.config.getSearchRelays,
|
||||||
export const addMinimalFallbacks = (count: number, limit: number) => (count > 0 ? 0 : 1)
|
})
|
||||||
|
|
||||||
export const addMaximalFallbacks = (count: number, limit: number) => limit - count
|
|
||||||
|
|
||||||
// Router class
|
|
||||||
|
|
||||||
export class Router {
|
|
||||||
constructor(readonly ctx: IClient) {}
|
|
||||||
|
|
||||||
// Utilities derived from the relay-list collection and client config
|
|
||||||
|
|
||||||
getRelaysForPubkey = (pubkey: string, mode?: RelayMode) =>
|
|
||||||
this.ctx.use(RelayLists).getRelaysForPubkey(pubkey, mode)
|
|
||||||
|
|
||||||
getRelaysForPubkeys = (pubkeys: string[], mode?: RelayMode) =>
|
|
||||||
pubkeys.map(pubkey => this.getRelaysForPubkey(pubkey, mode))
|
|
||||||
|
|
||||||
getRelaysForUser = (mode?: RelayMode) => {
|
|
||||||
const pubkey = this.ctx.user?.pubkey
|
|
||||||
|
|
||||||
return pubkey ? this.getRelaysForPubkey(pubkey, mode) : []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utilities for creating scenarios
|
|
||||||
|
|
||||||
scenario = (selections: Selection[]) => new RouterScenario(this, selections)
|
|
||||||
|
|
||||||
merge = (scenarios: RouterScenario[]) =>
|
|
||||||
this.scenario(scenarios.flatMap((scenario: RouterScenario) => scenario.selections))
|
|
||||||
|
|
||||||
// Routing scenarios
|
|
||||||
|
|
||||||
FromRelays = (relays: string[]) => this.scenario([makeSelection(relays)])
|
|
||||||
|
|
||||||
Search = () => this.FromRelays(this.ctx.config.getSearchRelays?.() || [])
|
|
||||||
|
|
||||||
Index = () => this.FromRelays(this.ctx.config.getIndexerRelays?.() || [])
|
|
||||||
|
|
||||||
Default = () => this.FromRelays(this.ctx.config.getDefaultRelays?.() || [])
|
|
||||||
|
|
||||||
ForUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Read))
|
|
||||||
|
|
||||||
FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write))
|
|
||||||
|
|
||||||
MessagesForUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Messaging))
|
|
||||||
|
|
||||||
ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
|
|
||||||
|
|
||||||
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
|
|
||||||
|
|
||||||
MessagesForPubkey = (pubkey: string) =>
|
|
||||||
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Messaging))
|
|
||||||
|
|
||||||
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
|
|
||||||
|
|
||||||
FromPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey)))
|
|
||||||
|
|
||||||
MessagesForPubkeys = (pubkeys: string[]) =>
|
|
||||||
this.merge(pubkeys.map(pubkey => this.MessagesForPubkey(pubkey)))
|
|
||||||
|
|
||||||
Event = (event: TrustedEvent) =>
|
|
||||||
this.FromRelays(this.getRelaysForPubkey(event.pubkey, RelayMode.Write))
|
|
||||||
|
|
||||||
Replies = (event: TrustedEvent) =>
|
|
||||||
this.FromRelays(this.getRelaysForPubkey(event.pubkey, RelayMode.Read))
|
|
||||||
|
|
||||||
Quote = (event: TrustedEvent, value: string, relays: string[] = []) => {
|
|
||||||
const tag = event.tags.find(t => t[1] === value)
|
|
||||||
const scenarios = [
|
|
||||||
this.FromRelays(relays),
|
|
||||||
this.ForPubkey(event.pubkey),
|
|
||||||
this.FromPubkey(event.pubkey),
|
|
||||||
]
|
|
||||||
|
|
||||||
if (tag?.[2] && isShareableRelayUrl(tag[2])) {
|
|
||||||
scenarios.push(this.FromRelays([tag[2]]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag?.[3]?.length === 64) {
|
|
||||||
scenarios.push(this.FromPubkeys([tag[3]]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.merge(scenarios)
|
|
||||||
}
|
|
||||||
|
|
||||||
EventParents = (event: TrustedEvent) => {
|
|
||||||
const {replies} = getAncestorTags(event)
|
|
||||||
const mentions = getPubkeyTags(event.tags)
|
|
||||||
const authors = replies.map(nth(3)).filter(p => p?.length === 64)
|
|
||||||
const others = mentions.map(nth(1)).filter(p => p?.length === 64)
|
|
||||||
const relays = uniq([...replies, ...mentions].map(nth(2)).filter(r => r && isRelayUrl(r)))
|
|
||||||
|
|
||||||
return this.merge([
|
|
||||||
this.FromPubkeys(authors).weight(10),
|
|
||||||
this.FromPubkeys(others),
|
|
||||||
this.FromRelays(relays),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
EventRoots = (event: TrustedEvent) => {
|
|
||||||
const {roots} = getAncestorTags(event)
|
|
||||||
const mentions = getPubkeyTags(event.tags)
|
|
||||||
const authors = roots.map(nth(3)).filter(p => p?.length === 64)
|
|
||||||
const others = mentions.map(nth(1)).filter(p => p?.length === 64)
|
|
||||||
const relays = uniq([...roots, ...mentions].map(nth(2)).filter(r => r && isRelayUrl(r)))
|
|
||||||
|
|
||||||
return this.merge([
|
|
||||||
this.FromPubkeys(authors).weight(10),
|
|
||||||
this.FromPubkeys(others),
|
|
||||||
this.FromRelays(relays),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishEvent = (event: TrustedEvent) => {
|
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
|
||||||
const scenarios = [
|
|
||||||
this.FromPubkey(event.pubkey),
|
|
||||||
...pubkeys.map(pubkey => this.ForPubkey(pubkey).weight(0.5)),
|
|
||||||
]
|
|
||||||
|
|
||||||
// Override the limit to ensure deliverability even when lots of pubkeys are mentioned
|
|
||||||
return this.merge(scenarios).limit(30)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router Scenario
|
|
||||||
|
|
||||||
export type RouterScenarioOptions = {
|
|
||||||
policy?: FallbackPolicy
|
|
||||||
limit?: number
|
|
||||||
allowLocal?: boolean
|
|
||||||
allowOnion?: boolean
|
|
||||||
allowInsecure?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RouterScenario {
|
|
||||||
constructor(
|
|
||||||
readonly router: Router,
|
|
||||||
readonly selections: Selection[],
|
|
||||||
readonly options: RouterScenarioOptions = {},
|
|
||||||
) {}
|
|
||||||
|
|
||||||
clone = (options: RouterScenarioOptions) =>
|
|
||||||
new RouterScenario(this.router, this.selections, {...this.options, ...options})
|
|
||||||
|
|
||||||
filter = (f: (selection: Selection) => boolean) =>
|
|
||||||
new RouterScenario(
|
|
||||||
this.router,
|
|
||||||
this.selections.filter(selection => f(selection)),
|
|
||||||
this.options,
|
|
||||||
)
|
|
||||||
|
|
||||||
update = (f: (selection: Selection) => Selection) =>
|
|
||||||
new RouterScenario(
|
|
||||||
this.router,
|
|
||||||
this.selections.map(selection => f(selection)),
|
|
||||||
this.options,
|
|
||||||
)
|
|
||||||
|
|
||||||
policy = (policy: FallbackPolicy) => this.clone({policy})
|
|
||||||
|
|
||||||
limit = (limit: number) => this.clone({limit})
|
|
||||||
|
|
||||||
allowLocal = (allowLocal: boolean) => this.clone({allowLocal})
|
|
||||||
|
|
||||||
allowOnion = (allowOnion: boolean) => this.clone({allowOnion})
|
|
||||||
|
|
||||||
allowInsecure = (allowInsecure: boolean) => this.clone({allowInsecure})
|
|
||||||
|
|
||||||
weight = (scale: number) =>
|
|
||||||
this.update(selection => ({...selection, weight: selection.weight * scale}))
|
|
||||||
|
|
||||||
getPolicy = () => this.options.policy ?? addNoFallbacks
|
|
||||||
|
|
||||||
getLimit = () => this.options.limit ?? 3
|
|
||||||
|
|
||||||
getUrls = () => {
|
|
||||||
const limit = this.getLimit()
|
|
||||||
const fallbackPolicy = this.getPolicy()
|
|
||||||
const relayWeights = new Map<string, number>()
|
|
||||||
const {allowOnion, allowLocal, allowInsecure} = this.options
|
|
||||||
|
|
||||||
for (const {weight, relays} of this.selections) {
|
|
||||||
for (const relay of relays) {
|
|
||||||
if (!isRelayUrl(relay)) continue
|
|
||||||
if (!allowOnion && isOnionUrl(relay)) continue
|
|
||||||
if (!allowLocal && isLocalUrl(relay)) continue
|
|
||||||
if (!allowInsecure && relay.startsWith("ws://") && !isOnionUrl(relay)) continue
|
|
||||||
|
|
||||||
relayWeights.set(relay, add(weight, relayWeights.get(relay)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scoreRelay = (relay: string) => {
|
|
||||||
const weight = relayWeights.get(relay)!
|
|
||||||
const quality = this.router.ctx.use(RelayStats).getQuality(relay)
|
|
||||||
|
|
||||||
// Log the weight, since it's a straight count which ends up over-weighting hubs.
|
|
||||||
// Also add some random noise so that we'll occasionally pick lower quality/less
|
|
||||||
// popular relays.
|
|
||||||
return -(quality * inc(Math.log(weight)) * Math.random())
|
|
||||||
}
|
|
||||||
|
|
||||||
const relays = take(
|
|
||||||
limit,
|
|
||||||
sortBy(scoreRelay, Array.from(relayWeights.keys()).filter(scoreRelay)),
|
|
||||||
)
|
|
||||||
|
|
||||||
const fallbacksNeeded = fallbackPolicy(relays.length, limit)
|
|
||||||
const allFallbackRelays: string[] = this.router.ctx.config.getDefaultRelays?.() || []
|
|
||||||
const fallbackRelays = shuffle(allFallbackRelays).slice(0, fallbacksNeeded)
|
|
||||||
|
|
||||||
for (const fallbackRelay of fallbackRelays) {
|
|
||||||
relays.push(fallbackRelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
return relays
|
|
||||||
}
|
|
||||||
|
|
||||||
getUrl = () => first(this.getUrls())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"@welshman/feeds": ["../feeds/src/index.js"],
|
"@welshman/feeds": ["../feeds/src/index.js"],
|
||||||
"@welshman/lib": ["../lib/src/index.js"],
|
"@welshman/lib": ["../lib/src/index.js"],
|
||||||
"@welshman/net": ["../net/src/index.js"],
|
"@welshman/net": ["../net/src/index.js"],
|
||||||
|
"@welshman/router": ["../router/src/index.js"],
|
||||||
"@welshman/signer": ["../signer/src/index.js"],
|
"@welshman/signer": ["../signer/src/index.js"],
|
||||||
"@welshman/store": ["../store/src/index.js"],
|
"@welshman/store": ["../store/src/index.js"],
|
||||||
"@welshman/util": ["../util/src/index.js"]
|
"@welshman/util": ["../util/src/index.js"]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {ISigner} from "@welshman/signer"
|
import {ISigner} from "@welshman/signer"
|
||||||
import {AdapterContext} from "@welshman/net"
|
import {AdapterContext} from "@welshman/net"
|
||||||
|
import type {Router} from "@welshman/router"
|
||||||
import {
|
import {
|
||||||
CreatedAtItem,
|
CreatedAtItem,
|
||||||
RequestItem,
|
RequestItem,
|
||||||
@@ -25,6 +26,7 @@ import {getFeedArgs, feedsFromTags} from "./utils.js"
|
|||||||
import {requestPage, requestDVM} from "./request.js"
|
import {requestPage, requestDVM} from "./request.js"
|
||||||
|
|
||||||
export type FeedCompilerOptions = {
|
export type FeedCompilerOptions = {
|
||||||
|
router: Router
|
||||||
signer?: ISigner
|
signer?: ISigner
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
@@ -157,6 +159,7 @@ export class FeedCompiler {
|
|||||||
items.map(({mappings, ...request}) =>
|
items.map(({mappings, ...request}) =>
|
||||||
requestDVM({
|
requestDVM({
|
||||||
...request,
|
...request,
|
||||||
|
router: this.options.router,
|
||||||
signer: this.options.signer,
|
signer: this.options.signer,
|
||||||
context: this.options.context,
|
context: this.options.context,
|
||||||
onResult: async (e: TrustedEvent) => {
|
onResult: async (e: TrustedEvent) => {
|
||||||
@@ -267,6 +270,7 @@ export class FeedCompiler {
|
|||||||
const eventsByAddress = new Map<string, TrustedEvent>()
|
const eventsByAddress = new Map<string, TrustedEvent>()
|
||||||
|
|
||||||
await requestPage({
|
await requestPage({
|
||||||
|
router: this.options.router,
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
signal: this.options.signal,
|
signal: this.options.signal,
|
||||||
context: this.options.context,
|
context: this.options.context,
|
||||||
@@ -304,6 +308,7 @@ export class FeedCompiler {
|
|||||||
labelItems.map(({mappings, relays, ...filter}) =>
|
labelItems.map(({mappings, relays, ...filter}) =>
|
||||||
requestPage({
|
requestPage({
|
||||||
relays,
|
relays,
|
||||||
|
router: this.options.router,
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
signal: this.options.signal,
|
signal: this.options.signal,
|
||||||
context: this.options.context,
|
context: this.options.context,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ export class FeedController {
|
|||||||
await requestPage(
|
await requestPage(
|
||||||
omitVals([undefined], {
|
omitVals([undefined], {
|
||||||
relays,
|
relays,
|
||||||
|
router: this.options.router,
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
filters: trimFilters(requestFilters),
|
filters: trimFilters(requestFilters),
|
||||||
signal: this.options.signal,
|
signal: this.options.signal,
|
||||||
@@ -360,6 +361,7 @@ export class FeedController {
|
|||||||
requestPage(
|
requestPage(
|
||||||
omitVals([undefined], {
|
omitVals([undefined], {
|
||||||
relays,
|
relays,
|
||||||
|
router: this.options.router,
|
||||||
signal,
|
signal,
|
||||||
onEvent: (event: TrustedEvent) => onEvent?.(event),
|
onEvent: (event: TrustedEvent) => onEvent?.(event),
|
||||||
filters: trimFilters(requestFilters),
|
filters: trimFilters(requestFilters),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {LOCAL_RELAY_URL, Tracker, AdapterContext, request, publish} from "@welsh
|
|||||||
|
|
||||||
export type RequestPageOptions = {
|
export type RequestPageOptions = {
|
||||||
filters: Filter[]
|
filters: Filter[]
|
||||||
|
router: Router
|
||||||
onEvent: (event: TrustedEvent) => void
|
onEvent: (event: TrustedEvent) => void
|
||||||
relays?: string[]
|
relays?: string[]
|
||||||
tracker?: Tracker
|
tracker?: Tracker
|
||||||
@@ -25,6 +26,7 @@ export type RequestPageOptions = {
|
|||||||
|
|
||||||
export const requestPage = async ({
|
export const requestPage = async ({
|
||||||
filters,
|
filters,
|
||||||
|
router,
|
||||||
onEvent,
|
onEvent,
|
||||||
relays = [],
|
relays = [],
|
||||||
tracker = new Tracker(),
|
tracker = new Tracker(),
|
||||||
@@ -49,14 +51,14 @@ export const requestPage = async ({
|
|||||||
threshold: 0.1,
|
threshold: 0.1,
|
||||||
autoClose,
|
autoClose,
|
||||||
filters: withSearch,
|
filters: withSearch,
|
||||||
relays: Router.get().Search().getUrls(),
|
relays: router.Search().getUrls(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withoutSearch.length > 0) {
|
if (withoutSearch.length > 0) {
|
||||||
promises.push(
|
promises.push(
|
||||||
...getFilterSelections(filters).flatMap(({relays, filters}) =>
|
...getFilterSelections(filters, router).flatMap(({relays, filters}) =>
|
||||||
request({
|
request({
|
||||||
tracker,
|
tracker,
|
||||||
signal,
|
signal,
|
||||||
@@ -91,6 +93,7 @@ export const requestPage = async ({
|
|||||||
|
|
||||||
export type RequestDVMOptions = {
|
export type RequestDVMOptions = {
|
||||||
kind: number
|
kind: number
|
||||||
|
router: Router
|
||||||
tags?: string[][]
|
tags?: string[][]
|
||||||
relays?: string[]
|
relays?: string[]
|
||||||
signer?: ISigner
|
signer?: ISigner
|
||||||
@@ -100,6 +103,7 @@ export type RequestDVMOptions = {
|
|||||||
|
|
||||||
export const requestDVM = async ({
|
export const requestDVM = async ({
|
||||||
kind,
|
kind,
|
||||||
|
router,
|
||||||
onResult,
|
onResult,
|
||||||
tags = [],
|
tags = [],
|
||||||
relays = [],
|
relays = [],
|
||||||
@@ -110,10 +114,10 @@ export const requestDVM = async ({
|
|||||||
const events = await request({
|
const events = await request({
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
filters: [{kinds: [RELAYS], authors: getPubkeyTagValues(tags)}],
|
filters: [{kinds: [RELAYS], authors: getPubkeyTagValues(tags)}],
|
||||||
relays: Router.get().Index().policy(addMinimalFallbacks).getUrls(),
|
relays: router.Index().policy(addMinimalFallbacks).getUrls(),
|
||||||
})
|
})
|
||||||
|
|
||||||
relays = Router.get()
|
relays = router
|
||||||
.FromRelays(events.flatMap(e => getRelaysFromList(readList(asDecryptedEvent(e)))))
|
.FromRelays(events.flatMap(e => getRelaysFromList(readList(asDecryptedEvent(e)))))
|
||||||
.policy(addMinimalFallbacks)
|
.policy(addMinimalFallbacks)
|
||||||
.getUrls()
|
.getUrls()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
nth,
|
nth,
|
||||||
uniq,
|
uniq,
|
||||||
intersection,
|
intersection,
|
||||||
mergeLeft,
|
|
||||||
first,
|
first,
|
||||||
clamp,
|
clamp,
|
||||||
sortBy,
|
sortBy,
|
||||||
@@ -28,14 +27,10 @@ import {
|
|||||||
normalizeRelayUrl,
|
normalizeRelayUrl,
|
||||||
TrustedEvent,
|
TrustedEvent,
|
||||||
Filter,
|
Filter,
|
||||||
readList,
|
|
||||||
getAncestorTags,
|
getAncestorTags,
|
||||||
asDecryptedEvent,
|
|
||||||
getRelaysFromList,
|
|
||||||
getPubkeyTags,
|
getPubkeyTags,
|
||||||
RelayMode,
|
RelayMode,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Repository} from "@welshman/net"
|
|
||||||
|
|
||||||
export const INDEXED_KINDS = [PROFILE, RELAYS, MESSAGING_RELAYS, FOLLOWS]
|
export const INDEXED_KINDS = [PROFILE, RELAYS, MESSAGING_RELAYS, FOLLOWS]
|
||||||
|
|
||||||
@@ -114,30 +109,8 @@ export const addMaximalFallbacks = (count: number, limit: number) => limit - cou
|
|||||||
|
|
||||||
// Router class
|
// Router class
|
||||||
|
|
||||||
export const routerContext: RouterOptions = {
|
|
||||||
getPubkeyRelays: (pubkey: string, mode?: RelayMode) => {
|
|
||||||
return uniq(
|
|
||||||
Repository.get()
|
|
||||||
.query([{kinds: [RELAYS], authors: [pubkey]}])
|
|
||||||
.flatMap(event => getRelaysFromList(readList(asDecryptedEvent(event)), mode)),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Router {
|
export class Router {
|
||||||
readonly options: RouterOptions
|
constructor(readonly options: RouterOptions) {}
|
||||||
|
|
||||||
static configure(options: RouterOptions) {
|
|
||||||
Object.assign(routerContext, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
static get() {
|
|
||||||
return new Router(routerContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(options: RouterOptions) {
|
|
||||||
this.options = mergeLeft(options, routerContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utilities derived from options
|
// Utilities derived from options
|
||||||
|
|
||||||
@@ -357,58 +330,58 @@ export class RouterScenario {
|
|||||||
|
|
||||||
type FilterScenario = {filter: Filter; scenario: RouterScenario}
|
type FilterScenario = {filter: Filter; scenario: RouterScenario}
|
||||||
|
|
||||||
type FilterSelectionRule = (filter: Filter) => FilterScenario[]
|
type FilterSelectionRule = (filter: Filter, router: Router) => FilterScenario[]
|
||||||
|
|
||||||
export const getFilterSelectionsForSearch = (filter: Filter) => {
|
export const getFilterSelectionsForSearch = (filter: Filter, router: Router) => {
|
||||||
if (!filter.search) return []
|
if (!filter.search) return []
|
||||||
|
|
||||||
const relays = routerContext.getSearchRelays?.() || []
|
const relays = router.options.getSearchRelays?.() || []
|
||||||
|
|
||||||
return [{filter, scenario: Router.get().FromRelays(relays).weight(10)}]
|
return [{filter, scenario: router.FromRelays(relays).weight(10)}]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilterSelectionsForWraps = (filter: Filter) => {
|
export const getFilterSelectionsForWraps = (filter: Filter, router: Router) => {
|
||||||
if (!filter.kinds?.includes(WRAP) || filter.authors) return []
|
if (!filter.kinds?.includes(WRAP) || filter.authors) return []
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
filter: {...filter, kinds: [WRAP]},
|
filter: {...filter, kinds: [WRAP]},
|
||||||
scenario: Router.get().MessagesForUser(),
|
scenario: router.MessagesForUser(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilterSelectionsForIndexedKinds = (filter: Filter) => {
|
export const getFilterSelectionsForIndexedKinds = (filter: Filter, router: Router) => {
|
||||||
const kinds = intersection(INDEXED_KINDS, filter.kinds || [])
|
const kinds = intersection(INDEXED_KINDS, filter.kinds || [])
|
||||||
|
|
||||||
if (kinds.length === 0) return []
|
if (kinds.length === 0) return []
|
||||||
|
|
||||||
const relays = routerContext.getIndexerRelays?.() || []
|
const relays = router.options.getIndexerRelays?.() || []
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
filter: {...filter, kinds},
|
filter: {...filter, kinds},
|
||||||
scenario: Router.get().FromRelays(relays),
|
scenario: router.FromRelays(relays),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilterSelectionsForAuthors = (filter: Filter) => {
|
export const getFilterSelectionsForAuthors = (filter: Filter, router: Router) => {
|
||||||
if (!filter.authors) return []
|
if (!filter.authors) return []
|
||||||
|
|
||||||
const chunkCount = clamp([1, 30], Math.round(filter.authors.length / 30))
|
const chunkCount = clamp([1, 30], Math.round(filter.authors.length / 30))
|
||||||
|
|
||||||
return chunks(chunkCount, filter.authors).map(authors => ({
|
return chunks(chunkCount, filter.authors).map(authors => ({
|
||||||
filter: {...filter, authors},
|
filter: {...filter, authors},
|
||||||
scenario: Router.get().FromPubkeys(authors),
|
scenario: router.FromPubkeys(authors),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilterSelectionsForUser = (filter: Filter) => [
|
export const getFilterSelectionsForUser = (filter: Filter, router: Router) => [
|
||||||
{filter, scenario: Router.get().ForUser().weight(0.2)},
|
{filter, scenario: router.ForUser().weight(0.2)},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultFilterSelectionRules = [
|
export const defaultFilterSelectionRules: FilterSelectionRule[] = [
|
||||||
getFilterSelectionsForSearch,
|
getFilterSelectionsForSearch,
|
||||||
getFilterSelectionsForWraps,
|
getFilterSelectionsForWraps,
|
||||||
getFilterSelectionsForIndexedKinds,
|
getFilterSelectionsForIndexedKinds,
|
||||||
@@ -418,13 +391,14 @@ export const defaultFilterSelectionRules = [
|
|||||||
|
|
||||||
export const getFilterSelections = (
|
export const getFilterSelections = (
|
||||||
filters: Filter[],
|
filters: Filter[],
|
||||||
|
router: Router,
|
||||||
rules: FilterSelectionRule[] = defaultFilterSelectionRules,
|
rules: FilterSelectionRule[] = defaultFilterSelectionRules,
|
||||||
): RelaysAndFilters[] => {
|
): RelaysAndFilters[] => {
|
||||||
const filtersById = new Map<string, Filter>()
|
const filtersById = new Map<string, Filter>()
|
||||||
const scenariosById = new Map<string, RouterScenario[]>()
|
const scenariosById = new Map<string, RouterScenario[]>()
|
||||||
|
|
||||||
for (const filter of filters) {
|
for (const filter of filters) {
|
||||||
for (const filterScenario of rules.flatMap(rule => rule(filter))) {
|
for (const filterScenario of rules.flatMap(rule => rule(filter, router))) {
|
||||||
const id = getFilterId(filterScenario.filter)
|
const id = getFilterId(filterScenario.filter)
|
||||||
|
|
||||||
filtersById.set(id, filterScenario.filter)
|
filtersById.set(id, filterScenario.filter)
|
||||||
@@ -435,9 +409,7 @@ export const getFilterSelections = (
|
|||||||
const result = []
|
const result = []
|
||||||
|
|
||||||
for (const [id, filter] of filtersById.entries()) {
|
for (const [id, filter] of filtersById.entries()) {
|
||||||
const scenario = Router.get()
|
const scenario = router.merge(scenariosById.get(id) || []).policy(addMinimalFallbacks)
|
||||||
.merge(scenariosById.get(id) || [])
|
|
||||||
.policy(addMinimalFallbacks)
|
|
||||||
|
|
||||||
result.push({filters: [filter], relays: scenario.getUrls()})
|
result.push({filters: [filter], relays: scenario.getUrls()})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user