n8n-nodes-script-runner 1.0.0 → 1.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.
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class HttpRequestRunner implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HttpRequestRunner = void 0;
|
|
7
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const user_agents_1 = __importDefault(require("user-agents"));
|
|
10
|
+
const userAgent = new user_agents_1.default({
|
|
11
|
+
deviceCategory: 'desktop',
|
|
12
|
+
platform: 'Win32',
|
|
13
|
+
});
|
|
14
|
+
function getRandomUserAgent() {
|
|
15
|
+
return userAgent.random().toString();
|
|
16
|
+
}
|
|
17
|
+
class HttpRequestRunner {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.description = {
|
|
20
|
+
displayName: 'HTTP Request Runner',
|
|
21
|
+
name: 'httpRequestRunner',
|
|
22
|
+
icon: 'file:httprequest.svg',
|
|
23
|
+
group: ['transform'],
|
|
24
|
+
version: 1,
|
|
25
|
+
subtitle: '={{$parameter["library"]}}',
|
|
26
|
+
description: 'Make HTTP requests with axios, fetch, or custom scripts',
|
|
27
|
+
defaults: {
|
|
28
|
+
name: 'HTTP Request Runner',
|
|
29
|
+
},
|
|
30
|
+
inputs: ['main'],
|
|
31
|
+
outputs: ['main'],
|
|
32
|
+
properties: [
|
|
33
|
+
{
|
|
34
|
+
displayName: 'Library',
|
|
35
|
+
name: 'library',
|
|
36
|
+
type: 'options',
|
|
37
|
+
options: [
|
|
38
|
+
{
|
|
39
|
+
name: 'Axios',
|
|
40
|
+
value: 'axios',
|
|
41
|
+
description: 'Promise-based HTTP client with interceptors',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'Fetch',
|
|
45
|
+
value: 'fetch',
|
|
46
|
+
description: 'Native fetch API for HTTP requests',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Custom Script',
|
|
50
|
+
value: 'custom',
|
|
51
|
+
description: 'Write custom HTTP request code with all libraries',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
default: 'axios',
|
|
55
|
+
description: 'Choose the library to use for HTTP requests',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
displayName: 'Method',
|
|
59
|
+
name: 'method',
|
|
60
|
+
type: 'options',
|
|
61
|
+
displayOptions: {
|
|
62
|
+
show: {
|
|
63
|
+
library: ['axios', 'fetch'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
options: [
|
|
67
|
+
{ name: 'GET', value: 'GET' },
|
|
68
|
+
{ name: 'POST', value: 'POST' },
|
|
69
|
+
{ name: 'PUT', value: 'PUT' },
|
|
70
|
+
{ name: 'PATCH', value: 'PATCH' },
|
|
71
|
+
{ name: 'DELETE', value: 'DELETE' },
|
|
72
|
+
{ name: 'HEAD', value: 'HEAD' },
|
|
73
|
+
{ name: 'OPTIONS', value: 'OPTIONS' },
|
|
74
|
+
],
|
|
75
|
+
default: 'GET',
|
|
76
|
+
description: 'HTTP method to use',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
displayName: 'URL',
|
|
80
|
+
name: 'url',
|
|
81
|
+
type: 'string',
|
|
82
|
+
displayOptions: {
|
|
83
|
+
show: {
|
|
84
|
+
library: ['axios', 'fetch'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
default: '',
|
|
88
|
+
placeholder: 'https://api.example.com/data',
|
|
89
|
+
description: 'The URL to make the request to',
|
|
90
|
+
required: true,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
displayName: 'Headers',
|
|
94
|
+
name: 'headers',
|
|
95
|
+
type: 'json',
|
|
96
|
+
displayOptions: {
|
|
97
|
+
show: {
|
|
98
|
+
library: ['axios', 'fetch'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
default: '{\n "Content-Type": "application/json"\n}',
|
|
102
|
+
description: 'Request headers as JSON object',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
displayName: 'Random User-Agent',
|
|
106
|
+
name: 'randomUserAgent',
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
displayOptions: {
|
|
109
|
+
show: {
|
|
110
|
+
library: ['axios', 'fetch'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
default: true,
|
|
114
|
+
description: 'Whether to use a random User-Agent header automatically',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
displayName: 'Body',
|
|
118
|
+
name: 'body',
|
|
119
|
+
type: 'json',
|
|
120
|
+
displayOptions: {
|
|
121
|
+
show: {
|
|
122
|
+
library: ['axios', 'fetch'],
|
|
123
|
+
method: ['POST', 'PUT', 'PATCH'],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
default: '{}',
|
|
127
|
+
description: 'Request body as JSON',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
displayName: 'Query Parameters',
|
|
131
|
+
name: 'queryParams',
|
|
132
|
+
type: 'json',
|
|
133
|
+
displayOptions: {
|
|
134
|
+
show: {
|
|
135
|
+
library: ['axios', 'fetch'],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
default: '{}',
|
|
139
|
+
description: 'URL query parameters as JSON object',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
displayName: 'Timeout (ms)',
|
|
143
|
+
name: 'timeout',
|
|
144
|
+
type: 'number',
|
|
145
|
+
displayOptions: {
|
|
146
|
+
show: {
|
|
147
|
+
library: ['axios', 'fetch'],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
default: 30000,
|
|
151
|
+
description: 'Request timeout in milliseconds',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
displayName: 'Custom Script',
|
|
155
|
+
name: 'script',
|
|
156
|
+
type: 'string',
|
|
157
|
+
displayOptions: {
|
|
158
|
+
show: {
|
|
159
|
+
library: ['custom'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
typeOptions: {
|
|
163
|
+
rows: 15,
|
|
164
|
+
alwaysOpenEditWindow: true,
|
|
165
|
+
},
|
|
166
|
+
default: `// Axios example:
|
|
167
|
+
const response = await axios({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
url: 'https://api.example.com/data',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' }
|
|
171
|
+
});
|
|
172
|
+
return response.data;
|
|
173
|
+
|
|
174
|
+
// Fetch example:
|
|
175
|
+
// const response = await fetch('https://api.example.com/data', {
|
|
176
|
+
// method: 'POST',
|
|
177
|
+
// headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
// body: JSON.stringify({ key: 'value' })
|
|
179
|
+
// });
|
|
180
|
+
// return await response.json();
|
|
181
|
+
|
|
182
|
+
// Access current item: $item.json
|
|
183
|
+
// Access all items: items`,
|
|
184
|
+
description: 'Custom JavaScript code for HTTP requests. Available: axios, fetch, items, $item, itemIndex',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
displayName: 'Return Full Response',
|
|
188
|
+
name: 'returnFullResponse',
|
|
189
|
+
type: 'boolean',
|
|
190
|
+
default: false,
|
|
191
|
+
description: 'Whether to return full response including headers and status',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
displayName: 'Parallel Execution',
|
|
195
|
+
name: 'parallelExecution',
|
|
196
|
+
type: 'boolean',
|
|
197
|
+
default: true,
|
|
198
|
+
description: 'Whether to process items in parallel for faster execution',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async execute() {
|
|
204
|
+
const items = this.getInputData();
|
|
205
|
+
const library = this.getNodeParameter('library', 0);
|
|
206
|
+
const returnFullResponse = this.getNodeParameter('returnFullResponse', 0);
|
|
207
|
+
const parallelExecution = this.getNodeParameter('parallelExecution', 0);
|
|
208
|
+
const processItem = async (itemIndex) => {
|
|
209
|
+
try {
|
|
210
|
+
const $item = items[itemIndex];
|
|
211
|
+
let result;
|
|
212
|
+
if (library === 'custom') {
|
|
213
|
+
// Custom script execution
|
|
214
|
+
const script = this.getNodeParameter('script', itemIndex);
|
|
215
|
+
const url = this.getNodeParameter('url', itemIndex, '');
|
|
216
|
+
const executeScript = new Function('axios', 'fetch', 'items', '$item', 'itemIndex', `return (async () => { ${script} })();`);
|
|
217
|
+
result = await executeScript(axios_1.default, fetch, items, $item, itemIndex);
|
|
218
|
+
}
|
|
219
|
+
else if (library === 'axios') {
|
|
220
|
+
// Axios execution
|
|
221
|
+
const method = this.getNodeParameter('method', itemIndex);
|
|
222
|
+
const url = this.getNodeParameter('url', itemIndex);
|
|
223
|
+
const headersJson = this.getNodeParameter('headers', itemIndex, '{}');
|
|
224
|
+
const queryParamsJson = this.getNodeParameter('queryParams', itemIndex, '{}');
|
|
225
|
+
const timeout = this.getNodeParameter('timeout', itemIndex, 30000);
|
|
226
|
+
const randomUserAgent = this.getNodeParameter('randomUserAgent', itemIndex, true);
|
|
227
|
+
const headers = JSON.parse(headersJson);
|
|
228
|
+
const params = JSON.parse(queryParamsJson);
|
|
229
|
+
// Add random user-agent if enabled and not already set
|
|
230
|
+
if (randomUserAgent && !headers['User-Agent'] && !headers['user-agent']) {
|
|
231
|
+
headers['User-Agent'] = getRandomUserAgent();
|
|
232
|
+
}
|
|
233
|
+
const config = {
|
|
234
|
+
method: method,
|
|
235
|
+
url,
|
|
236
|
+
headers,
|
|
237
|
+
params,
|
|
238
|
+
timeout,
|
|
239
|
+
};
|
|
240
|
+
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
241
|
+
const bodyJson = this.getNodeParameter('body', itemIndex, '{}');
|
|
242
|
+
config.data = JSON.parse(bodyJson);
|
|
243
|
+
}
|
|
244
|
+
const response = await (0, axios_1.default)(config);
|
|
245
|
+
if (returnFullResponse) {
|
|
246
|
+
result = {
|
|
247
|
+
data: response.data,
|
|
248
|
+
status: response.status,
|
|
249
|
+
statusText: response.statusText,
|
|
250
|
+
headers: response.headers,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
result = response.data;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (library === 'fetch') {
|
|
258
|
+
// Fetch execution
|
|
259
|
+
const method = this.getNodeParameter('method', itemIndex);
|
|
260
|
+
const url = this.getNodeParameter('url', itemIndex);
|
|
261
|
+
const headersJson = this.getNodeParameter('headers', itemIndex, '{}');
|
|
262
|
+
const queryParamsJson = this.getNodeParameter('queryParams', itemIndex, '{}');
|
|
263
|
+
const timeout = this.getNodeParameter('timeout', itemIndex, 30000);
|
|
264
|
+
const randomUserAgent = this.getNodeParameter('randomUserAgent', itemIndex, true);
|
|
265
|
+
const headers = JSON.parse(headersJson);
|
|
266
|
+
const params = JSON.parse(queryParamsJson);
|
|
267
|
+
// Add random user-agent if enabled and not already set
|
|
268
|
+
if (randomUserAgent && !headers['User-Agent'] && !headers['user-agent']) {
|
|
269
|
+
headers['User-Agent'] = getRandomUserAgent();
|
|
270
|
+
}
|
|
271
|
+
// Build URL with query params
|
|
272
|
+
const urlObj = new URL(url);
|
|
273
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
274
|
+
urlObj.searchParams.append(key, String(value));
|
|
275
|
+
});
|
|
276
|
+
const fetchOptions = {
|
|
277
|
+
method,
|
|
278
|
+
headers,
|
|
279
|
+
};
|
|
280
|
+
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
281
|
+
const bodyJson = this.getNodeParameter('body', itemIndex, '{}');
|
|
282
|
+
fetchOptions.body = bodyJson;
|
|
283
|
+
}
|
|
284
|
+
// Timeout handling
|
|
285
|
+
const controller = new AbortController();
|
|
286
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
287
|
+
fetchOptions.signal = controller.signal;
|
|
288
|
+
try {
|
|
289
|
+
const response = await fetch(urlObj.toString(), fetchOptions);
|
|
290
|
+
clearTimeout(timeoutId);
|
|
291
|
+
const contentType = response.headers.get('content-type');
|
|
292
|
+
let data;
|
|
293
|
+
if (contentType?.includes('application/json')) {
|
|
294
|
+
data = await response.json();
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
data = await response.text();
|
|
298
|
+
}
|
|
299
|
+
if (returnFullResponse) {
|
|
300
|
+
result = {
|
|
301
|
+
data,
|
|
302
|
+
status: response.status,
|
|
303
|
+
statusText: response.statusText,
|
|
304
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
result = data;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
clearTimeout(timeoutId);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
json: typeof result === 'object' ? result : { result },
|
|
318
|
+
pairedItem: { item: itemIndex },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
323
|
+
if (this.continueOnFail()) {
|
|
324
|
+
return {
|
|
325
|
+
json: {
|
|
326
|
+
error: errorMessage,
|
|
327
|
+
},
|
|
328
|
+
pairedItem: { item: itemIndex },
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `HTTP request failed: ${errorMessage}`, { itemIndex });
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
let returnData;
|
|
335
|
+
if (parallelExecution && items.length > 1) {
|
|
336
|
+
// Process all items in parallel
|
|
337
|
+
returnData = await Promise.all(items.map((_, index) => processItem(index)));
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Process items sequentially
|
|
341
|
+
returnData = [];
|
|
342
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
343
|
+
const result = await processItem(itemIndex);
|
|
344
|
+
returnData.push(result);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return [returnData];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
exports.HttpRequestRunner = HttpRequestRunner;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-script-runner",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Custom n8n
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Custom n8n nodes for script execution (jsdom, cheerio) and HTTP requests (axios, fetch)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc && gulp build:icons",
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"n8n",
|
|
16
16
|
"jsdom",
|
|
17
17
|
"cheerio",
|
|
18
|
-
"script"
|
|
18
|
+
"script",
|
|
19
|
+
"axios",
|
|
20
|
+
"fetch",
|
|
21
|
+
"http"
|
|
19
22
|
],
|
|
20
23
|
"files": [
|
|
21
24
|
"dist"
|
|
@@ -24,21 +27,24 @@
|
|
|
24
27
|
"n8nNodesApiVersion": 1,
|
|
25
28
|
"credentials": [],
|
|
26
29
|
"nodes": [
|
|
27
|
-
"dist/nodes/ScriptRunner/ScriptRunner.node.js"
|
|
30
|
+
"dist/nodes/ScriptRunner/ScriptRunner.node.js",
|
|
31
|
+
"dist/nodes/HttpRequestRunner/HttpRequestRunner.node.js"
|
|
28
32
|
]
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
35
|
"@types/jsdom": "^27.0.0",
|
|
32
36
|
"@types/node": "^20.10.0",
|
|
37
|
+
"@types/user-agents": "^1.0.4",
|
|
33
38
|
"gulp": "^4.0.2",
|
|
34
39
|
"n8n-workflow": "^1.0.0",
|
|
35
40
|
"typescript": "^5.3.0"
|
|
36
41
|
},
|
|
37
42
|
"dependencies": {
|
|
43
|
+
"axios": "^1.6.0",
|
|
38
44
|
"cheerio": "^1.0.0-rc.12",
|
|
39
|
-
"jsdom": "^23.0.0"
|
|
45
|
+
"jsdom": "^23.0.0",
|
|
46
|
+
"user-agents": "^1.1.669"
|
|
40
47
|
},
|
|
41
48
|
"author": "",
|
|
42
49
|
"license": "MIT"
|
|
43
|
-
|
|
44
50
|
}
|