@pythnetwork/pyth-lazer-sdk 5.2.1 → 6.2.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,21 +1,26 @@
1
1
  import TTLCache from "@isaacs/ttlcache";
2
2
  import { dummyLogger } from "ts-log";
3
- import { ResilientWebSocket } from "./resilient-websocket.mjs";
4
3
  import { DEFAULT_STREAM_SERVICE_0_URL, DEFAULT_STREAM_SERVICE_1_URL } from "../constants.mjs";
5
4
  import { IsomorphicEventEmitter } from "../emitter/index.mjs";
6
5
  import { addAuthTokenToWebSocketUrl, bufferFromWebsocketData, envIsBrowserOrWorker } from "../util/index.mjs";
6
+ import { ResilientWebSocket } from "./resilient-websocket.mjs";
7
7
  const DEFAULT_NUM_CONNECTIONS = 4;
8
8
  export class WebSocketPool extends IsomorphicEventEmitter {
9
9
  logger;
10
+ abortSignal;
10
11
  rwsPool;
11
12
  cache;
12
13
  subscriptions;
13
14
  messageListeners;
14
15
  allConnectionsDownListeners;
16
+ connectionRestoredListeners;
17
+ connectionTimeoutListeners;
18
+ connectionReconnectListeners;
15
19
  wasAllDown = true;
16
20
  checkConnectionStatesInterval;
17
- constructor(logger){
18
- super(), this.logger = logger;
21
+ isShutdown = false;
22
+ constructor(logger, abortSignal){
23
+ super(), this.logger = logger, this.abortSignal = abortSignal;
19
24
  this.rwsPool = [];
20
25
  this.cache = new TTLCache({
21
26
  ttl: 1000 * 10
@@ -23,6 +28,9 @@ export class WebSocketPool extends IsomorphicEventEmitter {
23
28
  this.subscriptions = new Map();
24
29
  this.messageListeners = [];
25
30
  this.allConnectionsDownListeners = [];
31
+ this.connectionRestoredListeners = [];
32
+ this.connectionTimeoutListeners = [];
33
+ this.connectionReconnectListeners = [];
26
34
  // Start monitoring connection states
27
35
  this.checkConnectionStatesInterval = setInterval(()=>{
28
36
  this.checkConnectionStates();
@@ -35,13 +43,13 @@ export class WebSocketPool extends IsomorphicEventEmitter {
35
43
  * @param token - Authentication token to use for the connections
36
44
  * @param numConnections - Number of parallel WebSocket connections to maintain (default: 3)
37
45
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
38
- */ static async create(config, token, logger) {
46
+ */ static async create(config, token, abortSignal, logger) {
39
47
  const urls = config.urls ?? [
40
48
  DEFAULT_STREAM_SERVICE_0_URL,
41
49
  DEFAULT_STREAM_SERVICE_1_URL
42
50
  ];
43
51
  const log = logger ?? dummyLogger;
44
- const pool = new WebSocketPool(log);
52
+ const pool = new WebSocketPool(log, abortSignal);
45
53
  const numConnections = config.numConnections ?? DEFAULT_NUM_CONNECTIONS;
46
54
  // bind a handler to capture any emitted errors and send them to the user-provided
47
55
  // onWebSocketPoolError callback (if it is present)
@@ -68,15 +76,20 @@ export class WebSocketPool extends IsomorphicEventEmitter {
68
76
  const rws = new ResilientWebSocket({
69
77
  ...config.rwsConfig,
70
78
  endpoint: url,
71
- wsOptions,
72
- logger: log
79
+ logger: log,
80
+ wsOptions
73
81
  });
82
+ const connectionIndex = i;
83
+ const connectionEndpoint = url;
74
84
  // If a websocket client unexpectedly disconnects, ResilientWebSocket will reestablish
75
85
  // the connection and call the onReconnect callback.
76
86
  rws.onReconnect = ()=>{
77
- if (rws.wsUserClosed) {
87
+ if (rws.wsUserClosed || pool.isShutdown || pool.abortSignal?.aborted) {
78
88
  return;
79
89
  }
90
+ for (const listener of pool.connectionReconnectListeners){
91
+ listener(connectionIndex, connectionEndpoint);
92
+ }
80
93
  for (const [, request] of pool.subscriptions){
81
94
  try {
82
95
  rws.send(JSON.stringify(request));
@@ -85,6 +98,11 @@ export class WebSocketPool extends IsomorphicEventEmitter {
85
98
  }
86
99
  }
87
100
  };
101
+ rws.onTimeout = ()=>{
102
+ for (const listener of pool.connectionTimeoutListeners){
103
+ listener(connectionIndex, connectionEndpoint);
104
+ }
105
+ };
88
106
  // eslint-disable-next-line @typescript-eslint/no-deprecated
89
107
  const onErrorHandler = config.onWebSocketError ?? config.onError;
90
108
  if (typeof onErrorHandler === "function") {
@@ -101,6 +119,11 @@ export class WebSocketPool extends IsomorphicEventEmitter {
101
119
  }
102
120
  pool.logger.info(`Started WebSocketPool with ${numConnections.toString()} connections. Waiting for at least one to connect...`);
103
121
  while(!pool.isAnyConnectionEstablished()){
122
+ if (pool.abortSignal?.aborted) {
123
+ pool.logger.warn("the WebSocket Pool's abort signal was aborted during connection. Shutting down.");
124
+ pool.shutdown();
125
+ return pool;
126
+ }
104
127
  await new Promise((resolve)=>setTimeout(resolve, 100));
105
128
  }
106
129
  pool.logger.info(`At least one WebSocket connection is established. WebSocketPool is ready.`);
@@ -156,6 +179,10 @@ export class WebSocketPool extends IsomorphicEventEmitter {
156
179
  }
157
180
  };
158
181
  sendRequest(request) {
182
+ if (this.isShutdown || this.abortSignal?.aborted) {
183
+ this.logger.warn("Cannot send request: WebSocketPool is shutdown or aborted");
184
+ return;
185
+ }
159
186
  for (const rws of this.rwsPool){
160
187
  try {
161
188
  rws.send(JSON.stringify(request));
@@ -165,6 +192,10 @@ export class WebSocketPool extends IsomorphicEventEmitter {
165
192
  }
166
193
  }
167
194
  addSubscription(request) {
195
+ if (this.isShutdown || this.abortSignal?.aborted) {
196
+ this.logger.warn("Cannot add subscription: WebSocketPool is shutdown or aborted");
197
+ return;
198
+ }
168
199
  if (request.type !== "subscribe") {
169
200
  this.emitPoolError(new Error("Request must be a subscribe request"));
170
201
  return;
@@ -173,10 +204,14 @@ export class WebSocketPool extends IsomorphicEventEmitter {
173
204
  this.sendRequest(request);
174
205
  }
175
206
  removeSubscription(subscriptionId) {
207
+ if (this.isShutdown || this.abortSignal?.aborted) {
208
+ this.logger.warn("Cannot remove subscription: WebSocketPool is shutdown or aborted");
209
+ return;
210
+ }
176
211
  this.subscriptions.delete(subscriptionId);
177
212
  const request = {
178
- type: "unsubscribe",
179
- subscriptionId
213
+ subscriptionId,
214
+ type: "unsubscribe"
180
215
  };
181
216
  this.sendRequest(request);
182
217
  }
@@ -189,6 +224,23 @@ export class WebSocketPool extends IsomorphicEventEmitter {
189
224
  */ addAllConnectionsDownListener(handler) {
190
225
  this.allConnectionsDownListeners.push(handler);
191
226
  }
227
+ /**
228
+ * Calls the handler when at least one connection is restored after all connections were down.
229
+ */ addConnectionRestoredListener(handler) {
230
+ this.connectionRestoredListeners.push(handler);
231
+ }
232
+ /**
233
+ * Calls the handler when an individual connection times out (heartbeat timeout).
234
+ * @param handler - Callback with connection index and endpoint URL
235
+ */ addConnectionTimeoutListener(handler) {
236
+ this.connectionTimeoutListeners.push(handler);
237
+ }
238
+ /**
239
+ * Calls the handler when an individual connection reconnects after being down.
240
+ * @param handler - Callback with connection index and endpoint URL
241
+ */ addConnectionReconnectListener(handler) {
242
+ this.connectionReconnectListeners.push(handler);
243
+ }
192
244
  areAllConnectionsDown() {
193
245
  return this.rwsPool.every((ws)=>!ws.isConnected() || ws.isReconnecting());
194
246
  }
@@ -196,6 +248,10 @@ export class WebSocketPool extends IsomorphicEventEmitter {
196
248
  return this.rwsPool.some((ws)=>ws.isConnected());
197
249
  }
198
250
  checkConnectionStates() {
251
+ // Stop monitoring if shutdown or aborted
252
+ if (this.isShutdown || this.abortSignal?.aborted) {
253
+ return;
254
+ }
199
255
  const allDown = this.areAllConnectionsDown();
200
256
  // If all connections just went down
201
257
  if (allDown && !this.wasAllDown) {
@@ -210,12 +266,25 @@ export class WebSocketPool extends IsomorphicEventEmitter {
210
266
  }
211
267
  }
212
268
  }
213
- // If at least one connection was restored
269
+ // If at least one connection was restored after all were down
214
270
  if (!allDown && this.wasAllDown) {
215
271
  this.wasAllDown = false;
272
+ this.logger.info("At least one WebSocket connection restored");
273
+ for (const listener of this.connectionRestoredListeners){
274
+ try {
275
+ listener();
276
+ } catch (error) {
277
+ this.emitPoolError(error, "Connection-restored listener threw");
278
+ }
279
+ }
216
280
  }
217
281
  }
218
282
  shutdown() {
283
+ // Prevent multiple shutdown calls
284
+ if (this.isShutdown) {
285
+ return;
286
+ }
287
+ this.isShutdown = true;
219
288
  for (const rws of this.rwsPool){
220
289
  rws.closeWebSocket();
221
290
  }
@@ -223,6 +292,9 @@ export class WebSocketPool extends IsomorphicEventEmitter {
223
292
  this.subscriptions.clear();
224
293
  this.messageListeners = [];
225
294
  this.allConnectionsDownListeners = [];
295
+ this.connectionRestoredListeners = [];
296
+ this.connectionTimeoutListeners = [];
297
+ this.connectionReconnectListeners = [];
226
298
  clearInterval(this.checkConnectionStatesInterval);
227
299
  // execute all bound shutdown handlers
228
300
  for (const shutdownHandler of this.getListeners("shutdown")){
package/package.json CHANGED
@@ -1,176 +1,168 @@
1
1
  {
2
- "name": "@pythnetwork/pyth-lazer-sdk",
3
- "version": "5.2.1",
2
+ "bugs": {
3
+ "url": "https://github.com/pyth-network/pyth-crosschain/issues"
4
+ },
5
+ "dependencies": {
6
+ "@isaacs/ttlcache": "^1.4.1",
7
+ "buffer": "^6.0.3",
8
+ "isomorphic-ws": "^5.0.0",
9
+ "ts-log": "^2.2.7",
10
+ "ws": "^8.19.0"
11
+ },
4
12
  "description": "Pyth Lazer SDK",
13
+ "devDependencies": {
14
+ "@cprussin/tsconfig": "^4.0.2",
15
+ "@types/node": "^24.10.1",
16
+ "@types/ws": "^8.18.1",
17
+ "typedoc": "^0.28.17"
18
+ },
5
19
  "engines": {
6
20
  "node": "^24.0.0"
7
21
  },
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "files": [
12
- "dist/**/*"
13
- ],
14
- "main": "./dist/cjs/index.cjs",
15
- "types": "./dist/cjs/index.d.ts",
16
22
  "exports": {
17
- "./client": {
18
- "require": {
19
- "types": "./dist/cjs/client.d.ts",
20
- "default": "./dist/cjs/client.cjs"
23
+ ".": {
24
+ "import": {
25
+ "default": "./dist/esm/index.mjs",
26
+ "types": "./dist/esm/index.d.ts"
21
27
  },
28
+ "require": {
29
+ "default": "./dist/cjs/index.cjs",
30
+ "types": "./dist/cjs/index.d.ts"
31
+ }
32
+ },
33
+ "./client": {
22
34
  "import": {
23
- "types": "./dist/esm/client.d.ts",
24
- "default": "./dist/esm/client.mjs"
35
+ "default": "./dist/esm/client.mjs",
36
+ "types": "./dist/esm/client.d.ts"
37
+ },
38
+ "require": {
39
+ "default": "./dist/cjs/client.cjs",
40
+ "types": "./dist/cjs/client.d.ts"
25
41
  }
26
42
  },
27
43
  "./constants": {
28
- "require": {
29
- "types": "./dist/cjs/constants.d.ts",
30
- "default": "./dist/cjs/constants.cjs"
31
- },
32
44
  "import": {
33
- "types": "./dist/esm/constants.d.ts",
34
- "default": "./dist/esm/constants.mjs"
45
+ "default": "./dist/esm/constants.mjs",
46
+ "types": "./dist/esm/constants.d.ts"
47
+ },
48
+ "require": {
49
+ "default": "./dist/cjs/constants.cjs",
50
+ "types": "./dist/cjs/constants.d.ts"
35
51
  }
36
52
  },
37
53
  "./emitter": {
38
- "require": {
39
- "types": "./dist/cjs/emitter/index.d.ts",
40
- "default": "./dist/cjs/emitter/index.cjs"
41
- },
42
54
  "import": {
43
- "types": "./dist/esm/emitter/index.d.ts",
44
- "default": "./dist/esm/emitter/index.mjs"
45
- }
46
- },
47
- ".": {
48
- "require": {
49
- "types": "./dist/cjs/index.d.ts",
50
- "default": "./dist/cjs/index.cjs"
55
+ "default": "./dist/esm/emitter/index.mjs",
56
+ "types": "./dist/esm/emitter/index.d.ts"
51
57
  },
52
- "import": {
53
- "types": "./dist/esm/index.d.ts",
54
- "default": "./dist/esm/index.mjs"
58
+ "require": {
59
+ "default": "./dist/cjs/emitter/index.cjs",
60
+ "types": "./dist/cjs/emitter/index.d.ts"
55
61
  }
56
62
  },
63
+ "./package.json": "./package.json",
57
64
  "./protocol": {
58
- "require": {
59
- "types": "./dist/cjs/protocol.d.ts",
60
- "default": "./dist/cjs/protocol.cjs"
61
- },
62
65
  "import": {
63
- "types": "./dist/esm/protocol.d.ts",
64
- "default": "./dist/esm/protocol.mjs"
66
+ "default": "./dist/esm/protocol.mjs",
67
+ "types": "./dist/esm/protocol.d.ts"
68
+ },
69
+ "require": {
70
+ "default": "./dist/cjs/protocol.cjs",
71
+ "types": "./dist/cjs/protocol.d.ts"
65
72
  }
66
73
  },
67
74
  "./socket/resilient-websocket": {
68
- "require": {
69
- "types": "./dist/cjs/socket/resilient-websocket.d.ts",
70
- "default": "./dist/cjs/socket/resilient-websocket.cjs"
71
- },
72
75
  "import": {
73
- "types": "./dist/esm/socket/resilient-websocket.d.ts",
74
- "default": "./dist/esm/socket/resilient-websocket.mjs"
76
+ "default": "./dist/esm/socket/resilient-websocket.mjs",
77
+ "types": "./dist/esm/socket/resilient-websocket.d.ts"
78
+ },
79
+ "require": {
80
+ "default": "./dist/cjs/socket/resilient-websocket.cjs",
81
+ "types": "./dist/cjs/socket/resilient-websocket.d.ts"
75
82
  }
76
83
  },
77
84
  "./socket/websocket-pool": {
78
- "require": {
79
- "types": "./dist/cjs/socket/websocket-pool.d.ts",
80
- "default": "./dist/cjs/socket/websocket-pool.cjs"
85
+ "import": {
86
+ "default": "./dist/esm/socket/websocket-pool.mjs",
87
+ "types": "./dist/esm/socket/websocket-pool.d.ts"
81
88
  },
89
+ "require": {
90
+ "default": "./dist/cjs/socket/websocket-pool.cjs",
91
+ "types": "./dist/cjs/socket/websocket-pool.d.ts"
92
+ }
93
+ },
94
+ "./util": {
82
95
  "import": {
83
- "types": "./dist/esm/socket/websocket-pool.d.ts",
84
- "default": "./dist/esm/socket/websocket-pool.mjs"
96
+ "default": "./dist/esm/util/index.mjs",
97
+ "types": "./dist/esm/util/index.d.ts"
98
+ },
99
+ "require": {
100
+ "default": "./dist/cjs/util/index.cjs",
101
+ "types": "./dist/cjs/util/index.d.ts"
85
102
  }
86
103
  },
87
104
  "./util/buffer-util": {
88
- "require": {
89
- "types": "./dist/cjs/util/buffer-util.d.ts",
90
- "default": "./dist/cjs/util/buffer-util.cjs"
91
- },
92
105
  "import": {
93
- "types": "./dist/esm/util/buffer-util.d.ts",
94
- "default": "./dist/esm/util/buffer-util.mjs"
106
+ "default": "./dist/esm/util/buffer-util.mjs",
107
+ "types": "./dist/esm/util/buffer-util.d.ts"
108
+ },
109
+ "require": {
110
+ "default": "./dist/cjs/util/buffer-util.cjs",
111
+ "types": "./dist/cjs/util/buffer-util.d.ts"
95
112
  }
96
113
  },
97
114
  "./util/env-util": {
98
- "require": {
99
- "types": "./dist/cjs/util/env-util.d.ts",
100
- "default": "./dist/cjs/util/env-util.cjs"
101
- },
102
115
  "import": {
103
- "types": "./dist/esm/util/env-util.d.ts",
104
- "default": "./dist/esm/util/env-util.mjs"
105
- }
106
- },
107
- "./util": {
108
- "require": {
109
- "types": "./dist/cjs/util/index.d.ts",
110
- "default": "./dist/cjs/util/index.cjs"
116
+ "default": "./dist/esm/util/env-util.mjs",
117
+ "types": "./dist/esm/util/env-util.d.ts"
111
118
  },
112
- "import": {
113
- "types": "./dist/esm/util/index.d.ts",
114
- "default": "./dist/esm/util/index.mjs"
119
+ "require": {
120
+ "default": "./dist/cjs/util/env-util.cjs",
121
+ "types": "./dist/cjs/util/env-util.d.ts"
115
122
  }
116
123
  },
117
124
  "./util/url-util": {
118
- "require": {
119
- "types": "./dist/cjs/util/url-util.d.ts",
120
- "default": "./dist/cjs/util/url-util.cjs"
121
- },
122
125
  "import": {
123
- "types": "./dist/esm/util/url-util.d.ts",
124
- "default": "./dist/esm/util/url-util.mjs"
126
+ "default": "./dist/esm/util/url-util.mjs",
127
+ "types": "./dist/esm/util/url-util.d.ts"
128
+ },
129
+ "require": {
130
+ "default": "./dist/cjs/util/url-util.cjs",
131
+ "types": "./dist/cjs/util/url-util.d.ts"
125
132
  }
126
- },
127
- "./package.json": "./package.json"
128
- },
129
- "devDependencies": {
130
- "@cprussin/eslint-config": "^4.0.2",
131
- "@cprussin/tsconfig": "^3.1.2",
132
- "@types/jest": "^29.5.14",
133
- "@types/node": "^18.19.54",
134
- "@types/ws": "^8.5.12",
135
- "eslint": "^9.23.0",
136
- "prettier": "^3.5.3",
137
- "ts-node": "^10.9.2",
138
- "typedoc": "^0.26.8"
139
- },
140
- "bugs": {
141
- "url": "https://github.com/pyth-network/pyth-crosschain/issues"
133
+ }
142
134
  },
143
- "type": "module",
135
+ "files": [
136
+ "dist/**/*"
137
+ ],
144
138
  "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/lazer/sdk/js",
145
- "repository": {
146
- "type": "git",
147
- "url": "https://github.com/pyth-network/pyth-crosschain",
148
- "directory": "lazer/sdk/js"
149
- },
150
139
  "keywords": [
151
140
  "pyth",
152
141
  "oracle"
153
142
  ],
154
143
  "license": "Apache-2.0",
155
- "dependencies": {
156
- "@isaacs/ttlcache": "^1.4.1",
157
- "buffer": "^6.0.3",
158
- "isomorphic-ws": "^5.0.0",
159
- "ts-log": "^2.2.7",
160
- "ws": "^8.18.0"
161
- },
144
+ "main": "./dist/cjs/index.cjs",
162
145
  "module": "./dist/esm/index.mjs",
146
+ "name": "@pythnetwork/pyth-lazer-sdk",
147
+ "publishConfig": {
148
+ "access": "public"
149
+ },
150
+ "repository": {
151
+ "directory": "lazer/sdk/js",
152
+ "type": "git",
153
+ "url": "https://github.com/pyth-network/pyth-crosschain"
154
+ },
163
155
  "scripts": {
164
156
  "build": "ts-duality --clean",
165
- "fix:lint": "eslint --fix . --max-warnings 0",
166
- "test:lint": "eslint . --max-warnings 0",
167
- "test:types": "tsc",
168
- "test:format": "prettier --check .",
169
- "fix:format": "prettier --write .",
170
- "example:streaming": "node --loader ts-node/esm examples/streaming.js",
171
- "example:history": "node --loader ts-node/esm examples/history.js",
172
- "example:symbols": "node --loader ts-node/esm examples/symbols.js",
157
+ "clean": "rm -rf ./dist",
173
158
  "doc": "typedoc --out docs/typedoc src",
174
- "clean": "rm -rf ./dist"
175
- }
176
- }
159
+ "example:history": "bun examples/history.js",
160
+ "example:streaming": "bun examples/streaming.js",
161
+ "example:symbols": "bun examples/symbols.js",
162
+ "test:types": "tsc --project ./tsconfig.build.json --noEmit",
163
+ "test:unit": "bun test"
164
+ },
165
+ "type": "module",
166
+ "types": "./dist/cjs/index.d.ts",
167
+ "version": "6.2.0"
168
+ }
package/LICENSE DELETED
@@ -1,13 +0,0 @@
1
- Copyright 2025 Pyth Data Association.
2
-
3
- Licensed under the Apache License, Version 2.0 (the "License");
4
- you may not use this file except in compliance with the License.
5
- You may obtain a copy of the License at
6
-
7
- http://www.apache.org/licenses/LICENSE-2.0
8
-
9
- Unless required by applicable law or agreed to in writing, software
10
- distributed under the License is distributed on an "AS IS" BASIS,
11
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- See the License for the specific language governing permissions and
13
- limitations under the License.