n8n-nodes-better-http-request 0.1.0 → 0.3.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 +210 -270
- 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,134 @@ 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
|
-
|
|
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
|
+
try {
|
|
469
|
+
const requestData = requests[itemIndex];
|
|
470
|
+
if (requestData) {
|
|
471
|
+
const { options, authKeys, credentialType } = requestData;
|
|
472
|
+
let secrets = [];
|
|
473
|
+
if (credentialType) {
|
|
474
|
+
const properties = this.getCredentialsProperties(credentialType);
|
|
475
|
+
const credentials = await this.getCredentials(credentialType, itemIndex);
|
|
476
|
+
secrets = (0, helpers_1.getSecrets)(properties, credentials);
|
|
477
|
+
}
|
|
478
|
+
const sanitizedRequestOptions = (0, helpers_1.sanitizeUiMessage)(options, authKeys, secrets);
|
|
479
|
+
sanitizedRequests[itemIndex] = sanitizedRequestOptions;
|
|
480
|
+
this.sendMessageToUI(sanitizedRequestOptions);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch { }
|
|
586
484
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
completedCount++;
|
|
497
|
+
reportProgress();
|
|
498
|
+
continue;
|
|
590
499
|
}
|
|
591
|
-
|
|
592
|
-
|
|
500
|
+
while (inFlightTasks.size >= constants_1.MAX_CONCURRENT_REQUESTS) {
|
|
501
|
+
await Promise.race(inFlightTasks);
|
|
502
|
+
}
|
|
503
|
+
let task;
|
|
504
|
+
task = executeRequestWithTracking(itemIndex, executor).finally(() => {
|
|
505
|
+
inFlightTasks.delete(task);
|
|
506
|
+
});
|
|
507
|
+
inFlightTasks.add(task);
|
|
508
|
+
}
|
|
509
|
+
if (inFlightTasks.size > 0) {
|
|
510
|
+
await Promise.all(inFlightTasks);
|
|
511
|
+
}
|
|
593
512
|
let responseData;
|
|
594
513
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
595
514
|
try {
|
|
596
|
-
responseData = promisesResponses
|
|
515
|
+
responseData = promisesResponses[itemIndex];
|
|
597
516
|
if (errorItems[itemIndex]) {
|
|
598
517
|
returnItems.push({
|
|
599
518
|
json: { error: errorItems[itemIndex] },
|
|
@@ -603,8 +522,7 @@ class BetterHttpRequest {
|
|
|
603
522
|
}
|
|
604
523
|
if (responseData.status !== 'fulfilled') {
|
|
605
524
|
if (responseData.reason.statusCode === 429) {
|
|
606
|
-
responseData.reason.message =
|
|
607
|
-
"Try spacing your requests out using the batching settings under 'Options'";
|
|
525
|
+
responseData.reason.message = constants_1.UI_MESSAGES.RATE_LIMITED_HINT;
|
|
608
526
|
}
|
|
609
527
|
if (!this.continueOnFail()) {
|
|
610
528
|
if (autoDetectResponseFormat &&
|
|
@@ -639,7 +557,7 @@ class BetterHttpRequest {
|
|
|
639
557
|
...(reason.code !== undefined ? { code: reason.code } : {}),
|
|
640
558
|
...(reason.description !== undefined ? { description: reason.description } : {}),
|
|
641
559
|
...(reason.headers ? { headers: reason.headers } : {}),
|
|
642
|
-
...(((
|
|
560
|
+
...(((_o = reason.response) === null || _o === void 0 ? void 0 : _o.headers) ? { response: { headers: reason.response.headers } } : {}),
|
|
643
561
|
};
|
|
644
562
|
}
|
|
645
563
|
else if (typeof reason === 'object' && reason !== null) {
|
|
@@ -661,17 +579,13 @@ class BetterHttpRequest {
|
|
|
661
579
|
continue;
|
|
662
580
|
}
|
|
663
581
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
responses = [responseData.value];
|
|
670
|
-
}
|
|
582
|
+
const responses = Array.isArray(responseData.value)
|
|
583
|
+
? responseData.value
|
|
584
|
+
: [responseData.value];
|
|
671
585
|
let responseFormat = this.getNodeParameter('options.response.response.responseFormat', 0, 'autodetect');
|
|
672
586
|
fullResponse = this.getNodeParameter('options.response.response.fullResponse', 0, false);
|
|
673
587
|
for (let [index, response] of Object.entries(responses)) {
|
|
674
|
-
if (((
|
|
588
|
+
if (((_p = response === null || response === void 0 ? void 0 : response.request) === null || _p === void 0 ? void 0 : _p.constructor.name) === 'ClientRequest')
|
|
675
589
|
delete response.request;
|
|
676
590
|
if (this.getMode() === 'manual' && index === '0') {
|
|
677
591
|
const nodeContext = this.getContext('node');
|
|
@@ -682,7 +596,7 @@ class BetterHttpRequest {
|
|
|
682
596
|
nodeContext.response = responseData.value;
|
|
683
597
|
}
|
|
684
598
|
}
|
|
685
|
-
const responseContentType = (
|
|
599
|
+
const responseContentType = (_r = (_q = response.headers) === null || _q === void 0 ? void 0 : _q['content-type']) !== null && _r !== void 0 ? _r : '';
|
|
686
600
|
if (autoDetectResponseFormat) {
|
|
687
601
|
if (responseContentType.includes('application/json')) {
|
|
688
602
|
responseFormat = 'json';
|
|
@@ -758,7 +672,7 @@ class BetterHttpRequest {
|
|
|
758
672
|
const returnItem = {};
|
|
759
673
|
for (const property of fullResponseProperties) {
|
|
760
674
|
if (property === 'body') {
|
|
761
|
-
returnItem[outputPropertyName] = toText(response[property]);
|
|
675
|
+
returnItem[outputPropertyName] = (0, RequestUtils_1.toText)(response[property]);
|
|
762
676
|
continue;
|
|
763
677
|
}
|
|
764
678
|
returnItem[property] = response[property];
|
|
@@ -771,7 +685,7 @@ class BetterHttpRequest {
|
|
|
771
685
|
else {
|
|
772
686
|
returnItems.push({
|
|
773
687
|
json: {
|
|
774
|
-
[outputPropertyName]: toText(response),
|
|
688
|
+
[outputPropertyName]: (0, RequestUtils_1.toText)(response),
|
|
775
689
|
},
|
|
776
690
|
pairedItem: { item: itemIndex },
|
|
777
691
|
});
|
|
@@ -829,7 +743,13 @@ class BetterHttpRequest {
|
|
|
829
743
|
if (!this.continueOnFail())
|
|
830
744
|
throw error;
|
|
831
745
|
returnItems.push({
|
|
832
|
-
json: {
|
|
746
|
+
json: {
|
|
747
|
+
error: {
|
|
748
|
+
message: error.message,
|
|
749
|
+
code: error.code,
|
|
750
|
+
statusCode: error.statusCode,
|
|
751
|
+
},
|
|
752
|
+
},
|
|
833
753
|
pairedItem: { item: itemIndex },
|
|
834
754
|
});
|
|
835
755
|
continue;
|
|
@@ -837,13 +757,19 @@ class BetterHttpRequest {
|
|
|
837
757
|
}
|
|
838
758
|
const retryOnFail = this.getNodeParameter('options.retryOnFail', 0, false);
|
|
839
759
|
if (retryOnFail && this.continueOnFail()) {
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
760
|
+
const getOriginalItemIndex = (item, fallback) => item.pairedItem &&
|
|
761
|
+
typeof item.pairedItem === 'object' &&
|
|
762
|
+
!Array.isArray(item.pairedItem)
|
|
763
|
+
? item.pairedItem.item
|
|
764
|
+
: fallback;
|
|
765
|
+
const maxRetries = this.getNodeParameter('options.maxRetries', 0, constants_1.DEFAULT_MAX_RETRIES);
|
|
766
|
+
const retryDelay = this.getNodeParameter('options.retryDelay', 0, constants_1.DEFAULT_RETRY_DELAY);
|
|
767
|
+
const retryOnStatusCodesStr = this.getNodeParameter('options.retryOnStatusCodes', 0, constants_1.DEFAULT_RETRY_ON_STATUS_CODES);
|
|
843
768
|
const retryOnStatusCodes = new Set(retryOnStatusCodesStr
|
|
844
769
|
.split(',')
|
|
845
770
|
.map((s) => parseInt(s.trim(), 10))
|
|
846
771
|
.filter((n) => !isNaN(n)));
|
|
772
|
+
const retryOnErrorCodes = new Set(constants_1.RETRYABLE_CONNECTION_ERRORS);
|
|
847
773
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
848
774
|
const failedIndices = [];
|
|
849
775
|
for (let i = 0; i < returnItems.length; i++) {
|
|
@@ -851,25 +777,30 @@ class BetterHttpRequest {
|
|
|
851
777
|
if (item.json && item.json.error) {
|
|
852
778
|
const errObj = item.json.error;
|
|
853
779
|
let statusCode;
|
|
780
|
+
let errorCode;
|
|
854
781
|
if (typeof errObj === 'object' && errObj !== null) {
|
|
855
782
|
statusCode =
|
|
856
|
-
(
|
|
783
|
+
(_s = errObj.statusCode) !== null && _s !== void 0 ? _s : errObj.httpCode;
|
|
784
|
+
errorCode = errObj.code;
|
|
857
785
|
}
|
|
858
|
-
if (statusCode !== undefined &&
|
|
859
|
-
retryOnStatusCodes.has(statusCode))
|
|
786
|
+
if ((statusCode !== undefined &&
|
|
787
|
+
retryOnStatusCodes.has(statusCode)) ||
|
|
788
|
+
(errorCode !== undefined &&
|
|
789
|
+
retryOnErrorCodes.has(errorCode))) {
|
|
860
790
|
failedIndices.push(i);
|
|
861
791
|
}
|
|
862
792
|
}
|
|
863
793
|
}
|
|
864
794
|
if (failedIndices.length === 0)
|
|
865
795
|
break;
|
|
796
|
+
this.logger.info(`Retrying ${failedIndices.length} failed items, attempt ${attempt + 1} of ${maxRetries}`);
|
|
866
797
|
let effectiveDelay = retryDelay;
|
|
867
798
|
for (const idx of failedIndices) {
|
|
868
799
|
const errObj = returnItems[idx].json.error;
|
|
869
800
|
if (typeof errObj === 'object' && errObj !== null) {
|
|
870
801
|
const sc = errObj.statusCode;
|
|
871
802
|
if (sc === 429) {
|
|
872
|
-
const retryAfterHeader = (
|
|
803
|
+
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
804
|
if (retryAfterHeader) {
|
|
874
805
|
const retryAfterSeconds = parseInt(retryAfterHeader, 10);
|
|
875
806
|
if (!isNaN(retryAfterSeconds)) {
|
|
@@ -884,12 +815,7 @@ class BetterHttpRequest {
|
|
|
884
815
|
}
|
|
885
816
|
const retryPromises = [];
|
|
886
817
|
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;
|
|
818
|
+
const originalItemIndex = getOriginalItemIndex(returnItems[idx], idx);
|
|
893
819
|
if (requests[originalItemIndex]) {
|
|
894
820
|
const { options } = requests[originalItemIndex];
|
|
895
821
|
const retryRequest = this.helpers
|
|
@@ -905,12 +831,7 @@ class BetterHttpRequest {
|
|
|
905
831
|
for (let ri = 0; ri < retryResults.length; ri++) {
|
|
906
832
|
const result = retryResults[ri];
|
|
907
833
|
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;
|
|
834
|
+
const originalItemIndex = getOriginalItemIndex(returnItems[idx], idx);
|
|
914
835
|
if (result.status === 'fulfilled' && result.value != null) {
|
|
915
836
|
const response = result.value;
|
|
916
837
|
if (typeof response === 'object' &&
|
|
@@ -919,7 +840,7 @@ class BetterHttpRequest {
|
|
|
919
840
|
let responseFormat = this.getNodeParameter('options.response.response.responseFormat', 0, 'autodetect');
|
|
920
841
|
const currentFullResponse = this.getNodeParameter('options.response.response.fullResponse', 0, false);
|
|
921
842
|
if (responseFormat === 'autodetect') {
|
|
922
|
-
const ct = (
|
|
843
|
+
const ct = (_y = (_x = response.headers) === null || _x === void 0 ? void 0 : _x['content-type']) !== null && _y !== void 0 ? _y : '';
|
|
923
844
|
if (ct.includes('application/json')) {
|
|
924
845
|
responseFormat = 'json';
|
|
925
846
|
}
|
|
@@ -963,12 +884,22 @@ class BetterHttpRequest {
|
|
|
963
884
|
}
|
|
964
885
|
if (typeof bodyData === 'object' &&
|
|
965
886
|
bodyData !== null) {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
887
|
+
if (Object.keys(bodyData).length === 0) {
|
|
888
|
+
returnItems[idx] = {
|
|
889
|
+
json: { item: originalItemIndex },
|
|
890
|
+
pairedItem: {
|
|
891
|
+
item: originalItemIndex,
|
|
892
|
+
},
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
returnItems[idx] = {
|
|
897
|
+
json: bodyData,
|
|
898
|
+
pairedItem: {
|
|
899
|
+
item: originalItemIndex,
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
}
|
|
972
903
|
}
|
|
973
904
|
else {
|
|
974
905
|
returnItems[idx] = {
|
|
@@ -982,10 +913,18 @@ class BetterHttpRequest {
|
|
|
982
913
|
}
|
|
983
914
|
else if (typeof response === 'object' &&
|
|
984
915
|
response !== null) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
916
|
+
if (Object.keys(response).length === 0) {
|
|
917
|
+
returnItems[idx] = {
|
|
918
|
+
json: { item: originalItemIndex },
|
|
919
|
+
pairedItem: { item: originalItemIndex },
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
returnItems[idx] = {
|
|
924
|
+
json: response,
|
|
925
|
+
pairedItem: { item: originalItemIndex },
|
|
926
|
+
};
|
|
927
|
+
}
|
|
989
928
|
}
|
|
990
929
|
else {
|
|
991
930
|
try {
|
|
@@ -1011,10 +950,11 @@ class BetterHttpRequest {
|
|
|
1011
950
|
}
|
|
1012
951
|
}
|
|
1013
952
|
returnItems = returnItems.map(helpers_1.replaceNullValues);
|
|
953
|
+
this.logger.debug('Better HTTP Request node execution finished', { returnItemsLength: returnItems.length });
|
|
1014
954
|
if (returnItems.length === 1 &&
|
|
1015
955
|
returnItems[0].json.data &&
|
|
1016
956
|
Array.isArray(returnItems[0].json.data)) {
|
|
1017
|
-
const message =
|
|
957
|
+
const message = constants_1.UI_MESSAGES.SPLIT_OUT_HINT;
|
|
1018
958
|
if (this.addExecutionHints) {
|
|
1019
959
|
this.addExecutionHints({
|
|
1020
960
|
message,
|