@pythnetwork/pyth-lazer-sdk 4.0.0 → 5.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.
Files changed (45) hide show
  1. package/README.md +57 -0
  2. package/dist/cjs/client.cjs +229 -0
  3. package/dist/cjs/constants.cjs +36 -0
  4. package/dist/cjs/index.cjs +20 -0
  5. package/dist/cjs/package.json +1 -1
  6. package/dist/cjs/protocol.cjs +33 -0
  7. package/dist/cjs/protocol.d.ts +4 -1
  8. package/dist/cjs/socket/{resilient-websocket.js → resilient-websocket.cjs} +60 -45
  9. package/dist/cjs/socket/resilient-websocket.d.ts +15 -1
  10. package/dist/cjs/socket/{websocket-pool.js → websocket-pool.cjs} +77 -64
  11. package/dist/cjs/socket/websocket-pool.d.ts +5 -2
  12. package/dist/cjs/util/buffer-util.cjs +33 -0
  13. package/dist/cjs/util/buffer-util.d.ts +7 -0
  14. package/dist/cjs/util/env-util.cjs +33 -0
  15. package/dist/cjs/util/env-util.d.ts +17 -0
  16. package/dist/cjs/util/index.cjs +20 -0
  17. package/dist/cjs/util/index.d.ts +3 -0
  18. package/dist/cjs/util/url-util.cjs +17 -0
  19. package/dist/cjs/util/url-util.d.ts +8 -0
  20. package/dist/esm/client.mjs +219 -0
  21. package/dist/esm/index.mjs +3 -0
  22. package/dist/esm/package.json +1 -1
  23. package/dist/esm/protocol.d.ts +4 -1
  24. package/dist/esm/protocol.mjs +12 -0
  25. package/dist/esm/socket/resilient-websocket.d.ts +15 -1
  26. package/dist/esm/socket/{resilient-websocket.js → resilient-websocket.mjs} +42 -35
  27. package/dist/esm/socket/websocket-pool.d.ts +5 -2
  28. package/dist/esm/socket/{websocket-pool.js → websocket-pool.mjs} +58 -54
  29. package/dist/esm/util/buffer-util.d.ts +7 -0
  30. package/dist/esm/util/buffer-util.mjs +27 -0
  31. package/dist/esm/util/env-util.d.ts +17 -0
  32. package/dist/esm/util/env-util.mjs +23 -0
  33. package/dist/esm/util/index.d.ts +3 -0
  34. package/dist/esm/util/index.mjs +3 -0
  35. package/dist/esm/util/url-util.d.ts +8 -0
  36. package/dist/esm/util/url-util.mjs +13 -0
  37. package/package.json +111 -15
  38. package/dist/cjs/client.js +0 -236
  39. package/dist/cjs/constants.js +0 -9
  40. package/dist/cjs/index.js +0 -19
  41. package/dist/cjs/protocol.js +0 -11
  42. package/dist/esm/client.js +0 -230
  43. package/dist/esm/index.js +0 -3
  44. package/dist/esm/protocol.js +0 -8
  45. /package/dist/esm/{constants.js → constants.mjs} +0 -0
@@ -1,7 +1,7 @@
1
1
  export type Format = "evm" | "solana" | "leEcdsa" | "leUnsigned";
2
2
  export type DeliveryFormat = "json" | "binary";
3
3
  export type JsonBinaryEncoding = "base64" | "hex";
4
- export type PriceFeedProperty = "price" | "bestBidPrice" | "bestAskPrice" | "exponent" | "publisherCount" | "confidence";
4
+ export type PriceFeedProperty = "price" | "bestBidPrice" | "bestAskPrice" | "exponent" | "publisherCount" | "confidence" | "fundingRate" | "fundingTimestamp" | "fundingRateInterval";
5
5
  export type Channel = "real_time" | "fixed_rate@50ms" | "fixed_rate@200ms";
