applesauce-core 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +7 -13
  2. package/dist/event-store/{database.d.ts → event-set.d.ts} +36 -21
  3. package/dist/event-store/{database.js → event-set.js} +98 -67
  4. package/dist/event-store/event-store.d.ts +64 -25
  5. package/dist/event-store/event-store.js +164 -207
  6. package/dist/event-store/index.d.ts +1 -1
  7. package/dist/event-store/index.js +1 -1
  8. package/dist/event-store/interface.d.ts +71 -13
  9. package/dist/helpers/app-handlers.d.ts +23 -0
  10. package/dist/helpers/app-handlers.js +68 -0
  11. package/dist/helpers/article.d.ts +9 -0
  12. package/dist/helpers/article.js +21 -0
  13. package/dist/helpers/bolt11.d.ts +1 -0
  14. package/dist/helpers/bolt11.js +2 -0
  15. package/dist/helpers/bookmarks.js +1 -2
  16. package/dist/helpers/emoji.d.ts +10 -2
  17. package/dist/helpers/emoji.js +21 -3
  18. package/dist/helpers/encrypted-content-cache.d.ts +15 -0
  19. package/dist/helpers/encrypted-content-cache.js +125 -0
  20. package/dist/helpers/encrypted-content.d.ts +48 -0
  21. package/dist/helpers/encrypted-content.js +65 -0
  22. package/dist/helpers/encryption.d.ts +5 -0
  23. package/dist/helpers/encryption.js +10 -0
  24. package/dist/helpers/event.d.ts +5 -8
  25. package/dist/helpers/event.js +25 -11
  26. package/dist/helpers/expiration.d.ts +6 -0
  27. package/dist/helpers/expiration.js +16 -0
  28. package/dist/helpers/filter.d.ts +1 -3
  29. package/dist/helpers/filter.js +1 -3
  30. package/dist/helpers/gift-wraps.d.ts +17 -5
  31. package/dist/helpers/gift-wraps.js +65 -27
  32. package/dist/helpers/groups.d.ts +5 -0
  33. package/dist/helpers/groups.js +12 -2
  34. package/dist/helpers/hidden-content.d.ts +27 -32
  35. package/dist/helpers/hidden-content.js +35 -65
  36. package/dist/helpers/hidden-tags.d.ts +23 -4
  37. package/dist/helpers/hidden-tags.js +39 -4
  38. package/dist/helpers/index.d.ts +11 -1
  39. package/dist/helpers/index.js +11 -1
  40. package/dist/helpers/legacy-messages.d.ts +21 -0
  41. package/dist/helpers/legacy-messages.js +39 -0
  42. package/dist/helpers/lists.d.ts +3 -1
  43. package/dist/helpers/lists.js +9 -3
  44. package/dist/helpers/messages.d.ts +11 -0
  45. package/dist/helpers/messages.js +19 -0
  46. package/dist/helpers/mutes.js +1 -1
  47. package/dist/helpers/pointers.d.ts +33 -9
  48. package/dist/helpers/pointers.js +80 -44
  49. package/dist/helpers/profile.d.ts +10 -2
  50. package/dist/helpers/profile.js +33 -4
  51. package/dist/helpers/reactions.d.ts +8 -0
  52. package/dist/helpers/reactions.js +56 -0
  53. package/dist/helpers/reports.d.ts +28 -0
  54. package/dist/helpers/reports.js +38 -0
  55. package/dist/helpers/share.d.ts +10 -1
  56. package/dist/helpers/share.js +22 -8
  57. package/dist/helpers/url.d.ts +4 -0
  58. package/dist/helpers/url.js +20 -0
  59. package/dist/helpers/user-status.js +2 -1
  60. package/dist/helpers/wrapped-messages.d.ts +23 -0
  61. package/dist/helpers/wrapped-messages.js +38 -0
  62. package/dist/helpers/zap.d.ts +8 -5
  63. package/dist/helpers/zap.js +11 -6
  64. package/dist/index.d.ts +2 -2
  65. package/dist/index.js +2 -2
  66. package/dist/models/blossom.d.ts +3 -0
  67. package/dist/models/blossom.js +8 -0
  68. package/dist/models/bookmarks.d.ts +8 -0
  69. package/dist/{queries → models}/bookmarks.js +9 -9
  70. package/dist/models/channels.d.ts +11 -0
  71. package/dist/{queries → models}/channels.js +9 -9
  72. package/dist/models/comments.d.ts +4 -0
  73. package/dist/models/comments.js +11 -0
  74. package/dist/models/common.d.ts +16 -0
  75. package/dist/models/common.js +176 -0
  76. package/dist/models/contacts.d.ts +8 -0
  77. package/dist/{queries → models}/contacts.js +10 -10
  78. package/dist/models/encrypted-content.d.ts +4 -0
  79. package/dist/models/encrypted-content.js +11 -0
  80. package/dist/models/gift-wrap.d.ts +7 -0
  81. package/dist/models/gift-wrap.js +20 -0
  82. package/dist/{queries → models}/index.d.ts +6 -2
  83. package/dist/{queries → models}/index.js +6 -2
  84. package/dist/models/legacy-messages.d.ts +8 -0
  85. package/dist/models/legacy-messages.js +29 -0
  86. package/dist/models/mailboxes.d.ts +6 -0
  87. package/dist/{queries → models}/mailboxes.js +2 -2
  88. package/dist/models/mutes.d.ts +8 -0
  89. package/dist/{queries → models}/mutes.js +9 -9
  90. package/dist/models/pins.d.ts +4 -0
  91. package/dist/{queries → models}/pins.js +3 -3
  92. package/dist/models/profile.d.ts +4 -0
  93. package/dist/models/profile.js +14 -0
  94. package/dist/models/reactions.d.ts +4 -0
  95. package/dist/{queries → models}/reactions.js +2 -2
  96. package/dist/models/relays.d.ts +27 -0
  97. package/dist/{queries → models}/relays.js +13 -13
  98. package/dist/{queries → models}/thread.d.ts +6 -5
  99. package/dist/{queries → models}/thread.js +4 -3
  100. package/dist/models/user-status.d.ts +11 -0
  101. package/dist/{queries → models}/user-status.js +5 -5
  102. package/dist/models/wrapped-messages.d.ts +25 -0
  103. package/dist/models/wrapped-messages.js +61 -0
  104. package/dist/models/zaps.d.ts +9 -0
  105. package/dist/{queries → models}/zaps.js +11 -3
  106. package/dist/observable/claim-events.d.ts +3 -3
  107. package/dist/observable/claim-events.js +4 -4
  108. package/dist/observable/claim-latest.d.ts +3 -3
  109. package/dist/observable/claim-latest.js +4 -4
  110. package/dist/observable/index.d.ts +3 -1
  111. package/dist/observable/index.js +3 -1
  112. package/dist/observable/map-events-timeline.d.ts +7 -0
  113. package/dist/observable/map-events-timeline.js +9 -0
  114. package/dist/observable/map-events-to-store.d.ts +5 -0
  115. package/dist/observable/map-events-to-store.js +12 -0
  116. package/dist/observable/simple-timeout.d.ts +1 -0
  117. package/dist/observable/simple-timeout.js +1 -0
  118. package/dist/observable/watch-event-updates.d.ts +7 -0
  119. package/dist/observable/watch-event-updates.js +25 -0
  120. package/package.json +11 -16
  121. package/dist/__tests__/exports.test.d.ts +0 -1
  122. package/dist/__tests__/exports.test.js +0 -17
  123. package/dist/__tests__/fixtures.d.ts +0 -8
  124. package/dist/__tests__/fixtures.js +0 -20
  125. package/dist/event-store/__tests__/event-store.test.d.ts +0 -1
  126. package/dist/event-store/__tests__/event-store.test.js +0 -354
  127. package/dist/helpers/__tests__/blossom.test.d.ts +0 -1
  128. package/dist/helpers/__tests__/blossom.test.js +0 -13
  129. package/dist/helpers/__tests__/bookmarks.test.d.ts +0 -1
  130. package/dist/helpers/__tests__/bookmarks.test.js +0 -88
  131. package/dist/helpers/__tests__/comment.test.d.ts +0 -1
  132. package/dist/helpers/__tests__/comment.test.js +0 -249
  133. package/dist/helpers/__tests__/contacts.test.d.ts +0 -1
  134. package/dist/helpers/__tests__/contacts.test.js +0 -34
  135. package/dist/helpers/__tests__/emoji.test.d.ts +0 -1
  136. package/dist/helpers/__tests__/emoji.test.js +0 -15
  137. package/dist/helpers/__tests__/event.test.d.ts +0 -1
  138. package/dist/helpers/__tests__/event.test.js +0 -36
  139. package/dist/helpers/__tests__/events.test.d.ts +0 -1
  140. package/dist/helpers/__tests__/events.test.js +0 -32
  141. package/dist/helpers/__tests__/exports.test.d.ts +0 -1
  142. package/dist/helpers/__tests__/exports.test.js +0 -220
  143. package/dist/helpers/__tests__/file-metadata.test.d.ts +0 -1
  144. package/dist/helpers/__tests__/file-metadata.test.js +0 -103
  145. package/dist/helpers/__tests__/hidden-tags.test.d.ts +0 -1
  146. package/dist/helpers/__tests__/hidden-tags.test.js +0 -29
  147. package/dist/helpers/__tests__/mailboxes.test.d.ts +0 -1
  148. package/dist/helpers/__tests__/mailboxes.test.js +0 -81
  149. package/dist/helpers/__tests__/mutes.test.d.ts +0 -1
  150. package/dist/helpers/__tests__/mutes.test.js +0 -55
  151. package/dist/helpers/__tests__/nip-19.test.d.ts +0 -1
  152. package/dist/helpers/__tests__/nip-19.test.js +0 -42
  153. package/dist/helpers/__tests__/relays.test.d.ts +0 -1
  154. package/dist/helpers/__tests__/relays.test.js +0 -21
  155. package/dist/helpers/__tests__/tags.test.d.ts +0 -1
  156. package/dist/helpers/__tests__/tags.test.js +0 -24
  157. package/dist/helpers/__tests__/threading.test.d.ts +0 -1
  158. package/dist/helpers/__tests__/threading.test.js +0 -41
  159. package/dist/helpers/direct-messages.d.ts +0 -4
  160. package/dist/helpers/direct-messages.js +0 -5
  161. package/dist/helpers/nip-19.d.ts +0 -18
  162. package/dist/helpers/nip-19.js +0 -56
  163. package/dist/observable/__tests__/claim-events.test.d.ts +0 -1
  164. package/dist/observable/__tests__/claim-events.test.js +0 -23
  165. package/dist/observable/__tests__/claim-latest.test.d.ts +0 -1
  166. package/dist/observable/__tests__/claim-latest.test.js +0 -37
  167. package/dist/observable/__tests__/exports.test.d.ts +0 -1
  168. package/dist/observable/__tests__/exports.test.js +0 -18
  169. package/dist/observable/__tests__/listen-latest-updates.test.d.ts +0 -1
  170. package/dist/observable/__tests__/listen-latest-updates.test.js +0 -55
  171. package/dist/observable/__tests__/simple-timeout.test.d.ts +0 -1
  172. package/dist/observable/__tests__/simple-timeout.test.js +0 -34
  173. package/dist/observable/listen-latest-updates.d.ts +0 -5
  174. package/dist/observable/listen-latest-updates.js +0 -12
  175. package/dist/promise/__tests__/exports.test.d.ts +0 -1
  176. package/dist/promise/__tests__/exports.test.js +0 -11
  177. package/dist/queries/__tests__/exports.test.d.ts +0 -1
  178. package/dist/queries/__tests__/exports.test.js +0 -41
  179. package/dist/queries/blossom.d.ts +0 -2
  180. package/dist/queries/blossom.js +0 -5
  181. package/dist/queries/bookmarks.d.ts +0 -8
  182. package/dist/queries/channels.d.ts +0 -11
  183. package/dist/queries/comments.d.ts +0 -4
  184. package/dist/queries/comments.js +0 -11
  185. package/dist/queries/contacts.d.ts +0 -8
  186. package/dist/queries/mailboxes.d.ts +0 -6
  187. package/dist/queries/mutes.d.ts +0 -8
  188. package/dist/queries/pins.d.ts +0 -4
  189. package/dist/queries/profile.d.ts +0 -4
  190. package/dist/queries/profile.js +0 -7
  191. package/dist/queries/reactions.d.ts +0 -4
  192. package/dist/queries/relays.d.ts +0 -27
  193. package/dist/queries/simple.d.ts +0 -16
  194. package/dist/queries/simple.js +0 -21
  195. package/dist/queries/user-status.d.ts +0 -11
  196. package/dist/queries/zaps.d.ts +0 -5
  197. package/dist/query-store/__tests__/exports.test.d.ts +0 -1
  198. package/dist/query-store/__tests__/exports.test.js +0 -12
  199. package/dist/query-store/__tests__/query-store.test.d.ts +0 -1
  200. package/dist/query-store/__tests__/query-store.test.js +0 -63
  201. package/dist/query-store/index.d.ts +0 -1
  202. package/dist/query-store/index.js +0 -1
  203. package/dist/query-store/query-store.d.ts +0 -54
  204. package/dist/query-store/query-store.js +0 -102
