@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
@@ -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,16 +1,16 @@
1
1
  ![Build Status](https://github.com/internetarchive/iaux-fetch-handler-service/actions/workflows/ci.yml/badge.svg) [![codecov](https://codecov.io/gh/internetarchive/iaux-fetch-handler-service/graph/badge.svg?token=ZOYRJ2BV9W)](https://codecov.io/gh/internetarchive/iaux-fetch-handler-service)
2
2
 
3
3
 
4
- # Internet Archive Fetch Handler Service
4
+ # Internet Archive Fetch Handler library
5
5
 
6
6
 
7
- A custom service for handling API requests.
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-service
13
+ npm install @internetarchive/iaux-fetch-handler
14
14
  ```
15
15
 
16
16
 
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/ia-fetch-handler';
4
+ import { IaFetchHandler } from '../src/fetch-handler';
5
5
 
6
6
  @customElement('app-root')
7
7
  export class AppRoot extends LitElement {
@@ -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
  }
@@ -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/ia-fetch-handler';
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,yBAAyB,CAAC;AAGlD,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/ia-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"]}
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/ia-fetch-handler';
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/ia-fetch-handler';
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,wBAAwB,CAAC","sourcesContent":["export { IaFetchHandler } from './src/ia-fetch-handler';\nexport { FetchHandlerInterface } from './src/fetch-handler-interface';\n"]}
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 init RequestInit
8
+ * @param options RequestInit | FetchOptions
7
9
  */
8
- fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
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,10 @@ 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?: { includeCredentials?: boolean }
31
+ * @param options?: { includeCredentials?: boolean, retryConfig?: RetryConfiguring }
29
32
  */
30
33
  fetchIAApiResponse<T>(path: string, options?: {
31
34
  includeCredentials?: boolean;
35
+ retryConfig?: RetryConfiguring;
32
36
  }): Promise<T>;
33
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-handler-interface.js","sourceRoot":"","sources":["../../src/fetch-handler-interface.ts"],"names":[],"mappings":"","sourcesContent":["export interface FetchHandlerInterface {\n /**\n * Generic fetch function that handles retries and common IA parameters like `reCache=1`\n *\n * @param input RequestInfo\n * @param init RequestInit\n */\n fetch(input: RequestInfo, init?: RequestInit): 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 },\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?: { includeCredentials?: boolean }\n */\n fetchIAApiResponse<T>(\n path: string,\n options?: { includeCredentials?: boolean },\n ): Promise<T>;\n}\n"]}
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?: { includeCredentials?: boolean, retryConfig?: RetryConfiguring }\n */\n fetchIAApiResponse<T>(\n path: string,\n options?: { includeCredentials?: boolean; retryConfig?: RetryConfiguring },\n ): Promise<T>;\n}\n"]}
@@ -1,5 +1,7 @@
1
- import { FetchRetrierInterface } from './utils/fetch-retrier';
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,12 @@ 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
+ retryConfig?: RetryConfiguring;
21
25
  }): Promise<T>;
22
26
  /** @inheritdoc */
23
27
  fetchApiResponse<T>(url: string, options?: {
@@ -25,9 +29,10 @@ export declare class IaFetchHandler implements FetchHandlerInterface {
25
29
  method?: string;
26
30
  body?: BodyInit;
27
31
  headers?: HeadersInit;
32
+ retryConfig?: RetryConfiguring;
28
33
  }): Promise<T>;
29
34
  /** @inheritdoc */
30
- fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
35
+ fetch(request: RequestInfo, options?: RequestInit | FetchOptions): Promise<Response>;
31
36
  /**
32
37
  * Since RequestInfo can be either a `Request` or `string`, we need to change
33
38
  * the way we add search params to it depending on the input.
@@ -1,4 +1,4 @@
1
- import { FetchRetrier } from './utils/fetch-retrier';
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, requestInit);
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(input, init) {
44
- let finalInput = input;
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
- finalInput = this.addSearchParams(input, { reCache: '1' });
50
+ const urlString = typeof request === 'string' ? request : request.url;
51
+ finalRequest = this.addSearchParams(urlString, { reCache: '1' });
48
52
  }
49
- return this.fetchRetrier.fetchRetry(finalInput, init);
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(input, params) {
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
- if (typeof input === 'string') {
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=ia-fetch-handler.js.map
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 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,5 @@
1
+ import type { RetryConfiguring } from './fetch-retry/configuration/retry-configuring';
2
+ export type FetchOptions = {
3
+ requestInit?: RequestInit;
4
+ retryConfig?: RetryConfiguring;
5
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fetch-options.js.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=milliseconds.js.map
@@ -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,6 @@
1
+ import type { RetryConfiguring } from './retry-configuring';
2
+ import type { Milliseconds } from './milliseconds';
3
+ export declare class NoRetryConfiguration implements RetryConfiguring {
4
+ shouldRetry(): boolean;
5
+ retryDelay(): Milliseconds;
6
+ }
@@ -0,0 +1,9 @@
1
+ export class NoRetryConfiguration {
2
+ shouldRetry() {
3
+ return false;
4
+ }
5
+ retryDelay() {
6
+ return 0;
7
+ }
8
+ }
9
+ //# sourceMappingURL=no-retry-configuration.js.map
@@ -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,5 @@
1
+ import type { Milliseconds } from './milliseconds';
2
+ export interface RetryConfiguring {
3
+ shouldRetry(response: Response | null, retryNumber: number, error?: unknown): boolean;
4
+ retryDelay(retryNumber: number): Milliseconds;
5
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=retry-configuring.js.map
@@ -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 requestInfo RequestInfo
10
- * @param init Optional RequestInit
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(requestInfo: RequestInfo, init?: RequestInit, retries?: number): Promise<Response>;
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 sleep;
20
- private retryCount;
21
- private retryDelay;
19
+ private analyticsHandler?;
20
+ private retryConfiguration;
22
21
  constructor(options?: {
23
22
  analyticsHandler?: AnalyticsHandlerInterface;
24
- retryCount?: number;
25
- retryDelay?: number;
26
- sleepFn?: (ms: number) => Promise<void>;
23
+ retryConfiguration?: RetryConfiguring;
27
24
  });
28
25
  /** @inheritdoc */
29
- fetchRetry(requestInfo: RequestInfo, init?: RequestInit, retries?: number): Promise<Response>;
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 log404Event;
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":"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","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,2 @@
1
+ import type { FetchOptions } from '../fetch-options';
2
+ export declare const legacyArgsAsFetchOptions: (options?: RequestInit | FetchOptions) => FetchOptions | undefined;
@@ -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"]}
@@ -0,0 +1,30 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { DefaultRetryConfiguration } from '../src/fetch-retry/configuration/default-retry-configuration';
3
+ describe('DefaultRetryConfiguration', () => {
4
+ it('should not retry on null response', async () => {
5
+ const config = new DefaultRetryConfiguration();
6
+ expect(config.shouldRetry(null, 1)).to.be.false;
7
+ });
8
+ it('should not retry after max retries exceeded', async () => {
9
+ const config = new DefaultRetryConfiguration({ maxRetries: 2 });
10
+ const mockResponse = new Response(null, { status: 500 });
11
+ expect(config.shouldRetry(mockResponse, 3)).to.be.false;
12
+ });
13
+ it('should retry on 5xx status codes', async () => {
14
+ const config = new DefaultRetryConfiguration();
15
+ const mockResponse = new Response(null, { status: 502 });
16
+ expect(config.shouldRetry(mockResponse, 1)).to.be.true;
17
+ });
18
+ it('should not retry on non-5xx status codes', async () => {
19
+ const config = new DefaultRetryConfiguration();
20
+ const mockResponse = new Response(null, { status: 404 });
21
+ expect(config.shouldRetry(mockResponse, 1)).to.be.false;
22
+ });
23
+ it('has exponential backoff delay', async () => {
24
+ const config = new DefaultRetryConfiguration();
25
+ expect(config.retryDelay(0)).to.equal(500);
26
+ expect(config.retryDelay(1)).to.equal(1000);
27
+ expect(config.retryDelay(2)).to.equal(2000);
28
+ });
29
+ });
30
+ //# sourceMappingURL=default-retry-config.test.js.map