@raevon/n8n-nodes-whatsapp 2.0.6 → 2.0.7

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.
@@ -11,5 +11,6 @@ export interface WhatsAppCredentials {
11
11
  }
12
12
  export declare function getServerUrl(cfg: WhatsAppCredentials): string;
13
13
  export declare function ensureServerRunning(cfg: WhatsAppCredentials): Promise<void>;
14
+ export declare function stopServer(cfg: WhatsAppCredentials): Promise<void>;
14
15
  export declare function disconnect(): Promise<void>;
15
16
  export declare function getWhatsAppCredentials(credentials: Record<string, any>): Promise<WhatsAppCredentials>;
@@ -38,8 +38,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.getServerUrl = getServerUrl;
40
40
  exports.ensureServerRunning = ensureServerRunning;
41
+ exports.stopServer = stopServer;
41
42
  exports.disconnect = disconnect;
42
43
  exports.getWhatsAppCredentials = getWhatsAppCredentials;
44
+ const node_child_process_1 = require("node:child_process");
43
45
  const node_path_1 = __importDefault(require("node:path"));
44
46
  const node_fs_1 = __importDefault(require("node:fs"));
45
47
  const node_net_1 = __importDefault(require("node:net"));
@@ -356,9 +358,55 @@ async function ensureServerRunning(cfg) {
356
358
  return; // Server alive, use it
357
359
  }
358
360
  catch { }
359
- // Port in use but not responding — this is a stale/zombie process we can't kill
360
- // Don't try to start another server, just report the issue
361
- throw new Error(`Port ${port} is in use but server not responding. Restart n8n to clear stale processes.`);
361
+ // Port in use but not responding — try to kill via /proc
362
+ try {
363
+ const out = (0, node_child_process_1.execSync)(`cat /proc/net/tcp | awk '{if(NR>1){split($2,a,\":\");if(strtonum("0x"a[2])==${port})print $10}}'`, { encoding: 'utf-8' }).trim();
364
+ if (out) {
365
+ // Find PID from inode
366
+ const inode = out;
367
+ const pidOut = (0, node_child_process_1.execSync)(`for p in /proc/[0-9]*/fd/*; do if readlink "$p" 2>/dev/null | grep -q "socket:\\[${inode}]"; then echo "$p" | cut -d/ -f3; break; fi; done`, { encoding: 'utf-8' }).trim();
368
+ if (pidOut)
369
+ (0, node_child_process_1.execSync)(`kill -9 ${pidOut}`);
370
+ await new Promise(r => setTimeout(r, 1500));
371
+ }
372
+ }
373
+ catch { }
374
+ // Try again
375
+ if (await isPortOpen(port)) {
376
+ await startServer(cfg);
377
+ return;
378
+ }
379
+ throw new Error(`Port ${port} occupied by unknown process. Restart n8n container to clear.`);
380
+ }
381
+ async function stopServer(cfg) {
382
+ const port = getPort(cfg);
383
+ const state = servers.get(port);
384
+ if (state) {
385
+ if (state.socket) {
386
+ try {
387
+ state.socket.end(new Error('Server stop'));
388
+ }
389
+ catch { }
390
+ }
391
+ if (state.server) {
392
+ try {
393
+ state.server.close();
394
+ }
395
+ catch { }
396
+ }
397
+ servers.delete(port);
398
+ }
399
+ // Also try to kill any process on the port
400
+ try {
401
+ const out = (0, node_child_process_1.execSync)(`cat /proc/net/tcp | awk '{if(NR>1){split($2,a,\":\");if(strtonum("0x"a[2])==${port})print $10}}'`, { encoding: 'utf-8' }).trim();
402
+ if (out) {
403
+ const inode = out;
404
+ const pidOut = (0, node_child_process_1.execSync)(`for p in /proc/[0-9]*/fd/*; do if readlink "$p" 2>/dev/null | grep -q "socket:\\[${inode}]"; then echo "$p" | cut -d/ -f3; break; fi; done`, { encoding: 'utf-8' }).trim();
405
+ if (pidOut)
406
+ (0, node_child_process_1.execSync)(`kill -9 ${pidOut}`);
407
+ }
408
+ }
409
+ catch { }
362
410
  }
363
411
  async function disconnect() { }
