@whanext/core 0.19.18 → 0.19.20
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 +31 -0
- package/README.md +48 -1
- package/dist/index.d.ts +103 -1
- package/dist/index.js +457 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2346,7 +2346,23 @@ var WhaNextApp = class {
|
|
|
2346
2346
|
const messaging = provider?.messaging ?? {
|
|
2347
2347
|
sent: 0,
|
|
2348
2348
|
received: 0,
|
|
2349
|
-
failed: 0
|
|
2349
|
+
failed: 0,
|
|
2350
|
+
decryptedPayloads: 0,
|
|
2351
|
+
unavailable: 0,
|
|
2352
|
+
resendRequested: 0,
|
|
2353
|
+
recovered: 0,
|
|
2354
|
+
recoveryFailed: 0,
|
|
2355
|
+
unavailableUnrecoverable: 0,
|
|
2356
|
+
decodeFailures: 0,
|
|
2357
|
+
unhandledStanzas: 0,
|
|
2358
|
+
ignoredOffline: 0,
|
|
2359
|
+
duplicates: 0,
|
|
2360
|
+
normalizationFailures: 0,
|
|
2361
|
+
transportFramesIn: 0,
|
|
2362
|
+
transportNodesIn: 0,
|
|
2363
|
+
transportDecodeErrors: 0,
|
|
2364
|
+
messageStanzasIn: 0,
|
|
2365
|
+
ingressStalls: 0
|
|
2350
2366
|
};
|
|
2351
2367
|
const crypto = provider?.crypto ?? {
|
|
2352
2368
|
backend: "unknown",
|
|
@@ -2467,6 +2483,20 @@ var WhaNextApp = class {
|
|
|
2467
2483
|
await this.#events.emit("groupMetadataRecovered", event.payload);
|
|
2468
2484
|
} else if (event.type === "cryptoDegraded") {
|
|
2469
2485
|
await this.#events.emit("cryptoDegraded", event.payload);
|
|
2486
|
+
} else if (event.type === "messageUnavailable") {
|
|
2487
|
+
await this.#events.emit("messageUnavailable", event.payload);
|
|
2488
|
+
} else if (event.type === "messageRecovered") {
|
|
2489
|
+
await this.#events.emit("messageRecovered", event.payload);
|
|
2490
|
+
} else if (event.type === "messageRecoveryFailed") {
|
|
2491
|
+
await this.#events.emit("messageRecoveryFailed", event.payload);
|
|
2492
|
+
} else if (event.type === "messageDecodeFailure") {
|
|
2493
|
+
await this.#events.emit("messageDecodeFailure", event.payload);
|
|
2494
|
+
} else if (event.type === "messageDiscarded") {
|
|
2495
|
+
await this.#events.emit("messageDiscarded", event.payload);
|
|
2496
|
+
} else if (event.type === "messageIngressStalled") {
|
|
2497
|
+
await this.#events.emit("messageIngressStalled", event.payload);
|
|
2498
|
+
} else if (event.type === "transportDecodeFailure") {
|
|
2499
|
+
await this.#events.emit("transportDecodeFailure", event.payload);
|
|
2470
2500
|
}
|
|
2471
2501
|
await this.#refreshHealthState();
|
|
2472
2502
|
});
|
|
@@ -3136,11 +3166,14 @@ var sharedMediaProcessor = createMediaProcessor();
|
|
|
3136
3166
|
var REMOTE_MEDIA_TIMEOUT_MS = 12e4;
|
|
3137
3167
|
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
3138
3168
|
var DEFAULT_NODE_QUERY_TIMEOUT_MS = 3e4;
|
|
3169
|
+
var DEFAULT_INGRESS_STALL_TIMEOUT_MS = 1e4;
|
|
3139
3170
|
var PROVIDER_DEGRADED_WINDOW_MS = 12e4;
|
|
3140
3171
|
var GROUP_METADATA_CACHE_TTL_MS = 18e4;
|
|
3141
3172
|
var DEVICE_LIST_CACHE_TTL_MS = 18e4;
|
|
3142
3173
|
var GROUP_METADATA_RECOVERY_COOLDOWN_MS = 6e4;
|
|
3143
3174
|
var GROUP_METADATA_MISMATCH_WARNING = "group message publish acknowledged with mismatch metadata";
|
|
3175
|
+
var MESSAGE_RECOVERY_TIMEOUT_MS = 3e4;
|
|
3176
|
+
var DECRYPT_CORRELATION_TTL_MS = 15e3;
|
|
3144
3177
|
var messageSnapshotRetentionSeconds = 7 * 24 * 60 * 60;
|
|
3145
3178
|
var messageSnapshotMaxPerSession = 2e4;
|
|
3146
3179
|
var messageSnapshotPruneInterval = 256;
|
|
@@ -3151,6 +3184,9 @@ var ZapoProvider = class {
|
|
|
3151
3184
|
#messageStore = /* @__PURE__ */ new Map();
|
|
3152
3185
|
#messageKeyStore = /* @__PURE__ */ new WeakMap();
|
|
3153
3186
|
#deliveredMessageStore = /* @__PURE__ */ new Set();
|
|
3187
|
+
#pendingUnavailableRecoveries = /* @__PURE__ */ new Map();
|
|
3188
|
+
#recentDecryptedPayloads = /* @__PURE__ */ new Map();
|
|
3189
|
+
#pendingIngressMessages = /* @__PURE__ */ new Map();
|
|
3154
3190
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
3155
3191
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
3156
3192
|
#groupMetadataRecoveryAt = /* @__PURE__ */ new Map();
|
|
@@ -3159,6 +3195,8 @@ var ZapoProvider = class {
|
|
|
3159
3195
|
#cryptoBackend;
|
|
3160
3196
|
#connectTimeoutMs;
|
|
3161
3197
|
#nodeQueryTimeoutMs;
|
|
3198
|
+
#ingressDiagnostics;
|
|
3199
|
+
#ingressStallTimeoutMs;
|
|
3162
3200
|
#protocolMutationQueue = Promise.resolve();
|
|
3163
3201
|
#state = "idle";
|
|
3164
3202
|
#client;
|
|
@@ -3181,6 +3219,22 @@ var ZapoProvider = class {
|
|
|
3181
3219
|
#sentMessages = 0;
|
|
3182
3220
|
#receivedMessages = 0;
|
|
3183
3221
|
#failedMessages = 0;
|
|
3222
|
+
#decryptedPayloads = 0;
|
|
3223
|
+
#unavailableMessages = 0;
|
|
3224
|
+
#resendRequestedMessages = 0;
|
|
3225
|
+
#recoveredMessages = 0;
|
|
3226
|
+
#recoveryFailedMessages = 0;
|
|
3227
|
+
#unavailableUnrecoverableMessages = 0;
|
|
3228
|
+
#decodeFailures = 0;
|
|
3229
|
+
#unhandledStanzas = 0;
|
|
3230
|
+
#ignoredOfflineMessages = 0;
|
|
3231
|
+
#duplicateMessages = 0;
|
|
3232
|
+
#normalizationFailures = 0;
|
|
3233
|
+
#transportFramesIn = 0;
|
|
3234
|
+
#transportNodesIn = 0;
|
|
3235
|
+
#transportDecodeErrors = 0;
|
|
3236
|
+
#messageStanzasIn = 0;
|
|
3237
|
+
#ingressStalls = 0;
|
|
3184
3238
|
#lastIncomingAt;
|
|
3185
3239
|
#lastOutgoingAt;
|
|
3186
3240
|
#decryptFailures = 0;
|
|
@@ -3198,6 +3252,8 @@ var ZapoProvider = class {
|
|
|
3198
3252
|
this.#messageCacheSize = Math.max(1, options.messageCacheSize ?? 1e3);
|
|
3199
3253
|
this.#connectTimeoutMs = normalizeProviderTimeout(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
3200
3254
|
this.#nodeQueryTimeoutMs = normalizeProviderTimeout(options.nodeQueryTimeoutMs, DEFAULT_NODE_QUERY_TIMEOUT_MS);
|
|
3255
|
+
this.#ingressDiagnostics = options.ingressDiagnostics === true;
|
|
3256
|
+
this.#ingressStallTimeoutMs = normalizeProviderTimeout(options.ingressStallTimeoutMs, DEFAULT_INGRESS_STALL_TIMEOUT_MS);
|
|
3201
3257
|
this.#cryptoBackend = detectCryptoBackend();
|
|
3202
3258
|
}
|
|
3203
3259
|
on(event, listener) {
|
|
@@ -3221,6 +3277,22 @@ var ZapoProvider = class {
|
|
|
3221
3277
|
sent: this.#sentMessages,
|
|
3222
3278
|
received: this.#receivedMessages,
|
|
3223
3279
|
failed: this.#failedMessages,
|
|
3280
|
+
decryptedPayloads: this.#decryptedPayloads,
|
|
3281
|
+
unavailable: this.#unavailableMessages,
|
|
3282
|
+
resendRequested: this.#resendRequestedMessages,
|
|
3283
|
+
recovered: this.#recoveredMessages,
|
|
3284
|
+
recoveryFailed: this.#recoveryFailedMessages,
|
|
3285
|
+
unavailableUnrecoverable: this.#unavailableUnrecoverableMessages,
|
|
3286
|
+
decodeFailures: this.#decodeFailures,
|
|
3287
|
+
unhandledStanzas: this.#unhandledStanzas,
|
|
3288
|
+
ignoredOffline: this.#ignoredOfflineMessages,
|
|
3289
|
+
duplicates: this.#duplicateMessages,
|
|
3290
|
+
normalizationFailures: this.#normalizationFailures,
|
|
3291
|
+
transportFramesIn: this.#transportFramesIn,
|
|
3292
|
+
transportNodesIn: this.#transportNodesIn,
|
|
3293
|
+
transportDecodeErrors: this.#transportDecodeErrors,
|
|
3294
|
+
messageStanzasIn: this.#messageStanzasIn,
|
|
3295
|
+
ingressStalls: this.#ingressStalls,
|
|
3224
3296
|
...this.#lastIncomingAt ? { lastIncomingAt: new Date(this.#lastIncomingAt) } : {},
|
|
3225
3297
|
...this.#lastOutgoingAt ? { lastOutgoingAt: new Date(this.#lastOutgoingAt) } : {}
|
|
3226
3298
|
},
|
|
@@ -3267,6 +3339,15 @@ var ZapoProvider = class {
|
|
|
3267
3339
|
clearTimeout(this.#healthRefreshTimer);
|
|
3268
3340
|
this.#healthRefreshTimer = void 0;
|
|
3269
3341
|
}
|
|
3342
|
+
for (const pending of this.#pendingUnavailableRecoveries.values()) {
|
|
3343
|
+
clearTimeout(pending.timer);
|
|
3344
|
+
}
|
|
3345
|
+
this.#pendingUnavailableRecoveries.clear();
|
|
3346
|
+
for (const pending of this.#pendingIngressMessages.values()) {
|
|
3347
|
+
clearTimeout(pending.timer);
|
|
3348
|
+
}
|
|
3349
|
+
this.#pendingIngressMessages.clear();
|
|
3350
|
+
this.#recentDecryptedPayloads.clear();
|
|
3270
3351
|
const client = this.#client;
|
|
3271
3352
|
this.#connectPromise = void 0;
|
|
3272
3353
|
try {
|
|
@@ -3605,7 +3686,22 @@ var ZapoProvider = class {
|
|
|
3605
3686
|
);
|
|
3606
3687
|
void this.#stopTerminalConnection(client, error);
|
|
3607
3688
|
});
|
|
3689
|
+
client.on("debug_transport_frame_in", ({ frame }) => {
|
|
3690
|
+
this.#transportFramesIn += 1;
|
|
3691
|
+
if (this.#ingressDiagnostics) {
|
|
3692
|
+
this.#logger.debug("Inbound WhatsApp transport frame received.", {
|
|
3693
|
+
frameBytes: frame.byteLength
|
|
3694
|
+
});
|
|
3695
|
+
}
|
|
3696
|
+
});
|
|
3697
|
+
client.on("debug_transport_node_in", ({ node }) => {
|
|
3698
|
+
this.#handleTransportNodeIn(node);
|
|
3699
|
+
});
|
|
3700
|
+
client.on("debug_transport_decode_error", ({ error, frame }) => {
|
|
3701
|
+
this.#handleTransportDecodeError(error, frame.byteLength);
|
|
3702
|
+
});
|
|
3608
3703
|
client.on("message", (event) => {
|
|
3704
|
+
this.#resolveIngressMessage(event.key, "message_event");
|
|
3609
3705
|
const protocol = this.#protocolMessage(event);
|
|
3610
3706
|
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3611
3707
|
this.#enqueueProtocolEvent({
|
|
@@ -3628,6 +3724,16 @@ var ZapoProvider = class {
|
|
|
3628
3724
|
}
|
|
3629
3725
|
this.#handleMessage(event);
|
|
3630
3726
|
});
|
|
3727
|
+
client.on("message_unavailable", (event) => {
|
|
3728
|
+
this.#resolveIngressMessage(event.key, "unavailable");
|
|
3729
|
+
this.#handleUnavailableMessage(event);
|
|
3730
|
+
});
|
|
3731
|
+
client.on("debug_decrypted_payload", (event) => {
|
|
3732
|
+
this.#handleDecryptedPayload(event);
|
|
3733
|
+
});
|
|
3734
|
+
client.on("debug_unhandled_stanza", (event) => {
|
|
3735
|
+
this.#handleUnhandledStanza(event);
|
|
3736
|
+
});
|
|
3631
3737
|
client.on("message_send", (event) => {
|
|
3632
3738
|
if (!event.id || !event.message) return;
|
|
3633
3739
|
this.#sentMessages += 1;
|
|
@@ -3659,6 +3765,139 @@ var ZapoProvider = class {
|
|
|
3659
3765
|
void this.#handleConnectionEvent(client, event);
|
|
3660
3766
|
});
|
|
3661
3767
|
}
|
|
3768
|
+
#handleTransportNodeIn(node) {
|
|
3769
|
+
this.#transportNodesIn += 1;
|
|
3770
|
+
if (node.tag !== "message") return;
|
|
3771
|
+
this.#messageStanzasIn += 1;
|
|
3772
|
+
const stanzaId = stringValue(node.attrs.id);
|
|
3773
|
+
const chatId = normalizeIngressJid(stringValue(node.attrs.from));
|
|
3774
|
+
const participantId = normalizeIngressJid(
|
|
3775
|
+
stringValue(node.attrs.participant) ?? stringValue(node.attrs.participant_pn) ?? stringValue(node.attrs.participant_lid) ?? stringValue(node.attrs.sender_pn) ?? stringValue(node.attrs.sender_lid)
|
|
3776
|
+
);
|
|
3777
|
+
const stanzaType = stringValue(node.attrs.type);
|
|
3778
|
+
const addressingMode = stringValue(node.attrs.addressing_mode);
|
|
3779
|
+
const children = Array.isArray(node.content) ? node.content : [];
|
|
3780
|
+
const childTags = [...new Set(children.map((child) => child.tag))];
|
|
3781
|
+
const encTypes = [...new Set(children.filter((child) => child.tag === "enc").map((child) => stringValue(child.attrs.type)).filter((value) => value !== void 0))];
|
|
3782
|
+
if (this.#ingressDiagnostics) {
|
|
3783
|
+
this.#logger.info("Inbound WhatsApp message stanza observed.", {
|
|
3784
|
+
...stanzaId ? { stanzaId } : {},
|
|
3785
|
+
...chatId ? { chatId } : {},
|
|
3786
|
+
...participantId ? { participantId } : {},
|
|
3787
|
+
...stanzaType ? { stanzaType } : {},
|
|
3788
|
+
...addressingMode ? { addressingMode } : {},
|
|
3789
|
+
...childTags.length > 0 ? { childTags } : {},
|
|
3790
|
+
...encTypes.length > 0 ? { encTypes } : {}
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
const correlationKey = this.#stanzaCorrelationKey(chatId, stanzaId);
|
|
3794
|
+
if (!correlationKey || !stanzaId) return;
|
|
3795
|
+
const previous = this.#pendingIngressMessages.get(correlationKey);
|
|
3796
|
+
if (previous) clearTimeout(previous.timer);
|
|
3797
|
+
const observedAt = Date.now();
|
|
3798
|
+
const timer = setTimeout(() => {
|
|
3799
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3800
|
+
if (!pending || pending.observedAt !== observedAt) return;
|
|
3801
|
+
this.#pendingIngressMessages.delete(correlationKey);
|
|
3802
|
+
this.#ingressStalls += 1;
|
|
3803
|
+
this.#markDegraded();
|
|
3804
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
3805
|
+
this.#emitStability({
|
|
3806
|
+
type: "messageIngressStalled",
|
|
3807
|
+
payload: {
|
|
3808
|
+
occurredAt,
|
|
3809
|
+
waitedMs: occurredAt.getTime() - pending.observedAt,
|
|
3810
|
+
lastStage: pending.lastStage,
|
|
3811
|
+
stanzaId: pending.stanzaId,
|
|
3812
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3813
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3814
|
+
...pending.stanzaType ? { stanzaType: pending.stanzaType } : {},
|
|
3815
|
+
...pending.addressingMode ? { addressingMode: pending.addressingMode } : {}
|
|
3816
|
+
}
|
|
3817
|
+
});
|
|
3818
|
+
this.#logger.warn("Inbound WhatsApp message stanza did not reach a terminal provider event.", {
|
|
3819
|
+
stanzaId: pending.stanzaId,
|
|
3820
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3821
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3822
|
+
lastStage: pending.lastStage,
|
|
3823
|
+
waitedMs: occurredAt.getTime() - pending.observedAt
|
|
3824
|
+
});
|
|
3825
|
+
}, this.#ingressStallTimeoutMs);
|
|
3826
|
+
this.#pendingIngressMessages.set(correlationKey, {
|
|
3827
|
+
observedAt,
|
|
3828
|
+
lastStage: "stanza_received",
|
|
3829
|
+
timer,
|
|
3830
|
+
stanzaId,
|
|
3831
|
+
...chatId ? { chatId } : {},
|
|
3832
|
+
...participantId ? { participantId } : {},
|
|
3833
|
+
...stanzaType ? { stanzaType } : {},
|
|
3834
|
+
...addressingMode ? { addressingMode } : {}
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
#handleTransportDecodeError(error, frameBytes) {
|
|
3838
|
+
this.#transportDecodeErrors += 1;
|
|
3839
|
+
this.#markDegraded();
|
|
3840
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
3841
|
+
this.#emitStability({
|
|
3842
|
+
type: "transportDecodeFailure",
|
|
3843
|
+
payload: {
|
|
3844
|
+
occurredAt,
|
|
3845
|
+
frameBytes,
|
|
3846
|
+
errorName: error.name || "Error",
|
|
3847
|
+
errorMessage: error.message || String(error)
|
|
3848
|
+
}
|
|
3849
|
+
});
|
|
3850
|
+
this.#logger.warn("Inbound WhatsApp transport frame could not be decoded.", {
|
|
3851
|
+
frameBytes,
|
|
3852
|
+
errorName: error.name || "Error",
|
|
3853
|
+
errorMessage: error.message || String(error)
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
#markIngressDecrypted(chatId, stanzaId) {
|
|
3857
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizeIngressJid(chatId), stanzaId);
|
|
3858
|
+
if (!correlationKey) return;
|
|
3859
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3860
|
+
if (!pending) return;
|
|
3861
|
+
pending.lastStage = "decrypted";
|
|
3862
|
+
if (this.#ingressDiagnostics) {
|
|
3863
|
+
this.#logger.info("Inbound WhatsApp message payload decrypted.", {
|
|
3864
|
+
stanzaId: pending.stanzaId,
|
|
3865
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3866
|
+
...pending.participantId ? { participantId: pending.participantId } : {}
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
#resolveIngressMessage(key, outcome) {
|
|
3871
|
+
const chatId = normalizeIngressJid(key.remoteJid ?? void 0);
|
|
3872
|
+
const correlationKey = this.#stanzaCorrelationKey(chatId, key.id ?? void 0);
|
|
3873
|
+
if (!correlationKey) return;
|
|
3874
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3875
|
+
if (!pending) return;
|
|
3876
|
+
clearTimeout(pending.timer);
|
|
3877
|
+
this.#pendingIngressMessages.delete(correlationKey);
|
|
3878
|
+
if (this.#ingressDiagnostics) {
|
|
3879
|
+
this.#logger.info("Inbound WhatsApp message stanza reached provider terminal stage.", {
|
|
3880
|
+
stanzaId: pending.stanzaId,
|
|
3881
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3882
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3883
|
+
lastStage: pending.lastStage,
|
|
3884
|
+
outcome,
|
|
3885
|
+
durationMs: Date.now() - pending.observedAt
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
#resolveIngressByStanza(chatId, stanzaId, outcome) {
|
|
3890
|
+
const normalizedChatId = normalizeIngressJid(chatId);
|
|
3891
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizedChatId, stanzaId);
|
|
3892
|
+
if (!correlationKey) return;
|
|
3893
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3894
|
+
if (!pending) return;
|
|
3895
|
+
const remoteJid = pending.chatId ?? normalizedChatId;
|
|
3896
|
+
this.#resolveIngressMessage({
|
|
3897
|
+
id: pending.stanzaId,
|
|
3898
|
+
...remoteJid ? { remoteJid } : {}
|
|
3899
|
+
}, outcome);
|
|
3900
|
+
}
|
|
3662
3901
|
#handleMessage(event) {
|
|
3663
3902
|
const stored = event;
|
|
3664
3903
|
if (stored.key?.id && stored.message) this.#remember(stored);
|
|
@@ -3666,7 +3905,16 @@ var ZapoProvider = class {
|
|
|
3666
3905
|
if (quoted?.key.id && quoted.message) {
|
|
3667
3906
|
this.#remember(quoted);
|
|
3668
3907
|
}
|
|
3908
|
+
const deliveryKey = this.#messageDeliveryKey(stored.key);
|
|
3909
|
+
this.#resolveUnavailableRecovery(deliveryKey, stored.key);
|
|
3910
|
+
const decryptedCorrelationKey = this.#stanzaCorrelationKey(
|
|
3911
|
+
normalizeIngressJid(stored.key.remoteJid ?? void 0),
|
|
3912
|
+
stored.key.id ?? void 0
|
|
3913
|
+
);
|
|
3914
|
+
if (decryptedCorrelationKey) this.#recentDecryptedPayloads.delete(decryptedCorrelationKey);
|
|
3669
3915
|
if (this.#isOfflineMessage(stored)) {
|
|
3916
|
+
this.#ignoredOfflineMessages += 1;
|
|
3917
|
+
this.#emitMessageDiscarded("offline", stored.key);
|
|
3670
3918
|
this.#logger.debug("Ignored message queued before the current live connection.", {
|
|
3671
3919
|
messageId: stored.key.id ?? void 0,
|
|
3672
3920
|
chatId: stored.key.remoteJid ?? void 0,
|
|
@@ -3674,8 +3922,9 @@ var ZapoProvider = class {
|
|
|
3674
3922
|
});
|
|
3675
3923
|
return;
|
|
3676
3924
|
}
|
|
3677
|
-
const deliveryKey = this.#messageDeliveryKey(stored.key);
|
|
3678
3925
|
if (stored.key.id && this.#deliveredMessageStore.has(deliveryKey)) {
|
|
3926
|
+
this.#duplicateMessages += 1;
|
|
3927
|
+
this.#emitMessageDiscarded("duplicate", stored.key);
|
|
3679
3928
|
this.#logger.debug("Ignored duplicate Zapo message event.", {
|
|
3680
3929
|
messageId: stored.key.id,
|
|
3681
3930
|
chatId: stored.key.remoteJid ?? void 0
|
|
@@ -3683,10 +3932,28 @@ var ZapoProvider = class {
|
|
|
3683
3932
|
return;
|
|
3684
3933
|
}
|
|
3685
3934
|
const message = normalizeZapoMessage(event);
|
|
3686
|
-
if (!message)
|
|
3935
|
+
if (!message) {
|
|
3936
|
+
this.#normalizationFailures += 1;
|
|
3937
|
+
this.#emitMessageDiscarded("normalization_failed", stored.key);
|
|
3938
|
+
this.#logger.warn("Could not normalize incoming Zapo message.", {
|
|
3939
|
+
messageId: stored.key.id ?? void 0,
|
|
3940
|
+
chatId: stored.key.remoteJid ?? void 0
|
|
3941
|
+
});
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3687
3944
|
if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
|
|
3688
3945
|
this.#receivedMessages += 1;
|
|
3689
3946
|
this.#lastIncomingAt = /* @__PURE__ */ new Date();
|
|
3947
|
+
if (this.#ingressDiagnostics) {
|
|
3948
|
+
this.#logger.info("Inbound WhatsApp message emitted to WhaNext.", {
|
|
3949
|
+
messageId: message.id,
|
|
3950
|
+
chatId: message.chatId,
|
|
3951
|
+
userId: message.sender.id,
|
|
3952
|
+
contentKind: message.contentKind,
|
|
3953
|
+
hasMedia: message.media !== void 0,
|
|
3954
|
+
hasQuoted: message.quoted !== void 0
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3690
3957
|
this.#messageKeyStore.set(message.keys, stored);
|
|
3691
3958
|
if (message.quoted && quoted?.message) {
|
|
3692
3959
|
this.#messageKeyStore.set(
|
|
@@ -3696,6 +3963,166 @@ var ZapoProvider = class {
|
|
|
3696
3963
|
}
|
|
3697
3964
|
void this.#events.emit("message", message);
|
|
3698
3965
|
}
|
|
3966
|
+
#handleUnavailableMessage(event) {
|
|
3967
|
+
this.#unavailableMessages += 1;
|
|
3968
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
3969
|
+
const messageId = event.key.id || void 0;
|
|
3970
|
+
const chatId = event.key.remoteJid || void 0;
|
|
3971
|
+
const participantId = event.key.participant ?? event.key.participantAlt ?? void 0;
|
|
3972
|
+
this.#emitStability({
|
|
3973
|
+
type: "messageUnavailable",
|
|
3974
|
+
payload: {
|
|
3975
|
+
kind: event.kind,
|
|
3976
|
+
resendRequested: event.resendRequested,
|
|
3977
|
+
occurredAt,
|
|
3978
|
+
...messageId ? { messageId } : {},
|
|
3979
|
+
...chatId ? { chatId } : {},
|
|
3980
|
+
...participantId ? { participantId } : {}
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
if (!event.resendRequested) {
|
|
3984
|
+
this.#unavailableUnrecoverableMessages += 1;
|
|
3985
|
+
const log = event.kind === "other" ? this.#logger.warn.bind(this.#logger) : this.#logger.debug.bind(this.#logger);
|
|
3986
|
+
log("Incoming message is unavailable and was not queued for recovery.", {
|
|
3987
|
+
kind: event.kind,
|
|
3988
|
+
...messageId ? { messageId } : {},
|
|
3989
|
+
...chatId ? { chatId } : {},
|
|
3990
|
+
...participantId ? { participantId } : {}
|
|
3991
|
+
});
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
this.#resendRequestedMessages += 1;
|
|
3995
|
+
const deliveryKey = this.#messageDeliveryKey(event.key);
|
|
3996
|
+
const previous = this.#pendingUnavailableRecoveries.get(deliveryKey);
|
|
3997
|
+
if (previous) clearTimeout(previous.timer);
|
|
3998
|
+
const requestedAt = Date.now();
|
|
3999
|
+
const timer = setTimeout(() => {
|
|
4000
|
+
const pending = this.#pendingUnavailableRecoveries.get(deliveryKey);
|
|
4001
|
+
if (!pending || pending.requestedAt !== requestedAt) return;
|
|
4002
|
+
this.#pendingUnavailableRecoveries.delete(deliveryKey);
|
|
4003
|
+
this.#recoveryFailedMessages += 1;
|
|
4004
|
+
this.#markDegraded();
|
|
4005
|
+
const failedAt = /* @__PURE__ */ new Date();
|
|
4006
|
+
this.#emitStability({
|
|
4007
|
+
type: "messageRecoveryFailed",
|
|
4008
|
+
payload: {
|
|
4009
|
+
failedAt,
|
|
4010
|
+
waitedMs: failedAt.getTime() - pending.requestedAt,
|
|
4011
|
+
...pending.messageId ? { messageId: pending.messageId } : {},
|
|
4012
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
4013
|
+
...pending.participantId ? { participantId: pending.participantId } : {}
|
|
4014
|
+
}
|
|
4015
|
+
});
|
|
4016
|
+
this.#logger.warn("Unavailable message recovery did not arrive in time.", {
|
|
4017
|
+
...pending.messageId ? { messageId: pending.messageId } : {},
|
|
4018
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
4019
|
+
waitedMs: failedAt.getTime() - pending.requestedAt
|
|
4020
|
+
});
|
|
4021
|
+
}, MESSAGE_RECOVERY_TIMEOUT_MS);
|
|
4022
|
+
this.#pendingUnavailableRecoveries.set(deliveryKey, {
|
|
4023
|
+
requestedAt,
|
|
4024
|
+
timer,
|
|
4025
|
+
...messageId ? { messageId } : {},
|
|
4026
|
+
...chatId ? { chatId } : {},
|
|
4027
|
+
...participantId ? { participantId } : {}
|
|
4028
|
+
});
|
|
4029
|
+
this.#logger.info("Incoming message unavailable; primary-device resend requested.", {
|
|
4030
|
+
...messageId ? { messageId } : {},
|
|
4031
|
+
...chatId ? { chatId } : {},
|
|
4032
|
+
...participantId ? { participantId } : {}
|
|
4033
|
+
});
|
|
4034
|
+
}
|
|
4035
|
+
#resolveUnavailableRecovery(deliveryKey, key) {
|
|
4036
|
+
const pending = this.#pendingUnavailableRecoveries.get(deliveryKey);
|
|
4037
|
+
if (!pending) return;
|
|
4038
|
+
clearTimeout(pending.timer);
|
|
4039
|
+
this.#pendingUnavailableRecoveries.delete(deliveryKey);
|
|
4040
|
+
this.#recoveredMessages += 1;
|
|
4041
|
+
const recoveredAt = /* @__PURE__ */ new Date();
|
|
4042
|
+
const participantId = key.participant ?? key.participantAlt ?? pending.participantId;
|
|
4043
|
+
this.#emitStability({
|
|
4044
|
+
type: "messageRecovered",
|
|
4045
|
+
payload: {
|
|
4046
|
+
recoveredAt,
|
|
4047
|
+
recoveryMs: recoveredAt.getTime() - pending.requestedAt,
|
|
4048
|
+
...key.id ? { messageId: key.id } : pending.messageId ? { messageId: pending.messageId } : {},
|
|
4049
|
+
...key.remoteJid ? { chatId: key.remoteJid } : pending.chatId ? { chatId: pending.chatId } : {},
|
|
4050
|
+
...participantId ? { participantId } : {}
|
|
4051
|
+
}
|
|
4052
|
+
});
|
|
4053
|
+
this.#logger.info("Recovered unavailable message from the primary device.", {
|
|
4054
|
+
...key.id ? { messageId: key.id } : {},
|
|
4055
|
+
...key.remoteJid ? { chatId: key.remoteJid } : {},
|
|
4056
|
+
recoveryMs: recoveredAt.getTime() - pending.requestedAt
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
#handleDecryptedPayload(event) {
|
|
4060
|
+
this.#decryptedPayloads += 1;
|
|
4061
|
+
this.#markIngressDecrypted(event.chatJid, event.stanzaId);
|
|
4062
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizeIngressJid(event.chatJid), event.stanzaId);
|
|
4063
|
+
if (!correlationKey) return;
|
|
4064
|
+
const now = Date.now();
|
|
4065
|
+
this.#recentDecryptedPayloads.set(correlationKey, {
|
|
4066
|
+
observedAt: now,
|
|
4067
|
+
encType: event.encType
|
|
4068
|
+
});
|
|
4069
|
+
for (const [key, value] of this.#recentDecryptedPayloads) {
|
|
4070
|
+
if (now - value.observedAt > DECRYPT_CORRELATION_TTL_MS) {
|
|
4071
|
+
this.#recentDecryptedPayloads.delete(key);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
#handleUnhandledStanza(event) {
|
|
4076
|
+
this.#unhandledStanzas += 1;
|
|
4077
|
+
const normalizedChatId = normalizeIngressJid(event.chatJid);
|
|
4078
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizedChatId, event.stanzaId);
|
|
4079
|
+
const decrypted = correlationKey ? this.#recentDecryptedPayloads.get(correlationKey) : void 0;
|
|
4080
|
+
const now = Date.now();
|
|
4081
|
+
if (decrypted && now - decrypted.observedAt <= DECRYPT_CORRELATION_TTL_MS) {
|
|
4082
|
+
this.#decodeFailures += 1;
|
|
4083
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
4084
|
+
this.#markDegraded();
|
|
4085
|
+
this.#emitStability({
|
|
4086
|
+
type: "messageDecodeFailure",
|
|
4087
|
+
payload: {
|
|
4088
|
+
occurredAt: new Date(now),
|
|
4089
|
+
reason: event.reason,
|
|
4090
|
+
encType: decrypted.encType,
|
|
4091
|
+
...event.stanzaId ? { stanzaId: event.stanzaId } : {},
|
|
4092
|
+
...event.chatJid ? { chatId: event.chatJid } : {}
|
|
4093
|
+
}
|
|
4094
|
+
});
|
|
4095
|
+
this.#logger.warn("Decrypted incoming payload could not be decoded into a supported stanza.", {
|
|
4096
|
+
reason: event.reason,
|
|
4097
|
+
encType: decrypted.encType,
|
|
4098
|
+
...event.stanzaId ? { stanzaId: event.stanzaId } : {},
|
|
4099
|
+
...event.chatJid ? { chatId: event.chatJid } : {}
|
|
4100
|
+
});
|
|
4101
|
+
if (correlationKey) this.#recentDecryptedPayloads.delete(correlationKey);
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
4105
|
+
this.#logger.debug("Incoming stanza was not handled by Zapo.", {
|
|
4106
|
+
reason: event.reason,
|
|
4107
|
+
...event.stanzaId ? { stanzaId: event.stanzaId } : {},
|
|
4108
|
+
...event.chatJid ? { chatId: event.chatJid } : {}
|
|
4109
|
+
});
|
|
4110
|
+
}
|
|
4111
|
+
#emitMessageDiscarded(reason, key) {
|
|
4112
|
+
this.#emitStability({
|
|
4113
|
+
type: "messageDiscarded",
|
|
4114
|
+
payload: {
|
|
4115
|
+
reason,
|
|
4116
|
+
occurredAt: /* @__PURE__ */ new Date(),
|
|
4117
|
+
...key.id ? { messageId: key.id } : {},
|
|
4118
|
+
...key.remoteJid ? { chatId: key.remoteJid } : {}
|
|
4119
|
+
}
|
|
4120
|
+
});
|
|
4121
|
+
}
|
|
4122
|
+
#stanzaCorrelationKey(chatJid, stanzaId) {
|
|
4123
|
+
if (!stanzaId) return void 0;
|
|
4124
|
+
return `${chatJid ?? ""}:${stanzaId}`;
|
|
4125
|
+
}
|
|
3699
4126
|
#handleAddonEvent(event) {
|
|
3700
4127
|
const decrypted = this.#addonRecord(event.decrypted);
|
|
3701
4128
|
const protocol = this.#addonProtocolMessage(decrypted);
|
|
@@ -3953,6 +4380,11 @@ var ZapoProvider = class {
|
|
|
3953
4380
|
}
|
|
3954
4381
|
if (message === "failed to decrypt incoming message") {
|
|
3955
4382
|
this.#decryptFailures += 1;
|
|
4383
|
+
this.#resolveIngressByStanza(
|
|
4384
|
+
stringValue(context.from) ?? stringValue(context.groupJid),
|
|
4385
|
+
stringValue(context.id),
|
|
4386
|
+
"decrypt_failure"
|
|
4387
|
+
);
|
|
3956
4388
|
const detail = stringValue(context.message);
|
|
3957
4389
|
const kind = detail === "sender key id mismatch" ? "sender_key_mismatch" : "decrypt_failure";
|
|
3958
4390
|
if (kind === "sender_key_mismatch") this.#senderKeyMismatches += 1;
|
|
@@ -4433,11 +4865,16 @@ var ZapoProvider = class {
|
|
|
4433
4865
|
return mentions.map((mention) => typeof mention === "string" ? mention : mention.mentionId);
|
|
4434
4866
|
}
|
|
4435
4867
|
#toZapoKey(key) {
|
|
4868
|
+
const original = this.#messageKeyStore.get(key)?.key;
|
|
4869
|
+
const participant = original?.participant ?? key.participantId;
|
|
4436
4870
|
return {
|
|
4437
4871
|
id: key.id,
|
|
4438
4872
|
remoteJid: key.chatId,
|
|
4439
4873
|
fromMe: key.fromMe,
|
|
4440
|
-
...
|
|
4874
|
+
...original?.remoteJidAlt ? { remoteJidAlt: original.remoteJidAlt } : {},
|
|
4875
|
+
...participant ? { participant } : {},
|
|
4876
|
+
...original?.participantAlt ? { participantAlt: original.participantAlt } : {},
|
|
4877
|
+
...original?.addressingMode ? { addressingMode: original.addressingMode } : {}
|
|
4441
4878
|
};
|
|
4442
4879
|
}
|
|
4443
4880
|
#sent(result, chatId) {
|
|
@@ -5113,6 +5550,13 @@ function normalizeProviderTimeout(value, fallback) {
|
|
|
5113
5550
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
5114
5551
|
return Math.max(1e3, Math.floor(value));
|
|
5115
5552
|
}
|
|
5553
|
+
function normalizeIngressJid(value) {
|
|
5554
|
+
if (!value) return void 0;
|
|
5555
|
+
const at = value.indexOf("@");
|
|
5556
|
+
if (at <= 0) return value;
|
|
5557
|
+
const local = value.slice(0, at).replace(/:\d+$/, "");
|
|
5558
|
+
return `${local}${value.slice(at)}`;
|
|
5559
|
+
}
|
|
5116
5560
|
function detectCryptoBackend() {
|
|
5117
5561
|
const requested = process.env.ZAPO_NATIVE_BACKEND?.trim().toLowerCase() ?? "auto";
|
|
5118
5562
|
if (requested === "js" || requested === "none") return "js";
|
|
@@ -5213,7 +5657,9 @@ async function create(options = {}) {
|
|
|
5213
5657
|
...options.processOfflineMessages !== void 0 ? { processOfflineMessages: options.processOfflineMessages } : {},
|
|
5214
5658
|
...options.reconnect ? { reconnect: options.reconnect } : {},
|
|
5215
5659
|
...options.providerTimeouts?.connectTimeoutMs !== void 0 ? { connectTimeoutMs: options.providerTimeouts.connectTimeoutMs } : {},
|
|
5216
|
-
...options.providerTimeouts?.nodeQueryTimeoutMs !== void 0 ? { nodeQueryTimeoutMs: options.providerTimeouts.nodeQueryTimeoutMs } : {}
|
|
5660
|
+
...options.providerTimeouts?.nodeQueryTimeoutMs !== void 0 ? { nodeQueryTimeoutMs: options.providerTimeouts.nodeQueryTimeoutMs } : {},
|
|
5661
|
+
...options.providerDiagnostics?.ingress !== void 0 ? { ingressDiagnostics: options.providerDiagnostics.ingress } : {},
|
|
5662
|
+
...options.providerDiagnostics?.ingressStallTimeoutMs !== void 0 ? { ingressStallTimeoutMs: options.providerDiagnostics.ingressStallTimeoutMs } : {}
|
|
5217
5663
|
});
|
|
5218
5664
|
return new WhaNextApp(provider, {
|
|
5219
5665
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
@@ -5451,6 +5897,12 @@ function mergeCreateOptions(shared, account) {
|
|
|
5451
5897
|
...account.providerTimeouts
|
|
5452
5898
|
};
|
|
5453
5899
|
}
|
|
5900
|
+
if (shared.providerDiagnostics || account.providerDiagnostics) {
|
|
5901
|
+
merged.providerDiagnostics = {
|
|
5902
|
+
...shared.providerDiagnostics,
|
|
5903
|
+
...account.providerDiagnostics
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5454
5906
|
if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
|
|
5455
5907
|
merged.logger = {
|
|
5456
5908
|
...shared.logger,
|