@@ -0,0 +1,68 @@
1
+ import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, } from "nostr-tools/nip19";
2
+ import { processTags } from "./tags.js";
3
+ import { getDisplayName, getProfileContent, getProfilePicture, isValidProfile } from "./profile.js";
4
+ import { isAddressPointer, isEventPointer } from "./pointers.js";
5
+ /** Returns an array of supported kinds for a given handler */
6
+ export function getHandlerSupportedKinds(handler) {
7
+ return processTags(handler.tags, (t) => (t[0] === "k" && t[1] ? parseInt(t[1]) : undefined));
8
+ }
9
+ /** Returns the name of the handler */
10
+ export function getHandlerName(handler) {
11
+ return getDisplayName(handler);
12
+ }
13
+ /** Returns the picture of the handler */
14
+ export function getHandlerPicture(handler, fallback) {
15
+ if (!isValidProfile(handler))
16
+ return fallback;
17
+ return getProfilePicture(handler, fallback);
18
+ }
19
+ /** Returns the description of the handler */
20
+ export function getHandlerDescription(handler) {
21
+ if (!isValidProfile(handler))
22
+ return;
23
+ return getProfileContent(handler).about;
24
+ }
25
+ /** Returns the web link template for a handler and type */
26
+ export function getHandlerLinkTemplate(handler, platform = "web", type) {
27
+ // Get all tags for this platform
28
+ const tags = handler.tags.filter((t) => t[0] === platform);
29
+ // Find the tag for this type, otherwise use default
30
+ if (type)
31
+ return tags.find((t) => t[2] === type)?.[1];
32
+ else
33
+ return tags.find((t) => t[2] === undefined || t[2] === "")?.[1];
34
+ }
35
+ /** Returns a link for a Profile Pointer */
36
+ export function createHandlerProfileLink(handler, pointer, platform = "web") {
37
+ // First attempt to use a nprofile link, then fallback to npub
38
+ return (getHandlerLinkTemplate(handler, platform, "nprofile")?.replace("<bech32>", nprofileEncode(pointer)) ||
39
+ getHandlerLinkTemplate(handler, platform, "npub")?.replace("<bech32>", npubEncode(pointer.pubkey)) ||
40
+ getHandlerLinkTemplate(handler, platform)?.replace("<bech32>", nprofileEncode(pointer)));
41
+ }
42
+ /** Returns a link for an Event Pointer */
43
+ export function createHandlerEventLink(handler, pointer, platform = "web") {
44
+ // First attempt to use a nevent link, then fallback to note
45
+ return (getHandlerLinkTemplate(handler, platform, "nevent")?.replace("<bech32>", neventEncode(pointer)) ||
46
+ getHandlerLinkTemplate(handler, platform, "note")?.replace("<bech32>", noteEncode(pointer.id)) ||
47
+ getHandlerLinkTemplate(handler, platform)?.replace("<bech32>", neventEncode(pointer)));
48
+ }
49
+ /** Returns a link for an Address Pointer */
50
+ export function createHandlerAddressLink(handler, pointer, platform = "web") {
51
+ // First attempt to use a nevent link, then fallback to note
52
+ return (getHandlerLinkTemplate(handler, platform, "naddr")?.replace("<bech32>", naddrEncode(pointer)) ||
53
+ getHandlerLinkTemplate(handler, platform)?.replace("<bech32>", naddrEncode(pointer)));
54
+ }
55
+ /** Creates a handler link for a pointer and optionally fallsback to a web link */
56
+ export function createHandlerLink(handler, pointer, platform, webFallback = true) {
57
+ let link = undefined;
58
+ if (isEventPointer(pointer))
59
+ link = createHandlerEventLink(handler, pointer, platform);
60
+ else if (isAddressPointer(pointer))
61
+ link = createHandlerAddressLink(handler, pointer, platform);
62
+ else
63
+ link = createHandlerProfileLink(handler, pointer, platform);
64
+ // Fallback to a web link if one cant be found for the platform
65
+ if (!link && platform && platform !== "web" && webFallback)
66
+ link = createHandlerLink(handler, pointer, "web");
67
+ return link;
68
+ }
@@ -0,0 +1,9 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ /** Returns an articles title, if it exists */
3
+ export declare function getArticleTitle(article: NostrEvent): string | undefined;
4
+ /** Returns an articles image, if it exists */
5
+ export declare function getArticleImage(article: NostrEvent): string | undefined;
6
+ /** Returns an articles summary, if it exists */
7
+ export declare function getArticleSummary(article: NostrEvent): string | undefined;
8
+ /** Returns an articles published date, if it exists */
9
+ export declare function getArticlePublished(article: NostrEvent): number;
@@ -0,0 +1,21 @@
1
+ import { getTagValue } from "./event.js";
2
+ /** Returns an articles title, if it exists */
3
+ export function getArticleTitle(article) {
4
+ return getTagValue(article, "title");
5
+ }
6
+ /** Returns an articles image, if it exists */
7
+ export function getArticleImage(article) {
8
+ return getTagValue(article, "image");
9
+ }
10
+ /** Returns an articles summary, if it exists */
11
+ export function getArticleSummary(article) {
12
+ return getTagValue(article, "summary");
13
+ }
14
+ /** Returns an articles published date, if it exists */
15
+ export function getArticlePublished(article) {
16
+ const ts = getTagValue(article, "published_at");
17
+ if (ts && !Number.isNaN(parseInt(ts)))
18
+ return parseInt(ts);
19
+ else
20
+ return article.created_at;
21
+ }
@@ -4,6 +4,7 @@ export type ParsedInvoice = {
4
4
  amount?: number;
5
5
  timestamp: number;
6
6
  expiry: number;
7
+ paymentHash?: string;
7
8
  };
