@nostrify/nostrify 0.46.4
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/.turbo/turbo-build.log +5 -0
- package/BunkerURI.test.ts +28 -0
- package/BunkerURI.ts +58 -0
- package/CHANGELOG.md +562 -0
- package/LICENSE +21 -0
- package/NBrowserSigner.test.ts +170 -0
- package/NBrowserSigner.ts +100 -0
- package/NCache.bench.ts +81 -0
- package/NCache.test.ts +22 -0
- package/NCache.ts +73 -0
- package/NConnectSigner.test.ts +102 -0
- package/NConnectSigner.ts +189 -0
- package/NIP05.test.ts +67 -0
- package/NIP05.ts +52 -0
- package/NIP50.test.ts +58 -0
- package/NIP50.ts +24 -0
- package/NIP98.test.ts +181 -0
- package/NIP98.ts +97 -0
- package/NKinds.test.ts +42 -0
- package/NKinds.ts +26 -0
- package/NPool.test.ts +117 -0
- package/NPool.ts +224 -0
- package/NRelay1.test.ts +174 -0
- package/NRelay1.ts +440 -0
- package/NSchema.test.ts +94 -0
- package/NSchema.ts +255 -0
- package/NSecSigner.bench.ts +55 -0
- package/NSecSigner.test.ts +26 -0
- package/NSecSigner.ts +60 -0
- package/NSet.bench.ts +10 -0
- package/NSet.test.ts +92 -0
- package/NSet.ts +203 -0
- package/README.md +314 -0
- package/RelayError.test.ts +23 -0
- package/RelayError.ts +22 -0
- package/dist/BunkerURI.d.ts +23 -0
- package/dist/BunkerURI.d.ts.map +1 -0
- package/dist/BunkerURI.js +52 -0
- package/dist/BunkerURI.js.map +1 -0
- package/dist/NBrowserSigner.d.ts +27 -0
- package/dist/NBrowserSigner.d.ts.map +1 -0
- package/dist/NBrowserSigner.js +96 -0
- package/dist/NBrowserSigner.js.map +1 -0
- package/dist/NCache.d.ts +34 -0
- package/dist/NCache.d.ts.map +1 -0
- package/dist/NCache.js +63 -0
- package/dist/NCache.js.map +1 -0
- package/dist/NConnectSigner.d.ts +50 -0
- package/dist/NConnectSigner.d.ts.map +1 -0
- package/dist/NConnectSigner.js +130 -0
- package/dist/NConnectSigner.js.map +1 -0
- package/dist/NIP05.d.ts +15 -0
- package/dist/NIP05.d.ts.map +1 -0
- package/dist/NIP05.js +40 -0
- package/dist/NIP05.js.map +1 -0
- package/dist/NIP50.d.ts +10 -0
- package/dist/NIP50.d.ts.map +1 -0
- package/dist/NIP50.js +26 -0
- package/dist/NIP50.js.map +1 -0
- package/dist/NIP98.d.ts +15 -0
- package/dist/NIP98.d.ts.map +1 -0
- package/dist/NIP98.js +71 -0
- package/dist/NIP98.js.map +1 -0
- package/dist/NKinds.d.ts +13 -0
- package/dist/NKinds.d.ts.map +1 -0
- package/dist/NKinds.js +27 -0
- package/dist/NKinds.js.map +1 -0
- package/dist/NPool.d.ts +91 -0
- package/dist/NPool.d.ts.map +1 -0
- package/dist/NPool.js +185 -0
- package/dist/NPool.js.map +1 -0
- package/dist/NRelay1.d.ts +80 -0
- package/dist/NRelay1.d.ts.map +1 -0
- package/dist/NRelay1.js +336 -0
- package/dist/NRelay1.js.map +1 -0
- package/dist/NSchema.d.ts +73 -0
- package/dist/NSchema.d.ts.map +1 -0
- package/dist/NSchema.js +215 -0
- package/dist/NSchema.js.map +1 -0
- package/dist/NSecSigner.d.ts +29 -0
- package/dist/NSecSigner.d.ts.map +1 -0
- package/dist/NSecSigner.js +52 -0
- package/dist/NSecSigner.js.map +1 -0
- package/dist/NSet.d.ts +76 -0
- package/dist/NSet.d.ts.map +1 -0
- package/dist/NSet.js +174 -0
- package/dist/NSet.js.map +1 -0
- package/dist/RelayError.d.ts +10 -0
- package/dist/RelayError.d.ts.map +1 -0
- package/dist/RelayError.js +23 -0
- package/dist/RelayError.js.map +1 -0
- package/dist/ln/LNURL.d.ts +55 -0
- package/dist/ln/LNURL.d.ts.map +1 -0
- package/dist/ln/LNURL.js +105 -0
- package/dist/ln/LNURL.js.map +1 -0
- package/dist/ln/mod.d.ts +4 -0
- package/dist/ln/mod.d.ts.map +1 -0
- package/dist/ln/mod.js +6 -0
- package/dist/ln/mod.js.map +1 -0
- package/dist/ln/types/LNURLCallback.d.ts +8 -0
- package/dist/ln/types/LNURLCallback.d.ts.map +1 -0
- package/dist/ln/types/LNURLCallback.js +3 -0
- package/dist/ln/types/LNURLCallback.js.map +1 -0
- package/dist/ln/types/LNURLDetails.d.ts +20 -0
- package/dist/ln/types/LNURLDetails.d.ts.map +1 -0
- package/dist/ln/types/LNURLDetails.js +3 -0
- package/dist/ln/types/LNURLDetails.js.map +1 -0
- package/dist/mod.d.ts +16 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +32 -0
- package/dist/mod.js.map +1 -0
- package/dist/test/ErrorRelay.d.ts +21 -0
- package/dist/test/ErrorRelay.d.ts.map +1 -0
- package/dist/test/ErrorRelay.js +26 -0
- package/dist/test/ErrorRelay.js.map +1 -0
- package/dist/test/MockRelay.d.ts +20 -0
- package/dist/test/MockRelay.d.ts.map +1 -0
- package/dist/test/MockRelay.js +66 -0
- package/dist/test/MockRelay.js.map +1 -0
- package/dist/test/TestRelayServer.d.ts +25 -0
- package/dist/test/TestRelayServer.d.ts.map +1 -0
- package/dist/test/TestRelayServer.js +134 -0
- package/dist/test/TestRelayServer.js.map +1 -0
- package/dist/test/mod.d.ts +8 -0
- package/dist/test/mod.d.ts.map +1 -0
- package/dist/test/mod.js +28 -0
- package/dist/test/mod.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/uploaders/BlossomUploader.d.ts +26 -0
- package/dist/uploaders/BlossomUploader.d.ts.map +1 -0
- package/dist/uploaders/BlossomUploader.js +71 -0
- package/dist/uploaders/BlossomUploader.js.map +1 -0
- package/dist/uploaders/NostrBuildUploader.d.ts +24 -0
- package/dist/uploaders/NostrBuildUploader.d.ts.map +1 -0
- package/dist/uploaders/NostrBuildUploader.js +67 -0
- package/dist/uploaders/NostrBuildUploader.js.map +1 -0
- package/dist/uploaders/mod.d.ts +3 -0
- package/dist/uploaders/mod.d.ts.map +1 -0
- package/dist/uploaders/mod.js +8 -0
- package/dist/uploaders/mod.js.map +1 -0
- package/dist/utils/CircularSet.d.ts +13 -0
- package/dist/utils/CircularSet.d.ts.map +1 -0
- package/dist/utils/CircularSet.js +35 -0
- package/dist/utils/CircularSet.js.map +1 -0
- package/dist/utils/Machina.d.ts +36 -0
- package/dist/utils/Machina.d.ts.map +1 -0
- package/dist/utils/Machina.js +66 -0
- package/dist/utils/Machina.js.map +1 -0
- package/dist/utils/N64.d.ts +9 -0
- package/dist/utils/N64.d.ts.map +1 -0
- package/dist/utils/N64.js +23 -0
- package/dist/utils/N64.js.map +1 -0
- package/dist/utils/mod.d.ts +3 -0
- package/dist/utils/mod.d.ts.map +1 -0
- package/dist/utils/mod.js +8 -0
- package/dist/utils/mod.js.map +1 -0
- package/ln/LNURL.test.ts +87 -0
- package/ln/LNURL.ts +146 -0
- package/ln/mod.ts +4 -0
- package/ln/types/LNURLCallback.ts +7 -0
- package/ln/types/LNURLDetails.ts +19 -0
- package/mod.ts +16 -0
- package/package.json +23 -0
- package/test/ErrorRelay.test.ts +19 -0
- package/test/ErrorRelay.ts +40 -0
- package/test/MockRelay.test.ts +20 -0
- package/test/MockRelay.ts +92 -0
- package/test/TestRelayServer.ts +156 -0
- package/test/mod.ts +28 -0
- package/tsconfig.json +14 -0
- package/uploaders/BlossomUploader.test.ts +26 -0
- package/uploaders/BlossomUploader.ts +98 -0
- package/uploaders/NostrBuildUploader.test.ts +22 -0
- package/uploaders/NostrBuildUploader.ts +89 -0
- package/uploaders/mod.ts +2 -0
- package/utils/CircularSet.test.ts +15 -0
- package/utils/CircularSet.ts +34 -0
- package/utils/Machina.test.ts +91 -0
- package/utils/Machina.ts +66 -0
- package/utils/N64.test.ts +27 -0
- package/utils/N64.ts +23 -0
- package/utils/mod.ts +2 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { NSecSigner } from '@nostrify/nostrify';
|
|
2
|
+
import { assertEquals, assertRejects } from '@std/assert';
|
|
3
|
+
import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools';
|
|
4
|
+
|
|
5
|
+
import { NBrowserSigner } from './NBrowserSigner.ts';
|
|
6
|
+
|
|
7
|
+
import type { NostrEvent, NostrSigner } from '@nostrify/types';
|
|
8
|
+
|
|
9
|
+
Deno.test('NBrowserSigner - without extension', async () => {
|
|
10
|
+
// Ensure no extension is available
|
|
11
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
12
|
+
|
|
13
|
+
const signer = new NBrowserSigner();
|
|
14
|
+
|
|
15
|
+
await assertRejects(
|
|
16
|
+
() => signer.getPublicKey(),
|
|
17
|
+
Error,
|
|
18
|
+
'Browser extension not available',
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
await assertRejects(
|
|
22
|
+
() => signer.signEvent({ kind: 1, content: 'Hello, world!', tags: [], created_at: 0 }),
|
|
23
|
+
Error,
|
|
24
|
+
'Browser extension not available',
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
Deno.test('NBrowserSigner - with extension polyfill', async () => {
|
|
29
|
+
const secretKey = generateSecretKey();
|
|
30
|
+
const mockExtension = new NSecSigner(secretKey);
|
|
31
|
+
|
|
32
|
+
// Set up the polyfill
|
|
33
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
34
|
+
|
|
35
|
+
const signer = new NBrowserSigner();
|
|
36
|
+
|
|
37
|
+
// Test getPublicKey
|
|
38
|
+
assertEquals(await signer.getPublicKey(), getPublicKey(secretKey));
|
|
39
|
+
|
|
40
|
+
// Test signEvent
|
|
41
|
+
const template = { kind: 1, content: 'Hello, world!', tags: [], created_at: 0 };
|
|
42
|
+
assertEquals(await signer.signEvent(template), finalizeEvent(template, secretKey));
|
|
43
|
+
|
|
44
|
+
// Clean up
|
|
45
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Deno.test('NBrowserSigner.nip44 - with extension polyfill', async () => {
|
|
49
|
+
const secretKey = generateSecretKey();
|
|
50
|
+
const mockExtension = new NSecSigner(secretKey);
|
|
51
|
+
|
|
52
|
+
// Set up the polyfill
|
|
53
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
54
|
+
|
|
55
|
+
const signer = new NBrowserSigner();
|
|
56
|
+
|
|
57
|
+
const pubkey = await signer.getPublicKey();
|
|
58
|
+
const plaintext = 'Hello, world!';
|
|
59
|
+
|
|
60
|
+
const ciphertext = await signer.nip44!.encrypt(pubkey, plaintext);
|
|
61
|
+
assertEquals(await signer.nip44!.decrypt(pubkey, ciphertext), plaintext);
|
|
62
|
+
|
|
63
|
+
// Clean up
|
|
64
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
Deno.test('NBrowserSigner.nip04 - with extension polyfill', async () => {
|
|
68
|
+
const secretKey = generateSecretKey();
|
|
69
|
+
const mockExtension = new NSecSigner(secretKey);
|
|
70
|
+
|
|
71
|
+
// Set up the polyfill
|
|
72
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
73
|
+
|
|
74
|
+
const signer = new NBrowserSigner();
|
|
75
|
+
|
|
76
|
+
const pubkey = await signer.getPublicKey();
|
|
77
|
+
const plaintext = 'Hello, world!';
|
|
78
|
+
|
|
79
|
+
const ciphertext = await signer.nip04!.encrypt(pubkey, plaintext);
|
|
80
|
+
assertEquals(await signer.nip04!.decrypt(pubkey, ciphertext), plaintext);
|
|
81
|
+
|
|
82
|
+
// Clean up
|
|
83
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
Deno.test('NBrowserSigner.getRelays - with extension polyfill', async () => {
|
|
87
|
+
const secretKey = generateSecretKey();
|
|
88
|
+
const mockExtension = new NSecSigner(secretKey);
|
|
89
|
+
|
|
90
|
+
// Set up the polyfill
|
|
91
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
92
|
+
|
|
93
|
+
const signer = new NBrowserSigner();
|
|
94
|
+
|
|
95
|
+
// Since NSecSigner doesn't implement getRelays, this should return empty object
|
|
96
|
+
const relays = await signer.getRelays();
|
|
97
|
+
assertEquals(relays, {});
|
|
98
|
+
|
|
99
|
+
// Clean up
|
|
100
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
Deno.test('NBrowserSigner - missing nip44 support', () => {
|
|
104
|
+
// Create a mock extension without nip44 support
|
|
105
|
+
const mockExtension = {
|
|
106
|
+
// deno-lint-ignore require-await
|
|
107
|
+
getPublicKey: async () => 'pubkey',
|
|
108
|
+
// deno-lint-ignore require-await
|
|
109
|
+
signEvent: async (event: NostrEvent) => event,
|
|
110
|
+
// No nip44 property
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
114
|
+
|
|
115
|
+
const signer = new NBrowserSigner();
|
|
116
|
+
|
|
117
|
+
// Should return undefined when nip44 is not supported
|
|
118
|
+
assertEquals(signer.nip44, undefined);
|
|
119
|
+
|
|
120
|
+
// Clean up
|
|
121
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
Deno.test('NBrowserSigner - missing nip04 support', () => {
|
|
125
|
+
// Create a mock extension without nip04 support
|
|
126
|
+
const mockExtension = {
|
|
127
|
+
// deno-lint-ignore require-await
|
|
128
|
+
getPublicKey: async () => 'pubkey',
|
|
129
|
+
// deno-lint-ignore require-await
|
|
130
|
+
signEvent: async (event: NostrEvent) => event,
|
|
131
|
+
// No nip04 property
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
135
|
+
|
|
136
|
+
const signer = new NBrowserSigner();
|
|
137
|
+
|
|
138
|
+
// Should return undefined when nip04 is not supported
|
|
139
|
+
assertEquals(signer.nip04, undefined);
|
|
140
|
+
|
|
141
|
+
// Clean up
|
|
142
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
Deno.test('NBrowserSigner - feature detection', () => {
|
|
146
|
+
const secretKey = generateSecretKey();
|
|
147
|
+
const mockExtension = new NSecSigner(secretKey);
|
|
148
|
+
|
|
149
|
+
// Set up the polyfill
|
|
150
|
+
(globalThis as { nostr?: NostrSigner }).nostr = mockExtension;
|
|
151
|
+
|
|
152
|
+
const signer = new NBrowserSigner();
|
|
153
|
+
|
|
154
|
+
// Should be able to detect nip44 support
|
|
155
|
+
if (signer.nip44) {
|
|
156
|
+
// This should work since NSecSigner supports nip44
|
|
157
|
+
assertEquals(typeof signer.nip44.encrypt, 'function');
|
|
158
|
+
assertEquals(typeof signer.nip44.decrypt, 'function');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Should be able to detect nip04 support
|
|
162
|
+
if (signer.nip04) {
|
|
163
|
+
// This should work since NSecSigner supports nip04
|
|
164
|
+
assertEquals(typeof signer.nip04.encrypt, 'function');
|
|
165
|
+
assertEquals(typeof signer.nip04.decrypt, 'function');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Clean up
|
|
169
|
+
(globalThis as { nostr?: NostrSigner }).nostr = undefined;
|
|
170
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { NostrEvent, NostrSigner } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NIP-07-compatible signer that proxies to browser extension, normalizing behavior across different implementations.
|
|
5
|
+
*
|
|
6
|
+
* This signer delegates all operations to the browser's `window.nostr` object,
|
|
7
|
+
* which is typically provided by browser extensions like Alby, nos2x, etc.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const signer = new NBrowserSigner();
|
|
13
|
+
* const pubkey = await signer.getPublicKey();
|
|
14
|
+
* const event = await signer.signEvent({ kind: 1, content: 'Hello, world!', tags: [], created_at: 0 });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class NBrowserSigner implements NostrSigner {
|
|
18
|
+
private get nostr(): NostrSigner {
|
|
19
|
+
const nostr = (globalThis as { nostr?: NostrSigner }).nostr;
|
|
20
|
+
if (!nostr) {
|
|
21
|
+
throw new Error('Browser extension not available');
|
|
22
|
+
}
|
|
23
|
+
return nostr;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getPublicKey(): Promise<string> {
|
|
27
|
+
const pubkey = await this.nostr.getPublicKey();
|
|
28
|
+
if (typeof pubkey !== 'string') {
|
|
29
|
+
throw new Error(`Nostr public key retrieval failed: expected string, got ${JSON.stringify(pubkey)}`);
|
|
30
|
+
}
|
|
31
|
+
return pubkey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async signEvent(event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<NostrEvent> {
|
|
35
|
+
const signed = await this.nostr.signEvent(event);
|
|
36
|
+
if (typeof signed !== 'object' || !signed.id || !signed.pubkey || !signed.sig) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Nostr event signing failed: expected object with id, pubkey, and sig, got ${JSON.stringify(signed)}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return signed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getRelays(): Promise<Record<string, { read: boolean; write: boolean }>> {
|
|
45
|
+
if (!this.nostr.getRelays) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const relays = await this.nostr.getRelays();
|
|
49
|
+
if (typeof relays !== 'object' || relays === null) {
|
|
50
|
+
throw new Error(`Nostr getRelays failed: expected object, got ${JSON.stringify(relays)}`);
|
|
51
|
+
}
|
|
52
|
+
return this.nostr.getRelays();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get nip04(): NostrSigner['nip04'] {
|
|
56
|
+
const nostr = this.nostr;
|
|
57
|
+
if (!nostr.nip04) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
|
62
|
+
const encrypted = await nostr.nip04!.encrypt(pubkey, plaintext);
|
|
63
|
+
if (typeof encrypted !== 'string') {
|
|
64
|
+
throw new Error(`NIP-04 encryption failed: expected string result, got ${JSON.stringify(encrypted)}`);
|
|
65
|
+
}
|
|
66
|
+
return encrypted;
|
|
67
|
+
},
|
|
68
|
+
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
|
69
|
+
const decrypted = await nostr.nip04!.decrypt(pubkey, ciphertext);
|
|
70
|
+
if (typeof decrypted !== 'string') {
|
|
71
|
+
throw new Error(`NIP-04 decryption failed: expected string result, got ${JSON.stringify(decrypted)}`);
|
|
72
|
+
}
|
|
73
|
+
return decrypted;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get nip44(): NostrSigner['nip44'] {
|
|
79
|
+
const nostr = this.nostr;
|
|
80
|
+
if (!nostr.nip44) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
|
85
|
+
const encrypted = await nostr.nip44!.encrypt(pubkey, plaintext);
|
|
86
|
+
if (typeof encrypted !== 'string') {
|
|
87
|
+
throw new Error(`NIP-44 encryption failed: expected string result, got ${JSON.stringify(encrypted)}`);
|
|
88
|
+
}
|
|
89
|
+
return encrypted;
|
|
90
|
+
},
|
|
91
|
+
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
|
92
|
+
const decrypted = await nostr.nip44!.decrypt(pubkey, ciphertext);
|
|
93
|
+
if (typeof decrypted !== 'string') {
|
|
94
|
+
throw new Error(`NIP-44 decryption failed: expected string result, got ${JSON.stringify(decrypted)}`);
|
|
95
|
+
}
|
|
96
|
+
return decrypted;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
package/NCache.bench.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
|
2
|
+
|
|
3
|
+
import { NCache } from './NCache.ts';
|
|
4
|
+
|
|
5
|
+
import events from '../../fixtures/events.json' with { type: 'json' };
|
|
6
|
+
|
|
7
|
+
const cache = new NCache({ max: 5000 });
|
|
8
|
+
|
|
9
|
+
// Seed cache with 1000 events.
|
|
10
|
+
for (const event of events) {
|
|
11
|
+
await cache.event(event);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Deno.bench('NCache.event', async (b) => {
|
|
15
|
+
const secretKey = generateSecretKey();
|
|
16
|
+
const event = finalizeEvent({
|
|
17
|
+
kind: 1,
|
|
18
|
+
content: 'hello world!',
|
|
19
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
20
|
+
tags: [['t', 'test']],
|
|
21
|
+
}, secretKey);
|
|
22
|
+
|
|
23
|
+
b.start();
|
|
24
|
+
|
|
25
|
+
await cache.event(event);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
Deno.bench('NCache.event with many tags', async (b) => {
|
|
29
|
+
const secretKey = generateSecretKey();
|
|
30
|
+
|
|
31
|
+
const tags: string[][] = new Array(300)
|
|
32
|
+
.fill('')
|
|
33
|
+
.map(() => ['p', '570a9c85c7dd56eca0d8c7f258d7fc178f1b2bb3aab4136ba674dc4879eee88a']);
|
|
34
|
+
|
|
35
|
+
const event = finalizeEvent({
|
|
36
|
+
kind: 1,
|
|
37
|
+
content: 'hello world!',
|
|
38
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
39
|
+
tags,
|
|
40
|
+
}, secretKey);
|
|
41
|
+
|
|
42
|
+
b.start();
|
|
43
|
+
|
|
44
|
+
await cache.event(event);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
Deno.bench('NCache.query by id', async () => {
|
|
48
|
+
await cache.query([{ ids: ['119abcfcebf253a6b1af1a03e2ff1c05798c2f46cadfa2efc98eaef686095292'], limit: 1 }]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Deno.bench('NCache.query by multiple ids', async () => {
|
|
52
|
+
await cache.query([{
|
|
53
|
+
ids: [
|
|
54
|
+
'119abcfcebf253a6b1af1a03e2ff1c05798c2f46cadfa2efc98eaef686095292',
|
|
55
|
+
'a9d877196e64eec8645c9c28a1051f3cdde94b6272c0769517f47cfae518ea0c',
|
|
56
|
+
],
|
|
57
|
+
limit: 20,
|
|
58
|
+
}]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
Deno.bench('NCache.query by kind', async () => {
|
|
62
|
+
await cache.query([{ kinds: [1], limit: 20 }]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
Deno.bench('NCache.query by multiple kinds', async () => {
|
|
66
|
+
await cache.query([{ kinds: [6, 7], limit: 20 }]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
Deno.bench('NCache.query by author', async () => {
|
|
70
|
+
await cache.query([{ authors: ['753d025936c8c3238b1b2b2f748be6df92743c2201e5198946e9d6a29156793f'], limit: 20 }]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
Deno.bench('NCache.query by multiple authors', async () => {
|
|
74
|
+
await cache.query([{
|
|
75
|
+
authors: [
|
|
76
|
+
'753d025936c8c3238b1b2b2f748be6df92743c2201e5198946e9d6a29156793f',
|
|
77
|
+
'79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6',
|
|
78
|
+
],
|
|
79
|
+
limit: 20,
|
|
80
|
+
}]);
|
|
81
|
+
});
|
package/NCache.test.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { assertEquals } from '@std/assert';
|
|
2
|
+
|
|
3
|
+
import { NCache } from './NCache.ts';
|
|
4
|
+
|
|
5
|
+
import event1 from '../../fixtures/event-1.json' with { type: 'json' };
|
|
6
|
+
|
|
7
|
+
Deno.test('NCache', async () => {
|
|
8
|
+
const cache = new NCache({
|
|
9
|
+
max: 3000,
|
|
10
|
+
maxEntrySize: 5000,
|
|
11
|
+
sizeCalculation: (event) => JSON.stringify(event).length,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
assertEquals(await cache.count([{ ids: [event1.id] }]), { count: 0, approximate: false });
|
|
15
|
+
|
|
16
|
+
await cache.event(event1);
|
|
17
|
+
|
|
18
|
+
assertEquals(await cache.count([{ ids: [event1.id] }]), { count: 1, approximate: false });
|
|
19
|
+
|
|
20
|
+
const result = await cache.query([{ ids: [event1.id] }]);
|
|
21
|
+
assertEquals(result[0], event1);
|
|
22
|
+
});
|
package/NCache.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// deno-lint-ignore-file require-await
|
|
2
|
+
|
|
3
|
+
import { NostrEvent, NostrFilter, NostrRelayCOUNT, NStore } from '@nostrify/types';
|
|
4
|
+
import { LRUCache } from 'lru-cache';
|
|
5
|
+
import { matchFilters } from 'nostr-tools';
|
|
6
|
+
|
|
7
|
+
import { NSet } from './NSet';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Nostr LRU cache based on [`npm:lru-cache`](https://www.npmjs.com/package/lru-cache).
|
|
11
|
+
* It implements both `NStore` and `NSet` interfaces.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Accepts the options of `npm:lru-cache`:
|
|
15
|
+
* const cache = new NCache({ max: 1000 });
|
|
16
|
+
*
|
|
17
|
+
* // Events can be added like a regular `Set`:
|
|
18
|
+
* cache.add(event1);
|
|
19
|
+
* cache.add(event2);
|
|
20
|
+
*
|
|
21
|
+
* // Can be queried like `NStore`:
|
|
22
|
+
* const events = await cache.query([{ kinds: [1] }]);
|
|
23
|
+
*
|
|
24
|
+
* // Can be iterated like `NSet`:
|
|
25
|
+
* for (const event of cache) {
|
|
26
|
+
* console.log(event);
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
class NCache extends NSet implements NStore {
|
|
31
|
+
constructor(
|
|
32
|
+
...args: ConstructorParameters<typeof LRUCache<string, NostrEvent>>
|
|
33
|
+
) {
|
|
34
|
+
super(new LRUCache<string, NostrEvent>(...args) as Map<string, NostrEvent>);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async event(event: NostrEvent): Promise<void> {
|
|
38
|
+
this.add(event);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async query(filters: NostrFilter[]): Promise<NostrEvent[]> {
|
|
42
|
+
const events: NostrEvent[] = [];
|
|
43
|
+
|
|
44
|
+
for (const event of this) {
|
|
45
|
+
if (matchFilters(filters, event)) {
|
|
46
|
+
this.cache.get(event.id);
|
|
47
|
+
events.push(event);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return events;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async remove(filters: NostrFilter[]): Promise<void> {
|
|
55
|
+
for (const event of this) {
|
|
56
|
+
if (matchFilters(filters, event)) {
|
|
57
|
+
this.delete(event);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async count(filters: NostrFilter[]): Promise<NostrRelayCOUNT[2]> {
|
|
63
|
+
const events = await this.query(filters);
|
|
64
|
+
return {
|
|
65
|
+
count: events.length,
|
|
66
|
+
approximate: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override [Symbol.toStringTag] = 'NCache';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { NCache };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { NostrConnectResponse } from '@nostrify/types';
|
|
2
|
+
import { assert, assertEquals } from '@std/assert';
|
|
3
|
+
import { generateSecretKey, verifyEvent } from 'nostr-tools';
|
|
4
|
+
|
|
5
|
+
import { MockRelay } from './test/MockRelay.ts';
|
|
6
|
+
import { NConnectSigner } from './NConnectSigner.ts';
|
|
7
|
+
import { NSchema as n } from './NSchema.ts';
|
|
8
|
+
import { NSecSigner } from './NSecSigner.ts';
|
|
9
|
+
|
|
10
|
+
Deno.test('NConnectSigner.signEvent with nip04 encryption', async () => {
|
|
11
|
+
const relay = new MockRelay();
|
|
12
|
+
const remote = new NSecSigner(generateSecretKey());
|
|
13
|
+
const pubkey = await remote.getPublicKey();
|
|
14
|
+
|
|
15
|
+
const connect = new NConnectSigner({
|
|
16
|
+
relay,
|
|
17
|
+
pubkey,
|
|
18
|
+
signer: new NSecSigner(generateSecretKey()),
|
|
19
|
+
encryption: 'nip04',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const req = relay.req([{ kinds: [24133], '#p': [pubkey] }]);
|
|
23
|
+
|
|
24
|
+
const promise = connect.signEvent({
|
|
25
|
+
kind: 1,
|
|
26
|
+
content: 'hello world',
|
|
27
|
+
tags: [],
|
|
28
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
for await (const msg of req) {
|
|
32
|
+
if (msg[0] === 'EVENT') {
|
|
33
|
+
const event = msg[2];
|
|
34
|
+
const decrypted = await remote.nip04!.decrypt(event.pubkey, event.content);
|
|
35
|
+
const request = n.json().pipe(n.connectRequest()).parse(decrypted);
|
|
36
|
+
assertEquals(request.method, 'sign_event');
|
|
37
|
+
const response: NostrConnectResponse = {
|
|
38
|
+
id: request.id,
|
|
39
|
+
result: JSON.stringify(await remote.signEvent(JSON.parse(request.params[0]))),
|
|
40
|
+
};
|
|
41
|
+
await relay.event(
|
|
42
|
+
await remote.signEvent({
|
|
43
|
+
kind: 24133,
|
|
44
|
+
content: await remote.nip04!.encrypt(event.pubkey, JSON.stringify(response)),
|
|
45
|
+
tags: [['p', event.pubkey]],
|
|
46
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
assert(verifyEvent(await promise));
|
|
54
|
+
assertEquals(relay.subs.size, 0); // cleanup
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
Deno.test('NConnectSigner.signEvent with nip44 encryption', async () => {
|
|
58
|
+
const relay = new MockRelay();
|
|
59
|
+
const remote = new NSecSigner(generateSecretKey());
|
|
60
|
+
const pubkey = await remote.getPublicKey();
|
|
61
|
+
|
|
62
|
+
const connect = new NConnectSigner({
|
|
63
|
+
relay,
|
|
64
|
+
pubkey,
|
|
65
|
+
signer: new NSecSigner(generateSecretKey()),
|
|
66
|
+
encryption: 'nip44',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const req = relay.req([{ kinds: [24133], '#p': [pubkey] }]);
|
|
70
|
+
|
|
71
|
+
const promise = connect.signEvent({
|
|
72
|
+
kind: 1,
|
|
73
|
+
content: 'hello world',
|
|
74
|
+
tags: [],
|
|
75
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for await (const msg of req) {
|
|
79
|
+
if (msg[0] === 'EVENT') {
|
|
80
|
+
const event = msg[2];
|
|
81
|
+
const decrypted = await remote.nip44!.decrypt(event.pubkey, event.content);
|
|
82
|
+
const request = n.json().pipe(n.connectRequest()).parse(decrypted);
|
|
83
|
+
assertEquals(request.method, 'sign_event');
|
|
84
|
+
const response: NostrConnectResponse = {
|
|
85
|
+
id: request.id,
|
|
86
|
+
result: JSON.stringify(await remote.signEvent(JSON.parse(request.params[0]))),
|
|
87
|
+
};
|
|
88
|
+
await relay.event(
|
|
89
|
+
await remote.signEvent({
|
|
90
|
+
kind: 24133,
|
|
91
|
+
content: await remote.nip44!.encrypt(event.pubkey, JSON.stringify(response)),
|
|
92
|
+
tags: [['p', event.pubkey]],
|
|
93
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
assert(verifyEvent(await promise));
|
|
101
|
+
assertEquals(relay.subs.size, 0); // cleanup
|
|
102
|
+
});
|