Skip to content

Commit 9321546

Browse files
committed
revert price-service changes
1 parent f9bc7c0 commit 9321546

File tree

7 files changed

+68
-907
lines changed

7 files changed

+68
-907
lines changed

price_service/client/js/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
],
3737
"license": "Apache-2.0",
3838
"devDependencies": {
39-
"@types/eventsource": "^1.1.15",
4039
"@types/jest": "^29.4.0",
4140
"@types/yargs": "^17.0.10",
4241
"@typescript-eslint/eslint-plugin": "^5.21.0",
@@ -51,7 +50,8 @@
5150
"dependencies": {
5251
"@pythnetwork/price-service-sdk": "*",
5352
"@types/ws": "^8.5.3",
54-
"eventsource": "^2.0.2",
53+
"axios": "^1.5.1",
54+
"axios-retry": "^3.8.0",
5555
"isomorphic-ws": "^4.0.1",
5656
"ts-log": "^2.2.4",
5757
"ws": "^8.6.0"

price_service/client/js/src/PriceServiceConnection.ts

+48-214
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
import {
2-
EncodingType,
3-
HexString,
4-
PriceFeed,
5-
PriceFeedMetadataV2,
6-
PriceUpdate,
7-
UnixTimestamp,
8-
} from "@pythnetwork/price-service-sdk";
1+
import { HexString, PriceFeed } from "@pythnetwork/price-service-sdk";
2+
import axios, { AxiosInstance } from "axios";
3+
import axiosRetry from "axios-retry";
94
import * as WebSocket from "isomorphic-ws";
105
import { Logger } from "ts-log";
116
import { ResilientWebSocket } from "./ResillientWebSocket";
127
import { makeWebsocketUrl, removeLeading0xIfExists } from "./utils";
13-
import EventSource from "eventsource";
148

159
export type DurationInMs = number;
1610

@@ -65,12 +59,14 @@ type ServerMessage = ServerResponse | ServerPriceUpdate;
6559
export type PriceFeedUpdateCallback = (priceFeed: PriceFeed) => void;
6660

6761
export class PriceServiceConnection {
68-
private baseURL: string;
69-
private timeout: DurationInMs;
62+
private httpClient: AxiosInstance;
63+
7064
private priceFeedCallbacks: Map<HexString, Set<PriceFeedUpdateCallback>>;
7165
private wsClient: undefined | ResilientWebSocket;
7266
private wsEndpoint: undefined | string;
67+
7368
private logger: Logger;
69+
7470
private priceFeedRequestConfig: PriceFeedRequestConfig;
7571

7672
/**
@@ -87,8 +83,14 @@ export class PriceServiceConnection {
8783
* @param config Optional PriceServiceConnectionConfig for custom configurations.
8884
*/
8985
constructor(endpoint: string, config?: PriceServiceConnectionConfig) {
90-
this.baseURL = endpoint;
91-
this.timeout = config?.timeout || 5000;
86+
this.httpClient = axios.create({
87+
baseURL: endpoint,
88+
timeout: config?.timeout || 5000,
89+
});
90+
axiosRetry(this.httpClient, {
91+
retries: config?.httpRetries || 3,
92+
retryDelay: axiosRetry.exponentialDelay,
93+
});
9294

9395
this.priceFeedRequestConfig = {
9496
binary: config?.priceFeedRequestConfig?.binary,
@@ -127,166 +129,6 @@ export class PriceServiceConnection {
127129
this.wsEndpoint = makeWebsocketUrl(endpoint);
128130
}
129131

130-
private async httpRequest(
131-
url: string,
132-
options?: RequestInit,
133-
retries = 3,
134-
backoff = 300
135-
): Promise<any> {
136-
const controller = new AbortController();
137-
const { signal } = controller;
138-
options = { ...options, signal }; // Merge any existing options with the signal
139-
140-
// Set a timeout to abort the request if it takes too long
141-
const timeout = setTimeout(() => controller.abort(), this.timeout);
142-
143-
try {
144-
const response = await fetch(url, options);
145-
clearTimeout(timeout); // Clear the timeout if the request completes in time
146-
if (!response.ok) {
147-
throw new Error(`HTTP error! status: ${response.status}`);
148-
}
149-
return await response.json();
150-
} catch (error) {
151-
clearTimeout(timeout);
152-
if (
153-
retries > 0 &&
154-
!(error instanceof Error && error.name === "AbortError")
155-
) {
156-
// Wait for a backoff period before retrying
157-
await new Promise((resolve) => setTimeout(resolve, backoff));
158-
return this.httpRequest(url, options, retries - 1, backoff * 2); // Exponential backoff
159-
}
160-
if (error instanceof Error) {
161-
this.logger.error("HTTP Request Failed", error);
162-
throw error;
163-
} else {
164-
// If the caught error is not an instance of Error, handle it as an unknown error.
165-
this.logger.error("An unknown error occurred", error);
166-
throw new Error("An unknown error occurred");
167-
}
168-
}
169-
}
170-
171-
/**
172-
* Fetch the set of available price feeds.
173-
* This endpoint can be filtered by asset type and query string.
174-
* This will throw an axios error if there is a network problem or the price service returns a non-ok response.
175-
*
176-
* @param query Optional query string to filter the price feeds. If provided, the results will be filtered to all price feeds whose symbol contains the query string. Query string is case insensitive. Example : bitcoin
177-
* @param filter Optional filter string to filter the price feeds. If provided, the results will be filtered by asset type. Possible values are crypto, equity, fx, metal, rates. Filter string is case insensitive. Available values : crypto, fx, equity, metals, rates
178-
* @returns Array of hex-encoded price ids.
179-
*/
180-
async getV2PriceFeeds(
181-
query?: string,
182-
filter?: string
183-
): Promise<PriceFeedMetadataV2[]> {
184-
const url = new URL(`${this.baseURL}/v2/price_feeds`);
185-
if (query) {
186-
url.searchParams.append("query", query);
187-
}
188-
if (filter) {
189-
url.searchParams.append("filter", filter);
190-
}
191-
return await this.httpRequest(url.toString());
192-
}
193-
194-
/**
195-
* Fetch the latest price updates for a set of price feed IDs.
196-
* This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
197-
* This will throw an axios error if there is a network problem or the price service returns a non-ok response.
198-
*
199-
* @param ids Array of hex-encoded price feed IDs for which updates are requested.
200-
* @param encoding Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is hex.
201-
* @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
202-
* @returns Array of PriceFeed objects containing the latest updates.
203-
*/
204-
async getV2LatestPriceUpdates(
205-
ids: HexString[],
206-
encoding?: EncodingType,
207-
parsed?: boolean
208-
): Promise<PriceUpdate[]> {
209-
const url = new URL(`${this.baseURL}/v2/updates/price/latest`);
210-
// Append parameters to the URL search parameters
211-
ids.forEach((id) => url.searchParams.append("ids[]", id));
212-
if (encoding) {
213-
url.searchParams.append("encoding", encoding);
214-
}
215-
if (parsed !== undefined) {
216-
url.searchParams.append("parsed", String(parsed));
217-
}
218-
return await this.httpRequest(url.toString());
219-
}
220-
221-
/**
222-
* Fetch the price updates for a set of price feed IDs at a given timestamp.
223-
* This endpoint can be customized by specifying the encoding type and whether the results should also return the parsed price update.
224-
* This will throw an axios error if there is a network problem or the price service returns a non-ok response.
225-
*
226-
* @param publishTime Unix timestamp in seconds.
227-
* @param ids Array of hex-encoded price feed IDs for which updates are requested.
228-
* @param encoding Optional encoding type. If true, return the price update in the encoding specified by the encoding parameter. Default is hex.
229-
* @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
230-
* @returns Array of PriceFeed objects containing the latest updates.
231-
*/
232-
async getV2TimestampPriceUpdates(
233-
publishTime: UnixTimestamp,
234-
ids: HexString[],
235-
encoding?: EncodingType,
236-
parsed?: boolean
237-
): Promise<PriceUpdate[]> {
238-
const url = new URL(`${this.baseURL}/v2/updates/price/${publishTime}`);
239-
ids.forEach((id) => url.searchParams.append("ids[]", id));
240-
if (encoding) {
241-
url.searchParams.append("encoding", encoding);
242-
}
243-
if (parsed !== undefined) {
244-
url.searchParams.append("parsed", String(parsed));
245-
}
246-
return await this.httpRequest(url.toString());
247-
}
248-
249-
/**
250-
* Fetch streaming price updates for a set of price feed IDs.
251-
* This endpoint can be customized by specifying the encoding type, whether the results should include parsed updates,
252-
* and if unordered updates or only benchmark updates are allowed.
253-
* This will return an EventSource that can be used to listen to streaming updates.
254-
*
255-
* @param ids Array of hex-encoded price feed IDs for which streaming updates are requested.
256-
* @param encoding Optional encoding type. If specified, updates are returned in the specified encoding. Default is hex.
257-
* @param parsed Optional boolean to specify if the parsed price update should be included in the response. Default is false.
258-
* @param allow_unordered Optional boolean to specify if unordered updates are allowed to be included in the stream. Default is false.
259-
* @param benchmarks_only Optional boolean to specify if only benchmark prices that are the initial price updates at a given timestamp (i.e., prevPubTime != pubTime) should be returned. Default is false.
260-
* @returns An EventSource instance for receiving streaming updates.
261-
*/
262-
async getV2StreamingPriceUpdates(
263-
ids: HexString[],
264-
encoding?: EncodingType,
265-
parsed?: boolean,
266-
allow_unordered?: boolean,
267-
benchmarks_only?: boolean
268-
): Promise<EventSource> {
269-
const url = new URL("/v2/updates/price/stream", this.baseURL);
270-
ids.forEach((id) => {
271-
url.searchParams.append("ids[]", id);
272-
});
273-
const params = {
274-
encoding,
275-
parsed: parsed !== undefined ? String(parsed) : undefined,
276-
allow_unordered:
277-
allow_unordered !== undefined ? String(allow_unordered) : undefined,
278-
benchmarks_only:
279-
benchmarks_only !== undefined ? String(benchmarks_only) : undefined,
280-
};
281-
Object.entries(params).forEach(([key, value]) => {
282-
if (value !== undefined) {
283-
url.searchParams.append(key, value);
284-
}
285-
});
286-
const eventSource = new EventSource(url.toString());
287-
return eventSource;
288-
}
289-
290132
/**
291133
* Fetch Latest PriceFeeds of given price ids.
292134
* 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)
@@ -301,19 +143,15 @@ export class PriceServiceConnection {
301143
return [];
302144
}
303145

304-
const url = new URL(`${this.baseURL}/api/latest_price_feeds`);
305-
priceIds.forEach((id) => url.searchParams.append("ids[]", id));
306-
url.searchParams.append(
307-
"verbose",
308-
String(this.priceFeedRequestConfig.verbose)
309-
);
310-
url.searchParams.append(
311-
"binary",
312-
String(this.priceFeedRequestConfig.binary)
313-
);
314-
315-
const priceFeedsJson = await this.httpRequest(url.toString());
316-
return priceFeedsJson.map((priceFeedJson: any) =>
146+
const response = await this.httpClient.get("/api/latest_price_feeds", {
147+
params: {
148+
ids: priceIds,
149+
verbose: this.priceFeedRequestConfig.verbose,
150+
binary: this.priceFeedRequestConfig.binary,
151+
},
152+
});
153+
const priceFeedsJson = response.data as any[];
154+
return priceFeedsJson.map((priceFeedJson) =>
317155
PriceFeed.fromJson(priceFeedJson)
318156
);
319157
}
@@ -328,11 +166,12 @@ export class PriceServiceConnection {
328166
* @returns Array of base64 encoded VAAs.
329167
*/
330168
async getLatestVaas(priceIds: HexString[]): Promise<string[]> {
331-
const url = new URL(`${this.baseURL}/api/latest_vaas`);
332-
priceIds.forEach((id) => url.searchParams.append("ids[]", id));
333-
334-
const vaasJson = await this.httpRequest(url.toString());
335-
return vaasJson as string[];
169+
const response = await this.httpClient.get("/api/latest_vaas", {
170+
params: {
171+
ids: priceIds,
172+
},
173+
});
174+
return response.data;
336175
}
337176

338177
/**
@@ -351,12 +190,13 @@ export class PriceServiceConnection {
351190
priceId: HexString,
352191
publishTime: EpochTimeStamp
353192
): Promise<[string, EpochTimeStamp]> {
354-
const url = new URL(`${this.baseURL}/api/get_vaa`);
355-
url.searchParams.append("id", priceId);
356-
url.searchParams.append("publish_time", publishTime.toString());
357-
358-
const responseJson = await this.httpRequest(url.toString());
359-
return [responseJson.vaa, responseJson.publishTime];
193+
const response = await this.httpClient.get("/api/get_vaa", {
194+
params: {
195+
id: priceId,
196+
publish_time: publishTime,
197+
},
198+
});
199+
return [response.data.vaa, response.data.publishTime];
360200
}
361201

362202
/**
@@ -373,20 +213,16 @@ export class PriceServiceConnection {
373213
priceId: HexString,
374214
publishTime: EpochTimeStamp
375215
): Promise<PriceFeed> {
376-
const url = new URL(`${this.baseURL}/api/get_price_feed`);
377-
url.searchParams.append("id", priceId);
378-
url.searchParams.append("publish_time", publishTime.toString());
379-
url.searchParams.append(
380-
"verbose",
381-
String(this.priceFeedRequestConfig.verbose)
382-
);
383-
url.searchParams.append(
384-
"binary",
385-
String(this.priceFeedRequestConfig.binary)
386-
);
216+
const response = await this.httpClient.get("/api/get_price_feed", {
217+
params: {
218+
id: priceId,
219+
publish_time: publishTime,
220+
verbose: this.priceFeedRequestConfig.verbose,
221+
binary: this.priceFeedRequestConfig.binary,
222+
},
223+
});
387224

388-
const responseJson = await this.httpRequest(url.toString());
389-
return PriceFeed.fromJson(responseJson);
225+
return PriceFeed.fromJson(response.data);
390226
}
391227

392228
/**
@@ -396,10 +232,8 @@ export class PriceServiceConnection {
396232
* @returns Array of hex-encoded price ids.
397233
*/
398234
async getPriceFeedIds(): Promise<HexString[]> {
399-
const url = new URL(`${this.baseURL}/api/price_feed_ids`);
400-
401-
const responseJson = await this.httpRequest(url.toString());
402-
return responseJson as HexString[];
235+
const response = await this.httpClient.get("/api/price_feed_ids");
236+
return response.data;
403237
}
404238

405239
/**

price_service/client/js/src/examples/PriceServiceClient.ts

+17-19
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,30 @@ async function run() {
3737
});
3838

3939
const priceIds = argv.priceIds as string[];
40-
41-
// Get price feeds
42-
const priceFeeds = await connection.getV2PriceFeeds("btc", "crypto");
40+
const priceFeeds = await connection.getLatestPriceFeeds(priceIds);
4341
console.log(priceFeeds);
42+
console.log(priceFeeds?.at(0)?.getPriceNoOlderThan(60));
4443

45-
// Latest price updates
46-
const priceUpdates = await connection.getV2LatestPriceUpdates(priceIds);
47-
console.log(priceUpdates);
44+
console.log("Subscribing to price feed updates.");
4845

49-
// Streaming price updates
50-
const eventSource = await connection.getV2StreamingPriceUpdates(priceIds);
46+
await connection.subscribePriceFeedUpdates(priceIds, (priceFeed) => {
47+
console.log(
48+
`Current price for ${priceFeed.id}: ${JSON.stringify(
49+
priceFeed.getPriceNoOlderThan(60)
50+
)}.`
51+
);
52+
console.log(priceFeed.getVAA());
53+
});
5154

52-
eventSource.onmessage = (event) => {
53-
console.log("Received price update:", event.data);
54-
};
55+
await sleep(600000);
5556

56-
eventSource.onerror = (error) => {
57-
console.error("Error receiving updates:", error);
58-
eventSource.close();
59-
};
57+
// To close the websocket you should either unsubscribe from all
58+
// price feeds or call `connection.stopWebSocket()` directly.
6059

61-
await sleep(5000);
60+
console.log("Unsubscribing from price feed updates.");
61+
await connection.unsubscribePriceFeedUpdates(priceIds);
6262

63-
// To stop listening to the updates, you can call eventSource.close();
64-
console.log("Closing event source.");
65-
eventSource.close();
63+
// connection.closeWebSocket();
6664
}
6765

6866
run();

0 commit comments

Comments
 (0)