@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.
Files changed (182) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/BunkerURI.test.ts +28 -0
  3. package/BunkerURI.ts +58 -0
  4. package/CHANGELOG.md +562 -0
  5. package/LICENSE +21 -0
  6. package/NBrowserSigner.test.ts +170 -0
  7. package/NBrowserSigner.ts +100 -0
  8. package/NCache.bench.ts +81 -0
  9. package/NCache.test.ts +22 -0
  10. package/NCache.ts +73 -0
  11. package/NConnectSigner.test.ts +102 -0
  12. package/NConnectSigner.ts +189 -0
  13. package/NIP05.test.ts +67 -0
  14. package/NIP05.ts +52 -0
  15. package/NIP50.test.ts +58 -0
  16. package/NIP50.ts +24 -0
  17. package/NIP98.test.ts +181 -0
  18. package/NIP98.ts +97 -0
  19. package/NKinds.test.ts +42 -0
  20. package/NKinds.ts +26 -0
  21. package/NPool.test.ts +117 -0
  22. package/NPool.ts +224 -0
  23. package/NRelay1.test.ts +174 -0
  24. package/NRelay1.ts +440 -0
  25. package/NSchema.test.ts +94 -0
  26. package/NSchema.ts +255 -0
  27. package/NSecSigner.bench.ts +55 -0
  28. package/NSecSigner.test.ts +26 -0
  29. package/NSecSigner.ts +60 -0
  30. package/NSet.bench.ts +10 -0
  31. package/NSet.test.ts +92 -0
  32. package/NSet.ts +203 -0
  33. package/README.md +314 -0
  34. package/RelayError.test.ts +23 -0
  35. package/RelayError.ts +22 -0
  36. package/dist/BunkerURI.d.ts +23 -0
  37. package/dist/BunkerURI.d.ts.map +1 -0
  38. package/dist/BunkerURI.js +52 -0
  39. package/dist/BunkerURI.js.map +1 -0
  40. package/dist/NBrowserSigner.d.ts +27 -0
  41. package/dist/NBrowserSigner.d.ts.map +1 -0
  42. package/dist/NBrowserSigner.js +96 -0
  43. package/dist/NBrowserSigner.js.map +1 -0
  44. package/dist/NCache.d.ts +34 -0
  45. package/dist/NCache.d.ts.map +1 -0
  46. package/dist/NCache.js +63 -0
  47. package/dist/NCache.js.map +1 -0
  48. package/dist/NConnectSigner.d.ts +50 -0
  49. package/dist/NConnectSigner.d.ts.map +1 -0
  50. package/dist/NConnectSigner.js +130 -0
  51. package/dist/NConnectSigner.js.map +1 -0
  52. package/dist/NIP05.d.ts +15 -0
  53. package/dist/NIP05.d.ts.map +1 -0
  54. package/dist/NIP05.js +40 -0
  55. package/dist/NIP05.js.map +1 -0
  56. package/dist/NIP50.d.ts +10 -0
  57. package/dist/NIP50.d.ts.map +1 -0
  58. package/dist/NIP50.js +26 -0
  59. package/dist/NIP50.js.map +1 -0
  60. package/dist/NIP98.d.ts +15 -0
  61. package/dist/NIP98.d.ts.map +1 -0
  62. package/dist/NIP98.js +71 -0
  63. package/dist/NIP98.js.map +1 -0
  64. package/dist/NKinds.d.ts +13 -0
  65. package/dist/NKinds.d.ts.map +1 -0
  66. package/dist/NKinds.js +27 -0
  67. package/dist/NKinds.js.map +1 -0
  68. package/dist/NPool.d.ts +91 -0
  69. package/dist/NPool.d.ts.map +1 -0
  70. package/dist/NPool.js +185 -0
  71. package/dist/NPool.js.map +1 -0
  72. package/dist/NRelay1.d.ts +80 -0
  73. package/dist/NRelay1.d.ts.map +1 -0
  74. package/dist/NRelay1.js +336 -0
  75. package/dist/NRelay1.js.map +1 -0
  76. package/dist/NSchema.d.ts +73 -0
  77. package/dist/NSchema.d.ts.map +1 -0
  78. package/dist/NSchema.js +215 -0
  79. package/dist/NSchema.js.map +1 -0
  80. package/dist/NSecSigner.d.ts +29 -0
  81. package/dist/NSecSigner.d.ts.map +1 -0
  82. package/dist/NSecSigner.js +52 -0
  83. package/dist/NSecSigner.js.map +1 -0
  84. package/dist/NSet.d.ts +76 -0
  85. package/dist/NSet.d.ts.map +1 -0
  86. package/dist/NSet.js +174 -0
  87. package/dist/NSet.js.map +1 -0
  88. package/dist/RelayError.d.ts +10 -0
  89. package/dist/RelayError.d.ts.map +1 -0
  90. package/dist/RelayError.js +23 -0
  91. package/dist/RelayError.js.map +1 -0
  92. package/dist/ln/LNURL.d.ts +55 -0
  93. package/dist/ln/LNURL.d.ts.map +1 -0
  94. package/dist/ln/LNURL.js +105 -0
  95. package/dist/ln/LNURL.js.map +1 -0
  96. package/dist/ln/mod.d.ts +4 -0
  97. package/dist/ln/mod.d.ts.map +1 -0
  98. package/dist/ln/mod.js +6 -0
  99. package/dist/ln/mod.js.map +1 -0
  100. package/dist/ln/types/LNURLCallback.d.ts +8 -0
  101. package/dist/ln/types/LNURLCallback.d.ts.map +1 -0
  102. package/dist/ln/types/LNURLCallback.js +3 -0
  103. package/dist/ln/types/LNURLCallback.js.map +1 -0
  104. package/dist/ln/types/LNURLDetails.d.ts +20 -0
  105. package/dist/ln/types/LNURLDetails.d.ts.map +1 -0
  106. package/dist/ln/types/LNURLDetails.js +3 -0
  107. package/dist/ln/types/LNURLDetails.js.map +1 -0
  108. package/dist/mod.d.ts +16 -0
  109. package/dist/mod.d.ts.map +1 -0
  110. package/dist/mod.js +32 -0
  111. package/dist/mod.js.map +1 -0
  112. package/dist/test/ErrorRelay.d.ts +21 -0
  113. package/dist/test/ErrorRelay.d.ts.map +1 -0
  114. package/dist/test/ErrorRelay.js +26 -0
  115. package/dist/test/ErrorRelay.js.map +1 -0
  116. package/dist/test/MockRelay.d.ts +20 -0
  117. package/dist/test/MockRelay.d.ts.map +1 -0
  118. package/dist/test/MockRelay.js +66 -0
  119. package/dist/test/MockRelay.js.map +1 -0
  120. package/dist/test/TestRelayServer.d.ts +25 -0
  121. package/dist/test/TestRelayServer.d.ts.map +1 -0
  122. package/dist/test/TestRelayServer.js +134 -0
  123. package/dist/test/TestRelayServer.js.map +1 -0
  124. package/dist/test/mod.d.ts +8 -0
  125. package/dist/test/mod.d.ts.map +1 -0
  126. package/dist/test/mod.js +28 -0
  127. package/dist/test/mod.js.map +1 -0
  128. package/dist/tsconfig.tsbuildinfo +1 -0
  129. package/dist/uploaders/BlossomUploader.d.ts +26 -0
  130. package/dist/uploaders/BlossomUploader.d.ts.map +1 -0
  131. package/dist/uploaders/BlossomUploader.js +71 -0
  132. package/dist/uploaders/BlossomUploader.js.map +1 -0
  133. package/dist/uploaders/NostrBuildUploader.d.ts +24 -0
  134. package/dist/uploaders/NostrBuildUploader.d.ts.map +1 -0
  135. package/dist/uploaders/NostrBuildUploader.js +67 -0
  136. package/dist/uploaders/NostrBuildUploader.js.map +1 -0
  137. package/dist/uploaders/mod.d.ts +3 -0
  138. package/dist/uploaders/mod.d.ts.map +1 -0
  139. package/dist/uploaders/mod.js +8 -0
  140. package/dist/uploaders/mod.js.map +1 -0
  141. package/dist/utils/CircularSet.d.ts +13 -0
  142. package/dist/utils/CircularSet.d.ts.map +1 -0
  143. package/dist/utils/CircularSet.js +35 -0
  144. package/dist/utils/CircularSet.js.map +1 -0
  145. package/dist/utils/Machina.d.ts +36 -0
  146. package/dist/utils/Machina.d.ts.map +1 -0
  147. package/dist/utils/Machina.js +66 -0
  148. package/dist/utils/Machina.js.map +1 -0
  149. package/dist/utils/N64.d.ts +9 -0
  150. package/dist/utils/N64.d.ts.map +1 -0
  151. package/dist/utils/N64.js +23 -0
  152. package/dist/utils/N64.js.map +1 -0
  153. package/dist/utils/mod.d.ts +3 -0
  154. package/dist/utils/mod.d.ts.map +1 -0
  155. package/dist/utils/mod.js +8 -0
  156. package/dist/utils/mod.js.map +1 -0
  157. package/ln/LNURL.test.ts +87 -0
  158. package/ln/LNURL.ts +146 -0
  159. package/ln/mod.ts +4 -0
  160. package/ln/types/LNURLCallback.ts +7 -0
  161. package/ln/types/LNURLDetails.ts +19 -0
  162. package/mod.ts +16 -0
  163. package/package.json +23 -0
  164. package/test/ErrorRelay.test.ts +19 -0
  165. package/test/ErrorRelay.ts +40 -0
  166. package/test/MockRelay.test.ts +20 -0
  167. package/test/MockRelay.ts +92 -0
  168. package/test/TestRelayServer.ts +156 -0
  169. package/test/mod.ts +28 -0
  170. package/tsconfig.json +14 -0
  171. package/uploaders/BlossomUploader.test.ts +26 -0
  172. package/uploaders/BlossomUploader.ts +98 -0
  173. package/uploaders/NostrBuildUploader.test.ts +22 -0
  174. package/uploaders/NostrBuildUploader.ts +89 -0
  175. package/uploaders/mod.ts +2 -0
  176. package/utils/CircularSet.test.ts +15 -0
  177. package/utils/CircularSet.ts +34 -0
  178. package/utils/Machina.test.ts +91 -0
  179. package/utils/Machina.ts +66 -0
  180. package/utils/N64.test.ts +27 -0
  181. package/utils/N64.ts +23 -0
  182. 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
+ }
@@ -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
+ });