@pythnetwork/price-service-client 1.9.1 → 1.11.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.
@@ -0,0 +1,322 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable unicorn/no-process-exit */ /* eslint-disable n/no-process-exit */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable no-console */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "PriceServiceConnection", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return PriceServiceConnection;
9
+ }
10
+ });
11
+ const _priceservicesdk = require("@pythnetwork/price-service-sdk");
12
+ const _axios = /*#__PURE__*/ _interop_require_default(require("axios"));
13
+ const _axiosretry = /*#__PURE__*/ _interop_require_default(require("axios-retry"));
14
+ const _ResillientWebSocket = require("./ResillientWebSocket.cjs");
15
+ const _utils = require("./utils.cjs");
16
+ function _interop_require_default(obj) {
17
+ return obj && obj.__esModule ? obj : {
18
+ default: obj
19
+ };
20
+ }
21
+ class PriceServiceConnection {
22
+ httpClient;
23
+ priceFeedCallbacks;
24
+ wsClient;
25
+ wsEndpoint;
26
+ logger;
27
+ priceFeedRequestConfig;
28
+ accessToken;
29
+ /**
30
+ * Custom handler for web socket errors (connection and message parsing).
31
+ *
32
+ * Default handler only logs the errors.
33
+ */ onWsError;
34
+ /**
35
+ * Constructs a new Connection.
36
+ *
37
+ * @param endpoint - endpoint URL to the price service. Example: https://website/example/
38
+ * @param config - Optional PriceServiceConnectionConfig for custom configurations.
39
+ */ constructor(endpoint, config){
40
+ this.accessToken = config?.accessToken;
41
+ this.httpClient = _axios.default.create({
42
+ baseURL: endpoint,
43
+ timeout: config?.timeout || 5000,
44
+ headers: this.accessToken === undefined ? {} : {
45
+ Authorization: `Bearer ${this.accessToken}`
46
+ }
47
+ });
48
+ (0, _axiosretry.default)(this.httpClient, {
49
+ retries: config?.httpRetries || 3,
50
+ // eslint-disable-next-line import/no-named-as-default-member
51
+ retryDelay: _axiosretry.default.exponentialDelay.bind(_axiosretry.default)
52
+ });
53
+ this.priceFeedRequestConfig = {
54
+ binary: config?.priceFeedRequestConfig?.binary,
55
+ verbose: config?.priceFeedRequestConfig?.verbose ?? config?.verbose,
56
+ allowOutOfOrder: config?.priceFeedRequestConfig?.allowOutOfOrder
57
+ };
58
+ this.priceFeedCallbacks = new Map();
59
+ // Default logger is console for only warnings and errors.
60
+ this.logger = config?.logger || {
61
+ trace: ()=>{},
62
+ debug: ()=>{},
63
+ info: ()=>{},
64
+ warn: console.warn,
65
+ error: console.error
66
+ };
67
+ this.onWsError = (error)=>{
68
+ this.logger.error(error);
69
+ // Exit the process if it is running in node.
70
+ if (typeof process !== "undefined" && typeof process.exit === "function") {
71
+ this.logger.error("Halting the process due to the websocket error");
72
+ process.exit(1);
73
+ } else {
74
+ this.logger.error("Cannot halt process. Please handle the websocket error.");
75
+ }
76
+ };
77
+ this.wsEndpoint = (0, _utils.makeWebsocketUrl)(endpoint);
78
+ // Append access token as query param for WebSocket connections
79
+ // since browser WebSocket API does not support custom headers.
80
+ if (this.accessToken && this.wsEndpoint) {
81
+ const wsUrl = new URL(this.wsEndpoint);
82
+ wsUrl.searchParams.append("ACCESS_TOKEN", this.accessToken);
83
+ this.wsEndpoint = wsUrl.toString();
84
+ }
85
+ }
86
+ /**
87
+ * Fetch Latest PriceFeeds of given price ids.
88
+ * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price ids)
89
+ *
90
+ * @param priceIds - Array of hex-encoded price ids.
91
+ * @returns Array of PriceFeeds
92
+ */ async getLatestPriceFeeds(priceIds) {
93
+ if (priceIds.length === 0) {
94
+ return [];
95
+ }
96
+ const response = await this.httpClient.get("/api/latest_price_feeds", {
97
+ params: {
98
+ ids: priceIds,
99
+ verbose: this.priceFeedRequestConfig.verbose,
100
+ binary: this.priceFeedRequestConfig.binary
101
+ }
102
+ });
103
+ const priceFeedsJson = response.data;
104
+ return priceFeedsJson.map((priceFeedJson)=>_priceservicesdk.PriceFeed.fromJson(priceFeedJson));
105
+ }
106
+ /**
107
+ * Fetch latest VAA of given price ids.
108
+ * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price ids)
109
+ *
110
+ * This function is coupled to wormhole implemntation.
111
+ *
112
+ * @param priceIds - Array of hex-encoded price ids.
113
+ * @returns Array of base64 encoded VAAs.
114
+ */ async getLatestVaas(priceIds) {
115
+ const response = await this.httpClient.get("/api/latest_vaas", {
116
+ params: {
117
+ ids: priceIds
118
+ }
119
+ });
120
+ return response.data;
121
+ }
122
+ /**
123
+ * Fetch the earliest VAA of the given price id that is published since the given publish time.
124
+ * This will throw an error if the given publish time is in the future, or if the publish time
125
+ * is old and the price service endpoint does not have a db backend for historical requests.
126
+ * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price id)
127
+ *
128
+ * This function is coupled to wormhole implemntation.
129
+ *
130
+ * @param priceId - Hex-encoded price id.
131
+ * @param publishTime - Epoch timestamp in seconds.
132
+ * @returns Tuple of VAA and publishTime.
133
+ */ async getVaa(priceId, publishTime) {
134
+ const response = await this.httpClient.get("/api/get_vaa", {
135
+ params: {
136
+ id: priceId,
137
+ publish_time: publishTime
138
+ }
139
+ });
140
+ return [
141
+ response.data.vaa,
142
+ response.data.publishTime
143
+ ];
144
+ }
145
+ /**
146
+ * Fetch the PriceFeed of the given price id that is published since the given publish time.
147
+ * This will throw an error if the given publish time is in the future, or if the publish time
148
+ * is old and the price service endpoint does not have a db backend for historical requests.
149
+ * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price id)
150
+ *
151
+ * @param priceId - Hex-encoded price id.
152
+ * @param publishTime - Epoch timestamp in seconds.
153
+ * @returns PriceFeed
154
+ */ async getPriceFeed(priceId, publishTime) {
155
+ const response = await this.httpClient.get("/api/get_price_feed", {
156
+ params: {
157
+ id: priceId,
158
+ publish_time: publishTime,
159
+ verbose: this.priceFeedRequestConfig.verbose,
160
+ binary: this.priceFeedRequestConfig.binary
161
+ }
162
+ });
163
+ return _priceservicesdk.PriceFeed.fromJson(response.data);
164
+ }
165
+ /**
166
+ * Fetch the list of available price feed ids.
167
+ * This will throw an axios error if there is a network problem or the price service returns a non-ok response.
168
+ *
169
+ * @returns Array of hex-encoded price ids.
170
+ */ async getPriceFeedIds() {
171
+ const response = await this.httpClient.get("/api/price_feed_ids");
172
+ return response.data;
173
+ }
174
+ /**
175
+ * Subscribe to updates for given price ids.
176
+ *
177
+ * It will start a websocket connection if it's not started yet.
178
+ * Also, it won't throw any exception if given price ids are invalid or connection errors. Instead,
179
+ * it calls `connection.onWsError`. If you want to handle the errors you should set the
180
+ * `onWsError` function to your custom error handler.
181
+ *
182
+ * @param priceIds - Array of hex-encoded price ids.
183
+ * @param cb - Callback function that is called with a PriceFeed upon updates to given price ids.
184
+ */ async subscribePriceFeedUpdates(priceIds, cb) {
185
+ if (this.wsClient === undefined) {
186
+ await this.startWebSocket();
187
+ }
188
+ priceIds = priceIds.map((priceId)=>(0, _utils.removeLeading0xIfExists)(priceId));
189
+ const newPriceIds = [];
190
+ for (const id of priceIds){
191
+ if (!this.priceFeedCallbacks.has(id)) {
192
+ this.priceFeedCallbacks.set(id, new Set());
193
+ newPriceIds.push(id);
194
+ }
195
+ this.priceFeedCallbacks.get(id).add(cb);
196
+ }
197
+ const message = {
198
+ ids: newPriceIds,
199
+ type: "subscribe",
200
+ verbose: this.priceFeedRequestConfig.verbose,
201
+ binary: this.priceFeedRequestConfig.binary,
202
+ allow_out_of_order: this.priceFeedRequestConfig.allowOutOfOrder
203
+ };
204
+ await this.wsClient?.send(JSON.stringify(message));
205
+ }
206
+ /**
207
+ * Unsubscribe from updates for given price ids.
208
+ *
209
+ * It will close the websocket connection if it's not subscribed to any price feed updates anymore.
210
+ * Also, it won't throw any exception if given price ids are invalid or connection errors. Instead,
211
+ * it calls `connection.onWsError`. If you want to handle the errors you should set the
212
+ * `onWsError` function to your custom error handler.
213
+ *
214
+ * @param priceIds - Array of hex-encoded price ids.
215
+ * @param cb - Optional callback, if set it will only unsubscribe this callback from updates for given price ids.
216
+ */ async unsubscribePriceFeedUpdates(priceIds, cb) {
217
+ if (this.wsClient === undefined) {
218
+ await this.startWebSocket();
219
+ }
220
+ priceIds = priceIds.map((priceId)=>(0, _utils.removeLeading0xIfExists)(priceId));
221
+ const removedPriceIds = [];
222
+ for (const id of priceIds){
223
+ if (this.priceFeedCallbacks.has(id)) {
224
+ let idRemoved = false;
225
+ if (cb === undefined) {
226
+ this.priceFeedCallbacks.delete(id);
227
+ idRemoved = true;
228
+ } else {
229
+ this.priceFeedCallbacks.get(id).delete(cb);
230
+ if (this.priceFeedCallbacks.get(id).size === 0) {
231
+ this.priceFeedCallbacks.delete(id);
232
+ idRemoved = true;
233
+ }
234
+ }
235
+ if (idRemoved) {
236
+ removedPriceIds.push(id);
237
+ }
238
+ }
239
+ }
240
+ const message = {
241
+ ids: removedPriceIds,
242
+ type: "unsubscribe"
243
+ };
244
+ await this.wsClient?.send(JSON.stringify(message));
245
+ if (this.priceFeedCallbacks.size === 0) {
246
+ this.closeWebSocket();
247
+ }
248
+ }
249
+ /**
250
+ * Starts connection websocket.
251
+ *
252
+ * This function is called automatically upon subscribing to price feed updates.
253
+ */ async startWebSocket() {
254
+ if (this.wsEndpoint === undefined) {
255
+ throw new Error("Websocket endpoint is undefined.");
256
+ }
257
+ this.wsClient = new _ResillientWebSocket.ResilientWebSocket(this.wsEndpoint, this.logger);
258
+ this.wsClient.onError = this.onWsError;
259
+ this.wsClient.onReconnect = ()=>{
260
+ if (this.priceFeedCallbacks.size > 0) {
261
+ const message = {
262
+ ids: [
263
+ ...this.priceFeedCallbacks.keys()
264
+ ],
265
+ type: "subscribe",
266
+ verbose: this.priceFeedRequestConfig.verbose,
267
+ binary: this.priceFeedRequestConfig.binary,
268
+ allow_out_of_order: this.priceFeedRequestConfig.allowOutOfOrder
269
+ };
270
+ this.logger.info("Resubscribing to existing price feeds.");
271
+ this.wsClient?.send(JSON.stringify(message));
272
+ }
273
+ };
274
+ this.wsClient.onMessage = (data)=>{
275
+ this.logger.info(`Received message ${data.toString()}`);
276
+ let message;
277
+ try {
278
+ message = JSON.parse(data.toString());
279
+ } catch (error) {
280
+ this.logger.error(`Error parsing message ${data.toString()} as JSON.`);
281
+ this.logger.error(error);
282
+ this.onWsError(error);
283
+ return;
284
+ }
285
+ if (message.type === "response") {
286
+ if (message.status === "error") {
287
+ this.logger.error(`Error response from the websocket server ${message.error}.`);
288
+ this.onWsError(new Error(message.error));
289
+ }
290
+ } else if (message.type === "price_update") {
291
+ let priceFeed;
292
+ try {
293
+ priceFeed = _priceservicesdk.PriceFeed.fromJson(message.price_feed);
294
+ } catch (error) {
295
+ this.logger.error(`Error parsing price feeds from message ${data.toString()}.`);
296
+ this.logger.error(error);
297
+ this.onWsError(error);
298
+ return;
299
+ }
300
+ if (this.priceFeedCallbacks.has(priceFeed.id)) {
301
+ for (const cb of this.priceFeedCallbacks.get(priceFeed.id)){
302
+ cb(priceFeed);
303
+ }
304
+ }
305
+ } else {
306
+ this.logger.warn(`Ignoring unsupported server response ${data.toString()}.`);
307
+ }
308
+ };
309
+ await this.wsClient.startWebSocket();
310
+ }
311
+ /**
312
+ * Closes connection websocket.
313
+ *
314
+ * At termination, the websocket should be closed to finish the
315
+ * process elegantly. It will automatically close when the connection
316
+ * is subscribed to no price feeds.
317
+ */ closeWebSocket() {
318
+ this.wsClient?.closeWebSocket();
319
+ this.wsClient = undefined;
320
+ this.priceFeedCallbacks.clear();
321
+ }
322
+ }
@@ -1,23 +1,31 @@
1
- import { HexString, PriceFeed } from "@pythnetwork/price-service-sdk";
2
- import { Logger } from "ts-log";
1
+ import type { HexString } from "@pythnetwork/price-service-sdk";
2
+ import { PriceFeed } from "@pythnetwork/price-service-sdk";
3
+ import type { Logger } from "ts-log";
3
4
  export type DurationInMs = number;
