@whanext/core 0.19.19 → 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 +13 -0
- package/README.md +13 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +217 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.20
|
|
4
|
+
|
|
5
|
+
### Ingress diagnostics
|
|
6
|
+
- Added opt-in provider ingress tracing with `providerDiagnostics.ingress`, correlating Zapo transport message stanzas through decrypt and terminal provider events without logging message content or decrypted bytes.
|
|
7
|
+
- Added a bounded ingress watchdog through `providerDiagnostics.ingressStallTimeoutMs`, defaulting to 10 seconds. A decoded `<message>` stanza that never becomes `message`, `message_unavailable`, a decode failure, or a decrypt failure emits `messageIngressStalled` and temporarily marks the provider as degraded.
|
|
8
|
+
- Added transport decode diagnostics from Zapo 1.8.0 `debug_transport_decode_error` with a typed `transportDecodeFailure` event. Only frame size and error metadata are exposed; raw frame bytes are never logged or emitted.
|
|
9
|
+
- Expanded `health().messaging` with `transportFramesIn`, `transportNodesIn`, `transportDecodeErrors`, `messageStanzasIn`, and `ingressStalls`.
|
|
10
|
+
- When ingress diagnostics are enabled, message stanzas log their stanza id, chat, participant, addressing mode, encryption types, decrypt stage, and terminal provider stage. Message text and plaintext payloads are never included.
|
|
11
|
+
|
|
12
|
+
### Reliability
|
|
13
|
+
- Correlates decrypt warnings with pending message stanzas so known Signal failures do not appear as generic ingress stalls.
|
|
14
|
+
- Normalizes device-qualified PN/LID JIDs before ingress correlation, keeping group and direct-message diagnostics aligned with Zapo message keys.
|
|
15
|
+
|
|
3
16
|
## 0.19.19
|
|
4
17
|
|
|
5
18
|
### Provider
|
package/README.md
CHANGED
|
@@ -347,6 +347,19 @@ console.log(messaging.duplicates);
|
|
|
347
347
|
|
|
348
348
|
`decryptedPayloads` conta payloads `<enc>` individuais, portanto pode ser maior que `received` em fanout para vários devices. O WhaNext usa esse sinal apenas para correlação e nunca expõe os bytes descriptografados no health, eventos públicos ou logs.
|
|
349
349
|
|
|
350
|
+
Para diagnosticar mensagens que chegam ao socket mas não viram um `Message` público, ative a trilha de ingresso:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
const app = await create({
|
|
354
|
+
providerDiagnostics: {
|
|
355
|
+
ingress: true,
|
|
356
|
+
ingressStallTimeoutMs: 10_000,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Com essa opção, o provider registra apenas metadados seguros de stanzas `<message>` e correlaciona `debug_transport_node_in`, `debug_decrypted_payload`, `message`, `message_unavailable`, falhas de decrypt e falhas de decode. `health().messaging` também expõe `transportFramesIn`, `transportNodesIn`, `transportDecodeErrors`, `messageStanzasIn` e `ingressStalls`. Frames brutos, bytes descriptografados e texto das mensagens nunca são escritos nos logs.
|
|
362
|
+
|
|
350
363
|
```ts
|
|
351
364
|
server.get('/health', async () => app.health());
|
|
352
365
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -326,6 +326,11 @@ interface ProviderMessagingHealth {
|
|
|
326
326
|
ignoredOffline: number;
|
|
327
327
|
duplicates: number;
|
|
328
328
|
normalizationFailures: number;
|
|
329
|
+
transportFramesIn: number;
|
|
330
|
+
transportNodesIn: number;
|
|
331
|
+
transportDecodeErrors: number;
|
|
332
|
+
messageStanzasIn: number;
|
|
333
|
+
ingressStalls: number;
|
|
329
334
|
lastIncomingAt?: Date;
|
|
330
335
|
lastOutgoingAt?: Date;
|
|
331
336
|
}
|
|
@@ -401,6 +406,23 @@ interface MessageDiscardedEvent {
|
|
|
401
406
|
messageId?: string;
|
|
402
407
|
chatId?: string;
|
|
403
408
|
}
|
|
409
|
+
type MessageIngressStage = 'stanza_received' | 'decrypted';
|
|
410
|
+
interface MessageIngressStalledEvent {
|
|
411
|
+
occurredAt: Date;
|
|
412
|
+
waitedMs: number;
|
|
413
|
+
lastStage: MessageIngressStage;
|
|
414
|
+
stanzaId: string;
|
|
415
|
+
chatId?: string;
|
|
416
|
+
participantId?: string;
|
|
417
|
+
stanzaType?: string;
|
|
418
|
+
addressingMode?: string;
|
|
419
|
+
}
|
|
420
|
+
interface TransportDecodeFailureEvent {
|
|
421
|
+
occurredAt: Date;
|
|
422
|
+
frameBytes: number;
|
|
423
|
+
errorName: string;
|
|
424
|
+
errorMessage: string;
|
|
425
|
+
}
|
|
404
426
|
type ProviderStabilityEvent = {
|
|
405
427
|
type: 'groupMetadataRecovered';
|
|
406
428
|
payload: GroupMetadataRecoveredEvent;
|
|
@@ -422,6 +444,12 @@ type ProviderStabilityEvent = {
|
|
|
422
444
|
} | {
|
|
423
445
|
type: 'messageDiscarded';
|
|
424
446
|
payload: MessageDiscardedEvent;
|
|
447
|
+
} | {
|
|
448
|
+
type: 'messageIngressStalled';
|
|
449
|
+
payload: MessageIngressStalledEvent;
|
|
450
|
+
} | {
|
|
451
|
+
type: 'transportDecodeFailure';
|
|
452
|
+
payload: TransportDecodeFailureEvent;
|
|
425
453
|
} | {
|
|
426
454
|
type: 'healthRefresh';
|
|
427
455
|
payload: {
|
|
@@ -1003,6 +1031,8 @@ interface AppEvents {
|
|
|
1003
1031
|
messageRecoveryFailed: MessageRecoveryFailedEvent;
|
|
1004
1032
|
messageDecodeFailure: MessageDecodeFailureEvent;
|
|
1005
1033
|
messageDiscarded: MessageDiscardedEvent;
|
|
1034
|
+
messageIngressStalled: MessageIngressStalledEvent;
|
|
1035
|
+
transportDecodeFailure: TransportDecodeFailureEvent;
|
|
1006
1036
|
commandQueueTimeout: CommandQueueTimeoutEvent;
|
|
1007
1037
|
commandQueueFull: CommandQueueFullEvent;
|
|
1008
1038
|
}
|
|
@@ -1068,6 +1098,10 @@ interface ProviderTimeoutOptions {
|
|
|
1068
1098
|
connectTimeoutMs?: number;
|
|
1069
1099
|
nodeQueryTimeoutMs?: number;
|
|
1070
1100
|
}
|
|
1101
|
+
interface ProviderDiagnosticsOptions {
|
|
1102
|
+
ingress?: boolean;
|
|
1103
|
+
ingressStallTimeoutMs?: number;
|
|
1104
|
+
}
|
|
1071
1105
|
interface ReconnectOptions {
|
|
1072
1106
|
enabled?: boolean;
|
|
1073
1107
|
maxAttempts?: number;
|
|
@@ -1085,6 +1119,7 @@ interface CreateOptions {
|
|
|
1085
1119
|
router?: Omit<RouterOptions, 'prefix'>;
|
|
1086
1120
|
reconnect?: ReconnectOptions;
|
|
1087
1121
|
providerTimeouts?: ProviderTimeoutOptions;
|
|
1122
|
+
providerDiagnostics?: ProviderDiagnosticsOptions;
|
|
1088
1123
|
messageCacheSize?: number;
|
|
1089
1124
|
processOfflineMessages?: boolean;
|
|
1090
1125
|
provider?: WhatsAppProvider;
|
|
@@ -1172,4 +1207,4 @@ declare class SqliteMuteStore implements MuteStore {
|
|
|
1172
1207
|
close(): void;
|
|
1173
1208
|
}
|
|
1174
1209
|
|
|
1175
|
-
export { AccountService, type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type ButtonsContent, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandAccountContext, type CommandCatalogOptions, type CommandCatalogView, type CommandChatContext, type CommandConcurrency, type CommandConcurrencyContext, type CommandConcurrencyEvent, type CommandConcurrencyHealth, type CommandContext, type CommandCooldown, type CommandDefinition, type CommandErrorHandler, type CommandGroupContext, type CommandGroupDefinition, type CommandGuard, type CommandHelpOptions, type CommandHooks, type CommandLocalization, type CommandMetadata, type CommandMiddleware, type CommandOptionDefinition, type CommandOptionSchema, type CommandOptionValue, type CommandOptionValues, type CommandQueueFullEvent, type CommandQueueTimeoutEvent, type CommandRegistrar, CommandRouter, type CommandRouterEvents, type CommandRuntimeServices, type CommandScope, type ConcurrencyStrategy, type ConnectionRecoveredEvent, type ConnectionState, type ConnectionUpdate, type CopyCodeButton, type CreateMultiOptions, type CreateOptions, type CryptoAccelerationBackend, type CryptoDegradationKind, type CryptoDegradedEvent, DeferredReply, type DownloadedMedia, type DurationOption, type EnumOption, type ExecutableCommandDefinition, type GroupAccess, type GroupAddressingMode, type GroupMetadataRecoveredEvent, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type GuardResult, type HealthChangedEvent, type ImageContent, type InteractiveResponse, type InteractiveResponseKind, type InviteResult, type LinkButton, type ListContent, type ListRow, type ListSection, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MemoryCacheStats, type MentionTarget, type Message, type MessageButton, type MessageContent, type MessageContentKind, type MessageDecodeFailureEvent, type MessageDeleted, type MessageDiscardReason, type MessageDiscardedEvent, type MessageEdited, type MessageKey, type MessageMedia, type MessagePayloadKind, type MessageProtocolKind, type MessageRecoveredEvent, type MessageRecoveryFailedEvent, type MessageUnavailableEvent, type MultiAccountOptions, type MultiAppEvent, type MultiAppHealth, MultiCommandRouter, type MultiLoadCommandsResult, type MultiLoginOptions, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type NumberOption, ParsedCommandOptions, type ParticipantUpdateResult, type PollContent, type PresenceState, type ProviderConnectionHealth, type ProviderCryptoHealth, type ProviderGroupHealth, type ProviderHealth, type ProviderMessagingHealth, type ProviderStabilityEvent, type ProviderTimeoutHealth, type ProviderTimeoutOptions, type QuickReplyButton, type QuotedMessage, type ReconnectOptions, type RegisteredCommand, type RemoveMuteResult, type ReplyOptions, type RepostMessageOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StabilityHealthStatus, type StickerContent, type StoredMute, type StringOption, type TextContent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, WhaNextMultiApp, type WhatsAppProvider, create, createMulti, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };
|
|
1210
|
+
export { AccountService, type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, type BooleanOption, Browser, type ButtonsContent, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandAccountContext, type CommandCatalogOptions, type CommandCatalogView, type CommandChatContext, type CommandConcurrency, type CommandConcurrencyContext, type CommandConcurrencyEvent, type CommandConcurrencyHealth, type CommandContext, type CommandCooldown, type CommandDefinition, type CommandErrorHandler, type CommandGroupContext, type CommandGroupDefinition, type CommandGuard, type CommandHelpOptions, type CommandHooks, type CommandLocalization, type CommandMetadata, type CommandMiddleware, type CommandOptionDefinition, type CommandOptionSchema, type CommandOptionValue, type CommandOptionValues, type CommandQueueFullEvent, type CommandQueueTimeoutEvent, type CommandRegistrar, CommandRouter, type CommandRouterEvents, type CommandRuntimeServices, type CommandScope, type ConcurrencyStrategy, type ConnectionRecoveredEvent, type ConnectionState, type ConnectionUpdate, type CopyCodeButton, type CreateMultiOptions, type CreateOptions, type CryptoAccelerationBackend, type CryptoDegradationKind, type CryptoDegradedEvent, DeferredReply, type DownloadedMedia, type DurationOption, type EnumOption, type ExecutableCommandDefinition, type GroupAccess, type GroupAddressingMode, type GroupMetadataRecoveredEvent, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type GuardResult, type HealthChangedEvent, type ImageContent, type InteractiveResponse, type InteractiveResponseKind, type InviteResult, type LinkButton, type ListContent, type ListRow, type ListSection, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MemoryCacheStats, type MentionTarget, type Message, type MessageButton, type MessageContent, type MessageContentKind, type MessageDecodeFailureEvent, type MessageDeleted, type MessageDiscardReason, type MessageDiscardedEvent, type MessageEdited, type MessageIngressStage, type MessageIngressStalledEvent, type MessageKey, type MessageMedia, type MessagePayloadKind, type MessageProtocolKind, type MessageRecoveredEvent, type MessageRecoveryFailedEvent, type MessageUnavailableEvent, type MultiAccountOptions, type MultiAppEvent, type MultiAppHealth, MultiCommandRouter, type MultiLoadCommandsResult, type MultiLoginOptions, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type NumberOption, ParsedCommandOptions, type ParticipantUpdateResult, type PollContent, type PresenceState, type ProviderConnectionHealth, type ProviderCryptoHealth, type ProviderDiagnosticsOptions, type ProviderGroupHealth, type ProviderHealth, type ProviderMessagingHealth, type ProviderStabilityEvent, type ProviderTimeoutHealth, type ProviderTimeoutOptions, type QuickReplyButton, type QuotedMessage, type ReconnectOptions, type RegisteredCommand, type RemoveMuteResult, type ReplyOptions, type RepostMessageOptions, type RouterOptions, type SentMessage, SqliteMuteStore, type StabilityHealthStatus, type StickerContent, type StoredMute, type StringOption, type TextContent, type TransportDecodeFailureEvent, User, type UserData, type UserOption, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, WhaNextMultiApp, type WhatsAppProvider, create, createMulti, defineCommand, defineCommandGroup, defineCommands, defineSubcommand, guards, isCommandGroup, loadCommands, option, toWhaNextError };
|
package/dist/index.js
CHANGED
|
@@ -2357,7 +2357,12 @@ var WhaNextApp = class {
|
|
|
2357
2357
|
unhandledStanzas: 0,
|
|
2358
2358
|
ignoredOffline: 0,
|
|
2359
2359
|
duplicates: 0,
|
|
2360
|
-
normalizationFailures: 0
|
|
2360
|
+
normalizationFailures: 0,
|
|
2361
|
+
transportFramesIn: 0,
|
|
2362
|
+
transportNodesIn: 0,
|
|
2363
|
+
transportDecodeErrors: 0,
|
|
2364
|
+
messageStanzasIn: 0,
|
|
2365
|
+
ingressStalls: 0
|
|
2361
2366
|
};
|
|
2362
2367
|
const crypto = provider?.crypto ?? {
|
|
2363
2368
|
backend: "unknown",
|
|
@@ -2488,6 +2493,10 @@ var WhaNextApp = class {
|
|
|
2488
2493
|
await this.#events.emit("messageDecodeFailure", event.payload);
|
|
2489
2494
|
} else if (event.type === "messageDiscarded") {
|
|
2490
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);
|
|
2491
2500
|
}
|
|
2492
2501
|
await this.#refreshHealthState();
|
|
2493
2502
|
});
|
|
@@ -3157,6 +3166,7 @@ var sharedMediaProcessor = createMediaProcessor();
|
|
|
3157
3166
|
var REMOTE_MEDIA_TIMEOUT_MS = 12e4;
|
|
3158
3167
|
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
3159
3168
|
var DEFAULT_NODE_QUERY_TIMEOUT_MS = 3e4;
|
|
3169
|
+
var DEFAULT_INGRESS_STALL_TIMEOUT_MS = 1e4;
|
|
3160
3170
|
var PROVIDER_DEGRADED_WINDOW_MS = 12e4;
|
|
3161
3171
|
var GROUP_METADATA_CACHE_TTL_MS = 18e4;
|
|
3162
3172
|
var DEVICE_LIST_CACHE_TTL_MS = 18e4;
|
|
@@ -3176,6 +3186,7 @@ var ZapoProvider = class {
|
|
|
3176
3186
|
#deliveredMessageStore = /* @__PURE__ */ new Set();
|
|
3177
3187
|
#pendingUnavailableRecoveries = /* @__PURE__ */ new Map();
|
|
3178
3188
|
#recentDecryptedPayloads = /* @__PURE__ */ new Map();
|
|
3189
|
+
#pendingIngressMessages = /* @__PURE__ */ new Map();
|
|
3179
3190
|
#handledProtocolStore = /* @__PURE__ */ new Set();
|
|
3180
3191
|
#callCreatorStore = /* @__PURE__ */ new Map();
|
|
3181
3192
|
#groupMetadataRecoveryAt = /* @__PURE__ */ new Map();
|
|
@@ -3184,6 +3195,8 @@ var ZapoProvider = class {
|
|
|
3184
3195
|
#cryptoBackend;
|
|
3185
3196
|
#connectTimeoutMs;
|
|
3186
3197
|
#nodeQueryTimeoutMs;
|
|
3198
|
+
#ingressDiagnostics;
|
|
3199
|
+
#ingressStallTimeoutMs;
|
|
3187
3200
|
#protocolMutationQueue = Promise.resolve();
|
|
3188
3201
|
#state = "idle";
|
|
3189
3202
|
#client;
|
|
@@ -3217,6 +3230,11 @@ var ZapoProvider = class {
|
|
|
3217
3230
|
#ignoredOfflineMessages = 0;
|
|
3218
3231
|
#duplicateMessages = 0;
|
|
3219
3232
|
#normalizationFailures = 0;
|
|
3233
|
+
#transportFramesIn = 0;
|
|
3234
|
+
#transportNodesIn = 0;
|
|
3235
|
+
#transportDecodeErrors = 0;
|
|
3236
|
+
#messageStanzasIn = 0;
|
|
3237
|
+
#ingressStalls = 0;
|
|
3220
3238
|
#lastIncomingAt;
|
|
3221
3239
|
#lastOutgoingAt;
|
|
3222
3240
|
#decryptFailures = 0;
|
|
@@ -3234,6 +3252,8 @@ var ZapoProvider = class {
|
|
|
3234
3252
|
this.#messageCacheSize = Math.max(1, options.messageCacheSize ?? 1e3);
|
|
3235
3253
|
this.#connectTimeoutMs = normalizeProviderTimeout(options.connectTimeoutMs, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
3236
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);
|
|
3237
3257
|
this.#cryptoBackend = detectCryptoBackend();
|
|
3238
3258
|
}
|
|
3239
3259
|
on(event, listener) {
|
|
@@ -3268,6 +3288,11 @@ var ZapoProvider = class {
|
|
|
3268
3288
|
ignoredOffline: this.#ignoredOfflineMessages,
|
|
3269
3289
|
duplicates: this.#duplicateMessages,
|
|
3270
3290
|
normalizationFailures: this.#normalizationFailures,
|
|
3291
|
+
transportFramesIn: this.#transportFramesIn,
|
|
3292
|
+
transportNodesIn: this.#transportNodesIn,
|
|
3293
|
+
transportDecodeErrors: this.#transportDecodeErrors,
|
|
3294
|
+
messageStanzasIn: this.#messageStanzasIn,
|
|
3295
|
+
ingressStalls: this.#ingressStalls,
|
|
3271
3296
|
...this.#lastIncomingAt ? { lastIncomingAt: new Date(this.#lastIncomingAt) } : {},
|
|
3272
3297
|
...this.#lastOutgoingAt ? { lastOutgoingAt: new Date(this.#lastOutgoingAt) } : {}
|
|
3273
3298
|
},
|
|
@@ -3318,6 +3343,10 @@ var ZapoProvider = class {
|
|
|
3318
3343
|
clearTimeout(pending.timer);
|
|
3319
3344
|
}
|
|
3320
3345
|
this.#pendingUnavailableRecoveries.clear();
|
|
3346
|
+
for (const pending of this.#pendingIngressMessages.values()) {
|
|
3347
|
+
clearTimeout(pending.timer);
|
|
3348
|
+
}
|
|
3349
|
+
this.#pendingIngressMessages.clear();
|
|
3321
3350
|
this.#recentDecryptedPayloads.clear();
|
|
3322
3351
|
const client = this.#client;
|
|
3323
3352
|
this.#connectPromise = void 0;
|
|
@@ -3657,7 +3686,22 @@ var ZapoProvider = class {
|
|
|
3657
3686
|
);
|
|
3658
3687
|
void this.#stopTerminalConnection(client, error);
|
|
3659
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
|
+
});
|
|
3660
3703
|
client.on("message", (event) => {
|
|
3704
|
+
this.#resolveIngressMessage(event.key, "message_event");
|
|
3661
3705
|
const protocol = this.#protocolMessage(event);
|
|
3662
3706
|
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3663
3707
|
this.#enqueueProtocolEvent({
|
|
@@ -3681,6 +3725,7 @@ var ZapoProvider = class {
|
|
|
3681
3725
|
this.#handleMessage(event);
|
|
3682
3726
|
});
|
|
3683
3727
|
client.on("message_unavailable", (event) => {
|
|
3728
|
+
this.#resolveIngressMessage(event.key, "unavailable");
|
|
3684
3729
|
this.#handleUnavailableMessage(event);
|
|
3685
3730
|
});
|
|
3686
3731
|
client.on("debug_decrypted_payload", (event) => {
|
|
@@ -3720,6 +3765,139 @@ var ZapoProvider = class {
|
|
|
3720
3765
|
void this.#handleConnectionEvent(client, event);
|
|
3721
3766
|
});
|
|
3722
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
|
+
}
|
|
3723
3901
|
#handleMessage(event) {
|
|
3724
3902
|
const stored = event;
|
|
3725
3903
|
if (stored.key?.id && stored.message) this.#remember(stored);
|
|
@@ -3730,7 +3908,7 @@ var ZapoProvider = class {
|
|
|
3730
3908
|
const deliveryKey = this.#messageDeliveryKey(stored.key);
|
|
3731
3909
|
this.#resolveUnavailableRecovery(deliveryKey, stored.key);
|
|
3732
3910
|
const decryptedCorrelationKey = this.#stanzaCorrelationKey(
|
|
3733
|
-
stored.key.remoteJid ?? void 0,
|
|
3911
|
+
normalizeIngressJid(stored.key.remoteJid ?? void 0),
|
|
3734
3912
|
stored.key.id ?? void 0
|
|
3735
3913
|
);
|
|
3736
3914
|
if (decryptedCorrelationKey) this.#recentDecryptedPayloads.delete(decryptedCorrelationKey);
|
|
@@ -3766,6 +3944,16 @@ var ZapoProvider = class {
|
|
|
3766
3944
|
if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
|
|
3767
3945
|
this.#receivedMessages += 1;
|
|
3768
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
|
+
}
|
|
3769
3957
|
this.#messageKeyStore.set(message.keys, stored);
|
|
3770
3958
|
if (message.quoted && quoted?.message) {
|
|
3771
3959
|
this.#messageKeyStore.set(
|
|
@@ -3870,7 +4058,8 @@ var ZapoProvider = class {
|
|
|
3870
4058
|
}
|
|
3871
4059
|
#handleDecryptedPayload(event) {
|
|
3872
4060
|
this.#decryptedPayloads += 1;
|
|
3873
|
-
|
|
4061
|
+
this.#markIngressDecrypted(event.chatJid, event.stanzaId);
|
|
4062
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizeIngressJid(event.chatJid), event.stanzaId);
|
|
3874
4063
|
if (!correlationKey) return;
|
|
3875
4064
|
const now = Date.now();
|
|
3876
4065
|
this.#recentDecryptedPayloads.set(correlationKey, {
|
|
@@ -3885,11 +4074,13 @@ var ZapoProvider = class {
|
|
|
3885
4074
|
}
|
|
3886
4075
|
#handleUnhandledStanza(event) {
|
|
3887
4076
|
this.#unhandledStanzas += 1;
|
|
3888
|
-
const
|
|
4077
|
+
const normalizedChatId = normalizeIngressJid(event.chatJid);
|
|
4078
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizedChatId, event.stanzaId);
|
|
3889
4079
|
const decrypted = correlationKey ? this.#recentDecryptedPayloads.get(correlationKey) : void 0;
|
|
3890
4080
|
const now = Date.now();
|
|
3891
4081
|
if (decrypted && now - decrypted.observedAt <= DECRYPT_CORRELATION_TTL_MS) {
|
|
3892
4082
|
this.#decodeFailures += 1;
|
|
4083
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
3893
4084
|
this.#markDegraded();
|
|
3894
4085
|
this.#emitStability({
|
|
3895
4086
|
type: "messageDecodeFailure",
|
|
@@ -3910,6 +4101,7 @@ var ZapoProvider = class {
|
|
|
3910
4101
|
if (correlationKey) this.#recentDecryptedPayloads.delete(correlationKey);
|
|
3911
4102
|
return;
|
|
3912
4103
|
}
|
|
4104
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
3913
4105
|
this.#logger.debug("Incoming stanza was not handled by Zapo.", {
|
|
3914
4106
|
reason: event.reason,
|
|
3915
4107
|
...event.stanzaId ? { stanzaId: event.stanzaId } : {},
|
|
@@ -4188,6 +4380,11 @@ var ZapoProvider = class {
|
|
|
4188
4380
|
}
|
|
4189
4381
|
if (message === "failed to decrypt incoming message") {
|
|
4190
4382
|
this.#decryptFailures += 1;
|
|
4383
|
+
this.#resolveIngressByStanza(
|
|
4384
|
+
stringValue(context.from) ?? stringValue(context.groupJid),
|
|
4385
|
+
stringValue(context.id),
|
|
4386
|
+
"decrypt_failure"
|
|
4387
|
+
);
|
|
4191
4388
|
const detail = stringValue(context.message);
|
|
4192
4389
|
const kind = detail === "sender key id mismatch" ? "sender_key_mismatch" : "decrypt_failure";
|
|
4193
4390
|
if (kind === "sender_key_mismatch") this.#senderKeyMismatches += 1;
|
|
@@ -5353,6 +5550,13 @@ function normalizeProviderTimeout(value, fallback) {
|
|
|
5353
5550
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
5354
5551
|
return Math.max(1e3, Math.floor(value));
|
|
5355
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
|
+
}
|
|
5356
5560
|
function detectCryptoBackend() {
|
|
5357
5561
|
const requested = process.env.ZAPO_NATIVE_BACKEND?.trim().toLowerCase() ?? "auto";
|
|
5358
5562
|
if (requested === "js" || requested === "none") return "js";
|
|
@@ -5453,7 +5657,9 @@ async function create(options = {}) {
|
|
|
5453
5657
|
...options.processOfflineMessages !== void 0 ? { processOfflineMessages: options.processOfflineMessages } : {},
|
|
5454
5658
|
...options.reconnect ? { reconnect: options.reconnect } : {},
|
|
5455
5659
|
...options.providerTimeouts?.connectTimeoutMs !== void 0 ? { connectTimeoutMs: options.providerTimeouts.connectTimeoutMs } : {},
|
|
5456
|
-
...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 } : {}
|
|
5457
5663
|
});
|
|
5458
5664
|
return new WhaNextApp(provider, {
|
|
5459
5665
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
@@ -5691,6 +5897,12 @@ function mergeCreateOptions(shared, account) {
|
|
|
5691
5897
|
...account.providerTimeouts
|
|
5692
5898
|
};
|
|
5693
5899
|
}
|
|
5900
|
+
if (shared.providerDiagnostics || account.providerDiagnostics) {
|
|
5901
|
+
merged.providerDiagnostics = {
|
|
5902
|
+
...shared.providerDiagnostics,
|
|
5903
|
+
...account.providerDiagnostics
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5694
5906
|
if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
|
|
5695
5907
|
merged.logger = {
|
|
5696
5908
|
...shared.logger,
|