@raevon/n8n-nodes-whatsapp 1.0.3 → 1.0.5
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.
|
@@ -19,13 +19,6 @@ class WhatsAppApi {
|
|
|
19
19
|
required: true,
|
|
20
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.',
|
|
21
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.',
|
|
28
|
-
},
|
|
29
22
|
{
|
|
30
23
|
displayName: 'Message Delay Min (ms)',
|
|
31
24
|
name: 'messageDelayMinMs',
|
|
@@ -47,10 +47,20 @@ 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;
|
|
52
|
+
// Silent logger — Baileys expects pino-compatible logger with these methods
|
|
53
|
+
const noop = () => { };
|
|
54
|
+
const silentLogger = {
|
|
55
|
+
level: 'silent',
|
|
56
|
+
info: noop,
|
|
57
|
+
warn: noop,
|
|
58
|
+
error: noop,
|
|
59
|
+
debug: noop,
|
|
60
|
+
fatal: noop,
|
|
61
|
+
trace: noop,
|
|
62
|
+
child: () => silentLogger,
|
|
63
|
+
};
|
|
54
64
|
// I8: Extracted ~ path expansion to avoid duplication
|
|
55
65
|
function expandHome(p) {
|
|
56
66
|
if (!p.startsWith('~'))
|
|
@@ -74,8 +84,6 @@ let sentTodayDate = '';
|
|
|
74
84
|
let reconnectAttempts = 0;
|
|
75
85
|
let reconnectTimer = null;
|
|
76
86
|
let qrResolve = null;
|
|
77
|
-
let qrHttpServer = null;
|
|
78
|
-
let qrTimeout = null; // C3: Store timeout ref for cleanup
|
|
79
87
|
let latestQr = null;
|
|
80
88
|
let generation = 0; // #10: Generation counter — prevents stale handlers on reconnect
|
|
81
89
|
function todayStartIso() {
|
|
@@ -100,39 +108,6 @@ function normalizeRecipient(to) {
|
|
|
100
108
|
}
|
|
101
109
|
return `${digits}@s.whatsapp.net`;
|
|
102
110
|
}
|
|
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
111
|
// #7: Reconnect backoff with jitter — exponential 1s→60s, ±20% randomization
|
|
137
112
|
async function scheduleReconnect(cfg) {
|
|
138
113
|
if (reconnectTimer)
|
|
@@ -161,14 +136,14 @@ async function initSocket(cfg, authPath) {
|
|
|
161
136
|
browser: baileys_1.Browsers.ubuntu('n8n WhatsApp Node'),
|
|
162
137
|
auth: {
|
|
163
138
|
creds: state.creds,
|
|
164
|
-
keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys,
|
|
139
|
+
keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, silentLogger),
|
|
165
140
|
},
|
|
166
141
|
// #11: Stealth flags — don't go online, don't sync history, no link previews
|
|
167
142
|
markOnlineOnConnect: false,
|
|
168
143
|
syncFullHistory: false,
|
|
169
144
|
shouldSyncHistoryMessage: () => false,
|
|
170
145
|
generateHighQualityLinkPreview: false,
|
|
171
|
-
logger:
|
|
146
|
+
logger: silentLogger,
|
|
172
147
|
});
|
|
173
148
|
socketInstance = sock;
|
|
174
149
|
socketStatus = 'connecting';
|
|
@@ -188,6 +163,7 @@ async function initSocket(cfg, authPath) {
|
|
|
188
163
|
if (qr) {
|
|
189
164
|
latestQr = qr;
|
|
190
165
|
socketStatus = 'qr_ready';
|
|
166
|
+
// Output quickchart URL — works from anywhere, no localhost needed
|
|
191
167
|
if (qrResolve) {
|
|
192
168
|
const qrUrl = `https://quickchart.io/qr?text=${encodeURIComponent(qr)}&size=300`;
|
|
193
169
|
qrResolve({ qr, qrUrl });
|
|
@@ -198,8 +174,6 @@ async function initSocket(cfg, authPath) {
|
|
|
198
174
|
socketStatus = 'connected';
|
|
199
175
|
reconnectAttempts = 0;
|
|
200
176
|
latestQr = null;
|
|
201
|
-
clearQrTimeout(); // C3: Clear QR timeout on successful connection
|
|
202
|
-
stopQrServer();
|
|
203
177
|
return;
|
|
204
178
|
}
|
|
205
179
|
if (connection === 'close') {
|
|
@@ -208,7 +182,6 @@ async function initSocket(cfg, authPath) {
|
|
|
208
182
|
socketStatus = 'logged_out';
|
|
209
183
|
socketInstance = null;
|
|
210
184
|
++generation; // #10: Invalidate all handlers from this session
|
|
211
|
-
stopQrServer();
|
|
212
185
|
}
|
|
213
186
|
else {
|
|
214
187
|
socketInstance = null;
|
|
@@ -217,7 +190,6 @@ async function initSocket(cfg, authPath) {
|
|
|
217
190
|
}
|
|
218
191
|
});
|
|
219
192
|
// #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
193
|
return sock;
|
|
222
194
|
}
|
|
223
195
|
// --- Public API ---
|
|
@@ -225,7 +197,6 @@ async function getWhatsAppCredentials(credentials) {
|
|
|
225
197
|
var _a, _b;
|
|
226
198
|
return {
|
|
227
199
|
sessionPath: credentials.sessionPath || '~/.n8n/whatsapp-auth',
|
|
228
|
-
qrPort: credentials.qrPort || 3456,
|
|
229
200
|
messageDelayMinMs: credentials.messageDelayMinMs || 5000,
|
|
230
201
|
messageDelayMaxMs: credentials.messageDelayMaxMs || 9000,
|
|
231
202
|
burstSize: (_a = credentials.burstSize) !== null && _a !== void 0 ? _a : 20,
|
|
@@ -255,29 +226,25 @@ async function ensureConnected(cfg) {
|
|
|
255
226
|
if (!queue) {
|
|
256
227
|
queue = new p_queue_1.default({ concurrency: 1 });
|
|
257
228
|
}
|
|
258
|
-
//
|
|
229
|
+
// Check if session already exists
|
|
259
230
|
const resolvedPath = expandHome(cfg.sessionPath);
|
|
260
231
|
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
232
|
const sock = await initSocket(antiBanCfg, cfg.sessionPath);
|
|
265
233
|
// If no session, wait for QR scan
|
|
266
234
|
if (!hasSession) {
|
|
267
235
|
const qrData = await new Promise((resolve) => {
|
|
268
236
|
qrResolve = resolve;
|
|
269
|
-
//
|
|
270
|
-
|
|
237
|
+
// Timeout after 2 minutes — QR codes expire quickly
|
|
238
|
+
setTimeout(() => {
|
|
271
239
|
if (qrResolve) {
|
|
272
240
|
qrResolve(null);
|
|
273
241
|
qrResolve = null;
|
|
274
242
|
}
|
|
275
|
-
|
|
276
|
-
}, 300000);
|
|
243
|
+
}, 120000);
|
|
277
244
|
});
|
|
278
245
|
if (!qrData) {
|
|
279
246
|
throw new n8n_workflow_1.NodeApiError({}, {
|
|
280
|
-
message: 'QR code expired.
|
|
247
|
+
message: 'QR code expired. Run the Connect node again to get a new QR code.',
|
|
281
248
|
});
|
|
282
249
|
}
|
|
283
250
|
// Wait for connection to open after scan
|
|
@@ -50,9 +50,8 @@ class WhatsAppConnect {
|
|
|
50
50
|
status: status.status,
|
|
51
51
|
connected: status.connected,
|
|
52
52
|
message: status.connected
|
|
53
|
-
? 'WhatsApp connected successfully'
|
|
54
|
-
:
|
|
55
|
-
qrUrl: status.qrAvailable ? `http://localhost:${cfg.qrPort}/qr` : null,
|
|
53
|
+
? 'WhatsApp connected successfully. You can now use Send and Trigger nodes.'
|
|
54
|
+
: 'Waiting for QR scan...',
|
|
56
55
|
sessionPath: cfg.sessionPath,
|
|
57
56
|
},
|
|
58
57
|
pairedItem: { item: i },
|
package/package.json
CHANGED