From c9d3d5b9cf307e6b0c8927b1ce9573edc4fa6ded Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju Date: Fri, 30 Aug 2024 19:06:10 +0530 Subject: [PATCH] fix: xhr queue plugin --- .../analytics-js-common/src/utilities/http.ts | 2 +- .../src/utilities/retryQueue/RetryQueue.ts | 28 ++++- .../src/utilities/retryQueue/constants.ts | 3 + .../src/utilities/retryQueue/types.ts | 10 -- .../__tests__/xhrQueue/utilities.test.ts | 113 +++++++----------- .../src/xhrQueue/index.ts | 111 +++++++++++++---- .../src/xhrQueue/logMessages.ts | 23 +++- .../src/xhrQueue/utilities.ts | 11 +- packages/analytics-js/.size-limit.mjs | 2 +- .../analytics-js/src/constants/logMessages.ts | 3 - .../src/services/HttpClient/xhr/index.ts | 87 -------------- 11 files changed, 180 insertions(+), 213 deletions(-) delete mode 100644 packages/analytics-js/src/services/HttpClient/xhr/index.ts diff --git a/packages/analytics-js-common/src/utilities/http.ts b/packages/analytics-js-common/src/utilities/http.ts index 2f81987968..6aa4593478 100644 --- a/packages/analytics-js-common/src/utilities/http.ts +++ b/packages/analytics-js-common/src/utilities/http.ts @@ -1,7 +1,7 @@ import type { IResponseDetails } from '../types/HttpClient'; const isErrRetryable = (details: IResponseDetails) => { - const status = details.error?.status || 0; + const status = details.error?.status ?? 0; return status === 429 || (status >= 500 && status < 600); }; diff --git a/packages/analytics-js-common/src/utilities/retryQueue/RetryQueue.ts b/packages/analytics-js-common/src/utilities/retryQueue/RetryQueue.ts index 3dcaeaec0a..b73aa4a5bc 100644 --- a/packages/analytics-js-common/src/utilities/retryQueue/RetryQueue.ts +++ b/packages/analytics-js-common/src/utilities/retryQueue/RetryQueue.ts @@ -37,6 +37,7 @@ import { DEFAULT_BATCH_FLUSH_INTERVAL_MS, MIN_TIMER_SCALE_FACTOR, MAX_TIMER_SCALE_FACTOR, + MAX_PAGE_UNLOAD_BATCH_SIZE_BYTES, } from './constants'; const sortByTime = (a: QueueItem, b: QueueItem) => a.time - b.time; @@ -244,14 +245,33 @@ class RetryQueue implements IQueue { this.isPageAccessible = isAccessible; if (!this.batchingInProgress) { this.batchingInProgress = true; - let batchQueue = + const batchQueue = (this.getStorageEntry(QueueStatuses.BATCH_QUEUE) as Nullable) ?? []; if (batchQueue.length > 0) { - batchQueue = batchQueue.slice(-batchQueue.length); + let batchItems: QueueItem[] = []; + let remainingBatchItems: QueueItem[] = []; + if (!this.isPageAccessible) { + // eslint-disable-next-line no-restricted-syntax + for (const queueItem of batchQueue) { + if ( + (this.batchSizeCalcCb as QueueBatchItemsSizeCalculatorCallback)( + [...batchItems, queueItem].map(queueItem => queueItem.item), + ) > MAX_PAGE_UNLOAD_BATCH_SIZE_BYTES + ) { + break; + } + + batchItems.push(queueItem); + } + + remainingBatchItems = batchQueue.slice(batchItems.length); + } else { + batchItems = batchQueue.slice(-batchQueue.length); + } - const batchEntry = this.genQueueItem(batchQueue.map(queueItem => queueItem.item)); + const batchEntry = this.genQueueItem(batchItems.map(queueItem => queueItem.item)); - this.setStorageEntry(QueueStatuses.BATCH_QUEUE, []); + this.setStorageEntry(QueueStatuses.BATCH_QUEUE, remainingBatchItems); this.pushToMainQueue(batchEntry); } diff --git a/packages/analytics-js-common/src/utilities/retryQueue/constants.ts b/packages/analytics-js-common/src/utilities/retryQueue/constants.ts index 5ed1d8023f..33f1be3427 100644 --- a/packages/analytics-js-common/src/utilities/retryQueue/constants.ts +++ b/packages/analytics-js-common/src/utilities/retryQueue/constants.ts @@ -17,6 +17,8 @@ const DEFAULT_MAX_BATCH_SIZE_BYTES = 512 * 1024; // 512 KB; this is also the max const DEFAULT_MAX_BATCH_ITEMS = 100; const DEFAULT_BATCH_FLUSH_INTERVAL_MS = 60 * 1000; // 1 minutes +const MAX_PAGE_UNLOAD_BATCH_SIZE_BYTES = 64 * 1024; // 64 KB + export { DEFAULT_MIN_RETRY_DELAY_MS, DEFAULT_MAX_RETRY_DELAY_MS, @@ -33,4 +35,5 @@ export { DEFAULT_BATCH_FLUSH_INTERVAL_MS, MIN_TIMER_SCALE_FACTOR, MAX_TIMER_SCALE_FACTOR, + MAX_PAGE_UNLOAD_BATCH_SIZE_BYTES, }; diff --git a/packages/analytics-js-common/src/utilities/retryQueue/types.ts b/packages/analytics-js-common/src/utilities/retryQueue/types.ts index 4cb987765a..f8000c6c14 100644 --- a/packages/analytics-js-common/src/utilities/retryQueue/types.ts +++ b/packages/analytics-js-common/src/utilities/retryQueue/types.ts @@ -67,17 +67,7 @@ export type QueueBatchItemsSizeCalculatorCallback = (item: T) => number export interface IQueue { name: string; id: string; - processQueueCb: QueueProcessCallback; - store: IStore; - storeManager: IStoreManager; - maxItems: number; - timeouts: Record; scheduleTimeoutActive: boolean; - getStorageEntry(name?: string): Nullable[] | Record | number>; - setStorageEntry( - name?: string, - value?: Nullable[] | Record | number>, - ): void; start(): void; stop(): void; enqueue(item: QueueItem): void; diff --git a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts index 054e9ce87c..6c32dc9c98 100644 --- a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts +++ b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts @@ -118,103 +118,70 @@ describe('xhrQueue Plugin Utilities', () => { describe('logErrorOnFailure', () => { it('should not log error if there is no error', () => { - const details = { - response: {}, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, mockLogger); + logErrorOnFailure(false, 'https://test.com/v1/page', undefined, false, 1, 10, mockLogger); expect(mockLogger.error).not.toHaveBeenCalled(); }); it('should log an error for delivery failure', () => { - const details = { - error: {}, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, mockLogger); + logErrorOnFailure( + false, + 'https://test.com/v1/page', + 'Something bad happened', + false, + 1, + 10, + mockLogger, + ); expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. The event(s) will be dropped.', + 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Original error: Something bad happened. The event(s) will be dropped.', ); }); it('should log an error for retryable network failure', () => { - let details = { - error: { - status: 429, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); - - expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', - ); - - // Retryable error but it's the first attempt - details = { - error: { - status: 429, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', true, 0, 10, mockLogger); - - expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried.', + logErrorOnFailure( + true, + 'https://test.com/v1/page', + 'Something bad happened', + true, + 1, + 10, + mockLogger, ); - // 500 error - details = { - error: { - status: 500, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); - expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', + 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Original error: Something bad happened. It/they will be retried. Retry attempt 1 of 10.', ); - // 5xx error - details = { - error: { - status: 501, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); - - expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', + // Retryable error but it's the first attempt + logErrorOnFailure( + true, + 'https://test.com/v1/page', + 'Something bad happened', + true, + 0, + 10, + mockLogger, ); - // 600 error - details = { - error: { - status: 600, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); - expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. The event(s) will be dropped.', + 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Original error: Something bad happened. It/they will be retried.', ); // Retryable error but exhausted all tries - details = { - error: { - status: 520, - }, - } as IResponseDetails; - - logErrorOnFailure(details, 'https://test.com/v1/page', false, 10, 10, mockLogger); + logErrorOnFailure( + true, + 'https://test.com/v1/page', + 'Something bad happened', + false, + 10, + 10, + mockLogger, + ); expect(mockLogger.error).toHaveBeenCalledWith( - 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Retries exhausted (10). The event(s) will be dropped.', + 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Original error: Something bad happened. Retries exhausted (10). The event(s) will be dropped.', ); }); }); diff --git a/packages/analytics-js-plugins/src/xhrQueue/index.ts b/packages/analytics-js-plugins/src/xhrQueue/index.ts index 8b285aa2eb..24116d2e76 100644 --- a/packages/analytics-js-plugins/src/xhrQueue/index.ts +++ b/packages/analytics-js-plugins/src/xhrQueue/index.ts @@ -2,7 +2,10 @@ /* eslint-disable no-param-reassign */ import type { ExtensionPlugin } from '@rudderstack/analytics-js-common/types/PluginEngine'; import type { ApplicationState } from '@rudderstack/analytics-js-common/types/ApplicationState'; -import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; +import type { + IHttpClient, + IResponseDetails, +} from '@rudderstack/analytics-js-common/types/HttpClient'; import type { IErrorHandler } from '@rudderstack/analytics-js-common/types/ErrorHandler'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import type { IStoreManager } from '@rudderstack/analytics-js-common/types/Store'; @@ -15,6 +18,8 @@ import type { QueueItemData, DoneCallback, } from '@rudderstack/analytics-js-common/utilities/retryQueue/types'; +import { toBase64 } from '@rudderstack/analytics-js-common/utilities/string'; +import { FAILED_REQUEST_ERR_MSG_PREFIX } from '@rudderstack/analytics-js-common/constants/errors'; import { storages, http, timestamp, string, eventsDelivery } from '../shared-chunks/common'; import { getNormalizedQueueOptions, @@ -26,6 +31,7 @@ import { import { QUEUE_NAME, REQUEST_TIMEOUT_MS } from './constants'; import type { XHRRetryQueueItemData, XHRQueueItemData } from './types'; import { RetryQueue } from '../shared-chunks/retryQueue'; +import { DELIVERY_ERROR, REQUEST_ERROR } from './logMessages'; const pluginName: PluginName = 'XhrQueue'; @@ -76,33 +82,87 @@ const XhrQueue = (): ExtensionPlugin => ({ logger, ); - httpClient.request({ - url, - options: { - method: 'POST', - headers, - body: data as string, - sendRawData: true, - useAuth: true, - }, - isRawResponse: true, - timeout: REQUEST_TIMEOUT_MS, - callback: (result, details) => { - // null means item will not be requeued - const queueErrResp = http.isErrRetryable(details) ? details : null; - - logErrorOnFailure( - details, + const handleResponse = ( + err?: any, + status?: number, + statusText?: string, + ev?: ProgressEvent, + ) => { + let errMsg; + if (err) { + errMsg = REQUEST_ERROR( + FAILED_REQUEST_ERR_MSG_PREFIX, url, - willBeRetried, - attemptNumber, - maxRetryAttempts, - logger, + REQUEST_TIMEOUT_MS, + err, + ev, ); + } else if (status && (status < 200 || status > 300)) { + errMsg = DELIVERY_ERROR(FAILED_REQUEST_ERR_MSG_PREFIX, status, url, statusText, ev); + } + + const details = { + error: { + status, + }, + } as IResponseDetails; + + const isRetryableFailure = http.isErrRetryable(details); + + // null means item will not be requeued + const queueErrResp = isRetryableFailure ? details : null; + + logErrorOnFailure( + isRetryableFailure, + url, + errMsg, + willBeRetried, + attemptNumber, + maxRetryAttempts, + logger, + ); + + done(queueErrResp); + }; + + const xhr = new XMLHttpRequest(); + try { + xhr.open('POST', url, true); + } catch (err: any) { + handleResponse(err); + return; + } + + // The timeout property may be set only in the time interval between a call to the open method + // and the first call to the send method in legacy browsers + xhr.timeout = REQUEST_TIMEOUT_MS; + + if (headers) { + Object.entries(headers).forEach(([headerName, headerValue]) => { + xhr.setRequestHeader(headerName, headerValue); + }); + } + + xhr.onload = () => { + // This is same as fetch API + if (xhr.status >= 200 && xhr.status < 300) { + handleResponse(null, xhr.status, xhr.statusText); + } + }; + + const xhrCallback = (ev: ProgressEvent) => { + handleResponse(undefined, xhr.status, xhr.statusText, ev); + }; + + xhr.ontimeout = xhrCallback; + xhr.onerror = xhrCallback; + xhr.onabort = xhrCallback; - done(queueErrResp, result); - }, - }); + try { + xhr.send(data); + } catch (err: any) { + handleResponse(err, xhr.status, xhr.statusText); + } }, storeManager, storages.LOCAL_STORAGE, @@ -146,6 +206,7 @@ const XhrQueue = (): ExtensionPlugin => ({ const headers = { Accept: 'application/json', 'Content-Type': 'application/json;charset=UTF-8', + Authorization: `Basic ${toBase64(`${state.lifecycle.writeKey.value as string}:`)}`, // To maintain event ordering while using the HTTP API as per is documentation, // make sure to include anonymousId as a header AnonymousId: string.toBase64(event.anonymousId), diff --git a/packages/analytics-js-plugins/src/xhrQueue/logMessages.ts b/packages/analytics-js-plugins/src/xhrQueue/logMessages.ts index 3449d49b66..b3aad60d26 100644 --- a/packages/analytics-js-plugins/src/xhrQueue/logMessages.ts +++ b/packages/analytics-js-plugins/src/xhrQueue/logMessages.ts @@ -1,6 +1,23 @@ import { LOG_CONTEXT_SEPARATOR } from '@rudderstack/analytics-js-common/constants/logMessages'; -const EVENT_DELIVERY_FAILURE_ERROR_PREFIX = (context: string, url: string): string => - `${context}${LOG_CONTEXT_SEPARATOR}Failed to deliver event(s) to ${url}.`; +const DELIVERY_ERROR = ( + prefix: string, + status: number, + url: string | URL, + statusText?: string, + ev?: ProgressEvent, +): string => `${prefix} with status ${status} (${statusText}) (${ev?.type ?? ''}) for URL: ${url}.`; -export { EVENT_DELIVERY_FAILURE_ERROR_PREFIX }; +const REQUEST_ERROR = ( + prefix: string, + url: string | URL, + timeout: number, + errMsg?: string, + ev?: ProgressEvent, +): string => + `${prefix} due to timeout after ${timeout}ms or no connection (${ev?.type ?? ''}) or aborted for URL: ${url}. Original message: ${errMsg}.`; + +const EVENT_DELIVERY_FAILURE_ERROR_PREFIX = (context: string, err: string, url: string): string => + `${context}${LOG_CONTEXT_SEPARATOR}Failed to deliver event(s) to ${url}. Original error: ${err}.`; + +export { EVENT_DELIVERY_FAILURE_ERROR_PREFIX, DELIVERY_ERROR, REQUEST_ERROR }; diff --git a/packages/analytics-js-plugins/src/xhrQueue/utilities.ts b/packages/analytics-js-plugins/src/xhrQueue/utilities.ts index ffa25b2b8e..28e4222571 100644 --- a/packages/analytics-js-plugins/src/xhrQueue/utilities.ts +++ b/packages/analytics-js-plugins/src/xhrQueue/utilities.ts @@ -1,13 +1,12 @@ import { mergeDeepRight } from '@rudderstack/analytics-js-common/utilities/object'; import type { QueueOpts } from '@rudderstack/analytics-js-common/types/LoadOptions'; -import type { IResponseDetails } from '@rudderstack/analytics-js-common/types/HttpClient'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import type { ApplicationState } from '@rudderstack/analytics-js-common/types/ApplicationState'; import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import { clone } from 'ramda'; import { getCurrentTimeFormatted } from '@rudderstack/analytics-js-common/utilities/timestamp'; -import { checks, http, url, json, eventsDelivery } from '../shared-chunks/common'; +import { checks, url, json, eventsDelivery } from '../shared-chunks/common'; import { DATA_PLANE_API_VERSION, DEFAULT_RETRY_QUEUE_OPTIONS, XHR_QUEUE_PLUGIN } from './constants'; import type { XHRRetryQueueItemData, XHRQueueItemData, XHRBatchPayload } from './types'; import { EVENT_DELIVERY_FAILURE_ERROR_PREFIX } from './logMessages'; @@ -37,19 +36,19 @@ const getDeliveryUrl = (dataplaneUrl: string, endpoint: string): string => { const getBatchDeliveryUrl = (dataplaneUrl: string): string => getDeliveryUrl(dataplaneUrl, 'batch'); const logErrorOnFailure = ( - details: IResponseDetails, + isRetryableFailure: boolean, url: string, + err?: string, willBeRetried?: boolean, attemptNumber?: number, maxRetryAttempts?: number, logger?: ILogger, ) => { - if (checks.isUndefined(details.error) || checks.isUndefined(logger)) { + if (checks.isUndefined(err) || checks.isUndefined(logger)) { return; } - const isRetryableFailure = http.isErrRetryable(details); - let errMsg = EVENT_DELIVERY_FAILURE_ERROR_PREFIX(XHR_QUEUE_PLUGIN, url); + let errMsg = EVENT_DELIVERY_FAILURE_ERROR_PREFIX(XHR_QUEUE_PLUGIN, err, url); const dropMsg = `The event(s) will be dropped.`; if (isRetryableFailure) { if (willBeRetried) { diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index 3e80456e50..adb7b2854a 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -47,7 +47,7 @@ export default [ { name: 'Core - Modern - CDN', path: 'dist/cdn/modern/iife/rsa.min.js', - limit: '27 KiB', + limit: '27.5 KiB', }, { name: 'Core (Bundled) - Legacy - NPM (ESM)', diff --git a/packages/analytics-js/src/constants/logMessages.ts b/packages/analytics-js/src/constants/logMessages.ts index 7f5ee4fc73..e71969cfb6 100644 --- a/packages/analytics-js/src/constants/logMessages.ts +++ b/packages/analytics-js/src/constants/logMessages.ts @@ -90,8 +90,6 @@ const REQUEST_ERROR = ( ): string => `${prefix} due to timeout after ${timeout}ms or no connection (${e?.type ?? ''}) or aborted for URL: ${url}.`; -const XHR_SEND_ERROR = (prefix: string, url: string | URL): string => `${prefix} for URL: ${url}`; - const STORE_DATA_SAVE_ERROR = (key: string): string => `Failed to save the value for "${key}" to storage`; @@ -292,7 +290,6 @@ export { READY_API_CALLBACK_ERROR, DELIVERY_ERROR, REQUEST_ERROR, - XHR_SEND_ERROR, PAYLOAD_PREP_ERROR, STORE_DATA_SAVE_ERROR, STORE_DATA_FETCH_ERROR, diff --git a/packages/analytics-js/src/services/HttpClient/xhr/index.ts b/packages/analytics-js/src/services/HttpClient/xhr/index.ts deleted file mode 100644 index d230212191..0000000000 --- a/packages/analytics-js/src/services/HttpClient/xhr/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -// import { FAILED_REQUEST_ERR_MSG_PREFIX } from '@rudderstack/analytics-js-common/constants/errors'; -// import type { IXHRRequestOptions } from '@rudderstack/analytics-js-common/types/HttpClient'; -// import { clone } from 'ramda'; -// import { DELIVERY_ERROR, REQUEST_ERROR, XHR_SEND_ERROR } from '../../../constants/logMessages'; -// import { HttpClientError } from '../utils'; - -// const makeXHRRequest = (url: string | URL, options: IXHRRequestOptions): Promise => -// new Promise((resolve, reject) => { -// const xhr = new XMLHttpRequest(); -// const xhrError = (e?: ProgressEvent) => { -// reject( -// new HttpClientError( -// REQUEST_ERROR(FAILED_REQUEST_ERR_MSG_PREFIX, url, options.timeout as number, e), -// xhr.status, -// xhr.statusText, -// xhr.responseText, -// ), -// ); -// }; - -// try { -// xhr.open(options.method, url, true); -// } catch (err: any) { -// // clone the error object and add the status, statusText, and responseBody properties -// const clonedErr: HttpClientError = clone(err); -// clonedErr.message = `${XHR_SEND_ERROR(FAILED_REQUEST_ERR_MSG_PREFIX, url)}: ${err.message}`; -// clonedErr.status = xhr.status; -// clonedErr.statusText = xhr.statusText; -// clonedErr.responseBody = xhr.responseText; - -// reject(clonedErr); -// return; -// } - -// if (options.withCredentials === true) { -// xhr.withCredentials = true; -// } -// // The timeout property may be set only in the time interval between a call to the open method -// // and the first call to the send method in legacy browsers -// xhr.timeout = options.timeout as number; - -// if (options.headers) { -// Object.entries(options.headers).forEach(([headerName, headerValue]) => { -// xhr.setRequestHeader(headerName, headerValue); -// }); -// } - -// xhr.onload = () => { -// // This is same as fetch API -// if (xhr.status >= 200 && xhr.status < 300) { -// resolve( -// new Response(xhr.responseText, { -// status: xhr.status, -// statusText: xhr.statusText, -// }), -// ); -// } else { -// reject( -// new HttpClientError( -// DELIVERY_ERROR(FAILED_REQUEST_ERR_MSG_PREFIX, xhr.status, xhr.statusText, url), -// xhr.status, -// xhr.statusText, -// xhr.responseText, -// ), -// ); -// } -// }; - -// xhr.ontimeout = xhrError; -// xhr.onerror = xhrError; -// xhr.onabort = xhrError; - -// try { -// xhr.send(options.body); -// } catch (err: any) { -// // clone the error object and add the status, statusText, and responseBody properties -// const clonedErr: HttpClientError = clone(err); -// clonedErr.message = `${XHR_SEND_ERROR(FAILED_REQUEST_ERR_MSG_PREFIX, url)}: ${err.message}`; -// clonedErr.status = xhr.status; -// clonedErr.statusText = xhr.statusText; -// clonedErr.responseBody = xhr.responseText; - -// reject(clonedErr); -// } -// }); - -// export { makeXHRRequest };