applesauce-core 0.10.0 → 0.12.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/__tests__/fixtures.d.ts +8 -0
- package/dist/__tests__/fixtures.js +20 -0
- package/dist/event-store/__tests__/event-store.test.js +272 -0
- package/dist/event-store/database.d.ts +7 -5
- package/dist/event-store/database.js +14 -8
- package/dist/event-store/event-store.d.ts +40 -20
- package/dist/event-store/event-store.js +269 -314
- package/dist/event-store/index.d.ts +1 -0
- package/dist/event-store/index.js +1 -0
- package/dist/event-store/interface.d.ts +27 -0
- package/dist/helpers/__tests__/blossom.test.js +13 -0
- package/dist/helpers/__tests__/comment.test.d.ts +1 -0
- package/dist/helpers/__tests__/comment.test.js +235 -0
- package/dist/helpers/__tests__/emoji.test.d.ts +1 -0
- package/dist/helpers/__tests__/emoji.test.js +15 -0
- package/dist/helpers/__tests__/event.test.d.ts +1 -0
- package/dist/helpers/__tests__/event.test.js +36 -0
- package/dist/helpers/__tests__/file-metadata.test.d.ts +1 -0
- package/dist/helpers/__tests__/file-metadata.test.js +103 -0
- package/dist/helpers/__tests__/hidden-tags.test.d.ts +1 -0
- package/dist/helpers/{hidden-tags.test.js → __tests__/hidden-tags.test.js} +2 -1
- package/dist/helpers/__tests__/mailboxes.test.d.ts +1 -0
- package/dist/helpers/{mailboxes.test.js → __tests__/mailboxes.test.js} +1 -1
- package/dist/helpers/__tests__/nip-19.test.d.ts +1 -0
- package/dist/helpers/__tests__/nip-19.test.js +42 -0
- package/dist/helpers/__tests__/relays.test.d.ts +1 -0
- package/dist/helpers/__tests__/relays.test.js +21 -0
- package/dist/helpers/__tests__/tags.test.d.ts +1 -0
- package/dist/helpers/__tests__/tags.test.js +24 -0
- package/dist/helpers/__tests__/threading.test.d.ts +1 -0
- package/dist/helpers/{threading.test.js → __tests__/threading.test.js} +1 -1
- package/dist/helpers/blossom.d.ts +9 -0
- package/dist/helpers/blossom.js +22 -0
- package/dist/helpers/bookmarks.d.ts +15 -0
- package/dist/helpers/bookmarks.js +27 -0
- package/dist/helpers/cache.d.ts +3 -4
- package/dist/helpers/cache.js +1 -1
- package/dist/helpers/channels.d.ts +10 -0
- package/dist/helpers/channels.js +27 -0
- package/dist/helpers/comment.d.ts +3 -4
- package/dist/helpers/comment.js +20 -16
- package/dist/helpers/contacts.d.ts +3 -0
- package/dist/helpers/contacts.js +25 -0
- package/dist/helpers/direct-messages.d.ts +4 -0
- package/dist/helpers/direct-messages.js +5 -0
- package/dist/helpers/dns-identity.d.ts +7 -0
- package/dist/helpers/dns-identity.js +10 -0
- package/dist/helpers/emoji.d.ts +3 -1
- package/dist/helpers/emoji.js +2 -2
- package/dist/helpers/event.d.ts +15 -1
- package/dist/helpers/event.js +34 -11
- package/dist/helpers/file-metadata.d.ts +55 -0
- package/dist/helpers/file-metadata.js +99 -0
- package/dist/helpers/filter.d.ts +4 -0
- package/dist/helpers/filter.js +34 -1
- package/dist/helpers/gift-wraps.d.ts +12 -0
- package/dist/helpers/gift-wraps.js +49 -0
- package/dist/helpers/groups.d.ts +24 -0
- package/dist/helpers/groups.js +39 -0
- package/dist/helpers/hidden-content.d.ts +48 -0
- package/dist/helpers/hidden-content.js +88 -0
- package/dist/helpers/hidden-tags.d.ts +17 -35
- package/dist/helpers/hidden-tags.js +26 -83
- package/dist/helpers/index.d.ts +16 -1
- package/dist/helpers/index.js +16 -1
- package/dist/helpers/lists.d.ts +28 -0
- package/dist/helpers/lists.js +65 -0
- package/dist/helpers/mailboxes.js +16 -9
- package/dist/helpers/mutes.d.ts +15 -0
- package/dist/helpers/mutes.js +24 -0
- package/dist/helpers/nip-19.d.ts +4 -0
- package/dist/helpers/nip-19.js +27 -0
- package/dist/helpers/picture-post.d.ts +4 -0
- package/dist/helpers/picture-post.js +6 -0
- package/dist/helpers/pointers.js +13 -17
- package/dist/helpers/profile.d.ts +6 -1
- package/dist/helpers/profile.js +4 -0
- package/dist/helpers/relays.d.ts +6 -3
- package/dist/helpers/relays.js +25 -18
- package/dist/helpers/share.d.ts +4 -0
- package/dist/helpers/share.js +12 -0
- package/dist/helpers/tags.d.ts +17 -0
- package/dist/helpers/tags.js +28 -6
- package/dist/helpers/threading.js +3 -3
- package/dist/helpers/url.d.ts +7 -0
- package/dist/helpers/url.js +27 -0
- package/dist/helpers/user-status.d.ts +18 -0
- package/dist/helpers/user-status.js +21 -0
- package/dist/observable/__tests__/claim-events.test.d.ts +1 -0
- package/dist/observable/__tests__/claim-events.test.js +23 -0
- package/dist/observable/__tests__/claim-latest.test.d.ts +1 -0
- package/dist/observable/__tests__/claim-latest.test.js +37 -0
- package/dist/observable/__tests__/simple-timeout.test.d.ts +1 -0
- package/dist/observable/__tests__/simple-timeout.test.js +34 -0
- package/dist/observable/claim-events.d.ts +5 -0
- package/dist/observable/claim-events.js +28 -0
- package/dist/observable/claim-latest.d.ts +5 -0
- package/dist/observable/claim-latest.js +21 -0
- package/dist/observable/{get-value.d.ts → get-observable-value.d.ts} +1 -1
- package/dist/observable/{get-value.js → get-observable-value.js} +3 -8
- package/dist/observable/index.d.ts +2 -1
- package/dist/observable/index.js +2 -1
- package/dist/observable/share-latest-value.d.ts +2 -4
- package/dist/observable/share-latest-value.js +19 -16
- package/dist/observable/simple-timeout.d.ts +4 -0
- package/dist/observable/simple-timeout.js +6 -0
- package/dist/observable/with-immediate-value.d.ts +3 -0
- package/dist/observable/with-immediate-value.js +19 -0
- package/dist/queries/blossom.d.ts +2 -0
- package/dist/queries/blossom.js +10 -0
- package/dist/queries/bookmarks.d.ts +8 -0
- package/dist/queries/bookmarks.js +23 -0
- package/dist/queries/channels.d.ts +11 -0
- package/dist/queries/channels.js +73 -0
- package/dist/queries/contacts.d.ts +3 -0
- package/dist/queries/contacts.js +12 -0
- package/dist/queries/index.d.ts +6 -0
- package/dist/queries/index.js +6 -0
- package/dist/queries/mutes.d.ts +8 -0
- package/dist/queries/mutes.js +23 -0
- package/dist/queries/pins.d.ts +3 -0
- package/dist/queries/pins.js +12 -0
- package/dist/queries/simple.d.ts +3 -3
- package/dist/queries/simple.js +3 -3
- package/dist/queries/thread.js +1 -1
- package/dist/queries/user-status.d.ts +11 -0
- package/dist/queries/user-status.js +39 -0
- package/dist/query-store/__tests__/query-store.test.d.ts +1 -0
- package/dist/query-store/__tests__/query-store.test.js +63 -0
- package/dist/query-store/index.d.ts +1 -57
- package/dist/query-store/index.js +1 -66
- package/dist/query-store/query-store.d.ts +53 -0
- package/dist/query-store/query-store.js +97 -0
- package/package.json +20 -8
- package/dist/helpers/media-attachment.d.ts +0 -33
- package/dist/helpers/media-attachment.js +0 -60
- /package/dist/{helpers/hidden-tags.test.d.ts → event-store/__tests__/event-store.test.d.ts} +0 -0
- /package/dist/{helpers/mailboxes.test.d.ts → event-store/interface.js} +0 -0
- /package/dist/helpers/{threading.test.d.ts → __tests__/blossom.test.d.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { safeRelayUrl } from "./relays.js";
|
|
2
1
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
2
|
+
import { isSafeRelayURL } from "./relays.js";
|
|
3
|
+
import { normalizeURL } from "./url.js";
|
|
3
4
|
export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
|
|
4
5
|
export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
|
|
5
6
|
/**
|
|
@@ -9,10 +10,13 @@ export function getInboxes(event) {
|
|
|
9
10
|
return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
|
|
10
11
|
const inboxes = [];
|
|
11
12
|
for (const tag of event.tags) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const [name, url, mode] = tag;
|
|
14
|
+
if (name === "r" &&
|
|
15
|
+
url &&
|
|
16
|
+
isSafeRelayURL(url) &&
|
|
17
|
+
!inboxes.includes(url) &&
|
|
18
|
+
(mode === "read" || mode === undefined)) {
|
|
19
|
+
inboxes.push(normalizeURL(url));
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
return inboxes;
|
|
@@ -25,10 +29,13 @@ export function getOutboxes(event) {
|
|
|
25
29
|
return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
|
|
26
30
|
const outboxes = [];
|
|
27
31
|
for (const tag of event.tags) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const [name, url, mode] = tag;
|
|
33
|
+
if (name === "r" &&
|
|
34
|
+
url &&
|
|
35
|
+
isSafeRelayURL(url) &&
|
|
36
|
+
!outboxes.includes(url) &&
|
|
37
|
+
(mode === "write" || mode === undefined)) {
|
|
38
|
+
outboxes.push(normalizeURL(url));
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
return outboxes;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const MutePublicSymbol: unique symbol;
|
|
3
|
+
export declare const MuteHiddenSymbol: unique symbol;
|
|
4
|
+
export type Mutes = {
|
|
5
|
+
pubkeys: Set<string>;
|
|
6
|
+
threads: Set<string>;
|
|
7
|
+
hashtags: Set<string>;
|
|
8
|
+
words: Set<string>;
|
|
9
|
+
};
|
|
10
|
+
/** Parses mute tags */
|
|
11
|
+
export declare function parseMutedTags(tags: string[][]): Mutes;
|
|
12
|
+
/** Returns muted things */
|
|
13
|
+
export declare function getMutedThings(mute: NostrEvent): Mutes;
|
|
14
|
+
/** Returns the hidden muted content if the event is unlocked */
|
|
15
|
+
export declare function getHiddenMutedThings(mute: NostrEvent): Mutes | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isETag, isPTag, isTTag } from "./tags.js";
|
|
2
|
+
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
+
import { getHiddenTags } from "./hidden-tags.js";
|
|
4
|
+
export const MutePublicSymbol = Symbol.for("mute-public");
|
|
5
|
+
export const MuteHiddenSymbol = Symbol.for("mute-hidden");
|
|
6
|
+
/** Parses mute tags */
|
|
7
|
+
export function parseMutedTags(tags) {
|
|
8
|
+
const pubkeys = new Set(tags.filter(isPTag).map((t) => t[1]));
|
|
9
|
+
const threads = new Set(tags.filter(isETag).map((t) => t[1]));
|
|
10
|
+
const hashtags = new Set(tags.filter(isTTag).map((t) => t[1].toLocaleLowerCase()));
|
|
11
|
+
const words = new Set(tags.filter((t) => t[0] === "word" && t[1]).map((t) => t[1].toLocaleLowerCase()));
|
|
12
|
+
return { pubkeys, threads, hashtags, words };
|
|
13
|
+
}
|
|
14
|
+
/** Returns muted things */
|
|
15
|
+
export function getMutedThings(mute) {
|
|
16
|
+
return getOrComputeCachedValue(mute, MutePublicSymbol, () => parseMutedTags(mute.tags));
|
|
17
|
+
}
|
|
18
|
+
/** Returns the hidden muted content if the event is unlocked */
|
|
19
|
+
export function getHiddenMutedThings(mute) {
|
|
20
|
+
return getOrComputeCachedValue(mute, MuteHiddenSymbol, () => {
|
|
21
|
+
const tags = getHiddenTags(mute);
|
|
22
|
+
return tags && parseMutedTags(tags);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { nip19 } from "nostr-tools";
|
|
2
|
+
import { hexToBytes } from "@noble/hashes/utils";
|
|
3
|
+
import { isHexKey } from "./string.js";
|
|
4
|
+
import { getPubkeyFromDecodeResult } from "./pointers.js";
|
|
5
|
+
/** Gets the hex pubkey from any nip-19 encoded string */
|
|
6
|
+
export function normalizeToPubkey(str) {
|
|
7
|
+
if (isHexKey(str))
|
|
8
|
+
return str;
|
|
9
|
+
else {
|
|
10
|
+
const decode = nip19.decode(str);
|
|
11
|
+
const pubkey = getPubkeyFromDecodeResult(decode);
|
|
12
|
+
if (!pubkey)
|
|
13
|
+
throw new Error(`Cant find pubkey in ${decode.type}`);
|
|
14
|
+
return pubkey;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Converts hex to nsec strings into Uint8 secret keys */
|
|
18
|
+
export function normalizeToSecretKey(str) {
|
|
19
|
+
if (isHexKey(str))
|
|
20
|
+
return hexToBytes(str);
|
|
21
|
+
else {
|
|
22
|
+
const decode = nip19.decode(str);
|
|
23
|
+
if (decode.type !== "nsec")
|
|
24
|
+
throw new Error(`Cant get secret key from ${decode.type}`);
|
|
25
|
+
return decode.data;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/helpers/pointers.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
|
|
2
2
|
import { getPublicKey, kinds } from "nostr-tools";
|
|
3
|
-
import {
|
|
4
|
-
import { getTagValue } from "./index.js";
|
|
3
|
+
import { getReplaceableIdentifier } from "./event.js";
|
|
5
4
|
import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
|
|
5
|
+
import { isSafeRelayURL } from "./relays.js";
|
|
6
6
|
export function parseCoordinate(a, requireD = false, silent = true) {
|
|
7
7
|
const parts = a.split(":");
|
|
8
8
|
const kind = parts[0] && parseInt(parts[0]);
|
|
@@ -74,8 +74,8 @@ export function getEventPointerFromETag(tag) {
|
|
|
74
74
|
if (!tag[1])
|
|
75
75
|
throw new Error("Missing event id in tag");
|
|
76
76
|
let pointer = { id: tag[1] };
|
|
77
|
-
if (tag[2])
|
|
78
|
-
pointer.relays =
|
|
77
|
+
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
78
|
+
pointer.relays = [tag[2]];
|
|
79
79
|
return pointer;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
@@ -86,8 +86,8 @@ export function getEventPointerFromQTag(tag) {
|
|
|
86
86
|
if (!tag[1])
|
|
87
87
|
throw new Error("Missing event id in tag");
|
|
88
88
|
let pointer = { id: tag[1] };
|
|
89
|
-
if (tag[2])
|
|
90
|
-
pointer.relays =
|
|
89
|
+
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
90
|
+
pointer.relays = [tag[2]];
|
|
91
91
|
if (tag[3] && tag[3].length === 64)
|
|
92
92
|
pointer.author = tag[3];
|
|
93
93
|
return pointer;
|
|
@@ -100,8 +100,8 @@ export function getAddressPointerFromATag(tag) {
|
|
|
100
100
|
if (!tag[1])
|
|
101
101
|
throw new Error("Missing coordinate in tag");
|
|
102
102
|
const pointer = parseCoordinate(tag[1], true, false);
|
|
103
|
-
if (tag[2])
|
|
104
|
-
pointer.relays =
|
|
103
|
+
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
104
|
+
pointer.relays = [tag[2]];
|
|
105
105
|
return pointer;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
@@ -112,8 +112,8 @@ export function getProfilePointerFromPTag(tag) {
|
|
|
112
112
|
if (!tag[1])
|
|
113
113
|
throw new Error("Missing pubkey in tag");
|
|
114
114
|
const pointer = { pubkey: tag[1] };
|
|
115
|
-
if (tag[2])
|
|
116
|
-
pointer.relays =
|
|
115
|
+
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
116
|
+
pointer.relays = [tag[2]];
|
|
117
117
|
return pointer;
|
|
118
118
|
}
|
|
119
119
|
/** Parses "e", "a", "p", and "q" tags into a pointer */
|
|
@@ -148,7 +148,7 @@ export function isEventPointer(pointer) {
|
|
|
148
148
|
}
|
|
149
149
|
/** Returns the coordinate string for an AddressPointer */
|
|
150
150
|
export function getCoordinateFromAddressPointer(pointer) {
|
|
151
|
-
return
|
|
151
|
+
return pointer.kind + ":" + pointer.pubkey + ":" + pointer.identifier;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
154
|
* Returns an AddressPointer for a replaceable event
|
|
@@ -157,9 +157,7 @@ export function getCoordinateFromAddressPointer(pointer) {
|
|
|
157
157
|
export function getAddressPointerForEvent(event, relays) {
|
|
158
158
|
if (!isParameterizedReplaceableKind(event.kind))
|
|
159
159
|
throw new Error("Cant get AddressPointer for non-replaceable event");
|
|
160
|
-
const d =
|
|
161
|
-
if (!d)
|
|
162
|
-
throw new Error("Event missing identifier");
|
|
160
|
+
const d = getReplaceableIdentifier(event);
|
|
163
161
|
return {
|
|
164
162
|
identifier: d,
|
|
165
163
|
kind: event.kind,
|
|
@@ -182,9 +180,7 @@ export function getEventPointerForEvent(event, relays) {
|
|
|
182
180
|
/** Returns a pointer for a given event */
|
|
183
181
|
export function getPointerForEvent(event, relays) {
|
|
184
182
|
if (kinds.isParameterizedReplaceableKind(event.kind)) {
|
|
185
|
-
const d =
|
|
186
|
-
if (!d)
|
|
187
|
-
throw new Error("Event missing identifier");
|
|
183
|
+
const d = getReplaceableIdentifier(event);
|
|
188
184
|
return {
|
|
189
185
|
type: "naddr",
|
|
190
186
|
data: {
|
|
@@ -2,10 +2,13 @@ import { NostrEvent } from "nostr-tools";
|
|
|
2
2
|
export declare const ProfileContentSymbol: unique symbol;
|
|
3
3
|
export type ProfileContent = {
|
|
4
4
|
name?: string;
|
|
5
|
+
/** @deprecated use name instead */
|
|
6
|
+
username?: string;
|
|
5
7
|
display_name?: string;
|
|
8
|
+
/** @deprecated use display_name instead */
|
|
6
9
|
displayName?: string;
|
|
7
10
|
about?: string;
|
|
8
|
-
/** @deprecated */
|
|
11
|
+
/** @deprecated use picture instead */
|
|
9
12
|
image?: string;
|
|
10
13
|
picture?: string;
|
|
11
14
|
banner?: string;
|
|
@@ -18,3 +21,5 @@ export type ProfileContent = {
|
|
|
18
21
|
export declare function getProfileContent(event: NostrEvent): ProfileContent;
|
|
19
22
|
/** Checks if the content of the kind 0 event is valid JSON */
|
|
20
23
|
export declare function isValidProfile(profile?: NostrEvent): boolean;
|
|
24
|
+
/** Gets the display name from a profile with fallbacks */
|
|
25
|
+
export declare function getDisplayName(metadata?: ProfileContent): string | undefined;
|
package/dist/helpers/profile.js
CHANGED
package/dist/helpers/relays.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ declare module "nostr-tools" {
|
|
|
5
5
|
[SeenRelaysSymbol]?: Set<string>;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
+
/** Marks an event as being seen on a relay */
|
|
8
9
|
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
10
|
+
/** Returns the set of relays this event was seen on */
|
|
9
11
|
export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
|
|
10
|
-
|
|
11
|
-
export declare function
|
|
12
|
-
|
|
12
|
+
/** A fast check to make sure relay hints are safe to connect to */
|
|
13
|
+
export declare function isSafeRelayURL(relay: string): boolean;
|
|
14
|
+
/** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
|
|
15
|
+
export declare function mergeRelaySets(...sources: (Iterable<string> | undefined)[]): string[];
|
package/dist/helpers/relays.js
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
+
import { normalizeURL } from "./url.js";
|
|
1
2
|
export const SeenRelaysSymbol = Symbol.for("seen-relays");
|
|
2
|
-
|
|
3
|
+
/** Marks an event as being seen on a relay */
|
|
3
4
|
export function addSeenRelay(event, relay) {
|
|
4
5
|
if (!event[SeenRelaysSymbol])
|
|
5
6
|
event[SeenRelaysSymbol] = new Set();
|
|
6
7
|
event[SeenRelaysSymbol].add(relay);
|
|
7
8
|
return event[SeenRelaysSymbol];
|
|
8
9
|
}
|
|
10
|
+
/** Returns the set of relays this event was seen on */
|
|
9
11
|
export function getSeenRelays(event) {
|
|
10
12
|
return event[SeenRelaysSymbol];
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (url.protocol !== "wss:" && url.protocol !== "ws:")
|
|
18
|
-
throw new Error("Incorrect protocol");
|
|
19
|
-
return url;
|
|
14
|
+
const WEBSOCKET_URL_CHECK = /^wss?:\/\/([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}|localhost)\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)$/;
|
|
15
|
+
/** A fast check to make sure relay hints are safe to connect to */
|
|
16
|
+
export function isSafeRelayURL(relay) {
|
|
17
|
+
// anything smaller than 8 is not a URL
|
|
18
|
+
return relay.length >= 8 && WEBSOCKET_URL_CHECK.test(relay);
|
|
20
19
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
/** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
|
|
21
|
+
export function mergeRelaySets(...sources) {
|
|
22
|
+
const set = new Set();
|
|
23
|
+
for (const src of sources) {
|
|
24
|
+
if (!src)
|
|
25
|
+
continue;
|
|
26
|
+
for (const url of src) {
|
|
27
|
+
try {
|
|
28
|
+
const safe = normalizeURL(url).toString();
|
|
29
|
+
if (safe)
|
|
30
|
+
set.add(safe);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// failed to parse URL, ignore
|
|
34
|
+
}
|
|
35
|
+
}
|
|
24
36
|
}
|
|
25
|
-
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function safeRelayUrls(urls) {
|
|
30
|
-
return Array.from(urls).map(safeRelayUrl).filter(Boolean);
|
|
37
|
+
return Array.from(set);
|
|
31
38
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { safeParse } from "./json.js";
|
|
2
|
+
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
+
export const SharedEventSymbol = Symbol.for("shared-event");
|
|
4
|
+
/** Returns the stringified event in the content of a kind 6 or 16 share event */
|
|
5
|
+
export function parseSharedEvent(event) {
|
|
6
|
+
return getOrComputeCachedValue(event, SharedEventSymbol, () => {
|
|
7
|
+
const json = safeParse(event.content);
|
|
8
|
+
if (!json)
|
|
9
|
+
return;
|
|
10
|
+
return json;
|
|
11
|
+
});
|
|
12
|
+
}
|
package/dist/helpers/tags.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** A tag with at least two indexes, the first being the name, the second the value */
|
|
2
|
+
export type NameValueTag<Name extends string = string> = [Name, string, ...string[]];
|
|
3
|
+
/** Tests if a tag has at least two indexes, and optionally the value of the first */
|
|
4
|
+
export declare function isNameValueTag<Name extends string>(tag: string[], name?: Name): tag is NameValueTag<Name>;
|
|
1
5
|
/** Checks if tag is an "e" tag and has at least one value */
|
|
2
6
|
export declare function isETag(tag: string[]): tag is ["e", string, ...string[]];
|
|
3
7
|
/** Checks if tag is an "p" tag and has at least one value */
|
|
@@ -10,3 +14,16 @@ export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]]
|
|
|
10
14
|
export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
|
|
11
15
|
/** Checks if tag is an "a" tag and has at least one value */
|
|
12
16
|
export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];
|
|
17
|
+
/** A pipeline that filters and maps each tag */
|
|
18
|
+
type TagPipe = {
|
|
19
|
+
<A>(tags: string[][], ta: (tag: string[]) => A | undefined): A[];
|
|
20
|
+
<A, B>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined): B[];
|
|
21
|
+
<A, B, C>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined): C[];
|
|
22
|
+
<A, B, C, D>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined): D[];
|
|
23
|
+
<A, B, C, D, E>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined): E[];
|
|
24
|
+
<A, B, C, D, E, F>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined, ef: (e: E) => F | undefined): F[];
|
|
25
|
+
<A, B, C, D, E, F, G>(tags: string[][], ta: (tag: string[]) => A | undefined, ab: (a: A) => B | undefined, bc: (b: B) => C | undefined, cd: (c: C) => D | undefined, de: (d: D) => E | undefined, ef: (e: E) => F | undefined, fg: (f: F) => G | undefined): G[];
|
|
26
|
+
};
|
|
27
|
+
/** Filter and transform tags */
|
|
28
|
+
export declare const processTags: TagPipe;
|
|
29
|
+
export {};
|
package/dist/helpers/tags.js
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
+
/** Tests if a tag has at least two indexes, and optionally the value of the first */
|
|
2
|
+
export function isNameValueTag(tag, name) {
|
|
3
|
+
return tag[0] !== undefined && tag[1] !== undefined && (name ? tag[0] === name : true);
|
|
4
|
+
}
|
|
1
5
|
/** Checks if tag is an "e" tag and has at least one value */
|
|
2
6
|
export function isETag(tag) {
|
|
3
|
-
return tag
|
|
7
|
+
return isNameValueTag(tag, "e");
|
|
4
8
|
}
|
|
5
9
|
/** Checks if tag is an "p" tag and has at least one value */
|
|
6
10
|
export function isPTag(tag) {
|
|
7
|
-
return tag
|
|
11
|
+
return isNameValueTag(tag, "p");
|
|
8
12
|
}
|
|
9
13
|
/** Checks if tag is an "r" tag and has at least one value */
|
|
10
14
|
export function isRTag(tag) {
|
|
11
|
-
return tag
|
|
15
|
+
return isNameValueTag(tag, "r");
|
|
12
16
|
}
|
|
13
17
|
/** Checks if tag is an "d" tag and has at least one value */
|
|
14
18
|
export function isDTag(tag) {
|
|
15
|
-
return tag
|
|
19
|
+
return isNameValueTag(tag, "d");
|
|
16
20
|
}
|
|
17
21
|
/** Checks if tag is an "a" tag and has at least one value */
|
|
18
22
|
export function isATag(tag) {
|
|
19
|
-
return tag
|
|
23
|
+
return isNameValueTag(tag, "a");
|
|
20
24
|
}
|
|
21
25
|
/** Checks if tag is an "a" tag and has at least one value */
|
|
22
26
|
export function isTTag(tag) {
|
|
23
|
-
return tag
|
|
27
|
+
return isNameValueTag(tag, "t");
|
|
24
28
|
}
|
|
29
|
+
/** Filter and transform tags */
|
|
30
|
+
export const processTags = (tags, ...fns) => {
|
|
31
|
+
return fns.reduce((step, fn) => {
|
|
32
|
+
const next = [];
|
|
33
|
+
for (const value of step) {
|
|
34
|
+
try {
|
|
35
|
+
const result = fn(value);
|
|
36
|
+
if (result === undefined)
|
|
37
|
+
continue; // value is undefined, ignore
|
|
38
|
+
next.push(result);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// failed to process value, ignore
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return next;
|
|
45
|
+
}, tags);
|
|
46
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAddressPointerFromATag } from "./pointers.js";
|
|
2
2
|
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isSafeRelayURL } from "./relays.js";
|
|
4
4
|
export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
|
|
5
5
|
/**
|
|
6
6
|
* Gets an EventPointer form a NIP-10 threading "e" tag
|
|
@@ -10,8 +10,8 @@ export function getEventPointerFromThreadTag(tag) {
|
|
|
10
10
|
if (!tag[1])
|
|
11
11
|
throw new Error("Missing event id in tag");
|
|
12
12
|
let pointer = { id: tag[1] };
|
|
13
|
-
if (tag[2])
|
|
14
|
-
pointer.relays =
|
|
13
|
+
if (tag[2] && isSafeRelayURL(tag[2]))
|
|
14
|
+
pointer.relays = [tag[2]];
|
|
15
15
|
// get author from NIP-18 quote tags, nip-22 comments tags, or nip-10 thread tags
|
|
16
16
|
if (tag[0] === "e" &&
|
|
17
17
|
(tag[3] === "root" || tag[3] === "reply" || tag[3] === "mention") &&
|
package/dist/helpers/url.d.ts
CHANGED
|
@@ -12,3 +12,10 @@ export declare function isVideoURL(url: string | URL): boolean;
|
|
|
12
12
|
export declare function isStreamURL(url: string | URL): boolean;
|
|
13
13
|
/** Checks if a url is a audio URL */
|
|
14
14
|
export declare function isAudioURL(url: string | URL): boolean;
|
|
15
|
+
/** Tests if two URLs are the same */
|
|
16
|
+
export declare function isSameURL(a: string | URL, b: string | URL): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes a string into a relay URL
|
|
19
|
+
* Does not remove the trailing slash
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeURL<T extends string | URL>(url: T): T;
|
package/dist/helpers/url.js
CHANGED
|
@@ -28,3 +28,30 @@ export function isAudioURL(url) {
|
|
|
28
28
|
const filename = getURLFilename(url);
|
|
29
29
|
return !!filename && AUDIO_EXT.some((ext) => filename.endsWith(ext));
|
|
30
30
|
}
|
|
31
|
+
/** Tests if two URLs are the same */
|
|
32
|
+
export function isSameURL(a, b) {
|
|
33
|
+
try {
|
|
34
|
+
a = normalizeURL(a);
|
|
35
|
+
b = normalizeURL(b);
|
|
36
|
+
return a === b;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes a string into a relay URL
|
|
44
|
+
* Does not remove the trailing slash
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeURL(url) {
|
|
47
|
+
let p = new URL(url);
|
|
48
|
+
// remove any double slashes
|
|
49
|
+
p.pathname = p.pathname.replace(/\/+/g, "/");
|
|
50
|
+
// drop the port if its not needed
|
|
51
|
+
if ((p.port === "80" && (p.protocol === "ws:" || p.protocol === "http:")) ||
|
|
52
|
+
(p.port === "443" && (p.protocol === "wss:" || p.protocol === "https:")))
|
|
53
|
+
p.port = "";
|
|
54
|
+
// return a string if a string was passed in
|
|
55
|
+
// @ts-expect-error
|
|
56
|
+
return typeof url === "string" ? p.toString() : p;
|
|
57
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
3
|
+
export declare const UserStatusPointerSymbol: unique symbol;
|
|
4
|
+
export type UserStatusPointer = {
|
|
5
|
+
type: "nevent";
|
|
6
|
+
data: EventPointer;
|
|
7
|
+
} | {
|
|
8
|
+
type: "nprofile";
|
|
9
|
+
data: ProfilePointer;
|
|
10
|
+
} | {
|
|
11
|
+
type: "naddr";
|
|
12
|
+
data: AddressPointer;
|
|
13
|
+
} | {
|
|
14
|
+
type: "url";
|
|
15
|
+
data: string;
|
|
16
|
+
};
|
|
17
|
+
/** Gets the {@link UserStatusPointer} for a status event */
|
|
18
|
+
export declare function getUserStatusPointer(status: NostrEvent): UserStatusPointer | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getAddressPointerFromATag, getEventPointerFromETag, getOrComputeCachedValue, getProfilePointerFromPTag, } from "applesauce-core/helpers";
|
|
2
|
+
export const UserStatusPointerSymbol = Symbol.for("user-status-pointer");
|
|
3
|
+
function getStatusPointer(status) {
|
|
4
|
+
const pTag = status.tags.find((t) => t[0] === "p" && t[1]);
|
|
5
|
+
if (pTag)
|
|
6
|
+
return { type: "nprofile", data: getProfilePointerFromPTag(pTag) };
|
|
7
|
+
const eTag = status.tags.find((t) => t[0] === "e" && t[1]);
|
|
8
|
+
if (eTag)
|
|
9
|
+
return { type: "nevent", data: getEventPointerFromETag(eTag) };
|
|
10
|
+
const aTag = status.tags.find((t) => t[0] === "a" && t[1]);
|
|
11
|
+
if (aTag)
|
|
12
|
+
return { type: "naddr", data: getAddressPointerFromATag(aTag) };
|
|
13
|
+
const rTag = status.tags.find((t) => t[0] === "r" && t[1]);
|
|
14
|
+
if (rTag)
|
|
15
|
+
return { type: "url", data: rTag[1] };
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/** Gets the {@link UserStatusPointer} for a status event */
|
|
19
|
+
export function getUserStatusPointer(status) {
|
|
20
|
+
return getOrComputeCachedValue(status, UserStatusPointerSymbol, () => getStatusPointer(status));
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Database } from "../../event-store/database.js";
|
|
3
|
+
import { claimEvents } from "../claim-events.js";
|
|
4
|
+
const event = {
|
|
5
|
+
content: '{"name":"hzrd149","picture":"https://cdn.hzrd149.com/5ed3fe5df09a74e8c126831eac999364f9eb7624e2b86d521521b8021de20bdc.png","about":"JavaScript developer working on some nostr stuff\\n- noStrudel https://nostrudel.ninja/ \\n- Blossom https://github.com/hzrd149/blossom \\n- Applesauce https://hzrd149.github.io/applesauce/","website":"https://hzrd149.com","nip05":"_@hzrd149.com","lud16":"hzrd1499@minibits.cash","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","display_name":"hzrd149","displayName":"hzrd149","banner":""}',
|
|
6
|
+
created_at: 1738362529,
|
|
7
|
+
id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
|
|
8
|
+
kind: 0,
|
|
9
|
+
pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
|
|
10
|
+
relays: [""],
|
|
11
|
+
sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
|
|
12
|
+
tags: [],
|
|
13
|
+
};
|
|
14
|
+
describe("claimEvents", () => {
|
|
15
|
+
it("it should claim events", () => {
|
|
16
|
+
const database = new Database();
|
|
17
|
+
const sub = database.inserted.pipe(claimEvents(database)).subscribe();
|
|
18
|
+
database.addEvent(event);
|
|
19
|
+
expect(database.isClaimed(event)).toBe(true);
|
|
20
|
+
sub.unsubscribe();
|
|
21
|
+
expect(database.isClaimed(event)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Database } from "../../event-store/database.js";
|
|
3
|
+
import { claimLatest } from "../claim-latest.js";
|
|
4
|
+
const event1 = {
|
|
5
|
+
content: '{"name":"hzrd149","picture":"https://cdn.hzrd149.com/5ed3fe5df09a74e8c126831eac999364f9eb7624e2b86d521521b8021de20bdc.png","about":"JavaScript developer working on some nostr stuff\\n- noStrudel https://nostrudel.ninja/ \\n- Blossom https://github.com/hzrd149/blossom \\n- Applesauce https://hzrd149.github.io/applesauce/","website":"https://hzrd149.com","nip05":"_@hzrd149.com","lud16":"hzrd1499@minibits.cash","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","display_name":"hzrd149","displayName":"hzrd149","banner":""}',
|
|
6
|
+
created_at: 1738362529,
|
|
7
|
+
id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
|
|
8
|
+
kind: 0,
|
|
9
|
+
pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
|
|
10
|
+
relays: [""],
|
|
11
|
+
sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
|
|
12
|
+
tags: [],
|
|
13
|
+
};
|
|
14
|
+
const event2 = {
|
|
15
|
+
content: '{"name":"Cesar Dias","website":"dev.nosotros.app","picture":"https://nostr.build/i/5b0e4387b0fdfff9897ee7f8dcc554761fe377583a5fb71bbf3b915e7c4971c2.jpg","display_name":"Cesar Dias","nip05":"_@nosotros.app","lud16":"cesardias@getalby.com","about":"Developer 🇧🇷, building a client https://dev.nosotros.app and nostr-editor https://github.com/cesardeazevedo/nostr-editor","banner":"https://image.nostr.build/87dbc55a6391d15bddda206561d53867a5679dd95e84fe8ed62bfe2e3adcadf3.jpg\\",\\"ox 87dbc55a6391d15bddda206561d53867a5679dd95e84fe8ed62bfe2e3adcadf3"}',
|
|
16
|
+
created_at: 1727998492,
|
|
17
|
+
id: "c771fe19ac255ea28690c5547258a5e146d2f47805f7f48093b773478bdd137c",
|
|
18
|
+
kind: 0,
|
|
19
|
+
pubkey: "c6603b0f1ccfec625d9c08b753e4f774eaf7d1cf2769223125b5fd4da728019e",
|
|
20
|
+
relays: [""],
|
|
21
|
+
sig: "5220d6a8cdb4837b2569c26a84a2ac6a44427a224cb1602c05c578c6a63fe122a37e16455b09cb38bf297fc8161a8e715d7b444d017624c044d87a77e092c881",
|
|
22
|
+
tags: [["alt", "User profile for Cesar Dias"]],
|
|
23
|
+
};
|
|
24
|
+
describe("claimLatest", () => {
|
|
25
|
+
it("it should claim events", () => {
|
|
26
|
+
const database = new Database();
|
|
27
|
+
const sub = database.inserted.pipe(claimLatest(database)).subscribe();
|
|
28
|
+
database.addEvent(event1);
|
|
29
|
+
expect(database.isClaimed(event1)).toBe(true);
|
|
30
|
+
database.addEvent(event2);
|
|
31
|
+
expect(database.isClaimed(event1)).toBe(false);
|
|
32
|
+
expect(database.isClaimed(event2)).toBe(true);
|
|
33
|
+
sub.unsubscribe();
|
|
34
|
+
expect(database.isClaimed(event1)).toBe(false);
|
|
35
|
+
expect(database.isClaimed(event2)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Observable, Subject, firstValueFrom } from "rxjs";
|
|
3
|
+
import { simpleTimeout, TimeoutError } from "../simple-timeout.js";
|
|
4
|
+
describe("simpleTimeout operator", () => {
|
|
5
|
+
it("should throw TimeoutError after specified timeout period", async () => {
|
|
6
|
+
const subject = new Subject();
|
|
7
|
+
const obs = subject.pipe(simpleTimeout(10));
|
|
8
|
+
const promise = firstValueFrom(obs);
|
|
9
|
+
await expect(promise).rejects.toThrow(TimeoutError);
|
|
10
|
+
await expect(promise).rejects.toThrow("Timeout");
|
|
11
|
+
});
|
|
12
|
+
it("should throw TimeoutError with custom message", async () => {
|
|
13
|
+
const subject = new Subject();
|
|
14
|
+
const customMessage = "Custom timeout message";
|
|
15
|
+
const obs = subject.pipe(simpleTimeout(10, customMessage));
|
|
16
|
+
const promise = firstValueFrom(obs);
|
|
17
|
+
await expect(promise).rejects.toThrow(TimeoutError);
|
|
18
|
+
await expect(promise).rejects.toThrow(customMessage);
|
|
19
|
+
});
|
|
20
|
+
it("should not throw when value emitted before timeout", async () => {
|
|
21
|
+
const subject = new Subject();
|
|
22
|
+
const obs = subject.pipe(simpleTimeout(1000));
|
|
23
|
+
const promise = firstValueFrom(obs);
|
|
24
|
+
subject.next("test value");
|
|
25
|
+
await expect(promise).resolves.toBe("test value");
|
|
26
|
+
});
|
|
27
|
+
it("should complete without error when source emits non-null value before timeout", async () => {
|
|
28
|
+
const source = new Observable((subscriber) => {
|
|
29
|
+
subscriber.next("test value");
|
|
30
|
+
});
|
|
31
|
+
const result = await firstValueFrom(source.pipe(simpleTimeout(10)));
|
|
32
|
+
expect(result).toBe("test value");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { MonoTypeOperatorFunction } from "rxjs";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { Database } from "../event-store/database.js";
|
|
4
|
+
/** keep a claim on any event that goes through this observable, claims are removed when the observable completes */
|
|
5
|
+
export declare function claimEvents<T extends NostrEvent[] | NostrEvent | undefined>(database: Database): MonoTypeOperatorFunction<T>;
|