6
6
  export type Request = {
7
7
  type: "subscribe";
@@ -122,3 +122,6 @@ export type JsonUpdate = {
122
122
  leEcdsa?: JsonBinaryData;
123
123
  leUnsigned?: JsonBinaryData;
124
124
  };
125
+ export declare enum CustomSocketClosureCodes {
126
+ CLIENT_TIMEOUT_BUT_RECONNECTING = 4000
127
+ }
@@ -0,0 +1,12 @@
1
+ export const BINARY_UPDATE_FORMAT_MAGIC_LE = 461_928_307;
2
+ export const FORMAT_MAGICS_LE = {
3
+ JSON: 3_302_625_434,
4
+ EVM: 2_593_727_018,
5
+ SOLANA: 2_182_742_457,
6
+ LE_ECDSA: 1_296_547_300,
7
+ LE_UNSIGNED: 1_499_680_012
8
+ };
9
+ export var CustomSocketClosureCodes = /*#__PURE__*/ function(CustomSocketClosureCodes) {
10
+ CustomSocketClosureCodes[CustomSocketClosureCodes["CLIENT_TIMEOUT_BUT_RECONNECTING"] = 4000] = "CLIENT_TIMEOUT_BUT_RECONNECTING";
11
+ return CustomSocketClosureCodes;
12
+ }({});
@@ -10,6 +10,19 @@ export type ResilientWebSocketConfig = {
10
10
  maxRetryDelayMs?: number;
11
11
  logAfterRetryCount?: number;
12
12
  };
13
+ /**
14
+ * the isomorphic-ws package ships with some slightly-erroneous typings.
15
+ * namely, it returns a WebSocket with typings that indicate the "terminate()" function
16
+ * is available on all platforms.
17
+ * Given that, under the hood, it is using the globalThis.WebSocket class, if it's available,
18
+ * and falling back to using the https://www.npmjs.com/package/ws package, this
19
+ * means there are API differences between the native WebSocket (the one in a web browser)
20
+ * and the server-side version from the "ws" package.
21
+ *
22
+ * This type creates a WebSocket type reference we use to indicate the unknown
23
+ * nature of the env in which is code is run.
24
+ */
25
+ type UnsafeWebSocket = Omit<WebSocket, "terminate"> & Partial<Pick<WebSocket, "terminate">>;
13
26
  export declare class ResilientWebSocket {
14
27
  private endpoint;
15
28
  private wsOptions?;
@@ -17,7 +30,7 @@ export declare class ResilientWebSocket {
17
30
  private heartbeatTimeoutDurationMs;
18
31
  private maxRetryDelayMs;
19
32
  private logAfterRetryCount;
20
- wsClient: undefined | WebSocket;
33
+ wsClient: UnsafeWebSocket | undefined;
21
34
  wsUserClosed: boolean;
22
35
  private wsFailedAttempts;
23
36
  private heartbeatTimeout?;
@@ -47,3 +60,4 @@ export declare class ResilientWebSocket {
47
60
  */
48
61
  private retryDelayMs;
49
62
  }
63
+ export {};
@@ -1,5 +1,7 @@
1
1
  import WebSocket from "isomorphic-ws";
2
2
  import { dummyLogger } from "ts-log";
3
+ import { CustomSocketClosureCodes } from "../protocol.mjs";
4
+ import { envIsBrowserOrWorker } from "../util/env-util.mjs";
3
5
  const DEFAULT_HEARTBEAT_TIMEOUT_DURATION_MS = 5000; // 5 seconds
4
6
  const DEFAULT_MAX_RETRY_DELAY_MS = 1000; // 1 second'
5
7
  const DEFAULT_LOG_AFTER_RETRY_COUNT = 10;
@@ -28,33 +30,29 @@ export class ResilientWebSocket {
28
30
  onError;
29
31
  onMessage;
30
32
  onReconnect;
31
- constructor(config) {
33
+ constructor(config){
32
34
  this.endpoint = config.endpoint;
33
35
  this.wsOptions = config.wsOptions;
34
36
  this.logger = config.logger ?? dummyLogger;
35
- this.heartbeatTimeoutDurationMs =
36
- config.heartbeatTimeoutDurationMs ??
37
- DEFAULT_HEARTBEAT_TIMEOUT_DURATION_MS;
37
+ this.heartbeatTimeoutDurationMs = config.heartbeatTimeoutDurationMs ?? DEFAULT_HEARTBEAT_TIMEOUT_DURATION_MS;
38
38
  this.maxRetryDelayMs = config.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
39
- this.logAfterRetryCount =
40
- config.logAfterRetryCount ?? DEFAULT_LOG_AFTER_RETRY_COUNT;
39
+ this.logAfterRetryCount = config.logAfterRetryCount ?? DEFAULT_LOG_AFTER_RETRY_COUNT;
41
40
  this.wsFailedAttempts = 0;
42
- this.onError = (error) => {
41
+ this.onError = (error)=>{
43
42
  void error;
44
43
  };
45
- this.onMessage = (data) => {
44
+ this.onMessage = (data)=>{
46
45
  void data;
47
46
  };
48
- this.onReconnect = () => {
49
- // Empty function, can be set by the user.
47
+ this.onReconnect = ()=>{
48
+ // Empty function, can be set by the user.
50
49
  };
51
50
  }
52
51
  send(data) {
53
52
  this.logger.debug(`Sending message`);
54
53
  if (this.isConnected()) {
55
54
  this.wsClient.send(data);
56
- }
57
- else {
55
+ } else {
58
56
  this.logger.warn(`WebSocket to ${this.endpoint} is not connected. Cannot send message.`);
59
57
  }
60
58
  }
@@ -74,34 +72,36 @@ export class ResilientWebSocket {
74
72
  clearTimeout(this.retryTimeout);
75
73
  this.retryTimeout = undefined;
76
74
  }
77
- this.wsClient = new WebSocket(this.endpoint, this.wsOptions);
78
- this.wsClient.addEventListener("open", () => {
75
+ // browser constructor supports a different 2nd argument for the constructor,
76
+ // so we need to ensure it's not included if we're running in that environment:
77
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#protocols
78
+ this.wsClient = new WebSocket(this.endpoint, envIsBrowserOrWorker() ? undefined : this.wsOptions);
79
+ this.wsClient.addEventListener("open", ()=>{
79
80
  this.logger.info("WebSocket connection established");
80
81
  this.wsFailedAttempts = 0;
81
82
  this._isReconnecting = false;
82
83
  this.resetHeartbeat();
83
84
  this.onReconnect();
84
85
  });
85
- this.wsClient.addEventListener("close", (e) => {
86
+ this.wsClient.addEventListener("close", (e)=>{
86
87
  if (this.wsUserClosed) {
87
88
  this.logger.info(`WebSocket connection to ${this.endpoint} closed by user`);
88
- }
89
- else {
89
+ } else {
90
90
  if (this.shouldLogRetry()) {
91
91
  this.logger.warn(`WebSocket connection to ${this.endpoint} closed unexpectedly: Code: ${e.code.toString()}`);
92
92
  }
93
93
  this.handleReconnect();
94
94
  }
95
95
  });
96
- this.wsClient.addEventListener("error", (event) => {
96
+ this.wsClient.addEventListener("error", (event)=>{
97
97
  this.onError(event);
98
98
  });
99
- this.wsClient.addEventListener("message", (event) => {
99
+ this.wsClient.addEventListener("message", (event)=>{
100
100
  this.resetHeartbeat();
101
101
  this.onMessage(event.data);
102
102
  });
103
103
  if ("on" in this.wsClient) {
104
- this.wsClient.on("ping", () => {
104
+ this.wsClient.on("ping", ()=>{
105
105
  this.logger.info("Ping received");
106
106
  this.resetHeartbeat();
107
107
  });
@@ -111,9 +111,19 @@ export class ResilientWebSocket {
111
111
  if (this.heartbeatTimeout !== undefined) {
112
112
  clearTimeout(this.heartbeatTimeout);
113
113
  }
114
- this.heartbeatTimeout = setTimeout(() => {
115
- this.logger.warn("Connection timed out. Reconnecting...");
116
- this.wsClient?.terminate();
114
+ this.heartbeatTimeout = setTimeout(()=>{
115
+ const warnMsg = "Connection timed out. Reconnecting...";
116
+ this.logger.warn(warnMsg);
117
+ if (this.wsClient) {
118
+ if (typeof this.wsClient.terminate === "function") {
119
+ this.wsClient.terminate();
120
+ } else {
121
+ // terminate is an implementation detail of the node-friendly
122
+ // https://www.npmjs.com/package/ws package, but is not a native WebSocket API,
123
+ // so we have to use the close method
124
+ this.wsClient.close(CustomSocketClosureCodes.CLIENT_TIMEOUT_BUT_RECONNECTING, warnMsg);
125
+ }
126
+ }
117
127
  this.handleReconnect();
118
128
  }, this.heartbeatTimeoutDurationMs);
119
129
  }
@@ -132,11 +142,9 @@ export class ResilientWebSocket {
132
142
  this.wsClient = undefined;
133
143
  this._isReconnecting = true;
134
144
  if (this.shouldLogRetry()) {
135
- this.logger.error("Connection closed unexpectedly or because of timeout. Reconnecting after " +
136
- String(this.retryDelayMs()) +
137
- "ms.");
145
+ this.logger.error("Connection closed unexpectedly or because of timeout. Reconnecting after " + String(this.retryDelayMs()) + "ms.");
138
146
  }
139
- this.retryTimeout = setTimeout(() => {
147
+ this.retryTimeout = setTimeout(()=>{
140
148
  this.startWebSocket();
141
149
  }, this.retryDelayMs());
142
150
  }
@@ -148,14 +156,13 @@ export class ResilientWebSocket {
148
156
  this.wsUserClosed = true;
149
157
  }
150
158
  /**
151
- * Calculates the delay in milliseconds for exponential backoff based on the number of failed attempts.
152
- *
153
- * The delay increases exponentially with each attempt, starting at 20ms for the first attempt,
154
- * and is capped at maxRetryDelayMs for attempts greater than or equal to 10.
155
- *
156
- * @returns The calculated delay in milliseconds before the next retry.
157
- */
158
- retryDelayMs() {
159
+ * Calculates the delay in milliseconds for exponential backoff based on the number of failed attempts.
160
+ *
161
+ * The delay increases exponentially with each attempt, starting at 20ms for the first attempt,
162
+ * and is capped at maxRetryDelayMs for attempts greater than or equal to 10.
163
+ *
164
+ * @returns The calculated delay in milliseconds before the next retry.
165
+ */ retryDelayMs() {
159
166
  if (this.wsFailedAttempts >= 10) {
160
167
  return this.maxRetryDelayMs;
161
168
  }
@@ -4,6 +4,7 @@ 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
+ type WebSocketOnMessageCallback = (data: WebSocket.Data) => void | Promise<void>;
7
8
  export type WebSocketPoolConfig = {
8
9
  urls?: string[];
9
10
  numConnections?: number;
@@ -33,15 +34,16 @@ export declare class WebSocketPool {
33
34
  * Checks for error responses in JSON messages and throws appropriate errors
34
35
  */
35
36
  private handleErrorMessages;
37
+ private constructCacheKeyFromWebsocketData;
36
38
  /**
37
39
  * Handles incoming websocket messages by deduplicating identical messages received across
38
40
  * multiple connections before forwarding to registered handlers
39
41
  */
40
- dedupeHandler: (data: WebSocket.Data) => void;
42
+ dedupeHandler: (data: WebSocket.Data) => Promise<void>;
41
43
  sendRequest(request: Request): void;
42
44
  addSubscription(request: Request): void;
43
45
  removeSubscription(subscriptionId: number): void;
44
- addMessageListener(handler: (data: WebSocket.Data) => void): void;
46
+ addMessageListener(handler: WebSocketOnMessageCallback): void;
45
47
  /**
46
48
  * Calls the handler if all websocket connections are currently down or in reconnecting state.
47
49
  * The connections may still try to reconnect in the background.
@@ -52,3 +54,4 @@ export declare class WebSocketPool {
52
54
  private checkConnectionStates;
53
55
  shutdown(): void;
54
56
  }
57
+ export {};
@@ -1,74 +1,76 @@
1
1
  import TTLCache from "@isaacs/ttlcache";
2
- import WebSocket from "isomorphic-ws";
3
2
  import { dummyLogger } from "ts-log";
4
- import { ResilientWebSocket } from "./resilient-websocket.js";
5
- import { DEFAULT_STREAM_SERVICE_0_URL, DEFAULT_STREAM_SERVICE_1_URL, } from "../constants.js";
3
+ import { ResilientWebSocket } from "./resilient-websocket.mjs";
4
+ import { DEFAULT_STREAM_SERVICE_0_URL, DEFAULT_STREAM_SERVICE_1_URL } from "../constants.mjs";
5
+ import { addAuthTokenToWebSocketUrl, bufferFromWebsocketData, envIsBrowserOrWorker } from "../util/index.mjs";
6
6
  const DEFAULT_NUM_CONNECTIONS = 4;
7
7
  export class WebSocketPool {
8
8
  logger;
9
9
  rwsPool;
10
10
  cache;
11
- subscriptions; // id -> subscription Request
11
+ subscriptions;
12
12
  messageListeners;
13
13
  allConnectionsDownListeners;
14
14
  wasAllDown = true;
15
15
  checkConnectionStatesInterval;
16
- constructor(logger) {
16
+ constructor(logger){
17
17
  this.logger = logger;
18
18
  this.rwsPool = [];
19
- this.cache = new TTLCache({ ttl: 1000 * 10 }); // TTL of 10 seconds
19
+ this.cache = new TTLCache({
20
+ ttl: 1000 * 10
21
+ }); // TTL of 10 seconds
20
22
  this.subscriptions = new Map();
21
23
  this.messageListeners = [];
22
24
  this.allConnectionsDownListeners = [];
23
25
  // Start monitoring connection states
24
- this.checkConnectionStatesInterval = setInterval(() => {
26
+ this.checkConnectionStatesInterval = setInterval(()=>{
25
27
  this.checkConnectionStates();
26
28
  }, 100);
27
29
  }
28
30
  /**
29
- * Creates a new WebSocketPool instance that uses multiple redundant WebSocket connections for reliability.
30
- * Usage semantics are similar to using a regular WebSocket client.
31
- * @param urls - List of WebSocket URLs to connect to
32
- * @param token - Authentication token to use for the connections
33
- * @param numConnections - Number of parallel WebSocket connections to maintain (default: 3)
34
- * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
35
- */
36
- static async create(config, token, logger) {
31
+ * Creates a new WebSocketPool instance that uses multiple redundant WebSocket connections for reliability.
32
+ * Usage semantics are similar to using a regular WebSocket client.
33
+ * @param urls - List of WebSocket URLs to connect to
34
+ * @param token - Authentication token to use for the connections
35
+ * @param numConnections - Number of parallel WebSocket connections to maintain (default: 3)
36
+ * @param logger - Optional logger to get socket level logs. Compatible with most loggers such as the built-in console and `bunyan`.
37
+ */ static async create(config, token, logger) {
37
38
  const urls = config.urls ?? [
38
39
  DEFAULT_STREAM_SERVICE_0_URL,
39
- DEFAULT_STREAM_SERVICE_1_URL,
40
+ DEFAULT_STREAM_SERVICE_1_URL
40
41
  ];
41
42
  const log = logger ?? dummyLogger;
42
43
  const pool = new WebSocketPool(log);
43
44
  const numConnections = config.numConnections ?? DEFAULT_NUM_CONNECTIONS;
44
- for (let i = 0; i < numConnections; i++) {
45
- const url = urls[i % urls.length];
45
+ for(let i = 0; i < numConnections; i++){
46
+ const baseUrl = urls[i % urls.length];
47
+ const isBrowser = envIsBrowserOrWorker();
48
+ const url = isBrowser ? addAuthTokenToWebSocketUrl(baseUrl, token) : baseUrl;
46
49
  if (!url) {
47
50
  throw new Error(`URLs must not be null or empty`);
48
51
  }
49
52
  const wsOptions = {
50
53
  ...config.rwsConfig?.wsOptions,
51
- headers: {
52
- Authorization: `Bearer ${token}`,
53
- },
54
+ headers: isBrowser ? undefined : {
55
+ Authorization: `Bearer ${token}`
56
+ }
54
57
  };
55
58
  const rws = new ResilientWebSocket({
56
59
  ...config.rwsConfig,
57
60
  endpoint: url,
58
61
  wsOptions,
59
- logger: log,
62
+ logger: log
60
63
  });
61
64
  // If a websocket client unexpectedly disconnects, ResilientWebSocket will reestablish
62
65
  // the connection and call the onReconnect callback.
63
- rws.onReconnect = () => {
66
+ rws.onReconnect = ()=>{
64
67
  if (rws.wsUserClosed) {
65
68
  return;
66
69
  }
67
- for (const [, request] of pool.subscriptions) {
70
+ for (const [, request] of pool.subscriptions){
68
71
  try {
69
72
  rws.send(JSON.stringify(request));
70
- }
71
- catch (error) {
73
+ } catch (error) {
72
74
  pool.logger.error("Failed to resend subscription on reconnect:", error);
73
75
  }
74
76
  }
@@ -77,37 +79,42 @@ export class WebSocketPool {
77
79
  rws.onError = config.onError;
78
80
  }
79
81
  // Handle all client messages ourselves. Dedupe before sending to registered message handlers.
80
- rws.onMessage = pool.dedupeHandler;
82
+ rws.onMessage = (data)=>{
83
+ 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);
86
+ });
87
+ };
81
88
  pool.rwsPool.push(rws);
82
89
  rws.startWebSocket();
83
90
  }
84
91
  pool.logger.info(`Started WebSocketPool with ${numConnections.toString()} connections. Waiting for at least one to connect...`);
85
- while (!pool.isAnyConnectionEstablished()) {
86
- await new Promise((resolve) => setTimeout(resolve, 100));
92
+ while(!pool.isAnyConnectionEstablished()){
93
+ await new Promise((resolve)=>setTimeout(resolve, 100));
87
94
  }
88
95
  pool.logger.info(`At least one WebSocket connection is established. WebSocketPool is ready.`);
89
96
  return pool;
90
97
  }
91
98
  /**
92
- * Checks for error responses in JSON messages and throws appropriate errors
93
- */
94
- handleErrorMessages(data) {
99
+ * Checks for error responses in JSON messages and throws appropriate errors
100
+ */ handleErrorMessages(data) {
95
101
  const message = JSON.parse(data);
96
102
  if (message.type === "subscriptionError") {
97
103
  throw new Error(`Error occurred for subscription ID ${String(message.subscriptionId)}: ${message.error}`);
98
- }
99
- else if (message.type === "error") {
104
+ } else if (message.type === "error") {
100
105
  throw new Error(`Error: ${message.error}`);
101
106
  }
102
107
  }
108
+ async constructCacheKeyFromWebsocketData(data) {
109
+ if (typeof data === "string") return data;
110
+ const buff = await bufferFromWebsocketData(data);
111
+ return buff.toString("hex");
112
+ }
103
113
  /**
104
- * Handles incoming websocket messages by deduplicating identical messages received across
105
- * multiple connections before forwarding to registered handlers
106
- */
107
- dedupeHandler = (data) => {
108
- const cacheKey = typeof data === "string"
109
- ? data
110
- : Buffer.from(data).toString("hex");
114
+ * Handles incoming websocket messages by deduplicating identical messages received across
115
+ * multiple connections before forwarding to registered handlers
116
+ */ dedupeHandler = async (data)=>{
117
+ const cacheKey = await this.constructCacheKeyFromWebsocketData(data);
111
118
  if (this.cache.has(cacheKey)) {
112
119
  this.logger.debug("Dropping duplicate message");
113
120
  return;
@@ -116,12 +123,10 @@ export class WebSocketPool {
116
123
  if (typeof data === "string") {
117
124
  this.handleErrorMessages(data);
118
125
  }
119
- for (const handler of this.messageListeners) {
120
- handler(data);
121
- }
126
+ await Promise.all(this.messageListeners.map((handler)=>handler(data)));
122
127
  };
123
128
  sendRequest(request) {
124
- for (const rws of this.rwsPool) {
129
+ for (const rws of this.rwsPool){
125
130
  rws.send(JSON.stringify(request));
126
131
  }
127
132
  }
@@ -136,7 +141,7 @@ export class WebSocketPool {
136
141
  this.subscriptions.delete(subscriptionId);
137
142
  const request = {
138
143
  type: "unsubscribe",
139
- subscriptionId,
144
+ subscriptionId
140
145
  };
141
146
  this.sendRequest(request);
142
147
  }
@@ -144,17 +149,16 @@ export class WebSocketPool {
144
149
  this.messageListeners.push(handler);
145
150
  }
146
151
  /**
147
- * Calls the handler if all websocket connections are currently down or in reconnecting state.
148
- * The connections may still try to reconnect in the background.
149
- */
150
- addAllConnectionsDownListener(handler) {
152
+ * Calls the handler if all websocket connections are currently down or in reconnecting state.
153
+ * The connections may still try to reconnect in the background.
154
+ */ addAllConnectionsDownListener(handler) {
151
155
  this.allConnectionsDownListeners.push(handler);
152
156
  }
153
157
  areAllConnectionsDown() {
154
- return this.rwsPool.every((ws) => !ws.isConnected() || ws.isReconnecting());
158
+ return this.rwsPool.every((ws)=>!ws.isConnected() || ws.isReconnecting());
155
159
  }
156
160
  isAnyConnectionEstablished() {
157
- return this.rwsPool.some((ws) => ws.isConnected());
161
+ return this.rwsPool.some((ws)=>ws.isConnected());
158
162
  }
159
163
  checkConnectionStates() {
160
164
  const allDown = this.areAllConnectionsDown();
@@ -163,7 +167,7 @@ export class WebSocketPool {
163
167
  this.wasAllDown = true;
164
168
  this.logger.error("All WebSocket connections are down or reconnecting");
165
169
  // Notify all listeners
166
- for (const listener of this.allConnectionsDownListeners) {
170
+ for (const listener of this.allConnectionsDownListeners){
167
171
  listener();
168
172
  }
169
173
  }
@@ -173,7 +177,7 @@ export class WebSocketPool {
173
177
  }
174
178
  }
175
179
  shutdown() {
176
- for (const rws of this.rwsPool) {
180
+ for (const rws of this.rwsPool){
177
181
  rws.closeWebSocket();
178
182
  }
179
183
  this.rwsPool = [];
@@ -0,0 +1,7 @@
1
+ import type { Data } from "isomorphic-ws";
2
+ /**
3
+ * given a relatively unknown websocket frame data object,
4
+ * returns a valid Buffer instance that is safe to use
5
+ * isomorphically in any JS runtime environment
6
+ */
7
+ export declare function bufferFromWebsocketData(data: Data): Promise<Buffer>;
@@ -0,0 +1,27 @@
1
+ // the linting rules don't allow importing anything that might clash with
2
+ // a global, top-level import. we disable this rule because we need this
3
+ // imported from our installed dependency
4
+ // eslint-disable-next-line unicorn/prefer-node-protocol
5
+ import { Buffer as BrowserBuffer } from "buffer";
6
+ const BufferClassToUse = "Buffer" in globalThis ? globalThis.Buffer : BrowserBuffer;
7
+ /**
8
+ * given a relatively unknown websocket frame data object,
9
+ * returns a valid Buffer instance that is safe to use
10
+ * isomorphically in any JS runtime environment
11
+ */ export async function bufferFromWebsocketData(data) {
12
+ if (typeof data === "string") {
13
+ return BufferClassToUse.from(new TextEncoder().encode(data).buffer);
14
+ }
15
+ if (data instanceof BufferClassToUse) return data;
16
+ if (data instanceof Blob) {
17
+ // let the uncaught promise exception bubble up if there's an issue
18
+ return BufferClassToUse.from(await data.arrayBuffer());
19
+ }
20
+ if (data instanceof ArrayBuffer) return BufferClassToUse.from(data);
21
+ if (Array.isArray(data)) {
22
+ // an array of buffers is highly unlikely, but it is a possibility
23
+ // indicated by the WebSocket Data interface
24
+ return BufferClassToUse.concat(data);
25
+ }
26
+ return data;
27
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Detects if this code is running within any Service or WebWorker context.
3
+ * @returns true if in a worker of some kind, false if otherwise
4
+ */
5
+ export declare function envIsServiceOrWebWorker(): boolean;
6
+ /**
7
+ * Detects if the code is running in a regular DOM or Web Worker context.
8
+ * @returns true if running in a DOM or Web Worker context, false if running in Node.js
9
+ */
10
+ export declare function envIsBrowser(): boolean;
11
+ /**
12
+ * a convenience method that returns whether or not
13
+ * this code is executing in some type of browser-centric environment
14
+ *
15
+ * @returns true if in the browser's main UI thread or in a worker, false if otherwise
16
+ */
17
+ export declare function envIsBrowserOrWorker(): boolean;
@@ -0,0 +1,23 @@
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
+ const g = globalThis;
4
+ /**
5
+ * Detects if this code is running within any Service or WebWorker context.
6
+ * @returns true if in a worker of some kind, false if otherwise
7
+ */ export function envIsServiceOrWebWorker() {
8
+ return typeof WorkerGlobalScope !== "undefined" && g.self instanceof WorkerGlobalScope;
9
+ }
10
+ /**
11
+ * Detects if the code is running in a regular DOM or Web Worker context.
12
+ * @returns true if running in a DOM or Web Worker context, false if running in Node.js
13
+ */ export function envIsBrowser() {
14
+ return g.window !== undefined;
15
+ }
16
+ /**
17
+ * a convenience method that returns whether or not
18
+ * this code is executing in some type of browser-centric environment
19
+ *
20
+ * @returns true if in the browser's main UI thread or in a worker, false if otherwise
21
+ */ export function envIsBrowserOrWorker() {
22
+ return envIsServiceOrWebWorker() || envIsBrowser();
23
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./buffer-util.js";
2
+ export * from "./env-util.js";
3
+ export * from "./url-util.js";
@@ -0,0 +1,3 @@
1
+ export * from "./buffer-util.mjs";
2
+ export * from "./env-util.mjs";
3
+ export * from "./url-util.mjs";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Given a URL to a hosted lazer stream service and a possible auth token,
3
+ * appends the auth token as a query parameter and returns the URL with the token
4
+ * contained within.
5
+ * If the URL provided is nullish, it is returned as-is (in the same nullish format).
6
+ * If the token is nullish, the baseUrl given is returned, instead.
7
+ */
8
+ export declare function addAuthTokenToWebSocketUrl(baseUrl: string | null | undefined, authToken: string | null | undefined): string | null | undefined;
@@ -0,0 +1,13 @@
1
+ const ACCESS_TOKEN_QUERY_PARAM_KEY = "ACCESS_TOKEN";
2
+ /**
3
+ * Given a URL to a hosted lazer stream service and a possible auth token,
4
+ * appends the auth token as a query parameter and returns the URL with the token
5
+ * contained within.
6
+ * If the URL provided is nullish, it is returned as-is (in the same nullish format).
7
+ * If the token is nullish, the baseUrl given is returned, instead.
8
+ */ export function addAuthTokenToWebSocketUrl(baseUrl, authToken) {
9
+ if (!baseUrl || !authToken) return baseUrl;
10
+ const parsedUrl = new URL(baseUrl);
11
+ parsedUrl.searchParams.set(ACCESS_TOKEN_QUERY_PARAM_KEY, authToken);
12
+ return parsedUrl.toString();
13
+ }