Skip to content

Commit 0a05633

Browse files
committed
feat(hermes-client): allow passing fetch options
This PR enables passing the `RequestInit` options for `fetch` to individual `HermesClient` methods. This PR also fixes merging `AbortControllers`, which was previously not done correctly (but also no way to pass an `AbortController` was actually exposed via the public API, so this is not a breaking change), and upgrades typescript to the catalog version, which is required to support `AbortSignal.any`.
1 parent 1b335a5 commit 0a05633

File tree

3 files changed

+168
-482
lines changed

3 files changed

+168
-482
lines changed

apps/hermes/client/js/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/hermes-client",
3-
"version": "1.3.1",
3+
"version": "1.4.0",
44
"description": "Pyth Hermes Client",
55
"author": {
66
"name": "Pyth Data Association"
@@ -47,7 +47,7 @@
4747
"openapi-zod-client": "^1.18.1",
4848
"prettier": "^2.6.2",
4949
"ts-jest": "^29.0.5",
50-
"typescript": "^4.6.3",
50+
"typescript": "catalog:",
5151
"yargs": "^17.4.1"
5252
},
5353
"dependencies": {

apps/hermes/client/js/src/HermesClient.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,16 @@ export class HermesClient {
6464
options?: RequestInit,
6565
retries = this.httpRetries,
6666
backoff = 100 + Math.floor(Math.random() * 100), // Adding randomness to the initial backoff to avoid "thundering herd" scenario where a lot of clients that get kicked off all at the same time (say some script or something) and fail to connect all retry at exactly the same time too
67-
externalAbortController?: AbortController
6867
): Promise<ResponseData> {
69-
const controller = externalAbortController ?? new AbortController();
70-
const { signal } = controller;
71-
options = {
72-
...options,
73-
signal,
74-
headers: { ...this.headers, ...options?.headers },
75-
}; // Merge any existing options with the signal and headers
76-
77-
// Set a timeout to abort the request if it takes too long
78-
const timeout = setTimeout(() => controller.abort(), this.timeout);
79-
8068
try {
81-
const response = await fetch(url, options);
82-
clearTimeout(timeout); // Clear the timeout if the request completes in time
69+
const response = await fetch(url, {
70+
...options,
71+
signal: AbortSignal.any([
72+
...(options?.signal ? [options.signal] : []),
73+
AbortSignal.timeout(this.timeout)
74+
]),
75+
headers: { ...this.headers, ...options?.headers },
76+
});
8377
if (!response.ok) {
8478
const errorBody = await response.text();
8579
throw new Error(
@@ -91,7 +85,6 @@ export class HermesClient {
9185
const data = await response.json();
9286
return schema.parse(data);
9387
} catch (error) {
94-
clearTimeout(timeout);
9588
if (
9689
retries > 0 &&
9790
!(error instanceof Error && error.name === "AbortError")
@@ -115,17 +108,19 @@ export class HermesClient {
115108
*
116109
* @returns Array of PriceFeedMetadata objects.
117110
*/
118-
async getPriceFeeds(options?: {
111+
async getPriceFeeds({ fetchOptions, ...options }: {
119112
query?: string;
120113
filter?: string;
121-
}): Promise<PriceFeedMetadata[]> {
114+
fetchOptions?: RequestInit;
115+
} = {}): Promise<PriceFeedMetadata[]> {
122116
const url = this.buildURL("price_feeds");
123117
if (options) {
124118
this.appendUrlSearchParams(url, options);
125119
}
126120
return await this.httpRequest(
127121
url.toString(),
128-
schemas.PriceFeedMetadata.array()
122+
schemas.PriceFeedMetadata.array(),
123+
fetchOptions
129124
);
130125
}
131126

@@ -140,10 +135,11 @@ export class HermesClient {
140135
*
141136
* @returns PublisherCaps object containing the latest publisher stake caps.
142137
*/
143-
async getLatestPublisherCaps(options?: {
138+
async getLatestPublisherCaps({ fetchOptions, ...options }: {
144139
encoding?: EncodingType;
145140
parsed?: boolean;
146-
}): Promise<PublisherCaps> {
141+
fetchOptions?: RequestInit;
142+
} = {}): Promise<PublisherCaps> {
147143
const url = this.buildURL("updates/publisher_stake_caps/latest");
148144
if (options) {
149145
this.appendUrlSearchParams(url, options);
@@ -173,7 +169,8 @@ export class HermesClient {
173169
encoding?: EncodingType;
174170
parsed?: boolean;
175171
ignoreInvalidPriceIds?: boolean;
176-
}
172+
},
173+
fetchOptions?: RequestInit
177174
): Promise<PriceUpdate> {
178175
const url = this.buildURL("updates/price/latest");
179176
for (const id of ids) {
@@ -185,7 +182,7 @@ export class HermesClient {
185182
this.appendUrlSearchParams(url, transformedOptions);
186183
}
187184

188-
return this.httpRequest(url.toString(), schemas.PriceUpdate);
185+
return this.httpRequest(url.toString(), schemas.PriceUpdate, fetchOptions);
189186
}
190187

191188
/**
@@ -209,7 +206,8 @@ export class HermesClient {
209206
encoding?: EncodingType;
210207
parsed?: boolean;
211208
ignoreInvalidPriceIds?: boolean;
212-
}
209+
},
210+
fetchOptions?: RequestInit
213211
): Promise<PriceUpdate> {
214212
const url = this.buildURL(`updates/price/${publishTime}`);
215213
for (const id of ids) {
@@ -221,7 +219,7 @@ export class HermesClient {
221219
this.appendUrlSearchParams(url, transformedOptions);
222220
}
223221

224-
return this.httpRequest(url.toString(), schemas.PriceUpdate);
222+
return this.httpRequest(url.toString(), schemas.PriceUpdate, fetchOptions);
225223
}
226224

227225
/**
@@ -286,7 +284,8 @@ export class HermesClient {
286284
encoding?: EncodingType;
287285
parsed?: boolean;
288286
ignoreInvalidPriceIds?: boolean;
289-
}
287+
},
288+
fetchOptions?: RequestInit
290289
): Promise<TwapsResponse> {
291290
const url = this.buildURL(`updates/twap/${window_seconds}/latest`);
292291
for (const id of ids) {
@@ -298,7 +297,7 @@ export class HermesClient {
298297
this.appendUrlSearchParams(url, transformedOptions);
299298
}
300299

301-
return this.httpRequest(url.toString(), schemas.TwapsResponse);
300+
return this.httpRequest(url.toString(), schemas.TwapsResponse, fetchOptions);
302301
}
303302

304303
private appendUrlSearchParams(

0 commit comments

Comments
 (0)