UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit

1,349 lines (1,330 loc) 191 kB
import { EventEmitter } from 'tseep'; import * as nostr_tools_lib_types_nip19_js from 'nostr-tools/lib/types/nip19.js'; import debug$1 from 'debug'; import { LRUCache } from 'typescript-lru-cache'; 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, 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, 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, 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, LegacyCashuWallet = 37375, GroupMetadata = 39000,// NIP-29 GroupAdmins = 39001,// NIP-29 GroupMembers = 39002,// NIP-29 FollowPack = 39089, MediaFollowPack = 39092, AppRecommendation = 31989, AppHandler = 31990 } declare const NDKListKinds: NDKKind[]; /** * 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 a follow to this user's contact list * * @param newFollow {NDKUser} The user to follow * @param currentFollowList {Set<NDKUser>} The current follow list * @param kind {NDKKind} The kind to use for this contact list (defaults to `3`) * @returns {Promise<boolean>} True if the follow was added, false if the follow already exists */ follow(newFollow: NDKUser, currentFollowList?: Set<NDKUser>, kind?: NDKKind): Promise<boolean>; /** * Remove a follow from this user's contact list * * @param user {NDKUser} The user to unfollow * @param currentFollowList {Set<NDKUser>} The current follow list * @param kind {NDKKind} The kind to use for this contact list (defaults to `3`) * @returns The relays were the follow list was published or false if the user wasn't found */ unfollow(user: NDKUser, currentFollowList?: Set<NDKUser>, 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>; poolBlacklistRelayUrls: Set<string>; private debug; private temporaryRelayTimers; private flappingRelays; private backoffTimes; private ndk; private disconnectionTimes; private systemEventDetector?; get blacklistRelayUrls(): Set<string>; /** * @param relayUrls - The URLs of the relays to connect to. * @param blacklistedRelayUrls - URLs to blacklist for this pool IN ADDITION to those blacklisted at the ndk-level * @param ndk - The NDK instance. * @param opts - Options for the pool. */ constructor(relayUrls: WebSocket["url"][], blacklistedRelayUrls: 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: Map<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 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 */ 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; /** * 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 number of matching events. * @throws {Error} If attempting to send the count request on a closed relay connection. */ count(filters: NDKFilter[], params: { id?: string | null; }): Promise<number>; 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; } type NDKRelayScore = number; /** * 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; } /** * 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; /** @deprecated Use `WebSocket['url']` instead. */ type NDKRelayUrl = WebSocket["url"]; 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; /** * 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; } /** * Creates a NDKRelaySet for the specified event. * TODO: account for relays where tagged pubkeys or hashtags * tend to write to. * @param ndk {NDK} * @param event {Event} * @returns Promise<NDKRelaySet> */ declare function calculateRelaySetFromEvent(ndk: NDK, event: NDKEvent, requiredRelayCount?: number): Promise<NDKRelaySet>; declare class NDKPublishError extends Error { errors: Map<NDKRelay, Error>; publishedToRelays: Set<NDKRelay>; /** * Intended relay set where the publishing was intended to happen. */ intendedRelaySet?: NDKRelaySet; constructor(message: string, errors: Map<NDKRelay, Error>, publishedToRelays: Set<NDKRelay>, intendedRelaySet?: NDKRelaySet); get relayErrors(): string; } /** * A relay set is a group of relays. This grouping can be short-living, for a single * REQ or can be long-lasting, for example for the explicit relay list the user * has specified. * * Requests to relays should be sent through this interface. */ declare class NDKRelaySet { readonly relays: Set<NDKRelay>; private debug; private ndk; private pool; constructor(relays: Set<NDKRelay>, ndk: NDK, pool?: NDKPool); /** * Adds a relay to this set. */ addRelay(relay: NDKRelay): void; get relayUrls(): WebSocket["url"][]; /** * Creates a relay set from a list of relay URLs. * * If no connection to the relay is found in the pool it will temporarily * connect to it. * * @param relayUrls - list of relay URLs to include in this set * @param ndk * @param connect - whether to connect to the relay immediately if it was already in the pool but not connected * @returns NDKRelaySet */ static fromRelayUrls(relayUrls: readonly string[], ndk: NDK, connect?: boolean, pool?: NDKPool): NDKRelaySet; /** * Publish an event to all relays in this relay set. * * This method implements a robust mechanism for publishing events to multiple relays with * built-in handling for race conditions, timeouts, and partial failures. The implementation * uses a dual-tracking mechanism to ensure accurate reporting of which relays successfully * received an event. * * Key aspects of this implementation: * * 1. DUAL-TRACKING MECHANISM: * - Promise-based tracking: Records successes/failures from the promises returned by relay.publish() * - Event-based tracking: Listens for 'relay:published' events that indicate successful publishing * This approach ensures we don't miss successful publishes even if there are subsequent errors in * the promise chain. * * 2. RACE CONDITION HANDLING: * - If a relay emits a success event but later fails in the promise chain, we still count it as a success * - If a relay times out after successfully publishing, we still count it as a success * - All relay operations happen in parallel, with proper tracking regardless of completion order * * 3. TIMEOUT MANAGEMENT: * - Individual timeouts for each relay operation * - Proper cleanup of timeouts to prevent memory leaks * - Clear timeout error reporting * * 4. ERROR HANDLING: * - Detailed tracking of specific errors for each failed relay * - Special handling for ephemeral events (which don't expect acknowledgement) * - RequiredRelayCount parameter to control the minimum success threshold * * @param event Event to publish * @param timeoutMs Timeout in milliseconds for each relay publish operation * @param requiredRelayCount The minimum number of relays we expect the event to be published to * @returns A set of relays the event was published to * @throws {NDKPublishError} If the event could not be published to at least `requiredRelayCount` relays * @example * ```typescript * const relaySet = new NDKRelaySet(new Set([relay1, relay2]), ndk); * const publishedToRelays = await relaySet.publish(event); * // publishedToRelays can contain relay1, relay2, both, or none * // depending on which relays the event was successfully published to * if (publishedToRelays.size > 0) { * console.log("Event published to at least one relay"); * } * ``` */ publish(event: NDKEvent, timeoutMs?: number, requiredRelayCount?: number): Promise<Set<NDKRelay>>; get size(): number; } /** * Options on how to handle when a relay hint doesn't respond * with the requested event. * * When a tag includes a relay hint, and the relay hint doesn't come back * with the event, the fallback options are used to try to fetch the event * from somewhere else. */ type NDKFetchFallbackOptions = { /** * Relay set to use as a fallback when the hint relay doesn't respond. * If not provided, the normal NDK calculation is used (whether explicit relays or outbox calculation) * Default is `undefined`. */ relaySet?: NDKRelaySet; /** * Type of fallback to use when the hint relay doesn't respond. * - "timeout" will wait for a timeout before falling back * - "eose" will wait for the EOSE before falling back * - "none" will not fall back * Default is "timeout". */ type: "timeout" | "eose" | "none"; /** * Timeout in milliseconds for the fallback relay. * Default is 1500ms. */ timeout?: number; }; type NDKZapConfirmationLN = { preimage: string; }; type NDKPaymentConfirmationLN = { preimage: string; }; type LNPaymentRequest = string; type LnPaymentInfo = { pr: LNPaymentRequest; }; type NDKLUD18ServicePayerData = Partial<{ name: { mandatory: boolean; }; pubkey: { mandatory: boolean; }; identifier: { mandatory: boolean; }; email: { mandatory: boolean; }; auth: { mandatory: boolean; k1: string; }; }> & Record<string, unknown>; type NDKLnUrlData = { tag: string; callback: string; minSendable: number; maxSendable: number; metadata: string; payerData?: NDKLUD18ServicePayerData; commentAllowed?: number; /** * Pubkey of the zapper that should publish zap receipts for this user */ nostrPubkey?: Hexpubkey; allowsNostr?: boolean; }; declare function getNip57ZapSpecFromLud({ lud06, lud16 }: { lud06?: string; lud16?: string; }, ndk: NDK): Promise<NDKLnUrlData | undefined>; type NDKCacheEntry<T> = T & { cachedAt?: number; }; interface NDKCacheAdapter { /** * Whether this cache adapter is expected to be fast. * If this is true, the cache will be queried before the relays. * When this is false, the cache will be queried in addition to the relays. */ locking: boolean; /** * Weather the cache is ready. */ ready?: boolean; initializeAsync?(ndk: NDK): Promise<void>; initialize?(ndk: NDK): void; /** * Either synchronously or asynchronously queries the cache. * * Cache adapters that return values synchronously should return an array of events. * Asynchronous cache adapters should call the subscription.eventReceived method for each event. */ query(subscription: NDKSubscription): NDKEvent[] | Promise<NDKEvent[]>; setEvent(event: NDKEvent, filters: NDKFilter[], relay?: NDKRelay): Promise<void>; /** * Called when an event is deleted by the client. * Cache adapters should remove the event from their cache. * @param eventIds - The ids of the events that were deleted. */ deleteEventIds?(eventIds: NDKEventId[]): Promise<void>; /** * Fetches a profile from the cache synchronously. * @param pubkey - The pubkey of the profile to fetch. * @returns The profile, or null if it is not in the cache. */ fetchProfileSync?(pubkey: Hexpubkey): NDKCacheEntry<NDKUserProfile> | null; /** * Retrieve all profiles from the cache synchronously. * @returns A map of pubkeys to profiles. */ getAllProfilesSync?(): Map<Hexpubkey, NDKCacheEntry<NDKUserProfile>>; /** * Special purpose */ fetchProfile?(pubkey: Hexpubkey): Promise<NDKCacheEntry<NDKUserProfile> | null>; saveProfile?(pubkey: Hexpubkey, profile: NDKUserProfile): void; /** * Fetches profiles that match the given filter. * @param filter * @returns NDKUserProfiles that match the filter. * @example * const searchFunc = (pubkey, profile) => profile.name.toLowerCase().includes("alice"); * const allAliceProfiles = await cache.getProfiles(searchFunc); */ getProfiles?: (filter: (pubkey: Hexpubkey, profile: NDKUserProfile) => boolean) => Promise<Map<Hexpubkey, NDKUserProfile> | undefined>; loadNip05?(nip05: string, maxAgeForMissing?: number): Promise<ProfilePointer | null | "missing">; saveNip05?(nip05: string, profile: ProfilePointer | null): void; /** * Fetches a user's LNURL data from the cache. * @param pubkey The pubkey of the user to fetch the LNURL data for. * @param maxAgeInSecs The maximum age of the data in seconds. * @param maxAgeForMissing The maximum age of the data in seconds if it is missing before it returns that it should be refetched. * @returns The LNURL data, null if it is not in the cache and under the maxAgeForMissing, or "missing" if i