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