@arcaelas/whatsapp 7.4.0 → 7.4.2
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/build/cjs/index.d.ts +1 -0
- package/build/cjs/lib/chat/index.js +16 -1
- package/build/cjs/lib/whatsapp/index.d.ts +28 -1
- package/build/cjs/lib/whatsapp/index.js +42 -7
- package/build/esm/index.d.ts +1 -0
- package/build/esm/lib/chat/index.js +16 -1
- package/build/esm/lib/whatsapp/index.d.ts +28 -1
- package/build/esm/lib/whatsapp/index.js +42 -7
- package/package.json +1 -1
package/build/cjs/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import WhatsApp from './lib/whatsapp';
|
|
|
7
7
|
export { WhatsApp };
|
|
8
8
|
export default WhatsApp;
|
|
9
9
|
export type IWhatsApp = ConstructorParameters<typeof WhatsApp>[0];
|
|
10
|
+
export type { Farewell } from './lib/whatsapp';
|
|
10
11
|
export type DisconnectOptions = NonNullable<Parameters<WhatsApp['disconnect']>[0]>;
|
|
11
12
|
export type ReconnectOption = NonNullable<IWhatsApp['reconnect']>;
|
|
12
13
|
export { FileSystemEngine, RedisEngine, S3Engine, SQLiteEngine, serialize, deserialize } from './lib/store';
|
|
@@ -215,7 +215,22 @@ function chat(init) {
|
|
|
215
215
|
*/
|
|
216
216
|
async seen() {
|
|
217
217
|
const last = await tail(this._raw.id);
|
|
218
|
-
|
|
218
|
+
// Marcar leído se acusa con recibos, no con una mutación del estado de la app.
|
|
219
|
+
// `chatModify({ markRead })` emite un app patch, y cuando el estado local va por
|
|
220
|
+
// detrás del servidor —lo normal en un dispositivo recién vinculado, que arranca en
|
|
221
|
+
// v0— WhatsApp responde al patch expulsando el dispositivo: `conflict
|
|
222
|
+
// (device_removed)`. Abrir un chat basta para provocarlo, así que la línea se caía
|
|
223
|
+
// sola al primer chat que se mirara. Los recibos no tocan el estado de la app.
|
|
224
|
+
// Marking read is acknowledged with receipts, not with an app-state mutation.
|
|
225
|
+
// `chatModify({ markRead })` emits an app patch, and when the local state trails the
|
|
226
|
+
// server —the norm on a freshly linked device, which starts at v0— WhatsApp answers
|
|
227
|
+
// that patch by dropping the device: `conflict (device_removed)`. Opening a chat was
|
|
228
|
+
// enough to trigger it, so the line died on the first chat anyone looked at.
|
|
229
|
+
// Receipts leave the app state alone.
|
|
230
|
+
const keys = last.messages.map(({ key }) => ({ remoteJid: key.remoteJid, id: key.id, participant: undefined }));
|
|
231
|
+
if (keys.length) {
|
|
232
|
+
await init.socket.readMessages(keys);
|
|
233
|
+
}
|
|
219
234
|
this._raw.unread_count = 0;
|
|
220
235
|
await init.engine.set(`/chat/${this._raw.id}`, (0, store_1.serialize)(this._raw), this._raw.activity ?? last.at);
|
|
221
236
|
return true;
|
|
@@ -32,10 +32,37 @@ interface Options {
|
|
|
32
32
|
* more than one should name them.
|
|
33
33
|
*/
|
|
34
34
|
device?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Nivel del log interno de baileys; `silent` por defecto. En silencio un cierre remoto no
|
|
37
|
+
* deja rastro de por qué ocurrió: `Farewell` da el código, pero el intercambio que llevó
|
|
38
|
+
* hasta él —el nodo que WhatsApp rechazó— sólo aparece subiendo esto a `debug` o `trace`.
|
|
39
|
+
* Level of baileys' internal log; `silent` by default. Kept silent, a remote close leaves no
|
|
40
|
+
* trace of why it happened: `Farewell` gives the code, but the exchange leading to it —the
|
|
41
|
+
* node WhatsApp rejected— only shows up by raising this to `debug` or `trace`.
|
|
42
|
+
*/
|
|
43
|
+
debug?: 'silent' | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Por qué se cerró la sesión. Sin esto un cierre remoto es indistinguible de otro: el evento
|
|
47
|
+
* sólo decía «se cerró», el logger va en silencio y `autoclean` borra la evidencia antes de que
|
|
48
|
+
* nadie pueda mirarla, así que no queda con qué diagnosticar por qué WhatsApp echó la línea.
|
|
49
|
+
* Why the session closed. Without it one remote close is indistinguishable from another: the
|
|
50
|
+
* event only said «it closed», the logger runs silent and `autoclean` wipes the evidence before
|
|
51
|
+
* anyone can look at it, leaving nothing to diagnose why WhatsApp dropped the line.
|
|
52
|
+
*/
|
|
53
|
+
export interface Farewell {
|
|
54
|
+
/** Código de baileys, si vino. / Baileys status code, if any. */
|
|
55
|
+
code: number | null;
|
|
56
|
+
/** Nombre del motivo (`loggedOut`, `connectionReplaced`, `badSession`…) o `unknown`. / Reason name or `unknown`. */
|
|
57
|
+
reason: string;
|
|
58
|
+
/** El teléfono desvinculó la sesión: no se reintenta y las credenciales ya no sirven. / The phone unlinked it: no retry, credentials are dead. */
|
|
59
|
+
expired: boolean;
|
|
60
|
+
/** Mensaje del error subyacente, si lo hubo. / Underlying error message, if any. */
|
|
61
|
+
detail: string | null;
|
|
35
62
|
}
|
|
36
63
|
interface EventMap {
|
|
37
64
|
connected: [WhatsApp];
|
|
38
|
-
disconnected: [WhatsApp];
|
|
65
|
+
disconnected: [WhatsApp, Farewell];
|
|
39
66
|
'contact:created': [ContactInstance, ChatInstance, WhatsApp];
|
|
40
67
|
'contact:updated': [ContactInstance, ChatInstance, WhatsApp];
|
|
41
68
|
'chat:created': [ChatInstance, WhatsApp];
|
|
@@ -53,6 +53,25 @@ const store_1 = require("../../lib/store");
|
|
|
53
53
|
* whole. That is not a name: taking it hides the real one, which is stored on the own contact
|
|
54
54
|
* card.
|
|
55
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* Cola por ruta para las escrituras de sesión. Baileys emite `creds.update` varias veces
|
|
58
|
+
* seguidas y lanza `keys.set` en paralelo; sin serializar, dos escrituras sobre el mismo
|
|
59
|
+
* archivo pueden resolverse en orden inverso y dejar el estado viejo encima del nuevo. Un
|
|
60
|
+
* archivo íntegro pero atrasado no se nota —el `tmp+rename` del engine lo deja bien formado—,
|
|
61
|
+
* y es peor que uno corrupto: WhatsApp lo rechaza en el handshake y cierra la sesión.
|
|
62
|
+
* Su propia referencia (`useMultiFileAuthState`) toma un mutex por archivo por esto mismo.
|
|
63
|
+
* Per-path queue for session writes. Baileys emits `creds.update` several times in a row and
|
|
64
|
+
* fires `keys.set` in parallel; without serializing, two writes to the same file can settle in
|
|
65
|
+
* reverse order and leave the old state on top of the new one. A whole but stale file goes
|
|
66
|
+
* unnoticed —the engine's `tmp+rename` leaves it well formed— and is worse than a corrupt one:
|
|
67
|
+
* WhatsApp rejects it at the handshake and closes the session. Their own reference
|
|
68
|
+
* (`useMultiFileAuthState`) takes a mutex per file for this very reason.
|
|
69
|
+
*/
|
|
70
|
+
const queued = (locks, path, work) => {
|
|
71
|
+
const next = (locks.get(path) ?? Promise.resolve()).then(work, work);
|
|
72
|
+
locks.set(path, next.catch(() => { }));
|
|
73
|
+
return next;
|
|
74
|
+
};
|
|
56
75
|
const readable = (value) => (value && !/^\+?[\d\s·•∙⋅]+$/.test(value) ? value : null);
|
|
57
76
|
class WhatsApp {
|
|
58
77
|
#event = new node_events_1.EventEmitter();
|
|
@@ -86,7 +105,7 @@ class WhatsApp {
|
|
|
86
105
|
}
|
|
87
106
|
async connect(callback) {
|
|
88
107
|
const { engine } = this;
|
|
89
|
-
const { phone, method, autoclean = true, sync = true, reconnect = true, device } = this.#options;
|
|
108
|
+
const { phone, method, autoclean = true, sync = true, reconnect = true, device, debug } = this.#options;
|
|
90
109
|
const digits = phone !== undefined ? String(phone).replace(/\D+/g, '') : '';
|
|
91
110
|
const budget = reconnect === false ? 0 : reconnect === true ? null : typeof reconnect === 'number' ? reconnect : reconnect.max ?? null;
|
|
92
111
|
const wait = typeof reconnect === 'object' ? (reconnect.interval ?? 60) * 1_000 : 60_000;
|
|
@@ -99,6 +118,7 @@ class WhatsApp {
|
|
|
99
118
|
let alive = null;
|
|
100
119
|
let timer = null;
|
|
101
120
|
let chain = Promise.resolve();
|
|
121
|
+
const locks = new Map();
|
|
102
122
|
return new Promise((resolve, reject) => {
|
|
103
123
|
const start = async () => {
|
|
104
124
|
const creds = (0, store_1.deserialize)(await engine.get('/session/creds')) ?? (0, baileys_1.initAuthCreds)();
|
|
@@ -120,14 +140,15 @@ class WhatsApp {
|
|
|
120
140
|
return data;
|
|
121
141
|
},
|
|
122
142
|
set: async (data) => {
|
|
123
|
-
await Promise.all(Object.entries(data).flatMap(([category, entries]) => Object.entries(entries).map(([id, value]) =>
|
|
124
|
-
|
|
125
|
-
: engine.unset(
|
|
143
|
+
await Promise.all(Object.entries(data).flatMap(([category, entries]) => Object.entries(entries).map(([id, value]) => {
|
|
144
|
+
const path = `/session/${category}/${id}`;
|
|
145
|
+
return queued(locks, path, () => (value != null ? engine.set(path, (0, store_1.serialize)(value)) : engine.unset(path)));
|
|
146
|
+
})));
|
|
126
147
|
},
|
|
127
148
|
},
|
|
128
149
|
},
|
|
129
150
|
browser: baileys_1.Browsers.windows(device ?? 'Chrome'),
|
|
130
|
-
logger: (0, pino_1.default)({ level: 'silent' }),
|
|
151
|
+
logger: (0, pino_1.default)({ level: debug ?? 'silent' }),
|
|
131
152
|
syncFullHistory: sync,
|
|
132
153
|
shouldSyncHistoryMessage: ({ syncType }) => sync || syncType !== baileys_1.proto.HistorySync.HistorySyncType.FULL,
|
|
133
154
|
// Cuando el receptor no puede descifrar pide un retry; el cache interno de
|
|
@@ -272,7 +293,7 @@ class WhatsApp {
|
|
|
272
293
|
}
|
|
273
294
|
}
|
|
274
295
|
};
|
|
275
|
-
socket.ev.on('creds.update', () => engine.set('/session/creds', (0, store_1.serialize)(creds)));
|
|
296
|
+
socket.ev.on('creds.update', () => queued(locks, '/session/creds', () => engine.set('/session/creds', (0, store_1.serialize)(creds))));
|
|
276
297
|
socket.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => {
|
|
277
298
|
if (qr && !creds.registered) {
|
|
278
299
|
await callback(digits && (method ?? 'otp') === 'otp'
|
|
@@ -295,11 +316,25 @@ class WhatsApp {
|
|
|
295
316
|
else if (connection === 'close') {
|
|
296
317
|
const code = lastDisconnect?.error?.output?.statusCode;
|
|
297
318
|
const transient = code === baileys_1.DisconnectReason.restartRequired;
|
|
319
|
+
const farewell = {
|
|
320
|
+
code: code ?? null,
|
|
321
|
+
reason: Object.entries(baileys_1.DisconnectReason).find(([, value]) => value === code)?.[0] ?? 'unknown',
|
|
322
|
+
expired: code === baileys_1.DisconnectReason.loggedOut,
|
|
323
|
+
detail: lastDisconnect?.error instanceof Error ? lastDisconnect.error.message : null,
|
|
324
|
+
};
|
|
298
325
|
if (code === baileys_1.DisconnectReason.loggedOut) {
|
|
326
|
+
// Las escrituras en vuelo tienen que aterrizar antes de borrar, o una
|
|
327
|
+
// de ellas resucita las credenciales justo después del clear y la
|
|
328
|
+
// siguiente conexión arranca con restos de una sesión ya muerta.
|
|
329
|
+
// In-flight writes must land before wiping, or one of them resurrects
|
|
330
|
+
// the credentials right after the clear and the next connection starts
|
|
331
|
+
// on the leftovers of an already dead session.
|
|
332
|
+
await Promise.allSettled([...locks.values()]);
|
|
333
|
+
locks.clear();
|
|
299
334
|
await (autoclean ? engine.clear() : engine.unset('/session/creds'));
|
|
300
335
|
}
|
|
301
336
|
if (connected && !transient && !silent) {
|
|
302
|
-
this.emit('disconnected', this);
|
|
337
|
+
this.emit('disconnected', this, farewell);
|
|
303
338
|
}
|
|
304
339
|
if (intentional) {
|
|
305
340
|
/* cierre pedido por disconnect(): sin reintentos / close requested by disconnect(): no retries */
|
package/build/esm/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import WhatsApp from './lib/whatsapp/index.js';
|
|
|
7
7
|
export { WhatsApp };
|
|
8
8
|
export default WhatsApp;
|
|
9
9
|
export type IWhatsApp = ConstructorParameters<typeof WhatsApp>[0];
|
|
10
|
+
export type { Farewell } from './lib/whatsapp/index.js';
|
|
10
11
|
export type DisconnectOptions = NonNullable<Parameters<WhatsApp['disconnect']>[0]>;
|
|
11
12
|
export type ReconnectOption = NonNullable<IWhatsApp['reconnect']>;
|
|
12
13
|
export { FileSystemEngine, RedisEngine, S3Engine, SQLiteEngine, serialize, deserialize } from './lib/store/index.js';
|
|
@@ -211,7 +211,22 @@ export function chat(init) {
|
|
|
211
211
|
*/
|
|
212
212
|
async seen() {
|
|
213
213
|
const last = await tail(this._raw.id);
|
|
214
|
-
|
|
214
|
+
// Marcar leído se acusa con recibos, no con una mutación del estado de la app.
|
|
215
|
+
// `chatModify({ markRead })` emite un app patch, y cuando el estado local va por
|
|
216
|
+
// detrás del servidor —lo normal en un dispositivo recién vinculado, que arranca en
|
|
217
|
+
// v0— WhatsApp responde al patch expulsando el dispositivo: `conflict
|
|
218
|
+
// (device_removed)`. Abrir un chat basta para provocarlo, así que la línea se caía
|
|
219
|
+
// sola al primer chat que se mirara. Los recibos no tocan el estado de la app.
|
|
220
|
+
// Marking read is acknowledged with receipts, not with an app-state mutation.
|
|
221
|
+
// `chatModify({ markRead })` emits an app patch, and when the local state trails the
|
|
222
|
+
// server —the norm on a freshly linked device, which starts at v0— WhatsApp answers
|
|
223
|
+
// that patch by dropping the device: `conflict (device_removed)`. Opening a chat was
|
|
224
|
+
// enough to trigger it, so the line died on the first chat anyone looked at.
|
|
225
|
+
// Receipts leave the app state alone.
|
|
226
|
+
const keys = last.messages.map(({ key }) => ({ remoteJid: key.remoteJid, id: key.id, participant: undefined }));
|
|
227
|
+
if (keys.length) {
|
|
228
|
+
await init.socket.readMessages(keys);
|
|
229
|
+
}
|
|
215
230
|
this._raw.unread_count = 0;
|
|
216
231
|
await init.engine.set(`/chat/${this._raw.id}`, serialize(this._raw), this._raw.activity ?? last.at);
|
|
217
232
|
return true;
|
|
@@ -32,10 +32,37 @@ interface Options {
|
|
|
32
32
|
* more than one should name them.
|
|
33
33
|
*/
|
|
34
34
|
device?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Nivel del log interno de baileys; `silent` por defecto. En silencio un cierre remoto no
|
|
37
|
+
* deja rastro de por qué ocurrió: `Farewell` da el código, pero el intercambio que llevó
|
|
38
|
+
* hasta él —el nodo que WhatsApp rechazó— sólo aparece subiendo esto a `debug` o `trace`.
|
|
39
|
+
* Level of baileys' internal log; `silent` by default. Kept silent, a remote close leaves no
|
|
40
|
+
* trace of why it happened: `Farewell` gives the code, but the exchange leading to it —the
|
|
41
|
+
* node WhatsApp rejected— only shows up by raising this to `debug` or `trace`.
|
|
42
|
+
*/
|
|
43
|
+
debug?: 'silent' | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Por qué se cerró la sesión. Sin esto un cierre remoto es indistinguible de otro: el evento
|
|
47
|
+
* sólo decía «se cerró», el logger va en silencio y `autoclean` borra la evidencia antes de que
|
|
48
|
+
* nadie pueda mirarla, así que no queda con qué diagnosticar por qué WhatsApp echó la línea.
|
|
49
|
+
* Why the session closed. Without it one remote close is indistinguishable from another: the
|
|
50
|
+
* event only said «it closed», the logger runs silent and `autoclean` wipes the evidence before
|
|
51
|
+
* anyone can look at it, leaving nothing to diagnose why WhatsApp dropped the line.
|
|
52
|
+
*/
|
|
53
|
+
export interface Farewell {
|
|
54
|
+
/** Código de baileys, si vino. / Baileys status code, if any. */
|
|
55
|
+
code: number | null;
|
|
56
|
+
/** Nombre del motivo (`loggedOut`, `connectionReplaced`, `badSession`…) o `unknown`. / Reason name or `unknown`. */
|
|
57
|
+
reason: string;
|
|
58
|
+
/** El teléfono desvinculó la sesión: no se reintenta y las credenciales ya no sirven. / The phone unlinked it: no retry, credentials are dead. */
|
|
59
|
+
expired: boolean;
|
|
60
|
+
/** Mensaje del error subyacente, si lo hubo. / Underlying error message, if any. */
|
|
61
|
+
detail: string | null;
|
|
35
62
|
}
|
|
36
63
|
interface EventMap {
|
|
37
64
|
connected: [WhatsApp];
|
|
38
|
-
disconnected: [WhatsApp];
|
|
65
|
+
disconnected: [WhatsApp, Farewell];
|
|
39
66
|
'contact:created': [ContactInstance, ChatInstance, WhatsApp];
|
|
40
67
|
'contact:updated': [ContactInstance, ChatInstance, WhatsApp];
|
|
41
68
|
'chat:created': [ChatInstance, WhatsApp];
|
|
@@ -15,6 +15,25 @@ import { deserialize, jid_of, serialize } from '../../lib/store/index.js';
|
|
|
15
15
|
* whole. That is not a name: taking it hides the real one, which is stored on the own contact
|
|
16
16
|
* card.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* Cola por ruta para las escrituras de sesión. Baileys emite `creds.update` varias veces
|
|
20
|
+
* seguidas y lanza `keys.set` en paralelo; sin serializar, dos escrituras sobre el mismo
|
|
21
|
+
* archivo pueden resolverse en orden inverso y dejar el estado viejo encima del nuevo. Un
|
|
22
|
+
* archivo íntegro pero atrasado no se nota —el `tmp+rename` del engine lo deja bien formado—,
|
|
23
|
+
* y es peor que uno corrupto: WhatsApp lo rechaza en el handshake y cierra la sesión.
|
|
24
|
+
* Su propia referencia (`useMultiFileAuthState`) toma un mutex por archivo por esto mismo.
|
|
25
|
+
* Per-path queue for session writes. Baileys emits `creds.update` several times in a row and
|
|
26
|
+
* fires `keys.set` in parallel; without serializing, two writes to the same file can settle in
|
|
27
|
+
* reverse order and leave the old state on top of the new one. A whole but stale file goes
|
|
28
|
+
* unnoticed —the engine's `tmp+rename` leaves it well formed— and is worse than a corrupt one:
|
|
29
|
+
* WhatsApp rejects it at the handshake and closes the session. Their own reference
|
|
30
|
+
* (`useMultiFileAuthState`) takes a mutex per file for this very reason.
|
|
31
|
+
*/
|
|
32
|
+
const queued = (locks, path, work) => {
|
|
33
|
+
const next = (locks.get(path) ?? Promise.resolve()).then(work, work);
|
|
34
|
+
locks.set(path, next.catch(() => { }));
|
|
35
|
+
return next;
|
|
36
|
+
};
|
|
18
37
|
const readable = (value) => (value && !/^\+?[\d\s·•∙⋅]+$/.test(value) ? value : null);
|
|
19
38
|
export default class WhatsApp {
|
|
20
39
|
#event = new EventEmitter();
|
|
@@ -48,7 +67,7 @@ export default class WhatsApp {
|
|
|
48
67
|
}
|
|
49
68
|
async connect(callback) {
|
|
50
69
|
const { engine } = this;
|
|
51
|
-
const { phone, method, autoclean = true, sync = true, reconnect = true, device } = this.#options;
|
|
70
|
+
const { phone, method, autoclean = true, sync = true, reconnect = true, device, debug } = this.#options;
|
|
52
71
|
const digits = phone !== undefined ? String(phone).replace(/\D+/g, '') : '';
|
|
53
72
|
const budget = reconnect === false ? 0 : reconnect === true ? null : typeof reconnect === 'number' ? reconnect : reconnect.max ?? null;
|
|
54
73
|
const wait = typeof reconnect === 'object' ? (reconnect.interval ?? 60) * 1_000 : 60_000;
|
|
@@ -61,6 +80,7 @@ export default class WhatsApp {
|
|
|
61
80
|
let alive = null;
|
|
62
81
|
let timer = null;
|
|
63
82
|
let chain = Promise.resolve();
|
|
83
|
+
const locks = new Map();
|
|
64
84
|
return new Promise((resolve, reject) => {
|
|
65
85
|
const start = async () => {
|
|
66
86
|
const creds = deserialize(await engine.get('/session/creds')) ?? initAuthCreds();
|
|
@@ -82,14 +102,15 @@ export default class WhatsApp {
|
|
|
82
102
|
return data;
|
|
83
103
|
},
|
|
84
104
|
set: async (data) => {
|
|
85
|
-
await Promise.all(Object.entries(data).flatMap(([category, entries]) => Object.entries(entries).map(([id, value]) =>
|
|
86
|
-
|
|
87
|
-
: engine.unset(
|
|
105
|
+
await Promise.all(Object.entries(data).flatMap(([category, entries]) => Object.entries(entries).map(([id, value]) => {
|
|
106
|
+
const path = `/session/${category}/${id}`;
|
|
107
|
+
return queued(locks, path, () => (value != null ? engine.set(path, serialize(value)) : engine.unset(path)));
|
|
108
|
+
})));
|
|
88
109
|
},
|
|
89
110
|
},
|
|
90
111
|
},
|
|
91
112
|
browser: Browsers.windows(device ?? 'Chrome'),
|
|
92
|
-
logger: pino({ level: 'silent' }),
|
|
113
|
+
logger: pino({ level: debug ?? 'silent' }),
|
|
93
114
|
syncFullHistory: sync,
|
|
94
115
|
shouldSyncHistoryMessage: ({ syncType }) => sync || syncType !== proto.HistorySync.HistorySyncType.FULL,
|
|
95
116
|
// Cuando el receptor no puede descifrar pide un retry; el cache interno de
|
|
@@ -234,7 +255,7 @@ export default class WhatsApp {
|
|
|
234
255
|
}
|
|
235
256
|
}
|
|
236
257
|
};
|
|
237
|
-
socket.ev.on('creds.update', () => engine.set('/session/creds', serialize(creds)));
|
|
258
|
+
socket.ev.on('creds.update', () => queued(locks, '/session/creds', () => engine.set('/session/creds', serialize(creds))));
|
|
238
259
|
socket.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => {
|
|
239
260
|
if (qr && !creds.registered) {
|
|
240
261
|
await callback(digits && (method ?? 'otp') === 'otp'
|
|
@@ -257,11 +278,25 @@ export default class WhatsApp {
|
|
|
257
278
|
else if (connection === 'close') {
|
|
258
279
|
const code = lastDisconnect?.error?.output?.statusCode;
|
|
259
280
|
const transient = code === DisconnectReason.restartRequired;
|
|
281
|
+
const farewell = {
|
|
282
|
+
code: code ?? null,
|
|
283
|
+
reason: Object.entries(DisconnectReason).find(([, value]) => value === code)?.[0] ?? 'unknown',
|
|
284
|
+
expired: code === DisconnectReason.loggedOut,
|
|
285
|
+
detail: lastDisconnect?.error instanceof Error ? lastDisconnect.error.message : null,
|
|
286
|
+
};
|
|
260
287
|
if (code === DisconnectReason.loggedOut) {
|
|
288
|
+
// Las escrituras en vuelo tienen que aterrizar antes de borrar, o una
|
|
289
|
+
// de ellas resucita las credenciales justo después del clear y la
|
|
290
|
+
// siguiente conexión arranca con restos de una sesión ya muerta.
|
|
291
|
+
// In-flight writes must land before wiping, or one of them resurrects
|
|
292
|
+
// the credentials right after the clear and the next connection starts
|
|
293
|
+
// on the leftovers of an already dead session.
|
|
294
|
+
await Promise.allSettled([...locks.values()]);
|
|
295
|
+
locks.clear();
|
|
261
296
|
await (autoclean ? engine.clear() : engine.unset('/session/creds'));
|
|
262
297
|
}
|
|
263
298
|
if (connected && !transient && !silent) {
|
|
264
|
-
this.emit('disconnected', this);
|
|
299
|
+
this.emit('disconnected', this, farewell);
|
|
265
300
|
}
|
|
266
301
|
if (intentional) {
|
|
267
302
|
/* cierre pedido por disconnect(): sin reintentos / close requested by disconnect(): no retries */
|