@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.
- package/dist/credentials/WhatsAppApi.credentials.js +1 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/nodes/WhatsApp/WhatsAppApiHelper.d.ts +0 -1
- package/dist/nodes/WhatsApp/WhatsAppApiHelper.js +6 -51
- package/dist/nodes/WhatsApp/WhatsAppConnect.node.d.ts +5 -0
- package/dist/nodes/WhatsApp/WhatsAppConnect.node.js +88 -0
- package/package.json +3 -2
|
@@ -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; } });
|
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
270
|
-
|
|
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
|
-
|
|
276
|
-
}, 300000);
|
|
231
|
+
}, 120000);
|
|
277
232
|
});
|
|
278
233
|
if (!qrData) {
|
|
279
234
|
throw new n8n_workflow_1.NodeApiError({}, {
|
|
280
|
-
message: 'QR code expired.
|
|
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.
|
|
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": {
|