@zcatalyst/transport 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/fetch-handler.js +266 -0
- package/dist-cjs/http-handler.js +402 -0
- package/dist-cjs/index.browser.js +18 -0
- package/dist-cjs/index.js +32 -0
- package/dist-cjs/utils/clonable-stream.js +107 -0
- package/dist-cjs/utils/constants.js +55 -0
- package/dist-cjs/utils/enums.js +22 -0
- package/dist-cjs/utils/errors.js +10 -0
- package/dist-cjs/utils/form-data.js +217 -0
- package/dist-cjs/utils/helpers.js +10 -0
- package/dist-cjs/utils/interfaces.js +2 -0
- package/dist-cjs/utils/request-agent.js +22 -0
- package/dist-cjs/utils/request-timeout.js +14 -0
- package/dist-es/fetch-handler.js +261 -0
- package/dist-es/http-handler.js +361 -0
- package/dist-es/index.browser.js +12 -0
- package/dist-es/index.js +23 -0
- package/dist-es/utils/clonable-stream.js +105 -0
- package/dist-es/utils/constants.js +52 -0
- package/dist-es/utils/enums.js +19 -0
- package/dist-es/utils/errors.js +6 -0
- package/dist-es/utils/form-data.js +213 -0
- package/dist-es/utils/helpers.js +7 -0
- package/dist-es/utils/interfaces.js +1 -0
- package/dist-es/utils/request-agent.js +20 -0
- package/dist-es/utils/request-timeout.js +11 -0
- package/dist-types/fetch-handler.d.ts +40 -0
- package/dist-types/http-handler.d.ts +39 -0
- package/dist-types/index.browser.d.ts +13 -0
- package/dist-types/index.d.ts +16 -0
- package/dist-types/ts3.4/fetch-handler.d.ts +40 -0
- package/dist-types/ts3.4/http-handler.d.ts +39 -0
- package/dist-types/ts3.4/index.browser.d.ts +13 -0
- package/dist-types/ts3.4/index.d.ts +16 -0
- package/dist-types/ts3.4/utils/clonable-stream.d.ts +21 -0
- package/dist-types/ts3.4/utils/constants.d.ts +11 -0
- package/dist-types/ts3.4/utils/enums.d.ts +16 -0
- package/dist-types/ts3.4/utils/errors.d.ts +4 -0
- package/dist-types/ts3.4/utils/form-data.d.ts +44 -0
- package/dist-types/ts3.4/utils/helpers.d.ts +1 -0
- package/dist-types/ts3.4/utils/interfaces.d.ts +64 -0
- package/dist-types/ts3.4/utils/request-agent.d.ts +6 -0
- package/dist-types/ts3.4/utils/request-timeout.d.ts +1 -0
- package/dist-types/utils/clonable-stream.d.ts +21 -0
- package/dist-types/utils/constants.d.ts +11 -0
- package/dist-types/utils/enums.d.ts +16 -0
- package/dist-types/utils/errors.d.ts +4 -0
- package/dist-types/utils/form-data.d.ts +44 -0
- package/dist-types/utils/helpers.d.ts +1 -0
- package/dist-types/utils/interfaces.d.ts +64 -0
- package/dist-types/utils/request-agent.d.ts +6 -0
- package/dist-types/utils/request-timeout.d.ts +1 -0
- package/package.json +10 -5
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _a, _ResponseHandler_attachAppSpecificHeaders, _ResponseHandler_followZcrfTokenProtocol, _ResponseHandler_followJwtZCAuthProtocol, _ResponseHandler_sendRequest;
|
|
7
|
+
import { Auth_Protocol, ConfigManager, zcAuth } from '@zcatalyst/auth-client';
|
|
8
|
+
import { CONSTANTS, getServicePath, getToken } from '@zcatalyst/utils';
|
|
9
|
+
import { HTTP_HEADER_MAP as HEADER_MAP, HTTP_HEADER_MAP, X_ZCSRF_TOKEN, ZD_CSRPARAM } from './utils/constants';
|
|
10
|
+
import { CatalystAPIError } from './utils/errors';
|
|
11
|
+
import { requestTimeout } from './utils/request-timeout';
|
|
12
|
+
const { REQ_METHOD } = CONSTANTS;
|
|
13
|
+
export const keepAliveSupport = {
|
|
14
|
+
supported: undefined
|
|
15
|
+
};
|
|
16
|
+
export class DefaultHttpResponse {
|
|
17
|
+
constructor(resp) {
|
|
18
|
+
this.statusCode = resp.statusCode;
|
|
19
|
+
this.headers = resp.headers;
|
|
20
|
+
this.config = resp.config;
|
|
21
|
+
this.resp = resp;
|
|
22
|
+
}
|
|
23
|
+
get data() {
|
|
24
|
+
if (this.resp.data === undefined) {
|
|
25
|
+
throw new CatalystAPIError('UNPARSABLE_RESPONSE', `Error while processing response data. Raw server ` +
|
|
26
|
+
`response: "${this.resp.data}". `, '', this.statusCode);
|
|
27
|
+
}
|
|
28
|
+
return this.resp.data;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ResponseHandler {
|
|
32
|
+
constructor() { }
|
|
33
|
+
static async fireGeneralRequest({ requestCore, url }, requestOptions = {}) {
|
|
34
|
+
try {
|
|
35
|
+
const headers = requestCore.headers || {};
|
|
36
|
+
const options = {
|
|
37
|
+
method: requestCore.method,
|
|
38
|
+
headers,
|
|
39
|
+
credentials: requestOptions.auth ? 'include' : 'omit',
|
|
40
|
+
body: requestCore.method !== REQ_METHOD.get ? requestCore.body : undefined
|
|
41
|
+
};
|
|
42
|
+
if (requestOptions.auth) {
|
|
43
|
+
options.headers = await _a.attachZCAuthHeaders(headers);
|
|
44
|
+
options.headers = __classPrivateFieldGet(this, _a, "m", _ResponseHandler_attachAppSpecificHeaders).call(this, headers);
|
|
45
|
+
}
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
if (requestOptions.abortSignal) {
|
|
48
|
+
requestOptions.abortSignal.addEventListener('abort', () => controller.abort());
|
|
49
|
+
}
|
|
50
|
+
options.signal = controller.signal;
|
|
51
|
+
const response = await Promise.race([
|
|
52
|
+
fetch(url, options),
|
|
53
|
+
requestTimeout(requestOptions?.requestTimeout)
|
|
54
|
+
]);
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const errorData = await response.json().catch(() => null);
|
|
57
|
+
throw new CatalystAPIError(`HTTP_ERROR_${response.status}`, errorData?.message || response.statusText, errorData, response.status);
|
|
58
|
+
}
|
|
59
|
+
if (requestOptions.retry && requestOptions.retry > 0) {
|
|
60
|
+
try {
|
|
61
|
+
return await _a.wrapResponse(response, {
|
|
62
|
+
...requestOptions,
|
|
63
|
+
request: requestCore
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
requestOptions.retry--;
|
|
68
|
+
return this.fireGeneralRequest({ requestCore, url }, requestOptions);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return await _a.wrapResponse(response, {
|
|
72
|
+
...requestOptions,
|
|
73
|
+
request: requestCore
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof CatalystAPIError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
if (error.name === 'AbortError') {
|
|
81
|
+
throw new CatalystAPIError('REQUEST_ABORTED', 'The request was aborted', error);
|
|
82
|
+
}
|
|
83
|
+
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
|
|
84
|
+
throw new CatalystAPIError('NETWORK_ERROR', 'Network error occurred while making the request', error);
|
|
85
|
+
}
|
|
86
|
+
throw new CatalystAPIError('REQUEST_FAILED', error.message || 'Request failed', error, error.statusCode);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
static async wrapResponse(response, options) {
|
|
90
|
+
try {
|
|
91
|
+
let data;
|
|
92
|
+
switch (options?.expecting || "json") {
|
|
93
|
+
case "buffer":
|
|
94
|
+
data = await response.arrayBuffer();
|
|
95
|
+
break;
|
|
96
|
+
case "raw":
|
|
97
|
+
data = await response.blob();
|
|
98
|
+
break;
|
|
99
|
+
case "json":
|
|
100
|
+
data = await response.json();
|
|
101
|
+
break;
|
|
102
|
+
case "string":
|
|
103
|
+
data = await response.text();
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
throw new CatalystAPIError('UNSUPPORTED_RESPONSE_TYPE', `Unsupported response type: ${options?.expecting}`);
|
|
107
|
+
}
|
|
108
|
+
return new DefaultHttpResponse({
|
|
109
|
+
headers: response.headers,
|
|
110
|
+
statusCode: response.status,
|
|
111
|
+
data,
|
|
112
|
+
request: options?.request || {},
|
|
113
|
+
config: options || {}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw new CatalystAPIError('RESPONSE_PARSE_ERROR', 'Failed to parse response data', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
static async fireRawRequest(requestCore, reqOptions) {
|
|
121
|
+
let headers = requestCore.headers;
|
|
122
|
+
if (reqOptions.auth) {
|
|
123
|
+
headers = await _a.attachZCAuthHeaders(headers);
|
|
124
|
+
headers = __classPrivateFieldGet(this, _a, "m", _ResponseHandler_attachAppSpecificHeaders).call(this, headers);
|
|
125
|
+
headers = { ...headers, credentials: 'include' };
|
|
126
|
+
}
|
|
127
|
+
const options = {
|
|
128
|
+
method: requestCore.method,
|
|
129
|
+
headers
|
|
130
|
+
};
|
|
131
|
+
if (requestCore.method !== REQ_METHOD.get && requestCore.body !== null)
|
|
132
|
+
options.body = requestCore.body;
|
|
133
|
+
const url = this.configManager.APIDomain
|
|
134
|
+
? `${this.configManager.APIDomain}${requestCore.url}`
|
|
135
|
+
: requestCore.url;
|
|
136
|
+
return await this.wrapResponse(await fetch(url, options));
|
|
137
|
+
}
|
|
138
|
+
static attachZCAuthHeaders(headers) {
|
|
139
|
+
switch (this.configManager.AuthProtocol) {
|
|
140
|
+
case Auth_Protocol.ZcrfTokenProtocol:
|
|
141
|
+
return __classPrivateFieldGet(_a, _a, "m", _ResponseHandler_followZcrfTokenProtocol).call(_a, headers);
|
|
142
|
+
case Auth_Protocol.JwtTokenProtocol:
|
|
143
|
+
return __classPrivateFieldGet(_a, _a, "m", _ResponseHandler_followJwtZCAuthProtocol).call(_a, headers);
|
|
144
|
+
default:
|
|
145
|
+
return Promise.resolve(headers);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
static getJWTZCAuthToken() {
|
|
149
|
+
const conf = ConfigManager.getInstance();
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const jwtZCAuthToken = getToken();
|
|
152
|
+
if (jwtZCAuthToken === '') {
|
|
153
|
+
reject('Unable to get the JWT Access Token.');
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
resolve({
|
|
157
|
+
access_token: `${conf.jwtAuthTokenPrefix} ${jwtZCAuthToken}`
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
static appendQueryString(url, qs) {
|
|
163
|
+
if (!qs || Object.keys(qs).length === 0) {
|
|
164
|
+
return url;
|
|
165
|
+
}
|
|
166
|
+
const searchParams = new URLSearchParams();
|
|
167
|
+
Object.entries(qs)
|
|
168
|
+
.filter(([_, value]) => value != null)
|
|
169
|
+
.forEach(([key, value]) => searchParams.append(key, String(value)));
|
|
170
|
+
const baseUrl = url.split('?')[0];
|
|
171
|
+
const existingParams = url.split('?')[1] || '';
|
|
172
|
+
return `${baseUrl}?${existingParams ? `${existingParams}&` : ''}${searchParams.toString()}`;
|
|
173
|
+
}
|
|
174
|
+
static async send(options) {
|
|
175
|
+
const headers = options.headers || {};
|
|
176
|
+
let data = options.data;
|
|
177
|
+
if (data !== undefined) {
|
|
178
|
+
switch (options.type) {
|
|
179
|
+
case "json":
|
|
180
|
+
data = JSON.stringify(data);
|
|
181
|
+
headers['Content-Type'] = HEADER_MAP.CONTENT_JSON;
|
|
182
|
+
break;
|
|
183
|
+
case "file":
|
|
184
|
+
const formData = new FormData();
|
|
185
|
+
const keyData = Object.keys(data);
|
|
186
|
+
keyData.forEach((key) => {
|
|
187
|
+
formData.append(key, data[key]);
|
|
188
|
+
});
|
|
189
|
+
data = formData;
|
|
190
|
+
break;
|
|
191
|
+
case "raw":
|
|
192
|
+
data = JSON.stringify(data);
|
|
193
|
+
if (headers['Content-Type'] === undefined) {
|
|
194
|
+
headers['Content-Type'] = 'application/octet-stream';
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
data = JSON.stringify(data);
|
|
199
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
200
|
+
headers['Content-Length'] = Buffer.byteLength(data) + '';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (this.configManager.APIDomain === null && !options.origin) {
|
|
204
|
+
throw new CatalystAPIError('API_REQUEST_ERROR', 'Unable to get the base url');
|
|
205
|
+
}
|
|
206
|
+
if (options.service !== "external" && options.path) {
|
|
207
|
+
options.path = `${getServicePath(options.service)}/project/${this.configManager.ProjectID}${options.path}`;
|
|
208
|
+
}
|
|
209
|
+
const request = {
|
|
210
|
+
url: options.url ?? `${options.origin ?? this.configManager.APIDomain}${options.path}`,
|
|
211
|
+
method: options.method,
|
|
212
|
+
...(data ? { body: data } : {}),
|
|
213
|
+
headers
|
|
214
|
+
};
|
|
215
|
+
request.url = this.appendQueryString(request.url, options.qs);
|
|
216
|
+
return await __classPrivateFieldGet(this, _a, "m", _ResponseHandler_sendRequest).call(this, request, {
|
|
217
|
+
expecting: options.expecting,
|
|
218
|
+
auth: options.auth ?? true
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
_a = ResponseHandler, _ResponseHandler_attachAppSpecificHeaders = function _ResponseHandler_attachAppSpecificHeaders(headers) {
|
|
223
|
+
const normalizedHeaders = headers;
|
|
224
|
+
const currentAccept = normalizedHeaders['Accept'];
|
|
225
|
+
if (!currentAccept) {
|
|
226
|
+
normalizedHeaders['Accept'] = 'application/vnd.catalyst.v2+json';
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
normalizedHeaders['Accept'] = `application/vnd.catalyst.v2+json, ${currentAccept}`;
|
|
230
|
+
}
|
|
231
|
+
if (typeof this.configManager?.OrgId === 'string') {
|
|
232
|
+
normalizedHeaders['CATALYST-ORG'] = this.configManager.OrgId;
|
|
233
|
+
}
|
|
234
|
+
normalizedHeaders['CATALYST-COMPONENT'] = 'true';
|
|
235
|
+
return normalizedHeaders;
|
|
236
|
+
}, _ResponseHandler_followZcrfTokenProtocol = async function _ResponseHandler_followZcrfTokenProtocol(headers) {
|
|
237
|
+
return zcAuth
|
|
238
|
+
.collectZCRFToken()
|
|
239
|
+
.then(() => {
|
|
240
|
+
headers[X_ZCSRF_TOKEN] =
|
|
241
|
+
`${ZD_CSRPARAM}=${this.configManager.CsrfToken}`;
|
|
242
|
+
return headers;
|
|
243
|
+
})
|
|
244
|
+
.catch((err) => {
|
|
245
|
+
throw new CatalystAPIError('API_ERROR', err.message, err.status);
|
|
246
|
+
});
|
|
247
|
+
}, _ResponseHandler_followJwtZCAuthProtocol = async function _ResponseHandler_followJwtZCAuthProtocol(headers) {
|
|
248
|
+
return this.getJWTZCAuthToken()
|
|
249
|
+
.then((resp) => {
|
|
250
|
+
headers[HTTP_HEADER_MAP.AUTHORIZATION_KEY] =
|
|
251
|
+
resp.access_token;
|
|
252
|
+
return headers;
|
|
253
|
+
})
|
|
254
|
+
.catch((err) => {
|
|
255
|
+
throw new CatalystAPIError('API_ERROR', err.message, err.status);
|
|
256
|
+
});
|
|
257
|
+
}, _ResponseHandler_sendRequest = async function _ResponseHandler_sendRequest(request, options) {
|
|
258
|
+
const { url, ...requestCore } = request;
|
|
259
|
+
return _a.fireGeneralRequest({ url, requestCore }, options);
|
|
260
|
+
};
|
|
261
|
+
ResponseHandler.configManager = ConfigManager.getInstance();
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { CONSTANTS, getServicePath, isNonEmptyString, LOGGER } from '@zcatalyst/utils';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { ReadableStream } from 'node:stream/web';
|
|
6
|
+
import { stringify } from 'querystring';
|
|
7
|
+
import { Readable } from 'stream';
|
|
8
|
+
import { URL } from 'url';
|
|
9
|
+
import { inspect } from 'util';
|
|
10
|
+
import { version } from '../package.json';
|
|
11
|
+
import { CatalystAPIError } from './utils/errors';
|
|
12
|
+
import FORM from './utils/form-data';
|
|
13
|
+
import { isHttps } from './utils/helpers';
|
|
14
|
+
import RequestAgent from './utils/request-agent';
|
|
15
|
+
const { PROJECT_KEY_NAME, IS_LOCAL, ENVIRONMENT_KEY_NAME, ENVIRONMENT, USER_KEY_NAME, CREDENTIAL_USER, CATALYST_ORIGIN, X_ZOHO_CATALYST_ORG_ID, USER_AGENT, APM_INSIGHT, ACCEPT_HEADER, REQ_RETRY_THRESHOLD, PROJECT_HEADER, IS_APM } = CONSTANTS;
|
|
16
|
+
export class DefaultHttpResponse {
|
|
17
|
+
constructor(resp) {
|
|
18
|
+
this.statusCode = resp.statusCode;
|
|
19
|
+
this.headers = resp.headers;
|
|
20
|
+
this.config = resp.config;
|
|
21
|
+
this.resp = resp;
|
|
22
|
+
}
|
|
23
|
+
get data() {
|
|
24
|
+
switch (this.config.expecting) {
|
|
25
|
+
case "string":
|
|
26
|
+
if (this.resp.data === undefined) {
|
|
27
|
+
throw new CatalystAPIError('UNPARSABLE_RESPONSE', `Error while processing response data. Raw server ` +
|
|
28
|
+
`response: "${this.resp.data}". Status code: "${this.statusCode}".`, '', this.statusCode);
|
|
29
|
+
}
|
|
30
|
+
return this.resp.data;
|
|
31
|
+
case "buffer":
|
|
32
|
+
if (this.resp.buffer === undefined) {
|
|
33
|
+
throw new CatalystAPIError('UNPARSABLE_RESPONSE', `Error while processing response buffer. Raw server ` +
|
|
34
|
+
`response: "${this.resp.data}". Status code: "${this.statusCode}".`, '', this.statusCode);
|
|
35
|
+
}
|
|
36
|
+
return this.resp.buffer;
|
|
37
|
+
case "raw":
|
|
38
|
+
return this.resp.stream;
|
|
39
|
+
default:
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(this.resp.data);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
throw new CatalystAPIError('UNPARSABLE_RESPONSE', `Error while parsing response data: "${inspect(e)}". Raw server ` +
|
|
45
|
+
`response: "${this.resp.data}". Status code: "${this.statusCode}".`, '', this.statusCode);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function rejectWithContext(reject, statusCode, data) {
|
|
51
|
+
try {
|
|
52
|
+
const catalystError = JSON.parse(data);
|
|
53
|
+
reject({
|
|
54
|
+
statusCode,
|
|
55
|
+
code: catalystError.data.error_code,
|
|
56
|
+
message: catalystError.data.message
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
reject({
|
|
62
|
+
statusCode,
|
|
63
|
+
message: inspect(data)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function streamToBuffer(stream) {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
stream.destroyed && reject('Invalid response stream');
|
|
71
|
+
stream.on('data', (chunk) => {
|
|
72
|
+
chunks.push(chunk);
|
|
73
|
+
});
|
|
74
|
+
stream.on('error', reject);
|
|
75
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function constructFormData(data) {
|
|
79
|
+
const formData = new FORM();
|
|
80
|
+
const keyData = Object.keys(data);
|
|
81
|
+
keyData.forEach((key) => {
|
|
82
|
+
formData.append(key, data[key]);
|
|
83
|
+
});
|
|
84
|
+
return formData;
|
|
85
|
+
}
|
|
86
|
+
async function _finalizeRequest(resolve, reject, response) {
|
|
87
|
+
if (response.statusCode === undefined) {
|
|
88
|
+
reject(new CatalystAPIError('UNKNOWN_STATUSCODE', 'unable to obtain status code from response', response));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
92
|
+
resolve(response);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (response.stream?.pipe === undefined) {
|
|
96
|
+
rejectWithContext(reject, response.statusCode, response.data);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
if (response.stream !== undefined && response.data === undefined) {
|
|
101
|
+
const responseBuffer = await streamToBuffer(response.stream);
|
|
102
|
+
response.data = responseBuffer.toString();
|
|
103
|
+
}
|
|
104
|
+
if (response.statusCode === 404) {
|
|
105
|
+
rejectWithContext(reject, response.statusCode, response.data || 'Not Found');
|
|
106
|
+
}
|
|
107
|
+
else if (response.statusCode === 403) {
|
|
108
|
+
rejectWithContext(reject, response.statusCode, response.data || 'Access Denied');
|
|
109
|
+
}
|
|
110
|
+
else if (response.statusCode === 401) {
|
|
111
|
+
rejectWithContext(reject, response.statusCode, response.data || 'Unauthorized');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
rejectWithContext(reject, response.statusCode, response.data || 'Unknown response');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
const errMsg = e instanceof Error ? e.message : inspect(e);
|
|
119
|
+
rejectWithContext(reject, response.statusCode, errMsg);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function _appendQueryData(url, data) {
|
|
123
|
+
if (data && Object.keys(data).length > 0) {
|
|
124
|
+
url += url.includes('?') ? '&' : '?';
|
|
125
|
+
url += stringify(data);
|
|
126
|
+
}
|
|
127
|
+
return url;
|
|
128
|
+
}
|
|
129
|
+
async function _request(transport, options, config, data, retryCount = 0) {
|
|
130
|
+
const clonedData = data === undefined
|
|
131
|
+
? undefined
|
|
132
|
+
: config.type !== "file"
|
|
133
|
+
?
|
|
134
|
+
data
|
|
135
|
+
:
|
|
136
|
+
data.createClone();
|
|
137
|
+
return new Promise(async (resolve, reject) => {
|
|
138
|
+
const retryRequest = async (err) => {
|
|
139
|
+
LOGGER.warn('>>> RETRYING REQUEST ');
|
|
140
|
+
if (retryCount++ === REQ_RETRY_THRESHOLD) {
|
|
141
|
+
reject(err);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
options.agent = new RequestAgent(isHttps(config.url), options.hostname, true).agent;
|
|
146
|
+
const resp = await _request(transport, options, config, clonedData, retryCount);
|
|
147
|
+
resolve(resp);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
reject(e);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const startTimeStamp = Date.now();
|
|
154
|
+
const req = transport.request(options, async (res) => {
|
|
155
|
+
if (req.destroyed) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const response = {
|
|
159
|
+
headers: res.headers,
|
|
160
|
+
request: req,
|
|
161
|
+
stream: res,
|
|
162
|
+
statusCode: res.statusCode,
|
|
163
|
+
config
|
|
164
|
+
};
|
|
165
|
+
LOGGER.debug(`>>> HTTP REQUEST : ${req.method?.toUpperCase()} ${req.protocol}//${req.host}${req.path}`);
|
|
166
|
+
process.env.ZC_SECURE?.toLowerCase() === 'override' &&
|
|
167
|
+
LOGGER.fine(`>>> REQUEST HEADERS : ${JSON.stringify(options.headers)}`);
|
|
168
|
+
LOGGER.debug(`<<< HTTP RESPONSE : ${res.statusCode} : ${Date.now() - startTimeStamp} ms`);
|
|
169
|
+
process.env.ZC_SECURE?.toLowerCase() === 'override' &&
|
|
170
|
+
LOGGER.fine(`<<< RESPONSE HEADERS : ${JSON.stringify(res.headers)}`);
|
|
171
|
+
if (config.expecting === "raw") {
|
|
172
|
+
return _finalizeRequest(resolve, reject, response);
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const responseBuffer = await streamToBuffer(res);
|
|
176
|
+
response.data = responseBuffer.toString();
|
|
177
|
+
response.buffer = responseBuffer;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (req.destroyed || (config.abortSignal && config.abortSignal.aborted)) {
|
|
181
|
+
req.destroy();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
reject(err);
|
|
185
|
+
}
|
|
186
|
+
_finalizeRequest(resolve, reject, response);
|
|
187
|
+
});
|
|
188
|
+
req.on('error', (err) => {
|
|
189
|
+
LOGGER.debug(`>>> HTTP REQUEST : ${req.method?.toUpperCase()} ${req.protocol}//${req.host}${req.path}`);
|
|
190
|
+
process.env.ZC_SECURE?.toLowerCase() === 'override' &&
|
|
191
|
+
LOGGER.fine(`>>> REQUEST HEADERS : ${JSON.stringify(options.headers)}`);
|
|
192
|
+
LOGGER.debug(`<<< HTTP REQUEST ERROR : ${inspect(err)} : ${Date.now() - startTimeStamp} ms`);
|
|
193
|
+
if (req.destroyed || config.type === "raw") {
|
|
194
|
+
return reject(err);
|
|
195
|
+
}
|
|
196
|
+
retryRequest(err);
|
|
197
|
+
});
|
|
198
|
+
if (data === undefined) {
|
|
199
|
+
req.end();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (config.type !== "file" && config.type !== "raw") {
|
|
203
|
+
req.write(data);
|
|
204
|
+
req.end();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (data instanceof ReadableStream) {
|
|
208
|
+
data = webStreamToNodeStream(data);
|
|
209
|
+
}
|
|
210
|
+
data.on('error', (er) => {
|
|
211
|
+
reject(er);
|
|
212
|
+
req.end();
|
|
213
|
+
});
|
|
214
|
+
data.pipe(req).on('finish', req.end);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function webStreamToNodeStream(webStream) {
|
|
218
|
+
const reader = webStream.getReader();
|
|
219
|
+
return new Readable({
|
|
220
|
+
async read() {
|
|
221
|
+
const { done, value } = await reader.read();
|
|
222
|
+
if (done) {
|
|
223
|
+
this.push(null);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
this.push(value);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async function sendRequest(config) {
|
|
232
|
+
let data;
|
|
233
|
+
let headers = Object.assign({
|
|
234
|
+
[USER_AGENT.KEY]: USER_AGENT.PREFIX + version
|
|
235
|
+
}, config.headers);
|
|
236
|
+
if (config.data !== undefined) {
|
|
237
|
+
switch (config.type) {
|
|
238
|
+
case "json":
|
|
239
|
+
data = JSON.stringify(config.data);
|
|
240
|
+
headers['Content-Type'] = 'application/json';
|
|
241
|
+
break;
|
|
242
|
+
case "file":
|
|
243
|
+
data = constructFormData(config.data);
|
|
244
|
+
headers = data.getHeaders(headers);
|
|
245
|
+
break;
|
|
246
|
+
case "raw":
|
|
247
|
+
data = config.data;
|
|
248
|
+
if (headers['Content-Type'] === undefined) {
|
|
249
|
+
headers['Content-Type'] = 'application/octet-stream';
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
default:
|
|
253
|
+
data = stringify(config.data);
|
|
254
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
255
|
+
headers['Content-Length'] = Buffer.byteLength(data) + '';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const origin = config.origin || CATALYST_ORIGIN;
|
|
259
|
+
config.url = config.url || new URL(config.path || '', origin).href;
|
|
260
|
+
if (config.qs !== undefined) {
|
|
261
|
+
config.url = _appendQueryData(config.url, config.qs);
|
|
262
|
+
}
|
|
263
|
+
const parsedUrl = new URL(config.url);
|
|
264
|
+
if (parsedUrl.hostname === null) {
|
|
265
|
+
throw new CatalystAPIError('UNPARSABLE_CONFIG', 'Hostname cannot be null', config.path, 400);
|
|
266
|
+
}
|
|
267
|
+
const isHttpsProtocol = isHttps(parsedUrl);
|
|
268
|
+
const requestAgent = new RequestAgent(isHttpsProtocol, parsedUrl.hostname, false);
|
|
269
|
+
parsedUrl.searchParams?.sort();
|
|
270
|
+
const options = {
|
|
271
|
+
hostname: parsedUrl.hostname,
|
|
272
|
+
port: parsedUrl.port,
|
|
273
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
274
|
+
method: config.method,
|
|
275
|
+
headers,
|
|
276
|
+
agent: requestAgent.agent
|
|
277
|
+
};
|
|
278
|
+
const transport = isHttpsProtocol ? https : http;
|
|
279
|
+
return _request(transport, options, config, data);
|
|
280
|
+
}
|
|
281
|
+
export class HttpClient {
|
|
282
|
+
constructor(app) {
|
|
283
|
+
this.app = app;
|
|
284
|
+
this.user = CREDENTIAL_USER.admin;
|
|
285
|
+
}
|
|
286
|
+
async send(req, apmTrackerName) {
|
|
287
|
+
req.headers = Object.assign({}, req.headers);
|
|
288
|
+
req.qs = Object.assign({}, req.qs);
|
|
289
|
+
req.retry = req.retry || true;
|
|
290
|
+
if (this.app !== undefined && req.service !== "external") {
|
|
291
|
+
this.user = this.app.credential.getCurrentUser();
|
|
292
|
+
req.headers[PROJECT_KEY_NAME] = this.app.config.projectKey;
|
|
293
|
+
req.headers[ENVIRONMENT_KEY_NAME] = this.app.config.environment;
|
|
294
|
+
req.headers[ENVIRONMENT] = this.app.config.environment;
|
|
295
|
+
if (isNonEmptyString(process.env.X_ZOHO_CATALYST_ORG_ID)) {
|
|
296
|
+
req.headers[X_ZOHO_CATALYST_ORG_ID] = process.env.X_ZOHO_CATALYST_ORG_ID;
|
|
297
|
+
}
|
|
298
|
+
if (isNonEmptyString(this.app.config.projectSecretKey)) {
|
|
299
|
+
req.headers[PROJECT_HEADER.projectSecretKey] = this.app.config
|
|
300
|
+
.projectSecretKey;
|
|
301
|
+
}
|
|
302
|
+
req.headers[USER_KEY_NAME] = this.app.credential.getCurrentUserType();
|
|
303
|
+
if (IS_LOCAL === 'true') {
|
|
304
|
+
switch (this.user) {
|
|
305
|
+
case CREDENTIAL_USER.admin:
|
|
306
|
+
req.origin =
|
|
307
|
+
'https://' +
|
|
308
|
+
CATALYST_ORIGIN.replace('https://', '').replace('http://', '');
|
|
309
|
+
break;
|
|
310
|
+
case CREDENTIAL_USER.user:
|
|
311
|
+
req.origin = 'https://' + this.app.config.projectDomain;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (req.service === "baas") {
|
|
316
|
+
req.headers[ACCEPT_HEADER.KEY] =
|
|
317
|
+
ACCEPT_HEADER.VALUE + ', ' + (req.headers[ACCEPT_HEADER.KEY] || '');
|
|
318
|
+
}
|
|
319
|
+
req.path =
|
|
320
|
+
getServicePath(req.service) + `/project/${this.app.config.projectId}` + req.path;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
let resp;
|
|
324
|
+
if (req.track && apmTrackerName && IS_APM === 'true') {
|
|
325
|
+
try {
|
|
326
|
+
const apminsight = await import('apminsight');
|
|
327
|
+
resp = await apminsight.startTracker(APM_INSIGHT.tracker_name, apmTrackerName, () => sendRequest(req));
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
throw new CatalystAPIError('APM_TRACKER_ERROR', 'To enable APM tracking locally, please download the apminsight package from the UI and place it in the node_modules directory of your project.', err, 400);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
resp = await sendRequest(req);
|
|
335
|
+
}
|
|
336
|
+
return new DefaultHttpResponse(resp);
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
if (err instanceof Error) {
|
|
340
|
+
throw new CatalystAPIError('REQUEST_FAILURE', err.message, err, err.message.includes('ECONNREFUSED') ? 503 : 400);
|
|
341
|
+
}
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
export class AuthorizedHttpClient extends HttpClient {
|
|
347
|
+
constructor(app, component) {
|
|
348
|
+
super(app);
|
|
349
|
+
if (component) {
|
|
350
|
+
this.componentName = component.getComponentName();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async send(request) {
|
|
354
|
+
const requestCopy = Object.assign({ user: CREDENTIAL_USER.user }, request);
|
|
355
|
+
requestCopy.headers = Object.assign({}, request.headers);
|
|
356
|
+
if (request.auth !== false) {
|
|
357
|
+
await this.app?.authenticateRequest(requestCopy);
|
|
358
|
+
}
|
|
359
|
+
return await super.send(requestCopy, this.componentName);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { ResponseHandler } from './fetch-handler';
|
|
3
|
+
export class Handler {
|
|
4
|
+
constructor(app, component) {
|
|
5
|
+
this.component = component;
|
|
6
|
+
}
|
|
7
|
+
async send(options) {
|
|
8
|
+
return (await ResponseHandler.send(options));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export { RequestType, ResponseType } from './utils/enums';
|
|
12
|
+
export { CatalystAPIError } from './utils/errors';
|
package/dist-es/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { CatalystApp, CatalystAppError, ZCAuth } from '@zcatalyst/auth';
|
|
3
|
+
import { AuthorizedHttpClient } from './http-handler';
|
|
4
|
+
import FormData from './utils/form-data';
|
|
5
|
+
export class Handler {
|
|
6
|
+
constructor(app, component) {
|
|
7
|
+
if (!app) {
|
|
8
|
+
app = new ZCAuth().getDefaultCredentials();
|
|
9
|
+
}
|
|
10
|
+
if (!(app instanceof CatalystApp)) {
|
|
11
|
+
throw new CatalystAppError('INVALID_PROJECT_CREDENTIALS', 'Unable to process the project credentials. Please verify that the initialization is configured correctly.');
|
|
12
|
+
}
|
|
13
|
+
this.app = app;
|
|
14
|
+
this.component = component;
|
|
15
|
+
}
|
|
16
|
+
async send(options) {
|
|
17
|
+
const _httpRequester = new AuthorizedHttpClient(this.app, this.component);
|
|
18
|
+
return (await _httpRequester.send(options));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export { RequestType, ResponseType } from './utils/enums';
|
|
22
|
+
export { CatalystAPIError } from './utils/errors';
|
|
23
|
+
export { FormData };
|