@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,381 @@
1
+ /**
2
+ * Tests for Event Waiter Utility
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
6
+ import { EventEmitter } from "eventemitter3";
7
+ import { waitForEvent, waitForAnyEvent, waitForAllEvents } from "./event-waiter";
8
+
9
+ describe("waitForEvent", () => {
10
+ let emitter: EventEmitter;
11
+
12
+ beforeEach(() => {
13
+ emitter = new EventEmitter();
14
+ vi.useFakeTimers();
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ vi.useRealTimers();
20
+ });
21
+
22
+ describe("basic functionality", () => {
23
+ it("should resolve when event is emitted", async () => {
24
+ const eventData = { message: "test data" };
25
+
26
+ // Start waiting
27
+ const promise = waitForEvent(emitter, "test:event", { timeout: 5000 });
28
+
29
+ // Emit the event
30
+ emitter.emit("test:event", eventData);
31
+
32
+ // Should resolve with the event data
33
+ await expect(promise).resolves.toEqual(eventData);
34
+ });
35
+
36
+ it("should resolve with the correct event data type", async () => {
37
+ interface TestData {
38
+ id: number;
39
+ name: string;
40
+ }
41
+
42
+ const eventData: TestData = { id: 123, name: "test" };
43
+
44
+ const promise = waitForEvent<TestData>(emitter, "typed:event", {
45
+ timeout: 5000
46
+ });
47
+
48
+ emitter.emit("typed:event", eventData);
49
+
50
+ const result = await promise;
51
+ expect(result.id).toBe(123);
52
+ expect(result.name).toBe("test");
53
+ });
54
+
55
+ it("should work with multiple sequential waits", async () => {
56
+ const promise1 = waitForEvent(emitter, "event1", { timeout: 5000 });
57
+ emitter.emit("event1", "data1");
58
+ await expect(promise1).resolves.toBe("data1");
59
+
60
+ const promise2 = waitForEvent(emitter, "event2", { timeout: 5000 });
61
+ emitter.emit("event2", "data2");
62
+ await expect(promise2).resolves.toBe("data2");
63
+ });
64
+ });
65
+
66
+ describe("timeout handling", () => {
67
+ it("should reject after timeout", async () => {
68
+ const promise = waitForEvent(emitter, "never:happens", { timeout: 1000 });
69
+
70
+ // Advance time past timeout
71
+ vi.advanceTimersByTime(1001);
72
+
73
+ await expect(promise).rejects.toThrow(/Timeout waiting for event/);
74
+ });
75
+
76
+ it("should use custom timeout message", async () => {
77
+ const customMessage = "Custom timeout error";
78
+ const promise = waitForEvent(emitter, "never:happens", {
79
+ timeout: 1000,
80
+ timeoutMessage: customMessage
81
+ });
82
+
83
+ vi.advanceTimersByTime(1001);
84
+
85
+ await expect(promise).rejects.toThrow(customMessage);
86
+ });
87
+
88
+ it("should not timeout if event arrives in time", async () => {
89
+ const promise = waitForEvent(emitter, "quick:event", { timeout: 5000 });
90
+
91
+ // Emit before timeout
92
+ vi.advanceTimersByTime(2000);
93
+ emitter.emit("quick:event", "success");
94
+
95
+ await expect(promise).resolves.toBe("success");
96
+ });
97
+ });
98
+
99
+ describe("filtering", () => {
100
+ it("should filter events and wait for matching one", async () => {
101
+ interface Task {
102
+ taskId: string;
103
+ result: string;
104
+ }
105
+
106
+ const promise = waitForEvent<Task>(emitter, "task:response", {
107
+ timeout: 5000,
108
+ filter: (data) => data.taskId === "task-123"
109
+ });
110
+
111
+ // Emit non-matching events
112
+ emitter.emit("task:response", { taskId: "task-999", result: "wrong" });
113
+ emitter.emit("task:response", { taskId: "task-888", result: "wrong" });
114
+
115
+ // Emit matching event
116
+ emitter.emit("task:response", { taskId: "task-123", result: "correct" });
117
+
118
+ const result = await promise;
119
+ expect(result.taskId).toBe("task-123");
120
+ expect(result.result).toBe("correct");
121
+ });
122
+
123
+ it("should timeout if no matching event arrives", async () => {
124
+ const promise = waitForEvent(emitter, "filtered:event", {
125
+ timeout: 1000,
126
+ filter: (data: any) => data.id === 999
127
+ });
128
+
129
+ // Emit non-matching events
130
+ emitter.emit("filtered:event", { id: 1 });
131
+ emitter.emit("filtered:event", { id: 2 });
132
+ emitter.emit("filtered:event", { id: 3 });
133
+
134
+ // Advance past timeout
135
+ vi.advanceTimersByTime(1001);
136
+
137
+ await expect(promise).rejects.toThrow(/Timeout/);
138
+ });
139
+
140
+ it("should handle complex filter logic", async () => {
141
+ interface Response {
142
+ requestId: string;
143
+ status: number;
144
+ data?: any;
145
+ }
146
+
147
+ const promise = waitForEvent<Response>(emitter, "response", {
148
+ timeout: 5000,
149
+ filter: (r) => r.requestId === "req-123" && r.status === 200 && r.data !== undefined
150
+ });
151
+
152
+ // Non-matching: wrong requestId
153
+ emitter.emit("response", { requestId: "req-999", status: 200, data: "test" });
154
+
155
+ // Non-matching: wrong status
156
+ emitter.emit("response", { requestId: "req-123", status: 404, data: "test" });
157
+
158
+ // Non-matching: no data
159
+ emitter.emit("response", { requestId: "req-123", status: 200 });
160
+
161
+ // Matching
162
+ emitter.emit("response", { requestId: "req-123", status: 200, data: "success" });
163
+
164
+ await expect(promise).resolves.toMatchObject({
165
+ requestId: "req-123",
166
+ status: 200,
167
+ data: "success"
168
+ });
169
+ });
170
+ });
171
+
172
+ describe("cleanup", () => {
173
+ it("should remove event listener after success", async () => {
174
+ const promise = waitForEvent(emitter, "cleanup:test", { timeout: 5000 });
175
+
176
+ // Check listener count before
177
+ expect(emitter.listenerCount("cleanup:test")).toBe(1);
178
+
179
+ // Emit event
180
+ emitter.emit("cleanup:test", "data");
181
+ await promise;
182
+
183
+ // Check listener count after
184
+ expect(emitter.listenerCount("cleanup:test")).toBe(0);
185
+ });
186
+
187
+ it("should remove event listener after timeout", async () => {
188
+ const promise = waitForEvent(emitter, "cleanup:timeout", { timeout: 1000 });
189
+
190
+ // Check listener count before
191
+ expect(emitter.listenerCount("cleanup:timeout")).toBe(1);
192
+
193
+ // Timeout
194
+ vi.advanceTimersByTime(1001);
195
+
196
+ await promise.catch(() => {}); // Ignore rejection
197
+
198
+ // Check listener count after
199
+ expect(emitter.listenerCount("cleanup:timeout")).toBe(0);
200
+ });
201
+
202
+ it("should clear timeout after success", async () => {
203
+ const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
204
+
205
+ const promise = waitForEvent(emitter, "timeout:clear", { timeout: 5000 });
206
+
207
+ emitter.emit("timeout:clear", "data");
208
+ await promise;
209
+
210
+ // clearTimeout should have been called
211
+ expect(clearTimeoutSpy).toHaveBeenCalled();
212
+ });
213
+
214
+ it("should not leak listeners with filter", async () => {
215
+ const promise = waitForEvent(emitter, "filter:cleanup", {
216
+ timeout: 5000,
217
+ filter: (data: any) => data.id === 5
218
+ });
219
+
220
+ // Emit multiple non-matching events
221
+ emitter.emit("filter:cleanup", { id: 1 });
222
+ emitter.emit("filter:cleanup", { id: 2 });
223
+ emitter.emit("filter:cleanup", { id: 3 });
224
+
225
+ // Check listener is still there
226
+ expect(emitter.listenerCount("filter:cleanup")).toBe(1);
227
+
228
+ // Emit matching event
229
+ emitter.emit("filter:cleanup", { id: 5 });
230
+ await promise;
231
+
232
+ // Listener should be removed
233
+ expect(emitter.listenerCount("filter:cleanup")).toBe(0);
234
+ });
235
+
236
+ it("should handle multiple concurrent waits and cleanup correctly", async () => {
237
+ const promise1 = waitForEvent(emitter, "concurrent", {
238
+ timeout: 5000,
239
+ filter: (data: any) => data.id === 1
240
+ });
241
+
242
+ const promise2 = waitForEvent(emitter, "concurrent", {
243
+ timeout: 5000,
244
+ filter: (data: any) => data.id === 2
245
+ });
246
+
247
+ // Should have 2 listeners
248
+ expect(emitter.listenerCount("concurrent")).toBe(2);
249
+
250
+ // Resolve first
251
+ emitter.emit("concurrent", { id: 1 });
252
+ await promise1;
253
+
254
+ // Should have 1 listener left
255
+ expect(emitter.listenerCount("concurrent")).toBe(1);
256
+
257
+ // Resolve second
258
+ emitter.emit("concurrent", { id: 2 });
259
+ await promise2;
260
+
261
+ // Should have 0 listeners
262
+ expect(emitter.listenerCount("concurrent")).toBe(0);
263
+ });
264
+ });
265
+ });
266
+
267
+ describe("waitForAnyEvent", () => {
268
+ let emitter: EventEmitter;
269
+
270
+ beforeEach(() => {
271
+ emitter = new EventEmitter();
272
+ vi.useFakeTimers();
273
+ });
274
+
275
+ afterEach(() => {
276
+ vi.restoreAllMocks();
277
+ vi.useRealTimers();
278
+ });
279
+
280
+ it("should resolve with first event that fires", async () => {
281
+ const promise = waitForAnyEvent([
282
+ { emitter, eventName: "event1", options: { timeout: 5000 } },
283
+ { emitter, eventName: "event2", options: { timeout: 5000 } },
284
+ { emitter, eventName: "event3", options: { timeout: 5000 } }
285
+ ]);
286
+
287
+ // Emit second event first
288
+ emitter.emit("event2", "second");
289
+
290
+ await expect(promise).resolves.toBe("second");
291
+ });
292
+
293
+ it("should reject if all events timeout", async () => {
294
+ const promise = waitForAnyEvent([
295
+ { emitter, eventName: "never1", options: { timeout: 1000 } },
296
+ { emitter, eventName: "never2", options: { timeout: 1000 } }
297
+ ]);
298
+
299
+ vi.advanceTimersByTime(1001);
300
+
301
+ await expect(promise).rejects.toThrow();
302
+ });
303
+
304
+ it("should work with different emitters", async () => {
305
+ const emitter1 = new EventEmitter();
306
+ const emitter2 = new EventEmitter();
307
+
308
+ const promise = waitForAnyEvent([
309
+ { emitter: emitter1, eventName: "event", options: { timeout: 5000 } },
310
+ { emitter: emitter2, eventName: "event", options: { timeout: 5000 } }
311
+ ]);
312
+
313
+ emitter2.emit("event", "from emitter2");
314
+
315
+ await expect(promise).resolves.toBe("from emitter2");
316
+ });
317
+ });
318
+
319
+ describe("waitForAllEvents", () => {
320
+ let emitter: EventEmitter;
321
+
322
+ beforeEach(() => {
323
+ emitter = new EventEmitter();
324
+ vi.useFakeTimers();
325
+ });
326
+
327
+ afterEach(() => {
328
+ vi.restoreAllMocks();
329
+ vi.useRealTimers();
330
+ });
331
+
332
+ it("should resolve when all events fire", async () => {
333
+ const promise = waitForAllEvents([
334
+ { emitter, eventName: "event1", options: { timeout: 5000 } },
335
+ { emitter, eventName: "event2", options: { timeout: 5000 } },
336
+ { emitter, eventName: "event3", options: { timeout: 5000 } }
337
+ ]);
338
+
339
+ // Emit all events
340
+ emitter.emit("event1", "first");
341
+ emitter.emit("event2", "second");
342
+ emitter.emit("event3", "third");
343
+
344
+ const results = await promise;
345
+ expect(results).toEqual(["first", "second", "third"]);
346
+ });
347
+
348
+ it("should reject if any event times out", async () => {
349
+ const promise = waitForAllEvents([
350
+ { emitter, eventName: "event1", options: { timeout: 5000 } },
351
+ { emitter, eventName: "never", options: { timeout: 1000 } }
352
+ ]);
353
+
354
+ emitter.emit("event1", "data");
355
+ vi.advanceTimersByTime(1001);
356
+
357
+ await expect(promise).rejects.toThrow();
358
+ });
359
+
360
+ it("should work with filters on all events", async () => {
361
+ const promise = waitForAllEvents([
362
+ {
363
+ emitter,
364
+ eventName: "data",
365
+ options: { timeout: 5000, filter: (d: any) => d.id === 1 }
366
+ },
367
+ {
368
+ emitter,
369
+ eventName: "data",
370
+ options: { timeout: 5000, filter: (d: any) => d.id === 2 }
371
+ }
372
+ ]);
373
+
374
+ // Emit in reverse order
375
+ emitter.emit("data", { id: 2, value: "second" });
376
+ emitter.emit("data", { id: 1, value: "first" });
377
+
378
+ const results = await promise;
379
+ expect(results).toEqual([{ id: 1, value: "first" }, { id: 2, value: "second" }]);
380
+ });
381
+ });
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Event Waiter Utility
3
+ * Provides a clean Promise-based API for waiting for events with timeout and filtering
4
+ * Eliminates callback hell and ensures proper cleanup of listeners and timers
5
+ */
6
+
7
+ import { EventEmitter } from "eventemitter3";
8
+
9
+ /**
10
+ * Options for waiting for an event
11
+ */
12
+ export interface WaitForEventOptions<T> {
13
+ /**
14
+ * Timeout in milliseconds
15
+ */
16
+ timeout: number;
17
+
18
+ /**
19
+ * Optional filter function to match specific events
20
+ * If provided, only events that pass the filter will resolve the promise
21
+ *
22
+ * @param data - The event data
23
+ * @returns true if this is the event we're waiting for, false to keep waiting
24
+ */
25
+ filter?: (data: T) => boolean;
26
+
27
+ /**
28
+ * Optional custom timeout error message
29
+ * If not provided, a default message will be used
30
+ */
31
+ timeoutMessage?: string;
32
+ }
33
+
34
+ /**
35
+ * Wait for an event to be emitted with automatic timeout and cleanup
36
+ *
37
+ * This utility encapsulates the complex pattern of:
38
+ * - Setting up event listeners
39
+ * - Managing timeouts
40
+ * - Cleaning up listeners and timers in all code paths
41
+ * - Filtering events
42
+ *
43
+ * All cleanup is guaranteed to happen, preventing memory leaks.
44
+ *
45
+ * @param emitter - The EventEmitter to listen to
46
+ * @param eventName - The name of the event to wait for
47
+ * @param options - Configuration options
48
+ * @returns Promise that resolves with the event data or rejects on timeout
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // Simple usage: wait for any 'data' event
53
+ * const data = await waitForEvent(emitter, 'data', { timeout: 5000 });
54
+ *
55
+ * // With filter: wait for specific event
56
+ * const response = await waitForEvent(wsClient, 'agent:response', {
57
+ * timeout: 30000,
58
+ * filter: (r) => r.taskId === myTaskId,
59
+ * timeoutMessage: 'Agent did not respond in time'
60
+ * });
61
+ * ```
62
+ */
63
+ export async function waitForEvent<T = any>(
64
+ emitter: EventEmitter,
65
+ eventName: string,
66
+ options: WaitForEventOptions<T>
67
+ ): Promise<T> {
68
+ return new Promise<T>((resolve, reject) => {
69
+ let timeoutHandle: NodeJS.Timeout | undefined;
70
+ let eventHandler: ((data: T) => void) | undefined;
71
+ let cleaned = false;
72
+
73
+ // Cleanup function - ensures we only clean up once
74
+ const cleanup = () => {
75
+ if (cleaned) return;
76
+ cleaned = true;
77
+
78
+ if (timeoutHandle) {
79
+ clearTimeout(timeoutHandle);
80
+ timeoutHandle = undefined;
81
+ }
82
+
83
+ if (eventHandler) {
84
+ emitter.off(eventName, eventHandler);
85
+ eventHandler = undefined;
86
+ }
87
+ };
88
+
89
+ // Event handler - filters and resolves
90
+ eventHandler = (data: T) => {
91
+ // If filter is provided, check if this event matches
92
+ if (options.filter && !options.filter(data)) {
93
+ // Not the event we're looking for, keep waiting
94
+ return;
95
+ }
96
+
97
+ // This is the event we want!
98
+ cleanup();
99
+ resolve(data);
100
+ };
101
+
102
+ // Timeout handler - rejects after timeout
103
+ timeoutHandle = setTimeout(() => {
104
+ cleanup();
105
+ const message = options.timeoutMessage || `Timeout waiting for event '${eventName}' after ${options.timeout}ms`;
106
+ reject(new Error(message));
107
+ }, options.timeout);
108
+
109
+ // Start listening
110
+ emitter.on(eventName, eventHandler);
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Wait for multiple events simultaneously (race condition)
116
+ * Resolves with the first event that fires and matches its filter
117
+ *
118
+ * @param waiters - Array of event waiter configurations
119
+ * @returns Promise that resolves with the first matching event
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * // Wait for either success or error
124
+ * const result = await waitForAnyEvent([
125
+ * { emitter: client, eventName: 'auth:success', options: { timeout: 5000 } },
126
+ * { emitter: client, eventName: 'auth:error', options: { timeout: 5000 } }
127
+ * ]);
128
+ * ```
129
+ */
130
+ export async function waitForAnyEvent<T = any>(
131
+ waiters: Array<{
132
+ emitter: EventEmitter;
133
+ eventName: string;
134
+ options: WaitForEventOptions<T>;
135
+ }>
136
+ ): Promise<T> {
137
+ return Promise.race(
138
+ waiters.map(({ emitter, eventName, options }) =>
139
+ waitForEvent<T>(emitter, eventName, options)
140
+ )
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Wait for all events to fire (all must complete)
146
+ * Useful when you need multiple events to occur before proceeding
147
+ *
148
+ * @param waiters - Array of event waiter configurations
149
+ * @returns Promise that resolves with array of all event data
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * // Wait for both auth and connection
154
+ * const [authState, connState] = await waitForAllEvents([
155
+ * { emitter: client, eventName: 'auth:success', options: { timeout: 5000 } },
156
+ * { emitter: client, eventName: 'connection:open', options: { timeout: 5000 } }
157
+ * ]);
158
+ * ```
159
+ */
160
+ export async function waitForAllEvents<T = any>(
161
+ waiters: Array<{
162
+ emitter: EventEmitter;
163
+ eventName: string;
164
+ options: WaitForEventOptions<T>;
165
+ }>
166
+ ): Promise<T[]> {
167
+ return Promise.all(
168
+ waiters.map(({ emitter, eventName, options }) =>
169
+ waitForEvent<T>(emitter, eventName, options)
170
+ )
171
+ );
172
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Utility functions and helpers for the Teneo Protocol SDK
3
+ */
4
+
5
+ /**
6
+ * Logger creation utilities
7
+ */
8
+ export { createPinoLogger } from "./logger";
9
+
10
+ /**
11
+ * SSRF protection utilities for webhook URL validation
12
+ * Prevents Server-Side Request Forgery attacks
13
+ */
14
+ export { validateWebhookUrl, isPrivateIP, isCloudMetadataEndpoint } from "./ssrf-validator";
15
+
16
+ /**
17
+ * Event waiting utilities for async event handling
18
+ * Wait for specific events with timeout support
19
+ */
20
+ export {
21
+ waitForEvent,
22
+ waitForAnyEvent,
23
+ waitForAllEvents,
24
+ type WaitForEventOptions
25
+ } from "./event-waiter";
26
+
27
+ /**
28
+ * Memory-safe bounded queue with overflow strategies
29
+ * Prevents unbounded memory growth
30
+ */
31
+ export { BoundedQueue, QueueOverflowError, type OverflowStrategy } from "./bounded-queue";
32
+
33
+ /**
34
+ * Token bucket rate limiter for request throttling
35
+ */
36
+ export { TokenBucketRateLimiter, RateLimitError } from "./rate-limiter";
37
+
38
+ /**
39
+ * Circuit breaker pattern for fault tolerance
40
+ * Prevents cascading failures with automatic recovery
41
+ */
42
+ export { CircuitBreaker, CircuitBreakerError, type CircuitState } from "./circuit-breaker";
43
+
44
+ /**
45
+ * Message deduplication cache with TTL support
46
+ */
47
+ export { DeduplicationCache } from "./deduplication-cache";
48
+
49
+ /**
50
+ * Ethereum signature verification utilities (SEC-2)
51
+ * ECDSA signature validation for message authenticity
52
+ */
53
+ export {
54
+ SignatureVerifier,
55
+ type SignatureVerificationOptions,
56
+ type VerificationResult
57
+ } from "./signature-verifier";
58
+
59
+ /**
60
+ * Secure private key storage with AES-256-GCM encryption (SEC-3)
61
+ * Protects private keys from memory dumps
62
+ */
63
+ export { SecurePrivateKey } from "./secure-private-key";
64
+
65
+ /**
66
+ * Configurable retry strategies for resilient operations (REL-3)
67
+ * Supports exponential, linear, and constant backoff
68
+ */
69
+ export {
70
+ RetryPolicy,
71
+ RetryStrategySchema,
72
+ type RetryStrategy,
73
+ type RetryStrategyType
74
+ } from "./retry-policy";
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Logger utility for Teneo Protocol SDK
3
+ * Provides pino-based logging with structured output and optional pretty printing
4
+ */
5
+
6
+ import pino from "pino";
7
+ import type { Logger, LogLevel } from "../types";
8
+
9
+ /**
10
+ * Creates a pino-based logger that conforms to the SDK Logger interface.
11
+ * Automatically configures pretty printing for development environments.
12
+ *
13
+ * @param level - Log level (debug, info, warn, error, silent)
14
+ * @param name - Logger name for identifying log source (e.g., "TeneoSDK", "WebSocketClient")
15
+ * @returns Logger instance compatible with SDK Logger interface
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const logger = createPinoLogger('info', 'TeneoSDK');
20
+ * logger.info('SDK initialized', { wsUrl: 'wss://example.com' });
21
+ * logger.error('Connection failed', { code: 'CONN_FAILED', attempt: 3 });
22
+ * ```
23
+ */
24
+ export function createPinoLogger(level: LogLevel, name?: string): Logger {
25
+ // Map 'silent' to pino's 'silent' level
26
+ const pinoLevel = level === "silent" ? "silent" : level;
27
+
28
+ // Create pino logger with optional pretty printing for development
29
+ const pinoLogger = pino({
30
+ level: pinoLevel,
31
+ name: name || "TeneoSDK",
32
+ // Use pino-pretty in development for readable logs
33
+ transport:
34
+ process.env.NODE_ENV !== "production"
35
+ ? {
36
+ target: "pino-pretty",
37
+ options: {
38
+ colorize: true,
39
+ ignore: "pid,hostname",
40
+ translateTime: "HH:MM:ss.l",
41
+ singleLine: false
42
+ }
43
+ }
44
+ : undefined,
45
+ // Production: fast JSON logs
46
+ formatters:
47
+ process.env.NODE_ENV === "production"
48
+ ? {
49
+ level: (label) => {
50
+ return { level: label };
51
+ }
52
+ }
53
+ : undefined
54
+ });
55
+
56
+ // Adapt pino's API to match our Logger interface
57
+ return {
58
+ debug: (message: string, data?: any) => {
59
+ if (data !== undefined) {
60
+ pinoLogger.debug(data, message);
61
+ } else {
62
+ pinoLogger.debug(message);
63
+ }
64
+ },
65
+ info: (message: string, data?: any) => {
66
+ if (data !== undefined) {
67
+ pinoLogger.info(data, message);
68
+ } else {
69
+ pinoLogger.info(message);
70
+ }
71
+ },
72
+ warn: (message: string, data?: any) => {
73
+ if (data !== undefined) {
74
+ pinoLogger.warn(data, message);
75
+ } else {
76
+ pinoLogger.warn(message);
77
+ }
78
+ },
79
+ error: (message: string, data?: any) => {
80
+ if (data !== undefined) {
81
+ pinoLogger.error(data, message);
82
+ } else {
83
+ pinoLogger.error(message);
84
+ }
85
+ }
86
+ };
87
+ }