@internetarchive/fetch-handler 1.0.1 → 1.1.0-webdev-7731.1

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.
Files changed (87) hide show
  1. package/.github/workflows/ci.yml +5 -0
  2. package/.github/workflows/gh-pages-main.yml +4 -0
  3. package/.github/workflows/pr-preview.yml +4 -0
  4. package/README.md +3 -3
  5. package/demo/app-root.ts +1 -1
  6. package/dist/demo/app-root.d.ts +1 -1
  7. package/dist/demo/app-root.js +1 -1
  8. package/dist/demo/app-root.js.map +1 -1
  9. package/dist/index.d.ts +6 -1
  10. package/dist/index.js +4 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/src/fetch-handler-interface.d.ts +7 -3
  13. package/dist/src/fetch-handler-interface.js.map +1 -1
  14. package/dist/src/{ia-fetch-handler.d.ts → fetch-handler.d.ts} +7 -2
  15. package/dist/src/{ia-fetch-handler.js → fetch-handler.js} +13 -16
  16. package/dist/src/fetch-handler.js.map +1 -0
  17. package/dist/src/fetch-options.d.ts +5 -0
  18. package/dist/src/fetch-options.js +2 -0
  19. package/dist/src/fetch-options.js.map +1 -0
  20. package/dist/src/fetch-retry/configuration/default-retry-configuration.d.ts +10 -0
  21. package/dist/src/fetch-retry/configuration/default-retry-configuration.js +20 -0
  22. package/dist/src/fetch-retry/configuration/default-retry-configuration.js.map +1 -0
  23. package/dist/src/fetch-retry/configuration/milliseconds.d.ts +1 -0
  24. package/dist/src/fetch-retry/configuration/milliseconds.js +2 -0
  25. package/dist/src/fetch-retry/configuration/milliseconds.js.map +1 -0
  26. package/dist/src/fetch-retry/configuration/no-retry-configuration.d.ts +6 -0
  27. package/dist/src/fetch-retry/configuration/no-retry-configuration.js +9 -0
  28. package/dist/src/fetch-retry/configuration/no-retry-configuration.js.map +1 -0
  29. package/dist/src/fetch-retry/configuration/retry-configuring.d.ts +5 -0
  30. package/dist/src/fetch-retry/configuration/retry-configuring.js +2 -0
  31. package/dist/src/fetch-retry/configuration/retry-configuring.js.map +1 -0
  32. package/dist/src/{utils → fetch-retry}/fetch-retrier.d.ts +11 -13
  33. package/dist/src/fetch-retry/fetch-retrier.js +97 -0
  34. package/dist/src/fetch-retry/fetch-retrier.js.map +1 -0
  35. package/dist/src/fetch-retry/legacy-args.d.ts +2 -0
  36. package/dist/src/fetch-retry/legacy-args.js +11 -0
  37. package/dist/src/fetch-retry/legacy-args.js.map +1 -0
  38. package/dist/test/default-retry-config.test.js +30 -0
  39. package/dist/test/default-retry-config.test.js.map +1 -0
  40. package/dist/test/fetch-handler.test.d.ts +1 -0
  41. package/dist/test/fetch-handler.test.js +87 -0
  42. package/dist/test/fetch-handler.test.js.map +1 -0
  43. package/dist/test/fetch-retrier.test.js +76 -42
  44. package/dist/test/fetch-retrier.test.js.map +1 -1
  45. package/dist/test/legacy-args.test.d.ts +1 -0
  46. package/dist/test/legacy-args.test.js +21 -0
  47. package/dist/test/legacy-args.test.js.map +1 -0
  48. package/dist/test/mocks/mock-fetch-retrier.d.ts +10 -0
  49. package/dist/test/mocks/mock-fetch-retrier.js +11 -0
  50. package/dist/test/mocks/mock-fetch-retrier.js.map +1 -0
  51. package/dist/test/mocks/mock-retry-config.d.ts +7 -0
  52. package/dist/test/mocks/mock-retry-config.js +13 -0
  53. package/dist/test/mocks/mock-retry-config.js.map +1 -0
  54. package/dist/test/no-retry-config.test.d.ts +1 -0
  55. package/dist/test/no-retry-config.test.js +13 -0
  56. package/dist/test/no-retry-config.test.js.map +1 -0
  57. package/dist/test/retrier-legacy-args.test.d.ts +1 -0
  58. package/dist/test/retrier-legacy-args.test.js +27 -0
  59. package/dist/test/retrier-legacy-args.test.js.map +1 -0
  60. package/index.ts +9 -1
  61. package/package.json +5 -5
  62. package/src/fetch-handler-interface.ts +11 -4
  63. package/src/{ia-fetch-handler.ts → fetch-handler.ts} +24 -15
  64. package/src/fetch-options.ts +6 -0
  65. package/src/fetch-retry/configuration/default-retry-configuration.ts +23 -0
  66. package/src/fetch-retry/configuration/milliseconds.ts +1 -0
  67. package/src/fetch-retry/configuration/no-retry-configuration.ts +12 -0
  68. package/src/fetch-retry/configuration/retry-configuring.ts +11 -0
  69. package/src/fetch-retry/fetch-retrier.ts +146 -0
  70. package/src/fetch-retry/legacy-args.ts +13 -0
  71. package/test/default-retry-config.test.ts +34 -0
  72. package/test/fetch-handler.test.ts +99 -0
  73. package/test/fetch-retrier.test.ts +87 -46
  74. package/test/legacy-args.test.ts +24 -0
  75. package/test/mocks/mock-fetch-retrier.ts +22 -0
  76. package/test/mocks/mock-retry-config.ts +19 -0
  77. package/test/no-retry-config.test.ts +14 -0
  78. package/test/retrier-legacy-args.test.ts +28 -0
  79. package/web-test-runner.config.mjs +5 -3
  80. package/dist/src/ia-fetch-handler.js.map +0 -1
  81. package/dist/src/utils/fetch-retrier.js +0 -94
  82. package/dist/src/utils/fetch-retrier.js.map +0 -1
  83. package/dist/test/ia-fetch-handler.test.js +0 -50
  84. package/dist/test/ia-fetch-handler.test.js.map +0 -1
  85. package/src/utils/fetch-retrier.ts +0 -141
  86. package/test/ia-fetch-handler.test.ts +0 -66
  87. /package/dist/test/{ia-fetch-handler.test.d.ts → default-retry-config.test.d.ts} +0 -0
