n8n-nodes-better-http-request 0.1.0 → 0.2.0
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/README.md +0 -1
- package/dist/nodes/BetterHttpRequest/BetterHttpRequest.node.js +209 -271
- package/dist/nodes/BetterHttpRequest/BetterHttpRequest.node.js.map +1 -1
- package/dist/nodes/BetterHttpRequest/CredentialHandler.d.ts +18 -0
- package/dist/nodes/BetterHttpRequest/CredentialHandler.js +86 -0
- package/dist/nodes/BetterHttpRequest/CredentialHandler.js.map +1 -0
- package/dist/nodes/BetterHttpRequest/DomainValidator.d.ts +4 -0
- package/dist/nodes/BetterHttpRequest/DomainValidator.js +22 -0
- package/dist/nodes/BetterHttpRequest/DomainValidator.js.map +1 -0
- package/dist/nodes/BetterHttpRequest/RequestUtils.d.ts +3 -0
- package/dist/nodes/BetterHttpRequest/RequestUtils.js +24 -0
- package/dist/nodes/BetterHttpRequest/RequestUtils.js.map +1 -0
- package/dist/nodes/BetterHttpRequest/constants.d.ts +16 -0
- package/dist/nodes/BetterHttpRequest/constants.js +30 -0
- package/dist/nodes/BetterHttpRequest/constants.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -8,38 +8,10 @@ const set_1 = __importDefault(require("lodash/set"));
|
|
|
8
8
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
9
9
|
const Description_1 = require("./Description");
|
|
10
10
|
const helpers_1 = require("./helpers");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return data;
|
|
16
|
-
}
|
|
17
|
-
function parseJsonParameter(node, jsonString, fieldName, itemIndex) {
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(jsonString);
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
const error = (0, n8n_workflow_1.ensureError)(e);
|
|
23
|
-
throw new n8n_workflow_1.NodeOperationError(node, `The value in the "${fieldName}" field is not valid JSON`, {
|
|
24
|
-
itemIndex,
|
|
25
|
-
description: error.message,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function isDomainAllowedLocal(url, opts) {
|
|
30
|
-
try {
|
|
31
|
-
const parsedUrl = new URL(url);
|
|
32
|
-
const hostname = parsedUrl.hostname.toLowerCase();
|
|
33
|
-
const allowedDomains = opts.allowedDomains
|
|
34
|
-
.split(',')
|
|
35
|
-
.map((d) => d.trim().toLowerCase())
|
|
36
|
-
.filter((d) => d.length > 0);
|
|
37
|
-
return allowedDomains.some((domain) => hostname === domain || hostname.endsWith('.' + domain));
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
11
|
+
const RequestUtils_1 = require("./RequestUtils");
|
|
12
|
+
const DomainValidator_1 = require("./DomainValidator");
|
|
13
|
+
const CredentialHandler_1 = require("./CredentialHandler");
|
|
14
|
+
const constants_1 = require("./constants");
|
|
43
15
|
class BetterHttpRequest {
|
|
44
16
|
constructor() {
|
|
45
17
|
this.description = {
|
|
@@ -71,10 +43,11 @@ class BetterHttpRequest {
|
|
|
71
43
|
};
|
|
72
44
|
}
|
|
73
45
|
async execute() {
|
|
74
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y
|
|
46
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
75
47
|
const items = this.getInputData();
|
|
76
48
|
const nodeVersion = this.getNode().typeVersion;
|
|
77
|
-
|
|
49
|
+
this.logger.debug('Starting Better HTTP Request node execution', { numberOfItems: items.length });
|
|
50
|
+
const fullResponseProperties = constants_1.FULL_RESPONSE_PROPERTIES;
|
|
78
51
|
let authentication;
|
|
79
52
|
try {
|
|
80
53
|
authentication = this.getNodeParameter('authentication', 0);
|
|
@@ -96,14 +69,14 @@ class BetterHttpRequest {
|
|
|
96
69
|
};
|
|
97
70
|
let returnItems = [];
|
|
98
71
|
const errorItems = {};
|
|
99
|
-
const
|
|
72
|
+
const requestExecutors = new Array(items.length);
|
|
100
73
|
let fullResponse = false;
|
|
101
74
|
let autoDetectResponseFormat = false;
|
|
102
75
|
let responseFileName;
|
|
103
76
|
const pagination = this.getNodeParameter('options.pagination.pagination', 0, null, {
|
|
104
77
|
rawExpressions: true,
|
|
105
78
|
});
|
|
106
|
-
const requests =
|
|
79
|
+
const requests = new Array(items.length);
|
|
107
80
|
const updadeQueryParameter = (0, helpers_1.updadeQueryParameterConfig)(nodeVersion);
|
|
108
81
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
109
82
|
try {
|
|
@@ -138,58 +111,7 @@ class BetterHttpRequest {
|
|
|
138
111
|
nodeCredentialType = this.getNodeParameter('nodeCredentialType', itemIndex);
|
|
139
112
|
}
|
|
140
113
|
const url = this.getNodeParameter('url', itemIndex);
|
|
141
|
-
|
|
142
|
-
const actualType = url === null ? 'null' : typeof url;
|
|
143
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `URL parameter must be a string, got ${actualType}`);
|
|
144
|
-
}
|
|
145
|
-
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
146
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid URL: ${url}. URL must start with "http" or "https".`);
|
|
147
|
-
}
|
|
148
|
-
const checkDomainRestrictions = async (credentialData, requestUrl, credentialType) => {
|
|
149
|
-
if (credentialData.allowedHttpRequestDomains === 'domains') {
|
|
150
|
-
const allowedDomains = credentialData.allowedDomains;
|
|
151
|
-
if (!allowedDomains || allowedDomains.trim() === '') {
|
|
152
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No allowed domains specified. Configure allowed domains or change restriction setting.');
|
|
153
|
-
}
|
|
154
|
-
if (!isDomainAllowedLocal(requestUrl, { allowedDomains })) {
|
|
155
|
-
const credentialInfo = credentialType ? ` (${credentialType})` : '';
|
|
156
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Domain not allowed: This credential${credentialInfo} is restricted from accessing ${requestUrl}. ` +
|
|
157
|
-
`Only the following domains are allowed: ${allowedDomains}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else if (credentialData.allowedHttpRequestDomains === 'none') {
|
|
161
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'This credential is configured to prevent use within an HTTP Request node');
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
if (httpBasicAuth)
|
|
165
|
-
await checkDomainRestrictions(httpBasicAuth, url);
|
|
166
|
-
if (httpBearerAuth)
|
|
167
|
-
await checkDomainRestrictions(httpBearerAuth, url);
|
|
168
|
-
if (httpDigestAuth)
|
|
169
|
-
await checkDomainRestrictions(httpDigestAuth, url);
|
|
170
|
-
if (httpHeaderAuth)
|
|
171
|
-
await checkDomainRestrictions(httpHeaderAuth, url);
|
|
172
|
-
if (httpQueryAuth)
|
|
173
|
-
await checkDomainRestrictions(httpQueryAuth, url);
|
|
174
|
-
if (httpCustomAuth)
|
|
175
|
-
await checkDomainRestrictions(httpCustomAuth, url);
|
|
176
|
-
if (oAuth1Api)
|
|
177
|
-
await checkDomainRestrictions(oAuth1Api, url);
|
|
178
|
-
if (oAuth2Api)
|
|
179
|
-
await checkDomainRestrictions(oAuth2Api, url);
|
|
180
|
-
if (nodeCredentialType) {
|
|
181
|
-
try {
|
|
182
|
-
const credentialData = await this.getCredentials(nodeCredentialType, itemIndex);
|
|
183
|
-
await checkDomainRestrictions(credentialData, url, nodeCredentialType);
|
|
184
|
-
}
|
|
185
|
-
catch (error) {
|
|
186
|
-
if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('Domain not allowed')) ||
|
|
187
|
-
((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('configured to prevent')) ||
|
|
188
|
-
((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('No allowed domains specified'))) {
|
|
189
|
-
throw error;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
114
|
+
(0, DomainValidator_1.validateUrl)(this.getNode(), url, itemIndex);
|
|
193
115
|
const provideSslCertificates = this.getNodeParameter('provideSslCertificates', itemIndex, false);
|
|
194
116
|
if (provideSslCertificates) {
|
|
195
117
|
sslCertificates = (await this.getCredentials('httpSslAuth', itemIndex));
|
|
@@ -210,12 +132,12 @@ class BetterHttpRequest {
|
|
|
210
132
|
const specifyHeaders = this.getNodeParameter('specifyHeaders', itemIndex, 'keypair');
|
|
211
133
|
const jsonHeadersParameter = this.getNodeParameter('jsonHeaders', itemIndex, '');
|
|
212
134
|
const { redirect, batching, proxy, timeout, allowUnauthorizedCerts, queryParameterArrays, response, lowercaseHeaders, sendCredentialsOnCrossOriginRedirect, } = this.getNodeParameter('options', itemIndex, {});
|
|
213
|
-
responseFileName = (
|
|
214
|
-
const responseFormat = ((
|
|
215
|
-
fullResponse = ((
|
|
135
|
+
responseFileName = (_a = response === null || response === void 0 ? void 0 : response.response) === null || _a === void 0 ? void 0 : _a.outputPropertyName;
|
|
136
|
+
const responseFormat = ((_b = response === null || response === void 0 ? void 0 : response.response) === null || _b === void 0 ? void 0 : _b.responseFormat) || 'autodetect';
|
|
137
|
+
fullResponse = ((_c = response === null || response === void 0 ? void 0 : response.response) === null || _c === void 0 ? void 0 : _c.fullResponse) || false;
|
|
216
138
|
autoDetectResponseFormat = responseFormat === 'autodetect';
|
|
217
|
-
const batchSize = ((
|
|
218
|
-
const batchInterval = (
|
|
139
|
+
const batchSize = ((_d = batching === null || batching === void 0 ? void 0 : batching.batch) === null || _d === void 0 ? void 0 : _d.batchSize) > 0 ? (_e = batching === null || batching === void 0 ? void 0 : batching.batch) === null || _e === void 0 ? void 0 : _e.batchSize : 1;
|
|
140
|
+
const batchInterval = (_f = batching === null || batching === void 0 ? void 0 : batching.batch) === null || _f === void 0 ? void 0 : _f.batchInterval;
|
|
219
141
|
if (itemIndex > 0 && batchSize >= 0 && batchInterval > 0) {
|
|
220
142
|
if (itemIndex % batchSize === 0) {
|
|
221
143
|
await (0, n8n_workflow_1.sleep)(batchInterval);
|
|
@@ -235,25 +157,20 @@ class BetterHttpRequest {
|
|
|
235
157
|
requestOptions = { ...requestOptions, followAllRedirects: false };
|
|
236
158
|
}
|
|
237
159
|
const defaultRedirect = redirect === undefined;
|
|
238
|
-
if (((
|
|
160
|
+
if (((_g = redirect === null || redirect === void 0 ? void 0 : redirect.redirect) === null || _g === void 0 ? void 0 : _g.followRedirects) || defaultRedirect) {
|
|
239
161
|
requestOptions.followRedirect = true;
|
|
240
162
|
requestOptions.followAllRedirects = true;
|
|
241
163
|
}
|
|
242
|
-
if (((
|
|
243
|
-
requestOptions.maxRedirects = (
|
|
164
|
+
if (((_h = redirect === null || redirect === void 0 ? void 0 : redirect.redirect) === null || _h === void 0 ? void 0 : _h.maxRedirects) || defaultRedirect) {
|
|
165
|
+
requestOptions.maxRedirects = (_j = redirect === null || redirect === void 0 ? void 0 : redirect.redirect) === null || _j === void 0 ? void 0 : _j.maxRedirects;
|
|
244
166
|
}
|
|
245
|
-
if ((
|
|
167
|
+
if ((_k = response === null || response === void 0 ? void 0 : response.response) === null || _k === void 0 ? void 0 : _k.neverError) {
|
|
246
168
|
requestOptions.simple = false;
|
|
247
169
|
}
|
|
248
170
|
if (proxy) {
|
|
249
171
|
requestOptions.proxy = proxy;
|
|
250
172
|
}
|
|
251
|
-
|
|
252
|
-
requestOptions.timeout = timeout;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
requestOptions.timeout = 300000;
|
|
256
|
-
}
|
|
173
|
+
requestOptions.timeout = timeout || constants_1.DEFAULT_TIMEOUT_MS;
|
|
257
174
|
if (sendQuery && queryParameterArrays) {
|
|
258
175
|
Object.assign(requestOptions, {
|
|
259
176
|
qsStringifyOptions: { arrayFormat: queryParameterArrays },
|
|
@@ -290,7 +207,7 @@ class BetterHttpRequest {
|
|
|
290
207
|
else if (specifyBody === 'json') {
|
|
291
208
|
if (typeof jsonBodyParameter !== 'object' &&
|
|
292
209
|
jsonBodyParameter !== null) {
|
|
293
|
-
requestOptions.body = parseJsonParameter(this.getNode(), jsonBodyParameter, 'JSON Body', itemIndex);
|
|
210
|
+
requestOptions.body = (0, RequestUtils_1.parseJsonParameter)(this.getNode(), jsonBodyParameter, 'JSON Body', itemIndex);
|
|
294
211
|
}
|
|
295
212
|
else {
|
|
296
213
|
requestOptions.body = jsonBodyParameter;
|
|
@@ -327,7 +244,7 @@ class BetterHttpRequest {
|
|
|
327
244
|
requestOptions.headers = {
|
|
328
245
|
...requestOptions.headers,
|
|
329
246
|
'content-length': contentLength,
|
|
330
|
-
'content-type': (
|
|
247
|
+
'content-type': (_l = itemBinaryData.mimeType) !== null && _l !== void 0 ? _l : 'application/octet-stream',
|
|
331
248
|
};
|
|
332
249
|
}
|
|
333
250
|
else if (bodyContentType === 'raw') {
|
|
@@ -339,7 +256,7 @@ class BetterHttpRequest {
|
|
|
339
256
|
requestOptions.qs = await (0, helpers_1.reduceAsync)(queryParameters, parametersToKeyValue);
|
|
340
257
|
}
|
|
341
258
|
else if (specifyQuery === 'json') {
|
|
342
|
-
requestOptions.qs = parseJsonParameter(this.getNode(), jsonQueryParameter, 'JSON Query Parameters', itemIndex);
|
|
259
|
+
requestOptions.qs = (0, RequestUtils_1.parseJsonParameter)(this.getNode(), jsonQueryParameter, 'JSON Query Parameters', itemIndex);
|
|
343
260
|
}
|
|
344
261
|
}
|
|
345
262
|
if (sendHeaders && headerParameters) {
|
|
@@ -348,7 +265,7 @@ class BetterHttpRequest {
|
|
|
348
265
|
additionalHeaders = await (0, helpers_1.reduceAsync)(headerParameters.filter((header) => header.name), parametersToKeyValue);
|
|
349
266
|
}
|
|
350
267
|
else if (specifyHeaders === 'json') {
|
|
351
|
-
additionalHeaders = parseJsonParameter(this.getNode(), jsonHeadersParameter, 'JSON Headers', itemIndex);
|
|
268
|
+
additionalHeaders = (0, RequestUtils_1.parseJsonParameter)(this.getNode(), jsonHeadersParameter, 'JSON Headers', itemIndex);
|
|
352
269
|
}
|
|
353
270
|
requestOptions.headers = {
|
|
354
271
|
...requestOptions.headers,
|
|
@@ -381,77 +298,28 @@ class BetterHttpRequest {
|
|
|
381
298
|
if (requestOptions.agentOptions) {
|
|
382
299
|
authDataKeys.agentOptions = Object.keys(requestOptions.agentOptions);
|
|
383
300
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
requestOptions.headers = (_q = requestOptions.headers) !== null && _q !== void 0 ? _q : {};
|
|
393
|
-
requestOptions.headers.Authorization = `Bearer ${String(httpBearerAuth.token)}`;
|
|
394
|
-
authDataKeys.headers = ['Authorization'];
|
|
395
|
-
}
|
|
396
|
-
if (httpHeaderAuth !== undefined) {
|
|
397
|
-
requestOptions.headers[httpHeaderAuth.name] =
|
|
398
|
-
httpHeaderAuth.value;
|
|
399
|
-
authDataKeys.headers = [httpHeaderAuth.name];
|
|
400
|
-
}
|
|
401
|
-
if (httpQueryAuth !== undefined) {
|
|
402
|
-
if (!requestOptions.qs) {
|
|
403
|
-
requestOptions.qs = {};
|
|
404
|
-
}
|
|
405
|
-
requestOptions.qs[httpQueryAuth.name] = httpQueryAuth.value;
|
|
406
|
-
authDataKeys.qs = [httpQueryAuth.name];
|
|
407
|
-
}
|
|
408
|
-
if (httpDigestAuth !== undefined) {
|
|
409
|
-
requestOptions.auth = {
|
|
410
|
-
user: httpDigestAuth.user,
|
|
411
|
-
pass: httpDigestAuth.password,
|
|
412
|
-
sendImmediately: false,
|
|
413
|
-
};
|
|
414
|
-
authDataKeys.auth = ['pass'];
|
|
415
|
-
}
|
|
416
|
-
if (httpCustomAuth !== undefined) {
|
|
417
|
-
const customAuth = (0, n8n_workflow_1.jsonParse)(httpCustomAuth.json || '{}', { errorMessage: 'Invalid Custom Auth JSON' });
|
|
418
|
-
if (customAuth.headers) {
|
|
419
|
-
requestOptions.headers = {
|
|
420
|
-
...requestOptions.headers,
|
|
421
|
-
...customAuth.headers,
|
|
422
|
-
};
|
|
423
|
-
authDataKeys.headers = Object.keys(customAuth.headers);
|
|
424
|
-
}
|
|
425
|
-
if (customAuth.body) {
|
|
426
|
-
requestOptions.body = {
|
|
427
|
-
...requestOptions.body,
|
|
428
|
-
...customAuth.body,
|
|
429
|
-
};
|
|
430
|
-
authDataKeys.body = Object.keys(customAuth.body);
|
|
431
|
-
}
|
|
432
|
-
if (customAuth.qs) {
|
|
433
|
-
requestOptions.qs = { ...requestOptions.qs, ...customAuth.qs };
|
|
434
|
-
authDataKeys.qs = Object.keys(customAuth.qs);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
301
|
+
(0, CredentialHandler_1.applyAllCredentials)(requestOptions, {
|
|
302
|
+
httpBasicAuth,
|
|
303
|
+
httpBearerAuth,
|
|
304
|
+
httpHeaderAuth,
|
|
305
|
+
httpQueryAuth,
|
|
306
|
+
httpDigestAuth,
|
|
307
|
+
httpCustomAuth,
|
|
308
|
+
}, authDataKeys);
|
|
437
309
|
if (requestOptions.headers.accept === undefined) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
requests.push({
|
|
451
|
-
options: requestOptions,
|
|
310
|
+
requestOptions.headers.accept =
|
|
311
|
+
responseFormat === 'json'
|
|
312
|
+
? constants_1.ACCEPT_HEADERS.JSON
|
|
313
|
+
: responseFormat === 'text'
|
|
314
|
+
? constants_1.ACCEPT_HEADERS.TEXT
|
|
315
|
+
: constants_1.ACCEPT_HEADERS.AUTO;
|
|
316
|
+
}
|
|
317
|
+
const itemRequestOptions = requestOptions;
|
|
318
|
+
requests[itemIndex] = {
|
|
319
|
+
options: itemRequestOptions,
|
|
452
320
|
authKeys: authDataKeys,
|
|
453
321
|
credentialType: nodeCredentialType,
|
|
454
|
-
}
|
|
322
|
+
};
|
|
455
323
|
if (pagination && pagination.paginationMode !== 'off') {
|
|
456
324
|
let continueExpression = '={{false}}';
|
|
457
325
|
if (pagination.paginationCompleteWhen === 'receiveSpecificStatusCodes') {
|
|
@@ -472,7 +340,7 @@ class BetterHttpRequest {
|
|
|
472
340
|
const completionExpression = pagination.completeExpression
|
|
473
341
|
.trim()
|
|
474
342
|
.slice(3, -2);
|
|
475
|
-
if ((
|
|
343
|
+
if ((_m = response === null || response === void 0 ? void 0 : response.response) === null || _m === void 0 ? void 0 : _m.neverError) {
|
|
476
344
|
continueExpression = `={{ !(${completionExpression}) }}`;
|
|
477
345
|
}
|
|
478
346
|
else {
|
|
@@ -517,83 +385,132 @@ class BetterHttpRequest {
|
|
|
517
385
|
if (responseFormat === 'file') {
|
|
518
386
|
paginationData.binaryResult = true;
|
|
519
387
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
error
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
388
|
+
requestExecutors[itemIndex] = async () => {
|
|
389
|
+
return await this.helpers.requestWithAuthenticationPaginated
|
|
390
|
+
.call(this, itemRequestOptions, itemIndex, paginationData, nodeCredentialType !== null && nodeCredentialType !== void 0 ? nodeCredentialType : genericCredentialType)
|
|
391
|
+
.catch((error) => {
|
|
392
|
+
if (error instanceof n8n_workflow_1.NodeOperationError &&
|
|
393
|
+
error.type === 'invalid_url') {
|
|
394
|
+
const urlParameterName = pagination.paginationMode ===
|
|
395
|
+
'responseContainsNextURL'
|
|
396
|
+
? 'Next URL'
|
|
397
|
+
: 'URL';
|
|
398
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, {
|
|
399
|
+
description: `Make sure the "${urlParameterName}" parameter evaluates to a valid URL.`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
throw error;
|
|
403
|
+
});
|
|
404
|
+
};
|
|
536
405
|
}
|
|
537
406
|
else if (authentication === 'genericCredentialType' ||
|
|
538
407
|
authentication === 'none') {
|
|
539
408
|
if (oAuth1Api) {
|
|
540
|
-
|
|
541
|
-
requestOAuth1.catch(() => { });
|
|
542
|
-
requestPromises.push(requestOAuth1);
|
|
409
|
+
requestExecutors[itemIndex] = async () => await this.helpers.requestOAuth1.call(this, 'oAuth1Api', itemRequestOptions);
|
|
543
410
|
}
|
|
544
411
|
else if (oAuth2Api) {
|
|
545
|
-
|
|
546
|
-
requestOAuth2.catch(() => { });
|
|
547
|
-
requestPromises.push(requestOAuth2);
|
|
412
|
+
requestExecutors[itemIndex] = async () => await this.helpers.requestOAuth2.call(this, 'oAuth2Api', itemRequestOptions, { tokenType: 'Bearer' });
|
|
548
413
|
}
|
|
549
414
|
else {
|
|
550
|
-
|
|
551
|
-
request.catch(() => { });
|
|
552
|
-
requestPromises.push(request);
|
|
415
|
+
requestExecutors[itemIndex] = async () => await this.helpers.request(itemRequestOptions);
|
|
553
416
|
}
|
|
554
417
|
}
|
|
555
418
|
else if (authentication === 'predefinedCredentialType' &&
|
|
556
419
|
nodeCredentialType) {
|
|
557
|
-
const
|
|
558
|
-
const
|
|
420
|
+
const credentialType = nodeCredentialType;
|
|
421
|
+
const additionalOAuth2Options = (0, helpers_1.getOAuth2AdditionalParameters)(credentialType);
|
|
422
|
+
requestExecutors[itemIndex] = async () => await this.helpers.requestWithAuthentication.call(this, credentialType, itemRequestOptions, additionalOAuth2Options && {
|
|
559
423
|
oauth2: additionalOAuth2Options,
|
|
560
424
|
}, itemIndex);
|
|
561
|
-
requestWithAuthentication.catch(() => { });
|
|
562
|
-
requestPromises.push(requestWithAuthentication);
|
|
563
425
|
}
|
|
564
426
|
}
|
|
565
427
|
catch (error) {
|
|
566
428
|
if (!this.continueOnFail())
|
|
567
429
|
throw error;
|
|
568
|
-
requestPromises.push(Promise.reject(error).catch(() => { }));
|
|
569
430
|
errorItems[itemIndex] = error.message;
|
|
431
|
+
this.logger.warn(`Failed to process item ${itemIndex}`, { error: error.message });
|
|
570
432
|
continue;
|
|
571
433
|
}
|
|
572
434
|
}
|
|
573
|
-
const sanitizedRequests =
|
|
574
|
-
const promisesResponses =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
435
|
+
const sanitizedRequests = new Array(items.length);
|
|
436
|
+
const promisesResponses = new Array(items.length);
|
|
437
|
+
const inFlightTasks = new Set();
|
|
438
|
+
let completedCount = 0;
|
|
439
|
+
const totalCount = items.length;
|
|
440
|
+
const reportProgress = () => {
|
|
441
|
+
const percentage = Math.round((completedCount / totalCount) * 100);
|
|
442
|
+
this.sendMessageToUI({
|
|
443
|
+
type: 'progress',
|
|
444
|
+
message: `${percentage}% complete (${completedCount}/${totalCount} items)`,
|
|
445
|
+
percentage,
|
|
446
|
+
completed: completedCount,
|
|
447
|
+
total: totalCount,
|
|
448
|
+
});
|
|
449
|
+
};
|
|
450
|
+
const executeRequestWithTracking = async (itemIndex, executor) => {
|
|
451
|
+
this.logger.debug(`Executing request for item ${itemIndex}`);
|
|
579
452
|
try {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
453
|
+
const value = await executor();
|
|
454
|
+
promisesResponses[itemIndex] = {
|
|
455
|
+
status: 'fulfilled',
|
|
456
|
+
value,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
catch (reason) {
|
|
460
|
+
promisesResponses[itemIndex] = {
|
|
461
|
+
status: 'rejected',
|
|
462
|
+
reason,
|
|
463
|
+
};
|
|
464
|
+
this.logger.debug(`Request failed for item ${itemIndex}`, { error: reason });
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
if (errorItems[itemIndex])
|
|
468
|
+
return;
|
|
469
|
+
try {
|
|
470
|
+
const requestData = requests[itemIndex];
|
|
471
|
+
if (!requestData)
|
|
472
|
+
return;
|
|
473
|
+
const { options, authKeys, credentialType } = requestData;
|
|
474
|
+
let secrets = [];
|
|
475
|
+
if (credentialType) {
|
|
476
|
+
const properties = this.getCredentialsProperties(credentialType);
|
|
477
|
+
const credentials = await this.getCredentials(credentialType, itemIndex);
|
|
478
|
+
secrets = (0, helpers_1.getSecrets)(properties, credentials);
|
|
479
|
+
}
|
|
480
|
+
const sanitizedRequestOptions = (0, helpers_1.sanitizeUiMessage)(options, authKeys, secrets);
|
|
481
|
+
sanitizedRequests[itemIndex] = sanitizedRequestOptions;
|
|
482
|
+
this.sendMessageToUI(sanitizedRequestOptions);
|
|
483
|
+
}
|
|
484
|
+
catch { }
|
|
485
|
+
completedCount++;
|
|
486
|
+
reportProgress();
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
490
|
+
const executor = requestExecutors[itemIndex];
|
|
491
|
+
if (errorItems[itemIndex] || !executor) {
|
|
492
|
+
promisesResponses[itemIndex] = {
|
|
493
|
+
status: 'fulfilled',
|
|
494
|
+
value: undefined,
|
|
495
|
+
};
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
while (inFlightTasks.size >= constants_1.MAX_CONCURRENT_REQUESTS) {
|
|
499
|
+
await Promise.race(inFlightTasks);
|
|
590
500
|
}
|
|
591
|
-
|
|
592
|
-
|
|
501
|
+
let task;
|
|
502
|
+
task = executeRequestWithTracking(itemIndex, executor).finally(() => {
|
|
503
|
+
inFlightTasks.delete(task);
|
|
504
|
+
});
|
|
505
|
+
inFlightTasks.add(task);
|
|
506
|
+
}
|
|
507
|
+
if (inFlightTasks.size > 0) {
|
|
508
|
+
await Promise.all(inFlightTasks);
|
|
509
|
+
}
|
|
593
510
|
let responseData;
|
|
594
511
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
595
512
|
try {
|
|
596
|
-
responseData = promisesResponses
|
|
513
|
+
responseData = promisesResponses[itemIndex];
|
|
597
514
|
if (errorItems[itemIndex]) {
|
|
598
515
|
returnItems.push({
|
|
599
516
|
json: { error: errorItems[itemIndex] },
|
|
@@ -603,8 +520,7 @@ class BetterHttpRequest {
|
|
|
603
520
|
}
|
|
604
521
|
if (responseData.status !== 'fulfilled') {
|
|
605
522
|
if (responseData.reason.statusCode === 429) {
|
|
606
|
-
responseData.reason.message =
|
|
607
|
-
"Try spacing your requests out using the batching settings under 'Options'";
|
|
523
|
+
responseData.reason.message = constants_1.UI_MESSAGES.RATE_LIMITED_HINT;
|
|
608
524
|
}
|
|
609
525
|
if (!this.continueOnFail()) {
|
|
610
526
|
if (autoDetectResponseFormat &&
|
|
@@ -639,7 +555,7 @@ class BetterHttpRequest {
|
|
|
639
555
|
...(reason.code !== undefined ? { code: reason.code } : {}),
|
|
640
556
|
...(reason.description !== undefined ? { description: reason.description } : {}),
|
|
641
557
|
...(reason.headers ? { headers: reason.headers } : {}),
|
|
642
|
-
...(((
|
|
558
|
+
...(((_o = reason.response) === null || _o === void 0 ? void 0 : _o.headers) ? { response: { headers: reason.response.headers } } : {}),
|
|
643
559
|
};
|
|
644
560
|
}
|
|
645
561
|
else if (typeof reason === 'object' && reason !== null) {
|
|
@@ -661,17 +577,13 @@ class BetterHttpRequest {
|
|
|
661
577
|
continue;
|
|
662
578
|
}
|
|
663
579
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
responses = [responseData.value];
|
|
670
|
-
}
|
|
580
|
+
const responses = Array.isArray(responseData.value)
|
|
581
|
+
? responseData.value
|
|
582
|
+
: [responseData.value];
|
|
671
583
|
let responseFormat = this.getNodeParameter('options.response.response.responseFormat', 0, 'autodetect');
|
|
672
584
|
fullResponse = this.getNodeParameter('options.response.response.fullResponse', 0, false);
|
|
673
585
|
for (let [index, response] of Object.entries(responses)) {
|
|
674
|
-
if (((
|
|
586
|
+
if (((_p = response === null || response === void 0 ? void 0 : response.request) === null || _p === void 0 ? void 0 : _p.constructor.name) === 'ClientRequest')
|
|
675
587
|
delete response.request;
|
|
676
588
|
if (this.getMode() === 'manual' && index === '0') {
|
|
677
589
|
const nodeContext = this.getContext('node');
|
|
@@ -682,7 +594,7 @@ class BetterHttpRequest {
|
|
|
682
594
|
nodeContext.response = responseData.value;
|
|
683
595
|
}
|
|
684
596
|
}
|
|
685
|
-
const responseContentType = (
|
|
597
|
+
const responseContentType = (_r = (_q = response.headers) === null || _q === void 0 ? void 0 : _q['content-type']) !== null && _r !== void 0 ? _r : '';
|
|
686
598
|
if (autoDetectResponseFormat) {
|
|
687
599
|
if (responseContentType.includes('application/json')) {
|
|
688
600
|
responseFormat = 'json';
|
|
@@ -758,7 +670,7 @@ class BetterHttpRequest {
|
|
|
758
670
|
const returnItem = {};
|
|
759
671
|
for (const property of fullResponseProperties) {
|
|
760
672
|
if (property === 'body') {
|
|
761
|
-
returnItem[outputPropertyName] = toText(response[property]);
|
|
673
|
+
returnItem[outputPropertyName] = (0, RequestUtils_1.toText)(response[property]);
|
|
762
674
|
continue;
|
|
763
675
|
}
|
|
764
676
|
returnItem[property] = response[property];
|
|
@@ -771,7 +683,7 @@ class BetterHttpRequest {
|
|
|
771
683
|
else {
|
|
772
684
|
returnItems.push({
|
|
773
685
|
json: {
|
|
774
|
-
[outputPropertyName]: toText(response),
|
|
686
|
+
[outputPropertyName]: (0, RequestUtils_1.toText)(response),
|
|
775
687
|
},
|
|
776
688
|
pairedItem: { item: itemIndex },
|
|
777
689
|
});
|
|
@@ -829,7 +741,13 @@ class BetterHttpRequest {
|
|
|
829
741
|
if (!this.continueOnFail())
|
|
830
742
|
throw error;
|
|
831
743
|
returnItems.push({
|
|
832
|
-
json: {
|
|
744
|
+
json: {
|
|
745
|
+
error: {
|
|
746
|
+
message: error.message,
|
|
747
|
+
code: error.code,
|
|
748
|
+
statusCode: error.statusCode,
|
|
749
|
+
},
|
|
750
|
+
},
|
|
833
751
|
pairedItem: { item: itemIndex },
|
|
834
752
|
});
|
|
835
753
|
continue;
|
|
@@ -837,13 +755,19 @@ class BetterHttpRequest {
|
|
|
837
755
|
}
|
|
838
756
|
const retryOnFail = this.getNodeParameter('options.retryOnFail', 0, false);
|
|
839
757
|
if (retryOnFail && this.continueOnFail()) {
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
758
|
+
const getOriginalItemIndex = (item, fallback) => item.pairedItem &&
|
|
759
|
+
typeof item.pairedItem === 'object' &&
|
|
760
|
+
!Array.isArray(item.pairedItem)
|
|
761
|
+
? item.pairedItem.item
|
|
762
|
+
: fallback;
|
|
763
|
+
const maxRetries = this.getNodeParameter('options.maxRetries', 0, constants_1.DEFAULT_MAX_RETRIES);
|
|
764
|
+
const retryDelay = this.getNodeParameter('options.retryDelay', 0, constants_1.DEFAULT_RETRY_DELAY);
|
|
765
|
+
const retryOnStatusCodesStr = this.getNodeParameter('options.retryOnStatusCodes', 0, constants_1.DEFAULT_RETRY_ON_STATUS_CODES);
|
|
843
766
|
const retryOnStatusCodes = new Set(retryOnStatusCodesStr
|
|
844
767
|
.split(',')
|
|
845
768
|
.map((s) => parseInt(s.trim(), 10))
|
|
846
769
|
.filter((n) => !isNaN(n)));
|
|
770
|
+
const retryOnErrorCodes = new Set(constants_1.RETRYABLE_CONNECTION_ERRORS);
|
|
847
771
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
848
772
|
const failedIndices = [];
|
|
849
773
|
for (let i = 0; i < returnItems.length; i++) {
|
|
@@ -851,25 +775,30 @@ class BetterHttpRequest {
|
|
|
851
775
|
if (item.json && item.json.error) {
|
|
852
776
|
const errObj = item.json.error;
|
|
853
777
|
let statusCode;
|
|
778
|
+
let errorCode;
|
|
854
779
|
if (typeof errObj === 'object' && errObj !== null) {
|
|
855
780
|
statusCode =
|
|
856
|
-
(
|
|
781
|
+
(_s = errObj.statusCode) !== null && _s !== void 0 ? _s : errObj.httpCode;
|
|
782
|
+
errorCode = errObj.code;
|
|
857
783
|
}
|
|
858
|
-
if (statusCode !== undefined &&
|
|
859
|
-
retryOnStatusCodes.has(statusCode))
|
|
784
|
+
if ((statusCode !== undefined &&
|
|
785
|
+
retryOnStatusCodes.has(statusCode)) ||
|
|
786
|
+
(errorCode !== undefined &&
|
|
787
|
+
retryOnErrorCodes.has(errorCode))) {
|
|
860
788
|
failedIndices.push(i);
|
|
861
789
|
}
|
|
862
790
|
}
|
|
863
791
|
}
|
|
864
792
|
if (failedIndices.length === 0)
|
|
865
793
|
break;
|
|
794
|
+
this.logger.info(`Retrying ${failedIndices.length} failed items, attempt ${attempt + 1} of ${maxRetries}`);
|
|
866
795
|
let effectiveDelay = retryDelay;
|
|
867
796
|
for (const idx of failedIndices) {
|
|
868
797
|
const errObj = returnItems[idx].json.error;
|
|
869
798
|
if (typeof errObj === 'object' && errObj !== null) {
|
|
870
799
|
const sc = errObj.statusCode;
|
|
871
800
|
if (sc === 429) {
|
|
872
|
-
const retryAfterHeader = (
|
|
801
|
+
const retryAfterHeader = (_u = (_t = errObj.headers) === null || _t === void 0 ? void 0 : _t['retry-after']) !== null && _u !== void 0 ? _u : (_w = (_v = errObj.response) === null || _v === void 0 ? void 0 : _v.headers) === null || _w === void 0 ? void 0 : _w['retry-after'];
|
|
873
802
|
if (retryAfterHeader) {
|
|
874
803
|
const retryAfterSeconds = parseInt(retryAfterHeader, 10);
|
|
875
804
|
if (!isNaN(retryAfterSeconds)) {
|
|
@@ -884,12 +813,7 @@ class BetterHttpRequest {
|
|
|
884
813
|
}
|
|
885
814
|
const retryPromises = [];
|
|
886
815
|
for (const idx of failedIndices) {
|
|
887
|
-
const originalItemIndex = returnItems[idx]
|
|
888
|
-
typeof returnItems[idx].pairedItem === 'object' &&
|
|
889
|
-
!Array.isArray(returnItems[idx].pairedItem)
|
|
890
|
-
? returnItems[idx].pairedItem
|
|
891
|
-
.item
|
|
892
|
-
: idx;
|
|
816
|
+
const originalItemIndex = getOriginalItemIndex(returnItems[idx], idx);
|
|
893
817
|
if (requests[originalItemIndex]) {
|
|
894
818
|
const { options } = requests[originalItemIndex];
|
|
895
819
|
const retryRequest = this.helpers
|
|
@@ -905,12 +829,7 @@ class BetterHttpRequest {
|
|
|
905
829
|
for (let ri = 0; ri < retryResults.length; ri++) {
|
|
906
830
|
const result = retryResults[ri];
|
|
907
831
|
const idx = retryPromises[ri].index;
|
|
908
|
-
const originalItemIndex = returnItems[idx]
|
|
909
|
-
typeof returnItems[idx].pairedItem === 'object' &&
|
|
910
|
-
!Array.isArray(returnItems[idx].pairedItem)
|
|
911
|
-
? returnItems[idx].pairedItem
|
|
912
|
-
.item
|
|
913
|
-
: idx;
|
|
832
|
+
const originalItemIndex = getOriginalItemIndex(returnItems[idx], idx);
|
|
914
833
|
if (result.status === 'fulfilled' && result.value != null) {
|
|
915
834
|
const response = result.value;
|
|
916
835
|
if (typeof response === 'object' &&
|
|
@@ -919,7 +838,7 @@ class BetterHttpRequest {
|
|
|
919
838
|
let responseFormat = this.getNodeParameter('options.response.response.responseFormat', 0, 'autodetect');
|
|
920
839
|
const currentFullResponse = this.getNodeParameter('options.response.response.fullResponse', 0, false);
|
|
921
840
|
if (responseFormat === 'autodetect') {
|
|
922
|
-
const ct = (
|
|
841
|
+
const ct = (_y = (_x = response.headers) === null || _x === void 0 ? void 0 : _x['content-type']) !== null && _y !== void 0 ? _y : '';
|
|
923
842
|
if (ct.includes('application/json')) {
|
|
924
843
|
responseFormat = 'json';
|
|
925
844
|
}
|
|
@@ -963,12 +882,22 @@ class BetterHttpRequest {
|
|
|
963
882
|
}
|
|
964
883
|
if (typeof bodyData === 'object' &&
|
|
965
884
|
bodyData !== null) {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
885
|
+
if (Object.keys(bodyData).length === 0) {
|
|
886
|
+
returnItems[idx] = {
|
|
887
|
+
json: { item: originalItemIndex },
|
|
888
|
+
pairedItem: {
|
|
889
|
+
item: originalItemIndex,
|
|
890
|
+
},
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
returnItems[idx] = {
|
|
895
|
+
json: bodyData,
|
|
896
|
+
pairedItem: {
|
|
897
|
+
item: originalItemIndex,
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
}
|
|
972
901
|
}
|
|
973
902
|
else {
|
|
974
903
|
returnItems[idx] = {
|
|
@@ -982,10 +911,18 @@ class BetterHttpRequest {
|
|
|
982
911
|
}
|
|
983
912
|
else if (typeof response === 'object' &&
|
|
984
913
|
response !== null) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
914
|
+
if (Object.keys(response).length === 0) {
|
|
915
|
+
returnItems[idx] = {
|
|
916
|
+
json: { item: originalItemIndex },
|
|
917
|
+
pairedItem: { item: originalItemIndex },
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
returnItems[idx] = {
|
|
922
|
+
json: response,
|
|
923
|
+
pairedItem: { item: originalItemIndex },
|
|
924
|
+
};
|
|
925
|
+
}
|
|
989
926
|
}
|
|
990
927
|
else {
|
|
991
928
|
try {
|
|
@@ -1011,10 +948,11 @@ class BetterHttpRequest {
|
|
|
1011
948
|
}
|
|
1012
949
|
}
|
|
1013
950
|
returnItems = returnItems.map(helpers_1.replaceNullValues);
|
|
951
|
+
this.logger.debug('Better HTTP Request node execution finished', { returnItemsLength: returnItems.length });
|
|
1014
952
|
if (returnItems.length === 1 &&
|
|
1015
953
|
returnItems[0].json.data &&
|
|
1016
954
|
Array.isArray(returnItems[0].json.data)) {
|
|
1017
|
-
const message =
|
|
955
|
+
const message = constants_1.UI_MESSAGES.SPLIT_OUT_HINT;
|
|
1018
956
|
if (this.addExecutionHints) {
|
|
1019
957
|
this.addExecutionHints({
|
|
1020
958
|
message,
|