Remove tsc-multi, re-install gts, apply autoformatting and linting
This commit is contained in:
@@ -15,21 +15,16 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./build/src/index.d.ts",
|
||||
"import": "./build/src/index.mjs",
|
||||
"require": "./build/src/index.cjs"
|
||||
"import": "./build/src/index.js",
|
||||
"require": "./build/src/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"pub": "npm run lint && npm run build && npm publish",
|
||||
"build": "gts clean && tsc-multi",
|
||||
"build": "gts clean && tsc",
|
||||
"lint": "gts lint",
|
||||
"fix": "gts fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gts": "^5.0.1",
|
||||
"tsc-multi": "^1.1.0",
|
||||
"typescript": "~5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/throttle-debounce": "^5.0.2",
|
||||
"@welshman/dvm": "~0.0.11",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {readable, derived, type Readable} from 'svelte/store'
|
||||
import {indexBy, type Maybe, now} from '@welshman/lib'
|
||||
import {withGetter} from '@welshman/store'
|
||||
import {getFreshness, setFreshnessThrottled} from './freshness'
|
||||
import {readable, derived, type Readable} from "svelte/store"
|
||||
import {indexBy, type Maybe, now} from "@welshman/lib"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import {getFreshness, setFreshnessThrottled} from "./freshness.js"
|
||||
|
||||
export const collection = <T, LoadArgs extends any[]>({
|
||||
name,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {get} from 'svelte/store'
|
||||
import {ctx} from '@welshman/lib'
|
||||
import {addToListPublicly, removeFromList, makeList, FOLLOWS, MUTES} from '@welshman/util'
|
||||
import {userFollows, userMutes} from './user'
|
||||
import {nip44EncryptToSelf} from './session'
|
||||
import {publishThunk} from './thunk'
|
||||
import {get} from "svelte/store"
|
||||
import {ctx} from "@welshman/lib"
|
||||
import {addToListPublicly, removeFromList, makeList, FOLLOWS, MUTES} from "@welshman/util"
|
||||
import {userFollows, userMutes} from "./user.js"
|
||||
import {nip44EncryptToSelf} from "./session.js"
|
||||
import {publishThunk} from "./thunk.js"
|
||||
|
||||
export const unfollow = async (value: string) => {
|
||||
const list = get(userFollows) || makeList({kind: FOLLOWS})
|
||||
@@ -32,4 +32,3 @@ export const mute = async (tag: string[]) => {
|
||||
|
||||
return publishThunk({event, relays: ctx.app.router.FromUser().getUrls()})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import {partition} from "@welshman/lib"
|
||||
import {defaultOptimizeSubscriptions, getDefaultNetContext as originalGetDefaultNetContext} from "@welshman/net"
|
||||
import {
|
||||
defaultOptimizeSubscriptions,
|
||||
getDefaultNetContext as originalGetDefaultNetContext,
|
||||
} from "@welshman/net"
|
||||
import type {Subscription, RelaysAndFilters, NetContext} from "@welshman/net"
|
||||
import {LOCAL_RELAY_URL, isEphemeralKind, isDVMKind, unionFilters} from "@welshman/util"
|
||||
import {LOCAL_RELAY_URL, isEphemeralKind, isDVMKind, unionFilters} from "@welshman/util"
|
||||
import type {TrustedEvent, StampedEvent} from "@welshman/util"
|
||||
import {tracker, repository} from './core'
|
||||
import {makeRouter, getFilterSelections} from './router'
|
||||
import {signer} from './session'
|
||||
import type {Router} from './router'
|
||||
import {tracker, repository} from "./core.js"
|
||||
import {makeRouter, getFilterSelections} from "./router.js"
|
||||
import {signer} from "./session.js"
|
||||
import type {Router} from "./router.js"
|
||||
|
||||
export type AppContext = {
|
||||
router: Router
|
||||
@@ -52,4 +55,3 @@ export const getDefaultAppContext = (overrides: Partial<AppContext> = {}) => ({
|
||||
requestTimeout: 3000,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
|
||||
+29
-23
@@ -1,4 +1,4 @@
|
||||
import {throttle} from '@welshman/lib'
|
||||
import {throttle} from "@welshman/lib"
|
||||
import {Repository, Relay} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {Tracker} from "@welshman/net"
|
||||
@@ -13,33 +13,39 @@ export const tracker = new Tracker()
|
||||
// Adapt above objects to stores
|
||||
|
||||
export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
|
||||
custom(setter => {
|
||||
let onUpdate = () => setter(repository)
|
||||
custom(
|
||||
setter => {
|
||||
let onUpdate = () => setter(repository)
|
||||
|
||||
if (t) {
|
||||
onUpdate = throttle(t, onUpdate)
|
||||
}
|
||||
if (t) {
|
||||
onUpdate = throttle(t, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
repository.on('update', onUpdate)
|
||||
onUpdate()
|
||||
repository.on("update", onUpdate)
|
||||
|
||||
return () => repository.off('update', onUpdate)
|
||||
}, {
|
||||
set: (other: Repository) => repository.load(other.dump()),
|
||||
})
|
||||
return () => repository.off("update", onUpdate)
|
||||
},
|
||||
{
|
||||
set: (other: Repository) => repository.load(other.dump()),
|
||||
},
|
||||
)
|
||||
|
||||
export const makeTrackerStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
|
||||
custom(setter => {
|
||||
let onUpdate = () => setter(tracker)
|
||||
custom(
|
||||
setter => {
|
||||
let onUpdate = () => setter(tracker)
|
||||
|
||||
if (t) {
|
||||
onUpdate = throttle(t, onUpdate)
|
||||
}
|
||||
if (t) {
|
||||
onUpdate = throttle(t, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
tracker.on('update', onUpdate)
|
||||
onUpdate()
|
||||
tracker.on("update", onUpdate)
|
||||
|
||||
return () => tracker.off('update', onUpdate)
|
||||
}, {
|
||||
set: (other: Tracker) => tracker.load(other.relaysById),
|
||||
})
|
||||
return () => tracker.off("update", onUpdate)
|
||||
},
|
||||
{
|
||||
set: (other: Tracker) => tracker.load(other.relaysById),
|
||||
},
|
||||
)
|
||||
|
||||
+30
-29
@@ -1,23 +1,20 @@
|
||||
import {ctx, nthEq, now} from '@welshman/lib'
|
||||
import {createEvent, getPubkeyTagValues} from '@welshman/util'
|
||||
import {Scope, FeedController} from '@welshman/feeds'
|
||||
import type {RequestOpts, FeedOptions, DVMOpts, Feed} from '@welshman/feeds'
|
||||
import {makeDvmRequest, DVMEvent} from '@welshman/dvm'
|
||||
import {makeSecret, Nip01Signer} from '@welshman/signer'
|
||||
import {pubkey, signer} from './session'
|
||||
import {getFilterSelections} from './router'
|
||||
import {loadRelaySelections} from './relaySelections'
|
||||
import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from './wot'
|
||||
import {load} from './subscribe'
|
||||
import {ctx, nthEq, now} from "@welshman/lib"
|
||||
import {createEvent, getPubkeyTagValues} from "@welshman/util"
|
||||
import {Scope, FeedController} from "@welshman/feeds"
|
||||
import type {RequestOpts, FeedOptions, DVMOpts, Feed} from "@welshman/feeds"
|
||||
import {makeDvmRequest, DVMEvent} from "@welshman/dvm"
|
||||
import {makeSecret, Nip01Signer} from "@welshman/signer"
|
||||
import {pubkey, signer} from "./session.js"
|
||||
import {getFilterSelections} from "./router.js"
|
||||
import {loadRelaySelections} from "./relaySelections.js"
|
||||
import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from "./wot.js"
|
||||
import {load} from "./subscribe.js"
|
||||
|
||||
export const request = async ({filters = [{}], relays = [], onEvent}: RequestOpts) => {
|
||||
if (relays.length > 0) {
|
||||
await load({onEvent, filters, relays})
|
||||
} else {
|
||||
await Promise.all(
|
||||
getFilterSelections(filters)
|
||||
.map(opts => load({onEvent, ...opts}))
|
||||
)
|
||||
await Promise.all(getFilterSelections(filters).map(opts => load({onEvent, ...opts})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,24 +29,23 @@ export const requestDVM = async ({kind, onEvent, ...request}: DVMOpts) => {
|
||||
const tags = request.tags || []
|
||||
const $signer = signer.get() || new Nip01Signer(makeSecret())
|
||||
const pubkey = await $signer.getPubkey()
|
||||
const relays =
|
||||
request.relays
|
||||
? ctx.app.router.FromRelays(request.relays).getUrls()
|
||||
: ctx.app.router.FromPubkeys(getPubkeyTagValues(tags)).getUrls()
|
||||
const relays = request.relays
|
||||
? ctx.app.router.FromRelays(request.relays).getUrls()
|
||||
: ctx.app.router.FromPubkeys(getPubkeyTagValues(tags)).getUrls()
|
||||
|
||||
if (!tags.some(nthEq(0, 'expiration'))) {
|
||||
if (!tags.some(nthEq(0, "expiration"))) {
|
||||
tags.push(["expiration", String(now() + 60)])
|
||||
}
|
||||
|
||||
if (!tags.some(nthEq(0, 'relays'))) {
|
||||
if (!tags.some(nthEq(0, "relays"))) {
|
||||
tags.push(["relays", ...relays])
|
||||
}
|
||||
|
||||
if (!tags.some(nthEq(1, 'user'))) {
|
||||
if (!tags.some(nthEq(1, "user"))) {
|
||||
tags.push(["param", "user", pubkey])
|
||||
}
|
||||
|
||||
if (!tags.some(nthEq(1, 'max_results'))) {
|
||||
if (!tags.some(nthEq(1, "max_results"))) {
|
||||
tags.push(["param", "max_results", "200"])
|
||||
}
|
||||
|
||||
@@ -72,11 +68,16 @@ export const getPubkeysForScope = (scope: string) => {
|
||||
}
|
||||
|
||||
switch (scope) {
|
||||
case Scope.Self: return [$pubkey]
|
||||
case Scope.Follows: return getFollows($pubkey)
|
||||
case Scope.Network: return getNetwork($pubkey)
|
||||
case Scope.Followers: return getFollowers($pubkey)
|
||||
default: return []
|
||||
case Scope.Self:
|
||||
return [$pubkey]
|
||||
case Scope.Follows:
|
||||
return getFollows($pubkey)
|
||||
case Scope.Network:
|
||||
return getNetwork($pubkey)
|
||||
case Scope.Followers:
|
||||
return getFollowers($pubkey)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ export const getPubkeysForWOTRange = (min: number, max: number) => {
|
||||
return pubkeys
|
||||
}
|
||||
|
||||
type _FeedOptions = Partial<Omit<FeedOptions, 'feed'>> & {feed: Feed}
|
||||
type _FeedOptions = Partial<Omit<FeedOptions, "feed">> & {feed: Feed}
|
||||
|
||||
export const createFeedController = (options: _FeedOptions) =>
|
||||
new FeedController({
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import {FOLLOWS, asDecryptedEvent, readList} from '@welshman/util'
|
||||
import {type TrustedEvent, type PublishedList} from '@welshman/util'
|
||||
import {FOLLOWS, asDecryptedEvent, readList} from "@welshman/util"
|
||||
import {type TrustedEvent, type PublishedList} from "@welshman/util"
|
||||
import {type SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {deriveEventsMapped} from '@welshman/store'
|
||||
import {repository} from './core'
|
||||
import {load} from './subscribe'
|
||||
import {collection} from './collection'
|
||||
import {loadRelaySelections} from './relaySelections'
|
||||
import {deriveEventsMapped} from "@welshman/store"
|
||||
import {repository} from "./core.js"
|
||||
import {load} from "./subscribe.js"
|
||||
import {collection} from "./collection.js"
|
||||
import {loadRelaySelections} from "./relaySelections.js"
|
||||
|
||||
export const follows = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [FOLLOWS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) =>
|
||||
readList(asDecryptedEvent(event)),
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
export const {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {writable} from 'svelte/store'
|
||||
import {assoc, batch} from '@welshman/lib'
|
||||
import {withGetter} from '@welshman/store'
|
||||
import {writable} from "svelte/store"
|
||||
import {assoc, batch} from "@welshman/lib"
|
||||
import {withGetter} from "@welshman/store"
|
||||
|
||||
export type FreshnessUpdate = {
|
||||
ns: string
|
||||
@@ -25,5 +25,5 @@ export const setFreshnessThrottled = batch(100, (updates: FreshnessUpdate[]) =>
|
||||
}
|
||||
|
||||
return $freshness
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
+30
-21
@@ -1,8 +1,8 @@
|
||||
import {writable, derived} from 'svelte/store'
|
||||
import {writable, derived} from "svelte/store"
|
||||
import {type SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {ctx, tryCatch, fetchJson, uniq, batcher, postJson, last} from '@welshman/lib'
|
||||
import {collection} from './collection'
|
||||
import {deriveProfile} from './profiles'
|
||||
import {ctx, tryCatch, fetchJson, uniq, batcher, postJson, last} from "@welshman/lib"
|
||||
import {collection} from "./collection.js"
|
||||
import {deriveProfile} from "./profiles.js"
|
||||
|
||||
export type Handle = {
|
||||
nip05: string
|
||||
@@ -18,10 +18,14 @@ export async function queryProfile(nip05: string) {
|
||||
|
||||
if (!match) return undefined
|
||||
|
||||
const [_, name = '_', domain] = match
|
||||
const [_, name = "_", domain] = match
|
||||
|
||||
try {
|
||||
const {names, relays = {}, nip46 = {}} = await fetchJson(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||
const {
|
||||
names,
|
||||
relays = {},
|
||||
nip46 = {},
|
||||
} = await fetchJson(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||
|
||||
const pubkey = names[name]
|
||||
|
||||
@@ -48,14 +52,19 @@ export const fetchHandles = async (nip05s: string[]) => {
|
||||
|
||||
// Use dufflepud if we it's set up to protect user privacy, otherwise fetch directly
|
||||
if (base) {
|
||||
const res: any = await tryCatch(async () => await postJson(`${base}/handle/info`, {handles: nip05s}))
|
||||
const res: any = await tryCatch(
|
||||
async () => await postJson(`${base}/handle/info`, {handles: nip05s}),
|
||||
)
|
||||
|
||||
for (const {handle: nip05, info} of res?.data || []) {
|
||||
handlesByNip05.set(nip05, info)
|
||||
}
|
||||
} else {
|
||||
const results = await Promise.all(
|
||||
nip05s.map(async nip05 => ({nip05, info: await tryCatch(async () => await queryProfile(nip05))}))
|
||||
nip05s.map(async nip05 => ({
|
||||
nip05,
|
||||
info: await tryCatch(async () => await queryProfile(nip05)),
|
||||
})),
|
||||
)
|
||||
|
||||
for (const {nip05, info} of results) {
|
||||
@@ -94,21 +103,21 @@ export const {
|
||||
}),
|
||||
})
|
||||
|
||||
export const deriveHandleForPubkey = (pubkey: string, request: Partial<SubscribeRequestWithHandlers> = {}) =>
|
||||
derived(
|
||||
[handlesByNip05, deriveProfile(pubkey, request)],
|
||||
([$handlesByNip05, $profile]) => {
|
||||
if (!$profile?.nip05) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
loadHandle($profile.nip05)
|
||||
|
||||
return $handlesByNip05.get($profile.nip05)
|
||||
export const deriveHandleForPubkey = (
|
||||
pubkey: string,
|
||||
request: Partial<SubscribeRequestWithHandlers> = {},
|
||||
) =>
|
||||
derived([handlesByNip05, deriveProfile(pubkey, request)], ([$handlesByNip05, $profile]) => {
|
||||
if (!$profile?.nip05) {
|
||||
return undefined
|
||||
}
|
||||
)
|
||||
|
||||
loadHandle($profile.nip05)
|
||||
|
||||
return $handlesByNip05.get($profile.nip05)
|
||||
})
|
||||
|
||||
export const displayNip05 = (nip05: string) =>
|
||||
(nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05)
|
||||
nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05
|
||||
|
||||
export const displayHandle = (handle: Handle) => displayNip05(handle.nip05)
|
||||
|
||||
+36
-26
@@ -1,26 +1,36 @@
|
||||
export * from './context'
|
||||
export * from './core'
|
||||
export * from './collection'
|
||||
export * from './commands'
|
||||
export * from './feeds'
|
||||
export * from './freshness'
|
||||
export * from './follows'
|
||||
export * from './handles'
|
||||
export * from './mutes'
|
||||
export * from './plaintext'
|
||||
export * from './profiles'
|
||||
export * from './relays'
|
||||
export * from './relaySelections'
|
||||
export * from './router'
|
||||
export * from './search'
|
||||
export * from './session'
|
||||
export * from './storage'
|
||||
export * from './subscribe'
|
||||
export * from './sync'
|
||||
export * from './tags'
|
||||
export * from './thunk'
|
||||
export * from './topics'
|
||||
export * from './user'
|
||||
export * from './util'
|
||||
export * from './wot'
|
||||
export * from './zappers'
|
||||
export * from "./context.js"
|
||||
export * from "./core.js"
|
||||
export * from "./collection.js"
|
||||
export * from "./commands.js"
|
||||
export * from "./feeds.js"
|
||||
export * from "./freshness.js"
|
||||
export * from "./follows.js"
|
||||
export * from "./handles.js"
|
||||
export * from "./mutes.js"
|
||||
export * from "./plaintext.js"
|
||||
export * from "./profiles.js"
|
||||
export * from "./relays.js"
|
||||
export * from "./relaySelections.js"
|
||||
export * from "./router.js"
|
||||
export * from "./search.js"
|
||||
export * from "./session.js"
|
||||
export * from "./storage.js"
|
||||
export * from "./subscribe.js"
|
||||
export * from "./sync.js"
|
||||
export * from "./tags.js"
|
||||
export * from "./thunk.js"
|
||||
export * from "./topics.js"
|
||||
export * from "./user.js"
|
||||
export * from "./util.js"
|
||||
export * from "./wot.js"
|
||||
export * from "./zappers.js"
|
||||
|
||||
import type {NetContext} from "@welshman/net"
|
||||
import type {AppContext} from "./context.js"
|
||||
|
||||
declare module "@welshman/lib" {
|
||||
interface Context {
|
||||
net: NetContext
|
||||
app: AppContext
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {MUTES, asDecryptedEvent, readList} from '@welshman/util'
|
||||
import {type TrustedEvent, type PublishedList} from '@welshman/util'
|
||||
import {MUTES, asDecryptedEvent, readList} from "@welshman/util"
|
||||
import {type TrustedEvent, type PublishedList} from "@welshman/util"
|
||||
import {type SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {deriveEventsMapped} from '@welshman/store'
|
||||
import {repository} from './core'
|
||||
import {load} from './subscribe'
|
||||
import {collection} from './collection'
|
||||
import {ensurePlaintext} from './plaintext'
|
||||
import {loadRelaySelections} from './relaySelections'
|
||||
import {deriveEventsMapped} from "@welshman/store"
|
||||
import {repository} from "./core.js"
|
||||
import {load} from "./subscribe.js"
|
||||
import {collection} from "./collection.js"
|
||||
import {ensurePlaintext} from "./plaintext.js"
|
||||
import {loadRelaySelections} from "./relaySelections.js"
|
||||
|
||||
export const mutes = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [MUTES]}],
|
||||
@@ -32,4 +32,3 @@ export const {
|
||||
await load({...request, filters: [{kinds: [MUTES], authors: [pubkey]}]})
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {writable} from 'svelte/store'
|
||||
import {assoc} from '@welshman/lib'
|
||||
import type {TrustedEvent} from '@welshman/util'
|
||||
import {withGetter} from '@welshman/store'
|
||||
import {writable} from "svelte/store"
|
||||
import {assoc} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import {decrypt} from "@welshman/signer"
|
||||
import {getSigner, getSession} from './session'
|
||||
import {getSigner, getSession} from "./session.js"
|
||||
|
||||
export const plaintext = withGetter(writable<Record<string, string>>({}))
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import {derived, readable} from 'svelte/store'
|
||||
import {readProfile, displayProfile, displayPubkey, PROFILE} from '@welshman/util'
|
||||
import {derived, readable} from "svelte/store"
|
||||
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
|
||||
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import type {PublishedProfile} from "@welshman/util"
|
||||
import {deriveEventsMapped, withGetter} from '@welshman/store'
|
||||
import {repository} from './core'
|
||||
import {load} from './subscribe'
|
||||
import {collection} from './collection'
|
||||
import {loadRelaySelections} from './relaySelections'
|
||||
import {deriveEventsMapped, withGetter} from "@welshman/store"
|
||||
import {repository} from "./core.js"
|
||||
import {load} from "./subscribe.js"
|
||||
import {collection} from "./collection.js"
|
||||
import {loadRelaySelections} from "./relaySelections.js"
|
||||
|
||||
export const profiles = withGetter(
|
||||
deriveEventsMapped<PublishedProfile>(repository, {
|
||||
filters: [{kinds: [PROFILE]}],
|
||||
eventToItem: readProfile,
|
||||
itemToEvent: item => item.event,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const {
|
||||
@@ -45,9 +45,7 @@ export const {
|
||||
})
|
||||
|
||||
export const displayProfileByPubkey = (pubkey: string | undefined) =>
|
||||
pubkey
|
||||
? displayProfile(profilesByPubkey.get().get(pubkey), displayPubkey(pubkey))
|
||||
: ""
|
||||
pubkey ? displayProfile(profilesByPubkey.get().get(pubkey), displayPubkey(pubkey)) : ""
|
||||
|
||||
export const deriveProfileDisplay = (pubkey: string | undefined) =>
|
||||
pubkey
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import {uniq} from '@welshman/lib'
|
||||
import {INBOX_RELAYS, RELAYS, normalizeRelayUrl, asDecryptedEvent, readList, getListTags, getRelayTags, getRelayTagValues} from '@welshman/util'
|
||||
import type {TrustedEvent, PublishedList, List} from '@welshman/util'
|
||||
import {uniq} from "@welshman/lib"
|
||||
import {
|
||||
INBOX_RELAYS,
|
||||
RELAYS,
|
||||
normalizeRelayUrl,
|
||||
asDecryptedEvent,
|
||||
readList,
|
||||
getListTags,
|
||||
getRelayTags,
|
||||
getRelayTagValues,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, PublishedList, List} from "@welshman/util"
|
||||
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {deriveEventsMapped} from '@welshman/store'
|
||||
import {repository} from './core'
|
||||
import {load} from './subscribe'
|
||||
import {collection} from './collection'
|
||||
import {deriveEventsMapped} from "@welshman/store"
|
||||
import {repository} from "./core.js"
|
||||
import {load} from "./subscribe.js"
|
||||
import {collection} from "./collection.js"
|
||||
|
||||
export const getRelayUrls = (list?: List): string[] =>
|
||||
uniq(getRelayTagValues(getListTags(list)).map(normalizeRelayUrl))
|
||||
@@ -14,21 +23,20 @@ export const getReadRelayUrls = (list?: List): string[] =>
|
||||
uniq(
|
||||
getRelayTags(getListTags(list))
|
||||
.filter((t: string[]) => !t[2] || t[2] === "read")
|
||||
.map((t: string[]) => normalizeRelayUrl(t[1]))
|
||||
.map((t: string[]) => normalizeRelayUrl(t[1])),
|
||||
)
|
||||
|
||||
export const getWriteRelayUrls = (list?: List): string[] =>
|
||||
uniq(
|
||||
getRelayTags(getListTags(list))
|
||||
.filter((t: string[]) => !t[2] || t[2] === "write")
|
||||
.map((t: string[]) => normalizeRelayUrl(t[1]))
|
||||
.map((t: string[]) => normalizeRelayUrl(t[1])),
|
||||
)
|
||||
|
||||
export const relaySelections = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [RELAYS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) =>
|
||||
readList(asDecryptedEvent(event)),
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
export const {
|
||||
@@ -46,8 +54,7 @@ export const {
|
||||
export const inboxRelaySelections = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [INBOX_RELAYS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) =>
|
||||
readList(asDecryptedEvent(event)),
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
export const {
|
||||
|
||||
+87
-57
@@ -1,11 +1,11 @@
|
||||
import {writable, derived} from 'svelte/store'
|
||||
import {withGetter} from '@welshman/store'
|
||||
import {ctx, groupBy, indexBy, batch, now, ago, uniq, batcher, postJson} from '@welshman/lib'
|
||||
import {writable, derived} from "svelte/store"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import {ctx, groupBy, indexBy, batch, now, ago, uniq, batcher, postJson} from "@welshman/lib"
|
||||
import type {RelayProfile} from "@welshman/util"
|
||||
import {normalizeRelayUrl, displayRelayUrl, displayRelayProfile} from "@welshman/util"
|
||||
import {ConnectionEvent} from '@welshman/net'
|
||||
import type {Connection, Message} from '@welshman/net'
|
||||
import {collection} from './collection'
|
||||
import {ConnectionEvent} from "@welshman/net"
|
||||
import type {Connection, Message} from "@welshman/net"
|
||||
import {collection} from "./collection.js"
|
||||
|
||||
export type RelayStats = {
|
||||
first_seen: number
|
||||
@@ -151,78 +151,108 @@ const updateRelayStats = batch(500, (updates: RelayStatsUpdate[]) => {
|
||||
})
|
||||
|
||||
const onConnectionOpen = ({url}: Connection) =>
|
||||
updateRelayStats([url, stats => {
|
||||
stats.last_open = now()
|
||||
stats.open_count++
|
||||
}])
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.last_open = now()
|
||||
stats.open_count++
|
||||
},
|
||||
])
|
||||
|
||||
const onConnectionClose = ({url}: Connection) =>
|
||||
updateRelayStats([url, stats => {
|
||||
stats.last_close = now()
|
||||
stats.close_count++
|
||||
}])
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.last_close = now()
|
||||
stats.close_count++
|
||||
},
|
||||
])
|
||||
|
||||
const onConnectionSend = ({url}: Connection, [verb]: Message) => {
|
||||
if (verb === 'REQ') {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.request_count++
|
||||
stats.last_request = now()
|
||||
}])
|
||||
} else if (verb === 'EVENT') {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.publish_count++
|
||||
stats.last_publish = now()
|
||||
}])
|
||||
if (verb === "REQ") {
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.request_count++
|
||||
stats.last_request = now()
|
||||
},
|
||||
])
|
||||
} else if (verb === "EVENT") {
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.publish_count++
|
||||
stats.last_publish = now()
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const onConnectionReceive = ({url, state}: Connection, [verb, ...extra]: Message) => {
|
||||
if (verb === 'OK') {
|
||||
if (verb === "OK") {
|
||||
const [eventId, ok] = extra
|
||||
const pub = state.pendingPublishes.get(eventId)
|
||||
|
||||
updateRelayStats([url, stats => {
|
||||
if (pub) {
|
||||
stats.publish_timer += ago(pub.sent)
|
||||
}
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
if (pub) {
|
||||
stats.publish_timer += ago(pub.sent)
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
stats.publish_success_count++
|
||||
} else {
|
||||
stats.publish_failure_count++
|
||||
}
|
||||
}])
|
||||
} else if (verb === 'AUTH') {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.last_auth = now()
|
||||
}])
|
||||
} else if (verb === 'EVENT') {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.event_count++
|
||||
stats.last_event = now()
|
||||
}])
|
||||
} else if (verb === 'EOSE') {
|
||||
if (ok) {
|
||||
stats.publish_success_count++
|
||||
} else {
|
||||
stats.publish_failure_count++
|
||||
}
|
||||
},
|
||||
])
|
||||
} else if (verb === "AUTH") {
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.last_auth = now()
|
||||
},
|
||||
])
|
||||
} else if (verb === "EVENT") {
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.event_count++
|
||||
stats.last_event = now()
|
||||
},
|
||||
])
|
||||
} else if (verb === "EOSE") {
|
||||
const request = state.pendingRequests.get(extra[0])
|
||||
|
||||
// Only count the first eose
|
||||
if (request && !request.eose) {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.eose_count++
|
||||
stats.eose_timer += now() - request.sent
|
||||
}])
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.eose_count++
|
||||
stats.eose_timer += now() - request.sent
|
||||
},
|
||||
])
|
||||
}
|
||||
} else if (verb === 'NOTICE') {
|
||||
updateRelayStats([url, stats => {
|
||||
stats.notice_count++
|
||||
}])
|
||||
} else if (verb === "NOTICE") {
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.notice_count++
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const onConnectionError = ({url}: Connection) =>
|
||||
updateRelayStats([url, stats => {
|
||||
stats.last_error = now()
|
||||
stats.recent_errors = uniq(stats.recent_errors.concat(now())).slice(-10)
|
||||
}])
|
||||
updateRelayStats([
|
||||
url,
|
||||
stats => {
|
||||
stats.last_error = now()
|
||||
stats.recent_errors = uniq(stats.recent_errors.concat(now())).slice(-10)
|
||||
},
|
||||
])
|
||||
|
||||
export const trackRelayStats = (connection: Connection) => {
|
||||
connection.on(ConnectionEvent.Open, onConnectionOpen)
|
||||
|
||||
+70
-70
@@ -32,15 +32,15 @@ import {
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, Filter} from "@welshman/util"
|
||||
import type {RelaysAndFilters} from "@welshman/net"
|
||||
import {pubkey} from "./session"
|
||||
import {pubkey} from "./session.js"
|
||||
import {
|
||||
relaySelectionsByPubkey,
|
||||
inboxRelaySelectionsByPubkey,
|
||||
getReadRelayUrls,
|
||||
getWriteRelayUrls,
|
||||
getRelayUrls,
|
||||
} from "./relaySelections"
|
||||
import {relays, relaysByUrl} from "./relays"
|
||||
} from "./relaySelections.js"
|
||||
import {relays, relaysByUrl} from "./relays.js"
|
||||
|
||||
export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS]
|
||||
|
||||
@@ -99,12 +99,14 @@ export type RouterOptions = {
|
||||
}
|
||||
|
||||
export type Selection = {
|
||||
weight: number,
|
||||
relays: string[],
|
||||
weight: number
|
||||
relays: string[]
|
||||
}
|
||||
|
||||
const makeSelection = (relays: string[], weight = 1): Selection =>
|
||||
({relays: relays.map(normalizeRelayUrl), weight})
|
||||
const makeSelection = (relays: string[], weight = 1): Selection => ({
|
||||
relays: relays.map(normalizeRelayUrl),
|
||||
weight,
|
||||
})
|
||||
|
||||
// Fallback policies
|
||||
|
||||
@@ -112,7 +114,7 @@ export type FallbackPolicy = (count: number, limit: number) => number
|
||||
|
||||
export const addNoFallbacks = (count: number, limit: number) => 0
|
||||
|
||||
export const addMinimalFallbacks = (count: number, limit: number) => count > 0 ? 0 : 1
|
||||
export const addMinimalFallbacks = (count: number, limit: number) => (count > 0 ? 0 : 1)
|
||||
|
||||
export const addMaximalFallbacks = (count: number, limit: number) => limit - count
|
||||
|
||||
@@ -142,35 +144,26 @@ export class Router {
|
||||
|
||||
// Routing scenarios
|
||||
|
||||
FromRelays = (relays: string[]) =>
|
||||
this.scenario([makeSelection(relays)])
|
||||
FromRelays = (relays: string[]) => this.scenario([makeSelection(relays)])
|
||||
|
||||
ForUser = () =>
|
||||
this.FromRelays(this.getRelaysForUser(RelayMode.Read))
|
||||
ForUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Read))
|
||||
|
||||
FromUser = () =>
|
||||
this.FromRelays(this.getRelaysForUser(RelayMode.Write))
|
||||
FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write))
|
||||
|
||||
UserInbox = () =>
|
||||
this.FromRelays(this.getRelaysForUser(RelayMode.Inbox)).policy(addNoFallbacks)
|
||||
UserInbox = () => this.FromRelays(this.getRelaysForUser(RelayMode.Inbox)).policy(addNoFallbacks)
|
||||
|
||||
ForPubkey = (pubkey: string) =>
|
||||
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
|
||||
ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
|
||||
|
||||
FromPubkey = (pubkey: string) =>
|
||||
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
|
||||
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
|
||||
|
||||
PubkeyInbox = (pubkey: string) =>
|
||||
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Inbox)).policy(addNoFallbacks)
|
||||
|
||||
ForPubkeys = (pubkeys: string[]) =>
|
||||
this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
|
||||
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
|
||||
|
||||
FromPubkeys = (pubkeys: string[]) =>
|
||||
this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey)))
|
||||
FromPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey)))
|
||||
|
||||
PubkeyInboxes = (pubkeys: string[]) =>
|
||||
this.merge(pubkeys.map(pubkey => this.PubkeyInbox(pubkey)))
|
||||
PubkeyInboxes = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.PubkeyInbox(pubkey)))
|
||||
|
||||
Event = (event: TrustedEvent) =>
|
||||
this.FromRelays(this.getRelaysForPubkey(event.pubkey, RelayMode.Write))
|
||||
@@ -180,10 +173,7 @@ export class Router {
|
||||
|
||||
Quote = (event: TrustedEvent, value: string, relays: string[] = []) => {
|
||||
const tag = event.tags.find(t => t[1] === value)
|
||||
const scenarios = [
|
||||
this.ForPubkey(event.pubkey),
|
||||
this.FromPubkey(event.pubkey),
|
||||
]
|
||||
const scenarios = [this.ForPubkey(event.pubkey), this.FromPubkey(event.pubkey)]
|
||||
|
||||
if (tag?.[2] && isShareableRelayUrl(tag[2])) {
|
||||
scenarios.push(this.FromRelays([tag[2]]))
|
||||
@@ -198,21 +188,19 @@ export class Router {
|
||||
|
||||
EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => {
|
||||
return this.scenario(
|
||||
getAncestorTags(event.tags)[type].flatMap(
|
||||
([_, value, relay, pubkey]) => {
|
||||
const selections = [makeSelection(this.ForUser().getUrls(), 0.5)]
|
||||
getAncestorTags(event.tags)[type].flatMap(([_, value, relay, pubkey]) => {
|
||||
const selections = [makeSelection(this.ForUser().getUrls(), 0.5)]
|
||||
|
||||
if (pubkey) {
|
||||
selections.push(makeSelection(this.FromPubkey(pubkey).getUrls()))
|
||||
}
|
||||
|
||||
if (relay) {
|
||||
selections.push(makeSelection([relay], 0.9))
|
||||
}
|
||||
|
||||
return selections
|
||||
if (pubkey) {
|
||||
selections.push(makeSelection(this.FromPubkey(pubkey).getUrls()))
|
||||
}
|
||||
)
|
||||
|
||||
if (relay) {
|
||||
selections.push(makeSelection([relay], 0.9))
|
||||
}
|
||||
|
||||
return selections
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -240,16 +228,28 @@ export type RouterScenarioOptions = {
|
||||
}
|
||||
|
||||
export class RouterScenario {
|
||||
constructor(readonly router: Router, readonly selections: Selection[], readonly options: RouterScenarioOptions = {}) {}
|
||||
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)
|
||||
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)
|
||||
new RouterScenario(
|
||||
this.router,
|
||||
this.selections.map(selection => f(selection)),
|
||||
this.options,
|
||||
)
|
||||
|
||||
policy = (policy: FallbackPolicy) => this.clone({policy})
|
||||
|
||||
@@ -290,11 +290,7 @@ export class RouterScenario {
|
||||
|
||||
const relays = take(
|
||||
limit,
|
||||
sortBy(
|
||||
scoreRelay,
|
||||
Array.from(relayWeights.keys())
|
||||
.filter(scoreRelay)
|
||||
)
|
||||
sortBy(scoreRelay, Array.from(relayWeights.keys()).filter(scoreRelay)),
|
||||
)
|
||||
|
||||
const fallbacksNeeded = fallbackPolicy(relays.length, limit)
|
||||
@@ -348,14 +344,14 @@ 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)
|
||||
.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)
|
||||
.map(r => r.url),
|
||||
)
|
||||
|
||||
export const makeRouter = (options: Partial<RouterOptions> = {}) =>
|
||||
@@ -372,7 +368,7 @@ export const makeRouter = (options: Partial<RouterOptions> = {}) =>
|
||||
|
||||
// Infer relay selections from filters
|
||||
|
||||
type FilterScenario = {filter: Filter, scenario: RouterScenario}
|
||||
type FilterScenario = {filter: Filter; scenario: RouterScenario}
|
||||
|
||||
type FilterSelectionRule = (filter: Filter) => FilterScenario[]
|
||||
|
||||
@@ -387,10 +383,12 @@ export const getFilterSelectionsForSearch = (filter: Filter) => {
|
||||
export const getFilterSelectionsForWraps = (filter: Filter) => {
|
||||
if (!filter.kinds?.includes(WRAP) || filter.authors) return []
|
||||
|
||||
return [{
|
||||
filter: {...filter, kinds: [WRAP]},
|
||||
scenario: ctx.app.router.UserInbox(),
|
||||
}]
|
||||
return [
|
||||
{
|
||||
filter: {...filter, kinds: [WRAP]},
|
||||
scenario: ctx.app.router.UserInbox(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export const getFilterSelectionsForIndexedKinds = (filter: Filter) => {
|
||||
@@ -400,10 +398,12 @@ export const getFilterSelectionsForIndexedKinds = (filter: Filter) => {
|
||||
|
||||
const relays = ctx.app.router.options.getIndexerRelays?.() || []
|
||||
|
||||
return [{
|
||||
filter: {...filter, kinds},
|
||||
scenario: ctx.app.router.FromRelays(relays),
|
||||
}]
|
||||
return [
|
||||
{
|
||||
filter: {...filter, kinds},
|
||||
scenario: ctx.app.router.FromRelays(relays),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export const getFilterSelectionsForAuthors = (filter: Filter) => {
|
||||
@@ -411,15 +411,15 @@ export const getFilterSelectionsForAuthors = (filter: Filter) => {
|
||||
|
||||
const chunkCount = clamp([1, 30], Math.round(filter.authors.length / 30))
|
||||
|
||||
return chunks(chunkCount, filter.authors)
|
||||
.map(authors => ({
|
||||
filter: {...filter, authors},
|
||||
scenario: ctx.app.router.FromPubkeys(authors),
|
||||
}))
|
||||
return chunks(chunkCount, filter.authors).map(authors => ({
|
||||
filter: {...filter, authors},
|
||||
scenario: ctx.app.router.FromPubkeys(authors),
|
||||
}))
|
||||
}
|
||||
|
||||
export const getFilterSelectionsForUser = (filter: Filter) =>
|
||||
[{filter, scenario: ctx.app.router.ForUser().weight(0.2)}]
|
||||
export const getFilterSelectionsForUser = (filter: Filter) => [
|
||||
{filter, scenario: ctx.app.router.ForUser().weight(0.2)},
|
||||
]
|
||||
|
||||
export const defaultFilterSelectionRules = [
|
||||
getFilterSelectionsForSearch,
|
||||
@@ -431,7 +431,7 @@ export const defaultFilterSelectionRules = [
|
||||
|
||||
export const getFilterSelections = (
|
||||
filters: Filter[],
|
||||
rules: FilterSelectionRule[] = defaultFilterSelectionRules
|
||||
rules: FilterSelectionRule[] = defaultFilterSelectionRules,
|
||||
): RelaysAndFilters[] => {
|
||||
const filtersById = new Map<string, Filter>()
|
||||
const scenariosById = new Map<string, RouterScenario[]>()
|
||||
|
||||
+18
-19
@@ -1,19 +1,19 @@
|
||||
import Fuse from "fuse.js"
|
||||
import type {IFuseOptions, FuseResult} from "fuse.js"
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {derived} from 'svelte/store'
|
||||
import {dec, sortBy} from '@welshman/lib'
|
||||
import {PROFILE} from '@welshman/util'
|
||||
import {throttled} from '@welshman/store'
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {derived} from "svelte/store"
|
||||
import {dec, sortBy} from "@welshman/lib"
|
||||
import {PROFILE} from "@welshman/util"
|
||||
import {throttled} from "@welshman/store"
|
||||
import type {PublishedProfile} from "@welshman/util"
|
||||
import {load} from './subscribe'
|
||||
import {wotGraph} from './wot'
|
||||
import {profiles} from './profiles'
|
||||
import {topics} from './topics'
|
||||
import type {Topic} from './topics'
|
||||
import {relays} from './relays'
|
||||
import type {Relay} from './relays'
|
||||
import {handlesByNip05} from './handles'
|
||||
import {load} from "./subscribe.js"
|
||||
import {wotGraph} from "./wot.js"
|
||||
import {profiles} from "./profiles.js"
|
||||
import {topics} from "./topics.js"
|
||||
import type {Topic} from "./topics.js"
|
||||
import {relays} from "./relays.js"
|
||||
import type {Relay} from "./relays.js"
|
||||
import {handlesByNip05} from "./handles.js"
|
||||
|
||||
export type SearchOptions<V, T> = {
|
||||
getValue: (item: T) => V
|
||||
@@ -65,12 +65,11 @@ export const profileSearch = derived(
|
||||
[throttled(800, profiles), throttled(800, handlesByNip05)],
|
||||
([$profiles, $handlesByNip05]) => {
|
||||
// Remove invalid nip05's from profiles
|
||||
const options = $profiles
|
||||
.map(p => {
|
||||
const isNip05Valid = !p.nip05 || $handlesByNip05.get(p.nip05)?.pubkey === p.event.pubkey
|
||||
const options = $profiles.map(p => {
|
||||
const isNip05Valid = !p.nip05 || $handlesByNip05.get(p.nip05)?.pubkey === p.event.pubkey
|
||||
|
||||
return isNip05Valid ? p : {...p, nip05: ""}
|
||||
})
|
||||
return isNip05Valid ? p : {...p, nip05: ""}
|
||||
})
|
||||
|
||||
return createSearch(options, {
|
||||
onSearch: searchProfiles,
|
||||
@@ -93,7 +92,7 @@ export const profileSearch = derived(
|
||||
shouldSort: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const topicSearch = derived(topics, $topics =>
|
||||
|
||||
+12
-12
@@ -4,18 +4,18 @@ import {withGetter, synced} from "@welshman/store"
|
||||
import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer, Nip55Signer} from "@welshman/signer"
|
||||
|
||||
export type SessionNip01 = {
|
||||
method: 'nip01'
|
||||
method: "nip01"
|
||||
pubkey: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
export type SessionNip07 = {
|
||||
method: 'nip07'
|
||||
method: "nip07"
|
||||
pubkey: string
|
||||
}
|
||||
|
||||
export type SessionNip46 = {
|
||||
method: 'nip46'
|
||||
method: "nip46"
|
||||
pubkey: string
|
||||
secret: string
|
||||
handler: {
|
||||
@@ -25,22 +25,22 @@ export type SessionNip46 = {
|
||||
}
|
||||
|
||||
export type SessionNip55 = {
|
||||
method: 'nip55'
|
||||
method: "nip55"
|
||||
pubkey: string
|
||||
signer: string
|
||||
}
|
||||
|
||||
export type SessionPubkey = {
|
||||
method: 'pubkey'
|
||||
method: "pubkey"
|
||||
pubkey: string
|
||||
}
|
||||
|
||||
export type SessionAnyMethod =
|
||||
SessionNip01 |
|
||||
SessionNip07 |
|
||||
SessionNip46 |
|
||||
SessionNip55 |
|
||||
SessionPubkey
|
||||
| SessionNip01
|
||||
| SessionNip07
|
||||
| SessionNip46
|
||||
| SessionNip55
|
||||
| SessionPubkey
|
||||
|
||||
export type Session = SessionAnyMethod & Record<string, any>
|
||||
|
||||
@@ -88,14 +88,14 @@ export const getSigner = cached({
|
||||
clientSecret: session.secret!,
|
||||
relays: session.handler!.relays,
|
||||
signerPubkey: session.handler!.pubkey,
|
||||
})
|
||||
}),
|
||||
)
|
||||
case "nip55":
|
||||
return new Nip55Signer(session.signer!)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const signer = withGetter(derived(session, getSigner))
|
||||
|
||||
+117
-96
@@ -49,35 +49,37 @@ export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapt
|
||||
|
||||
adapter.store.set(prevRecords)
|
||||
|
||||
adapter.store.subscribe(
|
||||
async (currentRecords: any[]) => {
|
||||
if (dead.get()) {
|
||||
return
|
||||
}
|
||||
adapter.store.subscribe(async (currentRecords: any[]) => {
|
||||
if (dead.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentIds = new Set(currentRecords.map(item => item[adapter.keyPath]))
|
||||
const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath]))
|
||||
const currentIds = new Set(currentRecords.map(item => item[adapter.keyPath]))
|
||||
const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath]))
|
||||
|
||||
const prevRecordsById = indexBy(item => item[adapter.keyPath], prevRecords)
|
||||
const updatedRecords = currentRecords.filter(r => r !== prevRecordsById.get(r[adapter.keyPath]))
|
||||
const prevRecordsById = indexBy(item => item[adapter.keyPath], prevRecords)
|
||||
const updatedRecords = currentRecords.filter(r => r !== prevRecordsById.get(r[adapter.keyPath]))
|
||||
|
||||
prevRecords = currentRecords
|
||||
prevRecords = currentRecords
|
||||
|
||||
if (updatedRecords.length > 0) {
|
||||
await bulkPut(name, updatedRecords)
|
||||
}
|
||||
if (updatedRecords.length > 0) {
|
||||
await bulkPut(name, updatedRecords)
|
||||
}
|
||||
|
||||
if (removedRecords.length > 0) {
|
||||
await bulkDelete(
|
||||
name,
|
||||
removedRecords.map(item => item[adapter.keyPath]),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
if (removedRecords.length > 0) {
|
||||
await bulkDelete(
|
||||
name,
|
||||
removedRecords.map(item => item[adapter.keyPath]),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const initStorage = async (name: string, version: number, adapters: Record<string, IndexedDbAdapter>) => {
|
||||
export const initStorage = async (
|
||||
name: string,
|
||||
version: number,
|
||||
adapters: Record<string, IndexedDbAdapter>,
|
||||
) => {
|
||||
if (!window.indexedDB) return
|
||||
|
||||
window.addEventListener("beforeunload", () => closeStorage())
|
||||
@@ -131,14 +133,20 @@ const migrate = (data: any[], options: StorageAdapterOptions) =>
|
||||
options.migrate ? options.migrate(data) : data
|
||||
|
||||
export const storageAdapters = {
|
||||
fromObjectStore: <T>(store: Writable<Record<string, T>>, options: StorageAdapterOptions = {}) => ({
|
||||
fromObjectStore: <T>(
|
||||
store: Writable<Record<string, T>>,
|
||||
options: StorageAdapterOptions = {},
|
||||
) => ({
|
||||
options,
|
||||
keyPath: "key",
|
||||
store: adapter({
|
||||
store: throttled(options.throttle || 0, store),
|
||||
forward: (data: Record<string, T>) =>
|
||||
migrate(Object.entries(data).map(([key, value]) => ({key, value})), options),
|
||||
backward: (data: {key: string, value: T}[]) =>
|
||||
migrate(
|
||||
Object.entries(data).map(([key, value]) => ({key, value})),
|
||||
options,
|
||||
),
|
||||
backward: (data: {key: string; value: T}[]) =>
|
||||
fromPairs(data.map(({key, value}) => [key, value])),
|
||||
}),
|
||||
}),
|
||||
@@ -148,100 +156,113 @@ export const storageAdapters = {
|
||||
store: adapter({
|
||||
store: throttled(options.throttle || 0, store),
|
||||
forward: (data: Map<string, T>) =>
|
||||
migrate(Array.from(data.entries()).map(([key, value]) => ({key, value})), options),
|
||||
backward: (data: {key: string, value: T}[]) =>
|
||||
migrate(
|
||||
Array.from(data.entries()).map(([key, value]) => ({key, value})),
|
||||
options,
|
||||
),
|
||||
backward: (data: {key: string; value: T}[]) =>
|
||||
new Map(data.map(({key, value}) => [key, value])),
|
||||
}),
|
||||
}),
|
||||
fromTracker: (tracker: Tracker, options: StorageAdapterOptions = {}) => ({
|
||||
options,
|
||||
keyPath: 'key',
|
||||
store: custom(setter => {
|
||||
let onUpdate = () =>
|
||||
setter(
|
||||
migrate(
|
||||
Array.from(tracker.relaysById.entries())
|
||||
.map(([key, urls]) => ({key, value: Array.from(urls)})),
|
||||
options
|
||||
keyPath: "key",
|
||||
store: custom(
|
||||
setter => {
|
||||
let onUpdate = () =>
|
||||
setter(
|
||||
migrate(
|
||||
Array.from(tracker.relaysById.entries()).map(([key, urls]) => ({
|
||||
key,
|
||||
value: Array.from(urls),
|
||||
})),
|
||||
options,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
tracker.on('update', onUpdate)
|
||||
onUpdate()
|
||||
tracker.on("update", onUpdate)
|
||||
|
||||
return () => tracker.off('update', onUpdate)
|
||||
}, {
|
||||
set: (data: {key: string, value: string[]}[]) =>
|
||||
tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))),
|
||||
}),
|
||||
return () => tracker.off("update", onUpdate)
|
||||
},
|
||||
{
|
||||
set: (data: {key: string; value: string[]}[]) =>
|
||||
tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))),
|
||||
},
|
||||
),
|
||||
}),
|
||||
fromRepository: (repository: Repository, options: StorageAdapterOptions = {}) => ({
|
||||
options,
|
||||
keyPath: 'id',
|
||||
store: custom(setter => {
|
||||
let onUpdate = () => setter(migrate(repository.dump(), options))
|
||||
keyPath: "id",
|
||||
store: custom(
|
||||
setter => {
|
||||
let onUpdate = () => setter(migrate(repository.dump(), options))
|
||||
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
repository.on('update', onUpdate)
|
||||
onUpdate()
|
||||
repository.on("update", onUpdate)
|
||||
|
||||
return () => repository.off('update', onUpdate)
|
||||
}, {
|
||||
set: (events: TrustedEvent[]) => repository.load(events),
|
||||
}),
|
||||
return () => repository.off("update", onUpdate)
|
||||
},
|
||||
{
|
||||
set: (events: TrustedEvent[]) => repository.load(events),
|
||||
},
|
||||
),
|
||||
}),
|
||||
fromRepositoryAndTracker: (
|
||||
repository: Repository,
|
||||
tracker: Tracker,
|
||||
options: StorageAdapterOptions = {}
|
||||
options: StorageAdapterOptions = {},
|
||||
) => ({
|
||||
options,
|
||||
keyPath: 'id',
|
||||
store: custom(setter => {
|
||||
let onUpdate = () => {
|
||||
const events = migrate(repository.dump(), options)
|
||||
keyPath: "id",
|
||||
store: custom(
|
||||
setter => {
|
||||
let onUpdate = () => {
|
||||
const events = migrate(repository.dump(), options)
|
||||
|
||||
setter(
|
||||
events.map(event => {
|
||||
const relays = Array.from(tracker.getRelays(event.id))
|
||||
setter(
|
||||
events.map(event => {
|
||||
const relays = Array.from(tracker.getRelays(event.id))
|
||||
|
||||
return {id: event.id, event, relays}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
tracker.on('update', onUpdate)
|
||||
repository.on('update', onUpdate)
|
||||
|
||||
return () => {
|
||||
tracker.off('update', onUpdate)
|
||||
}
|
||||
}, {
|
||||
set: (items: {event: TrustedEvent, relays: string[]}[]) => {
|
||||
const events: TrustedEvent[] = []
|
||||
const relaysById = new Map<string, Set<string>>()
|
||||
|
||||
for (const {event, relays} of items) {
|
||||
events.push(event)
|
||||
relaysById.set(event.id, new Set(relays))
|
||||
return {id: event.id, event, relays}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
repository.load(events)
|
||||
tracker.load(relaysById)
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (options.throttle) {
|
||||
onUpdate = throttle(options.throttle, onUpdate)
|
||||
}
|
||||
|
||||
onUpdate()
|
||||
tracker.on("update", onUpdate)
|
||||
repository.on("update", onUpdate)
|
||||
|
||||
return () => {
|
||||
tracker.off("update", onUpdate)
|
||||
}
|
||||
},
|
||||
{
|
||||
set: (items: {event: TrustedEvent; relays: string[]}[]) => {
|
||||
const events: TrustedEvent[] = []
|
||||
const relaysById = new Map<string, Set<string>>()
|
||||
|
||||
for (const {event, relays} of items) {
|
||||
events.push(event)
|
||||
relaysById.set(event.id, new Set(relays))
|
||||
}
|
||||
|
||||
repository.load(events)
|
||||
tracker.load(relaysById)
|
||||
},
|
||||
},
|
||||
),
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {LOCAL_RELAY_URL, getFilterResultCardinality} from "@welshman/util"
|
||||
import type {TrustedEvent, Filter} from "@welshman/util"
|
||||
import {subscribe as baseSubscribe, SubscriptionEvent} from "@welshman/net"
|
||||
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {repository} from './core'
|
||||
import {repository} from "./core.js"
|
||||
|
||||
export type PartialSubscribeRequest = Partial<SubscribeRequestWithHandlers> & {filters: Filter[]}
|
||||
|
||||
|
||||
+25
-25
@@ -1,14 +1,21 @@
|
||||
import type {Filter} from '@welshman/util'
|
||||
import {isSignedEvent} from '@welshman/util'
|
||||
import {push as basePush, pull as basePull, sync as baseSync, pushWithoutNegentropy, pullWithoutNegentropy, syncWithoutNegentropy} from "@welshman/net"
|
||||
import {repository} from './core'
|
||||
import {relaysByUrl} from './relays'
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {isSignedEvent} from "@welshman/util"
|
||||
import {
|
||||
push as basePush,
|
||||
pull as basePull,
|
||||
sync as baseSync,
|
||||
pushWithoutNegentropy,
|
||||
pullWithoutNegentropy,
|
||||
syncWithoutNegentropy,
|
||||
} from "@welshman/net"
|
||||
import {repository} from "./core.js"
|
||||
import {relaysByUrl} from "./relays.js"
|
||||
|
||||
export const hasNegentropy = (url: string) => {
|
||||
const p = relaysByUrl.get().get(url)?.profile
|
||||
|
||||
if (p?.supported_nips?.includes(77)) return true
|
||||
if (p?.software?.includes('strfry') && !p?.version?.match(/^0\./)) return true
|
||||
if (p?.software?.includes("strfry") && !p?.version?.match(/^0\./)) return true
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -23,12 +30,10 @@ export const pull = async ({relays, filters}: AppSyncOpts) => {
|
||||
|
||||
await Promise.all(
|
||||
relays.map(async relay => {
|
||||
await (
|
||||
hasNegentropy(relay)
|
||||
? basePull({filters, events, relays: [relay]})
|
||||
: pullWithoutNegentropy({filters, relays: [relay]})
|
||||
)
|
||||
})
|
||||
await (hasNegentropy(relay)
|
||||
? basePull({filters, events, relays: [relay]})
|
||||
: pullWithoutNegentropy({filters, relays: [relay]}))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,12 +42,10 @@ export const push = async ({relays, filters}: AppSyncOpts) => {
|
||||
|
||||
await Promise.all(
|
||||
relays.map(async relay => {
|
||||
await (
|
||||
hasNegentropy(relay)
|
||||
? basePush({filters, events, relays: [relay]})
|
||||
: pushWithoutNegentropy({events, relays: [relay]})
|
||||
)
|
||||
})
|
||||
await (hasNegentropy(relay)
|
||||
? basePush({filters, events, relays: [relay]})
|
||||
: pushWithoutNegentropy({events, relays: [relay]}))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,12 +54,9 @@ export const sync = async ({relays, filters}: AppSyncOpts) => {
|
||||
|
||||
await Promise.all(
|
||||
relays.map(async relay => {
|
||||
await (
|
||||
hasNegentropy(relay)
|
||||
? baseSync({filters, events, relays: [relay]})
|
||||
: syncWithoutNegentropy({filters, events, relays: [relay]})
|
||||
)
|
||||
})
|
||||
await (hasNegentropy(relay)
|
||||
? baseSync({filters, events, relays: [relay]})
|
||||
: syncWithoutNegentropy({filters, events, relays: [relay]}))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import {ctx} from '@welshman/lib'
|
||||
import {getAddress, isReplaceable, getAncestorTags, getPubkeyTagValues, getIdAndAddress} from '@welshman/util'
|
||||
import type {TrustedEvent} from '@welshman/util'
|
||||
import {displayProfileByPubkey} from './profiles'
|
||||
import {pubkey} from './session'
|
||||
import {ctx} from "@welshman/lib"
|
||||
import {
|
||||
getAddress,
|
||||
isReplaceable,
|
||||
getAncestorTags,
|
||||
getPubkeyTagValues,
|
||||
getIdAndAddress,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {displayProfileByPubkey} from "./profiles.js"
|
||||
import {pubkey} from "./session.js"
|
||||
|
||||
export const tagZapSplit = (pubkey: string, split = 1) => [
|
||||
"zap",
|
||||
@@ -102,6 +108,3 @@ export const tagReactionTo = (event: TrustedEvent) => {
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+28
-16
@@ -1,13 +1,26 @@
|
||||
import {writable, derived, get} from 'svelte/store'
|
||||
import type {Writable, Readable} from 'svelte/store'
|
||||
import {Worker, identity, uniq, defer, sleep, assoc} from '@welshman/lib'
|
||||
import type {Deferred} from '@welshman/lib'
|
||||
import {writable, derived, get} from "svelte/store"
|
||||
import type {Writable, Readable} from "svelte/store"
|
||||
import {Worker, identity, uniq, defer, sleep, assoc} from "@welshman/lib"
|
||||
import type {Deferred} from "@welshman/lib"
|
||||
import {stamp, own, hash} from "@welshman/signer"
|
||||
import type {TrustedEvent, HashedEvent, EventTemplate, SignedEvent, StampedEvent, OwnedEvent} from '@welshman/util'
|
||||
import {isStampedEvent, isOwnedEvent, isHashedEvent, isUnwrappedEvent, isSignedEvent} from '@welshman/util'
|
||||
import type {
|
||||
TrustedEvent,
|
||||
HashedEvent,
|
||||
EventTemplate,
|
||||
SignedEvent,
|
||||
StampedEvent,
|
||||
OwnedEvent,
|
||||
} from "@welshman/util"
|
||||
import {
|
||||
isStampedEvent,
|
||||
isOwnedEvent,
|
||||
isHashedEvent,
|
||||
isUnwrappedEvent,
|
||||
isSignedEvent,
|
||||
} from "@welshman/util"
|
||||
import {publish, PublishStatus} from "@welshman/net"
|
||||
import {repository, tracker} from './core'
|
||||
import {pubkey, getSession, getSigner} from './session'
|
||||
import {repository, tracker} from "./core.js"
|
||||
import {pubkey, getSession, getSigner} from "./session.js"
|
||||
|
||||
const {Pending, Success, Failure, Timeout, Aborted} = PublishStatus
|
||||
|
||||
@@ -53,8 +66,8 @@ export const prepEvent = (event: ThunkEvent) => {
|
||||
export const makeThunk = (request: ThunkRequest) => {
|
||||
const event = prepEvent(request.event)
|
||||
const controller = new AbortController()
|
||||
const result: Thunk['result'] = defer()
|
||||
const status: Thunk['status'] = writable({})
|
||||
const result: Thunk["result"] = defer()
|
||||
const status: Thunk["status"] = writable({})
|
||||
|
||||
return {event, request, controller, result, status}
|
||||
}
|
||||
@@ -72,7 +85,7 @@ export const isMergedThunk = (thunk: Thunk | MergedThunk): thunk is MergedThunk
|
||||
export const mergeThunks = (thunks: Thunk[]) => {
|
||||
const controller = new AbortController()
|
||||
|
||||
controller.signal.addEventListener('abort', () => {
|
||||
controller.signal.addEventListener("abort", () => {
|
||||
for (const thunk of thunks) {
|
||||
thunk.controller.abort()
|
||||
}
|
||||
@@ -99,8 +112,8 @@ export const mergeThunks = (thunks: Thunk[]) => {
|
||||
}
|
||||
|
||||
return mergedStatus
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +138,7 @@ export const publishThunk = (request: ThunkRequest) => {
|
||||
|
||||
thunks.update(assoc(thunk.event.id, thunk))
|
||||
|
||||
thunk.controller.signal.addEventListener('abort', () => {
|
||||
thunk.controller.signal.addEventListener("abort", () => {
|
||||
repository.removeEvent(thunk.event.id)
|
||||
})
|
||||
|
||||
@@ -143,7 +156,7 @@ export const publishThunks = (requests: ThunkRequest[]) => {
|
||||
|
||||
thunks.update(assoc(thunk.event.id, mergedThunk))
|
||||
|
||||
thunk.controller.signal.addEventListener('abort', () => {
|
||||
thunk.controller.signal.addEventListener("abort", () => {
|
||||
repository.removeEvent(thunk.event.id)
|
||||
})
|
||||
}
|
||||
@@ -221,4 +234,3 @@ thunkWorker.addGlobalHandler((thunk: Thunk) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {inc, throttle} from '@welshman/lib'
|
||||
import {custom} from '@welshman/store'
|
||||
import {repository} from './core'
|
||||
import {inc, throttle} from "@welshman/lib"
|
||||
import {custom} from "@welshman/store"
|
||||
import {repository} from "./core.js"
|
||||
|
||||
export type Topic = {
|
||||
name: string
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
import type {NetContext} from '@welshman/net'
|
||||
import type {AppContext} from './context'
|
||||
|
||||
declare module "@welshman/lib" {
|
||||
interface Context {
|
||||
net: NetContext
|
||||
app: AppContext
|
||||
}
|
||||
}
|
||||
+29
-33
@@ -1,43 +1,39 @@
|
||||
import {derived} from 'svelte/store'
|
||||
import {pubkey} from './session'
|
||||
import {profilesByPubkey, loadProfile} from './profiles'
|
||||
import {followsByPubkey, loadFollows} from './follows'
|
||||
import {mutesByPubkey, loadMutes} from './mutes'
|
||||
import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, loadRelaySelections, loadInboxRelaySelections} from './relaySelections'
|
||||
import {wotGraph} from './wot'
|
||||
import {derived} from "svelte/store"
|
||||
import {pubkey} from "./session.js"
|
||||
import {profilesByPubkey, loadProfile} from "./profiles.js"
|
||||
import {followsByPubkey, loadFollows} from "./follows.js"
|
||||
import {mutesByPubkey, loadMutes} from "./mutes.js"
|
||||
import {
|
||||
relaySelectionsByPubkey,
|
||||
inboxRelaySelectionsByPubkey,
|
||||
loadRelaySelections,
|
||||
loadInboxRelaySelections,
|
||||
} from "./relaySelections.js"
|
||||
import {wotGraph} from "./wot.js"
|
||||
|
||||
export const userProfile = derived(
|
||||
[profilesByPubkey, pubkey],
|
||||
([$profilesByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
export const userProfile = derived([profilesByPubkey, pubkey], ([$profilesByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
|
||||
loadProfile($pubkey)
|
||||
loadProfile($pubkey)
|
||||
|
||||
return $profilesByPubkey.get($pubkey)
|
||||
}
|
||||
)
|
||||
return $profilesByPubkey.get($pubkey)
|
||||
})
|
||||
|
||||
export const userFollows = derived(
|
||||
[followsByPubkey, pubkey],
|
||||
([$followsByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
export const userFollows = derived([followsByPubkey, pubkey], ([$followsByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
|
||||
loadFollows($pubkey)
|
||||
loadFollows($pubkey)
|
||||
|
||||
return $followsByPubkey.get($pubkey)
|
||||
}
|
||||
)
|
||||
return $followsByPubkey.get($pubkey)
|
||||
})
|
||||
|
||||
export const userMutes = derived(
|
||||
[mutesByPubkey, pubkey],
|
||||
([$mutesByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
export const userMutes = derived([mutesByPubkey, pubkey], ([$mutesByPubkey, $pubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
|
||||
loadMutes($pubkey)
|
||||
loadMutes($pubkey)
|
||||
|
||||
return $mutesByPubkey.get($pubkey)
|
||||
}
|
||||
)
|
||||
return $mutesByPubkey.get($pubkey)
|
||||
})
|
||||
|
||||
export const userRelaySelections = derived(
|
||||
[relaySelectionsByPubkey, pubkey],
|
||||
@@ -47,7 +43,7 @@ export const userRelaySelections = derived(
|
||||
loadRelaySelections($pubkey)
|
||||
|
||||
return $relaySelectionsByPubkey.get($pubkey)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const userInboxRelaySelections = derived(
|
||||
@@ -58,7 +54,7 @@ export const userInboxRelaySelections = derived(
|
||||
loadInboxRelaySelections($pubkey)
|
||||
|
||||
return $inboxRelaySelectionsByPubkey.get($pubkey)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export const getUserWotScore = (tpk: string) => wotGraph.get().get(tpk) || 0
|
||||
|
||||
+24
-32
@@ -1,10 +1,10 @@
|
||||
import {derived, writable} from 'svelte/store'
|
||||
import {max, throttle, addToMapKey, inc, dec} from '@welshman/lib'
|
||||
import {getListTags, getPubkeyTagValues} from '@welshman/util'
|
||||
import {throttled, withGetter} from '@welshman/store'
|
||||
import {pubkey} from './session'
|
||||
import {follows, followsByPubkey} from './follows'
|
||||
import {mutes, mutesByPubkey} from './mutes'
|
||||
import {derived, writable} from "svelte/store"
|
||||
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
|
||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||
import {throttled, withGetter} from "@welshman/store"
|
||||
import {pubkey} from "./session.js"
|
||||
import {follows, followsByPubkey} from "./follows.js"
|
||||
import {mutes, mutesByPubkey} from "./mutes.js"
|
||||
|
||||
export const getFollows = (pubkey: string) =>
|
||||
getPubkeyTagValues(getListTags(followsByPubkey.get().get(pubkey)))
|
||||
@@ -27,46 +27,38 @@ export const getNetwork = (pubkey: string) => {
|
||||
return Array.from(network)
|
||||
}
|
||||
|
||||
|
||||
export const followersByPubkey = withGetter(
|
||||
derived(
|
||||
throttled(1000, follows),
|
||||
lists => {
|
||||
const $followersByPubkey = new Map<string, Set<string>>()
|
||||
derived(throttled(1000, follows), lists => {
|
||||
const $followersByPubkey = new Map<string, Set<string>>()
|
||||
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
|
||||
return $followersByPubkey
|
||||
}
|
||||
)
|
||||
|
||||
return $followersByPubkey
|
||||
}),
|
||||
)
|
||||
|
||||
export const mutersByPubkey = withGetter(
|
||||
derived(
|
||||
throttled(1000, mutes),
|
||||
lists => {
|
||||
const $mutersByPubkey = new Map<string, Set<string>>()
|
||||
derived(throttled(1000, mutes), lists => {
|
||||
const $mutersByPubkey = new Map<string, Set<string>>()
|
||||
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
|
||||
return $mutersByPubkey
|
||||
}
|
||||
)
|
||||
|
||||
return $mutersByPubkey
|
||||
}),
|
||||
)
|
||||
|
||||
export const getFollowers = (pubkey: string) =>
|
||||
Array.from(followersByPubkey.get().get(pubkey) || [])
|
||||
|
||||
export const getMuters = (pubkey: string) =>
|
||||
Array.from(mutersByPubkey.get().get(pubkey) || [])
|
||||
export const getMuters = (pubkey: string) => Array.from(mutersByPubkey.get().get(pubkey) || [])
|
||||
|
||||
export const getFollowsWhoFollow = (pubkey: string, target: string) =>
|
||||
getFollows(pubkey).filter(other => getFollows(other).includes(target))
|
||||
|
||||
+30
-19
@@ -1,9 +1,19 @@
|
||||
import {writable, derived} from 'svelte/store'
|
||||
import {type Zapper} from '@welshman/util'
|
||||
import {writable, derived} from "svelte/store"
|
||||
import {type Zapper} from "@welshman/util"
|
||||
import {type SubscribeRequestWithHandlers} from "@welshman/net"
|
||||
import {ctx, identity, fetchJson, uniq, bech32ToHex, hexToBech32, tryCatch, batcher, postJson} from '@welshman/lib'
|
||||
import {collection} from './collection'
|
||||
import {deriveProfile} from './profiles'
|
||||
import {
|
||||
ctx,
|
||||
identity,
|
||||
fetchJson,
|
||||
uniq,
|
||||
bech32ToHex,
|
||||
hexToBech32,
|
||||
tryCatch,
|
||||
batcher,
|
||||
postJson,
|
||||
} from "@welshman/lib"
|
||||
import {collection} from "./collection.js"
|
||||
import {deriveProfile} from "./profiles.js"
|
||||
|
||||
export const zappers = writable<Zapper[]>([])
|
||||
|
||||
@@ -16,7 +26,9 @@ export const fetchZappers = async (lnurls: string[]) => {
|
||||
const hexUrls = lnurls.map(lnurl => tryCatch(() => bech32ToHex(lnurl))).filter(identity)
|
||||
|
||||
if (hexUrls.length > 0) {
|
||||
const res: any = await tryCatch(async () => await postJson(`${base}/zapper/info`, {lnurls: hexUrls}))
|
||||
const res: any = await tryCatch(
|
||||
async () => await postJson(`${base}/zapper/info`, {lnurls: hexUrls}),
|
||||
)
|
||||
|
||||
for (const {lnurl, info} of res?.data || []) {
|
||||
tryCatch(() => zappersByLnurl.set(hexToBech32("lnurl", lnurl), info))
|
||||
@@ -29,7 +41,7 @@ export const fetchZappers = async (lnurls: string[]) => {
|
||||
const info = hexUrl ? await tryCatch(async () => await fetchJson(hexUrl)) : undefined
|
||||
|
||||
return {lnurl, hexUrl, info}
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
for (const {lnurl, info} of results) {
|
||||
@@ -68,17 +80,16 @@ export const {
|
||||
}),
|
||||
})
|
||||
|
||||
export const deriveZapperForPubkey = (pubkey: string, request: Partial<SubscribeRequestWithHandlers> = {}) =>
|
||||
derived(
|
||||
[zappersByLnurl, deriveProfile(pubkey, request)],
|
||||
([$zappersByLnurl, $profile]) => {
|
||||
if (!$profile?.lnurl) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
loadZapper($profile.lnurl)
|
||||
|
||||
return $zappersByLnurl.get($profile.lnurl)
|
||||
export const deriveZapperForPubkey = (
|
||||
pubkey: string,
|
||||
request: Partial<SubscribeRequestWithHandlers> = {},
|
||||
) =>
|
||||
derived([zappersByLnurl, deriveProfile(pubkey, request)], ([$zappersByLnurl, $profile]) => {
|
||||
if (!$profile?.lnurl) {
|
||||
return undefined
|
||||
}
|
||||
)
|
||||
|
||||
loadZapper($profile.lnurl)
|
||||
|
||||
return $zappersByLnurl.get($profile.lnurl)
|
||||
})
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"targets": [
|
||||
{"extname": ".cjs", "module": "commonjs"},
|
||||
{"extname": ".mjs", "module": "esnext", "moduleResolution": "node"}
|
||||
],
|
||||
"projects": ["tsconfig.json"]
|
||||
}
|
||||
@@ -3,9 +3,12 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "build",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user