@@ -1,17 +1,16 @@
1
1
  import { expect } from '@open-wc/testing';
2
2
  import sinon from 'sinon';
3
- import { FetchRetrier } from '../src/utils/fetch-retrier';
3
+ import { FetchRetrier } from '../src/fetch-retry/fetch-retrier';
4
4
  import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
5
+ import { MockRetryConfig } from './mocks/mock-retry-config';
5
6
 
6
7
  describe('FetchRetrier', () => {
7
8
  let fetchStub: sinon.SinonStub;
8
- let sleepStub: sinon.SinonStub;
9
9
  let analytics: MockAnalyticsHandler;
10
10
 
11
11
  beforeEach(() => {
12
12
  analytics = new MockAnalyticsHandler();
13
13
  fetchStub = sinon.stub(globalThis, 'fetch');
14
- sleepStub = sinon.stub().resolves(); // stubbed promisedSleep
15
14
  });
16
15
 
17
16
  afterEach(() => {
@@ -22,30 +21,57 @@ describe('FetchRetrier', () => {
22
21
  fetchStub.resolves(new Response('ok', { status: 200 }));
23
22
  const retrier = new FetchRetrier({
24
23
  analyticsHandler: analytics,
25
- sleepFn: sleepStub,
24
+ retryConfiguration: new MockRetryConfig(),
26
25
  });
27
26
 
28
27
  const res = await retrier.fetchRetry('https://foo.org/data');
29
28
 
30
29
  expect(res.status).to.equal(200);
31
30
  expect(fetchStub.callCount).to.equal(1);
32
- expect(sleepStub.callCount).to.equal(0);
33
31
  expect(analytics.events.length).to.equal(0);
34
32
  });
35
33
 
34
+ it('does not retry on 4xx and logs event', async () => {
35
+ fetchStub.resolves(new Response('forbidden', { status: 403 }));
36
+ const retrier = new FetchRetrier({
37
+ analyticsHandler: analytics,
38
+ });
39
+
40
+ const res = await retrier.fetchRetry('https://foo.org/403');
41
+
42
+ expect(res.status).to.equal(403);
43
+ expect(fetchStub.callCount).to.equal(1);
44
+ expect(analytics.events[0].action).to.equal('status4xxResponse');
45
+ });
46
+
36
47
  it('does not retry on 404 and logs event', async () => {
37
48
  fetchStub.resolves(new Response('not found', { status: 404 }));
38
49
  const retrier = new FetchRetrier({
39
50
  analyticsHandler: analytics,
40
- sleepFn: sleepStub,
41
51
  });
42
52
 
43
53
  const res = await retrier.fetchRetry('https://foo.org/404');
44
54
 
45
55
  expect(res.status).to.equal(404);
46
56
  expect(fetchStub.callCount).to.equal(1);
47
- expect(sleepStub.callCount).to.equal(0);
48
- expect(analytics.events[0].action).to.equal('status404NotRetrying');
57
+ expect(analytics.events[0].action).to.equal('status4xxResponse');
58
+ });
59
+
60
+ it('retries on 4xx if shouldRetry is true in ApiRequestInit', async () => {
61
+ fetchStub.onCall(0).resolves(new Response('bad request', { status: 400 }));
62
+ fetchStub.onCall(1).resolves(new Response('ok', { status: 200 }));
63
+
64
+ const retrier = new FetchRetrier({
65
+ analyticsHandler: analytics,
66
+ });
67
+
68
+ const res = await retrier.fetchRetry('https://foo.org/should-retry', {
69
+ retryConfig: new MockRetryConfig(),
70
+ });
71
+
72
+ expect(res.status).to.equal(200);
73
+ expect(fetchStub.callCount).to.equal(2);
74
+ expect(analytics.events.some(e => e.action === 'retryingFetch')).to.be.true;
49
75
  });
50
76
 
51
77
  it('retries on 500 and logs retry/failure events', async () => {
@@ -55,16 +81,13 @@ describe('FetchRetrier', () => {
55
81
 
56
82
  const retrier = new FetchRetrier({
57
83
  analyticsHandler: analytics,
58
- retryCount: 2,
59
- retryDelay: 1,
60
- sleepFn: sleepStub,
84
+ retryConfiguration: new MockRetryConfig(),
61
85
  });
62
86
 
63
87
  const res = await retrier.fetchRetry('https://foo.org/fail');
64
88
 
65
89
  expect(res.status).to.equal(500);
66
90
  expect(fetchStub.callCount).to.equal(3);
67
- expect(sleepStub.callCount).to.equal(2);
68
91
  expect(analytics.events.some(e => e.action === 'retryingFetch')).to.be.true;
69
92
  expect(analytics.events.some(e => e.action === 'fetchFailed')).to.be.true;
70
93
  });
@@ -75,16 +98,13 @@ describe('FetchRetrier', () => {
75
98
 
76
99
  const retrier = new FetchRetrier({
77
100
  analyticsHandler: analytics,
78
- retryCount: 1,
79
- retryDelay: 1,
80
- sleepFn: sleepStub,
101
+ retryConfiguration: new MockRetryConfig(),
81
102
  });
82
103
 
83
104
  const res = await retrier.fetchRetry('https://foo.org/retry');
84
105
 
85
106
  expect(res.status).to.equal(200);
86
107
  expect(fetchStub.callCount).to.equal(2);
87
- expect(sleepStub.calledOnce).to.be.true;
88
108
  expect(analytics.events.some(e => e.action === 'retryingFetch')).to.be.true;
89
109
  });
90
110
 
@@ -93,20 +113,17 @@ describe('FetchRetrier', () => {
93
113
 
94
114
  const retrier = new FetchRetrier({
95
115
  analyticsHandler: analytics,
96
- retryCount: 1,
97
- retryDelay: 1,
98
- sleepFn: sleepStub,
116
+ retryConfiguration: new MockRetryConfig(),
99
117
  });
100
118
 
101
119
  try {
102
120
  await retrier.fetchRetry('https://foo.org/networkfail');
103
121
  throw new Error('Should have thrown');
104
- } catch (err: any) {
105
- expect(err.message).to.equal('Boom');
122
+ } catch (err: unknown) {
123
+ expect((err as Error).message).to.equal('Boom');
106
124
  }
107
125
 
108
- expect(fetchStub.callCount).to.equal(2);
109
- expect(sleepStub.callCount).to.equal(1);
126
+ expect(fetchStub.callCount).to.equal(3);
110
127
  expect(analytics.events.some(e => e.action === 'fetchFailed')).to.be.true;
111
128
  });
112
129
 
@@ -116,19 +133,16 @@ describe('FetchRetrier', () => {
116
133
 
117
134
  const retrier = new FetchRetrier({
118
135
  analyticsHandler: analytics,
119
- retryCount: 2,
120
- sleepFn: sleepStub,
121
136
  });
122
137
 
123
138
  try {
124
139
  await retrier.fetchRetry('https://foo.org/blocked');
125
140
  throw new Error('Should have thrown');
126
- } catch (err: any) {
141
+ } catch (err: unknown) {
127
142
  expect(err).to.equal(blockerError);
128
143
  }
129
144
 
130
145
  expect(fetchStub.callCount).to.equal(1);
131
- expect(sleepStub.callCount).to.equal(0);
132
146
  expect(
133
147
  analytics.events.some(
134
148
  e => e.action === 'contentBlockerDetectedNotRetrying',
@@ -136,38 +150,65 @@ describe('FetchRetrier', () => {
136
150
  ).to.be.true;
137
151
  });
138
152
 
139
- it('calls sleepFn on retry with correct delay', async () => {
140
- fetchStub.onCall(0).resolves(new Response(null, { status: 500 }));
141
- fetchStub.onCall(1).resolves(new Response('ok', { status: 200 }));
153
+ it('sleeps for each retry attempt', async () => {
154
+ const retryConfig = new MockRetryConfig();
155
+ const retryDelaySpy = sinon.spy(retryConfig, 'retryDelay');
156
+ fetchStub.resolves(new Response(null, { status: 500 }));
142
157
 
143
158
  const retrier = new FetchRetrier({
144
159
  analyticsHandler: analytics,
145
- retryCount: 2,
146
- retryDelay: 1234,
147
- sleepFn: sleepStub,
160
+ retryConfiguration: retryConfig,
148
161
  });
149
162
 
150
- const res = await retrier.fetchRetry('https://foo.org/retry-with-sleep');
163
+ const res = await retrier.fetchRetry('https://foo.org/retry-fail');
151
164
 
152
- expect(res.status).to.equal(200);
153
- expect(sleepStub.calledOnce).to.be.true;
154
- expect(sleepStub.calledWith(1234)).to.be.true;
165
+ expect(res.status).to.equal(500);
166
+ expect(fetchStub.callCount).to.equal(3);
167
+ expect(retryDelaySpy.callCount).to.equal(2);
155
168
  });
156
169
 
157
- it('sleeps for each retry attempt', async () => {
158
- fetchStub.resolves(new Response(null, { status: 500 }));
170
+ it('does not retry 5xx when NoRetryConfiguration is used', async () => {
171
+ fetchStub.resolves(new Response('server error', { status: 500 }));
172
+ const retrier = new FetchRetrier({
173
+ analyticsHandler: analytics,
174
+ retryConfiguration: new (class {
175
+ shouldRetry() {
176
+ return false;
177
+ }
178
+ retryDelay() {
179
+ return 0;
180
+ }
181
+ })(),
182
+ });
159
183
 
184
+ const res = await retrier.fetchRetry('https://foo.org/no-retry-500');
185
+ expect(res.status).to.equal(500);
186
+ expect(fetchStub.callCount).to.equal(1);
187
+ expect(analytics.events.some(e => e.action === 'fetchFailed')).to.be.true;
188
+ });
189
+
190
+ it('does not retry on error when configuration disables retries', async () => {
191
+ fetchStub.rejects(new Error('Immediate failure'));
160
192
  const retrier = new FetchRetrier({
161
193
  analyticsHandler: analytics,
162
- retryCount: 2,
163
- retryDelay: 300,
164
- sleepFn: sleepStub,
194
+ retryConfiguration: new (class {
195
+ shouldRetry() {
196
+ return false;
197
+ }
198
+ retryDelay() {
199
+ return 0;
200
+ }
201
+ })(),
165
202
  });
166
203
 
167
- const res = await retrier.fetchRetry('https://foo.org/retry-fail');
204
+ try {
205
+ await retrier.fetchRetry('https://foo.org/no-retry-error');
206
+ throw new Error('Should have thrown');
207
+ } catch (err: unknown) {
208
+ expect((err as Error).message).to.equal('Immediate failure');
209
+ }
168
210
 
169
- expect(res.status).to.equal(500);
170
- expect(sleepStub.callCount).to.equal(2);
171
- expect(sleepStub.alwaysCalledWith(300)).to.be.true;
211
+ expect(fetchStub.callCount).to.equal(1);
212
+ expect(analytics.events.some(e => e.action === 'fetchFailed')).to.be.true;
172
213
  });
173
214
  });
@@ -0,0 +1,24 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { legacyArgsAsFetchOptions } from '../src/fetch-retry/legacy-args';
3
+ import type { FetchOptions } from '../src/fetch-options';
4
+
5
+ describe('legacyArgsAsFetchOptions', () => {
6
+ it('wraps RequestInit into FetchOptions', () => {
7
+ const init: RequestInit = { method: 'PUT' };
8
+ const fo = legacyArgsAsFetchOptions(init);
9
+ expect(fo).to.deep.equal({ requestInit: init });
10
+ });
11
+
12
+ it('returns FetchOptions unchanged', () => {
13
+ const options: FetchOptions = {
14
+ requestInit: { headers: { foo: 'bar' } },
15
+ };
16
+ const fo = legacyArgsAsFetchOptions(options);
17
+ expect(fo).to.equal(options);
18
+ });
19
+
20
+ it('returns undefined when no options provided', () => {
21
+ const fo = legacyArgsAsFetchOptions(undefined);
22
+ expect(fo).to.equal(undefined);
23
+ });
24
+ });
@@ -0,0 +1,22 @@
1
+ import type { FetchOptions } from '../../src/fetch-options';
2
+ import type { FetchRetrierInterface } from '../../src/fetch-retry/fetch-retrier';
3
+ import type { RetryConfiguring } from '../../src/fetch-retry/configuration/retry-configuring';
4
+ import { legacyArgsAsFetchOptions } from '../../src/fetch-retry/legacy-args';
5
+
6
+ export class MockFetchRetrier implements FetchRetrierInterface {
7
+ requestInfo?: RequestInfo;
8
+ init?: RequestInit;
9
+ retries?: number;
10
+ retryConfig?: RetryConfiguring;
11
+
12
+ async fetchRetry(
13
+ request: RequestInfo,
14
+ options?: RequestInit | FetchOptions,
15
+ ): Promise<Response> {
16
+ const fetchOptions = legacyArgsAsFetchOptions(options);
17
+ this.init = fetchOptions?.requestInit;
18
+ this.retryConfig = fetchOptions?.retryConfig;
19
+ this.requestInfo = request;
20
+ return new Response(JSON.stringify({ boop: 'snoot' }), { status: 200 });
21
+ }
22
+ }
@@ -0,0 +1,19 @@
1
+ import type { RetryConfiguring } from '../../src/fetch-retry/configuration/retry-configuring';
2
+
3
+ export class MockRetryConfig implements RetryConfiguring {
4
+ mockRetryCount: number = 2;
5
+
6
+ mockRetryDelay: number = 0;
7
+
8
+ shouldRetry(
9
+ response: Response | null,
10
+ retryNumber: number,
11
+ error?: unknown,
12
+ ): boolean {
13
+ return retryNumber < this.mockRetryCount;
14
+ }
15
+
16
+ retryDelay(): number {
17
+ return this.mockRetryDelay;
18
+ }
19
+ }
@@ -0,0 +1,14 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { NoRetryConfiguration } from '../src/fetch-retry/configuration/no-retry-configuration';
3
+
4
+ describe('NoRetryConfiguration', () => {
5
+ it('should not retry', async () => {
6
+ const config = new NoRetryConfiguration();
7
+ expect(config.shouldRetry()).to.be.false;
8
+ });
9
+
10
+ it('has no delay', async () => {
11
+ const config = new NoRetryConfiguration();
12
+ expect(config.retryDelay()).to.equal(0);
13
+ });
14
+ });
@@ -0,0 +1,28 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { legacyArgsAsFetchOptions } from '../src/fetch-retry/legacy-args';
3
+ import { MockRetryConfig } from './mocks/mock-retry-config';
4
+
5
+ describe('FetchRetrier Legacy Args', () => {
6
+ it('can convert RequestInit to FetchOptions', async () => {
7
+ const options = legacyArgsAsFetchOptions({ method: 'GET' });
8
+ expect(options).to.deep.equal({
9
+ requestInit: {
10
+ method: 'GET',
11
+ },
12
+ });
13
+ });
14
+
15
+ it('leaves FetchOptions unchanged', async () => {
16
+ const retryConfig = new MockRetryConfig();
17
+ const options = legacyArgsAsFetchOptions({
18
+ requestInit: { method: 'POST' },
19
+ retryConfig: retryConfig,
20
+ });
21
+ expect(options).to.deep.equal({
22
+ requestInit: {
23
+ method: 'POST',
24
+ },
25
+ retryConfig: retryConfig,
26
+ });
27
+ });
28
+ });
@@ -7,9 +7,11 @@ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
7
7
  files: 'dist/test/**/*.test.js',
8
8
 
9
9
  /** Resolve bare module imports */
10
- nodeResolve: {
11
- exportConditions: ['browser', 'development'],
12
- },
10
+ nodeResolve: { exportConditions: ['browser', 'development'] },
11
+
12
+ coverageConfig: { include: ['**/src/**'] },
13
+
14
+ testsFinishTimeout: 5000,
13
15
 
14
16
  /** Filter out lit dev mode logs */
15
17
  filterBrowserLogs(log) {
@@ -1 +0,0 @@
1
- {"version":3,"file":"ia-fetch-handler.js","sourceRoot":"","sources":["../../src/ia-fetch-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { FetchRetrier, FetchRetrierInterface } from './utils/fetch-retrier';\nimport type { FetchHandlerInterface } from './fetch-handler-interface';\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 }) {\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 },\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 },\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, requestInit);\n const json = await response.json();\n return json as T;\n }\n\n /** @inheritdoc */\n async fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {\n let finalInput = input;\n const urlParams = new URLSearchParams(this.searchParams);\n if (urlParams.get('reCache') === '1') {\n finalInput = this.addSearchParams(input, { reCache: '1' });\n }\n return this.fetchRetrier.fetchRetry(finalInput, init);\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 input: RequestInfo,\n params: Record<string, string>,\n ): RequestInfo {\n const urlString = typeof input === 'string' ? input : input.url;\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 if (typeof input === 'string') {\n return url.href;\n } else {\n const newRequest = new Request(url.href, input);\n return newRequest;\n }\n }\n}\n"]}
@@ -1,94 +0,0 @@
1
- import { AnalyticsHandler } from '@internetarchive/analytics-manager';
2
- import { promisedSleep } from './promised-sleep';
3
- /** @inheritdoc */
4
- export class FetchRetrier {
5
- constructor(options) {
6
- this.analyticsHandler = new AnalyticsHandler({ enableAnalytics: true });
7
- this.sleep = promisedSleep; // default sleep function
8
- this.retryCount = 2;
9
- this.retryDelay = 1000;
10
- this.eventCategory = 'offshootFetchRetry';
11
- if (options === null || options === void 0 ? void 0 : options.analyticsHandler)
12
- this.analyticsHandler = options.analyticsHandler;
13
- if (options === null || options === void 0 ? void 0 : options.retryCount)
14
- this.retryCount = options.retryCount;
15
- if (options === null || options === void 0 ? void 0 : options.retryDelay)
16
- this.retryDelay = options.retryDelay;
17
- if (options === null || options === void 0 ? void 0 : options.sleepFn)
18
- this.sleep = options.sleepFn; // override if provided
19
- }
20
- /** @inheritdoc */
21
- async fetchRetry(requestInfo, init, retries = this.retryCount) {
22
- const urlString = typeof requestInfo === 'string' ? requestInfo : requestInfo.url;
23
- const retryNumber = this.retryCount - retries + 1;
24
- try {
25
- const response = await fetch(requestInfo, init);
26
- if (response.ok)
27
- return response;
28
- // don't retry on a 404 since this will never succeed
29
- if (response.status === 404) {
30
- this.log404Event(urlString);
31
- return response;
32
- }
33
- if (retries > 0) {
34
- await this.sleep(this.retryDelay);
35
- this.logRetryEvent(urlString, retryNumber, response.statusText, response.status);
36
- return this.fetchRetry(requestInfo, init, retries - 1);
37
- }
38
- this.logFailureEvent(urlString, response.status);
39
- return response;
40
- }
41
- catch (error) {
42
- // if a content blocker is detected, log it and don't retry
43
- if (this.isContentBlockerError(error)) {
44
- this.logContentBlockingEvent(urlString, error);
45
- throw error;
46
- }
47
- if (retries > 0) {
48
- await this.sleep(this.retryDelay);
49
- // intentionally duplicating the error message here since we want something
50
- // in the status code even though we won't have an actual code
51
- this.logRetryEvent(urlString, retryNumber, error, error);
52
- return this.fetchRetry(requestInfo, init, retries - 1);
53
- }
54
- this.logFailureEvent(urlString, error);
55
- throw error;
56
- }
57
- }
58
- isContentBlockerError(error) {
59
- // all of the content blocker errors are `TypeError`
60
- if (!(error instanceof TypeError))
61
- return false;
62
- const message = error.message.toLowerCase();
63
- return message.includes('content blocker');
64
- }
65
- logRetryEvent(urlString, retryNumber, status, code) {
66
- this.analyticsHandler.sendEvent({
67
- category: this.eventCategory,
68
- action: 'retryingFetch',
69
- label: `retryNumber: ${retryNumber} / ${this.retryCount}, code: ${code}, status: ${status}, url: ${urlString}`,
70
- });
71
- }
72
- logFailureEvent(urlString, error) {
73
- this.analyticsHandler.sendEvent({
74
- category: this.eventCategory,
75
- action: 'fetchFailed',
76
- label: `error: ${error}, url: ${urlString}`,
77
- });
78
- }
79
- log404Event(urlString) {
80
- this.analyticsHandler.sendEvent({
81
- category: this.eventCategory,
82
- action: 'status404NotRetrying',
83
- label: `url: ${urlString}`,
84
- });
85
- }
86
- logContentBlockingEvent(urlString, error) {
87
- this.analyticsHandler.sendEvent({
88
- category: this.eventCategory,
89
- action: 'contentBlockerDetectedNotRetrying',
90
- label: `error: ${error}, url: ${urlString}`,
91
- });
92
- }
93
- }
94
- //# sourceMappingURL=fetch-retrier.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetch-retrier.js","sourceRoot":"","sources":["../../../src/utils/fetch-retrier.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnalyticsHandlerInterface } from '@internetarchive/analytics-manager';\nimport { AnalyticsHandler } from '@internetarchive/analytics-manager';\nimport { promisedSleep } from './promised-sleep';\n\n/**\n * A class that retries a fetch request.\n */\nexport interface FetchRetrierInterface {\n /**\n * Execute a fetch with retry.\n *\n * @param requestInfo RequestInfo\n * @param init Optional RequestInit\n * @param retries Optional number of retries to attempt\n * @returns Promise<Response>\n */\n fetchRetry(\n requestInfo: RequestInfo,\n init?: RequestInit,\n retries?: number,\n ): Promise<Response>;\n}\n\n/** @inheritdoc */\nexport class FetchRetrier implements FetchRetrierInterface {\n private analyticsHandler = new AnalyticsHandler({ enableAnalytics: true });\n private sleep = promisedSleep; // default sleep function\n\n private retryCount = 2;\n\n private retryDelay = 1000;\n\n constructor(options?: {\n analyticsHandler?: AnalyticsHandlerInterface;\n retryCount?: number;\n retryDelay?: number;\n sleepFn?: (ms: number) => Promise<void>; // <-- add this!\n }) {\n if (options?.analyticsHandler)\n this.analyticsHandler = options.analyticsHandler;\n if (options?.retryCount) this.retryCount = options.retryCount;\n if (options?.retryDelay) this.retryDelay = options.retryDelay;\n if (options?.sleepFn) this.sleep = options.sleepFn; // override if provided\n }\n\n /** @inheritdoc */\n public async fetchRetry(\n requestInfo: RequestInfo,\n init?: RequestInit,\n retries: number = this.retryCount,\n ): Promise<Response> {\n const urlString =\n typeof requestInfo === 'string' ? requestInfo : requestInfo.url;\n const retryNumber = this.retryCount - retries + 1;\n\n try {\n const response = await fetch(requestInfo, init);\n if (response.ok) return response;\n // don't retry on a 404 since this will never succeed\n if (response.status === 404) {\n this.log404Event(urlString);\n return response;\n }\n if (retries > 0) {\n await this.sleep(this.retryDelay);\n this.logRetryEvent(\n urlString,\n retryNumber,\n response.statusText,\n response.status,\n );\n return this.fetchRetry(requestInfo, init, retries - 1);\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 if (retries > 0) {\n await this.sleep(this.retryDelay);\n // intentionally duplicating the error message here since we want something\n // in the status code even though we won't have an actual code\n this.logRetryEvent(urlString, retryNumber, error, error);\n return this.fetchRetry(requestInfo, init, retries - 1);\n }\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} / ${this.retryCount}, 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 log404Event(urlString: string) {\n this.analyticsHandler.sendEvent({\n category: this.eventCategory,\n action: 'status404NotRetrying',\n label: `url: ${urlString}`,\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"]}
@@ -1,50 +0,0 @@
1
- import { expect } from '@open-wc/testing';
2
- import { IaFetchHandler } from '../src/ia-fetch-handler';
3
- class MockFetchRetrier {
4
- async fetchRetry(requestInfo, init, retries) {
5
- this.init = init;
6
- this.requestInfo = requestInfo;
7
- this.retries = retries;
8
- return new Response(JSON.stringify({ boop: 'snoot' }), { status: 200 });
9
- }
10
- }
11
- describe('Fetch Handler', () => {
12
- describe('fetch', () => {
13
- it('adds reCache=1 if it is in the current url', async () => {
14
- const fetchRetrier = new MockFetchRetrier();
15
- const fetchHandler = new IaFetchHandler({
16
- fetchRetrier: fetchRetrier,
17
- searchParams: '?reCache=1',
18
- });
19
- await fetchHandler.fetch('https://foo.org/api/v1/snoot');
20
- expect(fetchRetrier.requestInfo).to.equal('https://foo.org/api/v1/snoot?reCache=1');
21
- });
22
- });
23
- describe('fetchIAApiResponse', () => {
24
- it('prepends the IA basehost to the url when making a request', async () => {
25
- const endpoint = '/foo/service/endpoint.php';
26
- const fetchRetrier = new MockFetchRetrier();
27
- const fetchHandler = new IaFetchHandler({
28
- iaApiBaseUrl: 'www.example.com',
29
- fetchRetrier: fetchRetrier,
30
- });
31
- await fetchHandler.fetchIAApiResponse(endpoint);
32
- expect(fetchRetrier.requestInfo).to.equal('www.example.com/foo/service/endpoint.php');
33
- });
34
- });
35
- describe('fetchApiResponse', () => {
36
- it('adds credentials: include if requested', async () => {
37
- const endpoint = '/foo/service/endpoint.php';
38
- const fetchRetrier = new MockFetchRetrier();
39
- const fetchHandler = new IaFetchHandler({
40
- iaApiBaseUrl: 'www.example.com',
41
- fetchRetrier: fetchRetrier,
42
- });
43
- await fetchHandler.fetchApiResponse(endpoint, {
44
- includeCredentials: true,
45
- });
46
- expect(fetchRetrier.init).to.deep.equal({ credentials: 'include' });
47
- });
48
- });
49
- });
50
- //# sourceMappingURL=ia-fetch-handler.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ia-fetch-handler.test.js","sourceRoot":"","sources":["../../test/ia-fetch-handler.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,MAAM,gBAAgB;IAKpB,KAAK,CAAC,UAAU,CACd,WAAwB,EACxB,IAAkB,EAClB,OAAgB;QAEhB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1E,CAAC;CACF;AAED,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,cAAc,CAAC;gBACtC,YAAY,EAAE,YAAY;gBAC1B,YAAY,EAAE,YAAY;aAC3B,CAAC,CAAC;YACH,MAAM,YAAY,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;YACzD,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,KAAK,CACvC,wCAAwC,CACzC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YACzE,MAAM,QAAQ,GAAG,2BAA2B,CAAC;YAC7C,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,cAAc,CAAC;gBACtC,YAAY,EAAE,iBAAiB;gBAC/B,YAAY,EAAE,YAAY;aAC3B,CAAC,CAAC;YACH,MAAM,YAAY,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,KAAK,CACvC,0CAA0C,CAC3C,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,QAAQ,GAAG,2BAA2B,CAAC;YAC7C,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,cAAc,CAAC;gBACtC,YAAY,EAAE,iBAAiB;gBAC/B,YAAY,EAAE,YAAY;aAC3B,CAAC,CAAC;YACH,MAAM,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE;gBAC5C,kBAAkB,EAAE,IAAI;aACzB,CAAC,CAAC;YACH,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { IaFetchHandler } from '../src/ia-fetch-handler';\nimport { FetchRetrierInterface } from '../src/utils/fetch-retrier';\n\nclass MockFetchRetrier implements FetchRetrierInterface {\n requestInfo?: RequestInfo;\n init?: RequestInit;\n retries?: number;\n\n async fetchRetry(\n requestInfo: RequestInfo,\n init?: RequestInit,\n retries?: number,\n ): Promise<Response> {\n this.init = init;\n this.requestInfo = requestInfo;\n this.retries = retries;\n return new Response(JSON.stringify({ boop: 'snoot' }), { status: 200 });\n }\n}\n\ndescribe('Fetch Handler', () => {\n describe('fetch', () => {\n it('adds reCache=1 if it is in the current url', async () => {\n const fetchRetrier = new MockFetchRetrier();\n const fetchHandler = new IaFetchHandler({\n fetchRetrier: fetchRetrier,\n searchParams: '?reCache=1',\n });\n await fetchHandler.fetch('https://foo.org/api/v1/snoot');\n expect(fetchRetrier.requestInfo).to.equal(\n 'https://foo.org/api/v1/snoot?reCache=1',\n );\n });\n });\n\n describe('fetchIAApiResponse', () => {\n it('prepends the IA basehost to the url when making a request', async () => {\n const endpoint = '/foo/service/endpoint.php';\n const fetchRetrier = new MockFetchRetrier();\n const fetchHandler = new IaFetchHandler({\n iaApiBaseUrl: 'www.example.com',\n fetchRetrier: fetchRetrier,\n });\n await fetchHandler.fetchIAApiResponse(endpoint);\n expect(fetchRetrier.requestInfo).to.equal(\n 'www.example.com/foo/service/endpoint.php',\n );\n });\n });\n\n describe('fetchApiResponse', () => {\n it('adds credentials: include if requested', async () => {\n const endpoint = '/foo/service/endpoint.php';\n const fetchRetrier = new MockFetchRetrier();\n const fetchHandler = new IaFetchHandler({\n iaApiBaseUrl: 'www.example.com',\n fetchRetrier: fetchRetrier,\n });\n await fetchHandler.fetchApiResponse(endpoint, {\n includeCredentials: true,\n });\n expect(fetchRetrier.init).to.deep.equal({ credentials: 'include' });\n });\n });\n});\n"]}