4
5
  export type PriceFeedRequestConfig = {
5
- verbose?: boolean;
6
- binary?: boolean;
7
- allowOutOfOrder?: boolean;
6
+ verbose?: boolean | undefined;
7
+ binary?: boolean | undefined;
8
+ allowOutOfOrder?: boolean | undefined;
8
9
  };
9
10
  export type PriceServiceConnectionConfig = {
10
- timeout?: DurationInMs;
11
+ timeout?: DurationInMs | undefined;
11
12
  /**
12
13
  * Number of times a HTTP request will be retried before the API returns a failure. Default: 3.
13
14
  *
14
15
  * The connection uses exponential back-off for the delay between retries. However,
15
16
  * it will timeout regardless of the retries at the configured `timeout` time.
16
17
  */
17
- httpRetries?: number;
18
- logger?: Logger;
19
- verbose?: boolean;
20
- priceFeedRequestConfig?: PriceFeedRequestConfig;
18
+ httpRetries?: number | undefined;
19
+ logger?: Logger | undefined;
20
+ verbose?: boolean | undefined;
21
+ priceFeedRequestConfig?: PriceFeedRequestConfig | undefined;
22
+ /**
23
+ * Optional API access token for authentication.
24
+ * When provided, this token will be included in all requests either:
25
+ * - As a Bearer token in the Authorization header (for HTTP requests)
26
+ * - As an ACCESS_TOKEN query parameter (for WebSocket connections)
27
+ */
28
+ accessToken?: string | undefined;
21
29
  };
