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;
@@ -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.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
  }