applesauce-core 3.1.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/event-store/async-event-store.d.ts +136 -0
  2. package/dist/event-store/async-event-store.js +364 -0
  3. package/dist/event-store/{event-set.d.ts → event-memory.d.ts} +17 -25
  4. package/dist/event-store/{event-set.js → event-memory.js} +54 -53
  5. package/dist/event-store/event-store.d.ts +59 -63
  6. package/dist/event-store/event-store.js +126 -190
  7. package/dist/event-store/index.d.ts +2 -1
  8. package/dist/event-store/index.js +2 -1
  9. package/dist/event-store/interface.d.ts +115 -38
  10. package/dist/event-store/model-mixin.d.ts +59 -0
  11. package/dist/event-store/model-mixin.js +147 -0
  12. package/dist/helpers/app-data.d.ts +39 -0
  13. package/dist/helpers/app-data.js +68 -0
  14. package/dist/helpers/bookmarks.d.ts +11 -1
  15. package/dist/helpers/bookmarks.js +29 -4
  16. package/dist/helpers/comment.d.ts +13 -20
  17. package/dist/helpers/comment.js +16 -27
  18. package/dist/helpers/contacts.d.ts +10 -1
  19. package/dist/helpers/contacts.js +30 -3
  20. package/dist/helpers/encrypted-content-cache.js +7 -7
  21. package/dist/helpers/encrypted-content.d.ts +9 -2
  22. package/dist/helpers/encrypted-content.js +12 -9
  23. package/dist/helpers/event-cache.d.ts +3 -1
  24. package/dist/helpers/event-cache.js +3 -1
  25. package/dist/helpers/event-tags.d.ts +6 -0
  26. package/dist/helpers/event-tags.js +4 -0
  27. package/dist/helpers/event.d.ts +8 -1
  28. package/dist/helpers/event.js +6 -0
  29. package/dist/helpers/file-metadata.d.ts +4 -9
  30. package/dist/helpers/file-metadata.js +2 -10
  31. package/dist/helpers/filter.d.ts +4 -3
  32. package/dist/helpers/filter.js +3 -3
  33. package/dist/helpers/gift-wraps.d.ts +35 -14
  34. package/dist/helpers/gift-wraps.js +59 -50
  35. package/dist/helpers/groups.d.ts +2 -5
  36. package/dist/helpers/groups.js +2 -5
  37. package/dist/helpers/hidden-content.d.ts +14 -5
  38. package/dist/helpers/hidden-content.js +19 -8
  39. package/dist/helpers/hidden-tags.d.ts +16 -7
  40. package/dist/helpers/hidden-tags.js +47 -26
  41. package/dist/helpers/index.d.ts +1 -0
  42. package/dist/helpers/index.js +1 -0
  43. package/dist/helpers/legacy-messages.d.ts +17 -13
  44. package/dist/helpers/legacy-messages.js +21 -19
  45. package/dist/helpers/lists.js +2 -1
  46. package/dist/helpers/lnurl.d.ts +4 -0
  47. package/dist/helpers/lnurl.js +7 -3
  48. package/dist/helpers/mailboxes.d.ts +2 -6
  49. package/dist/helpers/mailboxes.js +26 -20
  50. package/dist/helpers/mutes.d.ts +11 -1
  51. package/dist/helpers/mutes.js +30 -5
  52. package/dist/helpers/picture-post.d.ts +2 -1
  53. package/dist/helpers/pointers.d.ts +3 -1
  54. package/dist/helpers/pointers.js +4 -1
  55. package/dist/helpers/profile.d.ts +7 -3
  56. package/dist/helpers/profile.js +7 -8
  57. package/dist/helpers/relay-selection.d.ts +17 -0
  58. package/dist/helpers/relay-selection.js +102 -0
  59. package/dist/helpers/relays.d.ts +3 -1
  60. package/dist/helpers/relays.js +18 -2
  61. package/dist/helpers/url.js +3 -3
  62. package/dist/helpers/wrapped-messages.d.ts +5 -3
  63. package/dist/helpers/wrapped-messages.js +5 -3
  64. package/dist/helpers/zap.d.ts +18 -14
  65. package/dist/helpers/zap.js +26 -28
  66. package/dist/models/common.d.ts +4 -4
  67. package/dist/models/common.js +79 -40
  68. package/dist/models/gift-wrap.d.ts +1 -1
  69. package/dist/models/gift-wrap.js +4 -4
  70. package/dist/models/index.d.ts +1 -0
  71. package/dist/models/index.js +1 -0
  72. package/dist/models/legacy-messages.d.ts +2 -2
  73. package/dist/models/legacy-messages.js +13 -10
  74. package/dist/models/outbox.d.ts +13 -0
  75. package/dist/models/outbox.js +18 -0
  76. package/dist/models/profile.d.ts +1 -1
  77. package/dist/models/zaps.d.ts +5 -4
  78. package/dist/models/zaps.js +2 -2
  79. package/dist/observable/index.d.ts +4 -3
  80. package/dist/observable/index.js +5 -4
  81. package/dist/observable/map-events-to-store.d.ts +5 -3
  82. package/dist/observable/map-events-to-store.js +14 -3
  83. package/dist/observable/map-events-to-timeline.js +12 -0
  84. package/dist/observable/relay-selection.d.ts +9 -0
  85. package/dist/observable/relay-selection.js +43 -0
  86. package/package.json +5 -3
  87. package/dist/observable/map-events-timeline.js +0 -9
  88. /package/dist/observable/{map-events-timeline.d.ts → map-events-to-timeline.d.ts} +0 -0
