rework context

This commit is contained in:
Jon Staab
2024-09-05 13:38:48 -07:00
parent 233a4e3576
commit 44f29c9da4
19 changed files with 157 additions and 138 deletions
+45
View File
@@ -0,0 +1,45 @@
import {partition} from "@welshman/lib"
import {defaultOptimizeSubscriptions, getDefaultNetContext as originalGetDefaultNetContext} from "@welshman/net"
import type {Subscription, RelaysAndFilters} from "@welshman/net"
import {unionFilters, isSignedEvent, hasValidSignature} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {tracker, repository} from './core'
import {makeRouter, getFilterSelections} from './router'
import {onAuth, getSession} from './session'
import type {Router} from './router'
export type AppContext = {
router: Router
requestDelay: number
requestTimeout: number
dufflepudUrl?: string
}
export const getDefaultNetContext = () => ({
...originalGetDefaultNetContext(),
onAuth: onAuth,
onEvent: (url: string, event: TrustedEvent) => tracker.track(event.id, url),
isDeleted: (url: string, event: TrustedEvent) => repository.isDeleted(event),
hasValidSignature: (event: TrustedEvent) =>
getSession(event.pubkey) || (isSignedEvent(event) && hasValidSignature(event)),
optimizeSubscriptions: (subs: Subscription[]) => {
const [withRelays, withoutRelays] = partition(sub => sub.request.relays.length > 0, subs)
const filters = unionFilters(withoutRelays.flatMap(sub => sub.request.filters))
const selections: RelaysAndFilters[] = defaultOptimizeSubscriptions(withRelays)
if (filters.length > 0) {
for (const selection of getFilterSelections(filters)) {
selections.push(selection)
}
}
return selections
},
})
export const getDefaultAppContext = () => ({
router: makeRouter(),
requestDelay: 50,
requestTimeout: 3000,
})
+4 -16
View File
@@ -1,21 +1,9 @@
import {isNil} from "@welshman/lib"
import {ctx, isNil} from "@welshman/lib"
import {Repository, Relay, LOCAL_RELAY_URL, getFilterResultCardinality} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util"
import {Tracker, subscribe as baseSubscribe} from "@welshman/net"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {createEventStore} from "@welshman/store"
import type {Router} from './router'
export const AppContext: {
router: Router,
requestDelay: number
requestTimeout: number
dufflepudUrl?: string
} = {
router: undefined as unknown as Router,
requestDelay: 50,
requestTimeout: 3000,
}
export const repository = new Repository<TrustedEvent>()
@@ -52,8 +40,8 @@ export const subscribe = (request: PartialSubscribeRequest) => {
}
// Make sure to query our local relay too
const delay = AppContext.requestDelay
const timeout = AppContext.requestTimeout
const delay = ctx.app.requestDelay
const timeout = ctx.app.requestTimeout
const sub = baseSubscribe({delay, authTimeout: timeout, relays: [], ...request})
sub.emitter.on("event", (url: string, e: TrustedEvent) => {
@@ -72,7 +60,7 @@ export const subscribe = (request: PartialSubscribeRequest) => {
export const load = (request: PartialSubscribeRequest) =>
new Promise<TrustedEvent[]>(resolve => {
const sub = subscribe({closeOnEose: true, timeout: AppContext.requestTimeout, ...request})
const sub = subscribe({closeOnEose: true, timeout: ctx.app.requestTimeout, ...request})
const events: TrustedEvent[] = []
sub.emitter.on("event", (url: string, e: TrustedEvent) => events.push(e))
+3 -4
View File
@@ -1,8 +1,7 @@
import {writable, derived} from 'svelte/store'
import {withGetter} from '@welshman/store'
import {type SubscribeRequest} from "@welshman/net"
import {uniq, uniqBy, batcher, postJson, last} from '@welshman/lib'
import {AppContext} from './core'
import {ctx, uniq, uniqBy, batcher, postJson, last} from '@welshman/lib'
import {collection} from './collection'
import {deriveProfile} from './profiles'
@@ -16,10 +15,10 @@ export type Handle = {
export const handles = withGetter(writable<Handle[]>([]))
export const fetchHandles = (handles: string[]) => {
const base = AppContext.dufflepudUrl!
const base = ctx.app.dufflepudUrl!
if (!base) {
throw new Error("AppContext.dufflepudUrl is required to fetch nip05 info")
throw new Error("ctx.app.dufflepudUrl is required to fetch nip05 info")
}
const res: any = postJson(`${base}/handle/info`, {handles})
+1 -31
View File
@@ -1,3 +1,4 @@
export * from './context'
export * from './core'
export * from './collection'
export * from './freshness'
@@ -15,34 +16,3 @@ export * from './thunk'
export * from './topics'
export * from './util'
export * from './zappers'
import {partition} from "@welshman/lib"
import {type Subscription, NetworkContext, defaultOptimizeSubscriptions} from "@welshman/net"
import {type TrustedEvent, unionFilters, isSignedEvent, hasValidSignature} from "@welshman/util"
import {tracker, repository, AppContext} from './core'
import {makeRouter, getFilterSelections} from './router'
import {onAuth, getSession} from './session'
export function* optimizeSubscriptions(subs: Subscription[]) {
const [withRelays, withoutRelays] = partition(sub => sub.request.relays.length > 0, subs)
const filters = unionFilters(withoutRelays.flatMap(sub => sub.request.filters))
yield* defaultOptimizeSubscriptions(withRelays)
if (filters.length > 0) {
yield* getFilterSelections(filters)
}
}
Object.assign(NetworkContext, {
onAuth,
onEvent: (url: string, event: TrustedEvent) => tracker.track(event.id, url),
isDeleted: (url: string, event: TrustedEvent) => repository.isDeleted(event),
hasValidSignature: (event: TrustedEvent) =>
getSession(event.pubkey) || (isSignedEvent(event) && hasValidSignature(event)),
optimizeSubscriptions,
})
Object.assign(AppContext, {
router: makeRouter(),
})
+4 -5
View File
@@ -1,9 +1,8 @@
import {writable, derived} from 'svelte/store'
import {withGetter} from '@welshman/store'
import {groupBy, indexBy, batch, now, uniq, uniqBy, batcher, postJson} from '@welshman/lib'
import {type RelayProfile} from "@welshman/util"
import {ctx, groupBy, indexBy, batch, now, uniq, uniqBy, batcher, postJson} from '@welshman/lib'
import type {RelayProfile} from "@welshman/util"
import {AuthStatus, asMessage, type Connection, type SocketMessage} from '@welshman/net'
import {AppContext} from './core'
import {createSearch} from './util'
import {collection} from './collection'
@@ -45,10 +44,10 @@ export const relaysByPubkey = derived(relays, $relays =>
)
export const fetchRelayProfiles = (urls: string[]) => {
const base = AppContext.dufflepudUrl!
const base = ctx.app.dufflepudUrl!
if (!base) {
throw new Error("AppContext.dufflepudUrl is required to fetch relay metadata")
throw new Error("ctx.app.dufflepudUrl is required to fetch relay metadata")
}
const res: any = postJson(`${base}/relay/info`, {urls})
+17 -22
View File
@@ -1,14 +1,14 @@
import {
intersection, first, switcher, throttleWithValue, clamp, last, splitAt, identity, sortBy, uniq, shuffle,
pushToMapKey, now, assoc,
pushToMapKey, now, assoc, ctx,
} from '@welshman/lib'
import {
Tags, getFilterId, unionFilters, isShareableRelayUrl, isCommunityAddress, isGroupAddress, isContextAddress,
PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS,
} from '@welshman/util'
import type {TrustedEvent, Filter} from '@welshman/util'
import {NetworkContext, ConnectionStatus} from '@welshman/net'
import {AppContext} from './core'
import {ConnectionStatus} from '@welshman/net'
import type {RelaysAndFilters} from '@welshman/net'
import {pubkey} from './session'
import {relaySelectionsByPubkey, getReadRelayUrls, getWriteRelayUrls, getRelayUrls} from './relaySelections'
import {relays, relaysByUrl} from './relays'
@@ -396,7 +396,7 @@ export const getRelayQuality = (url: string) => {
const relay = relaysByUrl.get().get(url)
const connect_count = relay?.stats?.connect_count || 0
const recent_errors = relay?.stats?.recent_errors || []
const connection = NetworkContext.pool.get(url, {autoConnect: false})
const connection = ctx.net.pool.get(url, {autoConnect: false})
// If we haven't connected, consult our relay record and see if there has
// been a recent fault. If there has been, penalize the relay. If there have been several,
@@ -467,11 +467,6 @@ export const makeRouter = (options: Partial<RouterOptions> = {}) =>
// Infer relay selections from filters
export type RelayFilters = {
relays: string[]
filters: Filter[]
}
export type FilterSelection = {
id: string,
filter: Filter,
@@ -492,8 +487,8 @@ export const getFilterSelectionsForSearch = (state: FilterSelectionRuleState) =>
if (!state.filter.search) return false
const id = getFilterId(state.filter)
const relays = AppContext.router.options.getSearchRelays?.() || []
const scenario = AppContext.router.product([id], relays)
const relays = ctx.app.router.options.getSearchRelays?.() || []
const scenario = ctx.app.router.product([id], relays)
state.selections.push(makeFilterSelection(id, state.filter, scenario))
@@ -505,12 +500,12 @@ export const getFilterSelectionsForContext = (state: FilterSelectionRuleState) =
if (contexts.length === 0) return false
const scenario = AppContext.router.WithinMultipleContexts(contexts)
const scenario = ctx.app.router.WithinMultipleContexts(contexts)
for (const {relay, values} of scenario.getSelections()) {
const contextFilter = {...state.filter, "#a": Array.from(values)}
const id = getFilterId(contextFilter)
const scenario = AppContext.router.product([id], [relay])
const scenario = ctx.app.router.product([id], [relay])
state.selections.push(makeFilterSelection(id, contextFilter, scenario))
}
@@ -524,8 +519,8 @@ export const getFilterSelectionsForIndexedKinds = (state: FilterSelectionRuleSta
if (kinds.length === 0) return false
const id = getFilterId({...state.filter, kinds})
const relays = AppContext.router.options.getIndexerRelays?.() || []
const scenario = AppContext.router.product([id], relays)
const relays = ctx.app.router.options.getIndexerRelays?.() || []
const scenario = ctx.app.router.product([id], relays)
state.selections.push(makeFilterSelection(id, state.filter, scenario))
@@ -536,7 +531,7 @@ export const getFilterSelectionsForAuthors = (state: FilterSelectionRuleState) =
if (!state.filter.authors) return false
const id = getFilterId(state.filter)
const scenario = AppContext.router.FromPubkeys(state.filter.authors!).update(assoc('value', id))
const scenario = ctx.app.router.FromPubkeys(state.filter.authors!).update(assoc('value', id))
state.selections.push(makeFilterSelection(id, state.filter, scenario))
@@ -545,8 +540,8 @@ export const getFilterSelectionsForAuthors = (state: FilterSelectionRuleState) =
export const getFilterSelectionsForUser = (state: FilterSelectionRuleState) => {
const id = getFilterId(state.filter)
const relays = AppContext.router.ReadRelays().getUrls()
const scenario = AppContext.router.product([id], relays)
const relays = ctx.app.router.ReadRelays().getUrls()
const scenario = ctx.app.router.product([id], relays)
state.selections.push(makeFilterSelection(id, state.filter, scenario))
@@ -561,7 +556,7 @@ export const defaultFilterSelectionRules = [
getFilterSelectionsForUser,
]
export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRule[] = defaultFilterSelectionRules): RelayFilters[] => {
export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRule[] = defaultFilterSelectionRules): RelaysAndFilters[] => {
const scenarios: RouterScenario[] = []
const filtersById = new Map<string, Filter>()
@@ -584,7 +579,7 @@ export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRul
// Use low redundancy because filters will be very low cardinality
const selections = AppContext.router
const selections = ctx.app.router
.merge(scenarios)
.redundancy(1)
.getSelections()
@@ -594,8 +589,8 @@ export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRul
}))
// Pubkey-based selections can get really big. Use the most popular relays for the long tail
const limit = AppContext.router.options.getLimit?.() || 8
const redundancy = AppContext.router.options.getRedundancy?.() || 3
const limit = ctx.app.router.options.getLimit?.() || 8
const redundancy = ctx.app.router.options.getRedundancy?.() || 3
const [keep, discard] = splitAt(limit, selections)
for (const target of keep.slice(0, redundancy)) {
+2 -3
View File
@@ -1,9 +1,8 @@
import {derived} from "svelte/store"
import {memoize, omit, equals, assoc} from "@welshman/lib"
import {ctx, memoize, omit, equals, assoc} from "@welshman/lib"
import {createEvent} from "@welshman/util"
import {withGetter, synced} from "@welshman/store"
import {type Nip46Handler} from "@welshman/signer"
import {NetworkContext} from "@welshman/net"
import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer} from "@welshman/signer"
export type Session = {
@@ -76,7 +75,7 @@ export const onAuth = async (url: string, challenge: string) => {
}),
)
NetworkContext.pool.get(url).send(["AUTH", event])
ctx.net.pool.get(url).send(["AUTH", event])
return event
}
+9
View File
@@ -0,0 +1,9 @@
import type {NetContext} from '@welshman/net'
import type {AppContext} from './context'
declare module "@welshman/lib" {
interface Context {
net: NetContext
app: AppContext
}
}
+3 -4
View File
@@ -2,18 +2,17 @@ import {writable, derived} from 'svelte/store'
import {withGetter} from '@welshman/store'
import {type Zapper} from '@welshman/util'
import {type SubscribeRequest} from "@welshman/net"
import {uniq, identity, bech32ToHex, tryCatch, uniqBy, batcher, postJson} from '@welshman/lib'
import {AppContext} from './core'
import {ctx, uniq, identity, bech32ToHex, tryCatch, uniqBy, batcher, postJson} from '@welshman/lib'
import {collection} from './collection'
import {deriveProfile} from './profiles'
export const zappers = withGetter(writable<Zapper[]>([]))
export const fetchZappers = (lnurls: string[]) => {
const base = AppContext.dufflepudUrl!
const base = ctx.app.dufflepudUrl!
if (!base) {
throw new Error("AppContext.dufflepudUrl is required to fetch zapper info")
throw new Error("ctx.app.dufflepudUrl is required to fetch zapper info")
}
const zappersByLnurl = new Map<string, Zapper>()