@tehw0lf/n8n-nodes-unix-socket-bridge 1.3.0 → 1.3.2

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 CHANGED
@@ -212,6 +212,34 @@ Run any custom script or command:
212
212
  }
213
213
  ```
214
214
 
215
+ ## Parameter Validation
216
+
217
+ The server supports comprehensive parameter validation for security and correctness:
218
+
219
+ ### Parameter Types and Styles
220
+
221
+ | Type | Description | Validation |
222
+ |------|-------------|------------|
223
+ | `string` | Text values | Regex pattern, length limits |
224
+ | `number` | Numeric values | Min/max value validation |
225
+ | `boolean` | True/false | Converted to command flags |
226
+ | `json` | Complex objects | JSON string validation |
227
+
228
+ | Style | Format | Example |
229
+ |-------|--------|---------|
230
+ | `argument` | Positional argument | `command value` |
231
+ | `flag` | `--name value` | `command --verbose true` |
232
+ | `single_flag` | `--name=value` | `command --player=spotify` |
233
+
234
+ ### Security Features
235
+
236
+ - **Regex Validation**: String parameters enforce pattern matching
237
+ - **Length Limits**: `max_length` prevents oversized inputs
238
+ - **Type Safety**: Parameters validated before execution
239
+ - **Required Fields**: Missing required parameters are rejected
240
+
241
+ See the [main repository](https://github.com/tehw0lf/n8n-nodes-unix-socket-bridge) examples for complete parameter configuration.
242
+
215
243
  ## Server Component
216
244
 
217
245
  This node requires the Unix Socket Bridge server to be running. The server:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tehw0lf/n8n-nodes-unix-socket-bridge",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Generic Unix domain socket communication with configurable server support for n8n",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,73 +0,0 @@
1
- import { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from "n8n-workflow";
2
- /**
3
- * Unix Socket Bridge Node for n8n
4
- *
5
- * This node provides a generic interface for communicating with Unix domain socket servers.
6
- * It supports auto-discovery of available commands, parameter validation, and multiple
7
- * response formats. Security: All authentication tokens are hashed using SHA-256 before transmission.
8
- *
9
- * @example
10
- * // Basic usage with auto-discovery
11
- * const node = new UnixSocketBridge();
12
- * // Configure socket path: /tmp/socket-bridge/playerctl.sock
13
- * // Enable auto-discovery to see available commands
14
- * // Select command from dropdown and execute
15
- *
16
- * @example
17
- * // Manual command execution
18
- * const node = new UnixSocketBridge();
19
- * // Set autoDiscover to false
20
- * // Manually specify command and parameters
21
- *
22
- * @since 1.0.0
23
- */
24
- export declare class UnixSocketBridge implements INodeType {
25
- description: INodeTypeDescription;
26
- /**
27
- * Methods available on this node for dynamic option loading
28
- */
29
- methods: {
30
- loadOptions: {
31
- /**
32
- * Discovers available commands from a Unix socket server
33
- *
34
- * This method connects to the specified socket and sends an introspection
35
- * request to discover what commands are available. It provides user-friendly
36
- * error messages and fallback options when the server is unavailable.
37
- *
38
- * @param this - The load options function context from n8n
39
- * @returns Promise resolving to an array of command options for the dropdown
40
- */
41
- getAvailableCommands(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
42
- };
43
- };
44
- /**
45
- * Executes the Unix Socket Bridge node
46
- *
47
- * This method handles the main execution logic, including:
48
- * - Parameter validation and processing
49
- * - Socket communication
50
- * - Response parsing and formatting
51
- * - Error handling with optional continuation
52
- *
53
- * @param this - The execution function context from n8n
54
- * @returns Promise resolving to node execution data
55
- */
56
- execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
57
- }
58
- /**
59
- * Helper function for Unix socket communication with improved robustness
60
- *
61
- * Establishes a connection to a Unix domain socket, sends a message,
62
- * and returns the response. Includes timeout handling, chunked reading
63
- * for large responses, and proper cleanup of socket resources.
64
- *
65
- * @param socketPath - Path to the Unix domain socket file
66
- * @param message - Message to send (typically JSON-formatted command)
67
- * @param timeoutMs - Connection timeout in milliseconds
68
- * @param maxResponseSize - Maximum response size in bytes
69
- * @returns Promise resolving to the server response as a string
70
- *
71
- * @throws {Error} When socket connection fails, times out, or response too large
72
- */
73
- export declare function sendToUnixSocket(socketPath: string, message: string, timeoutMs: number, maxResponseSize?: number): Promise<string>;
@@ -1,717 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.sendToUnixSocket = exports.UnixSocketBridge = void 0;
27
- const n8n_workflow_1 = require("n8n-workflow");
28
- const net = __importStar(require("net"));
29
- const crypto = __importStar(require("crypto"));
30
- /**
31
- * Securely hash a token using SHA-256
32
- *
33
- * @param token - The plain text token to hash
34
- * @returns SHA-256 hash of the token in hexadecimal format
35
- */
36
- function hashToken(token) {
37
- return crypto.createHash('sha256').update(token).digest('hex');
38
- }
39
- /**
40
- * Unix Socket Bridge Node for n8n
41
- *
42
- * This node provides a generic interface for communicating with Unix domain socket servers.
43
- * It supports auto-discovery of available commands, parameter validation, and multiple
44
- * response formats. Security: All authentication tokens are hashed using SHA-256 before transmission.
45
- *
46
- * @example
47
- * // Basic usage with auto-discovery
48
- * const node = new UnixSocketBridge();
49
- * // Configure socket path: /tmp/socket-bridge/playerctl.sock
50
- * // Enable auto-discovery to see available commands
51
- * // Select command from dropdown and execute
52
- *
53
- * @example
54
- * // Manual command execution
55
- * const node = new UnixSocketBridge();
56
- * // Set autoDiscover to false
57
- * // Manually specify command and parameters
58
- *
59
- * @since 1.0.0
60
- */
61
- class UnixSocketBridge {
62
- constructor() {
63
- this.description = {
64
- displayName: "Unix Socket Bridge",
65
- name: "unixSocketBridge",
66
- icon: "fa:plug",
67
- group: ["communication"],
68
- version: 1,
69
- subtitle: '={{$parameter["autoDiscover"] ? $parameter["discoveredCommand"] : $parameter["command"]}} @ {{$parameter["socketPath"]}}',
70
- description: "Unix domain socket communication with configurable servers",
71
- defaults: {
72
- name: "Unix Socket Bridge",
73
- },
74
- inputs: ["main" /* NodeConnectionType.Main */],
75
- outputs: ["main" /* NodeConnectionType.Main */],
76
- credentials: [
77
- {
78
- name: "httpHeaderAuth",
79
- required: false,
80
- },
81
- ],
82
- properties: [
83
- {
84
- displayName: "Socket Path",
85
- name: "socketPath",
86
- type: "string",
87
- default: "/tmp/socket-bridge/service.sock",
88
- placeholder: "/tmp/socket-bridge/service.sock",
89
- description: "Path to the Unix domain socket file",
90
- required: true,
91
- },
92
- {
93
- displayName: "Auto-Discover Commands",
94
- name: "autoDiscover",
95
- type: "boolean",
96
- default: true,
97
- description: "Automatically discover available commands from the server (recommended)",
98
- },
99
- {
100
- displayName: "Available Commands",
101
- name: "discoveredCommand",
102
- type: "options",
103
- typeOptions: {
104
- loadOptionsMethod: "getAvailableCommands",
105
- loadOptionsDependsOn: ["socketPath"], // Force reload when socket path changes
106
- },
107
- displayOptions: {
108
- show: {
109
- autoDiscover: [true],
110
- },
111
- },
112
- default: "",
113
- description: "Choose from available commands on the server",
114
- placeholder: "Select a command...",
115
- },
116
- {
117
- displayName: "Refresh Commands",
118
- name: "refreshCommands",
119
- type: "notice",
120
- displayOptions: {
121
- show: {
122
- autoDiscover: [true],
123
- },
124
- },
125
- default: "",
126
- description: "If commands don't load, try changing the socket path slightly and changing it back, or toggle auto-discovery off/on",
127
- },
128
- {
129
- displayName: "Manual Command",
130
- name: "command",
131
- type: "string",
132
- displayOptions: {
133
- show: {
134
- autoDiscover: [false],
135
- },
136
- },
137
- default: '={{ $json.command || "status" }}',
138
- description: "Command to send to the server (manual mode)",
139
- placeholder: "status",
140
- },
141
- {
142
- displayName: "Parameters",
143
- name: "parameters",
144
- type: "fixedCollection",
145
- placeholder: "Add Parameter",
146
- typeOptions: {
147
- multipleValues: true,
148
- },
149
- default: {},
150
- options: [
151
- {
152
- name: "parameter",
153
- displayName: "Parameter",
154
- values: [
155
- {
156
- displayName: "Name",
157
- name: "name",
158
- type: "string",
159
- default: "",
160
- description: 'Parameter name (e.g., "player")',
161
- placeholder: "player",
162
- required: false,
163
- },
164
- {
165
- displayName: "Value",
166
- name: "value",
167
- type: "string",
168
- default: "",
169
- description: 'Parameter value (e.g., "spotify")',
170
- placeholder: "spotify",
171
- },
172
- {
173
- displayName: "Type",
174
- name: "type",
175
- type: "options",
176
- options: [
177
- {
178
- name: "Auto-Detect",
179
- value: "auto",
180
- description: "Automatically detect the type",
181
- },
182
- {
183
- name: "String",
184
- value: "string",
185
- description: "Text value",
186
- },
187
- {
188
- name: "Number",
189
- value: "number",
190
- description: "Numeric value",
191
- },
192
- {
193
- name: "Boolean",
194
- value: "boolean",
195
- description: "True/False value",
196
- },
197
- {
198
- name: "JSON",
199
- value: "json",
200
- description: "Complex JSON value",
201
- },
202
- ],
203
- default: "auto",
204
- description: "Parameter value type",
205
- },
206
- ],
207
- },
208
- ],
209
- description: "Additional parameters to pass with the command",
210
- },
211
- {
212
- displayName: "Timeout (ms)",
213
- name: "timeout",
214
- type: "number",
215
- default: 5000,
216
- description: "Connection timeout in milliseconds",
217
- typeOptions: {
218
- minValue: 1000,
219
- maxValue: 30000,
220
- },
221
- },
222
- {
223
- displayName: "Response Format",
224
- name: "responseFormat",
225
- type: "options",
226
- options: [
227
- {
228
- name: "Auto-Detect",
229
- value: "auto",
230
- description: "Automatically detect JSON or return as text",
231
- },
232
- {
233
- name: "JSON",
234
- value: "json",
235
- description: "Parse response as JSON (will error if not valid JSON)",
236
- },
237
- {
238
- name: "Text",
239
- value: "text",
240
- description: "Return response as plain text",
241
- },
242
- ],
243
- default: "auto",
244
- description: "How to interpret the server response",
245
- },
246
- {
247
- displayName: "Options",
248
- name: "options",
249
- type: "collection",
250
- placeholder: "Add Option",
251
- default: {},
252
- options: [
253
- {
254
- displayName: "Continue On Fail",
255
- name: "continueOnFail",
256
- type: "boolean",
257
- default: false,
258
- description: "Whether to continue workflow execution on error",
259
- },
260
- {
261
- displayName: "Max Response Size",
262
- name: "maxResponseSize",
263
- type: "number",
264
- default: 1048576,
265
- description: "Maximum response size in bytes (default: 1MB)",
266
- typeOptions: {
267
- minValue: 1024,
268
- maxValue: 10485760,
269
- },
270
- },
271
- {
272
- displayName: "Include Metadata",
273
- name: "includeMetadata",
274
- type: "boolean",
275
- default: true,
276
- description: "Include metadata like timestamp and socket path in response",
277
- },
278
- ],
279
- },
280
- ],
281
- };
282
- /**
283
- * Methods available on this node for dynamic option loading
284
- */
285
- this.methods = {
286
- loadOptions: {
287
- /**
288
- * Discovers available commands from a Unix socket server
289
- *
290
- * This method connects to the specified socket and sends an introspection
291
- * request to discover what commands are available. It provides user-friendly
292
- * error messages and fallback options when the server is unavailable.
293
- *
294
- * @param this - The load options function context from n8n
295
- * @returns Promise resolving to an array of command options for the dropdown
296
- */
297
- async getAvailableCommands() {
298
- const socketPath = this.getNodeParameter("socketPath");
299
- const timeout = 5000;
300
- // Get authentication token from credentials (secure approach only)
301
- let authToken;
302
- try {
303
- const credentials = await this.getCredentials('httpHeaderAuth');
304
- if (credentials && credentials.value) {
305
- authToken = credentials.value;
306
- }
307
- }
308
- catch (error) {
309
- // Credentials not configured - authentication will be skipped
310
- }
311
- // Provide immediate feedback
312
- if (!socketPath || socketPath.trim() === "") {
313
- return [{ name: "⚠️ Please set a socket path first", value: "" }];
314
- }
315
- try {
316
- const introspectionRequest = {
317
- command: "__introspect__",
318
- request_id: `discovery-${Date.now()}`
319
- };
320
- // Add auth token hash if provided (security: never send plain text tokens)
321
- if (authToken && authToken.trim() !== "") {
322
- introspectionRequest.auth_token_hash = hashToken(authToken.trim());
323
- }
324
- const response = await sendToUnixSocket(socketPath, JSON.stringify(introspectionRequest), timeout);
325
- const serverInfo = JSON.parse(response);
326
- if (serverInfo.success &&
327
- serverInfo.server_info &&
328
- serverInfo.server_info.commands) {
329
- const commands = serverInfo.server_info.commands;
330
- const options = [];
331
- // Add server info as header
332
- options.push({
333
- name: `📡 ${serverInfo.server_info.name} (${Object.keys(commands).length} commands available)`,
334
- value: "__server_info__",
335
- });
336
- // Add separator
337
- options.push({
338
- name: "──────────────────────────",
339
- value: "",
340
- });
341
- // Add commands with better formatting
342
- for (const [commandName, commandInfo] of Object.entries(commands)) {
343
- const description = commandInfo.description || "No description";
344
- const hasParams = commandInfo.parameters &&
345
- Object.keys(commandInfo.parameters).length > 0;
346
- const paramIndicator = hasParams ? " 🔧" : "";
347
- options.push({
348
- name: `${commandName}${paramIndicator} - ${description}`,
349
- value: commandName,
350
- description: hasParams
351
- ? "This command accepts parameters"
352
- : undefined,
353
- });
354
- }
355
- return options;
356
- }
357
- else if (serverInfo.error) {
358
- return [
359
- { name: `❌ Server error: ${serverInfo.error}`, value: "" },
360
- { name: "Check server logs for details", value: "" },
361
- ];
362
- }
363
- else {
364
- return [
365
- { name: "⚠️ Server responded but no commands found", value: "" },
366
- { name: "Check server configuration", value: "" },
367
- ];
368
- }
369
- }
370
- catch (error) {
371
- // More specific error messages
372
- if (error.message.includes("timeout")) {
373
- return [
374
- {
375
- name: "⏱️ Connection timeout - is the server running?",
376
- value: "",
377
- },
378
- {
379
- name: "Try: python3 socket-server.py config.json",
380
- value: "",
381
- },
382
- ];
383
- }
384
- else if (error.message.includes("ENOENT") ||
385
- error.message.includes("not found")) {
386
- return [
387
- { name: "🔍 Socket file not found", value: "" },
388
- { name: `Path: ${socketPath}`, value: "" },
389
- { name: "Check if server is running", value: "" },
390
- ];
391
- }
392
- else if (error.message.includes("ECONNREFUSED")) {
393
- return [
394
- { name: "🚫 Connection refused", value: "" },
395
- { name: "Server may not be listening on this socket", value: "" },
396
- ];
397
- }
398
- else if (error.message.includes("EACCES")) {
399
- return [
400
- { name: "🔒 Permission denied", value: "" },
401
- { name: "Check socket file permissions", value: "" },
402
- ];
403
- }
404
- else {
405
- const shortError = error.message.substring(0, 50);
406
- return [
407
- {
408
- name: `❌ Error: ${shortError}${error.message.length > 50 ? "..." : ""}`,
409
- value: "",
410
- },
411
- { name: "You can still use manual mode", value: "" },
412
- ];
413
- }
414
- }
415
- },
416
- },
417
- };
418
- }
419
- /**
420
- * Executes the Unix Socket Bridge node
421
- *
422
- * This method handles the main execution logic, including:
423
- * - Parameter validation and processing
424
- * - Socket communication
425
- * - Response parsing and formatting
426
- * - Error handling with optional continuation
427
- *
428
- * @param this - The execution function context from n8n
429
- * @returns Promise resolving to node execution data
430
- */
431
- async execute() {
432
- const items = this.getInputData();
433
- const returnData = [];
434
- for (let i = 0; i < items.length; i++) {
435
- try {
436
- const socketPath = this.getNodeParameter("socketPath", i);
437
- const autoDiscover = this.getNodeParameter("autoDiscover", i);
438
- const timeout = this.getNodeParameter("timeout", i);
439
- const responseFormat = this.getNodeParameter("responseFormat", i);
440
- // Get options
441
- const options = this.getNodeParameter("options", i, {});
442
- const maxResponseSize = options.maxResponseSize || 1048576;
443
- const includeMetadata = options.includeMetadata !== false;
444
- let command;
445
- if (autoDiscover) {
446
- command = this.getNodeParameter("discoveredCommand", i);
447
- // Validate command selection
448
- if (command === "__server_info__" || command === "" || !command) {
449
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Please select a valid command from the dropdown", {
450
- itemIndex: i,
451
- description: "Use the dropdown to select an available command",
452
- });
453
- }
454
- }
455
- else {
456
- command = this.getNodeParameter("command", i);
457
- if (!command) {
458
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Command is required", { itemIndex: i });
459
- }
460
- }
461
- // Get authentication token from credentials (secure approach only)
462
- let authToken;
463
- try {
464
- const credentials = await this.getCredentials('httpHeaderAuth');
465
- if (credentials && credentials.value) {
466
- authToken = credentials.value;
467
- }
468
- }
469
- catch (error) {
470
- // Credentials not configured - authentication will be skipped
471
- }
472
- // Build the request
473
- const jsonMessage = {
474
- command,
475
- request_id: `n8n-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
476
- };
477
- // Add auth token hash if provided (security: never send plain text tokens)
478
- if (authToken && authToken.trim() !== "") {
479
- jsonMessage.auth_token_hash = hashToken(authToken.trim());
480
- }
481
- // Process parameters with type handling
482
- const parameters = this.getNodeParameter("parameters", i, {});
483
- if (parameters &&
484
- parameters.parameter &&
485
- Array.isArray(parameters.parameter) &&
486
- parameters.parameter.length > 0) {
487
- jsonMessage.parameters = {};
488
- for (const param of parameters.parameter) {
489
- if (param.name && param.value !== undefined && param.value !== "") {
490
- const paramType = param.type || "auto";
491
- let processedValue = param.value;
492
- try {
493
- switch (paramType) {
494
- case "number":
495
- processedValue = Number(param.value);
496
- if (isNaN(processedValue)) {
497
- throw new Error(`Invalid number: ${param.value}`);
498
- }
499
- break;
500
- case "boolean":
501
- if (typeof param.value === "string") {
502
- processedValue = ["true", "yes", "1", "on"].includes(param.value.toLowerCase());
503
- }
504
- else {
505
- processedValue = Boolean(param.value);
506
- }
507
- break;
508
- case "json":
509
- processedValue = JSON.parse(param.value);
510
- break;
511
- case "string":
512
- processedValue = String(param.value);
513
- break;
514
- case "auto":
515
- default:
516
- // Try to parse as JSON first
517
- try {
518
- processedValue = JSON.parse(param.value);
519
- }
520
- catch (_a) {
521
- // If not JSON, keep as string
522
- processedValue = param.value;
523
- }
524
- break;
525
- }
526
- jsonMessage.parameters[param.name] = processedValue;
527
- }
528
- catch (error) {
529
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to process parameter "${param.name}": ${error.message}`, { itemIndex: i });
530
- }
531
- }
532
- }
533
- }
534
- const messageToSend = JSON.stringify(jsonMessage);
535
- // Send message to Unix socket with size limit
536
- const response = await sendToUnixSocket(socketPath, messageToSend, timeout, maxResponseSize);
537
- // Parse response based on format setting
538
- let parsedResponse;
539
- if (responseFormat === "auto") {
540
- try {
541
- parsedResponse = JSON.parse(response);
542
- }
543
- catch (_b) {
544
- parsedResponse = response;
545
- }
546
- }
547
- else if (responseFormat === "json") {
548
- try {
549
- parsedResponse = JSON.parse(response);
550
- }
551
- catch (error) {
552
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse response as JSON: ${error.message}`, {
553
- itemIndex: i,
554
- description: "Server response is not valid JSON. Try using 'Text' response format.",
555
- });
556
- }
557
- }
558
- else {
559
- parsedResponse = response;
560
- }
561
- // Build response data
562
- let responseData;
563
- if (includeMetadata) {
564
- responseData = {
565
- socketPath,
566
- command,
567
- request: jsonMessage,
568
- response: parsedResponse,
569
- success: true,
570
- timestamp: new Date().toISOString(),
571
- };
572
- // Extract structured response fields if available
573
- if (typeof parsedResponse === "object" &&
574
- parsedResponse !== null &&
575
- "success" in parsedResponse) {
576
- const cmdResponse = parsedResponse;
577
- responseData.success = cmdResponse.success;
578
- responseData.output =
579
- cmdResponse.stdout || cmdResponse.output || "";
580
- responseData.error = cmdResponse.error || cmdResponse.stderr || "";
581
- responseData.returncode = cmdResponse.returncode;
582
- // Include parsed output if available
583
- if (cmdResponse.parsed_output) {
584
- responseData.parsedOutput = cmdResponse.parsed_output;
585
- }
586
- }
587
- }
588
- else {
589
- // Return just the response without metadata
590
- if (typeof parsedResponse === "object" &&
591
- parsedResponse !== null &&
592
- "success" in parsedResponse) {
593
- responseData = parsedResponse;
594
- }
595
- else {
596
- responseData = { response: parsedResponse };
597
- }
598
- }
599
- returnData.push({
600
- json: responseData,
601
- pairedItem: i,
602
- });
603
- }
604
- catch (error) {
605
- if (this.continueOnFail()) {
606
- returnData.push({
607
- json: {
608
- error: error.message,
609
- success: false,
610
- timestamp: new Date().toISOString(),
611
- itemIndex: i,
612
- },
613
- pairedItem: i,
614
- });
615
- continue;
616
- }
617
- throw error;
618
- }
619
- }
620
- return [returnData];
621
- }
622
- }
623
- exports.UnixSocketBridge = UnixSocketBridge;
624
- /**
625
- * Helper function for Unix socket communication with improved robustness
626
- *
627
- * Establishes a connection to a Unix domain socket, sends a message,
628
- * and returns the response. Includes timeout handling, chunked reading
629
- * for large responses, and proper cleanup of socket resources.
630
- *
631
- * @param socketPath - Path to the Unix domain socket file
632
- * @param message - Message to send (typically JSON-formatted command)
633
- * @param timeoutMs - Connection timeout in milliseconds
634
- * @param maxResponseSize - Maximum response size in bytes
635
- * @returns Promise resolving to the server response as a string
636
- *
637
- * @throws {Error} When socket connection fails, times out, or response too large
638
- */
639
- async function sendToUnixSocket(socketPath, message, timeoutMs, maxResponseSize = 1048576) {
640
- return new Promise((resolve, reject) => {
641
- const socket = new net.Socket();
642
- let response = "";
643
- let responseBuffer = Buffer.alloc(0);
644
- const timeoutHandle = setTimeout(() => {
645
- socket.destroy();
646
- reject(new Error(`Socket connection timeout after ${timeoutMs}ms`));
647
- }, timeoutMs);
648
- socket.on("connect", () => {
649
- socket.write(message);
650
- });
651
- socket.on("data", (data) => {
652
- // Use buffer concatenation for binary safety
653
- responseBuffer = Buffer.concat([responseBuffer, data]);
654
- // Check size limit
655
- if (responseBuffer.length > maxResponseSize) {
656
- socket.destroy();
657
- clearTimeout(timeoutHandle);
658
- reject(new Error(`Response too large (max ${maxResponseSize} bytes)`));
659
- }
660
- });
661
- socket.on("end", () => {
662
- clearTimeout(timeoutHandle);
663
- try {
664
- response = responseBuffer.toString("utf-8");
665
- resolve(response);
666
- }
667
- catch (error) {
668
- reject(new Error(`Failed to decode response: ${error.message}`));
669
- }
670
- });
671
- socket.on("close", (hadError) => {
672
- clearTimeout(timeoutHandle);
673
- if (!hadError && responseBuffer.length > 0) {
674
- try {
675
- response = responseBuffer.toString("utf-8");
676
- resolve(response);
677
- }
678
- catch (error) {
679
- reject(new Error(`Failed to decode response: ${error.message}`));
680
- }
681
- }
682
- else if (!hadError && response) {
683
- resolve(response);
684
- }
685
- else if (!hadError) {
686
- reject(new Error("Socket closed without response"));
687
- }
688
- });
689
- socket.on("error", (error) => {
690
- clearTimeout(timeoutHandle);
691
- // Provide more helpful error messages
692
- let errorMessage = error.message;
693
- if (error.message.includes("ENOENT")) {
694
- errorMessage = `Socket not found at ${socketPath}`;
695
- }
696
- else if (error.message.includes("ECONNREFUSED")) {
697
- errorMessage = `Connection refused at ${socketPath} - is the server running?`;
698
- }
699
- else if (error.message.includes("EACCES")) {
700
- errorMessage = `Permission denied accessing ${socketPath}`;
701
- }
702
- else if (error.message.includes("ETIMEDOUT")) {
703
- errorMessage = `Connection timed out to ${socketPath}`;
704
- }
705
- reject(new Error(`Socket error: ${errorMessage}`));
706
- });
707
- // Connect to socket
708
- try {
709
- socket.connect(socketPath);
710
- }
711
- catch (error) {
712
- clearTimeout(timeoutHandle);
713
- reject(new Error(`Failed to connect: ${error.message}`));
714
- }
715
- });
716
- }
717
- exports.sendToUnixSocket = sendToUnixSocket;