@teneo-protocol/sdk 1.0.0 → 2.0.0
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/.github/workflows/publish-npm.yml +8 -6
- package/CHANGELOG.md +265 -0
- package/README.md +406 -53
- package/dist/core/websocket-client.d.ts +13 -0
- package/dist/core/websocket-client.d.ts.map +1 -1
- package/dist/core/websocket-client.js +34 -3
- package/dist/core/websocket-client.js.map +1 -1
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.js +70 -0
- package/dist/handlers/message-handlers/agent-room-operation-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +92 -38
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +904 -0
- package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-status-update-handler.js +51 -0
- package/dist/handlers/message-handlers/agent-status-update-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +45 -31
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +6 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-message-handler.js +65 -5
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -1
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +49 -31
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +6 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/auth-success-handler.js +46 -4
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -1
- package/dist/handlers/message-handlers/challenge-handler.d.ts +45 -31
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/error-message-handler.d.ts +49 -31
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/index.d.ts +5 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -1
- package/dist/handlers/message-handlers/index.js +23 -1
- package/dist/handlers/message-handlers/index.js.map +1 -1
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +877 -0
- package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-available-agents-handler.js +38 -0
- package/dist/handlers/message-handlers/list-available-agents-handler.js.map +1 -0
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +886 -0
- package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-room-agents-handler.js +51 -0
- package/dist/handlers/message-handlers/list-room-agents-handler.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +178 -89
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +62 -58
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +31 -29
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/regular-message-handler.js +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -1
- package/dist/handlers/message-handlers/room-operation-response-handler.d.ts +328 -0
- package/dist/handlers/message-handlers/room-operation-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/room-operation-response-handler.js +92 -0
- package/dist/handlers/message-handlers/room-operation-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +53 -31
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -1
- package/dist/handlers/message-handlers/types.d.ts +2 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -1
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +53 -31
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -1
- package/dist/managers/agent-room-manager.d.ts +222 -0
- package/dist/managers/agent-room-manager.d.ts.map +1 -0
- package/dist/managers/agent-room-manager.js +508 -0
- package/dist/managers/agent-room-manager.js.map +1 -0
- package/dist/managers/index.d.ts +2 -0
- package/dist/managers/index.d.ts.map +1 -1
- package/dist/managers/index.js +5 -1
- package/dist/managers/index.js.map +1 -1
- package/dist/managers/message-router.d.ts +1 -1
- package/dist/managers/message-router.d.ts.map +1 -1
- package/dist/managers/message-router.js +41 -4
- package/dist/managers/message-router.js.map +1 -1
- package/dist/managers/room-management-manager.d.ts +213 -0
- package/dist/managers/room-management-manager.d.ts.map +1 -0
- package/dist/managers/room-management-manager.js +440 -0
- package/dist/managers/room-management-manager.js.map +1 -0
- package/dist/managers/room-manager.d.ts +4 -4
- package/dist/managers/room-manager.d.ts.map +1 -1
- package/dist/managers/room-manager.js +1 -1
- package/dist/managers/room-manager.js.map +1 -1
- package/dist/teneo-sdk.d.ts +362 -14
- package/dist/teneo-sdk.d.ts.map +1 -1
- package/dist/teneo-sdk.js +497 -7
- package/dist/teneo-sdk.js.map +1 -1
- package/dist/types/config.d.ts +63 -54
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +9 -5
- package/dist/types/config.js.map +1 -1
- package/dist/types/error-codes.d.ts +2 -0
- package/dist/types/error-codes.d.ts.map +1 -1
- package/dist/types/error-codes.js +3 -0
- package/dist/types/error-codes.js.map +1 -1
- package/dist/types/events.d.ts +132 -68
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/events.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +27 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/messages.d.ts +11396 -2559
- package/dist/types/messages.d.ts.map +1 -1
- package/dist/types/messages.js +294 -27
- package/dist/types/messages.js.map +1 -1
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types/validation.js +1 -1
- package/dist/types/validation.js.map +1 -1
- package/dist/utils/bounded-queue.d.ts +1 -1
- package/dist/utils/bounded-queue.js +6 -6
- package/dist/utils/circuit-breaker.d.ts.map +1 -1
- package/dist/utils/circuit-breaker.js.map +1 -1
- package/dist/utils/event-waiter.d.ts.map +1 -1
- package/dist/utils/event-waiter.js +2 -1
- package/dist/utils/event-waiter.js.map +1 -1
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +4 -6
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/secure-private-key.d.ts.map +1 -1
- package/dist/utils/secure-private-key.js +9 -15
- package/dist/utils/secure-private-key.js.map +1 -1
- package/dist/utils/signature-verifier.d.ts +2 -2
- package/dist/utils/signature-verifier.d.ts.map +1 -1
- package/dist/utils/signature-verifier.js +5 -5
- package/dist/utils/signature-verifier.js.map +1 -1
- package/examples/.env.example +1 -1
- package/examples/agent-room-management-example.ts +334 -0
- package/examples/claude-agent-x-follower/.env.example +117 -0
- package/examples/claude-agent-x-follower/QUICKSTART.md +243 -0
- package/examples/claude-agent-x-follower/README.md +540 -0
- package/examples/claude-agent-x-follower/index.ts +248 -0
- package/examples/claude-agent-x-follower/package.json +37 -0
- package/examples/claude-agent-x-follower/tsconfig.json +20 -0
- package/examples/n8n-teneo/.env.example +127 -0
- package/examples/n8n-teneo/Dockerfile +42 -0
- package/examples/n8n-teneo/README.md +564 -0
- package/examples/n8n-teneo/docker-compose.yml +71 -0
- package/examples/n8n-teneo/index.ts +177 -0
- package/examples/n8n-teneo/package.json +22 -0
- package/examples/n8n-teneo/tsconfig.json +12 -0
- package/examples/n8n-teneo/workflows/x-timeline.json +66 -0
- package/examples/openai-teneo/.env.example +130 -0
- package/examples/openai-teneo/README.md +635 -0
- package/examples/openai-teneo/index.ts +280 -0
- package/examples/openai-teneo/package.json +24 -0
- package/examples/openai-teneo/tsconfig.json +16 -0
- package/examples/production-dashboard/.env.example +5 -3
- package/examples/production-dashboard/README.md +839 -0
- package/examples/production-dashboard/pnpm-lock.yaml +92 -0
- package/examples/production-dashboard/public/dashboard.html +1150 -504
- package/examples/production-dashboard/server.ts +428 -12
- package/examples/room-management-example.ts +285 -0
- package/examples/usage/.env.example +17 -0
- package/examples/usage/01-connect.ts +116 -0
- package/examples/usage/02-list-agents.ts +153 -0
- package/examples/usage/03-pick-agent.ts +201 -0
- package/examples/usage/04-find-by-capability.ts +237 -0
- package/examples/usage/05-webhook-example.ts +319 -0
- package/examples/usage/06-simple-api-server.ts +396 -0
- package/examples/usage/07-event-listener.ts +402 -0
- package/examples/usage/README.md +383 -0
- package/examples/usage/package.json +42 -0
- package/package.json +13 -3
- package/src/core/websocket-client.ts +43 -9
- package/src/formatters/response-formatter.test.ts +8 -2
- package/src/handlers/message-handlers/agent-room-operation-response-handler.ts +83 -0
- package/src/handlers/message-handlers/agent-status-update-handler.ts +58 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +73 -5
- package/src/handlers/message-handlers/auth-success-handler.ts +58 -6
- package/src/handlers/message-handlers/index.ts +19 -0
- package/src/handlers/message-handlers/list-available-agents-handler.ts +41 -0
- package/src/handlers/message-handlers/list-room-agents-handler.ts +61 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +1 -0
- package/src/handlers/message-handlers/room-operation-response-handler.ts +105 -0
- package/src/handlers/message-handlers/types.ts +6 -0
- package/src/handlers/webhook-handler.test.ts +13 -10
- package/src/managers/agent-room-manager.ts +609 -0
- package/src/managers/index.ts +2 -0
- package/src/managers/message-router.ts +48 -6
- package/src/managers/room-management-manager.ts +523 -0
- package/src/managers/room-manager.ts +12 -6
- package/src/teneo-sdk.ts +543 -10
- package/src/types/config.ts +13 -6
- package/src/types/error-codes.ts +4 -0
- package/src/types/events.ts +24 -0
- package/src/types/index.ts +55 -0
- package/src/types/messages.ts +374 -41
- package/src/types/validation.ts +4 -1
- package/src/utils/bounded-queue.ts +9 -9
- package/src/utils/circuit-breaker.ts +4 -1
- package/src/utils/deduplication-cache.test.ts +2 -6
- package/src/utils/event-waiter.test.ts +4 -1
- package/src/utils/event-waiter.ts +5 -7
- package/src/utils/rate-limiter.test.ts +5 -17
- package/src/utils/rate-limiter.ts +6 -9
- package/src/utils/secure-private-key.test.ts +66 -59
- package/src/utils/secure-private-key.ts +10 -16
- package/src/utils/signature-verifier.test.ts +75 -70
- package/src/utils/signature-verifier.ts +7 -8
- package/src/utils/ssrf-validator.test.ts +3 -3
- package/tests/integration/room-management.test.ts +514 -0
- package/tests/integration/websocket.test.ts +1 -1
- package/tests/unit/handlers/agent-room-operation-response-handler.test.ts +394 -0
- package/tests/unit/handlers/agent-status-update-handler.test.ts +407 -0
- package/tests/unit/handlers/auth-success-handler-rooms.test.ts +699 -0
- package/tests/unit/handlers/list-available-agents-handler.test.ts +256 -0
- package/tests/unit/handlers/list-room-agents-handler.test.ts +294 -0
- package/tests/unit/handlers/room-operation-response-handler.test.ts +527 -0
- package/tests/unit/managers/agent-room-manager.test.ts +534 -0
- package/tests/unit/managers/room-management-manager.test.ts +438 -0
|
@@ -31,7 +31,10 @@ export interface CircuitBreakerOptions {
|
|
|
31
31
|
* Error thrown when circuit is open
|
|
32
32
|
*/
|
|
33
33
|
export class CircuitBreakerError extends Error {
|
|
34
|
-
constructor(
|
|
34
|
+
constructor(
|
|
35
|
+
message: string,
|
|
36
|
+
public readonly state: CircuitState
|
|
37
|
+
) {
|
|
35
38
|
super(message);
|
|
36
39
|
this.name = "CircuitBreakerError";
|
|
37
40
|
|
|
@@ -25,15 +25,11 @@ describe("DeduplicationCache", () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it("should throw error if TTL is less than 1000ms", () => {
|
|
28
|
-
expect(() => new DeduplicationCache(999)).toThrow(
|
|
29
|
-
"TTL must be at least 1000ms"
|
|
30
|
-
);
|
|
28
|
+
expect(() => new DeduplicationCache(999)).toThrow("TTL must be at least 1000ms");
|
|
31
29
|
});
|
|
32
30
|
|
|
33
31
|
it("should throw error if maxSize is less than 1", () => {
|
|
34
|
-
expect(() => new DeduplicationCache(5000, 0)).toThrow(
|
|
35
|
-
"maxSize must be at least 1"
|
|
36
|
-
);
|
|
32
|
+
expect(() => new DeduplicationCache(5000, 0)).toThrow("maxSize must be at least 1");
|
|
37
33
|
});
|
|
38
34
|
});
|
|
39
35
|
|
|
@@ -376,6 +376,9 @@ describe("waitForAllEvents", () => {
|
|
|
376
376
|
emitter.emit("data", { id: 1, value: "first" });
|
|
377
377
|
|
|
378
378
|
const results = await promise;
|
|
379
|
-
expect(results).toEqual([
|
|
379
|
+
expect(results).toEqual([
|
|
380
|
+
{ id: 1, value: "first" },
|
|
381
|
+
{ id: 2, value: "second" }
|
|
382
|
+
]);
|
|
380
383
|
});
|
|
381
384
|
});
|
|
@@ -102,7 +102,9 @@ export async function waitForEvent<T = any>(
|
|
|
102
102
|
// Timeout handler - rejects after timeout
|
|
103
103
|
timeoutHandle = setTimeout(() => {
|
|
104
104
|
cleanup();
|
|
105
|
-
const message =
|
|
105
|
+
const message =
|
|
106
|
+
options.timeoutMessage ||
|
|
107
|
+
`Timeout waiting for event '${eventName}' after ${options.timeout}ms`;
|
|
106
108
|
reject(new Error(message));
|
|
107
109
|
}, options.timeout);
|
|
108
110
|
|
|
@@ -135,9 +137,7 @@ export async function waitForAnyEvent<T = any>(
|
|
|
135
137
|
}>
|
|
136
138
|
): Promise<T> {
|
|
137
139
|
return Promise.race(
|
|
138
|
-
waiters.map(({ emitter, eventName, options }) =>
|
|
139
|
-
waitForEvent<T>(emitter, eventName, options)
|
|
140
|
-
)
|
|
140
|
+
waiters.map(({ emitter, eventName, options }) => waitForEvent<T>(emitter, eventName, options))
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -165,8 +165,6 @@ export async function waitForAllEvents<T = any>(
|
|
|
165
165
|
}>
|
|
166
166
|
): Promise<T[]> {
|
|
167
167
|
return Promise.all(
|
|
168
|
-
waiters.map(({ emitter, eventName, options }) =>
|
|
169
|
-
waitForEvent<T>(emitter, eventName, options)
|
|
170
|
-
)
|
|
168
|
+
waiters.map(({ emitter, eventName, options }) => waitForEvent<T>(emitter, eventName, options))
|
|
171
169
|
);
|
|
172
170
|
}
|
|
@@ -17,21 +17,15 @@ describe("TokenBucketRateLimiter", () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it("should throw error if tokensPerSecond is less than 1", () => {
|
|
20
|
-
expect(() => new TokenBucketRateLimiter(0, 10)).toThrow(
|
|
21
|
-
"tokensPerSecond must be at least 1"
|
|
22
|
-
);
|
|
20
|
+
expect(() => new TokenBucketRateLimiter(0, 10)).toThrow("tokensPerSecond must be at least 1");
|
|
23
21
|
expect(() => new TokenBucketRateLimiter(-1, 10)).toThrow(
|
|
24
22
|
"tokensPerSecond must be at least 1"
|
|
25
23
|
);
|
|
26
24
|
});
|
|
27
25
|
|
|
28
26
|
it("should throw error if maxBurst is less than 1", () => {
|
|
29
|
-
expect(() => new TokenBucketRateLimiter(10, 0)).toThrow(
|
|
30
|
-
|
|
31
|
-
);
|
|
32
|
-
expect(() => new TokenBucketRateLimiter(10, -1)).toThrow(
|
|
33
|
-
"maxBurst must be at least 1"
|
|
34
|
-
);
|
|
27
|
+
expect(() => new TokenBucketRateLimiter(10, 0)).toThrow("maxBurst must be at least 1");
|
|
28
|
+
expect(() => new TokenBucketRateLimiter(10, -1)).toThrow("maxBurst must be at least 1");
|
|
35
29
|
});
|
|
36
30
|
});
|
|
37
31
|
|
|
@@ -159,9 +153,7 @@ describe("TokenBucketRateLimiter", () => {
|
|
|
159
153
|
// Reset and try again to verify error message
|
|
160
154
|
limiter.reset();
|
|
161
155
|
limiter.tryConsume();
|
|
162
|
-
await expect(limiter.consume(10)).rejects.toThrow(
|
|
163
|
-
/Rate limit timeout/
|
|
164
|
-
);
|
|
156
|
+
await expect(limiter.consume(10)).rejects.toThrow(/Rate limit timeout/);
|
|
165
157
|
});
|
|
166
158
|
|
|
167
159
|
it("should succeed within timeout if token becomes available", async () => {
|
|
@@ -309,11 +301,7 @@ describe("TokenBucketRateLimiter", () => {
|
|
|
309
301
|
limiter.tryConsume();
|
|
310
302
|
|
|
311
303
|
// Start multiple blocking consume calls
|
|
312
|
-
const promises = [
|
|
313
|
-
limiter.consume(1000),
|
|
314
|
-
limiter.consume(1000),
|
|
315
|
-
limiter.consume(1000)
|
|
316
|
-
];
|
|
304
|
+
const promises = [limiter.consume(1000), limiter.consume(1000), limiter.consume(1000)];
|
|
317
305
|
|
|
318
306
|
// All should eventually succeed as tokens refill
|
|
319
307
|
await expect(Promise.all(promises)).resolves.toBeDefined();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
export class RateLimitError extends Error {
|
|
14
14
|
constructor(message: string) {
|
|
15
15
|
super(message);
|
|
16
|
-
this.name =
|
|
16
|
+
this.name = "RateLimitError";
|
|
17
17
|
|
|
18
18
|
if (Error.captureStackTrace) {
|
|
19
19
|
Error.captureStackTrace(this, RateLimitError);
|
|
@@ -64,10 +64,10 @@ export class TokenBucketRateLimiter {
|
|
|
64
64
|
private readonly maxBurst: number
|
|
65
65
|
) {
|
|
66
66
|
if (tokensPerSecond < 1) {
|
|
67
|
-
throw new Error(
|
|
67
|
+
throw new Error("TokenBucketRateLimiter tokensPerSecond must be at least 1");
|
|
68
68
|
}
|
|
69
69
|
if (maxBurst < 1) {
|
|
70
|
-
throw new Error(
|
|
70
|
+
throw new Error("TokenBucketRateLimiter maxBurst must be at least 1");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
this.tokens = maxBurst; // Start with full bucket
|
|
@@ -131,17 +131,14 @@ export class TokenBucketRateLimiter {
|
|
|
131
131
|
// Check timeout
|
|
132
132
|
const elapsed = Date.now() - startTime;
|
|
133
133
|
if (timeout !== undefined && elapsed >= timeout) {
|
|
134
|
-
throw new RateLimitError(
|
|
135
|
-
`Rate limit timeout: no token available after ${timeout}ms`
|
|
136
|
-
);
|
|
134
|
+
throw new RateLimitError(`Rate limit timeout: no token available after ${timeout}ms`);
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
// Calculate wait time until next token
|
|
140
138
|
// If timeout is specified, don't wait longer than remaining timeout
|
|
141
139
|
const baseWaitTime = Math.min(this.refillInterval, 100);
|
|
142
|
-
const waitTime =
|
|
143
|
-
? Math.min(baseWaitTime, timeout - elapsed)
|
|
144
|
-
: baseWaitTime;
|
|
140
|
+
const waitTime =
|
|
141
|
+
timeout !== undefined ? Math.min(baseWaitTime, timeout - elapsed) : baseWaitTime;
|
|
145
142
|
|
|
146
143
|
// If waitTime is very small or negative, check timeout immediately
|
|
147
144
|
if (waitTime <= 0) {
|
|
@@ -3,65 +3,72 @@
|
|
|
3
3
|
* Verifies secure encryption, decryption, memory cleanup, and integration with viem
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { SecurePrivateKey } from
|
|
7
|
-
import { privateKeyToAccount } from
|
|
6
|
+
import { SecurePrivateKey } from "./secure-private-key";
|
|
7
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
8
8
|
|
|
9
|
-
describe(
|
|
9
|
+
describe("SecurePrivateKey", () => {
|
|
10
10
|
// Test private key (do NOT use in production)
|
|
11
|
-
const testPrivateKey =
|
|
11
|
+
const testPrivateKey = "0x1234567890123456789012345678901234567890123456789012345678901234";
|
|
12
12
|
|
|
13
|
-
describe(
|
|
14
|
-
it(
|
|
13
|
+
describe("constructor", () => {
|
|
14
|
+
it("should create instance with valid private key", () => {
|
|
15
15
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
16
16
|
expect(secureKey).toBeInstanceOf(SecurePrivateKey);
|
|
17
17
|
expect(secureKey.isDestroyed()).toBe(false);
|
|
18
18
|
secureKey.destroy();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it(
|
|
22
|
-
expect(() => new SecurePrivateKey(
|
|
21
|
+
it("should throw error with empty private key", () => {
|
|
22
|
+
expect(() => new SecurePrivateKey("")).toThrow("Private key must be a non-empty string");
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it(
|
|
26
|
-
expect(() => new SecurePrivateKey(null as any)).toThrow(
|
|
25
|
+
it("should throw error with null private key", () => {
|
|
26
|
+
expect(() => new SecurePrivateKey(null as any)).toThrow(
|
|
27
|
+
"Private key must be a non-empty string"
|
|
28
|
+
);
|
|
27
29
|
});
|
|
28
30
|
|
|
29
|
-
it(
|
|
31
|
+
it("should throw error with undefined private key", () => {
|
|
30
32
|
expect(() => new SecurePrivateKey(undefined as any)).toThrow(
|
|
31
|
-
|
|
33
|
+
"Private key must be a non-empty string"
|
|
32
34
|
);
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
it(
|
|
36
|
-
expect(() => new SecurePrivateKey(123 as any)).toThrow(
|
|
37
|
+
it("should throw error with non-string private key", () => {
|
|
38
|
+
expect(() => new SecurePrivateKey(123 as any)).toThrow(
|
|
39
|
+
"Private key must be a non-empty string"
|
|
40
|
+
);
|
|
37
41
|
});
|
|
38
42
|
});
|
|
39
43
|
|
|
40
|
-
describe(
|
|
41
|
-
it(
|
|
44
|
+
describe("use()", () => {
|
|
45
|
+
it("should decrypt and pass key to callback", () => {
|
|
42
46
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
43
47
|
|
|
44
48
|
const result = secureKey.use((key) => {
|
|
45
49
|
expect(key).toBe(testPrivateKey);
|
|
46
|
-
return
|
|
50
|
+
return "success";
|
|
47
51
|
});
|
|
48
52
|
|
|
49
|
-
expect(result).toBe(
|
|
53
|
+
expect(result).toBe("success");
|
|
50
54
|
secureKey.destroy();
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
it(
|
|
57
|
+
it("should return callback result", () => {
|
|
54
58
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
55
59
|
|
|
56
60
|
const result = secureKey.use((key) => {
|
|
57
61
|
return { key: key.substring(0, 10), length: key.length };
|
|
58
62
|
});
|
|
59
63
|
|
|
60
|
-
expect(result).toEqual({
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
key: testPrivateKey.substring(0, 10),
|
|
66
|
+
length: testPrivateKey.length
|
|
67
|
+
});
|
|
61
68
|
secureKey.destroy();
|
|
62
69
|
});
|
|
63
70
|
|
|
64
|
-
it(
|
|
71
|
+
it("should work with async callbacks", async () => {
|
|
65
72
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
66
73
|
|
|
67
74
|
const result = await secureKey.use(async (key) => {
|
|
@@ -73,7 +80,7 @@ describe('SecurePrivateKey', () => {
|
|
|
73
80
|
secureKey.destroy();
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
it(
|
|
83
|
+
it("should allow multiple uses", () => {
|
|
77
84
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
78
85
|
|
|
79
86
|
const result1 = secureKey.use((key) => key.length);
|
|
@@ -87,33 +94,33 @@ describe('SecurePrivateKey', () => {
|
|
|
87
94
|
secureKey.destroy();
|
|
88
95
|
});
|
|
89
96
|
|
|
90
|
-
it(
|
|
97
|
+
it("should throw if key has been destroyed", () => {
|
|
91
98
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
92
99
|
secureKey.destroy();
|
|
93
100
|
|
|
94
101
|
expect(() => {
|
|
95
102
|
secureKey.use((key) => key);
|
|
96
|
-
}).toThrow(
|
|
103
|
+
}).toThrow("SecurePrivateKey has been destroyed and can no longer be used");
|
|
97
104
|
});
|
|
98
105
|
|
|
99
|
-
it(
|
|
106
|
+
it("should propagate errors from callback", () => {
|
|
100
107
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
101
108
|
|
|
102
109
|
expect(() => {
|
|
103
110
|
secureKey.use((key) => {
|
|
104
|
-
throw new Error(
|
|
111
|
+
throw new Error("Test error");
|
|
105
112
|
});
|
|
106
|
-
}).toThrow(
|
|
113
|
+
}).toThrow("Test error");
|
|
107
114
|
|
|
108
115
|
secureKey.destroy();
|
|
109
116
|
});
|
|
110
117
|
|
|
111
|
-
it(
|
|
118
|
+
it("should clean up even if callback throws", () => {
|
|
112
119
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
113
120
|
|
|
114
121
|
try {
|
|
115
122
|
secureKey.use((key) => {
|
|
116
|
-
throw new Error(
|
|
123
|
+
throw new Error("Test error");
|
|
117
124
|
});
|
|
118
125
|
} catch (error) {
|
|
119
126
|
// Expected
|
|
@@ -127,8 +134,8 @@ describe('SecurePrivateKey', () => {
|
|
|
127
134
|
});
|
|
128
135
|
});
|
|
129
136
|
|
|
130
|
-
describe(
|
|
131
|
-
it(
|
|
137
|
+
describe("destroy()", () => {
|
|
138
|
+
it("should mark instance as destroyed", () => {
|
|
132
139
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
133
140
|
expect(secureKey.isDestroyed()).toBe(false);
|
|
134
141
|
|
|
@@ -136,7 +143,7 @@ describe('SecurePrivateKey', () => {
|
|
|
136
143
|
expect(secureKey.isDestroyed()).toBe(true);
|
|
137
144
|
});
|
|
138
145
|
|
|
139
|
-
it(
|
|
146
|
+
it("should be idempotent (safe to call multiple times)", () => {
|
|
140
147
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
141
148
|
|
|
142
149
|
secureKey.destroy();
|
|
@@ -146,32 +153,32 @@ describe('SecurePrivateKey', () => {
|
|
|
146
153
|
expect(secureKey.isDestroyed()).toBe(true);
|
|
147
154
|
});
|
|
148
155
|
|
|
149
|
-
it(
|
|
156
|
+
it("should prevent further use after destruction", () => {
|
|
150
157
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
151
158
|
secureKey.destroy();
|
|
152
159
|
|
|
153
160
|
expect(() => {
|
|
154
161
|
secureKey.use((key) => key);
|
|
155
|
-
}).toThrow(
|
|
162
|
+
}).toThrow("SecurePrivateKey has been destroyed");
|
|
156
163
|
});
|
|
157
164
|
});
|
|
158
165
|
|
|
159
|
-
describe(
|
|
160
|
-
it(
|
|
166
|
+
describe("isDestroyed()", () => {
|
|
167
|
+
it("should return false for new instance", () => {
|
|
161
168
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
162
169
|
expect(secureKey.isDestroyed()).toBe(false);
|
|
163
170
|
secureKey.destroy();
|
|
164
171
|
});
|
|
165
172
|
|
|
166
|
-
it(
|
|
173
|
+
it("should return true after destruction", () => {
|
|
167
174
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
168
175
|
secureKey.destroy();
|
|
169
176
|
expect(secureKey.isDestroyed()).toBe(true);
|
|
170
177
|
});
|
|
171
178
|
});
|
|
172
179
|
|
|
173
|
-
describe(
|
|
174
|
-
it(
|
|
180
|
+
describe("encryption/decryption", () => {
|
|
181
|
+
it("should correctly encrypt and decrypt private key", () => {
|
|
175
182
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
176
183
|
|
|
177
184
|
const decrypted = secureKey.use((key) => key);
|
|
@@ -180,8 +187,8 @@ describe('SecurePrivateKey', () => {
|
|
|
180
187
|
secureKey.destroy();
|
|
181
188
|
});
|
|
182
189
|
|
|
183
|
-
it(
|
|
184
|
-
const specialKey =
|
|
190
|
+
it("should handle keys with special characters", () => {
|
|
191
|
+
const specialKey = "0xABCDEF123456!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
185
192
|
const secureKey = new SecurePrivateKey(specialKey);
|
|
186
193
|
|
|
187
194
|
const decrypted = secureKey.use((key) => key);
|
|
@@ -190,8 +197,8 @@ describe('SecurePrivateKey', () => {
|
|
|
190
197
|
secureKey.destroy();
|
|
191
198
|
});
|
|
192
199
|
|
|
193
|
-
it(
|
|
194
|
-
const longKey =
|
|
200
|
+
it("should handle very long keys", () => {
|
|
201
|
+
const longKey = "0x" + "a".repeat(1000);
|
|
195
202
|
const secureKey = new SecurePrivateKey(longKey);
|
|
196
203
|
|
|
197
204
|
const decrypted = secureKey.use((key) => key);
|
|
@@ -200,7 +207,7 @@ describe('SecurePrivateKey', () => {
|
|
|
200
207
|
secureKey.destroy();
|
|
201
208
|
});
|
|
202
209
|
|
|
203
|
-
it(
|
|
210
|
+
it("should produce different encrypted data for same key (random IV)", () => {
|
|
204
211
|
const secureKey1 = new SecurePrivateKey(testPrivateKey);
|
|
205
212
|
const secureKey2 = new SecurePrivateKey(testPrivateKey);
|
|
206
213
|
|
|
@@ -223,8 +230,8 @@ describe('SecurePrivateKey', () => {
|
|
|
223
230
|
});
|
|
224
231
|
});
|
|
225
232
|
|
|
226
|
-
describe(
|
|
227
|
-
it(
|
|
233
|
+
describe("integration with viem", () => {
|
|
234
|
+
it("should work with privateKeyToAccount", () => {
|
|
228
235
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
229
236
|
|
|
230
237
|
const account = secureKey.use((key) => {
|
|
@@ -238,14 +245,14 @@ describe('SecurePrivateKey', () => {
|
|
|
238
245
|
secureKey.destroy();
|
|
239
246
|
});
|
|
240
247
|
|
|
241
|
-
it(
|
|
248
|
+
it("should allow signing messages with account created from secure key", async () => {
|
|
242
249
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
243
250
|
|
|
244
251
|
const account = secureKey.use((key) => {
|
|
245
252
|
return privateKeyToAccount(key as `0x${string}`);
|
|
246
253
|
});
|
|
247
254
|
|
|
248
|
-
const message =
|
|
255
|
+
const message = "Hello, Teneo!";
|
|
249
256
|
const signature = await account.signMessage({ message });
|
|
250
257
|
|
|
251
258
|
expect(signature).toBeDefined();
|
|
@@ -255,7 +262,7 @@ describe('SecurePrivateKey', () => {
|
|
|
255
262
|
secureKey.destroy();
|
|
256
263
|
});
|
|
257
264
|
|
|
258
|
-
it(
|
|
265
|
+
it("should create consistent account address across multiple uses", () => {
|
|
259
266
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
260
267
|
|
|
261
268
|
const address1 = secureKey.use((key) => {
|
|
@@ -272,8 +279,8 @@ describe('SecurePrivateKey', () => {
|
|
|
272
279
|
});
|
|
273
280
|
});
|
|
274
281
|
|
|
275
|
-
describe(
|
|
276
|
-
it(
|
|
282
|
+
describe("security properties", () => {
|
|
283
|
+
it("should not expose private key in toString()", () => {
|
|
277
284
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
278
285
|
|
|
279
286
|
const stringified = String(secureKey);
|
|
@@ -283,7 +290,7 @@ describe('SecurePrivateKey', () => {
|
|
|
283
290
|
secureKey.destroy();
|
|
284
291
|
});
|
|
285
292
|
|
|
286
|
-
it(
|
|
293
|
+
it("should not expose private key in JSON.stringify()", () => {
|
|
287
294
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
288
295
|
|
|
289
296
|
const jsonString = JSON.stringify(secureKey);
|
|
@@ -293,7 +300,7 @@ describe('SecurePrivateKey', () => {
|
|
|
293
300
|
secureKey.destroy();
|
|
294
301
|
});
|
|
295
302
|
|
|
296
|
-
it(
|
|
303
|
+
it("should not expose private key when inspecting object", () => {
|
|
297
304
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
298
305
|
|
|
299
306
|
// Try to access private properties (they're still accessible via 'any' but not exposed)
|
|
@@ -307,23 +314,23 @@ describe('SecurePrivateKey', () => {
|
|
|
307
314
|
secureKey.destroy();
|
|
308
315
|
});
|
|
309
316
|
|
|
310
|
-
it(
|
|
317
|
+
it("should store key encrypted in memory", () => {
|
|
311
318
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
312
319
|
|
|
313
320
|
// Access encrypted buffer via any
|
|
314
321
|
const encrypted = (secureKey as any).encrypted as Buffer;
|
|
315
322
|
|
|
316
323
|
// Encrypted data should not contain the plaintext key
|
|
317
|
-
const encryptedString = encrypted.toString(
|
|
324
|
+
const encryptedString = encrypted.toString("utf8");
|
|
318
325
|
expect(encryptedString).not.toContain(testPrivateKey);
|
|
319
|
-
expect(encryptedString).not.toContain(
|
|
326
|
+
expect(encryptedString).not.toContain("1234567890");
|
|
320
327
|
|
|
321
328
|
secureKey.destroy();
|
|
322
329
|
});
|
|
323
330
|
});
|
|
324
331
|
|
|
325
|
-
describe(
|
|
326
|
-
it(
|
|
332
|
+
describe("memory cleanup", () => {
|
|
333
|
+
it("should zero out encryption key on destroy", () => {
|
|
327
334
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
328
335
|
|
|
329
336
|
// Get reference to encryption key before destroy
|
|
@@ -338,7 +345,7 @@ describe('SecurePrivateKey', () => {
|
|
|
338
345
|
expect(encryptionKey.every((byte) => byte === 0)).toBe(true);
|
|
339
346
|
});
|
|
340
347
|
|
|
341
|
-
it(
|
|
348
|
+
it("should zero out encrypted buffer on destroy", () => {
|
|
342
349
|
const secureKey = new SecurePrivateKey(testPrivateKey);
|
|
343
350
|
|
|
344
351
|
// Get reference to encrypted buffer before destroy
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import crypto from
|
|
22
|
+
import crypto from "crypto";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Securely stores and manages an encrypted private key in memory.
|
|
@@ -42,8 +42,8 @@ export class SecurePrivateKey {
|
|
|
42
42
|
* @throws {Error} If private key is empty or invalid
|
|
43
43
|
*/
|
|
44
44
|
constructor(privateKey: string) {
|
|
45
|
-
if (!privateKey || typeof privateKey !==
|
|
46
|
-
throw new Error(
|
|
45
|
+
if (!privateKey || typeof privateKey !== "string") {
|
|
46
|
+
throw new Error("Private key must be a non-empty string");
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Generate a random encryption key for AES-256
|
|
@@ -136,13 +136,10 @@ export class SecurePrivateKey {
|
|
|
136
136
|
const iv = crypto.randomBytes(16);
|
|
137
137
|
|
|
138
138
|
// Create cipher
|
|
139
|
-
const cipher = crypto.createCipheriv(
|
|
139
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", this.encryptionKey, iv);
|
|
140
140
|
|
|
141
141
|
// Encrypt the data
|
|
142
|
-
const encrypted = Buffer.concat([
|
|
143
|
-
cipher.update(data, 'utf8'),
|
|
144
|
-
cipher.final()
|
|
145
|
-
]);
|
|
142
|
+
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
146
143
|
|
|
147
144
|
// Get authentication tag for integrity verification
|
|
148
145
|
const authTag = cipher.getAuthTag();
|
|
@@ -164,16 +161,13 @@ export class SecurePrivateKey {
|
|
|
164
161
|
const ciphertext = this.encrypted.subarray(32);
|
|
165
162
|
|
|
166
163
|
// Create decipher
|
|
167
|
-
const decipher = crypto.createDecipheriv(
|
|
164
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", this.encryptionKey, iv);
|
|
168
165
|
decipher.setAuthTag(authTag);
|
|
169
166
|
|
|
170
167
|
// Decrypt the data
|
|
171
|
-
const decrypted = Buffer.concat([
|
|
172
|
-
decipher.update(ciphertext),
|
|
173
|
-
decipher.final()
|
|
174
|
-
]);
|
|
168
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
175
169
|
|
|
176
|
-
return decrypted.toString(
|
|
170
|
+
return decrypted.toString("utf8");
|
|
177
171
|
}
|
|
178
172
|
|
|
179
173
|
/**
|
|
@@ -185,7 +179,7 @@ export class SecurePrivateKey {
|
|
|
185
179
|
*/
|
|
186
180
|
private zeroOutString(str: string): void {
|
|
187
181
|
// Convert to buffer and zero it out
|
|
188
|
-
const buffer = Buffer.from(str,
|
|
182
|
+
const buffer = Buffer.from(str, "utf8");
|
|
189
183
|
buffer.fill(0);
|
|
190
184
|
|
|
191
185
|
// Note: The original string object may still exist in memory
|
|
@@ -199,7 +193,7 @@ export class SecurePrivateKey {
|
|
|
199
193
|
*/
|
|
200
194
|
private checkNotDestroyed(): void {
|
|
201
195
|
if (this.destroyed) {
|
|
202
|
-
throw new Error(
|
|
196
|
+
throw new Error("SecurePrivateKey has been destroyed and can no longer be used");
|
|
203
197
|
}
|
|
204
198
|
}
|
|
205
199
|
}
|