@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.
- package/dist/cjs/emitter/index.cjs +53 -0
- package/dist/cjs/emitter/index.d.ts +29 -0
- package/dist/cjs/socket/websocket-pool.cjs +63 -14
- package/dist/cjs/socket/websocket-pool.d.ts +37 -3
- package/dist/cjs/util/env-util.cjs +2 -2
- package/dist/esm/emitter/index.d.ts +29 -0
- package/dist/esm/emitter/index.mjs +43 -0
- package/dist/esm/socket/websocket-pool.d.ts +37 -3
- package/dist/esm/socket/websocket-pool.mjs +59 -10
- package/package.json +13 -2
|
@@ -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("../
|
|
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,
|
|
63
|
-
const url = isBrowser ? (0,
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Maximum number of open, parallel websocket connections
|
|
12
|
+
* @defaultValue 3
|
|
13
|
+
*/
|
|
10
14
|
numConnections?: number;
|
|
11
|
-
|
|
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
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Maximum number of open, parallel websocket connections
|
|
12
|
+
* @defaultValue 3
|
|
13
|
+
*/
|
|
10
14
|
numConnections?: number;
|
|
11
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Pyth Lazer SDK",
|
|
5
5
|
"engines": {
|
|
6
|
-
"node": "
|
|
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",
|