UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

1,397 lines (1,380 loc) 257 kB
import { EventEmitter } from 'tseep'; import debug$1, { Debugger } from 'debug'; import { LRUCache } from 'typescript-lru-cache'; import { nip19 as nip19$1 } from 'nostr-tools'; import * as nip19 from 'nostr-tools/nip19'; import * as nip49 from 'nostr-tools/nip49'; function _mergeNamespaces(n, m) { m.forEach(function (e) { e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { if (k !== 'default' && !(k in n)) { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); }); return Object.freeze(n); } declare enum NDKKind { Metadata = 0, Text = 1, RecommendRelay = 2, Contacts = 3, EncryptedDirectMessage = 4, EventDeletion = 5, Repost = 6, Reaction = 7, BadgeAward = 8, GroupChat = 9, Thread = 11, GroupReply = 12, GiftWrapSeal = 13, PrivateDirectMessage = 14, Image = 20, Video = 21, ShortVideo = 22, Story = 23, Vanish = 62, CashuWalletBackup = 375, GiftWrap = 1059, GenericRepost = 16, ChannelCreation = 40, ChannelMetadata = 41, ChannelMessage = 42, ChannelHideMessage = 43, ChannelMuteUser = 44, WikiMergeRequest = 818, GenericReply = 1111, Media = 1063, VoiceMessage = 1222, VoiceReply = 1244, DraftCheckpoint = 1234, Task = 1934, Report = 1984, Label = 1985, DVMReqTextExtraction = 5000, DVMReqTextSummarization = 5001, DVMReqTextTranslation = 5002, DVMReqTextGeneration = 5050, DVMReqImageGeneration = 5100, DVMReqTextToSpeech = 5250, DVMReqDiscoveryNostrContent = 5300, DVMReqDiscoveryNostrPeople = 5301, DVMReqTimestamping = 5900, DVMEventSchedule = 5905, DVMJobFeedback = 7000, Subscribe = 7001, Unsubscribe = 7002, SubscriptionReceipt = 7003, CashuReserve = 7373, CashuQuote = 7374, CashuToken = 7375, CashuWalletTx = 7376, GroupAdminAddUser = 9000, GroupAdminRemoveUser = 9001, GroupAdminEditMetadata = 9002, GroupAdminEditStatus = 9006, GroupAdminCreateGroup = 9007, GroupAdminRequestJoin = 9021, MuteList = 10000, PinList = 10001, RelayList = 10002, BookmarkList = 10003, CommunityList = 10004, PublicChatList = 10005, BlockRelayList = 10006, SearchRelayList = 10007, SimpleGroupList = 10009, RelayFeedList = 10012, InterestList = 10015, CashuMintList = 10019, EmojiList = 10030, DirectMessageReceiveRelayList = 10050, BlossomList = 10063, NostrWaletConnectInfo = 13194, TierList = 17000, CashuWallet = 17375, FollowSet = 30000, CategorizedPeopleList = 30000,// Deprecated but left for backwards compatibility CategorizedBookmarkList = 30001,// Deprecated but left for backwards compatibility RelaySet = 30002, CategorizedRelayList = 30002,// Deprecated but left for backwards compatibility BookmarkSet = 30003, /** * @deprecated Use ArticleCurationSet instead */ CurationSet = 30004,// Deprecated but left for backwards compatibility ArticleCurationSet = 30004, VideoCurationSet = 30005, ImageCurationSet = 30006, InterestSet = 30015, InterestsList = 30015,// Deprecated but left for backwards compatibility ProjectTemplate = 30717, EmojiSet = 30030, ModularArticle = 30040, ModularArticleItem = 30041, Wiki = 30818, Draft = 31234, Project = 31933, SubscriptionTier = 37001, EcashMintRecommendation = 38000, CashuMintAnnouncement = 38172, FedimintMintAnnouncement = 38173, P2POrder = 38383, CollaborativeEvent = 39382, HighlightSet = 39802, CategorizedHighlightList = 39802,// Deprecated but left for backwards compatibility Nutzap = 9321, ZapRequest = 9734, Zap = 9735, Highlight = 9802, ClientAuth = 22242, NostrWalletConnectReq = 23194, NostrWalletConnectRes = 23195, NostrConnect = 24133, BlossomUpload = 24242, HttpAuth = 27235, ProfileBadge = 30008, BadgeDefinition = 30009, MarketStall = 30017, MarketProduct = 30018, Article = 30023, AppSpecificData = 30078, Classified = 30402, HorizontalVideo = 34235, VerticalVideo = 34236, GroupMetadata = 39000,// NIP-29 GroupAdmins = 39001,// NIP-29 GroupMembers = 39002,// NIP-29 FollowPack = 39089, MediaFollowPack = 39092, AppRecommendation = 31989, AppHandler = 31990 } declare const NDKListKinds: NDKKind[]; /** * NIP-45 COUNT with HyperLogLog (HLL) support * * This module provides support for COUNT requests with HLL cardinality estimation * as specified in NIP-45. * * HLL allows relays to provide approximate unique counts that can be merged * across multiple relays without double-counting. */ /** * The number of HLL registers (256 as per NIP-45) */ declare const HLL_REGISTER_COUNT = 256; /** * Represents an HLL (HyperLogLog) data structure for cardinality estimation. * Contains 256 uint8 registers as specified in NIP-45. */ declare class NDKCountHll { /** * The 256 uint8 registers used for HLL estimation */ readonly registers: Uint8Array; constructor(registers?: Uint8Array); /** * Creates an NDKCountHll from a hex-encoded string (512 characters). * Each register is a uint8 value encoded as 2 hex characters. * * @param hex - The hex string (512 characters = 256 bytes) * @returns A new NDKCountHll instance * @throws Error if the hex string is invalid */ static fromHex(hex: string): NDKCountHll; /** * Converts the HLL registers to a hex-encoded string. * * @returns The hex string representation (512 characters) */ toHex(): string; /** * Merges this HLL with another HLL by taking the maximum value for each register. * This is the standard HLL merge operation that allows combining counts * from multiple relays without double-counting. * * @param other - The other HLL to merge with * @returns A new NDKCountHll with the merged registers */ merge(other: NDKCountHll): NDKCountHll; /** * Merges multiple HLLs by taking the maximum value for each register. * * @param hlls - Array of HLLs to merge * @returns A new NDKCountHll with the merged registers */ static merge(hlls: NDKCountHll[]): NDKCountHll; /** * Estimates the cardinality (unique count) using the HyperLogLog algorithm. * * Uses the standard HLL formula with bias correction for small and large cardinalities. * * @returns The estimated unique count */ estimate(): number; /** * Checks if this HLL is empty (all registers are zero). * * @returns True if all registers are zero */ isEmpty(): boolean; /** * Creates a copy of this HLL. * * @returns A new NDKCountHll with the same register values */ clone(): NDKCountHll; } /** * Result of a COUNT request, including optional HLL data. */ interface NDKCountResult { /** * The count value returned by the relay. * This is the exact count if HLL is not available, * or an approximate count if HLL is used. */ count: number; /** * The HLL data if returned by the relay. * Can be used to merge counts from multiple relays. */ hll?: NDKCountHll; } /** * Aggregated result from multiple relays for a COUNT request. */ interface NDKAggregatedCountResult { /** * The best estimate of the count. * If HLL data is available from multiple relays, this is computed from the merged HLL. * Otherwise, it's the maximum count from all relays. */ count: number; /** * The merged HLL from all relays that returned HLL data. * Can be used for further aggregation or analysis. */ mergedHll?: NDKCountHll; /** * Individual results from each relay. */ relayResults: Map<string, NDKCountResult>; } /** * Options for count requests. */ interface NDKCountOptions { /** * Custom ID for the count request. */ id?: string; /** * Timeout in milliseconds for the count request. * @default 5000 */ timeout?: number; } /** * NDKUserProfile represents a user's kind 0 profile metadata */ interface NDKUserProfile { [key: string]: string | number | undefined; created_at?: number; name?: string; displayName?: string; /** * @deprecated Use picture instead */ image?: string; picture?: string; banner?: string; bio?: string; nip05?: string; lud06?: string; lud16?: string; about?: string; website?: string; profileEvent?: string; } declare function profileFromEvent(event: NDKEvent): NDKUserProfile; declare function serializeProfile(profile: NDKUserProfile): string; type Hexpubkey = string; type Npub = string; type ProfilePointer = { pubkey: string; relays?: string[]; nip46?: string[]; }; type EventPointer = { id: string; relays?: string[]; author?: string; kind?: number; }; interface NDKUserParams { npub?: Npub; hexpubkey?: Hexpubkey; pubkey?: Hexpubkey; nip05?: string; relayUrls?: string[]; nip46Urls?: string[]; nprofile?: string; } /** * Represents a pubkey. */ declare class NDKUser { ndk: NDK | undefined; profile?: NDKUserProfile; profileEvent?: NDKEvent; private _npub?; private _pubkey?; relayUrls: string[]; readonly nip46Urls: string[]; constructor(opts: NDKUserParams); get npub(): string; get nprofile(): string; set npub(npub: Npub); /** * Get the user's pubkey * @returns {string} The user's pubkey */ get pubkey(): string; /** * Set the user's pubkey * @param pubkey {string} The user's pubkey */ set pubkey(pubkey: string); /** * Equivalent to NDKEvent.filters(). * @returns {NDKFilter} */ filter(): NDKFilter; /** * Gets NIP-57 and NIP-61 information that this user has signaled * * @param getAll {boolean} Whether to get all zap info or just the first one */ getZapInfo(timeoutMs?: number): Promise<Map<NDKZapMethod, NDKZapMethodInfo>>; /** * Instantiate an NDKUser from a NIP-05 string * @param nip05Id {string} The user's NIP-05 * @param ndk {NDK} An NDK instance * @param skipCache {boolean} Whether to skip the cache or not * @returns {NDKUser | undefined} An NDKUser if one is found for the given NIP-05, undefined otherwise. */ static fromNip05(nip05Id: string, ndk: NDK, skipCache?: boolean): Promise<NDKUser | undefined>; /** * Fetch a user's profile * @param opts {NDKSubscriptionOptions} A set of NDKSubscriptionOptions * @param storeProfileEvent {boolean} Whether to store the profile event or not * @returns User Profile */ fetchProfile(opts?: NDKSubscriptionOptions, storeProfileEvent?: boolean): Promise<NDKUserProfile | null>; /** * Returns a set of users that this user follows. * * @deprecated Use followSet instead */ follows: (opts?: NDKSubscriptionOptions | undefined, outbox?: boolean | undefined, kind?: number | undefined) => Promise<Set<NDKUser>>; /** * Returns a set of pubkeys that this user follows. * * @param opts - NDKSubscriptionOptions * @param outbox - boolean * @param kind - number */ followSet(opts?: NDKSubscriptionOptions, outbox?: boolean, kind?: number): Promise<Set<Hexpubkey>>; /** @deprecated Use referenceTags instead. */ /** * Get the tag that can be used to reference this user in an event * @returns {NDKTag} an NDKTag */ tagReference(): NDKTag; /** * Get the tags that can be used to reference this user in an event * @returns {NDKTag[]} an array of NDKTag */ referenceTags(marker?: string): NDKTag[]; /** * Publishes the current profile. */ publish(): Promise<void>; /** * Add one or more follows to this user's contact list * * @param newFollow {NDKUser | Hexpubkey | Array} The user(s) to follow * @param currentFollowList {Set<NDKUser | Hexpubkey>} The current follow list * @param kind {NDKKind} The kind to use for this contact list (defaults to `3`) * @returns {Promise<boolean>} True if any follows were added, false if all already exist */ follow(newFollow: NDKUser | Hexpubkey | (NDKUser | Hexpubkey)[], currentFollowList?: Set<NDKUser | Hexpubkey>, kind?: NDKKind): Promise<boolean>; /** * Remove one or more follows from this user's contact list * * @param user {NDKUser | Hexpubkey | Array} The user(s) to unfollow * @param currentFollowList {Set<NDKUser | Hexpubkey>} The current follow list * @param kind {NDKKind} The kind to use for this contact list (defaults to `3`) * @returns The relays where the follow list was published or false if none were found */ unfollow(user: NDKUser | Hexpubkey | (NDKUser | Hexpubkey)[], currentFollowList?: Set<NDKUser | Hexpubkey>, kind?: NDKKind): Promise<Set<NDKRelay> | boolean>; /** * Validate a user's NIP-05 identifier (usually fetched from their kind:0 profile data) * * @param nip05Id The NIP-05 string to validate * @returns {Promise<boolean | null>} True if the NIP-05 is found and matches this user's pubkey, * False if the NIP-05 is found but doesn't match this user's pubkey, * null if the NIP-05 isn't found on the domain or we're unable to verify (because of network issues, etc.) */ validateNip05(nip05Id: string): Promise<boolean | null>; } /** * Interface for NDK signers. */ /** * Interface for a serialized signer payload */ interface NDKSignerPayload { type: string; payload: string; } interface NDKSigner { /** * Synchronously get the public key of the signer. * @throws {Error} "Not ready" when the signer is not ready to provide a pubkey synchronously (e.g., NIP-07 or NIP-46 signers) * @returns The public key in hex format */ get pubkey(): string; /** * Blocks until the signer is ready and returns the associated NDKUser. * @returns A promise that resolves to the NDKUser instance. */ blockUntilReady(): Promise<NDKUser>; /** * Getter for the user property. * @returns A promise that resolves to the NDKUser instance. */ user(): Promise<NDKUser>; get userSync(): NDKUser; /** * Signs the given Nostr event. * @param event - The Nostr event to be signed. * @returns A promise that resolves to the signature of the signed event. */ sign(event: NostrEvent): Promise<string>; /** * Getter for the preferred relays. * @returns A promise containing a simple map of preferred relays and their read/write policies. */ relays?(ndk?: NDK): Promise<NDKRelay[]>; /** * Determine the types of encryption (by nip) that this signer can perform. * Implementing classes SHOULD return a value even for legacy (only nip04) third party signers. * @nip Optionally returns an array with single supported nip or empty, to check for truthy or falsy. * @return A promised list of any (or none) of these strings ['nip04', 'nip44'] */ encryptionEnabled?(scheme?: NDKEncryptionScheme): Promise<NDKEncryptionScheme[]>; /** * Encrypts the given Nostr event for the given recipient. * Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined * @param recipient - The recipient (pubkey or conversationKey) of the encrypted value. * @param value - The value to be encrypted. * @param nip - which NIP is being implemented ('nip04', 'nip44') */ encrypt(recipient: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string>; /** * Decrypts the given value. * Implementing classes SHOULD equate legacy (only nip04) to nip == `nip04` || undefined * @param sender - The sender (pubkey or conversationKey) of the encrypted value * @param value - The value to be decrypted * @param scheme - which NIP is being implemented ('nip04', 'nip44', 'nip49') */ decrypt(sender: NDKUser, value: string, scheme?: NDKEncryptionScheme): Promise<string>; /** * Serializes the signer's essential data into a storable format. * @returns A JSON string containing the type and payload. */ toPayload(): string; } /** * Interface for Signer classes that support static deserialization */ interface NDKSignerStatic<T extends NDKSigner> { /** * Deserializes the signer from a payload string. * @param payload The JSON string obtained from toPayload(). * @param ndk Optional NDK instance, required by some signers (e.g., NIP-46). * @returns An instance of the specific signer class. */ fromPayload(payload: string, ndk?: NDK): Promise<T>; } type NDKPoolStats = { total: number; connected: number; disconnected: number; connecting: number; }; /** * Handles connections to all relays. A single pool should be used per NDK instance. * * @emit connecting - Emitted when a relay in the pool is connecting. * @emit connect - Emitted when all relays in the pool are connected, or when the specified timeout has elapsed, and some relays are connected. * @emit notice - Emitted when a relay in the pool sends a notice. * @emit flapping - Emitted when a relay in the pool is flapping. * @emit relay:connect - Emitted when a relay in the pool connects. * @emit relay:ready - Emitted when a relay in the pool is ready to serve requests. * @emit relay:disconnect - Emitted when a relay in the pool disconnects. */ declare class NDKPool extends EventEmitter<{ notice: (relay: NDKRelay, notice: string) => void; flapping: (relay: NDKRelay) => void; connect: () => void; "relay:connecting": (relay: NDKRelay) => void; /** * Emitted when a relay in the pool connects. * @param relay - The relay that connected. */ "relay:connect": (relay: NDKRelay) => void; "relay:ready": (relay: NDKRelay) => void; "relay:disconnect": (relay: NDKRelay) => void; "relay:auth": (relay: NDKRelay, challenge: string) => void; "relay:authed": (relay: NDKRelay) => void; }> { private _relays; private status; autoConnectRelays: Set<string>; private debug; private temporaryRelayTimers; private flappingRelays; private backoffTimes; private ndk; private disconnectionTimes; private systemEventDetector?; /** * @param relayUrls - The URLs of the relays to connect to. * @param ndk - The NDK instance. * @param opts - Options for the pool. */ constructor(relayUrls: WebSocket["url"][], ndk: NDK, { debug, name, }?: { debug?: debug$1.Debugger; name?: string; }); get relays(): Map<string, NDKRelay>; set relayUrls(urls: WebSocket["url"][]); private _name; get name(): string; set name(name: string); /** * Adds a relay to the pool, and sets a timer to remove it if it is not used within the specified time. * @param relay - The relay to add to the pool. * @param removeIfUnusedAfter - The time in milliseconds to wait before removing the relay from the pool after it is no longer used. */ useTemporaryRelay(relay: NDKRelay, removeIfUnusedAfter?: number, filters?: NDKFilter[] | string): void; /** * Adds a relay to the pool. * * @param relay - The relay to add to the pool. * @param connect - Whether or not to connect to the relay. */ addRelay(relay: NDKRelay, connect?: boolean): void; /** * Removes a relay from the pool. * @param relayUrl - The URL of the relay to remove. * @returns {boolean} True if the relay was removed, false if it was not found. */ removeRelay(relayUrl: string): boolean; /** * Checks whether a relay is already connected in the pool. */ isRelayConnected(url: WebSocket["url"]): boolean; /** * Fetches a relay from the pool, or creates a new one if it does not exist. * * New relays will be attempted to be connected. */ getRelay(url: WebSocket["url"], connect?: boolean, temporary?: boolean, filters?: NDKFilter[]): NDKRelay; private handleRelayConnect; private handleRelayReady; /** * Attempts to establish a connection to each relay in the pool. * * @async * @param {number} [timeoutMs] - Optional timeout in milliseconds for each connection attempt. * @returns {Promise<void>} A promise that resolves when all connection attempts have completed. * @throws {Error} If any of the connection attempts result in an error or timeout. */ connect(timeoutMs?: number): Promise<void>; private checkOnFlappingRelays; /** * Records when a relay disconnects to detect system-wide events */ private recordDisconnection; /** * Checks if multiple relays disconnected simultaneously, indicating a system event */ private checkForSystemWideDisconnection; /** * Handles system-wide reconnection (e.g., after sleep/wake or network change) */ private handleSystemWideReconnection; private handleFlapping; size(): number; /** * Returns the status of each relay in the pool. * @returns {NDKPoolStats} An object containing the number of relays in each status. */ stats(): NDKPoolStats; connectedRelays(): NDKRelay[]; permanentAndConnectedRelays(): NDKRelay[]; /** * Get a list of all relay urls in the pool. */ urls(): string[]; } /** * NDKAuthPolicies are functions that are called when a relay requests authentication * so that you can define a behavior for your application. * * @param relay The relay that requested authentication. * @param challenge The challenge that the relay sent. */ type NDKAuthPolicy = (relay: NDKRelay, challenge: string) => Promise<boolean | undefined | NDKEvent>; /** * This policy will disconnect from relays that request authentication. */ declare function disconnect(pool: NDKPool, debug?: debug.Debugger): (relay: NDKRelay) => Promise<void>; type ISignIn = { ndk?: NDK; signer?: NDKSigner; debug?: debug.Debugger; }; /** * Uses the signer to sign an event and then authenticate with the relay. * If no signer is provided the NDK signer will be used. * If none is not available it will wait for one to be ready. */ declare function signIn({ ndk, signer, debug }?: ISignIn): (relay: NDKRelay, challenge: string) => Promise<NDKEvent>; declare const NDKRelayAuthPolicies: { disconnect: typeof disconnect; signIn: typeof signIn; }; type NDKFilterFingerprint = string; /** * Creates a fingerprint for this filter * * This a deterministic association of the filters * used in a filters. When the combination of filters makes it * possible to group them, the fingerprint is used to group them. * * The different filters in the array are differentiated so that * filters can only be grouped with other filters that have the same signature * * The calculated group ID uses a + prefix to avoid grouping subscriptions * that intend to close immediately after EOSE and those that are probably * going to be kept open. * * @returns The fingerprint, or undefined if the filters are not groupable. */ declare function filterFingerprint(filters: NDKFilter[], closeOnEose: boolean): NDKFilterFingerprint | undefined; /** * Go through all the passed filters, which should be * relatively similar, and merge them. */ declare function mergeFilters(filters: NDKFilter[]): NDKFilter[]; type NDKSubscriptionId = string; /** * This class monitors active subscriptions. */ declare class NDKSubscriptionManager { subscriptions: Map<NDKSubscriptionId, NDKSubscription>; seenEvents: LRUCache<string, NDKRelay[]>; constructor(); add(sub: NDKSubscription): void; seenEvent(eventId: NDKEventId, relay: NDKRelay): void; /** * Whenever an event comes in, this function is called. * This function matches the received event against all the * known (i.e. active) NDKSubscriptions, and if it matches, * it sends the event to the subscription. * * This is the single place in the codebase that matches * incoming events with parties interested in the event. * * This is also what allows for reactivity in NDK apps, such that * whenever an active subscription receives an event that some * other active subscription would want to receive, both receive it. * * TODO This also allows for subscriptions that overlap in meaning * to be collapsed into one. * * I.e. if a subscription with filter: kinds: [1], authors: [alice] * is created and EOSEs, and then a subsequent subscription with * kinds: [1], authors: [alice] is created, once the second subscription * EOSEs we can safely close it, increment its refCount and close it, * and when the first subscription receives a new event from Alice this * code will make the second subscription receive the event even though * it has no active subscription on a relay. * @param event Raw event received from a relay * @param relay Relay that sent the event * @param optimisticPublish Whether the event is coming from an optimistic publish */ dispatchEvent(event: NostrEvent, relay?: NDKRelay, optimisticPublish?: boolean): void; } type Item = { subscription: NDKSubscription; filters: NDKFilter[]; }; declare enum NDKRelaySubscriptionStatus { INITIAL = 0, /** * The subscription is pending execution. */ PENDING = 1, /** * The subscription is waiting for the relay to be ready. */ WAITING = 2, /** * The subscription is currently running. */ RUNNING = 3, CLOSED = 4 } /** * Groups together a number of NDKSubscriptions (as created by the user), * filters (as computed internally), executed, or to be executed, within * a single specific relay. */ declare class NDKRelaySubscription { fingerprint: NDKFilterFingerprint; items: Map<NDKSubscriptionInternalId, Item>; topSubManager: NDKSubscriptionManager; debug: debug.Debugger; /** * Tracks the status of this REQ. */ status: NDKRelaySubscriptionStatus; onClose?: (sub: NDKRelaySubscription) => void; private relay; /** * Whether this subscription has reached EOSE. */ private eosed; /** * Timeout at which this subscription will * start executing. */ private executionTimer?; /** * Track the time at which this subscription will fire. */ private fireTime?; /** * The delay type that the current fireTime was calculated with. */ private delayType?; /** * The filters that have been executed. */ executeFilters?: NDKFilter[]; readonly id: string; /** * * @param fingerprint The fingerprint of this subscription. */ constructor(relay: NDKRelay, fingerprint: NDKFilterFingerprint | null, topSubManager: NDKSubscriptionManager); private _subId?; get subId(): string; private subIdParts; private addSubIdPart; addItem(subscription: NDKSubscription, filters: NDKFilter[]): void; /** * A subscription has been closed, remove it from the list of items. * @param subscription */ removeItem(subscription: NDKSubscription): void; private close; cleanup(): void; private evaluateExecutionPlan; private schedule; private executeOnRelayReady; private finalizeSubId; private reExecuteAfterAuth; private execute; onstart(): void; onevent(event: NostrEvent): void; oneose(subId: string): void; onclose(_reason?: string): void; onclosed(reason?: string): void; /** * Grabs the filters from all the subscriptions * and merges them into a single filter. */ private compileFilters; } declare class NDKRelayConnectivity { private ndkRelay; private ws?; private _status; private timeoutMs?; private connectedAt?; private _connectionStats; private debug; netDebug?: NDKNetDebug; private connectTimeout; private reconnectTimeout; private ndk?; openSubs: Map<string, NDKRelaySubscription>; private openCountRequests; private openEventPublishes; private pendingAuthPublishes; private serial; baseEoseTimeout: number; private keepalive?; private wsStateMonitor?; private sleepDetector?; private lastSleepCheck; private lastMessageSent; private wasIdle; constructor(ndkRelay: NDKRelay, ndk?: NDK); /** * Sets up keepalive, WebSocket state monitoring, and sleep detection */ private setupMonitoring; /** * Handles detection of a stale connection by cleaning up and triggering reconnection. */ private handleStaleConnection; /** * Handles possible system wake event */ private handlePossibleWake; /** * Resets the reconnection state for system-wide events * Used by NDKPool when detecting system sleep/wake */ resetReconnectionState(): void; /** * Connects to the NDK relay and handles the connection lifecycle. * * This method attempts to establish a WebSocket connection to the NDK relay specified in the `ndkRelay` object. * If the connection is successful, it updates the connection statistics, sets the connection status to `CONNECTED`, * and emits `connect` and `ready` events on the `ndkRelay` object. * * If the connection attempt fails, it handles the error by either initiating a reconnection attempt or emitting a * `delayed-connect` event on the `ndkRelay` object, depending on the `reconnect` parameter. * * @param timeoutMs - The timeout in milliseconds for the connection attempt. If not provided, the default timeout from the `ndkRelay` object is used. * @param reconnect - Indicates whether a reconnection should be attempted if the connection fails. Defaults to `true`. * @returns A Promise that resolves when the connection is established, or rejects if the connection fails. */ connect(timeoutMs?: number, reconnect?: boolean): Promise<void>; /** * Disconnects the WebSocket connection to the NDK relay. * This method sets the connection status to `NDKRelayStatus.DISCONNECTING`, * attempts to close the WebSocket connection, and sets the status to * `NDKRelayStatus.DISCONNECTED` if the disconnect operation fails. */ disconnect(): void; /** * Handles the error that occurred when attempting to connect to the NDK relay. * If `reconnect` is `true`, this method will initiate a reconnection attempt. * Otherwise, it will emit a `delayed-connect` event on the `ndkRelay` object, * indicating that a reconnection should be attempted after a delay. * * @param reconnect - Indicates whether a reconnection should be attempted. */ onConnectionError(reconnect: boolean): void; /** * Handles the connection event when the WebSocket connection is established. * This method is called when the WebSocket connection is successfully opened. * It clears any existing connection and reconnection timeouts, updates the connection statistics, * sets the connection status to `CONNECTED`, and emits `connect` and `ready` events on the `ndkRelay` object. */ private onConnect; /** * Handles the disconnection event when the WebSocket connection is closed. * This method is called when the WebSocket connection is successfully closed. * It updates the connection statistics, sets the connection status to `DISCONNECTED`, * initiates a reconnection attempt if we didn't disconnect ourselves, * and emits a `disconnect` event on the `ndkRelay` object. */ private onDisconnect; /** * Handles incoming messages from the NDK relay WebSocket connection. * This method is called whenever a message is received from the relay. * It parses the message data and dispatches the appropriate handling logic based on the message type. * * @param event - The MessageEvent containing the received message data. */ private onMessage; /** * Handles an authentication request from the NDK relay. * * If an authentication policy is configured, it will be used to authenticate the connection. * Otherwise, the `auth` event will be emitted to allow the application to handle the authentication. * * @param challenge - The authentication challenge provided by the NDK relay. */ private onAuthRequested; /** * Handles errors that occur on the WebSocket connection to the relay. * @param error - The error or event that occurred. */ private onError; /** * Gets the current status of the NDK relay connection. * @returns {NDKRelayStatus} The current status of the NDK relay connection. */ get status(): NDKRelayStatus; /** * Checks if the NDK relay connection is currently available. * @returns {boolean} `true` if the relay connection is in the `CONNECTED` status, `false` otherwise. */ isAvailable(): boolean; /** * Checks if the NDK relay connection is flapping, which means the connection is rapidly * disconnecting and reconnecting. This is determined by analyzing the durations of the * last three connection attempts. If the standard deviation of the durations is less * than 1000 milliseconds, the connection is considered to be flapping. * * @returns {boolean} `true` if the connection is flapping, `false` otherwise. */ private isFlapping; /** * Handles a notice received from the NDK relay. * If the notice indicates the relay is complaining (e.g. "too many" or "maximum"), * the method disconnects from the relay and attempts to reconnect after a 2-second delay. * A debug message is logged with the relay URL and the notice text. * The "notice" event is emitted on the ndkRelay instance with the notice text. * * @param notice - The notice text received from the NDK relay. */ private onNotice; /** * Attempts to reconnect to the NDK relay after a connection is lost. * This function is called recursively to handle multiple reconnection attempts. * It checks if the relay is flapping and emits a "flapping" event if so. * It then calculates a delay before the next reconnection attempt based on the number of previous attempts. * The function sets a timeout to execute the next reconnection attempt after the calculated delay. * If the maximum number of reconnection attempts is reached, a debug message is logged. * * @param attempt - The current attempt number (default is 0). */ private handleReconnection; /** * Sends a message to the NDK relay if the connection is in the CONNECTED state and the WebSocket is open. * If the connection is not in the CONNECTED state or the WebSocket is not open, logs a debug message and throws an error. * * @param message - The message to send to the NDK relay. * @throws {Error} If attempting to send on a closed relay connection. */ send(message: string): Promise<void>; /** * Authenticates the NDK event by sending it to the NDK relay and returning a promise that resolves with the result. * * @param event - The NDK event to authenticate. * @returns A promise that resolves with the authentication result. */ private auth; /** * Clears all pending publish promises by rejecting them with the provided error. * This is called on disconnection to prevent memory leaks and ensure promises * don't hang indefinitely. * @param error The error to reject the promises with */ private clearPendingPublishes; /** * Retries all pending publishes that failed due to auth-required. * Called after successful authentication. */ private retryPendingAuthPublishes; /** * Rejects all pending publishes that failed due to auth-required. * Called when authentication fails. */ private rejectPendingAuthPublishes; /** * Publishes an NDK event to the relay and returns a promise that resolves with the result. * * @param event - The NDK event to publish. * @returns A promise that resolves with the result of the event publication. * @throws {Error} If attempting to publish on a closed relay connection. */ publish(event: NostrEvent): Promise<string>; /** * Counts the number of events that match the provided filters. * * @param filters - The filters to apply to the count request. * @param params - An optional object containing a custom id for the count request. * @returns A promise that resolves with the count result including optional HLL data. * @throws {Error} If attempting to send the count request on a closed relay connection. */ count(filters: NDKFilter[], params: { id?: string | null; }): Promise<NDKCountResult>; close(subId: string, reason?: string): void; /** * Subscribes to the NDK relay with the provided filters and parameters. * * @param filters - The filters to apply to the subscription. * @param params - The subscription parameters, including an optional custom id. * @returns A new NDKRelaySubscription instance. */ req(relaySub: NDKRelaySubscription): void; /** * Utility functions to update the connection stats. */ private updateConnectionStats; /** Returns the connection stats. */ get connectionStats(): NDKRelayConnectionStats; /** Returns the relay URL */ get url(): WebSocket["url"]; get connected(): boolean; } /** * NIP-11: Relay Information Document * https://github.com/nostr-protocol/nips/blob/master/11.md */ interface NDKRelayInformation { name?: string; description?: string; banner?: string; icon?: string; pubkey?: string; contact?: string; supported_nips?: number[]; software?: string; version?: string; privacy_policy?: string; terms_of_service?: string; limitation?: { max_message_length?: number; max_subscriptions?: number; max_subid_length?: number; max_limit?: number; max_event_tags?: number; max_content_length?: number; min_pow_difficulty?: number; auth_required?: boolean; payment_required?: boolean; restricted_writes?: boolean; created_at_lower_limit?: number; created_at_upper_limit?: number; default_limit?: number; }; retention?: Array<{ kinds?: Array<number | [number, number]>; time?: number | null; count?: number; }>; relay_countries?: string[]; language_tags?: string[]; tags?: string[]; posting_policy?: string; payments_url?: string; fees?: { admission?: Array<{ amount: number; unit: string; }>; subscription?: Array<{ amount: number; unit: string; period: number; }>; publication?: Array<{ kinds?: number[]; amount: number; unit: string; }>; }; [key: string]: unknown; } /** * Fetches NIP-11 relay information document from a relay. * * @param relayUrl The WebSocket URL of the relay (e.g., "wss://relay.example.com") * @returns The relay information document * @throws Error if the fetch fails or returns invalid JSON */ declare function fetchRelayInformation(relayUrl: string): Promise<NDKRelayInformation>; type NDKRelayScore = number; /** * SignatureVerificationStats - A class to track and report signature verification statistics * for all relays in an NDK instance. */ declare class SignatureVerificationStats { private ndk; private debug; private intervalId; private intervalMs; /** * Creates a new SignatureVerificationStats instance * * @param ndk - The NDK instance to track stats for * @param intervalMs - How often to print stats (in milliseconds) */ constructor(ndk: NDK, intervalMs?: number); /** * Start tracking and reporting signature verification statistics */ start(): void; /** * Stop tracking and reporting signature verification statistics */ stop(): void; /** * Report current signature verification statistics for all relays */ reportStats(): void; /** * Collect statistics from all relays */ private collectStats; } /** * Create and start a signature verification stats tracker for the given NDK instance * * @param ndk - The NDK instance to track stats for * @param intervalMs - How often to print stats (in milliseconds) * @returns The created SignatureVerificationStats instance */ declare function startSignatureVerificationStats(ndk: NDK, intervalMs?: number): SignatureVerificationStats; /** * The subscription manager of an NDKRelay is in charge of orchestrating the subscriptions * that are created and closed in a given relay. * * The manager is responsible for: * * restarting subscriptions when they are unexpectedly closed * * scheduling subscriptions that are received before the relay is connected * * grouping similar subscriptions to be compiled into individual REQs */ declare class NDKRelaySubscriptionManager { private relay; subscriptions: Map<NDKFilterFingerprint, NDKRelaySubscription[]>; private generalSubManager; /** * @param relay - The relay instance. * @param generalSubManager - The subscription manager instance. */ constructor(relay: NDKRelay, generalSubManager: NDKSubscriptionManager); /** * Adds a subscription to the manager. */ addSubscription(sub: NDKSubscription, filters: NDKFilter[]): void; createSubscription(_sub: NDKSubscription, _filters: NDKFilter[], fingerprint?: NDKFilterFingerprint): NDKRelaySubscription; private onRelaySubscriptionClose; } /** @deprecated Use `WebSocket['url']` instead. */ type NDKRelayUrl = WebSocket["url"]; /** * Protocol handler function type for handling custom relay messages. * @param relay The relay that received the message * @param message The parsed message array from the relay */ type NDKProtocolHandler = (relay: NDKRelay, message: unknown[]) => void; declare enum NDKRelayStatus { DISCONNECTING = 0,// 0 DISCONNECTED = 1,// 1 RECONNECTING = 2,// 2 FLAPPING = 3,// 3 CONNECTING = 4,// 4 CONNECTED = 5,// 5 AUTH_REQUESTED = 6,// 6 AUTHENTICATING = 7,// 7 AUTHENTICATED = 8 } interface NDKRelayConnectionStats { /** * The number of times a connection has been attempted. */ attempts: number; /** * The number of times a connection has been successfully established. */ success: number; /** * The durations of the last 100 connections in milliseconds. */ durations: number[]; /** * The time the current connection was established in milliseconds. */ connectedAt?: number; /** * Timestamp of the next reconnection attempt. */ nextReconnectAt?: number; /** * Signature validation ratio for this relay. * @see NDKRelayOptions.validationRatio */ validationRatio?: number; } /** * The NDKRelay class represents a connection to a relay. * * @emits NDKRelay#connect * @emits NDKRelay#ready * @emits NDKRelay#disconnect * @emits NDKRelay#notice * @emits NDKRelay#event * @emits NDKRelay#published when an event is published to the relay * @emits NDKRelay#publish:failed when an event fails to publish to the relay * @emits NDKRelay#eose when the relay has reached the end of stored events * @emits NDKRelay#auth when the relay requires authentication * @emits NDKRelay#authed when the relay has authenticated * @emits NDKRelay#delayed-connect when the relay will wait before reconnecting */ declare class NDKRelay extends EventEmitter<{ connect: () => void; ready: () => void; /** * Emitted when the relay has reached the end of stored events. */ disconnect: () => void; flapping: (stats: NDKRelayConnectionStats) => void; notice: (notice: string) => void; auth: (challenge: string) => void; authed: () => void; "auth:failed": (error: Error) => void; published: (event: NDKEvent) => void; "publish:failed": (event: NDKEvent, error: Error) => void; "delayed-connect": (delayInMs: number) => void; }> { readonly url: WebSocket["url"]; readonly scores: Map<NDKUser, NDKRelayScore>; connectivity: NDKRelayConnectivity; subs: NDKRelaySubscriptionManager; private publisher; authPolicy?: NDKAuthPolicy; /** * Protocol handlers for custom relay message types (e.g., NEG-OPEN, NEG-MSG). * Allows external packages to handle non-standard relay messages. */ private protocolHandlers; /** * Cached relay information from NIP-11. */ private _relayInfo?; /** * The lowest validation ratio this relay can reach. */ lowestValidationRatio?: number; /** * Current validation ratio this relay is targeting. */ targetValidationRatio?: number; validationRatioFn?: (relay: NDKRelay, validatedCount: number, nonValidatedCount: number) => number; /** * This tracks events that have been seen by this relay * with a valid signature. */ validatedEventCount: number; /** * This tracks events that have been seen by this relay * but have not been validated. */ nonValidatedEventCount: number; /** * Whether this relay is trusted. * * Trusted relay's events do not get their signature verified. */ trusted: boolean; complaining: boolean; readonly debug: debug$1.Debugger; static defaultValidationRatioUpdateFn: (relay: NDKRelay, validatedCount: number, _nonValidatedCount: number) => number; constructor(url: WebSocket["url"], authPolicy: NDKAuthPolicy | undefined, ndk: NDK); private updateValidationRatio; get status(): NDKRelayStatus; get connectionStats(): NDKRelayConnectionStats; /** * Connects to the relay. */ connect(timeoutMs?: number, reconnect?: boolean): Promise<void>; /** * Disconnects from the relay. */ disconnect(): void; /** * Queues or executes the subscription of a specific set of filters * within this relay. * * @param subscription NDKSubscription this filters belong to. * @param filters Filters to execute */ subscribe(subscription: NDKSubscription, filters: NDKFilter[]): void; /** * Publishes an event to the relay with an optional timeout. * * If the relay is not connected, the event will be published when the relay connects, * unless the timeout is reached before the relay connects. * * @param event The event to publish * @param timeoutMs The timeout for the publish operation in milliseconds * @returns A promise that resolves when the event has been published or rejects if the operation times out */ publish(event: NDKEvent, timeoutMs?: number): Promise<boolean>; referenceTags(): NDKTag[]; addValidatedEvent(): void; addNonValidatedEvent(): void; /** * The current validation ratio this relay has achieved. */ get validationRatio(): number; shouldValidateEvent(): boolean; get connected(): boolean; req: (relaySub: NDKRelaySubscription) => void; close: (subId: string) => void; /** * Registers a protocol handler for a specific message type. * This allows external packages to handle custom relay messages (e.g., NIP-77 NEG-* messages). * * @param messageType The message type to handle (e.g., "NEG-OPEN", "NEG-MSG") * @param handler The function to call when a message of this type is received * * @example * ```typescript * relay.registerProtocolHandler('NEG-MSG', (relay, message) => { * console.log('Received NEG-MSG:', message); * }); * ``` */ registerProtocolHandler(messageType: string, handler: NDKProtocolHandler): void; /** * Unregisters a protocol handler for a specific message type. * * @param messageType The message type to stop handling */ unregisterProtocolHandler(messageType: string): void; /** * Checks if a protocol handler is registered for a message type. * This is used internally by the connectivity layer to route messages. * * @internal * @param messageType The message type to check * @returns The handler function if registered, undefined otherwise */ getProtocolHandler(messageType: string): NDKProtocolHandler | undefined; /** * Fetches relay information (NIP-11) from the relay. * Results are cached in persistent storage when cache adapter is available (24-hour TTL). * Falls back to in-memory cache. Pass force=true to bypass all caches. * * @param force Force a fresh fetch, bypassing all caches * @returns The relay information document * @throws Error if the fetch fails * * @example * ```typescript * const info = await relay.fetchInfo(); * console.log(`Relay: ${info.name}`); * console.log(`Supported NIPs: $