Skip to content

Commit 9b4489a

Browse files
committed
feat: Add optional transportConfig to Transporter
- Introduced `transportConfig` option to the Transporter class to allow customization of the HTTP client layer. - Set native fetch as the default transport in Transporter, while providing a smooth fallback mechanism. - Enhanced JSDoc comments to document the new `transportConfig` option. - Added two example files to demonstrate the flexibility of the new feature: - One showing integration with the deprecated `request` library. - Another leveraging the `axios` library for HTTP requests. This change provides users with the flexibility to switch between different HTTP libraries, catering to various needs and preferences.
1 parent d669a06 commit 9b4489a

File tree

4 files changed

+157
-5
lines changed

4 files changed

+157
-5
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env node
2+
const process = require('node:process');
3+
const dotenv = require('dotenv');
4+
const axios = require('axios');
5+
const zd = require('../src/index.js');
6+
7+
dotenv.config();
8+
9+
const transportConfigUsingAxios = {
10+
async transportFn(uri, options) {
11+
// Convert the options to be compatible with axios
12+
const requestOptions = {
13+
...options,
14+
url: uri,
15+
method: options.method || 'GET', // Axios uses 'method', not just 'GET' or 'POST' as direct options
16+
data: options.body, // Axios uses 'data' instead of 'body'
17+
};
18+
19+
try {
20+
const response = await axios(requestOptions);
21+
return response;
22+
} catch (error) {
23+
// If axios throws an error, it usually encapsulates the actual server response within error.response.
24+
// This is to ensure that even for error HTTP statuses, the response can be adapted consistently.
25+
if (error.response) {
26+
return error.response;
27+
}
28+
29+
throw error; // If there's no error.response, then throw the error as is.
30+
}
31+
},
32+
33+
responseAdapter(response) {
34+
return {
35+
json: () => Promise.resolve(response.data),
36+
status: response.status,
37+
headers: {
38+
get: (headerName) => response.headers[headerName.toLowerCase()],
39+
},
40+
statusText: response.statusText,
41+
};
42+
},
43+
};
44+
45+
const setupClient = (config) => {
46+
return zd.createClient({
47+
username: process.env.ZENDESK_USERNAME,
48+
subdomain: process.env.ZENDESK_SUBDOMAIN,
49+
token: process.env.ZENDESK_TOKEN,
50+
transportConfig: transportConfigUsingAxios,
51+
...config,
52+
});
53+
};
54+
55+
async function foo() {
56+
try {
57+
const client = setupClient({debug: false});
58+
const result = await client.users.list();
59+
60+
console.dir(result);
61+
} catch (error) {
62+
console.error(`Failed: ${error.message}`);
63+
}
64+
}
65+
66+
foo();
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env node
2+
const process = require('node:process');
3+
const dotenv = require('dotenv');
4+
const request = require('request');
5+
const zd = require('../src/index.js');
6+
7+
dotenv.config();
8+
9+
const transportConfigUsingRequest = {
10+
transportFn(uri, options) {
11+
// Convert the options to be compatible with the request library
12+
const requestOptions = {
13+
...options,
14+
uri,
15+
headers: options.headers,
16+
json: true, // Automatically stringifies the body to JSON
17+
};
18+
19+
return new Promise((resolve, reject) => {
20+
request(requestOptions, (error, response) => {
21+
if (error) {
22+
reject(error);
23+
} else {
24+
resolve(response);
25+
}
26+
});
27+
});
28+
},
29+
30+
responseAdapter(response) {
31+
return {
32+
json: () => Promise.resolve(response.body),
33+
status: response.statusCode,
34+
headers: {
35+
get: (headerName) => response.headers[headerName.toLowerCase()],
36+
},
37+
statusText: response.statusMessage,
38+
};
39+
},
40+
};
41+
42+
const setupClient = (config) => {
43+
return zd.createClient({
44+
username: process.env.ZENDESK_USERNAME,
45+
subdomain: process.env.ZENDESK_SUBDOMAIN,
46+
token: process.env.ZENDESK_TOKEN,
47+
transportConfig: transportConfigUsingRequest,
48+
...config,
49+
});
50+
};
51+
52+
async function foo() {
53+
try {
54+
const client = setupClient({debug: false});
55+
const result = await client.users.list();
56+
57+
console.dir(result);
58+
} catch (error) {
59+
console.error(`Failed: ${error.message}`);
60+
}
61+
}
62+
63+
foo();

src/client/transporter.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,33 @@ const {CustomEventTarget} = require('./custom-event-target');
55
const {EndpointChecker} = require('./endpoint-checker');
66
const {assembleUrl} = require('./helpers');
77

8+
// Default transport config using fetch
9+
const defaultTransportConfig = {
10+
transportFn(uri, options) {
11+
return fetch(uri, options);
12+
},
13+
14+
responseAdapter(response) {
15+
return {
16+
json: () => response.json(),
17+
status: response.status,
18+
headers: {
19+
get: (headerName) => response.headers.get(headerName),
20+
},
21+
statusText: response.statusText,
22+
};
23+
},
24+
};
825
class Transporter {
926
constructor(options) {
1027
this.options = options;
1128
this.authHandler = new AuthorizationHandler(this.options);
1229
this.eventTarget = new CustomEventTarget();
1330
this.endpointChecker = new EndpointChecker();
31+
const transportConfig =
32+
this.options.transportConfig || defaultTransportConfig;
33+
this.transportFn = transportConfig.transportFn;
34+
this.responseAdapter = transportConfig.responseAdapter;
1435
}
1536

1637
// Transporter methods
@@ -49,7 +70,10 @@ class Transporter {
4970

5071
async sendRequest(options) {
5172
this.emit('debug::request', options); // Emit before the request
52-
const response = await this.fetchWithOptions(options.uri, options);
73+
74+
const rawResponse = await this.transportFn(options.uri, options);
75+
const response = this.responseAdapter(rawResponse);
76+
5377
this.emit('debug::response', response); // Emit after the request
5478
let result = {};
5579
if (
@@ -81,10 +105,6 @@ class Transporter {
81105
};
82106
}
83107

84-
fetchWithOptions(uri, options) {
85-
return fetch(options.uri, options);
86-
}
87-
88108
getHeadersForRequest() {
89109
const headers = {
90110
Authorization: this.authHandler.createAuthorizationHeader(),

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const {MODULES, ENDPOINTS} = require('./constants');
2424
* @property {boolean} [options.throttle] - Flag to enable throttling of requests.
2525
* @property {boolean} [options.debug=false] - Flag to enable or disable debug logging.
2626
* @property {object} [options.logger=ConsoleLogger] - Optional logger for logging. Should have methods like `info`, `error`, etc. Defaults to a basic console logger.
27+
* @property {object} [options.transportConfig] - Configuration for custom transport functionality.
28+
* @property {function} [options.transportConfig.transportFn] - Custom function to perform the request. By default, it uses `fetch`. It should return a response object.
29+
* @property {function} [options.transportConfig.responseAdapter] - Custom function to adapt the response from `transportFn` into a consistent format. By default, it adapts for `fetch` response.
2730
*
2831
* @example
2932
* const zendeskOptions = {

0 commit comments

Comments
 (0)