n8n-nodes-script-runner 1.16.2 → 1.17.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 WebSocketClient implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,335 @@
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.WebSocketClient = void 0;
7
+ const n8n_workflow_1 = require("n8n-workflow");
8
+ const ws_1 = __importDefault(require("ws"));
9
+ async function sendWebSocketMessage(wsUrl, message, waitForResponse, timeout) {
10
+ return new Promise((resolve, reject) => {
11
+ const messages = [];
12
+ let ws;
13
+ let timeoutId;
14
+ let isResolved = false;
15
+ const cleanup = () => {
16
+ if (timeoutId)
17
+ clearTimeout(timeoutId);
18
+ if (ws && ws.readyState === ws_1.default.OPEN) {
19
+ ws.close();
20
+ }
21
+ };
22
+ const resolveOnce = (result) => {
23
+ if (!isResolved) {
24
+ isResolved = true;
25
+ cleanup();
26
+ resolve(result);
27
+ }
28
+ };
29
+ const rejectOnce = (error) => {
30
+ if (!isResolved) {
31
+ isResolved = true;
32
+ cleanup();
33
+ reject(error);
34
+ }
35
+ };
36
+ try {
37
+ ws = new ws_1.default(wsUrl);
38
+ // Set timeout
39
+ timeoutId = setTimeout(() => {
40
+ rejectOnce(new Error(`WebSocket connection timeout after ${timeout}ms`));
41
+ }, timeout);
42
+ ws.on('open', () => {
43
+ this.logger?.info?.(`WebSocket connected to ${wsUrl}`);
44
+ messages.push({
45
+ timestamp: new Date().toISOString(),
46
+ type: 'connection',
47
+ data: 'Connected',
48
+ });
49
+ // Send message if provided
50
+ if (message) {
51
+ try {
52
+ ws.send(message);
53
+ messages.push({
54
+ timestamp: new Date().toISOString(),
55
+ type: 'sent',
56
+ data: message,
57
+ });
58
+ this.logger?.info?.(`Message sent: ${message.substring(0, 100)}...`);
59
+ // If not waiting for response, resolve immediately
60
+ if (!waitForResponse) {
61
+ resolveOnce(messages);
62
+ }
63
+ }
64
+ catch (err) {
65
+ rejectOnce(new Error(`Failed to send message: ${err.message}`));
66
+ }
67
+ }
68
+ else if (!waitForResponse) {
69
+ // No message to send and not waiting, just resolve
70
+ resolveOnce(messages);
71
+ }
72
+ });
73
+ ws.on('message', (data) => {
74
+ let parsedData;
75
+ try {
76
+ const stringData = data.toString();
77
+ parsedData = JSON.parse(stringData);
78
+ }
79
+ catch {
80
+ parsedData = data.toString();
81
+ }
82
+ this.logger?.info?.(`Message received: ${JSON.stringify(parsedData).substring(0, 100)}...`);
83
+ messages.push({
84
+ timestamp: new Date().toISOString(),
85
+ type: 'received',
86
+ data: parsedData,
87
+ });
88
+ // If waiting for response, resolve after first message
89
+ if (waitForResponse) {
90
+ resolveOnce(messages);
91
+ }
92
+ });
93
+ ws.on('error', (error) => {
94
+ this.logger?.error?.(`WebSocket error: ${error.message}`);
95
+ messages.push({
96
+ timestamp: new Date().toISOString(),
97
+ type: 'error',
98
+ data: error.message,
99
+ });
100
+ rejectOnce(error);
101
+ });
102
+ ws.on('close', (code, reason) => {
103
+ this.logger?.info?.(`WebSocket closed: ${code} - ${reason.toString()}`);
104
+ if (!isResolved) {
105
+ resolveOnce(messages);
106
+ }
107
+ });
108
+ }
109
+ catch (err) {
110
+ rejectOnce(err);
111
+ }
112
+ });
113
+ }
114
+ async function listenWebSocket(wsUrl, duration) {
115
+ return new Promise((resolve, reject) => {
116
+ const messages = [];
117
+ let ws;
118
+ let timeoutId;
119
+ let isResolved = false;
120
+ const cleanup = () => {
121
+ if (timeoutId)
122
+ clearTimeout(timeoutId);
123
+ if (ws && ws.readyState === ws_1.default.OPEN) {
124
+ ws.close();
125
+ }
126
+ };
127
+ const resolveOnce = (result) => {
128
+ if (!isResolved) {
129
+ isResolved = true;
130
+ cleanup();
131
+ resolve(result);
132
+ }
133
+ };
134
+ const rejectOnce = (error) => {
135
+ if (!isResolved) {
136
+ isResolved = true;
137
+ cleanup();
138
+ reject(error);
139
+ }
140
+ };
141
+ try {
142
+ ws = new ws_1.default(wsUrl);
143
+ ws.on('open', () => {
144
+ this.logger?.info?.(`WebSocket listening on ${wsUrl} for ${duration}ms`);
145
+ messages.push({
146
+ timestamp: new Date().toISOString(),
147
+ type: 'connection',
148
+ data: 'Connected and listening',
149
+ });
150
+ // Auto-resolve after duration
151
+ timeoutId = setTimeout(() => {
152
+ resolveOnce(messages);
153
+ }, duration);
154
+ });
155
+ ws.on('message', (data) => {
156
+ let parsedData;
157
+ try {
158
+ const stringData = data.toString();
159
+ parsedData = JSON.parse(stringData);
160
+ }
161
+ catch {
162
+ parsedData = data.toString();
163
+ }
164
+ this.logger?.info?.(`Message received: ${JSON.stringify(parsedData).substring(0, 100)}...`);
165
+ messages.push({
166
+ timestamp: new Date().toISOString(),
167
+ type: 'received',
168
+ data: parsedData,
169
+ });
170
+ });
171
+ ws.on('error', (error) => {
172
+ this.logger?.error?.(`WebSocket error: ${error.message}`);
173
+ messages.push({
174
+ timestamp: new Date().toISOString(),
175
+ type: 'error',
176
+ data: error.message,
177
+ });
178
+ rejectOnce(error);
179
+ });
180
+ ws.on('close', (code, reason) => {
181
+ this.logger?.info?.(`WebSocket closed: ${code} - ${reason.toString()}`);
182
+ if (!isResolved) {
183
+ resolveOnce(messages);
184
+ }
185
+ });
186
+ }
187
+ catch (err) {
188
+ rejectOnce(err);
189
+ }
190
+ });
191
+ }
192
+ class WebSocketClient {
193
+ constructor() {
194
+ this.description = {
195
+ displayName: 'WebSocket Client',
196
+ name: 'webSocketClient',
197
+ icon: 'file:websocket.svg',
198
+ group: ['transform'],
199
+ version: 1,
200
+ description: 'Connect to any WebSocket server to send and receive messages',
201
+ defaults: {
202
+ name: 'WebSocket Client',
203
+ },
204
+ inputs: ['main'],
205
+ outputs: ['main'],
206
+ properties: [
207
+ {
208
+ displayName: 'Operation',
209
+ name: 'operation',
210
+ type: 'options',
211
+ noDataExpression: true,
212
+ options: [
213
+ {
214
+ name: 'Send Message',
215
+ value: 'send',
216
+ description: 'Send a message to the WebSocket server',
217
+ },
218
+ {
219
+ name: 'Listen',
220
+ value: 'listen',
221
+ description: 'Listen for messages from the WebSocket server',
222
+ },
223
+ ],
224
+ default: 'send',
225
+ },
226
+ {
227
+ displayName: 'WebSocket URL',
228
+ name: 'wsUrl',
229
+ type: 'string',
230
+ default: '',
231
+ placeholder: 'ws://localhost:8080 or wss://example.com/ws',
232
+ description: 'The WebSocket server URL to connect to',
233
+ required: true,
234
+ },
235
+ {
236
+ displayName: 'Message',
237
+ name: 'message',
238
+ type: 'string',
239
+ displayOptions: {
240
+ show: {
241
+ operation: ['send'],
242
+ },
243
+ },
244
+ default: '',
245
+ placeholder: 'Message to send',
246
+ description: 'The message to send to the WebSocket server (can be JSON string)',
247
+ },
248
+ {
249
+ displayName: 'Wait for Response',
250
+ name: 'waitForResponse',
251
+ type: 'boolean',
252
+ displayOptions: {
253
+ show: {
254
+ operation: ['send'],
255
+ },
256
+ },
257
+ default: true,
258
+ description: 'Whether to wait for a response after sending the message',
259
+ },
260
+ {
261
+ displayName: 'Listen Duration (ms)',
262
+ name: 'listenDuration',
263
+ type: 'number',
264
+ displayOptions: {
265
+ show: {
266
+ operation: ['listen'],
267
+ },
268
+ },
269
+ default: 30000,
270
+ typeOptions: { minValue: 1000, maxValue: 300000 },
271
+ description: 'How long to listen for messages (in milliseconds)',
272
+ },
273
+ {
274
+ displayName: 'Timeout (ms)',
275
+ name: 'timeout',
276
+ type: 'number',
277
+ default: 30000,
278
+ typeOptions: { minValue: 1000, maxValue: 300000 },
279
+ description: 'Connection timeout in milliseconds',
280
+ },
281
+ ],
282
+ };
283
+ }
284
+ async execute() {
285
+ const items = this.getInputData();
286
+ const returnData = [];
287
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
288
+ try {
289
+ const operation = this.getNodeParameter('operation', itemIndex);
290
+ const wsUrl = this.getNodeParameter('wsUrl', itemIndex)?.trim();
291
+ const timeout = this.getNodeParameter('timeout', itemIndex);
292
+ if (!wsUrl) {
293
+ throw new Error('WebSocket URL is required');
294
+ }
295
+ if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
296
+ throw new Error('WebSocket URL must start with ws:// or wss://');
297
+ }
298
+ let messages;
299
+ if (operation === 'send') {
300
+ const message = this.getNodeParameter('message', itemIndex, '');
301
+ const waitForResponse = this.getNodeParameter('waitForResponse', itemIndex);
302
+ messages = await sendWebSocketMessage.call(this, wsUrl, message, waitForResponse, timeout);
303
+ }
304
+ else {
305
+ // listen operation
306
+ const listenDuration = this.getNodeParameter('listenDuration', itemIndex);
307
+ messages = await listenWebSocket.call(this, wsUrl, listenDuration);
308
+ }
309
+ returnData.push({
310
+ json: {
311
+ wsUrl,
312
+ operation,
313
+ messages,
314
+ totalMessages: messages.length,
315
+ receivedMessages: messages.filter(m => m.type === 'received').length,
316
+ },
317
+ pairedItem: { item: itemIndex },
318
+ });
319
+ }
320
+ catch (err) {
321
+ const error = err;
322
+ if (this.continueOnFail()) {
323
+ returnData.push({
324
+ json: { error: error.message || 'WebSocket operation failed' },
325
+ pairedItem: { item: itemIndex },
326
+ });
327
+ continue;
328
+ }
329
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
330
+ }
331
+ }
332
+ return [returnData];
333
+ }
334
+ }
335
+ exports.WebSocketClient = WebSocketClient;
@@ -0,0 +1,26 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <!-- Connection lines -->
3
+ <path d="M12 2 L12 6" stroke="#4CAF50"/>
4
+ <path d="M12 18 L12 22" stroke="#4CAF50"/>
5
+
6
+ <!-- Top circle (server) -->
7
+ <circle cx="12" cy="6" r="2" fill="#4CAF50"/>
8
+
9
+ <!-- Bottom circle (client) -->
10
+ <circle cx="12" cy="18" r="2" fill="#2196F3"/>
11
+
12
+ <!-- Bidirectional arrows -->
13
+ <path d="M8 10 L8 14" stroke="#FF9800" stroke-width="1.5"/>
14
+ <path d="M8 10 L6 11.5" stroke="#FF9800" stroke-width="1.5"/>
15
+ <path d="M8 10 L10 11.5" stroke="#FF9800" stroke-width="1.5"/>
16
+
17
+ <path d="M16 14 L16 10" stroke="#FF9800" stroke-width="1.5"/>
18
+ <path d="M16 10 L14 11.5" stroke="#FF9800" stroke-width="1.5"/>
19
+ <path d="M16 10 L18 11.5" stroke="#FF9800" stroke-width="1.5"/>
20
+
21
+ <!-- Signal waves -->
22
+ <path d="M4 12 Q4 8, 8 8" stroke="#9C27B0" stroke-width="1" fill="none" opacity="0.6"/>
23
+ <path d="M20 12 Q20 8, 16 8" stroke="#9C27B0" stroke-width="1" fill="none" opacity="0.6"/>
24
+ <path d="M4 12 Q4 16, 8 16" stroke="#9C27B0" stroke-width="1" fill="none" opacity="0.6"/>
25
+ <path d="M20 12 Q20 16, 16 16" stroke="#9C27B0" stroke-width="1" fill="none" opacity="0.6"/>
26
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-script-runner",
3
- "version": "1.16.2",
3
+ "version": "1.17.0",
4
4
  "description": "Custom n8n nodes for script execution (jsdom, cheerio) and HTTP requests (axios, fetch)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,7 +30,8 @@
