Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import https from 'node:https';

jest.mock('node-fetch');
import fetch from 'node-fetch';
import { redactUrlQueryParametersKeys } from '../errors';

const { Response, Headers } = jest.requireActual('node-fetch');

Expand Down Expand Up @@ -565,5 +566,28 @@ describe('APIClient', () => {
});
});

describe('redactUrlQueryParametersKeys', () => {
it('should redact the query parameters from the URL', () => {
const url = 'https://api.example.com?key1=value1&key2=value2';
const keys = ['key1'];
const result = redactUrlQueryParametersKeys(url, keys);
expect(result).toBe('https://api.example.com?key2=value2');
});

it('should redact the query parameters from the URL', () => {
const url = 'https://api.example.com?key1=value1&key2=value2';
const keys = ['key1', 'key2'];
const result = redactUrlQueryParametersKeys(url, keys);
expect(result).toBe('https://api.example.com');
});

it('should not redact the query parameters from the URL if the keys are not provided', () => {
const url = 'https://api.example.com?key1=value1&key2=value2';
const keys = [];
const result = redactUrlQueryParametersKeys(url, keys);
expect(result).toBe('https://api.example.com?key1=value1&key2=value2');
});
});

// Add more test cases for other methods as needed
});
4 changes: 4 additions & 0 deletions packages/integration-sdk-http-client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export abstract class BaseAPIClient {
protected tokenBucketInitialConfig?: TokenBucketOptions | undefined;
protected endpointTokenBuckets: Record<string, HierarchicalTokenBucket> = {};
protected refreshAuth?: RefreshAuthOptions;
protected redactUrlQueryParamsKeys?: string[];

protected readonly integrationConfig: IntegrationInstanceConfig;
protected readonly agent?: Agent;
Expand Down Expand Up @@ -97,6 +98,7 @@ export abstract class BaseAPIClient {
* if not provided, token bucket will not be enabled
* @param {boolean} [config.refreshAuth.enabled] - If true, the auth headers will be refreshed on 401 and 403 errors
* @param {number[]} [config.refreshAuth.errorCodes] - If provided, the auth headers will be refreshed on the provided error codes
* @param {string[]} [config.redactUrlQueryParamsKeys] - If provided, the query parameters will be redacted from the URL
*
* @example
* ```typescript
Expand Down Expand Up @@ -136,6 +138,7 @@ export abstract class BaseAPIClient {
});
}
this.refreshAuth = config.refreshAuth;
this.redactUrlQueryParamsKeys = config.redactUrlQueryParamsKeys;
}

protected withBaseUrl(endpoint: string): string {
Expand Down Expand Up @@ -333,6 +336,7 @@ export abstract class BaseAPIClient {
response,
logger: this.logger,
logErrorBody: this.logErrorBody,
redactUrlQueryParamsKeys: this.redactUrlQueryParamsKeys,
};
if (isRetryableRequest(response.status)) {
error = await retryableRequestError(requestErrorParams);
Expand Down
26 changes: 25 additions & 1 deletion packages/integration-sdk-http-client/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface RequestErrorParams {
response: Response;
logger: IntegrationLogger;
logErrorBody: boolean;
redactUrlQueryParamsKeys?: string[];
}

export class RetryableIntegrationProviderApiError extends IntegrationProviderAPIError {
Expand Down Expand Up @@ -101,11 +102,32 @@ export async function retryableRequestError({
});
}

/**
* Redact the query parameters from the URL
* @param url - The URL to redact
* @param keys - The keys to redact
* @returns The URL with the query parameters redacted
*/
export function redactUrlQueryParametersKeys(
url: string,
keys: string[],
): string {
const [base, query] = url.split('?');
if (!query) return url;

const params = new URLSearchParams(query);
keys.forEach((key) => params.delete(key));

const qs = params.toString();
return qs ? `${base}?${qs}` : base;
}

export async function fatalRequestError({
endpoint,
response,
logger,
logErrorBody,
redactUrlQueryParamsKeys,
}: RequestErrorParams): Promise<IntegrationProviderAPIError> {
const errorBody = await getErrorBody(response, logger, logErrorBody);
const headers = headersToRecord(response.headers);
Expand All @@ -115,7 +137,9 @@ export async function fatalRequestError({
typeof errorBody === 'string'
? new ResponseBodyError(errorBody, headers)
: undefined,
endpoint,
endpoint: redactUrlQueryParamsKeys?.length
? redactUrlQueryParametersKeys(endpoint, redactUrlQueryParamsKeys)
: endpoint,
status: response.status,
statusText: response.statusText,
};
Expand Down
1 change: 1 addition & 0 deletions packages/integration-sdk-http-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface ClientConfig {
rateLimitThrottling?: RateLimitThrottlingOptions;
tokenBucket?: TokenBucketOptions;
refreshAuth?: RefreshAuthOptions;
redactUrlQueryParamsKeys?: string[];
}

export interface IterateCallbackResult {
Expand Down