@whanext/core 0.19.5 → 0.19.8
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 +34 -0
- package/MIGRATING_TO_ZAPO.md +2 -2
- package/README.md +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +666 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -90,11 +90,40 @@ function toWhaNextError(error, context) {
|
|
|
90
90
|
return error;
|
|
91
91
|
}
|
|
92
92
|
const message = error instanceof Error ? error.message : "An unknown error occurred.";
|
|
93
|
+
if (isReachoutTimelockError(error)) {
|
|
94
|
+
return new WhaNextError(
|
|
95
|
+
"MESSAGE_REACHOUT_LOCKED",
|
|
96
|
+
"WhatsApp rejected the outgoing message (ack 463: reach-out time-lock).",
|
|
97
|
+
{
|
|
98
|
+
cause: error,
|
|
99
|
+
context: {
|
|
100
|
+
...context ?? {},
|
|
101
|
+
ackCode: 463
|
|
102
|
+
},
|
|
103
|
+
recoverable: false
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
93
107
|
return new WhaNextError("UNKNOWN_ERROR", message, {
|
|
94
108
|
cause: error,
|
|
95
109
|
...context ? { context } : {}
|
|
96
110
|
});
|
|
97
111
|
}
|
|
112
|
+
function isReachoutTimelockError(error) {
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
let current = error;
|
|
115
|
+
for (let depth = 0; depth < 8 && current !== void 0 && current !== null; depth += 1) {
|
|
116
|
+
if (seen.has(current)) return false;
|
|
117
|
+
seen.add(current);
|
|
118
|
+
const message = current instanceof Error ? current.message : typeof current === "string" ? current : void 0;
|
|
119
|
+
if (message && /negative publish ack:.*\berror=463\b/i.test(message)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (typeof current !== "object") return false;
|
|
123
|
+
current = Reflect.get(current, "cause");
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
98
127
|
|
|
99
128
|
// src/models/identity.ts
|
|
100
129
|
function normalizeIdentity(identity) {
|
|
@@ -370,7 +399,7 @@ var CommandConcurrencyController = class {
|
|
|
370
399
|
recoverable: true
|
|
371
400
|
});
|
|
372
401
|
} else if (strategy === "queue" && state.active >= max) {
|
|
373
|
-
await new Promise((
|
|
402
|
+
await new Promise((resolve4) => state.queue.push(resolve4));
|
|
374
403
|
}
|
|
375
404
|
const controller = new AbortController();
|
|
376
405
|
state.controllers.add(controller);
|
|
@@ -1617,8 +1646,8 @@ var SqliteMuteStore = class {
|
|
|
1617
1646
|
}
|
|
1618
1647
|
let database;
|
|
1619
1648
|
try {
|
|
1620
|
-
const
|
|
1621
|
-
const sqlite =
|
|
1649
|
+
const require3 = createRequire(import.meta.url);
|
|
1650
|
+
const sqlite = require3("node:sqlite");
|
|
1622
1651
|
database = new sqlite.DatabaseSync(databasePath);
|
|
1623
1652
|
database.exec("PRAGMA journal_mode = WAL");
|
|
1624
1653
|
database.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -2203,12 +2232,12 @@ var WhaNextApp = class {
|
|
|
2203
2232
|
this.logger.info("Login started");
|
|
2204
2233
|
let unsubscribe = () => void 0;
|
|
2205
2234
|
let timer;
|
|
2206
|
-
const connected = new Promise((
|
|
2235
|
+
const connected = new Promise((resolve4, reject) => {
|
|
2207
2236
|
unsubscribe = this.#provider.on("connection", (update) => {
|
|
2208
|
-
if (update.state === "connected")
|
|
2237
|
+
if (update.state === "connected") resolve4();
|
|
2209
2238
|
if (update.state === "closed") {
|
|
2210
2239
|
reject(
|
|
2211
|
-
new WhaNextError(
|
|
2240
|
+
update.error instanceof WhaNextError ? update.error : new WhaNextError(
|
|
2212
2241
|
"CONNECTION_FAILED",
|
|
2213
2242
|
"WhatsApp closed the connection before login completed.",
|
|
2214
2243
|
{
|
|
@@ -2375,8 +2404,9 @@ var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
|
2375
2404
|
})(Browser || {});
|
|
2376
2405
|
|
|
2377
2406
|
// src/provider/zapo/zapo-provider.ts
|
|
2378
|
-
import { mkdir } from "fs/promises";
|
|
2379
|
-
import {
|
|
2407
|
+
import { mkdir, stat } from "fs/promises";
|
|
2408
|
+
import { createRequire as createRequire2 } from "module";
|
|
2409
|
+
import { basename, dirname as dirname2, join, resolve as resolve2 } from "path";
|
|
2380
2410
|
import { createMediaProcessor } from "@zapo-js/media-utils";
|
|
2381
2411
|
import { createSqliteStore } from "@zapo-js/store-sqlite";
|
|
2382
2412
|
import {
|
|
@@ -2706,6 +2736,25 @@ function toNumber(value) {
|
|
|
2706
2736
|
}
|
|
2707
2737
|
|
|
2708
2738
|
// src/provider/zapo/zapo-provider.ts
|
|
2739
|
+
var require2 = createRequire2(import.meta.url);
|
|
2740
|
+
var sharedZapoStores = /* @__PURE__ */ new Map();
|
|
2741
|
+
var sharedZapoStorePromises = /* @__PURE__ */ new Map();
|
|
2742
|
+
var fatalDisconnectReasons = /* @__PURE__ */ new Set([
|
|
2743
|
+
"stream_error_replaced",
|
|
2744
|
+
"stream_error_device_removed",
|
|
2745
|
+
"stream_error_force_logout",
|
|
2746
|
+
"failure_not_authorized",
|
|
2747
|
+
"failure_banned",
|
|
2748
|
+
"failure_locked",
|
|
2749
|
+
"failure_client_too_old",
|
|
2750
|
+
"failure_bad_user_agent",
|
|
2751
|
+
"primary_identity_key_change"
|
|
2752
|
+
]);
|
|
2753
|
+
var fatalDisconnectCodes = /* @__PURE__ */ new Set([401, 403, 405, 406, 409, 516]);
|
|
2754
|
+
var sharedMediaProcessor = createMediaProcessor();
|
|
2755
|
+
var messageSnapshotRetentionSeconds = 7 * 24 * 60 * 60;
|
|
2756
|
+
var messageSnapshotMaxPerSession = 2e4;
|
|
2757
|
+
var messageSnapshotPruneInterval = 256;
|
|
2709
2758
|
var ZapoProvider = class {
|
|
2710
2759
|
#options;
|
|
2711
2760
|
#events = new TypedEventEmitter();
|
|
@@ -2716,14 +2765,19 @@ var ZapoProvider = class {
|
|
|
2716
2765
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
2717
2766
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
2718
2767
|
#messageCacheSize;
|
|
2768
|
+
#protocolMutationQueue = Promise.resolve();
|
|
2719
2769
|
#client;
|
|
2770
|
+
#storeEntry;
|
|
2771
|
+
#storePath;
|
|
2720
2772
|
#intentionalClose = false;
|
|
2773
|
+
#closedNotified = false;
|
|
2721
2774
|
#reconnectAttempt = 0;
|
|
2722
2775
|
#reconnectTimer;
|
|
2723
2776
|
#connectPromise;
|
|
2724
2777
|
#pairingRequired = false;
|
|
2725
2778
|
#connected = false;
|
|
2726
2779
|
#connectedAtSeconds = 0;
|
|
2780
|
+
#messageSnapshotsSincePrune = 0;
|
|
2727
2781
|
#pairingReady = Promise.resolve();
|
|
2728
2782
|
#resolvePairingReady;
|
|
2729
2783
|
constructor(options) {
|
|
@@ -2736,6 +2790,7 @@ var ZapoProvider = class {
|
|
|
2736
2790
|
}
|
|
2737
2791
|
async connect() {
|
|
2738
2792
|
this.#intentionalClose = false;
|
|
2793
|
+
this.#closedNotified = false;
|
|
2739
2794
|
const client = await this.#ensureClient();
|
|
2740
2795
|
if (this.#connected || this.#connectPromise) {
|
|
2741
2796
|
return;
|
|
@@ -2755,12 +2810,16 @@ var ZapoProvider = class {
|
|
|
2755
2810
|
}
|
|
2756
2811
|
const client = this.#client;
|
|
2757
2812
|
this.#connectPromise = void 0;
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2813
|
+
try {
|
|
2814
|
+
if (client) {
|
|
2815
|
+
await client.disconnect();
|
|
2816
|
+
} else {
|
|
2817
|
+
await this.#emitClosedOnce();
|
|
2818
|
+
}
|
|
2819
|
+
} finally {
|
|
2820
|
+
this.#connected = false;
|
|
2821
|
+
await this.#releaseStore();
|
|
2762
2822
|
}
|
|
2763
|
-
this.#connected = false;
|
|
2764
2823
|
}
|
|
2765
2824
|
getCurrentUserIds() {
|
|
2766
2825
|
const credentials = this.#client?.getCredentials();
|
|
@@ -2819,7 +2878,7 @@ var ZapoProvider = class {
|
|
|
2819
2878
|
return this.#sent(result, chatId);
|
|
2820
2879
|
}
|
|
2821
2880
|
async repostMessage(source, chatId, options = {}) {
|
|
2822
|
-
const original = this.#findStoredMessage(this.#toZapoKey(source));
|
|
2881
|
+
const original = await this.#findStoredMessage(this.#toZapoKey(source));
|
|
2823
2882
|
if (!original?.message) {
|
|
2824
2883
|
throw new WhaNextError(
|
|
2825
2884
|
"MESSAGE_NOT_FOUND",
|
|
@@ -2849,7 +2908,7 @@ var ZapoProvider = class {
|
|
|
2849
2908
|
return this.#sent(result, key.chatId);
|
|
2850
2909
|
}
|
|
2851
2910
|
async downloadMedia(key) {
|
|
2852
|
-
const message = this.#messageKeyStore.get(key) ?? this.#findStoredMessage(this.#toZapoKey(key));
|
|
2911
|
+
const message = this.#messageKeyStore.get(key) ?? await this.#findStoredMessage(this.#toZapoKey(key));
|
|
2853
2912
|
if (!message?.message) {
|
|
2854
2913
|
throw new WhaNextError(
|
|
2855
2914
|
"MEDIA_NOT_AVAILABLE",
|
|
@@ -2993,44 +3052,36 @@ var ZapoProvider = class {
|
|
|
2993
3052
|
}
|
|
2994
3053
|
async #ensureClient() {
|
|
2995
3054
|
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
|
-
});
|
|
3055
|
+
const authPath = resolve2(this.#options.auth);
|
|
3056
|
+
const sessionId = this.#sessionId();
|
|
3057
|
+
const authPathExisted = await fileExists(authPath);
|
|
3058
|
+
const legacyStorePath = join(authPath, "state.sqlite");
|
|
3059
|
+
const canShareByDirectory = sessionId !== "default" && basename(authPath) === sessionId;
|
|
3060
|
+
const storePath = canShareByDirectory ? join(dirname2(authPath), "state.sqlite") : legacyStorePath;
|
|
3061
|
+
const resetSharedSession = canShareByDirectory && !authPathExisted && await fileExists(storePath);
|
|
3062
|
+
await mkdir(authPath, { recursive: true });
|
|
3063
|
+
const storeEntry = await acquireSharedZapoStore(
|
|
3064
|
+
storePath,
|
|
3065
|
+
sessionId,
|
|
3066
|
+
legacyStorePath,
|
|
3067
|
+
resetSharedSession,
|
|
3068
|
+
this.#logger
|
|
3069
|
+
);
|
|
3021
3070
|
const client = new WaClient({
|
|
3022
|
-
store,
|
|
3023
|
-
sessionId
|
|
3071
|
+
store: storeEntry.store,
|
|
3072
|
+
sessionId,
|
|
3024
3073
|
markOnlineOnConnect: false,
|
|
3025
3074
|
deviceBrowser: this.#deviceBrowser(),
|
|
3026
3075
|
deviceOsDisplayName: this.#deviceOsDisplayName(),
|
|
3027
|
-
history: { enabled:
|
|
3076
|
+
history: { enabled: true, requireFullSync: true },
|
|
3028
3077
|
addons: {
|
|
3029
3078
|
autoDecrypt: true,
|
|
3030
3079
|
persistAllSecrets: true
|
|
3031
3080
|
},
|
|
3032
|
-
media: { processor:
|
|
3081
|
+
media: { processor: sharedMediaProcessor }
|
|
3033
3082
|
}, new WhaNextZapoLogger(this.#logger));
|
|
3083
|
+
this.#storeEntry = storeEntry;
|
|
3084
|
+
this.#storePath = storePath;
|
|
3034
3085
|
this.#client = client;
|
|
3035
3086
|
this.#bind(client);
|
|
3036
3087
|
return client;
|
|
@@ -3045,10 +3096,19 @@ var ZapoProvider = class {
|
|
|
3045
3096
|
client.on("auth_paired", () => {
|
|
3046
3097
|
this.#pairingRequired = false;
|
|
3047
3098
|
});
|
|
3099
|
+
client.on("auth_passkey_required", ({ hasSigner }) => {
|
|
3100
|
+
if (hasSigner) return;
|
|
3101
|
+
const error = new WhaNextError(
|
|
3102
|
+
"AUTH_PASSKEY_REQUIRED",
|
|
3103
|
+
"WhatsApp requires a passkey assertion to link this account, but no passkey signer is configured.",
|
|
3104
|
+
{ recoverable: false }
|
|
3105
|
+
);
|
|
3106
|
+
void this.#stopTerminalConnection(client, error);
|
|
3107
|
+
});
|
|
3048
3108
|
client.on("message", (event) => {
|
|
3049
3109
|
const protocol = this.#protocolMessage(event);
|
|
3050
3110
|
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3051
|
-
this.#
|
|
3111
|
+
this.#enqueueProtocolEvent({
|
|
3052
3112
|
...event,
|
|
3053
3113
|
protocolMessage: protocol
|
|
3054
3114
|
});
|
|
@@ -3069,7 +3129,7 @@ var ZapoProvider = class {
|
|
|
3069
3129
|
});
|
|
3070
3130
|
});
|
|
3071
3131
|
client.on("message_protocol", (event) => {
|
|
3072
|
-
this.#
|
|
3132
|
+
this.#enqueueProtocolEvent(event);
|
|
3073
3133
|
});
|
|
3074
3134
|
client.on("message_addon", (event) => {
|
|
3075
3135
|
this.#handleAddonEvent(event);
|
|
@@ -3139,7 +3199,7 @@ var ZapoProvider = class {
|
|
|
3139
3199
|
...protocolKey?.participant !== void 0 ? { participant: protocolKey.participant } : event.key.participant !== void 0 ? { participant: event.key.participant } : {},
|
|
3140
3200
|
...protocolKey?.participantAlt !== void 0 ? { participantAlt: protocolKey.participantAlt } : event.key.participantAlt !== void 0 ? { participantAlt: event.key.participantAlt } : {}
|
|
3141
3201
|
};
|
|
3142
|
-
this.#handleProtocolEvent({
|
|
3202
|
+
void this.#handleProtocolEvent({
|
|
3143
3203
|
key: event.key,
|
|
3144
3204
|
...event.timestampSeconds !== void 0 ? { timestampSeconds: event.timestampSeconds } : {},
|
|
3145
3205
|
protocolMessage: {
|
|
@@ -3212,7 +3272,15 @@ var ZapoProvider = class {
|
|
|
3212
3272
|
if (timestamp === void 0) return false;
|
|
3213
3273
|
return timestamp < this.#connectedAtSeconds - 3;
|
|
3214
3274
|
}
|
|
3215
|
-
#
|
|
3275
|
+
#enqueueProtocolEvent(event) {
|
|
3276
|
+
this.#protocolMutationQueue = this.#protocolMutationQueue.then(() => this.#handleProtocolEvent(event)).catch((error) => {
|
|
3277
|
+
this.#logger.debug("Could not process a Zapo protocol mutation.", {
|
|
3278
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3279
|
+
messageId: event.key.id ?? void 0
|
|
3280
|
+
});
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
async #handleProtocolEvent(event) {
|
|
3216
3284
|
if (this.#shouldIgnoreProtocolEvent(event)) {
|
|
3217
3285
|
this.#logger.debug("Ignored protocol mutation from before the current live connection.", {
|
|
3218
3286
|
messageId: event.key.id ?? void 0,
|
|
@@ -3240,7 +3308,7 @@ var ZapoProvider = class {
|
|
|
3240
3308
|
...participant !== void 0 ? { participant } : {},
|
|
3241
3309
|
...participantAlt !== void 0 ? { participantAlt } : {}
|
|
3242
3310
|
};
|
|
3243
|
-
const stored = this.#findStoredMessage(target);
|
|
3311
|
+
const stored = await this.#findStoredMessage(target);
|
|
3244
3312
|
if (!target.remoteJid && stored?.key.remoteJid) target.remoteJid = stored.key.remoteJid;
|
|
3245
3313
|
if (!target.remoteJid) return;
|
|
3246
3314
|
const type = protocol.type;
|
|
@@ -3335,20 +3403,26 @@ var ZapoProvider = class {
|
|
|
3335
3403
|
this.#connected = true;
|
|
3336
3404
|
this.#connectedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
3337
3405
|
this.#reconnectAttempt = 0;
|
|
3406
|
+
this.#closedNotified = false;
|
|
3338
3407
|
await this.#events.emit("connection", { state: "connected" });
|
|
3339
3408
|
return;
|
|
3340
3409
|
}
|
|
3341
3410
|
this.#connectPromise = void 0;
|
|
3342
3411
|
this.#connected = false;
|
|
3343
|
-
const
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3412
|
+
const details = disconnectDetails(event.reason, event.code);
|
|
3413
|
+
const error = connectionError(event.reason, details);
|
|
3414
|
+
if (this.#intentionalClose || event.isLogout === true || shouldStopAutomaticReconnect(details)) {
|
|
3415
|
+
if (!this.#intentionalClose && shouldStopAutomaticReconnect(details)) {
|
|
3416
|
+
this.#logger.warn("WhatsApp closed the session with a non-retryable reason.", {
|
|
3417
|
+
...details.reason ? { reason: details.reason } : {},
|
|
3418
|
+
...details.code !== void 0 ? { code: details.code } : {}
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
await this.#emitClosedOnce(error);
|
|
3422
|
+
await this.#releaseStore();
|
|
3349
3423
|
return;
|
|
3350
3424
|
}
|
|
3351
|
-
await this.#scheduleReconnect(error);
|
|
3425
|
+
await this.#scheduleReconnect(error, reconnectDelayOverride(details));
|
|
3352
3426
|
}
|
|
3353
3427
|
#startConnect(client) {
|
|
3354
3428
|
const promise = client.connect();
|
|
@@ -3357,20 +3431,65 @@ var ZapoProvider = class {
|
|
|
3357
3431
|
if (this.#connectPromise !== promise) return;
|
|
3358
3432
|
this.#connectPromise = void 0;
|
|
3359
3433
|
if (this.#intentionalClose) return;
|
|
3360
|
-
const
|
|
3434
|
+
const details = disconnectDetails(error);
|
|
3435
|
+
const normalized = connectionError(error, details) ?? (error instanceof Error ? error : new Error(String(error)));
|
|
3361
3436
|
this.#logger.warn("WhatsApp connection attempt failed.", { error: normalized });
|
|
3362
|
-
|
|
3437
|
+
if (shouldStopAutomaticReconnect(details)) {
|
|
3438
|
+
await this.#stopTerminalConnection(client, normalized);
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
await this.#scheduleReconnect(normalized, reconnectDelayOverride(details));
|
|
3363
3442
|
});
|
|
3364
3443
|
}
|
|
3365
|
-
async #
|
|
3444
|
+
async #stopTerminalConnection(client, error) {
|
|
3445
|
+
if (client !== this.#client) return;
|
|
3446
|
+
this.#intentionalClose = true;
|
|
3447
|
+
if (this.#reconnectTimer) {
|
|
3448
|
+
clearTimeout(this.#reconnectTimer);
|
|
3449
|
+
this.#reconnectTimer = void 0;
|
|
3450
|
+
}
|
|
3451
|
+
this.#connectPromise = void 0;
|
|
3452
|
+
this.#connected = false;
|
|
3453
|
+
await this.#emitClosedOnce(error);
|
|
3454
|
+
this.#client = void 0;
|
|
3455
|
+
try {
|
|
3456
|
+
await client.disconnect();
|
|
3457
|
+
} catch (disconnectError) {
|
|
3458
|
+
this.#logger.debug("Could not close a terminal Zapo connection cleanly.", {
|
|
3459
|
+
error: disconnectError instanceof Error ? disconnectError : new Error(String(disconnectError))
|
|
3460
|
+
});
|
|
3461
|
+
} finally {
|
|
3462
|
+
await this.#releaseStore();
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
async #emitClosedOnce(error) {
|
|
3466
|
+
if (this.#closedNotified) return;
|
|
3467
|
+
this.#closedNotified = true;
|
|
3468
|
+
await this.#events.emit("connection", {
|
|
3469
|
+
state: "closed",
|
|
3470
|
+
...error ? { error } : {}
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
async #releaseStore() {
|
|
3474
|
+
const entry = this.#storeEntry;
|
|
3475
|
+
const storePath = this.#storePath;
|
|
3476
|
+
const sessionId = this.#sessionId();
|
|
3477
|
+
this.#storeEntry = void 0;
|
|
3478
|
+
this.#storePath = void 0;
|
|
3479
|
+
this.#client = void 0;
|
|
3480
|
+
if (!entry || !storePath) return;
|
|
3481
|
+
await releaseSharedZapoStore(storePath, sessionId, entry, this.#logger);
|
|
3482
|
+
}
|
|
3483
|
+
#sessionId() {
|
|
3484
|
+
return this.#options.sessionId ?? "default";
|
|
3485
|
+
}
|
|
3486
|
+
async #scheduleReconnect(error, delayOverrideMs) {
|
|
3366
3487
|
if (this.#reconnectTimer) return;
|
|
3367
3488
|
const options = this.#options.reconnect;
|
|
3368
3489
|
const maxAttempts = options?.maxAttempts ?? 10;
|
|
3369
3490
|
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
3370
|
-
await this.#
|
|
3371
|
-
|
|
3372
|
-
...error ? { error } : {}
|
|
3373
|
-
});
|
|
3491
|
+
await this.#emitClosedOnce(error);
|
|
3492
|
+
await this.#releaseStore();
|
|
3374
3493
|
return;
|
|
3375
3494
|
}
|
|
3376
3495
|
this.#reconnectAttempt += 1;
|
|
@@ -3381,11 +3500,13 @@ var ZapoProvider = class {
|
|
|
3381
3500
|
});
|
|
3382
3501
|
const initial = options?.initialDelayMs ?? 1e3;
|
|
3383
3502
|
const maximum = options?.maxDelayMs ?? 3e4;
|
|
3384
|
-
const
|
|
3503
|
+
const exponential = Math.min(maximum, initial * 2 ** (this.#reconnectAttempt - 1));
|
|
3504
|
+
const delay = delayOverrideMs ?? exponential;
|
|
3505
|
+
const jitter = delay > 0 ? Math.floor(Math.random() * 250) : 0;
|
|
3385
3506
|
this.#reconnectTimer = setTimeout(() => {
|
|
3386
3507
|
this.#reconnectTimer = void 0;
|
|
3387
3508
|
void this.connect();
|
|
3388
|
-
}, delay +
|
|
3509
|
+
}, delay + jitter);
|
|
3389
3510
|
}
|
|
3390
3511
|
async #sendButtons(chatId, content, replyTo) {
|
|
3391
3512
|
this.#validateButtons(content);
|
|
@@ -3718,7 +3839,7 @@ var ZapoProvider = class {
|
|
|
3718
3839
|
#groupParticipantIds(event) {
|
|
3719
3840
|
return uniqueIdentities((event.participants ?? []).map((participant) => participant.jid ?? participant.lidJid ?? participant.phoneJid));
|
|
3720
3841
|
}
|
|
3721
|
-
#findStoredMessage(key) {
|
|
3842
|
+
async #findStoredMessage(key) {
|
|
3722
3843
|
const direct = this.#messageStore.get(this.#messageStoreKey(key));
|
|
3723
3844
|
if (direct) return direct;
|
|
3724
3845
|
if (!key.id) return void 0;
|
|
@@ -3734,18 +3855,103 @@ var ZapoProvider = class {
|
|
|
3734
3855
|
this.#remember(storedQuoted);
|
|
3735
3856
|
return storedQuoted;
|
|
3736
3857
|
}
|
|
3737
|
-
return
|
|
3858
|
+
return this.#readArchivedMessage(key);
|
|
3738
3859
|
}
|
|
3739
3860
|
#remember(message) {
|
|
3740
3861
|
if (!message.key.id || !message.message) return;
|
|
3741
3862
|
const key = this.#messageStoreKey(message.key);
|
|
3742
3863
|
this.#messageStore.delete(key);
|
|
3743
3864
|
this.#messageStore.set(key, message);
|
|
3865
|
+
void this.#archiveMessage(message);
|
|
3744
3866
|
while (this.#messageStore.size > this.#messageCacheSize) {
|
|
3745
3867
|
const oldest = this.#messageStore.keys().next().value;
|
|
3746
3868
|
if (oldest) this.#messageStore.delete(oldest);
|
|
3747
3869
|
}
|
|
3748
3870
|
}
|
|
3871
|
+
async #archiveMessage(message) {
|
|
3872
|
+
const id = message.key.id;
|
|
3873
|
+
const chatId = message.key.remoteJid;
|
|
3874
|
+
const content = message.message;
|
|
3875
|
+
const entry = this.#storeEntry;
|
|
3876
|
+
if (!id || !chatId || !content || !entry) return;
|
|
3877
|
+
try {
|
|
3878
|
+
const participantId = message.key.participant ?? message.key.participantAlt ?? null;
|
|
3879
|
+
const timestampSeconds = toSeconds(message.timestampSeconds) ?? null;
|
|
3880
|
+
const messageBytes = Buffer.from(proto.Message.encode(content).finish());
|
|
3881
|
+
const storedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
3882
|
+
const sessionId = this.#sessionId();
|
|
3883
|
+
entry.snapshotUpsert.run(
|
|
3884
|
+
sessionId,
|
|
3885
|
+
id,
|
|
3886
|
+
chatId,
|
|
3887
|
+
participantId,
|
|
3888
|
+
message.key.fromMe === true ? 1 : 0,
|
|
3889
|
+
timestampSeconds,
|
|
3890
|
+
messageBytes,
|
|
3891
|
+
storedAtSeconds
|
|
3892
|
+
);
|
|
3893
|
+
this.#messageSnapshotsSincePrune += 1;
|
|
3894
|
+
if (this.#messageSnapshotsSincePrune >= messageSnapshotPruneInterval) {
|
|
3895
|
+
this.#messageSnapshotsSincePrune = 0;
|
|
3896
|
+
entry.snapshotPruneAge.run(
|
|
3897
|
+
sessionId,
|
|
3898
|
+
storedAtSeconds - messageSnapshotRetentionSeconds
|
|
3899
|
+
);
|
|
3900
|
+
entry.snapshotPruneOverflow.run(
|
|
3901
|
+
sessionId,
|
|
3902
|
+
sessionId,
|
|
3903
|
+
messageSnapshotMaxPerSession
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
this.#logger.debug("Could not archive a message snapshot for mutation recovery.", {
|
|
3908
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3909
|
+
messageId: id,
|
|
3910
|
+
chatId
|
|
3911
|
+
});
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
async #readArchivedMessage(key) {
|
|
3915
|
+
const id = key.id;
|
|
3916
|
+
const entry = this.#storeEntry;
|
|
3917
|
+
if (!id || !entry) return void 0;
|
|
3918
|
+
try {
|
|
3919
|
+
const archived = entry.snapshotGet.get(
|
|
3920
|
+
this.#sessionId(),
|
|
3921
|
+
id
|
|
3922
|
+
);
|
|
3923
|
+
const messageBytes = bytesField(archived?.message_bytes);
|
|
3924
|
+
if (!archived || !messageBytes) return void 0;
|
|
3925
|
+
const archivedChatId = stringValue(archived.chat_id);
|
|
3926
|
+
const remoteJid = key.remoteJid ?? archivedChatId;
|
|
3927
|
+
if (!remoteJid) return void 0;
|
|
3928
|
+
const participant = key.participant ?? key.participantAlt ?? stringValue(archived.participant_id);
|
|
3929
|
+
const archivedFromMe = booleanValue(archived.from_me);
|
|
3930
|
+
const timestampSeconds = numberValue(archived.timestamp_seconds);
|
|
3931
|
+
const fromMe = key.fromMe ?? archivedFromMe;
|
|
3932
|
+
const restored = {
|
|
3933
|
+
key: {
|
|
3934
|
+
...key,
|
|
3935
|
+
id,
|
|
3936
|
+
remoteJid,
|
|
3937
|
+
...fromMe !== void 0 ? { fromMe } : {},
|
|
3938
|
+
...participant ? { participant } : {}
|
|
3939
|
+
},
|
|
3940
|
+
message: proto.Message.decode(messageBytes),
|
|
3941
|
+
...timestampSeconds !== void 0 ? { timestampSeconds } : {}
|
|
3942
|
+
};
|
|
3943
|
+
const cacheKey = this.#messageStoreKey(restored.key);
|
|
3944
|
+
this.#messageStore.delete(cacheKey);
|
|
3945
|
+
this.#messageStore.set(cacheKey, restored);
|
|
3946
|
+
return restored;
|
|
3947
|
+
} catch (error) {
|
|
3948
|
+
this.#logger.debug("Could not restore a message snapshot for mutation recovery.", {
|
|
3949
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3950
|
+
messageId: id
|
|
3951
|
+
});
|
|
3952
|
+
return void 0;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3749
3955
|
#messageStoreKey(key) {
|
|
3750
3956
|
return `${key.remoteJid ?? ""}:${key.id ?? ""}:${key.participant ?? key.participantAlt ?? ""}`;
|
|
3751
3957
|
}
|
|
@@ -3788,8 +3994,8 @@ var ZapoProvider = class {
|
|
|
3788
3994
|
}
|
|
3789
3995
|
#preparePairingGate() {
|
|
3790
3996
|
this.#pairingRequired = false;
|
|
3791
|
-
this.#pairingReady = new Promise((
|
|
3792
|
-
this.#resolvePairingReady =
|
|
3997
|
+
this.#pairingReady = new Promise((resolve4) => {
|
|
3998
|
+
this.#resolvePairingReady = resolve4;
|
|
3793
3999
|
});
|
|
3794
4000
|
}
|
|
3795
4001
|
#deviceBrowser() {
|
|
@@ -3831,6 +4037,395 @@ var ZapoProvider = class {
|
|
|
3831
4037
|
}
|
|
3832
4038
|
}
|
|
3833
4039
|
};
|
|
4040
|
+
function disconnectDetails(value, explicitCode) {
|
|
4041
|
+
let code = typeof explicitCode === "number" ? explicitCode : void 0;
|
|
4042
|
+
let reason;
|
|
4043
|
+
const queue = [value];
|
|
4044
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4045
|
+
for (let depth = 0; queue.length > 0 && depth < 12; depth += 1) {
|
|
4046
|
+
const current = queue.shift();
|
|
4047
|
+
if (typeof current === "string") {
|
|
4048
|
+
if (/^\d+$/.test(current)) {
|
|
4049
|
+
code ??= Number(current);
|
|
4050
|
+
} else {
|
|
4051
|
+
reason ??= current;
|
|
4052
|
+
}
|
|
4053
|
+
continue;
|
|
4054
|
+
}
|
|
4055
|
+
if (!current || typeof current !== "object") continue;
|
|
4056
|
+
if (seen.has(current)) continue;
|
|
4057
|
+
seen.add(current);
|
|
4058
|
+
const record = current;
|
|
4059
|
+
const directCode = record.code;
|
|
4060
|
+
const statusCode = record.statusCode;
|
|
4061
|
+
if (typeof directCode === "number") code ??= directCode;
|
|
4062
|
+
if (typeof statusCode === "number") code ??= statusCode;
|
|
4063
|
+
const directReason = record.reason;
|
|
4064
|
+
const failureReason = record.failureReason;
|
|
4065
|
+
if (typeof directReason === "string") {
|
|
4066
|
+
if (/^\d+$/.test(directReason)) code ??= Number(directReason);
|
|
4067
|
+
else reason ??= directReason;
|
|
4068
|
+
}
|
|
4069
|
+
if (typeof failureReason === "string") reason ??= failureReason;
|
|
4070
|
+
const output = record.output;
|
|
4071
|
+
const data = record.data;
|
|
4072
|
+
if (output && typeof output === "object") queue.push(output);
|
|
4073
|
+
if (data && typeof data === "object") queue.push(data);
|
|
4074
|
+
if (record.cause !== void 0) queue.push(record.cause);
|
|
4075
|
+
}
|
|
4076
|
+
return {
|
|
4077
|
+
...reason ? { reason } : {},
|
|
4078
|
+
...code !== void 0 ? { code } : {}
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
function shouldStopAutomaticReconnect(details) {
|
|
4082
|
+
if (details.reason === "client_disconnected") return true;
|
|
4083
|
+
if (details.reason && fatalDisconnectReasons.has(details.reason)) return true;
|
|
4084
|
+
return details.code !== void 0 && fatalDisconnectCodes.has(details.code);
|
|
4085
|
+
}
|
|
4086
|
+
function reconnectDelayOverride(details) {
|
|
4087
|
+
if (details.code === 515) return 0;
|
|
4088
|
+
if (details.code === 402) return 6e4;
|
|
4089
|
+
return void 0;
|
|
4090
|
+
}
|
|
4091
|
+
function connectionError(value, details) {
|
|
4092
|
+
if (value === void 0 && details.code === void 0 && details.reason === void 0) {
|
|
4093
|
+
return void 0;
|
|
4094
|
+
}
|
|
4095
|
+
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";
|
|
4096
|
+
const cause = value instanceof Error ? value : details.code !== void 0 ? {
|
|
4097
|
+
output: { statusCode: details.code },
|
|
4098
|
+
data: { reason: String(details.code) },
|
|
4099
|
+
...value !== void 0 ? { cause: value } : {}
|
|
4100
|
+
} : value;
|
|
4101
|
+
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.";
|
|
4102
|
+
return new WhaNextError(
|
|
4103
|
+
authExpired ? "AUTH_EXPIRED" : "CONNECTION_FAILED",
|
|
4104
|
+
message,
|
|
4105
|
+
{
|
|
4106
|
+
...cause !== void 0 ? { cause } : {},
|
|
4107
|
+
context: {
|
|
4108
|
+
...details.reason ? { reason: details.reason } : {},
|
|
4109
|
+
...details.code !== void 0 ? { statusCode: details.code } : {}
|
|
4110
|
+
},
|
|
4111
|
+
recoverable: details.code === 402 || details.code === 500 || details.code === 503 || details.code === 515
|
|
4112
|
+
}
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4115
|
+
async function acquireSharedZapoStore(storePath, sessionId, legacyStorePath, resetSession, logger) {
|
|
4116
|
+
if (storePath === legacyStorePath) {
|
|
4117
|
+
const entry2 = createZapoStoreEntry(storePath);
|
|
4118
|
+
entry2.refs = 1;
|
|
4119
|
+
entry2.sessionRefs.set(sessionId, 1);
|
|
4120
|
+
return entry2;
|
|
4121
|
+
}
|
|
4122
|
+
let pending = sharedZapoStorePromises.get(storePath);
|
|
4123
|
+
if (!pending) {
|
|
4124
|
+
pending = (async () => {
|
|
4125
|
+
await mkdir(dirname2(storePath), { recursive: true });
|
|
4126
|
+
const entry2 = createZapoStoreEntry(storePath);
|
|
4127
|
+
sharedZapoStores.set(storePath, entry2);
|
|
4128
|
+
return entry2;
|
|
4129
|
+
})();
|
|
4130
|
+
sharedZapoStorePromises.set(storePath, pending);
|
|
4131
|
+
}
|
|
4132
|
+
let entry;
|
|
4133
|
+
try {
|
|
4134
|
+
entry = await pending;
|
|
4135
|
+
} catch (error) {
|
|
4136
|
+
if (sharedZapoStorePromises.get(storePath) === pending) {
|
|
4137
|
+
sharedZapoStorePromises.delete(storePath);
|
|
4138
|
+
}
|
|
4139
|
+
throw error;
|
|
4140
|
+
}
|
|
4141
|
+
entry.refs += 1;
|
|
4142
|
+
entry.sessionRefs.set(sessionId, (entry.sessionRefs.get(sessionId) ?? 0) + 1);
|
|
4143
|
+
if (resetSession || await fileExists(legacyStorePath)) {
|
|
4144
|
+
entry.migrationQueue = entry.migrationQueue.then(async () => {
|
|
4145
|
+
if (resetSession) {
|
|
4146
|
+
clearZapoSessionData(storePath, sessionId);
|
|
4147
|
+
entry.snapshotClearSession.run(sessionId);
|
|
4148
|
+
}
|
|
4149
|
+
if (await fileExists(legacyStorePath)) {
|
|
4150
|
+
await migrateLegacyZapoStore(storePath, legacyStorePath, sessionId, logger);
|
|
4151
|
+
}
|
|
4152
|
+
});
|
|
4153
|
+
await entry.migrationQueue;
|
|
4154
|
+
}
|
|
4155
|
+
return entry;
|
|
4156
|
+
}
|
|
4157
|
+
function createZapoStoreEntry(storePath) {
|
|
4158
|
+
const store = createStore({
|
|
4159
|
+
backends: {
|
|
4160
|
+
sqlite: createSqliteStore({
|
|
4161
|
+
path: storePath,
|
|
4162
|
+
driver: "auto"
|
|
4163
|
+
})
|
|
4164
|
+
},
|
|
4165
|
+
providers: {
|
|
4166
|
+
auth: "sqlite",
|
|
4167
|
+
signal: "sqlite",
|
|
4168
|
+
preKey: "sqlite",
|
|
4169
|
+
session: "sqlite",
|
|
4170
|
+
identity: "sqlite",
|
|
4171
|
+
senderKey: "sqlite",
|
|
4172
|
+
appState: "sqlite",
|
|
4173
|
+
privacyToken: "sqlite",
|
|
4174
|
+
messages: "sqlite",
|
|
4175
|
+
threads: "sqlite",
|
|
4176
|
+
contacts: "sqlite"
|
|
4177
|
+
},
|
|
4178
|
+
cacheProviders: {
|
|
4179
|
+
messageSecret: "sqlite"
|
|
4180
|
+
}
|
|
4181
|
+
});
|
|
4182
|
+
const Database = require2("better-sqlite3");
|
|
4183
|
+
const snapshotDb = new Database(messageSnapshotStorePath(storePath));
|
|
4184
|
+
snapshotDb.exec(`
|
|
4185
|
+
PRAGMA busy_timeout = 5000;
|
|
4186
|
+
CREATE TABLE IF NOT EXISTS whanext_message_snapshots (
|
|
4187
|
+
session_id TEXT NOT NULL,
|
|
4188
|
+
message_id TEXT NOT NULL,
|
|
4189
|
+
chat_id TEXT NOT NULL,
|
|
4190
|
+
participant_id TEXT,
|
|
4191
|
+
from_me INTEGER NOT NULL,
|
|
4192
|
+
timestamp_seconds INTEGER,
|
|
4193
|
+
message_bytes BLOB NOT NULL,
|
|
4194
|
+
stored_at_seconds INTEGER NOT NULL,
|
|
4195
|
+
PRIMARY KEY (session_id, message_id)
|
|
4196
|
+
);
|
|
4197
|
+
CREATE INDEX IF NOT EXISTS idx_whanext_message_snapshots_chat
|
|
4198
|
+
ON whanext_message_snapshots (session_id, chat_id);
|
|
4199
|
+
`);
|
|
4200
|
+
const snapshotColumns = snapshotDb.prepare(
|
|
4201
|
+
"PRAGMA table_info(whanext_message_snapshots)"
|
|
4202
|
+
).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4203
|
+
if (!snapshotColumns.includes("stored_at_seconds")) {
|
|
4204
|
+
snapshotDb.exec(
|
|
4205
|
+
"ALTER TABLE whanext_message_snapshots ADD COLUMN stored_at_seconds INTEGER NOT NULL DEFAULT 0"
|
|
4206
|
+
);
|
|
4207
|
+
}
|
|
4208
|
+
snapshotDb.exec(`
|
|
4209
|
+
CREATE INDEX IF NOT EXISTS idx_whanext_message_snapshots_retention
|
|
4210
|
+
ON whanext_message_snapshots (session_id, stored_at_seconds);
|
|
4211
|
+
`);
|
|
4212
|
+
return {
|
|
4213
|
+
store,
|
|
4214
|
+
snapshotDb,
|
|
4215
|
+
snapshotUpsert: snapshotDb.prepare(`
|
|
4216
|
+
INSERT INTO whanext_message_snapshots (
|
|
4217
|
+
session_id, message_id, chat_id, participant_id, from_me,
|
|
4218
|
+
timestamp_seconds, message_bytes, stored_at_seconds
|
|
4219
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
4220
|
+
ON CONFLICT(session_id, message_id) DO UPDATE SET
|
|
4221
|
+
chat_id = excluded.chat_id,
|
|
4222
|
+
participant_id = excluded.participant_id,
|
|
4223
|
+
from_me = excluded.from_me,
|
|
4224
|
+
timestamp_seconds = excluded.timestamp_seconds,
|
|
4225
|
+
message_bytes = excluded.message_bytes,
|
|
4226
|
+
stored_at_seconds = excluded.stored_at_seconds
|
|
4227
|
+
`),
|
|
4228
|
+
snapshotGet: snapshotDb.prepare(`
|
|
4229
|
+
SELECT chat_id, participant_id, from_me, timestamp_seconds, message_bytes
|
|
4230
|
+
FROM whanext_message_snapshots
|
|
4231
|
+
WHERE session_id = ? AND message_id = ?
|
|
4232
|
+
LIMIT 1
|
|
4233
|
+
`),
|
|
4234
|
+
snapshotPruneAge: snapshotDb.prepare(`
|
|
4235
|
+
DELETE FROM whanext_message_snapshots
|
|
4236
|
+
WHERE session_id = ? AND stored_at_seconds < ?
|
|
4237
|
+
`),
|
|
4238
|
+
snapshotPruneOverflow: snapshotDb.prepare(`
|
|
4239
|
+
DELETE FROM whanext_message_snapshots
|
|
4240
|
+
WHERE session_id = ?
|
|
4241
|
+
AND message_id IN (
|
|
4242
|
+
SELECT message_id
|
|
4243
|
+
FROM whanext_message_snapshots
|
|
4244
|
+
WHERE session_id = ?
|
|
4245
|
+
ORDER BY stored_at_seconds DESC
|
|
4246
|
+
LIMIT -1 OFFSET ?
|
|
4247
|
+
)
|
|
4248
|
+
`),
|
|
4249
|
+
snapshotClearSession: snapshotDb.prepare(`
|
|
4250
|
+
DELETE FROM whanext_message_snapshots
|
|
4251
|
+
WHERE session_id = ?
|
|
4252
|
+
`),
|
|
4253
|
+
refs: 0,
|
|
4254
|
+
sessionRefs: /* @__PURE__ */ new Map(),
|
|
4255
|
+
migrationQueue: Promise.resolve()
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
function messageSnapshotStorePath(storePath) {
|
|
4259
|
+
return join(dirname2(storePath), "whanext-messages.sqlite");
|
|
4260
|
+
}
|
|
4261
|
+
async function releaseSharedZapoStore(storePath, sessionId, entry, logger) {
|
|
4262
|
+
const currentSessionRefs = entry.sessionRefs.get(sessionId) ?? 0;
|
|
4263
|
+
if (currentSessionRefs <= 1) {
|
|
4264
|
+
entry.sessionRefs.delete(sessionId);
|
|
4265
|
+
try {
|
|
4266
|
+
await entry.store.session(sessionId).destroy();
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
logger.warn("Could not release the Zapo session store cleanly.", {
|
|
4269
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4270
|
+
sessionId
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
} else {
|
|
4274
|
+
entry.sessionRefs.set(sessionId, currentSessionRefs - 1);
|
|
4275
|
+
}
|
|
4276
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
4277
|
+
if (entry.refs > 0) return;
|
|
4278
|
+
if (sharedZapoStores.get(storePath) === entry) {
|
|
4279
|
+
sharedZapoStores.delete(storePath);
|
|
4280
|
+
sharedZapoStorePromises.delete(storePath);
|
|
4281
|
+
}
|
|
4282
|
+
try {
|
|
4283
|
+
await entry.store.destroy();
|
|
4284
|
+
} catch (error) {
|
|
4285
|
+
logger.warn("Could not close the shared Zapo store cleanly.", {
|
|
4286
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4287
|
+
storePath
|
|
4288
|
+
});
|
|
4289
|
+
} finally {
|
|
4290
|
+
try {
|
|
4291
|
+
entry.snapshotDb.close();
|
|
4292
|
+
} catch (error) {
|
|
4293
|
+
logger.debug("Could not close the WhaNext message snapshot database cleanly.", {
|
|
4294
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4295
|
+
storePath
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
function clearZapoSessionData(storePath, sessionId) {
|
|
4301
|
+
let db;
|
|
4302
|
+
try {
|
|
4303
|
+
const Database = require2("better-sqlite3");
|
|
4304
|
+
db = new Database(storePath);
|
|
4305
|
+
const tables = db.prepare(
|
|
4306
|
+
"SELECT name FROM main.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
4307
|
+
).all();
|
|
4308
|
+
db.exec("BEGIN IMMEDIATE");
|
|
4309
|
+
try {
|
|
4310
|
+
for (const row of tables) {
|
|
4311
|
+
const table = stringField2(row, "name");
|
|
4312
|
+
if (!table || table === "wa_migrations" || table === "whanext_core_migrations") {
|
|
4313
|
+
continue;
|
|
4314
|
+
}
|
|
4315
|
+
const quoted = sqliteIdentifier(table);
|
|
4316
|
+
const columns = db.prepare(`PRAGMA main.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4317
|
+
if (!columns.includes("session_id")) continue;
|
|
4318
|
+
db.prepare(`DELETE FROM main.${quoted} WHERE session_id = ?`).run(sessionId);
|
|
4319
|
+
}
|
|
4320
|
+
db.exec("COMMIT");
|
|
4321
|
+
} catch (error) {
|
|
4322
|
+
db.exec("ROLLBACK");
|
|
4323
|
+
throw error;
|
|
4324
|
+
}
|
|
4325
|
+
} finally {
|
|
4326
|
+
db?.close();
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
async function migrateLegacyZapoStore(storePath, legacyStorePath, sessionId, logger) {
|
|
4330
|
+
let db;
|
|
4331
|
+
try {
|
|
4332
|
+
const Database = require2("better-sqlite3");
|
|
4333
|
+
db = new Database(storePath);
|
|
4334
|
+
db.exec(`ATTACH DATABASE ${sqliteString(legacyStorePath)} AS legacy`);
|
|
4335
|
+
db.exec(`
|
|
4336
|
+
CREATE TABLE IF NOT EXISTS whanext_core_migrations (
|
|
4337
|
+
migration_key TEXT PRIMARY KEY,
|
|
4338
|
+
migrated_at INTEGER NOT NULL
|
|
4339
|
+
)
|
|
4340
|
+
`);
|
|
4341
|
+
const migrationKey = `shared-store:${resolve2(legacyStorePath)}:${sessionId}`;
|
|
4342
|
+
const migrated = db.prepare(
|
|
4343
|
+
"SELECT migration_key FROM whanext_core_migrations WHERE migration_key = ?"
|
|
4344
|
+
).get(migrationKey);
|
|
4345
|
+
if (migrated) return;
|
|
4346
|
+
const mainTables = new Set(
|
|
4347
|
+
db.prepare("SELECT name FROM main.sqlite_master WHERE type = 'table'").all().map((row) => stringField2(row, "name")).filter((name) => name !== void 0)
|
|
4348
|
+
);
|
|
4349
|
+
const legacyTables = db.prepare(
|
|
4350
|
+
"SELECT name FROM legacy.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
4351
|
+
).all();
|
|
4352
|
+
db.exec("BEGIN IMMEDIATE");
|
|
4353
|
+
try {
|
|
4354
|
+
for (const row of legacyTables) {
|
|
4355
|
+
const table = stringField2(row, "name");
|
|
4356
|
+
if (!table || !mainTables.has(table) || table === "wa_migrations" || table === "whanext_core_migrations") {
|
|
4357
|
+
continue;
|
|
4358
|
+
}
|
|
4359
|
+
const quoted = sqliteIdentifier(table);
|
|
4360
|
+
const mainColumns = db.prepare(`PRAGMA main.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0);
|
|
4361
|
+
const legacyColumns = new Set(
|
|
4362
|
+
db.prepare(`PRAGMA legacy.table_info(${quoted})`).all().map((column) => stringField2(column, "name")).filter((name) => name !== void 0)
|
|
4363
|
+
);
|
|
4364
|
+
const columns = mainColumns.filter((column) => legacyColumns.has(column));
|
|
4365
|
+
if (!columns.includes("session_id") || columns.length === 0) continue;
|
|
4366
|
+
const selected = columns.map(sqliteIdentifier).join(", ");
|
|
4367
|
+
db.prepare(
|
|
4368
|
+
`INSERT OR REPLACE INTO main.${quoted} (${selected}) SELECT ${selected} FROM legacy.${quoted} WHERE session_id = ?`
|
|
4369
|
+
).run(sessionId);
|
|
4370
|
+
}
|
|
4371
|
+
db.prepare(
|
|
4372
|
+
"INSERT OR REPLACE INTO whanext_core_migrations (migration_key, migrated_at) VALUES (?, ?)"
|
|
4373
|
+
).run(migrationKey, Date.now());
|
|
4374
|
+
db.exec("COMMIT");
|
|
4375
|
+
} catch (error) {
|
|
4376
|
+
db.exec("ROLLBACK");
|
|
4377
|
+
throw error;
|
|
4378
|
+
}
|
|
4379
|
+
} catch (error) {
|
|
4380
|
+
logger.warn("Could not migrate the legacy per-account Zapo store; keeping the shared store active.", {
|
|
4381
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4382
|
+
sessionId,
|
|
4383
|
+
legacyStorePath,
|
|
4384
|
+
storePath
|
|
4385
|
+
});
|
|
4386
|
+
} finally {
|
|
4387
|
+
if (db) {
|
|
4388
|
+
try {
|
|
4389
|
+
db.exec("DETACH DATABASE legacy");
|
|
4390
|
+
} catch {
|
|
4391
|
+
}
|
|
4392
|
+
db.close();
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
async function fileExists(path2) {
|
|
4397
|
+
try {
|
|
4398
|
+
await stat(path2);
|
|
4399
|
+
return true;
|
|
4400
|
+
} catch {
|
|
4401
|
+
return false;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
function sqliteIdentifier(value) {
|
|
4405
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
4406
|
+
}
|
|
4407
|
+
function sqliteString(value) {
|
|
4408
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
4409
|
+
}
|
|
4410
|
+
function stringField2(record, field) {
|
|
4411
|
+
const value = record[field];
|
|
4412
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4413
|
+
}
|
|
4414
|
+
function stringValue(value) {
|
|
4415
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4416
|
+
}
|
|
4417
|
+
function numberValue(value) {
|
|
4418
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4419
|
+
}
|
|
4420
|
+
function booleanValue(value) {
|
|
4421
|
+
if (value === true || value === 1) return true;
|
|
4422
|
+
if (value === false || value === 0) return false;
|
|
4423
|
+
return void 0;
|
|
4424
|
+
}
|
|
4425
|
+
function bytesField(value) {
|
|
4426
|
+
if (value instanceof Uint8Array) return value;
|
|
4427
|
+
return void 0;
|
|
4428
|
+
}
|
|
3834
4429
|
var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
3835
4430
|
level;
|
|
3836
4431
|
#logger;
|
|
@@ -3903,7 +4498,7 @@ async function create(options = {}) {
|
|
|
3903
4498
|
// src/app/multi-app.ts
|
|
3904
4499
|
import {
|
|
3905
4500
|
join as join2,
|
|
3906
|
-
resolve as
|
|
4501
|
+
resolve as resolve3
|
|
3907
4502
|
} from "path";
|
|
3908
4503
|
var MultiCommandRouter = class {
|
|
3909
4504
|
#apps;
|
|
@@ -4048,7 +4643,7 @@ async function createMulti(options) {
|
|
|
4048
4643
|
}
|
|
4049
4644
|
normalizedIds.add(normalizedId);
|
|
4050
4645
|
if (account.provider === void 0) {
|
|
4051
|
-
const authPath =
|
|
4646
|
+
const authPath = resolve3(account.auth ?? join2(authRoot, account.id));
|
|
4052
4647
|
if (authPaths.has(authPath)) {
|
|
4053
4648
|
throw new WhaNextError(
|
|
4054
4649
|
"ARGUMENT_INVALID",
|