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