Skip to content

Commit e9931e3

Browse files
committed
Merge branch 'main' into release/20260223
2 parents fdc58ae + 9880bb3 commit e9931e3

File tree

6 files changed

+82
-7
lines changed

6 files changed

+82
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
| shopper-seo | 1.0.17 |
2424
| shopper-stores | 1.2.0 |
2525

26+
### Enchancements
27+
28+
- Allow developers to pass in custom fetch implementation via `clientConfig` [#272](https://github.com/SalesforceCommerceCloud/commerce-sdk-isomorphic/pull/272)
29+
2630
## v5.0.0
2731

2832
### API Versions

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,45 @@ await helpers.callCustomEndpoint({
223223

224224
For more documentation about this helper function, please refer to the [commerce-sdk-isomorphic docs](https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/modules/helpers.html).
225225

226+
#### Custom Fetch function
227+
228+
You can provide your own custom fetch function to intercept, log, or modify all SDK requests. This is useful for:
229+
- **Request/Response Logging**: Track all API calls for debugging and monitoring
230+
- **Request Interception**: Add custom headers, modify request URLs, or implement custom retry logic
231+
- **Error Handling**: Add custom error processing or transformation before responses reach your application
232+
- **Performance Monitoring**: Measure request/response times and track API performance metrics
233+
234+
**Example with Logging:**
235+
```javascript
236+
// Custom fetch function with detailed logging
237+
const customFetch = async (url, options) => {
238+
console.log(`[SDK Request] ${options?.method || 'GET'} ${url}`);
239+
console.log('[SDK Request Headers]', options?.headers);
240+
if (options?.body) {
241+
console.log('[SDK Request Body]', options.body);
242+
}
243+
244+
const startTime = Date.now();
245+
const response = await fetch(url, options);
246+
const duration = Date.now() - startTime;
247+
248+
console.log(`[SDK Response] ${response.status} ${response.statusText} (${duration}ms)`);
249+
console.log('[SDK Response Headers]', Object.fromEntries(response.headers.entries()));
250+
251+
return response;
252+
};
253+
254+
const config = {
255+
parameters: {
256+
clientId: '<your-client-id>',
257+
organizationId: '<your-org-id>',
258+
shortCode: '<your-short-code>',
259+
siteId: '<your-site-id>',
260+
},
261+
fetch: customFetch,
262+
};
263+
```
264+
226265
#### Encoding special characters
227266
228267
The SDK currently single encodes special characters for query parameters in UTF-8 format based on SCAPI guidelines. However, the SDK does NOT encode path parameters, and will require the developer to encode any path parameters with special characters.

src/static/clientConfig.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77
import {BaseUriParameters} from 'lib/helpers';
8-
import ClientConfig, {ClientConfigInit} from './clientConfig';
8+
import ClientConfig, {ClientConfigInit, FetchFunction} from './clientConfig';
99

1010
describe('ClientConfig constructor', () => {
1111
test('will throw if missing shortCode parameter', () => {
@@ -26,6 +26,7 @@ describe('ClientConfig constructor', () => {
2626
proxy: 'https://proxy.com',
2727
transformRequest: ClientConfig.defaults.transformRequest,
2828
throwOnBadResponse: false,
29+
fetch: fetch as FetchFunction,
2930
};
3031
expect(new ClientConfig(init)).toEqual({...init});
3132
});

src/static/clientConfig.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ type BrowserRequestInit = RequestInit;
1818
*/
1919
export type FetchOptions = NodeRequestInit & BrowserRequestInit;
2020

21+
/**
22+
* A function that can be used to fetch data from an API
23+
*/
24+
export type FetchFunction = (
25+
input: RequestInfo,
26+
init?: FetchOptions | undefined
27+
) => Promise<Response>;
28+
2129
/**
2230
* Base options that can be passed to the `ClientConfig` class.
2331
*/
@@ -27,18 +35,14 @@ export interface ClientConfigInit<Params extends BaseUriParameters> {
2735
headers?: {[key: string]: string};
2836
parameters: Params;
2937
fetchOptions?: FetchOptions;
38+
fetch?: FetchFunction;
3039
transformRequest?: (
3140
data: unknown,
3241
headers: {[key: string]: string}
3342
) => Required<FetchOptions>['body'];
3443
throwOnBadResponse?: boolean;
3544
}
3645

37-
export type FetchFunction = (
38-
input: RequestInfo,
39-
init?: FetchOptions | undefined
40-
) => Promise<Response>;
41-
4246
/**
4347
* Configuration parameters common to Commerce SDK clients
4448
*/
@@ -55,6 +59,8 @@ export default class ClientConfig<Params extends BaseUriParameters>
5559

5660
public fetchOptions: FetchOptions;
5761

62+
public fetch?: FetchFunction;
63+
5864
public transformRequest: NonNullable<
5965
ClientConfigInit<Params>['transformRequest']
6066
>;
@@ -83,6 +89,8 @@ export default class ClientConfig<Params extends BaseUriParameters>
8389
this.proxy = config.proxy;
8490
}
8591
this.throwOnBadResponse = !!config.throwOnBadResponse;
92+
93+
this.fetch = config.fetch;
8694
}
8795

8896
static readonly defaults: Pick<

src/static/helpers/fetchHelper.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ describe('doFetch', () => {
138138
expect.objectContaining(clientConfig.fetchOptions)
139139
);
140140
});
141+
142+
test('uses fetch from clientConfig if provided', async () => {
143+
nock(basePath).post(endpointPath).query(true).reply(200, responseBody);
144+
145+
const spy = jest.spyOn(global, 'fetch');
146+
const environmentFetchSpy = jest.spyOn(environment, 'fetch');
147+
148+
const clientConfigCopy = {
149+
...clientConfig,
150+
fetch,
151+
};
152+
153+
await doFetch(url, options, clientConfigCopy, false);
154+
expect(spy).toBeCalledTimes(1);
155+
expect(environmentFetchSpy).not.toBeCalled();
156+
expect(spy).toBeCalledWith(
157+
expect.any(String),
158+
expect.objectContaining(clientConfig.fetchOptions)
159+
);
160+
});
141161
});
142162

143163
describe('encodeSCAPISpecialCharacters', () => {

src/static/helpers/fetchHelper.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ export const doFetch = async <Params extends BaseUriParameters>(
5151
method: options?.method ?? 'GET',
5252
};
5353

54-
const response = await fetch(url, requestOptions);
54+
// Use fetch from clientConfig, otherwise fallback to fetch implementation in environment.ts
55+
const fetcher = clientConfig?.fetch || fetch;
56+
57+
const response = await fetcher(url, requestOptions);
5558
if (rawResponse) {
5659
return response;
5760
}

0 commit comments

Comments
 (0)