@whanext/core 0.19.15 → 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 +45 -0
- package/README.md +88 -6
- package/dist/index.d.ts +137 -4
- package/dist/index.js +597 -86
- 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";
|
|
@@ -2406,6 +2608,7 @@ var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
|
2406
2608
|
// src/provider/zapo/zapo-provider.ts
|
|
2407
2609
|
import { mkdir, stat } from "fs/promises";
|
|
2408
2610
|
import { createRequire as createRequire2 } from "module";
|
|
2611
|
+
import { Readable } from "stream";
|
|
2409
2612
|
import { basename, dirname as dirname2, join, resolve as resolve2 } from "path";
|
|
2410
2613
|
import { createMediaProcessor } from "@zapo-js/media-utils";
|
|
2411
2614
|
import { createSqliteStore } from "@zapo-js/store-sqlite";
|
|
@@ -2930,6 +3133,14 @@ var fatalDisconnectReasons = /* @__PURE__ */ new Set([
|
|
|
2930
3133
|
]);
|
|
2931
3134
|
var fatalDisconnectCodes = /* @__PURE__ */ new Set([401, 403, 405, 406, 409, 516]);
|
|
2932
3135
|
var sharedMediaProcessor = createMediaProcessor();
|
|
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";
|
|
2933
3144
|
var messageSnapshotRetentionSeconds = 7 * 24 * 60 * 60;
|
|
2934
3145
|
var messageSnapshotMaxPerSession = 2e4;
|
|
2935
3146
|
var messageSnapshotPruneInterval = 256;
|
|
@@ -2942,8 +3153,14 @@ var ZapoProvider = class {
|
|
|
2942
3153
|
#deliveredMessageStore = /* @__PURE__ */ new Set();
|
|
2943
3154
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
2944
3155
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
3156
|
+
#groupMetadataRecoveryAt = /* @__PURE__ */ new Map();
|
|
3157
|
+
#groupMetadataRecoveryInFlight = /* @__PURE__ */ new Set();
|
|
2945
3158
|
#messageCacheSize;
|
|
3159
|
+
#cryptoBackend;
|
|
3160
|
+
#connectTimeoutMs;
|
|
3161
|
+
#nodeQueryTimeoutMs;
|
|
2946
3162
|
#protocolMutationQueue = Promise.resolve();
|
|
3163
|
+
#state = "idle";
|
|
2947
3164
|
#client;
|
|
2948
3165
|
#storeEntry;
|
|
2949
3166
|
#storePath;
|
|
@@ -2958,14 +3175,73 @@ var ZapoProvider = class {
|
|
|
2958
3175
|
#messageSnapshotsSincePrune = 0;
|
|
2959
3176
|
#pairingReady = Promise.resolve();
|
|
2960
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;
|
|
2961
3195
|
constructor(options) {
|
|
2962
3196
|
this.#options = options;
|
|
2963
3197
|
this.#logger = options.logger ?? new Logger("silent");
|
|
2964
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();
|
|
2965
3202
|
}
|
|
2966
3203
|
on(event, listener) {
|
|
2967
3204
|
return this.#events.on(event, listener);
|
|
2968
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
|
+
}
|
|
2969
3245
|
async connect() {
|
|
2970
3246
|
this.#intentionalClose = false;
|
|
2971
3247
|
this.#closedNotified = false;
|
|
@@ -2974,8 +3250,9 @@ var ZapoProvider = class {
|
|
|
2974
3250
|
return;
|
|
2975
3251
|
}
|
|
2976
3252
|
this.#preparePairingGate();
|
|
3253
|
+
this.#state = this.#reconnectAttempt > 0 ? "reconnecting" : "connecting";
|
|
2977
3254
|
await this.#events.emit("connection", {
|
|
2978
|
-
state: this.#
|
|
3255
|
+
state: this.#state,
|
|
2979
3256
|
attempt: this.#reconnectAttempt
|
|
2980
3257
|
});
|
|
2981
3258
|
this.#startConnect(client);
|
|
@@ -2986,6 +3263,10 @@ var ZapoProvider = class {
|
|
|
2986
3263
|
clearTimeout(this.#reconnectTimer);
|
|
2987
3264
|
this.#reconnectTimer = void 0;
|
|
2988
3265
|
}
|
|
3266
|
+
if (this.#healthRefreshTimer) {
|
|
3267
|
+
clearTimeout(this.#healthRefreshTimer);
|
|
3268
|
+
this.#healthRefreshTimer = void 0;
|
|
3269
|
+
}
|
|
2989
3270
|
const client = this.#client;
|
|
2990
3271
|
this.#connectPromise = void 0;
|
|
2991
3272
|
try {
|
|
@@ -2996,6 +3277,8 @@ var ZapoProvider = class {
|
|
|
2996
3277
|
}
|
|
2997
3278
|
} finally {
|
|
2998
3279
|
this.#connected = false;
|
|
3280
|
+
this.#state = "closed";
|
|
3281
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
2999
3282
|
await this.#releaseStore();
|
|
3000
3283
|
}
|
|
3001
3284
|
}
|
|
@@ -3043,50 +3326,56 @@ var ZapoProvider = class {
|
|
|
3043
3326
|
}
|
|
3044
3327
|
}
|
|
3045
3328
|
async sendMessage(chatId, content, replyTo) {
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
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);
|
|
3058
3344
|
});
|
|
3059
|
-
return this.#sent(result, chatId);
|
|
3060
3345
|
}
|
|
3061
3346
|
async repostMessage(source, chatId, options = {}) {
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
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,
|
|
3067
3362
|
{
|
|
3068
|
-
|
|
3069
|
-
|
|
3363
|
+
forward: true,
|
|
3364
|
+
...options.mentions && options.mentions.length > 0 ? { mentions: this.#mentions(options.mentions) } : {}
|
|
3070
3365
|
}
|
|
3071
3366
|
);
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
chatId,
|
|
3075
|
-
original.message,
|
|
3076
|
-
{
|
|
3077
|
-
forward: true,
|
|
3078
|
-
...options.mentions && options.mentions.length > 0 ? { mentions: this.#mentions(options.mentions) } : {}
|
|
3079
|
-
}
|
|
3080
|
-
);
|
|
3081
|
-
return this.#sent(result, chatId);
|
|
3367
|
+
return this.#sent(result, chatId);
|
|
3368
|
+
});
|
|
3082
3369
|
}
|
|
3083
3370
|
async reactToMessage(key, emoji) {
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
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);
|
|
3088
3378
|
});
|
|
3089
|
-
return this.#sent(result, key.chatId);
|
|
3090
3379
|
}
|
|
3091
3380
|
async downloadMedia(key) {
|
|
3092
3381
|
const message = this.#messageKeyStore.get(key) ?? await this.#findStoredMessage(this.#toZapoKey(key));
|
|
@@ -3122,17 +3411,21 @@ var ZapoProvider = class {
|
|
|
3122
3411
|
}
|
|
3123
3412
|
}
|
|
3124
3413
|
async editMessage(key, content) {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
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
|
+
});
|
|
3131
3422
|
}
|
|
3132
3423
|
async deleteMessage(key) {
|
|
3133
|
-
await this.#
|
|
3134
|
-
|
|
3135
|
-
|
|
3424
|
+
await this.#trackOutgoing(async () => {
|
|
3425
|
+
await this.#requireClient().message.send(key.chatId, {
|
|
3426
|
+
type: "revoke",
|
|
3427
|
+
target: this.#toZapoKey(key)
|
|
3428
|
+
});
|
|
3136
3429
|
});
|
|
3137
3430
|
}
|
|
3138
3431
|
async getGroup(groupId) {
|
|
@@ -3184,13 +3477,15 @@ var ZapoProvider = class {
|
|
|
3184
3477
|
return result.code;
|
|
3185
3478
|
}
|
|
3186
3479
|
async setMessagePin(groupId, key, pinned) {
|
|
3187
|
-
await this.#
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
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
|
+
});
|
|
3194
3489
|
});
|
|
3195
3490
|
}
|
|
3196
3491
|
async updateParticipant(groupId, memberId, action) {
|
|
@@ -3231,6 +3526,14 @@ var ZapoProvider = class {
|
|
|
3231
3526
|
]
|
|
3232
3527
|
});
|
|
3233
3528
|
}
|
|
3529
|
+
async #trackOutgoing(operation) {
|
|
3530
|
+
try {
|
|
3531
|
+
return await operation();
|
|
3532
|
+
} catch (error) {
|
|
3533
|
+
this.#failedMessages += 1;
|
|
3534
|
+
throw error;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3234
3537
|
async #ensureClient() {
|
|
3235
3538
|
if (this.#client) return this.#client;
|
|
3236
3539
|
const authPath = resolve2(this.#options.auth);
|
|
@@ -3254,13 +3557,29 @@ var ZapoProvider = class {
|
|
|
3254
3557
|
markOnlineOnConnect: false,
|
|
3255
3558
|
deviceBrowser: this.#deviceBrowser(),
|
|
3256
3559
|
deviceOsDisplayName: this.#deviceOsDisplayName(),
|
|
3560
|
+
connectTimeoutMs: this.#connectTimeoutMs,
|
|
3561
|
+
nodeQueryTimeoutMs: this.#nodeQueryTimeoutMs,
|
|
3257
3562
|
history: { enabled: true, requireFullSync: true },
|
|
3258
3563
|
addons: {
|
|
3259
3564
|
autoDecrypt: true,
|
|
3260
3565
|
persistAllSecrets: true
|
|
3261
3566
|
},
|
|
3262
3567
|
media: { processor: sharedMediaProcessor }
|
|
3263
|
-
}, 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
|
+
}
|
|
3264
3583
|
this.#storeEntry = storeEntry;
|
|
3265
3584
|
this.#storePath = storePath;
|
|
3266
3585
|
this.#client = client;
|
|
@@ -3311,6 +3630,8 @@ var ZapoProvider = class {
|
|
|
3311
3630
|
});
|
|
3312
3631
|
client.on("message_send", (event) => {
|
|
3313
3632
|
if (!event.id || !event.message) return;
|
|
3633
|
+
this.#sentMessages += 1;
|
|
3634
|
+
this.#lastOutgoingAt = /* @__PURE__ */ new Date();
|
|
3314
3635
|
this.#remember({
|
|
3315
3636
|
key: {
|
|
3316
3637
|
id: event.id,
|
|
@@ -3364,6 +3685,8 @@ var ZapoProvider = class {
|
|
|
3364
3685
|
const message = normalizeZapoMessage(event);
|
|
3365
3686
|
if (!message) return;
|
|
3366
3687
|
if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
|
|
3688
|
+
this.#receivedMessages += 1;
|
|
3689
|
+
this.#lastIncomingAt = /* @__PURE__ */ new Date();
|
|
3367
3690
|
this.#messageKeyStore.set(message.keys, stored);
|
|
3368
3691
|
if (message.quoted && quoted?.message) {
|
|
3369
3692
|
this.#messageKeyStore.set(
|
|
@@ -3620,21 +3943,121 @@ var ZapoProvider = class {
|
|
|
3620
3943
|
}
|
|
3621
3944
|
void this.#events.emit("groupChanged", { groupId });
|
|
3622
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
|
+
}
|
|
3623
4039
|
async #handleConnectionEvent(client, event) {
|
|
3624
4040
|
if (client !== this.#client) return;
|
|
3625
4041
|
if (event.status === "open") {
|
|
4042
|
+
const wasReconnect = this.#reconnectAttempt > 0;
|
|
3626
4043
|
this.#connectPromise = void 0;
|
|
3627
4044
|
this.#connected = true;
|
|
4045
|
+
this.#state = "connected";
|
|
3628
4046
|
this.#resolvePairingReady?.();
|
|
3629
4047
|
this.#resolvePairingReady = void 0;
|
|
3630
4048
|
this.#connectedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
4049
|
+
this.#lastConnectedAt = /* @__PURE__ */ new Date();
|
|
4050
|
+
if (wasReconnect) this.#reconnects += 1;
|
|
3631
4051
|
this.#reconnectAttempt = 0;
|
|
3632
4052
|
this.#closedNotified = false;
|
|
4053
|
+
this.#groupMetadataRecoveryAt.clear();
|
|
4054
|
+
this.#groupMetadataRecoveryInFlight.clear();
|
|
3633
4055
|
await this.#events.emit("connection", { state: "connected" });
|
|
3634
4056
|
return;
|
|
3635
4057
|
}
|
|
3636
4058
|
this.#connectPromise = void 0;
|
|
3637
4059
|
this.#connected = false;
|
|
4060
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
3638
4061
|
const details = disconnectDetails(event.reason, event.code);
|
|
3639
4062
|
const error = connectionError(event.reason, details);
|
|
3640
4063
|
if (this.#intentionalClose || event.isLogout === true || shouldStopAutomaticReconnect(details)) {
|
|
@@ -3676,6 +4099,8 @@ var ZapoProvider = class {
|
|
|
3676
4099
|
}
|
|
3677
4100
|
this.#connectPromise = void 0;
|
|
3678
4101
|
this.#connected = false;
|
|
4102
|
+
this.#state = "closed";
|
|
4103
|
+
this.#lastDisconnectedAt = /* @__PURE__ */ new Date();
|
|
3679
4104
|
await this.#emitClosedOnce(error);
|
|
3680
4105
|
this.#client = void 0;
|
|
3681
4106
|
try {
|
|
@@ -3691,6 +4116,7 @@ var ZapoProvider = class {
|
|
|
3691
4116
|
async #emitClosedOnce(error) {
|
|
3692
4117
|
if (this.#closedNotified) return;
|
|
3693
4118
|
this.#closedNotified = true;
|
|
4119
|
+
this.#state = "closed";
|
|
3694
4120
|
await this.#events.emit("connection", {
|
|
3695
4121
|
state: "closed",
|
|
3696
4122
|
...error ? { error } : {}
|
|
@@ -3712,15 +4138,16 @@ var ZapoProvider = class {
|
|
|
3712
4138
|
async #scheduleReconnect(error, delayOverrideMs) {
|
|
3713
4139
|
if (this.#reconnectTimer) return;
|
|
3714
4140
|
const options = this.#options.reconnect;
|
|
3715
|
-
const maxAttempts = options?.maxAttempts ??
|
|
4141
|
+
const maxAttempts = options?.maxAttempts ?? Number.POSITIVE_INFINITY;
|
|
3716
4142
|
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
3717
4143
|
await this.#emitClosedOnce(error);
|
|
3718
4144
|
await this.#releaseStore();
|
|
3719
4145
|
return;
|
|
3720
4146
|
}
|
|
3721
4147
|
this.#reconnectAttempt += 1;
|
|
4148
|
+
this.#state = "reconnecting";
|
|
3722
4149
|
await this.#events.emit("connection", {
|
|
3723
|
-
state:
|
|
4150
|
+
state: this.#state,
|
|
3724
4151
|
attempt: this.#reconnectAttempt,
|
|
3725
4152
|
...error ? { error } : {}
|
|
3726
4153
|
});
|
|
@@ -3968,8 +4395,20 @@ var ZapoProvider = class {
|
|
|
3968
4395
|
async #media(source) {
|
|
3969
4396
|
if (source instanceof Uint8Array) return source;
|
|
3970
4397
|
if ("path" in source) return source.path;
|
|
3971
|
-
|
|
4398
|
+
let response;
|
|
4399
|
+
try {
|
|
4400
|
+
response = await fetch(source.url, {
|
|
4401
|
+
signal: AbortSignal.timeout(REMOTE_MEDIA_TIMEOUT_MS)
|
|
4402
|
+
});
|
|
4403
|
+
} catch (error) {
|
|
4404
|
+
throw new WhaNextError(
|
|
4405
|
+
"PROVIDER_ERROR",
|
|
4406
|
+
"Could not open the remote media source.",
|
|
4407
|
+
{ cause: error, recoverable: true }
|
|
4408
|
+
);
|
|
4409
|
+
}
|
|
3972
4410
|
if (!response.ok) {
|
|
4411
|
+
await response.body?.cancel().catch(() => void 0);
|
|
3973
4412
|
throw new WhaNextError(
|
|
3974
4413
|
"PROVIDER_ERROR",
|
|
3975
4414
|
"Could not download the remote media source.",
|
|
@@ -3979,7 +4418,16 @@ var ZapoProvider = class {
|
|
|
3979
4418
|
}
|
|
3980
4419
|
);
|
|
3981
4420
|
}
|
|
3982
|
-
|
|
4421
|
+
if (!response.body) {
|
|
4422
|
+
throw new WhaNextError(
|
|
4423
|
+
"PROVIDER_ERROR",
|
|
4424
|
+
"The remote media source returned an empty response body.",
|
|
4425
|
+
{ recoverable: true }
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
return Readable.fromWeb(
|
|
4429
|
+
response.body
|
|
4430
|
+
);
|
|
3983
4431
|
}
|
|
3984
4432
|
#mentions(mentions) {
|
|
3985
4433
|
return mentions.map((mention) => typeof mention === "string" ? mention : mention.mentionId);
|
|
@@ -4385,7 +4833,12 @@ function createZapoStoreEntry(storePath) {
|
|
|
4385
4833
|
backends: {
|
|
4386
4834
|
sqlite: createSqliteStore({
|
|
4387
4835
|
path: storePath,
|
|
4388
|
-
driver: "auto"
|
|
4836
|
+
driver: "auto",
|
|
4837
|
+
pragmas: {
|
|
4838
|
+
journal_mode: "WAL",
|
|
4839
|
+
synchronous: "NORMAL",
|
|
4840
|
+
busy_timeout: 5e3
|
|
4841
|
+
}
|
|
4389
4842
|
})
|
|
4390
4843
|
},
|
|
4391
4844
|
providers: {
|
|
@@ -4403,11 +4856,19 @@ function createZapoStoreEntry(storePath) {
|
|
|
4403
4856
|
},
|
|
4404
4857
|
cacheProviders: {
|
|
4405
4858
|
messageSecret: "sqlite"
|
|
4859
|
+
},
|
|
4860
|
+
memory: {
|
|
4861
|
+
cacheTtlMs: {
|
|
4862
|
+
groupMetadataMs: GROUP_METADATA_CACHE_TTL_MS,
|
|
4863
|
+
deviceListMs: DEVICE_LIST_CACHE_TTL_MS
|
|
4864
|
+
}
|
|
4406
4865
|
}
|
|
4407
4866
|
});
|
|
4408
4867
|
const Database = require2("better-sqlite3");
|
|
4409
4868
|
const snapshotDb = new Database(messageSnapshotStorePath(storePath));
|
|
4410
4869
|
snapshotDb.exec(`
|
|
4870
|
+
PRAGMA journal_mode = WAL;
|
|
4871
|
+
PRAGMA synchronous = NORMAL;
|
|
4411
4872
|
PRAGMA busy_timeout = 5000;
|
|
4412
4873
|
CREATE TABLE IF NOT EXISTS whanext_message_snapshots (
|
|
4413
4874
|
session_id TEXT NOT NULL,
|
|
@@ -4648,6 +5109,44 @@ function booleanValue(value) {
|
|
|
4648
5109
|
if (value === false || value === 0) return false;
|
|
4649
5110
|
return void 0;
|
|
4650
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
|
+
}
|
|
4651
5150
|
function bytesField(value) {
|
|
4652
5151
|
if (value instanceof Uint8Array) return value;
|
|
4653
5152
|
return void 0;
|
|
@@ -4656,9 +5155,11 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4656
5155
|
level;
|
|
4657
5156
|
#logger;
|
|
4658
5157
|
#context;
|
|
4659
|
-
|
|
5158
|
+
#onWarn;
|
|
5159
|
+
constructor(logger, context = {}, onWarn) {
|
|
4660
5160
|
this.#logger = logger;
|
|
4661
5161
|
this.#context = context;
|
|
5162
|
+
this.#onWarn = onWarn;
|
|
4662
5163
|
this.level = logger.level === "debug" ? "debug" : logger.level === "warn" ? "warn" : logger.level === "error" || logger.level === "silent" ? "error" : "info";
|
|
4663
5164
|
}
|
|
4664
5165
|
trace(message, context) {
|
|
@@ -4671,7 +5172,9 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4671
5172
|
this.#logger.info(message, this.#merge(context));
|
|
4672
5173
|
}
|
|
4673
5174
|
warn(message, context) {
|
|
4674
|
-
|
|
5175
|
+
const merged = this.#merge(context);
|
|
5176
|
+
this.#logger.warn(message, merged);
|
|
5177
|
+
this.#onWarn?.(message, merged);
|
|
4675
5178
|
}
|
|
4676
5179
|
error(message, context) {
|
|
4677
5180
|
this.#logger.error(message, this.#merge(context));
|
|
@@ -4680,7 +5183,7 @@ var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
|
4680
5183
|
return new _WhaNextZapoLogger(this.#logger, {
|
|
4681
5184
|
...this.#context,
|
|
4682
5185
|
...bindings
|
|
4683
|
-
});
|
|
5186
|
+
}, this.#onWarn);
|
|
4684
5187
|
}
|
|
4685
5188
|
#merge(context) {
|
|
4686
5189
|
return context ? { ...this.#context, ...context } : this.#context;
|
|
@@ -4708,7 +5211,9 @@ async function create(options = {}) {
|
|
|
4708
5211
|
...options.accountId ? { sessionId: options.accountId } : {},
|
|
4709
5212
|
...options.messageCacheSize !== void 0 ? { messageCacheSize: options.messageCacheSize } : {},
|
|
4710
5213
|
...options.processOfflineMessages !== void 0 ? { processOfflineMessages: options.processOfflineMessages } : {},
|
|
4711
|
-
...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 } : {}
|
|
4712
5217
|
});
|
|
4713
5218
|
return new WhaNextApp(provider, {
|
|
4714
5219
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
@@ -4940,6 +5445,12 @@ function mergeCreateOptions(shared, account) {
|
|
|
4940
5445
|
...account.reconnect
|
|
4941
5446
|
};
|
|
4942
5447
|
}
|
|
5448
|
+
if (shared.providerTimeouts || account.providerTimeouts) {
|
|
5449
|
+
merged.providerTimeouts = {
|
|
5450
|
+
...shared.providerTimeouts,
|
|
5451
|
+
...account.providerTimeouts
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
4943
5454
|
if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
|
|
4944
5455
|
merged.logger = {
|
|
4945
5456
|
...shared.logger,
|