@whanext/core 0.19.19 → 0.19.21
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 +21 -0
- package/README.md +13 -0
- package/dist/index.d.ts +45 -3
- package/dist/index.js +224 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `MessageService.edit()` accepts an optional protocol-level `id` override for the outgoing edit envelope.
|
|
7
|
+
|
|
8
|
+
### Zapo
|
|
9
|
+
- Forwards the optional edit `id` to `WaSendMessageOptions.id`, enabling specialized moderation workflows without exposing the provider client.
|
|
10
|
+
|
|
11
|
+
## 0.19.20
|
|
12
|
+
|
|
13
|
+
### Ingress diagnostics
|
|
14
|
+
- 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.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
- Expanded `health().messaging` with `transportFramesIn`, `transportNodesIn`, `transportDecodeErrors`, `messageStanzasIn`, and `ingressStalls`.
|
|
18
|
+
- 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.
|
|
19
|
+
|
|
20
|
+
### Reliability
|
|
21
|
+
- Correlates decrypt warnings with pending message stanzas so known Signal failures do not appear as generic ingress stalls.
|
|
22
|
+
- Normalizes device-qualified PN/LID JIDs before ingress correlation, keeping group and direct-message diagnostics aligned with Zapo message keys.
|
|
23
|
+
|
|
3
24
|
## 0.19.19
|
|
4
25
|
|
|
5
26
|
### 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
|
@@ -163,6 +163,13 @@ type MentionTarget = string | User;
|
|
|
163
163
|
interface RepostMessageOptions {
|
|
164
164
|
mentions?: readonly MentionTarget[];
|
|
165
165
|
}
|
|
166
|
+
interface EditMessageOptions {
|
|
167
|
+
/**
|
|
168
|
+
* Overrides the stanza id used by the outgoing edit envelope.
|
|
169
|
+
* Advanced/protocol-level option; omitted for normal edits.
|
|
170
|
+
*/
|
|
171
|
+
id?: string;
|
|
172
|
+
}
|
|
166
173
|
interface TextContent {
|
|
167
174
|
text: string;
|
|
168
175
|
mentions?: MentionTarget[];
|
|
@@ -326,6 +333,11 @@ interface ProviderMessagingHealth {
|
|
|
326
333
|
ignoredOffline: number;
|
|
327
334
|
duplicates: number;
|
|
328
335
|
normalizationFailures: number;
|
|
336
|
+
transportFramesIn: number;
|
|
337
|
+
transportNodesIn: number;
|
|
338
|
+
transportDecodeErrors: number;
|
|
339
|
+
messageStanzasIn: number;
|
|
340
|
+
ingressStalls: number;
|
|
329
341
|
lastIncomingAt?: Date;
|
|
330
342
|
lastOutgoingAt?: Date;
|
|
331
343
|
}
|
|
@@ -401,6 +413,23 @@ interface MessageDiscardedEvent {
|
|
|
401
413
|
messageId?: string;
|
|
402
414
|
chatId?: string;
|
|
403
415
|
}
|
|
416
|
+
type MessageIngressStage = 'stanza_received' | 'decrypted';
|
|
417
|
+
interface MessageIngressStalledEvent {
|
|
418
|
+
occurredAt: Date;
|
|
419
|
+
waitedMs: number;
|
|
420
|
+
lastStage: MessageIngressStage;
|
|
421
|
+
stanzaId: string;
|
|
422
|
+
chatId?: string;
|
|
423
|
+
participantId?: string;
|
|
424
|
+
stanzaType?: string;
|
|
425
|
+
addressingMode?: string;
|
|
426
|
+
}
|
|
427
|
+
interface TransportDecodeFailureEvent {
|
|
428
|
+
occurredAt: Date;
|
|
429
|
+
frameBytes: number;
|
|
430
|
+
errorName: string;
|
|
431
|
+
errorMessage: string;
|
|
432
|
+
}
|
|
404
433
|
type ProviderStabilityEvent = {
|
|
405
434
|
type: 'groupMetadataRecovered';
|
|
406
435
|
payload: GroupMetadataRecoveredEvent;
|
|
@@ -422,6 +451,12 @@ type ProviderStabilityEvent = {
|
|
|
422
451
|
} | {
|
|
423
452
|
type: 'messageDiscarded';
|
|
424
453
|
payload: MessageDiscardedEvent;
|
|
454
|
+
} | {
|
|
455
|
+
type: 'messageIngressStalled';
|
|
456
|
+
payload: MessageIngressStalledEvent;
|
|
457
|
+
} | {
|
|
458
|
+
type: 'transportDecodeFailure';
|
|
459
|
+
payload: TransportDecodeFailureEvent;
|
|
425
460
|
} | {
|
|
426
461
|
type: 'healthRefresh';
|
|
427
462
|
payload: {
|
|
@@ -452,7 +487,7 @@ interface WhatsAppProvider {
|
|
|
452
487
|
repostMessage(source: MessageKey, chatId: string, options?: RepostMessageOptions): Promise<SentMessage>;
|
|
453
488
|
reactToMessage(key: MessageKey, emoji?: string): Promise<SentMessage>;
|
|
454
489
|
downloadMedia(key: MessageKey): Promise<DownloadedMedia>;
|
|
455
|
-
editMessage(key: MessageKey, content: string): Promise<SentMessage>;
|
|
490
|
+
editMessage(key: MessageKey, content: string, options?: EditMessageOptions): Promise<SentMessage>;
|
|
456
491
|
deleteMessage(key: MessageKey): Promise<void>;
|
|
457
492
|
getGroup(groupId: string): Promise<GroupSnapshot>;
|
|
458
493
|
setGroupAccess(groupId: string, access: GroupAccess): Promise<void>;
|
|
@@ -658,7 +693,7 @@ declare class MessageService {
|
|
|
658
693
|
* media and structured WhatsApp messages as well as text.
|
|
659
694
|
*/
|
|
660
695
|
repost(source: Message | MessageKey, chatId: string, options?: RepostMessageOptions): Promise<SentMessage>;
|
|
661
|
-
edit(message: Message | SentMessage | MessageKey, text: string): Promise<SentMessage>;
|
|
696
|
+
edit(message: Message | SentMessage | MessageKey, text: string, options?: EditMessageOptions): Promise<SentMessage>;
|
|
662
697
|
delete(message: Message | SentMessage | MessageKey): Promise<void>;
|
|
663
698
|
react(message: Message | SentMessage | MessageKey, emoji: string): Promise<SentMessage>;
|
|
664
699
|
unreact(message: Message | SentMessage | MessageKey): Promise<SentMessage>;
|
|
@@ -1003,6 +1038,8 @@ interface AppEvents {
|
|
|
1003
1038
|
messageRecoveryFailed: MessageRecoveryFailedEvent;
|
|
1004
1039
|
messageDecodeFailure: MessageDecodeFailureEvent;
|
|
1005
1040
|
messageDiscarded: MessageDiscardedEvent;
|
|
1041
|
+
messageIngressStalled: MessageIngressStalledEvent;
|
|
1042
|
+
transportDecodeFailure: TransportDecodeFailureEvent;
|
|
1006
1043
|
commandQueueTimeout: CommandQueueTimeoutEvent;
|
|
1007
1044
|
commandQueueFull: CommandQueueFullEvent;
|
|
1008
1045
|
}
|
|
@@ -1068,6 +1105,10 @@ interface ProviderTimeoutOptions {
|
|
|
1068
1105
|
connectTimeoutMs?: number;
|
|
1069
1106
|
nodeQueryTimeoutMs?: number;
|
|
1070
1107
|
}
|
|
1108
|
+
interface ProviderDiagnosticsOptions {
|
|
1109
|
+
ingress?: boolean;
|
|
1110
|
+
ingressStallTimeoutMs?: number;
|
|
1111
|
+
}
|
|
1071
1112
|
interface ReconnectOptions {
|
|
1072
1113
|
enabled?: boolean;
|
|
1073
1114
|
maxAttempts?: number;
|
|
@@ -1085,6 +1126,7 @@ interface CreateOptions {
|
|
|
1085
1126
|
router?: Omit<RouterOptions, 'prefix'>;
|
|
1086
1127
|
reconnect?: ReconnectOptions;
|
|
1087
1128
|
providerTimeouts?: ProviderTimeoutOptions;
|
|
1129
|
+
providerDiagnostics?: ProviderDiagnosticsOptions;
|
|
1088
1130
|
messageCacheSize?: number;
|
|
1089
1131
|
processOfflineMessages?: boolean;
|
|
1090
1132
|
provider?: WhatsAppProvider;
|
|
@@ -1172,4 +1214,4 @@ declare class SqliteMuteStore implements MuteStore {
|
|
|
1172
1214
|
close(): void;
|
|
1173
1215
|
}
|
|
1174
1216
|
|
|
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 };
|
|
1217
|
+
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 EditMessageOptions, 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
|
@@ -2239,9 +2239,9 @@ var MessageService = class {
|
|
|
2239
2239
|
const key = "keys" in source ? source.keys : source;
|
|
2240
2240
|
return this.#provider.repostMessage(key, chatId, options);
|
|
2241
2241
|
}
|
|
2242
|
-
edit(message, text) {
|
|
2242
|
+
edit(message, text, options = {}) {
|
|
2243
2243
|
const key = "keys" in message ? message.keys : message;
|
|
2244
|
-
return this.#provider.editMessage(key, text);
|
|
2244
|
+
return this.#provider.editMessage(key, text, options);
|
|
2245
2245
|
}
|
|
2246
2246
|
delete(message) {
|
|
2247
2247
|
const key = "keys" in message ? message.keys : message;
|
|
@@ -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;
|
|
@@ -3462,12 +3491,15 @@ var ZapoProvider = class {
|
|
|
3462
3491
|
);
|
|
3463
3492
|
}
|
|
3464
3493
|
}
|
|
3465
|
-
async editMessage(key, content) {
|
|
3494
|
+
async editMessage(key, content, options = {}) {
|
|
3466
3495
|
return this.#trackOutgoing(async () => {
|
|
3467
3496
|
const result = await this.#requireClient().message.send(
|
|
3468
3497
|
key.chatId,
|
|
3469
3498
|
content,
|
|
3470
|
-
{
|
|
3499
|
+
{
|
|
3500
|
+
editKey: this.#toZapoKey(key),
|
|
3501
|
+
...options.id ? { id: options.id } : {}
|
|
3502
|
+
}
|
|
3471
3503
|
);
|
|
3472
3504
|
return this.#sent(result, key.chatId);
|
|
3473
3505
|
});
|
|
@@ -3657,7 +3689,22 @@ var ZapoProvider = class {
|
|
|
3657
3689
|
);
|
|
3658
3690
|
void this.#stopTerminalConnection(client, error);
|
|
3659
3691
|
});
|
|
3692
|
+
client.on("debug_transport_frame_in", ({ frame }) => {
|
|
3693
|
+
this.#transportFramesIn += 1;
|
|
3694
|
+
if (this.#ingressDiagnostics) {
|
|
3695
|
+
this.#logger.debug("Inbound WhatsApp transport frame received.", {
|
|
3696
|
+
frameBytes: frame.byteLength
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
client.on("debug_transport_node_in", ({ node }) => {
|
|
3701
|
+
this.#handleTransportNodeIn(node);
|
|
3702
|
+
});
|
|
3703
|
+
client.on("debug_transport_decode_error", ({ error, frame }) => {
|
|
3704
|
+
this.#handleTransportDecodeError(error, frame.byteLength);
|
|
3705
|
+
});
|
|
3660
3706
|
client.on("message", (event) => {
|
|
3707
|
+
this.#resolveIngressMessage(event.key, "message_event");
|
|
3661
3708
|
const protocol = this.#protocolMessage(event);
|
|
3662
3709
|
if (protocol && this.#isMessageMutationProtocol(protocol.type)) {
|
|
3663
3710
|
this.#enqueueProtocolEvent({
|
|
@@ -3681,6 +3728,7 @@ var ZapoProvider = class {
|
|
|
3681
3728
|
this.#handleMessage(event);
|
|
3682
3729
|
});
|
|
3683
3730
|
client.on("message_unavailable", (event) => {
|
|
3731
|
+
this.#resolveIngressMessage(event.key, "unavailable");
|
|
3684
3732
|
this.#handleUnavailableMessage(event);
|
|
3685
3733
|
});
|
|
3686
3734
|
client.on("debug_decrypted_payload", (event) => {
|
|
@@ -3720,6 +3768,139 @@ var ZapoProvider = class {
|
|
|
3720
3768
|
void this.#handleConnectionEvent(client, event);
|
|
3721
3769
|
});
|
|
3722
3770
|
}
|
|
3771
|
+
#handleTransportNodeIn(node) {
|
|
3772
|
+
this.#transportNodesIn += 1;
|
|
3773
|
+
if (node.tag !== "message") return;
|
|
3774
|
+
this.#messageStanzasIn += 1;
|
|
3775
|
+
const stanzaId = stringValue(node.attrs.id);
|
|
3776
|
+
const chatId = normalizeIngressJid(stringValue(node.attrs.from));
|
|
3777
|
+
const participantId = normalizeIngressJid(
|
|
3778
|
+
stringValue(node.attrs.participant) ?? stringValue(node.attrs.participant_pn) ?? stringValue(node.attrs.participant_lid) ?? stringValue(node.attrs.sender_pn) ?? stringValue(node.attrs.sender_lid)
|
|
3779
|
+
);
|
|
3780
|
+
const stanzaType = stringValue(node.attrs.type);
|
|
3781
|
+
const addressingMode = stringValue(node.attrs.addressing_mode);
|
|
3782
|
+
const children = Array.isArray(node.content) ? node.content : [];
|
|
3783
|
+
const childTags = [...new Set(children.map((child) => child.tag))];
|
|
3784
|
+
const encTypes = [...new Set(children.filter((child) => child.tag === "enc").map((child) => stringValue(child.attrs.type)).filter((value) => value !== void 0))];
|
|
3785
|
+
if (this.#ingressDiagnostics) {
|
|
3786
|
+
this.#logger.info("Inbound WhatsApp message stanza observed.", {
|
|
3787
|
+
...stanzaId ? { stanzaId } : {},
|
|
3788
|
+
...chatId ? { chatId } : {},
|
|
3789
|
+
...participantId ? { participantId } : {},
|
|
3790
|
+
...stanzaType ? { stanzaType } : {},
|
|
3791
|
+
...addressingMode ? { addressingMode } : {},
|
|
3792
|
+
...childTags.length > 0 ? { childTags } : {},
|
|
3793
|
+
...encTypes.length > 0 ? { encTypes } : {}
|
|
3794
|
+
});
|
|
3795
|
+
}
|
|
3796
|
+
const correlationKey = this.#stanzaCorrelationKey(chatId, stanzaId);
|
|
3797
|
+
if (!correlationKey || !stanzaId) return;
|
|
3798
|
+
const previous = this.#pendingIngressMessages.get(correlationKey);
|
|
3799
|
+
if (previous) clearTimeout(previous.timer);
|
|
3800
|
+
const observedAt = Date.now();
|
|
3801
|
+
const timer = setTimeout(() => {
|
|
3802
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3803
|
+
if (!pending || pending.observedAt !== observedAt) return;
|
|
3804
|
+
this.#pendingIngressMessages.delete(correlationKey);
|
|
3805
|
+
this.#ingressStalls += 1;
|
|
3806
|
+
this.#markDegraded();
|
|
3807
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
3808
|
+
this.#emitStability({
|
|
3809
|
+
type: "messageIngressStalled",
|
|
3810
|
+
payload: {
|
|
3811
|
+
occurredAt,
|
|
3812
|
+
waitedMs: occurredAt.getTime() - pending.observedAt,
|
|
3813
|
+
lastStage: pending.lastStage,
|
|
3814
|
+
stanzaId: pending.stanzaId,
|
|
3815
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3816
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3817
|
+
...pending.stanzaType ? { stanzaType: pending.stanzaType } : {},
|
|
3818
|
+
...pending.addressingMode ? { addressingMode: pending.addressingMode } : {}
|
|
3819
|
+
}
|
|
3820
|
+
});
|
|
3821
|
+
this.#logger.warn("Inbound WhatsApp message stanza did not reach a terminal provider event.", {
|
|
3822
|
+
stanzaId: pending.stanzaId,
|
|
3823
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3824
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3825
|
+
lastStage: pending.lastStage,
|
|
3826
|
+
waitedMs: occurredAt.getTime() - pending.observedAt
|
|
3827
|
+
});
|
|
3828
|
+
}, this.#ingressStallTimeoutMs);
|
|
3829
|
+
this.#pendingIngressMessages.set(correlationKey, {
|
|
3830
|
+
observedAt,
|
|
3831
|
+
lastStage: "stanza_received",
|
|
3832
|
+
timer,
|
|
3833
|
+
stanzaId,
|
|
3834
|
+
...chatId ? { chatId } : {},
|
|
3835
|
+
...participantId ? { participantId } : {},
|
|
3836
|
+
...stanzaType ? { stanzaType } : {},
|
|
3837
|
+
...addressingMode ? { addressingMode } : {}
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
#handleTransportDecodeError(error, frameBytes) {
|
|
3841
|
+
this.#transportDecodeErrors += 1;
|
|
3842
|
+
this.#markDegraded();
|
|
3843
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
3844
|
+
this.#emitStability({
|
|
3845
|
+
type: "transportDecodeFailure",
|
|
3846
|
+
payload: {
|
|
3847
|
+
occurredAt,
|
|
3848
|
+
frameBytes,
|
|
3849
|
+
errorName: error.name || "Error",
|
|
3850
|
+
errorMessage: error.message || String(error)
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
this.#logger.warn("Inbound WhatsApp transport frame could not be decoded.", {
|
|
3854
|
+
frameBytes,
|
|
3855
|
+
errorName: error.name || "Error",
|
|
3856
|
+
errorMessage: error.message || String(error)
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
#markIngressDecrypted(chatId, stanzaId) {
|
|
3860
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizeIngressJid(chatId), stanzaId);
|
|
3861
|
+
if (!correlationKey) return;
|
|
3862
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3863
|
+
if (!pending) return;
|
|
3864
|
+
pending.lastStage = "decrypted";
|
|
3865
|
+
if (this.#ingressDiagnostics) {
|
|
3866
|
+
this.#logger.info("Inbound WhatsApp message payload decrypted.", {
|
|
3867
|
+
stanzaId: pending.stanzaId,
|
|
3868
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3869
|
+
...pending.participantId ? { participantId: pending.participantId } : {}
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
#resolveIngressMessage(key, outcome) {
|
|
3874
|
+
const chatId = normalizeIngressJid(key.remoteJid ?? void 0);
|
|
3875
|
+
const correlationKey = this.#stanzaCorrelationKey(chatId, key.id ?? void 0);
|
|
3876
|
+
if (!correlationKey) return;
|
|
3877
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3878
|
+
if (!pending) return;
|
|
3879
|
+
clearTimeout(pending.timer);
|
|
3880
|
+
this.#pendingIngressMessages.delete(correlationKey);
|
|
3881
|
+
if (this.#ingressDiagnostics) {
|
|
3882
|
+
this.#logger.info("Inbound WhatsApp message stanza reached provider terminal stage.", {
|
|
3883
|
+
stanzaId: pending.stanzaId,
|
|
3884
|
+
...pending.chatId ? { chatId: pending.chatId } : {},
|
|
3885
|
+
...pending.participantId ? { participantId: pending.participantId } : {},
|
|
3886
|
+
lastStage: pending.lastStage,
|
|
3887
|
+
outcome,
|
|
3888
|
+
durationMs: Date.now() - pending.observedAt
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
#resolveIngressByStanza(chatId, stanzaId, outcome) {
|
|
3893
|
+
const normalizedChatId = normalizeIngressJid(chatId);
|
|
3894
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizedChatId, stanzaId);
|
|
3895
|
+
if (!correlationKey) return;
|
|
3896
|
+
const pending = this.#pendingIngressMessages.get(correlationKey);
|
|
3897
|
+
if (!pending) return;
|
|
3898
|
+
const remoteJid = pending.chatId ?? normalizedChatId;
|
|
3899
|
+
this.#resolveIngressMessage({
|
|
3900
|
+
id: pending.stanzaId,
|
|
3901
|
+
...remoteJid ? { remoteJid } : {}
|
|
3902
|
+
}, outcome);
|
|
3903
|
+
}
|
|
3723
3904
|
#handleMessage(event) {
|
|
3724
3905
|
const stored = event;
|
|
3725
3906
|
if (stored.key?.id && stored.message) this.#remember(stored);
|
|
@@ -3730,7 +3911,7 @@ var ZapoProvider = class {
|
|
|
3730
3911
|
const deliveryKey = this.#messageDeliveryKey(stored.key);
|
|
3731
3912
|
this.#resolveUnavailableRecovery(deliveryKey, stored.key);
|
|
3732
3913
|
const decryptedCorrelationKey = this.#stanzaCorrelationKey(
|
|
3733
|
-
stored.key.remoteJid ?? void 0,
|
|
3914
|
+
normalizeIngressJid(stored.key.remoteJid ?? void 0),
|
|
3734
3915
|
stored.key.id ?? void 0
|
|
3735
3916
|
);
|
|
3736
3917
|
if (decryptedCorrelationKey) this.#recentDecryptedPayloads.delete(decryptedCorrelationKey);
|
|
@@ -3766,6 +3947,16 @@ var ZapoProvider = class {
|
|
|
3766
3947
|
if (stored.key.id) this.#rememberDeliveredMessage(deliveryKey);
|
|
3767
3948
|
this.#receivedMessages += 1;
|
|
3768
3949
|
this.#lastIncomingAt = /* @__PURE__ */ new Date();
|
|
3950
|
+
if (this.#ingressDiagnostics) {
|
|
3951
|
+
this.#logger.info("Inbound WhatsApp message emitted to WhaNext.", {
|
|
3952
|
+
messageId: message.id,
|
|
3953
|
+
chatId: message.chatId,
|
|
3954
|
+
userId: message.sender.id,
|
|
3955
|
+
contentKind: message.contentKind,
|
|
3956
|
+
hasMedia: message.media !== void 0,
|
|
3957
|
+
hasQuoted: message.quoted !== void 0
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3769
3960
|
this.#messageKeyStore.set(message.keys, stored);
|
|
3770
3961
|
if (message.quoted && quoted?.message) {
|
|
3771
3962
|
this.#messageKeyStore.set(
|
|
@@ -3870,7 +4061,8 @@ var ZapoProvider = class {
|
|
|
3870
4061
|
}
|
|
3871
4062
|
#handleDecryptedPayload(event) {
|
|
3872
4063
|
this.#decryptedPayloads += 1;
|
|
3873
|
-
|
|
4064
|
+
this.#markIngressDecrypted(event.chatJid, event.stanzaId);
|
|
4065
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizeIngressJid(event.chatJid), event.stanzaId);
|
|
3874
4066
|
if (!correlationKey) return;
|
|
3875
4067
|
const now = Date.now();
|
|
3876
4068
|
this.#recentDecryptedPayloads.set(correlationKey, {
|
|
@@ -3885,11 +4077,13 @@ var ZapoProvider = class {
|
|
|
3885
4077
|
}
|
|
3886
4078
|
#handleUnhandledStanza(event) {
|
|
3887
4079
|
this.#unhandledStanzas += 1;
|
|
3888
|
-
const
|
|
4080
|
+
const normalizedChatId = normalizeIngressJid(event.chatJid);
|
|
4081
|
+
const correlationKey = this.#stanzaCorrelationKey(normalizedChatId, event.stanzaId);
|
|
3889
4082
|
const decrypted = correlationKey ? this.#recentDecryptedPayloads.get(correlationKey) : void 0;
|
|
3890
4083
|
const now = Date.now();
|
|
3891
4084
|
if (decrypted && now - decrypted.observedAt <= DECRYPT_CORRELATION_TTL_MS) {
|
|
3892
4085
|
this.#decodeFailures += 1;
|
|
4086
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
3893
4087
|
this.#markDegraded();
|
|
3894
4088
|
this.#emitStability({
|
|
3895
4089
|
type: "messageDecodeFailure",
|
|
@@ -3910,6 +4104,7 @@ var ZapoProvider = class {
|
|
|
3910
4104
|
if (correlationKey) this.#recentDecryptedPayloads.delete(correlationKey);
|
|
3911
4105
|
return;
|
|
3912
4106
|
}
|
|
4107
|
+
this.#resolveIngressByStanza(normalizedChatId, event.stanzaId, "decode_failure");
|
|
3913
4108
|
this.#logger.debug("Incoming stanza was not handled by Zapo.", {
|
|
3914
4109
|
reason: event.reason,
|
|
3915
4110
|
...event.stanzaId ? { stanzaId: event.stanzaId } : {},
|
|
@@ -4188,6 +4383,11 @@ var ZapoProvider = class {
|
|
|
4188
4383
|
}
|
|
4189
4384
|
if (message === "failed to decrypt incoming message") {
|
|
4190
4385
|
this.#decryptFailures += 1;
|
|
4386
|
+
this.#resolveIngressByStanza(
|
|
4387
|
+
stringValue(context.from) ?? stringValue(context.groupJid),
|
|
4388
|
+
stringValue(context.id),
|
|
4389
|
+
"decrypt_failure"
|
|
4390
|
+
);
|
|
4191
4391
|
const detail = stringValue(context.message);
|
|
4192
4392
|
const kind = detail === "sender key id mismatch" ? "sender_key_mismatch" : "decrypt_failure";
|
|
4193
4393
|
if (kind === "sender_key_mismatch") this.#senderKeyMismatches += 1;
|
|
@@ -5353,6 +5553,13 @@ function normalizeProviderTimeout(value, fallback) {
|
|
|
5353
5553
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
5354
5554
|
return Math.max(1e3, Math.floor(value));
|
|
5355
5555
|
}
|
|
5556
|
+
function normalizeIngressJid(value) {
|
|
5557
|
+
if (!value) return void 0;
|
|
5558
|
+
const at = value.indexOf("@");
|
|
5559
|
+
if (at <= 0) return value;
|
|
5560
|
+
const local = value.slice(0, at).replace(/:\d+$/, "");
|
|
5561
|
+
return `${local}${value.slice(at)}`;
|
|
5562
|
+
}
|
|
5356
5563
|
function detectCryptoBackend() {
|
|
5357
5564
|
const requested = process.env.ZAPO_NATIVE_BACKEND?.trim().toLowerCase() ?? "auto";
|
|
5358
5565
|
if (requested === "js" || requested === "none") return "js";
|
|
@@ -5453,7 +5660,9 @@ async function create(options = {}) {
|
|
|
5453
5660
|
...options.processOfflineMessages !== void 0 ? { processOfflineMessages: options.processOfflineMessages } : {},
|
|
5454
5661
|
...options.reconnect ? { reconnect: options.reconnect } : {},
|
|
5455
5662
|
...options.providerTimeouts?.connectTimeoutMs !== void 0 ? { connectTimeoutMs: options.providerTimeouts.connectTimeoutMs } : {},
|
|
5456
|
-
...options.providerTimeouts?.nodeQueryTimeoutMs !== void 0 ? { nodeQueryTimeoutMs: options.providerTimeouts.nodeQueryTimeoutMs } : {}
|
|
5663
|
+
...options.providerTimeouts?.nodeQueryTimeoutMs !== void 0 ? { nodeQueryTimeoutMs: options.providerTimeouts.nodeQueryTimeoutMs } : {},
|
|
5664
|
+
...options.providerDiagnostics?.ingress !== void 0 ? { ingressDiagnostics: options.providerDiagnostics.ingress } : {},
|
|
5665
|
+
...options.providerDiagnostics?.ingressStallTimeoutMs !== void 0 ? { ingressStallTimeoutMs: options.providerDiagnostics.ingressStallTimeoutMs } : {}
|
|
5457
5666
|
});
|
|
5458
5667
|
return new WhaNextApp(provider, {
|
|
5459
5668
|
...options.accountId ? { accountId: options.accountId } : {},
|
|
@@ -5691,6 +5900,12 @@ function mergeCreateOptions(shared, account) {
|
|
|
5691
5900
|
...account.providerTimeouts
|
|
5692
5901
|
};
|
|
5693
5902
|
}
|
|
5903
|
+
if (shared.providerDiagnostics || account.providerDiagnostics) {
|
|
5904
|
+
merged.providerDiagnostics = {
|
|
5905
|
+
...shared.providerDiagnostics,
|
|
5906
|
+
...account.providerDiagnostics
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5694
5909
|
if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
|
|
5695
5910
|
merged.logger = {
|
|
5696
5911
|
...shared.logger,
|