applesauce-core 2.1.1 → 2.3.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/common.d.ts +1 -1
- package/dist/event-store/common.js +1 -2
- package/dist/event-store/event-set.d.ts +2 -0
- package/dist/event-store/event-set.js +12 -2
- package/dist/event-store/event-store.d.ts +0 -2
- package/dist/event-store/event-store.js +2 -4
- package/dist/helpers/article.js +1 -1
- package/dist/helpers/emoji.js +1 -1
- package/dist/helpers/encrypted-content-cache.d.ts +9 -2
- package/dist/helpers/encrypted-content-cache.js +29 -15
- package/dist/helpers/encrypted-content.js +2 -0
- package/dist/helpers/event-tags.d.ts +14 -0
- package/dist/helpers/event-tags.js +30 -0
- package/dist/helpers/event.d.ts +3 -13
- package/dist/helpers/event.js +4 -32
- package/dist/helpers/expiration.js +1 -1
- package/dist/helpers/filter.js +1 -1
- package/dist/helpers/gift-wraps.d.ts +33 -11
- package/dist/helpers/gift-wraps.js +155 -46
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/messages.d.ts +20 -0
- package/dist/helpers/messages.js +38 -0
- package/dist/helpers/reactions.js +1 -1
- package/dist/helpers/wrapped-messages.d.ts +0 -18
- package/dist/helpers/wrapped-messages.js +0 -29
- package/dist/helpers/zap.d.ts +1 -1
- package/dist/helpers/zap.js +3 -3
- package/dist/models/legacy-messages.d.ts +8 -2
- package/dist/models/legacy-messages.js +46 -14
- package/dist/models/thread.d.ts +1 -1
- package/dist/models/thread.js +4 -3
- package/dist/models/wrapped-messages.d.ts +9 -3
- package/dist/models/wrapped-messages.js +25 -10
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export const INDEXABLE_TAGS = new Set((LETTERS + LETTERS.toUpperCase()).split(""));
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
2
2
|
import { Subject } from "rxjs";
|
|
3
|
-
import {
|
|
3
|
+
import { getIndexableTags, INDEXABLE_TAGS } from "../helpers/event-tags.js";
|
|
4
|
+
import { createReplaceableAddress, getReplaceableAddress, isReplaceable } from "../helpers/event.js";
|
|
4
5
|
import { LRU } from "../helpers/lru.js";
|
|
5
6
|
import { logger } from "../logger.js";
|
|
6
|
-
import { INDEXABLE_TAGS } from "./common.js";
|
|
7
7
|
/**
|
|
8
8
|
* A set of nostr events that can be queried and subscribed to
|
|
9
9
|
* NOTE: does not handle replaceable events or any deletion logic
|
|
@@ -344,4 +344,14 @@ export class EventSet {
|
|
|
344
344
|
}
|
|
345
345
|
return removed;
|
|
346
346
|
}
|
|
347
|
+
/** Resets the event set */
|
|
348
|
+
reset() {
|
|
349
|
+
this.events.clear();
|
|
350
|
+
this.kinds.clear();
|
|
351
|
+
this.authors.clear();
|
|
352
|
+
this.tags.clear();
|
|
353
|
+
this.created_at = [];
|
|
354
|
+
this.replaceable.clear();
|
|
355
|
+
this.claims = new WeakMap();
|
|
356
|
+
}
|
|
347
357
|
}
|
|
@@ -3,8 +3,6 @@ import { Observable } from "rxjs";
|
|
|
3
3
|
import { EventSet } from "./event-set.js";
|
|
4
4
|
import { IEventStore, ModelConstructor } from "./interface.js";
|
|
5
5
|
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
6
|
-
/** A symbol on an event that marks which event store its part of */
|
|
7
|
-
export declare const EventStoreSymbol: unique symbol;
|
|
8
6
|
export declare class EventStore implements IEventStore {
|
|
9
7
|
database: EventSet;
|
|
10
8
|
/** Enable this to keep old versions of replaceable events */
|
|
@@ -3,7 +3,7 @@ import { isAddressableKind } from "nostr-tools/kinds";
|
|
|
3
3
|
import { EMPTY, filter, finalize, from, merge, mergeMap, ReplaySubject, share, take, timer } from "rxjs";
|
|
4
4
|
import hash_sum from "hash-sum";
|
|
5
5
|
import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
|
|
6
|
-
import { FromCacheSymbol, getReplaceableAddress,
|
|
6
|
+
import { EventStoreSymbol, FromCacheSymbol, getReplaceableAddress, isReplaceable } from "../helpers/event.js";
|
|
7
7
|
import { matchFilters } from "../helpers/filter.js";
|
|
8
8
|
import { parseCoordinate } from "../helpers/pointers.js";
|
|
9
9
|
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
@@ -16,8 +16,6 @@ import { ReactionsModel } from "../models/reactions.js";
|
|
|
16
16
|
import { MailboxesModel } from "../models/mailboxes.js";
|
|
17
17
|
import { UserBlossomServersModel } from "../models/blossom.js";
|
|
18
18
|
import { CommentsModel, ThreadModel } from "../models/index.js";
|
|
19
|
-
/** A symbol on an event that marks which event store its part of */
|
|
20
|
-
export const EventStoreSymbol = Symbol.for("event-store");
|
|
21
19
|
export class EventStore {
|
|
22
20
|
database;
|
|
23
21
|
/** Enable this to keep old versions of replaceable events */
|
|
@@ -120,7 +118,7 @@ export class EventStore {
|
|
|
120
118
|
if (this.checkDeleted(event))
|
|
121
119
|
return event;
|
|
122
120
|
// Get the replaceable identifier
|
|
123
|
-
const d = isReplaceable(event.kind) ?
|
|
121
|
+
const d = isReplaceable(event.kind) ? event.tags.find((t) => t[0] === "d")?.[1] : undefined;
|
|
124
122
|
// Don't insert the event if there is already a newer version
|
|
125
123
|
if (!this.keepOldVersions && isReplaceable(event.kind)) {
|
|
126
124
|
const existing = this.database.getReplaceableHistory(event.kind, event.pubkey, d);
|
package/dist/helpers/article.js
CHANGED
package/dist/helpers/emoji.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
1
2
|
import { Observable } from "rxjs";
|
|
2
3
|
import { IEventStoreStreams } from "../event-store/interface.js";
|
|
3
4
|
/** A symbol that is used to mark encrypted content as being from a cache */
|
|
@@ -11,5 +12,11 @@ export interface EncryptedContentCache {
|
|
|
11
12
|
export declare function markEncryptedContentFromCache<T extends object>(event: T): void;
|
|
12
13
|
/** Checks if the encrypted content is from a cache */
|
|
13
14
|
export declare function isEncryptedContentFromCache<T extends object>(event: T): boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Starts a process that persists and restores all encrypted content
|
|
17
|
+
* @param eventStore - The event store to listen to
|
|
18
|
+
* @param storage - The storage to use
|
|
19
|
+
* @param fallback - A function that will be called when the encrypted content is not found in storage
|
|
20
|
+
* @returns A function that can be used to stop the process
|
|
21
|
+
*/
|
|
22
|
+
export declare function persistEncryptedContent(eventStore: IEventStoreStreams, storage: EncryptedContentCache | Observable<EncryptedContentCache>, fallback?: (event: NostrEvent) => any | Promise<any>): () => void;
|
|
@@ -3,7 +3,7 @@ import { catchError, combineLatest, distinct, EMPTY, filter, isObservable, map,
|
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
4
|
import { canHaveEncryptedContent, getEncryptedContent, isEncryptedContentLocked, setEncryptedContentCache, } from "./encrypted-content.js";
|
|
5
5
|
import { notifyEventUpdate } from "./event.js";
|
|
6
|
-
import { getGiftWrapSeal } from "./gift-wraps.js";
|
|
6
|
+
import { getGiftWrapSeal, getSealGiftWrap, getSealRumor } from "./gift-wraps.js";
|
|
7
7
|
/** A symbol that is used to mark encrypted content as being from a cache */
|
|
8
8
|
export const EncryptedContentFromCacheSymbol = Symbol.for("encrypted-content-from-cache");
|
|
9
9
|
/** Marks the encrypted content as being from a cache */
|
|
@@ -15,9 +15,19 @@ export function isEncryptedContentFromCache(event) {
|
|
|
15
15
|
return Reflect.has(event, EncryptedContentFromCacheSymbol);
|
|
16
16
|
}
|
|
17
17
|
const log = logger.extend("EncryptedContentCache");
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Starts a process that persists and restores all encrypted content
|
|
20
|
+
* @param eventStore - The event store to listen to
|
|
21
|
+
* @param storage - The storage to use
|
|
22
|
+
* @param fallback - A function that will be called when the encrypted content is not found in storage
|
|
23
|
+
* @returns A function that can be used to stop the process
|
|
24
|
+
*/
|
|
25
|
+
export function persistEncryptedContent(eventStore, storage, fallback) {
|
|
20
26
|
const storage$ = isObservable(storage) ? storage : of(storage);
|
|
27
|
+
// Get the encrypted content from storage or call the fallback
|
|
28
|
+
const getItem = async (storage, event) => {
|
|
29
|
+
return (await storage.getItem(event.id)) || (fallback ? await fallback(event) : null);
|
|
30
|
+
};
|
|
21
31
|
// Restore encrypted content when it is inserted
|
|
22
32
|
const restore = eventStore.insert$
|
|
23
33
|
.pipe(
|
|
@@ -26,7 +36,7 @@ export function persistEncryptedContent(eventStore, storage) {
|
|
|
26
36
|
// Get the encrypted content from storage
|
|
27
37
|
mergeMap((event) =>
|
|
28
38
|
// Wait for storage to be available
|
|
29
|
-
storage$.pipe(switchMap((storage) => combineLatest([of(event),
|
|
39
|
+
storage$.pipe(switchMap((storage) => combineLatest([of(event), getItem(storage, event)])), catchError((error) => {
|
|
30
40
|
log(`Failed to restore encrypted content for ${event.id}`, error);
|
|
31
41
|
return EMPTY;
|
|
32
42
|
}))))
|
|
@@ -44,25 +54,29 @@ export function persistEncryptedContent(eventStore, storage) {
|
|
|
44
54
|
// Look for gift wraps that are unlocked
|
|
45
55
|
filter((e) => e.kind === kinds.GiftWrap && !isEncryptedContentLocked(e)),
|
|
46
56
|
// Get the seal event
|
|
47
|
-
map((gift) =>
|
|
57
|
+
map((gift) => getGiftWrapSeal(gift)),
|
|
48
58
|
// Look for gift wraps with locked seals
|
|
49
|
-
filter((
|
|
59
|
+
filter((seal) => seal !== undefined && isEncryptedContentLocked(seal)),
|
|
50
60
|
// Only attempt to unlock seals once
|
|
51
|
-
distinct((
|
|
61
|
+
distinct((seal) => seal.id),
|
|
52
62
|
// Get encrypted content from storage
|
|
53
|
-
mergeMap((
|
|
63
|
+
mergeMap((seal) =>
|
|
54
64
|
// Wait for storage to be available
|
|
55
|
-
storage$.pipe(switchMap((storage) => combineLatest([of(
|
|
65
|
+
storage$.pipe(switchMap((storage) => combineLatest([of(seal), getItem(storage, seal)])), catchError((error) => {
|
|
56
66
|
log(`Failed to restore encrypted content for ${seal.id}`, error);
|
|
57
67
|
return EMPTY;
|
|
58
68
|
}))))
|
|
59
|
-
.subscribe(async ([
|
|
69
|
+
.subscribe(async ([seal, content]) => {
|
|
60
70
|
if (!seal || !content)
|
|
61
71
|
return;
|
|
62
72
|
markEncryptedContentFromCache(seal);
|
|
63
73
|
setEncryptedContentCache(seal, content);
|
|
74
|
+
// Parse the rumor event
|
|
75
|
+
getSealRumor(seal);
|
|
64
76
|
// Trigger an update to the gift wrap event
|
|
65
|
-
|
|
77
|
+
const gift = getSealGiftWrap(seal);
|
|
78
|
+
if (gift)
|
|
79
|
+
notifyEventUpdate(gift);
|
|
66
80
|
log(`Restored encrypted content for ${seal.id}`);
|
|
67
81
|
});
|
|
68
82
|
// Persist encrypted content when it is updated or inserted
|
|
@@ -94,14 +108,14 @@ export function persistEncryptedContent(eventStore, storage) {
|
|
|
94
108
|
// Look for gift wraps that are unlocked
|
|
95
109
|
filter(([event]) => event.kind === kinds.GiftWrap && !isEncryptedContentLocked(event)),
|
|
96
110
|
// Get the seal event
|
|
97
|
-
map(([gift, storage]) => [
|
|
111
|
+
map(([gift, storage]) => [getGiftWrapSeal(gift), storage]),
|
|
98
112
|
// Make sure the seal is defined
|
|
99
|
-
filter(([
|
|
113
|
+
filter(([seal]) => seal !== undefined),
|
|
100
114
|
// Make sure seal is unlocked and not from cache
|
|
101
|
-
filter(([
|
|
115
|
+
filter(([seal]) => !isEncryptedContentLocked(seal) && !isEncryptedContentFromCache(seal)),
|
|
102
116
|
// Only persist the seal once
|
|
103
117
|
distinct(([seal]) => seal.id))
|
|
104
|
-
.subscribe(async ([
|
|
118
|
+
.subscribe(async ([seal, storage]) => {
|
|
105
119
|
if (!seal)
|
|
106
120
|
return;
|
|
107
121
|
try {
|
|
@@ -16,6 +16,8 @@ export function setEncryptedContentEncryptionMethod(kind, method) {
|
|
|
16
16
|
/** Returns either nip04 or nip44 encryption methods depending on event kind */
|
|
17
17
|
export function getEncryptedContentEncryptionMethods(kind, signer) {
|
|
18
18
|
const method = EventContentEncryptionMethod[kind];
|
|
19
|
+
if (!method)
|
|
20
|
+
throw new Error(`Event kind ${kind} does not support encrypted content`);
|
|
19
21
|
const encryption = signer[method];
|
|
20
22
|
if (!encryption)
|
|
21
23
|
throw new Error(`Signer does not support ${method} encryption`);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const INDEXABLE_TAGS: Set<string>;
|
|
3
|
+
export declare const EventIndexableTagsSymbol: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the second index ( tag[1] ) of the first tag that matches the name
|
|
6
|
+
* If the event has any hidden tags they will be searched first
|
|
7
|
+
*/
|
|
8
|
+
export declare function getTagValue<T extends {
|
|
9
|
+
kind: number;
|
|
10
|
+
tags: string[][];
|
|
11
|
+
content: string;
|
|
12
|
+
}>(event: T, name: string): string | undefined;
|
|
13
|
+
/** Returns a Set of tag names and values that are indexable */
|
|
14
|
+
export declare function getIndexableTags(event: NostrEvent): Set<string>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getHiddenTags } from "./hidden-tags.js";
|
|
2
|
+
const LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
3
|
+
export const INDEXABLE_TAGS = new Set((LETTERS + LETTERS.toUpperCase()).split(""));
|
|
4
|
+
export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
|
|
5
|
+
/**
|
|
6
|
+
* Returns the second index ( tag[1] ) of the first tag that matches the name
|
|
7
|
+
* If the event has any hidden tags they will be searched first
|
|
8
|
+
*/
|
|
9
|
+
export function getTagValue(event, name) {
|
|
10
|
+
const hidden = getHiddenTags(event);
|
|
11
|
+
const hiddenValue = hidden?.find((t) => t[0] === name)?.[1];
|
|
12
|
+
if (hiddenValue)
|
|
13
|
+
return hiddenValue;
|
|
14
|
+
return event.tags.find((t) => t[0] === name)?.[1];
|
|
15
|
+
}
|
|
16
|
+
/** Returns a Set of tag names and values that are indexable */
|
|
17
|
+
export function getIndexableTags(event) {
|
|
18
|
+
let indexable = Reflect.get(event, EventIndexableTagsSymbol);
|
|
19
|
+
if (!indexable) {
|
|
20
|
+
const tags = new Set();
|
|
21
|
+
for (const tag of event.tags) {
|
|
22
|
+
if (tag.length >= 2 && tag[0].length === 1 && INDEXABLE_TAGS.has(tag[0])) {
|
|
23
|
+
tags.add(tag[0] + ":" + tag[1]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
indexable = tags;
|
|
27
|
+
Reflect.set(event, EventIndexableTagsSymbol, tags);
|
|
28
|
+
}
|
|
29
|
+
return indexable;
|
|
30
|
+
}
|
package/dist/helpers/event.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { NostrEvent, VerifiedEvent } from "nostr-tools";
|
|
2
2
|
import { IEventStore } from "../event-store/interface.js";
|
|
3
|
+
/** A symbol on an event that marks which event store its part of */
|
|
4
|
+
export declare const EventStoreSymbol: unique symbol;
|
|
3
5
|
export declare const EventUIDSymbol: unique symbol;
|
|
4
6
|
export declare const ReplaceableAddressSymbol: unique symbol;
|
|
5
|
-
export declare const EventIndexableTagsSymbol: unique symbol;
|
|
6
7
|
export declare const FromCacheSymbol: unique symbol;
|
|
7
8
|
export declare const ReplaceableIdentifierSymbol: unique symbol;
|
|
8
9
|
/**
|
|
9
10
|
* Checks if an object is a nostr event
|
|
10
|
-
* NOTE: does not
|
|
11
|
+
* NOTE: does not validate the signature on the event
|
|
11
12
|
*/
|
|
12
13
|
export declare function isEvent(event: any): event is NostrEvent;
|
|
13
14
|
/**
|
|
@@ -28,17 +29,6 @@ export declare function getReplaceableAddress(event: NostrEvent): string;
|
|
|
28
29
|
export declare function createReplaceableAddress(kind: number, pubkey: string, identifier?: string): string;
|
|
29
30
|
/** @deprecated use createReplaceableAddress instead */
|
|
30
31
|
export declare const getReplaceableUID: typeof createReplaceableAddress;
|
|
31
|
-
/** Returns a Set of tag names and values that are indexable */
|
|
32
|
-
export declare function getIndexableTags(event: NostrEvent): Set<string>;
|
|
33
|
-
/**
|
|
34
|
-
* Returns the second index ( tag[1] ) of the first tag that matches the name
|
|
35
|
-
* If the event has any hidden tags they will be searched first
|
|
36
|
-
*/
|
|
37
|
-
export declare function getTagValue<T extends {
|
|
38
|
-
kind: number;
|
|
39
|
-
tags: string[][];
|
|
40
|
-
content: string;
|
|
41
|
-
}>(event: T, name: string): string | undefined;
|
|
42
32
|
/** Sets events verified flag without checking anything */
|
|
43
33
|
export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
|
|
44
34
|
/** Marks an event as being from a cache */
|
package/dist/helpers/event.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { verifiedSymbol } from "nostr-tools";
|
|
2
2
|
import { isAddressableKind, isReplaceableKind } from "nostr-tools/kinds";
|
|
3
|
-
import { INDEXABLE_TAGS } from "../event-store/common.js";
|
|
4
|
-
import { EventStoreSymbol } from "../event-store/event-store.js";
|
|
5
3
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
6
|
-
|
|
4
|
+
/** A symbol on an event that marks which event store its part of */
|
|
5
|
+
export const EventStoreSymbol = Symbol.for("event-store");
|
|
7
6
|
export const EventUIDSymbol = Symbol.for("event-uid");
|
|
8
7
|
export const ReplaceableAddressSymbol = Symbol.for("replaceable-address");
|
|
9
|
-
export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
|
|
10
8
|
export const FromCacheSymbol = Symbol.for("from-cache");
|
|
11
9
|
export const ReplaceableIdentifierSymbol = Symbol.for("replaceable-identifier");
|
|
12
10
|
/**
|
|
13
11
|
* Checks if an object is a nostr event
|
|
14
|
-
* NOTE: does not
|
|
12
|
+
* NOTE: does not validate the signature on the event
|
|
15
13
|
*/
|
|
16
14
|
export function isEvent(event) {
|
|
17
15
|
if (event === undefined || event === null)
|
|
@@ -64,32 +62,6 @@ export function createReplaceableAddress(kind, pubkey, identifier) {
|
|
|
64
62
|
}
|
|
65
63
|
/** @deprecated use createReplaceableAddress instead */
|
|
66
64
|
export const getReplaceableUID = createReplaceableAddress;
|
|
67
|
-
/** Returns a Set of tag names and values that are indexable */
|
|
68
|
-
export function getIndexableTags(event) {
|
|
69
|
-
let indexable = Reflect.get(event, EventIndexableTagsSymbol);
|
|
70
|
-
if (!indexable) {
|
|
71
|
-
const tags = new Set();
|
|
72
|
-
for (const tag of event.tags) {
|
|
73
|
-
if (tag.length >= 2 && tag[0].length === 1 && INDEXABLE_TAGS.has(tag[0])) {
|
|
74
|
-
tags.add(tag[0] + ":" + tag[1]);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
indexable = tags;
|
|
78
|
-
Reflect.set(event, EventIndexableTagsSymbol, tags);
|
|
79
|
-
}
|
|
80
|
-
return indexable;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Returns the second index ( tag[1] ) of the first tag that matches the name
|
|
84
|
-
* If the event has any hidden tags they will be searched first
|
|
85
|
-
*/
|
|
86
|
-
export function getTagValue(event, name) {
|
|
87
|
-
const hidden = getHiddenTags(event);
|
|
88
|
-
const hiddenValue = hidden?.find((t) => t[0] === name)?.[1];
|
|
89
|
-
if (hiddenValue)
|
|
90
|
-
return hiddenValue;
|
|
91
|
-
return event.tags.find((t) => t[0] === name)?.[1];
|
|
92
|
-
}
|
|
93
65
|
/** Sets events verified flag without checking anything */
|
|
94
66
|
export function fakeVerifyEvent(event) {
|
|
95
67
|
event[verifiedSymbol] = true;
|
|
@@ -121,7 +93,7 @@ export function getReplaceableIdentifier(event) {
|
|
|
121
93
|
if (!isAddressableKind(event.kind))
|
|
122
94
|
throw new Error("Event is not addressable");
|
|
123
95
|
return getOrComputeCachedValue(event, ReplaceableIdentifierSymbol, () => {
|
|
124
|
-
const d =
|
|
96
|
+
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
|
125
97
|
if (d === undefined)
|
|
126
98
|
throw new Error("Event missing identifier");
|
|
127
99
|
return d;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getTagValue } from "./event.js";
|
|
2
1
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
2
|
import { unixNow } from "./time.js";
|
|
3
|
+
import { getTagValue } from "./event-tags.js";
|
|
4
4
|
export const ExpirationTimestampSymbol = Symbol("expiration-timestamp");
|
|
5
5
|
/** Returns the NIP-40 expiration timestamp for an event */
|
|
6
6
|
export function getExpirationTimestamp(event) {
|
package/dist/helpers/filter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getIndexableTags } from "./event.js";
|
|
2
1
|
import equal from "fast-deep-equal";
|
|
2
|
+
import { getIndexableTags } from "./event-tags.js";
|
|
3
3
|
/**
|
|
4
4
|
* Copied from nostr-tools and modified to use getIndexableTags
|
|
5
5
|
* @see https://github.com/nbd-wtf/nostr-tools/blob/a61cde77eacc9518001f11d7f67f1a50ae05fd80/filter.ts
|
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import { NostrEvent, UnsignedEvent } from "nostr-tools";
|
|
2
|
+
import { EventSet } from "../event-store/event-set.js";
|
|
2
3
|
import { EncryptedContentSigner } from "./encrypted-content.js";
|
|
4
|
+
/**
|
|
5
|
+
* An internal event set to keep track of seals and rumors
|
|
6
|
+
* This is intentially isolated from the main applications event store so to prevent seals and rumors from being leaked
|
|
7
|
+
*/
|
|
8
|
+
export declare const internalGiftWrapEvents: EventSet;
|
|
3
9
|
export type Rumor = UnsignedEvent & {
|
|
4
10
|
id: string;
|
|
5
11
|
};
|
|
6
|
-
|
|
7
|
-
export declare const
|
|
8
|
-
/**
|
|
9
|
-
export declare
|
|
12
|
+
/** Used to store a reference to the seal event on gift wraps (downstream) or the seal event on rumors (upstream[]) */
|
|
13
|
+
export declare const SealSymbol: unique symbol;
|
|
14
|
+
/** Used to store a reference to the rumor on seals (downstream) */
|
|
15
|
+
export declare const RumorSymbol: unique symbol;
|
|
16
|
+
/** Used to store a reference to the parent gift wrap event on seals (upstream) */
|
|
17
|
+
export declare const GiftWrapSymbol: unique symbol;
|
|
18
|
+
/** Checks if an event is a rumor (normal event with "id" and no "sig") */
|
|
19
|
+
export declare function isRumor(event: any): event is Rumor;
|
|
20
|
+
/** Returns all the parent gift wraps for a seal event */
|
|
21
|
+
export declare function getSealGiftWrap(seal: NostrEvent): NostrEvent | undefined;
|
|
22
|
+
/** Returns all the parent seals for a rumor event */
|
|
23
|
+
export declare function getRumorSeals(rumor: Rumor): NostrEvent[];
|
|
24
|
+
/** Returns all the parent gift wraps for a rumor event */
|
|
25
|
+
export declare function getRumorGiftWraps(rumor: Rumor): NostrEvent[];
|
|
10
26
|
/** Checks if a seal event is locked */
|
|
11
27
|
export declare function isSealLocked(seal: NostrEvent): boolean;
|
|
12
28
|
/** Gets the rumor from a seal event */
|
|
13
29
|
export declare function getSealRumor(seal: NostrEvent): Rumor | undefined;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
30
|
+
/** Returns the seal event in a gift-wrap event */
|
|
31
|
+
export declare function getGiftWrapSeal(gift: NostrEvent): NostrEvent | undefined;
|
|
32
|
+
/** Returns the unsigned rumor in the gift-wrap */
|
|
18
33
|
export declare function getGiftWrapRumor(gift: NostrEvent): Rumor | undefined;
|
|
19
34
|
/** Returns if a gift-wrap event or gift-wrap seal is locked */
|
|
20
35
|
export declare function isGiftWrapLocked(gift: NostrEvent): boolean;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Unlocks a seal event and returns the rumor event
|
|
38
|
+
* @throws {Error} If the author of the rumor event does not match the author of the seal
|
|
39
|
+
*/
|
|
40
|
+
export declare function unlockSeal(seal: NostrEvent, signer: EncryptedContentSigner): Promise<Rumor>;
|
|
41
|
+
/**
|
|
42
|
+
* Unlocks and returns the unsigned seal event in a gift-wrap
|
|
43
|
+
* @throws {Error} If the author of the rumor event does not match the author of the seal
|
|
44
|
+
*/
|
|
45
|
+
export declare function unlockGiftWrap(gift: NostrEvent, signer: EncryptedContentSigner): Promise<Rumor>;
|
|
24
46
|
export declare function lockGiftWrap(gift: NostrEvent): void;
|
|
@@ -1,21 +1,68 @@
|
|
|
1
1
|
import { verifyEvent } from "nostr-tools";
|
|
2
|
-
import {
|
|
2
|
+
import { EventSet } from "../event-store/event-set.js";
|
|
3
3
|
import { getEncryptedContent, isEncryptedContentLocked, lockEncryptedContent, unlockEncryptedContent, } from "./encrypted-content.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
import { notifyEventUpdate } from "./event.js";
|
|
5
|
+
/**
|
|
6
|
+
* An internal event set to keep track of seals and rumors
|
|
7
|
+
* This is intentially isolated from the main applications event store so to prevent seals and rumors from being leaked
|
|
8
|
+
*/
|
|
9
|
+
export const internalGiftWrapEvents = new EventSet();
|
|
10
|
+
/** Used to store a reference to the seal event on gift wraps (downstream) or the seal event on rumors (upstream[]) */
|
|
11
|
+
export const SealSymbol = Symbol.for("seal");
|
|
12
|
+
/** Used to store a reference to the rumor on seals (downstream) */
|
|
13
|
+
export const RumorSymbol = Symbol.for("rumor");
|
|
14
|
+
/** Used to store a reference to the parent gift wrap event on seals (upstream) */
|
|
15
|
+
export const GiftWrapSymbol = Symbol.for("gift-wrap");
|
|
16
|
+
/** Adds a parent reference to a seal or rumor */
|
|
17
|
+
function addParentSealReference(rumor, seal) {
|
|
18
|
+
const parents = Reflect.get(rumor, SealSymbol);
|
|
19
|
+
if (!parents)
|
|
20
|
+
Reflect.set(rumor, SealSymbol, new Set([seal]));
|
|
21
|
+
else
|
|
22
|
+
parents.add(seal);
|
|
23
|
+
}
|
|
24
|
+
/** Removes a parent reference from a seal or rumor */
|
|
25
|
+
function removeParentSealReference(rumor, seal) {
|
|
26
|
+
const parents = Reflect.get(rumor, SealSymbol);
|
|
27
|
+
if (parents)
|
|
28
|
+
parents.delete(seal);
|
|
29
|
+
}
|
|
30
|
+
/** Checks if an event is a rumor (normal event with "id" and no "sig") */
|
|
31
|
+
export function isRumor(event) {
|
|
32
|
+
if (event === undefined || event === null)
|
|
33
|
+
return false;
|
|
34
|
+
return (event.id?.length === 64 &&
|
|
35
|
+
!("sig" in event) &&
|
|
36
|
+
typeof event.pubkey === "string" &&
|
|
37
|
+
event.pubkey.length === 64 &&
|
|
38
|
+
typeof event.content === "string" &&
|
|
39
|
+
Array.isArray(event.tags) &&
|
|
40
|
+
typeof event.created_at === "number" &&
|
|
41
|
+
event.created_at > 0);
|
|
42
|
+
}
|
|
43
|
+
/** Returns all the parent gift wraps for a seal event */
|
|
44
|
+
export function getSealGiftWrap(seal) {
|
|
45
|
+
return Reflect.get(seal, GiftWrapSymbol);
|
|
46
|
+
}
|
|
47
|
+
/** Returns all the parent seals for a rumor event */
|
|
48
|
+
export function getRumorSeals(rumor) {
|
|
49
|
+
let set = Reflect.get(rumor, SealSymbol);
|
|
50
|
+
if (!set) {
|
|
51
|
+
set = new Set();
|
|
52
|
+
Reflect.set(rumor, SealSymbol, set);
|
|
53
|
+
}
|
|
54
|
+
return Array.from(set);
|
|
55
|
+
}
|
|
56
|
+
/** Returns all the parent gift wraps for a rumor event */
|
|
57
|
+
export function getRumorGiftWraps(rumor) {
|
|
58
|
+
const giftWraps = new Set();
|
|
59
|
+
const seals = getRumorSeals(rumor);
|
|
60
|
+
for (const seal of seals) {
|
|
61
|
+
const upstream = getSealGiftWrap(seal);
|
|
62
|
+
if (upstream)
|
|
63
|
+
giftWraps.add(upstream);
|
|
64
|
+
}
|
|
65
|
+
return Array.from(giftWraps);
|
|
19
66
|
}
|
|
20
67
|
/** Checks if a seal event is locked */
|
|
21
68
|
export function isSealLocked(seal) {
|
|
@@ -23,29 +70,64 @@ export function isSealLocked(seal) {
|
|
|
23
70
|
}
|
|
24
71
|
/** Gets the rumor from a seal event */
|
|
25
72
|
export function getSealRumor(seal) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
73
|
+
// Returned cached rumor if it exists (downstream)
|
|
74
|
+
const cached = Reflect.get(seal, RumorSymbol);
|
|
75
|
+
if (cached)
|
|
76
|
+
return cached;
|
|
77
|
+
// Get the encrypted content plaintext
|
|
28
78
|
const plaintext = getEncryptedContent(seal);
|
|
29
79
|
if (!plaintext)
|
|
30
80
|
return undefined;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
81
|
+
let rumor = JSON.parse(plaintext);
|
|
82
|
+
// Check if the rumor event already exists in the internal event set
|
|
83
|
+
const existing = internalGiftWrapEvents.getEvent(rumor.id);
|
|
84
|
+
if (existing)
|
|
85
|
+
// Reuse the existing rumor instance
|
|
86
|
+
rumor = existing;
|
|
87
|
+
else
|
|
88
|
+
// Add to the internal event set
|
|
89
|
+
internalGiftWrapEvents.add(rumor);
|
|
90
|
+
// Save a reference to the parent seal event
|
|
91
|
+
addParentSealReference(rumor, seal);
|
|
92
|
+
// Save a reference to the rumor on the seal (downstream)
|
|
93
|
+
Reflect.set(seal, RumorSymbol, rumor);
|
|
94
|
+
return rumor;
|
|
37
95
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
96
|
+
/** Returns the seal event in a gift-wrap event */
|
|
97
|
+
export function getGiftWrapSeal(gift) {
|
|
98
|
+
// Returned cached seal if it exists (downstream)
|
|
99
|
+
const cached = Reflect.get(gift, SealSymbol);
|
|
100
|
+
if (cached)
|
|
101
|
+
return cached;
|
|
102
|
+
// Get the encrypted content plaintext
|
|
103
|
+
const plaintext = getEncryptedContent(gift);
|
|
104
|
+
if (!plaintext)
|
|
44
105
|
return undefined;
|
|
106
|
+
let seal = JSON.parse(plaintext);
|
|
107
|
+
// Check if the seal event already exists in the internal event set
|
|
108
|
+
const existing = internalGiftWrapEvents.getEvent(seal.id);
|
|
109
|
+
if (existing) {
|
|
110
|
+
// Reuse the existing seal instance
|
|
111
|
+
seal = existing;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Verify the seal event
|
|
115
|
+
verifyEvent(seal);
|
|
116
|
+
// Add to the internal event set
|
|
117
|
+
internalGiftWrapEvents.add(seal);
|
|
118
|
+
// Set the reference to the parent gift wrap event (upstream)
|
|
119
|
+
Reflect.set(seal, GiftWrapSymbol, gift);
|
|
120
|
+
}
|
|
121
|
+
// Save a reference to the seal on the gift wrap (downstream)
|
|
122
|
+
Reflect.set(gift, SealSymbol, seal);
|
|
123
|
+
return seal;
|
|
124
|
+
}
|
|
125
|
+
/** Returns the unsigned rumor in the gift-wrap */
|
|
126
|
+
export function getGiftWrapRumor(gift) {
|
|
45
127
|
const seal = getGiftWrapSeal(gift);
|
|
46
|
-
if (!seal
|
|
128
|
+
if (!seal)
|
|
47
129
|
return undefined;
|
|
48
|
-
return
|
|
130
|
+
return getSealRumor(seal);
|
|
49
131
|
}
|
|
50
132
|
/** Returns if a gift-wrap event or gift-wrap seal is locked */
|
|
51
133
|
export function isGiftWrapLocked(gift) {
|
|
@@ -53,35 +135,62 @@ export function isGiftWrapLocked(gift) {
|
|
|
53
135
|
return true;
|
|
54
136
|
else {
|
|
55
137
|
const seal = getGiftWrapSeal(gift);
|
|
56
|
-
if (!seal ||
|
|
138
|
+
if (!seal || isSealLocked(seal))
|
|
57
139
|
return true;
|
|
58
140
|
else
|
|
59
141
|
return false;
|
|
60
142
|
}
|
|
61
143
|
}
|
|
62
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Unlocks a seal event and returns the rumor event
|
|
146
|
+
* @throws {Error} If the author of the rumor event does not match the author of the seal
|
|
147
|
+
*/
|
|
148
|
+
export async function unlockSeal(seal, signer) {
|
|
149
|
+
if (isEncryptedContentLocked(seal))
|
|
150
|
+
await unlockEncryptedContent(seal, seal.pubkey, signer);
|
|
151
|
+
// Parse the rumor event
|
|
152
|
+
const rumor = getSealRumor(seal);
|
|
153
|
+
if (!rumor)
|
|
154
|
+
throw new Error("Failed to read rumor in gift wrap");
|
|
155
|
+
// Check if the seal and rumor authors match
|
|
156
|
+
if (rumor.pubkey !== seal.pubkey)
|
|
157
|
+
throw new Error("Seal author does not match rumor author");
|
|
158
|
+
return rumor;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Unlocks and returns the unsigned seal event in a gift-wrap
|
|
162
|
+
* @throws {Error} If the author of the rumor event does not match the author of the seal
|
|
163
|
+
*/
|
|
63
164
|
export async function unlockGiftWrap(gift, signer) {
|
|
64
165
|
// First unlock the gift-wrap event
|
|
65
166
|
if (isEncryptedContentLocked(gift))
|
|
66
167
|
await unlockEncryptedContent(gift, gift.pubkey, signer);
|
|
67
|
-
//
|
|
168
|
+
// Get the seal event
|
|
68
169
|
const seal = getGiftWrapSeal(gift);
|
|
69
170
|
if (!seal)
|
|
70
171
|
throw new Error("Failed to read seal in gift wrap");
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Finally get the rumor event
|
|
74
|
-
const rumor = getGiftWrapRumor(gift);
|
|
75
|
-
if (!rumor)
|
|
76
|
-
throw new Error("Failed to read rumor in gift wrap");
|
|
172
|
+
// Unlock the seal event
|
|
173
|
+
const rumor = await unlockSeal(seal, signer);
|
|
77
174
|
// if the event has been added to an event store, notify it
|
|
78
|
-
|
|
79
|
-
notifyEventUpdate(gift);
|
|
175
|
+
notifyEventUpdate(gift);
|
|
80
176
|
return rumor;
|
|
81
177
|
}
|
|
82
|
-
/** Locks a gift-wrap event by removing its cached seal and encrypted content */
|
|
83
178
|
export function lockGiftWrap(gift) {
|
|
84
|
-
|
|
85
|
-
|
|
179
|
+
const seal = getGiftWrapSeal(gift);
|
|
180
|
+
if (seal) {
|
|
181
|
+
const rumor = getSealRumor(seal);
|
|
182
|
+
// Remove the rumors parent seal reference (upstream)
|
|
183
|
+
if (rumor)
|
|
184
|
+
removeParentSealReference(rumor, seal);
|
|
185
|
+
// Remove the seal's parent gift wrap reference (upstream)
|
|
186
|
+
Reflect.deleteProperty(seal, GiftWrapSymbol);
|
|
187
|
+
// Remove the seal's rumor reference (downstream)
|
|
188
|
+
Reflect.deleteProperty(seal, RumorSymbol);
|
|
189
|
+
// Lock the seal's encrypted content
|
|
190
|
+
lockEncryptedContent(seal);
|
|
191
|
+
}
|
|
192
|
+
// Remove the gift wrap's seal reference (downstream)
|
|
193
|
+
Reflect.deleteProperty(gift, SealSymbol);
|
|
194
|
+
// Lock the gift wrap's encrypted content
|
|
86
195
|
lockEncryptedContent(gift);
|
|
87
196
|
}
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./emoji.js";
|
|
|
14
14
|
export * from "./encrypted-content-cache.js";
|
|
15
15
|
export * from "./encrypted-content.js";
|
|
16
16
|
export * from "./encryption.js";
|
|
17
|
+
export * from "./event-tags.js";
|
|
17
18
|
export * from "./event.js";
|
|
18
19
|
export * from "./expiration.js";
|
|
19
20
|
export * from "./external-id.js";
|
package/dist/helpers/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./emoji.js";
|
|
|
14
14
|
export * from "./encrypted-content-cache.js";
|
|
15
15
|
export * from "./encrypted-content.js";
|
|
16
16
|
export * from "./encryption.js";
|
|
17
|
+
export * from "./event-tags.js";
|
|
17
18
|
export * from "./event.js";
|
|
18
19
|
export * from "./expiration.js";
|
|
19
20
|
export * from "./external-id.js";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Rumor } from "./gift-wraps.js";
|
|
1
3
|
/**
|
|
2
4
|
* Groups messages into bubble sets based on the pubkey and time
|
|
3
5
|
*
|
|
@@ -9,3 +11,21 @@ export declare function groupMessageEvents<T extends {
|
|
|
9
11
|
created_at: number;
|
|
10
12
|
pubkey: string;
|
|
11
13
|
}>(messages: T[], buffer?: number): T[][];
|
|
14
|
+
/**
|
|
15
|
+
* Returns all pubkeys of participants of a conversation
|
|
16
|
+
* @param participants - A conversation identifier (pubkey1:pubkey2:pubkey3), or a users pubkey, or a list of participant pubkeys, or a rumor message
|
|
17
|
+
* @returns The participants of the conversation
|
|
18
|
+
*/
|
|
19
|
+
export declare function getConversationParticipants(participants: string | string[] | Rumor | NostrEvent): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Creates a conversation identifier from a users pubkey and alist of correspondants
|
|
22
|
+
* @param participants - The participants of the conversation
|
|
23
|
+
* @returns The conversation identifier
|
|
24
|
+
*/
|
|
25
|
+
export declare function createConversationIdentifier(...participants: (string | string[])[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the conversation identifier for a wrapped direct message
|
|
28
|
+
* @param message - The NIP-17 Rumor or NIP-04 message event
|
|
29
|
+
* @returns The conversation identifier
|
|
30
|
+
*/
|
|
31
|
+
export declare function getConversationIdentifierFromMessage(message: Rumor | NostrEvent): string;
|
package/dist/helpers/messages.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { isPTag } from "./tags.js";
|
|
1
3
|
/**
|
|
2
4
|
* Groups messages into bubble sets based on the pubkey and time
|
|
3
5
|
*
|
|
@@ -17,3 +19,39 @@ export function groupMessageEvents(messages, buffer = 5 * 60) {
|
|
|
17
19
|
}
|
|
18
20
|
return groups;
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns all pubkeys of participants of a conversation
|
|
24
|
+
* @param participants - A conversation identifier (pubkey1:pubkey2:pubkey3), or a users pubkey, or a list of participant pubkeys, or a rumor message
|
|
25
|
+
* @returns The participants of the conversation
|
|
26
|
+
*/
|
|
27
|
+
export function getConversationParticipants(participants) {
|
|
28
|
+
let participantList;
|
|
29
|
+
if (typeof participants === "string") {
|
|
30
|
+
participantList = participants.split(":");
|
|
31
|
+
}
|
|
32
|
+
else if (Array.isArray(participants)) {
|
|
33
|
+
participantList = participants;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
if (participants.kind !== kinds.EncryptedDirectMessage && participants.kind !== kinds.PrivateDirectMessage)
|
|
37
|
+
throw new Error("Can only get participants from direct message event (4, 14)");
|
|
38
|
+
participantList = [participants.pubkey, ...participants.tags.filter(isPTag).map((t) => t[1])];
|
|
39
|
+
}
|
|
40
|
+
return Array.from(new Set(participantList));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a conversation identifier from a users pubkey and alist of correspondants
|
|
44
|
+
* @param participants - The participants of the conversation
|
|
45
|
+
* @returns The conversation identifier
|
|
46
|
+
*/
|
|
47
|
+
export function createConversationIdentifier(...participants) {
|
|
48
|
+
return Array.from(new Set(participants.flat())).sort().join(":");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the conversation identifier for a wrapped direct message
|
|
52
|
+
* @param message - The NIP-17 Rumor or NIP-04 message event
|
|
53
|
+
* @returns The conversation identifier
|
|
54
|
+
*/
|
|
55
|
+
export function getConversationIdentifierFromMessage(message) {
|
|
56
|
+
return createConversationIdentifier(getConversationParticipants(message));
|
|
57
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
2
|
-
import { getTagValue } from "./event.js";
|
|
2
|
+
import { getTagValue } from "./event-tags.js";
|
|
3
3
|
import { getAddressPointerFromATag, getEventPointerFromETag, getProfilePointerFromPTag } from "./pointers.js";
|
|
4
4
|
import { mergeRelaySets } from "./relays.js";
|
|
5
5
|
import { isATag, isETag, isPTag } from "./tags.js";
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
import { Rumor } from "./gift-wraps.js";
|
|
2
|
-
/**
|
|
3
|
-
* Returns all pubkeys of participants of a conversation
|
|
4
|
-
* @param participants - The conversation identifier (pubkey1:pubkey2:pubkey3), or a users pubkey, or a list of participant pubkeys, or a rumor message
|
|
5
|
-
* @returns The participants of the conversation
|
|
6
|
-
*/
|
|
7
|
-
export declare function getConversationParticipants(participants: string | string[] | Rumor): string[];
|
|
8
|
-
/**
|
|
9
|
-
* Creates a conversation identifier from a users pubkey and alist of correspondants
|
|
10
|
-
* @param participants - The participants of the conversation
|
|
11
|
-
* @returns The conversation identifier
|
|
12
|
-
*/
|
|
13
|
-
export declare function createConversationIdentifier(participants: string[]): string;
|
|
14
|
-
/**
|
|
15
|
-
* Returns the conversation identifier for a wrapped direct message
|
|
16
|
-
* @param message - The message to get the conversation identifier for
|
|
17
|
-
* @returns The conversation identifier
|
|
18
|
-
*/
|
|
19
|
-
export declare function getConversationIdentifierFromMessage(message: Rumor): string;
|
|
20
2
|
/** Returns the subject of a warpped direct message */
|
|
21
3
|
export declare function getWrappedMessageSubject(message: Rumor): string | undefined;
|
|
22
4
|
/** Returns the parent id of a wrapped direct message */
|
|
@@ -1,33 +1,4 @@
|
|
|
1
1
|
import { getTagValue } from "./index.js";
|
|
2
|
-
import { isPTag } from "./tags.js";
|
|
3
|
-
/**
|
|
4
|
-
* Returns all pubkeys of participants of a conversation
|
|
5
|
-
* @param participants - The conversation identifier (pubkey1:pubkey2:pubkey3), or a users pubkey, or a list of participant pubkeys, or a rumor message
|
|
6
|
-
* @returns The participants of the conversation
|
|
7
|
-
*/
|
|
8
|
-
export function getConversationParticipants(participants) {
|
|
9
|
-
return Array.from(new Set(typeof participants === "string"
|
|
10
|
-
? participants.split(":")
|
|
11
|
-
: Array.isArray(participants)
|
|
12
|
-
? participants
|
|
13
|
-
: [participants.pubkey, ...participants.tags.filter(isPTag).map((t) => t[1])]));
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Creates a conversation identifier from a users pubkey and alist of correspondants
|
|
17
|
-
* @param participants - The participants of the conversation
|
|
18
|
-
* @returns The conversation identifier
|
|
19
|
-
*/
|
|
20
|
-
export function createConversationIdentifier(participants) {
|
|
21
|
-
return Array.from(new Set(participants)).sort().join(":");
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Returns the conversation identifier for a wrapped direct message
|
|
25
|
-
* @param message - The message to get the conversation identifier for
|
|
26
|
-
* @returns The conversation identifier
|
|
27
|
-
*/
|
|
28
|
-
export function getConversationIdentifierFromMessage(message) {
|
|
29
|
-
return createConversationIdentifier(getConversationParticipants(message));
|
|
30
|
-
}
|
|
31
2
|
/** Returns the subject of a warpped direct message */
|
|
32
3
|
export function getWrappedMessageSubject(message) {
|
|
33
4
|
return getTagValue(message, "subject");
|
package/dist/helpers/zap.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
-
import { ParsedInvoice } from "./bolt11.js";
|
|
3
2
|
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
import { ParsedInvoice } from "./bolt11.js";
|
|
4
4
|
export declare const ZapRequestSymbol: unique symbol;
|
|
5
5
|
export declare const ZapSenderSymbol: unique symbol;
|
|
6
6
|
export declare const ZapReceiverSymbol: unique symbol;
|
package/dist/helpers/zap.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { kinds, nip57 } from "nostr-tools";
|
|
2
|
+
import { parseBolt11 } from "./bolt11.js";
|
|
2
3
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
-
import { getTagValue } from "./event.js";
|
|
4
|
-
import { isATag, isETag } from "./tags.js";
|
|
4
|
+
import { getTagValue } from "./event-tags.js";
|
|
5
5
|
import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isATag, isETag } from "./tags.js";
|
|
7
7
|
export const ZapRequestSymbol = Symbol.for("zap-request");
|
|
8
8
|
export const ZapSenderSymbol = Symbol.for("zap-sender");
|
|
9
9
|
export const ZapReceiverSymbol = Symbol.for("zap-receiver");
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
2
|
import { Model } from "../event-store/interface.js";
|
|
3
|
-
/**
|
|
4
|
-
export declare function
|
|
3
|
+
/** A model that returns all legacy message groups (1-1) that a pubkey is participating in */
|
|
4
|
+
export declare function LegacyMessagesGroups(self: string): Model<{
|
|
5
|
+
id: string;
|
|
6
|
+
participants: string[];
|
|
7
|
+
lastMessage: NostrEvent;
|
|
8
|
+
}[]>;
|
|
9
|
+
/** Returns all legacy direct messages in a group */
|
|
10
|
+
export declare function LegacyMessagesGroup(self: string, corraspondant: string): Model<NostrEvent[]>;
|
|
5
11
|
/** Returns an array of legacy messages that have replies */
|
|
6
12
|
export declare function LegacyMessageThreads(self: string, corraspondant: string): Model<NostrEvent[]>;
|
|
7
13
|
/** Returns all the legacy direct messages that are replies to a given message */
|
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
2
|
import { getLegacyMessageCorraspondant, getLegacyMessageParent } from "../helpers/legacy-messages.js";
|
|
3
3
|
import { map } from "rxjs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import { getConversationIdentifierFromMessage, getConversationParticipants } from "../helpers/messages.js";
|
|
5
|
+
/** A model that returns all legacy message groups (1-1) that a pubkey is participating in */
|
|
6
|
+
export function LegacyMessagesGroups(self) {
|
|
7
|
+
return (store) => store.timeline({ kinds: [kinds.EncryptedDirectMessage], "#p": [self] }).pipe(map((messages) => {
|
|
8
|
+
const groups = {};
|
|
9
|
+
for (const message of messages) {
|
|
10
|
+
const id = getConversationIdentifierFromMessage(message);
|
|
11
|
+
if (!groups[id] || groups[id].created_at < message.created_at)
|
|
12
|
+
groups[id] = message;
|
|
13
|
+
}
|
|
14
|
+
return Object.values(groups).map((message) => ({
|
|
15
|
+
id: getConversationIdentifierFromMessage(message),
|
|
16
|
+
participants: getConversationParticipants(message),
|
|
17
|
+
lastMessage: message,
|
|
18
|
+
}));
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
/** Returns all legacy direct messages in a group */
|
|
22
|
+
export function LegacyMessagesGroup(self, corraspondant) {
|
|
23
|
+
return (store) => store.timeline([
|
|
24
|
+
{
|
|
25
|
+
kinds: [kinds.EncryptedDirectMessage],
|
|
26
|
+
"#p": [self],
|
|
27
|
+
authors: [corraspondant],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
kinds: [kinds.EncryptedDirectMessage],
|
|
31
|
+
"#p": [corraspondant],
|
|
32
|
+
authors: [self],
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
11
35
|
}
|
|
12
36
|
/** Returns an array of legacy messages that have replies */
|
|
13
37
|
export function LegacyMessageThreads(self, corraspondant) {
|
|
14
|
-
return (store) => store.model(
|
|
38
|
+
return (store) => store.model(LegacyMessagesGroup, self, corraspondant).pipe(map((messages) => messages.filter((message) =>
|
|
15
39
|
// Only select messages that are not replies
|
|
16
40
|
!getLegacyMessageParent(message) &&
|
|
17
41
|
// Check if message has any replies
|
|
@@ -20,10 +44,18 @@ export function LegacyMessageThreads(self, corraspondant) {
|
|
|
20
44
|
/** Returns all the legacy direct messages that are replies to a given message */
|
|
21
45
|
export function LegacyMessageReplies(self, message) {
|
|
22
46
|
const corraspondant = getLegacyMessageCorraspondant(message, self);
|
|
23
|
-
return (store) => store.timeline(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
return (store) => store.timeline([
|
|
48
|
+
{
|
|
49
|
+
kinds: [kinds.EncryptedDirectMessage],
|
|
50
|
+
"#p": [self],
|
|
51
|
+
authors: [corraspondant],
|
|
52
|
+
"#e": [message.id],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
kinds: [kinds.EncryptedDirectMessage],
|
|
56
|
+
"#p": [corraspondant],
|
|
57
|
+
authors: [self],
|
|
58
|
+
"#e": [message.id],
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
29
61
|
}
|
package/dist/models/thread.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
2
|
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
-
import { ThreadReferences } from "../helpers/threading.js";
|
|
4
3
|
import { Model } from "../event-store/interface.js";
|
|
4
|
+
import { ThreadReferences } from "../helpers/threading.js";
|
|
5
5
|
export type Thread = {
|
|
6
6
|
root?: ThreadItem;
|
|
7
7
|
all: Map<string, ThreadItem>;
|
package/dist/models/thread.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
2
|
import { isAddressableKind } from "nostr-tools/kinds";
|
|
3
3
|
import { map } from "rxjs/operators";
|
|
4
|
-
import { getNip10References, interpretThreadTags } from "../helpers/threading.js";
|
|
5
|
-
import { getCoordinateFromAddressPointer, isAddressPointer, isEventPointer } from "../helpers/pointers.js";
|
|
6
|
-
import { getEventUID, createReplaceableAddress, getTagValue, isEvent } from "../helpers/event.js";
|
|
7
4
|
import { COMMENT_KIND } from "../helpers/comment.js";
|
|
5
|
+
import { getTagValue } from "../helpers/event-tags.js";
|
|
6
|
+
import { createReplaceableAddress, getEventUID, isEvent } from "../helpers/event.js";
|
|
7
|
+
import { getCoordinateFromAddressPointer, isAddressPointer, isEventPointer } from "../helpers/pointers.js";
|
|
8
|
+
import { getNip10References, interpretThreadTags } from "../helpers/threading.js";
|
|
8
9
|
const defaultOptions = {
|
|
9
10
|
kinds: [kinds.ShortTextNote],
|
|
10
11
|
};
|
|
@@ -5,18 +5,24 @@ import { Rumor } from "../helpers/gift-wraps.js";
|
|
|
5
5
|
* @param self - The pubkey of the user
|
|
6
6
|
*/
|
|
7
7
|
export declare function WrappedMessagesModel(self: string): Model<Rumor[]>;
|
|
8
|
+
/** A model that returns all conversations that a pubkey is participating in */
|
|
9
|
+
export declare function WrappedMessagesGroups(self: string): Model<{
|
|
10
|
+
id: string;
|
|
11
|
+
participants: string[];
|
|
12
|
+
lastMessage: Rumor;
|
|
13
|
+
}[]>;
|
|
8
14
|
/**
|
|
9
15
|
* A model that returns all wrapped direct messages in a conversation
|
|
10
16
|
* @param self - The pubkey of the user
|
|
11
17
|
* @param participants - A conversation identifier or a list of participant pubkeys
|
|
12
18
|
*/
|
|
13
|
-
export declare function
|
|
19
|
+
export declare function WrappedMessagesGroup(self: string, participants: string | string[]): Model<Rumor[]>;
|
|
14
20
|
/**
|
|
15
21
|
* Returns an array of root wrapped messages that have replies
|
|
16
22
|
* @param self - The pubkey of the user
|
|
17
|
-
* @param
|
|
23
|
+
* @param participants - A conversation identifier or a list of participant pubkeys
|
|
18
24
|
*/
|
|
19
|
-
export declare function WrappedMessageThreads(self: string,
|
|
25
|
+
export declare function WrappedMessageThreads(self: string, participants: string | string[]): Model<Rumor[]>;
|
|
20
26
|
/**
|
|
21
27
|
* A model that returns all the gift wrapped direct messages that are replies to a given message
|
|
22
28
|
* @param self - The pubkey of the user
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
2
|
import { map } from "rxjs";
|
|
3
3
|
import { getGiftWrapRumor } from "../helpers/gift-wraps.js";
|
|
4
|
-
import { createConversationIdentifier, getConversationIdentifierFromMessage, getConversationParticipants,
|
|
4
|
+
import { createConversationIdentifier, getConversationIdentifierFromMessage, getConversationParticipants, } from "../helpers/messages.js";
|
|
5
|
+
import { getWrappedMessageParent } from "../helpers/wrapped-messages.js";
|
|
5
6
|
import { watchEventsUpdates } from "../observable/watch-event-updates.js";
|
|
6
7
|
/**
|
|
7
8
|
* A model that returns all wrapped messages for a pubkey
|
|
@@ -15,19 +16,33 @@ export function WrappedMessagesModel(self) {
|
|
|
15
16
|
map((rumors) => rumors
|
|
16
17
|
.map((gift) => getGiftWrapRumor(gift))
|
|
17
18
|
.filter((e) => !!e)
|
|
19
|
+
.filter((e) => e.kind === kinds.PrivateDirectMessage)
|
|
18
20
|
.sort((a, b) => b.created_at - a.created_at)));
|
|
19
21
|
}
|
|
22
|
+
/** A model that returns all conversations that a pubkey is participating in */
|
|
23
|
+
export function WrappedMessagesGroups(self) {
|
|
24
|
+
return (store) => store.model(WrappedMessagesModel, self).pipe(map((messages) => {
|
|
25
|
+
const groups = {};
|
|
26
|
+
for (const message of messages) {
|
|
27
|
+
const id = getConversationIdentifierFromMessage(message);
|
|
28
|
+
if (!groups[id] || groups[id].created_at < message.created_at)
|
|
29
|
+
groups[id] = message;
|
|
30
|
+
}
|
|
31
|
+
return Object.values(groups).map((message) => ({
|
|
32
|
+
id: getConversationIdentifierFromMessage(message),
|
|
33
|
+
participants: getConversationParticipants(message),
|
|
34
|
+
lastMessage: message,
|
|
35
|
+
}));
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
20
38
|
/**
|
|
21
39
|
* A model that returns all wrapped direct messages in a conversation
|
|
22
40
|
* @param self - The pubkey of the user
|
|
23
41
|
* @param participants - A conversation identifier or a list of participant pubkeys
|
|
24
42
|
*/
|
|
25
|
-
export function
|
|
43
|
+
export function WrappedMessagesGroup(self, participants) {
|
|
26
44
|
// Get the conversation identifier include the users pubkey
|
|
27
|
-
const identifier = createConversationIdentifier(
|
|
28
|
-
self,
|
|
29
|
-
...(typeof participants === "string" ? getConversationParticipants(participants) : participants),
|
|
30
|
-
]);
|
|
45
|
+
const identifier = createConversationIdentifier(self, participants);
|
|
31
46
|
return (store) => store.model(WrappedMessagesModel, self).pipe(
|
|
32
47
|
// Only select direct messages for this conversation
|
|
33
48
|
map((rumors) => rumors.filter((rumor) => rumor.kind === kinds.PrivateDirectMessage &&
|
|
@@ -37,10 +52,10 @@ export function WrappedMessagesConversation(self, participants) {
|
|
|
37
52
|
/**
|
|
38
53
|
* Returns an array of root wrapped messages that have replies
|
|
39
54
|
* @param self - The pubkey of the user
|
|
40
|
-
* @param
|
|
55
|
+
* @param participants - A conversation identifier or a list of participant pubkeys
|
|
41
56
|
*/
|
|
42
|
-
export function WrappedMessageThreads(self,
|
|
43
|
-
return (store) => store.model(
|
|
57
|
+
export function WrappedMessageThreads(self, participants) {
|
|
58
|
+
return (store) => store.model(WrappedMessagesGroup, self, participants).pipe(
|
|
44
59
|
// Filter down messages to only include root messages that have replies
|
|
45
60
|
map((rumors) => rumors.filter((rumor) =>
|
|
46
61
|
// Only select root messages
|
|
@@ -55,7 +70,7 @@ export function WrappedMessageThreads(self, conversation) {
|
|
|
55
70
|
*/
|
|
56
71
|
export function WrappedMessageReplies(self, message) {
|
|
57
72
|
const conversation = getConversationIdentifierFromMessage(message);
|
|
58
|
-
return (store) => store.model(
|
|
73
|
+
return (store) => store.model(WrappedMessagesGroup, self, conversation).pipe(
|
|
59
74
|
// Only select replies to this message
|
|
60
75
|
map((rumors) => rumors.filter((rumor) => getWrappedMessageParent(rumor) === message.id)));
|
|
61
76
|
}
|