22
30
  export type PriceFeedUpdateCallback = (priceFeed: PriceFeed) => void;
23
31
  export declare class PriceServiceConnection {
@@ -27,6 +35,7 @@ export declare class PriceServiceConnection {
27
35
  private wsEndpoint;
28
36
  private logger;
29
37
  private priceFeedRequestConfig;
38
+ private accessToken;
30
39
  /**
31
40
  * Custom handler for web socket errors (connection and message parsing).
32
41
  *
@@ -36,15 +45,15 @@ export declare class PriceServiceConnection {
36
45
  /**
37
46
  * Constructs a new Connection.
38
47
  *
39
- * @param endpoint endpoint URL to the price service. Example: https://website/example/
40
- * @param config Optional PriceServiceConnectionConfig for custom configurations.
48
+ * @param endpoint - endpoint URL to the price service. Example: https://website/example/
49
+ * @param config - Optional PriceServiceConnectionConfig for custom configurations.
41
50
  */
42
51
  constructor(endpoint: string, config?: PriceServiceConnectionConfig);
43
52
  /**
44
53
  * Fetch Latest PriceFeeds of given price ids.
45
54
  * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price ids)
46
55
  *
47
- * @param priceIds Array of hex-encoded price ids.
56
+ * @param priceIds - Array of hex-encoded price ids.
48
57
  * @returns Array of PriceFeeds
49
58
  */
50
59
  getLatestPriceFeeds(priceIds: HexString[]): Promise<PriceFeed[] | undefined>;
@@ -54,7 +63,7 @@ export declare class PriceServiceConnection {
54
63
  *
55
64
  * This function is coupled to wormhole implemntation.
56
65
  *
57
- * @param priceIds Array of hex-encoded price ids.
66
+ * @param priceIds - Array of hex-encoded price ids.
58
67
  * @returns Array of base64 encoded VAAs.
59
68
  */
60
69
  getLatestVaas(priceIds: HexString[]): Promise<string[]>;
@@ -66,8 +75,8 @@ export declare class PriceServiceConnection {
66
75
  *
67
76
  * This function is coupled to wormhole implemntation.
68
77
  *
69
- * @param priceId Hex-encoded price id.
70
- * @param publishTime Epoch timestamp in seconds.
78
+ * @param priceId - Hex-encoded price id.
79
+ * @param publishTime - Epoch timestamp in seconds.
71
80
  * @returns Tuple of VAA and publishTime.
72
81
  */
73
82
  getVaa(priceId: HexString, publishTime: EpochTimeStamp): Promise<[string, EpochTimeStamp]>;
@@ -77,8 +86,8 @@ export declare class PriceServiceConnection {
77
86
  * is old and the price service endpoint does not have a db backend for historical requests.
78
87
  * This will throw an axios error if there is a network problem or the price service returns a non-ok response (e.g: Invalid price id)
79
88
  *
80
- * @param priceId Hex-encoded price id.
81
- * @param publishTime Epoch timestamp in seconds.
89
+ * @param priceId - Hex-encoded price id.
90
+ * @param publishTime - Epoch timestamp in seconds.
82
91
  * @returns PriceFeed
83
92
  */
84
93
  getPriceFeed(priceId: HexString, publishTime: EpochTimeStamp): Promise<PriceFeed>;
@@ -97,8 +106,8 @@ export declare class PriceServiceConnection {
97
106
  * it calls `connection.onWsError`. If you want to handle the errors you should set the
98
107
  * `onWsError` function to your custom error handler.
99
108
  *
100
- * @param priceIds Array of hex-encoded price ids.
101
- * @param cb Callback function that is called with a PriceFeed upon updates to given price ids.
109
+ * @param priceIds - Array of hex-encoded price ids.
110
+ * @param cb - Callback function that is called with a PriceFeed upon updates to given price ids.
102
111
  */
103
112
  subscribePriceFeedUpdates(priceIds: HexString[], cb: PriceFeedUpdateCallback): Promise<void>;
104
113
  /**
@@ -109,8 +118,8 @@ export declare class PriceServiceConnection {
109
118
  * it calls `connection.onWsError`. If you want to handle the errors you should set the
110
119
  * `onWsError` function to your custom error handler.
111
120
  *
112
- * @param priceIds Array of hex-encoded price ids.
113
- * @param cb Optional callback, if set it will only unsubscribe this callback from updates for given price ids.
121
+ * @param priceIds - Array of hex-encoded price ids.
122
+ * @param cb - Optional callback, if set it will only unsubscribe this callback from updates for given price ids.
114
123
  */
115
124
  unsubscribePriceFeedUpdates(priceIds: HexString[], cb?: PriceFeedUpdateCallback): Promise<void>;
116
125
  /**
@@ -128,4 +137,3 @@ export declare class PriceServiceConnection {
128
137
  */
129
138
  closeWebSocket(): void;
130
139
  }
131
- //# sourceMappingURL=PriceServiceConnection.d.ts.map
@@ -0,0 +1,144 @@
1
+ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable unicorn/prefer-add-event-listener */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-empty-function */ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "ResilientWebSocket", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return ResilientWebSocket;
9
+ }
10
+ });
11
+ const _isomorphicws = /*#__PURE__*/ _interop_require_default(require("isomorphic-ws"));
12
+ function _interop_require_default(obj) {
13
+ return obj && obj.__esModule ? obj : {
14
+ default: obj
15
+ };
16
+ }
17
+ const PING_TIMEOUT_DURATION = 30_000 + 3000; // It is 30s on the server and 3s is added for delays
18
+ class ResilientWebSocket {
19
+ endpoint;
20
+ wsClient;
21
+ wsUserClosed;
22
+ wsFailedAttempts;
23
+ pingTimeout;
24
+ logger;
25
+ onError;
26
+ onMessage;
27
+ onReconnect;
28
+ constructor(endpoint, logger){
29
+ this.endpoint = endpoint;
30
+ this.logger = logger;
31
+ this.wsFailedAttempts = 0;
32
+ this.onError = (error)=>{
33
+ this.logger?.error(error);
34
+ };
35
+ this.wsUserClosed = true;
36
+ this.onMessage = ()=>{};
37
+ this.onReconnect = ()=>{};
38
+ }
39
+ async send(data) {
40
+ this.logger?.info(`Sending ${data}`);
41
+ await this.waitForMaybeReadyWebSocket();
42
+ if (this.wsClient === undefined) {
43
+ this.logger?.error("Couldn't connect to the websocket server. Error callback is called.");
44
+ } else {
45
+ this.wsClient.send(data);
46
+ }
47
+ }
48
+ async startWebSocket() {
49
+ if (this.wsClient !== undefined) {
50
+ return;
51
+ }
52
+ this.logger?.info(`Creating Web Socket client`);
53
+ this.wsClient = new _isomorphicws.default(this.endpoint);
54
+ this.wsUserClosed = false;
55
+ this.wsClient.addEventListener("open", ()=>{
56
+ this.wsFailedAttempts = 0;
57
+ // Ping handler is undefined in browser side so heartbeat is disabled.
58
+ if (this.wsClient.on !== undefined) {
59
+ this.heartbeat();
60
+ }
61
+ });
62
+ this.wsClient.onerror = (event)=>{
63
+ this.onError(event.error);
64
+ };
65
+ this.wsClient.onmessage = (event)=>{
66
+ this.onMessage(event.data);
67
+ };
68
+ this.wsClient.addEventListener("close", async ()=>{
69
+ if (this.pingTimeout !== undefined) {
70
+ clearInterval(this.pingTimeout);
71
+ }
72
+ if (this.wsUserClosed) {
73
+ this.logger?.info("The connection has been closed successfully.");
74
+ } else {
75
+ this.wsFailedAttempts += 1;
76
+ this.wsClient = undefined;
77
+ const waitTime = expoBackoff(this.wsFailedAttempts);
78
+ this.logger?.error(`Connection closed unexpectedly or because of timeout. Reconnecting after ${waitTime}ms.`);
79
+ await sleep(waitTime);
80
+ this.restartUnexpectedClosedWebsocket();
81
+ }
82
+ });
83
+ if (this.wsClient.on !== undefined) {
84
+ // Ping handler is undefined in browser side
85
+ this.wsClient.on("ping", this.heartbeat.bind(this));
86
+ }
87
+ }
88
+ /**
89
+ * Heartbeat is only enabled in node clients because they support handling
90
+ * ping-pong events.
91
+ *
92
+ * This approach only works when server constantly pings the clients which.
93
+ * Otherwise you might consider sending ping and acting on pong responses
94
+ * yourself.
95
+ */ heartbeat() {
96
+ this.logger?.info("Heartbeat");
97
+ if (this.pingTimeout !== undefined) {
98
+ clearTimeout(this.pingTimeout);
99
+ }
100
+ this.pingTimeout = setTimeout(()=>{
101
+ this.logger?.warn(`Connection timed out. Reconnecting...`);
102
+ this.wsClient?.terminate();
103
+ this.restartUnexpectedClosedWebsocket();
104
+ }, PING_TIMEOUT_DURATION);
105
+ }
106
+ async waitForMaybeReadyWebSocket() {
107
+ let waitedTime = 0;
108
+ while(this.wsClient !== undefined && this.wsClient.readyState !== this.wsClient.OPEN){
109
+ if (waitedTime > 5000) {
110
+ this.wsClient.close();
111
+ return;
112
+ } else {
113
+ waitedTime += 10;
114
+ await sleep(10);
115
+ }
116
+ }
117
+ }
118
+ async restartUnexpectedClosedWebsocket() {
119
+ if (this.wsUserClosed) {
120
+ return;
121
+ }
122
+ await this.startWebSocket();
123
+ await this.waitForMaybeReadyWebSocket();
124
+ if (this.wsClient === undefined) {
125
+ this.logger?.error("Couldn't reconnect to websocket. Error callback is called.");
126
+ return;
127
+ }
128
+ this.onReconnect();
129
+ }
130
+ closeWebSocket() {
131
+ if (this.wsClient !== undefined) {
132
+ const client = this.wsClient;
133
+ this.wsClient = undefined;
134
+ client.close();
135
+ }
136
+ this.wsUserClosed = true;
137
+ }
138
+ }
139
+ async function sleep(ms) {
140
+ return new Promise((resolve)=>setTimeout(resolve, ms));
141
+ }
142
+ function expoBackoff(attempts) {
143
+ return 2 ** attempts * 100;
144
+ }
@@ -1,6 +1,5 @@
1
- /// <reference types="ws" />
2
1
  import WebSocket from "isomorphic-ws";
3
- import { Logger } from "ts-log";
2
+ import type { Logger } from "ts-log";
4
3
  /**
5
4
  * This class wraps websocket to provide a resilient web socket client.
6
5
  *
@@ -22,7 +21,7 @@ export declare class ResilientWebSocket {
22
21
  onMessage: (data: WebSocket.Data) => void;
23
22
  onReconnect: () => void;
24
23
  constructor(endpoint: string, logger?: Logger);
25
- send(data: any): Promise<void>;
24
+ send(data: unknown): Promise<void>;
26
25
  startWebSocket(): Promise<void>;
27
26
  /**
28
27
  * Heartbeat is only enabled in node clients because they support handling
@@ -37,4 +36,3 @@ export declare class ResilientWebSocket {
37
36
  private restartUnexpectedClosedWebsocket;
38
37
  closeWebSocket(): void;
39
38
  }
40
- //# sourceMappingURL=ResillientWebSocket.d.ts.map
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get Price () {
13
+ return _priceservicesdk.Price;
14
+ },
15
+ get PriceFeed () {
16
+ return _priceservicesdk.PriceFeed;
17
+ },
18
+ get PriceFeedMetadata () {
19
+ return _priceservicesdk.PriceFeedMetadata;
20
+ },
21
+ get PriceServiceConnection () {
22
+ return _PriceServiceConnection.PriceServiceConnection;
23
+ },
24
+ get isAccumulatorUpdateData () {
25
+ return _priceservicesdk.isAccumulatorUpdateData;
26
+ },
27
+ get parseAccumulatorUpdateData () {
28
+ return _priceservicesdk.parseAccumulatorUpdateData;
29
+ }
30
+ });
31
+ const _PriceServiceConnection = require("./PriceServiceConnection.cjs");
32
+ const _priceservicesdk = require("@pythnetwork/price-service-sdk");
@@ -0,0 +1,2 @@
1
+ export { type DurationInMs, PriceServiceConnection, type PriceServiceConnectionConfig, } from "./PriceServiceConnection.js";
2
+ export { type HexString, PriceFeedMetadata, PriceFeed, Price, type UnixTimestamp, isAccumulatorUpdateData, parseAccumulatorUpdateData, type AccumulatorUpdateData, } from "@pythnetwork/price-service-sdk";
@@ -0,0 +1 @@
1
+ { "type": "commonjs" }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get makeWebsocketUrl () {
13
+ return makeWebsocketUrl;
14
+ },
15
+ get removeLeading0xIfExists () {
16
+ return removeLeading0xIfExists;
17
+ }
18
+ });
19
+ function makeWebsocketUrl(endpoint) {
20
+ const url = new URL("ws", endpoint);
21
+ const useHttps = url.protocol === "https:";
22
+ url.protocol = useHttps ? "wss:" : "ws:";
23
+ return url.toString();
24
+ }
25
+ function removeLeading0xIfExists(id) {
26
+ return id.startsWith("0x") ? id.slice(2) : id;
27
+ }