diff --git a/packages/sdk-client/src/client/api-client-helpers.ts b/packages/sdk-client/src/client/api-client-helpers.ts index 764ef29a..a7adcdf7 100644 --- a/packages/sdk-client/src/client/api-client-helpers.ts +++ b/packages/sdk-client/src/client/api-client-helpers.ts @@ -62,3 +62,34 @@ export async function invalidateAndRegenerateJwt( throw new GenericError(errorMessage, errorContext); } } + +/** + * Go through all an object's properties and transform to date the values that match the right format + * @param {any} input - the response object after all the response plugins have been run + * @return {any} - the response where the values matching a date are revived as Date objects + */ +export const reviveDates = (input: any): any => { + if (Array.isArray(input)) { + // Process array elements recursively + return input.map((item) => reviveDates(item)); + } else if (typeof input === 'object' && input !== null) { + // Process object properties recursively + const newObj: any = {}; + for (const key in input) { + if (Object.prototype.hasOwnProperty.call(input, key)) { + newObj[key] = reviveDates(input[key]); + } + } + return newObj; + } else if (isDateString(input)) { + // Convert string date to Date object + return new Date(input); + } else { + // Return other types as-is + return input; + } +}; + +const isDateString = (value: any): boolean => { + return typeof value === 'string' && !isNaN(Date.parse(value)); +}; diff --git a/packages/sdk-client/src/client/api-client-pagination-helper.ts b/packages/sdk-client/src/client/api-client-pagination-helper.ts index 0a1b6b22..c61a296b 100644 --- a/packages/sdk-client/src/client/api-client-pagination-helper.ts +++ b/packages/sdk-client/src/client/api-client-pagination-helper.ts @@ -87,14 +87,13 @@ const updateQueryParamsAndSendRequest = ( ...requestOptions.queryParams, ...newParams, }; - const extractedParams = apiClient.extractQueryParams(newQueryParams, Object.keys(newQueryParams)); const newRequestOptions: RequestOptions = { ...requestOptions, - queryParams: extractedParams, + queryParams: newQueryParams, }; const newUrl = apiClient.prepareUrl( requestOptions.basePath, - extractedParams, + newQueryParams, ); return apiClient.processCallWithPagination({ url: newUrl, diff --git a/packages/sdk-client/src/client/api-fetch-client.ts b/packages/sdk-client/src/client/api-fetch-client.ts index 6a03eb7b..af344cab 100644 --- a/packages/sdk-client/src/client/api-fetch-client.ts +++ b/packages/sdk-client/src/client/api-fetch-client.ts @@ -12,7 +12,7 @@ import { ResponseJSONParseError, ApiCallParametersWithPagination, PageResult, } from '../api'; import fetch, { Response, Headers } from 'node-fetch'; -import { buildErrorContext, manageExpiredToken } from './api-client-helpers'; +import { buildErrorContext, manageExpiredToken, reviveDates } from './api-client-helpers'; import { buildPaginationContext, calculateNextPage, @@ -94,8 +94,8 @@ export class ApiFetchClient extends ApiClient { throw exception; } - // If everything went fine, we return the transformed API response - return transformedResponse; + // If everything went fine, we apply a last transformation to revive the dates, and we return the transformed API response + return reviveDates(transformedResponse); } private async sinchFetch( @@ -125,13 +125,15 @@ export class ApiFetchClient extends ApiClient { const errorContext: ErrorContext = buildErrorContext(props, origin); // Execute call - return this.sinchFetchWithPagination(props, errorContext); + return this.sinchFetchWithPagination(props, errorContext, origin); }; private async sinchFetchWithPagination( apiCallParameters: ApiCallParametersWithPagination, errorContext: ErrorContext, + origin: string | null, ): Promise> { + let exception: Error | undefined; const response = await fetch(apiCallParameters.url, apiCallParameters.requestOptions); if ( response.status === 401 @@ -146,14 +148,46 @@ export class ApiFetchClient extends ApiClient { } // When handling pagination, we won't return the raw response but a PageResult const body = await response.text(); - const result = JSON.parse(body); + let result; + try { + // Try to parse the body if there is one + result = body ? JSON.parse(body) : undefined; + } catch (error: any) { + exception = new ResponseJSONParseError( + error.message || 'Fail to parse response body', + (response && response.status) || 0, + errorContext, + body, + ); + } + + // Load and invoke the response plugins to transform the response + const responsePlugins = this.loadResponsePlugins( + this.apiClientOptions.responsePlugins, + apiCallParameters, + response, + exception, + origin); + let transformedResponse = result; + for (const pluginRunner of responsePlugins) { + transformedResponse = await pluginRunner.transform(transformedResponse); + } + + // Revive Date objects + transformedResponse = reviveDates(transformedResponse); + + // If there has been an error at some point in the process, throw it + if (exception) { + throw exception; + } + // Read the elements' array with its key - const responseData: Array = result[apiCallParameters.dataKey]; + const responseData: Array = transformedResponse[apiCallParameters.dataKey]; // Build the PageResult object - const nextPage = calculateNextPage(result, buildPaginationContext(apiCallParameters)); + const nextPage = JSON.stringify(calculateNextPage(transformedResponse, buildPaginationContext(apiCallParameters))); return { data: responseData || [], - hasNextPage: hasMore(result, buildPaginationContext(apiCallParameters)), + hasNextPage: hasMore(transformedResponse, buildPaginationContext(apiCallParameters)), nextPageValue: nextPage, nextPage: () => createNextPageMethod( this, buildPaginationContext(apiCallParameters), apiCallParameters.requestOptions, nextPage), diff --git a/packages/sdk-client/tests/client/api-client-helpers.test.ts b/packages/sdk-client/tests/client/api-client-helpers.test.ts new file mode 100644 index 00000000..5c0e3dfe --- /dev/null +++ b/packages/sdk-client/tests/client/api-client-helpers.test.ts @@ -0,0 +1,47 @@ +import { reviveDates } from '../../src/client/api-client-helpers'; + +describe('API client helpers', () => { + + it('should revive Dates', () => { + const obj = { + date1: '2024-01-31T12:00:00.000Z', + nested: { + date2: '2024-02-15T18:30:00.000Z', + }, + array: [ + { + date3: '2024-02-03T04:15:00.000Z', + }, + { + date3: '2024-02-04T20:22:00.123Z', + }, + ], + otherProp: 'otherValue', + otherNumber: 0, + otherBoolean: true, + otherUndefined: undefined, + otherNull: null, + }; + const expected = { + date1: new Date('2024-01-31T12:00:00.000Z'), + nested: { + date2: new Date('2024-02-15T18:30:00.000Z'), + }, + array: [ + { + date3: new Date('2024-02-03T04:15:00.000Z'), + }, + { + date3: new Date('2024-02-04T20:22:00.123Z'), + }, + ], + otherProp: 'otherValue', + otherNumber: 0, + otherBoolean: true, + otherUndefined: undefined, + otherNull: null, + }; + expect(reviveDates(obj)).toStrictEqual(expected); + }); + +});