@kodelyth/nostr 2026.5.42 → 2026.6.2

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 (47) hide show
  1. package/klaw.plugin.json +185 -2
  2. package/package.json +19 -6
  3. package/api.ts +0 -10
  4. package/channel-plugin-api.ts +0 -1
  5. package/index.ts +0 -95
  6. package/runtime-api.ts +0 -6
  7. package/setup-api.ts +0 -1
  8. package/setup-entry.ts +0 -9
  9. package/setup-plugin-api.ts +0 -3
  10. package/src/channel-api.ts +0 -11
  11. package/src/channel.inbound.test.ts +0 -187
  12. package/src/channel.outbound.test.ts +0 -163
  13. package/src/channel.setup.ts +0 -234
  14. package/src/channel.test.ts +0 -526
  15. package/src/channel.ts +0 -215
  16. package/src/config-schema.ts +0 -98
  17. package/src/default-relays.ts +0 -1
  18. package/src/gateway.ts +0 -321
  19. package/src/inbound-direct-dm-runtime.ts +0 -1
  20. package/src/metrics.ts +0 -458
  21. package/src/nostr-bus.fuzz.test.ts +0 -382
  22. package/src/nostr-bus.inbound.test.ts +0 -526
  23. package/src/nostr-bus.integration.test.ts +0 -477
  24. package/src/nostr-bus.test.ts +0 -231
  25. package/src/nostr-bus.ts +0 -789
  26. package/src/nostr-key-utils.ts +0 -94
  27. package/src/nostr-profile-core.ts +0 -134
  28. package/src/nostr-profile-http-runtime.ts +0 -6
  29. package/src/nostr-profile-http.test.ts +0 -632
  30. package/src/nostr-profile-http.ts +0 -583
  31. package/src/nostr-profile-import.test.ts +0 -119
  32. package/src/nostr-profile-import.ts +0 -262
  33. package/src/nostr-profile-url-safety.ts +0 -21
  34. package/src/nostr-profile.fuzz.test.ts +0 -430
  35. package/src/nostr-profile.test.ts +0 -415
  36. package/src/nostr-profile.ts +0 -144
  37. package/src/nostr-state-store.test.ts +0 -237
  38. package/src/nostr-state-store.ts +0 -206
  39. package/src/runtime.ts +0 -9
  40. package/src/seen-tracker.ts +0 -289
  41. package/src/session-route.ts +0 -25
  42. package/src/setup-surface.ts +0 -264
  43. package/src/test-fixtures.ts +0 -45
  44. package/src/types.ts +0 -117
  45. package/test/setup.ts +0 -5
  46. package/test-api.ts +0 -1
  47. package/tsconfig.json +0 -16
