applesauce-core 0.8.0 → 0.9.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.
@@ -1,4 +1,3 @@
1
- /// <reference types="debug" />
2
1
  import { Filter, NostrEvent } from "nostr-tools";
3
2
  import { Subject } from "rxjs";
4
3
  import { LRU } from "../helpers/lru.js";
@@ -0,0 +1,8 @@
1
+ export type ParsedInvoice = {
2
+ paymentRequest: string;
3
+ description: string;
4
+ amount?: number;
5
+ timestamp: number;
6
+ expiry: number;
7
+ };
8
+ export declare function parseBolt11(paymentRequest: string): ParsedInvoice;
@@ -0,0 +1,14 @@
1
+ import { decode } from "light-bolt11-decoder";
2
+ export function parseBolt11(paymentRequest) {
3
+ const decoded = decode(paymentRequest);
4
+ const timestamp = decoded.sections.find((s) => s.name === "timestamp")?.value ?? 0;
5
+ const description = decoded.sections.find((s) => s.name === "description")?.value ?? "";
6
+ const amount = parseInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
7
+ return {
8
+ paymentRequest: decoded.paymentRequest,
9
+ description: description,
10
+ amount: amount,
11
+ timestamp: timestamp,
12
+ expiry: timestamp + decoded.expiry,
13
+ };
14
+ }
@@ -1,17 +1,17 @@
1
1
  export function getCachedValue(event, symbol) {
2
- // @ts-expect-error
3
- return event[symbol];
2
+ return Reflect.get(event, symbol);
4
3
  }
5
4
  export function setCachedValue(event, symbol, value) {
6
- // @ts-expect-error
7
- event[symbol] = value;
5
+ Reflect.set(event, symbol, value);
8
6
  }
9
7
  /** Internal method used to cache computed values on events */
10
8
  export function getOrComputeCachedValue(event, symbol, compute) {
11
- let cached = getCachedValue(event, symbol);
12
- if (!cached) {
13
- // @ts-expect-error
14
- cached = event[symbol] = compute(event);
9
+ if (Reflect.has(event, symbol)) {
10
+ return Reflect.get(event, symbol);
11
+ }
12
+ else {
13
+ const value = compute(event);
14
+ Reflect.set(event, symbol, value);
15
+ return value;
15
16
  }
16
- return cached;
17
17
  }
@@ -12,3 +12,5 @@ export * from "./emoji.js";
12
12
  export * from "./lru.js";
13
13
  export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
+ export * from "./zap.js";
16
+ export * from "./bolt11.js";
@@ -12,3 +12,5 @@ export * from "./emoji.js";
12
12
  export * from "./lru.js";
13
13
  export * from "./hashtag.js";
14
14
  export * from "./url.js";
15
+ export * from "./zap.js";
16
+ export * from "./bolt11.js";
@@ -1,12 +1,6 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const MailboxesInboxesSymbol: unique symbol;
3
3
  export declare const MailboxesOutboxesSymbol: unique symbol;
4
- declare module "nostr-tools" {
5
- interface Event {
6
- [MailboxesInboxesSymbol]?: string[];
7
- [MailboxesOutboxesSymbol]?: string[];
8
- }
9
- }
10
4
  /**
11
5
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
12
6
  */
@@ -1,11 +1,12 @@
1
1
  import { safeRelayUrl } from "./relays.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
3
4
  export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
4
5
  /**
5
6
  * Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
6
7
  */
7
8
  export function getInboxes(event) {
8
- if (!event[MailboxesInboxesSymbol]) {
9
+ return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
9
10
  const inboxes = [];
10
11
  for (const tag of event.tags) {
11
12
  if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
@@ -14,15 +15,14 @@ export function getInboxes(event) {
14
15
  inboxes.push(url);
15
16
  }
16
17
  }
17
- event[MailboxesInboxesSymbol] = inboxes;
18
- }
19
- return event[MailboxesInboxesSymbol];
18
+ return inboxes;
19
+ });
20
20
  }
