app-manager-edge-worker 1.1.3 → 1.1.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.
- package/README.md +14 -0
- package/package.json +1 -1
- package/src/banner.js +1 -1
- package/src/client.js +51 -6
- package/src/config.js +1 -1
- package/src/handlers/whatsappHandler.js +154 -3
package/README.md
CHANGED
|
@@ -203,6 +203,20 @@ Log levels: `[INFO]` · `[SUCCESS]` · `[WARN]` · `[ERROR]` · `[TASK]` (job di
|
|
|
203
203
|
|
|
204
204
|
## 📝 Changelog
|
|
205
205
|
|
|
206
|
+
### v1.1.5
|
|
207
|
+
- **Real-Time WhatsApp Task & Document Streaming Telemetry**:
|
|
208
|
+
- Added timestamped terminal logging for incoming WhatsApp text and document task receipts.
|
|
209
|
+
- Live progress logs for authenticated PDF document streaming, byte transfer size, and SHA-256 integrity validation.
|
|
210
|
+
- Real-time typing presence simulation status and delivery confirmations with official message IDs (`wamid`).
|
|
211
|
+
|
|
212
|
+
### v1.1.4
|
|
213
|
+
- **Persistent MongoDB Session Restoration for WhatsApp**:
|
|
214
|
+
- Automatically restores WhatsApp companion device credentials (`creds.json`, pre-keys, session keys) from cluster MongoDB on worker boot.
|
|
215
|
+
- Workers restart without losing WhatsApp connection state or requiring QR code re-pairing.
|
|
216
|
+
- Live 150ms debounced synchronization of key rotations and credentials to MongoDB via `bulkWrite`.
|
|
217
|
+
- Automatic backup of pre-existing local disk sessions to cluster database on initial connection.
|
|
218
|
+
- Coordinated session wiping on remote logout and phone unlinking.
|
|
219
|
+
|
|
206
220
|
### v1.1.0
|
|
207
221
|
- **WhatsApp Multi-Device Companion Relay**:
|
|
208
222
|
- Independent companion device pairing via live QR streaming to Admin Dashboard.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-manager-edge-worker",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Distributed Scraper Edge Worker for Apps Manager. Run on any local machine, VPS, or Termux Android device to serve browser and HTTP scraping jobs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/banner.js
CHANGED
package/src/client.js
CHANGED
|
@@ -8,9 +8,16 @@ import { handleChromiumScrape, handleHttpScrape } from "./handlers/scrapeHandler
|
|
|
8
8
|
import { handlePreviewPage, handleInspectElement } from "./handlers/previewHandler.js";
|
|
9
9
|
import { handleSendEmail } from "./handlers/mailHandler.js";
|
|
10
10
|
import { handleAiSmartRepair } from "./handlers/aiRepairHandler.js";
|
|
11
|
+
import fs from "fs";
|
|
11
12
|
import { getWhatsAppHandler } from "./handlers/whatsappHandler.js";
|
|
12
13
|
import { printBanner, logInfo, logSuccess, logWarn, logError, logJob } from "./banner.js";
|
|
13
14
|
|
|
15
|
+
let workerPkgVersion = "1.1.5";
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
18
|
+
if (pkg?.version) workerPkgVersion = pkg.version;
|
|
19
|
+
} catch (_) {}
|
|
20
|
+
|
|
14
21
|
export async function startWorkerClient(config) {
|
|
15
22
|
const { workerKey, serverUrl, concurrency, workerName, keepAliveMinutes = 7 } = config;
|
|
16
23
|
const isTermux = localBrowserManager.isTermux();
|
|
@@ -115,15 +122,53 @@ export async function startWorkerClient(config) {
|
|
|
115
122
|
onQrGenerated: ({ qrDataUrl }) => {
|
|
116
123
|
socket.emit("whatsapp:qr_stream", { qrDataUrl });
|
|
117
124
|
},
|
|
125
|
+
onSessionBatchUpdate: (batchData) => {
|
|
126
|
+
socket.emit("whatsapp:session_batch_update", batchData);
|
|
127
|
+
},
|
|
128
|
+
onClearSession: () => {
|
|
129
|
+
socket.emit("whatsapp:clear_session");
|
|
130
|
+
},
|
|
118
131
|
});
|
|
119
132
|
|
|
120
133
|
socket.on("connect", () => {
|
|
121
134
|
logSuccess(`Connected to cluster! (Socket ID: ${socket.id})`);
|
|
122
135
|
logInfo(`Ready to accept scraping and visual preview jobs (Concurrency: ${activeConcurrency}).\n`);
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
|
|
137
|
+
// If Baileys is already CONNECTED in memory (e.g. quick network reconnection), announce immediately
|
|
138
|
+
if (waHandler.status === "CONNECTED" || waHandler.status === "STANDBY") {
|
|
139
|
+
const waTelemetry = waHandler.getTelemetry();
|
|
140
|
+
socket.emit("whatsapp:status_update", waTelemetry);
|
|
141
|
+
}
|
|
142
|
+
// If not connected yet, wait for whatsapp:session_sync from cluster server before announcing status
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Handle MongoDB WhatsApp Session restore from cluster server
|
|
146
|
+
socket.on("whatsapp:session_sync", async ({ hasSession, files }) => {
|
|
147
|
+
if (hasSession && files && Object.keys(files).length > 0) {
|
|
148
|
+
logInfo(`[WhatsApp] Cluster provided ${Object.keys(files).length} session credentials from MongoDB. Restoring session...`);
|
|
149
|
+
waHandler.hydrateFromSessionFiles(files);
|
|
150
|
+
try {
|
|
151
|
+
await waHandler.initialize(false);
|
|
152
|
+
logSuccess("[WhatsApp] Auto-connected successfully using restored MongoDB session!");
|
|
153
|
+
} catch (err) {
|
|
154
|
+
logWarn(`[WhatsApp] Auto-connect with MongoDB credentials failed: ${err.message}`);
|
|
155
|
+
socket.emit("whatsapp:status_update", waHandler.getTelemetry());
|
|
156
|
+
}
|
|
157
|
+
} else if (waHandler.hasCredentials()) {
|
|
158
|
+
logInfo("[WhatsApp] Found local credentials on disk not yet in MongoDB. Syncing to cluster database...");
|
|
159
|
+
const localFiles = waHandler.exportAllSessionFiles();
|
|
160
|
+
socket.emit("whatsapp:save_session_batch", { files: localFiles });
|
|
161
|
+
try {
|
|
162
|
+
await waHandler.initialize(false);
|
|
163
|
+
logSuccess("[WhatsApp] Connected using local credentials and backed up to cluster database.");
|
|
164
|
+
} catch (err) {
|
|
165
|
+
logWarn(`[WhatsApp] Auto-connect with local credentials failed: ${err.message}`);
|
|
166
|
+
socket.emit("whatsapp:status_update", waHandler.getTelemetry());
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// No stored credentials on cluster or local disk - report clean disconnected status
|
|
170
|
+
socket.emit("whatsapp:status_update", waHandler.getTelemetry());
|
|
171
|
+
}
|
|
127
172
|
});
|
|
128
173
|
|
|
129
174
|
socket.on("worker:registered", (data) => {
|
|
@@ -274,7 +319,7 @@ export async function startWorkerClient(config) {
|
|
|
274
319
|
timeout: 15000,
|
|
275
320
|
headers: {
|
|
276
321
|
"x-worker-key": workerKey || "",
|
|
277
|
-
"user-agent":
|
|
322
|
+
"user-agent": `AppsManagerEdgeWorker/${workerPkgVersion}`,
|
|
278
323
|
},
|
|
279
324
|
});
|
|
280
325
|
logInfo(`[Keep-Alive] Server awake ping OK (${res.status}) - Render instance active`);
|
|
@@ -285,7 +330,7 @@ export async function startWorkerClient(config) {
|
|
|
285
330
|
await axios.get(fallbackUrl, {
|
|
286
331
|
timeout: 10000,
|
|
287
332
|
headers: {
|
|
288
|
-
"user-agent":
|
|
333
|
+
"user-agent": `AppsManagerEdgeWorker/${workerPkgVersion}`,
|
|
289
334
|
},
|
|
290
335
|
});
|
|
291
336
|
logInfo(`[Keep-Alive] Fallback health ping OK - Render instance active`);
|
package/src/config.js
CHANGED
|
@@ -111,7 +111,7 @@ export async function resolveWorkerConfig() {
|
|
|
111
111
|
const cli = parseCliArgs();
|
|
112
112
|
|
|
113
113
|
if (cli.help) {
|
|
114
|
-
let pkgVer = "1.1.
|
|
114
|
+
let pkgVer = "1.1.5";
|
|
115
115
|
try {
|
|
116
116
|
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
117
117
|
if (pkg?.version) pkgVer = pkg.version;
|
|
@@ -11,6 +11,7 @@ import path from "path";
|
|
|
11
11
|
import os from "os";
|
|
12
12
|
import crypto from "crypto";
|
|
13
13
|
import axios from "axios";
|
|
14
|
+
import { logInfo, logSuccess, logWarn } from "../banner.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* WhatsApp Companion Device Engine for App Manager Edge Worker.
|
|
@@ -36,18 +37,116 @@ export class WhatsAppWorkerHandler {
|
|
|
36
37
|
this.IDLE_COOLDOWN_MS = options.idleCooldownMs || 10 * 60 * 1000; // 10 minutes
|
|
37
38
|
this.onStatusChange = options.onStatusChange || (() => {});
|
|
38
39
|
this.onQrGenerated = options.onQrGenerated || (() => {});
|
|
40
|
+
this.onSessionBatchUpdate = options.onSessionBatchUpdate || (() => {});
|
|
41
|
+
this.onClearSession = options.onClearSession || (() => {});
|
|
39
42
|
this.chatStore = new Map(); // JID -> Array of recent messages for reconciliation
|
|
40
43
|
this.isInitializing = false;
|
|
44
|
+
this.pendingWrites = new Map(); // keyId -> string data
|
|
45
|
+
this.pendingDeletes = new Set(); // keyId
|
|
46
|
+
this.syncDebounceTimer = null;
|
|
41
47
|
|
|
42
48
|
if (!fs.existsSync(this.authDir)) {
|
|
43
49
|
fs.mkdirSync(this.authDir, { recursive: true });
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Queues a key write to be batched and synced to the central MongoDB server.
|
|
55
|
+
*/
|
|
56
|
+
queueSyncWrite(keyId, data) {
|
|
57
|
+
if (!keyId || typeof data !== "string") return;
|
|
58
|
+
this.pendingDeletes.delete(keyId);
|
|
59
|
+
this.pendingWrites.set(keyId, data);
|
|
60
|
+
this._scheduleBatchSync();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Queues a key removal to be batched and deleted from central MongoDB server.
|
|
65
|
+
*/
|
|
66
|
+
queueSyncDelete(keyId) {
|
|
67
|
+
if (!keyId) return;
|
|
68
|
+
this.pendingWrites.delete(keyId);
|
|
69
|
+
this.pendingDeletes.add(keyId);
|
|
70
|
+
this._scheduleBatchSync();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Debounces and dispatches batched key updates to the cluster WebSocket.
|
|
75
|
+
*/
|
|
76
|
+
_scheduleBatchSync() {
|
|
77
|
+
if (this.syncDebounceTimer) return;
|
|
78
|
+
this.syncDebounceTimer = setTimeout(() => {
|
|
79
|
+
this.syncDebounceTimer = null;
|
|
80
|
+
if (this.pendingWrites.size === 0 && this.pendingDeletes.size === 0) return;
|
|
81
|
+
|
|
82
|
+
const writes = [];
|
|
83
|
+
for (const [keyId, data] of this.pendingWrites.entries()) {
|
|
84
|
+
writes.push({ keyId, data });
|
|
85
|
+
}
|
|
86
|
+
const deletes = Array.from(this.pendingDeletes);
|
|
87
|
+
|
|
88
|
+
this.pendingWrites.clear();
|
|
89
|
+
this.pendingDeletes.clear();
|
|
90
|
+
|
|
91
|
+
this.onSessionBatchUpdate({ writes, deletes });
|
|
92
|
+
}, 150);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Hydrates local session disk cache with stored credentials from MongoDB.
|
|
97
|
+
*/
|
|
98
|
+
hydrateFromSessionFiles(files) {
|
|
99
|
+
if (!files || typeof files !== "object") return;
|
|
100
|
+
if (!fs.existsSync(this.authDir)) {
|
|
101
|
+
fs.mkdirSync(this.authDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let hydratedCount = 0;
|
|
105
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
106
|
+
if (!filename || typeof content !== "string") continue;
|
|
107
|
+
const safeName = filename.replace(/\//g, "__").replace(/:/g, "-");
|
|
108
|
+
const targetPath = path.join(this.authDir, safeName);
|
|
109
|
+
try {
|
|
110
|
+
fs.writeFileSync(targetPath, content, "utf-8");
|
|
111
|
+
hydratedCount++;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(`[WhatsApp Worker] Failed to hydrate ${filename}:`, err.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log(`[WhatsApp Worker] Successfully hydrated ${hydratedCount} session files from MongoDB.`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reads all local JSON credential files from disk for initial backup to MongoDB.
|
|
121
|
+
*/
|
|
122
|
+
exportAllSessionFiles() {
|
|
123
|
+
if (!fs.existsSync(this.authDir)) return {};
|
|
124
|
+
const result = {};
|
|
125
|
+
try {
|
|
126
|
+
const files = fs.readdirSync(this.authDir);
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
if (file.endsWith(".json")) {
|
|
129
|
+
try {
|
|
130
|
+
const content = fs.readFileSync(path.join(this.authDir, file), "utf-8");
|
|
131
|
+
result[file] = content;
|
|
132
|
+
} catch (_) {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (_) {}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
47
139
|
/**
|
|
48
140
|
* Wipes local stored credentials.
|
|
49
141
|
*/
|
|
50
142
|
clearAuth() {
|
|
143
|
+
this.pendingWrites.clear();
|
|
144
|
+
this.pendingDeletes.clear();
|
|
145
|
+
if (this.syncDebounceTimer) {
|
|
146
|
+
clearTimeout(this.syncDebounceTimer);
|
|
147
|
+
this.syncDebounceTimer = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
51
150
|
if (fs.existsSync(this.authDir)) {
|
|
52
151
|
try {
|
|
53
152
|
fs.rmSync(this.authDir, { recursive: true, force: true });
|
|
@@ -142,6 +241,44 @@ export class WhatsAppWorkerHandler {
|
|
|
142
241
|
|
|
143
242
|
try {
|
|
144
243
|
const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
|
|
244
|
+
|
|
245
|
+
// Wrap saveCreds to mirror credentials to central MongoDB
|
|
246
|
+
const originalSaveCreds = saveCreds;
|
|
247
|
+
const wrappedSaveCreds = async () => {
|
|
248
|
+
await originalSaveCreds();
|
|
249
|
+
try {
|
|
250
|
+
const credsPath = path.join(this.authDir, "creds.json");
|
|
251
|
+
if (fs.existsSync(credsPath)) {
|
|
252
|
+
const credsStr = fs.readFileSync(credsPath, "utf-8");
|
|
253
|
+
this.queueSyncWrite("creds.json", credsStr);
|
|
254
|
+
}
|
|
255
|
+
} catch (_) {}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Wrap state.keys.set to mirror key additions, updates and removals to central MongoDB
|
|
259
|
+
const originalKeysSet = state.keys.set;
|
|
260
|
+
state.keys.set = async (data) => {
|
|
261
|
+
await originalKeysSet(data);
|
|
262
|
+
for (const category in data) {
|
|
263
|
+
for (const id in data[category]) {
|
|
264
|
+
const value = data[category][id];
|
|
265
|
+
const file = `${category}-${id}.json`;
|
|
266
|
+
const safeName = file.replace(/\//g, "__").replace(/:/g, "-");
|
|
267
|
+
if (value) {
|
|
268
|
+
try {
|
|
269
|
+
const filePath = path.join(this.authDir, safeName);
|
|
270
|
+
if (fs.existsSync(filePath)) {
|
|
271
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
272
|
+
this.queueSyncWrite(safeName, content);
|
|
273
|
+
}
|
|
274
|
+
} catch (_) {}
|
|
275
|
+
} else {
|
|
276
|
+
this.queueSyncDelete(safeName);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
145
282
|
let version = undefined;
|
|
146
283
|
try {
|
|
147
284
|
const fetched = await fetchLatestBaileysVersion();
|
|
@@ -167,7 +304,7 @@ export class WhatsAppWorkerHandler {
|
|
|
167
304
|
|
|
168
305
|
this.sock = makeWASocket(socketConfig);
|
|
169
306
|
|
|
170
|
-
this.sock.ev.on("creds.update",
|
|
307
|
+
this.sock.ev.on("creds.update", wrappedSaveCreds);
|
|
171
308
|
|
|
172
309
|
// Message Upsert listener for companion store sync & pre-send delivery reconciliation
|
|
173
310
|
this.sock.ev.on("messages.upsert", ({ messages }) => {
|
|
@@ -243,6 +380,7 @@ export class WhatsAppWorkerHandler {
|
|
|
243
380
|
this.user = null;
|
|
244
381
|
this.currentQrDataUrl = null;
|
|
245
382
|
this.clearAuth();
|
|
383
|
+
this.onClearSession();
|
|
246
384
|
|
|
247
385
|
if (this.sock) {
|
|
248
386
|
try {
|
|
@@ -326,6 +464,7 @@ export class WhatsAppWorkerHandler {
|
|
|
326
464
|
* Sends a plain text WhatsApp message with typing simulation and jitter.
|
|
327
465
|
*/
|
|
328
466
|
async sendMessage({ recipientPhone, message, idempotencyKey = null }) {
|
|
467
|
+
logInfo(`[WhatsApp] Incoming message dispatch for ${recipientPhone}`);
|
|
329
468
|
await this.initialize(false);
|
|
330
469
|
if (!this.sock || this.status !== "CONNECTED") {
|
|
331
470
|
throw new Error("WhatsApp companion device is not connected. Please pair via Admin Panel.");
|
|
@@ -337,6 +476,7 @@ export class WhatsAppWorkerHandler {
|
|
|
337
476
|
if (idempotencyKey) {
|
|
338
477
|
const alreadyDelivered = await this.reconcileDelivery(recipientPhone, idempotencyKey);
|
|
339
478
|
if (alreadyDelivered) {
|
|
479
|
+
logInfo(`[WhatsApp] Reconciled delivery for ${recipientPhone}; duplicate aborted.`);
|
|
340
480
|
return {
|
|
341
481
|
success: true,
|
|
342
482
|
reconciled: true,
|
|
@@ -355,6 +495,7 @@ export class WhatsAppWorkerHandler {
|
|
|
355
495
|
const targetJid = verified.jid || jid;
|
|
356
496
|
|
|
357
497
|
// 3. Human Typing Simulation
|
|
498
|
+
logInfo(`[WhatsApp] Simulating human typing for ${digits}...`);
|
|
358
499
|
await this.sock.sendPresenceUpdate("composing", targetJid).catch(() => {});
|
|
359
500
|
const typingDurationMs = Math.min(Math.max((message.length || 20) * 35, 2000), 4500);
|
|
360
501
|
await new Promise((r) => setTimeout(r, typingDurationMs));
|
|
@@ -370,10 +511,12 @@ export class WhatsAppWorkerHandler {
|
|
|
370
511
|
// 5. Dispatch
|
|
371
512
|
const result = await this.sock.sendMessage(targetJid, { text: finalMessage });
|
|
372
513
|
this._resetIdleTimer();
|
|
514
|
+
const msgId = result?.key?.id || `wamid-${Date.now()}`;
|
|
515
|
+
logSuccess(`[WhatsApp] Text message successfully delivered to ${recipientPhone} (ID: ${msgId})`);
|
|
373
516
|
|
|
374
517
|
return {
|
|
375
518
|
success: true,
|
|
376
|
-
messageId:
|
|
519
|
+
messageId: msgId,
|
|
377
520
|
timestamp: Date.now(),
|
|
378
521
|
};
|
|
379
522
|
}
|
|
@@ -391,6 +534,7 @@ export class WhatsAppWorkerHandler {
|
|
|
391
534
|
caption = "",
|
|
392
535
|
idempotencyKey = null,
|
|
393
536
|
}) {
|
|
537
|
+
logInfo(`[WhatsApp] Incoming document task for ${recipientPhone} (${fileName})`);
|
|
394
538
|
await this.initialize(false);
|
|
395
539
|
if (!this.sock || this.status !== "CONNECTED") {
|
|
396
540
|
throw new Error("WhatsApp companion device is not connected. Please pair via Admin Panel.");
|
|
@@ -402,6 +546,7 @@ export class WhatsAppWorkerHandler {
|
|
|
402
546
|
if (idempotencyKey) {
|
|
403
547
|
const alreadyDelivered = await this.reconcileDelivery(recipientPhone, idempotencyKey);
|
|
404
548
|
if (alreadyDelivered) {
|
|
549
|
+
logInfo(`[WhatsApp] Reconciled delivery for ${recipientPhone}; duplicate aborted.`);
|
|
405
550
|
return {
|
|
406
551
|
success: true,
|
|
407
552
|
reconciled: true,
|
|
@@ -420,6 +565,7 @@ export class WhatsAppWorkerHandler {
|
|
|
420
565
|
const targetJid = verified.jid || jid;
|
|
421
566
|
|
|
422
567
|
// 3. Stream PDF from authenticated URL (Zero buffer in central WebSocket)
|
|
568
|
+
logInfo(`[WhatsApp] Streaming document attachment (${fileName})...`);
|
|
423
569
|
const response = await axios.get(downloadUrl, {
|
|
424
570
|
responseType: "arraybuffer",
|
|
425
571
|
timeout: 25000,
|
|
@@ -444,8 +590,10 @@ export class WhatsAppWorkerHandler {
|
|
|
444
590
|
);
|
|
445
591
|
}
|
|
446
592
|
}
|
|
593
|
+
logInfo(`[WhatsApp] Document received (${(buffer.length / 1024).toFixed(1)} KB), SHA-256 integrity verified (${actualSha256.substring(0, 12)}...)`);
|
|
447
594
|
|
|
448
595
|
// 5. Human Typing Simulation
|
|
596
|
+
logInfo(`[WhatsApp] Simulating human typing for ${digits}...`);
|
|
449
597
|
await this.sock.sendPresenceUpdate("composing", targetJid).catch(() => {});
|
|
450
598
|
await new Promise((r) => setTimeout(r, 2500));
|
|
451
599
|
await this.sock.sendPresenceUpdate("paused", targetJid).catch(() => {});
|
|
@@ -466,10 +614,12 @@ export class WhatsAppWorkerHandler {
|
|
|
466
614
|
});
|
|
467
615
|
|
|
468
616
|
this._resetIdleTimer();
|
|
617
|
+
const msgId = result?.key?.id || `wamid-${Date.now()}`;
|
|
618
|
+
logSuccess(`[WhatsApp] Document successfully delivered to ${recipientPhone} (ID: ${msgId})`);
|
|
469
619
|
|
|
470
620
|
return {
|
|
471
621
|
success: true,
|
|
472
|
-
messageId:
|
|
622
|
+
messageId: msgId,
|
|
473
623
|
timestamp: Date.now(),
|
|
474
624
|
};
|
|
475
625
|
}
|
|
@@ -489,6 +639,7 @@ export class WhatsAppWorkerHandler {
|
|
|
489
639
|
}
|
|
490
640
|
|
|
491
641
|
this.clearAuth();
|
|
642
|
+
this.onClearSession();
|
|
492
643
|
this.status = "DISCONNECTED";
|
|
493
644
|
this.user = null;
|
|
494
645
|
this.currentQrDataUrl = null;
|