@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
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import type { AnalyticsHandlerInterface } from '@internetarchive/analytics-manager';
|
|
2
|
-
import { AnalyticsHandler } from '@internetarchive/analytics-manager';
|
|
3
|
-
import { promisedSleep } from './promised-sleep';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A class that retries a fetch request.
|
|
7
|
-
*/
|
|
8
|
-
export interface FetchRetrierInterface {
|
|
9
|
-
/**
|
|
10
|
-
* Execute a fetch with retry.
|
|
11
|
-
*
|
|
12
|
-
* @param requestInfo RequestInfo
|
|
13
|
-
* @param init Optional RequestInit
|
|
14
|
-
* @param retries Optional number of retries to attempt
|
|
15
|
-
* @returns Promise<Response>
|
|
16
|
-
*/
|
|
17
|
-
fetchRetry(
|
|
18
|
-
requestInfo: RequestInfo,
|
|
19
|
-
init?: RequestInit,
|
|
20
|
-
retries?: number,
|
|
21
|
-
): Promise<Response>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** @inheritdoc */
|
|
25
|
-
export class FetchRetrier implements FetchRetrierInterface {
|
|
26
|
-
private analyticsHandler = new AnalyticsHandler({ enableAnalytics: true });
|
|
27
|
-
private sleep = promisedSleep; // default sleep function
|
|
28
|
-
|
|
29
|
-
private retryCount = 2;
|
|
30
|
-
|
|
31
|
-
private retryDelay = 1000;
|
|
32
|
-
|
|
33
|
-
constructor(options?: {
|
|
34
|
-
analyticsHandler?: AnalyticsHandlerInterface;
|
|
35
|
-
retryCount?: number;
|
|
36
|
-
retryDelay?: number;
|
|
37
|
-
sleepFn?: (ms: number) => Promise<void>; // <-- add this!
|
|
38
|
-
}) {
|
|
39
|
-
if (options?.analyticsHandler)
|
|
40
|
-
this.analyticsHandler = options.analyticsHandler;
|
|
41
|
-
if (options?.retryCount) this.retryCount = options.retryCount;
|
|
42
|
-
if (options?.retryDelay) this.retryDelay = options.retryDelay;
|
|
43
|
-
if (options?.sleepFn) this.sleep = options.sleepFn; // override if provided
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** @inheritdoc */
|
|
47
|
-
public async fetchRetry(
|
|
48
|
-
requestInfo: RequestInfo,
|
|
49
|
-
init?: RequestInit,
|
|
50
|
-
retries: number = this.retryCount,
|
|
51
|
-
): Promise<Response> {
|
|
52
|
-
const urlString =
|
|
53
|
-
typeof requestInfo === 'string' ? requestInfo : requestInfo.url;
|
|
54
|
-
const retryNumber = this.retryCount - retries + 1;
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(requestInfo, init);
|
|
58
|
-
if (response.ok) return response;
|
|
59
|
-
// don't retry on a 404 since this will never succeed
|
|
60
|
-
if (response.status === 404) {
|
|
61
|
-
this.log404Event(urlString);
|
|
62
|
-
return response;
|
|
63
|
-
}
|
|
64
|
-
if (retries > 0) {
|
|
65
|
-
await this.sleep(this.retryDelay);
|
|
66
|
-
this.logRetryEvent(
|
|
67
|
-
urlString,
|
|
68
|
-
retryNumber,
|
|
69
|
-
response.statusText,
|
|
70
|
-
response.status,
|
|
71
|
-
);
|
|
72
|
-
return this.fetchRetry(requestInfo, init, retries - 1);
|
|
73
|
-
}
|
|
74
|
-
this.logFailureEvent(urlString, response.status);
|
|
75
|
-
return response;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
// if a content blocker is detected, log it and don't retry
|
|
78
|
-
if (this.isContentBlockerError(error)) {
|
|
79
|
-
this.logContentBlockingEvent(urlString, error);
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (retries > 0) {
|
|
84
|
-
await this.sleep(this.retryDelay);
|
|
85
|
-
// intentionally duplicating the error message here since we want something
|
|
86
|
-
// in the status code even though we won't have an actual code
|
|
87
|
-
this.logRetryEvent(urlString, retryNumber, error, error);
|
|
88
|
-
return this.fetchRetry(requestInfo, init, retries - 1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
this.logFailureEvent(urlString, error);
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private isContentBlockerError(error: unknown): boolean {
|
|
97
|
-
// all of the content blocker errors are `TypeError`
|
|
98
|
-
if (!(error instanceof TypeError)) return false;
|
|
99
|
-
const message = error.message.toLowerCase();
|
|
100
|
-
return message.includes('content blocker');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private readonly eventCategory = 'offshootFetchRetry';
|
|
104
|
-
|
|
105
|
-
private logRetryEvent(
|
|
106
|
-
urlString: string,
|
|
107
|
-
retryNumber: number,
|
|
108
|
-
status: unknown,
|
|
109
|
-
code: unknown,
|
|
110
|
-
) {
|
|
111
|
-
this.analyticsHandler.sendEvent({
|
|
112
|
-
category: this.eventCategory,
|
|
113
|
-
action: 'retryingFetch',
|
|
114
|
-
label: `retryNumber: ${retryNumber} / ${this.retryCount}, code: ${code}, status: ${status}, url: ${urlString}`,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private logFailureEvent(urlString: string, error: unknown) {
|
|
119
|
-
this.analyticsHandler.sendEvent({
|
|
120
|
-
category: this.eventCategory,
|
|
121
|
-
action: 'fetchFailed',
|
|
122
|
-
label: `error: ${error}, url: ${urlString}`,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private log404Event(urlString: string) {
|
|
127
|
-
this.analyticsHandler.sendEvent({
|
|
128
|
-
category: this.eventCategory,
|
|
129
|
-
action: 'status404NotRetrying',
|
|
130
|
-
label: `url: ${urlString}`,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private logContentBlockingEvent(urlString: string, error: unknown) {
|
|
135
|
-
this.analyticsHandler.sendEvent({
|
|
136
|
-
category: this.eventCategory,
|
|
137
|
-
action: 'contentBlockerDetectedNotRetrying',
|
|
138
|
-
label: `error: ${error}, url: ${urlString}`,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { expect } from '@open-wc/testing';
|
|
2
|
-
import { IaFetchHandler } from '../src/ia-fetch-handler';
|
|
3
|
-
import { FetchRetrierInterface } from '../src/utils/fetch-retrier';
|
|
4
|
-
|
|
5
|
-
class MockFetchRetrier implements FetchRetrierInterface {
|
|
6
|
-
requestInfo?: RequestInfo;
|
|
7
|
-
init?: RequestInit;
|
|
8
|
-
retries?: number;
|
|
9
|
-
|
|
10
|
-
async fetchRetry(
|
|
11
|
-
requestInfo: RequestInfo,
|
|
12
|
-
init?: RequestInit,
|
|
13
|
-
retries?: number,
|
|
14
|
-
): Promise<Response> {
|
|
15
|
-
this.init = init;
|
|
16
|
-
this.requestInfo = requestInfo;
|
|
17
|
-
this.retries = retries;
|
|
18
|
-
return new Response(JSON.stringify({ boop: 'snoot' }), { status: 200 });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('Fetch Handler', () => {
|
|
23
|
-
describe('fetch', () => {
|
|
24
|
-
it('adds reCache=1 if it is in the current url', async () => {
|
|
25
|
-
const fetchRetrier = new MockFetchRetrier();
|
|
26
|
-
const fetchHandler = new IaFetchHandler({
|
|
27
|
-
fetchRetrier: fetchRetrier,
|
|
28
|
-
searchParams: '?reCache=1',
|
|
29
|
-
});
|
|
30
|
-
await fetchHandler.fetch('https://foo.org/api/v1/snoot');
|
|
31
|
-
expect(fetchRetrier.requestInfo).to.equal(
|
|
32
|
-
'https://foo.org/api/v1/snoot?reCache=1',
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('fetchIAApiResponse', () => {
|
|
38
|
-
it('prepends the IA basehost to the url when making a request', async () => {
|
|
39
|
-
const endpoint = '/foo/service/endpoint.php';
|
|
40
|
-
const fetchRetrier = new MockFetchRetrier();
|
|
41
|
-
const fetchHandler = new IaFetchHandler({
|
|
42
|
-
iaApiBaseUrl: 'www.example.com',
|
|
43
|
-
fetchRetrier: fetchRetrier,
|
|
44
|
-
});
|
|
45
|
-
await fetchHandler.fetchIAApiResponse(endpoint);
|
|
46
|
-
expect(fetchRetrier.requestInfo).to.equal(
|
|
47
|
-
'www.example.com/foo/service/endpoint.php',
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('fetchApiResponse', () => {
|
|
53
|
-
it('adds credentials: include if requested', async () => {
|
|
54
|
-
const endpoint = '/foo/service/endpoint.php';
|
|
55
|
-
const fetchRetrier = new MockFetchRetrier();
|
|
56
|
-
const fetchHandler = new IaFetchHandler({
|
|
57
|
-
iaApiBaseUrl: 'www.example.com',
|
|
58
|
-
fetchRetrier: fetchRetrier,
|
|
59
|
-
});
|
|
60
|
-
await fetchHandler.fetchApiResponse(endpoint, {
|
|
61
|
-
includeCredentials: true,
|
|
62
|
-
});
|
|
63
|
-
expect(fetchRetrier.init).to.deep.equal({ credentials: 'include' });
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
File without changes
|