30
30
  "dist/nodes/ScriptRunner/ScriptRunner.node.js",
31
31
  "dist/nodes/HttpRequestRunner/HttpRequestRunner.node.js",
32
32
  "dist/nodes/LinkedInFetcher/LinkedInFetcher.node.js",
33
- "dist/nodes/HtmlToPdfConverter/HtmlToPdfConverter.node.js"
33
+ "dist/nodes/HtmlToPdfConverter/HtmlToPdfConverter.node.js",
34
+ "dist/nodes/WebSocketClient/WebSocketClient.node.js"
34
35
  ]
35
36
  },
36
37
  "devDependencies": {
@@ -38,6 +39,7 @@
38
39
  "@types/node": "^20.10.0",
39
40
  "@types/pdfkit": "^0.13.0",
40
41
  "@types/user-agents": "^1.0.4",
42
+ "@types/ws": "^8.18.1",
41
43
  "gulp": "^4.0.2",
42
44
  "n8n-workflow": "^1.0.0",
43
45
  "typescript": "^5.3.0"
@@ -49,7 +51,8 @@
49
51
  "html-to-text": "^9.0.5",
50
52
  "jsdom": "^23.0.0",
51
53
  "pdfkit": "^0.15.0",
52
- "user-agents": "^1.1.669"
54
+ "user-agents": "^1.1.669",
55
+ "ws": "^8.19.0"
53
56
  },
54
57
  "author": "",
55
58
  "license": "MIT"