@whanext/core 0.19.16 → 0.19.18
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/CHANGELOG.md +35 -0
- package/README.md +86 -6
- package/dist/index.d.ts +135 -2
- package/dist/index.js +572 -84
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -379,15 +379,37 @@ function isCommandGroup(definition) {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
// src/commands/concurrency.ts
|
|
382
|
+
var DEFAULT_MAX_QUEUE = 10;
|
|
383
|
+
var DEFAULT_QUEUE_TIMEOUT_MS = 6e4;
|
|
382
384
|
var CommandConcurrencyController = class {
|
|
383
385
|
#states = /* @__PURE__ */ new Map();
|
|
384
|
-
|
|
386
|
+
#onEvent;
|
|
387
|
+
#queueTimeouts = 0;
|
|
388
|
+
#queueRejected = 0;
|
|
389
|
+
constructor(onEvent) {
|
|
390
|
+
this.#onEvent = onEvent;
|
|
391
|
+
}
|
|
392
|
+
health() {
|
|
393
|
+
let running = 0;
|
|
394
|
+
let queued = 0;
|
|
395
|
+
for (const state of this.#states.values()) {
|
|
396
|
+
running += state.active;
|
|
397
|
+
queued += state.queue.length;
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
running,
|
|
401
|
+
queued,
|
|
402
|
+
queueTimeouts: this.#queueTimeouts,
|
|
403
|
+
queueRejected: this.#queueRejected
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
async run(key, options, execute, context) {
|
|
385
407
|
const strategy = options?.strategy ?? "parallel";
|
|
386
408
|
if (strategy === "parallel") {
|
|
387
409
|
await execute(new AbortController().signal);
|
|
388
410
|
return;
|
|
389
411
|
}
|
|
390
|
-
const max =
|
|
412
|
+
const max = normalizePositiveInteger(options?.max, 1);
|
|
391
413
|
const state = this.#states.get(key) ?? { active: 0, queue: [], controllers: /* @__PURE__ */ new Set() };
|
|
392
414
|
this.#states.set(key, state);
|
|
393
415
|
if (strategy === "replace") {
|
|
@@ -399,7 +421,7 @@ var CommandConcurrencyController = class {
|
|
|
399
421
|
recoverable: true
|
|
400
422
|
});
|
|
401
423
|
} else if (strategy === "queue" && state.active >= max) {
|
|
402
|
-
await
|
|
424
|
+
await this.#waitForQueue(key, state, options ?? {}, context);
|
|
403
425
|
}
|
|
404
426
|
const controller = new AbortController();
|
|
405
427
|
state.controllers.add(controller);
|
|
@@ -409,13 +431,97 @@ var CommandConcurrencyController = class {
|
|
|
409
431
|
} finally {
|
|
410
432
|
state.controllers.delete(controller);
|
|
411
433
|
state.active -= 1;
|
|
412
|
-
state
|
|
434
|
+
this.#releaseNext(state);
|
|
413
435
|
if (state.active === 0 && state.queue.length === 0) {
|
|
414
436
|
this.#states.delete(key);
|
|
415
437
|
}
|
|
416
438
|
}
|
|
417
439
|
}
|
|
440
|
+
async #waitForQueue(key, state, options, context) {
|
|
441
|
+
const maxQueue = normalizeQueueLimit(options.maxQueue);
|
|
442
|
+
if (state.queue.length >= maxQueue) {
|
|
443
|
+
this.#queueRejected += 1;
|
|
444
|
+
if (context && Number.isFinite(maxQueue)) {
|
|
445
|
+
this.#onEvent?.({
|
|
446
|
+
type: "commandQueueFull",
|
|
447
|
+
payload: {
|
|
448
|
+
...context,
|
|
449
|
+
key,
|
|
450
|
+
maxQueue,
|
|
451
|
+
queued: state.queue.length
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
throw new WhaNextError("COMMAND_QUEUE_FULL", "This command queue is full.", {
|
|
456
|
+
context: { key, maxQueue, queued: state.queue.length },
|
|
457
|
+
recoverable: true
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const queueTimeoutMs = normalizeQueueTimeout(options.queueTimeoutMs);
|
|
461
|
+
await new Promise((resolve4, reject) => {
|
|
462
|
+
const entry = {
|
|
463
|
+
resolve: resolve4,
|
|
464
|
+
reject,
|
|
465
|
+
enqueuedAt: Date.now(),
|
|
466
|
+
settled: false
|
|
467
|
+
};
|
|
468
|
+
if (queueTimeoutMs > 0) {
|
|
469
|
+
entry.timer = setTimeout(() => {
|
|
470
|
+
if (entry.settled) return;
|
|
471
|
+
entry.settled = true;
|
|
472
|
+
const index = state.queue.indexOf(entry);
|
|
473
|
+
if (index >= 0) state.queue.splice(index, 1);
|
|
474
|
+
const queuedForMs = Date.now() - entry.enqueuedAt;
|
|
475
|
+
this.#queueTimeouts += 1;
|
|
476
|
+
if (context) {
|
|
477
|
+
this.#onEvent?.({
|
|
478
|
+
type: "commandQueueTimeout",
|
|
479
|
+
payload: {
|
|
480
|
+
...context,
|
|
481
|
+
key,
|
|
482
|
+
queuedForMs,
|
|
483
|
+
queueTimeoutMs,
|
|
484
|
+
queued: state.queue.length
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
reject(new WhaNextError(
|
|
489
|
+
"COMMAND_QUEUE_TIMEOUT",
|
|
490
|
+
"This command waited too long in the execution queue.",
|
|
491
|
+
{
|
|
492
|
+
context: { key, queuedForMs, queueTimeoutMs },
|
|
493
|
+
recoverable: true
|
|
494
|
+
}
|
|
495
|
+
));
|
|
496
|
+
}, queueTimeoutMs);
|
|
497
|
+
}
|
|
498
|
+
state.queue.push(entry);
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
#releaseNext(state) {
|
|
502
|
+
while (state.queue.length > 0) {
|
|
503
|
+
const next = state.queue.shift();
|
|
504
|
+
if (!next || next.settled) continue;
|
|
505
|
+
next.settled = true;
|
|
506
|
+
if (next.timer) clearTimeout(next.timer);
|
|
507
|
+
next.resolve();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
418
511
|
};
|
|
512
|
+
function normalizePositiveInteger(value, fallback) {
|
|
513
|
+
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
514
|
+
return Math.max(1, Math.floor(value));
|
|
515
|
+
}
|
|
516
|
+
function normalizeQueueLimit(value) {
|
|
517
|
+
if (value === 0) return Number.POSITIVE_INFINITY;
|
|
518
|
+
return normalizePositiveInteger(value, DEFAULT_MAX_QUEUE);
|
|
519
|
+
}
|
|
520
|
+
function normalizeQueueTimeout(value) {
|
|
521
|
+
if (value === 0) return 0;
|
|
522
|
+
if (value === void 0 || !Number.isFinite(value)) return DEFAULT_QUEUE_TIMEOUT_MS;
|
|
523
|
+
return Math.max(1, Math.floor(value));
|
|
524
|
+
}
|
|
419
525
|
|
|
420
526
|
// src/commands/context.ts
|
|
421
527
|
var CommandContextImplementation = class {
|
|
@@ -739,6 +845,24 @@ function invalid(name, received, expected) {
|
|
|
739
845
|
});
|
|
740
846
|
}
|
|
741
847
|
|
|
848
|
+
// src/provider/event-emitter.ts
|
|
849
|
+
var TypedEventEmitter = class {
|
|
850
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
851
|
+
on(event, listener) {
|
|
852
|
+
const listeners = this.#listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
853
|
+
listeners.add(listener);
|
|
854
|
+
this.#listeners.set(event, listeners);
|
|
855
|
+
return () => listeners.delete(listener);
|
|
856
|
+
}
|
|
857
|
+
async emit(event, payload) {
|
|
858
|
+
const listeners = this.#listeners.get(event);
|
|
859
|
+
if (!listeners) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
await Promise.all([...listeners].map((listener) => listener(payload)));
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
|
|
742
866
|
// src/services/user-service.ts
|
|
743
867
|
var UserService = class {
|
|
744
868
|
#group;
|
|
@@ -781,11 +905,19 @@ var CommandRouter = class {
|
|
|
781
905
|
#globalMiddleware = [];
|
|
782
906
|
#errorHandlers = [];
|
|
783
907
|
#cooldowns = /* @__PURE__ */ new Map();
|
|
784
|
-
#
|
|
908
|
+
#events = new TypedEventEmitter();
|
|
909
|
+
#concurrency;
|
|
785
910
|
#beforeExecute;
|
|
786
911
|
#afterExecute;
|
|
787
912
|
#cooldownOperations = 0;
|
|
788
913
|
constructor(servicesOrGroup, options = {}) {
|
|
914
|
+
this.#concurrency = new CommandConcurrencyController((event) => {
|
|
915
|
+
if (event.type === "commandQueueTimeout") {
|
|
916
|
+
void this.#events.emit("commandQueueTimeout", event.payload).catch(() => void 0);
|
|
917
|
+
} else {
|
|
918
|
+
void this.#events.emit("commandQueueFull", event.payload).catch(() => void 0);
|
|
919
|
+
}
|
|
920
|
+
});
|
|
789
921
|
this.#services = isRuntimeServices(servicesOrGroup) ? servicesOrGroup : createLegacyServices(servicesOrGroup);
|
|
790
922
|
this.setPrefixes(options.prefix ?? "!");
|
|
791
923
|
this.#legacyOnError = options.onError;
|
|
@@ -809,6 +941,12 @@ var CommandRouter = class {
|
|
|
809
941
|
get size() {
|
|
810
942
|
return this.catalog({ includeHidden: true }).length;
|
|
811
943
|
}
|
|
944
|
+
health() {
|
|
945
|
+
return this.#concurrency.health();
|
|
946
|
+
}
|
|
947
|
+
on(event, listener) {
|
|
948
|
+
return this.#events.on(event, listener);
|
|
949
|
+
}
|
|
812
950
|
command(definition) {
|
|
813
951
|
this.#validateTree(definition, []);
|
|
814
952
|
for (const name of commandNames(definition)) {
|
|
@@ -970,6 +1108,12 @@ _Nenhum comando dispon\xEDvel._`;
|
|
|
970
1108
|
await this.#authorize(resolved.layers, context);
|
|
971
1109
|
this.#consumeCooldown(resolved, context);
|
|
972
1110
|
await this.#execute(resolved, context);
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
command: resolved.registered.path.join(" "),
|
|
1114
|
+
messageId: message.id,
|
|
1115
|
+
chatId: message.chatId,
|
|
1116
|
+
userId: message.sender.id
|
|
973
1117
|
}
|
|
974
1118
|
);
|
|
975
1119
|
return true;
|
|
@@ -1797,24 +1941,6 @@ var SqliteMuteStore = class {
|
|
|
1797
1941
|
}
|
|
1798
1942
|
};
|
|
1799
1943
|
|
|
1800
|
-
// src/provider/event-emitter.ts
|
|
1801
|
-
var TypedEventEmitter = class {
|
|
1802
|
-
#listeners = /* @__PURE__ */ new Map();
|
|
1803
|
-
on(event, listener) {
|
|
1804
|
-
const listeners = this.#listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
1805
|
-
listeners.add(listener);
|
|
1806
|
-
this.#listeners.set(event, listeners);
|
|
1807
|
-
return () => listeners.delete(listener);
|
|
1808
|
-
}
|
|
1809
|
-
async emit(event, payload) {
|
|
1810
|
-
const listeners = this.#listeners.get(event);
|
|
1811
|
-
if (!listeners) {
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
await Promise.all([...listeners].map((listener) => listener(payload)));
|
|
1815
|
-
}
|
|
1816
|
-
};
|
|
1817
|
-
|
|
1818
1944
|
// src/services/account-service.ts
|
|
1819
1945
|
var AccountService = class {
|
|
1820
1946
|
id;
|
|
@@ -2165,6 +2291,7 @@ var WhaNextApp = class {
|
|
|
2165
2291
|
#router;
|
|
2166
2292
|
#startedAt = Date.now();
|
|
2167
2293
|
#state = "idle";
|
|
2294
|
+
#lastStability = "offline";
|
|
2168
2295
|
constructor(provider, options = {}, logger = new Logger(options.logger)) {
|
|
2169
2296
|
this.#provider = provider;
|
|
2170
2297
|
this.#phone = options.phone;
|
|
@@ -2209,14 +2336,50 @@ var WhaNextApp = class {
|
|
|
2209
2336
|
return this.#state === "connected";
|
|
2210
2337
|
}
|
|
2211
2338
|
health() {
|
|
2339
|
+
const provider = this.#provider.health?.();
|
|
2340
|
+
const connection = provider?.connection ?? {
|
|
2341
|
+
state: this.#state,
|
|
2342
|
+
uptimeMs: this.isReady ? Date.now() - this.#startedAt : 0,
|
|
2343
|
+
reconnects: 0,
|
|
2344
|
+
reconnectAttempt: 0
|
|
2345
|
+
};
|
|
2346
|
+
const messaging = provider?.messaging ?? {
|
|
2347
|
+
sent: 0,
|
|
2348
|
+
received: 0,
|
|
2349
|
+
failed: 0
|
|
2350
|
+
};
|
|
2351
|
+
const crypto = provider?.crypto ?? {
|
|
2352
|
+
backend: "unknown",
|
|
2353
|
+
acceleration: false,
|
|
2354
|
+
decryptFailures: 0,
|
|
2355
|
+
addonDecryptFailures: 0,
|
|
2356
|
+
senderKeyMismatches: 0
|
|
2357
|
+
};
|
|
2358
|
+
const groups = provider?.groups ?? {
|
|
2359
|
+
phashMismatches: 0,
|
|
2360
|
+
metadataRecoveries: 0,
|
|
2361
|
+
metadataRecoveryFailures: 0
|
|
2362
|
+
};
|
|
2363
|
+
const timeouts = provider?.timeouts ?? {
|
|
2364
|
+
connectTimeoutMs: 0,
|
|
2365
|
+
nodeQueryTimeoutMs: 0
|
|
2366
|
+
};
|
|
2367
|
+
const stability = provider?.stability ?? this.#fallbackStability();
|
|
2212
2368
|
return {
|
|
2213
2369
|
status: this.#healthStatus(),
|
|
2370
|
+
stability,
|
|
2214
2371
|
state: this.#state,
|
|
2215
2372
|
ready: this.isReady,
|
|
2216
2373
|
uptimeMs: Date.now() - this.#startedAt,
|
|
2217
2374
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2218
2375
|
muteEnabled: this.mute.enabled,
|
|
2219
|
-
logLevel: this.logger.level
|
|
2376
|
+
logLevel: this.logger.level,
|
|
2377
|
+
connection,
|
|
2378
|
+
messaging,
|
|
2379
|
+
crypto,
|
|
2380
|
+
groups,
|
|
2381
|
+
commands: this.#router.health(),
|
|
2382
|
+
timeouts
|
|
2220
2383
|
};
|
|
2221
2384
|
}
|
|
2222
2385
|
router() {
|
|
@@ -2286,9 +2449,32 @@ var WhaNextApp = class {
|
|
|
2286
2449
|
}
|
|
2287
2450
|
#bind() {
|
|
2288
2451
|
this.#provider.on("connection", async (update) => {
|
|
2452
|
+
const previousState = this.#state;
|
|
2289
2453
|
this.#state = update.state;
|
|
2290
2454
|
this.#logConnection(update);
|
|
2291
2455
|
await this.#events.emit("connection", update);
|
|
2456
|
+
if (update.state === "connected" && previousState === "reconnecting") {
|
|
2457
|
+
const providerHealth = this.#provider.health?.();
|
|
2458
|
+
await this.#events.emit("connectionRecovered", {
|
|
2459
|
+
recoveredAt: /* @__PURE__ */ new Date(),
|
|
2460
|
+
reconnects: providerHealth?.connection.reconnects ?? 1
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
await this.#refreshHealthState();
|
|
2464
|
+
});
|
|
2465
|
+
this.#provider.on("stability", async (event) => {
|
|
2466
|
+
if (event.type === "groupMetadataRecovered") {
|
|
2467
|
+
await this.#events.emit("groupMetadataRecovered", event.payload);
|
|
2468
|
+
} else if (event.type === "cryptoDegraded") {
|
|
2469
|
+
await this.#events.emit("cryptoDegraded", event.payload);
|
|
2470
|
+
}
|
|
2471
|
+
await this.#refreshHealthState();
|
|
2472
|
+
});
|
|
2473
|
+
this.#router.on("commandQueueTimeout", async (event) => {
|
|
2474
|
+
await this.#events.emit("commandQueueTimeout", event);
|
|
2475
|
+
});
|
|
2476
|
+
this.#router.on("commandQueueFull", async (event) => {
|
|
2477
|
+
await this.#events.emit("commandQueueFull", event);
|
|
2292
2478
|
});
|
|
2293
2479
|
this.#provider.on("groupChanged", ({ groupId }) => this.group.invalidate(groupId));
|
|
2294
2480
|
this.#provider.on("groupParticipantsChanged", async (change) => {
|
|
@@ -2356,6 +2542,22 @@ var WhaNextApp = class {
|
|
|
2356
2542
|
}
|
|
2357
2543
|
});
|
|
2358
2544
|
}
|
|
2545
|
+
#fallbackStability() {
|
|
2546
|
+
if (this.#state === "connected") return "healthy";
|
|
2547
|
+
if (this.#state === "connecting" || this.#state === "reconnecting") return "reconnecting";
|
|
2548
|
+
return "offline";
|
|
2549
|
+
}
|
|
2550
|
+
async #refreshHealthState() {
|
|
2551
|
+
const health = this.health();
|
|
2552
|
+
if (health.stability === this.#lastStability) return;
|
|
2553
|
+
const previous = this.#lastStability;
|
|
2554
|
+
this.#lastStability = health.stability;
|
|
2555
|
+
await this.#events.emit("healthChanged", {
|
|
2556
|
+
previous,
|
|
2557
|
+
current: health.stability,
|
|
2558
|
+
health
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2359
2561
|
#healthStatus() {
|
|
2360
2562
|
if (this.#state === "connected") {
|
|
2361
2563
|
return "ready";
|
|
@@ -2932,6 +3134,13 @@ var fatalDisconnectReasons = /* @__PURE__ */ new Set([
|
|
|
2932
3134
|
var fatalDisconnectCodes = /* @__PURE__ */ new Set([401, 403, 405, 406, 409, 516]);
|
|
2933
3135
|
var sharedMediaProcessor = createMediaProcessor();
|
|
2934
3136
|
var REMOTE_MEDIA_TIMEOUT_MS = 12e4;
|
|
3137
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
3138
|
+
var DEFAULT_NODE_QUERY_TIMEOUT_MS = 3e4;
|
|
3139
|
+
var PROVIDER_DEGRADED_WINDOW_MS = 12e4;
|
|
3140
|
+
var GROUP_METADATA_CACHE_TTL_MS = 18e4;
|
|
3141
|
+
var DEVICE_LIST_CACHE_TTL_MS = 18e4;
|
|
3142
|
+
var GROUP_METADATA_RECOVERY_COOLDOWN_MS = 6e4;
|
|
3143
|
+
var GROUP_METADATA_MISMATCH_WARNING = "group message publish acknowledged with mismatch metadata";
|
|
2935
3144
|
var messageSnapshotRetentionSeconds = 7 * 24 * 60 * 60;
|
|
2936
3145
|
var messageSnapshotMaxPerSession = 2e4;
|
|
2937
3146
|
var messageSnapshotPruneInterval = 256;
|
|
@@ -2944,8 +3153,14 @@ var ZapoProvider = class {
|
|
|
2944
3153
|
#deliveredMessageStore = /* @__PURE__ */ new Set();
|
|
2945
3154
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
2946
3155
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
3156
|
+
#groupMetadataRecoveryAt = /* @__PURE__ */ new Map();
|
|
3157
|
+
#groupMetadataRecoveryInFlight = /* @__PURE__ */ new Set();
|
|
2947
3158
|
#messageCacheSize;
|
|
3159
|
+
#cryptoBackend;
|
|
3160
|
+
#connectTimeoutMs;
|
|
3161
|
+
#nodeQueryTimeoutMs;
|
|
2948
3162
|
#protocolMutationQueue = Promise.resolve();
|
|
3163
|
+
#state = "idle";
|
|
2949
3164
|
#client;
|
|
2950
3165
|
#storeEntry;
|
|
2951
3166
|
#storePath;
|
|
@@ -2960,14 +3175,73 @@ var ZapoProvider = class {
|
|
|
2960
3175
|
#messageSnapshotsSincePrune = 0;
|
|
2961
3176
|
#pairingReady = Promise.resolve();
|
|
2962
3177
|
#resolvePairingReady;
|
|
3178
|
+
#lastConnectedAt;
|
|
3179
|
+
#lastDisconnectedAt;
|
|
3180
|
+
#reconnects = 0;
|
|
3181
|
+
#sentMessages = 0;
|
|
3182
|
+
#receivedMessages = 0;
|
|
3183
|
+
#failedMessages = 0;
|
|
3184
|
+
#lastIncomingAt;
|
|
3185
|
+
#lastOutgoingAt;
|
|
3186
|
+
#decryptFailures = 0;
|
|
3187
|
+
#addonDecryptFailures = 0;
|
|
3188
|
+
#senderKeyMismatches = 0;
|
|
3189
|
+
#phashMismatches = 0;
|
|
3190
|
+
#metadataRecoveries = 0;
|
|
3191
|
+
#metadataRecoveryFailures = 0;
|
|
3192
|
+
#degradedUntil = 0;
|
|
3193
|
+
#healthRefreshTimer;
|
|
3194
|
+
#cryptoBackendLogged = false;
|
|
2963
3195
|
constructor(options) {
|
|
2964
3196
|
this.#options = options;
|
|
2965
3197
|
this.#logger = options.logger ?? new Logger("silent");
|
|
2966
3198
|
this.#messageCacheSize = Math.max(1, options.messageCacheSize ?? 1e3);
|
|
3199
|
+
this.#connectTimeoutMs = normalizeProviderTimeout(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
3200
|
+
this.#nodeQueryTimeoutMs = normalizeProviderTimeout(options.nodeQueryTimeoutMs, DEFAULT_NODE_QUERY_TIMEOUT_MS);
|
|
3201
|
+
this.#cryptoBackend = detectCryptoBackend();
|
|
2967
3202
|
}
|
|
2968
3203
|
on(event, listener) {
|
|
2969
3204
|
return this.#events.on(event, listener);
|
|
2970
3205
|
}
|
|
3206
|
+
health() {
|
|
3207
|
+
const now = Date.now();
|
|
3208
|
+
const state = this.#state;
|
|
3209
|
+
const stability = state === "connected" ? now < this.#degradedUntil ? "degraded" : "healthy" : state === "reconnecting" || state === "connecting" ? "reconnecting" : "offline";
|
|
3210
|
+
return {
|
|
3211
|
+
stability,
|
|
3212
|
+
connection: {
|
|
3213
|
+
state,
|
|
3214
|
+
uptimeMs: this.#connectedAtSeconds > 0 && this.#connected ? Math.max(0, now - this.#connectedAtSeconds * 1e3) : 0,
|
|
3215
|
+
reconnects: this.#reconnects,
|
|
3216
|
+
reconnectAttempt: this.#reconnectAttempt,
|
|
3217
|
+
...this.#lastConnectedAt ? { lastConnectedAt: new Date(this.#lastConnectedAt) } : {},
|
|
3218
|
+
...this.#lastDisconnectedAt ? { lastDisconnectedAt: new Date(this.#lastDisconnectedAt) } : {}
|
|
3219
|
+
},
|
|
3220
|
+
messaging: {
|
|
3221
|
+
sent: this.#sentMessages,
|
|
3222
|
+
received: this.#receivedMessages,
|
|
3223
|
+
failed: this.#failedMessages,
|
|
3224
|
+
...this.#lastIncomingAt ? { lastIncomingAt: new Date(this.#lastIncomingAt) } : {},
|
|
3225
|
+
...this.#lastOutgoingAt ? { lastOutgoingAt: new Date(this.#lastOutgoingAt) } : {}
|
|
3226
|
+
},
|
|
3227
|
+
crypto: {
|
|
3228
|
+
backend: this.#cryptoBackend,
|
|
3229
|
+
acceleration: this.#cryptoBackend === "napi" || this.#cryptoBackend === "wasm",
|
|
3230
|
+
decryptFailures: this.#decryptFailures,
|
|
3231
|
+
addonDecryptFailures: this.#addonDecryptFailures,
|
|
3232
|
+
senderKeyMismatches: this.#senderKeyMismatches
|
|
3233
|
+
},
|
|
3234
|
+
groups: {
|
|
3235
|
+
phashMismatches: this.#phashMismatches,
|
|
3236
|
+
metadataRecoveries: this.#metadataRecoveries,
|
|
3237
|
+
metadataRecoveryFailures: this.#metadataRecoveryFailures
|
|
3238
|
+
},
|
|
3239
|
+
timeouts: {
|
|
3240
|
+
connectTimeoutMs: this.#connectTimeoutMs,
|
|
3241
|
+
nodeQueryTimeoutMs: this.#nodeQueryTimeoutMs
|
|
3242
|
+
}
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
2971
3245
|
async connect() {
|
|
2972
3246
|
this.#intentionalClose = false;
|
|
2973
3247
|
this.#closedNotified = false;
|
|
@@ -2976,8 +3250,9 @@ var ZapoProvider = class {
|
|
|
2976
3250
|
return;
|
|
2977
3251
|
}
|
|
2978
3252
|
this.#preparePairingGate();
|
|
3253
|
+
this.#state = this.#reconnectAttempt > 0 ? "reconnecting" : "connecting";
|
|
2979
3254
|
await this.#events.emit("connection", {
|
|
2980
|
-
state: this.#
|
|
3255
|
+
state: this.#state,
|
|
2981
3256
|
attempt: this.#reconnectAttempt
|
|
2982
3257
|
});
|
|
2983
3258
|
this.#startConnect(client);
|
|
@@ -2988,6 +3263,10 @@ var ZapoProvider = class {
|
|
|
2988
3263
|
clearTimeout(this.#reconnectTimer);
|
|
2989
3264
|
this.#reconnectTimer = void 0;
|
|
2990
3265
|
}
|
|
3266
|
+
if (this.#healthRefreshTimer) {
|
|
3267
|
+
clearTimeout(this.#healthRefreshTimer);
|
|
3268
|
+
this.#healthRefreshTimer = void 0;
|
|
3269
|
+
}
|
|
2991
3270
|
const client = this.#client;
|
|
2992
3271
|
this.#connectPromise = void 0;
|
|
2993
3272
|
try {
|
|
@@ -2998,6 +3277,8 @@ var ZapoProvider = class {
|
|
|
2998
3277
|
}
|
|
2999
3278
|
} finally {
|
|
3000
3279
|
this.#connected = false;
|
|
3280
|
+
this.#state = "closed";
|
|
3281
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
3001
3282
|
await this.#releaseStore();
|
|
3002
3283
|
}
|
|
3003
3284
|
}
|
|
@@ -3045,50 +3326,56 @@ var ZapoProvider = class {
|
|
|
3045
3326
|
}
|
|
3046
3327
|
}
|
|
3047
3328
|
async sendMessage(chatId, content, replyTo) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3329
|
+
return this.#trackOutgoing(async () => {
|
|
3330
|
+
if ("buttons" in content) {
|
|
3331
|
+
return this.#sendButtons(chatId, content, replyTo);
|
|
3332
|
+
}
|
|
3333
|
+
if ("list" in content) {
|
|
3334
|
+
return this.#sendList(chatId, content, replyTo);
|
|
3335
|
+
}
|
|
3336
|
+
const client = this.#requireClient();
|
|
3337
|
+
const { value, mentions, viewOnce } = await this.#toContent(content);
|
|
3338
|
+
const result = await client.message.send(chatId, value, {
|
|
3339
|
+
...replyTo ? { quote: this.#toZapoKey(replyTo) } : {},
|
|
3340
|
+
...mentions.length > 0 ? { mentions } : {},
|
|
3341
|
+
...viewOnce !== void 0 ? { viewOnce } : {}
|
|
3342
|
+
});
|
|
3343
|
+
return this.#sent(result, chatId);
|
|
3060
3344
|
});
|
|
3061
|
-
return this.#sent(result, chatId);
|
|
3062
3345
|
}
|
|
3063
3346
|
async repostMessage(source, chatId, options = {}) {
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3347
|
+
return this.#trackOutgoing(async () => {
|
|
3348
|
+
const original = await this.#findStoredMessage(this.#toZapoKey(source));
|
|
3349
|
+
if (!original?.message) {
|
|
3350
|
+
throw new WhaNextError(
|
|
3351
|
+
"MESSAGE_NOT_FOUND",
|
|
3352
|
+
"The source message is no longer available in the recent-message cache.",
|
|
3353
|
+
{
|
|
3354
|
+
context: { messageId: source.id, chatId: source.chatId },
|
|
3355
|
+
recoverable: true
|
|
3356
|
+
}
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
const result = await this.#requireClient().message.send(
|
|
3360
|
+
chatId,
|
|
3361
|
+
original.message,
|
|
3069
3362
|
{
|
|
3070
|
-
|
|
3071
|
-
|
|
3363
|
+
forward: true,
|
|
3364
|
+
...options.mentions && options.mentions.length > 0 ? { mentions: this.#mentions(options.mentions) } : {}
|
|
3072
3365
|
}
|
|
3073
3366
|
);
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
chatId,
|
|
3077
|
-
original.message,
|
|
3078
|
-
{
|
|
3079
|
-
forward: true,
|
|
3080
|
-
...options.mentions && options.mentions.length > 0 ? { mentions: this.#mentions(options.mentions) } : {}
|
|
3081
|
-
}
|
|
3082
|
-
);
|
|
3083
|
-
return this.#sent(result, chatId);
|
|
3367
|
+
return this.#sent(result, chatId);
|
|
3368
|
+
});
|
|
3084
3369
|
}
|
|
3085
3370
|
async reactToMessage(key, emoji) {
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3371
|
+
return this.#trackOutgoing(async () => {
|
|
3372
|
+
const result = await this.#requireClient().message.send(key.chatId, {
|
|
3373
|
+
type: "reaction",
|
|
3374
|
+
emoji: emoji ?? "",
|
|
3375
|
+
target: this.#toZapoKey(key)
|
|
3376
|
+
});
|
|
3377
|
+
return this.#sent(result, key.chatId);
|
|
3090
3378
|
});
|
|
3091
|
-
return this.#sent(result, key.chatId);
|
|
3092
3379
|
}
|
|
3093
3380
|
async downloadMedia(key) {
|
|
3094
3381
|
const message = this.#messageKeyStore.get(key) ?? await this.#findStoredMessage(this.#toZapoKey(key));
|
|
@@ -3124,17 +3411,21 @@ var ZapoProvider = class {
|
|
|
3124
3411
|
}
|
|
3125
3412
|
}
|
|
3126
3413
|
async editMessage(key, content) {
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3414
|
+
return this.#trackOutgoing(async () => {
|
|
3415
|
+
const result = await this.#requireClient().message.send(
|
|
3416
|
+
key.chatId,
|
|
3417
|
+
content,
|
|
3418
|
+
{ editKey: this.#toZapoKey(key) }
|
|
3419
|
+
);
|
|
3420
|
+
return this.#sent(result, key.chatId);
|
|
3421
|
+
});
|
|
3133
3422
|
}
|
|
3134
3423
|
async deleteMessage(key) {
|
|
3135
|
-
await this.#
|
|
3136
|
-
|
|
3137
|
-
|
|
3424
|
+
await this.#trackOutgoing(async () => {
|
|
3425
|
+
await this.#requireClient().message.send(key.chatId, {
|
|
3426
|
+
type: "revoke",
|
|
3427
|
+
target: this.#toZapoKey(key)
|
|
3428
|
+
});
|
|
3138
3429
|
});
|
|
3139
3430
|
}
|
|
3140
3431
|
async getGroup(groupId) {
|
|
@@ -3186,13 +3477,15 @@ var ZapoProvider = class {
|
|
|
3186
3477
|
return result.code;
|
|
3187
3478
|
}
|
|
3188
3479
|
async setMessagePin(groupId, key, pinned) {
|
|
3189
|
-
await this.#
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3480
|
+
await this.#trackOutgoing(async () => {
|
|
3481
|
+
await this.#requireClient().message.send(groupId, pinned ? {
|
|
3482
|
+
type: "pin",
|
|
3483
|
+
target: this.#toZapoKey(key),
|
|
3484
|
+
durationSecs: 604800
|
|
3485
|
+
} : {
|
|
3486
|
+
type: "unpin",
|
|
3487
|
+
target: this.#toZapoKey(key)
|
|
3488
|
+
});
|
|
3196
3489
|
});
|
|
3197
3490
|
}
|
|
3198
3491
|
async updateParticipant(groupId, memberId, action) {
|
|
@@ -3233,6 +3526,14 @@ var ZapoProvider = class {
|
|
|
3233
3526
|
]
|
|
3234
3527
|
});
|
|
3235
3528
|
}
|
|
3529
|
+
async #trackOutgoing(operation) {
|
|
3530
|
+
try {
|
|
3531
|
+
return await operation();
|
|
3532
|
+
} catch (error) {
|
|
3533
|
+
this.#failedMessages += 1;
|
|
3534
|
+
throw error;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3236
3537
|
async #ensureClient() {
|
|
3237
3538
|
if (this.#client) return this.#client;
|
|
3238
3539
|
const authPath = resolve2(this.#options.auth);
|
|
@@ -3256,13 +3557,29 @@ var ZapoProvider = class {
|
|
|
3256
3557
|
markOnlineOnConnect: false,
|
|
3257
3558
|
deviceBrowser: this.#deviceBrowser(),
|
|
3258
3559
|
deviceOsDisplayName: this.#deviceOsDisplayName(),
|
|
3560
|
+
connectTimeoutMs: this.#connectTimeoutMs,
|
|
3561
|
+
nodeQueryTimeoutMs: this.#nodeQueryTimeoutMs,
|
|
3259
3562
|
history: { enabled: true, requireFullSync: true },
|
|
3260
3563
|
addons: {
|
|
3261
3564
|
autoDecrypt: true,
|
|
3262
3565
|
persistAllSecrets: true
|
|
3263
3566
|
},
|
|
3264
3567
|
media: { processor: sharedMediaProcessor }
|
|
3265
|
-
}, new WhaNextZapoLogger(
|
|
3568
|
+
}, new WhaNextZapoLogger(
|
|
3569
|
+
this.#logger,
|
|
3570
|
+
{},
|
|
3571
|
+
(message, context) => this.#observeZapoWarning(message, context)
|
|
3572
|
+
));
|
|
3573
|
+
if (!this.#cryptoBackendLogged) {
|
|
3574
|
+
this.#cryptoBackendLogged = true;
|
|
3575
|
+
if (this.#cryptoBackend === "napi" || this.#cryptoBackend === "wasm") {
|
|
3576
|
+
this.#logger.info("Crypto acceleration enabled.", { backend: this.#cryptoBackend });
|
|
3577
|
+
} else {
|
|
3578
|
+
this.#logger.debug("Crypto acceleration unavailable; using JavaScript backend.", {
|
|
3579
|
+
backend: this.#cryptoBackend
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3266
3583
|
this.#storeEntry = storeEntry;
|
|
3267
3584
|
this.#storePath = storePath;
|
|
3268
3585
|
this.#client = client;
|
|
@@ -3313,6 +3630,8 @@ var ZapoProvider = class {
|
|
|
3313
3630
|
});
|
|
3314
3631
|
client.on("message_send", (event) => {
|
|
3315
3632
|
if (!event.id || !event.message) return;
|
|
3633
|
+
this.#sentMessages += 1;
|
|
3634
|
+
this.#lastOutgoingAt = /* @__PURE__ */ new Date();
|
|
3316
3635
|
this.#remember({
|
|
3317
3636
|
key: {
|
|
3318
3637
|
id: event.id,
|
|
@@ -3366,6 +3685,8 @@ var ZapoProvider = class {
|
|
|
3366
3685
|
const message = normalizeZapoMessage(event);
|
|
3367
3686
|
if (!message) return;
|
|
3368
3687
|
if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
|
|
3688
|
+
this.#receivedMessages += 1;
|
|
3689
|
+
this.#lastIncomingAt = /* @__PURE__ */ new Date();
|
|
3369
3690
|
this.#messageKeyStore.set(message.keys, stored);
|
|
3370
3691
|
if (message.quoted && quoted?.message) {
|
|
3371
3692
|
this.#messageKeyStore.set(
|
|
@@ -3622,21 +3943,121 @@ var ZapoProvider = class {
|
|
|
3622
3943
|
}
|
|
3623
3944
|
void this.#events.emit("groupChanged", { groupId });
|
|
3624
3945
|
}
|
|
3946
|
+
#observeZapoWarning(message, context) {
|
|
3947
|
+
if (message === GROUP_METADATA_MISMATCH_WARNING) {
|
|
3948
|
+
this.#phashMismatches += 1;
|
|
3949
|
+
this.#markDegraded();
|
|
3950
|
+
const groupId = stringValue(context.groupJid);
|
|
3951
|
+
if (groupId?.endsWith("@g.us")) void this.#recoverGroupMetadata(groupId);
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
if (message === "failed to decrypt incoming message") {
|
|
3955
|
+
this.#decryptFailures += 1;
|
|
3956
|
+
const detail = stringValue(context.message);
|
|
3957
|
+
const kind = detail === "sender key id mismatch" ? "sender_key_mismatch" : "decrypt_failure";
|
|
3958
|
+
if (kind === "sender_key_mismatch") this.#senderKeyMismatches += 1;
|
|
3959
|
+
this.#markDegraded();
|
|
3960
|
+
this.#emitCryptoDegraded(kind, context);
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
if (message === "addon auto-decrypt failed") {
|
|
3964
|
+
this.#addonDecryptFailures += 1;
|
|
3965
|
+
this.#markDegraded();
|
|
3966
|
+
this.#emitCryptoDegraded("addon_decrypt_failure", context);
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
#emitCryptoDegraded(kind, context) {
|
|
3970
|
+
const messageId = stringValue(context.id);
|
|
3971
|
+
const chatId = stringValue(context.from) ?? stringValue(context.groupJid);
|
|
3972
|
+
const participantId = stringValue(context.participant);
|
|
3973
|
+
this.#emitStability({
|
|
3974
|
+
type: "cryptoDegraded",
|
|
3975
|
+
payload: {
|
|
3976
|
+
kind,
|
|
3977
|
+
occurredAt: /* @__PURE__ */ new Date(),
|
|
3978
|
+
...messageId ? { messageId } : {},
|
|
3979
|
+
...chatId ? { chatId } : {},
|
|
3980
|
+
...participantId ? { participantId } : {}
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
#markDegraded() {
|
|
3985
|
+
this.#degradedUntil = Math.max(this.#degradedUntil, Date.now() + PROVIDER_DEGRADED_WINDOW_MS);
|
|
3986
|
+
if (this.#healthRefreshTimer) clearTimeout(this.#healthRefreshTimer);
|
|
3987
|
+
const delay = Math.max(1, this.#degradedUntil - Date.now() + 5);
|
|
3988
|
+
this.#healthRefreshTimer = setTimeout(() => {
|
|
3989
|
+
this.#healthRefreshTimer = void 0;
|
|
3990
|
+
this.#emitStability({
|
|
3991
|
+
type: "healthRefresh",
|
|
3992
|
+
payload: { occurredAt: /* @__PURE__ */ new Date() }
|
|
3993
|
+
});
|
|
3994
|
+
}, delay);
|
|
3995
|
+
}
|
|
3996
|
+
#emitStability(event) {
|
|
3997
|
+
void this.#events.emit("stability", event).catch((error) => {
|
|
3998
|
+
this.#logger.warn("Stability event listener failed.", {
|
|
3999
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
4000
|
+
});
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
async #recoverGroupMetadata(groupId) {
|
|
4004
|
+
const client = this.#client;
|
|
4005
|
+
const storeEntry = this.#storeEntry;
|
|
4006
|
+
if (!client || !storeEntry || !this.#connected) return;
|
|
4007
|
+
const now = Date.now();
|
|
4008
|
+
const lastRecoveryAt = this.#groupMetadataRecoveryAt.get(groupId);
|
|
4009
|
+
if (lastRecoveryAt !== void 0 && now - lastRecoveryAt < GROUP_METADATA_RECOVERY_COOLDOWN_MS) {
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
if (this.#groupMetadataRecoveryInFlight.has(groupId)) return;
|
|
4013
|
+
this.#groupMetadataRecoveryAt.set(groupId, now);
|
|
4014
|
+
this.#groupMetadataRecoveryInFlight.add(groupId);
|
|
4015
|
+
try {
|
|
4016
|
+
const sessionStore = storeEntry.store.session(this.#sessionId());
|
|
4017
|
+
await sessionStore.groupMetadata.deleteGroupMetadata(groupId);
|
|
4018
|
+
await client.group.queryGroupMetadata(groupId);
|
|
4019
|
+
await this.#events.emit("groupChanged", { groupId });
|
|
4020
|
+
this.#metadataRecoveries += 1;
|
|
4021
|
+
const recoveredAt = /* @__PURE__ */ new Date();
|
|
4022
|
+
this.#emitStability({
|
|
4023
|
+
type: "groupMetadataRecovered",
|
|
4024
|
+
payload: { groupId, recoveredAt }
|
|
4025
|
+
});
|
|
4026
|
+
this.#logger.info("Refreshed group metadata after participant-hash mismatch.", {
|
|
4027
|
+
groupId
|
|
4028
|
+
});
|
|
4029
|
+
} catch (error) {
|
|
4030
|
+
this.#metadataRecoveryFailures += 1;
|
|
4031
|
+
this.#logger.warn("Could not refresh group metadata after participant-hash mismatch.", {
|
|
4032
|
+
groupId,
|
|
4033
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
4034
|
+
});
|
|
4035
|
+
} finally {
|
|
4036
|
+
this.#groupMetadataRecoveryInFlight.delete(groupId);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
3625
4039
|
async #handleConnectionEvent(client, event) {
|
|
3626
4040
|
if (client !== this.#client) return;
|
|
3627
4041
|
if (event.status === "open") {
|
|
4042
|
+
const wasReconnect = this.#reconnectAttempt > 0;
|
|
3628
4043
|
this.#connectPromise = void 0;
|
|
3629
4044
|
this.#connected = true;
|
|
4045
|
+
this.#state = "connected";
|
|
3630
4046
|
this.#resolvePairingReady?.();
|
|
3631
4047
|
this.#resolvePairingReady = void 0;
|
|
3632
4048
|
this.#connectedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
4049
|
+
this.#lastConnectedAt = /* @__PURE__ */ new Date();
|
|
4050
|
+
if (wasReconnect) this.#reconnects += 1;
|
|
3633
4051
|
this.#reconnectAttempt = 0;
|
|
3634
4052
|
this.#closedNotified = false;
|
|
4053
|
+
this.#groupMetadataRecoveryAt.clear();
|
|
4054
|
+
this.#groupMetadataRecoveryInFlight.clear();
|
|
3635
4055
|
await this.#events.emit("connection", { state: "connected" });
|
|
3636
4056
|
return;
|
|
3637
4057
|
}
|
|
3638
4058
|
this.#connectPromise = void 0;
|
|
3639
4059
|
this.#connected = false;
|
|
4060
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
3640
4061
|
const details = disconnectDetails(event.reason, event.code);
|
|
3641
4062
|
const error = connectionError(event.reason, details);
|
|
3642
4063
|
if (this.#intentionalClose || event.isLogout === true || shouldStopAutomaticReconnect(details)) {
|
|
@@ -3678,6 +4099,8 @@ var ZapoProvider = class {
|
|
|
3678
4099
|
}
|
|
3679
4100
|
this.#connectPromise = void 0;
|
|
3680
4101
|
this.#connected = false;
|
|
4102
|
+
this.#state = "closed";
|
|
4103
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
3681
4104
|
await this.#emitClosedOnce(error);
|
|
3682
4105
|
this.#client = void 0;
|
|
3683
4106
|
try {
|
|
@@ -3693,6 +4116,7 @@ var ZapoProvider = class {
|
|
|
3693
4116
|
async #emitClosedOnce(error) {
|
|
3694
4117
|
if (this.#closedNotified) return;
|
|
3695
4118
|
this.#closedNotified = true;
|
|
4119
|
+
this.#state = "closed";
|
|
3696
4120
|
await this.#events.emit("connection", {
|
|
3697
4121
|
state: "closed",
|
|
3698
4122
|
...error ? { error } : {}
|
|
@@ -3714,15 +4138,16 @@ var ZapoProvider = class {
|
|
|
3714
4138
|
async #scheduleReconnect(error, delayOverrideMs) {
|
|
3715
4139
|
if (this.#reconnectTimer) return;
|
|
3716
4140
|
const options = this.#options.reconnect;
|
|
3717
|
-
const maxAttempts = options?.maxAttempts ??
|
|
4141
|
+
const maxAttempts = options?.maxAttempts ?? Number.POSITIVE_INFINITY;
|
|
3718
4142
|
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
3719
4143
|
await this.#emitClosedOnce(error);
|
|
3720
4144
|
await this.#releaseStore();
|
|
3721
4145
|
return;
|
|
3722
4146
|
}
|
|
3723
4147
|
this.#reconnectAttempt += 1;
|
|
4148
|
+
this.#state = "reconnecting";
|
|
3724
4149
|
await this.#events.emit("connection", {
|
|
3725
|
-
state:
|
|
4150
|
+
state: this.#state,
|
|
3726
4151
|
attempt: this.#reconnectAttempt,
|
|
3727
4152
|
...error ? { error } : {}
|
|
3728
4153
|
});
|
|
@@ -4408,7 +4833,12 @@ function createZapoStoreEntry(storePath) {
|
|
|
4408
4833
|
backends: {
|
|
4409
4834
|
sqlite: createSqliteStore({
|
|
4410
4835
|
path: storePath,
|
|
4411
|
-
driver: "auto"
|
|
4836
|
+
driver: "auto",
|
|
4837
|
+
pragmas: {
|
|
4838
|
+
journal_mode: "WAL",
|
|
4839
|
+
synchronous: "NORMAL",
|
|
4840
|
+
busy_timeout: 5e3
|
|
4841
|
+
}
|
|
4412
4842
|
})
|
|
4413
4843
|
},
|
|
4414
4844
|
providers: {
|
|
@@ -4426,11 +4856,19 @@ function createZapoStoreEntry(storePath) {
|
|
|
4426
4856
|
},
|
|
4427
4857
|
cacheProviders: {
|
|
4428
4858
|
messageSecret: "sqlite"
|
|
4859
|
+
},
|
|
4860
|
+
memory: {
|
|
4861
|
+
cacheTtlMs: {
|
|
4862
|
+
groupMetadataMs: GROUP_METADATA_CACHE_TTL_MS,
|
|
4863
|
+
deviceListMs: DEVICE_LIST_CACHE_TTL_MS
|
|
4864
|
+
}
|
|
4429
4865
|
}
|
|
4430
4866
|
});
|
|
4431
4867
|
const Database = require2("better-sqlite3");
|
|
4432
4868
|
const snapshotDb = new Database(messageSnapshotStorePath(storePath));
|
|
4433
4869
|
snapshotDb.exec(`
|
|
4870
|
+
PRAGMA journal_mode = WAL;
|
|
4871
|
+
PRAGMA synchronous = NORMAL;
|
|
4434
4872
|
PRAGMA busy_timeout = 5000;
|
|
4435
4873
|
CREATE TABLE IF NOT EXISTS whanext_message_snapshots (
|
|
4436
4874
|
session_id TEXT NOT NULL,
|
|
@@ -4671,6 +5109,44 @@ function booleanValue(value) {
|
|
|
4671
5109
|
if (value === false || value === 0) return false;
|
|
4672
5110
|
return void 0;
|
|
4673
5111
|
}
|
|
5112
|
+
function normalizeProviderTimeout(value, fallback) {
|
|
5113
|
+
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
5114
|
+
return Math.max(1e3, Math.floor(value));
|
|
5115
|
+
}
|
|
5116
|
+
function detectCryptoBackend() {
|
|
5117
|
+
const requested = process.env.ZAPO_NATIVE_BACKEND?.trim().toLowerCase() ?? "auto";
|
|
5118
|
+
if (requested === "js" || requested === "none") return "js";
|
|
5119
|
+
if ((requested === "auto" || requested === "napi") && nativeNapiAvailable()) {
|
|
5120
|
+
return "napi";
|
|
5121
|
+
}
|
|
5122
|
+
if ((requested === "auto" || requested === "wasm") && supportsZapoWasmRuntime() && moduleResolvable("@zapo-js/native/wasm/pkg/zapo_native_wasm.js") && moduleResolvable("@zapo-js/native/wasm/pkg/zapo_native_wasm_bg.wasm")) {
|
|
5123
|
+
return "wasm";
|
|
5124
|
+
}
|
|
5125
|
+
return "js";
|
|
5126
|
+
}
|
|
5127
|
+
function nativeNapiAvailable() {
|
|
5128
|
+
try {
|
|
5129
|
+
require2("@zapo-js/native");
|
|
5130
|
+
return true;
|
|
5131
|
+
} catch {
|
|
5132
|
+
return false;
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
function moduleResolvable(specifier) {
|
|
5136
|
+
try {
|
|
5137
|
+
require2.resolve(specifier);
|
|
5138
|
+
return true;
|
|
5139
|
+
} catch {
|
|
5140
|
+
return false;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
function supportsZapoWasmRuntime() {
|
|
5144
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
5145
|
+
if (major > 22) return true;
|
|
5146
|
+
if (major === 22) return minor >= 12;
|
|
5147
|
+
if (major === 20) return minor >= 19;
|
|
5148
|
+
return false;
|
|
5149
|
+
}
|
|
4674
5150
|
function bytesField(value) {
|
|
4675
5151
|
if (value instanceof Uint8Array) return value;
|
|
4676
5152
|
return void 0;
|
|
@@ -4679,9 +5155,11 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4679
5155
|
level;
|
|
4680
5156
|
#logger;
|
|
4681
5157
|
#context;
|
|
4682
|
-
|
|
5158
|
+
#onWarn;
|
|
5159
|
+
constructor(logger, context = {}, onWarn) {
|
|
4683
5160
|
this.#logger = logger;
|
|
4684
5161
|
this.#context = context;
|
|
5162
|
+
this.#onWarn = onWarn;
|
|
4685
5163
|
this.level = logger.level === "debug" ? "debug" : logger.level === "warn" ? "warn" : logger.level === "error" || logger.level === "silent" ? "error" : "info";
|
|
4686
5164
|
}
|
|
4687
5165
|
trace(message, context) {
|
|
@@ -4694,7 +5172,9 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4694
5172
|
this.#logger.info(message, this.#merge(context));
|
|
4695
5173
|
}
|
|
4696
5174
|
warn(message, context) {
|
|
4697
|
-
|
|
5175
|
+
const merged = this.#merge(context);
|
|
5176
|
+
this.#logger.warn(message, merged);
|
|
5177
|
+
this.#onWarn?.(message, merged);
|
|
4698
5178
|
}
|
|
4699
5179
|
error(message, context) {
|
|
4700
5180
|
this.#logger.error(message, this.#merge(context));
|
|
@@ -4703,7 +5183,7 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4703
5183
|
return new _WhaNextZapoLogger(this.#logger, {
|
|
4704
5184
|
...this.#context,
|
|
4705
5185
|
...bindings
|
|
4706
|
-
});
|
|
5186
|
+
}, this.#onWarn);
|
|
4707
5187
|
}
|
|
4708
5188
|
#merge(context) {
|
|
4709
5189
|
return context ? { ...this.#context, ...context } : this.#context;
|
|
@@ -4731,7 +5211,9 @@ async function create(options = {}) {
|
|
|
4731
5211
|
...options.accountId ? { sessionId: options.accountId } : {},
|
|
4732
5212
|
...options.messageCacheSize !== void 0 ? { messageCacheSize: options.messageCacheSize } : {},
|
|
4733
5213
|
...options.processOfflineMessages !== void 0 ? { processOfflineMessages: options.processOfflineMessages } : {},
|
|
4734
|
-
...options.reconnect ? { reconnect: options.reconnect } : {}
|
|
5214
|
+
...options.reconnect ? { reconnect: options.reconnect } : {},
|
|
5215
|
+
...options.providerTimeouts?.connectTimeoutMs !== void 0 ? { connectTimeoutMs: options.providerTimeouts.connectTimeoutMs } : {},
|
|
5216
|
+
...options.providerTimeouts?.nodeQueryTimeoutMs !== void 0 ? { nodeQueryTimeoutMs: options.providerTimeouts.nodeQueryTimeoutMs } : {}
|
|
4735
5217
|
});
|
|
4736
5218
|
return new WhaNextApp(provider, {
|
|
4737
5219
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
@@ -4963,6 +5445,12 @@ function mergeCreateOptions(shared, account) {
|
|
|
4963
5445
|
...account.reconnect
|
|
4964
5446
|
};
|
|
4965
5447
|
}
|
|
5448
|
+
if (shared.providerTimeouts || account.providerTimeouts) {
|
|
5449
|
+
merged.providerTimeouts = {
|
|
5450
|
+
...shared.providerTimeouts,
|
|
5451
|
+
...account.providerTimeouts
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
4966
5454
|
if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
|
|
4967
5455
|
merged.logger = {
|
|
4968
5456
|
...shared.logger,
|