applesauce-core 3.0.1 → 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.
- package/dist/event-store/async-event-store.d.ts +134 -0
- package/dist/event-store/async-event-store.js +349 -0
- package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +15 -25
- package/dist/event-store/{event-set.js → event-memory.js} +43 -53
- package/dist/event-store/event-store.d.ts +57 -63
- package/dist/event-store/event-store.js +111 -190
- package/dist/event-store/index.d.ts +2 -1
- package/dist/event-store/index.js +2 -1
- package/dist/event-store/interface.d.ts +111 -38
- package/dist/event-store/model-mixin.d.ts +59 -0
- package/dist/event-store/model-mixin.js +147 -0
- package/dist/helpers/app-data.d.ts +39 -0
- package/dist/helpers/app-data.js +68 -0
- package/dist/helpers/bookmarks.d.ts +11 -1
- package/dist/helpers/bookmarks.js +29 -4
- package/dist/helpers/comment.d.ts +13 -20
- package/dist/helpers/comment.js +16 -27
- package/dist/helpers/contacts.d.ts +10 -1
- package/dist/helpers/contacts.js +30 -3
- package/dist/helpers/encrypted-content-cache.js +7 -7
- package/dist/helpers/encrypted-content.d.ts +9 -2
- package/dist/helpers/encrypted-content.js +12 -9
- package/dist/helpers/event-cache.d.ts +3 -1
- package/dist/helpers/event-cache.js +3 -1
- package/dist/helpers/event-tags.d.ts +6 -0
- package/dist/helpers/event-tags.js +4 -0
- package/dist/helpers/event.d.ts +8 -1
- package/dist/helpers/event.js +6 -0
- package/dist/helpers/file-metadata.d.ts +4 -9
- package/dist/helpers/file-metadata.js +2 -10
- package/dist/helpers/filter.d.ts +4 -3
- package/dist/helpers/filter.js +3 -3
- package/dist/helpers/gift-wraps.d.ts +35 -14
- package/dist/helpers/gift-wraps.js +59 -50
- package/dist/helpers/groups.d.ts +2 -5
- package/dist/helpers/groups.js +5 -5
- package/dist/helpers/hidden-content.d.ts +14 -5
- package/dist/helpers/hidden-content.js +19 -8
- package/dist/helpers/hidden-tags.d.ts +16 -7
- package/dist/helpers/hidden-tags.js +47 -26
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/legacy-messages.d.ts +19 -8
- package/dist/helpers/legacy-messages.js +25 -14
- package/dist/helpers/lists.js +2 -1
- package/dist/helpers/lnurl.d.ts +4 -0
- package/dist/helpers/lnurl.js +7 -3
- package/dist/helpers/mailboxes.d.ts +2 -6
- package/dist/helpers/mailboxes.js +26 -20
- package/dist/helpers/mutes.d.ts +11 -1
- package/dist/helpers/mutes.js +30 -5
- package/dist/helpers/picture-post.d.ts +2 -1
- package/dist/helpers/pointers.d.ts +1 -1
- package/dist/helpers/pointers.js +2 -1
- package/dist/helpers/profile.d.ts +7 -3
- package/dist/helpers/profile.js +7 -8
- package/dist/helpers/relay-selection.d.ts +13 -0
- package/dist/helpers/relay-selection.js +84 -0
- package/dist/helpers/relays.d.ts +3 -1
- package/dist/helpers/relays.js +18 -2
- package/dist/helpers/url.js +3 -3
- package/dist/helpers/wrapped-messages.d.ts +10 -1
- package/dist/helpers/wrapped-messages.js +15 -2
- package/dist/helpers/zap.d.ts +16 -14
- package/dist/helpers/zap.js +26 -28
- package/dist/models/common.d.ts +4 -4
- package/dist/models/common.js +79 -40
- package/dist/models/gift-wrap.d.ts +1 -1
- package/dist/models/gift-wrap.js +4 -4
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/legacy-messages.d.ts +2 -2
- package/dist/models/legacy-messages.js +13 -10
- package/dist/models/outbox.d.ts +13 -0
- package/dist/models/outbox.js +18 -0
- package/dist/models/profile.d.ts +1 -1
- package/dist/models/zaps.d.ts +5 -4
- package/dist/models/zaps.js +2 -2
- package/dist/observable/index.d.ts +4 -3
- package/dist/observable/index.js +5 -4
- package/dist/observable/map-events-to-store.d.ts +5 -3
- package/dist/observable/map-events-to-store.js +14 -3
- package/dist/observable/map-events-to-timeline.js +12 -0
- package/dist/observable/relay-selection.d.ts +7 -0
- package/dist/observable/relay-selection.js +38 -0
- package/package.json +5 -3
- package/dist/observable/map-events-timeline.js +0 -9
- /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:
|
|
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,
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/helpers/comment.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[]>;
|
package/dist/helpers/contacts.js
CHANGED
|
@@ -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,
|
|
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 (
|
|
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
|
-
|
|
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
|
}
|