@raevon/n8n-nodes-whatsapp 1.0.2 → 1.0.4

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.
@@ -17,14 +17,7 @@ class WhatsAppApi {
17
17
  type: 'string',
18
18
  default: '~/.n8n/whatsapp-auth',
19
19
  required: true,
20
- description: 'Directory path where WhatsApp session files are stored. After first QR scan, session persists here.',
21
- },
22
- {
23
- displayName: 'QR Server Port',
24
- name: 'qrPort',
25
- type: 'number',
26
- default: 3456,
27
- description: 'Local HTTP server port for serving QR code during first-time setup. Only used when no session exists.',
20
+ description: 'Directory path where WhatsApp session files are stored. After first QR scan, session persists here. Use the "WhatsApp Connect" node to scan QR on first setup.',
28
21
  },
29
22
  {
30
23
  displayName: 'Message Delay Min (ms)',
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { WhatsAppSend } from './nodes/WhatsApp/WhatsAppSend.node';
2
2
  export { WhatsAppTrigger } from './nodes/WhatsApp/WhatsAppTrigger.node';
3
+ export { WhatsAppConnect } from './nodes/WhatsApp/WhatsAppConnect.node';
3
4
  export { WhatsAppApi } from './credentials/WhatsAppApi.credentials';
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WhatsAppApi = exports.WhatsAppTrigger = exports.WhatsAppSend = void 0;
3
+ exports.WhatsAppApi = exports.WhatsAppConnect = exports.WhatsAppTrigger = exports.WhatsAppSend = void 0;
4
4
  var WhatsAppSend_node_1 = require("./nodes/WhatsApp/WhatsAppSend.node");
5
5
  Object.defineProperty(exports, "WhatsAppSend", { enumerable: true, get: function () { return WhatsAppSend_node_1.WhatsAppSend; } });
6
6
  var WhatsAppTrigger_node_1 = require("./nodes/WhatsApp/WhatsAppTrigger.node");
7
7
  Object.defineProperty(exports, "WhatsAppTrigger", { enumerable: true, get: function () { return WhatsAppTrigger_node_1.WhatsAppTrigger; } });
8
+ var WhatsAppConnect_node_1 = require("./nodes/WhatsApp/WhatsAppConnect.node");
9
+ Object.defineProperty(exports, "WhatsAppConnect", { enumerable: true, get: function () { return WhatsAppConnect_node_1.WhatsAppConnect; } });
8
10
  var WhatsAppApi_credentials_1 = require("./credentials/WhatsAppApi.credentials");
9
11
  Object.defineProperty(exports, "WhatsAppApi", { enumerable: true, get: function () { return WhatsAppApi_credentials_1.WhatsAppApi; } });
@@ -1,7 +1,6 @@
1
1
  import { type WASocket } from '@whiskeysockets/baileys';
2
2
  export interface WhatsAppCredentials {
3
3
  sessionPath: string;
4
- qrPort: number;
5
4
  messageDelayMinMs: number;
6
5
  messageDelayMaxMs: number;
7
6
  burstSize: number;
@@ -47,8 +47,6 @@ const baileys_1 = __importStar(require("@whiskeysockets/baileys"));
47
47
  const p_queue_1 = __importDefault(require("p-queue"));
48
48
  const node_path_1 = __importDefault(require("node:path"));
49
49
  const node_fs_1 = __importDefault(require("node:fs"));
50
- const node_http_1 = __importDefault(require("node:http"));
51
- const node_url_1 = require("node:url");
52
50
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
53
51
  const randBetween = (min, max) => max > min ? min + Math.floor(Math.random() * (max - min + 1)) : min;
54
52
  // I8: Extracted ~ path expansion to avoid duplication
@@ -74,8 +72,6 @@ let sentTodayDate = '';
74
72
  let reconnectAttempts = 0;
75
73
  let reconnectTimer = null;
76
74
  let qrResolve = null;
77
- let qrHttpServer = null;
78
- let qrTimeout = null; // C3: Store timeout ref for cleanup
79
75
  let latestQr = null;
80
76
  let generation = 0; // #10: Generation counter — prevents stale handlers on reconnect
81
77
  function todayStartIso() {
@@ -100,39 +96,6 @@ function normalizeRecipient(to) {
100
96
  }
101
97
  return `${digits}@s.whatsapp.net`;
102
98
  }
103
- function startQrServer(port) {
104
- return new Promise((resolve) => {
105
- if (qrHttpServer) {
106
- resolve();
107
- return;
108
- }
109
- qrHttpServer = node_http_1.default.createServer((req, res) => {
110
- const url = new node_url_1.URL(req.url || '/', `http://localhost:${port}`);
111
- if (url.pathname === '/qr' && latestQr) {
112
- const qrUrl = `https://quickchart.io/qr?text=${encodeURIComponent(latestQr)}&size=300`;
113
- res.writeHead(200, { 'Content-Type': 'text/html' });
114
- res.end(`<!DOCTYPE html><html><head><title>WhatsApp QR</title><style>body{display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#111;color:white;font-family:sans-serif}img{border-radius:12px}p{margin-top:20px;text-align:center}</style></head><body><div><img src="${qrUrl}" width="300"/><p>Scan with WhatsApp → Settings → Linked Devices → Link a Device</p><p style="color:#888;font-size:12px">This page auto-refreshes. Session saves after scan.</p></div></body></html>`);
115
- }
116
- else {
117
- res.writeHead(404);
118
- res.end('Not found');
119
- }
120
- });
121
- qrHttpServer.listen(port, () => resolve());
122
- });
123
- }
124
- function stopQrServer() {
125
- if (qrHttpServer) {
126
- qrHttpServer.close();
127
- qrHttpServer = null;
128
- }
129
- }
130
- function clearQrTimeout() {
131
- if (qrTimeout) {
132
- clearTimeout(qrTimeout);
133
- qrTimeout = null;
134
- }
135
- }
136
99
  // #7: Reconnect backoff with jitter — exponential 1s→60s, ±20% randomization
137
100
  async function scheduleReconnect(cfg) {
138
101
  if (reconnectTimer)
@@ -188,6 +151,7 @@ async function initSocket(cfg, authPath) {
188
151
  if (qr) {
189
152
  latestQr = qr;
190
153
  socketStatus = 'qr_ready';
154
+ // Output quickchart URL — works from anywhere, no localhost needed
191
155
  if (qrResolve) {
192
156
  const qrUrl = `https://quickchart.io/qr?text=${encodeURIComponent(qr)}&size=300`;
193
157
  qrResolve({ qr, qrUrl });
@@ -198,8 +162,6 @@ async function initSocket(cfg, authPath) {
198
162
  socketStatus = 'connected';
199
163
  reconnectAttempts = 0;
200
164
  latestQr = null;
201
- clearQrTimeout(); // C3: Clear QR timeout on successful connection
202
- stopQrServer();
203
165
  return;
204
166
  }
205
167
  if (connection === 'close') {
@@ -208,7 +170,6 @@ async function initSocket(cfg, authPath) {
208
170
  socketStatus = 'logged_out';
209
171
  socketInstance = null;
210
172
  ++generation; // #10: Invalidate all handlers from this session
211
- stopQrServer();
212
173
  }
213
174
  else {
214
175
  socketInstance = null;
@@ -217,7 +178,6 @@ async function initSocket(cfg, authPath) {
217
178
  }
218
179
  });
219
180
  // #8: Fire-and-forget — no receipt tracking, no delivery acks, minimal protocol chatter
220
- // (Baileys doesn't auto-track receipts unless you call readMessages/sendReceipt — we don't)
221
181
  return sock;
222
182
  }
223
183
  // --- Public API ---
@@ -225,7 +185,6 @@ async function getWhatsAppCredentials(credentials) {
225
185
  var _a, _b;
226
186
  return {
227
187
  sessionPath: credentials.sessionPath || '~/.n8n/whatsapp-auth',
228
- qrPort: credentials.qrPort || 3456,
229
188
  messageDelayMinMs: credentials.messageDelayMinMs || 5000,
230
189
  messageDelayMaxMs: credentials.messageDelayMaxMs || 9000,
231
190
  burstSize: (_a = credentials.burstSize) !== null && _a !== void 0 ? _a : 20,
@@ -255,29 +214,25 @@ async function ensureConnected(cfg) {
255
214
  if (!queue) {
256
215
  queue = new p_queue_1.default({ concurrency: 1 });
257
216
  }
258
- // Start QR server for first-time setup
217
+ // Check if session already exists
259
218
  const resolvedPath = expandHome(cfg.sessionPath);
260
219
  const hasSession = node_fs_1.default.existsSync(resolvedPath) && node_fs_1.default.readdirSync(resolvedPath).length > 0;
261
- if (!hasSession) {
262
- await startQrServer(cfg.qrPort);
263
- }
264
220
  const sock = await initSocket(antiBanCfg, cfg.sessionPath);
265
221
  // If no session, wait for QR scan
266
222
  if (!hasSession) {
267
223
  const qrData = await new Promise((resolve) => {
268
224
  qrResolve = resolve;
269
- // C3: Store timeout ref so it can be cleared on successful scan
270
- qrTimeout = setTimeout(() => {
225
+ // Timeout after 2 minutes QR codes expire quickly
226
+ setTimeout(() => {
271
227
  if (qrResolve) {
272
228
  qrResolve(null);
273
229
  qrResolve = null;
274
230
  }
275
- qrTimeout = null;
276
- }, 300000);
231
+ }, 120000);
277
232
  });
278
233
  if (!qrData) {
279
234
  throw new n8n_workflow_1.NodeApiError({}, {
280
- message: 'QR code expired. Please try again.',
235
+ message: 'QR code expired. Run the Connect node again to get a new QR code.',
281
236
  });
282
237
  }
283
238
  // Wait for connection to open after scan
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class WhatsAppConnect implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsAppConnect = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const WhatsAppApiHelper_1 = require("./WhatsAppApiHelper");
6
+ class WhatsAppConnect {
7
+ constructor() {
8
+ this.description = {
9
+ displayName: 'WhatsApp Connect',
10
+ name: 'whatsAppConnect',
11
+ icon: 'file:icons/whatsapp.svg',
12
+ group: ['transform'],
13
+ version: 1,
14
+ subtitle: '={{$parameter["operation"]}}',
15
+ description: 'Connect to WhatsApp — scan QR code on first run, then reconnects automatically',
16
+ defaults: { name: 'WhatsApp Connect' },
17
+ inputs: ['main'],
18
+ outputs: ['main'],
19
+ credentials: [{ name: 'whatsappApi', required: true }],
20
+ properties: [
21
+ {
22
+ displayName: 'Operation',
23
+ name: 'operation',
24
+ type: 'options',
25
+ noDataExpression: true,
26
+ options: [
27
+ { name: 'Connect', value: 'connect', description: 'Connect to WhatsApp (scan QR on first run)', action: 'Connect to WhatsApp' },
28
+ { name: 'Get Status', value: 'status', description: 'Get current connection status', action: 'Get connection status' },
29
+ ],
30
+ default: 'connect',
31
+ },
32
+ ],
33
+ };
34
+ }
35
+ async execute() {
36
+ const items = this.getInputData();
37
+ const returnData = [];
38
+ const credentials = await this.getCredentials('whatsappApi');
39
+ const cfg = await (0, WhatsAppApiHelper_1.getWhatsAppCredentials)(credentials);
40
+ const operation = this.getNodeParameter('operation', 0);
41
+ for (let i = 0; i < items.length; i++) {
42
+ try {
43
+ if (operation === 'connect') {
44
+ // Connect to WhatsApp — this triggers QR flow on first run
45
+ const sock = await (0, WhatsAppApiHelper_1.ensureConnected)(cfg);
46
+ const status = (0, WhatsAppApiHelper_1.getConnectionStatus)();
47
+ returnData.push({
48
+ json: {
49
+ success: true,
50
+ status: status.status,
51
+ connected: status.connected,
52
+ message: status.connected
53
+ ? 'WhatsApp connected successfully. You can now use Send and Trigger nodes.'
54
+ : 'Waiting for QR scan...',
55
+ sessionPath: cfg.sessionPath,
56
+ },
57
+ pairedItem: { item: i },
58
+ });
59
+ }
60
+ else if (operation === 'status') {
61
+ const status = (0, WhatsAppApiHelper_1.getConnectionStatus)();
62
+ returnData.push({
63
+ json: {
64
+ ...status,
65
+ sessionPath: cfg.sessionPath,
66
+ },
67
+ pairedItem: { item: i },
68
+ });
69
+ }
70
+ }
71
+ catch (error) {
72
+ if (this.continueOnFail()) {
73
+ returnData.push({
74
+ json: {
75
+ error: error.message,
76
+ success: false,
77
+ },
78
+ pairedItem: { item: i },
79
+ });
80
+ continue;
81
+ }
82
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
83
+ }
84
+ }
85
+ return [returnData];
86
+ }
87
+ }
88
+ exports.WhatsAppConnect = WhatsAppConnect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raevon/n8n-nodes-whatsapp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",
@@ -30,7 +30,8 @@
30
30
  ],
31
31
  "nodes": [
32
32
  "dist/nodes/WhatsApp/WhatsAppSend.node.js",
33
- "dist/nodes/WhatsApp/WhatsAppTrigger.node.js"
33
+ "dist/nodes/WhatsApp/WhatsAppTrigger.node.js",
34
+ "dist/nodes/WhatsApp/WhatsAppConnect.node.js"
34
35
  ]
35
36
  },
36
37
  "dependencies": {