@whanext/core 0.19.5 → 0.19.7
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 +22 -0
- package/MIGRATING_TO_ZAPO.md +2 -2
- package/README.md +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +636 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -370,7 +370,7 @@ var CommandConcurrencyController = class {
|
|
|
370
370
|
recoverable: true
|
|
371
371
|
});
|
|
372
372
|
} else if (strategy === "queue" && state.active >= max) {
|
|
373
|
-
await new Promise((
|
|
373
|
+
await new Promise((resolve4) => state.queue.push(resolve4));
|
|
374
374
|
}
|
|
375
375
|
const controller = new AbortController();
|
|
376
376
|
state.controllers.add(controller);
|
|
@@ -1617,8 +1617,8 @@ var SqliteMuteStore = class {
|
|
|
1617
1617
|
}
|
|
1618
1618
|
let database;
|
|
1619
1619
|
try {
|
|
1620
|
-
const
|
|
1621
|
-
const sqlite =
|
|
1620
|
+
const require3 = createRequire(import.meta.url);
|
|
1621
|
+
const sqlite = require3("node:sqlite");
|
|
1622
1622
|
database = new sqlite.DatabaseSync(databasePath);
|
|
1623
1623
|
database.exec("PRAGMA journal_mode = WAL");
|
|
1624
1624
|
database.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -2203,12 +2203,12 @@ var WhaNextApp = class {
|
|
|
2203
2203
|
this.logger.info("Login started");
|
|
2204
2204
|
let unsubscribe = () => void 0;
|
|
2205
2205
|
let timer;
|
|
2206
|
-
const connected = new Promise((
|
|
2206
|
+
const connected = new Promise((resolve4, reject) => {
|
|
2207
2207
|
unsubscribe = this.#provider.on("connection", (update) => {
|
|
2208
|
-
if (update.state === "connected")
|
|
2208
|
+
if (update.state === "connected") resolve4();
|
|
2209
2209
|
if (update.state === "closed") {
|
|
2210
2210
|
reject(
|
|
2211
|
-
new WhaNextError(
|
|
2211
|
+
update.error instanceof WhaNextError ? update.error : new WhaNextError(
|
|
2212
2212
|
"CONNECTION_FAILED",
|
|
2213
2213
|
"WhatsApp closed the connection before login completed.",
|
|
2214
2214
|
{
|
|
@@ -2375,8 +2375,9 @@ var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
|
2375
2375
|
})(Browser || {});
|
|
2376
2376
|
|
|
2377
2377
|
// src/provider/zapo/zapo-provider.ts
|
|
2378
|
-
import { mkdir } from "fs/promises";
|
|
2379
|
-
import {
|
|
2378
|
+
import { mkdir, stat } from "fs/promises";
|
|
2379
|
+
import { createRequire as createRequire2 } from "module";
|
|
2380
|
+
import { basename, dirname as dirname2, join, resolve as resolve2 } from "path";
|
|
2380
2381
|
import { createMediaProcessor } from "@zapo-js/media-utils";
|
|
2381
2382
|
import { createSqliteStore } from "@zapo-js/store-sqlite";
|
|
2382
2383
|
import {
|
|
@@ -2706,6 +2707,25 @@ function toNumber(value) {
|
|
|
2706
2707
|
}
|
|
2707
2708
|
|
|
2708
2709
|
// src/provider/zapo/zapo-provider.ts
|
|
2710
|
+
var require2 = createRequire2(import.meta.url);
|
|
2711
|
+
var sharedZapoStores = /* @__PURE__ */ new Map();
|
|
2712
|
+
var sharedZapoStorePromises = /* @__PURE__ */ new Map();
|
|
2713
|
+
var fatalDisconnectReasons = /* @__PURE__ */ new Set([
|
|
2714
|
+
"stream_error_replaced",
|
|
2715
|
+
"stream_error_device_removed",
|
|
2716
|
+
"stream_error_force_logout",
|
|
2717
|
+
"failure_not_authorized",
|
|
2718
|
+
"failure_banned",
|
|
2719
|
+
"failure_locked",
|
|
2720
|
+
"failure_client_too_old",
|
|
2721
|
+
"failure_bad_user_agent",
|
|
2722
|
+
"primary_identity_key_change"
|
|
2723
|
+
]);
|
|
2724
|
+
var fatalDisconnectCodes = /* @__PURE__ */ new Set([401, 403, 405, 406, 409, 516]);
|
|
2725
|
+
var sharedMediaProcessor = createMediaProcessor();
|
|
2726
|
+
var messageSnapshotRetentionSeconds = 7 * 24 * 60 * 60;
|
|
2727
|
+
var messageSnapshotMaxPerSession = 2e4;
|
|
2728
|
+
var messageSnapshotPruneInterval = 256;
|
|
2709
2729
|
var ZapoProvider = class {
|
|
2710
2730
|
#options;
|
|
2711
2731
|
#events = new TypedEventEmitter();
|
|
@@ -2716,14 +2736,19 @@ var ZapoProvider = class {
|
|
|
2716
2736
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
2717
2737
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
2718
2738
|
#messageCacheSize;
|
|
2739
|
+
#protocolMutationQueue = Promise.resolve();
|
|
2719
2740
|
#client;
|
|
2741
|
+
#storeEntry;
|
|
2742
|
+
#storePath;
|
|
2720
2743
|
#intentionalClose = false;
|
|
2744
|
+
#closedNotified = false;
|
|
2721
2745
|
#reconnectAttempt = 0;
|
|
2722
2746
|
#reconnectTimer;
|
|
2723
2747
|
#connectPromise;
|
|
2724
2748
|
#pairingRequired = false;
|
|
2725
2749
|
#connected = false;
|
|
2726
2750
|
#connectedAtSeconds = 0;
|
|
2751
|
+
#messageSnapshotsSincePrune = 0;
|
|
2727
2752
|
#pairingReady = Promise.resolve();
|
|
2728
2753
|
#resolvePairingReady;
|
|
2729
2754
|
constructor(options) {
|
|
@@ -2736,6 +2761,7 @@ var ZapoProvider = class {
|
|
|
2736
2761
|
}
|
|
2737
2762
|
async connect() {
|
|
2738
2763
|
this.#intentionalClose = false;
|
|
2764
|
+
this.#closedNotified = false;
|
|
2739
2765
|
const client = await this.#ensureClient();
|
|
2740
2766
|
if (this.#connected || this.#connectPromise) {
|
|
2741
2767
|
return;
|
|
@@ -2755,12 +2781,16 @@ var ZapoProvider = class {
|
|
|
2755
2781
|
}
|
|
2756
2782
|
const client = this.#client;
|
|
2757
2783
|
this.#connectPromise = void 0;
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2784
|
+
try {
|
|
2785
|
+
if (client) {
|
|
2786
|
+
await client.disconnect();
|
|
2787
|
+
} else {
|
|
2788
|
+
await this.#emitClosedOnce();
|
|
2789
|
+
}
|
|
2790
|
+
} finally {
|
|
2791
|
+
this.#connected = false;
|
|
2792
|
+
await this.#releaseStore();
|
|
2762
2793
|
}
|
|
2763
|
-
this.#connected = false;
|
|
2764
2794
|
}
|
|
2765
2795
|
getCurrentUserIds() {
|
|
2766
2796
|
const credentials = this.#client?.getCredentials();
|
|
@@ -2819,7 +2849,7 @@ var ZapoProvider = class {
|
|
|
2819
2849
|
return this.#sent(result, chatId);
|
|
2820
2850
|
}
|
|
2821
2851
|
async repostMessage(source, chatId, options = {}) {
|
|
2822
|
-
const original = this.#findStoredMessage(this.#toZapoKey(source));
|
|
2852
|
+
const original = await this.#findStoredMessage(this.#toZapoKey(source));
|
|
2823
2853
|
if (!original?.message) {
|
|
2824
2854
|
throw new WhaNextError(
|
|
2825
2855
|
"MESSAGE_NOT_FOUND",
|
|
@@ -2849,7 +2879,7 @@ var ZapoProvider = class {
|
|
|
2849
2879
|
return this.#sent(result, key.chatId);
|
|
2850
2880
|
}
|
|
2851
2881
|
async downloadMedia(key) {
|
|
2852
|
-
const message = this.#messageKeyStore.get(key) ?? this.#findStoredMessage(this.#toZapoKey(key));
|
|
2882
|
+
const message = this.#messageKeyStore.get(key) ?? await this.#findStoredMessage(this.#toZapoKey(key));
|
|
2853
2883
|
if (!message?.message) {
|
|
2854
2884
|
throw new WhaNextError(
|
|
2855
2885
|
"MEDIA_NOT_AVAILABLE",
|
|
@@ -2993,34 +3023,24 @@ var ZapoProvider = class {
|
|
|
2993
3023
|
}
|
|
2994
3024
|
async #ensureClient() {
|
|
2995
3025
|
if (this.#client) return this.#client;
|
|
2996
|
-
|
|
2997
|
-
const
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
appState: "sqlite",
|
|
3012
|
-
privacyToken: "sqlite",
|
|
3013
|
-
messages: "none",
|
|
3014
|
-
threads: "none",
|
|
3015
|
-
contacts: "none"
|
|
3016
|
-
},
|
|
3017
|
-
cacheProviders: {
|
|
3018
|
-
messageSecret: "sqlite"
|
|
3019
|
-
}
|
|
3020
|
-
});
|
|
3026
|
+
const authPath = resolve2(this.#options.auth);
|
|
3027
|
+
const sessionId = this.#sessionId();
|
|
3028
|
+
const authPathExisted = await fileExists(authPath);
|
|
3029
|
+
const legacyStorePath = join(authPath, "state.sqlite");
|
|
3030
|
+
const canShareByDirectory = sessionId !== "default" && basename(authPath) === sessionId;
|
|
3031
|
+
const storePath = canShareByDirectory ? join(dirname2(authPath), "state.sqlite") : legacyStorePath;
|
|
3032
|
+
const resetSharedSession = canShareByDirectory && !authPathExisted && await fileExists(storePath);
|
|
3033
|
+
await mkdir(authPath, { recursive: true });
|
|
3034
|
+
const storeEntry = await acquireSharedZapoStore(
|
|
3035
|
+
storePath,
|
|
3036
|
+
sessionId,
|
|
3037
|
+
legacyStorePath,
|
|
3038
|
+
resetSharedSession,
|
|
3039
|
+
this.#logger
|
|
3040
|
+
);
|
|
3021
3041
|
const client = new WaClient({
|
|
3022
|
-
store,
|
|
3023
|
-
sessionId
|
|
3042
|
+
store: storeEntry.store,
|
|
3043
|
+
sessionId,
|
|
3024
3044
|
markOnlineOnConnect: false,
|
|
3025
3045
|
deviceBrowser: this.#deviceBrowser(),
|
|
3026
3046
|
deviceOsDisplayName: this.#deviceOsDisplayName(),
|
|
@@ -3029,8 +3049,10 @@ var ZapoProvider = class {
|
|
|
3029
3049
|
autoDecrypt: true,
|
|
3030
3050
|
persistAllSecrets: true
|
|
3031
3051
|
},
|
|
3032
|
-
media: { processor:
|
|
3052
|
+
media: { processor: sharedMediaProcessor }
|
|
3033
3053
|
}, new WhaNextZapoLogger(this.#logger));
|
|
3054
|
+
this.#storeEntry = storeEntry;
|
|
3055
|
+
this.#storePath = storePath;
|
|
3034
3056
|
this.#client = client;
|
|
3035
3057
|
this.#bind(client);
|
|
3036
3058
|
return client;
|
|
@@ -3045,10 +3067,19 @@ var ZapoProvider = class {
|
|
|
3045
3067
|
client.on("auth_paired", () => {
|
|
3046
3068
|
this.#pairingRequired = false;
|
|
3047
3069
|
});
|
|
3070
|
+
client.on("auth_passkey_required", ({ hasSigner }) => {
|
|
3071
|
+
if (hasSigner) return;
|
|
3072
|
+
const error = new WhaNextError(
|
|
3073
|
+
"AUTH_PASSKEY_REQUIRED",
|
|
3074
|
+
"WhatsApp requires a passkey assertion to link this account, but no passkey signer is configured.",
|
|
3075
|
+
{ recoverable: false }
|
|
3076
|
+
);
|
|
3077
|
+
void this.#stopTerminalConnection(client, error);
|
|
3078
|
+
});
|
|
3048
3079
|
client.on("message", (event) => {
|
|
3049
3080
|
const protocol = this.#protocolMessage(event);
|
|
3050
3081
|
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3051
|
-
this.#
|
|
3082
|
+
this.#enqueueProtocolEvent({
|
|
3052
3083
|
...event,
|
|
3053
3084
|
protocolMessage: protocol
|
|
3054
3085
|
});
|
|
@@ -3069,7 +3100,7 @@ var ZapoProvider = class {
|
|
|
3069
3100
|
});
|
|
3070
3101
|
});
|
|
3071
3102
|
client.on("message_protocol", (event) => {
|
|
3072
|
-
this.#
|
|
3103
|
+
this.#enqueueProtocolEvent(event);
|
|
3073
3104
|
});
|
|
3074
3105
|
client.on("message_addon", (event) => {
|
|
3075
3106
|
this.#handleAddonEvent(event);
|
|
@@ -3139,7 +3170,7 @@ var ZapoProvider = class {
|
|
|
3139
3170
|
...protocolKey?.participant !== void 0 ? { participant: protocolKey.participant } : event.key.participant !== void 0 ? { participant: event.key.participant } : {},
|
|
3140
3171
|
...protocolKey?.participantAlt !== void 0 ? { participantAlt: protocolKey.participantAlt } : event.key.participantAlt !== void 0 ? { participantAlt: event.key.participantAlt } : {}
|
|
3141
3172
|
};
|
|
3142
|
-
this.#handleProtocolEvent({
|
|
3173
|
+
void this.#handleProtocolEvent({
|
|
3143
3174
|
key: event.key,
|
|
3144
3175
|
...event.timestampSeconds !== void 0 ? { timestampSeconds: event.timestampSeconds } : {},
|
|
3145
3176
|
protocolMessage: {
|
|
@@ -3212,7 +3243,15 @@ var ZapoProvider = class {
|
|
|
3212
3243
|
if (timestamp === void 0) return false;
|
|
3213
3244
|
return timestamp < this.#connectedAtSeconds - 3;
|
|
3214
3245
|
}
|
|
3215
|
-
#
|
|
3246
|
+
#enqueueProtocolEvent(event) {
|
|
3247
|
+
this.#protocolMutationQueue = this.#protocolMutationQueue.then(() => this.#handleProtocolEvent(event)).catch((error) => {
|
|
3248
|
+
this.#logger.debug("Could not process a Zapo protocol mutation.", {
|
|
3249
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3250
|
+
messageId: event.key.id ?? void 0
|
|
3251
|
+
});
|
|
3252
|
+
});
|
|
3253
|
+
}
|
|
3254
|
+
async #handleProtocolEvent(event) {
|
|
3216
3255
|
if (this.#shouldIgnoreProtocolEvent(event)) {
|
|
3217
3256
|
this.#logger.debug("Ignored protocol mutation from before the current live connection.", {
|
|
3218
3257
|
messageId: event.key.id ?? void 0,
|
|
@@ -3240,7 +3279,7 @@ var ZapoProvider = class {
|
|
|
3240
3279
|
...participant !== void 0 ? { participant } : {},
|
|
3241
3280
|
...participantAlt !== void 0 ? { participantAlt } : {}
|
|
3242
3281
|
};
|
|
3243
|
-
const stored = this.#findStoredMessage(target);
|
|
3282
|
+
const stored = await this.#findStoredMessage(target);
|
|
3244
3283
|
if (!target.remoteJid && stored?.key.remoteJid) target.remoteJid = stored.key.remoteJid;
|
|
3245
3284
|
if (!target.remoteJid) return;
|
|
3246
3285
|
const type = protocol.type;
|
|
@@ -3335,20 +3374,26 @@ var ZapoProvider = class {
|
|
|
3335
3374
|
this.#connected = true;
|
|
3336
3375
|
this.#connectedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
3337
3376
|
this.#reconnectAttempt = 0;
|
|
3377
|
+
this.#closedNotified = false;
|
|
3338
3378
|
await this.#events.emit("connection", { state: "connected" });
|
|
3339
3379
|
return;
|
|
3340
3380
|
}
|
|
3341
3381
|
this.#connectPromise = void 0;
|
|
3342
3382
|
this.#connected = false;
|
|
3343
|
-
const
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3383
|
+
const details = disconnectDetails(event.reason, event.code);
|
|
3384
|
+
const error = connectionError(event.reason, details);
|
|
3385
|
+
if (this.#intentionalClose || event.isLogout === true || shouldStopAutomaticReconnect(details)) {
|
|
3386
|
+
if (!this.#intentionalClose && shouldStopAutomaticReconnect(details)) {
|
|
3387
|
+
this.#logger.warn("WhatsApp closed the session with a non-retryable reason.", {
|
|
3388
|
+
...details.reason ? { reason: details.reason } : {},
|
|
3389
|
+
...details.code !== void 0 ? { code: details.code } : {}
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
await this.#emitClosedOnce(error);
|
|
3393
|
+
await this.#releaseStore();
|
|
3349
3394
|
return;
|
|
3350
3395
|
}
|
|
3351
|
-
await this.#scheduleReconnect(error);
|
|
3396
|
+
await this.#scheduleReconnect(error, reconnectDelayOverride(details));
|
|
3352
3397
|
}
|
|
3353
3398
|
#startConnect(client) {
|
|
3354
3399
|
const promise = client.connect();
|
|
@@ -3357,20 +3402,65 @@ var ZapoProvider = class {
|
|
|
3357
3402
|
if (this.#connectPromise !== promise) return;
|
|
3358
3403
|
this.#connectPromise = void 0;
|
|
3359
3404
|
if (this.#intentionalClose) return;
|
|
3360
|
-
const
|
|
3405
|
+
const details = disconnectDetails(error);
|
|
3406
|
+
const normalized = connectionError(error, details) ?? (error instanceof Error ? error : new Error(String(error)));
|
|
3361
3407
|
this.#logger.warn("WhatsApp connection attempt failed.", { error: normalized });
|
|
3362
|
-
|
|
3408
|
+
if (shouldStopAutomaticReconnect(details)) {
|
|
3409
|
+
await this.#stopTerminalConnection(client, normalized);
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
await this.#scheduleReconnect(normalized, reconnectDelayOverride(details));
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
async #stopTerminalConnection(client, error) {
|
|
3416
|
+
if (client !== this.#client) return;
|
|
3417
|
+
this.#intentionalClose = true;
|
|
3418
|
+
if (this.#reconnectTimer) {
|
|
3419
|
+
clearTimeout(this.#reconnectTimer);
|
|
3420
|
+
this.#reconnectTimer = void 0;
|
|
3421
|
+
}
|
|
3422
|
+
this.#connectPromise = void 0;
|
|
3423
|
+
this.#connected = false;
|
|
3424
|
+
await this.#emitClosedOnce(error);
|
|
3425
|
+
this.#client = void 0;
|
|
3426
|
+
try {
|
|
3427
|
+
await client.disconnect();
|
|
3428
|
+
} catch (disconnectError) {
|
|
3429
|
+
this.#logger.debug("Could not close a terminal Zapo connection cleanly.", {
|
|
3430
|
+
error: disconnectError instanceof Error ? disconnectError : new Error(String(disconnectError))
|
|
3431
|
+
});
|
|
3432
|
+
} finally {
|
|
3433
|
+
await this.#releaseStore();
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
async #emitClosedOnce(error) {
|
|
3437
|
+
if (this.#closedNotified) return;
|
|
3438
|
+
this.#closedNotified = true;
|
|
3439
|
+
await this.#events.emit("connection", {
|
|
3440
|
+
state: "closed",
|
|
3441
|
+
...error ? { error } : {}
|
|
3363
3442
|
});
|
|
3364
3443
|
}
|
|
3365
|
-
async #
|
|
3444
|
+
async #releaseStore() {
|
|
3445
|
+
const entry = this.#storeEntry;
|
|
3446
|
+
const storePath = this.#storePath;
|
|
3447
|
+
const sessionId = this.#sessionId();
|
|
3448
|
+
this.#storeEntry = void 0;
|
|
3449
|
+
this.#storePath = void 0;
|
|
3450
|
+
this.#client = void 0;
|
|
3451
|
+
if (!entry || !storePath) return;
|
|
3452
|
+
await releaseSharedZapoStore(storePath, sessionId, entry, this.#logger);
|
|
3453
|
+
}
|
|
3454
|
+
#sessionId() {
|
|
3455
|
+
return this.#options.sessionId ?? "default";
|
|
3456
|
+
}
|
|
3457
|
+
async #scheduleReconnect(error, delayOverrideMs) {
|
|
3366
3458
|
if (this.#reconnectTimer) return;
|
|
3367
3459
|
const options = this.#options.reconnect;
|
|
3368
3460
|
const maxAttempts = options?.maxAttempts ?? 10;
|
|
3369
3461
|
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
3370
|
-
await this.#
|
|
3371
|
-
|
|
3372
|
-
...error ? { error } : {}
|
|
3373
|
-
});
|
|
3462
|
+
await this.#emitClosedOnce(error);
|
|
3463
|
+
await this.#releaseStore();
|
|
3374
3464
|
return;
|
|
3375
3465
|
}
|
|
3376
3466
|
this.#reconnectAttempt += 1;
|
|
@@ -3381,11 +3471,13 @@ var ZapoProvider = class {
|
|
|
3381
3471
|
});
|
|
3382
3472
|
const initial = options?.initialDelayMs ?? 1e3;
|
|
3383
3473
|
const maximum = options?.maxDelayMs ?? 3e4;
|
|
3384
|
-
const
|
|
3474
|
+
const exponential = Math.min(maximum, initial * 2 ** (this.#reconnectAttempt - 1));
|
|
3475
|
+
const delay = delayOverrideMs ?? exponential;
|
|
3476
|
+
const jitter = delay > 0 ? Math.floor(Math.random() * 250) : 0;
|
|
3385
3477
|
this.#reconnectTimer = setTimeout(() => {
|
|
3386
3478
|
this.#reconnectTimer = void 0;
|
|
3387
3479
|
void this.connect();
|
|
3388
|
-
}, delay +
|
|
3480
|
+
}, delay + jitter);
|
|
3389
3481
|
}
|
|
3390
3482
|
async #sendButtons(chatId, content, replyTo) {
|
|
3391
3483
|
this.#validateButtons(content);
|
|
@@ -3718,7 +3810,7 @@ var ZapoProvider = class {
|
|
|
3718
3810
|
#groupParticipantIds(event) {
|
|
3719
3811
|
return uniqueIdentities((event.participants ?? []).map((participant) => participant.jid ?? participant.lidJid ?? participant.phoneJid));
|
|
3720
3812
|
}
|
|
3721
|
-
#findStoredMessage(key) {
|
|
3813
|
+
async #findStoredMessage(key) {
|
|
3722
3814
|
const direct = this.#messageStore.get(this.#messageStoreKey(key));
|
|
3723
3815
|
if (direct) return direct;
|
|
3724
3816
|
if (!key.id) return void 0;
|
|
@@ -3734,18 +3826,103 @@ var ZapoProvider = class {
|
|
|
3734
3826
|
this.#remember(storedQuoted);
|
|
3735
3827
|
return storedQuoted;
|
|
3736
3828
|
}
|
|
3737
|
-
return
|
|
3829
|
+
return this.#readArchivedMessage(key);
|
|
3738
3830
|
}
|
|
3739
3831
|
#remember(message) {
|
|
3740
3832
|
if (!message.key.id || !message.message) return;
|
|
3741
3833
|
const key = this.#messageStoreKey(message.key);
|
|
3742
3834
|
this.#messageStore.delete(key);
|
|
3743
3835
|
this.#messageStore.set(key, message);
|
|
3836
|
+
void this.#archiveMessage(message);
|
|
3744
3837
|
while (this.#messageStore.size > this.#messageCacheSize) {
|
|
3745
3838
|
const oldest = this.#messageStore.keys().next().value;
|
|
3746
3839
|
if (oldest) this.#messageStore.delete(oldest);
|
|
3747
3840
|
}
|
|
3748
3841
|
}
|
|
3842
|
+
async #archiveMessage(message) {
|
|
3843
|
+
const id = message.key.id;
|
|
3844
|
+
const chatId = message.key.remoteJid;
|
|
3845
|
+
const content = message.message;
|
|
3846
|
+
const entry = this.#storeEntry;
|
|
3847
|
+
if (!id || !chatId || !content || !entry) return;
|
|
3848
|
+
try {
|
|
3849
|
+
const participantId = message.key.participant ?? message.key.participantAlt ?? null;
|
|
3850
|
+
const timestampSeconds = toSeconds(message.timestampSeconds) ?? null;
|
|
3851
|
+
const messageBytes = Buffer.from(proto.Message.encode(content).finish());
|
|
3852
|
+
const storedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
3853
|
+
const sessionId = this.#sessionId();
|
|
3854
|
+
entry.snapshotUpsert.run(
|
|
3855
|
+
sessionId,
|
|
3856
|
+
id,
|
|
3857
|
+
chatId,
|
|
3858
|
+
participantId,
|
|
3859
|
+
message.key.fromMe === true ? 1 : 0,
|
|
3860
|
+
timestampSeconds,
|
|
3861
|
+
messageBytes,
|
|
3862
|
+
storedAtSeconds
|
|
3863
|
+
);
|
|
3864
|
+
this.#messageSnapshotsSincePrune += 1;
|
|
3865
|
+
if (this.#messageSnapshotsSincePrune >= messageSnapshotPruneInterval) {
|
|
3866
|
+
this.#messageSnapshotsSincePrune = 0;
|
|
3867
|
+
entry.snapshotPruneAge.run(
|
|
3868
|
+
sessionId,
|
|
3869
|
+
storedAtSeconds - messageSnapshotRetentionSeconds
|
|
3870
|
+
);
|
|
3871
|
+
entry.snapshotPruneOverflow.run(
|
|
3872
|
+
sessionId,
|
|
3873
|
+
sessionId,
|
|
3874
|
+
messageSnapshotMaxPerSession
|
|
3875
|
+
);
|
|
3876
|
+
}
|
|
3877
|
+
} catch (error) {
|
|
3878
|
+
this.#logger.debug("Could not archive a message snapshot for mutation recovery.", {
|
|
3879
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3880
|
+
messageId: id,
|
|
3881
|
+
chatId
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
async #readArchivedMessage(key) {
|
|
3886
|
+
const id = key.id;
|
|
3887
|
+
const entry = this.#storeEntry;
|
|
3888
|
+
if (!id || !entry) return void 0;
|
|
3889
|
+
try {
|
|
3890
|
+
const archived = entry.snapshotGet.get(
|
|
3891
|
+
this.#sessionId(),
|
|
3892
|
+
id
|
|
3893
|
+
);
|
|
3894
|
+
const messageBytes = bytesField(archived?.message_bytes);
|
|
3895
|
+
if (!archived || !messageBytes) return void 0;
|
|
3896
|
+
const archivedChatId = stringValue(archived.chat_id);
|
|
3897
|
+
const remoteJid = key.remoteJid ?? archivedChatId;
|
|
3898
|
+
if (!remoteJid) return void 0;
|
|
3899
|
+
const participant = key.participant ?? key.participantAlt ?? stringValue(archived.participant_id);
|
|
3900
|
+
const archivedFromMe = booleanValue(archived.from_me);
|
|
3901
|
+
const timestampSeconds = numberValue(archived.timestamp_seconds);
|
|
3902
|
+
const fromMe = key.fromMe ?? archivedFromMe;
|
|
3903
|
+
const restored = {
|
|
3904
|
+
key: {
|
|
3905
|
+
...key,
|
|
3906
|
+
id,
|
|
3907
|
+
remoteJid,
|
|
3908
|
+
...fromMe !== void 0 ? { fromMe } : {},
|
|
3909
|
+
...participant ? { participant } : {}
|
|
3910
|
+
},
|
|
3911
|
+
message: proto.Message.decode(messageBytes),
|
|
3912
|
+
...timestampSeconds !== void 0 ? { timestampSeconds } : {}
|
|
3913
|
+
};
|
|
3914
|
+
const cacheKey = this.#messageStoreKey(restored.key);
|
|
3915
|
+
this.#messageStore.delete(cacheKey);
|
|
3916
|
+
this.#messageStore.set(cacheKey, restored);
|
|
3917
|
+
return restored;
|
|
3918
|
+
} catch (error) {
|
|
3919
|
+
this.#logger.debug("Could not restore a message snapshot for mutation recovery.", {
|
|
3920
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3921
|
+
messageId: id
|
|
3922
|
+
});
|
|
3923
|
+
return void 0;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3749
3926
|
#messageStoreKey(key) {
|
|
3750
3927
|
return `${key.remoteJid ?? ""}:${key.id ?? ""}:${key.participant ?? key.participantAlt ?? ""}`;
|
|
3751
3928
|
}
|
|
@@ -3788,8 +3965,8 @@ var ZapoProvider = class {
|
|
|
3788
3965
|
}
|
|
3789
3966
|
#preparePairingGate() {
|
|
3790
3967
|
this.#pairingRequired = false;
|
|
3791
|
-
this.#pairingReady = new Promise((
|
|
3792
|
-
this.#resolvePairingReady =
|
|
3968
|
+
this.#pairingReady = new Promise((resolve4) => {
|
|
3969
|
+
this.#resolvePairingReady = resolve4;
|
|
3793
3970
|
});
|
|
3794
3971
|
}
|
|
3795
3972
|
#deviceBrowser() {
|
|
@@ -3831,6 +4008,395 @@ var ZapoProvider = class {
|
|
|
3831
4008
|
}
|
|
3832
4009
|
}
|
|
3833
4010
|
};
|
|
4011
|
+
function disconnectDetails(value, explicitCode) {
|
|
4012
|
+
let code = typeof explicitCode === "number" ? explicitCode : void 0;
|
|
4013
|
+
let reason;
|
|
4014
|
+
const queue = [value];
|
|
4015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4016
|
+
for (let depth = 0; queue.length > 0 && depth < 12; depth += 1) {
|
|
4017
|
+
const current = queue.shift();
|
|
4018
|
+
if (typeof current === "string") {
|
|
4019
|
+
if (/^\d+$/.test(current)) {
|
|
4020
|
+
code ??= Number(current);
|
|
4021
|
+
} else {
|
|
4022
|
+
reason ??= current;
|
|
4023
|
+
}
|
|
4024
|
+
continue;
|
|
4025
|
+
}
|
|
4026
|
+
if (!current || typeof current !== "object") continue;
|
|
4027
|
+
if (seen.has(current)) continue;
|
|
4028
|
+
seen.add(current);
|
|
4029
|
+
const record = current;
|
|
4030
|
+
const directCode = record.code;
|
|
4031
|
+
const statusCode = record.statusCode;
|
|
4032
|
+
if (typeof directCode === "number") code ??= directCode;
|
|
4033
|
+
if (typeof statusCode === "number") code ??= statusCode;
|
|
4034
|
+
const directReason = record.reason;
|
|
4035
|
+
const failureReason = record.failureReason;
|
|
4036
|
+
if (typeof directReason === "string") {
|
|
4037
|
+
if (/^\d+$/.test(directReason)) code ??= Number(directReason);
|
|
4038
|
+
else reason ??= directReason;
|
|
4039
|
+
}
|
|
4040
|
+
if (typeof failureReason === "string") reason ??= failureReason;
|
|
4041
|
+
const output = record.output;
|
|
4042
|
+
const data = record.data;
|
|
4043
|
+
if (output && typeof output === "object") queue.push(output);
|
|
4044
|
+
if (data && typeof data === "object") queue.push(data);
|
|
4045
|
+
if (record.cause !== void 0) queue.push(record.cause);
|
|
4046
|
+
}
|
|
4047
|
+
return {
|
|
4048
|
+
...reason ? { reason } : {},
|
|
4049
|
+
...code !== void 0 ? { code } : {}
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
function shouldStopAutomaticReconnect(details) {
|
|
4053
|
+
if (details.reason === "client_disconnected") return true;
|
|
4054
|
+
if (details.reason && fatalDisconnectReasons.has(details.reason)) return true;
|
|
4055
|
+
return details.code !== void 0 && fatalDisconnectCodes.has(details.code);
|
|
4056
|
+
}
|
|
4057
|
+
function reconnectDelayOverride(details) {
|
|
4058
|
+
if (details.code === 515) return 0;
|
|
4059
|
+
if (details.code === 402) return 6e4;
|
|
4060
|
+
return void 0;
|
|
4061
|
+
}
|
|
4062
|
+
function connectionError(value, details) {
|
|
4063
|
+
if (value === void 0 && details.code === void 0 && details.reason === void 0) {
|
|
4064
|
+
return void 0;
|
|
4065
|
+
}
|
|
4066
|
+
const authExpired = details.code === 401 || details.code === 516 || details.reason === "failure_not_authorized" || details.reason === "stream_error_device_removed" || details.reason === "stream_error_force_logout" || details.reason === "primary_identity_key_change";
|
|
4067
|
+
const cause = value instanceof Error ? value : details.code !== void 0 ? {
|
|
4068
|
+
output: { statusCode: details.code },
|
|
4069
|
+
data: { reason: String(details.code) },
|
|
4070
|
+
...value !== void 0 ? { cause: value } : {}
|
|
4071
|
+
} : value;
|
|
4072
|
+
const message = authExpired ? "WhatsApp authorization is no longer valid and the account must be paired again." : details.code === 402 ? "WhatsApp temporarily refused this session. The next reconnect will use an extended backoff." : "WhatsApp closed the connection.";
|
|
4073
|
+
return new WhaNextError(
|
|
4074
|
+
authExpired ? "AUTH_EXPIRED" : "CONNECTION_FAILED",
|
|
4075
|
+
message,
|
|
4076
|
+
{
|
|
4077
|
+
...cause !== void 0 ? { cause } : {},
|
|
4078
|
+
context: {
|
|
4079
|
+
...details.reason ? { reason: details.reason } : {},
|
|
4080
|
+
...details.code !== void 0 ? { statusCode: details.code } : {}
|
|
4081
|
+
},
|
|
4082
|
+
recoverable: details.code === 402 || details.code === 500 || details.code === 503 || details.code === 515
|
|
4083
|
+
}
|
|
4084
|
+
);
|
|
4085
|
+
}
|
|
4086
|
+
async function acquireSharedZapoStore(storePath, sessionId, legacyStorePath, resetSession, logger) {
|
|
4087
|
+
if (storePath === legacyStorePath) {
|
|
4088
|
+
const entry2 = createZapoStoreEntry(storePath);
|
|
4089
|
+
entry2.refs = 1;
|
|
4090
|
+
entry2.sessionRefs.set(sessionId, 1);
|
|
4091
|
+
return entry2;
|
|
4092
|
+
}
|
|
4093
|
+
let pending = sharedZapoStorePromises.get(storePath);
|
|
4094
|
+
if (!pending) {
|
|
4095
|
+
pending = (async () => {
|
|
4096
|
+
await mkdir(dirname2(storePath), { recursive: true });
|
|
4097
|
+
const entry2 = createZapoStoreEntry(storePath);
|
|
4098
|
+
sharedZapoStores.set(storePath, entry2);
|
|
4099
|
+
return entry2;
|
|
4100
|
+
})();
|
|
4101
|
+
sharedZapoStorePromises.set(storePath, pending);
|
|
4102
|
+
}
|
|
4103
|
+
let entry;
|
|
4104
|
+
try {
|
|
4105
|
+
entry = await pending;
|
|
4106
|
+
} catch (error) {
|
|
4107
|
+
if (sharedZapoStorePromises.get(storePath) === pending) {
|
|
4108
|
+
sharedZapoStorePromises.delete(storePath);
|
|
4109
|
+
}
|
|
4110
|
+
throw error;
|
|
4111
|
+
}
|
|
4112
|
+
entry.refs += 1;
|
|
4113
|
+
entry.sessionRefs.set(sessionId, (entry.sessionRefs.get(sessionId) ?? 0) + 1);
|
|
4114
|
+
if (resetSession || await fileExists(legacyStorePath)) {
|
|
4115
|
+
entry.migrationQueue = entry.migrationQueue.then(async () => {
|
|
4116
|
+
if (resetSession) {
|
|
4117
|
+
clearZapoSessionData(storePath, sessionId);
|
|
4118
|
+
entry.snapshotClearSession.run(sessionId);
|
|
4119
|
+
}
|
|
4120
|
+
if (await fileExists(legacyStorePath)) {
|
|
4121
|
+
await migrateLegacyZapoStore(storePath, legacyStorePath, sessionId, logger);
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
await entry.migrationQueue;
|
|
4125
|
+
}
|
|
4126
|
+
return entry;
|
|
4127
|
+
}
|
|
4128
|
+
function createZapoStoreEntry(storePath) {
|
|
4129
|
+
const store = createStore({
|
|
4130
|
+
backends: {
|
|
4131
|
+
sqlite: createSqliteStore({
|
|
4132
|
+
path: storePath,
|
|
4133
|
+
driver: "auto"
|
|
4134
|
+
})
|
|
4135
|
+
},
|
|
4136
|
+
providers: {
|
|
4137
|
+
auth: "sqlite",
|
|
4138
|
+
signal: "sqlite",
|
|
4139
|
+
preKey: "sqlite",
|
|
4140
|
+
session: "sqlite",
|
|
4141
|
+
identity: "sqlite",
|
|
4142
|
+
senderKey: "sqlite",
|
|
4143
|
+
appState: "sqlite",
|
|
4144
|
+
privacyToken: "sqlite",
|
|
4145
|
+
messages: "none",
|
|
4146
|
+
threads: "none",
|
|
4147
|
+
contacts: "none"
|
|
4148
|
+
},
|
|
4149
|
+
cacheProviders: {
|
|
4150
|
+
messageSecret: "sqlite"
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
4153
|
+
const Database = require2("better-sqlite3");
|
|
4154
|
+
const snapshotDb = new Database(messageSnapshotStorePath(storePath));
|
|
4155
|
+
snapshotDb.exec(`
|
|
4156
|
+
PRAGMA busy_timeout = 5000;
|
|
4157
|
+
CREATE TABLE IF NOT EXISTS whanext_message_snapshots (
|
|
4158
|
+
session_id TEXT NOT NULL,
|
|
4159
|
+
message_id TEXT NOT NULL,
|
|
4160
|
+
chat_id TEXT NOT NULL,
|
|
4161
|
+
participant_id TEXT,
|
|
4162
|
+
from_me INTEGER NOT NULL,
|
|
4163
|
+
timestamp_seconds INTEGER,
|
|
4164
|
+
message_bytes BLOB NOT NULL,
|
|
4165
|
+
stored_at_seconds INTEGER NOT NULL,
|
|
4166
|
+
PRIMARY KEY (session_id, message_id)
|
|
4167
|
+
);
|
|
4168
|
+
CREATE INDEX IF NOT EXISTS idx_whanext_message_snapshots_chat
|
|
4169
|
+
ON whanext_message_snapshots (session_id, chat_id);
|
|
4170
|
+
`);
|
|
4171
|
+
const snapshotColumns = snapshotDb.prepare(
|
|
4172
|
+
"PRAGMA table_info(whanext_message_snapshots)"
|
|
4173
|
+
).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4174
|
+
if (!snapshotColumns.includes("stored_at_seconds")) {
|
|
4175
|
+
snapshotDb.exec(
|
|
4176
|
+
"ALTER TABLE whanext_message_snapshots ADD COLUMN stored_at_seconds INTEGER NOT NULL DEFAULT 0"
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4179
|
+
snapshotDb.exec(`
|
|
4180
|
+
CREATE INDEX IF NOT EXISTS idx_whanext_message_snapshots_retention
|
|
4181
|
+
ON whanext_message_snapshots (session_id, stored_at_seconds);
|
|
4182
|
+
`);
|
|
4183
|
+
return {
|
|
4184
|
+
store,
|
|
4185
|
+
snapshotDb,
|
|
4186
|
+
snapshotUpsert: snapshotDb.prepare(`
|
|
4187
|
+
INSERT INTO whanext_message_snapshots (
|
|
4188
|
+
session_id, message_id, chat_id, participant_id, from_me,
|
|
4189
|
+
timestamp_seconds, message_bytes, stored_at_seconds
|
|
4190
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
4191
|
+
ON CONFLICT(session_id, message_id) DO UPDATE SET
|
|
4192
|
+
chat_id = excluded.chat_id,
|
|
4193
|
+
participant_id = excluded.participant_id,
|
|
4194
|
+
from_me = excluded.from_me,
|
|
4195
|
+
timestamp_seconds = excluded.timestamp_seconds,
|
|
4196
|
+
message_bytes = excluded.message_bytes,
|
|
4197
|
+
stored_at_seconds = excluded.stored_at_seconds
|
|
4198
|
+
`),
|
|
4199
|
+
snapshotGet: snapshotDb.prepare(`
|
|
4200
|
+
SELECT chat_id, participant_id, from_me, timestamp_seconds, message_bytes
|
|
4201
|
+
FROM whanext_message_snapshots
|
|
4202
|
+
WHERE session_id = ? AND message_id = ?
|
|
4203
|
+
LIMIT 1
|
|
4204
|
+
`),
|
|
4205
|
+
snapshotPruneAge: snapshotDb.prepare(`
|
|
4206
|
+
DELETE FROM whanext_message_snapshots
|
|
4207
|
+
WHERE session_id = ? AND stored_at_seconds < ?
|
|
4208
|
+
`),
|
|
4209
|
+
snapshotPruneOverflow: snapshotDb.prepare(`
|
|
4210
|
+
DELETE FROM whanext_message_snapshots
|
|
4211
|
+
WHERE session_id = ?
|
|
4212
|
+
AND message_id IN (
|
|
4213
|
+
SELECT message_id
|
|
4214
|
+
FROM whanext_message_snapshots
|
|
4215
|
+
WHERE session_id = ?
|
|
4216
|
+
ORDER BY stored_at_seconds DESC
|
|
4217
|
+
LIMIT -1 OFFSET ?
|
|
4218
|
+
)
|
|
4219
|
+
`),
|
|
4220
|
+
snapshotClearSession: snapshotDb.prepare(`
|
|
4221
|
+
DELETE FROM whanext_message_snapshots
|
|
4222
|
+
WHERE session_id = ?
|
|
4223
|
+
`),
|
|
4224
|
+
refs: 0,
|
|
4225
|
+
sessionRefs: /* @__PURE__ */ new Map(),
|
|
4226
|
+
migrationQueue: Promise.resolve()
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
function messageSnapshotStorePath(storePath) {
|
|
4230
|
+
return join(dirname2(storePath), "whanext-messages.sqlite");
|
|
4231
|
+
}
|
|
4232
|
+
async function releaseSharedZapoStore(storePath, sessionId, entry, logger) {
|
|
4233
|
+
const currentSessionRefs = entry.sessionRefs.get(sessionId) ?? 0;
|
|
4234
|
+
if (currentSessionRefs <= 1) {
|
|
4235
|
+
entry.sessionRefs.delete(sessionId);
|
|
4236
|
+
try {
|
|
4237
|
+
await entry.store.session(sessionId).destroy();
|
|
4238
|
+
} catch (error) {
|
|
4239
|
+
logger.warn("Could not release the Zapo session store cleanly.", {
|
|
4240
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4241
|
+
sessionId
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4244
|
+
} else {
|
|
4245
|
+
entry.sessionRefs.set(sessionId, currentSessionRefs - 1);
|
|
4246
|
+
}
|
|
4247
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
4248
|
+
if (entry.refs > 0) return;
|
|
4249
|
+
if (sharedZapoStores.get(storePath) === entry) {
|
|
4250
|
+
sharedZapoStores.delete(storePath);
|
|
4251
|
+
sharedZapoStorePromises.delete(storePath);
|
|
4252
|
+
}
|
|
4253
|
+
try {
|
|
4254
|
+
await entry.store.destroy();
|
|
4255
|
+
} catch (error) {
|
|
4256
|
+
logger.warn("Could not close the shared Zapo store cleanly.", {
|
|
4257
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4258
|
+
storePath
|
|
4259
|
+
});
|
|
4260
|
+
} finally {
|
|
4261
|
+
try {
|
|
4262
|
+
entry.snapshotDb.close();
|
|
4263
|
+
} catch (error) {
|
|
4264
|
+
logger.debug("Could not close the WhaNext message snapshot database cleanly.", {
|
|
4265
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4266
|
+
storePath
|
|
4267
|
+
});
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
function clearZapoSessionData(storePath, sessionId) {
|
|
4272
|
+
let db;
|
|
4273
|
+
try {
|
|
4274
|
+
const Database = require2("better-sqlite3");
|
|
4275
|
+
db = new Database(storePath);
|
|
4276
|
+
const tables = db.prepare(
|
|
4277
|
+
"SELECT name FROM main.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
4278
|
+
).all();
|
|
4279
|
+
db.exec("BEGIN IMMEDIATE");
|
|
4280
|
+
try {
|
|
4281
|
+
for (const row of tables) {
|
|
4282
|
+
const table = stringField2(row, "name");
|
|
4283
|
+
if (!table || table === "wa_migrations" || table === "whanext_core_migrations") {
|
|
4284
|
+
continue;
|
|
4285
|
+
}
|
|
4286
|
+
const quoted = sqliteIdentifier(table);
|
|
4287
|
+
const columns = db.prepare(`PRAGMA main.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4288
|
+
if (!columns.includes("session_id")) continue;
|
|
4289
|
+
db.prepare(`DELETE FROM main.${quoted} WHERE session_id = ?`).run(sessionId);
|
|
4290
|
+
}
|
|
4291
|
+
db.exec("COMMIT");
|
|
4292
|
+
} catch (error) {
|
|
4293
|
+
db.exec("ROLLBACK");
|
|
4294
|
+
throw error;
|
|
4295
|
+
}
|
|
4296
|
+
} finally {
|
|
4297
|
+
db?.close();
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
async function migrateLegacyZapoStore(storePath, legacyStorePath, sessionId, logger) {
|
|
4301
|
+
let db;
|
|
4302
|
+
try {
|
|
4303
|
+
const Database = require2("better-sqlite3");
|
|
4304
|
+
db = new Database(storePath);
|
|
4305
|
+
db.exec(`ATTACH DATABASE ${sqliteString(legacyStorePath)} AS legacy`);
|
|
4306
|
+
db.exec(`
|
|
4307
|
+
CREATE TABLE IF NOT EXISTS whanext_core_migrations (
|
|
4308
|
+
migration_key TEXT PRIMARY KEY,
|
|
4309
|
+
migrated_at INTEGER NOT NULL
|
|
4310
|
+
)
|
|
4311
|
+
`);
|
|
4312
|
+
const migrationKey = `shared-store:${resolve2(legacyStorePath)}:${sessionId}`;
|
|
4313
|
+
const migrated = db.prepare(
|
|
4314
|
+
"SELECT migration_key FROM whanext_core_migrations WHERE migration_key = ?"
|
|
4315
|
+
).get(migrationKey);
|
|
4316
|
+
if (migrated) return;
|
|
4317
|
+
const mainTables = new Set(
|
|
4318
|
+
db.prepare("SELECT name FROM main.sqlite_master WHERE type = 'table'").all().map((row) => stringField2(row, "name")).filter((name) => name !== void 0)
|
|
4319
|
+
);
|
|
4320
|
+
const legacyTables = db.prepare(
|
|
4321
|
+
"SELECT name FROM legacy.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
4322
|
+
).all();
|
|
4323
|
+
db.exec("BEGIN IMMEDIATE");
|
|
4324
|
+
try {
|
|
4325
|
+
for (const row of legacyTables) {
|
|
4326
|
+
const table = stringField2(row, "name");
|
|
4327
|
+
if (!table || !mainTables.has(table) || table === "wa_migrations" || table === "whanext_core_migrations") {
|
|
4328
|
+
continue;
|
|
4329
|
+
}
|
|
4330
|
+
const quoted = sqliteIdentifier(table);
|
|
4331
|
+
const mainColumns = db.prepare(`PRAGMA main.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4332
|
+
const legacyColumns = new Set(
|
|
4333
|
+
db.prepare(`PRAGMA legacy.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0)
|
|
4334
|
+
);
|
|
4335
|
+
const columns = mainColumns.filter((column) => legacyColumns.has(column));
|
|
4336
|
+
if (!columns.includes("session_id") || columns.length === 0) continue;
|
|
4337
|
+
const selected = columns.map(sqliteIdentifier).join(", ");
|
|
4338
|
+
db.prepare(
|
|
4339
|
+
`INSERT OR REPLACE INTO main.${quoted} (${selected}) SELECT ${selected} FROM legacy.${quoted} WHERE session_id = ?`
|
|
4340
|
+
).run(sessionId);
|
|
4341
|
+
}
|
|
4342
|
+
db.prepare(
|
|
4343
|
+
"INSERT OR REPLACE INTO whanext_core_migrations (migration_key, migrated_at) VALUES (?, ?)"
|
|
4344
|
+
).run(migrationKey, Date.now());
|
|
4345
|
+
db.exec("COMMIT");
|
|
4346
|
+
} catch (error) {
|
|
4347
|
+
db.exec("ROLLBACK");
|
|
4348
|
+
throw error;
|
|
4349
|
+
}
|
|
4350
|
+
} catch (error) {
|
|
4351
|
+
logger.warn("Could not migrate the legacy per-account Zapo store; keeping the shared store active.", {
|
|
4352
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4353
|
+
sessionId,
|
|
4354
|
+
legacyStorePath,
|
|
4355
|
+
storePath
|
|
4356
|
+
});
|
|
4357
|
+
} finally {
|
|
4358
|
+
if (db) {
|
|
4359
|
+
try {
|
|
4360
|
+
db.exec("DETACH DATABASE legacy");
|
|
4361
|
+
} catch {
|
|
4362
|
+
}
|
|
4363
|
+
db.close();
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
async function fileExists(path2) {
|
|
4368
|
+
try {
|
|
4369
|
+
await stat(path2);
|
|
4370
|
+
return true;
|
|
4371
|
+
} catch {
|
|
4372
|
+
return false;
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
function sqliteIdentifier(value) {
|
|
4376
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
4377
|
+
}
|
|
4378
|
+
function sqliteString(value) {
|
|
4379
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
4380
|
+
}
|
|
4381
|
+
function stringField2(record, field) {
|
|
4382
|
+
const value = record[field];
|
|
4383
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4384
|
+
}
|
|
4385
|
+
function stringValue(value) {
|
|
4386
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4387
|
+
}
|
|
4388
|
+
function numberValue(value) {
|
|
4389
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4390
|
+
}
|
|
4391
|
+
function booleanValue(value) {
|
|
4392
|
+
if (value === true || value === 1) return true;
|
|
4393
|
+
if (value === false || value === 0) return false;
|
|
4394
|
+
return void 0;
|
|
4395
|
+
}
|
|
4396
|
+
function bytesField(value) {
|
|
4397
|
+
if (value instanceof Uint8Array) return value;
|
|
4398
|
+
return void 0;
|
|
4399
|
+
}
|
|
3834
4400
|
var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
3835
4401
|
level;
|
|
3836
4402
|
#logger;
|
|
@@ -3903,7 +4469,7 @@ async function create(options = {}) {
|
|
|
3903
4469
|
// src/app/multi-app.ts
|
|
3904
4470
|
import {
|
|
3905
4471
|
join as join2,
|
|
3906
|
-
resolve as
|
|
4472
|
+
resolve as resolve3
|
|
3907
4473
|
} from "path";
|
|
3908
4474
|
var MultiCommandRouter = class {
|
|
3909
4475
|
#apps;
|
|
@@ -4048,7 +4614,7 @@ async function createMulti(options) {
|
|
|
4048
4614
|
}
|
|
4049
4615
|
normalizedIds.add(normalizedId);
|
|
4050
4616
|
if (account.provider === void 0) {
|
|
4051
|
-
const authPath =
|
|
4617
|
+
const authPath = resolve3(account.auth ?? join2(authRoot, account.id));
|
|
4052
4618
|
if (authPaths.has(authPath)) {
|
|
4053
4619
|
throw new WhaNextError(
|
|
4054
4620
|
"ARGUMENT_INVALID",
|