applesauce-core 3.1.0 → 4.0.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 (88) hide show
  1. package/dist/event-store/async-event-store.d.ts +134 -0
  2. package/dist/event-store/async-event-store.js +349 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +15 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +43 -53
  5. package/dist/event-store/event-store.d.ts +57 -63
  6. package/dist/event-store/event-store.js +111 -190
  7. package/dist/event-store/index.d.ts +2 -1
  8. package/dist/event-store/index.js +2 -1
  9. package/dist/event-store/interface.d.ts +111 -38
  10. package/dist/event-store/model-mixin.d.ts +59 -0
  11. package/dist/event-store/model-mixin.js +147 -0
  12. package/dist/helpers/app-data.d.ts +39 -0
  13. package/dist/helpers/app-data.js +68 -0
  14. package/dist/helpers/bookmarks.d.ts +11 -1
  15. package/dist/helpers/bookmarks.js +29 -4
  16. package/dist/helpers/comment.d.ts +13 -20
  17. package/dist/helpers/comment.js +16 -27
  18. package/dist/helpers/contacts.d.ts +10 -1
  19. package/dist/helpers/contacts.js +30 -3
  20. package/dist/helpers/encrypted-content-cache.js +7 -7
  21. package/dist/helpers/encrypted-content.d.ts +9 -2
  22. package/dist/helpers/encrypted-content.js +12 -9
  23. package/dist/helpers/event-cache.d.ts +3 -1
  24. package/dist/helpers/event-cache.js +3 -1
  25. package/dist/helpers/event-tags.d.ts +6 -0
  26. package/dist/helpers/event-tags.js +4 -0
  27. package/dist/helpers/event.d.ts +8 -1
  28. package/dist/helpers/event.js +6 -0
  29. package/dist/helpers/file-metadata.d.ts +4 -9
  30. package/dist/helpers/file-metadata.js +2 -10
  31. package/dist/helpers/filter.d.ts +4 -3
  32. package/dist/helpers/filter.js +3 -3
  33. package/dist/helpers/gift-wraps.d.ts +35 -14
  34. package/dist/helpers/gift-wraps.js +59 -50
  35. package/dist/helpers/groups.d.ts +2 -5
  36. package/dist/helpers/groups.js +2 -5
  37. package/dist/helpers/hidden-content.d.ts +14 -5
  38. package/dist/helpers/hidden-content.js +19 -8
  39. package/dist/helpers/hidden-tags.d.ts +16 -7
  40. package/dist/helpers/hidden-tags.js +47 -26
  41. package/dist/helpers/index.d.ts +1 -0
  42. package/dist/helpers/index.js +1 -0
  43. package/dist/helpers/legacy-messages.d.ts +17 -13
  44. package/dist/helpers/legacy-messages.js +21 -19
  45. package/dist/helpers/lists.js +2 -1
  46. package/dist/helpers/lnurl.d.ts +4 -0
  47. package/dist/helpers/lnurl.js +7 -3
  48. package/dist/helpers/mailboxes.d.ts +2 -6
  49. package/dist/helpers/mailboxes.js +26 -20
  50. package/dist/helpers/mutes.d.ts +11 -1
  51. package/dist/helpers/mutes.js +30 -5
  52. package/dist/helpers/picture-post.d.ts +2 -1
  53. package/dist/helpers/pointers.d.ts +1 -1
  54. package/dist/helpers/pointers.js +2 -1
  55. package/dist/helpers/profile.d.ts +7 -3
  56. package/dist/helpers/profile.js +7 -8
  57. package/dist/helpers/relay-selection.d.ts +13 -0
  58. package/dist/helpers/relay-selection.js +84 -0
  59. package/dist/helpers/relays.d.ts +3 -1
  60. package/dist/helpers/relays.js +18 -2
  61. package/dist/helpers/url.js +3 -3
  62. package/dist/helpers/wrapped-messages.d.ts +5 -3
  63. package/dist/helpers/wrapped-messages.js +5 -3
  64. package/dist/helpers/zap.d.ts +16 -14
  65. package/dist/helpers/zap.js +26 -28
  66. package/dist/models/common.d.ts +4 -4
  67. package/dist/models/common.js +79 -40
  68. package/dist/models/gift-wrap.d.ts +1 -1
  69. package/dist/models/gift-wrap.js +4 -4
  70. package/dist/models/index.d.ts +1 -0
  71. package/dist/models/index.js +1 -0
  72. package/dist/models/legacy-messages.d.ts +2 -2
  73. package/dist/models/legacy-messages.js +13 -10
  74. package/dist/models/outbox.d.ts +13 -0
  75. package/dist/models/outbox.js +18 -0
  76. package/dist/models/profile.d.ts +1 -1
  77. package/dist/models/zaps.d.ts +5 -4
  78. package/dist/models/zaps.js +2 -2
  79. package/dist/observable/index.d.ts +4 -3
  80. package/dist/observable/index.js +5 -4
  81. package/dist/observable/map-events-to-store.d.ts +5 -3
  82. package/dist/observable/map-events-to-store.js +14 -3
  83. package/dist/observable/map-events-to-timeline.js +12 -0
  84. package/dist/observable/relay-selection.d.ts +7 -0
  85. package/dist/observable/relay-selection.js +38 -0
  86. package/package.json +5 -3
  87. package/dist/observable/map-events-timeline.js +0 -9
  88. /package/dist/observable/{map-events-timeline.d.ts → map-events-to-timeline.d.ts} +0 -0
