@hmcts/opal-frontend-common 0.0.89 → 0.0.90

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.
@@ -0,0 +1,16 @@
1
+ const MAX_HTTP_RETRY_BACKOFF_MULTIPLIER = 10;
2
+
3
+ const MAX_HTTP_RETRY_COUNT = 2;
4
+
5
+ const MAX_HTTP_RETRY_DELAY_MS = 30000;
6
+
7
+ const HTTP_NON_RETRYABLE_STATUS_CODES = Object.freeze([400, 401, 403, 404, 409, 422]);
8
+
9
+ const HTTP_RETRYABLE_STATUS_CODES = Object.freeze([0, 408, 502, 503, 504]);
10
+
11
+ /**
12
+ * Generated bundle index. Do not edit.
13
+ */
14
+
15
+ export { HTTP_NON_RETRYABLE_STATUS_CODES, HTTP_RETRYABLE_STATUS_CODES, MAX_HTTP_RETRY_BACKOFF_MULTIPLIER, MAX_HTTP_RETRY_COUNT, MAX_HTTP_RETRY_DELAY_MS };
16
+ //# sourceMappingURL=hmcts-opal-frontend-common-interceptors-http-retry-constants.mjs.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ //# sourceMappingURL=hmcts-opal-frontend-common-interceptors-http-retry-interfaces.mjs.map
@@ -0,0 +1,162 @@
1
+ import { retry, throwError, timer, of } from 'rxjs';
2
+ import { HttpContextToken, HttpErrorResponse, HttpContext } from '@angular/common/http';
3
+
4
+ /**
5
+ * Stores per-request HTTP retry policy options in an Angular HTTP context.
6
+ */
7
+ const HTTP_RETRY_POLICY = new HttpContextToken(() => ({}));
8
+
9
+ const MAX_HTTP_RETRY_BACKOFF_MULTIPLIER = 10;
10
+
11
+ const HTTP_NON_RETRYABLE_STATUS_CODES = Object.freeze([400, 401, 403, 404, 409, 422]);
12
+
13
+ const HTTP_RETRYABLE_STATUS_CODES = Object.freeze([0, 408, 502, 503, 504]);
14
+
15
+ const MAX_HTTP_RETRY_COUNT = 2;
16
+
17
+ const MAX_HTTP_RETRY_DELAY_MS = 30000;
18
+
19
+ /**
20
+ * Returns whether the request is eligible for retry under the supplied policy.
21
+ */
22
+ function canRetryRequest(req, policy) {
23
+ return policy.retryCount > 0 && isGetRequest(req);
24
+ }
25
+ /**
26
+ * Calculates the delay before the next retry attempt, including backoff and maximum delay bounds.
27
+ */
28
+ function getRetryDelayMs(policy, retryAttempt) {
29
+ const retryDelayMs = policy.delayMs * Math.pow(policy.backoffMultiplier, Math.max(retryAttempt - 1, 0));
30
+ return Math.min(Math.round(retryDelayMs), policy.maxDelayMs);
31
+ }
32
+ /**
33
+ * Returns whether the error status is retryable and has not been explicitly marked non-retriable.
34
+ */
35
+ function isRetryableError(error, policy) {
36
+ if (isApiNonRetriableError(error)) {
37
+ return false;
38
+ }
39
+ const statusCode = getStatusCode(error);
40
+ if (statusCode === undefined || HTTP_NON_RETRYABLE_STATUS_CODES.includes(statusCode)) {
41
+ return false;
42
+ }
43
+ return policy.retryableStatusCodes.includes(statusCode);
44
+ }
45
+ /**
46
+ * Normalizes partial retry options into a bounded retry policy with default values.
47
+ */
48
+ function normalizeHttpRetryPolicy(policy = {}) {
49
+ const delayMs = getBoundedInteger(policy.delayMs, 0, MAX_HTTP_RETRY_DELAY_MS);
50
+ return {
51
+ retryCount: getBoundedInteger(policy.retryCount, 0, MAX_HTTP_RETRY_COUNT),
52
+ delayMs,
53
+ backoffMultiplier: getBoundedNumber(policy.backoffMultiplier, 1, 1, MAX_HTTP_RETRY_BACKOFF_MULTIPLIER),
54
+ maxDelayMs: getBoundedInteger(policy.maxDelayMs, delayMs, MAX_HTTP_RETRY_DELAY_MS),
55
+ retryableStatusCodes: getRetryableStatusCodes(policy.retryableStatusCodes),
56
+ };
57
+ }
58
+ /**
59
+ * Returns whether the request uses the GET method.
60
+ */
61
+ function isGetRequest(req) {
62
+ return req.method.toUpperCase() === 'GET';
63
+ }
64
+ /**
65
+ * Extracts an HTTP status code from an Angular error response or compatible error shape.
66
+ */
67
+ function getStatusCode(error) {
68
+ if (!(error instanceof HttpErrorResponse) && (typeof error !== 'object' || error === null)) {
69
+ return undefined;
70
+ }
71
+ if (typeof error === 'object' && error !== null && 'status' in error) {
72
+ const statusError = error;
73
+ if (typeof statusError.status === 'number') {
74
+ return statusError.status;
75
+ }
76
+ }
77
+ if (typeof error === 'object' && error !== null && 'error' in error) {
78
+ const nestedError = error;
79
+ if (typeof nestedError.error?.status === 'number') {
80
+ return nestedError.error.status;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ /**
86
+ * Returns whether the API response body explicitly marks the error as non-retriable.
87
+ */
88
+ function isApiNonRetriableError(error) {
89
+ if (typeof error !== 'object' || error === null || !('error' in error)) {
90
+ return false;
91
+ }
92
+ const responseError = error.error;
93
+ return typeof responseError === 'object' && responseError !== null && 'retriable' in responseError
94
+ ? responseError.retriable === false
95
+ : false;
96
+ }
97
+ /**
98
+ * Returns a finite integer clamped between zero and the supplied maximum.
99
+ */
100
+ function getBoundedInteger(value, fallback, max) {
101
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
102
+ return fallback;
103
+ }
104
+ return Math.min(Math.max(Math.floor(value), 0), max);
105
+ }
106
+ /**
107
+ * Returns a finite number clamped between the supplied minimum and maximum.
108
+ */
109
+ function getBoundedNumber(value, fallback, min, max) {
110
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
111
+ return fallback;
112
+ }
113
+ return Math.min(Math.max(value, min), max);
114
+ }
115
+ /**
116
+ * Filters and de-duplicates configured retryable status codes against the supported retryable list.
117
+ */
118
+ function getRetryableStatusCodes(statusCodes) {
119
+ const codes = statusCodes ?? HTTP_RETRYABLE_STATUS_CODES;
120
+ return [...new Set(codes.filter((statusCode) => HTTP_RETRYABLE_STATUS_CODES.includes(statusCode)))];
121
+ }
122
+
123
+ /**
124
+ * Retries opted-in GET requests when they fail with configured transient HTTP errors.
125
+ */
126
+ const httpRetryInterceptor = (req, next) => {
127
+ const retryPolicy = normalizeHttpRetryPolicy(req.context.get(HTTP_RETRY_POLICY));
128
+ if (!canRetryRequest(req, retryPolicy)) {
129
+ return next(req);
130
+ }
131
+ return next(req).pipe(retry({
132
+ count: retryPolicy.retryCount,
133
+ delay: (error, retryAttempt) => {
134
+ if (!isRetryableError(error, retryPolicy)) {
135
+ return throwError(() => error);
136
+ }
137
+ const delayMs = getRetryDelayMs(retryPolicy, retryAttempt);
138
+ return delayMs > 0 ? timer(delayMs) : of(0);
139
+ },
140
+ }));
141
+ };
142
+
143
+ /**
144
+ * Enables HTTP retry for a request by adding the normalized retry policy to its context.
145
+ */
146
+ function withHttpRetry(policy, context = new HttpContext()) {
147
+ return context.set(HTTP_RETRY_POLICY, normalizeHttpRetryPolicy(policy));
148
+ }
149
+
150
+ /**
151
+ * Disables HTTP retry for a request by setting a zero retry count on its context.
152
+ */
153
+ function withoutHttpRetry(context = new HttpContext()) {
154
+ return context.set(HTTP_RETRY_POLICY, { retryCount: 0 });
155
+ }
156
+
157
+ /**
158
+ * Generated bundle index. Do not edit.
159
+ */
160
+
161
+ export { httpRetryInterceptor, withHttpRetry, withoutHttpRetry };
162
+ //# sourceMappingURL=hmcts-opal-frontend-common-interceptors-http-retry.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmcts/opal-frontend-common",
3
- "version": "0.0.89",
3
+ "version": "0.0.90",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -671,6 +671,21 @@
671
671
  "types": "./types/hmcts-opal-frontend-common-interceptors-http-error-interfaces.d.ts",
672
672
  "default": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-error-interfaces.mjs"
673
673
  },
