@diegoaltoworks/chatter 0.28.0 → 0.29.0
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/bin/wa-pair.js +320 -0
- package/dist/channels/whatsapp/authState.d.ts +39 -0
- package/dist/channels/whatsapp/authState.d.ts.map +1 -0
- package/dist/channels/whatsapp/authState.test.d.ts +2 -0
- package/dist/channels/whatsapp/authState.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/baileys.d.ts +21 -0
- package/dist/channels/whatsapp/baileys.d.ts.map +1 -0
- package/dist/channels/whatsapp/baileys.test.d.ts +2 -0
- package/dist/channels/whatsapp/baileys.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/channel.d.ts +95 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -0
- package/dist/channels/whatsapp/channel.test.d.ts +2 -0
- package/dist/channels/whatsapp/channel.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/crypto.d.ts +10 -0
- package/dist/channels/whatsapp/crypto.d.ts.map +1 -0
- package/dist/channels/whatsapp/crypto.test.d.ts +2 -0
- package/dist/channels/whatsapp/crypto.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/index.d.ts +33 -0
- package/dist/channels/whatsapp/index.d.ts.map +1 -0
- package/dist/channels/whatsapp/index.js +478 -0
- package/dist/channels/whatsapp/index.mjs +428 -0
- package/dist/channels/whatsapp/lease.d.ts +42 -0
- package/dist/channels/whatsapp/lease.d.ts.map +1 -0
- package/dist/channels/whatsapp/lease.test.d.ts +2 -0
- package/dist/channels/whatsapp/lease.test.d.ts.map +1 -0
- package/package.json +23 -4
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/channels/whatsapp/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
LEASE_HEARTBEAT_MS: () => LEASE_HEARTBEAT_MS,
|
|
34
|
+
LEASE_STALE_MS: () => LEASE_STALE_MS,
|
|
35
|
+
LEASE_WAIT_MS: () => LEASE_WAIT_MS,
|
|
36
|
+
acquireSessionLease: () => acquireSessionLease,
|
|
37
|
+
canAcquireLease: () => canAcquireLease,
|
|
38
|
+
createTursoWaLeaseStore: () => createTursoWaLeaseStore,
|
|
39
|
+
createWhatsAppChannel: () => createWhatsAppChannel,
|
|
40
|
+
decrypt: () => decrypt,
|
|
41
|
+
encrypt: () => encrypt,
|
|
42
|
+
loadBaileys: () => loadBaileys,
|
|
43
|
+
reconnectDelayMs: () => reconnectDelayMs,
|
|
44
|
+
senderNameFor: () => senderNameFor,
|
|
45
|
+
shutdownWaSessions: () => shutdownWaSessions,
|
|
46
|
+
useTursoAuthState: () => useTursoAuthState
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/channels/whatsapp/crypto.ts
|
|
51
|
+
var import_node_crypto = require("node:crypto");
|
|
52
|
+
var IV_LENGTH = 12;
|
|
53
|
+
var TAG_LENGTH = 16;
|
|
54
|
+
function deriveKey(secret) {
|
|
55
|
+
return (0, import_node_crypto.createHash)("sha256").update(secret, "utf-8").digest();
|
|
56
|
+
}
|
|
57
|
+
function encrypt(plaintext, secret) {
|
|
58
|
+
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
59
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", deriveKey(secret), iv);
|
|
60
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
61
|
+
return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]).toString("base64");
|
|
62
|
+
}
|
|
63
|
+
function decrypt(payload, secret) {
|
|
64
|
+
const raw = Buffer.from(payload, "base64");
|
|
65
|
+
const iv = raw.subarray(0, IV_LENGTH);
|
|
66
|
+
const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
67
|
+
const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
|
|
68
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", deriveKey(secret), iv);
|
|
69
|
+
decipher.setAuthTag(tag);
|
|
70
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/channels/whatsapp/authState.ts
|
|
74
|
+
var schemaReady = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
function ensureAuthSchema(client) {
|
|
76
|
+
let ready = schemaReady.get(client);
|
|
77
|
+
if (!ready) {
|
|
78
|
+
ready = client.execute("CREATE TABLE IF NOT EXISTS wa_auth (id TEXT PRIMARY KEY, value TEXT NOT NULL)").catch((error) => {
|
|
79
|
+
schemaReady.delete(client);
|
|
80
|
+
throw error;
|
|
81
|
+
});
|
|
82
|
+
schemaReady.set(client, ready);
|
|
83
|
+
}
|
|
84
|
+
return ready;
|
|
85
|
+
}
|
|
86
|
+
async function useTursoAuthState(db, secret, sessionId, runtime) {
|
|
87
|
+
await ensureAuthSchema(db);
|
|
88
|
+
const rowId = (id) => sessionId === "default" ? id : `s:${sessionId}/${id}`;
|
|
89
|
+
async function read(id) {
|
|
90
|
+
const result = await db.execute({
|
|
91
|
+
sql: "SELECT value FROM wa_auth WHERE id = ?",
|
|
92
|
+
args: [rowId(id)]
|
|
93
|
+
});
|
|
94
|
+
const row = result.rows[0];
|
|
95
|
+
if (!row) return null;
|
|
96
|
+
return JSON.parse(decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
97
|
+
}
|
|
98
|
+
async function write(id, value) {
|
|
99
|
+
const payload = encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
100
|
+
await db.execute({
|
|
101
|
+
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
102
|
+
args: [rowId(id), payload]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async function remove(id) {
|
|
106
|
+
await db.execute({ sql: "DELETE FROM wa_auth WHERE id = ?", args: [rowId(id)] });
|
|
107
|
+
}
|
|
108
|
+
const creds = await read("creds") ?? runtime.initAuthCreds();
|
|
109
|
+
return {
|
|
110
|
+
state: {
|
|
111
|
+
creds,
|
|
112
|
+
keys: {
|
|
113
|
+
get: async (type, ids) => {
|
|
114
|
+
const data = {};
|
|
115
|
+
await Promise.all(
|
|
116
|
+
ids.map(async (id) => {
|
|
117
|
+
let value = await read(`${type}-${id}`);
|
|
118
|
+
if (type === "app-state-sync-key" && value) {
|
|
119
|
+
value = runtime.appStateSyncKeyFromObject(
|
|
120
|
+
value
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (value) data[id] = value;
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
return data;
|
|
127
|
+
},
|
|
128
|
+
set: async (data) => {
|
|
129
|
+
const jobs = [];
|
|
130
|
+
for (const category of Object.keys(data)) {
|
|
131
|
+
const entries = data[category];
|
|
132
|
+
if (!entries) continue;
|
|
133
|
+
for (const id of Object.keys(entries)) {
|
|
134
|
+
const value = entries[id];
|
|
135
|
+
jobs.push(value ? write(`${category}-${id}`, value) : remove(`${category}-${id}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await Promise.all(jobs);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
saveCreds: () => write("creds", creds),
|
|
143
|
+
clear: async () => {
|
|
144
|
+
if (sessionId === "default") {
|
|
145
|
+
await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
|
|
146
|
+
} else {
|
|
147
|
+
await db.execute({
|
|
148
|
+
sql: "DELETE FROM wa_auth WHERE id LIKE ?",
|
|
149
|
+
args: [`s:${sessionId}/%`]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/channels/whatsapp/baileys.ts
|
|
157
|
+
function wrapMissingBaileysError(cause) {
|
|
158
|
+
return new Error(
|
|
159
|
+
"The WhatsApp channel ('@diegoaltoworks/chatter/whatsapp') requires the optional peer dependency '@whiskeysockets/baileys', which is not installed. Install it with `bun add @whiskeysockets/baileys` (or npm/pnpm/yarn) to use this channel.",
|
|
160
|
+
{ cause }
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
var cached;
|
|
164
|
+
function loadBaileys() {
|
|
165
|
+
if (!cached) {
|
|
166
|
+
cached = import("@whiskeysockets/baileys").catch((error) => {
|
|
167
|
+
cached = void 0;
|
|
168
|
+
throw wrapMissingBaileysError(error);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return cached;
|
|
172
|
+
}
|
|
173
|
+
function silentLogger() {
|
|
174
|
+
const noop = () => void 0;
|
|
175
|
+
const logger = {
|
|
176
|
+
level: "silent",
|
|
177
|
+
trace: noop,
|
|
178
|
+
debug: noop,
|
|
179
|
+
info: noop,
|
|
180
|
+
warn: noop,
|
|
181
|
+
error: noop,
|
|
182
|
+
fatal: noop
|
|
183
|
+
};
|
|
184
|
+
logger.child = () => logger;
|
|
185
|
+
return logger;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/channels/whatsapp/lease.ts
|
|
189
|
+
var LEASE_STALE_MS = 9e4;
|
|
190
|
+
var LEASE_HEARTBEAT_MS = 3e4;
|
|
191
|
+
var LEASE_WAIT_MS = 3e4;
|
|
192
|
+
function canAcquireLease(existing, instanceId, now, staleMs = LEASE_STALE_MS) {
|
|
193
|
+
if (!existing) return true;
|
|
194
|
+
if (existing.instanceId === instanceId) return true;
|
|
195
|
+
return now - existing.heartbeatAt >= staleMs;
|
|
196
|
+
}
|
|
197
|
+
var schemaReady2 = /* @__PURE__ */ new WeakMap();
|
|
198
|
+
function ensureWaLeaseSchema(client) {
|
|
199
|
+
let ready = schemaReady2.get(client);
|
|
200
|
+
if (!ready) {
|
|
201
|
+
ready = client.execute(
|
|
202
|
+
`CREATE TABLE IF NOT EXISTS wa_lease (
|
|
203
|
+
session_id TEXT PRIMARY KEY,
|
|
204
|
+
instance_id TEXT NOT NULL,
|
|
205
|
+
heartbeat_at INTEGER NOT NULL
|
|
206
|
+
)`
|
|
207
|
+
).catch((error) => {
|
|
208
|
+
schemaReady2.delete(client);
|
|
209
|
+
throw error;
|
|
210
|
+
});
|
|
211
|
+
schemaReady2.set(client, ready);
|
|
212
|
+
}
|
|
213
|
+
return ready;
|
|
214
|
+
}
|
|
215
|
+
function createTursoWaLeaseStore(client) {
|
|
216
|
+
return {
|
|
217
|
+
async tryAcquire(sessionId, instanceId, now, staleMs) {
|
|
218
|
+
await ensureWaLeaseSchema(client);
|
|
219
|
+
const result = await client.execute({
|
|
220
|
+
sql: `INSERT INTO wa_lease (session_id, instance_id, heartbeat_at) VALUES (?, ?, ?)
|
|
221
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
222
|
+
instance_id = excluded.instance_id,
|
|
223
|
+
heartbeat_at = excluded.heartbeat_at
|
|
224
|
+
WHERE wa_lease.instance_id = excluded.instance_id
|
|
225
|
+
OR wa_lease.heartbeat_at <= excluded.heartbeat_at - ?
|
|
226
|
+
RETURNING instance_id`,
|
|
227
|
+
args: [sessionId, instanceId, now, staleMs]
|
|
228
|
+
});
|
|
229
|
+
const row = result.rows[0];
|
|
230
|
+
return Boolean(row) && row.instance_id === instanceId;
|
|
231
|
+
},
|
|
232
|
+
async release(sessionId, instanceId) {
|
|
233
|
+
await ensureWaLeaseSchema(client);
|
|
234
|
+
await client.execute({
|
|
235
|
+
sql: "DELETE FROM wa_lease WHERE session_id = ? AND instance_id = ?",
|
|
236
|
+
args: [sessionId, instanceId]
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/channels/whatsapp/channel.ts
|
|
243
|
+
var RECONNECT_DELAY_MS = 5e3;
|
|
244
|
+
var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
|
|
245
|
+
function reconnectDelayMs(failures) {
|
|
246
|
+
return Math.min(RECONNECT_DELAY_MS * 2 ** Math.min(failures, 20), RECONNECT_MAX_DELAY_MS);
|
|
247
|
+
}
|
|
248
|
+
async function shutdownWaSessions(sessions) {
|
|
249
|
+
await Promise.all(
|
|
250
|
+
[...sessions.values()].map(async (handle) => {
|
|
251
|
+
handle.stopHeartbeat();
|
|
252
|
+
handle.sock.end(void 0);
|
|
253
|
+
await handle.release().catch(() => void 0);
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
async function loseSession(sessionId, sessions) {
|
|
258
|
+
const handle = sessions.get(sessionId);
|
|
259
|
+
if (!handle) return;
|
|
260
|
+
sessions.delete(sessionId);
|
|
261
|
+
await shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]]));
|
|
262
|
+
}
|
|
263
|
+
async function acquireSessionLease(sessionId, deps) {
|
|
264
|
+
const { leaseStore, instanceId, sessions, isStopped, connect } = deps;
|
|
265
|
+
const now = deps.now ?? Date.now;
|
|
266
|
+
const staleMs = deps.staleMs ?? LEASE_STALE_MS;
|
|
267
|
+
const heartbeatMs = deps.heartbeatMs ?? LEASE_HEARTBEAT_MS;
|
|
268
|
+
const waitMs = deps.waitMs ?? LEASE_WAIT_MS;
|
|
269
|
+
const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
|
|
270
|
+
if (isStopped()) return;
|
|
271
|
+
const retry = () => {
|
|
272
|
+
schedule(
|
|
273
|
+
() => void acquireSessionLease(sessionId, deps).catch(
|
|
274
|
+
(error) => console.error(`WhatsApp[${sessionId}]: lease re-check failed:`, error)
|
|
275
|
+
),
|
|
276
|
+
waitMs
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
let acquired;
|
|
280
|
+
try {
|
|
281
|
+
acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
|
|
284
|
+
retry();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!acquired) {
|
|
288
|
+
console.warn(`WhatsApp[${sessionId}]: lease held by another instance, re-checking later`);
|
|
289
|
+
retry();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const heartbeat = setInterval(() => {
|
|
293
|
+
void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
|
|
294
|
+
if (stillHeld || isStopped()) return;
|
|
295
|
+
console.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
|
|
296
|
+
void loseSession(sessionId, sessions).then(retry);
|
|
297
|
+
}).catch((error) => console.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
|
|
298
|
+
}, heartbeatMs);
|
|
299
|
+
if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
|
|
300
|
+
sessions.set(sessionId, {
|
|
301
|
+
sock: { end: () => void 0 },
|
|
302
|
+
stopHeartbeat: () => clearInterval(heartbeat),
|
|
303
|
+
release: () => leaseStore.release(sessionId, instanceId)
|
|
304
|
+
});
|
|
305
|
+
try {
|
|
306
|
+
await connect(sessionId);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
|
|
309
|
+
await loseSession(sessionId, sessions);
|
|
310
|
+
retry();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function senderNameFor(channelName, sessionId) {
|
|
314
|
+
return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
|
|
315
|
+
}
|
|
316
|
+
function createWhatsAppChannel(config) {
|
|
317
|
+
const channelName = "whatsapp";
|
|
318
|
+
const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
|
|
319
|
+
const now = config.now ?? Date.now;
|
|
320
|
+
const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
|
|
321
|
+
const staleMs = config.staleMs ?? LEASE_STALE_MS;
|
|
322
|
+
const heartbeatMs = config.heartbeatMs ?? LEASE_HEARTBEAT_MS;
|
|
323
|
+
const waitMs = config.waitMs ?? LEASE_WAIT_MS;
|
|
324
|
+
const loadBaileys2 = config.loadBaileys ?? loadBaileys;
|
|
325
|
+
let stopped = false;
|
|
326
|
+
const instanceId = crypto.randomUUID();
|
|
327
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
328
|
+
const failureCounts = /* @__PURE__ */ new Map();
|
|
329
|
+
const registeredSenderNames = /* @__PURE__ */ new Map();
|
|
330
|
+
let senders;
|
|
331
|
+
return {
|
|
332
|
+
name: channelName,
|
|
333
|
+
async start(deps) {
|
|
334
|
+
const db = deps.db;
|
|
335
|
+
const leaseStore = createTursoWaLeaseStore(db);
|
|
336
|
+
senders = deps.senders;
|
|
337
|
+
await loadBaileys2();
|
|
338
|
+
async function connect(sessionId) {
|
|
339
|
+
if (stopped || !sessions.has(sessionId)) return;
|
|
340
|
+
const baileys = await loadBaileys2();
|
|
341
|
+
const runtime = {
|
|
342
|
+
bufferJSON: baileys.BufferJSON,
|
|
343
|
+
initAuthCreds: baileys.initAuthCreds,
|
|
344
|
+
appStateSyncKeyFromObject: (value) => baileys.proto.Message.AppStateSyncKeyData.fromObject(value)
|
|
345
|
+
};
|
|
346
|
+
const { state, saveCreds } = await useTursoAuthState(
|
|
347
|
+
db,
|
|
348
|
+
config.sessionSecret,
|
|
349
|
+
sessionId,
|
|
350
|
+
runtime
|
|
351
|
+
);
|
|
352
|
+
if (!state.creds.registered) {
|
|
353
|
+
console.warn(
|
|
354
|
+
`WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
|
|
355
|
+
);
|
|
356
|
+
schedule(() => void connect(sessionId).catch(() => void 0), 6e4);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const { version } = await baileys.fetchLatestBaileysVersion();
|
|
360
|
+
const logger = silentLogger();
|
|
361
|
+
const sock = baileys.makeWASocket({
|
|
362
|
+
version,
|
|
363
|
+
auth: {
|
|
364
|
+
creds: state.creds,
|
|
365
|
+
// biome-ignore lint/suspicious/noExplicitAny: silentLogger is structurally pino-compatible; Baileys' logger type is not exported
|
|
366
|
+
keys: baileys.makeCacheableSignalKeyStore(state.keys, logger)
|
|
367
|
+
},
|
|
368
|
+
// biome-ignore lint/suspicious/noExplicitAny: silentLogger is structurally pino-compatible; Baileys' logger type is not exported
|
|
369
|
+
logger,
|
|
370
|
+
markOnlineOnConnect: false,
|
|
371
|
+
syncFullHistory: false
|
|
372
|
+
});
|
|
373
|
+
const leaseHandle = sessions.get(sessionId);
|
|
374
|
+
if (leaseHandle) leaseHandle.sock = sock;
|
|
375
|
+
sock.ev.on(
|
|
376
|
+
"creds.update",
|
|
377
|
+
() => void saveCreds().catch(
|
|
378
|
+
(error) => console.warn(`WhatsApp[${sessionId}]: creds save failed:`, error)
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
sock.ev.on("connection.update", (update) => {
|
|
382
|
+
const { connection, lastDisconnect } = update;
|
|
383
|
+
if (connection === "open") {
|
|
384
|
+
failureCounts.set(sessionId, 0);
|
|
385
|
+
console.log(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
|
|
386
|
+
const senderName = senderNameFor(channelName, sessionId);
|
|
387
|
+
const sender = {
|
|
388
|
+
sendText: async (chatId, text) => {
|
|
389
|
+
await sock.sendMessage(chatId, { text });
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
deps.senders.register(senderName, sender);
|
|
393
|
+
registeredSenderNames.set(sessionId, senderName);
|
|
394
|
+
}
|
|
395
|
+
if (connection === "close") {
|
|
396
|
+
const senderName = registeredSenderNames.get(sessionId);
|
|
397
|
+
if (senderName) {
|
|
398
|
+
deps.senders.unregister(senderName);
|
|
399
|
+
registeredSenderNames.delete(sessionId);
|
|
400
|
+
}
|
|
401
|
+
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
402
|
+
if (statusCode === baileys.DisconnectReason.loggedOut) {
|
|
403
|
+
console.error(
|
|
404
|
+
`WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
|
|
405
|
+
);
|
|
406
|
+
void loseSession(sessionId, sessions);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (!stopped && sessions.has(sessionId)) {
|
|
410
|
+
const delay = reconnectDelayMs(failureCounts.get(sessionId) ?? 0);
|
|
411
|
+
failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
|
|
412
|
+
console.warn(
|
|
413
|
+
`WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
|
|
414
|
+
);
|
|
415
|
+
schedule(
|
|
416
|
+
() => void connect(sessionId).catch(
|
|
417
|
+
(error) => console.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
|
|
418
|
+
),
|
|
419
|
+
delay
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
if (config.onMessage) {
|
|
425
|
+
sock.ev.on("messages.upsert", async ({ messages, type }) => {
|
|
426
|
+
if (type !== "notify") return;
|
|
427
|
+
for (const message of messages) {
|
|
428
|
+
try {
|
|
429
|
+
await config.onMessage?.({ sessionId, sock, message });
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const sessionId of sessionIds) {
|
|
438
|
+
void acquireSessionLease(sessionId, {
|
|
439
|
+
leaseStore,
|
|
440
|
+
instanceId,
|
|
441
|
+
sessions,
|
|
442
|
+
isStopped: () => stopped,
|
|
443
|
+
connect,
|
|
444
|
+
now,
|
|
445
|
+
staleMs,
|
|
446
|
+
heartbeatMs,
|
|
447
|
+
waitMs,
|
|
448
|
+
schedule
|
|
449
|
+
}).catch((error) => console.error(`WhatsApp[${sessionId}]: failed to start:`, error));
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
async stop() {
|
|
453
|
+
stopped = true;
|
|
454
|
+
for (const senderName of registeredSenderNames.values()) {
|
|
455
|
+
senders?.unregister(senderName);
|
|
456
|
+
}
|
|
457
|
+
registeredSenderNames.clear();
|
|
458
|
+
await shutdownWaSessions(sessions);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
463
|
+
0 && (module.exports = {
|
|
464
|
+
LEASE_HEARTBEAT_MS,
|
|
465
|
+
LEASE_STALE_MS,
|
|
466
|
+
LEASE_WAIT_MS,
|
|
467
|
+
acquireSessionLease,
|
|
468
|
+
canAcquireLease,
|
|
469
|
+
createTursoWaLeaseStore,
|
|
470
|
+
createWhatsAppChannel,
|
|
471
|
+
decrypt,
|
|
472
|
+
encrypt,
|
|
473
|
+
loadBaileys,
|
|
474
|
+
reconnectDelayMs,
|
|
475
|
+
senderNameFor,
|
|
476
|
+
shutdownWaSessions,
|
|
477
|
+
useTursoAuthState
|
|
478
|
+
});
|