@pythnetwork/pyth-lazer-sdk 0.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import type { Logger } from "ts-log";
2
1
  import type { ParsedPayload, Request, Response } from "./protocol.js";
2
+ import type { WebSocketPoolConfig } from "./socket/websocket-pool.js";
3
3
  export type BinaryResponse = {
4
4
  subscriptionId: number;
5
5
  evm?: Buffer | undefined;
@@ -25,7 +25,7 @@ export declare class PythLazerClient {
25
25
  * @param numConnections - The number of parallel WebSocket connections to establish (default: 3). A higher number gives a more reliable stream. The connections will round-robin across the provided URLs.
26
26
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
27
27
  */
28
- static create(urls: string[], token: string, numConnections?: number, logger?: Logger): Promise<PythLazerClient>;
28
+ static create(config: WebSocketPoolConfig): Promise<PythLazerClient>;
29
29
  /**
30
30
  * Adds a message listener that receives either JSON or binary responses from the WebSocket connections.
31
31
  * The listener will be called for each message received, with deduplication across redundant connections.
@@ -33,9 +33,9 @@ export declare class PythLazerClient {
33
33
  * or a binary response containing EVM, Solana, or parsed payload data.
34
34
  */
35
35
  addMessageListener(handler: (event: JsonOrBinaryResponse) => void): void;
36
- subscribe(request: Request): Promise<void>;
37
- unsubscribe(subscriptionId: number): Promise<void>;
38
- send(request: Request): Promise<void>;
36
+ subscribe(request: Request): void;
37
+ unsubscribe(subscriptionId: number): void;
38
+ send(request: Request): void;
39
39
  /**
40
40
  * Registers a handler function that will be called whenever all WebSocket connections are down or attempting to reconnect.
41
41
  * The connections may still try to reconnect in the background. To shut down the pool, call `shutdown()`.
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PythLazerClient = void 0;
4
- const ts_log_1 = require("ts-log");
5
4
  const protocol_js_1 = require("./protocol.js");
6
5
  const websocket_pool_js_1 = require("./socket/websocket-pool.js");
7
6
  const UINT16_NUM_BYTES = 2;
@@ -19,8 +18,8 @@ class PythLazerClient {
19
18
  * @param numConnections - The number of parallel WebSocket connections to establish (default: 3). A higher number gives a more reliable stream. The connections will round-robin across the provided URLs.
20
19
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
21
20
  */
22
- static async create(urls, token, numConnections = 3, logger = ts_log_1.dummyLogger) {
23
- const wsp = await websocket_pool_js_1.WebSocketPool.create(urls, token, numConnections, logger);
21
+ static async create(config) {
22
+ const wsp = await websocket_pool_js_1.WebSocketPool.create(config);
24
23
  return new PythLazerClient(wsp);
25
24
  }
26
25
  /**
@@ -81,17 +80,17 @@ class PythLazerClient {
81
80
  }
82
81
  });
83
82
  }
84
- async subscribe(request) {
83
+ subscribe(request) {
85
84
  if (request.type !== "subscribe") {
86
85
  throw new Error("Request must be a subscribe request");
87
86
  }
88
- await this.wsp.addSubscription(request);
87
+ this.wsp.addSubscription(request);
89
88
  }
90
- async unsubscribe(subscriptionId) {
91
- await this.wsp.removeSubscription(subscriptionId);
89
+ unsubscribe(subscriptionId) {
90
+ this.wsp.removeSubscription(subscriptionId);
92
91
  }
93
- async send(request) {
94
- await this.wsp.sendRequest(request);
92
+ send(request) {
93
+ this.wsp.sendRequest(request);
95
94
  }
96
95
  /**
97
96
  * Registers a handler function that will be called whenever all WebSocket connections are down or attempting to reconnect.
@@ -1,3 +1,2 @@
1
- import { PublicKey } from "@solana/web3.js";
2
- export declare const SOLANA_LAZER_PROGRAM_ID: PublicKey;
3
- export declare const SOLANA_STORAGE_ID: PublicKey;
1
+ export declare const SOLANA_LAZER_PROGRAM_ID = "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt";
2
+ export declare const SOLANA_LAZER_STORAGE_ID = "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL";
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SOLANA_STORAGE_ID = exports.SOLANA_LAZER_PROGRAM_ID = void 0;
4
- const web3_js_1 = require("@solana/web3.js");
5
- exports.SOLANA_LAZER_PROGRAM_ID = new web3_js_1.PublicKey("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");
6
- exports.SOLANA_STORAGE_ID = new web3_js_1.PublicKey("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
3
+ exports.SOLANA_LAZER_STORAGE_ID = exports.SOLANA_LAZER_PROGRAM_ID = void 0;
4
+ exports.SOLANA_LAZER_PROGRAM_ID = "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt";
5
+ exports.SOLANA_LAZER_STORAGE_ID = "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL";
@@ -1,4 +1,3 @@
1
1
  export * from "./client.js";
2
2
  export * from "./protocol.js";
3
- export * from "./ed25519.js";
4
3
  export * from "./constants.js";
package/dist/cjs/index.js CHANGED
@@ -16,5 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./client.js"), exports);
18
18
  __exportStar(require("./protocol.js"), exports);
19
- __exportStar(require("./ed25519.js"), exports);
20
19
  __exportStar(require("./constants.js"), exports);
@@ -2,29 +2,48 @@ import type { ClientRequestArgs } from "node:http";
2
2
  import type { ClientOptions, ErrorEvent } from "isomorphic-ws";
3
3
  import WebSocket from "isomorphic-ws";
4
4
  import type { Logger } from "ts-log";
5
- export declare class ResilientWebSocket {
5
+ export type ResilientWebSocketConfig = {
6
6
  endpoint: string;
7
+ wsOptions?: ClientOptions | ClientRequestArgs | undefined;
8
+ logger?: Logger;
9
+ heartbeatTimeoutDurationMs?: number;
10
+ maxRetryDelayMs?: number;
11
+ logAfterRetryCount?: number;
12
+ };
13
+ export declare class ResilientWebSocket {
14
+ private endpoint;
15
+ private wsOptions?;
16
+ private logger;
17
+ private heartbeatTimeoutDurationMs;
18
+ private maxRetryDelayMs;
19
+ private logAfterRetryCount;
7
20
  wsClient: undefined | WebSocket;
8
21
  wsUserClosed: boolean;
9
- private wsOptions;
10
22
  private wsFailedAttempts;
11
- private heartbeatTimeout;
12
- private logger;
13
- private connectionPromise;
14
- private resolveConnection;
15
- private rejectConnection;
23
+ private heartbeatTimeout?;
24
+ private retryTimeout?;
16
25
  private _isReconnecting;
17
- get isReconnecting(): boolean;
18
- get isConnected(): boolean;
26
+ isReconnecting(): boolean;
27
+ isConnected(): this is this & {
28
+ wsClient: WebSocket;
29
+ };
30
+ private shouldLogRetry;
19
31
  onError: (error: ErrorEvent) => void;
20
32
  onMessage: (data: WebSocket.Data) => void;
21
33
  onReconnect: () => void;
22
- constructor(endpoint: string, wsOptions?: ClientOptions | ClientRequestArgs, logger?: Logger);
23
- send(data: string | Buffer): Promise<void>;
24
- startWebSocket(): Promise<void>;
34
+ constructor(config: ResilientWebSocketConfig);
35
+ send(data: string | Buffer): void;
36
+ startWebSocket(): void;
25
37
  private resetHeartbeat;
26
- private waitForMaybeReadyWebSocket;
27
- private handleClose;
28
- private restartUnexpectedClosedWebsocket;
38
+ private handleReconnect;
29
39
  closeWebSocket(): void;
40
+ /**
41
+ * Calculates the delay in milliseconds for exponential backoff based on the number of failed attempts.
42
+ *
43
+ * The delay increases exponentially with each attempt, starting at 20ms for the first attempt,
44
+ * and is capped at maxRetryDelayMs for attempts greater than or equal to 10.
45
+ *
46
+ * @returns The calculated delay in milliseconds before the next retry.
47
+ */
48
+ private retryDelayMs;
30
49
  }
@@ -5,38 +5,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ResilientWebSocket = void 0;
7
7
  const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
8
- const HEARTBEAT_TIMEOUT_DURATION = 10_000;
9
- const CONNECTION_TIMEOUT = 5000;
8
+ const ts_log_1 = require("ts-log");
9
+ const DEFAULT_HEARTBEAT_TIMEOUT_DURATION_MS = 5000; // 5 seconds
10
+ const DEFAULT_MAX_RETRY_DELAY_MS = 1000; // 1 second'
11
+ const DEFAULT_LOG_AFTER_RETRY_COUNT = 10;
10
12
  class ResilientWebSocket {
11
13
  endpoint;
12
- wsClient;
13
- wsUserClosed;
14
14
  wsOptions;
15
+ logger;
16
+ heartbeatTimeoutDurationMs;
17
+ maxRetryDelayMs;
18
+ logAfterRetryCount;
19
+ wsClient;
20
+ wsUserClosed = false;
15
21
  wsFailedAttempts;
16
22
  heartbeatTimeout;
17
- logger;
18
- connectionPromise;
19
- resolveConnection;
20
- rejectConnection;
23
+ retryTimeout;
21
24
  _isReconnecting = false;
22
- get isReconnecting() {
25
+ isReconnecting() {
23
26
  return this._isReconnecting;
24
27
  }
25
- get isConnected() {
28
+ isConnected() {
26
29
  return this.wsClient?.readyState === isomorphic_ws_1.default.OPEN;
27
30
  }
31
+ shouldLogRetry() {
32
+ return this.wsFailedAttempts % this.logAfterRetryCount === 0;
33
+ }
28
34
  onError;
29
35
  onMessage;
30
36
  onReconnect;
31
- constructor(endpoint, wsOptions, logger) {
32
- this.endpoint = endpoint;
33
- this.wsOptions = wsOptions;
34
- this.logger = logger;
37
+ constructor(config) {
38
+ this.endpoint = config.endpoint;
39
+ this.wsOptions = config.wsOptions;
40
+ this.logger = config.logger ?? ts_log_1.dummyLogger;
41
+ this.heartbeatTimeoutDurationMs =
42
+ config.heartbeatTimeoutDurationMs ??
43
+ DEFAULT_HEARTBEAT_TIMEOUT_DURATION_MS;
44
+ this.maxRetryDelayMs = config.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
45
+ this.logAfterRetryCount =
46
+ config.logAfterRetryCount ?? DEFAULT_LOG_AFTER_RETRY_COUNT;
35
47
  this.wsFailedAttempts = 0;
36
48
  this.onError = (error) => {
37
- this.logger?.error(error.error);
49
+ void error;
38
50
  };
39
- this.wsUserClosed = true;
40
51
  this.onMessage = (data) => {
41
52
  void data;
42
53
  };
@@ -44,144 +55,117 @@ class ResilientWebSocket {
44
55
  // Empty function, can be set by the user.
45
56
  };
46
57
  }
47
- async send(data) {
48
- this.logger?.info(`Sending message`);
49
- await this.waitForMaybeReadyWebSocket();
50
- if (this.wsClient === undefined) {
51
- this.logger?.error("Couldn't connect to the websocket server. Error callback is called.");
58
+ send(data) {
59
+ this.logger.debug(`Sending message`);
60
+ if (this.isConnected()) {
61
+ this.wsClient.send(data);
52
62
  }
53
63
  else {
54
- this.wsClient.send(data);
64
+ this.logger.warn(`WebSocket to ${this.endpoint} is not connected. Cannot send message.`);
55
65
  }
56
66
  }
57
- async startWebSocket() {
67
+ startWebSocket() {
68
+ if (this.wsUserClosed) {
69
+ this.logger.error("Connection was explicitly closed by user. Will not reconnect.");
70
+ return;
71
+ }
58
72
  if (this.wsClient !== undefined) {
59
- // If there's an existing connection attempt, wait for it
60
- if (this.connectionPromise) {
61
- return this.connectionPromise;
62
- }
73
+ this.logger.info("WebSocket client already started.");
63
74
  return;
64
75
  }
65
- this.logger?.info(`Creating Web Socket client`);
66
- // Create a new promise for this connection attempt
67
- this.connectionPromise = new Promise((resolve, reject) => {
68
- this.resolveConnection = resolve;
69
- this.rejectConnection = reject;
70
- });
71
- // Set a connection timeout
72
- const timeoutId = setTimeout(() => {
73
- if (this.rejectConnection) {
74
- this.rejectConnection(new Error(`Connection timeout after ${String(CONNECTION_TIMEOUT)}ms`));
75
- }
76
- }, CONNECTION_TIMEOUT);
76
+ if (this.wsFailedAttempts == 0) {
77
+ this.logger.info(`Creating Web Socket client`);
78
+ }
79
+ if (this.retryTimeout !== undefined) {
80
+ clearTimeout(this.retryTimeout);
81
+ this.retryTimeout = undefined;
82
+ }
77
83
  this.wsClient = new isomorphic_ws_1.default(this.endpoint, this.wsOptions);
78
- this.wsUserClosed = false;
79
84
  this.wsClient.addEventListener("open", () => {
85
+ this.logger.info("WebSocket connection established");
80
86
  this.wsFailedAttempts = 0;
81
- this.resetHeartbeat();
82
- clearTimeout(timeoutId);
83
87
  this._isReconnecting = false;
84
- this.resolveConnection?.();
88
+ this.resetHeartbeat();
89
+ this.onReconnect();
90
+ });
91
+ this.wsClient.addEventListener("close", (e) => {
92
+ if (this.wsUserClosed) {
93
+ this.logger.info(`WebSocket connection to ${this.endpoint} closed by user`);
94
+ }
95
+ else {
96
+ if (this.shouldLogRetry()) {
97
+ this.logger.warn(`WebSocket connection to ${this.endpoint} closed unexpectedly: Code: ${e.code.toString()}`);
98
+ }
99
+ this.handleReconnect();
100
+ }
85
101
  });
86
102
  this.wsClient.addEventListener("error", (event) => {
87
103
  this.onError(event);
88
- if (this.rejectConnection) {
89
- this.rejectConnection(new Error("WebSocket connection failed"));
90
- }
91
104
  });
92
105
  this.wsClient.addEventListener("message", (event) => {
93
106
  this.resetHeartbeat();
94
107
  this.onMessage(event.data);
95
108
  });
96
- this.wsClient.addEventListener("close", () => {
97
- clearTimeout(timeoutId);
98
- if (this.rejectConnection) {
99
- this.rejectConnection(new Error("WebSocket closed before connecting"));
100
- }
101
- void this.handleClose();
102
- });
103
109
  if ("on" in this.wsClient) {
104
110
  this.wsClient.on("ping", () => {
105
- this.logger?.info("Ping received");
111
+ this.logger.info("Ping received");
106
112
  this.resetHeartbeat();
107
113
  });
108
114
  }
109
- return this.connectionPromise;
110
115
  }
111
116
  resetHeartbeat() {
112
117
  if (this.heartbeatTimeout !== undefined) {
113
118
  clearTimeout(this.heartbeatTimeout);
114
119
  }
115
120
  this.heartbeatTimeout = setTimeout(() => {
116
- this.logger?.warn("Connection timed out. Reconnecting...");
121
+ this.logger.warn("Connection timed out. Reconnecting...");
117
122
  this.wsClient?.terminate();
118
- void this.restartUnexpectedClosedWebsocket();
119
- }, HEARTBEAT_TIMEOUT_DURATION);
123
+ this.handleReconnect();
124
+ }, this.heartbeatTimeoutDurationMs);
120
125
  }
121
- async waitForMaybeReadyWebSocket() {
122
- let waitedTime = 0;
123
- while (this.wsClient !== undefined &&
124
- this.wsClient.readyState !== this.wsClient.OPEN) {
125
- if (waitedTime > 5000) {
126
- this.wsClient.close();
127
- return;
128
- }
129
- else {
130
- waitedTime += 10;
131
- await sleep(10);
132
- }
126
+ handleReconnect() {
127
+ if (this.wsUserClosed) {
128
+ this.logger.info("WebSocket connection closed by user, not reconnecting.");
129
+ return;
133
130
  }
134
- }
135
- async handleClose() {
136
131
  if (this.heartbeatTimeout !== undefined) {
137
132
  clearTimeout(this.heartbeatTimeout);
138
133
  }
139
- if (this.wsUserClosed) {
140
- this.logger?.info("The connection has been closed successfully.");
134
+ if (this.retryTimeout !== undefined) {
135
+ clearTimeout(this.retryTimeout);
141
136
  }
142
- else {
143
- this.wsFailedAttempts += 1;
144
- this.wsClient = undefined;
145
- this.connectionPromise = undefined;
146
- this.resolveConnection = undefined;
147
- this.rejectConnection = undefined;
148
- const waitTime = expoBackoff(this.wsFailedAttempts);
149
- this._isReconnecting = true;
150
- this.logger?.error("Connection closed unexpectedly or because of timeout. Reconnecting after " +
151
- String(waitTime) +
137
+ this.wsFailedAttempts += 1;
138
+ this.wsClient = undefined;
139
+ this._isReconnecting = true;
140
+ if (this.shouldLogRetry()) {
141
+ this.logger.error("Connection closed unexpectedly or because of timeout. Reconnecting after " +
142
+ String(this.retryDelayMs()) +
152
143
  "ms.");
153
- await sleep(waitTime);
154
- await this.restartUnexpectedClosedWebsocket();
155
144
  }
156
- }
157
- async restartUnexpectedClosedWebsocket() {
158
- if (this.wsUserClosed) {
159
- return;
160
- }
161
- await this.startWebSocket();
162
- await this.waitForMaybeReadyWebSocket();
163
- if (this.wsClient === undefined) {
164
- this.logger?.error("Couldn't reconnect to websocket. Error callback is called.");
165
- return;
166
- }
167
- this.onReconnect();
145
+ this.retryTimeout = setTimeout(() => {
146
+ this.startWebSocket();
147
+ }, this.retryDelayMs());
168
148
  }
169
149
  closeWebSocket() {
170
150
  if (this.wsClient !== undefined) {
171
- const client = this.wsClient;
151
+ this.wsClient.close();
172
152
  this.wsClient = undefined;
173
- this.connectionPromise = undefined;
174
- this.resolveConnection = undefined;
175
- this.rejectConnection = undefined;
176
- client.close();
177
153
  }
178
154
  this.wsUserClosed = true;
179
155
  }
156
+ /**
157
+ * Calculates the delay in milliseconds for exponential backoff based on the number of failed attempts.
158
+ *
159
+ * The delay increases exponentially with each attempt, starting at 20ms for the first attempt,
160
+ * and is capped at maxRetryDelayMs for attempts greater than or equal to 10.
161
+ *
162
+ * @returns The calculated delay in milliseconds before the next retry.
163
+ */
164
+ retryDelayMs() {
165
+ if (this.wsFailedAttempts >= 10) {
166
+ return this.maxRetryDelayMs;
167
+ }
168
+ return Math.min(2 ** this.wsFailedAttempts * 10, this.maxRetryDelayMs);
169
+ }
180
170
  }
181
171
  exports.ResilientWebSocket = ResilientWebSocket;
182
- async function sleep(ms) {
183
- return new Promise((resolve) => setTimeout(resolve, ms));
184
- }
185
- function expoBackoff(attempts) {
186
- return 2 ** attempts * 100;
187
- }
@@ -1,7 +1,17 @@
1
+ import type { ErrorEvent } from "isomorphic-ws";
1
2
  import WebSocket from "isomorphic-ws";
2
3
  import type { Logger } from "ts-log";
3
4
  import type { Request } from "../protocol.js";
5
+ import type { ResilientWebSocketConfig } from "./resilient-websocket.js";
4
6
  import { ResilientWebSocket } from "./resilient-websocket.js";
7
+ export type WebSocketPoolConfig = {
8
+ urls: string[];
9
+ token: string;
10
+ numConnections?: number;
11
+ logger?: Logger;
12
+ rwsConfig?: Omit<ResilientWebSocketConfig, "logger" | "endpoint">;
13
+ onError?: (error: ErrorEvent) => void;
14
+ };
5
15
  export declare class WebSocketPool {
6
16
  private readonly logger;
7
17
  rwsPool: ResilientWebSocket[];
@@ -20,7 +30,7 @@ export declare class WebSocketPool {
20
30
  * @param numConnections - Number of parallel WebSocket connections to maintain (default: 3)
21
31
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
22
32
  */
23
- static create(urls: string[], token: string, numConnections?: number, logger?: Logger): Promise<WebSocketPool>;
33
+ static create(config: WebSocketPoolConfig): Promise<WebSocketPool>;
24
34
  /**
25
35
  * Checks for error responses in JSON messages and throws appropriate errors
26
36
  */
@@ -30,9 +40,9 @@ export declare class WebSocketPool {
30
40
  * multiple connections before forwarding to registered handlers
31
41
  */
32
42
  dedupeHandler: (data: WebSocket.Data) => void;
33
- sendRequest(request: Request): Promise<void>;
34
- addSubscription(request: Request): Promise<void>;
35
- removeSubscription(subscriptionId: number): Promise<void>;
43
+ sendRequest(request: Request): void;
44
+ addSubscription(request: Request): void;
45
+ removeSubscription(subscriptionId: number): void;
36
46
  addMessageListener(handler: (data: WebSocket.Data) => void): void;
37
47
  /**
38
48
  * Calls the handler if all websocket connections are currently down or in reconnecting state.
@@ -40,6 +50,7 @@ export declare class WebSocketPool {
40
50
  */
41
51
  addAllConnectionsDownListener(handler: () => void): void;
42
52
  private areAllConnectionsDown;
53
+ private isAnyConnectionEstablished;
43
54
  private checkConnectionStates;
44
55
  shutdown(): void;
45
56
  }
@@ -7,7 +7,7 @@ exports.WebSocketPool = void 0;
7
7
  const ttlcache_1 = __importDefault(require("@isaacs/ttlcache"));
8
8
  const ts_log_1 = require("ts-log");
9
9
  const resilient_websocket_js_1 = require("./resilient-websocket.js");
10
- const DEFAULT_NUM_CONNECTIONS = 3;
10
+ const DEFAULT_NUM_CONNECTIONS = 4;
11
11
  class WebSocketPool {
12
12
  logger;
13
13
  rwsPool;
@@ -17,7 +17,7 @@ class WebSocketPool {
17
17
  allConnectionsDownListeners;
18
18
  wasAllDown = true;
19
19
  checkConnectionStatesInterval;
20
- constructor(logger = ts_log_1.dummyLogger) {
20
+ constructor(logger) {
21
21
  this.logger = logger;
22
22
  this.rwsPool = [];
23
23
  this.cache = new ttlcache_1.default({ ttl: 1000 * 10 }); // TTL of 10 seconds
@@ -37,24 +37,30 @@ class WebSocketPool {
37
37
  * @param numConnections - Number of parallel WebSocket connections to maintain (default: 3)
38
38
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
39
39
  */
40
- static async create(urls, token, numConnections = DEFAULT_NUM_CONNECTIONS, logger = ts_log_1.dummyLogger) {
41
- if (urls.length === 0) {
40
+ static async create(config) {
41
+ if (config.urls.length === 0) {
42
42
  throw new Error("No URLs provided");
43
43
  }
44
+ const logger = config.logger ?? ts_log_1.dummyLogger;
44
45
  const pool = new WebSocketPool(logger);
45
- // Create all websocket instances
46
- const connectionPromises = [];
46
+ const numConnections = config.numConnections ?? DEFAULT_NUM_CONNECTIONS;
47
47
  for (let i = 0; i < numConnections; i++) {
48
- const url = urls[i % urls.length];
48
+ const url = config.urls[i % config.urls.length];
49
49
  if (!url) {
50
50
  throw new Error(`URLs must not be null or empty`);
51
51
  }
52
52
  const wsOptions = {
53
+ ...config.rwsConfig?.wsOptions,
53
54
  headers: {
54
- Authorization: `Bearer ${token}`,
55
+ Authorization: `Bearer ${config.token}`,
55
56
  },
56
57
  };
57
- const rws = new resilient_websocket_js_1.ResilientWebSocket(url, wsOptions, logger);
58
+ const rws = new resilient_websocket_js_1.ResilientWebSocket({
59
+ ...config.rwsConfig,
60
+ endpoint: url,
61
+ wsOptions,
62
+ logger,
63
+ });
58
64
  // If a websocket client unexpectedly disconnects, ResilientWebSocket will reestablish
59
65
  // the connection and call the onReconnect callback.
60
66
  rws.onReconnect = () => {
@@ -63,29 +69,26 @@ class WebSocketPool {
63
69
  }
64
70
  for (const [, request] of pool.subscriptions) {
65
71
  try {
66
- void rws.send(JSON.stringify(request));
72
+ rws.send(JSON.stringify(request));
67
73
  }
68
74
  catch (error) {
69
75
  pool.logger.error("Failed to resend subscription on reconnect:", error);
70
76
  }
71
77
  }
72
78
  };
79
+ if (config.onError) {
80
+ rws.onError = config.onError;
81
+ }
73
82
  // Handle all client messages ourselves. Dedupe before sending to registered message handlers.
74
83
  rws.onMessage = pool.dedupeHandler;
75
84
  pool.rwsPool.push(rws);
76
- // Start the websocket and collect the promise
77
- connectionPromises.push(rws.startWebSocket());
78
- }
79
- // Wait for all connections to be established
80
- try {
81
- await Promise.all(connectionPromises);
85
+ rws.startWebSocket();
82
86
  }
83
- catch (error) {
84
- // If any connection fails, clean up and throw
85
- pool.shutdown();
86
- throw error;
87
+ pool.logger.info(`Started WebSocketPool with ${numConnections.toString()} connections. Waiting for at least one to connect...`);
88
+ while (!pool.isAnyConnectionEstablished()) {
89
+ await new Promise((resolve) => setTimeout(resolve, 100));
87
90
  }
88
- pool.logger.info(`Successfully established ${numConnections.toString()} redundant WebSocket connections`);
91
+ pool.logger.info(`At least one WebSocket connection is established. WebSocketPool is ready.`);
89
92
  return pool;
90
93
  }
91
94
  /**
@@ -120,32 +123,25 @@ class WebSocketPool {
120
123
  handler(data);
121
124
  }
122
125
  };
123
- async sendRequest(request) {
124
- const sendPromises = this.rwsPool.map(async (rws) => {
125
- try {
126
- await rws.send(JSON.stringify(request));
127
- }
128
- catch (error) {
129
- this.logger.error("Failed to send request:", error);
130
- throw error;
131
- }
132
- });
133
- await Promise.all(sendPromises);
126
+ sendRequest(request) {
127
+ for (const rws of this.rwsPool) {
128
+ rws.send(JSON.stringify(request));
129
+ }
134
130
  }
135
- async addSubscription(request) {
131
+ addSubscription(request) {
136
132
  if (request.type !== "subscribe") {
137
133
  throw new Error("Request must be a subscribe request");
138
134
  }
139
135
  this.subscriptions.set(request.subscriptionId, request);
140
- await this.sendRequest(request);
136
+ this.sendRequest(request);
141
137
  }
142
- async removeSubscription(subscriptionId) {
138
+ removeSubscription(subscriptionId) {
143
139
  this.subscriptions.delete(subscriptionId);
144
140
  const request = {
145
141
  type: "unsubscribe",
146
142
  subscriptionId,
147
143
  };
148
- await this.sendRequest(request);
144
+ this.sendRequest(request);
149
145
  }
150
146
  addMessageListener(handler) {
151
147
  this.messageListeners.push(handler);
@@ -158,7 +154,10 @@ class WebSocketPool {
158
154
  this.allConnectionsDownListeners.push(handler);
159
155
  }
160
156
  areAllConnectionsDown() {
161
- return this.rwsPool.every((ws) => !ws.isConnected || ws.isReconnecting);
157
+ return this.rwsPool.every((ws) => !ws.isConnected() || ws.isReconnecting());
158
+ }
159
+ isAnyConnectionEstablished() {
160
+ return this.rwsPool.some((ws) => ws.isConnected());
162
161
  }
163
162
  checkConnectionStates() {
164
163
  const allDown = this.areAllConnectionsDown();