@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.
Files changed (281) hide show
  1. package/.dockerignore +14 -0
  2. package/.env.test.example +14 -0
  3. package/.eslintrc.json +26 -0
  4. package/.github/workflows/claude-code-review.yml +78 -0
  5. package/.github/workflows/claude-reviewer.yml +64 -0
  6. package/.github/workflows/publish-npm.yml +38 -0
  7. package/.github/workflows/push-to-main.yml +23 -0
  8. package/.node-version +1 -0
  9. package/.prettierrc +11 -0
  10. package/Dockerfile +25 -0
  11. package/LICENCE +661 -0
  12. package/README.md +709 -0
  13. package/dist/constants.d.ts +42 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +45 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/core/websocket-client.d.ts +261 -0
  18. package/dist/core/websocket-client.d.ts.map +1 -0
  19. package/dist/core/websocket-client.js +875 -0
  20. package/dist/core/websocket-client.js.map +1 -0
  21. package/dist/formatters/response-formatter.d.ts +354 -0
  22. package/dist/formatters/response-formatter.d.ts.map +1 -0
  23. package/dist/formatters/response-formatter.js +575 -0
  24. package/dist/formatters/response-formatter.js.map +1 -0
  25. package/dist/handlers/message-handler-registry.d.ts +155 -0
  26. package/dist/handlers/message-handler-registry.d.ts.map +1 -0
  27. package/dist/handlers/message-handler-registry.js +216 -0
  28. package/dist/handlers/message-handler-registry.js.map +1 -0
  29. package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
  30. package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
  31. package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
  32. package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
  33. package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
  34. package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
  35. package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
  36. package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
  37. package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
  38. package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
  39. package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
  40. package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
  41. package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
  42. package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
  43. package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
  44. package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
  45. package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
  46. package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
  47. package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
  48. package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
  49. package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
  50. package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
  51. package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
  52. package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
  53. package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
  54. package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
  55. package/dist/handlers/message-handlers/base-handler.js +83 -0
  56. package/dist/handlers/message-handlers/base-handler.js.map +1 -0
  57. package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
  58. package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
  59. package/dist/handlers/message-handlers/challenge-handler.js +47 -0
  60. package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
  61. package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
  62. package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
  63. package/dist/handlers/message-handlers/error-message-handler.js +29 -0
  64. package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
  65. package/dist/handlers/message-handlers/index.d.ts +28 -0
  66. package/dist/handlers/message-handlers/index.d.ts.map +1 -0
  67. package/dist/handlers/message-handlers/index.js +100 -0
  68. package/dist/handlers/message-handlers/index.js.map +1 -0
  69. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
  70. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
  71. package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
  72. package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
  73. package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
  74. package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
  75. package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
  76. package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
  77. package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
  78. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
  79. package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
  80. package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
  81. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
  82. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
  83. package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
  84. package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
  85. package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
  86. package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
  87. package/dist/handlers/message-handlers/task-response-handler.js +44 -0
  88. package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
  89. package/dist/handlers/message-handlers/types.d.ts +51 -0
  90. package/dist/handlers/message-handlers/types.d.ts.map +1 -0
  91. package/dist/handlers/message-handlers/types.js +7 -0
  92. package/dist/handlers/message-handlers/types.js.map +1 -0
  93. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
  94. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
  95. package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
  96. package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
  97. package/dist/handlers/webhook-handler.d.ts +202 -0
  98. package/dist/handlers/webhook-handler.d.ts.map +1 -0
  99. package/dist/handlers/webhook-handler.js +511 -0
  100. package/dist/handlers/webhook-handler.js.map +1 -0
  101. package/dist/index.d.ts +71 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +217 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/managers/agent-registry.d.ts +173 -0
  106. package/dist/managers/agent-registry.d.ts.map +1 -0
  107. package/dist/managers/agent-registry.js +310 -0
  108. package/dist/managers/agent-registry.js.map +1 -0
  109. package/dist/managers/connection-manager.d.ts +134 -0
  110. package/dist/managers/connection-manager.d.ts.map +1 -0
  111. package/dist/managers/connection-manager.js +176 -0
  112. package/dist/managers/connection-manager.js.map +1 -0
  113. package/dist/managers/index.d.ts +9 -0
  114. package/dist/managers/index.d.ts.map +1 -0
  115. package/dist/managers/index.js +16 -0
  116. package/dist/managers/index.js.map +1 -0
  117. package/dist/managers/message-router.d.ts +112 -0
  118. package/dist/managers/message-router.d.ts.map +1 -0
  119. package/dist/managers/message-router.js +260 -0
  120. package/dist/managers/message-router.js.map +1 -0
  121. package/dist/managers/room-manager.d.ts +165 -0
  122. package/dist/managers/room-manager.d.ts.map +1 -0
  123. package/dist/managers/room-manager.js +227 -0
  124. package/dist/managers/room-manager.js.map +1 -0
  125. package/dist/teneo-sdk.d.ts +703 -0
  126. package/dist/teneo-sdk.d.ts.map +1 -0
  127. package/dist/teneo-sdk.js +907 -0
  128. package/dist/teneo-sdk.js.map +1 -0
  129. package/dist/types/config.d.ts +1047 -0
  130. package/dist/types/config.d.ts.map +1 -0
  131. package/dist/types/config.js +720 -0
  132. package/dist/types/config.js.map +1 -0
  133. package/dist/types/error-codes.d.ts +29 -0
  134. package/dist/types/error-codes.d.ts.map +1 -0
  135. package/dist/types/error-codes.js +41 -0
  136. package/dist/types/error-codes.js.map +1 -0
  137. package/dist/types/events.d.ts +616 -0
  138. package/dist/types/events.d.ts.map +1 -0
  139. package/dist/types/events.js +261 -0
  140. package/dist/types/events.js.map +1 -0
  141. package/dist/types/health.d.ts +40 -0
  142. package/dist/types/health.d.ts.map +1 -0
  143. package/dist/types/health.js +6 -0
  144. package/dist/types/health.js.map +1 -0
  145. package/dist/types/index.d.ts +10 -0
  146. package/dist/types/index.d.ts.map +1 -0
  147. package/dist/types/index.js +123 -0
  148. package/dist/types/index.js.map +1 -0
  149. package/dist/types/messages.d.ts +3734 -0
  150. package/dist/types/messages.d.ts.map +1 -0
  151. package/dist/types/messages.js +482 -0
  152. package/dist/types/messages.js.map +1 -0
  153. package/dist/types/validation.d.ts +81 -0
  154. package/dist/types/validation.d.ts.map +1 -0
  155. package/dist/types/validation.js +115 -0
  156. package/dist/types/validation.js.map +1 -0
  157. package/dist/utils/bounded-queue.d.ts +127 -0
  158. package/dist/utils/bounded-queue.d.ts.map +1 -0
  159. package/dist/utils/bounded-queue.js +181 -0
  160. package/dist/utils/bounded-queue.js.map +1 -0
  161. package/dist/utils/circuit-breaker.d.ts +141 -0
  162. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  163. package/dist/utils/circuit-breaker.js +215 -0
  164. package/dist/utils/circuit-breaker.js.map +1 -0
  165. package/dist/utils/deduplication-cache.d.ts +110 -0
  166. package/dist/utils/deduplication-cache.d.ts.map +1 -0
  167. package/dist/utils/deduplication-cache.js +177 -0
  168. package/dist/utils/deduplication-cache.js.map +1 -0
  169. package/dist/utils/event-waiter.d.ts +101 -0
  170. package/dist/utils/event-waiter.d.ts.map +1 -0
  171. package/dist/utils/event-waiter.js +118 -0
  172. package/dist/utils/event-waiter.js.map +1 -0
  173. package/dist/utils/index.d.ts +51 -0
  174. package/dist/utils/index.d.ts.map +1 -0
  175. package/dist/utils/index.js +72 -0
  176. package/dist/utils/index.js.map +1 -0
  177. package/dist/utils/logger.d.ts +22 -0
  178. package/dist/utils/logger.d.ts.map +1 -0
  179. package/dist/utils/logger.js +91 -0
  180. package/dist/utils/logger.js.map +1 -0
  181. package/dist/utils/rate-limiter.d.ts +122 -0
  182. package/dist/utils/rate-limiter.d.ts.map +1 -0
  183. package/dist/utils/rate-limiter.js +190 -0
  184. package/dist/utils/rate-limiter.js.map +1 -0
  185. package/dist/utils/retry-policy.d.ts +191 -0
  186. package/dist/utils/retry-policy.d.ts.map +1 -0
  187. package/dist/utils/retry-policy.js +225 -0
  188. package/dist/utils/retry-policy.js.map +1 -0
  189. package/dist/utils/secure-private-key.d.ts +113 -0
  190. package/dist/utils/secure-private-key.d.ts.map +1 -0
  191. package/dist/utils/secure-private-key.js +188 -0
  192. package/dist/utils/secure-private-key.js.map +1 -0
  193. package/dist/utils/signature-verifier.d.ts +143 -0
  194. package/dist/utils/signature-verifier.d.ts.map +1 -0
  195. package/dist/utils/signature-verifier.js +238 -0
  196. package/dist/utils/signature-verifier.js.map +1 -0
  197. package/dist/utils/ssrf-validator.d.ts +36 -0
  198. package/dist/utils/ssrf-validator.d.ts.map +1 -0
  199. package/dist/utils/ssrf-validator.js +195 -0
  200. package/dist/utils/ssrf-validator.js.map +1 -0
  201. package/examples/.env.example +17 -0
  202. package/examples/basic-usage.ts +211 -0
  203. package/examples/production-dashboard/.env.example +153 -0
  204. package/examples/production-dashboard/package.json +39 -0
  205. package/examples/production-dashboard/public/dashboard.html +642 -0
  206. package/examples/production-dashboard/server.ts +753 -0
  207. package/examples/webhook-integration.ts +239 -0
  208. package/examples/x-influencer-battle-redesign.html +1065 -0
  209. package/examples/x-influencer-battle-server.ts +217 -0
  210. package/examples/x-influencer-battle.html +787 -0
  211. package/package.json +65 -0
  212. package/src/constants.ts +43 -0
  213. package/src/core/websocket-client.test.ts +512 -0
  214. package/src/core/websocket-client.ts +1056 -0
  215. package/src/formatters/response-formatter.test.ts +571 -0
  216. package/src/formatters/response-formatter.ts +677 -0
  217. package/src/handlers/message-handler-registry.ts +239 -0
  218. package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
  219. package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
  220. package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
  221. package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
  222. package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
  223. package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
  224. package/src/handlers/message-handlers/base-handler.ts +101 -0
  225. package/src/handlers/message-handlers/challenge-handler.ts +57 -0
  226. package/src/handlers/message-handlers/error-message-handler.ts +27 -0
  227. package/src/handlers/message-handlers/index.ts +77 -0
  228. package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
  229. package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
  230. package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
  231. package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
  232. package/src/handlers/message-handlers/task-response-handler.ts +45 -0
  233. package/src/handlers/message-handlers/types.ts +77 -0
  234. package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
  235. package/src/handlers/webhook-handler.test.ts +789 -0
  236. package/src/handlers/webhook-handler.ts +576 -0
  237. package/src/index.ts +269 -0
  238. package/src/managers/agent-registry.test.ts +466 -0
  239. package/src/managers/agent-registry.ts +347 -0
  240. package/src/managers/connection-manager.ts +195 -0
  241. package/src/managers/index.ts +9 -0
  242. package/src/managers/message-router.ts +349 -0
  243. package/src/managers/room-manager.ts +248 -0
  244. package/src/teneo-sdk.ts +1022 -0
  245. package/src/types/config.test.ts +325 -0
  246. package/src/types/config.ts +799 -0
  247. package/src/types/error-codes.ts +44 -0
  248. package/src/types/events.test.ts +302 -0
  249. package/src/types/events.ts +382 -0
  250. package/src/types/health.ts +46 -0
  251. package/src/types/index.ts +199 -0
  252. package/src/types/messages.test.ts +660 -0
  253. package/src/types/messages.ts +570 -0
  254. package/src/types/validation.ts +123 -0
  255. package/src/utils/bounded-queue.test.ts +356 -0
  256. package/src/utils/bounded-queue.ts +205 -0
  257. package/src/utils/circuit-breaker.test.ts +394 -0
  258. package/src/utils/circuit-breaker.ts +262 -0
  259. package/src/utils/deduplication-cache.test.ts +380 -0
  260. package/src/utils/deduplication-cache.ts +198 -0
  261. package/src/utils/event-waiter.test.ts +381 -0
  262. package/src/utils/event-waiter.ts +172 -0
  263. package/src/utils/index.ts +74 -0
  264. package/src/utils/logger.ts +87 -0
  265. package/src/utils/rate-limiter.test.ts +341 -0
  266. package/src/utils/rate-limiter.ts +211 -0
  267. package/src/utils/retry-policy.test.ts +558 -0
  268. package/src/utils/retry-policy.ts +272 -0
  269. package/src/utils/secure-private-key.test.ts +356 -0
  270. package/src/utils/secure-private-key.ts +205 -0
  271. package/src/utils/signature-verifier.test.ts +464 -0
  272. package/src/utils/signature-verifier.ts +298 -0
  273. package/src/utils/ssrf-validator.test.ts +372 -0
  274. package/src/utils/ssrf-validator.ts +224 -0
  275. package/tests/integration/real-server.test.ts +740 -0
  276. package/tests/integration/websocket.test.ts +381 -0
  277. package/tests/integration-setup.ts +16 -0
  278. package/tests/setup.ts +34 -0
  279. package/tsconfig.json +32 -0
  280. package/vitest.config.ts +42 -0
  281. package/vitest.integration.config.ts +23 -0
