@raevon/n8n-nodes-whatsapp 2.0.5 → 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,6 +38,7 @@ 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;
43
44
  const node_child_process_1 = require("node:child_process");
@@ -345,27 +346,67 @@ async function startServer(cfg) {
345
346
  }
346
347
  async function ensureServerRunning(cfg) {
347
348
  const port = getPort(cfg);
348
- // Check if server is already running on this port
349
+ // Port is free start server
349
350
  if (await isPortOpen(port)) {
350
- // Port is in use - check if it's our server by hitting /health
351
- try {
352
- const res = await fetch(`http://127.0.0.1:${port}/health`);
353
- if (res.ok) {
354
- // Server is alive and responding
355
- return;
351
+ await startServer(cfg);
352
+ return;
353
+ }
354
+ // Port is in use — server already running, just verify it responds
355
+ try {
356
+ const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(3000) });
357
+ if (res.ok)
358
+ return; // Server alive, use it
359
+ }
360
+ catch { }
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'));
356
388
  }
389
+ catch { }
357
390
  }
358
- catch { }
359
- // Port is in use but not responding - kill the stale process
360
- try {
361
- const pid = (0, node_child_process_1.execSync)(`lsof -ti:${port}`, { encoding: 'utf-8' }).trim();
362
- if (pid)
363
- (0, node_child_process_1.execSync)(`kill -9 ${pid}`);
364
- await new Promise(r => setTimeout(r, 1000)); // Wait for port to release
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}`);
365
407
  }
366
- catch { }
367
408
  }
368
- await startServer(cfg);
409
+ catch { }
369
410
  }
370
411
  async function disconnect() { }
371
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.5",
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",