diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 09b6a98..38a9dfa 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -3,7 +3,7 @@ import {call} from "@welshman/lib" import {Pool, Tracker, Repository, WrapManager} from "@welshman/net" import type {NetContext, AdapterFactory} from "@welshman/net" import type {User} from "./user.js" -import type {ClientPolicy} from "./policies.js" +import type {ClientPolicy} from "./policy.js" export type ClientConfig = { dufflepudUrl?: string diff --git a/packages/client/src/createApp.ts b/packages/client/src/createApp.ts index ea607ba..91a75cc 100644 --- a/packages/client/src/createApp.ts +++ b/packages/client/src/createApp.ts @@ -1,6 +1,6 @@ import {Client} from "./client.js" import type {ClientOptions} from "./client.js" -import {defaultClientPolicies} from "./policies.js" +import {defaultClientPolicies} from "./policy.js" /** * Creates a batteries-included client: a `Client` wired with the default client diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 26040af..a165284 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,34 +1,34 @@ export * from "./client.js" -export * from "./policies.js" -export * from "./network.js" -export * from "./stores.js" -export * from "./clientData.js" +export * from "./policy.js" export * from "./user.js" -export * from "./router.js" -export * from "./relays.js" -export * from "./relayStats.js" -export * from "./relayLists.js" -export * from "./blockedRelayLists.js" -export * from "./plaintext.js" -export * from "./profiles.js" -export * from "./follows.js" -export * from "./mutes.js" -export * from "./pins.js" -export * from "./blossom.js" -export * from "./messagingRelayLists.js" -export * from "./searchRelayLists.js" -export * from "./handles.js" -export * from "./zappers.js" -export * from "./topics.js" -export * from "./tags.js" export * from "./session.js" export * from "./logging.js" -export * from "./wot.js" -export * from "./feeds.js" -export * from "./search.js" -export * from "./sync.js" -export * from "./giftWraps.js" -export * from "./rooms.js" -export * from "./relayManagement.js" -export * from "./thunk.js" export * from "./createApp.js" +export * from "./plugins/base.js" +export * from "./plugins/network.js" +export * from "./plugins/stores.js" +export * from "./plugins/router.js" +export * from "./plugins/relays.js" +export * from "./plugins/relayStats.js" +export * from "./plugins/relayLists.js" +export * from "./plugins/blockedRelayLists.js" +export * from "./plugins/plaintext.js" +export * from "./plugins/profiles.js" +export * from "./plugins/follows.js" +export * from "./plugins/mutes.js" +export * from "./plugins/pins.js" +export * from "./plugins/blossom.js" +export * from "./plugins/messagingRelayLists.js" +export * from "./plugins/searchRelayLists.js" +export * from "./plugins/handles.js" +export * from "./plugins/zappers.js" +export * from "./plugins/topics.js" +export * from "./plugins/tags.js" +export * from "./plugins/wot.js" +export * from "./plugins/feeds.js" +export * from "./plugins/search.js" +export * from "./plugins/sync.js" +export * from "./plugins/wraps.js" +export * from "./plugins/rooms.js" +export * from "./plugins/relayManagement.js" +export * from "./plugins/thunk.js" diff --git a/packages/client/src/clientData.ts b/packages/client/src/plugins/base.ts similarity index 73% rename from packages/client/src/clientData.ts rename to packages/client/src/plugins/base.ts index deea713..ea704e0 100644 --- a/packages/client/src/clientData.ts +++ b/packages/client/src/plugins/base.ts @@ -4,7 +4,7 @@ import type {Maybe} from "@welshman/lib" import type {Filter} from "@welshman/util" import {deriveItems, getter, makeDeriveItem, makeLoadItem, makeForceLoadItem} from "@welshman/store" import type {EventToItem, ItemsByKey, MakeLoadItemOptions} from "@welshman/store" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" import {Stores} from "./stores.js" /** @@ -17,6 +17,48 @@ export type Projection = { export const projection = ($: Readable, get = getter($)) => ({$, get}) +/** + * Synchronous read access to a keyed collection's current value. Shared by + * collections that own a map (`MapPlugin`) and those that are read-only views + * over the repository (`DerivedPlugin`). + */ +export interface ReadableMap { + keys(): IterableIterator + values(): IterableIterator + get(key: string): Maybe +} + +/** + * Direct get/set access to a keyed collection the plugin owns. Extends the read + * accessors with mutation plus change notification. Only collections that own + * their map implement this — repository-backed collections are read-only. + */ +export interface Mappable extends ReadableMap { + set(key: string, value: T): void + delete(key: string): void + clear(): void + onItem(subscriber: (key: string, value: Maybe) => void): Unsubscriber +} + +/** + * Lazy, async loading of items by key from the network, with per-key caching + * and backoff (`load`) or a cache-bypassing refresh (`forceLoad`). + */ +export interface Loadable { + load(key: string, ...args: any[]): Promise> + forceLoad(key: string, ...args: any[]): Promise> +} + +/** + * Reactive derivations over a keyed collection: the whole map (`index`), its + * values (`all`), and a per-key on-demand store (`one`). + */ +export interface Derivable { + index: Projection> + all: Projection + one(key?: string, ...args: any[]): Readable> +} + /** * Base class for a reactive, keyed collection of "local" (non-event) data — * things like relay stats or NIP-11 profiles that aren't backed by the @@ -26,7 +68,7 @@ export const projection = ($: Readable, get = getter($)) => ({$, get}) * snapshot via `.get()`. Per-key access is `one(key)`, a plain on-demand store * (snapshot with svelte's `get(...)`, or read `get(key)` directly). */ -export class ClientData { +export class MapPlugin implements Mappable, Derivable { protected store = writable(new Map()) index: Projection> all: Projection @@ -93,11 +135,11 @@ export class ClientData { } /** - * A `ClientData` collection that knows how to lazily load items by key from the + * A `MapPlugin` collection that knows how to lazily load items by key from the * network. Subclasses implement `fetch`; `load`/`forceLoad`/`one` are derived * from it (with per-key caching and backoff via `makeLoadItem`). */ -export abstract class LoadableData extends ClientData { +export abstract class LoadableMapPlugin extends MapPlugin implements Loadable { load: (key: string, ...args: any[]) => Promise> forceLoad: (key: string, ...args: any[]) => Promise> @@ -118,7 +160,7 @@ export abstract class LoadableData extends ClientData { } } -export type DerivedDataOptions = { +export type DerivedPluginOptions = { filters: Filter[] eventToItem: EventToItem getKey: (item: T) => string @@ -135,7 +177,7 @@ export type DerivedDataOptions = { * `index` (map) and `all` (values) are `Projection`s — subscribe via `.$`, * snapshot via `.get()`. Per-key access is `one(key)`, a plain on-demand store. */ -export abstract class DerivedData { +export abstract class DerivedPlugin implements ReadableMap, Loadable, Derivable { index: Projection> all: Projection one: (key?: string, ...args: any[]) => Readable> @@ -146,7 +188,7 @@ export abstract class DerivedData { constructor( protected readonly ctx: IClient, - options: DerivedDataOptions, + options: DerivedPluginOptions, ) { const index = ctx.use(Stores).itemsByKey({ filters: options.filters, diff --git a/packages/client/src/blockedRelayLists.ts b/packages/client/src/plugins/blockedRelayLists.ts similarity index 90% rename from packages/client/src/blockedRelayLists.ts rename to packages/client/src/plugins/blockedRelayLists.ts index eb1194c..ef79f5e 100644 --- a/packages/client/src/blockedRelayLists.ts +++ b/packages/client/src/plugins/blockedRelayLists.ts @@ -9,19 +9,19 @@ import { removeFromList, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" import {Router} from "./router.js" -import {User} from "./user.js" +import {User} from "../user.js" import {Thunks} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Kind-10006 blocked-relay lists, keyed by pubkey. Loaded via the outbox model, * so it depends on the relay-list collection. Feeds `RelayStats.getQuality` so * blocked relays are never selected. */ -export class BlockedRelayLists extends DerivedData> { +export class BlockedRelayLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [BLOCKED_RELAYS]}], diff --git a/packages/client/src/blossom.ts b/packages/client/src/plugins/blossom.ts similarity index 80% rename from packages/client/src/blossom.ts rename to packages/client/src/plugins/blossom.ts index b23106c..a9a9710 100644 --- a/packages/client/src/blossom.ts +++ b/packages/client/src/plugins/blossom.ts @@ -1,14 +1,14 @@ import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Blossom server lists (kind 10063), keyed by pubkey. Loaded via the outbox * model (the author's write relays), so it depends on the relay-list collection. */ -export class BlossomServerLists extends DerivedData> { +export class BlossomServerLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [BLOSSOM_SERVERS]}], diff --git a/packages/client/src/feeds.ts b/packages/client/src/plugins/feeds.ts similarity index 98% rename from packages/client/src/feeds.ts rename to packages/client/src/plugins/feeds.ts index 2cda207..0dbb1ec 100644 --- a/packages/client/src/feeds.ts +++ b/packages/client/src/plugins/feeds.ts @@ -1,7 +1,7 @@ import {Scope, FeedController} from "@welshman/feeds" import type {FeedControllerOptions, Feed} from "@welshman/feeds" 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" diff --git a/packages/client/src/follows.ts b/packages/client/src/plugins/follows.ts similarity index 87% rename from packages/client/src/follows.ts rename to packages/client/src/plugins/follows.ts index abdda80..60be6a1 100644 --- a/packages/client/src/follows.ts +++ b/packages/client/src/plugins/follows.ts @@ -7,17 +7,17 @@ import { removeFromList, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" import {Thunks} from "./thunk.js" -import {User} from "./user.js" -import type {IClient} from "./client.js" +import {User} from "../user.js" +import type {IClient} from "../client.js" /** * Kind-3 follow lists, keyed by pubkey. Loaded via the outbox model (the * author's write relays), so it depends on the relay-list collection. */ -export class FollowLists extends DerivedData> { +export class FollowLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [FOLLOWS]}], diff --git a/packages/client/src/handles.ts b/packages/client/src/plugins/handles.ts similarity index 92% rename from packages/client/src/handles.ts rename to packages/client/src/plugins/handles.ts index 582bcdf..5f0c7ea 100644 --- a/packages/client/src/handles.ts +++ b/packages/client/src/plugins/handles.ts @@ -3,9 +3,9 @@ import type {Maybe} from "@welshman/lib" import {queryProfile, displayNip05} from "@welshman/util" import type {Handle} from "@welshman/util" import {deriveDeduplicated} from "@welshman/store" -import {LoadableData, projection} from "./clientData.js" -import type {Projection} from "./clientData.js" -import type {IClient} from "./client.js" +import {LoadableMapPlugin, projection} from "./base.js" +import type {Projection} from "./base.js" +import type {IClient} from "../client.js" import {Profiles} from "./profiles.js" /** @@ -15,7 +15,7 @@ import {Profiles} from "./profiles.js" * user privacy). Depends on the profiles collection to resolve a pubkey's * handle. */ -export class Handles extends LoadableData { +export class Handles extends LoadableMapPlugin { constructor(ctx: IClient) { super(ctx) } diff --git a/packages/client/src/messagingRelayLists.ts b/packages/client/src/plugins/messagingRelayLists.ts similarity index 89% rename from packages/client/src/messagingRelayLists.ts rename to packages/client/src/plugins/messagingRelayLists.ts index 5eb0e75..029dc17 100644 --- a/packages/client/src/messagingRelayLists.ts +++ b/packages/client/src/plugins/messagingRelayLists.ts @@ -8,19 +8,19 @@ import { removeFromList, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" import {Router} from "./router.js" -import {User} from "./user.js" +import {User} from "../user.js" import {Thunks} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Kind-10050 messaging relay lists (NIP-17), keyed by pubkey. Loaded via the * outbox model (the author's write relays), so it depends on the relay-list * collection. */ -export class MessagingRelayLists extends DerivedData> { +export class MessagingRelayLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [MESSAGING_RELAYS]}], diff --git a/packages/client/src/mutes.ts b/packages/client/src/plugins/mutes.ts similarity index 92% rename from packages/client/src/mutes.ts rename to packages/client/src/plugins/mutes.ts index 0909d29..2dc54b0 100644 --- a/packages/client/src/mutes.ts +++ b/packages/client/src/plugins/mutes.ts @@ -9,18 +9,18 @@ import { updateList, } from "@welshman/util" import type {TrustedEvent, PublishedList} from "@welshman/util" -import {DerivedData} from "./clientData.js" -import type {IClient} from "./client.js" +import {DerivedPlugin} from "./base.js" +import type {IClient} from "../client.js" import {Network} from "./network.js" import {Thunks} from "./thunk.js" import {Plaintext} from "./plaintext.js" -import {User} from "./user.js" +import {User} from "../user.js" /** * Kind-10000 mute lists, keyed by pubkey. Mute lists carry private entries in * encrypted content, so decoding goes through the plaintext cache. */ -export class MuteLists extends DerivedData { +export class MuteLists extends DerivedPlugin { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [MUTES]}], diff --git a/packages/client/src/network.ts b/packages/client/src/plugins/network.ts similarity index 98% rename from packages/client/src/network.ts rename to packages/client/src/plugins/network.ts index 1351200..1debaca 100644 --- a/packages/client/src/network.ts +++ b/packages/client/src/plugins/network.ts @@ -13,7 +13,7 @@ import type { } from "@welshman/net" import {Router, addMinimalFallbacks} from "./router.js" import {RelayLists} from "./relayLists.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Net utilities bound to the client's net context (its pool + repository). Reach diff --git a/packages/client/src/pins.ts b/packages/client/src/plugins/pins.ts similarity index 87% rename from packages/client/src/pins.ts rename to packages/client/src/plugins/pins.ts index a2cfcb9..b2234d0 100644 --- a/packages/client/src/pins.ts +++ b/packages/client/src/plugins/pins.ts @@ -7,17 +7,17 @@ import { removeFromList, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" import {Thunks} from "./thunk.js" -import {User} from "./user.js" -import type {IClient} from "./client.js" +import {User} from "../user.js" +import type {IClient} from "../client.js" /** * NIP-51 pin lists (kind 10001), keyed by pubkey. Loaded via the outbox model * (the author's write relays), so it depends on the relay-list collection. */ -export class PinLists extends DerivedData> { +export class PinLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [PINS]}], diff --git a/packages/client/src/plaintext.ts b/packages/client/src/plugins/plaintext.ts similarity index 87% rename from packages/client/src/plaintext.ts rename to packages/client/src/plugins/plaintext.ts index 8442407..e984d21 100644 --- a/packages/client/src/plaintext.ts +++ b/packages/client/src/plugins/plaintext.ts @@ -1,12 +1,12 @@ import {decrypt} from "@welshman/signer" import type {Maybe} from "@welshman/lib" import type {TrustedEvent} from "@welshman/util" -import {ClientData} from "./clientData.js" +import {MapPlugin} from "./base.js" /** * A cache of decrypted event content, keyed by event id. */ -export class Plaintext extends ClientData { +export class Plaintext extends MapPlugin { ensure = async (event: TrustedEvent): Promise> => { if (this.ctx.user?.pubkey !== event.pubkey) return diff --git a/packages/client/src/profiles.ts b/packages/client/src/plugins/profiles.ts similarity index 87% rename from packages/client/src/profiles.ts rename to packages/client/src/plugins/profiles.ts index cd07bbf..8bc5bd3 100644 --- a/packages/client/src/profiles.ts +++ b/packages/client/src/plugins/profiles.ts @@ -10,18 +10,18 @@ import { } from "@welshman/util" import type {Profile} from "@welshman/util" import type {Maybe} from "@welshman/lib" -import {DerivedData, projection} from "./clientData.js" -import type {Projection} from "./clientData.js" +import {DerivedPlugin, projection} from "./base.js" +import type {Projection} from "./base.js" import {Network} from "./network.js" import {Router} from "./router.js" import {Thunks} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Kind-0 profiles, keyed by pubkey. Loaded via the outbox model (the author's * write relays), resolved through the relay-list collection at fetch time. */ -export class Profiles extends DerivedData> { +export class Profiles extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [PROFILE]}], diff --git a/packages/client/src/relayLists.ts b/packages/client/src/plugins/relayLists.ts similarity index 96% rename from packages/client/src/relayLists.ts rename to packages/client/src/plugins/relayLists.ts index 9595498..0c253f4 100644 --- a/packages/client/src/relayLists.ts +++ b/packages/client/src/plugins/relayLists.ts @@ -12,18 +12,18 @@ import { makeEvent, } from "@welshman/util" import type {TrustedEvent, PublishedList} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Router, addMinimalFallbacks} from "./router.js" import {Network} from "./network.js" -import {User} from "./user.js" +import {User} from "../user.js" import {Thunks} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * NIP-65 relay lists, keyed by pubkey. This is the routing substrate every other * outbox-model load depends on (see `Network.loadUsingOutbox`). */ -export class RelayLists extends DerivedData { +export class RelayLists extends DerivedPlugin { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [RELAYS]}], diff --git a/packages/client/src/relayManagement.ts b/packages/client/src/plugins/relayManagement.ts similarity index 89% rename from packages/client/src/relayManagement.ts rename to packages/client/src/plugins/relayManagement.ts index be298c5..e639618 100644 --- a/packages/client/src/relayManagement.ts +++ b/packages/client/src/plugins/relayManagement.ts @@ -1,7 +1,7 @@ import {makeHttpAuth, sendManagementRequest} from "@welshman/util" import type {ManagementRequest} from "@welshman/util" -import {User} from "./user.js" -import type {IClient} from "./client.js" +import {User} from "../user.js" +import type {IClient} from "../client.js" /** * NIP-86 relay management. Signs an HTTP-auth event as the client's user and diff --git a/packages/client/src/relayStats.ts b/packages/client/src/plugins/relayStats.ts similarity index 98% rename from packages/client/src/relayStats.ts rename to packages/client/src/plugins/relayStats.ts index 06db7f6..d6481df 100644 --- a/packages/client/src/relayStats.ts +++ b/packages/client/src/plugins/relayStats.ts @@ -2,7 +2,7 @@ import {groupBy, batch, now, uniq, ago, DAY, HOUR, MINUTE} from "@welshman/lib" import {isOnionUrl, isLocalUrl, isIPAddress, isRelayUrl} from "@welshman/util" import {SocketStatus, SocketEvent} from "@welshman/net" import type {ClientMessage, RelayMessage, Socket} from "@welshman/net" -import {ClientData} from "./clientData.js" +import {MapPlugin} from "./base.js" import {BlockedRelayLists} from "./blockedRelayLists.js" export type RelayStatsUpdate = [string, (stats: RelayStatsItem) => void] @@ -56,7 +56,7 @@ export const makeRelayStatsItem = (url: string): RelayStatsItem => ({ * the router uses to rank relays. A pure store — the socket wiring that fills it * lives in `clientPolicyRelayStats`. */ -export class RelayStats extends ClientData { +export class RelayStats extends MapPlugin { getQuality = (url: string) => { // Skip non-relays entirely if (!isRelayUrl(url)) return 0 diff --git a/packages/client/src/relays.ts b/packages/client/src/plugins/relays.ts similarity index 66% rename from packages/client/src/relays.ts rename to packages/client/src/plugins/relays.ts index a2a7a35..b1690a5 100644 --- a/packages/client/src/relays.ts +++ b/packages/client/src/plugins/relays.ts @@ -3,14 +3,14 @@ import {fetchJson} from "@welshman/lib" import type {Maybe} from "@welshman/lib" import {displayRelayUrl, displayRelayProfile} from "@welshman/util" import type {RelayProfile} from "@welshman/util" -import {LoadableData, projection} from "./clientData.js" -import type {Projection} from "./clientData.js" +import {LoadableMapPlugin, projection} from "./base.js" +import type {Projection} from "./base.js" /** * NIP-11 relay profiles, keyed by url. A "local" loadable collection: items * aren't nostr events, they're fetched over HTTP from each relay. */ -export class Relays extends LoadableData { +export class Relays extends LoadableMapPlugin { fetch = async (url: string): Promise> => { try { const json = await fetchJson(url.replace(/^ws/, "http"), { @@ -42,4 +42,17 @@ export class Relays extends LoadableData { return projection(derived(this.one(url), read), () => read(this.get(url))) } + + hasNegentropy = async (url: string) => { + const relay = await this.load(url) + + if (relay?.negentropy) return true + if (relay?.supported_nips?.includes("77")) return true + if (relay?.software?.includes?.("strfry") && !relay?.version?.match(/^0\./)) return true + + return false + } + + hasNip = async (url: string, nip: number | string) => + (await this.load(url))?.supported_nips?.includes(String(nip)) ?? false } diff --git a/packages/client/src/rooms.ts b/packages/client/src/plugins/rooms.ts similarity index 96% rename from packages/client/src/rooms.ts rename to packages/client/src/plugins/rooms.ts index be6d5ed..4a542c2 100644 --- a/packages/client/src/rooms.ts +++ b/packages/client/src/plugins/rooms.ts @@ -10,7 +10,7 @@ import { import type {RoomMeta} from "@welshman/util" import {Thunks} from "./thunk.js" import type {ThunkOptions} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * NIP-29 relay-based group (room) management. Each method publishes the relevant diff --git a/packages/client/src/router.ts b/packages/client/src/plugins/router.ts similarity index 96% rename from packages/client/src/router.ts rename to packages/client/src/plugins/router.ts index beb8e09..4220e55 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/plugins/router.ts @@ -1,7 +1,7 @@ import {Router as BaseRouter} from "@welshman/router" import {RelayLists} from "./relayLists.js" import {RelayStats} from "./relayStats.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" // Re-export the upstream router surface (scenarios, fallback policies, // makeSelection, getFilterSelections, types). The local `Router` below shadows diff --git a/packages/client/src/search.ts b/packages/client/src/plugins/search.ts similarity index 98% rename from packages/client/src/search.ts rename to packages/client/src/plugins/search.ts index 7f5535b..3bad04e 100644 --- a/packages/client/src/search.ts +++ b/packages/client/src/plugins/search.ts @@ -7,7 +7,7 @@ import {dec, inc, sortBy} from "@welshman/lib" import {PROFILE} from "@welshman/util" import type {PublishedProfile, RelayProfile} from "@welshman/util" import {throttled} from "@welshman/store" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" import {Network} from "./network.js" import {Router} from "./router.js" import {Profiles} from "./profiles.js" diff --git a/packages/client/src/searchRelayLists.ts b/packages/client/src/plugins/searchRelayLists.ts similarity index 89% rename from packages/client/src/searchRelayLists.ts rename to packages/client/src/plugins/searchRelayLists.ts index de0846c..df278c1 100644 --- a/packages/client/src/searchRelayLists.ts +++ b/packages/client/src/plugins/searchRelayLists.ts @@ -8,19 +8,19 @@ import { removeFromList, } from "@welshman/util" import type {TrustedEvent} from "@welshman/util" -import {DerivedData} from "./clientData.js" +import {DerivedPlugin} from "./base.js" import {Network} from "./network.js" import {Router} from "./router.js" -import {User} from "./user.js" +import {User} from "../user.js" import {Thunks} from "./thunk.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * NIP-51 search relay lists (kind 10007), keyed by pubkey. Loaded via the * outbox model (the author's write relays), so it depends on the relay-list * collection. */ -export class SearchRelayLists extends DerivedData> { +export class SearchRelayLists extends DerivedPlugin> { constructor(ctx: IClient) { super(ctx, { filters: [{kinds: [SEARCH_RELAYS]}], diff --git a/packages/client/src/stores.ts b/packages/client/src/plugins/stores.ts similarity index 98% rename from packages/client/src/stores.ts rename to packages/client/src/plugins/stores.ts index 279b220..7d861f7 100644 --- a/packages/client/src/stores.ts +++ b/packages/client/src/plugins/stores.ts @@ -18,7 +18,7 @@ import type { ItemsByKeyOptions, } from "@welshman/store" import type {TrustedEvent} from "@welshman/util" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Store/derivation utilities bound to the client's repository and tracker. Reach diff --git a/packages/client/src/sync.ts b/packages/client/src/plugins/sync.ts similarity index 62% rename from packages/client/src/sync.ts rename to packages/client/src/plugins/sync.ts index 2154bf7..4edb5dd 100644 --- a/packages/client/src/sync.ts +++ b/packages/client/src/plugins/sync.ts @@ -1,6 +1,6 @@ import {isSignedEvent} from "@welshman/util" import type {Filter, SignedEvent} from "@welshman/util" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" import {Network} from "./network.js" import {Relays} from "./relays.js" @@ -21,25 +21,17 @@ export class Sync { query = (filters: Filter[]) => this.ctx.repository.query(filters, {shouldSort: filters.every(f => f.limit === undefined)}) - hasNegentropy = (url: string) => { - const relay = this.ctx.use(Relays).get(url) - - if (relay?.negentropy) return true - if (relay?.supported_nips?.includes?.("77")) return true - if (relay?.software?.includes?.("strfry") && !relay?.version?.match(/^0\./)) return true - - return false - } - pull = async ({relays, filters}: AppSyncOpts) => { const net = this.ctx.use(Network) const events = this.query(filters).filter(isSignedEvent) await Promise.all( relays.map(async relay => { - await (this.hasNegentropy(relay) - ? net.pull({filters, events, relays: [relay]}) - : net.request({filters, relays: [relay], autoClose: true})) + if (await this.ctx.use(Relays).hasNegentropy(relay)) { + await net.pull({filters, events, relays: [relay]}) + } else { + await net.request({filters, relays: [relay], autoClose: true}) + } }), ) } @@ -50,9 +42,11 @@ export class Sync { await Promise.all( relays.map(async relay => { - await (this.hasNegentropy(relay) - ? net.push({filters, events, relays: [relay]}) - : Promise.all(events.map((event: SignedEvent) => net.publish({event, relays: [relay]})))) + if (await this.ctx.use(Relays).hasNegentropy(relay)) { + await net.push({filters, events, relays: [relay]}) + } else { + await Promise.all(events.map((event: SignedEvent) => net.publish({event, relays: [relay]}))) + } }), ) } diff --git a/packages/client/src/tags.ts b/packages/client/src/plugins/tags.ts similarity index 98% rename from packages/client/src/tags.ts rename to packages/client/src/plugins/tags.ts index c382fe3..81005d9 100644 --- a/packages/client/src/tags.ts +++ b/packages/client/src/plugins/tags.ts @@ -10,7 +10,7 @@ import { import type {TrustedEvent} from "@welshman/util" import {Router} from "./router.js" import {Profiles} from "./profiles.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" /** * Builders for nostr tags (p/e/a/q/zap/reply/comment/reaction). Needs the router diff --git a/packages/client/src/thunk.ts b/packages/client/src/plugins/thunk.ts similarity index 99% rename from packages/client/src/thunk.ts rename to packages/client/src/plugins/thunk.ts index 73ab669..d339173 100644 --- a/packages/client/src/thunk.ts +++ b/packages/client/src/plugins/thunk.ts @@ -13,10 +13,10 @@ import { } from "@welshman/util" import {PublishStatus, PublishResult, PublishOptions, PublishResultsByRelay} from "@welshman/net" import {Nip01Signer, Nip59} from "@welshman/signer" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" import {Network} from "./network.js" import {Router, addMinimalFallbacks} from "./router.js" -import {User} from "./user.js" +import {User} from "../user.js" export type ThunkOptions = Override< PublishOptions, diff --git a/packages/client/src/topics.ts b/packages/client/src/plugins/topics.ts similarity index 97% rename from packages/client/src/topics.ts rename to packages/client/src/plugins/topics.ts index eef6392..bf79bb2 100644 --- a/packages/client/src/topics.ts +++ b/packages/client/src/plugins/topics.ts @@ -4,7 +4,7 @@ import {on} from "@welshman/lib" import {getTopicTagValues} from "@welshman/util" import type {RepositoryUpdate} from "@welshman/net" import {deriveItems} from "@welshman/store" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" export type Topic = { name: string diff --git a/packages/client/src/wot.ts b/packages/client/src/plugins/wot.ts similarity index 98% rename from packages/client/src/wot.ts rename to packages/client/src/plugins/wot.ts index bf18db5..a04541e 100644 --- a/packages/client/src/wot.ts +++ b/packages/client/src/plugins/wot.ts @@ -2,9 +2,9 @@ import {readable, derived} from "svelte/store" import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib" import {getListTags, getPubkeyTagValues} from "@welshman/util" import type {List} from "@welshman/util" -import type {IClient} from "./client.js" -import {projection} from "./clientData.js" -import type {Projection} from "./clientData.js" +import type {IClient} from "../client.js" +import {projection} from "./base.js" +import type {Projection} from "./base.js" import {FollowLists} from "./follows.js" import {MuteLists} from "./mutes.js" diff --git a/packages/client/src/giftWraps.ts b/packages/client/src/plugins/wraps.ts similarity index 92% rename from packages/client/src/giftWraps.ts rename to packages/client/src/plugins/wraps.ts index 3c914f7..b35e1cb 100644 --- a/packages/client/src/giftWraps.ts +++ b/packages/client/src/plugins/wraps.ts @@ -5,9 +5,9 @@ import type {TrustedEvent, SignedEvent, EventTemplate} from "@welshman/util" import {Nip59} from "@welshman/signer" import {MergedThunk, Thunks} from "./thunk.js" import type {ThunkOptions} from "./thunk.js" -import {User} from "./user.js" +import {User} from "../user.js" import {MessagingRelayLists} from "./messagingRelayLists.js" -import type {IClient} from "./client.js" +import type {IClient} from "../client.js" export type SendWrappedOptions = Omit< ThunkOptions, @@ -18,13 +18,13 @@ export type SendWrappedOptions = Omit< } /** - * Per-client gift-wrap (NIP-59) state: the unwrap queue plus failure/dedup + * Per-client wrap (NIP-59) state: the unwrap queue plus failure/dedup * tracking. Scoped to `ctx.user`, so a client only ever unwraps its own user's * messages into its own repository — which is what keeps DM history from being * merged across identities. The repository subscription that feeds it lives in - * `clientPolicyGiftWraps`. + * `clientPolicyWraps`. */ -export class GiftWraps { +export class Wraps { failedUnwraps = new Set() queue: TaskQueue diff --git a/packages/client/src/zappers.ts b/packages/client/src/plugins/zappers.ts similarity index 96% rename from packages/client/src/zappers.ts rename to packages/client/src/plugins/zappers.ts index efee6ff..1bd353f 100644 --- a/packages/client/src/zappers.ts +++ b/packages/client/src/plugins/zappers.ts @@ -12,9 +12,9 @@ import type {Maybe} from "@welshman/lib" import {getTagValue, getZapSplits, zapFromEvent} from "@welshman/util" import type {Zapper, Zap, TrustedEvent} from "@welshman/util" import {deriveDeduplicated, deriveDeduplicatedByValue} from "@welshman/store" -import {LoadableData, projection} from "./clientData.js" -import type {Projection} from "./clientData.js" -import type {IClient} from "./client.js" +import {LoadableMapPlugin, projection} from "./base.js" +import type {Projection} from "./base.js" +import type {IClient} from "../client.js" import {Profiles} from "./profiles.js" /** @@ -23,7 +23,7 @@ import {Profiles} from "./profiles.js" * lnurl, or via a dufflepud proxy to protect user privacy). Depends on the * profiles collection to resolve a pubkey's lnurl. */ -export class Zappers extends LoadableData { +export class Zappers extends LoadableMapPlugin { fetch = batcher(800, async (lnurls: string[]) => { const result = new Map() const valid = lnurls.filter(lnurl => lnurl.startsWith("lnurl1")) diff --git a/packages/client/src/policies.ts b/packages/client/src/policy.ts similarity index 92% rename from packages/client/src/policies.ts rename to packages/client/src/policy.ts index 585c4df..804474c 100644 --- a/packages/client/src/policies.ts +++ b/packages/client/src/policy.ts @@ -5,9 +5,9 @@ import type {TrustedEvent} from "@welshman/util" import {SocketEvent, isRelayEvent, makeSocketPolicyAuth} from "@welshman/net" import type {RelayMessage, Socket} from "@welshman/net" import type {IClient} from "./client.js" -import {RelayStats} from "./relayStats.js" -import {GiftWraps} from "./giftWraps.js" -import {BlockedRelayLists} from "./blockedRelayLists.js" +import {RelayStats} from "./plugins/relayStats.js" +import {Wraps} from "./plugins/wraps.js" +import {BlockedRelayLists} from "./plugins/blockedRelayLists.js" import {LoggingSigner} from "./logging.js" import type {LogMessage} from "./logging.js" @@ -103,17 +103,17 @@ export const clientPolicyRelayStats: ClientPolicy = client => { * Watches the client's repository for gift wraps (existing and incoming) and * feeds them to the unwrap queue. */ -export const clientPolicyGiftWraps: ClientPolicy = client => { - const giftWraps = client.use(GiftWraps) +export const clientPolicyWraps: ClientPolicy = client => { + const wraps = client.use(Wraps) for (const wrap of client.repository.query([{kinds: [WRAP]}])) { - giftWraps.enqueue(wrap) + wraps.enqueue(wrap) } return on(client.repository, "update", ({added}: {added: TrustedEvent[]}) => { for (const event of added) { if (event.kind === WRAP) { - giftWraps.enqueue(event) + wraps.enqueue(event) } } }) @@ -139,6 +139,6 @@ export const makeClientPolicyLogger = export const defaultClientPolicies: ClientPolicy[] = [ clientPolicyIngest, clientPolicyRelayStats, - clientPolicyGiftWraps, + clientPolicyWraps, clientPolicyAuthUnlessBlocked, ]