UNPKG

@nostr-dev-kit/ndk

Version:

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

1,313 lines (1,299 loc) 491 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { BECH32_REGEX: () => BECH32_REGEX, HLL_REGISTER_COUNT: () => HLL_REGISTER_COUNT, NDKAppHandlerEvent: () => NDKAppHandlerEvent, NDKAppSettings: () => NDKAppSettings, NDKArticle: () => NDKArticle, NDKBlossomList: () => NDKBlossomList, NDKCashuMintAnnouncement: () => NDKCashuMintAnnouncement, NDKCashuMintList: () => NDKCashuMintList, NDKCashuToken: () => NDKCashuToken, NDKCashuWalletTx: () => NDKCashuWalletTx, NDKClassified: () => NDKClassified, NDKCollaborativeEvent: () => NDKCollaborativeEvent, NDKCountHll: () => NDKCountHll, NDKDVMJobFeedback: () => NDKDVMJobFeedback, NDKDVMJobResult: () => NDKDVMJobResult, NDKDVMRequest: () => NDKDVMRequest, NDKDraft: () => NDKDraft, NDKDvmJobFeedbackStatus: () => NDKDvmJobFeedbackStatus, NDKEvent: () => NDKEvent, NDKFedimintMint: () => NDKFedimintMint, NDKFilterValidationMode: () => NDKFilterValidationMode, NDKFollowPack: () => NDKFollowPack, NDKHighlight: () => NDKHighlight, NDKImage: () => NDKImage, NDKInterestList: () => NDKInterestList, NDKKind: () => NDKKind, NDKList: () => NDKList, NDKListKinds: () => NDKListKinds, NDKMintRecommendation: () => NDKMintRecommendation, NDKNip07Signer: () => NDKNip07Signer, NDKNip46Backend: () => NDKNip46Backend, NDKNip46Signer: () => NDKNip46Signer, NDKNostrRpc: () => NDKNostrRpc, NDKNutzap: () => NDKNutzap, NDKPool: () => NDKPool, NDKPrivateKeySigner: () => NDKPrivateKeySigner, NDKProject: () => NDKProject, NDKProjectTemplate: () => NDKProjectTemplate, NDKPublishError: () => NDKPublishError, NDKRelay: () => NDKRelay, NDKRelayAuthPolicies: () => NDKRelayAuthPolicies, NDKRelayFeedList: () => NDKRelayFeedList, NDKRelayList: () => NDKRelayList, NDKRelaySet: () => NDKRelaySet, NDKRelayStatus: () => NDKRelayStatus, NDKRepost: () => NDKRepost, NDKSimpleGroup: () => NDKSimpleGroup, NDKSimpleGroupMemberList: () => NDKSimpleGroupMemberList, NDKSimpleGroupMetadata: () => NDKSimpleGroupMetadata, NDKStory: () => NDKStory, NDKStorySticker: () => NDKStorySticker, NDKStoryStickerType: () => NDKStoryStickerType, NDKSubscription: () => NDKSubscription, NDKSubscriptionCacheUsage: () => NDKSubscriptionCacheUsage, NDKSubscriptionReceipt: () => NDKSubscriptionReceipt, NDKSubscriptionStart: () => NDKSubscriptionStart, NDKSubscriptionTier: () => NDKSubscriptionTier, NDKTask: () => NDKTask, NDKThread: () => NDKThread, NDKTranscriptionDVM: () => NDKTranscriptionDVM, NDKUser: () => NDKUser, NDKVideo: () => NDKVideo, NDKVoiceMessage: () => NDKVoiceMessage, NDKVoiceReply: () => NDKVoiceReply, NDKWiki: () => NDKWiki, NDKWikiMergeRequest: () => NDKWikiMergeRequest, NDKZap: () => NDKZap, NDKZapper: () => NDKZapper, NIP33_A_REGEX: () => NIP33_A_REGEX, NdkNutzapStatus: () => NdkNutzapStatus, NutzapValidationCode: () => NutzapValidationCode, NutzapValidationSeverity: () => NutzapValidationSeverity, SignatureVerificationStats: () => SignatureVerificationStats, assertSignedEvent: () => assertSignedEvent, calculateRelaySetFromEvent: () => calculateRelaySetFromEvent, calculateTermDurationInSeconds: () => calculateTermDurationInSeconds, cashuPubkeyToNostrPubkey: () => cashuPubkeyToNostrPubkey, compareFilter: () => compareFilter, createSignedEvent: () => createSignedEvent, createValidationIssue: () => createValidationIssue, default: () => NDK, defaultOpts: () => defaultOpts, deserialize: () => deserialize, dvmSchedule: () => dvmSchedule, eventHasETagMarkers: () => eventHasETagMarkers, eventIsPartOfThread: () => eventIsPartOfThread, eventIsReply: () => eventIsReply, eventReplies: () => eventReplies, eventThreadIds: () => eventThreadIds, eventThreads: () => eventThreads, eventsBySameAuthor: () => eventsBySameAuthor, fetchRelayInformation: () => fetchRelayInformation, filterAndRelaySetFromBech32: () => filterAndRelaySetFromBech32, filterEphemeralKindsFromFilter: () => filterEphemeralKindsFromFilter, filterFingerprint: () => filterFingerprint, filterForCache: () => filterForCache, filterForEventsTaggingId: () => filterForEventsTaggingId, filterFromId: () => filterFromId, generateContentTags: () => generateContentTags, generateHashtags: () => generateHashtags, generateSubId: () => generateSubId, generateZapRequest: () => generateZapRequest, getEventReplyId: () => getEventReplyId, getNip57ZapSpecFromLud: () => getNip57ZapSpecFromLud, getRegisteredEventClasses: () => getRegisteredEventClasses, getRelayListForUser: () => getRelayListForUser, getRelayListForUsers: () => getRelayListForUsers, getReplyTag: () => getReplyTag, getRootEventId: () => getRootEventId, getRootTag: () => getRootTag, giftUnwrap: () => giftUnwrap, giftWrap: () => giftWrap, imetaTagToTag: () => imetaTagToTag, isEphemeralKind: () => isEphemeralKind, isEventOriginalPost: () => isEventOriginalPost, isNip33AValue: () => isNip33AValue, isSignedEvent: () => isSignedEvent, isUnsignedEvent: () => isUnsignedEvent, isValidEventId: () => isValidEventId, isValidHex64: () => isValidHex64, isValidNip05: () => isValidNip05, isValidPubkey: () => isValidPubkey, mapImetaTag: () => mapImetaTag, matchFilter: () => matchFilter, mergeFilters: () => mergeFilters, mergeTags: () => mergeTags, ndkSignerFromPayload: () => ndkSignerFromPayload, newAmount: () => newAmount, nip19: () => nip19_exports, nip49: () => nip49_exports, normalize: () => normalize, normalizeRelayUrl: () => normalizeRelayUrl, normalizeUrl: () => normalizeUrl, parseTagToSubscriptionAmount: () => parseTagToSubscriptionAmount, pinEvent: () => pinEvent, possibleIntervalFrequencies: () => possibleIntervalFrequencies, processFilters: () => processFilters, profileFromEvent: () => profileFromEvent, proofP2pk: () => proofP2pk, proofP2pkNostr: () => proofP2pkNostr, proofsTotalBalance: () => proofsTotalBalance, queryFullyFilled: () => queryFullyFilled, registerEventClass: () => registerEventClass, registerSigner: () => registerSigner, relayListFromKind3: () => relayListFromKind3, relaysFromBech32: () => relaysFromBech32, serialize: () => serialize, serializeProfile: () => serializeProfile, startSignatureVerificationStats: () => startSignatureVerificationStats, strToDimension: () => strToDimension, strToPosition: () => strToPosition, tryNormalizeRelayUrl: () => tryNormalizeRelayUrl, uniqueTag: () => uniqueTag, unregisterEventClass: () => unregisterEventClass, wrapEvent: () => wrapEvent, zapInvoiceFromEvent: () => zapInvoiceFromEvent }); module.exports = __toCommonJS(index_exports); // src/events/kinds/index.ts var NDKKind = /* @__PURE__ */ ((NDKKind2) => { NDKKind2[NDKKind2["Metadata"] = 0] = "Metadata"; NDKKind2[NDKKind2["Text"] = 1] = "Text"; NDKKind2[NDKKind2["RecommendRelay"] = 2] = "RecommendRelay"; NDKKind2[NDKKind2["Contacts"] = 3] = "Contacts"; NDKKind2[NDKKind2["EncryptedDirectMessage"] = 4] = "EncryptedDirectMessage"; NDKKind2[NDKKind2["EventDeletion"] = 5] = "EventDeletion"; NDKKind2[NDKKind2["Repost"] = 6] = "Repost"; NDKKind2[NDKKind2["Reaction"] = 7] = "Reaction"; NDKKind2[NDKKind2["BadgeAward"] = 8] = "BadgeAward"; NDKKind2[NDKKind2["GroupChat"] = 9] = "GroupChat"; NDKKind2[NDKKind2["Thread"] = 11] = "Thread"; NDKKind2[NDKKind2["GroupReply"] = 12] = "GroupReply"; NDKKind2[NDKKind2["GiftWrapSeal"] = 13] = "GiftWrapSeal"; NDKKind2[NDKKind2["PrivateDirectMessage"] = 14] = "PrivateDirectMessage"; NDKKind2[NDKKind2["Image"] = 20] = "Image"; NDKKind2[NDKKind2["Video"] = 21] = "Video"; NDKKind2[NDKKind2["ShortVideo"] = 22] = "ShortVideo"; NDKKind2[NDKKind2["Story"] = 23] = "Story"; NDKKind2[NDKKind2["Vanish"] = 62] = "Vanish"; NDKKind2[NDKKind2["CashuWalletBackup"] = 375] = "CashuWalletBackup"; NDKKind2[NDKKind2["GiftWrap"] = 1059] = "GiftWrap"; NDKKind2[NDKKind2["GenericRepost"] = 16] = "GenericRepost"; NDKKind2[NDKKind2["ChannelCreation"] = 40] = "ChannelCreation"; NDKKind2[NDKKind2["ChannelMetadata"] = 41] = "ChannelMetadata"; NDKKind2[NDKKind2["ChannelMessage"] = 42] = "ChannelMessage"; NDKKind2[NDKKind2["ChannelHideMessage"] = 43] = "ChannelHideMessage"; NDKKind2[NDKKind2["ChannelMuteUser"] = 44] = "ChannelMuteUser"; NDKKind2[NDKKind2["WikiMergeRequest"] = 818] = "WikiMergeRequest"; NDKKind2[NDKKind2["GenericReply"] = 1111] = "GenericReply"; NDKKind2[NDKKind2["Media"] = 1063] = "Media"; NDKKind2[NDKKind2["VoiceMessage"] = 1222] = "VoiceMessage"; NDKKind2[NDKKind2["VoiceReply"] = 1244] = "VoiceReply"; NDKKind2[NDKKind2["DraftCheckpoint"] = 1234] = "DraftCheckpoint"; NDKKind2[NDKKind2["Task"] = 1934] = "Task"; NDKKind2[NDKKind2["Report"] = 1984] = "Report"; NDKKind2[NDKKind2["Label"] = 1985] = "Label"; NDKKind2[NDKKind2["DVMReqTextExtraction"] = 5e3] = "DVMReqTextExtraction"; NDKKind2[NDKKind2["DVMReqTextSummarization"] = 5001] = "DVMReqTextSummarization"; NDKKind2[NDKKind2["DVMReqTextTranslation"] = 5002] = "DVMReqTextTranslation"; NDKKind2[NDKKind2["DVMReqTextGeneration"] = 5050] = "DVMReqTextGeneration"; NDKKind2[NDKKind2["DVMReqImageGeneration"] = 5100] = "DVMReqImageGeneration"; NDKKind2[NDKKind2["DVMReqTextToSpeech"] = 5250] = "DVMReqTextToSpeech"; NDKKind2[NDKKind2["DVMReqDiscoveryNostrContent"] = 5300] = "DVMReqDiscoveryNostrContent"; NDKKind2[NDKKind2["DVMReqDiscoveryNostrPeople"] = 5301] = "DVMReqDiscoveryNostrPeople"; NDKKind2[NDKKind2["DVMReqTimestamping"] = 5900] = "DVMReqTimestamping"; NDKKind2[NDKKind2["DVMEventSchedule"] = 5905] = "DVMEventSchedule"; NDKKind2[NDKKind2["DVMJobFeedback"] = 7e3] = "DVMJobFeedback"; NDKKind2[NDKKind2["Subscribe"] = 7001] = "Subscribe"; NDKKind2[NDKKind2["Unsubscribe"] = 7002] = "Unsubscribe"; NDKKind2[NDKKind2["SubscriptionReceipt"] = 7003] = "SubscriptionReceipt"; NDKKind2[NDKKind2["CashuReserve"] = 7373] = "CashuReserve"; NDKKind2[NDKKind2["CashuQuote"] = 7374] = "CashuQuote"; NDKKind2[NDKKind2["CashuToken"] = 7375] = "CashuToken"; NDKKind2[NDKKind2["CashuWalletTx"] = 7376] = "CashuWalletTx"; NDKKind2[NDKKind2["GroupAdminAddUser"] = 9e3] = "GroupAdminAddUser"; NDKKind2[NDKKind2["GroupAdminRemoveUser"] = 9001] = "GroupAdminRemoveUser"; NDKKind2[NDKKind2["GroupAdminEditMetadata"] = 9002] = "GroupAdminEditMetadata"; NDKKind2[NDKKind2["GroupAdminEditStatus"] = 9006] = "GroupAdminEditStatus"; NDKKind2[NDKKind2["GroupAdminCreateGroup"] = 9007] = "GroupAdminCreateGroup"; NDKKind2[NDKKind2["GroupAdminRequestJoin"] = 9021] = "GroupAdminRequestJoin"; NDKKind2[NDKKind2["MuteList"] = 1e4] = "MuteList"; NDKKind2[NDKKind2["PinList"] = 10001] = "PinList"; NDKKind2[NDKKind2["RelayList"] = 10002] = "RelayList"; NDKKind2[NDKKind2["BookmarkList"] = 10003] = "BookmarkList"; NDKKind2[NDKKind2["CommunityList"] = 10004] = "CommunityList"; NDKKind2[NDKKind2["PublicChatList"] = 10005] = "PublicChatList"; NDKKind2[NDKKind2["BlockRelayList"] = 10006] = "BlockRelayList"; NDKKind2[NDKKind2["SearchRelayList"] = 10007] = "SearchRelayList"; NDKKind2[NDKKind2["SimpleGroupList"] = 10009] = "SimpleGroupList"; NDKKind2[NDKKind2["RelayFeedList"] = 10012] = "RelayFeedList"; NDKKind2[NDKKind2["InterestList"] = 10015] = "InterestList"; NDKKind2[NDKKind2["CashuMintList"] = 10019] = "CashuMintList"; NDKKind2[NDKKind2["EmojiList"] = 10030] = "EmojiList"; NDKKind2[NDKKind2["DirectMessageReceiveRelayList"] = 10050] = "DirectMessageReceiveRelayList"; NDKKind2[NDKKind2["BlossomList"] = 10063] = "BlossomList"; NDKKind2[NDKKind2["NostrWaletConnectInfo"] = 13194] = "NostrWaletConnectInfo"; NDKKind2[NDKKind2["TierList"] = 17e3] = "TierList"; NDKKind2[NDKKind2["CashuWallet"] = 17375] = "CashuWallet"; NDKKind2[NDKKind2["FollowSet"] = 3e4] = "FollowSet"; NDKKind2[NDKKind2["CategorizedPeopleList"] = 3e4 /* FollowSet */] = "CategorizedPeopleList"; NDKKind2[NDKKind2["CategorizedBookmarkList"] = 30001] = "CategorizedBookmarkList"; NDKKind2[NDKKind2["RelaySet"] = 30002] = "RelaySet"; NDKKind2[NDKKind2["CategorizedRelayList"] = 30002 /* RelaySet */] = "CategorizedRelayList"; NDKKind2[NDKKind2["BookmarkSet"] = 30003] = "BookmarkSet"; NDKKind2[NDKKind2["CurationSet"] = 30004] = "CurationSet"; NDKKind2[NDKKind2["ArticleCurationSet"] = 30004] = "ArticleCurationSet"; NDKKind2[NDKKind2["VideoCurationSet"] = 30005] = "VideoCurationSet"; NDKKind2[NDKKind2["ImageCurationSet"] = 30006] = "ImageCurationSet"; NDKKind2[NDKKind2["InterestSet"] = 30015] = "InterestSet"; NDKKind2[NDKKind2["InterestsList"] = 30015 /* InterestSet */] = "InterestsList"; NDKKind2[NDKKind2["ProjectTemplate"] = 30717] = "ProjectTemplate"; NDKKind2[NDKKind2["EmojiSet"] = 30030] = "EmojiSet"; NDKKind2[NDKKind2["ModularArticle"] = 30040] = "ModularArticle"; NDKKind2[NDKKind2["ModularArticleItem"] = 30041] = "ModularArticleItem"; NDKKind2[NDKKind2["Wiki"] = 30818] = "Wiki"; NDKKind2[NDKKind2["Draft"] = 31234] = "Draft"; NDKKind2[NDKKind2["Project"] = 31933] = "Project"; NDKKind2[NDKKind2["SubscriptionTier"] = 37001] = "SubscriptionTier"; NDKKind2[NDKKind2["EcashMintRecommendation"] = 38e3] = "EcashMintRecommendation"; NDKKind2[NDKKind2["CashuMintAnnouncement"] = 38172] = "CashuMintAnnouncement"; NDKKind2[NDKKind2["FedimintMintAnnouncement"] = 38173] = "FedimintMintAnnouncement"; NDKKind2[NDKKind2["P2POrder"] = 38383] = "P2POrder"; NDKKind2[NDKKind2["CollaborativeEvent"] = 39382] = "CollaborativeEvent"; NDKKind2[NDKKind2["HighlightSet"] = 39802] = "HighlightSet"; NDKKind2[NDKKind2["CategorizedHighlightList"] = 39802 /* HighlightSet */] = "CategorizedHighlightList"; NDKKind2[NDKKind2["Nutzap"] = 9321] = "Nutzap"; NDKKind2[NDKKind2["ZapRequest"] = 9734] = "ZapRequest"; NDKKind2[NDKKind2["Zap"] = 9735] = "Zap"; NDKKind2[NDKKind2["Highlight"] = 9802] = "Highlight"; NDKKind2[NDKKind2["ClientAuth"] = 22242] = "ClientAuth"; NDKKind2[NDKKind2["NostrWalletConnectReq"] = 23194] = "NostrWalletConnectReq"; NDKKind2[NDKKind2["NostrWalletConnectRes"] = 23195] = "NostrWalletConnectRes"; NDKKind2[NDKKind2["NostrConnect"] = 24133] = "NostrConnect"; NDKKind2[NDKKind2["BlossomUpload"] = 24242] = "BlossomUpload"; NDKKind2[NDKKind2["HttpAuth"] = 27235] = "HttpAuth"; NDKKind2[NDKKind2["ProfileBadge"] = 30008] = "ProfileBadge"; NDKKind2[NDKKind2["BadgeDefinition"] = 30009] = "BadgeDefinition"; NDKKind2[NDKKind2["MarketStall"] = 30017] = "MarketStall"; NDKKind2[NDKKind2["MarketProduct"] = 30018] = "MarketProduct"; NDKKind2[NDKKind2["Article"] = 30023] = "Article"; NDKKind2[NDKKind2["AppSpecificData"] = 30078] = "AppSpecificData"; NDKKind2[NDKKind2["Classified"] = 30402] = "Classified"; NDKKind2[NDKKind2["HorizontalVideo"] = 34235] = "HorizontalVideo"; NDKKind2[NDKKind2["VerticalVideo"] = 34236] = "VerticalVideo"; NDKKind2[NDKKind2["GroupMetadata"] = 39e3] = "GroupMetadata"; NDKKind2[NDKKind2["GroupAdmins"] = 39001] = "GroupAdmins"; NDKKind2[NDKKind2["GroupMembers"] = 39002] = "GroupMembers"; NDKKind2[NDKKind2["FollowPack"] = 39089] = "FollowPack"; NDKKind2[NDKKind2["MediaFollowPack"] = 39092] = "MediaFollowPack"; NDKKind2[NDKKind2["AppRecommendation"] = 31989] = "AppRecommendation"; NDKKind2[NDKKind2["AppHandler"] = 31990] = "AppHandler"; return NDKKind2; })(NDKKind || {}); var NDKListKinds = [ 1e4 /* MuteList */, 10001 /* PinList */, 10002 /* RelayList */, 10003 /* BookmarkList */, 10004 /* CommunityList */, 10005 /* PublicChatList */, 10006 /* BlockRelayList */, 10007 /* SearchRelayList */, 10012 /* RelayFeedList */, 10015 /* InterestList */, 10030 /* EmojiList */, 10050 /* DirectMessageReceiveRelayList */, 3e4 /* FollowSet */, 30003 /* BookmarkSet */, 30001 /* CategorizedBookmarkList */, // Backwards compatibility 30002 /* RelaySet */, 30004 /* ArticleCurationSet */, 30005 /* VideoCurationSet */, 30015 /* InterestSet */, 30030 /* EmojiSet */, 39802 /* HighlightSet */ ]; // src/types.ts var NdkNutzapStatus = /* @__PURE__ */ ((NdkNutzapStatus2) => { NdkNutzapStatus2["INITIAL"] = "initial"; NdkNutzapStatus2["PROCESSING"] = "processing"; NdkNutzapStatus2["REDEEMED"] = "redeemed"; NdkNutzapStatus2["SPENT"] = "spent"; NdkNutzapStatus2["MISSING_PRIVKEY"] = "missing_privkey"; NdkNutzapStatus2["TEMPORARY_ERROR"] = "temporary_error"; NdkNutzapStatus2["PERMANENT_ERROR"] = "permanent_error"; NdkNutzapStatus2["INVALID_NUTZAP"] = "invalid_nutzap"; return NdkNutzapStatus2; })(NdkNutzapStatus || {}); // src/events/index.ts var import_tseep2 = require("tseep"); // src/relay/sets/calculate.ts var import_debug3 = __toESM(require("debug")); // src/outbox/write.ts function getRelaysForSync(ndk, author, type = "write") { if (!ndk.outboxTracker) return void 0; const item = ndk.outboxTracker.data.get(author); if (!item) return void 0; if (type === "write") { return item.writeRelays; } return item.readRelays; } async function getWriteRelaysFor(ndk, author, type = "write") { if (!ndk.outboxTracker) return void 0; if (!ndk.outboxTracker.data.has(author)) { await ndk.outboxTracker.trackUsers([author]); } return getRelaysForSync(ndk, author, type); } // src/outbox/relay-ranking.ts function getTopRelaysForAuthors(ndk, authors) { const relaysWithCount = /* @__PURE__ */ new Map(); authors.forEach((author) => { const writeRelays = getRelaysForSync(ndk, author); if (writeRelays) { writeRelays.forEach((relay) => { const count = relaysWithCount.get(relay) || 0; relaysWithCount.set(relay, count + 1); }); } }); const sortedRelays = Array.from(relaysWithCount.entries()).sort((a, b) => b[1] - a[1]); return sortedRelays.map((entry) => entry[0]); } // src/outbox/index.ts function getAllRelaysForAllPubkeys(ndk, pubkeys, type = "read") { const pubkeysToRelays = /* @__PURE__ */ new Map(); const authorsMissingRelays = /* @__PURE__ */ new Set(); pubkeys.forEach((pubkey) => { const relays = getRelaysForSync(ndk, pubkey, type); if (relays && relays.size > 0) { relays.forEach((relay) => { const pubkeysInRelay = pubkeysToRelays.get(relay) || /* @__PURE__ */ new Set(); pubkeysInRelay.add(pubkey); }); pubkeysToRelays.set(pubkey, relays); } else { authorsMissingRelays.add(pubkey); } }); return { pubkeysToRelays, authorsMissingRelays }; } function chooseRelayCombinationForPubkeys(ndk, pubkeys, type, { count, preferredRelays } = {}) { count ??= 2; preferredRelays ??= /* @__PURE__ */ new Set(); const pool = ndk.pool; const connectedRelays = pool.connectedRelays(); connectedRelays.forEach((relay) => { preferredRelays?.add(relay.url); }); const relayToAuthorsMap = /* @__PURE__ */ new Map(); const { pubkeysToRelays, authorsMissingRelays } = getAllRelaysForAllPubkeys(ndk, pubkeys, type); const sortedRelays = getTopRelaysForAuthors(ndk, pubkeys); const addAuthorToRelay = (author, relay) => { const authorsInRelay = relayToAuthorsMap.get(relay) || []; authorsInRelay.push(author); relayToAuthorsMap.set(relay, authorsInRelay); }; for (const [author, authorRelays] of pubkeysToRelays.entries()) { let missingRelayCount = count; const addedRelaysForAuthor = /* @__PURE__ */ new Set(); for (const relay of connectedRelays) { if (authorRelays.has(relay.url)) { addAuthorToRelay(author, relay.url); addedRelaysForAuthor.add(relay.url); missingRelayCount--; } } for (const authorRelay of authorRelays) { if (addedRelaysForAuthor.has(authorRelay)) continue; if (relayToAuthorsMap.has(authorRelay)) { addAuthorToRelay(author, authorRelay); addedRelaysForAuthor.add(authorRelay); missingRelayCount--; } } if (missingRelayCount <= 0) continue; for (const relay of sortedRelays) { if (missingRelayCount <= 0) break; if (addedRelaysForAuthor.has(relay)) continue; if (authorRelays.has(relay)) { addAuthorToRelay(author, relay); addedRelaysForAuthor.add(relay); missingRelayCount--; } } } for (const author of authorsMissingRelays) { pool.permanentAndConnectedRelays().forEach((relay) => { const authorsInRelay = relayToAuthorsMap.get(relay.url) || []; authorsInRelay.push(author); relayToAuthorsMap.set(relay.url, authorsInRelay); }); } return relayToAuthorsMap; } // src/outbox/read/with-authors.ts function getRelaysForFilterWithAuthors(ndk, authors, relayGoalPerAuthor = 2) { return chooseRelayCombinationForPubkeys(ndk, authors, "write", { count: relayGoalPerAuthor }); } // src/utils/normalize-url.ts function tryNormalizeRelayUrl(url) { try { return normalizeRelayUrl(url); } catch { return void 0; } } function normalizeRelayUrl(url) { let r = normalizeUrl(url, { stripAuthentication: false, stripWWW: false, stripHash: true }); if (!r.endsWith("/")) { r += "/"; } return r; } function normalize(urls) { const normalized = /* @__PURE__ */ new Set(); for (const url of urls) { try { normalized.add(normalizeRelayUrl(url)); } catch { } } return Array.from(normalized); } var DATA_URL_DEFAULT_MIME_TYPE = "text/plain"; var DATA_URL_DEFAULT_CHARSET = "us-ascii"; var testParameter = (name, filters) => filters.some((filter) => filter instanceof RegExp ? filter.test(name) : filter === name); var supportedProtocols = /* @__PURE__ */ new Set(["https:", "http:", "file:"]); var hasCustomProtocol = (urlString) => { try { const { protocol } = new URL(urlString); return protocol.endsWith(":") && !protocol.includes(".") && !supportedProtocols.has(protocol); } catch { return false; } }; var normalizeDataURL = (urlString, { stripHash }) => { const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString); if (!match) { throw new Error(`Invalid URL: ${urlString}`); } const type = match.groups?.type ?? ""; const data = match.groups?.data ?? ""; let hash = match.groups?.hash ?? ""; const mediaType = type.split(";"); hash = stripHash ? "" : hash; let isBase64 = false; if (mediaType[mediaType.length - 1] === "base64") { mediaType.pop(); isBase64 = true; } const mimeType = mediaType.shift()?.toLowerCase() ?? ""; const attributes = mediaType.map((attribute) => { let [key, value = ""] = attribute.split("=").map((string) => string.trim()); if (key === "charset") { value = value.toLowerCase(); if (value === DATA_URL_DEFAULT_CHARSET) { return ""; } } return `${key}${value ? `=${value}` : ""}`; }).filter(Boolean); const normalizedMediaType = [...attributes]; if (isBase64) { normalizedMediaType.push("base64"); } if (normalizedMediaType.length > 0 || mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE) { normalizedMediaType.unshift(mimeType); } return `data:${normalizedMediaType.join(";")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ""}`; }; function normalizeUrl(urlString, options = {}) { options = { defaultProtocol: "http", normalizeProtocol: true, forceHttp: false, forceHttps: false, stripAuthentication: true, stripHash: false, stripTextFragment: true, stripWWW: true, removeQueryParameters: [/^utm_\w+/i], removeTrailingSlash: true, removeSingleSlash: true, removeDirectoryIndex: false, removeExplicitPort: false, sortQueryParameters: true, ...options }; if (typeof options.defaultProtocol === "string" && !options.defaultProtocol.endsWith(":")) { options.defaultProtocol = `${options.defaultProtocol}:`; } urlString = urlString.trim(); if (/^data:/i.test(urlString)) { return normalizeDataURL(urlString, options); } if (hasCustomProtocol(urlString)) { return urlString; } const hasRelativeProtocol = urlString.startsWith("//"); const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); if (!isRelativeUrl) { urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol); } const urlObject = new URL(urlString); urlObject.hostname = urlObject.hostname.toLowerCase(); if (options.forceHttp && options.forceHttps) { throw new Error("The `forceHttp` and `forceHttps` options cannot be used together"); } if (options.forceHttp && urlObject.protocol === "https:") { urlObject.protocol = "http:"; } if (options.forceHttps && urlObject.protocol === "http:") { urlObject.protocol = "https:"; } if (options.stripAuthentication) { urlObject.username = ""; urlObject.password = ""; } if (options.stripHash) { urlObject.hash = ""; } else if (options.stripTextFragment) { urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, ""); } if (urlObject.pathname) { const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g; let lastIndex = 0; let result = ""; for (; ; ) { const match = protocolRegex.exec(urlObject.pathname); if (!match) { break; } const protocol = match[0]; const protocolAtIndex = match.index; const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex); result += intermediate.replace(/\/{2,}/g, "/"); result += protocol; lastIndex = protocolAtIndex + protocol.length; } const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length); result += remnant.replace(/\/{2,}/g, "/"); urlObject.pathname = result; } if (urlObject.pathname) { try { urlObject.pathname = decodeURI(urlObject.pathname); } catch { } } if (options.removeDirectoryIndex === true) { options.removeDirectoryIndex = [/^index\.[a-z]+$/]; } if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) { let pathComponents = urlObject.pathname.split("/"); const lastComponent = pathComponents[pathComponents.length - 1]; if (testParameter(lastComponent, options.removeDirectoryIndex)) { pathComponents = pathComponents.slice(0, -1); urlObject.pathname = `${pathComponents.slice(1).join("/")}/`; } } if (urlObject.hostname) { urlObject.hostname = urlObject.hostname.replace(/\.$/, ""); if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) { urlObject.hostname = urlObject.hostname.replace(/^www\./, ""); } } if (Array.isArray(options.removeQueryParameters)) { for (const key of [...urlObject.searchParams.keys()]) { if (testParameter(key, options.removeQueryParameters)) { urlObject.searchParams.delete(key); } } } if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) { urlObject.search = ""; } if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) { for (const key of [...urlObject.searchParams.keys()]) { if (!testParameter(key, options.keepQueryParameters)) { urlObject.searchParams.delete(key); } } } if (options.sortQueryParameters) { urlObject.searchParams.sort(); try { urlObject.search = decodeURIComponent(urlObject.search); } catch { } } if (options.removeTrailingSlash) { urlObject.pathname = urlObject.pathname.replace(/\/$/, ""); } if (options.removeExplicitPort && urlObject.port) { urlObject.port = ""; } const oldUrlString = urlString; urlString = urlObject.toString(); if (!options.removeSingleSlash && urlObject.pathname === "/" && !oldUrlString.endsWith("/") && urlObject.hash === "") { urlString = urlString.replace(/\/$/, ""); } if ((options.removeTrailingSlash || urlObject.pathname === "/") && urlObject.hash === "" && options.removeSingleSlash) { urlString = urlString.replace(/\/$/, ""); } if (hasRelativeProtocol && !options.normalizeProtocol) { urlString = urlString.replace(/^http:\/\//, "//"); } if (options.stripProtocol) { urlString = urlString.replace(/^(?:https?:)?\/\//, ""); } return urlString; } // src/count/index.ts var HLL_REGISTER_COUNT = 256; var NDKCountHll = class _NDKCountHll { /** * The 256 uint8 registers used for HLL estimation */ registers; constructor(registers) { if (registers) { if (registers.length !== HLL_REGISTER_COUNT) { throw new Error(`HLL must have exactly ${HLL_REGISTER_COUNT} registers, got ${registers.length}`); } this.registers = registers; } else { this.registers = new Uint8Array(HLL_REGISTER_COUNT); } } /** * 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) { if (hex.length !== HLL_REGISTER_COUNT * 2) { throw new Error(`HLL hex string must be ${HLL_REGISTER_COUNT * 2} characters, got ${hex.length}`); } const registers = new Uint8Array(HLL_REGISTER_COUNT); for (let i = 0; i < HLL_REGISTER_COUNT; i++) { registers[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return new _NDKCountHll(registers); } /** * Converts the HLL registers to a hex-encoded string. * * @returns The hex string representation (512 characters) */ toHex() { return Array.from(this.registers).map((v) => v.toString(16).padStart(2, "0")).join(""); } /** * 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) { const merged = new Uint8Array(HLL_REGISTER_COUNT); for (let i = 0; i < HLL_REGISTER_COUNT; i++) { merged[i] = Math.max(this.registers[i], other.registers[i]); } return new _NDKCountHll(merged); } /** * 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) { if (hlls.length === 0) { return new _NDKCountHll(); } const merged = new Uint8Array(HLL_REGISTER_COUNT); for (let i = 0; i < HLL_REGISTER_COUNT; i++) { merged[i] = Math.max(...hlls.map((hll) => hll.registers[i])); } return new _NDKCountHll(merged); } /** * 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() { const m = HLL_REGISTER_COUNT; const alpha = 0.7213 / (1 + 1.079 / m); let sum = 0; let zeros = 0; for (let i = 0; i < m; i++) { sum += Math.pow(2, -this.registers[i]); if (this.registers[i] === 0) { zeros++; } } let estimate = alpha * m * m / sum; if (estimate <= 2.5 * m && zeros > 0) { estimate = m * Math.log(m / zeros); } return Math.round(estimate); } /** * Checks if this HLL is empty (all registers are zero). * * @returns True if all registers are zero */ isEmpty() { return this.registers.every((v) => v === 0); } /** * Creates a copy of this HLL. * * @returns A new NDKCountHll with the same register values */ clone() { return new _NDKCountHll(new Uint8Array(this.registers)); } }; // src/relay/index.ts var import_debug2 = __toESM(require("debug")); var import_tseep = require("tseep"); // src/relay/keepalive.ts var NDKRelayKeepalive = class { /** * @param timeout - Time in milliseconds to wait before considering connection stale (default 30s) * @param onSilenceDetected - Callback when silence is detected */ constructor(timeout = 3e4, onSilenceDetected) { this.onSilenceDetected = onSilenceDetected; this.timeout = timeout; } lastActivity = Date.now(); timer; timeout; isRunning = false; /** * Records activity from the relay, resetting the silence timer */ recordActivity() { this.lastActivity = Date.now(); if (this.isRunning) { this.resetTimer(); } } /** * Starts monitoring for relay silence */ start() { if (this.isRunning) return; this.isRunning = true; this.lastActivity = Date.now(); this.resetTimer(); } /** * Stops monitoring for relay silence */ stop() { this.isRunning = false; if (this.timer) { clearTimeout(this.timer); this.timer = void 0; } } resetTimer() { if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(() => { const silenceTime = Date.now() - this.lastActivity; if (silenceTime >= this.timeout) { this.onSilenceDetected(); } else { const remainingTime = this.timeout - silenceTime; this.timer = setTimeout(() => { this.onSilenceDetected(); }, remainingTime); } }, this.timeout); } }; async function probeRelayConnection(relay) { const probeId = `probe-${Math.random().toString(36).substring(7)}`; return new Promise((resolve) => { let responded = false; const timeout = setTimeout(() => { if (!responded) { responded = true; relay.send(["CLOSE", probeId]); resolve(false); } }, 5e3); const handler = () => { if (!responded) { responded = true; clearTimeout(timeout); relay.send(["CLOSE", probeId]); resolve(true); } }; relay.once("message", handler); relay.send([ "REQ", probeId, { kinds: [99999], limit: 0 } ]); }); } // src/relay/connectivity.ts var FLAPPING_THRESHOLD_MS = 1e3; var NDKRelayConnectivity = class { ndkRelay; ws; _status; timeoutMs; connectedAt; _connectionStats = { attempts: 0, success: 0, durations: [] }; debug; netDebug; connectTimeout; reconnectTimeout; ndk; openSubs = /* @__PURE__ */ new Map(); openCountRequests = /* @__PURE__ */ new Map(); openEventPublishes = /* @__PURE__ */ new Map(); pendingAuthPublishes = /* @__PURE__ */ new Map(); serial = 0; baseEoseTimeout = 4400; // Keepalive and monitoring keepalive; wsStateMonitor; sleepDetector; lastSleepCheck = Date.now(); lastMessageSent = Date.now(); wasIdle = false; constructor(ndkRelay, ndk) { this.ndkRelay = ndkRelay; this._status = 1 /* DISCONNECTED */; const rand = Math.floor(Math.random() * 1e3); this.debug = this.ndkRelay.debug.extend(`connectivity${rand}`); this.ndk = ndk; this.setupMonitoring(); } /** * Sets up keepalive, WebSocket state monitoring, and sleep detection */ setupMonitoring() { this.keepalive = new NDKRelayKeepalive(12e4, async () => { this.debug("Relay silence detected, probing connection"); const isAlive = await probeRelayConnection({ send: (msg) => this.send(JSON.stringify(msg)), once: (event, handler) => { const messageHandler = (e) => { try { const data = JSON.parse(e.data); if (data[0] === "EOSE" || data[0] === "EVENT" || data[0] === "NOTICE") { handler(); this.ws?.removeEventListener("message", messageHandler); } } catch { } }; this.ws?.addEventListener("message", messageHandler); } }); if (!isAlive) { this.debug("Probe failed, connection is stale"); this.handleStaleConnection(); } }); this.wsStateMonitor = setInterval(() => { if (this._status === 5 /* CONNECTED */) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { this.debug("WebSocket died silently, reconnecting"); this.handleStaleConnection(); } } }, 5e3); this.sleepDetector = setInterval(() => { const now = Date.now(); const elapsed = now - this.lastSleepCheck; if (elapsed > 15e3) { this.debug(`Detected possible sleep/wake (${elapsed}ms gap)`); this.handlePossibleWake(); } this.lastSleepCheck = now; }, 1e4); } /** * Handles detection of a stale connection by cleaning up and triggering reconnection. */ handleStaleConnection() { this.wasIdle = true; this.keepalive?.stop(); if (this.ws) { try { this.ws.close(); } catch (e) { } this.ws = void 0; } this._status = 1 /* DISCONNECTED */; this.ndkRelay.emit("disconnect"); this.handleReconnection(); } /** * Handles possible system wake event */ handlePossibleWake() { this.debug("System wake detected, checking all connections"); this.wasIdle = true; if (this._status >= 5 /* CONNECTED */) { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { this.handleStaleConnection(); } else { probeRelayConnection({ send: (msg) => this.send(JSON.stringify(msg)), once: (event, handler) => { const messageHandler = (e) => { try { const data = JSON.parse(e.data); if (data[0] === "EOSE" || data[0] === "EVENT" || data[0] === "NOTICE") { handler(); this.ws?.removeEventListener("message", messageHandler); } } catch { } }; this.ws?.addEventListener("message", messageHandler); } }).then((isAlive) => { if (!isAlive) { this.handleStaleConnection(); } }); } } } /** * Resets the reconnection state for system-wide events * Used by NDKPool when detecting system sleep/wake */ resetReconnectionState() { this.wasIdle = true; if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = void 0; } } /** * 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. */ async connect(timeoutMs, reconnect = true) { if (this.ws && this.ws.readyState !== WebSocket.OPEN && this.ws.readyState !== WebSocket.CONNECTING) { this.debug("Cleaning up stale WebSocket connection"); try { this.ws.close(); } catch (e) { } this.ws = void 0; this._status = 1 /* DISCONNECTED */; } if (this._status !== 2 /* RECONNECTING */ && this._status !== 1 /* DISCONNECTED */ || this.reconnectTimeout) { this.debug( "Relay requested to be connected but was in state %s or it had a reconnect timeout", this._status ); return; } if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = void 0; } if (this.connectTimeout) { clearTimeout(this.connectTimeout); this.connectTimeout = void 0; } timeoutMs ??= this.timeoutMs; if (!this.timeoutMs && timeoutMs) this.timeoutMs = timeoutMs; if (this.timeoutMs) this.connectTimeout = setTimeout(() => this.onConnectionError(reconnect), this.timeoutMs); try { this.updateConnectionStats.attempt(); if (this._status === 1 /* DISCONNECTED */) this._status = 4 /* CONNECTING */; else this._status = 2 /* RECONNECTING */; this.ws = new WebSocket(this.ndkRelay.url); this.ws.onopen = this.onConnect.bind(this); this.ws.onclose = this.onDisconnect.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onerror = this.onError.bind(this); } catch (e) { this.debug(`Failed to connect to ${this.ndkRelay.url}`, e); this._status = 1 /* DISCONNECTED */; if (reconnect) this.handleReconnection(); else this.ndkRelay.emit("delayed-connect", 2 * 24 * 60 * 60 * 1e3); throw e; } } /** * 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() { this._status = 0 /* DISCONNECTING */; this.keepalive?.stop(); if (this.wsStateMonitor) { clearInterval(this.wsStateMonitor); this.wsStateMonitor = void 0; } if (this.sleepDetector) { clearInterval(this.sleepDetector); this.sleepDetector = void 0; } try { this.ws?.close(); } catch (e) { this.debug("Failed to disconnect", e); this._status = 1 /* DISCONNECTED */; } } /** * 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) { this.debug(`Error connecting to ${this.ndkRelay.url}`, this.timeoutMs); if (reconnect && !this.reconnectTimeout) { this.handleReconnection(); } } /** * 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. */ onConnect() { this.netDebug?.("connected", this.ndkRelay); if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = void 0; } if (this.connectTimeout) { clearTimeout(this.connectTimeout); this.connectTimeout = void 0; } this.updateConnectionStats.connected(); this._status = 5 /* CONNECTED */; this.keepalive?.start(); this.wasIdle = false; this.ndkRelay.emit("connect"); this.ndkRelay.emit("ready"); } /** * 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. */ onDisconnect() { this.netDebug?.("disconnected", this.ndkRelay); this.updateConnectionStats.disconnected(); this.keepalive?.stop(); this.clearPendingPublishes(new Error(`Relay ${this.ndkRelay.url} disconnected`)); if (this._status === 5 /* CONNECTED */) { this.handleReconnection(); } this._status = 1 /* DISCONNECTED */; this.ndkRelay.emit("disconnect"); } /** * 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. */ onMessage(event) { this.netDebug?.(event.data, this.ndkRelay, "recv"); this.keepalive?.recordActivity(); try { const data = JSON.parse(event.data); const [cmd, id, ..._rest] = data; const handler = this.ndkRelay.getProtocolHandler(cmd); if (handler) { handler(this.ndkRelay, data); return; } switch (cmd) { case "EVENT": { const so = this.openSubs.get(id); const event2 = data[2]; if (!so) { this.debug(`Received event for unknown subscription ${id}`); return; } so.onevent(event2); return; } case "COUNT": { const payload = data[2]; const cr = this.openCountRequests.get(id); if (cr) { const result = { count: payload.count }; if (payload.hll) { try { result.hll = NDKCountHll.fromHex(payload.hll); } catch (e) { this.debug("Failed to parse HLL from COUNT response:", e); } } cr.resolve(result); this.openCountRequests.delete(id); } return; } case "EOSE": { const so = this.openSubs.get(id); if (!so) return; so.oneose(id); return; } case "OK": { const ok = data[2]; const reason = data[3]; const ep = this.openEventPublishes.get(id); const firstEp = ep?.pop(); if (!ep || !firstEp) { this.debug("Received OK for unknown event publish", id); return; } if (ok) { firstEp.resolve(reason); this.pendingAuthPublishes.delete(id); } else { const isAuthRequired = reason && (reason.toLowerCase().includes("auth-required") || reason.toLowerCase().includes("not authorized") || reason.toLowerCase().includes("blocked: not authorized")); if (isAuthRequired) { const event2 = this.pendingAuthPublishes.get(id); if (event2) { this.debug("Publish failed due to auth-required, will retry after auth", id); ep.push(firstEp); this.openEventPublishes.set(id, ep); } else { firstEp.reject(new Error(reason)); } } else { firstEp.reject(new Error(reason)); this.pendingAuthPublishes.delete(id); } } if (ep.length === 0) { this.openEventPublishes.delete(id); } else if (!ok && !(reason?.toLowerCase().includes("auth-required") || reason?.toLowerCase().includes("not authorized") || reason?.toLowerCase().includes("blocked: not authorized"))) { this.openEventPublishes.set(id, ep); } return; } case "CLOSED": { const so = this.openSubs.get(id); if (!so) return; so.onclosed(data[2]); return; } case "NOTICE": this.onNotice(data[1]); return; case "AUTH": { this.onAuthRequested(data[1]); return; } } } catch (error) { this.debug(`Error parsing message from ${this.ndkRelay.url}: ${error.message}`, error?.stack); return; } } /** * 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. */ async onAuthRequested(challenge) { const authPolicy = this.ndkRelay.authPolicy ?? this.ndk?.relayAuthDefaultPolicy; this.debug("Relay requested authentication", { havePolicy: !!authPol