@@ -0,0 +1,875 @@
1
+ "use strict";
2
+ /**
3
+ * WebSocket client implementation for Teneo Protocol SDK
4
+ * Handles connection, authentication, and message management with Zod validation
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.WebSocketClient = void 0;
11
+ const ws_1 = __importDefault(require("ws"));
12
+ const eventemitter3_1 = require("eventemitter3");
13
+ const accounts_1 = require("viem/accounts");
14
+ const uuid_1 = require("uuid");
15
+ const zod_1 = require("zod");
16
+ const types_1 = require("../types");
17
+ const events_1 = require("../types/events");
18
+ const constants_1 = require("../constants");
19
+ const message_handler_registry_1 = require("../handlers/message-handler-registry");
20
+ const message_handlers_1 = require("../handlers/message-handlers");
21
+ const logger_1 = require("../utils/logger");
22
+ const rate_limiter_1 = require("../utils/rate-limiter");
23
+ const signature_verifier_1 = require("../utils/signature-verifier");
24
+ const secure_private_key_1 = require("../utils/secure-private-key");
25
+ const retry_policy_1 = require("../utils/retry-policy");
26
+ const deduplication_cache_1 = require("../utils/deduplication-cache");
27
+ class WebSocketClient extends eventemitter3_1.EventEmitter {
28
+ constructor(config) {
29
+ super();
30
+ this.ownsSecureKey = false; // Track if we created the SecurePrivateKey
31
+ this.connectionState = {
32
+ connected: false,
33
+ authenticated: false,
34
+ reconnecting: false,
35
+ reconnectAttempts: 0
36
+ };
37
+ this.authState = {
38
+ authenticated: false
39
+ };
40
+ this.messageQueue = [];
41
+ this.pendingMessages = new Map();
42
+ // Validate configuration with Zod
43
+ this.config = types_1.SDKConfigSchema.parse(config);
44
+ this.logger = this.config.logger || this.createDefaultLogger();
45
+ // Initialize message handler registry
46
+ this.handlerRegistry = new message_handler_registry_1.MessageHandlerRegistry(this.logger);
47
+ if (config.privateKey) {
48
+ try {
49
+ // Check if privateKey is already a SecurePrivateKey instance (SEC-3)
50
+ if (typeof config.privateKey === 'object' && 'use' in config.privateKey) {
51
+ // Use the provided SecurePrivateKey directly
52
+ this.secureKey = config.privateKey;
53
+ this.ownsSecureKey = false; // User provided it, we don't own it
54
+ // Create account using the secure key
55
+ this.account = this.secureKey.use((key) => (0, accounts_1.privateKeyToAccount)(key));
56
+ }
57
+ else {
58
+ // privateKey is a plain string - encrypt it immediately
59
+ const privateKeyString = config.privateKey;
60
+ // Ensure the private key starts with 0x
61
+ const privateKey = privateKeyString.startsWith("0x")
62
+ ? privateKeyString
63
+ : `0x${privateKeyString}`;
64
+ // Encrypt the private key immediately (SEC-3)
65
+ this.secureKey = new secure_private_key_1.SecurePrivateKey(privateKey);
66
+ this.ownsSecureKey = true; // We created it, we own it
67
+ // Create account using the secure key
68
+ this.account = this.secureKey.use((key) => (0, accounts_1.privateKeyToAccount)(key));
69
+ }
70
+ if (config.walletAddress &&
71
+ this.account.address.toLowerCase() !== config.walletAddress.toLowerCase()) {
72
+ throw new Error("Private key does not match provided wallet address");
73
+ }
74
+ // Remove plaintext private key from config to prevent exposure
75
+ // Note: We modify a copy to avoid mutating the original config object
76
+ this.config = { ...config, privateKey: undefined };
77
+ }
78
+ catch (error) {
79
+ // Clean up secure key if initialization fails (only if we created it)
80
+ if (this.secureKey && this.ownsSecureKey) {
81
+ this.secureKey.destroy();
82
+ this.secureKey = undefined;
83
+ }
84
+ throw new events_1.AuthenticationError("Invalid private key", error);
85
+ }
86
+ }
87
+ // Register all default message handlers
88
+ this.handlerRegistry.registerAll((0, message_handlers_1.getDefaultHandlers)(config.clientType || "user"));
89
+ // Initialize rate limiter if configured (CB-2)
90
+ if (this.config.maxMessagesPerSecond) {
91
+ // Burst capacity = 2x rate (allows temporary spikes)
92
+ const burstCapacity = this.config.maxMessagesPerSecond * 2;
93
+ this.rateLimiter = new rate_limiter_1.TokenBucketRateLimiter(this.config.maxMessagesPerSecond, burstCapacity);
94
+ this.logger.info("Rate limiter initialized", {
95
+ rate: this.config.maxMessagesPerSecond,
96
+ burst: burstCapacity
97
+ });
98
+ }
99
+ // Initialize signature verifier if configured (SEC-2)
100
+ if (this.config.validateSignatures) {
101
+ this.signatureVerifier = new signature_verifier_1.SignatureVerifier({
102
+ trustedAddresses: this.config.trustedAgentAddresses,
103
+ requireSignaturesFor: this.config.requireSignaturesFor,
104
+ strictMode: this.config.strictSignatureValidation
105
+ });
106
+ this.logger.info("Signature verifier initialized", {
107
+ strictMode: this.config.strictSignatureValidation,
108
+ trustedAddressCount: this.config.trustedAgentAddresses?.length || 0,
109
+ requiredTypes: this.config.requireSignaturesFor?.length || 0
110
+ });
111
+ }
112
+ // Initialize reconnection retry policy (REL-3)
113
+ if (this.config.reconnectStrategy) {
114
+ // User provided custom strategy
115
+ this.reconnectPolicy = new retry_policy_1.RetryPolicy(this.config.reconnectStrategy);
116
+ this.logger.info("Custom reconnection strategy configured", {
117
+ type: this.config.reconnectStrategy.type,
118
+ baseDelay: this.config.reconnectStrategy.baseDelay,
119
+ maxDelay: this.config.reconnectStrategy.maxDelay,
120
+ maxAttempts: this.config.reconnectStrategy.maxAttempts
121
+ });
122
+ }
123
+ else {
124
+ // Use default exponential backoff matching previous hardcoded behavior
125
+ this.reconnectPolicy = retry_policy_1.RetryPolicy.exponential(this.config.reconnectDelay || 5000, constants_1.RETRY.MAX_RECONNECT_DELAY, this.config.maxReconnectAttempts || 10, true // jitter enabled by default
126
+ );
127
+ }
128
+ // Initialize message deduplication cache if configured (CB-4)
129
+ if (this.config.enableMessageDeduplication !== false) {
130
+ // Default to enabled if not explicitly disabled
131
+ const ttl = this.config.messageDedupeTtl || 60000; // 1 minute default
132
+ const maxSize = this.config.messageDedupMaxSize || 10000; // 10k default
133
+ this.deduplicationCache = new deduplication_cache_1.DeduplicationCache(ttl, maxSize);
134
+ this.logger.info("Message deduplication enabled", {
135
+ ttl,
136
+ maxSize
137
+ });
138
+ }
139
+ }
140
+ /**
141
+ * Set the RoomManager instance for handler context
142
+ * Called by TeneoSDK after initialization
143
+ */
144
+ setRoomManager(roomManager) {
145
+ this.roomManager = roomManager;
146
+ }
147
+ /**
148
+ * Establishes a WebSocket connection to the Teneo server.
149
+ * Handles connection timeout, authentication challenge-response flow,
150
+ * and automatic message queue processing after successful connection.
151
+ * Emits 'connection:open', 'auth:challenge', 'auth:success', and 'ready' events.
152
+ *
153
+ * @returns Promise that resolves when connection and authentication are complete
154
+ * @throws {TimeoutError} If connection times out (default: 30 seconds)
155
+ * @throws {ConnectionError} If WebSocket connection fails
156
+ * @throws {AuthenticationError} If authentication fails or times out
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const wsClient = new WebSocketClient(config);
161
+ * await wsClient.connect();
162
+ * console.log('Connected and authenticated');
163
+ * ```
164
+ */
165
+ async connect() {
166
+ return new Promise((resolve, reject) => {
167
+ this.logger.info("Connecting to WebSocket server", {
168
+ url: this.config.wsUrl
169
+ });
170
+ // Clear any existing connection
171
+ this.disconnect();
172
+ // Build connection URL with webhook parameter
173
+ let url = this.config.wsUrl;
174
+ if (this.config.webhookUrl) {
175
+ const separator = url.includes("?") ? "&" : "?";
176
+ url += `${separator}webhookUrl=${encodeURIComponent(this.config.webhookUrl)}`;
177
+ }
178
+ // Create WebSocket connection
179
+ this.ws = new ws_1.default(url, {
180
+ headers: this.config.webhookHeaders,
181
+ handshakeTimeout: this.config.connectionTimeout || constants_1.TIMEOUTS.CONNECTION_TIMEOUT,
182
+ maxPayload: this.config.maxMessageSize || constants_1.LIMITS.MAX_MESSAGE_SIZE
183
+ });
184
+ // Set connection timeout
185
+ this.connectionTimer = setTimeout(() => {
186
+ this.ws?.close();
187
+ reject(new events_1.TimeoutError("Connection timeout", { url }));
188
+ }, this.config.connectionTimeout || constants_1.TIMEOUTS.CONNECTION_TIMEOUT);
189
+ // Handle connection open
190
+ this.ws.on("open", async () => {
191
+ clearTimeout(this.connectionTimer);
192
+ this.logger.info("WebSocket connection established");
193
+ this.updateConnectionState({
194
+ connected: true,
195
+ reconnecting: false,
196
+ reconnectAttempts: 0,
197
+ lastConnectedAt: new Date()
198
+ });
199
+ this.emit("connection:open");
200
+ this.startPingInterval();
201
+ try {
202
+ await this.authenticate();
203
+ this.processMessageQueue();
204
+ resolve();
205
+ }
206
+ catch (error) {
207
+ reject(error);
208
+ }
209
+ });
210
+ // Handle messages
211
+ this.ws.on("message", (data) => {
212
+ try {
213
+ const rawMessage = JSON.parse(data.toString());
214
+ // Validate message with Zod
215
+ const parseResult = (0, types_1.safeParseMessage)(rawMessage);
216
+ if (parseResult.success) {
217
+ this.handleMessage(parseResult.data);
218
+ }
219
+ else {
220
+ this.logger.error("Invalid message format", parseResult.error);
221
+ this.emit("message:error", new events_1.ValidationError("Invalid message format", parseResult.error), rawMessage);
222
+ }
223
+ }
224
+ catch (error) {
225
+ this.logger.error("Failed to parse message", error);
226
+ this.emit("message:error", new events_1.MessageError("Failed to parse message", error));
227
+ }
228
+ });
229
+ // Handle errors
230
+ this.ws.on("error", (error) => {
231
+ clearTimeout(this.connectionTimer);
232
+ this.logger.error("WebSocket error", error);
233
+ this.emit("connection:error", error);
234
+ if (!this.connectionState.connected) {
235
+ reject(new events_1.ConnectionError("Failed to connect", error));
236
+ }
237
+ });
238
+ // Handle close
239
+ this.ws.on("close", (code, reason) => {
240
+ clearTimeout(this.connectionTimer);
241
+ this.logger.info("WebSocket connection closed", {
242
+ code,
243
+ reason: reason.toString()
244
+ });
245
+ this.updateConnectionState({
246
+ connected: false,
247
+ authenticated: false,
248
+ lastDisconnectedAt: new Date(),
249
+ lastError: new Error(`Connection closed: ${reason}`)
250
+ });
251
+ this.updateAuthState({ authenticated: false });
252
+ this.emit("connection:close", code, reason.toString());
253
+ this.stopPingInterval();
254
+ this.handleReconnection();
255
+ });
256
+ // Handle pong
257
+ this.ws.on("pong", () => {
258
+ this.logger.debug("Received pong");
259
+ });
260
+ });
261
+ }
262
+ /**
263
+ * Disconnects from the WebSocket server and cleans up all resources.
264
+ * Stops reconnection attempts, clears all timers, rejects pending messages,
265
+ * and updates connection state. Emits 'disconnect' event.
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * wsClient.disconnect();
270
+ * console.log('Disconnected from server');
271
+ * ```
272
+ */
273
+ disconnect() {
274
+ this.logger.info("Disconnecting from WebSocket server");
275
+ // Clear all timers
276
+ if (this.reconnectTimer) {
277
+ clearTimeout(this.reconnectTimer);
278
+ this.reconnectTimer = undefined;
279
+ }
280
+ if (this.pingTimer) {
281
+ clearInterval(this.pingTimer);
282
+ this.pingTimer = undefined;
283
+ }
284
+ if (this.connectionTimer) {
285
+ clearTimeout(this.connectionTimer);
286
+ this.connectionTimer = undefined;
287
+ }
288
+ // Clear pending messages
289
+ for (const [, pending] of this.pendingMessages) {
290
+ clearTimeout(pending.timeout);
291
+ pending.reject(new Error("Connection closed"));
292
+ }
293
+ this.pendingMessages.clear();
294
+ // Close WebSocket
295
+ if (this.ws && this.ws.readyState !== ws_1.default.CLOSED) {
296
+ this.ws.close(1000, "Client disconnect");
297
+ this.ws = undefined;
298
+ }
299
+ // Clean up secure key (SEC-3) - only if we created it
300
+ if (this.secureKey && this.ownsSecureKey) {
301
+ this.secureKey.destroy();
302
+ this.secureKey = undefined;
303
+ this.ownsSecureKey = false;
304
+ }
305
+ // Clear deduplication cache (CB-4)
306
+ if (this.deduplicationCache) {
307
+ this.deduplicationCache.clear();
308
+ }
309
+ // Update state
310
+ this.updateConnectionState({
311
+ connected: false,
312
+ authenticated: false,
313
+ reconnecting: false
314
+ });
315
+ this.updateAuthState({ authenticated: false });
316
+ this.emit("disconnect");
317
+ }
318
+ /**
319
+ * Sends a message to the WebSocket server with validation and queueing support.
320
+ * Validates message with Zod schema, enforces size limits, and queues messages
321
+ * during reconnection. Adds timestamp if not present. Emits 'message:sent' event.
322
+ *
323
+ * @param message - The message to send
324
+ * @returns Promise that resolves when message is sent successfully
325
+ * @throws {ValidationError} If message fails Zod validation
326
+ * @throws {MessageError} If message size exceeds limit
327
+ * @throws {ConnectionError} If not connected and reconnection is disabled
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const message = createUserMessage('Hello', 'general', walletAddress);
332
+ * await wsClient.sendMessage(message);
333
+ * console.log('Message sent');
334
+ * ```
335
+ */
336
+ async sendMessage(message) {
337
+ // Validate outgoing message with Zod
338
+ let validatedMessage;
339
+ try {
340
+ validatedMessage = types_1.BaseMessageSchema.parse(message);
341
+ }
342
+ catch (error) {
343
+ this.logger.error("Failed to validate message", error);
344
+ if (error instanceof zod_1.z.ZodError) {
345
+ throw new events_1.ValidationError("Invalid message format", error);
346
+ }
347
+ throw new events_1.MessageError("Failed to validate message", error);
348
+ }
349
+ // Check connection
350
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
351
+ if (this.config.reconnect && this.connectionState.reconnecting) {
352
+ // Queue message if reconnecting
353
+ this.messageQueue.push(validatedMessage);
354
+ this.logger.debug("Message queued for reconnection", {
355
+ type: validatedMessage.type
356
+ });
357
+ return;
358
+ }
359
+ else {
360
+ throw new events_1.ConnectionError("WebSocket is not connected");
361
+ }
362
+ }
363
+ // Add timestamp if not present
364
+ if (!validatedMessage.timestamp) {
365
+ validatedMessage.timestamp = new Date().toISOString();
366
+ }
367
+ // Apply rate limiting if configured (CB-2)
368
+ if (this.rateLimiter) {
369
+ try {
370
+ await this.rateLimiter.consume();
371
+ }
372
+ catch (error) {
373
+ this.logger.warn("Rate limit exceeded, waiting for token", {
374
+ type: validatedMessage.type
375
+ });
376
+ throw new events_1.MessageError("Rate limit exceeded", error);
377
+ }
378
+ }
379
+ // Prepare message data
380
+ const data = JSON.stringify(validatedMessage);
381
+ if (data.length > (this.config.maxMessageSize || constants_1.LIMITS.MAX_MESSAGE_SIZE)) {
382
+ throw new Error("Message size exceeds maximum allowed");
383
+ }
384
+ // Send message
385
+ return new Promise((resolve, reject) => {
386
+ this.ws.send(data, (error) => {
387
+ if (error) {
388
+ this.logger.error("Failed to send message", error);
389
+ reject(error);
390
+ }
391
+ else {
392
+ this.logger.debug("Message sent", { type: validatedMessage.type });
393
+ this.emit("message:sent", validatedMessage);
394
+ resolve();
395
+ }
396
+ });
397
+ });
398
+ }
399
+ /**
400
+ * Sends a message and waits for a response with the same message ID.
401
+ * Implements request-response pattern over WebSocket with timeout support.
402
+ * Message is automatically assigned a unique ID for correlation.
403
+ *
404
+ * @template T - Expected response type
405
+ * @param message - The message to send
406
+ * @param timeout - Optional timeout in milliseconds (default: from config or 60000)
407
+ * @returns Promise that resolves with the response message
408
+ * @throws {TimeoutError} If response is not received within timeout
409
+ * @throws {ValidationError} If message fails validation
410
+ * @throws {MessageError} If message sending fails
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const requestMessage = createRequestChallenge('user', walletAddress);
415
+ * const response = await wsClient.sendMessageWithResponse(requestMessage, 30000);
416
+ * console.log('Response received:', response);
417
+ * ```
418
+ */
419
+ async sendMessageWithResponse(message, timeout) {
420
+ return new Promise((resolve, reject) => {
421
+ const messageId = (0, uuid_1.v4)();
422
+ const messageWithId = { ...message, id: messageId };
423
+ // Set timeout
424
+ const timeoutMs = timeout || this.config.messageTimeout || constants_1.TIMEOUTS.DEFAULT_MESSAGE_TIMEOUT;
425
+ const timeoutHandle = setTimeout(() => {
426
+ this.pendingMessages.delete(messageId);
427
+ reject(new events_1.TimeoutError(`Message timeout after ${timeoutMs}ms`, {
428
+ messageId
429
+ }));
430
+ }, timeoutMs);
431
+ // Store pending message
432
+ this.pendingMessages.set(messageId, {
433
+ resolve,
434
+ reject,
435
+ timeout: timeoutHandle
436
+ });
437
+ // Send message
438
+ this.sendMessage(messageWithId).catch((error) => {
439
+ this.pendingMessages.delete(messageId);
440
+ clearTimeout(timeoutHandle);
441
+ reject(error);
442
+ });
443
+ });
444
+ }
445
+ /**
446
+ * Authenticate with the server
447
+ */
448
+ async authenticate() {
449
+ if (!this.account && !this.config.walletAddress) {
450
+ this.logger.info("No authentication configured, continuing without auth");
451
+ this.updateAuthState({ authenticated: false });
452
+ this.emit("ready");
453
+ return;
454
+ }
455
+ try {
456
+ // Check for cached authentication first
457
+ if (this.config.walletAddress) {
458
+ this.logger.debug("Checking cached authentication");
459
+ await this.sendMessage((0, types_1.createCheckCachedAuth)(this.config.walletAddress));
460
+ // Wait briefly for cached auth response
461
+ await new Promise((resolve) => setTimeout(resolve, constants_1.TIMEOUTS.CACHED_AUTH_WAIT));
462
+ if (this.authState.authenticated) {
463
+ this.logger.info("Using cached authentication");
464
+ return;
465
+ }
466
+ }
467
+ // Request challenge
468
+ this.logger.debug("Requesting authentication challenge");
469
+ await this.sendMessage((0, types_1.createRequestChallenge)(this.config.clientType || "user", this.account?.address || this.config.walletAddress));
470
+ // Wait for authentication to complete
471
+ await new Promise((resolve, reject) => {
472
+ let timeout;
473
+ const pollTimeouts = [];
474
+ // Centralized cleanup function - guarantees cleanup in all scenarios
475
+ const cleanup = () => {
476
+ if (timeout) {
477
+ clearTimeout(timeout);
478
+ timeout = undefined;
479
+ }
480
+ // Clear all polling timeouts
481
+ pollTimeouts.forEach((t) => clearTimeout(t));
482
+ pollTimeouts.length = 0;
483
+ };
484
+ // Set main authentication timeout
485
+ timeout = setTimeout(() => {
486
+ cleanup();
487
+ reject(new events_1.AuthenticationError("Authentication timeout"));
488
+ }, constants_1.TIMEOUTS.AUTH_TIMEOUT);
489
+ const checkAuth = () => {
490
+ if (this.authState.authenticated) {
491
+ cleanup();
492
+ resolve();
493
+ }
494
+ else if (this.connectionState.lastError) {
495
+ cleanup();
496
+ reject(this.connectionState.lastError);
497
+ }
498
+ else {
499
+ // Store polling timeout for cleanup
500
+ const pollTimeout = setTimeout(checkAuth, constants_1.TIMEOUTS.AUTH_POLL_INTERVAL);
501
+ pollTimeouts.push(pollTimeout);
502
+ }
503
+ };
504
+ checkAuth();
505
+ });
506
+ }
507
+ catch (error) {
508
+ this.logger.error("Authentication failed", error);
509
+ throw new events_1.AuthenticationError("Failed to authenticate", error);
510
+ }
511
+ }
512
+ /**
513
+ * Create handler context with all dependencies
514
+ */
515
+ createHandlerContext() {
516
+ return {
517
+ emit: (event, ...args) => this.emit(event, ...args),
518
+ sendWebhook: async () => {
519
+ // Webhooks are handled by WebhookHandler in TeneoSDK
520
+ // Handlers shouldn't call webhooks directly - they emit events
521
+ // which are then forwarded to webhooks by MessageRouter
522
+ },
523
+ logger: this.logger,
524
+ getConnectionState: () => this.getConnectionState(),
525
+ getAuthState: () => this.getAuthState(),
526
+ updateConnectionState: (update) => this.updateConnectionState(update),
527
+ updateAuthState: (update) => this.updateAuthState(update),
528
+ roomManager: this.roomManager,
529
+ account: this.account,
530
+ sendMessage: (message) => this.sendMessage(message)
531
+ };
532
+ }
533
+ /**
534
+ * Handle incoming messages using the handler registry
535
+ */
536
+ async handleMessage(message) {
537
+ this.logger.debug("Received message", { type: message.type });
538
+ this.emit("message:received", message);
539
+ // Check for duplicate messages (CB-4)
540
+ if (this.deduplicationCache && message.id) {
541
+ if (this.deduplicationCache.has(message.id)) {
542
+ this.logger.debug("Duplicate message detected and skipped", {
543
+ type: message.type,
544
+ id: message.id
545
+ });
546
+ this.emit("message:duplicate", message);
547
+ return;
548
+ }
549
+ // Add to deduplication cache
550
+ this.deduplicationCache.add(message.id);
551
+ }
552
+ // Check for pending message response
553
+ if (message.id && this.pendingMessages.has(message.id)) {
554
+ const pending = this.pendingMessages.get(message.id);
555
+ clearTimeout(pending.timeout);
556
+ this.pendingMessages.delete(message.id);
557
+ pending.resolve(message);
558
+ return;
559
+ }
560
+ // Verify signature if enabled (SEC-2)
561
+ const shouldProcess = await this.verifyMessageSignature(message);
562
+ if (!shouldProcess) {
563
+ this.logger.debug("Message rejected by signature verification", {
564
+ type: message.type
565
+ });
566
+ return;
567
+ }
568
+ // Delegate to handler registry
569
+ const context = this.createHandlerContext();
570
+ this.handlerRegistry.handle(message, context).catch((error) => {
571
+ this.logger.error("Error in message handler", error);
572
+ this.emit("message:error", error, message);
573
+ });
574
+ }
575
+ /**
576
+ * Verify message signature if signature verification is enabled (SEC-2)
577
+ * Returns true if message should be processed, false if it should be rejected
578
+ */
579
+ async verifyMessageSignature(message) {
580
+ // Skip verification if disabled
581
+ if (!this.signatureVerifier) {
582
+ return true;
583
+ }
584
+ try {
585
+ const result = await this.signatureVerifier.verify(message);
586
+ if (result.signatureMissing) {
587
+ // Signature is missing
588
+ const isRequired = this.signatureVerifier.isSignatureRequired(message.type);
589
+ this.emit("signature:missing", message.type, isRequired);
590
+ if (!result.valid) {
591
+ // Signature required but missing - reject message
592
+ this.logger.warn("Message rejected: signature required but missing", {
593
+ type: message.type,
594
+ from: message.from
595
+ });
596
+ const error = new events_1.SignatureVerificationError(`Signature required for message type '${message.type}'`, {
597
+ messageType: message.type,
598
+ reason: "Signature missing"
599
+ });
600
+ this.emit("message:error", error, message);
601
+ return false;
602
+ }
603
+ else {
604
+ // Signature not required - allow message
605
+ this.logger.debug("Message accepted without signature", {
606
+ type: message.type
607
+ });
608
+ return true;
609
+ }
610
+ }
611
+ if (!result.valid) {
612
+ // Signature is invalid
613
+ this.logger.warn("Message rejected: invalid signature", {
614
+ type: message.type,
615
+ from: message.from,
616
+ reason: result.reason
617
+ });
618
+ this.emit("signature:failed", message.type, result.reason || "Invalid signature", result.recoveredAddress);
619
+ const error = new events_1.SignatureVerificationError(`Signature verification failed for message type '${message.type}': ${result.reason}`, {
620
+ messageType: message.type,
621
+ recoveredAddress: result.recoveredAddress,
622
+ reason: result.reason
623
+ });
624
+ this.emit("message:error", error, message);
625
+ return false;
626
+ }
627
+ // Signature is valid
628
+ this.logger.debug("Message signature verified", {
629
+ type: message.type,
630
+ address: result.recoveredAddress,
631
+ isTrusted: result.isTrusted
632
+ });
633
+ this.emit("signature:verified", message.type, result.recoveredAddress);
634
+ return true;
635
+ }
636
+ catch (error) {
637
+ this.logger.error("Signature verification error", error);
638
+ const verificationError = new events_1.SignatureVerificationError(`Signature verification error: ${error instanceof Error ? error.message : String(error)}`, {
639
+ messageType: message.type,
640
+ reason: error instanceof Error ? error.message : String(error)
641
+ });
642
+ this.emit("message:error", verificationError, message);
643
+ return false;
644
+ }
645
+ }
646
+ /**
647
+ * Handle reconnection logic with configurable retry strategy (REL-3)
648
+ */
649
+ handleReconnection() {
650
+ if (!this.config.reconnect || this.connectionState.reconnecting) {
651
+ return;
652
+ }
653
+ // Check if we should retry using the retry policy
654
+ if (!this.reconnectPolicy.shouldRetry(this.connectionState.reconnectAttempts + 1)) {
655
+ this.logger.error("Max reconnection attempts reached");
656
+ this.emit("error", new events_1.ConnectionError("Max reconnection attempts reached"));
657
+ return;
658
+ }
659
+ this.updateConnectionState({
660
+ reconnecting: true,
661
+ reconnectAttempts: this.connectionState.reconnectAttempts + 1
662
+ });
663
+ const delay = this.calculateReconnectDelay();
664
+ this.logger.info(`Reconnecting in ${delay}ms (attempt ${this.connectionState.reconnectAttempts})`);
665
+ this.emit("connection:reconnecting", this.connectionState.reconnectAttempts);
666
+ this.reconnectTimer = setTimeout(async () => {
667
+ try {
668
+ await this.connect();
669
+ this.emit("connection:reconnected");
670
+ }
671
+ catch (error) {
672
+ this.logger.error("Reconnection failed", error);
673
+ this.handleReconnection();
674
+ }
675
+ }, delay);
676
+ }
677
+ /**
678
+ * Calculate reconnection delay using retry policy (REL-3)
679
+ */
680
+ calculateReconnectDelay() {
681
+ return this.reconnectPolicy.calculateDelay(this.connectionState.reconnectAttempts);
682
+ }
683
+ /**
684
+ * Start ping interval to keep connection alive
685
+ */
686
+ startPingInterval() {
687
+ this.stopPingInterval();
688
+ this.pingTimer = setInterval(() => {
689
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
690
+ this.sendMessage((0, types_1.createPing)()).catch((error) => {
691
+ this.logger.error("Failed to send ping", error);
692
+ });
693
+ }
694
+ }, constants_1.TIMEOUTS.PING_INTERVAL);
695
+ }
696
+ /**
697
+ * Stop ping interval
698
+ */
699
+ stopPingInterval() {
700
+ if (this.pingTimer) {
701
+ clearInterval(this.pingTimer);
702
+ this.pingTimer = undefined;
703
+ }
704
+ }
705
+ /**
706
+ * Process queued messages after reconnection
707
+ */
708
+ processMessageQueue() {
709
+ if (this.messageQueue.length === 0) {
710
+ return;
711
+ }
712
+ this.logger.info(`Processing ${this.messageQueue.length} queued messages`);
713
+ const queue = [...this.messageQueue];
714
+ this.messageQueue = [];
715
+ for (const message of queue) {
716
+ this.sendMessage(message).catch((error) => {
717
+ this.logger.error("Failed to send queued message", error);
718
+ this.emit("message:error", error, message);
719
+ });
720
+ }
721
+ }
722
+ /**
723
+ * Update connection state with validation
724
+ */
725
+ updateConnectionState(update) {
726
+ const newState = { ...this.connectionState, ...update };
727
+ // Validate the new state
728
+ this.connectionState = types_1.ConnectionStateSchema.parse(newState);
729
+ this.emit("connection:state", this.connectionState);
730
+ }
731
+ /**
732
+ * Update authentication state with validation
733
+ */
734
+ updateAuthState(update) {
735
+ const newState = { ...this.authState, ...update };
736
+ // Validate the new state
737
+ this.authState = types_1.AuthenticationStateSchema.parse(newState);
738
+ this.emit("auth:state", this.authState);
739
+ }
740
+ /**
741
+ * Create default logger using pino
742
+ */
743
+ createDefaultLogger() {
744
+ return (0, logger_1.createPinoLogger)(this.config.logLevel || "info", "WebSocketClient");
745
+ }
746
+ // Getters
747
+ /**
748
+ * Quick check for whether the WebSocket connection is currently active.
749
+ * This getter provides immediate connection status without full state details.
750
+ *
751
+ * @returns True if WebSocket is connected, false otherwise
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * if (wsClient.isConnected) {
756
+ * await wsClient.sendMessage(message);
757
+ * }
758
+ * ```
759
+ */
760
+ get isConnected() {
761
+ return this.connectionState.connected;
762
+ }
763
+ /**
764
+ * Quick check for whether authentication is complete.
765
+ * This getter provides immediate authentication status without full state details.
766
+ *
767
+ * @returns True if authenticated, false otherwise
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * if (wsClient.isAuthenticated) {
772
+ * console.log('Ready to communicate with agents');
773
+ * }
774
+ * ```
775
+ */
776
+ get isAuthenticated() {
777
+ return this.authState.authenticated;
778
+ }
779
+ /**
780
+ * Gets a copy of the current connection state including detailed status information.
781
+ * Returns a shallow copy to prevent external modification of internal state.
782
+ *
783
+ * @returns Copy of connection state with connection status, reconnection info, and timestamps
784
+ *
785
+ * @example
786
+ * ```typescript
787
+ * const state = wsClient.getConnectionState();
788
+ * console.log(`Connected: ${state.connected}`);
789
+ * console.log(`Reconnecting: ${state.reconnecting}`);
790
+ * console.log(`Attempts: ${state.reconnectAttempts}`);
791
+ * ```
792
+ */
793
+ getConnectionState() {
794
+ return { ...this.connectionState };
795
+ }
796
+ /**
797
+ * Gets a copy of the current authentication state including wallet and room information.
798
+ * Returns a shallow copy to prevent external modification of internal state.
799
+ *
800
+ * @returns Copy of authentication state with wallet address, challenge, and room access
801
+ *
802
+ * @example
803
+ * ```typescript
804
+ * const authState = wsClient.getAuthState();
805
+ * console.log(`Authenticated: ${authState.authenticated}`);
806
+ * console.log(`Wallet: ${authState.walletAddress}`);
807
+ * console.log(`Rooms: ${authState.rooms?.length}`);
808
+ * ```
809
+ */
810
+ getAuthState() {
811
+ return { ...this.authState };
812
+ }
813
+ /**
814
+ * Gets the current rate limiter status including available tokens and configuration.
815
+ * Useful for monitoring rate limiting behavior and detecting potential throttling.
816
+ * Returns undefined if rate limiting is not configured.
817
+ *
818
+ * @returns Rate limiter status object, or undefined if not configured
819
+ * @returns {number} returns.availableTokens - Tokens currently available for consumption
820
+ * @returns {number} returns.tokensPerSecond - Configured rate limit (operations per second)
821
+ * @returns {number} returns.maxBurst - Maximum burst capacity
822
+ *
823
+ * @example
824
+ * ```typescript
825
+ * const status = wsClient.getRateLimiterStatus();
826
+ * if (status) {
827
+ * console.log(`Available: ${status.availableTokens}/${status.maxBurst}`);
828
+ * console.log(`Rate: ${status.tokensPerSecond}/sec`);
829
+ * }
830
+ * ```
831
+ */
832
+ getRateLimiterStatus() {
833
+ if (!this.rateLimiter) {
834
+ return undefined;
835
+ }
836
+ const config = this.rateLimiter.getConfig();
837
+ return {
838
+ availableTokens: this.rateLimiter.getAvailableTokens(),
839
+ tokensPerSecond: config.tokensPerSecond,
840
+ maxBurst: config.maxBurst
841
+ };
842
+ }
843
+ /**
844
+ * Gets the current message deduplication cache status (CB-4).
845
+ * Useful for monitoring deduplication behavior and cache health.
846
+ * Returns undefined if deduplication is not configured.
847
+ *
848
+ * @returns Deduplication cache status object, or undefined if not configured
849
+ * @returns {number} returns.cacheSize - Number of message IDs currently cached
850
+ * @returns {number} returns.ttl - Time-to-live for cache entries in milliseconds
851
+ * @returns {number} returns.maxSize - Maximum cache size
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * const status = wsClient.getDeduplicationStatus();
856
+ * if (status) {
857
+ * console.log(`Cache: ${status.cacheSize}/${status.maxSize}`);
858
+ * console.log(`TTL: ${status.ttl}ms`);
859
+ * }
860
+ * ```
861
+ */
862
+ getDeduplicationStatus() {
863
+ if (!this.deduplicationCache) {
864
+ return undefined;
865
+ }
866
+ const config = this.deduplicationCache.getConfig();
867
+ return {
868
+ cacheSize: this.deduplicationCache.size(),
869
+ ttl: config.ttl,
870
+ maxSize: config.maxSize
871
+ };
872
+ }
873
+ }
874
+ exports.WebSocketClient = WebSocketClient;
875
+ //# sourceMappingURL=websocket-client.js.map