@@ -0,0 +1,59 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
3
+ import { Observable } from "rxjs";
4
+ import { AddressPointerWithoutD } from "../helpers/pointers.js";
5
+ import { IAsyncEventStore, IEventStore, ModelConstructor } from "./interface.js";
6
+ /** A mixin that provides model functionality for both sync and async event stores */
7
+ export declare function EventStoreModelMixin<T extends new (...args: any[]) => any, TStore extends IEventStore | IAsyncEventStore>(Base: T): {
8
+ new (...args: any[]): {
9
+ [x: string]: any;
10
+ /** A directory of all active models */
11
+ models: Map<ModelConstructor<any, any[], TStore>, Map<string, Observable<any>>>;
12
+ /** How long a model should be kept "warm" while nothing is subscribed to it */
13
+ modelKeepWarm: number;
14
+ /** Get or create a model on the event store */
15
+ model<T_1 extends unknown, Args extends Array<any>>(constructor: ModelConstructor<T_1, Args, TStore>, ...args: Args): Observable<T_1>;
16
+ /**
17
+ * Creates an observable that streams all events that match the filter
18
+ * @param filters
19
+ * @param [onlyNew=false] Only subscribe to new events
20
+ */
21
+ filters(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
22
+ /** Creates a {@link EventModel} */
23
+ event(pointer: string | EventPointer): Observable<NostrEvent | undefined>;
24
+ /** Creates a {@link ReplaceableModel} */
25
+ replaceable(pointer: AddressPointer | AddressPointerWithoutD): Observable<NostrEvent | undefined>;
26
+ replaceable(kind: number, pubkey: string, identifier?: string): Observable<NostrEvent | undefined>;
27
+ /** Subscribe to an addressable event by pointer */
28
+ addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
29
+ /** Creates a {@link TimelineModel} */
30
+ timeline(filters: Filter | Filter[], includeOldVersion?: boolean): Observable<NostrEvent[]>;
31
+ /** Subscribe to a users profile */
32
+ profile(user: string | ProfilePointer): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
33
+ /** Subscribe to a users contacts */
34
+ contacts(user: string | ProfilePointer): Observable<ProfilePointer[]>;
35
+ /** Subscribe to a users mutes */
36
+ mutes(user: string | ProfilePointer): Observable<import("../helpers/mutes.js").Mutes | undefined>;
37
+ /** Subscribe to a users NIP-65 mailboxes */
38
+ mailboxes(user: string | ProfilePointer): Observable<{
39
+ inboxes: string[];
40
+ outboxes: string[];
41
+ } | undefined>;
42
+ /** Subscribe to a users blossom servers */
43
+ blossomServers(user: string | ProfilePointer): Observable<URL[]>;
44
+ /** Subscribe to an event's reactions */
45
+ reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
46
+ /** Subscribe to a thread */
47
+ thread(root: string | EventPointer | AddressPointer): Observable<import("../models/thread.js").Thread>;
48
+ /** Subscribe to a event's comments */
49
+ comments(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
50
+ /** @deprecated use multiple {@link EventModel} instead */
51
+ events(ids: string[]): Observable<Record<string, NostrEvent | undefined>>;
52
+ /** @deprecated use multiple {@link ReplaceableModel} instead */
53
+ replaceableSet(pointers: {
54
+ kind: number;
55
+ pubkey: string;
56
+ identifier?: string;
57
+ }[]): Observable<Record<string, NostrEvent | undefined>>;
58
+ };
59
+ } & T;
@@ -0,0 +1,147 @@
1
+ import hash_sum from "hash-sum";
2
+ import { EMPTY, ReplaySubject, filter, finalize, from, merge, mergeMap, share, timer } from "rxjs";
3
+ import { matchFilters } from "../helpers/filter.js";
4
+ // Model imports
5
+ import { UserBlossomServersModel } from "../models/blossom.js";
6
+ import { EventModel, EventsModel, ReplaceableModel, ReplaceableSetModel, TimelineModel } from "../models/common.js";
7
+ import { ContactsModel } from "../models/contacts.js";
8
+ import { CommentsModel, ThreadModel } from "../models/index.js";
9
+ import { MailboxesModel } from "../models/mailboxes.js";
10
+ import { MuteModel } from "../models/mutes.js";
11
+ import { ProfileModel } from "../models/profile.js";
12
+ import { ReactionsModel } from "../models/reactions.js";
13
+ /** A mixin that provides model functionality for both sync and async event stores */
14
+ export function EventStoreModelMixin(Base) {
15
+ return class extends Base {
16
+ /** A directory of all active models */
17
+ models = new Map();
18
+ /** How long a model should be kept "warm" while nothing is subscribed to it */
19
+ modelKeepWarm = 60_000;
20
+ /** Get or create a model on the event store */
21
+ model(constructor, ...args) {
22
+ let models = this.models.get(constructor);
23
+ if (!models) {
24
+ models = new Map();
25
+ this.models.set(constructor, models);
26
+ }
27
+ const key = constructor.getKey ? constructor.getKey(...args) : hash_sum(args);
28
+ let model = models.get(key);
29
+ // Create the model if it does not exist
30
+ if (!model) {
31
+ const cleanup = () => {
32
+ // Remove the model from the cache if its the same one
33
+ if (models.get(key) === model)
34
+ models.delete(key);
35
+ };
36
+ model = constructor(...args)(this).pipe(
37
+ // remove the model when its unsubscribed
38
+ finalize(cleanup),
39
+ // only subscribe to models once for all subscriptions
40
+ share({
41
+ connector: () => new ReplaySubject(1),
42
+ resetOnComplete: () => timer(this.modelKeepWarm),
43
+ resetOnRefCountZero: () => timer(this.modelKeepWarm),
44
+ }));
45
+ // Add the model to the cache
46
+ models.set(key, model);
47
+ }
48
+ return model;
49
+ }
50
+ /**
51
+ * Creates an observable that streams all events that match the filter
52
+ * @param filters
53
+ * @param [onlyNew=false] Only subscribe to new events
54
+ */
55
+ filters(filters, onlyNew = false) {
56
+ filters = Array.isArray(filters) ? filters : [filters];
57
+ const getByFiltersResult = this.getByFilters(filters);
58
+ // Create the existing events observable
59
+ const existingEvents$ = onlyNew
60
+ ? EMPTY
61
+ : // Check if result is a Promise (async) or direct Set (sync)
62
+ getByFiltersResult && typeof getByFiltersResult.then === "function"
63
+ ? from(getByFiltersResult).pipe(mergeMap((events) => from(Array.from(events))))
64
+ : from(Array.from(getByFiltersResult));
65
+ // Create the new events observable
66
+ const newEvents$ = this.insert$.pipe(filter((e) => matchFilters(filters, e)));
67
+ return merge(existingEvents$, newEvents$);
68
+ }
69
+ // Helper methods for creating models
70
+ /** Creates a {@link EventModel} */
71
+ event(pointer) {
72
+ if (typeof pointer === "string")
73
+ pointer = { id: pointer };
74
+ return this.model(EventModel, pointer);
75
+ }
76
+ replaceable(...args) {
77
+ let pointer;
78
+ // Parse arguments
79
+ if (args.length === 1) {
80
+ pointer = args[0];
81
+ }
82
+ else if (args.length === 3 || args.length === 2) {
83
+ let [kind, pubkey, identifier] = args;
84
+ pointer = { kind, pubkey, identifier };
85
+ }
86
+ if (!pointer)
87
+ throw new Error("Invalid arguments, expected address pointer or kind, pubkey, identifier");
88
+ return this.model(ReplaceableModel, pointer);
89
+ }
90
+ /** Subscribe to an addressable event by pointer */
91
+ addressable(pointer) {
92
+ return this.model(ReplaceableModel, pointer);
93
+ }
94
+ /** Creates a {@link TimelineModel} */
95
+ timeline(filters, includeOldVersion = false) {
96
+ return this.model(TimelineModel, filters, includeOldVersion);
97
+ }
98
+ /** Subscribe to a users profile */
99
+ profile(user) {
100
+ return this.model(ProfileModel, user);
101
+ }
102
+ /** Subscribe to a users contacts */
103
+ contacts(user) {
104
+ if (typeof user === "string")
105
+ user = { pubkey: user };
106
+ return this.model(ContactsModel, user);
107
+ }
108
+ /** Subscribe to a users mutes */
109
+ mutes(user) {
110
+ if (typeof user === "string")
111
+ user = { pubkey: user };
112
+ return this.model(MuteModel, user);
113
+ }
114
+ /** Subscribe to a users NIP-65 mailboxes */
115
+ mailboxes(user) {
116
+ if (typeof user === "string")
117
+ user = { pubkey: user };
118
+ return this.model(MailboxesModel, user);
119
+ }
120
+ /** Subscribe to a users blossom servers */
121
+ blossomServers(user) {
122
+ if (typeof user === "string")
123
+ user = { pubkey: user };
124
+ return this.model(UserBlossomServersModel, user);
125
+ }
126
+ /** Subscribe to an event's reactions */
127
+ reactions(event) {
128
+ return this.model(ReactionsModel, event);
129
+ }
130
+ /** Subscribe to a thread */
131
+ thread(root) {
132
+ return this.model(ThreadModel, root);
133
+ }
134
+ /** Subscribe to a event's comments */
135
+ comments(event) {
136
+ return this.model(CommentsModel, event);
137
+ }
138
+ /** @deprecated use multiple {@link EventModel} instead */
139
+ events(ids) {
140
+ return this.model(EventsModel, ids);
141
+ }
142
+ /** @deprecated use multiple {@link ReplaceableModel} instead */
143
+ replaceableSet(pointers) {
144
+ return this.model(ReplaceableSetModel, pointers);
145
+ }
146
+ };
147
+ }
@@ -0,0 +1,39 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { EncryptionMethod } from "./encrypted-content.js";
3
+ import { HiddenContentSigner } from "./hidden-content.js";
4
+ export declare const APP_DATA_KIND = 30078;
5
+ /** A symbol used to cache the application data content */
6
+ export declare const AppDataContentSymbol: unique symbol;
7
+ /** Checks if an event has application data */
8
+ export declare function hasAppData<T extends {
9
+ kind: number;
10
+ content: string;
11
+ }>(event: T): boolean;
12
+ /** Checks if the application data is encrypted */
13
+ export declare function getAppDataEncryption<T extends {
14
+ kind: number;
15
+ content: string;
16
+ }>(event: T): EncryptionMethod | undefined;
17
+ /** Checks if the application data is locked (encrypted and not decrypted) */
18
+ export declare function isAppDataUnlocked<T extends {
19
+ kind: number;
20
+ }>(event: T): boolean;
21
+ /** Returns the parsed application data for an event if it's unlocked */
22
+ export declare function getAppDataContent<R extends unknown = unknown, T extends {
23
+ kind: number;
24
+ content: string;
25
+ } = NostrEvent>(event: T): R | undefined;
26
+ /**
27
+ * Unlocks the encrypted application data in the event
28
+ * @param event The event with encrypted content to decrypt
29
+ * @param signer A signer to use to decrypt the content
30
+ * @param override The encryption method to use instead of the default
31
+ * @returns The decrypted application data
32
+ */
33
+ export declare function unlockAppData<R extends unknown = unknown, T extends {
34
+ kind: number;
35
+ pubkey: string;
36
+ content: string;
37
+ } = NostrEvent>(event: T, signer: HiddenContentSigner, override?: EncryptionMethod): Promise<R>;
38
+ /** Removes the unencrypted application data cache on an event */
39
+ export declare function lockAppData<T extends object>(event: T): void;
@@ -0,0 +1,68 @@
1
+ import { isNIP04Encrypted } from "./encryption.js";
2
+ import { getHiddenContent, isHiddenContentUnlocked, lockHiddenContent, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "./hidden-content.js";
3
+ import { safeParse } from "./json.js";
4
+ // NIP-78 Application Data event kind
5
+ export const APP_DATA_KIND = 30078;
6
+ /** A symbol used to cache the application data content */
7
+ export const AppDataContentSymbol = Symbol.for("app-data-content");
8
+ // Set the encryption method for app data events (default to nip44)
9
+ setHiddenContentEncryptionMethod(APP_DATA_KIND, "nip44");
10
+ /** Checks if an event has application data */
11
+ export function hasAppData(event) {
12
+ return event.kind === APP_DATA_KIND && event.content.length > 0;
13
+ }
14
+ /** Checks if the application data is encrypted */
15
+ export function getAppDataEncryption(event) {
16
+ // If content is empty, it can't be encrypted
17
+ if (event.content.length === 0)
18
+ return undefined;
19
+ // Try to parse as JSON - if it fails, it's likely encrypted
20
+ const parsed = safeParse(event.content);
21
+ if (parsed !== undefined)
22
+ return undefined;
23
+ return isNIP04Encrypted(event.content) ? "nip04" : "nip44";
24
+ }
25
+ /** Checks if the application data is locked (encrypted and not decrypted) */
26
+ export function isAppDataUnlocked(event) {
27
+ return isHiddenContentUnlocked(event);
28
+ }
29
+ /** Returns the parsed application data for an event if it's unlocked */
30
+ export function getAppDataContent(event) {
31
+ const cached = Reflect.get(event, AppDataContentSymbol);
32
+ if (cached)
33
+ return cached;
34
+ // If content is empty, return undefined
35
+ if (event.content.length === 0)
36
+ return undefined;
37
+ let data = getAppDataEncryption(event) ? undefined : safeParse(event.content);
38
+ if (!data) {
39
+ const decrypted = getHiddenContent(event);
40
+ if (decrypted)
41
+ data = safeParse(decrypted);
42
+ }
43
+ if (!data)
44
+ return undefined;
45
+ Reflect.set(event, AppDataContentSymbol, data);
46
+ return data;
47
+ }
48
+ /**
49
+ * Unlocks the encrypted application data in the event
50
+ * @param event The event with encrypted content to decrypt
51
+ * @param signer A signer to use to decrypt the content
52
+ * @param override The encryption method to use instead of the default
53
+ * @returns The decrypted application data
54
+ */
55
+ export async function unlockAppData(event, signer, override) {
56
+ if (!getAppDataEncryption(event))
57
+ return getAppDataContent(event);
58
+ const method = override ?? getAppDataEncryption(event);
59
+ const plaintext = await unlockHiddenContent(event, signer, method);
60
+ const parsed = safeParse(plaintext);
61
+ if (parsed === undefined)
62
+ throw new Error("Failed to parse decrypted application data as JSON");
63
+ return parsed;
64
+ }
65
+ /** Removes the unencrypted application data cache on an event */
66
+ export function lockAppData(event) {
67
+ lockHiddenContent(event);
68
+ }
@@ -1,7 +1,12 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
+ import { HiddenContentSigner } from "./index.js";
3
4
  export declare const BookmarkPublicSymbol: unique symbol;
4
5
  export declare const BookmarkHiddenSymbol: unique symbol;
6
+ /** Type for unlocked bookmarks events */
7
+ export type UnlockedBookmarks = {
8
+ [BookmarkHiddenSymbol]: Bookmarks;
9
+ };
5
10
  export type Bookmarks = {
6
11
  notes: EventPointer[];
7
12
  articles: AddressPointer[];
@@ -16,5 +21,10 @@ export declare function mergeBookmarks(...bookmarks: (Bookmarks | undefined)[]):
16
21
  export declare function getBookmarks(bookmark: NostrEvent): Bookmarks;
17
22
  /** Returns the public bookmarks of the event */
18
23
  export declare function getPublicBookmarks(bookmark: NostrEvent): Bookmarks;
24
+ /** Checks if the hidden bookmarks are unlocked */
25
+ export declare function isHiddenBookmarksUnlocked<T extends NostrEvent>(bookmark: T): bookmark is T & UnlockedBookmarks;
19
26
  /** Returns the bookmarks of the event if its unlocked */
20
- export declare function getHiddenBookmarks(bookmark: NostrEvent): Bookmarks | undefined;
27
+ export declare function getHiddenBookmarks<T extends NostrEvent & UnlockedBookmarks>(bookmark: T): Bookmarks;
28
+ export declare function getHiddenBookmarks<T extends NostrEvent>(bookmark: T): Bookmarks | undefined;
29
+ /** Unlocks the hidden bookmarks on a bookmarks event */
30
+ export declare function unlockHiddenBookmarks(bookmark: NostrEvent, signer: HiddenContentSigner): Promise<Bookmarks>;
@@ -1,6 +1,6 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
- import { getHiddenTags, isHiddenTagsLocked } from "./index.js";
3
+ import { getHiddenTags, isHiddenTagsUnlocked, notifyEventUpdate, unlockHiddenTags, } from "./index.js";
4
4
  import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag, mergeAddressPointers, mergeEventPointers, } from "./pointers.js";
5
5
  export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
6
6
  export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
@@ -63,9 +63,34 @@ export function getBookmarks(bookmark) {
63
63
  export function getPublicBookmarks(bookmark) {
64
64
  return getOrComputeCachedValue(bookmark, BookmarkPublicSymbol, () => parseBookmarkTags(bookmark.tags));
65
65
  }
66
- /** Returns the bookmarks of the event if its unlocked */
66
+ /** Checks if the hidden bookmarks are unlocked */
67
+ export function isHiddenBookmarksUnlocked(bookmark) {
68
+ return isHiddenTagsUnlocked(bookmark) && Reflect.has(bookmark, BookmarkHiddenSymbol);
69
+ }
67
70
  export function getHiddenBookmarks(bookmark) {
68
- if (isHiddenTagsLocked(bookmark))
71
+ if (isHiddenBookmarksUnlocked(bookmark))
72
+ return bookmark[BookmarkHiddenSymbol];
73
+ //get hidden tags
74
+ const tags = getHiddenTags(bookmark);
75
+ if (!tags)
69
76
  return undefined;
70
- return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => parseBookmarkTags(getHiddenTags(bookmark)));
77
+ // parse bookmarks
78
+ const bookmarks = parseBookmarkTags(tags);
79
+ // set cached value
80
+ Reflect.set(bookmark, BookmarkHiddenSymbol, bookmarks);
81
+ return bookmarks;
82
+ }
83
+ /** Unlocks the hidden bookmarks on a bookmarks event */
84
+ export async function unlockHiddenBookmarks(bookmark, signer) {
85
+ if (isHiddenBookmarksUnlocked(bookmark))
86
+ return bookmark[BookmarkHiddenSymbol];
87
+ // unlock hidden tags
88
+ await unlockHiddenTags(bookmark, signer);
89
+ // get hidden bookmarks
90
+ const bookmarks = getHiddenBookmarks(bookmark);
91
+ if (!bookmarks)
92
+ throw new Error("Failed to unlock hidden bookmarks");
93
+ // notify event store
94
+ notifyEventUpdate(bookmark);
95
+ return bookmarks;
71
96
  }
@@ -1,6 +1,9 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  import { ExternalPointer, ExternalIdentifiers } from "./external-id.js";
3
+ import { KnownEvent } from "./index.js";
3
4
  export declare const COMMENT_KIND = 1111;
5
+ /** Type for validated comment events */
6
+ export type CommentEvent = KnownEvent<typeof COMMENT_KIND>;
4
7
  export type CommentEventPointer = {
5
8
  type: "event";
6
9
  id: string;
@@ -22,30 +25,20 @@ export type CommentExternalPointer<T extends keyof ExternalIdentifiers> = Extern
22
25
  export type CommentPointer = CommentEventPointer | CommentAddressPointer | CommentExternalPointer<keyof ExternalIdentifiers>;
23
26
  export declare const CommentRootPointerSymbol: unique symbol;
24
27
  export declare const CommentReplyPointerSymbol: unique symbol;
25
- /**
26
- * Gets the EventPointer from an array of tags
27
- * @throws
28
- */
28
+ /** Gets the EventPointer from an array of tags */
29
29
  export declare function getCommentEventPointer(tags: string[][], root?: boolean): CommentEventPointer | null;
30
- /**
31
- * Gets the AddressPointer from an array of tags
32
- * @throws
33
- */
30
+ /** Gets the AddressPointer from an array of tags */
34
31
  export declare function getCommentAddressPointer(tags: string[][], root?: boolean): CommentAddressPointer | null;
35
- /**
36
- * Gets the ExternalPointer from an array of tags
37
- * @throws
38
- */
32
+ /** Gets the ExternalPointer from an array of tags */
39
33
  export declare function getCommentExternalPointer(tags: string[][], root?: boolean): CommentExternalPointer<keyof ExternalIdentifiers> | null;
40
- /**
41
- * Returns the root pointer for a comment
42
- * @throws
43
- */
34
+ /** Returns the root pointer for a comment */
35
+ export declare function getCommentRootPointer(comment: CommentEvent): CommentPointer;
44
36
  export declare function getCommentRootPointer(comment: NostrEvent): CommentPointer | null;
45
- /**
46
- * Returns the reply pointer for a comment
47
- * @throws
48
- */
37
+ /** Returns the reply pointer for a comment */
49
38
  export declare function getCommentReplyPointer(comment: NostrEvent): CommentPointer | null;
39
+ /** Checks if a pointer is a {@link CommentEventPointer} */
50
40
  export declare function isCommentEventPointer(pointer: any): pointer is CommentEventPointer;
41
+ /** Checks if a pointer is a {@link CommentAddressPointer} */
51
42
  export declare function isCommentAddressPointer(pointer: any): pointer is CommentAddressPointer;
43
+ /** Checks if a comment event is valid */
44
+ export declare function isValidComment(comment: NostrEvent): comment is CommentEvent;
@@ -5,16 +5,14 @@ import { isSafeRelayURL } from "./relays.js";
5
5
  export const COMMENT_KIND = 1111;
6
6
  export const CommentRootPointerSymbol = Symbol.for("comment-root-pointer");
7
7
  export const CommentReplyPointerSymbol = Symbol.for("comment-reply-pointer");
8
- /**
9
- * Gets the EventPointer from an array of tags
10
- * @throws
11
- */
8
+ /** Gets the EventPointer from an array of tags */
12
9
  export function getCommentEventPointer(tags, root = false) {
13
10
  const eTag = tags.find((t) => t[0] === (root ? "E" : "e"));
14
11
  const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
15
12
  if (eTag) {
13
+ // Missing kind tag, return null
16
14
  if (!kind)
17
- throw new Error("Missing kind tag");
15
+ return null;
18
16
  // only the root pubkey can be gotten from the tags, since due to quotes and mentions there will be many "p" tags for replies
19
17
  const rootPubkey = root ? tags.find((t) => t[0] === "P")?.[1] : undefined;
20
18
  const pointer = {
@@ -28,17 +26,15 @@ export function getCommentEventPointer(tags, root = false) {
28
26
  }
29
27
  return null;
30
28
  }
31
- /**
32
- * Gets the AddressPointer from an array of tags
33
- * @throws
34
- */
29
+ /** Gets the AddressPointer from an array of tags */
35
30
  export function getCommentAddressPointer(tags, root = false) {
36
31
  const aTag = tags.find((t) => t[0] === (root ? "A" : "a"));
37
32
  const eTag = tags.find((t) => t[0] === (root ? "E" : "e"));
38
33
  const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
39
34
  if (aTag) {
35
+ // Missing kind tag, return null
40
36
  if (!kind)
41
- throw new Error("Missing kind tag");
37
+ return null;
42
38
  const addressPointer = getAddressPointerFromATag(aTag);
43
39
  const pointer = {
44
40
  type: "address",
@@ -52,16 +48,10 @@ export function getCommentAddressPointer(tags, root = false) {
52
48
  }
53
49
  return null;
54
50
  }
55
- /**
56
- * Gets the ExternalPointer from an array of tags
57
- * @throws
58
- */
51
+ /** Gets the ExternalPointer from an array of tags */
59
52
  export function getCommentExternalPointer(tags, root = false) {
60
53
  const iTag = tags.find((t) => t[0] === (root ? "I" : "i"));
61
- const kind = tags.find((t) => t[0] === (root ? "K" : "k"))?.[1];
62
54
  if (iTag) {
63
- if (!kind)
64
- throw new Error("Missing kind tag");
65
55
  return {
66
56
  type: "external",
67
57
  ...getExternalPointerFromTag(iTag),
@@ -69,13 +59,9 @@ export function getCommentExternalPointer(tags, root = false) {
69
59
  }
70
60
  return null;
71
61
  }
72
- /**
73
- * Returns the root pointer for a comment
74
- * @throws
75
- */
76
62
  export function getCommentRootPointer(comment) {
77
63
  if (comment.kind !== COMMENT_KIND)
78
- throw new Error("Event is not a comment");
64
+ return null;
79
65
  return getOrComputeCachedValue(comment, CommentRootPointerSymbol, () => {
80
66
  // check for address pointer first since it can also have E tags
81
67
  const A = getCommentAddressPointer(comment.tags, true);
@@ -90,13 +76,10 @@ export function getCommentRootPointer(comment) {
90
76
  return null;
91
77
  });
92
78
  }
93
- /**
94
- * Returns the reply pointer for a comment
95
- * @throws
96
- */
79
+ /** Returns the reply pointer for a comment */
97
80
  export function getCommentReplyPointer(comment) {
98
81
  if (comment.kind !== COMMENT_KIND)
99
- throw new Error("Event is not a comment");
82
+ return null;
100
83
  return getOrComputeCachedValue(comment, CommentReplyPointerSymbol, () => {
101
84
  // check for address pointer first since it can also have E tags
102
85
  const A = getCommentAddressPointer(comment.tags, false);
@@ -111,15 +94,21 @@ export function getCommentReplyPointer(comment) {
111
94
  return null;
112
95
  });
113
96
  }
97
+ /** Checks if a pointer is a {@link CommentEventPointer} */
114
98
  export function isCommentEventPointer(pointer) {
115
99
  return (Reflect.has(pointer, "id") &&
116
100
  Reflect.has(pointer, "kind") &&
117
101
  !Reflect.has(pointer, "identifier") &&
118
102
  typeof pointer.kind === "number");
119
103
  }
104
+ /** Checks if a pointer is a {@link CommentAddressPointer} */
120
105
  export function isCommentAddressPointer(pointer) {
121
106
  return (Reflect.has(pointer, "identifier") &&
122
107
  Reflect.has(pointer, "pubkey") &&
123
108
  Reflect.has(pointer, "kind") &&
124
109
  typeof pointer.kind === "number");
125
110
  }
111
+ /** Checks if a comment event is valid */
112
+ export function isValidComment(comment) {
113
+ return (comment.kind === COMMENT_KIND && getCommentRootPointer(comment) !== null && getCommentReplyPointer(comment) !== null);
114
+ }
@@ -1,14 +1,23 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  import { ProfilePointer } from "nostr-tools/nip19";
3
+ import { HiddenContentSigner } from "./hidden-content.js";
3
4
  export declare const ContactsRelaysSymbol: unique symbol;
4
5
  export declare const PublicContactsSymbol: unique symbol;
5
6
  export declare const HiddenContactsSymbol: unique symbol;
6
- export declare function getRelaysFromContactsEvent(event: NostrEvent): Map<string, "all" | "inbox" | "outbox"> | null;
7
+ /** Type for contact events with unlocked hidden tags */
8
+ export type UnlockedContacts = {
9
+ [HiddenContactsSymbol]: ProfilePointer[];
10
+ };
11
+ export declare function getRelaysFromContactsEvent(event: NostrEvent): Map<string, "inbox" | "outbox" | "all"> | null;
7
12
  /** Merges any number of contact lists into a single list */
8
13
  export declare function mergeContacts(...pointers: (ProfilePointer | undefined | (ProfilePointer | undefined)[])[]): ProfilePointer[];
9
14
  /** Returns all public and hidden contacts from a contacts list event */
10
15
  export declare function getContacts(event: NostrEvent): ProfilePointer[];
11
16
  /** Returns only the public contacts from a contacts list event */
12
17
  export declare function getPublicContacts(event: NostrEvent): ProfilePointer[];
18
+ /** Checks if the hidden contacts are unlocked */
19
+ export declare function isHiddenContactsUnlocked<T extends NostrEvent>(event: T): event is T & UnlockedContacts;
13
20
  /** Returns only the hidden contacts from a contacts list event */
14
21
  export declare function getHiddenContacts(event: NostrEvent): ProfilePointer[] | undefined;
22
+ /** Unlocks the hidden contacts */
23
+ export declare function unlockHiddenContacts(event: NostrEvent, signer: HiddenContentSigner): Promise<ProfilePointer[]>;
@@ -2,7 +2,8 @@ import { getOrComputeCachedValue } from "./cache.js";
2
2
  import { isSafeRelayURL } from "./relays.js";
3
3
  import { isPTag, processTags } from "./tags.js";
4
4
  import { getProfilePointerFromPTag } from "./pointers.js";
5
- import { getHiddenTags, isHiddenTagsLocked } from "./hidden-tags.js";
5
+ import { getHiddenTags, isHiddenTagsUnlocked, unlockHiddenTags } from "./hidden-tags.js";
6
+ import { notifyEventUpdate } from "./index.js";
6
7
  export const ContactsRelaysSymbol = Symbol.for("contacts-relays");
7
8
  export const PublicContactsSymbol = Symbol.for("public-contacts");
8
9
  export const HiddenContactsSymbol = Symbol.for("hidden-contacts");
@@ -51,9 +52,35 @@ export function getContacts(event) {
51
52
  export function getPublicContacts(event) {
52
53
  return getOrComputeCachedValue(event, PublicContactsSymbol, () => processTags(event.tags, (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag));
53
54
  }
55
+ /** Checks if the hidden contacts are unlocked */
56
+ export function isHiddenContactsUnlocked(event) {
57
+ return isHiddenTagsUnlocked(event) && Reflect.has(event, HiddenContactsSymbol);
58
+ }
54
59
  /** Returns only the hidden contacts from a contacts list event */
55
60
  export function getHiddenContacts(event) {
56
- if (isHiddenTagsLocked(event))
61
+ if (isHiddenContactsUnlocked(event))
62
+ return event[HiddenContactsSymbol];
63
+ // Get hidden tags
64
+ const tags = getHiddenTags(event);
65
+ if (!tags)
57
66
  return undefined;
58
- return getOrComputeCachedValue(event, HiddenContactsSymbol, () => processTags(getHiddenTags(event), (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag));
67
+ // Parse tags
68
+ const contacts = processTags(tags, (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag);
69
+ // Set cache and notify event store
70
+ Reflect.set(event, HiddenContactsSymbol, contacts);
71
+ return contacts;
72
+ }
73
+ /** Unlocks the hidden contacts */
74
+ export async function unlockHiddenContacts(event, signer) {
75
+ if (isHiddenContactsUnlocked(event))
76
+ return event[HiddenContactsSymbol];
77
+ // Unlock hidden tags
78
+ await unlockHiddenTags(event, signer);
79
+ // Get hidden contacts
80
+ const contacts = getHiddenContacts(event);
81
+ if (!contacts)
82
+ throw new Error("Failed to unlock hidden contacts");
83
+ // Set cache and notify event store
84
+ notifyEventUpdate(event);
85
+ return contacts;
59
86
  }