@internetarchive/fetch-handler 1.0.1 → 1.1.0-webdev-7731.2
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/.github/workflows/ci.yml +5 -0
- package/.github/workflows/gh-pages-main.yml +4 -0
- package/.github/workflows/pr-preview.yml +4 -0
- package/README.md +5 -5
- package/demo/app-root.ts +1 -1
- package/dist/demo/app-root.d.ts +1 -1
- package/dist/demo/app-root.js +1 -1
- package/dist/demo/app-root.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/src/fetch-handler-interface.d.ts +16 -3
- package/dist/src/fetch-handler-interface.js.map +1 -1
- package/dist/src/{ia-fetch-handler.d.ts → fetch-handler.d.ts} +10 -2
- package/dist/src/{ia-fetch-handler.js → fetch-handler.js} +13 -16
- package/dist/src/fetch-handler.js.map +1 -0
- package/dist/src/fetch-options.d.ts +5 -0
- package/dist/src/fetch-options.js +2 -0
- package/dist/src/fetch-options.js.map +1 -0
- package/dist/src/fetch-retry/configuration/default-retry-configuration.d.ts +10 -0
- package/dist/src/fetch-retry/configuration/default-retry-configuration.js +20 -0
- package/dist/src/fetch-retry/configuration/default-retry-configuration.js.map +1 -0
- package/dist/src/fetch-retry/configuration/milliseconds.d.ts +1 -0
- package/dist/src/fetch-retry/configuration/milliseconds.js +2 -0
- package/dist/src/fetch-retry/configuration/milliseconds.js.map +1 -0
- package/dist/src/fetch-retry/configuration/no-retry-configuration.d.ts +6 -0
- package/dist/src/fetch-retry/configuration/no-retry-configuration.js +9 -0
- package/dist/src/fetch-retry/configuration/no-retry-configuration.js.map +1 -0
- package/dist/src/fetch-retry/configuration/retry-configuring.d.ts +5 -0
- package/dist/src/fetch-retry/configuration/retry-configuring.js +2 -0
- package/dist/src/fetch-retry/configuration/retry-configuring.js.map +1 -0
- package/dist/src/{utils → fetch-retry}/fetch-retrier.d.ts +11 -13
- package/dist/src/fetch-retry/fetch-retrier.js +97 -0
- package/dist/src/fetch-retry/fetch-retrier.js.map +1 -0
- package/dist/src/fetch-retry/legacy-args.d.ts +2 -0
- package/dist/src/fetch-retry/legacy-args.js +11 -0
- package/dist/src/fetch-retry/legacy-args.js.map +1 -0
- package/dist/test/default-retry-config.test.js +30 -0
- package/dist/test/default-retry-config.test.js.map +1 -0
- package/dist/test/fetch-handler.test.d.ts +1 -0
- package/dist/test/fetch-handler.test.js +87 -0
- package/dist/test/fetch-handler.test.js.map +1 -0
- package/dist/test/fetch-retrier.test.js +76 -42
- package/dist/test/fetch-retrier.test.js.map +1 -1
- package/dist/test/legacy-args.test.d.ts +1 -0
- package/dist/test/legacy-args.test.js +21 -0
- package/dist/test/legacy-args.test.js.map +1 -0
- package/dist/test/mocks/mock-fetch-retrier.d.ts +10 -0
- package/dist/test/mocks/mock-fetch-retrier.js +11 -0
- package/dist/test/mocks/mock-fetch-retrier.js.map +1 -0
- package/dist/test/mocks/mock-retry-config.d.ts +7 -0
- package/dist/test/mocks/mock-retry-config.js +13 -0
- package/dist/test/mocks/mock-retry-config.js.map +1 -0
- package/dist/test/no-retry-config.test.d.ts +1 -0
- package/dist/test/no-retry-config.test.js +13 -0
- package/dist/test/no-retry-config.test.js.map +1 -0
- package/dist/test/retrier-legacy-args.test.d.ts +1 -0
- package/dist/test/retrier-legacy-args.test.js +27 -0
- package/dist/test/retrier-legacy-args.test.js.map +1 -0
- package/index.ts +9 -1
- package/package.json +5 -5
- package/src/fetch-handler-interface.ts +23 -4
- package/src/{ia-fetch-handler.ts → fetch-handler.ts} +27 -15
- package/src/fetch-options.ts +6 -0
- package/src/fetch-retry/configuration/default-retry-configuration.ts +23 -0
- package/src/fetch-retry/configuration/milliseconds.ts +1 -0
- package/src/fetch-retry/configuration/no-retry-configuration.ts +12 -0
- package/src/fetch-retry/configuration/retry-configuring.ts +11 -0
- package/src/fetch-retry/fetch-retrier.ts +146 -0
- package/src/fetch-retry/legacy-args.ts +13 -0
- package/test/default-retry-config.test.ts +34 -0
- package/test/fetch-handler.test.ts +99 -0
- package/test/fetch-retrier.test.ts +87 -46
- package/test/legacy-args.test.ts +24 -0
- package/test/mocks/mock-fetch-retrier.ts +22 -0
- package/test/mocks/mock-retry-config.ts +19 -0
- package/test/no-retry-config.test.ts +14 -0
- package/test/retrier-legacy-args.test.ts +28 -0
- package/web-test-runner.config.mjs +5 -3
- package/dist/src/ia-fetch-handler.js.map +0 -1
- package/dist/src/utils/fetch-retrier.js +0 -94
- package/dist/src/utils/fetch-retrier.js.map +0 -1
- package/dist/test/ia-fetch-handler.test.js +0 -50
- package/dist/test/ia-fetch-handler.test.js.map +0 -1
- package/src/utils/fetch-retrier.ts +0 -141
- package/test/ia-fetch-handler.test.ts +0 -66
- /package/dist/test/{ia-fetch-handler.test.d.ts → default-retry-config.test.d.ts} +0 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -12,9 +12,14 @@ jobs:
|
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
14
|
- uses: actions/checkout@v4
|
|
15
|
+
|
|
15
16
|
- uses: actions/setup-node@v4
|
|
16
17
|
with:
|
|
17
18
|
node-version: latest
|
|
19
|
+
|
|
20
|
+
- name: Clean install (remove lockfile and node_modules)
|
|
21
|
+
run: |
|
|
22
|
+
rm -rf node_modules package-lock.json
|
|
18
23
|
|
|
19
24
|
- name: Install dependencies
|
|
20
25
|
run: npm install
|
|
@@ -18,10 +18,14 @@ jobs:
|
|
|
18
18
|
- uses: actions/checkout@v4
|
|
19
19
|
with:
|
|
20
20
|
persist-credentials: false
|
|
21
|
+
|
|
21
22
|
- uses: actions/setup-node@v4
|
|
22
23
|
with:
|
|
23
24
|
node-version: latest
|
|
24
25
|
|
|
26
|
+
- name: Clean install (remove lockfile and node_modules)
|
|
27
|
+
run: |
|
|
28
|
+
rm -rf node_modules package-lock.json
|
|
25
29
|
|
|
26
30
|
- name: Install dependencies
|
|
27
31
|
run: npm install
|
|
@@ -22,10 +22,14 @@ jobs:
|
|
|
22
22
|
steps:
|
|
23
23
|
- name: Checkout
|
|
24
24
|
uses: actions/checkout@v4
|
|
25
|
+
|
|
25
26
|
- uses: actions/setup-node@v4
|
|
26
27
|
with:
|
|
27
28
|
node-version: latest
|
|
28
29
|
|
|
30
|
+
- name: Clean install (remove lockfile and node_modules)
|
|
31
|
+
run: |
|
|
32
|
+
rm -rf node_modules package-lock.json
|
|
29
33
|
|
|
30
34
|
- name: Install and Build
|
|
31
35
|
run: |
|
package/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
 [](https://codecov.io/gh/internetarchive/iaux-fetch-handler)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
# Internet Archive Fetch Handler
|
|
4
|
+
# Internet Archive Fetch Handler library
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
A custom
|
|
7
|
+
A custom library for handling API requests.
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install @internetarchive/fetch-handler
|
|
13
|
+
npm install @internetarchive/fetch-handler
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
## Sample Usage
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import { IaFetchHandler } from '
|
|
20
|
+
import { IaFetchHandler } from '@internetarchive/fetch-handler';
|
|
21
21
|
|
|
22
22
|
@property({ type: Object }) data: any = null;
|
|
23
23
|
@property({ type: String }) error: string = '';
|
package/demo/app-root.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { html, LitElement, css } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
3
|
|
|
4
|
-
import { IaFetchHandler } from '../src/
|
|
4
|
+
import { IaFetchHandler } from '../src/fetch-handler';
|
|
5
5
|
|
|
6
6
|
@customElement('app-root')
|
|
7
7
|
export class AppRoot extends LitElement {
|
package/dist/demo/app-root.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ export declare class AppRoot extends LitElement {
|
|
|
7
7
|
constructor();
|
|
8
8
|
connectedCallback(): void;
|
|
9
9
|
fetchData(): Promise<void>;
|
|
10
|
-
render(): import("lit").TemplateResult<1>;
|
|
10
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
11
11
|
static styles: import("lit").CSSResult;
|
|
12
12
|
}
|
package/dist/demo/app-root.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { html, LitElement, css } from 'lit';
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
-
import { IaFetchHandler } from '../src/
|
|
4
|
+
import { IaFetchHandler } from '../src/fetch-handler';
|
|
5
5
|
let AppRoot = class AppRoot extends LitElement {
|
|
6
6
|
constructor() {
|
|
7
7
|
super();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-root.js","sourceRoot":"","sources":["../../demo/app-root.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"app-root.js","sourceRoot":"","sources":["../../demo/app-root.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG/C,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,UAAU;IAOrC;QACE,KAAK,EAAE,CAAC;QAPkB,SAAI,GAAQ,IAAI,CAAC;QACjB,UAAK,GAAW,EAAE,CAAC;QAClB,YAAO,GAAY,KAAK,CAAC;QAMpD,IAAI,CAAC,YAAY,GAAG,IAAI,cAAc,CAAC;YACrC,YAAY,EAAE,qBAAqB;SACpC,CAAC,CAAC;IACL,CAAC;IAED,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,MAAM,GACV,MAAM,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;YAChE,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,GAAG,wBAAwB,KAAK,EAAE,CAAC;QAC/C,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;UAGL,IAAI,CAAC,OAAO;YACZ,CAAC,CAAC,IAAI,CAAA,mBAAmB;YACzB,CAAC,CAAC,IAAI,CAAC,KAAK;gBACV,CAAC,CAAC,IAAI,CAAA,oBAAoB,IAAI,CAAC,KAAK,MAAM;gBAC1C,CAAC,CAAC,IAAI,CAAC,IAAI;oBACT,CAAC,CAAC,IAAI,CAAA,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ;oBACxD,CAAC,CAAC,IAAI,CAAA,2BAA2B;0BACrB,IAAI,CAAC,SAAS;;KAEnC,CAAC;IACJ,CAAC;;AAEM,cAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BlB,AA3BY,CA2BX;AA3E0B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;qCAAkB;AACjB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;sCAAoB;AAClB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;wCAA0B;AAH3C,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CA6EnB","sourcesContent":["import { html, LitElement, css } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\n\nimport { IaFetchHandler } from '../src/fetch-handler';\n\n@customElement('app-root')\nexport class AppRoot extends LitElement {\n @property({ type: Object }) data: any = null;\n @property({ type: String }) error: string = '';\n @property({ type: Boolean }) loading: boolean = false;\n\n private fetchHandler: IaFetchHandler;\n\n constructor() {\n super();\n this.fetchHandler = new IaFetchHandler({\n iaApiBaseUrl: 'https://archive.org',\n });\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.fetchData();\n }\n\n async fetchData() {\n this.loading = true;\n this.error = '';\n try {\n const result =\n await this.fetchHandler.fetchIAApiResponse('/metadata/goody');\n this.data = result;\n } catch (error) {\n this.error = `Error fetching data: ${error}`;\n } finally {\n this.loading = false;\n }\n }\n\n render() {\n return html`\n <div class=\"container\">\n <h1>Fetch Data</h1>\n ${this.loading\n ? html`<p>Loading...</p>`\n : this.error\n ? html`<p class=\"error\">${this.error}</p>`\n : this.data\n ? html`<pre>${JSON.stringify(this.data, null, 2)}</pre>`\n : html`<p>No data available.</p>`}\n <button @click=\"${this.fetchData}\">Retry</button>\n </div>\n `;\n }\n\n static styles = css`\n .container {\n padding: 20px;\n font-family: Arial, sans-serif;\n }\n\n h1 {\n color: #333;\n }\n\n .error {\n color: red;\n }\n\n button {\n margin-top: 20px;\n padding: 10px 20px;\n background-color: #007bff;\n color: white;\n border: none;\n cursor: pointer;\n border-radius: 5px;\n }\n\n button:hover {\n background-color: #0056b3;\n }\n `;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export { IaFetchHandler } from './src/
|
|
1
|
+
export { IaFetchHandler } from './src/fetch-handler';
|
|
2
2
|
export { FetchHandlerInterface } from './src/fetch-handler-interface';
|
|
3
|
+
export { FetchRetrier, FetchRetrierInterface, } from './src/fetch-retry/fetch-retrier';
|
|
4
|
+
export { DefaultRetryConfiguration } from './src/fetch-retry/configuration/default-retry-configuration';
|
|
5
|
+
export { NoRetryConfiguration } from './src/fetch-retry/configuration/no-retry-configuration';
|
|
6
|
+
export type { RetryConfiguring } from './src/fetch-retry/configuration/retry-configuring';
|
|
7
|
+
export type { FetchOptions } from './src/fetch-options';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
export { IaFetchHandler } from './src/
|
|
1
|
+
export { IaFetchHandler } from './src/fetch-handler';
|
|
2
|
+
export { FetchRetrier, } from './src/fetch-retry/fetch-retrier';
|
|
3
|
+
export { DefaultRetryConfiguration } from './src/fetch-retry/configuration/default-retry-configuration';
|
|
4
|
+
export { NoRetryConfiguration } from './src/fetch-retry/configuration/no-retry-configuration';
|
|
2
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EACL,YAAY,GAEb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6DAA6D,CAAC;AACxG,OAAO,EAAE,oBAAoB,EAAE,MAAM,wDAAwD,CAAC","sourcesContent":["export { IaFetchHandler } from './src/fetch-handler';\nexport { FetchHandlerInterface } from './src/fetch-handler-interface';\nexport {\n FetchRetrier,\n FetchRetrierInterface,\n} from './src/fetch-retry/fetch-retrier';\nexport { DefaultRetryConfiguration } from './src/fetch-retry/configuration/default-retry-configuration';\nexport { NoRetryConfiguration } from './src/fetch-retry/configuration/no-retry-configuration';\nexport type { RetryConfiguring } from './src/fetch-retry/configuration/retry-configuring';\nexport type { FetchOptions } from './src/fetch-options';\n"]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import type { FetchOptions } from './fetch-options';
|
|
2
|
+
import type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';
|
|
1
3
|
export interface FetchHandlerInterface {
|
|
2
4
|
/**
|
|
3
5
|
* Generic fetch function that handles retries and common IA parameters like `reCache=1`
|
|
4
6
|
*
|
|
5
7
|
* @param input RequestInfo
|
|
6
|
-
* @param
|
|
8
|
+
* @param options RequestInit | FetchOptions
|
|
7
9
|
*/
|
|
8
|
-
fetch(
|
|
10
|
+
fetch(request: RequestInfo, options?: RequestInit | FetchOptions): Promise<Response>;
|
|
9
11
|
/**
|
|
10
12
|
* A helper function to fetch a response from an API and get a JSON object
|
|
11
13
|
*
|
|
@@ -17,6 +19,7 @@ export interface FetchHandlerInterface {
|
|
|
17
19
|
method?: string;
|
|
18
20
|
body?: BodyInit;
|
|
19
21
|
headers?: HeadersInit;
|
|
22
|
+
retryConfig?: RetryConfiguring;
|
|
20
23
|
}): Promise<T>;
|
|
21
24
|
/**
|
|
22
25
|
* A helper function to fetch a response from the IA API and get a JSON object
|
|
@@ -25,9 +28,19 @@ export interface FetchHandlerInterface {
|
|
|
25
28
|
* of the full URL. If you need a full URL, use `fetchApiResponse` instead.
|
|
26
29
|
*
|
|
27
30
|
* @param path string
|
|
28
|
-
* @param options?: {
|
|
31
|
+
* @param options?: {
|
|
32
|
+
* includeCredentials?: boolean;
|
|
33
|
+
* method?: string;
|
|
34
|
+
* body?: BodyInit;
|
|
35
|
+
* headers?: HeadersInit;
|
|
36
|
+
* retryConfig?: RetryConfiguring;
|
|
37
|
+
* }
|
|
29
38
|
*/
|
|
30
39
|
fetchIAApiResponse<T>(path: string, options?: {
|
|
31
40
|
includeCredentials?: boolean;
|
|
41
|
+
method?: string;
|
|
42
|
+
body?: BodyInit;
|
|
43
|
+
headers?: HeadersInit;
|
|
44
|
+
retryConfig?: RetryConfiguring;
|
|
32
45
|
}): Promise<T>;
|
|
33
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-handler-interface.js","sourceRoot":"","sources":["../../src/fetch-handler-interface.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"fetch-handler-interface.js","sourceRoot":"","sources":["../../src/fetch-handler-interface.ts"],"names":[],"mappings":"","sourcesContent":["import type { FetchOptions } from './fetch-options';\nimport type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';\n\nexport interface FetchHandlerInterface {\n /**\n * Generic fetch function that handles retries and common IA parameters like `reCache=1`\n *\n * @param input RequestInfo\n * @param options RequestInit | FetchOptions\n */\n fetch(\n request: RequestInfo,\n options?: RequestInit | FetchOptions,\n ): Promise<Response>;\n\n /**\n * A helper function to fetch a response from an API and get a JSON object\n *\n * @param path string\n * @param options?: { includeCredentials?: boolean }\n */\n fetchApiResponse<T>(\n url: string,\n options?: {\n includeCredentials?: boolean;\n method?: string;\n body?: BodyInit;\n headers?: HeadersInit;\n retryConfig?: RetryConfiguring;\n },\n ): Promise<T>;\n\n /**\n * A helper function to fetch a response from the IA API and get a JSON object\n *\n * This allows you to just pass the path to the API and get the response instead\n * of the full URL. If you need a full URL, use `fetchApiResponse` instead.\n *\n * @param path string\n * @param options?: {\n * includeCredentials?: boolean;\n * method?: string;\n * body?: BodyInit;\n * headers?: HeadersInit;\n * retryConfig?: RetryConfiguring;\n * }\n */\n fetchIAApiResponse<T>(\n path: string,\n options?: {\n includeCredentials?: boolean;\n method?: string;\n body?: BodyInit;\n headers?: HeadersInit;\n retryConfig?: RetryConfiguring;\n },\n ): Promise<T>;\n}\n"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { FetchRetrierInterface } from './
|
|
1
|
+
import { FetchRetrierInterface } from './fetch-retry/fetch-retrier';
|
|
2
2
|
import type { FetchHandlerInterface } from './fetch-handler-interface';
|
|
3
|
+
import type { FetchOptions } from './fetch-options';
|
|
4
|
+
import type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';
|
|
3
5
|
/**
|
|
4
6
|
* The FetchHandler adds some common helpers:
|
|
5
7
|
* - retry the request if it fails
|
|
@@ -14,10 +16,15 @@ export declare class IaFetchHandler implements FetchHandlerInterface {
|
|
|
14
16
|
iaApiBaseUrl?: string;
|
|
15
17
|
fetchRetrier?: FetchRetrierInterface;
|
|
16
18
|
searchParams?: string;
|
|
19
|
+
defaultRetryConfiguration?: RetryConfiguring;
|
|
17
20
|
});
|
|
18
21
|
/** @inheritdoc */
|
|
19
22
|
fetchIAApiResponse<T>(path: string, options?: {
|
|
20
23
|
includeCredentials?: boolean;
|
|
24
|
+
method?: string;
|
|
25
|
+
body?: BodyInit;
|
|
26
|
+
headers?: HeadersInit;
|
|
27
|
+
retryConfig?: RetryConfiguring;
|
|
21
28
|
}): Promise<T>;
|
|
22
29
|
/** @inheritdoc */
|
|
23
30
|
fetchApiResponse<T>(url: string, options?: {
|
|
@@ -25,9 +32,10 @@ export declare class IaFetchHandler implements FetchHandlerInterface {
|
|
|
25
32
|
method?: string;
|
|
26
33
|
body?: BodyInit;
|
|
27
34
|
headers?: HeadersInit;
|
|
35
|
+
retryConfig?: RetryConfiguring;
|
|
28
36
|
}): Promise<T>;
|
|
29
37
|
/** @inheritdoc */
|
|
30
|
-
fetch(
|
|
38
|
+
fetch(request: RequestInfo, options?: RequestInit | FetchOptions): Promise<Response>;
|
|
31
39
|
/**
|
|
32
40
|
* Since RequestInfo can be either a `Request` or `string`, we need to change
|
|
33
41
|
* the way we add search params to it depending on the input.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchRetrier } from './
|
|
1
|
+
import { FetchRetrier, } from './fetch-retry/fetch-retrier';
|
|
2
2
|
/**
|
|
3
3
|
* The FetchHandler adds some common helpers:
|
|
4
4
|
* - retry the request if it fails
|
|
@@ -35,36 +35,33 @@ export class IaFetchHandler {
|
|
|
35
35
|
requestInit.body = options.body;
|
|
36
36
|
if (options === null || options === void 0 ? void 0 : options.headers)
|
|
37
37
|
requestInit.headers = options.headers;
|
|
38
|
-
const response = await this.fetch(url,
|
|
38
|
+
const response = await this.fetch(url, {
|
|
39
|
+
requestInit: requestInit,
|
|
40
|
+
retryConfig: options === null || options === void 0 ? void 0 : options.retryConfig,
|
|
41
|
+
});
|
|
39
42
|
const json = await response.json();
|
|
40
43
|
return json;
|
|
41
44
|
}
|
|
42
45
|
/** @inheritdoc */
|
|
43
|
-
async fetch(
|
|
44
|
-
let
|
|
46
|
+
async fetch(request, options) {
|
|
47
|
+
let finalRequest = request;
|
|
45
48
|
const urlParams = new URLSearchParams(this.searchParams);
|
|
46
49
|
if (urlParams.get('reCache') === '1') {
|
|
47
|
-
|
|
50
|
+
const urlString = typeof request === 'string' ? request : request.url;
|
|
51
|
+
finalRequest = this.addSearchParams(urlString, { reCache: '1' });
|
|
48
52
|
}
|
|
49
|
-
return this.fetchRetrier.fetchRetry(
|
|
53
|
+
return this.fetchRetrier.fetchRetry(finalRequest, options);
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
56
|
* Since RequestInfo can be either a `Request` or `string`, we need to change
|
|
53
57
|
* the way we add search params to it depending on the input.
|
|
54
58
|
*/
|
|
55
|
-
addSearchParams(
|
|
56
|
-
const urlString = typeof input === 'string' ? input : input.url;
|
|
59
|
+
addSearchParams(urlString, params) {
|
|
57
60
|
const url = new URL(urlString, window.location.href);
|
|
58
61
|
for (const [key, value] of Object.entries(params)) {
|
|
59
62
|
url.searchParams.set(key, value);
|
|
60
63
|
}
|
|
61
|
-
|
|
62
|
-
return url.href;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const newRequest = new Request(url.href, input);
|
|
66
|
-
return newRequest;
|
|
67
|
-
}
|
|
64
|
+
return url.href;
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
|
-
//# sourceMappingURL=
|
|
67
|
+
//# sourceMappingURL=fetch-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-handler.js","sourceRoot":"","sources":["../../src/fetch-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n FetchRetrier,\n FetchRetrierInterface,\n} from './fetch-retry/fetch-retrier';\nimport type { FetchHandlerInterface } from './fetch-handler-interface';\nimport type { FetchOptions } from './fetch-options';\nimport type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';\n\n/**\n * The FetchHandler adds some common helpers:\n * - retry the request if it fails\n * - add `reCache=1` to the request if it's in the current url so the backend sees it\n * - add convenience method for fetching/decoding an API response by just the path\n */\nexport class IaFetchHandler implements FetchHandlerInterface {\n private iaApiBaseUrl?: string;\n\n private fetchRetrier: FetchRetrierInterface = new FetchRetrier();\n\n private searchParams?: string;\n\n constructor(options?: {\n iaApiBaseUrl?: string;\n fetchRetrier?: FetchRetrierInterface;\n searchParams?: string;\n defaultRetryConfiguration?: RetryConfiguring;\n }) {\n if (options?.iaApiBaseUrl) this.iaApiBaseUrl = options.iaApiBaseUrl;\n if (options?.fetchRetrier) this.fetchRetrier = options.fetchRetrier;\n if (options?.searchParams) {\n this.searchParams = options.searchParams;\n } else {\n this.searchParams = window.location.search;\n }\n }\n\n /** @inheritdoc */\n async fetchIAApiResponse<T>(\n path: string,\n options?: {\n includeCredentials?: boolean;\n method?: string;\n body?: BodyInit;\n headers?: HeadersInit;\n retryConfig?: RetryConfiguring;\n },\n ): Promise<T> {\n const url = `${this.iaApiBaseUrl}${path}`;\n return this.fetchApiResponse(url, options);\n }\n\n /** @inheritdoc */\n async fetchApiResponse<T>(\n url: string,\n options?: {\n includeCredentials?: boolean;\n method?: string;\n body?: BodyInit;\n headers?: HeadersInit;\n retryConfig?: RetryConfiguring;\n },\n ): Promise<T> {\n const requestInit: RequestInit = {};\n if (options?.includeCredentials) requestInit.credentials = 'include';\n if (options?.method) requestInit.method = options.method;\n if (options?.body) requestInit.body = options.body;\n if (options?.headers) requestInit.headers = options.headers;\n const response = await this.fetch(url, {\n requestInit: requestInit,\n retryConfig: options?.retryConfig,\n });\n const json = await response.json();\n return json as T;\n }\n\n /** @inheritdoc */\n async fetch(\n request: RequestInfo,\n options?: RequestInit | FetchOptions,\n ): Promise<Response> {\n let finalRequest = request;\n const urlParams = new URLSearchParams(this.searchParams);\n if (urlParams.get('reCache') === '1') {\n const urlString = typeof request === 'string' ? request : request.url;\n finalRequest = this.addSearchParams(urlString, { reCache: '1' });\n }\n return this.fetchRetrier.fetchRetry(finalRequest, options);\n }\n\n /**\n * Since RequestInfo can be either a `Request` or `string`, we need to change\n * the way we add search params to it depending on the input.\n */\n private addSearchParams(\n urlString: string,\n params: Record<string, string>,\n ): string {\n const url = new URL(urlString, window.location.href);\n\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n\n return url.href;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-options.js","sourceRoot":"","sources":["../../src/fetch-options.ts"],"names":[],"mappings":"","sourcesContent":["import type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';\n\nexport type FetchOptions = {\n requestInit?: RequestInit;\n retryConfig?: RetryConfiguring;\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RetryConfiguring } from './retry-configuring';
|
|
2
|
+
import type { Milliseconds } from './milliseconds';
|
|
3
|
+
export declare class DefaultRetryConfiguration implements RetryConfiguring {
|
|
4
|
+
private readonly maxRetries;
|
|
5
|
+
constructor(options?: {
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
});
|
|
8
|
+
shouldRetry(response: Response | null, retryNumber: number): boolean;
|
|
9
|
+
retryDelay(retryNumber: number): Milliseconds;
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class DefaultRetryConfiguration {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.maxRetries = 2;
|
|
4
|
+
if ((options === null || options === void 0 ? void 0 : options.maxRetries) !== undefined) {
|
|
5
|
+
this.maxRetries = options.maxRetries;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
shouldRetry(response, retryNumber) {
|
|
9
|
+
if (response === null)
|
|
10
|
+
return false;
|
|
11
|
+
if (retryNumber > this.maxRetries)
|
|
12
|
+
return false;
|
|
13
|
+
return response.status >= 500 && response.status < 600;
|
|
14
|
+
}
|
|
15
|
+
retryDelay(retryNumber) {
|
|
16
|
+
// Exponential backoff up to 10 seconds
|
|
17
|
+
return Math.min(500 * 2 ** retryNumber, 10000);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=default-retry-configuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-retry-configuration.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/default-retry-configuration.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,yBAAyB;IAGpC,YAAY,OAAiC;QAF5B,eAAU,GAAqB,CAAC,CAAC;QAGhD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,MAAK,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACvC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,QAAyB,EAAE,WAAmB;QACxD,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,WAAW,GAAG,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAChD,OAAO,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IACzD,CAAC;IAED,UAAU,CAAC,WAAmB;QAC5B,uCAAuC;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;CACF","sourcesContent":["import type { RetryConfiguring } from './retry-configuring';\nimport type { Milliseconds } from './milliseconds';\n\nexport class DefaultRetryConfiguration implements RetryConfiguring {\n private readonly maxRetries: Readonly<number> = 2;\n\n constructor(options?: { maxRetries?: number }) {\n if (options?.maxRetries !== undefined) {\n this.maxRetries = options.maxRetries;\n }\n }\n\n shouldRetry(response: Response | null, retryNumber: number): boolean {\n if (response === null) return false;\n if (retryNumber > this.maxRetries) return false;\n return response.status >= 500 && response.status < 600;\n }\n\n retryDelay(retryNumber: number): Milliseconds {\n // Exponential backoff up to 10 seconds\n return Math.min(500 * 2 ** retryNumber, 10000);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Milliseconds = number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"milliseconds.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/milliseconds.ts"],"names":[],"mappings":"","sourcesContent":["export type Milliseconds = number;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-retry-configuration.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/no-retry-configuration.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,oBAAoB;IAC/B,WAAW;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IAED,UAAU;QACR,OAAO,CAAC,CAAC;IACX,CAAC;CACF","sourcesContent":["import type { RetryConfiguring } from './retry-configuring';\nimport type { Milliseconds } from './milliseconds';\n\nexport class NoRetryConfiguration implements RetryConfiguring {\n shouldRetry(): boolean {\n return false;\n }\n\n retryDelay(): Milliseconds {\n return 0;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-configuring.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/retry-configuring.ts"],"names":[],"mappings":"","sourcesContent":["import type { Milliseconds } from './milliseconds';\n\nexport interface RetryConfiguring {\n shouldRetry(\n response: Response | null,\n retryNumber: number,\n error?: unknown,\n ): boolean;\n\n retryDelay(retryNumber: number): Milliseconds;\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AnalyticsHandlerInterface } from '@internetarchive/analytics-manager';
|
|
2
|
+
import { type FetchOptions } from '../fetch-options';
|
|
3
|
+
import type { RetryConfiguring } from './configuration/retry-configuring';
|
|
2
4
|
/**
|
|
3
5
|
* A class that retries a fetch request.
|
|
4
6
|
*/
|
|
@@ -6,31 +8,27 @@ export interface FetchRetrierInterface {
|
|
|
6
8
|
/**
|
|
7
9
|
* Execute a fetch with retry.
|
|
8
10
|
*
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param retries Optional number of retries to attempt
|
|
11
|
+
* @param request RequestInfo
|
|
12
|
+
* @param options Optional RequestInit | FetchOptions
|
|
12
13
|
* @returns Promise<Response>
|
|
13
14
|
*/
|
|
14
|
-
fetchRetry(
|
|
15
|
+
fetchRetry(request: RequestInfo, options?: RequestInit | FetchOptions): Promise<Response>;
|
|
15
16
|
}
|
|
16
17
|
/** @inheritdoc */
|
|
17
18
|
export declare class FetchRetrier implements FetchRetrierInterface {
|
|
18
|
-
private analyticsHandler
|
|
19
|
-
private
|
|
20
|
-
private retryCount;
|
|
21
|
-
private retryDelay;
|
|
19
|
+
private analyticsHandler?;
|
|
20
|
+
private retryConfiguration;
|
|
22
21
|
constructor(options?: {
|
|
23
22
|
analyticsHandler?: AnalyticsHandlerInterface;
|
|
24
|
-
|
|
25
|
-
retryDelay?: number;
|
|
26
|
-
sleepFn?: (ms: number) => Promise<void>;
|
|
23
|
+
retryConfiguration?: RetryConfiguring;
|
|
27
24
|
});
|
|
28
25
|
/** @inheritdoc */
|
|
29
|
-
fetchRetry(
|
|
26
|
+
fetchRetry(request: RequestInfo, options?: RequestInit | FetchOptions): Promise<Response>;
|
|
27
|
+
private fetchRetryWithOptions;
|
|
30
28
|
private isContentBlockerError;
|
|
31
29
|
private readonly eventCategory;
|
|
32
30
|
private logRetryEvent;
|
|
33
31
|
private logFailureEvent;
|
|
34
|
-
private
|
|
32
|
+
private log4xxResponse;
|
|
35
33
|
private logContentBlockingEvent;
|
|
36
34
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { promisedSleep } from '../utils/promised-sleep';
|
|
2
|
+
import { legacyArgsAsFetchOptions } from './legacy-args';
|
|
3
|
+
import { DefaultRetryConfiguration } from './configuration/default-retry-configuration';
|
|
4
|
+
/** @inheritdoc */
|
|
5
|
+
export class FetchRetrier {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.retryConfiguration = new DefaultRetryConfiguration();
|
|
8
|
+
this.eventCategory = 'offshootFetchRetry';
|
|
9
|
+
if (options === null || options === void 0 ? void 0 : options.analyticsHandler)
|
|
10
|
+
this.analyticsHandler = options.analyticsHandler;
|
|
11
|
+
if (options === null || options === void 0 ? void 0 : options.retryConfiguration)
|
|
12
|
+
this.retryConfiguration = options.retryConfiguration;
|
|
13
|
+
}
|
|
14
|
+
/** @inheritdoc */
|
|
15
|
+
async fetchRetry(request, options) {
|
|
16
|
+
const fetchOptions = legacyArgsAsFetchOptions(options);
|
|
17
|
+
return await this.fetchRetryWithOptions(request, 0, fetchOptions);
|
|
18
|
+
}
|
|
19
|
+
async fetchRetryWithOptions(request, retryNumber, options) {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
const urlString = typeof request === 'string' ? request : request.url;
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(request, options === null || options === void 0 ? void 0 : options.requestInit);
|
|
24
|
+
if (response.ok)
|
|
25
|
+
return response;
|
|
26
|
+
if (response.status >= 400 && response.status < 500) {
|
|
27
|
+
this.log4xxResponse(response);
|
|
28
|
+
}
|
|
29
|
+
const retryConfig = (_a = options === null || options === void 0 ? void 0 : options.retryConfig) !== null && _a !== void 0 ? _a : this.retryConfiguration;
|
|
30
|
+
if (retryConfig.shouldRetry(response, retryNumber)) {
|
|
31
|
+
const delay = retryConfig.retryDelay(retryNumber);
|
|
32
|
+
await promisedSleep(delay);
|
|
33
|
+
this.logRetryEvent(urlString, retryNumber, response.statusText, response.status);
|
|
34
|
+
return this.fetchRetryWithOptions(request, retryNumber + 1, options);
|
|
35
|
+
}
|
|
36
|
+
this.logFailureEvent(urlString, response.status);
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// if a content blocker is detected, log it and don't retry
|
|
41
|
+
if (this.isContentBlockerError(error)) {
|
|
42
|
+
this.logContentBlockingEvent(urlString, error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const retryConfig = (_b = options === null || options === void 0 ? void 0 : options.retryConfig) !== null && _b !== void 0 ? _b : this.retryConfiguration;
|
|
46
|
+
if (retryConfig.shouldRetry(null, retryNumber)) {
|
|
47
|
+
const delay = retryConfig.retryDelay(retryNumber);
|
|
48
|
+
await promisedSleep(delay);
|
|
49
|
+
this.logRetryEvent(urlString, retryNumber, error, error);
|
|
50
|
+
return this.fetchRetryWithOptions(request, retryNumber + 1, options);
|
|
51
|
+
}
|
|
52
|
+
this.logFailureEvent(urlString, error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
isContentBlockerError(error) {
|
|
57
|
+
// all of the content blocker errors are `TypeError`
|
|
58
|
+
if (!(error instanceof TypeError))
|
|
59
|
+
return false;
|
|
60
|
+
const message = error.message.toLowerCase();
|
|
61
|
+
return message.includes('content blocker');
|
|
62
|
+
}
|
|
63
|
+
logRetryEvent(urlString, retryNumber, status, code) {
|
|
64
|
+
var _a;
|
|
65
|
+
(_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
|
|
66
|
+
category: this.eventCategory,
|
|
67
|
+
action: 'retryingFetch',
|
|
68
|
+
label: `retryNumber: ${retryNumber}, code: ${code}, status: ${status}, url: ${urlString}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
logFailureEvent(urlString, error) {
|
|
72
|
+
var _a;
|
|
73
|
+
(_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
|
|
74
|
+
category: this.eventCategory,
|
|
75
|
+
action: 'fetchFailed',
|
|
76
|
+
label: `error: ${error}, url: ${urlString}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
log4xxResponse(response) {
|
|
80
|
+
var _a;
|
|
81
|
+
const status = response.status;
|
|
82
|
+
(_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
|
|
83
|
+
category: this.eventCategory,
|
|
84
|
+
action: `status4xxResponse`,
|
|
85
|
+
label: `http status ${status}, url: ${response.url}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
logContentBlockingEvent(urlString, error) {
|
|
89
|
+
var _a;
|
|
90
|
+
(_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
|
|
91
|
+
category: this.eventCategory,
|
|
92
|
+
action: 'contentBlockerDetectedNotRetrying',
|
|
93
|
+
label: `error: ${error}, url: ${urlString}`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=fetch-retrier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-retrier.js","sourceRoot":"","sources":["../../../src/fetch-retry/fetch-retrier.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAoBxF,kBAAkB;AAClB,MAAM,OAAO,YAAY;IAMvB,YAAY,OAGX;QANO,uBAAkB,GACxB,IAAI,yBAAyB,EAAE,CAAC;QA4EjB,kBAAa,GAAG,oBAAoB,CAAC;QAtEpD,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,gBAAgB;YAC3B,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACnD,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,kBAAkB;YAC7B,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IACzD,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,UAAU,CACrB,OAAoB,EACpB,OAAoC;QAEpC,MAAM,YAAY,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;QACvD,OAAO,MAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC;IACpE,CAAC;IAEO,KAAK,CAAC,qBAAqB,CACjC,OAAoB,EACpB,WAAmB,EACnB,OAAsB;;QAEtB,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;QAEtE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,CAAC,CAAC;YAC5D,IAAI,QAAQ,CAAC,EAAE;gBAAE,OAAO,QAAQ,CAAC;YAEjC,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACpD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YAED,MAAM,WAAW,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,mCAAI,IAAI,CAAC,kBAAkB,CAAC;YACpE,IAAI,WAAW,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;gBAClD,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;gBAC3B,IAAI,CAAC,aAAa,CAChB,SAAS,EACT,WAAW,EACX,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,MAAM,CAChB,CAAC;gBACF,OAAO,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;YACvE,CAAC;YACD,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;YACjD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2DAA2D;YAC3D,IAAI,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtC,IAAI,CAAC,uBAAuB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBAC/C,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,WAAW,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,mCAAI,IAAI,CAAC,kBAAkB,CAAC;YACpE,IAAI,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;gBAClD,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;gBAC3B,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;gBACzD,OAAO,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;YACvE,CAAC;YACD,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACvC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,qBAAqB,CAAC,KAAc;QAC1C,oDAAoD;QACpD,IAAI,CAAC,CAAC,KAAK,YAAY,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAC7C,CAAC;IAIO,aAAa,CACnB,SAAiB,EACjB,WAAmB,EACnB,MAAe,EACf,IAAa;;QAEb,MAAA,IAAI,CAAC,gBAAgB,0CAAE,SAAS,CAAC;YAC/B,QAAQ,EAAE,IAAI,CAAC,aAAa;YAC5B,MAAM,EAAE,eAAe;YACvB,KAAK,EAAE,gBAAgB,WAAW,WAAW,IAAI,aAAa,MAAM,UAAU,SAAS,EAAE;SAC1F,CAAC,CAAC;IACL,CAAC;IAEO,eAAe,CAAC,SAAiB,EAAE,KAAc;;QACvD,MAAA,IAAI,CAAC,gBAAgB,0CAAE,SAAS,CAAC;YAC/B,QAAQ,EAAE,IAAI,CAAC,aAAa;YAC5B,MAAM,EAAE,aAAa;YACrB,KAAK,EAAE,UAAU,KAAK,UAAU,SAAS,EAAE;SAC5C,CAAC,CAAC;IACL,CAAC;IAEO,cAAc,CAAC,QAAkB;;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAE/B,MAAA,IAAI,CAAC,gBAAgB,0CAAE,SAAS,CAAC;YAC/B,QAAQ,EAAE,IAAI,CAAC,aAAa;YAC5B,MAAM,EAAE,mBAAmB;YAC3B,KAAK,EAAE,eAAe,MAAM,UAAU,QAAQ,CAAC,GAAG,EAAE;SACrD,CAAC,CAAC;IACL,CAAC;IAEO,uBAAuB,CAAC,SAAiB,EAAE,KAAc;;QAC/D,MAAA,IAAI,CAAC,gBAAgB,0CAAE,SAAS,CAAC;YAC/B,QAAQ,EAAE,IAAI,CAAC,aAAa;YAC5B,MAAM,EAAE,mCAAmC;YAC3C,KAAK,EAAE,UAAU,KAAK,UAAU,SAAS,EAAE;SAC5C,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import type { AnalyticsHandlerInterface } from '@internetarchive/analytics-manager';\nimport { promisedSleep } from '../utils/promised-sleep';\nimport { type FetchOptions } from '../fetch-options';\nimport { legacyArgsAsFetchOptions } from './legacy-args';\nimport { DefaultRetryConfiguration } from './configuration/default-retry-configuration';\nimport type { RetryConfiguring } from './configuration/retry-configuring';\n\n/**\n * A class that retries a fetch request.\n */\nexport interface FetchRetrierInterface {\n /**\n * Execute a fetch with retry.\n *\n * @param request RequestInfo\n * @param options Optional RequestInit | FetchOptions\n * @returns Promise<Response>\n */\n fetchRetry(\n request: RequestInfo,\n options?: RequestInit | FetchOptions,\n ): Promise<Response>;\n}\n\n/** @inheritdoc */\nexport class FetchRetrier implements FetchRetrierInterface {\n private analyticsHandler?: AnalyticsHandlerInterface;\n\n private retryConfiguration: RetryConfiguring =\n new DefaultRetryConfiguration();\n\n constructor(options?: {\n analyticsHandler?: AnalyticsHandlerInterface;\n retryConfiguration?: RetryConfiguring;\n }) {\n if (options?.analyticsHandler)\n this.analyticsHandler = options.analyticsHandler;\n if (options?.retryConfiguration)\n this.retryConfiguration = options.retryConfiguration;\n }\n\n /** @inheritdoc */\n public async fetchRetry(\n request: RequestInfo,\n options?: RequestInit | FetchOptions,\n ): Promise<Response> {\n const fetchOptions = legacyArgsAsFetchOptions(options);\n return await this.fetchRetryWithOptions(request, 0, fetchOptions);\n }\n\n private async fetchRetryWithOptions(\n request: RequestInfo,\n retryNumber: number,\n options?: FetchOptions,\n ): Promise<Response> {\n const urlString = typeof request === 'string' ? request : request.url;\n\n try {\n const response = await fetch(request, options?.requestInit);\n if (response.ok) return response;\n\n if (response.status >= 400 && response.status < 500) {\n this.log4xxResponse(response);\n }\n\n const retryConfig = options?.retryConfig ?? this.retryConfiguration;\n if (retryConfig.shouldRetry(response, retryNumber)) {\n const delay = retryConfig.retryDelay(retryNumber);\n await promisedSleep(delay);\n this.logRetryEvent(\n urlString,\n retryNumber,\n response.statusText,\n response.status,\n );\n return this.fetchRetryWithOptions(request, retryNumber + 1, options);\n }\n this.logFailureEvent(urlString, response.status);\n return response;\n } catch (error) {\n // if a content blocker is detected, log it and don't retry\n if (this.isContentBlockerError(error)) {\n this.logContentBlockingEvent(urlString, error);\n throw error;\n }\n\n const retryConfig = options?.retryConfig ?? this.retryConfiguration;\n if (retryConfig.shouldRetry(null, retryNumber)) {\n const delay = retryConfig.retryDelay(retryNumber);\n await promisedSleep(delay);\n this.logRetryEvent(urlString, retryNumber, error, error);\n return this.fetchRetryWithOptions(request, retryNumber + 1, options);\n }\n this.logFailureEvent(urlString, error);\n throw error;\n }\n }\n\n private isContentBlockerError(error: unknown): boolean {\n // all of the content blocker errors are `TypeError`\n if (!(error instanceof TypeError)) return false;\n const message = error.message.toLowerCase();\n return message.includes('content blocker');\n }\n\n private readonly eventCategory = 'offshootFetchRetry';\n\n private logRetryEvent(\n urlString: string,\n retryNumber: number,\n status: unknown,\n code: unknown,\n ) {\n this.analyticsHandler?.sendEvent({\n category: this.eventCategory,\n action: 'retryingFetch',\n label: `retryNumber: ${retryNumber}, code: ${code}, status: ${status}, url: ${urlString}`,\n });\n }\n\n private logFailureEvent(urlString: string, error: unknown) {\n this.analyticsHandler?.sendEvent({\n category: this.eventCategory,\n action: 'fetchFailed',\n label: `error: ${error}, url: ${urlString}`,\n });\n }\n\n private log4xxResponse(response: Response) {\n const status = response.status;\n\n this.analyticsHandler?.sendEvent({\n category: this.eventCategory,\n action: `status4xxResponse`,\n label: `http status ${status}, url: ${response.url}`,\n });\n }\n\n private logContentBlockingEvent(urlString: string, error: unknown) {\n this.analyticsHandler?.sendEvent({\n category: this.eventCategory,\n action: 'contentBlockerDetectedNotRetrying',\n label: `error: ${error}, url: ${urlString}`,\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const legacyArgsAsFetchOptions = (options) => {
|
|
2
|
+
if (!options)
|
|
3
|
+
return undefined;
|
|
4
|
+
// if options is already FetchOptions, return it
|
|
5
|
+
if ('requestInit' in options || 'retryConfig' in options) {
|
|
6
|
+
return options;
|
|
7
|
+
}
|
|
8
|
+
// otherwise, it's RequestInit
|
|
9
|
+
return { requestInit: options };
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=legacy-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-args.js","sourceRoot":"","sources":["../../../src/fetch-retry/legacy-args.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,OAAoC,EACV,EAAE;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,gDAAgD;IAChD,IAAI,aAAa,IAAI,OAAO,IAAI,aAAa,IAAI,OAAO,EAAE,CAAC;QACzD,OAAO,OAAuB,CAAC;IACjC,CAAC;IACD,8BAA8B;IAC9B,OAAO,EAAE,WAAW,EAAE,OAAsB,EAAE,CAAC;AACjD,CAAC,CAAC","sourcesContent":["import type { FetchOptions } from '../fetch-options';\n\nexport const legacyArgsAsFetchOptions = (\n options?: RequestInit | FetchOptions,\n): FetchOptions | undefined => {\n if (!options) return undefined;\n // if options is already FetchOptions, return it\n if ('requestInit' in options || 'retryConfig' in options) {\n return options as FetchOptions;\n }\n // otherwise, it's RequestInit\n return { requestInit: options as RequestInit };\n};\n"]}
|