@lightconexyz/lightcone-sdk 0.1.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 (158) hide show
  1. package/README.md +232 -0
  2. package/dist/api/client.d.ts +225 -0
  3. package/dist/api/client.d.ts.map +1 -0
  4. package/dist/api/client.js +452 -0
  5. package/dist/api/client.js.map +1 -0
  6. package/dist/api/error.d.ts +58 -0
  7. package/dist/api/error.d.ts.map +1 -0
  8. package/dist/api/error.js +98 -0
  9. package/dist/api/error.js.map +1 -0
  10. package/dist/api/index.d.ts +23 -0
  11. package/dist/api/index.d.ts.map +1 -0
  12. package/dist/api/index.js +51 -0
  13. package/dist/api/index.js.map +1 -0
  14. package/dist/api/types/admin.d.ts +49 -0
  15. package/dist/api/types/admin.d.ts.map +1 -0
  16. package/dist/api/types/admin.js +13 -0
  17. package/dist/api/types/admin.js.map +1 -0
  18. package/dist/api/types/index.d.ts +14 -0
  19. package/dist/api/types/index.d.ts.map +1 -0
  20. package/dist/api/types/index.js +13 -0
  21. package/dist/api/types/index.js.map +1 -0
  22. package/dist/api/types/market.d.ts +186 -0
  23. package/dist/api/types/market.d.ts.map +1 -0
  24. package/dist/api/types/market.js +6 -0
  25. package/dist/api/types/market.js.map +1 -0
  26. package/dist/api/types/order.d.ts +190 -0
  27. package/dist/api/types/order.d.ts.map +1 -0
  28. package/dist/api/types/order.js +6 -0
  29. package/dist/api/types/order.js.map +1 -0
  30. package/dist/api/types/orderbook.d.ts +36 -0
  31. package/dist/api/types/orderbook.d.ts.map +1 -0
  32. package/dist/api/types/orderbook.js +6 -0
  33. package/dist/api/types/orderbook.js.map +1 -0
  34. package/dist/api/types/position.d.ts +60 -0
  35. package/dist/api/types/position.d.ts.map +1 -0
  36. package/dist/api/types/position.js +6 -0
  37. package/dist/api/types/position.js.map +1 -0
  38. package/dist/api/types/price_history.d.ts +68 -0
  39. package/dist/api/types/price_history.d.ts.map +1 -0
  40. package/dist/api/types/price_history.js +13 -0
  41. package/dist/api/types/price_history.js.map +1 -0
  42. package/dist/api/types/trade.d.ts +67 -0
  43. package/dist/api/types/trade.d.ts.map +1 -0
  44. package/dist/api/types/trade.js +13 -0
  45. package/dist/api/types/trade.js.map +1 -0
  46. package/dist/api/validation.d.ts +24 -0
  47. package/dist/api/validation.d.ts.map +1 -0
  48. package/dist/api/validation.js +53 -0
  49. package/dist/api/validation.js.map +1 -0
  50. package/dist/auth.d.ts +80 -0
  51. package/dist/auth.d.ts.map +1 -0
  52. package/dist/auth.js +149 -0
  53. package/dist/auth.js.map +1 -0
  54. package/dist/index.d.ts +55 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +107 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/network.d.ts +5 -0
  59. package/dist/network.d.ts.map +1 -0
  60. package/dist/network.js +8 -0
  61. package/dist/network.js.map +1 -0
  62. package/dist/program/accounts.d.ts +98 -0
  63. package/dist/program/accounts.d.ts.map +1 -0
  64. package/dist/program/accounts.js +319 -0
  65. package/dist/program/accounts.js.map +1 -0
  66. package/dist/program/builder.d.ts +94 -0
  67. package/dist/program/builder.d.ts.map +1 -0
  68. package/dist/program/builder.js +175 -0
  69. package/dist/program/builder.js.map +1 -0
  70. package/dist/program/client.d.ts +56 -0
  71. package/dist/program/client.d.ts.map +1 -0
  72. package/dist/program/client.js +288 -0
  73. package/dist/program/client.js.map +1 -0
  74. package/dist/program/constants.d.ts +108 -0
  75. package/dist/program/constants.d.ts.map +1 -0
  76. package/dist/program/constants.js +112 -0
  77. package/dist/program/constants.js.map +1 -0
  78. package/dist/program/index.d.ts +14 -0
  79. package/dist/program/index.d.ts.map +1 -0
  80. package/dist/program/index.js +149 -0
  81. package/dist/program/index.js.map +1 -0
  82. package/dist/program/instructions.d.ts +248 -0
  83. package/dist/program/instructions.d.ts.map +1 -0
  84. package/dist/program/instructions.js +692 -0
  85. package/dist/program/instructions.js.map +1 -0
  86. package/dist/program/orders.d.ts +151 -0
  87. package/dist/program/orders.d.ts.map +1 -0
  88. package/dist/program/orders.js +417 -0
  89. package/dist/program/orders.js.map +1 -0
  90. package/dist/program/pda.d.ts +73 -0
  91. package/dist/program/pda.d.ts.map +1 -0
  92. package/dist/program/pda.js +131 -0
  93. package/dist/program/pda.js.map +1 -0
  94. package/dist/program/types.d.ts +380 -0
  95. package/dist/program/types.d.ts.map +1 -0
  96. package/dist/program/types.js +27 -0
  97. package/dist/program/types.js.map +1 -0
  98. package/dist/program/utils.d.ts +91 -0
  99. package/dist/program/utils.d.ts.map +1 -0
  100. package/dist/program/utils.js +219 -0
  101. package/dist/program/utils.js.map +1 -0
  102. package/dist/shared/index.d.ts +8 -0
  103. package/dist/shared/index.d.ts.map +1 -0
  104. package/dist/shared/index.js +18 -0
  105. package/dist/shared/index.js.map +1 -0
  106. package/dist/shared/price.d.ts +41 -0
  107. package/dist/shared/price.d.ts.map +1 -0
  108. package/dist/shared/price.js +57 -0
  109. package/dist/shared/price.js.map +1 -0
  110. package/dist/shared/scaling.d.ts +45 -0
  111. package/dist/shared/scaling.d.ts.map +1 -0
  112. package/dist/shared/scaling.js +84 -0
  113. package/dist/shared/scaling.js.map +1 -0
  114. package/dist/shared/types.d.ts +19 -0
  115. package/dist/shared/types.d.ts.map +1 -0
  116. package/dist/shared/types.js +23 -0
  117. package/dist/shared/types.js.map +1 -0
  118. package/dist/websocket/client.d.ts +238 -0
  119. package/dist/websocket/client.d.ts.map +1 -0
  120. package/dist/websocket/client.js +580 -0
  121. package/dist/websocket/client.js.map +1 -0
  122. package/dist/websocket/error.d.ts +47 -0
  123. package/dist/websocket/error.d.ts.map +1 -0
  124. package/dist/websocket/error.js +83 -0
  125. package/dist/websocket/error.js.map +1 -0
  126. package/dist/websocket/handlers.d.ts +97 -0
  127. package/dist/websocket/handlers.d.ts.map +1 -0
  128. package/dist/websocket/handlers.js +277 -0
  129. package/dist/websocket/handlers.js.map +1 -0
  130. package/dist/websocket/index.d.ts +38 -0
  131. package/dist/websocket/index.d.ts.map +1 -0
  132. package/dist/websocket/index.js +75 -0
  133. package/dist/websocket/index.js.map +1 -0
  134. package/dist/websocket/state/index.d.ts +7 -0
  135. package/dist/websocket/state/index.d.ts.map +1 -0
  136. package/dist/websocket/state/index.js +14 -0
  137. package/dist/websocket/state/index.js.map +1 -0
  138. package/dist/websocket/state/orderbook.d.ts +107 -0
  139. package/dist/websocket/state/orderbook.d.ts.map +1 -0
  140. package/dist/websocket/state/orderbook.js +293 -0
  141. package/dist/websocket/state/orderbook.js.map +1 -0
  142. package/dist/websocket/state/price.d.ts +108 -0
  143. package/dist/websocket/state/price.d.ts.map +1 -0
  144. package/dist/websocket/state/price.js +243 -0
  145. package/dist/websocket/state/price.js.map +1 -0
  146. package/dist/websocket/state/user.d.ts +83 -0
  147. package/dist/websocket/state/user.d.ts.map +1 -0
  148. package/dist/websocket/state/user.js +228 -0
  149. package/dist/websocket/state/user.js.map +1 -0
  150. package/dist/websocket/subscriptions.d.ts +143 -0
  151. package/dist/websocket/subscriptions.d.ts.map +1 -0
  152. package/dist/websocket/subscriptions.js +244 -0
  153. package/dist/websocket/subscriptions.js.map +1 -0
  154. package/dist/websocket/types.d.ts +417 -0
  155. package/dist/websocket/types.d.ts.map +1 -0
  156. package/dist/websocket/types.js +195 -0
  157. package/dist/websocket/types.js.map +1 -0
  158. package/package.json +75 -0
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ /**
3
+ * Main WebSocket client implementation.
4
+ *
5
+ * Provides a WebSocket client for real-time data streaming.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.LightconeWebSocketClient = exports.DEFAULT_WS_URL = void 0;
12
+ const ws_1 = __importDefault(require("ws"));
13
+ const error_1 = require("./error");
14
+ const handlers_1 = require("./handlers");
15
+ const subscriptions_1 = require("./subscriptions");
16
+ const types_1 = require("./types");
17
+ const auth_1 = require("../auth");
18
+ const network_1 = require("../network");
19
+ var network_2 = require("../network");
20
+ Object.defineProperty(exports, "DEFAULT_WS_URL", { enumerable: true, get: function () { return network_2.DEFAULT_WS_URL; } });
21
+ /**
22
+ * Main WebSocket client for Lightcone.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { LightconeWebSocketClient } from "@lightcone/sdk/websocket";
27
+ *
28
+ * const client = new LightconeWebSocketClient();
29
+ * await client.connect();
30
+ *
31
+ * client.on((event) => {
32
+ * if (event.type === "BookUpdate") {
33
+ * const book = client.getOrderbook(event.orderbookId);
34
+ * console.log("Best bid:", book?.bestBid());
35
+ * }
36
+ * });
37
+ *
38
+ * await client.subscribeBookUpdates(["market1:ob1"]);
39
+ * ```
40
+ */
41
+ class LightconeWebSocketClient {
42
+ url;
43
+ config;
44
+ state = "Disconnected";
45
+ ws = null;
46
+ subscriptions = new subscriptions_1.SubscriptionManager();
47
+ handler = new handlers_1.MessageHandler();
48
+ reconnectAttempt = 0;
49
+ pingInterval = null;
50
+ eventCallbacks = [];
51
+ authCredentials;
52
+ subscribedUser = null;
53
+ lastPong = Date.now();
54
+ awaitingPong = false;
55
+ isReconnecting = false;
56
+ constructor(url = network_1.DEFAULT_WS_URL, config = {}) {
57
+ this.url = url;
58
+ this.config = {
59
+ reconnectAttempts: config.reconnectAttempts ?? 10,
60
+ baseDelayMs: config.baseDelayMs ?? 1000,
61
+ maxDelayMs: config.maxDelayMs ?? 30000,
62
+ pingIntervalSecs: config.pingIntervalSecs ?? 30,
63
+ pongTimeoutSecs: config.pongTimeoutSecs ?? 60,
64
+ autoReconnect: config.autoReconnect ?? true,
65
+ autoResubscribe: config.autoResubscribe ?? true,
66
+ authToken: config.authToken ?? "",
67
+ connectionTimeoutMs: config.connectionTimeoutMs ?? 30000,
68
+ };
69
+ }
70
+ /**
71
+ * Create a client connected to the default URL.
72
+ */
73
+ static async connectDefault() {
74
+ const client = new LightconeWebSocketClient();
75
+ await client.connect();
76
+ return client;
77
+ }
78
+ /**
79
+ * Create a client connected to a custom URL.
80
+ */
81
+ static async connect(url, config) {
82
+ const client = new LightconeWebSocketClient(url, config);
83
+ await client.connect();
84
+ return client;
85
+ }
86
+ /**
87
+ * Create an authenticated client connected to the default URL.
88
+ */
89
+ static async connectAuthenticated(keypair) {
90
+ const credentials = await (0, auth_1.authenticate)(keypair);
91
+ const client = new LightconeWebSocketClient(network_1.DEFAULT_WS_URL, {
92
+ authToken: credentials.authToken,
93
+ });
94
+ client.authCredentials = credentials;
95
+ await client.connect();
96
+ return client;
97
+ }
98
+ /**
99
+ * Create an authenticated client with custom config.
100
+ */
101
+ static async connectAuthenticatedWithConfig(keypair, config) {
102
+ const credentials = await (0, auth_1.authenticate)(keypair);
103
+ config.authToken = credentials.authToken;
104
+ const client = new LightconeWebSocketClient(network_1.DEFAULT_WS_URL, config);
105
+ client.authCredentials = credentials;
106
+ await client.connect();
107
+ return client;
108
+ }
109
+ /**
110
+ * Connect to the WebSocket server.
111
+ */
112
+ async connect() {
113
+ if (this.state === "Connected" || this.state === "Connecting") {
114
+ return;
115
+ }
116
+ this.state = "Connecting";
117
+ await this.establishConnection();
118
+ }
119
+ /**
120
+ * Establish the WebSocket connection.
121
+ */
122
+ async establishConnection() {
123
+ return new Promise((resolve, reject) => {
124
+ const timeoutMs = this.config.connectionTimeoutMs;
125
+ let timeoutId = null;
126
+ let resolved = false;
127
+ // Set up connection timeout
128
+ timeoutId = setTimeout(() => {
129
+ if (!resolved) {
130
+ resolved = true;
131
+ this.state = "Disconnected";
132
+ if (this.ws) {
133
+ this.ws.close();
134
+ this.ws = null;
135
+ }
136
+ reject(error_1.WebSocketError.connectionTimeout());
137
+ }
138
+ }, timeoutMs);
139
+ try {
140
+ // Build WebSocket options with Cookie header if authenticated
141
+ const options = {};
142
+ if (this.config.authToken) {
143
+ options.headers = {
144
+ Cookie: `auth_token=${this.config.authToken}`,
145
+ };
146
+ }
147
+ this.ws = new ws_1.default(this.url, options);
148
+ }
149
+ catch (e) {
150
+ if (timeoutId)
151
+ clearTimeout(timeoutId);
152
+ this.state = "Disconnected";
153
+ reject(error_1.WebSocketError.connectionFailed(String(e)));
154
+ return;
155
+ }
156
+ this.ws.onopen = () => {
157
+ if (resolved)
158
+ return;
159
+ resolved = true;
160
+ if (timeoutId)
161
+ clearTimeout(timeoutId);
162
+ this.state = "Connected";
163
+ this.reconnectAttempt = 0;
164
+ this.startPingInterval();
165
+ this.emitEvent({ type: "Connected" });
166
+ resolve();
167
+ };
168
+ this.ws.onmessage = (event) => {
169
+ const events = this.handler.handleMessage(event.data);
170
+ for (const wsEvent of events) {
171
+ // Track pong responses for timeout detection
172
+ if (wsEvent.type === "Pong") {
173
+ this.lastPong = Date.now();
174
+ this.awaitingPong = false;
175
+ }
176
+ this.emitEvent(wsEvent);
177
+ }
178
+ };
179
+ this.ws.onerror = (event) => {
180
+ if (resolved)
181
+ return;
182
+ resolved = true;
183
+ if (timeoutId)
184
+ clearTimeout(timeoutId);
185
+ console.error("WebSocket error:", event);
186
+ this.state = "Disconnected";
187
+ reject(error_1.WebSocketError.connectionFailed("WebSocket error"));
188
+ };
189
+ this.ws.onclose = (event) => {
190
+ if (timeoutId)
191
+ clearTimeout(timeoutId);
192
+ this.stopPingInterval();
193
+ const reason = `code: ${event.code}, reason: ${event.reason || "no reason"}`;
194
+ this.emitEvent({ type: "Disconnected", reason });
195
+ // Check if rate limited
196
+ if (event.code === 1008) {
197
+ this.emitEvent({
198
+ type: "Error",
199
+ error: error_1.WebSocketError.rateLimited(),
200
+ });
201
+ }
202
+ // Try to reconnect if enabled
203
+ if (this.config.autoReconnect &&
204
+ this.reconnectAttempt < this.config.reconnectAttempts &&
205
+ this.state !== "Disconnecting" &&
206
+ !this.isReconnecting) {
207
+ this.attemptReconnect();
208
+ }
209
+ else if (!this.isReconnecting) {
210
+ this.state = "Disconnected";
211
+ }
212
+ };
213
+ });
214
+ }
215
+ /**
216
+ * Attempt to reconnect.
217
+ */
218
+ async attemptReconnect() {
219
+ if (this.isReconnecting)
220
+ return;
221
+ this.isReconnecting = true;
222
+ try {
223
+ this.reconnectAttempt++;
224
+ this.state = "Reconnecting";
225
+ this.emitEvent({ type: "Reconnecting", attempt: this.reconnectAttempt });
226
+ // Full jitter: randomize between 0 and exponential delay to prevent thundering herd
227
+ const maxDelay = this.config.baseDelayMs *
228
+ Math.pow(2, this.reconnectAttempt - 1);
229
+ const jitteredDelay = Math.random() * maxDelay;
230
+ const cappedDelay = Math.min(jitteredDelay, this.config.maxDelayMs);
231
+ await this.sleep(cappedDelay);
232
+ await this.establishConnection();
233
+ // Re-subscribe if enabled
234
+ if (this.config.autoResubscribe) {
235
+ this.handler.clearAll();
236
+ const subs = this.subscriptions.getAllSubscriptions();
237
+ for (const sub of subs) {
238
+ const request = (0, types_1.createSubscribeRequest)((0, subscriptions_1.subscriptionToParams)(sub));
239
+ this.send(request);
240
+ }
241
+ }
242
+ }
243
+ catch (e) {
244
+ console.error("Reconnect failed:", e);
245
+ this.emitEvent({
246
+ type: "Error",
247
+ error: e instanceof error_1.WebSocketError
248
+ ? e
249
+ : error_1.WebSocketError.connectionFailed(String(e)),
250
+ });
251
+ }
252
+ finally {
253
+ this.isReconnecting = false;
254
+ }
255
+ }
256
+ /**
257
+ * Start the ping interval.
258
+ */
259
+ startPingInterval() {
260
+ this.stopPingInterval();
261
+ // Reset pong tracking state on new connection
262
+ this.lastPong = Date.now();
263
+ this.awaitingPong = false;
264
+ this.pingInterval = setInterval(() => {
265
+ // Check if we're still waiting for a pong from a previous ping
266
+ if (this.awaitingPong) {
267
+ const elapsed = (Date.now() - this.lastPong) / 1000;
268
+ if (elapsed > this.config.pongTimeoutSecs) {
269
+ console.warn(`Pong timeout: no response in ${elapsed.toFixed(1)}s (limit: ${this.config.pongTimeoutSecs}s)`);
270
+ this.emitEvent({
271
+ type: "Error",
272
+ error: error_1.WebSocketError.connectionFailed("Pong timeout - connection appears dead"),
273
+ });
274
+ // Trigger reconnection by closing the socket
275
+ if (this.ws && this.state === "Connected") {
276
+ this.ws.close(4000, "Pong timeout");
277
+ }
278
+ return;
279
+ }
280
+ }
281
+ this.ping();
282
+ }, this.config.pingIntervalSecs * 1000);
283
+ }
284
+ /**
285
+ * Stop the ping interval.
286
+ */
287
+ stopPingInterval() {
288
+ if (this.pingInterval) {
289
+ clearInterval(this.pingInterval);
290
+ this.pingInterval = null;
291
+ }
292
+ }
293
+ /**
294
+ * Sleep for a given number of milliseconds.
295
+ */
296
+ sleep(ms) {
297
+ return new Promise((resolve) => setTimeout(resolve, ms));
298
+ }
299
+ /**
300
+ * Send a WebSocket message.
301
+ */
302
+ send(message) {
303
+ if (!this.ws || this.state !== "Connected") {
304
+ throw error_1.WebSocketError.notConnected();
305
+ }
306
+ this.ws.send(JSON.stringify(message));
307
+ }
308
+ /**
309
+ * Emit an event to all callbacks.
310
+ */
311
+ emitEvent(event) {
312
+ for (const callback of this.eventCallbacks) {
313
+ try {
314
+ const result = callback(event);
315
+ if (result instanceof Promise) {
316
+ result.catch((e) => {
317
+ console.error("Event callback promise rejected:", e);
318
+ });
319
+ }
320
+ }
321
+ catch (e) {
322
+ console.error("Event callback error:", e);
323
+ }
324
+ }
325
+ }
326
+ // ============================================================================
327
+ // SUBSCRIBE METHODS
328
+ // ============================================================================
329
+ /**
330
+ * Subscribe to orderbook updates.
331
+ */
332
+ subscribeBookUpdates(orderbookIds) {
333
+ if (orderbookIds.length === 0)
334
+ return;
335
+ // Initialize state for each orderbook
336
+ for (const id of orderbookIds) {
337
+ this.handler.initOrderbook(id);
338
+ }
339
+ // Track subscription
340
+ this.subscriptions.addBookUpdate(orderbookIds);
341
+ // Send subscribe request
342
+ const request = (0, types_1.createSubscribeRequest)((0, types_1.bookUpdateParams)(orderbookIds));
343
+ this.send(request);
344
+ }
345
+ /**
346
+ * Subscribe to trade executions.
347
+ */
348
+ subscribeTrades(orderbookIds) {
349
+ if (orderbookIds.length === 0)
350
+ return;
351
+ this.subscriptions.addTrades(orderbookIds);
352
+ const request = (0, types_1.createSubscribeRequest)((0, types_1.tradesParams)(orderbookIds));
353
+ this.send(request);
354
+ }
355
+ /**
356
+ * Subscribe to user events.
357
+ */
358
+ subscribeUser(user) {
359
+ this.subscribedUser = user;
360
+ this.handler.initUserState(user);
361
+ this.subscriptions.addUser(user);
362
+ const request = (0, types_1.createSubscribeRequest)((0, types_1.userParams)(user));
363
+ this.send(request);
364
+ }
365
+ /**
366
+ * Subscribe to price history.
367
+ */
368
+ subscribePriceHistory(orderbookId, resolution, includeOhlcv) {
369
+ this.handler.initPriceHistory(orderbookId, resolution, includeOhlcv);
370
+ this.subscriptions.addPriceHistory(orderbookId, resolution, includeOhlcv);
371
+ const request = (0, types_1.createSubscribeRequest)((0, types_1.priceHistoryParams)(orderbookId, resolution, includeOhlcv));
372
+ this.send(request);
373
+ }
374
+ /**
375
+ * Subscribe to market events.
376
+ */
377
+ subscribeMarket(marketPubkey) {
378
+ this.subscriptions.addMarket(marketPubkey);
379
+ const request = (0, types_1.createSubscribeRequest)((0, types_1.marketParams)(marketPubkey));
380
+ this.send(request);
381
+ }
382
+ // ============================================================================
383
+ // UNSUBSCRIBE METHODS
384
+ // ============================================================================
385
+ /**
386
+ * Unsubscribe from orderbook updates.
387
+ */
388
+ unsubscribeBookUpdates(orderbookIds) {
389
+ if (orderbookIds.length === 0)
390
+ return;
391
+ this.subscriptions.removeBookUpdate(orderbookIds);
392
+ const request = (0, types_1.createUnsubscribeRequest)((0, types_1.bookUpdateParams)(orderbookIds));
393
+ this.send(request);
394
+ }
395
+ /**
396
+ * Unsubscribe from trades.
397
+ */
398
+ unsubscribeTrades(orderbookIds) {
399
+ if (orderbookIds.length === 0)
400
+ return;
401
+ this.subscriptions.removeTrades(orderbookIds);
402
+ const request = (0, types_1.createUnsubscribeRequest)((0, types_1.tradesParams)(orderbookIds));
403
+ this.send(request);
404
+ }
405
+ /**
406
+ * Unsubscribe from user events.
407
+ */
408
+ unsubscribeUser(user) {
409
+ if (this.subscribedUser === user) {
410
+ this.subscribedUser = null;
411
+ }
412
+ this.handler.clearSubscribedUser(user);
413
+ this.subscriptions.removeUser(user);
414
+ const request = (0, types_1.createUnsubscribeRequest)((0, types_1.userParams)(user));
415
+ this.send(request);
416
+ }
417
+ /**
418
+ * Unsubscribe from price history.
419
+ */
420
+ unsubscribePriceHistory(orderbookId, resolution) {
421
+ this.subscriptions.removePriceHistory(orderbookId, resolution);
422
+ const request = (0, types_1.createUnsubscribeRequest)((0, types_1.priceHistoryParams)(orderbookId, resolution, false));
423
+ this.send(request);
424
+ }
425
+ /**
426
+ * Unsubscribe from market events.
427
+ */
428
+ unsubscribeMarket(marketPubkey) {
429
+ this.subscriptions.removeMarket(marketPubkey);
430
+ const request = (0, types_1.createUnsubscribeRequest)((0, types_1.marketParams)(marketPubkey));
431
+ this.send(request);
432
+ }
433
+ // ============================================================================
434
+ // CONTROL METHODS
435
+ // ============================================================================
436
+ /**
437
+ * Send a ping request.
438
+ */
439
+ ping() {
440
+ this.awaitingPong = true;
441
+ this.send((0, types_1.createPingRequest)());
442
+ }
443
+ /**
444
+ * Disconnect from the server.
445
+ * Returns a Promise that resolves when the connection is fully closed.
446
+ */
447
+ async disconnect() {
448
+ const ws = this.ws;
449
+ if (!ws || this.state === "Disconnected") {
450
+ return;
451
+ }
452
+ this.state = "Disconnecting";
453
+ this.stopPingInterval();
454
+ this.subscribedUser = null;
455
+ return new Promise((resolve) => {
456
+ const onClose = () => {
457
+ ws.removeEventListener("close", onClose);
458
+ this.ws = null;
459
+ this.state = "Disconnected";
460
+ resolve();
461
+ };
462
+ ws.addEventListener("close", onClose);
463
+ ws.close(1000, "Client disconnect");
464
+ });
465
+ }
466
+ /**
467
+ * Check if connected.
468
+ */
469
+ isConnected() {
470
+ return this.state === "Connected";
471
+ }
472
+ /**
473
+ * Check if authenticated.
474
+ */
475
+ isAuthenticated() {
476
+ return !!this.config.authToken;
477
+ }
478
+ /**
479
+ * Check if the WebSocket connection is healthy.
480
+ */
481
+ isHealthy() {
482
+ if (this.state !== "Connected") {
483
+ return false;
484
+ }
485
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
486
+ return false;
487
+ }
488
+ // Check if we're waiting for a pong that's overdue
489
+ if (this.awaitingPong) {
490
+ const elapsed = (Date.now() - this.lastPong) / 1000;
491
+ if (elapsed > this.config.pongTimeoutSecs) {
492
+ return false;
493
+ }
494
+ }
495
+ return true;
496
+ }
497
+ /**
498
+ * Get detailed connection status.
499
+ */
500
+ getConnectionStatus() {
501
+ return {
502
+ state: this.state,
503
+ isHealthy: this.isHealthy(),
504
+ lastPongMs: Date.now() - this.lastPong,
505
+ awaitingPong: this.awaitingPong,
506
+ wsReadyState: this.ws?.readyState ?? null,
507
+ };
508
+ }
509
+ /**
510
+ * Get the current connection state.
511
+ */
512
+ connectionState() {
513
+ return this.state;
514
+ }
515
+ // ============================================================================
516
+ // STATE ACCESS
517
+ // ============================================================================
518
+ /**
519
+ * Get orderbook state.
520
+ */
521
+ getOrderbook(orderbookId) {
522
+ return this.handler.getOrderbook(orderbookId);
523
+ }
524
+ /**
525
+ * Get user state.
526
+ */
527
+ getUserState(user) {
528
+ return this.handler.getUserState(user);
529
+ }
530
+ /**
531
+ * Get price history state.
532
+ */
533
+ getPriceHistory(orderbookId, resolution) {
534
+ return this.handler.getPriceHistory(orderbookId, resolution);
535
+ }
536
+ /**
537
+ * Get auth credentials (if authenticated).
538
+ */
539
+ getAuthCredentials() {
540
+ return this.authCredentials;
541
+ }
542
+ /**
543
+ * Get user public key (if authenticated).
544
+ */
545
+ userPubkey() {
546
+ return this.authCredentials?.userPubkey;
547
+ }
548
+ // ============================================================================
549
+ // EVENT HANDLING
550
+ // ============================================================================
551
+ /**
552
+ * Register an event callback.
553
+ */
554
+ on(callback) {
555
+ this.eventCallbacks.push(callback);
556
+ }
557
+ /**
558
+ * Remove an event callback.
559
+ */
560
+ off(callback) {
561
+ const index = this.eventCallbacks.indexOf(callback);
562
+ if (index !== -1) {
563
+ this.eventCallbacks.splice(index, 1);
564
+ }
565
+ }
566
+ /**
567
+ * Get the WebSocket URL.
568
+ */
569
+ getUrl() {
570
+ return this.url;
571
+ }
572
+ /**
573
+ * Get the configuration.
574
+ */
575
+ getConfig() {
576
+ return this.config;
577
+ }
578
+ }
579
+ exports.LightconeWebSocketClient = LightconeWebSocketClient;
580
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/websocket/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main WebSocket client implementation.\n *\n * Provides a WebSocket client for real-time data streaming.\n */\n\nimport WebSocket from \"ws\";\nimport { Keypair } from \"@solana/web3.js\";\nimport { WebSocketError } from \"./error\";\nimport { MessageHandler } from \"./handlers\";\nimport { SubscriptionManager, subscriptionToParams } from \"./subscriptions\";\nimport type { LocalOrderbook, UserState, PriceHistory } from \"./state\";\nimport type { WsRequest, WsEvent } from \"./types\";\nimport {\n createSubscribeRequest,\n createUnsubscribeRequest,\n createPingRequest,\n bookUpdateParams,\n tradesParams,\n userParams,\n priceHistoryParams,\n marketParams,\n} from \"./types\";\nimport { authenticate, type AuthCredentials } from \"../auth\";\nimport { DEFAULT_WS_URL } from \"../network\";\n\nexport { DEFAULT_WS_URL } from \"../network\";\n\n/**\n * WebSocket client configuration.\n */\nexport interface WebSocketConfig {\n /** Number of reconnect attempts before giving up */\n reconnectAttempts?: number;\n /** Base delay for exponential backoff (ms) */\n baseDelayMs?: number;\n /** Maximum delay for exponential backoff (ms) */\n maxDelayMs?: number;\n /** Interval for client ping (seconds) */\n pingIntervalSecs?: number;\n /** Timeout for pong response (seconds) - if no pong received within this time, reconnect */\n pongTimeoutSecs?: number;\n /** Whether to automatically reconnect on disconnect */\n autoReconnect?: boolean;\n /** Whether to automatically re-subscribe after reconnect */\n autoResubscribe?: boolean;\n /** Optional authentication token for private user streams */\n authToken?: string;\n /** Connection timeout in milliseconds (default: 30000) */\n connectionTimeoutMs?: number;\n}\n\n/**\n * Connection state.\n */\nexport type ConnectionState =\n | \"Disconnected\"\n | \"Connecting\"\n | \"Connected\"\n | \"Reconnecting\"\n | \"Disconnecting\";\n\n/**\n * Event callback type.\n */\nexport type EventCallback = (event: WsEvent) => void | Promise<void>;\n\n/**\n * Main WebSocket client for Lightcone.\n *\n * @example\n * ```typescript\n * import { LightconeWebSocketClient } from \"@lightcone/sdk/websocket\";\n *\n * const client = new LightconeWebSocketClient();\n * await client.connect();\n *\n * client.on((event) => {\n * if (event.type === \"BookUpdate\") {\n * const book = client.getOrderbook(event.orderbookId);\n * console.log(\"Best bid:\", book?.bestBid());\n * }\n * });\n *\n * await client.subscribeBookUpdates([\"market1:ob1\"]);\n * ```\n */\nexport class LightconeWebSocketClient {\n private url: string;\n private config: Required<WebSocketConfig>;\n private state: ConnectionState = \"Disconnected\";\n private ws: WebSocket | null = null;\n private subscriptions: SubscriptionManager = new SubscriptionManager();\n private handler: MessageHandler = new MessageHandler();\n private reconnectAttempt: number = 0;\n private pingInterval: ReturnType<typeof setInterval> | null = null;\n private eventCallbacks: EventCallback[] = [];\n private authCredentials?: AuthCredentials;\n private subscribedUser: string | null = null;\n private lastPong: number = Date.now();\n private awaitingPong: boolean = false;\n private isReconnecting: boolean = false;\n\n constructor(url: string = DEFAULT_WS_URL, config: WebSocketConfig = {}) {\n this.url = url;\n this.config = {\n reconnectAttempts: config.reconnectAttempts ?? 10,\n baseDelayMs: config.baseDelayMs ?? 1000,\n maxDelayMs: config.maxDelayMs ?? 30000,\n pingIntervalSecs: config.pingIntervalSecs ?? 30,\n pongTimeoutSecs: config.pongTimeoutSecs ?? 60,\n autoReconnect: config.autoReconnect ?? true,\n autoResubscribe: config.autoResubscribe ?? true,\n authToken: config.authToken ?? \"\",\n connectionTimeoutMs: config.connectionTimeoutMs ?? 30000,\n };\n }\n\n /**\n * Create a client connected to the default URL.\n */\n static async connectDefault(): Promise<LightconeWebSocketClient> {\n const client = new LightconeWebSocketClient();\n await client.connect();\n return client;\n }\n\n /**\n * Create a client connected to a custom URL.\n */\n static async connect(\n url: string,\n config?: WebSocketConfig\n ): Promise<LightconeWebSocketClient> {\n const client = new LightconeWebSocketClient(url, config);\n await client.connect();\n return client;\n }\n\n /**\n * Create an authenticated client connected to the default URL.\n */\n static async connectAuthenticated(\n keypair: Keypair\n ): Promise<LightconeWebSocketClient> {\n const credentials = await authenticate(keypair);\n const client = new LightconeWebSocketClient(DEFAULT_WS_URL, {\n authToken: credentials.authToken,\n });\n client.authCredentials = credentials;\n await client.connect();\n return client;\n }\n\n /**\n * Create an authenticated client with custom config.\n */\n static async connectAuthenticatedWithConfig(\n keypair: Keypair,\n config: WebSocketConfig\n ): Promise<LightconeWebSocketClient> {\n const credentials = await authenticate(keypair);\n config.authToken = credentials.authToken;\n const client = new LightconeWebSocketClient(DEFAULT_WS_URL, config);\n client.authCredentials = credentials;\n await client.connect();\n return client;\n }\n\n /**\n * Connect to the WebSocket server.\n */\n async connect(): Promise<void> {\n if (this.state === \"Connected\" || this.state === \"Connecting\") {\n return;\n }\n\n this.state = \"Connecting\";\n await this.establishConnection();\n }\n\n /**\n * Establish the WebSocket connection.\n */\n private async establishConnection(): Promise<void> {\n return new Promise((resolve, reject) => {\n const timeoutMs = this.config.connectionTimeoutMs;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let resolved = false;\n\n // Set up connection timeout\n timeoutId = setTimeout(() => {\n if (!resolved) {\n resolved = true;\n this.state = \"Disconnected\";\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n reject(WebSocketError.connectionTimeout());\n }\n }, timeoutMs);\n\n try {\n // Build WebSocket options with Cookie header if authenticated\n const options: WebSocket.ClientOptions = {};\n if (this.config.authToken) {\n options.headers = {\n Cookie: `auth_token=${this.config.authToken}`,\n };\n }\n\n this.ws = new WebSocket(this.url, options);\n } catch (e) {\n if (timeoutId) clearTimeout(timeoutId);\n this.state = \"Disconnected\";\n reject(WebSocketError.connectionFailed(String(e)));\n return;\n }\n\n this.ws.onopen = () => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n this.state = \"Connected\";\n this.reconnectAttempt = 0;\n this.startPingInterval();\n this.emitEvent({ type: \"Connected\" });\n resolve();\n };\n\n this.ws.onmessage = (event) => {\n const events = this.handler.handleMessage(event.data as string);\n for (const wsEvent of events) {\n // Track pong responses for timeout detection\n if (wsEvent.type === \"Pong\") {\n this.lastPong = Date.now();\n this.awaitingPong = false;\n }\n this.emitEvent(wsEvent);\n }\n };\n\n this.ws.onerror = (event) => {\n if (resolved) return;\n resolved = true;\n if (timeoutId) clearTimeout(timeoutId);\n console.error(\"WebSocket error:\", event);\n this.state = \"Disconnected\";\n reject(WebSocketError.connectionFailed(\"WebSocket error\"));\n };\n\n this.ws.onclose = (event) => {\n if (timeoutId) clearTimeout(timeoutId);\n this.stopPingInterval();\n const reason = `code: ${event.code}, reason: ${event.reason || \"no reason\"}`;\n this.emitEvent({ type: \"Disconnected\", reason });\n\n // Check if rate limited\n if (event.code === 1008) {\n this.emitEvent({\n type: \"Error\",\n error: WebSocketError.rateLimited(),\n });\n }\n\n // Try to reconnect if enabled\n if (\n this.config.autoReconnect &&\n this.reconnectAttempt < this.config.reconnectAttempts &&\n this.state !== \"Disconnecting\" &&\n !this.isReconnecting\n ) {\n this.attemptReconnect();\n } else if (!this.isReconnecting) {\n this.state = \"Disconnected\";\n }\n };\n });\n }\n\n /**\n * Attempt to reconnect.\n */\n private async attemptReconnect(): Promise<void> {\n if (this.isReconnecting) return;\n this.isReconnecting = true;\n\n try {\n this.reconnectAttempt++;\n this.state = \"Reconnecting\";\n this.emitEvent({ type: \"Reconnecting\", attempt: this.reconnectAttempt });\n\n // Full jitter: randomize between 0 and exponential delay to prevent thundering herd\n const maxDelay =\n this.config.baseDelayMs *\n Math.pow(2, this.reconnectAttempt - 1);\n const jitteredDelay = Math.random() * maxDelay;\n const cappedDelay = Math.min(jitteredDelay, this.config.maxDelayMs);\n\n await this.sleep(cappedDelay);\n\n await this.establishConnection();\n\n // Re-subscribe if enabled\n if (this.config.autoResubscribe) {\n this.handler.clearAll();\n const subs = this.subscriptions.getAllSubscriptions();\n for (const sub of subs) {\n const request = createSubscribeRequest(subscriptionToParams(sub));\n this.send(request);\n }\n }\n } catch (e) {\n console.error(\"Reconnect failed:\", e);\n this.emitEvent({\n type: \"Error\",\n error: e instanceof WebSocketError\n ? e\n : WebSocketError.connectionFailed(String(e)),\n });\n } finally {\n this.isReconnecting = false;\n }\n }\n\n /**\n * Start the ping interval.\n */\n private startPingInterval(): void {\n this.stopPingInterval();\n // Reset pong tracking state on new connection\n this.lastPong = Date.now();\n this.awaitingPong = false;\n\n this.pingInterval = setInterval(() => {\n // Check if we're still waiting for a pong from a previous ping\n if (this.awaitingPong) {\n const elapsed = (Date.now() - this.lastPong) / 1000;\n if (elapsed > this.config.pongTimeoutSecs) {\n console.warn(\n `Pong timeout: no response in ${elapsed.toFixed(1)}s (limit: ${this.config.pongTimeoutSecs}s)`\n );\n this.emitEvent({\n type: \"Error\",\n error: WebSocketError.connectionFailed(\"Pong timeout - connection appears dead\"),\n });\n // Trigger reconnection by closing the socket\n if (this.ws && this.state === \"Connected\") {\n this.ws.close(4000, \"Pong timeout\");\n }\n return;\n }\n }\n this.ping();\n }, this.config.pingIntervalSecs * 1000);\n }\n\n /**\n * Stop the ping interval.\n */\n private stopPingInterval(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n /**\n * Sleep for a given number of milliseconds.\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Send a WebSocket message.\n */\n private send(message: WsRequest): void {\n if (!this.ws || this.state !== \"Connected\") {\n throw WebSocketError.notConnected();\n }\n this.ws.send(JSON.stringify(message));\n }\n\n /**\n * Emit an event to all callbacks.\n */\n private emitEvent(event: WsEvent): void {\n for (const callback of this.eventCallbacks) {\n try {\n const result = callback(event);\n if (result instanceof Promise) {\n result.catch((e) => {\n console.error(\"Event callback promise rejected:\", e);\n });\n }\n } catch (e) {\n console.error(\"Event callback error:\", e);\n }\n }\n }\n\n // ============================================================================\n // SUBSCRIBE METHODS\n // ============================================================================\n\n /**\n * Subscribe to orderbook updates.\n */\n subscribeBookUpdates(orderbookIds: string[]): void {\n if (orderbookIds.length === 0) return;\n\n // Initialize state for each orderbook\n for (const id of orderbookIds) {\n this.handler.initOrderbook(id);\n }\n\n // Track subscription\n this.subscriptions.addBookUpdate(orderbookIds);\n\n // Send subscribe request\n const request = createSubscribeRequest(bookUpdateParams(orderbookIds));\n this.send(request);\n }\n\n /**\n * Subscribe to trade executions.\n */\n subscribeTrades(orderbookIds: string[]): void {\n if (orderbookIds.length === 0) return;\n\n this.subscriptions.addTrades(orderbookIds);\n const request = createSubscribeRequest(tradesParams(orderbookIds));\n this.send(request);\n }\n\n /**\n * Subscribe to user events.\n */\n subscribeUser(user: string): void {\n this.subscribedUser = user;\n this.handler.initUserState(user);\n this.subscriptions.addUser(user);\n const request = createSubscribeRequest(userParams(user));\n this.send(request);\n }\n\n /**\n * Subscribe to price history.\n */\n subscribePriceHistory(\n orderbookId: string,\n resolution: string,\n includeOhlcv: boolean\n ): void {\n this.handler.initPriceHistory(orderbookId, resolution, includeOhlcv);\n this.subscriptions.addPriceHistory(orderbookId, resolution, includeOhlcv);\n const request = createSubscribeRequest(\n priceHistoryParams(orderbookId, resolution, includeOhlcv)\n );\n this.send(request);\n }\n\n /**\n * Subscribe to market events.\n */\n subscribeMarket(marketPubkey: string): void {\n this.subscriptions.addMarket(marketPubkey);\n const request = createSubscribeRequest(marketParams(marketPubkey));\n this.send(request);\n }\n\n // ============================================================================\n // UNSUBSCRIBE METHODS\n // ============================================================================\n\n /**\n * Unsubscribe from orderbook updates.\n */\n unsubscribeBookUpdates(orderbookIds: string[]): void {\n if (orderbookIds.length === 0) return;\n\n this.subscriptions.removeBookUpdate(orderbookIds);\n const request = createUnsubscribeRequest(bookUpdateParams(orderbookIds));\n this.send(request);\n }\n\n /**\n * Unsubscribe from trades.\n */\n unsubscribeTrades(orderbookIds: string[]): void {\n if (orderbookIds.length === 0) return;\n\n this.subscriptions.removeTrades(orderbookIds);\n const request = createUnsubscribeRequest(tradesParams(orderbookIds));\n this.send(request);\n }\n\n /**\n * Unsubscribe from user events.\n */\n unsubscribeUser(user: string): void {\n if (this.subscribedUser === user) {\n this.subscribedUser = null;\n }\n this.handler.clearSubscribedUser(user);\n this.subscriptions.removeUser(user);\n const request = createUnsubscribeRequest(userParams(user));\n this.send(request);\n }\n\n /**\n * Unsubscribe from price history.\n */\n unsubscribePriceHistory(\n orderbookId: string,\n resolution: string\n ): void {\n this.subscriptions.removePriceHistory(orderbookId, resolution);\n const request = createUnsubscribeRequest(\n priceHistoryParams(orderbookId, resolution, false)\n );\n this.send(request);\n }\n\n /**\n * Unsubscribe from market events.\n */\n unsubscribeMarket(marketPubkey: string): void {\n this.subscriptions.removeMarket(marketPubkey);\n const request = createUnsubscribeRequest(marketParams(marketPubkey));\n this.send(request);\n }\n\n // ============================================================================\n // CONTROL METHODS\n // ============================================================================\n\n /**\n * Send a ping request.\n */\n ping(): void {\n this.awaitingPong = true;\n this.send(createPingRequest());\n }\n\n /**\n * Disconnect from the server.\n * Returns a Promise that resolves when the connection is fully closed.\n */\n async disconnect(): Promise<void> {\n const ws = this.ws;\n if (!ws || this.state === \"Disconnected\") {\n return;\n }\n\n this.state = \"Disconnecting\";\n this.stopPingInterval();\n this.subscribedUser = null;\n\n return new Promise((resolve) => {\n const onClose = () => {\n ws.removeEventListener(\"close\", onClose);\n this.ws = null;\n this.state = \"Disconnected\";\n resolve();\n };\n ws.addEventListener(\"close\", onClose);\n ws.close(1000, \"Client disconnect\");\n });\n }\n\n /**\n * Check if connected.\n */\n isConnected(): boolean {\n return this.state === \"Connected\";\n }\n\n /**\n * Check if authenticated.\n */\n isAuthenticated(): boolean {\n return !!this.config.authToken;\n }\n\n /**\n * Check if the WebSocket connection is healthy.\n */\n isHealthy(): boolean {\n if (this.state !== \"Connected\") {\n return false;\n }\n\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return false;\n }\n\n // Check if we're waiting for a pong that's overdue\n if (this.awaitingPong) {\n const elapsed = (Date.now() - this.lastPong) / 1000;\n if (elapsed > this.config.pongTimeoutSecs) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Get detailed connection status.\n */\n getConnectionStatus(): {\n state: ConnectionState;\n isHealthy: boolean;\n lastPongMs: number;\n awaitingPong: boolean;\n wsReadyState: number | null;\n } {\n return {\n state: this.state,\n isHealthy: this.isHealthy(),\n lastPongMs: Date.now() - this.lastPong,\n awaitingPong: this.awaitingPong,\n wsReadyState: this.ws?.readyState ?? null,\n };\n }\n\n /**\n * Get the current connection state.\n */\n connectionState(): ConnectionState {\n return this.state;\n }\n\n // ============================================================================\n // STATE ACCESS\n // ============================================================================\n\n /**\n * Get orderbook state.\n */\n getOrderbook(orderbookId: string): LocalOrderbook | undefined {\n return this.handler.getOrderbook(orderbookId);\n }\n\n /**\n * Get user state.\n */\n getUserState(user: string): UserState | undefined {\n return this.handler.getUserState(user);\n }\n\n /**\n * Get price history state.\n */\n getPriceHistory(\n orderbookId: string,\n resolution: string\n ): PriceHistory | undefined {\n return this.handler.getPriceHistory(orderbookId, resolution);\n }\n\n /**\n * Get auth credentials (if authenticated).\n */\n getAuthCredentials(): AuthCredentials | undefined {\n return this.authCredentials;\n }\n\n /**\n * Get user public key (if authenticated).\n */\n userPubkey(): string | undefined {\n return this.authCredentials?.userPubkey;\n }\n\n // ============================================================================\n // EVENT HANDLING\n // ============================================================================\n\n /**\n * Register an event callback.\n */\n on(callback: EventCallback): void {\n this.eventCallbacks.push(callback);\n }\n\n /**\n * Remove an event callback.\n */\n off(callback: EventCallback): void {\n const index = this.eventCallbacks.indexOf(callback);\n if (index !== -1) {\n this.eventCallbacks.splice(index, 1);\n }\n }\n\n /**\n * Get the WebSocket URL.\n */\n getUrl(): string {\n return this.url;\n }\n\n /**\n * Get the configuration.\n */\n getConfig(): Required<WebSocketConfig> {\n return this.config;\n }\n}\n"]}