@@ -1,11 +1,7 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const MailboxesInboxesSymbol: unique symbol;
3
3
  export declare const MailboxesOutboxesSymbol: unique symbol;
4
- /**
5
- * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
6
- */
4
+ /** Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol */
7
5
  export declare function getInboxes(event: NostrEvent): string[];
8
- /**
9
- * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
10
- */
6
+ /** Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol */
11
7
  export declare function getOutboxes(event: NostrEvent): string[];
@@ -1,41 +1,47 @@
1
1
  import { getOrComputeCachedValue } from "./cache.js";
2
2
  import { isSafeRelayURL } from "./relays.js";
3
+ import { isRTag } from "./tags.js";
3
4
  import { normalizeURL } from "./url.js";
4
5
  export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
5
6
  export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
6
- /**
7
- * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
8
- */
7
+ /** Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol */
9
8
  export function getInboxes(event) {
10
9
  return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
11
10
  const inboxes = [];
12
11
  for (const tag of event.tags) {
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));
12
+ if (!isRTag(tag))
13
+ continue;
14
+ try {
15
+ const [, url, mode] = tag;
16
+ if (url && isSafeRelayURL(url) && !inboxes.includes(url) && (mode === "read" || mode === undefined)) {
17
+ inboxes.push(normalizeURL(url));
18
+ }
19
+ }
20
+ catch {
21
+ // Ignore invalid url tags
20
22
  }
21
23
  }
22
24
  return inboxes;
23
25
  });
24
26
  }
