@teneo-protocol/sdk 1.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/.dockerignore +14 -0
- package/.env.test.example +14 -0
- package/.eslintrc.json +26 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude-reviewer.yml +64 -0
- package/.github/workflows/publish-npm.yml +38 -0
- package/.github/workflows/push-to-main.yml +23 -0
- package/.node-version +1 -0
- package/.prettierrc +11 -0
- package/Dockerfile +25 -0
- package/LICENCE +661 -0
- package/README.md +709 -0
- package/dist/constants.d.ts +42 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +45 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/websocket-client.d.ts +261 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +875 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/formatters/response-formatter.d.ts +354 -0
- package/dist/formatters/response-formatter.d.ts.map +1 -0
- package/dist/formatters/response-formatter.js +575 -0
- package/dist/formatters/response-formatter.js.map +1 -0
- package/dist/handlers/message-handler-registry.d.ts +155 -0
- package/dist/handlers/message-handler-registry.d.ts.map +1 -0
- package/dist/handlers/message-handler-registry.js +216 -0
- package/dist/handlers/message-handler-registry.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
- package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/base-handler.js +83 -0
- package/dist/handlers/message-handlers/base-handler.js.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.js +47 -0
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.js +29 -0
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/index.d.ts +28 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -0
- package/dist/handlers/message-handlers/index.js +100 -0
- package/dist/handlers/message-handlers/index.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +44 -0
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/types.d.ts +51 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -0
- package/dist/handlers/message-handlers/types.js +7 -0
- package/dist/handlers/message-handlers/types.js.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +202 -0
- package/dist/handlers/webhook-handler.d.ts.map +1 -0
- package/dist/handlers/webhook-handler.js +511 -0
- package/dist/handlers/webhook-handler.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +173 -0
- package/dist/managers/agent-registry.d.ts.map +1 -0
- package/dist/managers/agent-registry.js +310 -0
- package/dist/managers/agent-registry.js.map +1 -0
- package/dist/managers/connection-manager.d.ts +134 -0
- package/dist/managers/connection-manager.d.ts.map +1 -0
- package/dist/managers/connection-manager.js +176 -0
- package/dist/managers/connection-manager.js.map +1 -0
- package/dist/managers/index.d.ts +9 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +16 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/message-router.d.ts +112 -0
- package/dist/managers/message-router.d.ts.map +1 -0
- package/dist/managers/message-router.js +260 -0
- package/dist/managers/message-router.js.map +1 -0
- package/dist/managers/room-manager.d.ts +165 -0
- package/dist/managers/room-manager.d.ts.map +1 -0
- package/dist/managers/room-manager.js +227 -0
- package/dist/managers/room-manager.js.map +1 -0
- package/dist/teneo-sdk.d.ts +703 -0
- package/dist/teneo-sdk.d.ts.map +1 -0
- package/dist/teneo-sdk.js +907 -0
- package/dist/teneo-sdk.js.map +1 -0
- package/dist/types/config.d.ts +1047 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +720 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/error-codes.d.ts +29 -0
- package/dist/types/error-codes.d.ts.map +1 -0
- package/dist/types/error-codes.js +41 -0
- package/dist/types/error-codes.js.map +1 -0
- package/dist/types/events.d.ts +616 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +261 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +6 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +123 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +3734 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +482 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/validation.d.ts +81 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +115 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/bounded-queue.d.ts +127 -0
- package/dist/utils/bounded-queue.d.ts.map +1 -0
- package/dist/utils/bounded-queue.js +181 -0
- package/dist/utils/bounded-queue.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +141 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +215 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/deduplication-cache.d.ts +110 -0
- package/dist/utils/deduplication-cache.d.ts.map +1 -0
- package/dist/utils/deduplication-cache.js +177 -0
- package/dist/utils/deduplication-cache.js.map +1 -0
- package/dist/utils/event-waiter.d.ts +101 -0
- package/dist/utils/event-waiter.d.ts.map +1 -0
- package/dist/utils/event-waiter.js +118 -0
- package/dist/utils/event-waiter.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +72 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +122 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +190 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry-policy.d.ts +191 -0
- package/dist/utils/retry-policy.d.ts.map +1 -0
- package/dist/utils/retry-policy.js +225 -0
- package/dist/utils/retry-policy.js.map +1 -0
- package/dist/utils/secure-private-key.d.ts +113 -0
- package/dist/utils/secure-private-key.d.ts.map +1 -0
- package/dist/utils/secure-private-key.js +188 -0
- package/dist/utils/secure-private-key.js.map +1 -0
- package/dist/utils/signature-verifier.d.ts +143 -0
- package/dist/utils/signature-verifier.d.ts.map +1 -0
- package/dist/utils/signature-verifier.js +238 -0
- package/dist/utils/signature-verifier.js.map +1 -0
- package/dist/utils/ssrf-validator.d.ts +36 -0
- package/dist/utils/ssrf-validator.d.ts.map +1 -0
- package/dist/utils/ssrf-validator.js +195 -0
- package/dist/utils/ssrf-validator.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/basic-usage.ts +211 -0
- package/examples/production-dashboard/.env.example +153 -0
- package/examples/production-dashboard/package.json +39 -0
- package/examples/production-dashboard/public/dashboard.html +642 -0
- package/examples/production-dashboard/server.ts +753 -0
- package/examples/webhook-integration.ts +239 -0
- package/examples/x-influencer-battle-redesign.html +1065 -0
- package/examples/x-influencer-battle-server.ts +217 -0
- package/examples/x-influencer-battle.html +787 -0
- package/package.json +65 -0
- package/src/constants.ts +43 -0
- package/src/core/websocket-client.test.ts +512 -0
- package/src/core/websocket-client.ts +1056 -0
- package/src/formatters/response-formatter.test.ts +571 -0
- package/src/formatters/response-formatter.ts +677 -0
- package/src/handlers/message-handler-registry.ts +239 -0
- package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
- package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
- package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
- package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
- package/src/handlers/message-handlers/base-handler.ts +101 -0
- package/src/handlers/message-handlers/challenge-handler.ts +57 -0
- package/src/handlers/message-handlers/error-message-handler.ts +27 -0
- package/src/handlers/message-handlers/index.ts +77 -0
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
- package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
- package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
- package/src/handlers/message-handlers/task-response-handler.ts +45 -0
- package/src/handlers/message-handlers/types.ts +77 -0
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
- package/src/handlers/webhook-handler.test.ts +789 -0
- package/src/handlers/webhook-handler.ts +576 -0
- package/src/index.ts +269 -0
- package/src/managers/agent-registry.test.ts +466 -0
- package/src/managers/agent-registry.ts +347 -0
- package/src/managers/connection-manager.ts +195 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/message-router.ts +349 -0
- package/src/managers/room-manager.ts +248 -0
- package/src/teneo-sdk.ts +1022 -0
- package/src/types/config.test.ts +325 -0
- package/src/types/config.ts +799 -0
- package/src/types/error-codes.ts +44 -0
- package/src/types/events.test.ts +302 -0
- package/src/types/events.ts +382 -0
- package/src/types/health.ts +46 -0
- package/src/types/index.ts +199 -0
- package/src/types/messages.test.ts +660 -0
- package/src/types/messages.ts +570 -0
- package/src/types/validation.ts +123 -0
- package/src/utils/bounded-queue.test.ts +356 -0
- package/src/utils/bounded-queue.ts +205 -0
- package/src/utils/circuit-breaker.test.ts +394 -0
- package/src/utils/circuit-breaker.ts +262 -0
- package/src/utils/deduplication-cache.test.ts +380 -0
- package/src/utils/deduplication-cache.ts +198 -0
- package/src/utils/event-waiter.test.ts +381 -0
- package/src/utils/event-waiter.ts +172 -0
- package/src/utils/index.ts +74 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/rate-limiter.test.ts +341 -0
- package/src/utils/rate-limiter.ts +211 -0
- package/src/utils/retry-policy.test.ts +558 -0
- package/src/utils/retry-policy.ts +272 -0
- package/src/utils/secure-private-key.test.ts +356 -0
- package/src/utils/secure-private-key.ts +205 -0
- package/src/utils/signature-verifier.test.ts +464 -0
- package/src/utils/signature-verifier.ts +298 -0
- package/src/utils/ssrf-validator.test.ts +372 -0
- package/src/utils/ssrf-validator.ts +224 -0
- package/tests/integration/real-server.test.ts +740 -0
- package/tests/integration/websocket.test.ts +381 -0
- package/tests/integration-setup.ts +16 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +42 -0
- package/vitest.integration.config.ts +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teneo-protocol/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript SDK for external platforms to interact with Teneo agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"teneo",
|
|
9
|
+
"protocol",
|
|
10
|
+
"sdk",
|
|
11
|
+
"websocket",
|
|
12
|
+
"ai",
|
|
13
|
+
"agents",
|
|
14
|
+
"coordinator"
|
|
15
|
+
],
|
|
16
|
+
"author": "Teneo Protocol",
|
|
17
|
+
"license": "AGPL-3.0",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"eventemitter3": "^5.0.1",
|
|
20
|
+
"hono": "^4.9.12",
|
|
21
|
+
"node-fetch": "^3.3.2",
|
|
22
|
+
"pino": "^8.17.2",
|
|
23
|
+
"uuid": "^9.0.1",
|
|
24
|
+
"viem": "^2.37.11",
|
|
25
|
+
"ws": "^8.16.0",
|
|
26
|
+
"zod": "^3.22.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@faker-js/faker": "^10.0.0",
|
|
30
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
31
|
+
"@types/node": "^20.10.5",
|
|
32
|
+
"@types/uuid": "^9.0.7",
|
|
33
|
+
"@types/ws": "^8.5.10",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
35
|
+
"@typescript-eslint/parser": "^6.15.0",
|
|
36
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
37
|
+
"@vitest/ui": "^3.2.4",
|
|
38
|
+
"dotenv": "^17.2.3",
|
|
39
|
+
"eslint": "^8.56.0",
|
|
40
|
+
"eslint-config-prettier": "^9.1.0",
|
|
41
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
42
|
+
"happy-dom": "^19.0.2",
|
|
43
|
+
"msw": "^2.11.3",
|
|
44
|
+
"pino-pretty": "^13.1.2",
|
|
45
|
+
"prettier": "^3.1.1",
|
|
46
|
+
"typescript": "^5.3.3",
|
|
47
|
+
"vitest": "^3.2.4",
|
|
48
|
+
"ws-mock": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc",
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"test:coverage": "vitest run --coverage",
|
|
56
|
+
"test:ui": "vitest --ui",
|
|
57
|
+
"test:unit": "vitest run src/**/*.test.ts",
|
|
58
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
59
|
+
"lint": "eslint . --ext .ts",
|
|
60
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
61
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
62
|
+
"example:battle": "ts-node examples/x-influencer-battle-server.ts",
|
|
63
|
+
"example:dashboard": "cd examples/production-dashboard && bun run server.ts"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeout constants for various SDK operations
|
|
3
|
+
*/
|
|
4
|
+
export const TIMEOUTS = {
|
|
5
|
+
/** Wait time for cached auth response */
|
|
6
|
+
CACHED_AUTH_WAIT: 1000,
|
|
7
|
+
/** WebSocket ping interval */
|
|
8
|
+
PING_INTERVAL: 30_000,
|
|
9
|
+
/** Default message response timeout */
|
|
10
|
+
DEFAULT_MESSAGE_TIMEOUT: 30_000,
|
|
11
|
+
/** Authentication timeout */
|
|
12
|
+
AUTH_TIMEOUT: 30_000,
|
|
13
|
+
/** Initial connection timeout */
|
|
14
|
+
CONNECTION_TIMEOUT: 30_000,
|
|
15
|
+
/** Webhook delivery timeout */
|
|
16
|
+
WEBHOOK_TIMEOUT: 10_000,
|
|
17
|
+
/** Auth state polling interval */
|
|
18
|
+
AUTH_POLL_INTERVAL: 100
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retry and backoff constants
|
|
23
|
+
*/
|
|
24
|
+
export const RETRY = {
|
|
25
|
+
/** Base delay for exponential backoff */
|
|
26
|
+
BASE_DELAY: 1000,
|
|
27
|
+
/** Maximum retry delay */
|
|
28
|
+
MAX_DELAY: 30_000,
|
|
29
|
+
/** Maximum webhook retry delay */
|
|
30
|
+
MAX_WEBHOOK_DELAY: 30_000,
|
|
31
|
+
/** Maximum reconnection delay */
|
|
32
|
+
MAX_RECONNECT_DELAY: 60_000
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Limits and constraints
|
|
37
|
+
*/
|
|
38
|
+
export const LIMITS = {
|
|
39
|
+
/** Maximum message size (2MB) */
|
|
40
|
+
MAX_MESSAGE_SIZE: 2 * 1024 * 1024,
|
|
41
|
+
/** Random jitter for reconnection */
|
|
42
|
+
MAX_JITTER: 1000
|
|
43
|
+
} as const;
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { WebSocketClient } from "./websocket-client";
|
|
3
|
+
import { ConnectionError, AuthenticationError, TimeoutError } from "../types/events";
|
|
4
|
+
import type { SDKConfig } from "../types/config";
|
|
5
|
+
import WebSocket from "ws";
|
|
6
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
7
|
+
|
|
8
|
+
// Mock WebSocket
|
|
9
|
+
vi.mock("ws", () => {
|
|
10
|
+
return {
|
|
11
|
+
default: vi.fn(),
|
|
12
|
+
WebSocket: vi.fn()
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Mock viem
|
|
17
|
+
vi.mock("viem/accounts", () => ({
|
|
18
|
+
privateKeyToAccount: vi.fn()
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe("WebSocketClient", () => {
|
|
22
|
+
let client: WebSocketClient;
|
|
23
|
+
let mockConfig: SDKConfig;
|
|
24
|
+
let mockWs: any;
|
|
25
|
+
let mockAccount: any;
|
|
26
|
+
|
|
27
|
+
// Helper to simulate successful authentication
|
|
28
|
+
const simulateAuth = (client: any) => {
|
|
29
|
+
// Update auth state to bypass authentication wait
|
|
30
|
+
client.updateAuthState({ authenticated: true });
|
|
31
|
+
client.emit("ready");
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
|
|
37
|
+
mockConfig = {
|
|
38
|
+
wsUrl: "wss://example.com/ws",
|
|
39
|
+
privateKey: "0x1234567890123456789012345678901234567890123456789012345678901234",
|
|
40
|
+
reconnect: true,
|
|
41
|
+
reconnectDelay: 1000,
|
|
42
|
+
maxReconnectAttempts: 3,
|
|
43
|
+
connectionTimeout: 10000,
|
|
44
|
+
messageTimeout: 10000,
|
|
45
|
+
logLevel: "info"
|
|
46
|
+
} as SDKConfig;
|
|
47
|
+
|
|
48
|
+
// Create mock WebSocket instance
|
|
49
|
+
mockWs = {
|
|
50
|
+
on: vi.fn(),
|
|
51
|
+
once: vi.fn(),
|
|
52
|
+
send: vi.fn((data: any, callback?: any) => callback && callback()),
|
|
53
|
+
close: vi.fn(),
|
|
54
|
+
ping: vi.fn((callback?: any) => callback && callback()),
|
|
55
|
+
terminate: vi.fn(),
|
|
56
|
+
readyState: WebSocket.OPEN,
|
|
57
|
+
CONNECTING: 0,
|
|
58
|
+
OPEN: 1,
|
|
59
|
+
CLOSING: 2,
|
|
60
|
+
CLOSED: 3,
|
|
61
|
+
removeAllListeners: vi.fn(),
|
|
62
|
+
removeListener: vi.fn()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Mock WebSocket constructor
|
|
66
|
+
(WebSocket as any).mockImplementation(() => mockWs);
|
|
67
|
+
|
|
68
|
+
// Mock viem account
|
|
69
|
+
mockAccount = {
|
|
70
|
+
address: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb6",
|
|
71
|
+
signMessage: vi.fn().mockResolvedValue("0xmocked_signature")
|
|
72
|
+
};
|
|
73
|
+
(privateKeyToAccount as any).mockReturnValue(mockAccount);
|
|
74
|
+
|
|
75
|
+
client = new WebSocketClient(mockConfig);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
client.disconnect();
|
|
80
|
+
vi.clearAllMocks();
|
|
81
|
+
vi.clearAllTimers();
|
|
82
|
+
vi.useRealTimers();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("constructor", () => {
|
|
86
|
+
it("should initialize with config", () => {
|
|
87
|
+
expect(client).toBeDefined();
|
|
88
|
+
// SEC-3: Private key should be removed from config after initialization
|
|
89
|
+
const expectedConfig = { ...mockConfig, privateKey: undefined };
|
|
90
|
+
expect((client as any).config).toStrictEqual(expectedConfig);
|
|
91
|
+
expect(client.isConnected).toBe(false);
|
|
92
|
+
expect(client.isAuthenticated).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should initialize wallet account", () => {
|
|
96
|
+
expect(privateKeyToAccount).toHaveBeenCalledWith(mockConfig.privateKey);
|
|
97
|
+
expect((client as any).account).toBe(mockAccount);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("connect", () => {
|
|
102
|
+
it("should establish WebSocket connection", async () => {
|
|
103
|
+
const connectPromise = client.connect();
|
|
104
|
+
|
|
105
|
+
// Simulate connection open
|
|
106
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
107
|
+
if (openHandler) {
|
|
108
|
+
// Simulate successful auth immediately
|
|
109
|
+
simulateAuth(client);
|
|
110
|
+
await openHandler();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await connectPromise;
|
|
114
|
+
|
|
115
|
+
expect(WebSocket).toHaveBeenCalled();
|
|
116
|
+
expect(client.isConnected).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should emit connection events", async () => {
|
|
120
|
+
const openHandler = vi.fn();
|
|
121
|
+
|
|
122
|
+
client.on("connection:open", openHandler);
|
|
123
|
+
|
|
124
|
+
const connectPromise = client.connect();
|
|
125
|
+
|
|
126
|
+
// Simulate connection open
|
|
127
|
+
const wsOpenHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
128
|
+
if (wsOpenHandler) {
|
|
129
|
+
simulateAuth(client);
|
|
130
|
+
await wsOpenHandler();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await connectPromise;
|
|
134
|
+
|
|
135
|
+
expect(openHandler).toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should handle connection error", async () => {
|
|
139
|
+
const errorHandler = vi.fn();
|
|
140
|
+
client.on("connection:error", errorHandler);
|
|
141
|
+
|
|
142
|
+
const connectPromise = client.connect();
|
|
143
|
+
|
|
144
|
+
// Simulate connection error
|
|
145
|
+
const wsErrorHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "error")?.[1];
|
|
146
|
+
const error = new Error("Connection failed");
|
|
147
|
+
wsErrorHandler?.(error);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await connectPromise;
|
|
151
|
+
} catch (e) {
|
|
152
|
+
expect(e).toBeInstanceOf(ConnectionError);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
expect(errorHandler).toHaveBeenCalledWith(expect.any(Error));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it.skip("should not connect if already connected", async () => {
|
|
159
|
+
// First connection
|
|
160
|
+
const connectPromise1 = client.connect();
|
|
161
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
162
|
+
if (openHandler) {
|
|
163
|
+
simulateAuth(client);
|
|
164
|
+
await openHandler();
|
|
165
|
+
}
|
|
166
|
+
await connectPromise1;
|
|
167
|
+
|
|
168
|
+
// Clear mock calls
|
|
169
|
+
(WebSocket as any).mockClear();
|
|
170
|
+
|
|
171
|
+
// Second connection attempt - should disconnect first
|
|
172
|
+
const connectPromise2 = client.connect();
|
|
173
|
+
const openHandler2 = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
174
|
+
if (openHandler2) {
|
|
175
|
+
simulateAuth(client);
|
|
176
|
+
await openHandler2();
|
|
177
|
+
}
|
|
178
|
+
await connectPromise2;
|
|
179
|
+
|
|
180
|
+
// WebSocket should be created again (due to disconnect in connect())
|
|
181
|
+
expect(WebSocket).toHaveBeenCalled();
|
|
182
|
+
}, 15000);
|
|
183
|
+
|
|
184
|
+
it("should setup message handler", async () => {
|
|
185
|
+
const connectPromise = client.connect();
|
|
186
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
187
|
+
if (openHandler) {
|
|
188
|
+
simulateAuth(client);
|
|
189
|
+
await openHandler();
|
|
190
|
+
}
|
|
191
|
+
await connectPromise;
|
|
192
|
+
|
|
193
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "message")?.[1];
|
|
194
|
+
expect(messageHandler).toBeDefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should setup close handler", async () => {
|
|
198
|
+
const connectPromise = client.connect();
|
|
199
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
200
|
+
if (openHandler) {
|
|
201
|
+
simulateAuth(client);
|
|
202
|
+
await openHandler();
|
|
203
|
+
}
|
|
204
|
+
await connectPromise;
|
|
205
|
+
|
|
206
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "close")?.[1];
|
|
207
|
+
expect(closeHandler).toBeDefined();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("sendMessage", () => {
|
|
212
|
+
beforeEach(async () => {
|
|
213
|
+
// Connect first
|
|
214
|
+
const connectPromise = client.connect();
|
|
215
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
216
|
+
if (openHandler) {
|
|
217
|
+
simulateAuth(client);
|
|
218
|
+
await openHandler();
|
|
219
|
+
}
|
|
220
|
+
await connectPromise;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should send message when connected", async () => {
|
|
224
|
+
const message = { type: "message" as const, content: "test" };
|
|
225
|
+
await client.sendMessage(message);
|
|
226
|
+
|
|
227
|
+
expect(mockWs.send).toHaveBeenCalledWith(
|
|
228
|
+
expect.stringContaining('"content":"test"'),
|
|
229
|
+
expect.any(Function)
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should throw error when not connected", async () => {
|
|
234
|
+
// Create a new client that's never connected
|
|
235
|
+
const disconnectedClient = new WebSocketClient(mockConfig);
|
|
236
|
+
|
|
237
|
+
const message = { type: "message" as const, content: "test" };
|
|
238
|
+
await expect(disconnectedClient.sendMessage(message)).rejects.toThrow();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should handle send errors", async () => {
|
|
242
|
+
mockWs.send.mockImplementationOnce((data: any, callback: any) => {
|
|
243
|
+
callback(new Error("Send failed"));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const message = { type: "message" as const, content: "test" };
|
|
247
|
+
await expect(client.sendMessage(message)).rejects.toThrow("Send failed");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("reconnection logic", () => {
|
|
252
|
+
beforeEach(async () => {
|
|
253
|
+
// Connect first
|
|
254
|
+
const connectPromise = client.connect();
|
|
255
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
256
|
+
if (openHandler) {
|
|
257
|
+
simulateAuth(client);
|
|
258
|
+
await openHandler();
|
|
259
|
+
}
|
|
260
|
+
await connectPromise;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it.skip("should attempt reconnection on disconnect", async () => {
|
|
264
|
+
const reconnectingHandler = vi.fn();
|
|
265
|
+
client.on("connection:reconnecting", reconnectingHandler);
|
|
266
|
+
|
|
267
|
+
// Simulate disconnect
|
|
268
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "close")?.[1];
|
|
269
|
+
closeHandler?.(1006, "Connection lost");
|
|
270
|
+
|
|
271
|
+
// Should emit reconnecting event
|
|
272
|
+
expect(reconnectingHandler).toHaveBeenCalledWith(1);
|
|
273
|
+
|
|
274
|
+
// Clear previous mock
|
|
275
|
+
(WebSocket as any).mockClear();
|
|
276
|
+
|
|
277
|
+
// Advance timer to trigger reconnection
|
|
278
|
+
vi.advanceTimersByTime(mockConfig.reconnectDelay!);
|
|
279
|
+
|
|
280
|
+
// Should create new WebSocket
|
|
281
|
+
expect(WebSocket).toHaveBeenCalledWith(mockConfig.wsUrl);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it.skip("should use exponential backoff for reconnection", async () => {
|
|
285
|
+
// Simulate multiple failed reconnection attempts
|
|
286
|
+
for (let i = 1; i <= 3; i++) {
|
|
287
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "close")?.[1];
|
|
288
|
+
closeHandler?.(1006, "Connection lost");
|
|
289
|
+
|
|
290
|
+
(WebSocket as any).mockClear();
|
|
291
|
+
|
|
292
|
+
// Calculate expected delay with exponential backoff
|
|
293
|
+
const expectedDelay = mockConfig.reconnectDelay! * Math.pow(2, i - 1);
|
|
294
|
+
vi.advanceTimersByTime(expectedDelay);
|
|
295
|
+
|
|
296
|
+
if (i < 3) {
|
|
297
|
+
expect(WebSocket).toHaveBeenCalled();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it.skip("should stop reconnecting after max attempts", async () => {
|
|
303
|
+
const errorHandler = vi.fn();
|
|
304
|
+
client.on("error", errorHandler);
|
|
305
|
+
|
|
306
|
+
// Simulate max failed attempts
|
|
307
|
+
for (let i = 1; i <= mockConfig.maxReconnectAttempts! + 1; i++) {
|
|
308
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "close")?.[1];
|
|
309
|
+
closeHandler?.(1006, "Connection lost");
|
|
310
|
+
|
|
311
|
+
if (i <= mockConfig.maxReconnectAttempts!) {
|
|
312
|
+
vi.advanceTimersByTime(mockConfig.reconnectDelay! * Math.pow(2, i - 1));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Should emit error after max attempts
|
|
317
|
+
expect(errorHandler).toHaveBeenCalledWith(
|
|
318
|
+
expect.objectContaining({
|
|
319
|
+
message: expect.stringContaining("Max reconnection attempts")
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it.skip("should not reconnect if reconnect is false", () => {
|
|
325
|
+
const noReconnectConfig = { ...mockConfig, reconnect: false };
|
|
326
|
+
const noReconnectClient = new WebSocketClient(noReconnectConfig);
|
|
327
|
+
|
|
328
|
+
// Connect
|
|
329
|
+
noReconnectClient.connect();
|
|
330
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
331
|
+
openHandler?.();
|
|
332
|
+
|
|
333
|
+
(WebSocket as any).mockClear();
|
|
334
|
+
|
|
335
|
+
// Simulate disconnect
|
|
336
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "close")?.[1];
|
|
337
|
+
closeHandler?.(1000, "Normal closure");
|
|
338
|
+
|
|
339
|
+
// Advance timers
|
|
340
|
+
vi.advanceTimersByTime(10000);
|
|
341
|
+
|
|
342
|
+
// Should not attempt reconnection
|
|
343
|
+
expect(WebSocket).not.toHaveBeenCalled();
|
|
344
|
+
|
|
345
|
+
noReconnectClient.disconnect();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe("message handling", () => {
|
|
350
|
+
beforeEach(async () => {
|
|
351
|
+
const connectPromise = client.connect();
|
|
352
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
353
|
+
if (openHandler) {
|
|
354
|
+
simulateAuth(client);
|
|
355
|
+
await openHandler();
|
|
356
|
+
}
|
|
357
|
+
await connectPromise;
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should handle and validate incoming messages", () => {
|
|
361
|
+
const messageHandler = vi.fn();
|
|
362
|
+
client.on("message:received", messageHandler);
|
|
363
|
+
|
|
364
|
+
const wsMessageHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "message")?.[1];
|
|
365
|
+
|
|
366
|
+
const validMessage = JSON.stringify({
|
|
367
|
+
type: "task_response",
|
|
368
|
+
content: "test response",
|
|
369
|
+
from: "agent-1",
|
|
370
|
+
content_type: "text/plain",
|
|
371
|
+
data: {
|
|
372
|
+
task_id: "task-123",
|
|
373
|
+
success: true
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
wsMessageHandler?.(Buffer.from(validMessage));
|
|
378
|
+
|
|
379
|
+
expect(messageHandler).toHaveBeenCalledWith(
|
|
380
|
+
expect.objectContaining({
|
|
381
|
+
type: "task_response",
|
|
382
|
+
content: "test response"
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should handle invalid messages", () => {
|
|
388
|
+
const errorHandler = vi.fn();
|
|
389
|
+
client.on("message:error", errorHandler);
|
|
390
|
+
|
|
391
|
+
const wsMessageHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "message")?.[1];
|
|
392
|
+
|
|
393
|
+
// Invalid JSON
|
|
394
|
+
wsMessageHandler?.(Buffer.from("invalid json"));
|
|
395
|
+
|
|
396
|
+
expect(errorHandler).toHaveBeenCalled();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it.skip("should emit typed events for specific messages", () => {
|
|
400
|
+
const agentSelectedHandler = vi.fn();
|
|
401
|
+
const agentResponseHandler = vi.fn();
|
|
402
|
+
|
|
403
|
+
client.on("agent:selected", agentSelectedHandler);
|
|
404
|
+
client.on("agent:response", agentResponseHandler);
|
|
405
|
+
|
|
406
|
+
const wsMessageHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "message")?.[1];
|
|
407
|
+
|
|
408
|
+
// Agent selected message
|
|
409
|
+
wsMessageHandler?.(
|
|
410
|
+
Buffer.from(
|
|
411
|
+
JSON.stringify({
|
|
412
|
+
type: "agent_selected",
|
|
413
|
+
content: "selected",
|
|
414
|
+
from: "coordinator",
|
|
415
|
+
reasoning: "Best match",
|
|
416
|
+
data: {
|
|
417
|
+
agent_id: "agent-1",
|
|
418
|
+
agent_name: "Test Agent",
|
|
419
|
+
user_request: "help"
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
expect(agentSelectedHandler).toHaveBeenCalled();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe("disconnect", () => {
|
|
430
|
+
beforeEach(async () => {
|
|
431
|
+
const connectPromise = client.connect();
|
|
432
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
433
|
+
if (openHandler) {
|
|
434
|
+
simulateAuth(client);
|
|
435
|
+
await openHandler();
|
|
436
|
+
}
|
|
437
|
+
await connectPromise;
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it.skip("should close WebSocket connection", () => {
|
|
441
|
+
client.disconnect();
|
|
442
|
+
|
|
443
|
+
expect(mockWs.close).toHaveBeenCalledWith(1000, "Client disconnect");
|
|
444
|
+
expect(client.isConnected).toBe(false);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should clear intervals", () => {
|
|
448
|
+
const clearIntervalSpy = vi.spyOn(global, "clearInterval");
|
|
449
|
+
|
|
450
|
+
client.disconnect();
|
|
451
|
+
|
|
452
|
+
expect(clearIntervalSpy).toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("should emit disconnect event", () => {
|
|
456
|
+
const disconnectHandler = vi.fn();
|
|
457
|
+
client.on("disconnect", disconnectHandler);
|
|
458
|
+
|
|
459
|
+
client.disconnect();
|
|
460
|
+
|
|
461
|
+
expect(disconnectHandler).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it.skip("should handle multiple disconnect calls", () => {
|
|
465
|
+
client.disconnect();
|
|
466
|
+
client.disconnect();
|
|
467
|
+
client.disconnect();
|
|
468
|
+
|
|
469
|
+
expect(mockWs.close).toHaveBeenCalledTimes(1);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe("getConnectionState", () => {
|
|
474
|
+
it("should return current connection state", () => {
|
|
475
|
+
const state = client.getConnectionState();
|
|
476
|
+
|
|
477
|
+
expect(state).toEqual(
|
|
478
|
+
expect.objectContaining({
|
|
479
|
+
connected: false,
|
|
480
|
+
authenticated: false,
|
|
481
|
+
reconnecting: false,
|
|
482
|
+
reconnectAttempts: 0
|
|
483
|
+
})
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("should update state after connection", async () => {
|
|
488
|
+
const connectPromise = client.connect();
|
|
489
|
+
const openHandler = mockWs.on.mock.calls.find((call: any) => call[0] === "open")?.[1];
|
|
490
|
+
if (openHandler) {
|
|
491
|
+
simulateAuth(client);
|
|
492
|
+
await openHandler();
|
|
493
|
+
}
|
|
494
|
+
await connectPromise;
|
|
495
|
+
|
|
496
|
+
const state = client.getConnectionState();
|
|
497
|
+
expect(state.connected).toBe(true);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe("getAuthState", () => {
|
|
502
|
+
it("should return current auth state", () => {
|
|
503
|
+
const state = client.getAuthState();
|
|
504
|
+
|
|
505
|
+
expect(state).toEqual(
|
|
506
|
+
expect.objectContaining({
|
|
507
|
+
authenticated: false
|
|
508
|
+
})
|
|
509
|
+
);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
});
|