364
412
  async function getWhatsAppCredentials(credentials) {
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WhatsAppConnect = void 0;
7
7
  const n8n_workflow_1 = require("n8n-workflow");
8
8
  const WhatsAppApiHelper_1 = require("./WhatsAppApiHelper");
9
- const node_path_1 = __importDefault(require("node:path"));
10
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
11
  function expandHome(p) {
12
12
  if (!p.startsWith('~'))
13
13
  return p;
@@ -22,7 +22,7 @@ class WhatsAppConnect {
22
22
  group: ['transform'],
23
23
  version: 1,
24
24
  subtitle: '={{$parameter["operation"]}}',
25
- description: 'Connect to WhatsApp — starts background server, scan QR on first run',
25
+ description: 'Connect to WhatsApp — manage server, scan QR, sign out',
26
26
  defaults: { name: 'WhatsApp Connect' },
27
27
  inputs: ['main'],
28
28
  outputs: ['main'],
@@ -34,8 +34,10 @@ class WhatsAppConnect {
34
34
  type: 'options',
35
35
  noDataExpression: true,
36
36
  options: [
37
- { name: 'Connect', value: 'connect', description: 'Start WhatsApp server and connect', action: 'Connect to WhatsApp' },
37
+ { name: 'Connect', value: 'connect', description: 'Start server and connect to WhatsApp', action: 'Connect to WhatsApp' },
38
38
  { name: 'Get Status', value: 'status', description: 'Get server and connection status', action: 'Get status' },
39
+ { name: 'Start Server', value: 'startServer', description: 'Start the WhatsApp background server', action: 'Start server' },
40
+ { name: 'Stop Server', value: 'stopServer', description: 'Stop the WhatsApp background server', action: 'Stop server' },
39
41
  { name: 'Sign Out', value: 'signOut', description: 'Stop server and delete session', action: 'Sign out' },
40
42
  ],
41
43
  default: 'connect',
@@ -51,42 +53,49 @@ class WhatsAppConnect {
51
53
  const operation = this.getNodeParameter('operation', 0);
52
54
  for (let i = 0; i < items.length; i++) {
53
55
  try {
54
- if (operation === 'connect') {
55
- // Start the WhatsApp server
56
+ if (operation === 'startServer') {
57
+ await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
58
+ returnData.push({
59
+ json: { success: true, message: 'Server started', port: (0, WhatsAppApiHelper_1.getServerUrl)(cfg), sessionPath: cfg.sessionPath },
60
+ pairedItem: { item: i },
61
+ });
62
+ }
63
+ else if (operation === 'stopServer') {
64
+ await (0, WhatsAppApiHelper_1.stopServer)(cfg);
65
+ returnData.push({
66
+ json: { success: true, message: 'Server stopped', sessionPath: cfg.sessionPath },
67
+ pairedItem: { item: i },
68
+ });
69
+ }
70
+ else if (operation === 'connect') {
56
71
  try {
57
72
  await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
58
73
  }
59
- catch (serverErr) {
74
+ catch (err) {
60
75
  returnData.push({
61
- json: { connected: false, error: `Server start failed: ${serverErr.message}`, sessionPath: cfg.sessionPath },
76
+ json: { connected: false, error: err.message, sessionPath: cfg.sessionPath },
62
77
  pairedItem: { item: i },
63
78
  });
64
79
  continue;
65
80
  }
66
- // Connect to the server
67
81
  try {
68
82
  const response = await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/connect`, { method: 'POST' });
69
83
  const result = await response.json();
70
- returnData.push({
71
- json: { ...result, sessionPath: cfg.sessionPath },
72
- pairedItem: { item: i },
73
- });
84
+ returnData.push({ json: { ...result, sessionPath: cfg.sessionPath }, pairedItem: { item: i } });
74
85
  }
75
- catch (fetchErr) {
86
+ catch (err) {
76
87
  returnData.push({
77
- json: { connected: false, error: `Fetch failed: ${fetchErr.message}. Server URL: ${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}`, sessionPath: cfg.sessionPath },
88
+ json: { connected: false, error: `Server unreachable: ${err.message}`, sessionPath: cfg.sessionPath },
78
89
  pairedItem: { item: i },
79
90
  });
80
91
  }
81
92
  }
82
93
  else if (operation === 'status') {
83
94
  try {
95
+ await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
84
96
  const response = await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/status`);
85
97
  const result = await response.json();
86
- returnData.push({
87
- json: { ...result, sessionPath: cfg.sessionPath },
88
- pairedItem: { item: i },
89
- });
98
+ returnData.push({ json: { ...result, sessionPath: cfg.sessionPath }, pairedItem: { item: i } });
90
99
  }
91
100
  catch {
92
101
  returnData.push({
@@ -96,17 +105,10 @@ class WhatsAppConnect {
96
105
  }
97
106
  }
98
107
  else if (operation === 'signOut') {
99
- try {
100
- await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/signout`, { method: 'POST' });
101
- }
102
- catch { }
103
- // Delete session files
108
+ await (0, WhatsAppApiHelper_1.stopServer)(cfg);
104
109
  const resolvedPath = expandHome(cfg.sessionPath);
105
110
  if (node_fs_1.default.existsSync(resolvedPath)) {
106
- const files = node_fs_1.default.readdirSync(resolvedPath);
107
- for (const file of files) {
108
- node_fs_1.default.unlinkSync(node_path_1.default.join(resolvedPath, file));
109
- }
111
+ node_fs_1.default.readdirSync(resolvedPath).forEach(f => node_fs_1.default.unlinkSync(node_path_1.default.join(resolvedPath, f)));
110
112
  node_fs_1.default.rmdirSync(resolvedPath);
111
113
  }
112
114
  returnData.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raevon/n8n-nodes-whatsapp",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "n8n community node for WhatsApp — send and receive messages with anti-ban protection via the Baileys library",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",