25
- /**
26
- * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
27
- */
27
+ /** Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol */
28
28
  export function getOutboxes(event) {
29
29
  return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
30
30
  const outboxes = [];
31
31
  for (const tag of event.tags) {
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
+ if (!isRTag(tag))
33
+ continue;
34
+ try {
35
+ const [name, url, mode] = tag;
36
+ if (name === "r" &&
37
+ isSafeRelayURL(url) &&
38
+ !outboxes.includes(url) &&
39
+ (mode === "write" || mode === undefined)) {
40
+ outboxes.push(normalizeURL(url));
41
+ }
42
+ }
43
+ catch {
44
+ // Ignore invalid url tags
39
45
  }
40
46
  }
41
47
  return outboxes;
@@ -1,6 +1,11 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
+ import { HiddenContentSigner } from "./index.js";
2
3
  export declare const MutePublicSymbol: unique symbol;
3
4
  export declare const MuteHiddenSymbol: unique symbol;
5
+ /** Type for unlocked mute events */
6
+ export type UnlockedMutes = {
7
+ [MuteHiddenSymbol]: Mutes;
8
+ };
4
9
  export type Mutes = {
5
10
  pubkeys: Set<string>;
6
11
  threads: Set<string>;
@@ -15,8 +20,13 @@ export declare function parseMutedTags(tags: string[][]): Mutes;
15
20
  export declare function getMutedThings(mute: NostrEvent): Mutes;
16
21
  /** Returns only the public muted things from a mute event */
17
22
  export declare function getPublicMutedThings(mute: NostrEvent): Mutes;
23
+ /** Checks if the hidden mutes are unlocked */
24
+ export declare function isHiddenMutesUnlocked<T extends NostrEvent>(mute: T): mute is T & UnlockedMutes;
18
25
  /** Returns the hidden muted content if the event is unlocked */
19
- export declare function getHiddenMutedThings(mute: NostrEvent): Mutes | undefined;
26
+ export declare function getHiddenMutedThings<T extends NostrEvent & UnlockedMutes>(mute: T): Mutes;
27
+ export declare function getHiddenMutedThings<T extends NostrEvent>(mute: T): Mutes | undefined;
28
+ /** Unlocks the hidden mutes */
29
+ export declare function unlockHiddenMutes(mute: NostrEvent, signer: HiddenContentSigner): Promise<Mutes>;
20
30
  /** Creates a RegExp for matching muted words */
21
31
  export declare function createMutedWordsRegExp(mutedWords: string[]): RegExp;
22
32
  /** Returns true if the event matches the mutes */
@@ -1,7 +1,7 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { getOrComputeCachedValue } from "./cache.js";
3
- import { getHiddenTags, isHiddenTagsLocked } from "./hidden-tags.js";
4
- import { getIndexableTags, getNip10References } from "./index.js";
3
+ import { getHiddenTags, isHiddenTagsUnlocked, unlockHiddenTags } from "./hidden-tags.js";
4
+ import { getIndexableTags, getNip10References, notifyEventUpdate } from "./index.js";
5
5
  import { isETag, isPTag, isTTag } from "./tags.js";
6
6
  export const MutePublicSymbol = Symbol.for("mute-public");
7
7
  export const MuteHiddenSymbol = Symbol.for("mute-hidden");
@@ -40,11 +40,36 @@ export function getMutedThings(mute) {
40
40
  export function getPublicMutedThings(mute) {
41
41
  return getOrComputeCachedValue(mute, MutePublicSymbol, () => parseMutedTags(mute.tags));
42
42
  }
43
- /** Returns the hidden muted content if the event is unlocked */
43
+ /** Checks if the hidden mutes are unlocked */
44
+ export function isHiddenMutesUnlocked(mute) {
45
+ return isHiddenTagsUnlocked(mute) && Reflect.has(mute, MuteHiddenSymbol);
46
+ }
44
47
  export function getHiddenMutedThings(mute) {
45
- if (isHiddenTagsLocked(mute))
48
+ if (isHiddenMutesUnlocked(mute))
49
+ return mute[MuteHiddenSymbol];
50
+ // get hidden tags
51
+ const tags = getHiddenTags(mute);
52
+ if (!tags)
46
53
  return undefined;
47
- return getOrComputeCachedValue(mute, MuteHiddenSymbol, () => parseMutedTags(getHiddenTags(mute)));
54
+ // parse muted tags
55
+ const mutes = parseMutedTags(tags);
56
+ // set cached value
57
+ Reflect.set(mute, MuteHiddenSymbol, mutes);
58
+ return mutes;
59
+ }
60
+ /** Unlocks the hidden mutes */
61
+ export async function unlockHiddenMutes(mute, signer) {
62
+ if (isHiddenMutesUnlocked(mute))
63
+ return mute[MuteHiddenSymbol];
64
+ // Unlock hidden tags
65
+ await unlockHiddenTags(mute, signer);
66
+ // get hidden mutes
67
+ const mutes = getHiddenMutedThings(mute);
68
+ if (!mutes)
69
+ throw new Error("Failed to unlock hidden mutes");
70
+ // Notify event store
71
+ notifyEventUpdate(mute);
72
+ return mutes;
48
73
  }
49
74
  /** Creates a RegExp for matching muted words */
50
75
  export function createMutedWordsRegExp(mutedWords) {
@@ -1,4 +1,5 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
+ import { FileMetadata } from "./file-metadata.js";
2
3
  export declare const PICTURE_POST_KIND = 20;
3
4
  /** Return the media attachments from a kind 20 media post */
4
- export declare function getPicturePostAttachments(post: NostrEvent): import("./file-metadata.js").FileMetadata[];
5
+ export declare function getPicturePostAttachments(post: NostrEvent): FileMetadata[];
@@ -1,5 +1,7 @@
1
1
  import { AddressPointer, EventPointer, ProfilePointer, decode } from "nostr-tools/nip19";
2
2
  import { NostrEvent } from "nostr-tools";
3
+ export type { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
4
+ export { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, decode as decodePointer, } from "nostr-tools/nip19";
3
5
  export type DecodeResult = ReturnType<typeof decode>;
4
6
  export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
5
7
  identifier?: string;
@@ -15,7 +17,7 @@ export declare function parseCoordinate(a: string, requireD: false, silent: true
15
17
  /** Extra a pubkey from the result of nip19.decode */
16
18
  export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
17
19
  /** Encodes the result of nip19.decode */
18
- export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
20
+ export declare function encodeDecodeResult(result: DecodeResult): "" | `nevent1${string}` | `naddr1${string}` | `nprofile1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
19
21
  /**
20
22
  * Gets an EventPointer form a common "e" tag
21
23
  * @throws
@@ -1,10 +1,13 @@
1
1
  import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
2
2
  import { getPublicKey, kinds, nip19 } from "nostr-tools";
3
+ // export nip-19 helpers
4
+ export { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, decode as decodePointer, } from "nostr-tools/nip19";
3
5
  import { getReplaceableIdentifier } from "./event.js";
4
6
  import { isAddressableKind } from "nostr-tools/kinds";
5
7
  import { isSafeRelayURL, mergeRelaySets } from "./relays.js";
6
8
  import { isHexKey } from "./string.js";
7
9
  import { hexToBytes } from "@noble/hashes/utils";
10
+ import { normalizeURL } from "./url.js";
8
11
  export function parseCoordinate(a, requireD = false, silent = true) {
9
12
  const parts = a.split(":");
10
13
  const kind = parts[0] ? parseInt(parts[0]) : undefined;
@@ -117,7 +120,7 @@ export function getProfilePointerFromPTag(tag) {
117
120
  throw new Error("Invalid pubkey");
118
121
  const pointer = { pubkey: tag[1] };
119
122
  if (tag[2] && isSafeRelayURL(tag[2]))
120
- pointer.relays = [tag[2]];
123
+ pointer.relays = [normalizeURL(tag[2])];
121
124
  return pointer;
122
125
  }
123
126
  /** Checks if a pointer is an AddressPointer */
@@ -1,4 +1,5 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { kinds, NostrEvent } from "nostr-tools";
2
+ import { KnownEvent } from "./index.js";
2
3
  export declare const ProfileContentSymbol: unique symbol;
3
4
  export type ProfileContent = {
4
5
  name?: string;
@@ -17,10 +18,13 @@ export type ProfileContent = {
17
18
  lud06?: string;
18
19
  nip05?: string;
19
20
  };
21
+ /** Type for validated profile events */
22
+ export type ProfileEvent = KnownEvent<kinds.Metadata>;
20
23
  /** Returns the parsed profile content for a kind 0 event */
21
- export declare function getProfileContent(event: NostrEvent): ProfileContent;
24
+ export declare function getProfileContent(event: ProfileEvent): ProfileContent;
25
+ export declare function getProfileContent(event: NostrEvent): ProfileContent | undefined;
22
26
  /** Checks if the content of the kind 0 event is valid JSON */
23
- export declare function isValidProfile(profile?: NostrEvent): boolean;
27
+ export declare function isValidProfile(profile?: NostrEvent): profile is ProfileEvent;
24
28
  /** Gets the profile picture from a nostr event or profile content with fallback */
25
29
  export declare function getProfilePicture(metadata: ProfileContent | NostrEvent | undefined): string | undefined;
26
30
  export declare function getProfilePicture(metadata: ProfileContent | NostrEvent | undefined, fallback: string): string;
@@ -1,11 +1,13 @@
1
1
  import { kinds } from "nostr-tools";
2
2
  import { npubEncode } from "nostr-tools/nip19";
3
3
  import { getOrComputeCachedValue } from "./cache.js";
4
+ import { safeParse } from "./index.js";
4
5
  export const ProfileContentSymbol = Symbol.for("profile-content");
5
- /** Returns the parsed profile content for a kind 0 event */
6
6
  export function getProfileContent(event) {
7
7
  return getOrComputeCachedValue(event, ProfileContentSymbol, () => {
8
- const profile = JSON.parse(event.content);
8
+ const profile = safeParse(event.content);
9
+ if (!profile)
10
+ return undefined;
9
11
  // ensure nip05 is a string
10
12
  if (profile.nip05 && typeof profile.nip05 !== "string")
11
13
  profile.nip05 = String(profile.nip05);
@@ -22,13 +24,10 @@ export function isValidProfile(profile) {
22
24
  return false;
23
25
  if (profile.kind !== kinds.Metadata && profile.kind !== kinds.Handlerinformation)
24
26
  return false;
25
- try {
26
- getProfileContent(profile);
27
- return true;
28
- }
29
- catch (error) {
27
+ // Check if the profile content is valid
28
+ if (!getProfileContent(profile))
30
29
  return false;
31
- }
30
+ return true;
32
31
  }
33
32
  export function getProfilePicture(metadata, fallback) {
34
33
  if (!metadata)
@@ -0,0 +1,17 @@
1
+ import { ProfilePointer } from "nostr-tools/nip19";
2
+ export type SelectOptimalRelaysOptions = {
3
+ /** Maximum number of connections (relays) to select */
4
+ maxConnections: number;
5
+ /** Cap the number of relays a user can have */
6
+ maxRelaysPerUser?: number;
7
+ };
8
+ /** Selects the optimal relays for a list of ProfilePointers */
9
+ export declare function selectOptimalRelays(users: ProfilePointer[], { maxConnections, maxRelaysPerUser }: SelectOptimalRelaysOptions): ProfilePointer[];
10
+ /** Sets relays for any user that has 0 relays */
11
+ export declare function setFallbackRelays(users: ProfilePointer[], fallbacks: string[]): ProfilePointer[];
12
+ /** Removes blacklisted relays from the user's relays */
13
+ export declare function removeBlacklistedRelays(users: ProfilePointer[], blacklist: string[]): ProfilePointer[];
14
+ /** A map of pubkeys by relay */
15
+ export type OutboxMap = Record<string, ProfilePointer[]>;
16
+ /** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
17
+ export declare function groupPubkeysByRelay(pointers: ProfilePointer[]): OutboxMap;
@@ -0,0 +1,102 @@
1
+ /** Selects the optimal relays for a list of ProfilePointers */
2
+ export function selectOptimalRelays(users, { maxConnections, maxRelaysPerUser }) {
3
+ const usersWithRelays = users.filter((user) => user.relays && user.relays.length > 0);
4
+ // create map of popular relays
5
+ const popular = new Map();
6
+ for (const user of usersWithRelays) {
7
+ if (!user.relays)
8
+ continue;
9
+ for (const relay of user.relays)
10
+ popular.set(relay, (popular.get(relay) || 0) + 1);
11
+ }
12
+ // sort users relays by popularity
13
+ for (const user of usersWithRelays) {
14
+ if (!user.relays)
15
+ continue;
16
+ user.relays = Array.from(user.relays).sort((a, b) => popular.get(b) - popular.get(a));
17
+ }
18
+ // Create a pool of users to calculate relay coverage from
19
+ let selectionPool = Array.from(usersWithRelays);
20
+ // Create map of times a users relay has been selected
21
+ const selectionCount = new Map();
22
+ let selection = new Set();
23
+ while (selectionPool.length > 0 && selection.size < maxConnections) {
24
+ // Create map of number of pool users per relay
25
+ const relayUserCount = new Map();
26
+ for (const user of selectionPool) {
27
+ if (!user.relays)
28
+ continue;
29
+ for (const relay of user.relays) {
30
+ // Skip relays that are already selected
31
+ if (selection.has(relay))
32
+ continue;
33
+ // Increment relay user count
34
+ relayUserCount.set(relay, (relayUserCount.get(relay) || 0) + 1);
35
+ }
36
+ }
37
+ // Sort relays by coverage
38
+ const byCoverage = Array.from(relayUserCount.entries()).sort((a, b) => b[1] - a[1]);
39
+ // No more relays to select, exit loop
40
+ if (byCoverage.length === 0)
41
+ break;
42
+ // Pick the most popular relay
43
+ const relay = byCoverage[0][0];
44
+ // Add relay to selection
45
+ selection.add(relay);
46
+ // Increment user relay count and remove users over the limit
47
+ selectionPool = selectionPool.filter((user) => {
48
+ // Ignore users that don't have the relay
49
+ if (!user.relays || !user.relays.includes(relay))
50
+ return true;
51
+ // Increment user relay count
52
+ let count = selectionCount.get(relay) || 0;
53
+ selectionCount.set(relay, count++);
54
+ // Remove user if they their relay has been selected more than minRelaysPerUser times
55
+ if (count >= 1)
56
+ return false;
57
+ return true;
58
+ });
59
+ }
60
+ // Take the original users and only include relays that where selected
61
+ return users.map((user) => ({
62
+ ...user,
63
+ relays: maxRelaysPerUser
64
+ ? user.relays
65
+ ?.filter((relay) => selection.has(relay))
66
+ .sort((a, b) => (popular.get(a) ?? 0) - (popular.get(b) ?? 0))
67
+ .slice(0, maxRelaysPerUser)
68
+ : user.relays?.filter((relay) => selection.has(relay)),
69
+ }));
70
+ }
71
+ /** Sets relays for any user that has 0 relays */
72
+ export function setFallbackRelays(users, fallbacks) {
73
+ return users.map((user) => {
74
+ if (!user.relays || user.relays.length === 0)
75
+ return { ...user, relays: fallbacks };
76
+ else
77
+ return user;
78
+ });
79
+ }
80
+ /** Removes blacklisted relays from the user's relays */
81
+ export function removeBlacklistedRelays(users, blacklist) {
82
+ return users.map((user) => {
83
+ if (!user.relays || user.relays.length === 0)
84
+ return user;
85
+ else
86
+ return { ...user, relays: user.relays.filter((relay) => !blacklist.includes(relay)) };
87
+ });
88
+ }
89
+ /** RxJS operator that aggregates contacts with outboxes into a relay -> pubkeys map */
90
+ export function groupPubkeysByRelay(pointers) {
91
+ const outbox = {};
92
+ for (const pointer of pointers) {
93
+ if (!pointer.relays)
94
+ continue;
95
+ for (const relay of pointer.relays) {
96
+ if (!outbox[relay])
97
+ outbox[relay] = [];
98
+ outbox[relay].push(pointer);
99
+ }
100
+ }
101
+ return outbox;
102
+ }
@@ -12,4 +12,6 @@ export declare function getSeenRelays(event: NostrEvent): Set<string> | undefine
12
12
  /** A fast check to make sure relay hints are safe to connect to */
13
13
  export declare function isSafeRelayURL(relay: string): boolean;
14
14
  /** Merge multiple sets of relays and remove duplicates (ignores invalid URLs) */
15
- export declare function mergeRelaySets(...sources: (Iterable<string> | undefined)[]): string[];
15
+ export declare function mergeRelaySets(...sources: (Iterable<string> | string | undefined)[]): string[];
16
+ /** Alias for {@link mergeRelaySets} */
17
+ export declare const relaySet: typeof mergeRelaySets;
@@ -23,9 +23,10 @@ export function mergeRelaySets(...sources) {
23
23
  for (const src of sources) {
24
24
  if (!src)
25
25
  continue;
26
- for (const url of src) {
26
+ if (typeof src === "string") {
27
+ // Source is a string
27
28
  try {
28
- const safe = normalizeURL(url).toString();
29
+ const safe = normalizeURL(src).toString();
29
30
  if (safe)
30
31
  set.add(safe);
31
32
  }
@@ -33,6 +34,21 @@ export function mergeRelaySets(...sources) {
33
34
  // failed to parse URL, ignore
34
35
  }
35
36
  }
37
+ else {
38
+ // Source is iterable
39
+ for (const url of src) {
40
+ try {
41
+ const safe = normalizeURL(url).toString();
42
+ if (safe)
43
+ set.add(safe);
44
+ }
45
+ catch (error) {
46
+ // failed to parse URL, ignore
47
+ }
48
+ }
49
+ }
36
50
  }
37
51
  return Array.from(set);
38
52
  }
53
+ /** Alias for {@link mergeRelaySets} */
54
+ export const relaySet = mergeRelaySets;
@@ -78,13 +78,13 @@ export function ensureHttpURL(url) {
78
78
  */
79
79
  export function normalizeURL(url) {
80
80
  let p = new URL(url);
81
- // remove any double slashes
81
+ // Remove any double slashes
82
82
  p.pathname = p.pathname.replace(/\/+/g, "/");
83
- // drop the port if its not needed
83
+ // Remove the port if its not needed
84
84
  if ((p.port === "80" && (p.protocol === "ws:" || p.protocol === "http:")) ||
85
85
  (p.port === "443" && (p.protocol === "wss:" || p.protocol === "https:")))
86
86
  p.port = "";
87
- // return a string if a string was passed in
87
+ // Return a string if a string was passed in
88
88
  // @ts-expect-error
89
89
  return typeof url === "string" ? p.toString() : p;
90
90
  }
@@ -1,12 +1,14 @@
1
1
  import { Rumor } from "./gift-wraps.js";
2
- /** Returns the subject of a warpped direct message */
2
+ /** Returns the subject of a wrapped direct message */
3
3
  export declare function getWrappedMessageSubject(message: Rumor): string | undefined;
4
4
  /** Returns the parent id of a wrapped direct message */
5
5
  export declare function getWrappedMessageParent(message: Rumor): string | undefined;
6
6
  /** Returns the sender of a wrapped direct message */
7
- export declare function getWrappedMesssageSender(message: Rumor): string;
7
+ export declare function getWrappedMessageSender(message: Rumor): string;
8
+ /** @deprecated use {@link getWrappedMessageSender} instead */
9
+ export declare const getWrappedMesssageSender: typeof getWrappedMessageSender;
8
10
  /**
9
- * Returns the frist participant in a conversation that is not the sender
11
+ * Returns the first participant in a conversation that is not the sender
10
12
  * @see getConversationParticipants
11
13
  */
12
14
  export declare function getWrappedMessageReceiver(message: Rumor): string;
@@ -1,5 +1,5 @@
1
1
  import { getConversationParticipants, getTagValue } from "./index.js";
2
- /** Returns the subject of a warpped direct message */
2
+ /** Returns the subject of a wrapped direct message */
3
3
  export function getWrappedMessageSubject(message) {
4
4
  return getTagValue(message, "subject");
5
5
  }
@@ -8,11 +8,13 @@ export function getWrappedMessageParent(message) {
8
8
  return getTagValue(message, "e");
9
9
  }
10
10
  /** Returns the sender of a wrapped direct message */
11
- export function getWrappedMesssageSender(message) {
11
+ export function getWrappedMessageSender(message) {
12
12
  return message.pubkey;
13
13
  }
14
+ /** @deprecated use {@link getWrappedMessageSender} instead */
15
+ export const getWrappedMesssageSender = getWrappedMessageSender;
14
16
  /**
15
- * Returns the frist participant in a conversation that is not the sender
17
+ * Returns the first participant in a conversation that is not the sender
16
18
  * @see getConversationParticipants
17
19
  */
18
20
  export function getWrappedMessageReceiver(message) {
@@ -1,6 +1,9 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { kinds, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
3
  import { ParsedInvoice } from "./bolt11.js";
4
+ import { KnownEvent } from "./index.js";
5
+ /** Type for validated zap event */
6
+ export type ZapEvent = KnownEvent<kinds.Zap>;
4
7
  export declare const ZapRequestSymbol: unique symbol;
5
8
  export declare const ZapSenderSymbol: unique symbol;
6
9
  export declare const ZapReceiverSymbol: unique symbol;
@@ -8,30 +11,31 @@ export declare const ZapInvoiceSymbol: unique symbol;
8
11
  export declare const ZapEventPointerSymbol: unique symbol;
9
12
  export declare const ZapAddressPointerSymbol: unique symbol;
10
13
  /** Returns the senders pubkey */
11
- export declare function getZapSender(zap: NostrEvent): string;
12
- /**
13
- * Gets the receivers pubkey
14
- * @throws
15
- */
16
- export declare function getZapRecipient(zap: NostrEvent): string;
14
+ export declare function getZapSender(zap: ZapEvent): string;
15
+ export declare function getZapSender(zap: NostrEvent): string | undefined;
16
+ /** Gets the receivers pubkey */
17
+ export declare function getZapRecipient(zap: ZapEvent): string;
18
+ export declare function getZapRecipient(zap: NostrEvent): string | undefined;
17
19
  /** Returns the parsed bolt11 invoice */
20
+ export declare function getZapPayment(zap: ZapEvent): ParsedInvoice;
18
21
  export declare function getZapPayment(zap: NostrEvent): ParsedInvoice | undefined;
22
+ /** Returns the zap event amount in msats */
23
+ export declare function getZapAmount(zap: ZapEvent): number;
24
+ export declare function getZapAmount(zap: NostrEvent): number | undefined;
19
25
  /** Gets the AddressPointer that was zapped */
20
26
  export declare function getZapAddressPointer(zap: NostrEvent): AddressPointer | null;
21
27
  /** Gets the EventPointer that was zapped */
22
28
  export declare function getZapEventPointer(zap: NostrEvent): EventPointer | null;
23
29
  /** Gets the preimage for the bolt11 invoice */
24
30
  export declare function getZapPreimage(zap: NostrEvent): string | undefined;
31
+ /** Returns the zap request event inside the zap receipt */
32
+ export declare function getZapRequest(zap: ZapEvent): NostrEvent;
33
+ export declare function getZapRequest(zap: NostrEvent): NostrEvent | undefined;
25
34
  /**
26
- * Returns the zap request event inside the zap receipt
27
- * @throws
28
- */
29
- export declare function getZapRequest(zap: NostrEvent): NostrEvent;
30
- /**
31
- * Checks if the zap is valid
35
+ * Checks if a zap event is valid (not missing fields)
32
36
  * DOES NOT validate LNURL address
33
37
  */
34
- export declare function isValidZap(zap?: NostrEvent): boolean;
38
+ export declare function isValidZap(zap?: NostrEvent): zap is ZapEvent;
35
39
  export type ZapSplit = {
36
40
  pubkey: string;
37
41
  percent: number;
@@ -10,31 +10,25 @@ export const ZapReceiverSymbol = Symbol.for("zap-receiver");
10
10
  export const ZapInvoiceSymbol = Symbol.for("zap-bolt11");
11
11
  export const ZapEventPointerSymbol = Symbol.for("zap-event-pointer");
12
12
  export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
13
- /** Returns the senders pubkey */
14
13
  export function getZapSender(zap) {
15
14
  return getOrComputeCachedValue(zap, ZapSenderSymbol, () => {
16
- return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
15
+ return getTagValue(zap, "P") || getZapRequest(zap)?.pubkey;
17
16
  });
18
17
  }
19
- /**
20
- * Gets the receivers pubkey
21
- * @throws
22
- */
23
18
  export function getZapRecipient(zap) {
24
19
  return getOrComputeCachedValue(zap, ZapReceiverSymbol, () => {
25
- const recipient = getTagValue(zap, "p");
26
- if (!recipient)
27
- throw new Error("Missing recipient");
28
- return recipient;
20
+ return getTagValue(zap, "p");
29
21
  });
30
22
  }
31
- /** Returns the parsed bolt11 invoice */
32
23
  export function getZapPayment(zap) {
33
24
  return getOrComputeCachedValue(zap, ZapInvoiceSymbol, () => {
34
25
  const bolt11 = getTagValue(zap, "bolt11");
35
26
  return bolt11 ? parseBolt11(bolt11) : undefined;
36
27
  });
37
28
  }
29
+ export function getZapAmount(zap) {
30
+ return getZapPayment(zap)?.amount;
31
+ }
38
32
  /** Gets the AddressPointer that was zapped */
39
33
  export function getZapAddressPointer(zap) {
40
34
  return getOrComputeCachedValue(zap, ZapAddressPointerSymbol, () => {
@@ -53,23 +47,25 @@ export function getZapEventPointer(zap) {
53
47
  export function getZapPreimage(zap) {
54
48
  return getTagValue(zap, "preimage");
55
49
  }
56
- /**
57
- * Returns the zap request event inside the zap receipt
58
- * @throws
59
- */
60
50
  export function getZapRequest(zap) {
61
51
  return getOrComputeCachedValue(zap, ZapRequestSymbol, () => {
62
52
  const description = getTagValue(zap, "description");
63
53
  if (!description)
64
- throw new Error("Missing description tag");
65
- const error = nip57.validateZapRequest(description);
66
- if (error)
67
- throw new Error(error);
68
- return JSON.parse(description);
54
+ return;
55
+ // Attempt to parse the zap request
56
+ try {
57
+ const error = nip57.validateZapRequest(description);
58
+ if (error)
59
+ return;
60
+ return JSON.parse(description);
61
+ }
62
+ catch (error) {
63
+ return undefined;
64
+ }
69
65
  });
70
66
  }
71
67
  /**
72
- * Checks if the zap is valid
68
+ * Checks if a zap event is valid (not missing fields)
73
69
  * DOES NOT validate LNURL address
74
70
  */
75
71
  export function isValidZap(zap) {
@@ -77,14 +73,16 @@ export function isValidZap(zap) {
77
73
  return false;
78
74
  if (zap.kind !== kinds.Zap)
79
75
  return false;
80
- try {
81
- getZapRequest(zap);
82
- getZapRecipient(zap);
83
- return true;
84
- }
85
- catch (error) {
76
+ // Is not a valid zap kind if any of these is undefined
77
+ if (getZapPayment(zap) === undefined)
86
78
  return false;
87
- }
79
+ if (getZapRequest(zap) === undefined)
80
+ return false;
81
+ if (getZapRecipient(zap) === undefined)
82
+ return false;
83
+ if (getZapSender(zap) === undefined)
84
+ return false;
85
+ return true;
88
86
  }
89
87
  /** Returns the zap splits for an event */
90
88
  export function getZapSplits(event) {