11import MockedResponses from './responses/mockedResponses' ;
22import PendingRequestBody from './pendingRequest/pendingRequestBody' ;
3- import PendingRequestUrl from './pendingRequest/pendingRequestUrl' ;
43import { Options } from './types/options' ;
54import { HttpMethods } from './types/httpMethods' ;
65import { RequestAuthorization } from './types/requestAuthorization' ;
@@ -9,6 +8,13 @@ import { FetchOptions } from './types/fetchOptions';
98import { RequestOptions } from './types/requestOptions' ;
109
1110export default class HttpClient {
11+ /**
12+ * The base URL for the request.
13+ *
14+ * @var {string}
15+ */
16+ private baseUrl : string = '' ;
17+
1218 /**
1319 * The mocked responses instance.
1420 *
@@ -23,6 +29,13 @@ export default class HttpClient {
2329 */
2430 private options : Options = { } ;
2531
32+ /**
33+ * Number of attempts for the request.
34+ *
35+ * @var {number}
36+ */
37+ private requestAttempts : number = 0 ;
38+
2639 /**
2740 * The request body instance.
2841 *
@@ -31,11 +44,39 @@ export default class HttpClient {
3144 private requestBody : PendingRequestBody ;
3245
3346 /**
34- * The request Url instance.
47+ * The number of times to try the request.
48+ *
49+ * @var {number}
50+ */
51+ private retries : number = 0 ;
52+
53+ /**
54+ * The number of milliseconds to wait between retries.
55+ *
56+ * @var {number}
57+ */
58+ private retryDelay : number = 0 ;
59+
60+ /**
61+ * The callback that will determine if the request should be retried.
62+ *
63+ * @var {function}
64+ */
65+ private retryCallback ?: ( response : Response | undefined , request : this, error ?: unknown ) => boolean | null ;
66+
67+ /**
68+ * The URL for the request.
69+ *
70+ * @returns {string }
71+ */
72+ private url : string = '' ;
73+
74+ /**
75+ * The query parameters for the request URL.
3576 *
36- * @var {PendingRequestUrl }
77+ * @returns { Record<string, string>|undefined }
3778 */
38- private requestUrl : PendingRequestUrl ;
79+ private urlQueryParameters ?: Record < string , string > ;
3980
4081 /**
4182 * Create a new Http Client instance.
@@ -46,7 +87,8 @@ export default class HttpClient {
4687 constructor ( baseUrl ?: string , options ?: object ) {
4788 this . mockedResponses = new MockedResponses ( ) ;
4889 this . requestBody = new PendingRequestBody ( ) ;
49- this . requestUrl = new PendingRequestUrl ( baseUrl ) ;
90+
91+ this . setBaseUrl ( baseUrl ) ;
5092 this . setOptions ( options ) ;
5193 }
5294
@@ -106,6 +148,21 @@ export default class HttpClient {
106148 return this ;
107149 }
108150
151+ /**
152+ * Try the request to the given URL.
153+ *
154+ * @param {HttpMethods } method
155+ *
156+ * @returns {Promise<Response> }
157+ */
158+ private async attemptRequest ( method : HttpMethods ) : Promise < Response > {
159+ const url : URL = this . buildUrl ( ) ;
160+
161+ return this . mockedResponses . isMocked ( )
162+ ? this . mockedResponses . getMockedResponse ( url . toString ( ) )
163+ : await fetch ( url . toString ( ) , this . buildRequestOptions ( method ) ) ;
164+ }
165+
109166 /**
110167 * Construct the Fetch API Options for the request.
111168 *
@@ -131,6 +188,25 @@ export default class HttpClient {
131188 return options ;
132189 }
133190
191+ /**
192+ * Construct the URL for the pending request.
193+ *
194+ * @returns {URL }
195+ */
196+ private buildUrl ( ) : URL {
197+ let url = this . url . replace ( / ^ \/ | \/ $ / g, '' ) + '/' ;
198+
199+ if ( ! ( url . startsWith ( 'http://' ) || url . startsWith ( 'https://' ) ) ) {
200+ url = this . baseUrl . replace ( / \/ $ / , '' ) + '/' + url ;
201+ }
202+
203+ if ( this . urlQueryParameters !== undefined ) {
204+ url += '?' + new URLSearchParams ( this . urlQueryParameters ) ;
205+ }
206+
207+ return new URL ( url ) ;
208+ }
209+
134210 /**
135211 * Specify the request's content type.
136212 *
@@ -142,6 +218,19 @@ export default class HttpClient {
142218 return this . withHeader ( 'Content-Type' , contentType ) ;
143219 }
144220
221+ /**
222+ * Delays the execution of the request by the specified number of milliseconds.
223+ *
224+ * @param {number } milliseconds
225+ *
226+ * @returns {Promise<void> }
227+ */
228+ private delayRequest ( milliseconds : number ) : Promise < void > {
229+ return new Promise ( ( resolve ) => {
230+ setTimeout ( resolve , milliseconds ) ;
231+ } ) ;
232+ }
233+
145234 /**
146235 * Process a DELETE request to the given URL.
147236 *
@@ -150,7 +239,9 @@ export default class HttpClient {
150239 * @returns {Promise<Response> }
151240 */
152241 public delete ( url : string ) : Promise < Response > {
153- return this . sendRequest ( 'DELETE' , url ) ;
242+ this . withUrl ( url ) ;
243+
244+ return this . sendRequest ( 'DELETE' ) ;
154245 }
155246
156247 /**
@@ -175,9 +266,11 @@ export default class HttpClient {
175266 * @returns {Promise<Response> }
176267 */
177268 public get ( url : string , query ?: object ) : Promise < Response > {
269+ this . withUrl ( url ) ;
270+
178271 if ( query ) this . withQueryParameters ( query ) ;
179272
180- return this . sendRequest ( 'GET' , url ) ;
273+ return this . sendRequest ( 'GET' ) ;
181274 }
182275
183276 /**
@@ -198,9 +291,11 @@ export default class HttpClient {
198291 * @returns {Promise<Response> }
199292 */
200293 public head ( url : string , query ?: object ) : Promise < Response > {
294+ this . withUrl ( url ) ;
295+
201296 if ( query ) this . withQueryParameters ( query ) ;
202297
203- return this . sendRequest ( 'HEAD' , url ) ;
298+ return this . sendRequest ( 'HEAD' ) ;
204299 }
205300
206301 /**
@@ -212,9 +307,10 @@ export default class HttpClient {
212307 * @returns {Promise<Response> }
213308 */
214309 public patch ( url : string , data : object | string ) : Promise < Response > {
310+ this . withUrl ( url ) ;
215311 this . withBody ( data ) ;
216312
217- return this . sendRequest ( 'PATCH' , url ) ;
313+ return this . sendRequest ( 'PATCH' ) ;
218314 }
219315
220316 /**
@@ -226,9 +322,10 @@ export default class HttpClient {
226322 * @returns {Promise<Response> }
227323 */
228324 public post ( url : string , data : object | string ) : Promise < Response > {
325+ this . withUrl ( url ) ;
229326 this . withBody ( data ) ;
230327
231- return this . sendRequest ( 'POST' , url ) ;
328+ return this . sendRequest ( 'POST' ) ;
232329 }
233330
234331 /**
@@ -240,9 +337,10 @@ export default class HttpClient {
240337 * @returns {Promise<Response> }
241338 */
242339 public put ( url : string , data : object | string ) : Promise < Response > {
340+ this . withUrl ( url ) ;
243341 this . withBody ( data ) ;
244342
245- return this . sendRequest ( 'PUT' , url ) ;
343+ return this . sendRequest ( 'PUT' ) ;
246344 }
247345
248346 /**
@@ -276,24 +374,81 @@ export default class HttpClient {
276374 return this ;
277375 }
278376
377+ /**
378+ * Specify the number of times the request should be attempted.
379+ *
380+ * @param {number } maxAttempts
381+ * @param {number } intervalMilliseconds
382+ * @param {function } callback
383+ *
384+ * @returns {this }
385+ */
386+ retry (
387+ maxAttempts : number ,
388+ intervalMilliseconds : number ,
389+ callback ?: ( response : Response | undefined , request : this, error ?: unknown ) => boolean | null ,
390+ ) : this {
391+ this . retries = maxAttempts ;
392+ this . retryDelay = intervalMilliseconds ;
393+ this . retryCallback = callback ;
394+
395+ return this ;
396+ }
397+
279398 /**
280399 * Send the request to the given URL.
281400 *
282401 * @param {HttpMethods } method
283- * @param {string } endpoint
284402 *
285403 * @returns {Promise<Response> }
286404 */
287- private async sendRequest ( method : HttpMethods , endpoint : string ) : Promise < Response > {
288- const url = this . requestUrl . buildUrl ( endpoint ) ;
405+ private async sendRequest ( method : HttpMethods ) : Promise < Response > {
406+ this . requestAttempts ++ ;
289407
290- return this . mockedResponses . isMocked ( )
291- ? this . mockedResponses . getMockedResponse ( url . toString ( ) )
292- : await fetch ( url . toString ( ) , this . buildRequestOptions ( method ) ) ;
408+ try {
409+ const response : Response = await this . attemptRequest ( method ) ;
410+ const successfulResponse : boolean = response !== undefined && response . ok ;
411+ const shouldRetry = this . retryCallback ? this . retryCallback ( response , this ) : true ;
412+
413+ if ( ! successfulResponse && shouldRetry === true && this . requestAttempts <= this . retries ) {
414+ await this . delayRequest ( this . retryDelay ) ;
415+
416+ if ( this . retryCallback ) {
417+ this . retryCallback ( response , this ) ;
418+ }
419+
420+ return this . sendRequest ( method ) ;
421+ } else {
422+ return response ;
423+ }
424+ } catch ( error ) {
425+ if ( this . requestAttempts <= this . retries ) {
426+ await this . delayRequest ( this . retryDelay ) ;
427+
428+ if ( this . retryCallback ) {
429+ this . retryCallback ( undefined , this , error ) ;
430+ }
431+
432+ return this . sendRequest ( method ) ;
433+ } else {
434+ throw error ;
435+ }
436+ }
293437 }
294438
295439 /**
296- * Set the default options for the Http Client.
440+ * Set the base URL for the request.
441+ *
442+ * @param {string } baseUrl
443+ *
444+ * @returns {void }
445+ */
446+ private setBaseUrl ( baseUrl : string = '' ) : void {
447+ this . baseUrl = baseUrl ;
448+ }
449+
450+ /**
451+ * Set the default options for the request.
297452 *
298453 * @param {object|undefined } options
299454 */
@@ -417,7 +572,7 @@ export default class HttpClient {
417572 * @returns {this }
418573 */
419574 public withQueryParameters ( query : object ) : this {
420- this . requestUrl . withQueryParameters ( query ) ;
575+ this . urlQueryParameters = { ... this . urlQueryParameters , ... query } ;
421576
422577 return this ;
423578 }
@@ -433,4 +588,17 @@ export default class HttpClient {
433588 public withToken ( token : string , type : RequestAuthorization = 'Bearer' ) : this {
434589 return this . withHeader ( 'Authorization' , `${ type } ${ token . trim ( ) } ` ) ;
435590 }
591+
592+ /**
593+ * Specify the URL for the request.
594+ *
595+ * @param {string } url
596+ *
597+ * @returns {this }
598+ */
599+ public withUrl ( url : string ) : this {
600+ this . url = url ;
601+
602+ return this ;
603+ }
436604}
0 commit comments