applesauce-core 0.0.0-next-20250123205825 → 0.0.0-next-20250123215242
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 -0
- package/dist/event-store/common.js +2 -0
- package/dist/event-store/database.d.ts +64 -0
- package/dist/event-store/database.js +311 -0
- package/dist/event-store/event-store.d.ts +49 -0
- package/dist/event-store/event-store.js +389 -0
- package/dist/event-store/index.d.ts +2 -0
- package/dist/event-store/index.js +2 -0
- package/dist/helpers/bolt11.d.ts +9 -0
- package/dist/helpers/bolt11.js +15 -0
- package/dist/helpers/cache.d.ts +5 -0
- package/dist/helpers/cache.js +17 -0
- package/dist/helpers/comment.d.ts +47 -0
- package/dist/helpers/comment.js +116 -0
- package/dist/helpers/content.d.ts +3 -0
- package/dist/helpers/content.js +8 -0
- package/dist/helpers/delete.d.ts +3 -0
- package/dist/helpers/delete.js +7 -0
- package/dist/helpers/emoji.d.ts +11 -0
- package/dist/helpers/emoji.js +16 -0
- package/dist/helpers/event.d.ts +48 -0
- package/dist/helpers/event.js +105 -0
- package/dist/helpers/external-id.d.ts +29 -0
- package/dist/helpers/external-id.js +20 -0
- package/dist/helpers/file-metadata.d.ts +53 -0
- package/dist/helpers/file-metadata.js +90 -0
- package/dist/helpers/file-metadata.test.d.ts +1 -0
- package/dist/helpers/file-metadata.test.js +103 -0
- package/dist/helpers/filter.d.ts +10 -0
- package/dist/helpers/filter.js +46 -0
- package/dist/helpers/groups.d.ts +14 -0
- package/dist/helpers/groups.js +16 -0
- package/dist/helpers/hashtag.d.ts +2 -0
- package/dist/helpers/hashtag.js +7 -0
- package/dist/helpers/hidden-tags.d.ts +48 -0
- package/dist/helpers/hidden-tags.js +108 -0
- package/dist/helpers/hidden-tags.test.d.ts +1 -0
- package/dist/helpers/hidden-tags.test.js +28 -0
- package/dist/helpers/index.d.ts +28 -0
- package/dist/helpers/index.js +28 -0
- package/dist/helpers/json.d.ts +2 -0
- package/dist/helpers/json.js +9 -0
- package/dist/helpers/lnurl.d.ts +4 -0
- package/dist/helpers/lnurl.js +40 -0
- package/dist/helpers/lru.d.ts +32 -0
- package/dist/helpers/lru.js +148 -0
- package/dist/helpers/mailboxes.d.ts +11 -0
- package/dist/helpers/mailboxes.js +36 -0
- package/dist/helpers/mailboxes.test.d.ts +1 -0
- package/dist/helpers/mailboxes.test.js +81 -0
- package/dist/helpers/picture-post.d.ts +4 -0
- package/dist/helpers/picture-post.js +6 -0
- package/dist/helpers/pointers.d.ts +55 -0
- package/dist/helpers/pointers.js +205 -0
- package/dist/helpers/profile.d.ts +20 -0
- package/dist/helpers/profile.js +31 -0
- package/dist/helpers/relays.d.ts +12 -0
- package/dist/helpers/relays.js +31 -0
- package/dist/helpers/share.d.ts +4 -0
- package/dist/helpers/share.js +12 -0
- package/dist/helpers/string.d.ts +10 -0
- package/dist/helpers/string.js +15 -0
- package/dist/helpers/tags.d.ts +25 -0
- package/dist/helpers/tags.js +42 -0
- package/dist/helpers/tags.test.d.ts +1 -0
- package/dist/helpers/tags.test.js +16 -0
- package/dist/helpers/threading.d.ts +55 -0
- package/dist/helpers/threading.js +94 -0
- package/dist/helpers/threading.test.d.ts +1 -0
- package/dist/helpers/threading.test.js +41 -0
- package/dist/helpers/time.d.ts +2 -0
- package/dist/helpers/time.js +4 -0
- package/dist/helpers/url.d.ts +14 -0
- package/dist/helpers/url.js +30 -0
- package/dist/helpers/zap.d.ts +39 -0
- package/dist/helpers/zap.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +2 -0
- package/dist/observable/get-value.d.ts +3 -0
- package/dist/observable/get-value.js +14 -0
- package/dist/observable/index.d.ts +2 -0
- package/dist/observable/index.js +2 -0
- package/dist/observable/share-latest-value.d.ts +8 -0
- package/dist/observable/share-latest-value.js +21 -0
- package/dist/promise/deferred.d.ts +6 -0
- package/dist/promise/deferred.js +15 -0
- package/dist/promise/index.d.ts +1 -0
- package/dist/promise/index.js +1 -0
- package/dist/queries/comments.d.ts +4 -0
- package/dist/queries/comments.js +14 -0
- package/dist/queries/index.d.ts +7 -0
- package/dist/queries/index.js +7 -0
- package/dist/queries/mailboxes.d.ts +6 -0
- package/dist/queries/mailboxes.js +13 -0
- package/dist/queries/profile.d.ts +4 -0
- package/dist/queries/profile.js +12 -0
- package/dist/queries/reactions.d.ts +4 -0
- package/dist/queries/reactions.js +19 -0
- package/dist/queries/simple.d.ts +16 -0
- package/dist/queries/simple.js +38 -0
- package/dist/queries/thread.d.ts +25 -0
- package/dist/queries/thread.js +92 -0
- package/dist/queries/zaps.d.ts +5 -0
- package/dist/queries/zaps.js +21 -0
- package/dist/query-store/index.d.ts +57 -0
- package/dist/query-store/index.js +68 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { unixNow } from "applesauce-core/helpers";
|
|
2
|
+
import { kinds } from "nostr-tools";
|
|
3
|
+
export const HiddenTagsSymbol = Symbol.for("hidden-tags");
|
|
4
|
+
/** Various event kinds that can have encrypted tags in their content and which encryption method they use */
|
|
5
|
+
export const EventEncryptionMethod = {
|
|
6
|
+
// NIP-60 wallet
|
|
7
|
+
37375: "nip44",
|
|
8
|
+
// NIP-51 lists
|
|
9
|
+
[kinds.BookmarkList]: "nip04",
|
|
10
|
+
[kinds.InterestsList]: "nip04",
|
|
11
|
+
[kinds.Mutelist]: "nip04",
|
|
12
|
+
[kinds.CommunitiesList]: "nip04",
|
|
13
|
+
[kinds.PublicChatsList]: "nip04",
|
|
14
|
+
[kinds.SearchRelaysList]: "nip04",
|
|
15
|
+
// NIP-51 sets
|
|
16
|
+
[kinds.Bookmarksets]: "nip04",
|
|
17
|
+
[kinds.Relaysets]: "nip04",
|
|
18
|
+
[kinds.Followsets]: "nip04",
|
|
19
|
+
[kinds.Curationsets]: "nip04",
|
|
20
|
+
[kinds.Interestsets]: "nip04",
|
|
21
|
+
};
|
|
22
|
+
/** Checks if an event can have hidden tags */
|
|
23
|
+
export function canHaveHiddenTags(kind) {
|
|
24
|
+
return EventEncryptionMethod[kind] !== undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Checks if an event has hidden tags */
|
|
27
|
+
export function hasHiddenTags(event) {
|
|
28
|
+
return canHaveHiddenTags(event.kind) && event.content.length > 0;
|
|
29
|
+
}
|
|
30
|
+
/** Returns the hidden tags for an event if they are unlocked */
|
|
31
|
+
export function getHiddenTags(event) {
|
|
32
|
+
return Reflect.get(event, HiddenTagsSymbol);
|
|
33
|
+
}
|
|
34
|
+
/** Checks if the hidden tags are locked */
|
|
35
|
+
export function isHiddenTagsLocked(event) {
|
|
36
|
+
return hasHiddenTags(event) && getHiddenTags(event) === undefined;
|
|
37
|
+
}
|
|
38
|
+
function getEventEncryption(kind, signer) {
|
|
39
|
+
const method = EventEncryptionMethod[kind];
|
|
40
|
+
const encryption = signer[method];
|
|
41
|
+
if (!encryption)
|
|
42
|
+
throw new Error(`Signer does not support ${method} encryption`);
|
|
43
|
+
return encryption;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decrypts the private list
|
|
47
|
+
* @param event The list event to decrypt
|
|
48
|
+
* @param signer A signer to use to decrypt the tags
|
|
49
|
+
* @param store An optional EventStore to notify about the update
|
|
50
|
+
* @throws
|
|
51
|
+
*/
|
|
52
|
+
export async function unlockHiddenTags(event, signer, store) {
|
|
53
|
+
if (!canHaveHiddenTags(event.kind))
|
|
54
|
+
throw new Error("Event kind does not support hidden tags");
|
|
55
|
+
const encryption = getEventEncryption(event.kind, signer);
|
|
56
|
+
const plaintext = await encryption.decrypt(event.pubkey, event.content);
|
|
57
|
+
const parsed = JSON.parse(plaintext);
|
|
58
|
+
if (!Array.isArray(parsed))
|
|
59
|
+
throw new Error("Content is not an array of tags");
|
|
60
|
+
// Convert array to tags array string[][]
|
|
61
|
+
const tags = parsed.filter((t) => Array.isArray(t)).map((t) => t.map((v) => String(v)));
|
|
62
|
+
Reflect.set(event, HiddenTagsSymbol, tags);
|
|
63
|
+
if (store)
|
|
64
|
+
store.update(event);
|
|
65
|
+
return event;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Modifies tags and returns an EventTemplate
|
|
69
|
+
* @param event Event to modify
|
|
70
|
+
* @param operations Operations for hidden and public tags
|
|
71
|
+
* @param signer A signer to use to decrypt the tags
|
|
72
|
+
* @throws
|
|
73
|
+
*/
|
|
74
|
+
export async function modifyEventTags(event, operations, signer) {
|
|
75
|
+
const draft = { content: event.content, tags: event.tags, kind: event.kind, created_at: unixNow() };
|
|
76
|
+
if (operations.public) {
|
|
77
|
+
draft.tags = operations.public(event.tags);
|
|
78
|
+
}
|
|
79
|
+
if (operations.hidden) {
|
|
80
|
+
if (!signer)
|
|
81
|
+
throw new Error("Missing signer for hidden tags");
|
|
82
|
+
if (!canHaveHiddenTags(event.kind))
|
|
83
|
+
throw new Error("Event kind does not support hidden tags");
|
|
84
|
+
const hidden = hasHiddenTags(event) ? getHiddenTags(event) : [];
|
|
85
|
+
if (!hidden)
|
|
86
|
+
throw new Error("Hidden tags are locked");
|
|
87
|
+
const newHidden = operations.hidden(hidden);
|
|
88
|
+
const encryption = getEventEncryption(event.kind, signer);
|
|
89
|
+
draft.content = await encryption.encrypt(event.pubkey, JSON.stringify(newHidden));
|
|
90
|
+
}
|
|
91
|
+
return draft;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Override the hidden tags in an event
|
|
95
|
+
* @throws
|
|
96
|
+
*/
|
|
97
|
+
export async function overrideHiddenTags(event, hidden, signer) {
|
|
98
|
+
if (!canHaveHiddenTags(event.kind))
|
|
99
|
+
throw new Error("Event kind does not support hidden tags");
|
|
100
|
+
const encryption = getEventEncryption(event.kind, signer);
|
|
101
|
+
const ciphertext = await encryption.encrypt(event.pubkey, JSON.stringify(hidden));
|
|
102
|
+
return {
|
|
103
|
+
kind: event.kind,
|
|
104
|
+
content: ciphertext,
|
|
105
|
+
created_at: unixNow(),
|
|
106
|
+
tags: event.tags,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, beforeEach, it, expect } from "vitest";
|
|
2
|
+
import { getHiddenTags, unixNow, unlockHiddenTags } from "applesauce-core/helpers";
|
|
3
|
+
import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
|
|
4
|
+
const key = generateSecretKey();
|
|
5
|
+
const pubkey = getPublicKey(key);
|
|
6
|
+
const signer = {
|
|
7
|
+
nip04: {
|
|
8
|
+
encrypt: (pubkey, plaintext) => nip04.encrypt(key, pubkey, plaintext),
|
|
9
|
+
decrypt: (pubkey, ciphertext) => nip04.decrypt(key, pubkey, ciphertext),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
describe("Private Lists", () => {
|
|
13
|
+
describe("unlockHiddenTags", () => {
|
|
14
|
+
let list;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
list = finalizeEvent({
|
|
17
|
+
kind: kinds.Mutelist,
|
|
18
|
+
created_at: unixNow(),
|
|
19
|
+
content: await nip04.encrypt(key, pubkey, JSON.stringify([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]])),
|
|
20
|
+
tags: [],
|
|
21
|
+
}, key);
|
|
22
|
+
});
|
|
23
|
+
it("should unlock hidden tags", async () => {
|
|
24
|
+
await unlockHiddenTags(list, signer);
|
|
25
|
+
expect(getHiddenTags(list)).toEqual(expect.arrayContaining([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]]));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export * from "./bolt11.js";
|
|
2
|
+
export * from "./cache.js";
|
|
3
|
+
export * from "./comment.js";
|
|
4
|
+
export * from "./content.js";
|
|
5
|
+
export * from "./delete.js";
|
|
6
|
+
export * from "./emoji.js";
|
|
7
|
+
export * from "./event.js";
|
|
8
|
+
export * from "./external-id.js";
|
|
9
|
+
export * from "./file-metadata.js";
|
|
10
|
+
export * from "./filter.js";
|
|
11
|
+
export * from "./groups.js";
|
|
12
|
+
export * from "./hashtag.js";
|
|
13
|
+
export * from "./hidden-tags.js";
|
|
14
|
+
export * from "./json.js";
|
|
15
|
+
export * from "./lnurl.js";
|
|
16
|
+
export * from "./lru.js";
|
|
17
|
+
export * from "./mailboxes.js";
|
|
18
|
+
export * from "./picture-post.js";
|
|
19
|
+
export * from "./pointers.js";
|
|
20
|
+
export * from "./profile.js";
|
|
21
|
+
export * from "./relays.js";
|
|
22
|
+
export * from "./share.js";
|
|
23
|
+
export * from "./string.js";
|
|
24
|
+
export * from "./tags.js";
|
|
25
|
+
export * from "./threading.js";
|
|
26
|
+
export * from "./time.js";
|
|
27
|
+
export * from "./url.js";
|
|
28
|
+
export * from "./zap.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export * from "./bolt11.js";
|
|
2
|
+
export * from "./cache.js";
|
|
3
|
+
export * from "./comment.js";
|
|
4
|
+
export * from "./content.js";
|
|
5
|
+
export * from "./delete.js";
|
|
6
|
+
export * from "./emoji.js";
|
|
7
|
+
export * from "./event.js";
|
|
8
|
+
export * from "./external-id.js";
|
|
9
|
+
export * from "./file-metadata.js";
|
|
10
|
+
export * from "./filter.js";
|
|
11
|
+
export * from "./groups.js";
|
|
12
|
+
export * from "./hashtag.js";
|
|
13
|
+
export * from "./hidden-tags.js";
|
|
14
|
+
export * from "./json.js";
|
|
15
|
+
export * from "./lnurl.js";
|
|
16
|
+
export * from "./lru.js";
|
|
17
|
+
export * from "./mailboxes.js";
|
|
18
|
+
export * from "./picture-post.js";
|
|
19
|
+
export * from "./pointers.js";
|
|
20
|
+
export * from "./profile.js";
|
|
21
|
+
export * from "./relays.js";
|
|
22
|
+
export * from "./share.js";
|
|
23
|
+
export * from "./string.js";
|
|
24
|
+
export * from "./tags.js";
|
|
25
|
+
export * from "./threading.js";
|
|
26
|
+
export * from "./time.js";
|
|
27
|
+
export * from "./url.js";
|
|
28
|
+
export * from "./zap.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function parseLightningAddress(address: string): URL | undefined;
|
|
2
|
+
export declare function decodeLNURL(lnurl: string): URL | undefined;
|
|
3
|
+
export declare function parseLNURLOrAddress(addressOrLNURL: string): URL | undefined;
|
|
4
|
+
export declare function getInvoice(callback: URL): Promise<string>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { bech32 } from "@scure/base";
|
|
2
|
+
import { parseBolt11 } from "./bolt11.js";
|
|
3
|
+
const decoder = new TextDecoder();
|
|
4
|
+
export function parseLightningAddress(address) {
|
|
5
|
+
let [name, domain] = address.split("@");
|
|
6
|
+
if (!name || !domain)
|
|
7
|
+
return;
|
|
8
|
+
return new URL(`https://${domain}/.well-known/lnurlp/${name}`);
|
|
9
|
+
}
|
|
10
|
+
export function decodeLNURL(lnurl) {
|
|
11
|
+
try {
|
|
12
|
+
const { words, prefix } = bech32.decode(lnurl);
|
|
13
|
+
if (prefix !== "lnurl")
|
|
14
|
+
return;
|
|
15
|
+
const str = decoder.decode(bech32.fromWords(words));
|
|
16
|
+
return new URL(str);
|
|
17
|
+
}
|
|
18
|
+
catch (error) { }
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
export function parseLNURLOrAddress(addressOrLNURL) {
|
|
22
|
+
if (addressOrLNURL.includes("@")) {
|
|
23
|
+
return parseLightningAddress(addressOrLNURL);
|
|
24
|
+
}
|
|
25
|
+
return decodeLNURL(addressOrLNURL);
|
|
26
|
+
}
|
|
27
|
+
export async function getInvoice(callback) {
|
|
28
|
+
const { pr: payRequest } = await fetch(callback).then((res) => res.json());
|
|
29
|
+
const amount = callback.searchParams.get("amount");
|
|
30
|
+
if (!amount)
|
|
31
|
+
throw new Error("Missing amount");
|
|
32
|
+
if (payRequest) {
|
|
33
|
+
const parsed = parseBolt11(payRequest);
|
|
34
|
+
if (parsed.amount !== parseInt(amount))
|
|
35
|
+
throw new Error("Incorrect amount");
|
|
36
|
+
return payRequest;
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
throw new Error("Failed to get invoice");
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type Item<T> = {
|
|
2
|
+
key: string;
|
|
3
|
+
prev: Item<T> | null;
|
|
4
|
+
value: T;
|
|
5
|
+
next: Item<T> | null;
|
|
6
|
+
expiry: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Copied from tiny-lru and modified to support typescript
|
|
10
|
+
* @see https://github.com/avoidwork/tiny-lru/blob/master/src/lru.js
|
|
11
|
+
*/
|
|
12
|
+
export declare class LRU<T extends unknown> {
|
|
13
|
+
first: Item<T> | null;
|
|
14
|
+
items: Record<string, Item<T>>;
|
|
15
|
+
last: Item<T> | null;
|
|
16
|
+
max: number;
|
|
17
|
+
resetTtl: boolean;
|
|
18
|
+
size: number;
|
|
19
|
+
ttl: number;
|
|
20
|
+
constructor(max?: number, ttl?: number, resetTtl?: boolean);
|
|
21
|
+
clear(): this;
|
|
22
|
+
delete(key: string): this;
|
|
23
|
+
entries(keys?: string[]): (string | T | undefined)[][];
|
|
24
|
+
evict(bypass?: boolean): this;
|
|
25
|
+
expiresAt(key: string): number | undefined;
|
|
26
|
+
get(key: string): T | undefined;
|
|
27
|
+
has(key: string): boolean;
|
|
28
|
+
keys(): string[];
|
|
29
|
+
set(key: string, value: T, bypass?: boolean, resetTtl?: boolean): this;
|
|
30
|
+
values(keys?: string[]): NonNullable<T>[];
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied from tiny-lru and modified to support typescript
|
|
3
|
+
* @see https://github.com/avoidwork/tiny-lru/blob/master/src/lru.js
|
|
4
|
+
*/
|
|
5
|
+
export class LRU {
|
|
6
|
+
first = null;
|
|
7
|
+
items = Object.create(null);
|
|
8
|
+
last = null;
|
|
9
|
+
max;
|
|
10
|
+
resetTtl;
|
|
11
|
+
size;
|
|
12
|
+
ttl;
|
|
13
|
+
constructor(max = 0, ttl = 0, resetTtl = false) {
|
|
14
|
+
this.first = null;
|
|
15
|
+
this.items = Object.create(null);
|
|
16
|
+
this.last = null;
|
|
17
|
+
this.max = max;
|
|
18
|
+
this.resetTtl = resetTtl;
|
|
19
|
+
this.size = 0;
|
|
20
|
+
this.ttl = ttl;
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.first = null;
|
|
24
|
+
this.items = Object.create(null);
|
|
25
|
+
this.last = null;
|
|
26
|
+
this.size = 0;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
delete(key) {
|
|
30
|
+
if (this.has(key)) {
|
|
31
|
+
const item = this.items[key];
|
|
32
|
+
delete this.items[key];
|
|
33
|
+
this.size--;
|
|
34
|
+
if (item.prev !== null) {
|
|
35
|
+
item.prev.next = item.next;
|
|
36
|
+
}
|
|
37
|
+
if (item.next !== null) {
|
|
38
|
+
item.next.prev = item.prev;
|
|
39
|
+
}
|
|
40
|
+
if (this.first === item) {
|
|
41
|
+
this.first = item.next;
|
|
42
|
+
}
|
|
43
|
+
if (this.last === item) {
|
|
44
|
+
this.last = item.prev;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
entries(keys = this.keys()) {
|
|
50
|
+
return keys.map((key) => [key, this.get(key)]);
|
|
51
|
+
}
|
|
52
|
+
evict(bypass = false) {
|
|
53
|
+
if (bypass || this.size > 0) {
|
|
54
|
+
const item = this.first;
|
|
55
|
+
delete this.items[item.key];
|
|
56
|
+
if (--this.size === 0) {
|
|
57
|
+
this.first = null;
|
|
58
|
+
this.last = null;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.first = item.next;
|
|
62
|
+
this.first.prev = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
expiresAt(key) {
|
|
68
|
+
let result;
|
|
69
|
+
if (this.has(key)) {
|
|
70
|
+
result = this.items[key].expiry;
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
get(key) {
|
|
75
|
+
let result;
|
|
76
|
+
if (this.has(key)) {
|
|
77
|
+
const item = this.items[key];
|
|
78
|
+
if (this.ttl > 0 && item.expiry <= Date.now()) {
|
|
79
|
+
this.delete(key);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
result = item.value;
|
|
83
|
+
this.set(key, result, true);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
has(key) {
|
|
89
|
+
return key in this.items;
|
|
90
|
+
}
|
|
91
|
+
keys() {
|
|
92
|
+
const result = [];
|
|
93
|
+
let x = this.first;
|
|
94
|
+
while (x !== null) {
|
|
95
|
+
result.push(x.key);
|
|
96
|
+
x = x.next;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
set(key, value, bypass = false, resetTtl = this.resetTtl) {
|
|
101
|
+
let item;
|
|
102
|
+
if (bypass || this.has(key)) {
|
|
103
|
+
item = this.items[key];
|
|
104
|
+
item.value = value;
|
|
105
|
+
if (bypass === false && resetTtl) {
|
|
106
|
+
item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl;
|
|
107
|
+
}
|
|
108
|
+
if (this.last !== item) {
|
|
109
|
+
const last = this.last, next = item.next, prev = item.prev;
|
|
110
|
+
if (this.first === item) {
|
|
111
|
+
this.first = item.next;
|
|
112
|
+
}
|
|
113
|
+
item.next = null;
|
|
114
|
+
item.prev = this.last;
|
|
115
|
+
last.next = item;
|
|
116
|
+
if (prev !== null) {
|
|
117
|
+
prev.next = next;
|
|
118
|
+
}
|
|
119
|
+
if (next !== null) {
|
|
120
|
+
next.prev = prev;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
if (this.max > 0 && this.size === this.max) {
|
|
126
|
+
this.evict(true);
|
|
127
|
+
}
|
|
128
|
+
item = this.items[key] = {
|
|
129
|
+
expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl,
|
|
130
|
+
key: key,
|
|
131
|
+
prev: this.last,
|
|
132
|
+
next: null,
|
|
133
|
+
value,
|
|
134
|
+
};
|
|
135
|
+
if (++this.size === 1) {
|
|
136
|
+
this.first = item;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.last.next = item;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.last = item;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
values(keys = this.keys()) {
|
|
146
|
+
return keys.map((key) => this.get(key));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const MailboxesInboxesSymbol: unique symbol;
|
|
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
|
+
*/
|
|
7
|
+
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
|
+
*/
|
|
11
|
+
export declare function getOutboxes(event: NostrEvent): string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { safeRelayUrl } from "./relays.js";
|
|
2
|
+
import { getOrComputeCachedValue } from "./cache.js";
|
|
3
|
+
export const MailboxesInboxesSymbol = Symbol.for("mailboxes-inboxes");
|
|
4
|
+
export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
|
|
5
|
+
/**
|
|
6
|
+
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
7
|
+
*/
|
|
8
|
+
export function getInboxes(event) {
|
|
9
|
+
return getOrComputeCachedValue(event, MailboxesInboxesSymbol, () => {
|
|
10
|
+
const inboxes = [];
|
|
11
|
+
for (const tag of event.tags) {
|
|
12
|
+
if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
|
|
13
|
+
const url = safeRelayUrl(tag[1]);
|
|
14
|
+
if (url && !inboxes.includes(url))
|
|
15
|
+
inboxes.push(url);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return inboxes;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
23
|
+
*/
|
|
24
|
+
export function getOutboxes(event) {
|
|
25
|
+
return getOrComputeCachedValue(event, MailboxesOutboxesSymbol, () => {
|
|
26
|
+
const outboxes = [];
|
|
27
|
+
for (const tag of event.tags) {
|
|
28
|
+
if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
|
|
29
|
+
const url = safeRelayUrl(tag[1]);
|
|
30
|
+
if (url && !outboxes.includes(url))
|
|
31
|
+
outboxes.push(url);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return outboxes;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { getInboxes, getOutboxes } from "./mailboxes.js";
|
|
3
|
+
const emptyEvent = {
|
|
4
|
+
kind: 10002,
|
|
5
|
+
content: "",
|
|
6
|
+
tags: [],
|
|
7
|
+
created_at: 0,
|
|
8
|
+
sig: "",
|
|
9
|
+
id: "",
|
|
10
|
+
pubkey: "",
|
|
11
|
+
};
|
|
12
|
+
describe("Mailboxes", () => {
|
|
13
|
+
describe("getInboxes", () => {
|
|
14
|
+
test("should transform urls", () => {
|
|
15
|
+
expect(Array.from(getInboxes({
|
|
16
|
+
...emptyEvent,
|
|
17
|
+
tags: [["r", "wss://inbox.com"]],
|
|
18
|
+
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
|
|
19
|
+
});
|
|
20
|
+
test("should remove bad urls", () => {
|
|
21
|
+
expect(Array.from(getInboxes({
|
|
22
|
+
...emptyEvent,
|
|
23
|
+
tags: [["r", "bad://inbox.com"]],
|
|
24
|
+
}))).toHaveLength(0);
|
|
25
|
+
expect(Array.from(getInboxes({
|
|
26
|
+
...emptyEvent,
|
|
27
|
+
tags: [["r", "something that is not a url"]],
|
|
28
|
+
}))).toHaveLength(0);
|
|
29
|
+
expect(Array.from(getInboxes({
|
|
30
|
+
...emptyEvent,
|
|
31
|
+
tags: [["r", "wss://inbox.com,wss://inbox.org"]],
|
|
32
|
+
}))).toHaveLength(0);
|
|
33
|
+
});
|
|
34
|
+
test("without marker", () => {
|
|
35
|
+
expect(Array.from(getInboxes({
|
|
36
|
+
...emptyEvent,
|
|
37
|
+
tags: [["r", "wss://inbox.com/"]],
|
|
38
|
+
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
|
|
39
|
+
});
|
|
40
|
+
test("with marker", () => {
|
|
41
|
+
expect(Array.from(getInboxes({
|
|
42
|
+
...emptyEvent,
|
|
43
|
+
tags: [["r", "wss://inbox.com/", "read"]],
|
|
44
|
+
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("getOutboxes", () => {
|
|
48
|
+
test("should transform urls", () => {
|
|
49
|
+
expect(Array.from(getOutboxes({
|
|
50
|
+
...emptyEvent,
|
|
51
|
+
tags: [["r", "wss://outbox.com"]],
|
|
52
|
+
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
|
|
53
|
+
});
|
|
54
|
+
test("should remove bad urls", () => {
|
|
55
|
+
expect(Array.from(getOutboxes({
|
|
56
|
+
...emptyEvent,
|
|
57
|
+
tags: [["r", "bad://inbox.com"]],
|
|
58
|
+
}))).toHaveLength(0);
|
|
59
|
+
expect(Array.from(getOutboxes({
|
|
60
|
+
...emptyEvent,
|
|
61
|
+
tags: [["r", "something that is not a url"]],
|
|
62
|
+
}))).toHaveLength(0);
|
|
63
|
+
expect(Array.from(getOutboxes({
|
|
64
|
+
...emptyEvent,
|
|
65
|
+
tags: [["r", "wss://outbox.com,wss://inbox.org"]],
|
|
66
|
+
}))).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
test("without marker", () => {
|
|
69
|
+
expect(Array.from(getOutboxes({
|
|
70
|
+
...emptyEvent,
|
|
71
|
+
tags: [["r", "wss://outbox.com/"]],
|
|
72
|
+
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
|
|
73
|
+
});
|
|
74
|
+
test("with marker", () => {
|
|
75
|
+
expect(Array.from(getOutboxes({
|
|
76
|
+
...emptyEvent,
|
|
77
|
+
tags: [["r", "wss://outbox.com/", "write"]],
|
|
78
|
+
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AddressPointer, DecodeResult, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
|
|
4
|
+
identifier?: string;
|
|
5
|
+
};
|
|
6
|
+
/** Parse the value of an "a" tag into an AddressPointer */
|
|
7
|
+
export declare function parseCoordinate(a: string): AddressPointerWithoutD | null;
|
|
8
|
+
export declare function parseCoordinate(a: string, requireD: false): AddressPointerWithoutD | null;
|
|
9
|
+
export declare function parseCoordinate(a: string, requireD: true): AddressPointer | null;
|
|
10
|
+
export declare function parseCoordinate(a: string, requireD: false, silent: false): AddressPointerWithoutD;
|
|
11
|
+
export declare function parseCoordinate(a: string, requireD: true, silent: false): AddressPointer;
|
|
12
|
+
export declare function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
|
|
13
|
+
export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
|
|
14
|
+
/** Extra a pubkey from the result of nip19.decode */
|
|
15
|
+
export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
|
|
16
|
+
/** Encodes the result of nip19.decode */
|
|
17
|
+
export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
|
|
18
|
+
/**
|
|
19
|
+
* Gets an EventPointer form a common "e" tag
|
|
20
|
+
* @throws
|
|
21
|
+
*/
|
|
22
|
+
export declare function getEventPointerFromETag(tag: string[]): EventPointer;
|
|
23
|
+
/**
|
|
24
|
+
* Gets an EventPointer form a "q" tag
|
|
25
|
+
* @throws
|
|
26
|
+
*/
|
|
27
|
+
export declare function getEventPointerFromQTag(tag: string[]): EventPointer;
|
|
28
|
+
/**
|
|
29
|
+
* Get an AddressPointer from an "a" tag
|
|
30
|
+
* @throws
|
|
31
|
+
*/
|
|
32
|
+
export declare function getAddressPointerFromATag(tag: string[]): AddressPointer;
|
|
33
|
+
/**
|
|
34
|
+
* Gets a ProfilePointer from a "p" tag
|
|
35
|
+
* @throws
|
|
36
|
+
*/
|
|
37
|
+
export declare function getProfilePointerFromPTag(tag: string[]): ProfilePointer;
|
|
38
|
+
/** Parses "e", "a", "p", and "q" tags into a pointer */
|
|
39
|
+
export declare function getPointerFromTag(tag: string[]): DecodeResult | null;
|
|
40
|
+
export declare function isAddressPointer(pointer: DecodeResult["data"]): pointer is AddressPointer;
|
|
41
|
+
export declare function isEventPointer(pointer: DecodeResult["data"]): pointer is EventPointer;
|
|
42
|
+
/** Returns the coordinate string for an AddressPointer */
|
|
43
|
+
export declare function getCoordinateFromAddressPointer(pointer: AddressPointer): string;
|
|
44
|
+
/**
|
|
45
|
+
* Returns an AddressPointer for a replaceable event
|
|
46
|
+
* @throws
|
|
47
|
+
*/
|
|
48
|
+
export declare function getAddressPointerForEvent(event: NostrEvent, relays?: string[]): AddressPointer;
|
|
49
|
+
/**
|
|
50
|
+
* Returns an EventPointer for an event
|
|
51
|
+
* @throws
|
|
52
|
+
*/
|
|
53
|
+
export declare function getEventPointerForEvent(event: NostrEvent, relays?: string[]): EventPointer;
|
|
54
|
+
/** Returns a pointer for a given event */
|
|
55
|
+
export declare function getPointerForEvent(event: NostrEvent, relays?: string[]): DecodeResult;
|