@@ -1,382 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createMetrics, type MetricName } from "./metrics.js";
3
- import { validatePrivateKey, isValidPubkey, normalizePubkey } from "./nostr-key-utils.js";
4
- import { createSeenTracker } from "./seen-tracker.js";
5
- import { TEST_HEX_PRIVATE_KEY } from "./test-fixtures.js";
6
-
7
- function createTracker(maxEntries = 100) {
8
- return createSeenTracker({ maxEntries });
9
- }
10
-
11
- function createPlainMetrics() {
12
- return createMetrics();
13
- }
14
-
15
- function createCollectingMetrics() {
16
- const events: unknown[] = [];
17
- return {
18
- events,
19
- metrics: createMetrics((event) => events.push(event)),
20
- };
21
- }
22
-
23
- function expectThrowsError(run: () => unknown): void {
24
- let error: unknown;
25
- try {
26
- run();
27
- } catch (caught) {
28
- error = caught;
29
- }
30
- expect(error).toBeInstanceOf(Error);
31
- }
32
-
33
- // ============================================================================
34
- // Fuzz Tests for validatePrivateKey
35
- // ============================================================================
36
-
37
- describe("validatePrivateKey fuzz", () => {
38
- describe("validatePrivateKey type confusion", () => {
39
- it("rejects non-string input", () => {
40
- for (const value of [null, undefined, 123, true, {}, [], () => {}]) {
41
- expectThrowsError(() => validatePrivateKey(value as unknown as string));
42
- }
43
- });
44
- });
45
-
46
- describe("unicode attacks", () => {
47
- it("rejects unicode and control-character attacks", () => {
48
- const invalidKeys = [
49
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\u200Bf",
50
- `\u202E${TEST_HEX_PRIVATE_KEY}`,
51
- "0123456789\u0430bcdef0123456789abcdef0123456789abcdef0123456789abcdef",
52
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab😀",
53
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\u0301",
54
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\x00f",
55
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\nf",
56
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\rf",
57
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\tf",
58
- "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\ff",
59
- ];
60
-
61
- for (const key of invalidKeys) {
62
- expectThrowsError(() => validatePrivateKey(key));
63
- }
64
- });
65
- });
66
-
67
- describe("edge cases", () => {
68
- it("rejects very long string", () => {
69
- const veryLong = "a".repeat(10000);
70
- expectThrowsError(() => validatePrivateKey(veryLong));
71
- });
72
-
73
- it("rejects string of spaces matching length", () => {
74
- const spaces = " ".repeat(64);
75
- expectThrowsError(() => validatePrivateKey(spaces));
76
- });
77
-
78
- it("rejects hex with spaces between characters", () => {
79
- const withSpaces =
80
- "01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef";
81
- expectThrowsError(() => validatePrivateKey(withSpaces));
82
- });
83
- });
84
-
85
- describe("nsec format edge cases", () => {
86
- it("rejects nsec with invalid bech32 characters", () => {
87
- // 'b', 'i', 'o' are not valid bech32 characters
88
- const invalidBech32 = "nsec1qypqxpq9qtpqscx7peytbfwtdjmcv0mrz5rjpej8vjppfkqfqy8skqfv3l";
89
- expectThrowsError(() => validatePrivateKey(invalidBech32));
90
- });
91
-
92
- it("rejects nsec with wrong prefix", () => {
93
- expectThrowsError(() => validatePrivateKey("nsec0aaaa"));
94
- });
95
-
96
- it("rejects partial nsec", () => {
97
- expectThrowsError(() => validatePrivateKey("nsec1"));
98
- });
99
- });
100
- });
101
-
102
- // ============================================================================
103
- // Fuzz Tests for isValidPubkey
104
- // ============================================================================
105
-
106
- describe("isValidPubkey fuzz", () => {
107
- describe("isValidPubkey type confusion", () => {
108
- it("handles non-string input gracefully", () => {
109
- for (const value of [null, undefined, 123, {}]) {
110
- expect(isValidPubkey(value as unknown as string)).toBe(false);
111
- }
112
- });
113
- });
114
-
115
- describe("malicious inputs", () => {
116
- it("rejects prototype property names", () => {
117
- for (const value of ["__proto__", "constructor", "toString"]) {
118
- expect(isValidPubkey(value)).toBe(false);
119
- }
120
- });
121
- });
122
- });
123
-
124
- // ============================================================================
125
- // Fuzz Tests for normalizePubkey
126
- // ============================================================================
127
-
128
- describe("normalizePubkey fuzz", () => {
129
- describe("prototype pollution attempts", () => {
130
- it("throws for prototype property names", () => {
131
- for (const value of ["__proto__", "constructor", "prototype"]) {
132
- expectThrowsError(() => normalizePubkey(value));
133
- }
134
- });
135
- });
136
-
137
- describe("case sensitivity", () => {
138
- it("normalizes uppercase to lowercase", () => {
139
- const upper = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
140
- expect(normalizePubkey(upper)).toBe(TEST_HEX_PRIVATE_KEY);
141
- });
142
-
143
- it("normalizes mixed case to lowercase", () => {
144
- const mixed = "0123456789AbCdEf0123456789AbCdEf0123456789AbCdEf0123456789AbCdEf";
145
- expect(normalizePubkey(mixed)).toBe(TEST_HEX_PRIVATE_KEY);
146
- });
147
- });
148
- });
149
-
150
- // ============================================================================
151
- // Fuzz Tests for SeenTracker
152
- // ============================================================================
153
-
154
- describe("SeenTracker fuzz", () => {
155
- describe("malformed IDs", () => {
156
- it("handles empty string IDs", () => {
157
- const tracker = createTracker();
158
- expect(tracker.add("")).toBeUndefined();
159
- expect(tracker.peek("")).toBe(true);
160
- tracker.stop();
161
- });
162
-
163
- it("handles very long IDs", () => {
164
- const tracker = createTracker();
165
- const longId = "a".repeat(100000);
166
- expect(tracker.add(longId)).toBeUndefined();
167
- expect(tracker.peek(longId)).toBe(true);
168
- tracker.stop();
169
- });
170
-
171
- it("handles unicode IDs", () => {
172
- const tracker = createTracker();
173
- const unicodeId = "事件ID_🎉_тест";
174
- expect(tracker.add(unicodeId)).toBeUndefined();
175
- expect(tracker.peek(unicodeId)).toBe(true);
176
- tracker.stop();
177
- });
178
-
179
- it("handles IDs with null bytes", () => {
180
- const tracker = createTracker();
181
- const idWithNull = "event\x00id";
182
- expect(tracker.add(idWithNull)).toBeUndefined();
183
- expect(tracker.peek(idWithNull)).toBe(true);
184
- tracker.stop();
185
- });
186
-
187
- it("handles prototype property names as IDs", () => {
188
- const tracker = createTracker();
189
-
190
- // These should not affect the tracker's internal operation
191
- expect(tracker.add("__proto__")).toBeUndefined();
192
- expect(tracker.add("constructor")).toBeUndefined();
193
- expect(tracker.add("toString")).toBeUndefined();
194
- expect(tracker.add("hasOwnProperty")).toBeUndefined();
195
-
196
- expect(tracker.peek("__proto__")).toBe(true);
197
- expect(tracker.peek("constructor")).toBe(true);
198
- expect(tracker.peek("toString")).toBe(true);
199
- expect(tracker.peek("hasOwnProperty")).toBe(true);
200
-
201
- tracker.stop();
202
- });
203
- });
204
-
205
- describe("rapid operations", () => {
206
- it("handles rapid add/check cycles", () => {
207
- const tracker = createTracker(1000);
208
-
209
- for (let i = 0; i < 10000; i++) {
210
- const id = `event-${i}`;
211
- tracker.add(id);
212
- // Recently added should be findable
213
- if (i < 1000) {
214
- tracker.peek(id);
215
- }
216
- }
217
-
218
- // Size should be capped at maxEntries
219
- expect(tracker.size()).toBeLessThanOrEqual(1000);
220
- tracker.stop();
221
- });
222
-
223
- it("handles concurrent-style operations", () => {
224
- const tracker = createTracker();
225
-
226
- // Simulate interleaved operations
227
- for (let i = 0; i < 100; i++) {
228
- tracker.add(`add-${i}`);
229
- tracker.peek(`peek-${i}`);
230
- tracker.has(`has-${i}`);
231
- if (i % 10 === 0) {
232
- tracker.delete(`add-${i - 5}`);
233
- }
234
- }
235
-
236
- expect(tracker.size()).toBeGreaterThan(0);
237
- tracker.stop();
238
- });
239
- });
240
-
241
- describe("seed edge cases", () => {
242
- it("handles empty seed array", () => {
243
- const tracker = createTracker();
244
- expect(tracker.seed([])).toBeUndefined();
245
- expect(tracker.size()).toBe(0);
246
- tracker.stop();
247
- });
248
-
249
- it("handles seed with duplicate IDs", () => {
250
- const tracker = createTracker();
251
- tracker.seed(["id1", "id1", "id1", "id2", "id2"]);
252
- expect(tracker.size()).toBe(2);
253
- tracker.stop();
254
- });
255
-
256
- it("handles seed larger than maxEntries", () => {
257
- const tracker = createTracker(5);
258
- const ids = Array.from({ length: 100 }, (_, i) => `id-${i}`);
259
- tracker.seed(ids);
260
- expect(tracker.size()).toBeLessThanOrEqual(5);
261
- tracker.stop();
262
- });
263
- });
264
- });
265
-
266
- // ============================================================================
267
- // Fuzz Tests for Metrics
268
- // ============================================================================
269
-
270
- describe("Metrics fuzz", () => {
271
- describe("invalid metric names", () => {
272
- it("handles unknown metric names gracefully", () => {
273
- const metrics = createPlainMetrics();
274
-
275
- // Cast to bypass type checking - testing runtime behavior
276
- expect(metrics.emit("invalid.metric.name" as MetricName)).toBeUndefined();
277
- });
278
- });
279
-
280
- describe("invalid label values", () => {
281
- it("handles null relay label", () => {
282
- const metrics = createPlainMetrics();
283
- expect(
284
- metrics.emit("relay.connect", 1, { relay: null as unknown as string }),
285
- ).toBeUndefined();
286
- });
287
-
288
- it("handles undefined relay label", () => {
289
- const metrics = createPlainMetrics();
290
- expect(
291
- metrics.emit("relay.connect", 1, { relay: undefined as unknown as string }),
292
- ).toBeUndefined();
293
- });
294
-
295
- it("handles very long relay URL", () => {
296
- const metrics = createPlainMetrics();
297
- const longUrl = "wss://" + "a".repeat(10000) + ".com";
298
- expect(metrics.emit("relay.connect", 1, { relay: longUrl })).toBeUndefined();
299
-
300
- const snapshot = metrics.getSnapshot();
301
- expect(snapshot.relays[longUrl]).toEqual({
302
- connects: 1,
303
- disconnects: 0,
304
- reconnects: 0,
305
- errors: 0,
306
- messagesReceived: {
307
- event: 0,
308
- eose: 0,
309
- closed: 0,
310
- notice: 0,
311
- ok: 0,
312
- auth: 0,
313
- },
314
- circuitBreakerState: "closed",
315
- circuitBreakerOpens: 0,
316
- circuitBreakerCloses: 0,
317
- });
318
- });
319
- });
320
-
321
- describe("extreme values", () => {
322
- it("handles NaN value", () => {
323
- const metrics = createPlainMetrics();
324
- expect(metrics.emit("event.received", Number.NaN)).toBeUndefined();
325
-
326
- const snapshot = metrics.getSnapshot();
327
- expect(Number.isNaN(snapshot.eventsReceived)).toBe(true);
328
- });
329
-
330
- it("handles Infinity value", () => {
331
- const metrics = createPlainMetrics();
332
- expect(metrics.emit("event.received", Infinity)).toBeUndefined();
333
-
334
- const snapshot = metrics.getSnapshot();
335
- expect(snapshot.eventsReceived).toBe(Infinity);
336
- });
337
-
338
- it("handles negative value", () => {
339
- const metrics = createPlainMetrics();
340
- metrics.emit("event.received", -1);
341
-
342
- const snapshot = metrics.getSnapshot();
343
- expect(snapshot.eventsReceived).toBe(-1);
344
- });
345
-
346
- it("handles very large value", () => {
347
- const metrics = createPlainMetrics();
348
- metrics.emit("event.received", Number.MAX_SAFE_INTEGER);
349
-
350
- const snapshot = metrics.getSnapshot();
351
- expect(snapshot.eventsReceived).toBe(Number.MAX_SAFE_INTEGER);
352
- });
353
- });
354
-
355
- describe("rapid emissions", () => {
356
- it("handles many rapid emissions", () => {
357
- const { events, metrics } = createCollectingMetrics();
358
-
359
- for (let i = 0; i < 10000; i++) {
360
- metrics.emit("event.received");
361
- }
362
-
363
- expect(events).toHaveLength(10000);
364
- const snapshot = metrics.getSnapshot();
365
- expect(snapshot.eventsReceived).toBe(10000);
366
- });
367
- });
368
-
369
- describe("reset during operation", () => {
370
- it("handles reset mid-operation safely", () => {
371
- const metrics = createPlainMetrics();
372
-
373
- metrics.emit("event.received");
374
- metrics.emit("event.received");
375
- metrics.reset();
376
- metrics.emit("event.received");
377
-
378
- const snapshot = metrics.getSnapshot();
379
- expect(snapshot.eventsReceived).toBe(1);
380
- });
381
- });
382
- });