8
9
  /** Parses a lightning invoice */
9
10
  export declare function parseBolt11(paymentRequest: string): ParsedInvoice;
@@ -5,11 +5,13 @@ export function parseBolt11(paymentRequest) {
5
5
  const timestamp = decoded.sections.find((s) => s.name === "timestamp")?.value ?? 0;
6
6
  const description = decoded.sections.find((s) => s.name === "description")?.value ?? "";
7
7
  const amount = parseInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
8
+ const paymentHash = decoded.sections.find((s) => s.name === "payment_hash");
8
9
  return {
9
10
  paymentRequest: decoded.paymentRequest,
10
11
  description: description,
11
12
  amount: amount,
12
13
  timestamp: timestamp,
13
14
  expiry: timestamp + decoded.expiry,
15
+ paymentHash: paymentHash?.value,
14
16
  };
15
17
  }
@@ -1,8 +1,7 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
3
  import { getHiddenTags, isHiddenTagsLocked } from "./index.js";
4
- import { mergeAddressPointers, mergeEventPointers } from "./nip-19.js";
5
- import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag } from "./pointers.js";
4
+ import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag, mergeAddressPointers, mergeEventPointers, } from "./pointers.js";
6
5
  export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
7
6
  export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
8
7
  /** Parses an array of tags into a {@link Bookmarks} object */