674
+ "./interceptors/http-retry": {
675
+ "import": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry.mjs",
676
+ "types": "./types/hmcts-opal-frontend-common-interceptors-http-retry.d.ts",
677
+ "default": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry.mjs"
678
+ },
679
+ "./interceptors/http-retry/constants": {
680
+ "import": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry-constants.mjs",
681
+ "types": "./types/hmcts-opal-frontend-common-interceptors-http-retry-constants.d.ts",
682
+ "default": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry-constants.mjs"
683
+ },
684
+ "./interceptors/http-retry/interfaces": {
685
+ "import": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry-interfaces.mjs",
686
+ "types": "./types/hmcts-opal-frontend-common-interceptors-http-retry-interfaces.d.ts",
687
+ "default": "./fesm2022/hmcts-opal-frontend-common-interceptors-http-retry-interfaces.mjs"
688
+ },
674
689
  "./pages/dashboard-page": {
675
690
  "import": "./fesm2022/hmcts-opal-frontend-common-pages-dashboard-page.mjs",
676
691
  "types": "./types/hmcts-opal-frontend-common-pages-dashboard-page.d.ts",
@@ -0,0 +1,11 @@
1
+ declare const MAX_HTTP_RETRY_BACKOFF_MULTIPLIER = 10;
2
+
3
+ declare const MAX_HTTP_RETRY_COUNT = 2;
4
+
5
+ declare const MAX_HTTP_RETRY_DELAY_MS = 30000;
6
+
7
+ declare const HTTP_NON_RETRYABLE_STATUS_CODES: readonly number[];
8
+
9
+ declare const HTTP_RETRYABLE_STATUS_CODES: readonly number[];
10
+
11
+ export { HTTP_NON_RETRYABLE_STATUS_CODES, HTTP_RETRYABLE_STATUS_CODES, MAX_HTTP_RETRY_BACKOFF_MULTIPLIER, MAX_HTTP_RETRY_COUNT, MAX_HTTP_RETRY_DELAY_MS };
@@ -0,0 +1,10 @@
1
+ interface IHttpRetryPolicy {
2
+ retryCount: number;
3
+ delayMs: number;
4
+ backoffMultiplier: number;
5
+ maxDelayMs: number;
6
+ retryableStatusCodes: readonly number[];
7
+ }
8
+ type IHttpRetryPolicyOptions = Partial<IHttpRetryPolicy>;
9
+
10
+ export type { IHttpRetryPolicy, IHttpRetryPolicyOptions };
@@ -0,0 +1,27 @@
1
+ import { HttpInterceptorFn, HttpContext } from '@angular/common/http';
2
+
3
+ /**
4
+ * Retries opted-in GET requests when they fail with configured transient HTTP errors.
5
+ */
6
+ declare const httpRetryInterceptor: HttpInterceptorFn;
7
+
8
+ interface IHttpRetryPolicy {
9
+ retryCount: number;
10
+ delayMs: number;
11
+ backoffMultiplier: number;
12
+ maxDelayMs: number;
13
+ retryableStatusCodes: readonly number[];
14
+ }
15
+ type IHttpRetryPolicyOptions = Partial<IHttpRetryPolicy>;
16
+
17
+ /**
18
+ * Enables HTTP retry for a request by adding the normalized retry policy to its context.
19
+ */
20
+ declare function withHttpRetry(policy: IHttpRetryPolicyOptions, context?: HttpContext): HttpContext;
21
+
22
+ /**
23
+ * Disables HTTP retry for a request by setting a zero retry count on its context.
24
+ */
25
+ declare function withoutHttpRetry(context?: HttpContext): HttpContext;
26
+
27
+ export { httpRetryInterceptor, withHttpRetry, withoutHttpRetry };