@pythnetwork/pyth-lazer-sdk 5.2.0 → 5.2.1

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.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * T defines the "Event Map".
3
+ * Example: `{ 'data': (payload: string) => void; 'error': (err: Error) => void; }`
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ Object.defineProperty(exports, "IsomorphicEventEmitter", {
9
+ enumerable: true,
10
+ get: function() {
11
+ return IsomorphicEventEmitter;
12
+ }
13
+ });
14
+ class IsomorphicEventEmitter {
15
+ listeners = new Map();
16
+ /**
17
+ * Register a callback for a specific event.
18
+ */ on(eventName, callback) {
19
+ const currentListeners = this.listeners.get(eventName) ?? [];
20
+ this.listeners.set(eventName, [
21
+ ...currentListeners,
22
+ callback
23
+ ]);
24
+ }
25
+ /**
26
+ * Registers a callback for a specific event that
27
+ * will only be executed a single time i.e. the first occurence.
28
+ * After this, the handler will be automatically removed and cleaned up.
29
+ */ once(eventName, callback) {
30
+ const wrappedCallback = (...args)=>{
31
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
32
+ callback(...args);
33
+ this.off(eventName, wrappedCallback);
34
+ };
35
+ this.on(eventName, wrappedCallback);
36
+ }
37
+ /**
38
+ * Remove a callback from a specific event.
39
+ * If no specific callback is specified when off() is called,
40
+ * ALL event handler callbacks for the given eventName will be removed
41
+ * at once.
42
+ */ off(eventName, callback) {
43
+ const cbIsFunc = typeof callback === "function";
44
+ const currentListeners = this.listeners.get(eventName) ?? [];
45
+ this.listeners.set(eventName, currentListeners.filter((cb)=>cbIsFunc && cb !== callback));
46
+ }
47
+ /**
48
+ * Protected method to retrieve listeners for internal triggering.
49
+ * This allows the child class to decide how/when to execute them.
50
+ */ getListeners(eventName) {
51
+ return this.listeners.get(eventName) ?? [];
52
+ }
53
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * T defines the "Event Map".
3
+ * Example: `{ 'data': (payload: string) => void; 'error': (err: Error) => void; }`
4
+ */
5
+ export declare abstract class IsomorphicEventEmitter<T extends Record<string, (...args: any[]) => void>> {
6
+ private listeners;
7
+ /**
8
+ * Register a callback for a specific event.
9
+ */
10
+ on<K extends keyof T>(eventName: K, callback: T[K]): void;
11
+ /**
12
+ * Registers a callback for a specific event that
13
+ * will only be executed a single time i.e. the first occurence.
14
+ * After this, the handler will be automatically removed and cleaned up.
15
+ */
16
+ once<K extends keyof T>(eventName: K, callback: T[K]): void;
17
+ /**
18
+ * Remove a callback from a specific event.
19
+ * If no specific callback is specified when off() is called,
20
+ * ALL event handler callbacks for the given eventName will be removed
21
+ * at once.
22
+ */
23
+ off<K extends keyof T>(eventName: K, callback?: T[K]): void;
24
+ /**
25
+ * Protected method to retrieve listeners for internal triggering.
26
+ * This allows the child class to decide how/when to execute them.
27
+ */
28
+ protected getListeners<K extends keyof T>(eventName: K): T[K][];
29
+ }
@@ -12,14 +12,15 @@ const _ttlcache = /*#__PURE__*/ _interop_require_default(require("@isaacs/ttlcac
12
12
  const _tslog = require("ts-log");
13
13
  const _resilientwebsocket = require("./resilient-websocket.cjs");
14
14
  const _constants = require("../constants.cjs");
15
- const _index = require("../util/index.cjs");
15
+ const _index = require("../emitter/index.cjs");
16
+ const _index1 = require("../util/index.cjs");
16
17
  function _interop_require_default(obj) {
17
18
  return obj && obj.__esModule ? obj : {
18
19
  default: obj
19
20
  };
20
21
  }
21
22
  const DEFAULT_NUM_CONNECTIONS = 4;
22
- class WebSocketPool {
23
+ class WebSocketPool extends _index.IsomorphicEventEmitter {
23
24
  logger;
24
25
  rwsPool;
25
26
  cache;
@@ -29,7 +30,7 @@ class WebSocketPool {
29
30
  wasAllDown = true;
30
31
  checkConnectionStatesInterval;
31
32
  constructor(logger){
32
- this.logger = logger;
33
+ super(), this.logger = logger;
33
34
  this.rwsPool = [];
34
35
  this.cache = new _ttlcache.default({
35
36
  ttl: 1000 * 10
@@ -57,10 +58,19 @@ class WebSocketPool {
57
58
  const log = logger ?? _tslog.dummyLogger;
58
59
  const pool = new WebSocketPool(log);
59
60
  const numConnections = config.numConnections ?? DEFAULT_NUM_CONNECTIONS;
61
+ // bind a handler to capture any emitted errors and send them to the user-provided
62
+ // onWebSocketPoolError callback (if it is present)
63
+ if (typeof config.onWebSocketPoolError === "function") {
64
+ pool.on("error", config.onWebSocketPoolError);
65
+ pool.once("shutdown", ()=>{
66
+ // unbind all error handlers so we don't leak memory
67
+ pool.off("error");
68
+ });
69
+ }
60
70
  for(let i = 0; i < numConnections; i++){
61
71
  const baseUrl = urls[i % urls.length];
62
- const isBrowser = (0, _index.envIsBrowserOrWorker)();
63
- const url = isBrowser ? (0, _index.addAuthTokenToWebSocketUrl)(baseUrl, token) : baseUrl;
72
+ const isBrowser = (0, _index1.envIsBrowserOrWorker)();
73
+ const url = isBrowser ? (0, _index1.addAuthTokenToWebSocketUrl)(baseUrl, token) : baseUrl;
64
74
  if (!url) {
65
75
  throw new Error(`URLs must not be null or empty`);
66
76
  }
@@ -90,14 +100,15 @@ class WebSocketPool {
90
100
  }
91
101
  }
92
102
  };
93
- if (config.onError) {
94
- rws.onError = config.onError;
103
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
104
+ const onErrorHandler = config.onWebSocketError ?? config.onError;
105
+ if (typeof onErrorHandler === "function") {
106
+ rws.onError = onErrorHandler;
95
107
  }
96
108
  // Handle all client messages ourselves. Dedupe before sending to registered message handlers.
97
109
  rws.onMessage = (data)=>{
98
110
  pool.dedupeHandler(data).catch((error)=>{
99
- const errMsg = `An error occurred in the WebSocket pool's dedupeHandler: ${error instanceof Error ? error.message : String(error)}`;
100
- throw new Error(errMsg);
111
+ pool.emitPoolError(error, "Error in WebSocketPool dedupeHandler");
101
112
  });
102
113
  };
103
114
  pool.rwsPool.push(rws);
@@ -110,6 +121,21 @@ class WebSocketPool {
110
121
  pool.logger.info(`At least one WebSocket connection is established. WebSocketPool is ready.`);
111
122
  return pool;
112
123
  }
124
+ emitPoolError(error, context) {
125
+ const err = error instanceof Error ? error : new Error(String(error));
126
+ if (context) {
127
+ this.logger.error(context, err);
128
+ } else {
129
+ this.logger.error("WebSocketPool error", err);
130
+ }
131
+ for (const errHandler of this.getListeners("error")){
132
+ try {
133
+ errHandler(err);
134
+ } catch (handlerError) {
135
+ this.logger.error("WebSocketPool error handler threw", handlerError);
136
+ }
137
+ }
138
+ }
113
139
  /**
114
140
  * Checks for error responses in JSON messages and throws appropriate errors
115
141
  */ handleErrorMessages(data) {
@@ -122,7 +148,7 @@ class WebSocketPool {
122
148
  }
123
149
  async constructCacheKeyFromWebsocketData(data) {
124
150
  if (typeof data === "string") return data;
125
- const buff = await (0, _index.bufferFromWebsocketData)(data);
151
+ const buff = await (0, _index1.bufferFromWebsocketData)(data);
126
152
  return buff.toString("hex");
127
153
  }
128
154
  /**
@@ -138,16 +164,25 @@ class WebSocketPool {
138
164
  if (typeof data === "string") {
139
165
  this.handleErrorMessages(data);
140
166
  }
141
- await Promise.all(this.messageListeners.map((handler)=>handler(data)));
167
+ try {
168
+ await Promise.all(this.messageListeners.map((handler)=>handler(data)));
169
+ } catch (error) {
170
+ this.emitPoolError(error, "Error in WebSocketPool message handler");
171
+ }
142
172
  };
143
173
  sendRequest(request) {
144
174
  for (const rws of this.rwsPool){
145
- rws.send(JSON.stringify(request));
175
+ try {
176
+ rws.send(JSON.stringify(request));
177
+ } catch (error) {
178
+ this.emitPoolError(error, "Failed to send WebSocket request");
179
+ }
146
180
  }
147
181
  }
148
182
  addSubscription(request) {
149
183
  if (request.type !== "subscribe") {
150
- throw new Error("Request must be a subscribe request");
184
+ this.emitPoolError(new Error("Request must be a subscribe request"));
185
+ return;
151
186
  }
152
187
  this.subscriptions.set(request.subscriptionId, request);
153
188
  this.sendRequest(request);
@@ -183,7 +218,11 @@ class WebSocketPool {
183
218
  this.logger.error("All WebSocket connections are down or reconnecting");
184
219
  // Notify all listeners
185
220
  for (const listener of this.allConnectionsDownListeners){
186
- listener();
221
+ try {
222
+ listener();
223
+ } catch (error) {
224
+ this.emitPoolError(error, "All-connections-down listener threw");
225
+ }
187
226
  }
188
227
  }
189
228
  // If at least one connection was restored
@@ -200,5 +239,15 @@ class WebSocketPool {
200
239
  this.messageListeners = [];
201
240
  this.allConnectionsDownListeners = [];
202
241
  clearInterval(this.checkConnectionStatesInterval);
242
+ // execute all bound shutdown handlers
243
+ for (const shutdownHandler of this.getListeners("shutdown")){
244
+ try {
245
+ shutdownHandler();
246
+ } catch (error) {
247
+ this.emitPoolError(error, "Shutdown handler threw");
248
+ }
249
+ }
250
+ // ensure any error handlers are removed to avoid leaks
251
+ this.off("error");
203
252
  }
204
253
  }
@@ -4,14 +4,47 @@ import type { Logger } from "ts-log";
4
4
  import type { Request } from "../protocol.js";
5
5
  import type { ResilientWebSocketConfig } from "./resilient-websocket.js";
6
6
  import { ResilientWebSocket } from "./resilient-websocket.js";
7
+ import { IsomorphicEventEmitter } from "../emitter/index.js";
7
8
  type WebSocketOnMessageCallback = (data: WebSocket.Data) => void | Promise<void>;
8
9
  export type WebSocketPoolConfig = {
9
- urls?: string[];
10
+ /**
11
+ * Maximum number of open, parallel websocket connections
12
+ * @defaultValue 3
13
+ */
10
14
  numConnections?: number;
11
- rwsConfig?: Omit<ResilientWebSocketConfig, "logger" | "endpoint">;
15
+ /**
16
+ * Callback that will be executed whenever an error
17
+ * message or an error event occurs on an individual WebSocket connection
18
+ *
19
+ * @deprecated use onWebSocketError() instead
20
+ */
12
21
  onError?: (error: ErrorEvent) => void;
22
+ /**
23
+ * Callback that will be executed whenever an error
24
+ * message or an error event occurs on an individual WebSocket connection
25
+ */
26
+ onWebSocketError?: (error: ErrorEvent) => void;
27
+ /**
28
+ * Callback that will be executed whenever an error occurs
29
+ * directly within the WebSocket pool. These can typically
30
+ * be errors that would normally manifest as "unhandledRejection" or "uncaughtException"
31
+ * errors.
32
+ */
33
+ onWebSocketPoolError?: (error: Error) => void;
34
+ /**
35
+ * Additional websocket configuration
36
+ */
37
+ rwsConfig?: Omit<ResilientWebSocketConfig, "logger" | "endpoint">;
38
+ /**
39
+ * Pyth URLs to use when creating a connection
40
+ */
41
+ urls?: string[];
42
+ };
43
+ export type WebSocketPoolEvents = {
44
+ error: (error: Error) => void;
45
+ shutdown: () => void;
13
46
  };
14
- export declare class WebSocketPool {
47
+ export declare class WebSocketPool extends IsomorphicEventEmitter<WebSocketPoolEvents> {
15
48
  private readonly logger;
16
49
  rwsPool: ResilientWebSocket[];
17
50
  private cache;
@@ -30,6 +63,7 @@ export declare class WebSocketPool {
30
63
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
31
64
  */
32
65
  static create(config: WebSocketPoolConfig, token: string, logger?: Logger): Promise<WebSocketPool>;
66
+ private emitPoolError;
33
67
  /**
34
68
  * Checks for error responses in JSON messages and throws appropriate errors
35
69
  */
@@ -1,5 +1,3 @@
1
- // we create this local-only type, which has assertions made to indicate
2
- // that we do not know and cannot guarantee which JS environment we are in
3
1
  "use strict";
4
2
  Object.defineProperty(exports, "__esModule", {
5
3
  value: true
@@ -21,6 +19,8 @@ _export(exports, {
21
19
  return envIsServiceOrWebWorker;
22
20
  }
23
21
  });
22
+ // we create this local-only type, which has assertions made to indicate
23
+ // that we do not know and cannot guarantee which JS environment we are in
24
24
  const g = globalThis;
25
25
  function envIsServiceOrWebWorker() {
26
26
  return typeof WorkerGlobalScope !== "undefined" && g.self instanceof WorkerGlobalScope;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * T defines the "Event Map".
3
+ * Example: `{ 'data': (payload: string) => void; 'error': (err: Error) => void; }`
4
+ */
5
+ export declare abstract class IsomorphicEventEmitter<T extends Record<string, (...args: any[]) => void>> {
6
+ private listeners;
7
+ /**
8
+ * Register a callback for a specific event.
9
+ */
10
+ on<K extends keyof T>(eventName: K, callback: T[K]): void;
11
+ /**
12
+ * Registers a callback for a specific event that
13
+ * will only be executed a single time i.e. the first occurence.
14
+ * After this, the handler will be automatically removed and cleaned up.
15
+ */
16
+ once<K extends keyof T>(eventName: K, callback: T[K]): void;
17
+ /**
18
+ * Remove a callback from a specific event.
19
+ * If no specific callback is specified when off() is called,
20
+ * ALL event handler callbacks for the given eventName will be removed
21
+ * at once.
22
+ */
23
+ off<K extends keyof T>(eventName: K, callback?: T[K]): void;
24
+ /**
25
+ * Protected method to retrieve listeners for internal triggering.
26
+ * This allows the child class to decide how/when to execute them.
27
+ */
28
+ protected getListeners<K extends keyof T>(eventName: K): T[K][];
29
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * T defines the "Event Map".
3
+ * Example: `{ 'data': (payload: string) => void; 'error': (err: Error) => void; }`
4
+ */ export class IsomorphicEventEmitter {
5
+ listeners = new Map();
6
+ /**
7
+ * Register a callback for a specific event.
8
+ */ on(eventName, callback) {
9
+ const currentListeners = this.listeners.get(eventName) ?? [];
10
+ this.listeners.set(eventName, [
11
+ ...currentListeners,
12
+ callback
13
+ ]);
14
+ }
15
+ /**
16
+ * Registers a callback for a specific event that
17
+ * will only be executed a single time i.e. the first occurence.
18
+ * After this, the handler will be automatically removed and cleaned up.
19
+ */ once(eventName, callback) {
20
+ const wrappedCallback = (...args)=>{
21
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
22
+ callback(...args);
23
+ this.off(eventName, wrappedCallback);
24
+ };
25
+ this.on(eventName, wrappedCallback);
26
+ }
27
+ /**
28
+ * Remove a callback from a specific event.
29
+ * If no specific callback is specified when off() is called,
30
+ * ALL event handler callbacks for the given eventName will be removed
31
+ * at once.
32
+ */ off(eventName, callback) {
33
+ const cbIsFunc = typeof callback === "function";
34
+ const currentListeners = this.listeners.get(eventName) ?? [];
35
+ this.listeners.set(eventName, currentListeners.filter((cb)=>cbIsFunc && cb !== callback));
36
+ }
37
+ /**
38
+ * Protected method to retrieve listeners for internal triggering.
39
+ * This allows the child class to decide how/when to execute them.
40
+ */ getListeners(eventName) {
41
+ return this.listeners.get(eventName) ?? [];
42
+ }
43
+ }
@@ -4,14 +4,47 @@ import type { Logger } from "ts-log";
4
4
  import type { Request } from "../protocol.js";
5
5
  import type { ResilientWebSocketConfig } from "./resilient-websocket.js";
6
6
  import { ResilientWebSocket } from "./resilient-websocket.js";
7
+ import { IsomorphicEventEmitter } from "../emitter/index.js";
7
8
  type WebSocketOnMessageCallback = (data: WebSocket.Data) => void | Promise<void>;
8
9
  export type WebSocketPoolConfig = {
9
- urls?: string[];
10
+ /**
11
+ * Maximum number of open, parallel websocket connections
12
+ * @defaultValue 3
13
+ */
10
14
  numConnections?: number;
11
- rwsConfig?: Omit<ResilientWebSocketConfig, "logger" | "endpoint">;
15
+ /**
16
+ * Callback that will be executed whenever an error
17
+ * message or an error event occurs on an individual WebSocket connection
18
+ *
19
+ * @deprecated use onWebSocketError() instead
20
+ */
12
21
  onError?: (error: ErrorEvent) => void;
22
+ /**
23
+ * Callback that will be executed whenever an error
24
+ * message or an error event occurs on an individual WebSocket connection
25
+ */
26
+ onWebSocketError?: (error: ErrorEvent) => void;
27
+ /**
28
+ * Callback that will be executed whenever an error occurs
29
+ * directly within the WebSocket pool. These can typically
30
+ * be errors that would normally manifest as "unhandledRejection" or "uncaughtException"
31
+ * errors.
32
+ */
33
+ onWebSocketPoolError?: (error: Error) => void;
34
+ /**
35
+ * Additional websocket configuration
36
+ */
37
+ rwsConfig?: Omit<ResilientWebSocketConfig, "logger" | "endpoint">;
38
+ /**
39
+ * Pyth URLs to use when creating a connection
40
+ */
41
+ urls?: string[];
42
+ };
43
+ export type WebSocketPoolEvents = {
44
+ error: (error: Error) => void;
45
+ shutdown: () => void;
13
46
  };
14
- export declare class WebSocketPool {
47
+ export declare class WebSocketPool extends IsomorphicEventEmitter<WebSocketPoolEvents> {
15
48
  private readonly logger;
16
49
  rwsPool: ResilientWebSocket[];
17
50
  private cache;
@@ -30,6 +63,7 @@ export declare class WebSocketPool {
30
63
  * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
31
64
  */
32
65
  static create(config: WebSocketPoolConfig, token: string, logger?: Logger): Promise<WebSocketPool>;
66
+ private emitPoolError;
33
67
  /**
34
68
  * Checks for error responses in JSON messages and throws appropriate errors
35
69
  */
@@ -2,9 +2,10 @@ import TTLCache from "@isaacs/ttlcache";
2
2
  import { dummyLogger } from "ts-log";
3
3
  import { ResilientWebSocket } from "./resilient-websocket.mjs";
4
4
  import { DEFAULT_STREAM_SERVICE_0_URL, DEFAULT_STREAM_SERVICE_1_URL } from "../constants.mjs";
5
+ import { IsomorphicEventEmitter } from "../emitter/index.mjs";
5
6
  import { addAuthTokenToWebSocketUrl, bufferFromWebsocketData, envIsBrowserOrWorker } from "../util/index.mjs";
6
7
  const DEFAULT_NUM_CONNECTIONS = 4;
7
- export class WebSocketPool {
8
+ export class WebSocketPool extends IsomorphicEventEmitter {
8
9
  logger;
9
10
  rwsPool;
10
11
  cache;
@@ -14,7 +15,7 @@ export class WebSocketPool {
14
15
  wasAllDown = true;
15
16
  checkConnectionStatesInterval;
16
17
  constructor(logger){
17
- this.logger = logger;
18
+ super(), this.logger = logger;
18
19
  this.rwsPool = [];
19
20
  this.cache = new TTLCache({
20
21
  ttl: 1000 * 10
@@ -42,6 +43,15 @@ export class WebSocketPool {
42
43
  const log = logger ?? dummyLogger;
43
44
  const pool = new WebSocketPool(log);
44
45
  const numConnections = config.numConnections ?? DEFAULT_NUM_CONNECTIONS;
46
+ // bind a handler to capture any emitted errors and send them to the user-provided
47
+ // onWebSocketPoolError callback (if it is present)
48
+ if (typeof config.onWebSocketPoolError === "function") {
49
+ pool.on("error", config.onWebSocketPoolError);
50
+ pool.once("shutdown", ()=>{
51
+ // unbind all error handlers so we don't leak memory
52
+ pool.off("error");
53
+ });
54
+ }
45
55
  for(let i = 0; i < numConnections; i++){
46
56
  const baseUrl = urls[i % urls.length];
47
57
  const isBrowser = envIsBrowserOrWorker();
@@ -75,14 +85,15 @@ export class WebSocketPool {
75
85
  }
76
86
  }
77
87
  };
78
- if (config.onError) {
79
- rws.onError = config.onError;
88
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
89
+ const onErrorHandler = config.onWebSocketError ?? config.onError;
90
+ if (typeof onErrorHandler === "function") {
91
+ rws.onError = onErrorHandler;
80
92
  }
81
93
  // Handle all client messages ourselves. Dedupe before sending to registered message handlers.
82
94
  rws.onMessage = (data)=>{
83
95
  pool.dedupeHandler(data).catch((error)=>{
84
- const errMsg = `An error occurred in the WebSocket pool's dedupeHandler: ${error instanceof Error ? error.message : String(error)}`;
85
- throw new Error(errMsg);
96
+ pool.emitPoolError(error, "Error in WebSocketPool dedupeHandler");
86
97
  });
87
98
  };
88
99
  pool.rwsPool.push(rws);
@@ -95,6 +106,21 @@ export class WebSocketPool {
95
106
  pool.logger.info(`At least one WebSocket connection is established. WebSocketPool is ready.`);
96
107
  return pool;
97
108
  }
109
+ emitPoolError(error, context) {
110
+ const err = error instanceof Error ? error : new Error(String(error));
111
+ if (context) {
112
+ this.logger.error(context, err);
113
+ } else {
114
+ this.logger.error("WebSocketPool error", err);
115
+ }
116
+ for (const errHandler of this.getListeners("error")){
117
+ try {
118
+ errHandler(err);
119
+ } catch (handlerError) {
120
+ this.logger.error("WebSocketPool error handler threw", handlerError);
121
+ }
122
+ }
123
+ }
98
124
  /**
99
125
  * Checks for error responses in JSON messages and throws appropriate errors
100
126
  */ handleErrorMessages(data) {
@@ -123,16 +149,25 @@ export class WebSocketPool {
123
149
  if (typeof data === "string") {
124
150
  this.handleErrorMessages(data);
125
151
  }
126
- await Promise.all(this.messageListeners.map((handler)=>handler(data)));
152
+ try {
153
+ await Promise.all(this.messageListeners.map((handler)=>handler(data)));
154
+ } catch (error) {
155
+ this.emitPoolError(error, "Error in WebSocketPool message handler");
156
+ }
127
157
  };
128
158
  sendRequest(request) {
129
159
  for (const rws of this.rwsPool){
130
- rws.send(JSON.stringify(request));
160
+ try {
161
+ rws.send(JSON.stringify(request));
162
+ } catch (error) {
163
+ this.emitPoolError(error, "Failed to send WebSocket request");
164
+ }
131
165
  }
132
166
  }
133
167
  addSubscription(request) {
134
168
  if (request.type !== "subscribe") {
135
- throw new Error("Request must be a subscribe request");
169
+ this.emitPoolError(new Error("Request must be a subscribe request"));
170
+ return;
136
171
  }
137
172
  this.subscriptions.set(request.subscriptionId, request);
138
173
  this.sendRequest(request);
@@ -168,7 +203,11 @@ export class WebSocketPool {
168
203
  this.logger.error("All WebSocket connections are down or reconnecting");
169
204
  // Notify all listeners
170
205
  for (const listener of this.allConnectionsDownListeners){
171
- listener();
206
+ try {
207
+ listener();
208
+ } catch (error) {
209
+ this.emitPoolError(error, "All-connections-down listener threw");
210
+ }
172
211
  }
173
212
  }
174
213
  // If at least one connection was restored
@@ -185,5 +224,15 @@ export class WebSocketPool {
185
224
  this.messageListeners = [];
186
225
  this.allConnectionsDownListeners = [];
187
226
  clearInterval(this.checkConnectionStatesInterval);
227
+ // execute all bound shutdown handlers
228
+ for (const shutdownHandler of this.getListeners("shutdown")){
229
+ try {
230
+ shutdownHandler();
231
+ } catch (error) {
232
+ this.emitPoolError(error, "Shutdown handler threw");
233
+ }
234
+ }
235
+ // ensure any error handlers are removed to avoid leaks
236
+ this.off("error");
188
237
  }
189
238
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@pythnetwork/pyth-lazer-sdk",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
4
4
  "description": "Pyth Lazer SDK",
5
5
  "engines": {
6
- "node": ">=22.14.0"
6
+ "node": "^24.0.0"
7
7
  },
8
8
  "publishConfig": {
9
9
  "access": "public"
@@ -34,6 +34,16 @@
34
34
  "default": "./dist/esm/constants.mjs"
35
35
  }
36
36
  },
37
+ "./emitter": {
38
+ "require": {
39
+ "types": "./dist/cjs/emitter/index.d.ts",
40
+ "default": "./dist/cjs/emitter/index.cjs"
41
+ },
42
+ "import": {
43
+ "types": "./dist/esm/emitter/index.d.ts",
44
+ "default": "./dist/esm/emitter/index.mjs"
45
+ }
46
+ },
37
47
  ".": {
38
48
  "require": {
39
49
  "types": "./dist/cjs/index.d.ts",
@@ -119,6 +129,7 @@
119
129
  "devDependencies": {
120
130
  "@cprussin/eslint-config": "^4.0.2",
121
131
  "@cprussin/tsconfig": "^3.1.2",
132
+ "@types/jest": "^29.5.14",
122
133
  "@types/node": "^18.19.54",
123
134
  "@types/ws": "^8.5.12",
124
135
  "eslint": "^9.23.0",