@@ -1,6 +1,12 @@
1
- import { EventTemplate, NostrEvent } from "nostr-tools";
1
+ import { NostrEvent } from "nostr-tools";
2
2
  /** Gets an "emoji" tag that matches an emoji code */
3
- export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string] | undefined;
3
+ export declare function getEmojiTag(tags: {
4
+ tags: string[][];
5
+ } | string[][], code: string): ["emoji", string, string] | undefined;
6
+ /** Gets an emoji for a shortcode from an array of tags or event */
7
+ export declare function getEmojiFromTags(event: {
8
+ tags: string[][];
9
+ } | string[][], code: string): Emoji | undefined;
4
10
  /** Returns the name of a NIP-30 emoji pack */
5
11
  export declare function getPackName(pack: NostrEvent): string | undefined;
6
12
  export type Emoji = {
@@ -11,3 +17,5 @@ export type Emoji = {
11
17
  };
12
18
  /** Returns an array of emojis from a NIP-30 emoji pack */
13
19
  export declare function getEmojis(pack: NostrEvent): Emoji[];
20
+ /** Returns the custom emoji for a reaction event */
21
+ export declare function getReactionEmoji(event: NostrEvent): Emoji | undefined;
@@ -1,8 +1,18 @@
1
1
  import { getTagValue } from "./event.js";
2
2
  /** Gets an "emoji" tag that matches an emoji code */
3
- export function getEmojiTag(event, code) {
4
- code = code.replace(/^:|:$/g, "").toLocaleLowerCase();
5
- return event.tags.find((t) => t[0] === "emoji" && t.length >= 3 && t[1].toLowerCase() === code);
3
+ export function getEmojiTag(tags, code) {
4
+ code = code.replace(/^:|:$/g, "").toLowerCase();
5
+ return (Array.isArray(tags) ? tags : tags.tags).find((t) => t[0] === "emoji" && t.length >= 3 && t[1].toLowerCase() === code);
6
+ }
7
+ /** Gets an emoji for a shortcode from an array of tags or event */
8
+ export function getEmojiFromTags(event, code) {
9
+ const tag = getEmojiTag(event, code);
10
+ if (!tag)
11
+ return undefined;
12
+ return {
13
+ shortcode: tag[1],
14
+ url: tag[2],
15
+ };
6
16
  }
7
17
  /** Returns the name of a NIP-30 emoji pack */
8
18
  export function getPackName(pack) {
@@ -14,3 +24,11 @@ export function getEmojis(pack) {
14
24
  .filter((t) => t[0] === "emoji" && t[1] && t[2])
15
25
  .map((t) => ({ shortcode: t[1], url: t[2] }));
16
26
  }
27
+ /** Returns the custom emoji for a reaction event */
28
+ export function getReactionEmoji(event) {
29
+ // Trim and strip colons
30
+ const shortcode = /^:+(.+?):+$/g.exec(event.content.trim().toLowerCase())?.[1];
31
+ if (!shortcode)
32
+ return undefined;
33
+ return getEmojiFromTags(event, shortcode);
34
+ }
@@ -0,0 +1,15 @@
1
+ import { Observable } from "rxjs";
2
+ import { IEventStoreStreams } from "../event-store/interface.js";
3
+ /** A symbol that is used to mark encrypted content as being from a cache */
4
+ export declare const EncryptedContentFromCacheSymbol: unique symbol;
5
+ /** An interface that is used to cache encrypted content on events */
6
+ export interface EncryptedContentCache {
7
+ getItem: (key: string) => Promise<string | null>;
8
+ setItem: (key: string, value: string) => Promise<any>;
9
+ }
10
+ /** Marks the encrypted content as being from a cache */
11
+ export declare function markEncryptedContentFromCache<T extends object>(event: T): void;
12
+ /** Checks if the encrypted content is from a cache */
13
+ export declare function isEncryptedContentFromCache<T extends object>(event: T): boolean;
14
+ /** Starts a process that persists and restores all encrypted content */
15
+ export declare function persistEncryptedContent(eventStore: IEventStoreStreams, storage: EncryptedContentCache | Observable<EncryptedContentCache>): () => void;
@@ -0,0 +1,125 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { catchError, combineLatest, distinct, EMPTY, filter, isObservable, map, merge, mergeMap, of, switchMap, } from "rxjs";
3
+ import { logger } from "../logger.js";
4
+ import { canHaveEncryptedContent, getEncryptedContent, isEncryptedContentLocked, setEncryptedContentCache, } from "./encrypted-content.js";
5
+ import { notifyEventUpdate } from "./event.js";
6
+ import { getGiftWrapSeal } from "./gift-wraps.js";
7
+ /** A symbol that is used to mark encrypted content as being from a cache */
8
+ export const EncryptedContentFromCacheSymbol = Symbol.for("encrypted-content-from-cache");
9
+ /** Marks the encrypted content as being from a cache */
10
+ export function markEncryptedContentFromCache(event) {
11
+ Reflect.set(event, EncryptedContentFromCacheSymbol, true);
12
+ }
13
+ /** Checks if the encrypted content is from a cache */
14
+ export function isEncryptedContentFromCache(event) {
15
+ return Reflect.has(event, EncryptedContentFromCacheSymbol);
16
+ }
17
+ const log = logger.extend("EncryptedContentCache");
18
+ /** Starts a process that persists and restores all encrypted content */
19
+ export function persistEncryptedContent(eventStore, storage) {
20
+ const storage$ = isObservable(storage) ? storage : of(storage);
21
+ // Restore encrypted content when it is inserted
22
+ const restore = eventStore.insert$
23
+ .pipe(
24
+ // Look for events that support encrypted content and are locked
25
+ filter((e) => canHaveEncryptedContent(e.kind) && isEncryptedContentLocked(e)),
26
+ // Get the encrypted content from storage
27
+ mergeMap((event) =>
28
+ // Wait for storage to be available
29
+ storage$.pipe(switchMap((storage) => combineLatest([of(event), storage.getItem(event.id)])), catchError((error) => {
30
+ log(`Failed to restore encrypted content for ${event.id}`, error);
31
+ return EMPTY;
32
+ }))))
33
+ .subscribe(async ([event, content]) => {
34
+ if (!content)
35
+ return;
36
+ // Restore the encrypted content and set it as from a cache
37
+ markEncryptedContentFromCache(event);
38
+ setEncryptedContentCache(event, content);
39
+ log(`Restored encrypted content for ${event.id}`);
40
+ });
41
+ // Restore seals when they are unlocked
42
+ const restoreSeals = eventStore.update$
43
+ .pipe(
44
+ // Look for gift wraps that are unlocked
45
+ filter((e) => e.kind === kinds.GiftWrap && !isEncryptedContentLocked(e)),
46
+ // Get the seal event
47
+ map((gift) => [gift, getGiftWrapSeal(gift)]),
48
+ // Look for gift wraps with locked seals
49
+ filter(([_gift, seal]) => seal !== undefined && isEncryptedContentLocked(seal)),
50
+ // Only attempt to unlock seals once
51
+ distinct(([_gift, seal]) => seal.id),
52
+ // Get encrypted content from storage
53
+ mergeMap(([gift, seal]) =>
54
+ // Wait for storage to be available
55
+ storage$.pipe(switchMap((storage) => combineLatest([of(gift), of(seal), storage.getItem(seal.id)])), catchError((error) => {
56
+ log(`Failed to restore encrypted content for ${seal.id}`, error);
57
+ return EMPTY;
58
+ }))))
59
+ .subscribe(async ([gift, seal, content]) => {
60
+ if (!seal || !content)
61
+ return;
62
+ markEncryptedContentFromCache(seal);
63
+ setEncryptedContentCache(seal, content);
64
+ // Trigger an update to the gift wrap event
65
+ notifyEventUpdate(gift);
66
+ log(`Restored encrypted content for ${seal.id}`);
67
+ });
68
+ // Persist encrypted content when it is updated or inserted
69
+ const persist = combineLatest([merge(eventStore.update$, eventStore.insert$), storage$])
70
+ .pipe(
71
+ // Look for events that support encrypted content and are unlocked and not from the cache
72
+ filter(([event]) => canHaveEncryptedContent(event.kind) &&
73
+ !isEncryptedContentLocked(event) &&
74
+ !isEncryptedContentFromCache(event)),
75
+ // Only persist the encrypted content once
76
+ distinct(([event]) => event.id))
77
+ .subscribe(async ([event, storage]) => {
78
+ try {
79
+ const content = getEncryptedContent(event);
80
+ if (content) {
81
+ await storage.setItem(event.id, content);
82
+ log(`Persisted encrypted content for ${event.id}`);
83
+ }
84
+ }
85
+ catch (error) {
86
+ // Ignore errors when saving encrypted content
87
+ log(`Failed to persist encrypted content for ${event.id}`, error);
88
+ }
89
+ });
90
+ // Persist seals when the gift warp is unlocked or inserted unlocked
91
+ // This relies on the gift wrap event being updated when a seal is unlocked
92
+ const persistSeals = combineLatest([merge(eventStore.update$, eventStore.insert$), storage$])
93
+ .pipe(
94
+ // Look for gift wraps that are unlocked
95
+ filter(([event]) => event.kind === kinds.GiftWrap && !isEncryptedContentLocked(event)),
96
+ // Get the seal event
97
+ map(([gift, storage]) => [gift, getGiftWrapSeal(gift), storage]),
98
+ // Make sure the seal is defined
99
+ filter(([_gift, seal]) => seal !== undefined),
100
+ // Make sure seal is unlocked and not from cache
101
+ filter(([_gift, seal]) => !isEncryptedContentLocked(seal) && !isEncryptedContentFromCache(seal)),
102
+ // Only persist the seal once
103
+ distinct(([seal]) => seal.id))
104
+ .subscribe(async ([_gift, seal, storage]) => {
105
+ if (!seal)
106
+ return;
107
+ try {
108
+ const content = getEncryptedContent(seal);
109
+ if (content) {
110
+ await storage.setItem(seal.id, content);
111
+ log(`Persisted encrypted content for ${seal.id}`);
112
+ }
113
+ }
114
+ catch (error) {
115
+ // Ignore errors when saving encrypted content
116
+ log(`Failed to persist encrypted content for ${seal.id}`, error);
117
+ }
118
+ });
119
+ return () => {
120
+ restore.unsubscribe();
121
+ persist.unsubscribe();
122
+ restoreSeals.unsubscribe();
123
+ persistSeals.unsubscribe();
124
+ };
125
+ }
@@ -0,0 +1,48 @@
1
+ /** A symbol use to store the encrypted content of an event in memory */
2
+ export declare const EncryptedContentSymbol: unique symbol;
3
+ export interface EncryptedContentSigner {
4
+ nip04?: {
5
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
6
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
7
+ };
8
+ nip44?: {
9
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
10
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
11
+ };
12
+ }
13
+ /** Various event kinds that can have encrypted content and which encryption method they use */
14
+ export declare const EventContentEncryptionMethod: Record<number, "nip04" | "nip44">;
15
+ /** Sets the encryption method that is used for the contents of a specific event kind */
16
+ export declare function setEncryptedContentEncryptionMethod(kind: number, method: "nip04" | "nip44"): number;
17
+ /** Returns either nip04 or nip44 encryption methods depending on event kind */
18
+ export declare function getEncryptedContentEncryptionMethods(kind: number, signer: EncryptedContentSigner): {
19
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
20
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
21
+ } | {
22
+ encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
23
+ decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
24
+ };
25
+ /** Checks if an event can have encrypted content */
26
+ export declare function canHaveEncryptedContent(kind: number): boolean;
27
+ /** Checks if an event has encrypted content */
28
+ export declare function hasEncryptedContent<T extends {
29
+ content: string;
30
+ }>(event: T): boolean;
31
+ /** Returns the encrypted content for an event if it is unlocked */
32
+ export declare function getEncryptedContent<T extends object>(event: T): string | undefined;
33
+ /** Checks if the encrypted content is locked */
34
+ export declare function isEncryptedContentLocked<T extends object>(event: T): boolean;
35
+ /**
36
+ * Unlocks the encrypted content in an event and caches it
37
+ * @param event The event with content to decrypt
38
+ * @param pubkey The other pubkey that encrypted the content
39
+ * @param signer A signer to use to decrypt the content
40
+ */
41
+ export declare function unlockEncryptedContent<T extends {
42
+ kind: number;
43
+ content: string;
44
+ }>(event: T, pubkey: string, signer: EncryptedContentSigner): Promise<string>;
45
+ /** Sets the encrypted content on an event and updates it if its part of an event store */
46
+ export declare function setEncryptedContentCache<T extends object>(event: T, plaintext: string): void;
47
+ /** Removes the encrypted content cache on an event */
48
+ export declare function lockEncryptedContent<T extends object>(event: T): void;
@@ -0,0 +1,65 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { isEvent, notifyEventUpdate } from "./event.js";
3
+ /** A symbol use to store the encrypted content of an event in memory */
4
+ export const EncryptedContentSymbol = Symbol.for("encrypted-content");
5
+ /** Various event kinds that can have encrypted content and which encryption method they use */
6
+ export const EventContentEncryptionMethod = {
7
+ [kinds.EncryptedDirectMessage]: "nip04",
8
+ [kinds.Seal]: "nip44",
9
+ [kinds.GiftWrap]: "nip44",
10
+ };
11
+ /** Sets the encryption method that is used for the contents of a specific event kind */
12
+ export function setEncryptedContentEncryptionMethod(kind, method) {
13
+ EventContentEncryptionMethod[kind] = method;
14
+ return kind;
15
+ }
16
+ /** Returns either nip04 or nip44 encryption methods depending on event kind */
17
+ export function getEncryptedContentEncryptionMethods(kind, signer) {
18
+ const method = EventContentEncryptionMethod[kind];
19
+ const encryption = signer[method];
20
+ if (!encryption)
21
+ throw new Error(`Signer does not support ${method} encryption`);
22
+ return encryption;
23
+ }
24
+ /** Checks if an event can have encrypted content */
25
+ export function canHaveEncryptedContent(kind) {
26
+ return EventContentEncryptionMethod[kind] !== undefined;
27
+ }
28
+ /** Checks if an event has encrypted content */
29
+ export function hasEncryptedContent(event) {
30
+ return event.content.length > 0;
31
+ }
32
+ /** Returns the encrypted content for an event if it is unlocked */
33
+ export function getEncryptedContent(event) {
34
+ return Reflect.get(event, EncryptedContentSymbol);
35
+ }
36
+ /** Checks if the encrypted content is locked */
37
+ export function isEncryptedContentLocked(event) {
38
+ return Reflect.has(event, EncryptedContentSymbol) === false;
39
+ }
40
+ /**
41
+ * Unlocks the encrypted content in an event and caches it
42
+ * @param event The event with content to decrypt
43
+ * @param pubkey The other pubkey that encrypted the content
44
+ * @param signer A signer to use to decrypt the content
45
+ */
46
+ export async function unlockEncryptedContent(event, pubkey, signer) {
47
+ const encryption = getEncryptedContentEncryptionMethods(event.kind, signer);
48
+ const plaintext = await encryption.decrypt(pubkey, event.content);
49
+ setEncryptedContentCache(event, plaintext);
50
+ return plaintext;
51
+ }
52
+ /** Sets the encrypted content on an event and updates it if its part of an event store */
53
+ export function setEncryptedContentCache(event, plaintext) {
54
+ Reflect.set(event, EncryptedContentSymbol, plaintext);
55
+ // if the event has been added to an event store, notify it
56
+ if (isEvent(event))
57
+ notifyEventUpdate(event);
58
+ }
59
+ /** Removes the encrypted content cache on an event */
60
+ export function lockEncryptedContent(event) {
61
+ Reflect.deleteProperty(event, EncryptedContentSymbol);
62
+ // if the event has been added to an event store, notify it
63
+ if (isEvent(event))
64
+ notifyEventUpdate(event);
65
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Checks if a string is encrypted with NIP-04 or NIP-44
3
+ * @see https://github.com/nostr-protocol/nips/pull/1248#issuecomment-2437731316
4
+ */
5
+ export declare function isNIP04Encrypted(ciphertext: string): boolean;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Checks if a string is encrypted with NIP-04 or NIP-44
3
+ * @see https://github.com/nostr-protocol/nips/pull/1248#issuecomment-2437731316
4
+ */
5
+ export function isNIP04Encrypted(ciphertext) {
6
+ const l = ciphertext.length;
7
+ if (l < 28)
8
+ return false;
9
+ return (ciphertext[l - 28] == "?" && ciphertext[l - 27] == "i" && ciphertext[l - 26] == "v" && ciphertext[l - 25] == "=");
10
+ }
@@ -1,16 +1,10 @@
1
1
  import { NostrEvent, VerifiedEvent } from "nostr-tools";
2
2
  import { IEventStore } from "../event-store/interface.js";
3
3
  export declare const EventUIDSymbol: unique symbol;
4
+ export declare const ReplaceableAddressSymbol: unique symbol;
4
5
  export declare const EventIndexableTagsSymbol: unique symbol;
5
6
  export declare const FromCacheSymbol: unique symbol;
6
7
  export declare const ReplaceableIdentifierSymbol: unique symbol;
7
- declare module "nostr-tools" {
8
- interface Event {
9
- [EventUIDSymbol]?: string;
10
- [EventIndexableTagsSymbol]?: Set<string>;
11
- [FromCacheSymbol]?: boolean;
12
- }
13
- }
14
8
  /**
15
9
  * Checks if an object is a nostr event
16
10
  * NOTE: does not validation the signature on the event
@@ -44,7 +38,6 @@ export declare function getTagValue<T extends {
44
38
  kind: number;
45
39
  tags: string[][];
46
40
  content: string;
47
- pubkey: string;
48
41
  }>(event: T, name: string): string | undefined;
49
42
  /** Sets events verified flag without checking anything */
50
43
  export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
@@ -54,8 +47,12 @@ export declare function markFromCache(event: NostrEvent): void;
54
47
  export declare function isFromCache(event: NostrEvent): boolean;
55
48
  /** Returns the EventStore of an event if its been added to one */
56
49
  export declare function getParentEventStore<T extends object>(event: T): IEventStore | undefined;
50
+ /** Notifies the events parent store that an event has been updated */
51
+ export declare function notifyEventUpdate(event: NostrEvent): void;
57
52
  /**
58
53
  * Returns the replaceable identifier for a replaceable event
59
54
  * @throws
60
55
  */
61
56
  export declare function getReplaceableIdentifier(event: NostrEvent): string;
57
+ /** Checks if an event is a NIP-70 protected event */
58
+ export declare function isProtectedEvent(event: NostrEvent): boolean;
@@ -1,10 +1,11 @@
1
1
  import { verifiedSymbol } from "nostr-tools";
2
- import { INDEXABLE_TAGS } from "../event-store/common.js";
3
- import { getHiddenTags } from "./hidden-tags.js";
4
- import { getOrComputeCachedValue } from "./cache.js";
5
2
  import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
3
+ import { INDEXABLE_TAGS } from "../event-store/common.js";
6
4
  import { EventStoreSymbol } from "../event-store/event-store.js";
5
+ import { getOrComputeCachedValue } from "./cache.js";
6
+ import { getHiddenTags } from "./hidden-tags.js";
7
7
  export const EventUIDSymbol = Symbol.for("event-uid");
8
+ export const ReplaceableAddressSymbol = Symbol.for("replaceable-address");
8
9
  export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
9
10
  export const FromCacheSymbol = Symbol.for("from-cache");
10
11
  export const ReplaceableIdentifierSymbol = Symbol.for("replaceable-identifier");
@@ -38,13 +39,13 @@ export function isReplaceable(kind) {
38
39
  * For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d )
39
40
  */
40
41
  export function getEventUID(event) {
41
- let uid = event[EventUIDSymbol];
42
+ let uid = Reflect.get(event, EventUIDSymbol);
42
43
  if (!uid) {
43
44
  if (isReplaceable(event.kind))
44
45
  uid = getReplaceableAddress(event);
45
46
  else
46
47
  uid = event.id;
47
- event[EventUIDSymbol] = uid;
48
+ Reflect.set(event, EventUIDSymbol, uid);
48
49
  }
49
50
  return uid;
50
51
  }
@@ -52,8 +53,10 @@ export function getEventUID(event) {
52
53
  export function getReplaceableAddress(event) {
53
54
  if (!isReplaceable(event.kind))
54
55
  throw new Error("Event is not replaceable or addressable");
55
- const identifier = isAddressableKind(event.kind) ? getReplaceableIdentifier(event) : undefined;
56
- return createReplaceableAddress(event.kind, event.pubkey, identifier);
56
+ return getOrComputeCachedValue(event, ReplaceableAddressSymbol, () => {
57
+ const identifier = isAddressableKind(event.kind) ? getReplaceableIdentifier(event) : undefined;
58
+ return createReplaceableAddress(event.kind, event.pubkey, identifier);
59
+ });
57
60
  }
58
61
  /** Creates a replaceable event address from a kind, pubkey, and identifier */
59
62
  export function createReplaceableAddress(kind, pubkey, identifier) {
@@ -63,7 +66,7 @@ export function createReplaceableAddress(kind, pubkey, identifier) {
63
66
  export const getReplaceableUID = createReplaceableAddress;
64
67
  /** Returns a Set of tag names and values that are indexable */
65
68
  export function getIndexableTags(event) {
66
- let indexable = event[EventIndexableTagsSymbol];
69
+ let indexable = Reflect.get(event, EventIndexableTagsSymbol);
67
70
  if (!indexable) {
68
71
  const tags = new Set();
69
72
  for (const tag of event.tags) {
@@ -71,7 +74,8 @@ export function getIndexableTags(event) {
71
74
  tags.add(tag[0] + ":" + tag[1]);
72
75
  }
73
76
  }
74
- indexable = event[EventIndexableTagsSymbol] = tags;
77
+ indexable = tags;
78
+ Reflect.set(event, EventIndexableTagsSymbol, tags);
75
79
  }
76
80
  return indexable;
77
81
  }
@@ -93,16 +97,22 @@ export function fakeVerifyEvent(event) {
93
97
  }
94
98
  /** Marks an event as being from a cache */
95
99
  export function markFromCache(event) {
96
- event[FromCacheSymbol] = true;
100
+ Reflect.set(event, FromCacheSymbol, true);
97
101
  }
98
102
  /** Returns if an event was from a cache */
99
103
  export function isFromCache(event) {
100
- return !!event[FromCacheSymbol];
104
+ return Reflect.get(event, FromCacheSymbol) === true;
101
105
  }
102
106
  /** Returns the EventStore of an event if its been added to one */
103
107
  export function getParentEventStore(event) {
104
108
  return Reflect.get(event, EventStoreSymbol);
105
109
  }
110
+ /** Notifies the events parent store that an event has been updated */
111
+ export function notifyEventUpdate(event) {
112
+ const eventStore = getParentEventStore(event);
113
+ if (eventStore)
114
+ eventStore.update(event);
115
+ }
106
116
  /**
107
117
  * Returns the replaceable identifier for a replaceable event
108
118
  * @throws
@@ -117,3 +127,7 @@ export function getReplaceableIdentifier(event) {
117
127
  return d;
118
128
  });
119
129
  }
130
+ /** Checks if an event is a NIP-70 protected event */
131
+ export function isProtectedEvent(event) {
132
+ return event.tags.some((t) => t[0] === "-");
133
+ }
@@ -0,0 +1,6 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const ExpirationTimestampSymbol: unique symbol;
3
+ /** Returns the NIP-40 expiration timestamp for an event */
4
+ export declare function getExpirationTimestamp(event: NostrEvent): number | undefined;
5
+ /** Checks if an event has expired based on the NIP-40 expiration timestamp */
6
+ export declare function isExpired(event: NostrEvent): boolean;
@@ -0,0 +1,16 @@
1
+ import { getTagValue } from "./event.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { unixNow } from "./time.js";
4
+ export const ExpirationTimestampSymbol = Symbol("expiration-timestamp");
5
+ /** Returns the NIP-40 expiration timestamp for an event */
6
+ export function getExpirationTimestamp(event) {
7
+ return getOrComputeCachedValue(event, ExpirationTimestampSymbol, () => {
8
+ const expiration = getTagValue(event, "expiration");
9
+ return expiration ? parseInt(expiration) : undefined;
10
+ });
11
+ }
12
+ /** Checks if an event has expired based on the NIP-40 expiration timestamp */
13
+ export function isExpired(event) {
14
+ const expiration = getExpirationTimestamp(event);
15
+ return expiration ? unixNow() > expiration : false;
16
+ }
@@ -6,9 +6,7 @@ import { Filter, NostrEvent } from "nostr-tools";
6
6
  export declare function matchFilter(filter: Filter, event: NostrEvent): boolean;
7
7
  /** Copied from nostr-tools */
8
8
  export declare function matchFilters(filters: Filter[], event: NostrEvent): boolean;
9
- /**
10
- * Copied from nostr-tools and modified to support undefined
11
- */
9
+ /** Copied from nostr-tools and modified to support undefined */
12
10
  export declare function mergeFilters(...filters: Filter[]): Filter;
13
11
  /** Check if two filters are equal */
14
12
  export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
@@ -40,9 +40,7 @@ export function matchFilters(filters, event) {
40
40
  }
41
41
  return false;
42
42
  }
43
- /**
44
- * Copied from nostr-tools and modified to support undefined
45
- */
43
+ /** Copied from nostr-tools and modified to support undefined */
46
44
  export function mergeFilters(...filters) {
47
45
  let result = {};
48
46
  for (let i = 0; i < filters.length; i++) {