@raevon/n8n-nodes-whatsapp 1.0.5 → 1.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.
|
@@ -22,6 +22,11 @@ interface AntiBanConfig {
|
|
|
22
22
|
}
|
|
23
23
|
export declare function getWhatsAppCredentials(credentials: Record<string, any>): Promise<WhatsAppCredentials>;
|
|
24
24
|
export declare function ensureConnected(cfg: WhatsAppCredentials): Promise<WASocket>;
|
|
25
|
+
export declare function connectOrGetQr(cfg: WhatsAppCredentials): Promise<{
|
|
26
|
+
connected: boolean;
|
|
27
|
+
qrUrl?: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}>;
|
|
25
30
|
export declare function simulateTyping(jid: string, content: {
|
|
26
31
|
text?: string;
|
|
27
32
|
caption?: string;
|
|
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.getWhatsAppCredentials = getWhatsAppCredentials;
|
|
40
40
|
exports.ensureConnected = ensureConnected;
|
|
41
|
+
exports.connectOrGetQr = connectOrGetQr;
|
|
41
42
|
exports.simulateTyping = simulateTyping;
|
|
42
43
|
exports.sendMessageWithAntiBan = sendMessageWithAntiBan;
|
|
43
44
|
exports.getConnectionStatus = getConnectionStatus;
|
|
@@ -47,6 +48,7 @@ const baileys_1 = __importStar(require("@whiskeysockets/baileys"));
|
|
|
47
48
|
const p_queue_1 = __importDefault(require("p-queue"));
|
|
48
49
|
const node_path_1 = __importDefault(require("node:path"));
|
|
49
50
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
51
|
+
const qrcode_1 = __importDefault(require("qrcode"));
|
|
50
52
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
51
53
|
const randBetween = (min, max) => max > min ? min + Math.floor(Math.random() * (max - min + 1)) : min;
|
|
52
54
|
// Silent logger — Baileys expects pino-compatible logger with these methods
|
|
@@ -155,7 +157,7 @@ async function initSocket(cfg, authPath) {
|
|
|
155
157
|
console.error('[WhatsApp] Failed to save credentials:', err.message);
|
|
156
158
|
});
|
|
157
159
|
});
|
|
158
|
-
sock.ev.on('connection.update', (update) => {
|
|
160
|
+
sock.ev.on('connection.update', async (update) => {
|
|
159
161
|
var _a, _b;
|
|
160
162
|
if (gen !== generation)
|
|
161
163
|
return; // #10: Ignore stale connection events
|
|
@@ -163,10 +165,10 @@ async function initSocket(cfg, authPath) {
|
|
|
163
165
|
if (qr) {
|
|
164
166
|
latestQr = qr;
|
|
165
167
|
socketStatus = 'qr_ready';
|
|
166
|
-
//
|
|
168
|
+
// Generate QR locally using qrcode package — no external service
|
|
167
169
|
if (qrResolve) {
|
|
168
|
-
const
|
|
169
|
-
qrResolve({ qr, qrUrl });
|
|
170
|
+
const qrDataUrl = await qrcode_1.default.toDataURL(qr, { width: 300, margin: 2 });
|
|
171
|
+
qrResolve({ qr, qrUrl: qrDataUrl });
|
|
170
172
|
qrResolve = null;
|
|
171
173
|
}
|
|
172
174
|
}
|
|
@@ -230,26 +232,26 @@ async function ensureConnected(cfg) {
|
|
|
230
232
|
const resolvedPath = expandHome(cfg.sessionPath);
|
|
231
233
|
const hasSession = node_fs_1.default.existsSync(resolvedPath) && node_fs_1.default.readdirSync(resolvedPath).length > 0;
|
|
232
234
|
const sock = await initSocket(antiBanCfg, cfg.sessionPath);
|
|
233
|
-
// If no session, wait for QR
|
|
235
|
+
// If no session, wait for QR with a reasonable timeout
|
|
234
236
|
if (!hasSession) {
|
|
235
237
|
const qrData = await new Promise((resolve) => {
|
|
236
238
|
qrResolve = resolve;
|
|
237
|
-
//
|
|
239
|
+
// 90 second timeout — QR codes expire quickly, user needs to act fast
|
|
238
240
|
setTimeout(() => {
|
|
239
241
|
if (qrResolve) {
|
|
240
242
|
qrResolve(null);
|
|
241
243
|
qrResolve = null;
|
|
242
244
|
}
|
|
243
|
-
},
|
|
245
|
+
}, 90000);
|
|
244
246
|
});
|
|
245
247
|
if (!qrData) {
|
|
246
248
|
throw new n8n_workflow_1.NodeApiError({}, {
|
|
247
249
|
message: 'QR code expired. Run the Connect node again to get a new QR code.',
|
|
248
250
|
});
|
|
249
251
|
}
|
|
250
|
-
// Wait for connection to open after scan
|
|
252
|
+
// Wait for connection to open after scan (30s timeout)
|
|
251
253
|
await new Promise((resolve, reject) => {
|
|
252
|
-
const timeout = setTimeout(() => reject(new Error('Connection timeout
|
|
254
|
+
const timeout = setTimeout(() => reject(new Error('Connection timeout — QR was scanned but connection did not open in time')), 30000);
|
|
253
255
|
const checkInterval = setInterval(() => {
|
|
254
256
|
if (socketStatus === 'connected') {
|
|
255
257
|
clearTimeout(timeout);
|
|
@@ -266,6 +268,67 @@ async function ensureConnected(cfg) {
|
|
|
266
268
|
}
|
|
267
269
|
return sock;
|
|
268
270
|
}
|
|
271
|
+
// Non-blocking version for Connect node — starts socket, returns QR URL if needed, doesn't wait
|
|
272
|
+
async function connectOrGetQr(cfg) {
|
|
273
|
+
if (socketStatus === 'connected' && socketInstance) {
|
|
274
|
+
return { connected: true, message: 'Already connected' };
|
|
275
|
+
}
|
|
276
|
+
const antiBanCfg = {
|
|
277
|
+
messageDelayMinMs: cfg.messageDelayMinMs,
|
|
278
|
+
messageDelayMaxMs: cfg.messageDelayMaxMs,
|
|
279
|
+
burstSize: cfg.burstSize,
|
|
280
|
+
burstPauseMinMs: cfg.burstPauseMinMs,
|
|
281
|
+
burstPauseMaxMs: cfg.burstPauseMaxMs,
|
|
282
|
+
typingSimulation: cfg.typingSimulation,
|
|
283
|
+
dailySendLimit: cfg.dailySendLimit,
|
|
284
|
+
checkRecipientExists: cfg.checkRecipientExists,
|
|
285
|
+
};
|
|
286
|
+
socketConfig = antiBanCfg;
|
|
287
|
+
sessionPath = cfg.sessionPath;
|
|
288
|
+
if (!queue) {
|
|
289
|
+
queue = new p_queue_1.default({ concurrency: 1 });
|
|
290
|
+
}
|
|
291
|
+
const resolvedPath = expandHome(cfg.sessionPath);
|
|
292
|
+
const hasSession = node_fs_1.default.existsSync(resolvedPath) && node_fs_1.default.readdirSync(resolvedPath).length > 0;
|
|
293
|
+
// If session exists, just connect (no QR needed)
|
|
294
|
+
if (hasSession) {
|
|
295
|
+
await initSocket(antiBanCfg, cfg.sessionPath);
|
|
296
|
+
// Wait up to 15s for connection
|
|
297
|
+
await new Promise((resolve) => {
|
|
298
|
+
const check = setInterval(() => {
|
|
299
|
+
if (socketStatus === 'connected' || socketStatus === 'error' || socketStatus === 'logged_out') {
|
|
300
|
+
clearInterval(check);
|
|
301
|
+
resolve();
|
|
302
|
+
}
|
|
303
|
+
}, 500);
|
|
304
|
+
setTimeout(() => { clearInterval(check); resolve(); }, 15000);
|
|
305
|
+
});
|
|
306
|
+
if (socketStatus === 'connected') {
|
|
307
|
+
return { connected: true, message: 'Connected successfully' };
|
|
308
|
+
}
|
|
309
|
+
return { connected: false, message: `Connection status: ${socketStatus}. Check n8n logs for details.` };
|
|
310
|
+
}
|
|
311
|
+
// No session — start socket and wait for QR (up to 30s)
|
|
312
|
+
const sock = await initSocket(antiBanCfg, cfg.sessionPath);
|
|
313
|
+
const qrData = await new Promise((resolve) => {
|
|
314
|
+
qrResolve = resolve;
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
if (qrResolve) {
|
|
317
|
+
qrResolve(null);
|
|
318
|
+
qrResolve = null;
|
|
319
|
+
}
|
|
320
|
+
}, 30000);
|
|
321
|
+
});
|
|
322
|
+
if (!qrData) {
|
|
323
|
+
return { connected: false, message: 'QR code not generated yet. Try again in a moment.' };
|
|
324
|
+
}
|
|
325
|
+
// Return QR URL immediately — user opens it in browser, scans, then runs Connect again
|
|
326
|
+
return {
|
|
327
|
+
connected: false,
|
|
328
|
+
qrUrl: qrData.qrUrl,
|
|
329
|
+
message: `Open this URL in your browser to scan QR:\n${qrData.qrUrl}\n\nAfter scanning, run this node again to complete connection.`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
269
332
|
async function simulateTyping(jid, content, cfg) {
|
|
270
333
|
if (!cfg.typingSimulation || !socketInstance)
|
|
271
334
|
return;
|
|
@@ -41,17 +41,11 @@ class WhatsAppConnect {
|
|
|
41
41
|
for (let i = 0; i < items.length; i++) {
|
|
42
42
|
try {
|
|
43
43
|
if (operation === 'connect') {
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
const status = (0, WhatsAppApiHelper_1.getConnectionStatus)();
|
|
44
|
+
// Non-blocking: returns QR URL immediately if needed, doesn't wait for scan
|
|
45
|
+
const result = await (0, WhatsAppApiHelper_1.connectOrGetQr)(cfg);
|
|
47
46
|
returnData.push({
|
|
48
47
|
json: {
|
|
49
|
-
|
|
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...',
|
|
48
|
+
...result,
|
|
55
49
|
sessionPath: cfg.sessionPath,
|
|
56
50
|
},
|
|
57
51
|
pairedItem: { item: i },
|
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.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",
|
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@whiskeysockets/baileys": "^7.0.0-rc13",
|
|
39
|
-
"p-queue": "^8.0.1"
|
|
39
|
+
"p-queue": "^8.0.1",
|
|
40
|
+
"qrcode": "^1.5.4"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/node": "^20.19.39",
|
|
44
|
+
"@types/qrcode": "^1.5.6",
|
|
43
45
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
44
46
|
"@typescript-eslint/parser": "^6.21.0",
|
|
45
47
|
"eslint": "^8.57.1",
|