21
21
  /**
22
22
  * Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
23
23
  */
24
24
  export function getOutboxes(event) {
25
- if (!event[MailboxesOutboxesSymbol]) {
25
+ return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
26
26
  const outboxes = [];
27
27
  for (const tag of event.tags) {
28
28
  if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
@@ -31,7 +31,6 @@ export function getOutboxes(event) {
31
31
  outboxes.push(url);
32
32
  }
33
33
  }
34
- event[MailboxesOutboxesSymbol] = outboxes;
35
- }
36
- return event[MailboxesOutboxesSymbol];
34
+ return outboxes;
35
+ });
37
36
  }
@@ -10,7 +10,7 @@ export declare function parseCoordinate(a: string, requireD: true, silent: false
10
10
  export declare function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
11
11
  export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
12
12
  export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
13
- export declare function encodeDecodeResult(result: DecodeResult): "" | `nsec1${string}` | `npub1${string}` | `note1${string}` | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nrelay1${string}`;
13
+ export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
14
14
  export declare function getEventPointerFromTag(tag: string[]): EventPointer;
15
15
  export declare function getAddressPointerFromTag(tag: string[]): AddressPointer;
16
16
  export declare function getProfilePointerFromTag(tag: string[]): ProfilePointer;
@@ -1,4 +1,4 @@
1
- import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nrelayEncode, nsecEncode, } from "nostr-tools/nip19";
1
+ import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
2
2
  import { getPublicKey } from "nostr-tools";
3
3
  import { safeRelayUrls } from "./relays.js";
4
4
  export function parseCoordinate(a, requireD = false, silent = true) {
@@ -53,8 +53,6 @@ export function encodeDecodeResult(result) {
53
53
  return nprofileEncode(result.data);
54
54
  case "nevent":
55
55
  return neventEncode(result.data);
56
- case "nrelay":
57
- return nrelayEncode(result.data);
58
56
  case "nsec":
59
57
  return nsecEncode(result.data);
60
58
  case "npub":
@@ -1,10 +1,5 @@
1
1
  import { NostrEvent } from "nostr-tools";
2
2
  export declare const ProfileContentSymbol: unique symbol;
3
- declare module "nostr-tools" {
4
- interface Event {
5
- [ProfileContentSymbol]?: ProfileContent | Error;
6
- }
7
- }
8
3
  export type ProfileContent = {
9
4
  name?: string;
10
5
  display_name?: string;
@@ -21,5 +16,5 @@ export type ProfileContent = {
21
16
  };
22
17
  /** Returns the parsed profile content for a kind 0 event */
23
18
  export declare function getProfileContent(event: NostrEvent): ProfileContent;
24
- export declare function getProfileContent(event: NostrEvent, quite: false): ProfileContent;
25
- export declare function getProfileContent(event: NostrEvent, quite: true): ProfileContent | Error;
19
+ /** Checks if the content of the kind 0 event is valid JSON */
20
+ export declare function isValidProfile(profile?: NostrEvent): boolean;
@@ -1,28 +1,31 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { getOrComputeCachedValue } from "./cache.js";
1
3
  export const ProfileContentSymbol = Symbol.for("profile-content");
2
- export function getProfileContent(event, quite = false) {
3
- let cached = event[ProfileContentSymbol];
4
- if (!cached) {
5
- try {
6
- const profile = JSON.parse(event.content);
7
- // ensure nip05 is a string
8
- if (profile.nip05 && typeof profile.nip05 !== "string")
9
- profile.nip05 = String(profile.nip05);
10
- cached = event[ProfileContentSymbol] = profile;
4
+ /** Returns the parsed profile content for a kind 0 event */
5
+ export function getProfileContent(event) {
6
+ return getOrComputeCachedValue(event, ProfileContentSymbol, () => {
7
+ const profile = JSON.parse(event.content);
8
+ // ensure nip05 is a string
9
+ if (profile.nip05 && typeof profile.nip05 !== "string")
10
+ profile.nip05 = String(profile.nip05);
11
+ // add missing protocol to website
12
+ if (profile.website?.startsWith("http") === false) {
13
+ profile.website = "https://" + profile.website;
11
14
  }
12
- catch (e) {
13
- if (e instanceof Error)
14
- cached = event[ProfileContentSymbol] = e;
15
- }
16
- }
17
- if (cached === undefined) {
18
- throw new Error("Failed to parse profile");
15
+ return profile;
16
+ });
17
+ }
18
+ /** Checks if the content of the kind 0 event is valid JSON */
19
+ export function isValidProfile(profile) {
20
+ if (!profile)
21
+ return false;
22
+ if (profile.kind !== kinds.Metadata)
23
+ return false;
24
+ try {
25
+ getProfileContent(profile);
26
+ return true;
19
27
  }
20
- else if (cached instanceof Error) {
21
- if (!quite)
22
- throw cached;
23
- else
24
- return cached;
28
+ catch (error) {
29
+ return false;
25
30
  }
26
- else
27
- return cached;
28
31
  }
@@ -1,4 +1,4 @@
1
- import { NostrEvent } from "nostr-tools";
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
2
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
3
3
  export type ThreadReferences = {
4
4
  root?: {
@@ -29,7 +29,7 @@ declare module "nostr-tools" {
29
29
  }
30
30
  }
31
31
  /** Parses NIP-10 tags and handles legacy behavior */
32
- export declare function interpretThreadTags(event: NostrEvent): {
32
+ export declare function interpretThreadTags(event: NostrEvent | EventTemplate): {
33
33
  root?: {
34
34
  e: string[];
35
35
  a: undefined;
@@ -39,7 +39,7 @@ export declare function interpretThreadTags(event: NostrEvent): {
39
39
  } | {
40
40
  e: string[];
41
41
  a: string[];
42
- } | undefined;
42
+ };
43
43
  reply?: {
44
44
  e: string[];
45
45
  a: undefined;
@@ -49,7 +49,7 @@ export declare function interpretThreadTags(event: NostrEvent): {
49
49
  } | {
50
50
  e: string[];
51
51
  a: string[];
52
- } | undefined;
52
+ };
53
53
  };
54
54
  /** Returns the parsed NIP-10 tags for an event */
55
- export declare function getNip10References(event: NostrEvent): ThreadReferences;
55
+ export declare function getNip10References(event: NostrEvent | EventTemplate): ThreadReferences;
@@ -1,4 +1,5 @@
1
1
  import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
2
+ import { getOrComputeCachedValue } from "./cache.js";
2
3
  export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
3
4
  /** Parses NIP-10 tags and handles legacy behavior */
4
5
  export function interpretThreadTags(event) {
@@ -42,20 +43,31 @@ export function interpretThreadTags(event) {
42
43
  }
43
44
  /** Returns the parsed NIP-10 tags for an event */
44
45
  export function getNip10References(event) {
45
- let refs = event[Nip10ThreadRefsSymbol];
46
- if (!refs) {
47
- const e = event;
48
- const tags = interpretThreadTags(e);
49
- refs = event[Nip10ThreadRefsSymbol] = {
50
- root: tags.root && {
51
- e: tags.root.e && getEventPointerFromTag(tags.root.e),
52
- a: tags.root.a && getAddressPointerFromTag(tags.root.a),
53
- },
54
- reply: tags.reply && {
55
- e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
56
- a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
57
- },
46
+ return getOrComputeCachedValue(event, Nip10ThreadRefsSymbol, () => {
47
+ const tags = interpretThreadTags(event);
48
+ let root;
49
+ if (tags.root) {
50
+ try {
51
+ root = {
52
+ e: tags.root.e && getEventPointerFromTag(tags.root.e),
53
+ a: tags.root.a && getAddressPointerFromTag(tags.root.a),
54
+ };
55
+ }
56
+ catch (error) { }
57
+ }
58
+ let reply;
59
+ if (tags.reply) {
60
+ try {
61
+ reply = {
62
+ e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
63
+ a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
64
+ };
65
+ }
66
+ catch (error) { }
67
+ }
68
+ return {
69
+ root,
70
+ reply,
58
71
  };
59
- }
60
- return refs;
72
+ });
61
73
  }
@@ -0,0 +1,14 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const ZapRequestSymbol: unique symbol;
3
+ export declare const ZapFromSymbol: unique symbol;
4
+ export declare const ZapInvoiceSymbol: unique symbol;
5
+ export declare const ZapEventPointerSymbol: unique symbol;
6
+ export declare const ZapAddressPointerSymbol: unique symbol;
7
+ export declare function getZapSender(zap: NostrEvent): string;
8
+ export declare function getZapRecipient(zap: NostrEvent): string;
9
+ export declare function getZapPayment(zap: NostrEvent): import("./bolt11.js").ParsedInvoice | undefined;
10
+ export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-tools/nip19").AddressPointer | null;
11
+ export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
12
+ export declare function getZapPreimage(zap: NostrEvent): string | undefined;
13
+ export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
14
+ export declare function isValidZap(zap?: NostrEvent): boolean;
@@ -0,0 +1,66 @@
1
+ import { kinds, nip57 } from "nostr-tools";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { getTagValue } from "./event.js";
4
+ import { isATag, isETag } from "./tags.js";
5
+ import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
6
+ import { parseBolt11 } from "./bolt11.js";
7
+ export const ZapRequestSymbol = Symbol.for("zap-request");
8
+ export const ZapFromSymbol = Symbol.for("zap-from");
9
+ export const ZapInvoiceSymbol = Symbol.for("zap-bolt11");
10
+ export const ZapEventPointerSymbol = Symbol.for("zap-event-pointer");
11
+ export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
12
+ export function getZapSender(zap) {
13
+ return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
14
+ }
15
+ export function getZapRecipient(zap) {
16
+ const recipient = getTagValue(zap, "p");
17
+ if (!recipient)
18
+ throw new Error("Missing recipient");
19
+ return recipient;
20
+ }
21
+ export function getZapPayment(zap) {
22
+ return getOrComputeCachedValue(zap, ZapInvoiceSymbol, () => {
23
+ const bolt11 = getTagValue(zap, "bolt11");
24
+ return bolt11 ? parseBolt11(bolt11) : undefined;
25
+ });
26
+ }
27
+ export function getZapAddressPointer(zap) {
28
+ return getOrComputeCachedValue(zap, ZapAddressPointerSymbol, () => {
29
+ const a = zap.tags.find(isATag);
30
+ return a ? getAddressPointerFromTag(a) : null;
31
+ });
32
+ }
33
+ export function getZapEventPointer(zap) {
34
+ return getOrComputeCachedValue(zap, ZapEventPointerSymbol, () => {
35
+ const e = zap.tags.find(isETag);
36
+ return e ? getEventPointerFromTag(e) : null;
37
+ });
38
+ }
39
+ export function getZapPreimage(zap) {
40
+ return getTagValue(zap, "preimage");
41
+ }
42
+ export function getZapRequest(zap) {
43
+ return getOrComputeCachedValue(zap, ZapRequestSymbol, () => {
44
+ const description = getTagValue(zap, "description");
45
+ if (!description)
46
+ throw new Error("Missing description tag");
47
+ const error = nip57.validateZapRequest(description);
48
+ if (error)
49
+ throw new Error(error);
50
+ return JSON.parse(description);
51
+ });
52
+ }
53
+ export function isValidZap(zap) {
54
+ if (!zap)
55
+ return false;
56
+ if (zap.kind !== kinds.Zap)
57
+ return false;
58
+ try {
59
+ getZapRequest(zap);
60
+ getZapRecipient(zap);
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ return false;
65
+ }
66
+ }
@@ -2,6 +2,8 @@ import { BehaviorSubject } from "rxjs";
2
2
  export function getValue(observable) {
3
3
  if (observable instanceof BehaviorSubject)
4
4
  return observable.value;
5
+ if (Reflect.has(observable, "value"))
6
+ return Reflect.get(observable, "value");
5
7
  return new Promise((res) => {
6
8
  const sub = observable.subscribe((v) => {
7
9
  res(v);
@@ -3,3 +3,4 @@ export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
5
  export * from "./thread.js";
6
+ export * from "./zaps.js";
@@ -3,3 +3,4 @@ export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
5
  export * from "./thread.js";
6
+ export * from "./zaps.js";
@@ -1,11 +1,11 @@
1
1
  import { kinds } from "nostr-tools";
2
- import { map } from "rxjs/operators";
3
- import { getProfileContent } from "../helpers/profile.js";
2
+ import { filter, map } from "rxjs/operators";
3
+ import { getProfileContent, isValidProfile } from "../helpers/profile.js";
4
4
  export function ProfileQuery(pubkey) {
5
5
  return {
6
6
  key: pubkey,
7
7
  run: (events) => {
8
- return events.replaceable(kinds.Metadata, pubkey).pipe(map((event) => event && getProfileContent(event)));
8
+ return events.replaceable(kinds.Metadata, pubkey).pipe(filter(isValidProfile), map((event) => event && getProfileContent(event)));
9
9
  },
10
10
  };
11
11
  }
@@ -0,0 +1,4 @@
1
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Query } from "../query-store/index.js";
4
+ export declare function EventZapsQuery(id: string | EventPointer | AddressPointer): Query<NostrEvent[]>;
@@ -0,0 +1,20 @@
1
+ import { map } from "rxjs";
2
+ import { kinds } from "nostr-tools";
3
+ import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
4
+ import { isValidZap } from "../helpers/zap.js";
5
+ export function EventZapsQuery(id) {
6
+ return {
7
+ key: JSON.stringify(id),
8
+ run: (events) => {
9
+ if (isAddressPointer(id)) {
10
+ return events
11
+ .timeline([{ kinds: [kinds.Zap], "#a": [getCoordinateFromAddressPointer(id)] }])
12
+ .pipe(map((events) => events.filter(isValidZap)));
13
+ }
14
+ else {
15
+ id = typeof id === "string" ? id : id.id;
16
+ return events.timeline([{ kinds: [kinds.Zap], "#e": [id] }]).pipe(map((events) => events.filter(isValidZap)));
17
+ }
18
+ },
19
+ };
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,8 +54,9 @@
54
54
  "dependencies": {
55
55
  "debug": "^4.3.7",
56
56
  "json-stringify-deterministic": "^1.0.12",
57
+ "light-bolt11-decoder": "^3.2.0",
57
58
  "nanoid": "^5.0.7",
58
- "nostr-tools": "^2.7.2",
59
+ "nostr-tools": "^2.10.1",
59
60
  "rxjs": "^7.8.1"
60
61
  },
61
62
  "devDependencies": {
@@ -63,7 +64,8 @@
63
64
  "@types/debug": "^4.1.12",
64
65
  "@types/jest": "^29.5.13",
65
66
  "jest": "^29.7.0",
66
- "jest-extended": "^4.0.2"
67
+ "jest-extended": "^4.0.2",
68
+ "typescript": "^5.6.3"
67
69
  },
68
70
  "jest": {
69
71
  "roots": [
@@ -73,6 +75,10 @@
73
75
  "jest-extended/all"
74
76
  ]
75
77
  },
78
+ "funding": {
79
+ "type": "lightning",
80
+ "url": "lightning:nostrudel@geyser.fund"
81
+ },
76
82
  "scripts": {
77
83
  "build": "tsc",
78
84
  "watch:build": "tsc --watch > /dev/null",