@whanext/core 0.19.4 → 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 +32 -1
- package/MIGRATING_TO_ZAPO.md +2 -2
- package/README.md +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +748 -91
- 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 {
|
|
@@ -2388,9 +2389,13 @@ import {
|
|
|
2388
2389
|
// src/provider/zapo/normalize-message.ts
|
|
2389
2390
|
function unwrapZapoMessageContent(input) {
|
|
2390
2391
|
if (!input) return void 0;
|
|
2391
|
-
const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message;
|
|
2392
|
+
const nested = input.ephemeralMessage?.message ?? input.viewOnceMessage?.message ?? input.viewOnceMessageV2?.message ?? viewOnceV2ExtensionMessage(input) ?? input.deviceSentMessage?.message ?? input.documentWithCaptionMessage?.message ?? editedWrapperMessage(input);
|
|
2392
2393
|
return nested ? unwrapZapoMessageContent(nested) : input;
|
|
2393
2394
|
}
|
|
2395
|
+
function editedWrapperMessage(input) {
|
|
2396
|
+
const edited = input.editedMessage;
|
|
2397
|
+
return edited?.message ?? void 0;
|
|
2398
|
+
}
|
|
2394
2399
|
function viewOnceV2ExtensionMessage(input) {
|
|
2395
2400
|
const extension = input.viewOnceMessageV2Extension;
|
|
2396
2401
|
return extension?.message ?? void 0;
|
|
@@ -2702,6 +2707,25 @@ function toNumber(value) {
|
|
|
2702
2707
|
}
|
|
2703
2708
|
|
|
2704
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;
|
|
2705
2729
|
var ZapoProvider = class {
|
|
2706
2730
|
#options;
|
|
2707
2731
|
#events = new TypedEventEmitter();
|
|
@@ -2712,14 +2736,19 @@ var ZapoProvider = class {
|
|
|
2712
2736
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
2713
2737
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
2714
2738
|
#messageCacheSize;
|
|
2739
|
+
#protocolMutationQueue = Promise.resolve();
|
|
2715
2740
|
#client;
|
|
2741
|
+
#storeEntry;
|
|
2742
|
+
#storePath;
|
|
2716
2743
|
#intentionalClose = false;
|
|
2744
|
+
#closedNotified = false;
|
|
2717
2745
|
#reconnectAttempt = 0;
|
|
2718
2746
|
#reconnectTimer;
|
|
2719
2747
|
#connectPromise;
|
|
2720
2748
|
#pairingRequired = false;
|
|
2721
2749
|
#connected = false;
|
|
2722
2750
|
#connectedAtSeconds = 0;
|
|
2751
|
+
#messageSnapshotsSincePrune = 0;
|
|
2723
2752
|
#pairingReady = Promise.resolve();
|
|
2724
2753
|
#resolvePairingReady;
|
|
2725
2754
|
constructor(options) {
|
|
@@ -2732,6 +2761,7 @@ var ZapoProvider = class {
|
|
|
2732
2761
|
}
|
|
2733
2762
|
async connect() {
|
|
2734
2763
|
this.#intentionalClose = false;
|
|
2764
|
+
this.#closedNotified = false;
|
|
2735
2765
|
const client = await this.#ensureClient();
|
|
2736
2766
|
if (this.#connected || this.#connectPromise) {
|
|
2737
2767
|
return;
|
|
@@ -2751,12 +2781,16 @@ var ZapoProvider = class {
|
|
|
2751
2781
|
}
|
|
2752
2782
|
const client = this.#client;
|
|
2753
2783
|
this.#connectPromise = void 0;
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
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();
|
|
2758
2793
|
}
|
|
2759
|
-
this.#connected = false;
|
|
2760
2794
|
}
|
|
2761
2795
|
getCurrentUserIds() {
|
|
2762
2796
|
const credentials = this.#client?.getCredentials();
|
|
@@ -2815,7 +2849,7 @@ var ZapoProvider = class {
|
|
|
2815
2849
|
return this.#sent(result, chatId);
|
|
2816
2850
|
}
|
|
2817
2851
|
async repostMessage(source, chatId, options = {}) {
|
|
2818
|
-
const original = this.#findStoredMessage(this.#toZapoKey(source));
|
|
2852
|
+
const original = await this.#findStoredMessage(this.#toZapoKey(source));
|
|
2819
2853
|
if (!original?.message) {
|
|
2820
2854
|
throw new WhaNextError(
|
|
2821
2855
|
"MESSAGE_NOT_FOUND",
|
|
@@ -2845,7 +2879,7 @@ var ZapoProvider = class {
|
|
|
2845
2879
|
return this.#sent(result, key.chatId);
|
|
2846
2880
|
}
|
|
2847
2881
|
async downloadMedia(key) {
|
|
2848
|
-
const message = this.#messageKeyStore.get(key) ?? this.#findStoredMessage(this.#toZapoKey(key));
|
|
2882
|
+
const message = this.#messageKeyStore.get(key) ?? await this.#findStoredMessage(this.#toZapoKey(key));
|
|
2849
2883
|
if (!message?.message) {
|
|
2850
2884
|
throw new WhaNextError(
|
|
2851
2885
|
"MEDIA_NOT_AVAILABLE",
|
|
@@ -2989,34 +3023,24 @@ var ZapoProvider = class {
|
|
|
2989
3023
|
}
|
|
2990
3024
|
async #ensureClient() {
|
|
2991
3025
|
if (this.#client) return this.#client;
|
|
2992
|
-
|
|
2993
|
-
const
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
appState: "sqlite",
|
|
3008
|
-
privacyToken: "sqlite",
|
|
3009
|
-
messages: "none",
|
|
3010
|
-
threads: "none",
|
|
3011
|
-
contacts: "none"
|
|
3012
|
-
},
|
|
3013
|
-
cacheProviders: {
|
|
3014
|
-
messageSecret: "sqlite"
|
|
3015
|
-
}
|
|
3016
|
-
});
|
|
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
|
+
);
|
|
3017
3041
|
const client = new WaClient({
|
|
3018
|
-
store,
|
|
3019
|
-
sessionId
|
|
3042
|
+
store: storeEntry.store,
|
|
3043
|
+
sessionId,
|
|
3020
3044
|
markOnlineOnConnect: false,
|
|
3021
3045
|
deviceBrowser: this.#deviceBrowser(),
|
|
3022
3046
|
deviceOsDisplayName: this.#deviceOsDisplayName(),
|
|
@@ -3025,8 +3049,10 @@ var ZapoProvider = class {
|
|
|
3025
3049
|
autoDecrypt: true,
|
|
3026
3050
|
persistAllSecrets: true
|
|
3027
3051
|
},
|
|
3028
|
-
media: { processor:
|
|
3052
|
+
media: { processor: sharedMediaProcessor }
|
|
3029
3053
|
}, new WhaNextZapoLogger(this.#logger));
|
|
3054
|
+
this.#storeEntry = storeEntry;
|
|
3055
|
+
this.#storePath = storePath;
|
|
3030
3056
|
this.#client = client;
|
|
3031
3057
|
this.#bind(client);
|
|
3032
3058
|
return client;
|
|
@@ -3041,7 +3067,24 @@ var ZapoProvider = class {
|
|
|
3041
3067
|
client.on("auth_paired", () => {
|
|
3042
3068
|
this.#pairingRequired = false;
|
|
3043
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
|
+
});
|
|
3044
3079
|
client.on("message", (event) => {
|
|
3080
|
+
const protocol = this.#protocolMessage(event);
|
|
3081
|
+
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3082
|
+
this.#enqueueProtocolEvent({
|
|
3083
|
+
...event,
|
|
3084
|
+
protocolMessage: protocol
|
|
3085
|
+
});
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3045
3088
|
this.#handleMessage(event);
|
|
3046
3089
|
});
|
|
3047
3090
|
client.on("message_send", (event) => {
|
|
@@ -3057,7 +3100,7 @@ var ZapoProvider = class {
|
|
|
3057
3100
|
});
|
|
3058
3101
|
});
|
|
3059
3102
|
client.on("message_protocol", (event) => {
|
|
3060
|
-
this.#
|
|
3103
|
+
this.#enqueueProtocolEvent(event);
|
|
3061
3104
|
});
|
|
3062
3105
|
client.on("message_addon", (event) => {
|
|
3063
3106
|
this.#handleAddonEvent(event);
|
|
@@ -3109,26 +3152,56 @@ var ZapoProvider = class {
|
|
|
3109
3152
|
void this.#events.emit("message", message);
|
|
3110
3153
|
}
|
|
3111
3154
|
#handleAddonEvent(event) {
|
|
3112
|
-
|
|
3113
|
-
const
|
|
3155
|
+
const decrypted = this.#addonRecord(event.decrypted);
|
|
3156
|
+
const protocol = this.#addonProtocolMessage(decrypted);
|
|
3157
|
+
const kind = event.kind ?? this.#stringField(decrypted, "kind") ?? this.#stringField(decrypted, "type");
|
|
3158
|
+
const isEdit = kind === "message_edit" || protocol?.type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT;
|
|
3159
|
+
if (!isEdit) return;
|
|
3160
|
+
const targetMessageId = event.targetMessageId ?? this.#stringField(decrypted, "targetMessageId") ?? this.#stringField(decrypted, "targetMessageID") ?? protocol?.key?.id ?? void 0;
|
|
3161
|
+
if (!targetMessageId) return;
|
|
3162
|
+
const editedMessage = protocol?.editedMessage ?? this.#addonEditedMessage(event.decrypted);
|
|
3114
3163
|
if (!editedMessage) return;
|
|
3164
|
+
const protocolKey = protocol?.key;
|
|
3115
3165
|
const target = {
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
...event.key.
|
|
3119
|
-
...event.key.
|
|
3120
|
-
...event.key.
|
|
3166
|
+
...protocolKey ?? {},
|
|
3167
|
+
id: targetMessageId,
|
|
3168
|
+
...protocolKey?.remoteJid !== void 0 ? { remoteJid: protocolKey.remoteJid } : event.key.remoteJid !== void 0 ? { remoteJid: event.key.remoteJid } : {},
|
|
3169
|
+
...protocolKey?.fromMe !== void 0 ? { fromMe: protocolKey.fromMe } : event.key.fromMe !== void 0 ? { fromMe: event.key.fromMe } : {},
|
|
3170
|
+
...protocolKey?.participant !== void 0 ? { participant: protocolKey.participant } : event.key.participant !== void 0 ? { participant: event.key.participant } : {},
|
|
3171
|
+
...protocolKey?.participantAlt !== void 0 ? { participantAlt: protocolKey.participantAlt } : event.key.participantAlt !== void 0 ? { participantAlt: event.key.participantAlt } : {}
|
|
3121
3172
|
};
|
|
3122
|
-
this.#handleProtocolEvent({
|
|
3173
|
+
void this.#handleProtocolEvent({
|
|
3123
3174
|
key: event.key,
|
|
3124
|
-
...event.
|
|
3175
|
+
...event.timestampSeconds !== void 0 ? { timestampSeconds: event.timestampSeconds } : {},
|
|
3125
3176
|
protocolMessage: {
|
|
3126
3177
|
type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
|
|
3127
3178
|
key: target,
|
|
3128
|
-
editedMessage
|
|
3179
|
+
editedMessage,
|
|
3180
|
+
...protocol?.timestampMs !== void 0 ? { timestampMs: protocol.timestampMs } : {}
|
|
3129
3181
|
}
|
|
3130
3182
|
});
|
|
3131
3183
|
}
|
|
3184
|
+
#addonRecord(value) {
|
|
3185
|
+
return value && typeof value === "object" ? value : void 0;
|
|
3186
|
+
}
|
|
3187
|
+
#stringField(record, field) {
|
|
3188
|
+
const value = record?.[field];
|
|
3189
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3190
|
+
}
|
|
3191
|
+
#addonProtocolMessage(record) {
|
|
3192
|
+
const direct = record?.protocolMessage;
|
|
3193
|
+
if (direct && typeof direct === "object") {
|
|
3194
|
+
return direct;
|
|
3195
|
+
}
|
|
3196
|
+
const message = record?.message;
|
|
3197
|
+
if (message && typeof message === "object") {
|
|
3198
|
+
const nested = message.protocolMessage;
|
|
3199
|
+
if (nested && typeof nested === "object") {
|
|
3200
|
+
return nested;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
return void 0;
|
|
3204
|
+
}
|
|
3132
3205
|
#addonEditedMessage(value) {
|
|
3133
3206
|
if (!value || typeof value !== "object") return void 0;
|
|
3134
3207
|
const record = value;
|
|
@@ -3138,24 +3211,65 @@ var ZapoProvider = class {
|
|
|
3138
3211
|
if (edited2 && typeof edited2 === "object") return edited2;
|
|
3139
3212
|
}
|
|
3140
3213
|
const edited = record.editedMessage;
|
|
3141
|
-
if (edited && typeof edited === "object")
|
|
3214
|
+
if (edited && typeof edited === "object") {
|
|
3215
|
+
const nested = edited.message;
|
|
3216
|
+
return nested && typeof nested === "object" ? nested : edited;
|
|
3217
|
+
}
|
|
3142
3218
|
const message = record.message;
|
|
3143
|
-
if (message && typeof message === "object")
|
|
3219
|
+
if (message && typeof message === "object") {
|
|
3220
|
+
const messageRecord = message;
|
|
3221
|
+
const nestedEdited = messageRecord.editedMessage;
|
|
3222
|
+
if (nestedEdited && typeof nestedEdited === "object") {
|
|
3223
|
+
const nested = nestedEdited.message;
|
|
3224
|
+
if (nested && typeof nested === "object") return nested;
|
|
3225
|
+
}
|
|
3226
|
+
return message;
|
|
3227
|
+
}
|
|
3144
3228
|
return value;
|
|
3145
3229
|
}
|
|
3146
|
-
#
|
|
3147
|
-
if (
|
|
3148
|
-
|
|
3230
|
+
#protocolMessage(event) {
|
|
3231
|
+
if (event.protocolMessage) return event.protocolMessage;
|
|
3232
|
+
const content = unwrapZapoMessageContent(event.message);
|
|
3233
|
+
return content?.protocolMessage;
|
|
3234
|
+
}
|
|
3235
|
+
#isMessageMutationProtocol(type) {
|
|
3236
|
+
return type === proto.Message.ProtocolMessage.Type.REVOKE || type === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT;
|
|
3237
|
+
}
|
|
3238
|
+
#shouldIgnoreProtocolEvent(event) {
|
|
3239
|
+
if (this.#options.processOfflineMessages === true) return false;
|
|
3240
|
+
if (!this.#connected) return true;
|
|
3241
|
+
if (!this.#connectedAtSeconds || event.timestampSeconds == null) return false;
|
|
3242
|
+
const timestamp = toSeconds(event.timestampSeconds);
|
|
3243
|
+
if (timestamp === void 0) return false;
|
|
3244
|
+
return timestamp < this.#connectedAtSeconds - 3;
|
|
3245
|
+
}
|
|
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) {
|
|
3255
|
+
if (this.#shouldIgnoreProtocolEvent(event)) {
|
|
3256
|
+
this.#logger.debug("Ignored protocol mutation from before the current live connection.", {
|
|
3149
3257
|
messageId: event.key.id ?? void 0,
|
|
3150
3258
|
chatId: event.key.remoteJid ?? void 0,
|
|
3151
3259
|
timestampSeconds: event.timestampSeconds ?? void 0
|
|
3152
3260
|
});
|
|
3153
3261
|
return;
|
|
3154
3262
|
}
|
|
3155
|
-
const protocol =
|
|
3156
|
-
if (!protocol) return;
|
|
3263
|
+
const protocol = this.#protocolMessage(event);
|
|
3264
|
+
if (!protocol || !this.#isMessageMutationProtocol(protocol.type)) return;
|
|
3157
3265
|
const protocolKey = protocol.key;
|
|
3158
|
-
if (!protocolKey?.id)
|
|
3266
|
+
if (!protocolKey?.id) {
|
|
3267
|
+
this.#logger.debug("Ignored Zapo protocol mutation without a target message id.", {
|
|
3268
|
+
messageId: event.key.id ?? void 0,
|
|
3269
|
+
protocolType: protocol.type ?? void 0
|
|
3270
|
+
});
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3159
3273
|
const remoteJid = protocolKey.remoteJid ?? event.key.remoteJid;
|
|
3160
3274
|
const participant = protocolKey.participant ?? event.key.participant;
|
|
3161
3275
|
const participantAlt = protocolKey.participantAlt ?? event.key.participantAlt;
|
|
@@ -3165,9 +3279,10 @@ var ZapoProvider = class {
|
|
|
3165
3279
|
...participant !== void 0 ? { participant } : {},
|
|
3166
3280
|
...participantAlt !== void 0 ? { participantAlt } : {}
|
|
3167
3281
|
};
|
|
3282
|
+
const stored = await this.#findStoredMessage(target);
|
|
3283
|
+
if (!target.remoteJid && stored?.key.remoteJid) target.remoteJid = stored.key.remoteJid;
|
|
3168
3284
|
if (!target.remoteJid) return;
|
|
3169
|
-
const
|
|
3170
|
-
const type = protocol?.type;
|
|
3285
|
+
const type = protocol.type;
|
|
3171
3286
|
const mutationKey = this.#protocolDeliveryKey(event, target, type);
|
|
3172
3287
|
if (mutationKey && this.#handledProtocolStore.has(mutationKey)) {
|
|
3173
3288
|
this.#logger.debug("Ignored duplicate Zapo protocol event.", {
|
|
@@ -3181,6 +3296,12 @@ var ZapoProvider = class {
|
|
|
3181
3296
|
const previous2 = stored ? normalizeZapoMessage(stored) : void 0;
|
|
3182
3297
|
const deletedByMe = event.key.fromMe === true;
|
|
3183
3298
|
const deletedById = event.key.participant ?? event.key.participantAlt ?? (deletedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
|
|
3299
|
+
if (!previous2) {
|
|
3300
|
+
this.#logger.debug("Zapo revoke target was not present in the recent message cache.", {
|
|
3301
|
+
targetMessageId: target.id ?? void 0,
|
|
3302
|
+
chatId: target.remoteJid ?? void 0
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3184
3305
|
void this.#events.emit("messageDeleted", {
|
|
3185
3306
|
key: previous2?.keys ?? normalizeZapoKey(target),
|
|
3186
3307
|
...previous2 ? { message: previous2 } : {},
|
|
@@ -3190,9 +3311,8 @@ var ZapoProvider = class {
|
|
|
3190
3311
|
});
|
|
3191
3312
|
return;
|
|
3192
3313
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
}
|
|
3314
|
+
const editedContent = protocol.editedMessage ? this.#unwrapEditedContent(protocol.editedMessage) : void 0;
|
|
3315
|
+
if (!editedContent) return;
|
|
3196
3316
|
const pushName = event.pushName ?? stored?.pushName;
|
|
3197
3317
|
const edited = {
|
|
3198
3318
|
...stored ?? {},
|
|
@@ -3200,7 +3320,7 @@ var ZapoProvider = class {
|
|
|
3200
3320
|
...stored?.key ?? {},
|
|
3201
3321
|
...target
|
|
3202
3322
|
},
|
|
3203
|
-
message:
|
|
3323
|
+
message: editedContent,
|
|
3204
3324
|
timestampSeconds: toSeconds(protocol.timestampMs) ?? event.timestampSeconds ?? stored?.timestampSeconds ?? Math.floor(Date.now() / 1e3),
|
|
3205
3325
|
...pushName !== void 0 ? { pushName } : {}
|
|
3206
3326
|
};
|
|
@@ -3211,6 +3331,12 @@ var ZapoProvider = class {
|
|
|
3211
3331
|
this.#remember(edited);
|
|
3212
3332
|
const editedByMe = event.key.fromMe === true;
|
|
3213
3333
|
const editedById = event.key.participant ?? event.key.participantAlt ?? (editedByMe ? this.getCurrentUserIds()[0] : event.key.remoteJid ?? void 0);
|
|
3334
|
+
if (!previous) {
|
|
3335
|
+
this.#logger.debug("Zapo edit target was not present in the recent message cache.", {
|
|
3336
|
+
targetMessageId: target.id ?? void 0,
|
|
3337
|
+
chatId: target.remoteJid ?? void 0
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3214
3340
|
void this.#events.emit("messageEdited", {
|
|
3215
3341
|
key: message.keys,
|
|
3216
3342
|
...previous ? { previous } : {},
|
|
@@ -3220,6 +3346,10 @@ var ZapoProvider = class {
|
|
|
3220
3346
|
editedAt: message.timestamp
|
|
3221
3347
|
});
|
|
3222
3348
|
}
|
|
3349
|
+
#unwrapEditedContent(message) {
|
|
3350
|
+
const wrapper = message.editedMessage;
|
|
3351
|
+
return wrapper?.message ?? message;
|
|
3352
|
+
}
|
|
3223
3353
|
#handleGroupEvent(event) {
|
|
3224
3354
|
const groupId = event.groupJid;
|
|
3225
3355
|
if (!groupId) return;
|
|
@@ -3244,20 +3374,26 @@ var ZapoProvider = class {
|
|
|
3244
3374
|
this.#connected = true;
|
|
3245
3375
|
this.#connectedAtSeconds = Math.floor(Date.now() / 1e3);
|
|
3246
3376
|
this.#reconnectAttempt = 0;
|
|
3377
|
+
this.#closedNotified = false;
|
|
3247
3378
|
await this.#events.emit("connection", { state: "connected" });
|
|
3248
3379
|
return;
|
|
3249
3380
|
}
|
|
3250
3381
|
this.#connectPromise = void 0;
|
|
3251
3382
|
this.#connected = false;
|
|
3252
|
-
const
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
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();
|
|
3258
3394
|
return;
|
|
3259
3395
|
}
|
|
3260
|
-
await this.#scheduleReconnect(error);
|
|
3396
|
+
await this.#scheduleReconnect(error, reconnectDelayOverride(details));
|
|
3261
3397
|
}
|
|
3262
3398
|
#startConnect(client) {
|
|
3263
3399
|
const promise = client.connect();
|
|
@@ -3266,20 +3402,65 @@ var ZapoProvider = class {
|
|
|
3266
3402
|
if (this.#connectPromise !== promise) return;
|
|
3267
3403
|
this.#connectPromise = void 0;
|
|
3268
3404
|
if (this.#intentionalClose) return;
|
|
3269
|
-
const
|
|
3405
|
+
const details = disconnectDetails(error);
|
|
3406
|
+
const normalized = connectionError(error, details) ?? (error instanceof Error ? error : new Error(String(error)));
|
|
3270
3407
|
this.#logger.warn("WhatsApp connection attempt failed.", { error: normalized });
|
|
3271
|
-
|
|
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 } : {}
|
|
3272
3442
|
});
|
|
3273
3443
|
}
|
|
3274
|
-
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) {
|
|
3275
3458
|
if (this.#reconnectTimer) return;
|
|
3276
3459
|
const options = this.#options.reconnect;
|
|
3277
3460
|
const maxAttempts = options?.maxAttempts ?? 10;
|
|
3278
3461
|
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
3279
|
-
await this.#
|
|
3280
|
-
|
|
3281
|
-
...error ? { error } : {}
|
|
3282
|
-
});
|
|
3462
|
+
await this.#emitClosedOnce(error);
|
|
3463
|
+
await this.#releaseStore();
|
|
3283
3464
|
return;
|
|
3284
3465
|
}
|
|
3285
3466
|
this.#reconnectAttempt += 1;
|
|
@@ -3290,11 +3471,13 @@ var ZapoProvider = class {
|
|
|
3290
3471
|
});
|
|
3291
3472
|
const initial = options?.initialDelayMs ?? 1e3;
|
|
3292
3473
|
const maximum = options?.maxDelayMs ?? 3e4;
|
|
3293
|
-
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;
|
|
3294
3477
|
this.#reconnectTimer = setTimeout(() => {
|
|
3295
3478
|
this.#reconnectTimer = void 0;
|
|
3296
3479
|
void this.connect();
|
|
3297
|
-
}, delay +
|
|
3480
|
+
}, delay + jitter);
|
|
3298
3481
|
}
|
|
3299
3482
|
async #sendButtons(chatId, content, replyTo) {
|
|
3300
3483
|
this.#validateButtons(content);
|
|
@@ -3627,7 +3810,7 @@ var ZapoProvider = class {
|
|
|
3627
3810
|
#groupParticipantIds(event) {
|
|
3628
3811
|
return uniqueIdentities((event.participants ?? []).map((participant) => participant.jid ?? participant.lidJid ?? participant.phoneJid));
|
|
3629
3812
|
}
|
|
3630
|
-
#findStoredMessage(key) {
|
|
3813
|
+
async #findStoredMessage(key) {
|
|
3631
3814
|
const direct = this.#messageStore.get(this.#messageStoreKey(key));
|
|
3632
3815
|
if (direct) return direct;
|
|
3633
3816
|
if (!key.id) return void 0;
|
|
@@ -3643,18 +3826,103 @@ var ZapoProvider = class {
|
|
|
3643
3826
|
this.#remember(storedQuoted);
|
|
3644
3827
|
return storedQuoted;
|
|
3645
3828
|
}
|
|
3646
|
-
return
|
|
3829
|
+
return this.#readArchivedMessage(key);
|
|
3647
3830
|
}
|
|
3648
3831
|
#remember(message) {
|
|
3649
3832
|
if (!message.key.id || !message.message) return;
|
|
3650
3833
|
const key = this.#messageStoreKey(message.key);
|
|
3651
3834
|
this.#messageStore.delete(key);
|
|
3652
3835
|
this.#messageStore.set(key, message);
|
|
3836
|
+
void this.#archiveMessage(message);
|
|
3653
3837
|
while (this.#messageStore.size > this.#messageCacheSize) {
|
|
3654
3838
|
const oldest = this.#messageStore.keys().next().value;
|
|
3655
3839
|
if (oldest) this.#messageStore.delete(oldest);
|
|
3656
3840
|
}
|
|
3657
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
|
+
}
|
|
3658
3926
|
#messageStoreKey(key) {
|
|
3659
3927
|
return `${key.remoteJid ?? ""}:${key.id ?? ""}:${key.participant ?? key.participantAlt ?? ""}`;
|
|
3660
3928
|
}
|
|
@@ -3697,8 +3965,8 @@ var ZapoProvider = class {
|
|
|
3697
3965
|
}
|
|
3698
3966
|
#preparePairingGate() {
|
|
3699
3967
|
this.#pairingRequired = false;
|
|
3700
|
-
this.#pairingReady = new Promise((
|
|
3701
|
-
this.#resolvePairingReady =
|
|
3968
|
+
this.#pairingReady = new Promise((resolve4) => {
|
|
3969
|
+
this.#resolvePairingReady = resolve4;
|
|
3702
3970
|
});
|
|
3703
3971
|
}
|
|
3704
3972
|
#deviceBrowser() {
|
|
@@ -3740,6 +4008,395 @@ var ZapoProvider = class {
|
|
|
3740
4008
|
}
|
|
3741
4009
|
}
|
|
3742
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
|
+
}
|
|
3743
4400
|
var WhaNextZapoLogger = class _WhaNextZapoLogger {
|
|
3744
4401
|
level;
|
|
3745
4402
|
#logger;
|
|
@@ -3812,7 +4469,7 @@ async function create(options = {}) {
|
|
|
3812
4469
|
// src/app/multi-app.ts
|
|
3813
4470
|
import {
|
|
3814
4471
|
join as join2,
|
|
3815
|
-
resolve as
|
|
4472
|
+
resolve as resolve3
|
|
3816
4473
|
} from "path";
|
|
3817
4474
|
var MultiCommandRouter = class {
|
|
3818
4475
|
#apps;
|
|
@@ -3957,7 +4614,7 @@ async function createMulti(options) {
|
|
|
3957
4614
|
}
|
|
3958
4615
|
normalizedIds.add(normalizedId);
|
|
3959
4616
|
if (account.provider === void 0) {
|
|
3960
|
-
const authPath =
|
|
4617
|
+
const authPath = resolve3(account.auth ?? join2(authRoot, account.id));
|
|
3961
4618
|
if (authPaths.has(authPath)) {
|
|
3962
4619
|
throw new WhaNextError(
|
|
3963
4620
|
"ARGUMENT_INVALID",
|