Compare commits

..

3 Commits

Author SHA1 Message Date
Renato Alcara 6c09b14776 fix(signal): bot-review round 2 — injectE2ESession lock, doc/error fixes
Addresses bot review on commit 4bdcd5769d:

1) [Major — CodeRabbit + Copilot] `injectE2ESession` was the last
   session-mutating path still locking on raw `jid` while writing through
   `storeSession()` (which canonicalizes via `resolveLIDSignalAddress`). Same
   PN/LID lock-vs-storage drift as the original bug — fixed by computing
   `wireAddr` once and locking on it (same pattern as encrypt/decrypt).

2) [Minor — Copilot] `resolveLIDSignalAddress` docstring described the input
   as `userDomain.device` but the actual format from
   `jidToSignalProtocolAddress` is `signalUser.device`, where `signalUser` is
   either the bare user (WhatsApp domain) or `user_domainType` for LID/hosted.
   Updated to match.

3) [Minor — Copilot] `Missing device ID` error fired when the user portion
   (before the `.`) was empty, which was misleading. Split into two specific
   errors and added validation for the empty-device-portion case (e.g.
   trailing `.`). Each now points at the actual missing part.

4) [Doc — CodeRabbit residual race concern] Added a paragraph explaining the
   theoretical race between our `wireAddr` resolution and storage's internal
   re-resolution if `lidMapping` mutates between the two awaits. In production
   this is mitigated by the `migrateSession()` call upstream in
   `messages-recv.ts` running synchronously BEFORE `decryptMessage`, so the
   mapping is populated by the time the lock is acquired. Eliminating the
   residual entirely requires storage to skip re-resolution — bigger refactor
   reserved for a follow-up if the residual ever bites in prod.

5) [Out of scope — Copilot PR-metadata staleness] PR title still says "raw
   JID" from the original commit. Will be retitled on the GitHub side after
   this lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:36:20 -03:00
Renato Alcara 4bdcd5769d fix(signal): lock on wire address (not raw JID) — addresses bot review
Bots (Codex P1, Copilot, CodeRabbit) flagged a real defect in the previous
commit (79844a9d29): locking on the raw `jid` doesn't actually serialize
concurrent ops on the same session, because `signalStorage.loadSession`/
`storeSession` internally call `resolveLIDSignalAddress(id)` which canonicalizes
PN→LID before hitting `keys.get/set('session', ...)`. So:

  - Op A arrives as PN  → lock(PN_addr)  → storage rewrites to LID_addr
  - Op B arrives as LID → lock(LID_addr) → storage stays at LID_addr
  - Two locks, SAME session record → ratchet interleaving → Bad MAC.

This was the same drift the original `canonicalJid` was trying to prevent —
just expressed at a different layer. Pedro's snapshot doesn't trip it because
in Feb/6 there were no PN→LID mappings populated yet (WhatsApp hadn't fully
rolled out LID-addressed DSM), so storage canonicalization was a no-op and lock
key == storage key by accident.

Fix: extract `resolveLIDSignalAddress` to a top-level helper (parameterized by
`lidMapping`) and use it in BOTH places —
  1. `signalStorage.loadSession`/`storeSession` (unchanged behavior, just no
     longer a closure).
  2. `decryptMessage`/`encryptMessage` to compute the *wire address* and lock
     on it. Lock key now matches the storage key exactly.

This eliminates both directions of the PN/LID drift. PN/LID mapping latency on
the hot path is unchanged from the previous commit (one `getLIDForPN` lookup
per decrypt — same as before — but now correctly placed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 18:27:35 -03:00
Renato Alcara 79844a9d29 fix(signal): use raw JID as transaction key to stop Bad MAC loop on own DSM
PRODUCTION BUG: After WhatsApp rolled out LID-addressed DSM (deviceSentMessage),
own messages flooded the inbound pipeline with perpetual `Bad MAC` errors,
delaying delivery to the consumer by ~60s. Pedro's fork (Feb/6 snapshot, 484
commits behind) does NOT have this bug — diff isolated the regression to this
file.

ROOT CAUSE: `decryptMessage` was locking on `canonicalJid` (resolved via
`resolveCanonicalJid`, which awaits `lidMapping.getLIDForPN`) but the
`SessionCipher` is constructed from `jidToSignalProtocolAddress(jid)` — the
RAW jid. So:

  1. The mutex lock key (canonical LID) and the session-storage key (raw
     JID/PN-derived) drift apart. Two concurrent decrypts for the same
     logical session — one arriving as PN, one as LID — acquire DIFFERENT
     locks and interleave reads/writes on the same Signal session record,
     corrupting the ratchet. Symptom: perpetual `Bad MAC Error`.

  2. Each decrypt also paid the cost of an extra `await getLIDForPN()` round
     trip to the LID store on the hot path — a needless await per message.

`encryptMessage` had the same bug.

FIX: Lock on the raw `jid` (matches what `SessionCipher` actually uses for
storage). PN/LID race protection still happens upstream in `migrateSession`
+ `storeLIDPNMappings`, which run BEFORE decrypt and ensure the session is
already stored under the correct format by the time we acquire the lock.

`resolveCanonicalJid` is now unused — removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:57:40 -03:00
8 changed files with 27 additions and 3173 deletions
+1 -163
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package proto;
/// WhatsApp Version: 2.3000.1038469210
/// WhatsApp Version: 2.3000.1038164556
message ADVDeviceIdentity {
optional uint32 rawId = 1;
@@ -14,7 +14,6 @@ message ADVDeviceIdentity {
enum ADVEncryptionType {
E2EE = 0;
HOSTED = 1;
NON_E2EE = 2;
}
message ADVKeyIndexList {
optional uint32 rawId = 1;
@@ -62,7 +61,6 @@ message AIHomeState {
ANIMATE_PHOTO = 2;
ANALYZE_FILE = 3;
COLLABORATE = 4;
OPEN_GREETING_CARD = 5;
}
}
@@ -249,16 +247,6 @@ message AIRichResponseUnifiedResponse {
optional bytes data = 1;
}
enum AISubscriptionRequestType {
UNSPECIFIED = 0;
THINK_HARD = 1;
IMAGE_GEN = 2;
VIDEO_GEN = 3;
}
message AISubscriptionUpsellMetadata {
optional AISubscriptionRequestType requestType = 1;
}
message AIThreadInfo {
optional AIThreadServerInfo serverInfo = 1;
optional AIThreadClientInfo clientInfo = 2;
@@ -671,8 +659,6 @@ message BotMetadata {
optional BotInfrastructureDiagnostics botInfrastructureDiagnostics = 37;
optional AIMediaCollectionMetadata aiMediaCollectionMetadata = 38;
optional BotCommandMetadata commandMetadata = 39;
optional BotResolvedToolCallMetadata resolvedToolCallMetadata = 40;
optional AISubscriptionUpsellMetadata subscriptionUpsellMetadata = 41;
optional bytes internalMetadata = 999;
}
@@ -911,11 +897,6 @@ message BotRenderingMetadata {
}
message BotResolvedToolCallMetadata {
optional string toolCallId = 1;
optional string resolutionDataSerialized = 2;
}
message BotSessionMetadata {
optional string sessionId = 1;
optional BotSessionSource sessionSource = 2;
@@ -1151,7 +1132,6 @@ message ClientPayload {
optional int32 preacksCount = 45;
optional int32 processingQueueSize = 46;
repeated string pairedPeripherals = 47;
optional bytes testIsolationId = 48;
enum AccountType {
DEFAULT = 0;
GUEST = 1;
@@ -1247,7 +1227,6 @@ message ClientPayload {
optional string deviceExpId = 14;
optional DeviceType deviceType = 15;
optional string deviceModelType = 16;
optional DistributionChannel distributionChannel = 17;
message AppVersion {
optional uint32 primary = 1;
optional uint32 secondary = 2;
@@ -1263,12 +1242,6 @@ message ClientPayload {
WEARABLE = 3;
VR = 4;
}
enum DistributionChannel {
APPSTORE = 0;
WEBSITE = 1;
TESTFLIGHT = 2;
INTERNAL = 3;
}
enum Platform {
ANDROID = 0;
IOS = 1;
@@ -1438,9 +1411,6 @@ message ContextInfo {
optional MediaDomainInfo mediaDomainInfo = 74;
optional PartiallySelectedContent partiallySelectedContent = 75;
optional uint32 afterReadDuration = 76;
optional CrossAppSource crossAppSource = 77;
optional BusinessInteractionPills businessInteractionPills = 78;
optional string posterStatusId = 79;
message AdReplyInfo {
optional string advertiserName = 1;
optional MediaType mediaType = 2;
@@ -1453,47 +1423,10 @@ message ContextInfo {
}
}
message BusinessInteractionPills {
optional string businessJid = 1;
repeated Pill pills = 2;
optional EntryPoint entryPoint = 3;
enum EntryPoint {
ENTRY_POINT_UNKNOWN = 0;
P2P_LINK_SHARE = 1;
CONTACT_CARD_SHARING = 2;
PHONE_NUMBER = 3;
STATUS = 4;
IN_THREAD_CONTEXT_CARD = 5;
}
message Pill {
optional ContextInfo.BusinessInteractionPills.PillType pillType = 1;
optional string actionUrl = 2;
}
enum PillType {
UNKNOWN = 0;
VIEW_BUSINESS = 1;
CHAT = 2;
CALL = 3;
CATALOG = 4;
CHANNEL = 5;
BOOK_APPOINTMENT = 6;
OFFERS = 7;
BESTSELLERS = 8;
MENU = 9;
ABOUT = 10;
}
}
message BusinessMessageForwardInfo {
optional string businessOwnerJid = 1;
}
enum CrossAppSource {
CROSS_APP_SOURCE_UNKNOWN = 0;
CROSS_APP_SOURCE_INSTAGRAM = 1;
CROSS_APP_SOURCE_FACEBOOK = 2;
}
message DataSharingContext {
optional bool showMmDisclosure = 1;
optional string encryptedSignalTokenConsented = 2;
@@ -1702,7 +1635,6 @@ message Conversation {
optional bool isMarketingMessageThread = 55;
optional bool isSenderNewAccount = 56;
optional uint32 afterReadDuration = 57;
optional bool isSenderSuspicious = 58;
enum EndOfHistoryTransferType {
COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY = 0;
COMPLETE_AND_NO_MORE_MESSAGE_REMAIN_ON_PRIMARY = 1;
@@ -2416,8 +2348,6 @@ message Message {
optional PollAddOptionMessage pollAddOptionMessage = 121;
optional EventInviteMessage eventInviteMessage = 122;
optional GroupRootKeyShare groupRootKeyShare = 123;
optional P2PPaymentReminderNotification p2PPaymentReminderNotification = 124;
optional SplitPaymentMessage splitPaymentMessage = 125;
message AlbumMessage {
optional uint32 expectedImageCount = 2;
optional uint32 expectedVideoCount = 3;
@@ -2598,41 +2528,6 @@ message Message {
optional string id = 2;
}
message ChatCustomImageWallpaper {
optional string directPath = 1;
optional bytes mediaKey = 2;
optional bytes fileEncSha256 = 3;
optional bytes fileSha256 = 4;
optional float dimLevel = 5;
}
message ChatDefaultWallpaper {
optional bool isDoodleEnabled = 1;
}
message ChatSolidColorWallpaper {
optional string colorLight = 1;
optional string colorDark = 2;
optional bool isDoodleEnabled = 3;
}
message ChatStockImageWallpaper {
optional string stockImageId = 1;
optional float dimLevel = 2;
}
message ChatThemeSetting {
optional int64 settingTimestampMs = 1;
optional bool clearTheme = 2;
optional string colorSchemeId = 3;
oneof wallpaper {
Message.ChatDefaultWallpaper defaultWallpaper = 10;
Message.ChatSolidColorWallpaper solidColor = 11;
Message.ChatStockImageWallpaper stockImage = 12;
Message.ChatCustomImageWallpaper customImage = 13;
}
}
message CloudAPIThreadControlNotification {
optional CloudAPIThreadControl status = 1;
optional int64 senderNotificationTimestampMs = 2;
@@ -2742,7 +2637,6 @@ message Message {
optional int64 startTime = 5;
optional string caption = 6;
optional bool isCanceled = 7;
optional int64 endTime = 8;
}
message EventMessage {
@@ -3325,35 +3219,6 @@ message Message {
}
}
message P2PPaymentReminderNotification {
optional string reminderId = 1;
optional Money amount = 2;
optional ReminderFrequency frequency = 3;
optional int64 nextReminderTimestamp = 4;
optional int64 expiryTimestamp = 5;
optional ReminderState state = 6;
optional string description = 7;
optional string creatorJid = 8;
optional string receiverJid = 9;
optional string upiId = 10;
optional int64 createdTimestamp = 11;
enum ReminderFrequency {
UNKNOWN_FREQUENCY = 0;
WEEKLY = 1;
BIWEEKLY = 2;
MONTHLY = 3;
CUSTOM = 4;
}
enum ReminderState {
UNKNOWN_STATE = 0;
ACTIVE = 1;
PAUSED = 2;
STOPPED = 3;
EXPIRED = 4;
CANCELLED = 5;
}
}
message PaymentExtendedMetadata {
optional uint32 type = 1;
optional string platform = 2;
@@ -3763,7 +3628,6 @@ message Message {
optional MemberLabel memberLabel = 27;
optional AIMediaCollectionMessage aiMediaCollectionMessage = 28;
optional uint32 afterReadDuration = 29;
optional Message.ChatThemeSetting chatThemeSetting = 30;
enum Type {
REVOKE = 0;
EPHEMERAL_SETTING = 3;
@@ -3793,7 +3657,6 @@ message Message {
GROUP_MEMBER_LABEL_CHANGE = 30;
AI_MEDIA_COLLECTION_MESSAGE = 31;
MESSAGE_UNSCHEDULE = 32;
CHAT_THEME_SETTING = 34;
}
}
@@ -3885,26 +3748,6 @@ message Message {
optional bytes axolotlSenderKeyDistributionMessage = 2;
}
message SplitPaymentMessage {
optional string splitId = 1;
optional Money totalAmount = 2;
optional string description = 3;
optional string requesterJid = 4;
repeated Message.SplitPaymentParticipant participants = 5;
optional int64 createdAtMs = 6;
optional ContextInfo contextInfo = 17;
}
message SplitPaymentParticipant {
optional string jid = 1;
optional Money amount = 2;
optional SplitPaymentStatus status = 3;
enum SplitPaymentStatus {
PENDING = 0;
PAID = 1;
}
}
message StatusNotificationMessage {
optional MessageKey responseMessageKey = 1;
optional MessageKey originalMessageKey = 2;
@@ -4855,7 +4698,6 @@ message StatusAttribution {
SHARECHAT = 9;
GOOGLE_PHOTOS = 10;
SOUNDCLOUD = 11;
SHAZAM = 12;
}
}
@@ -4913,7 +4755,6 @@ message StatusAttribution {
LAYOUTS = 8;
NEWSLETTER_STATUS = 9;
STATUS_CLOSE_SHARING = 10;
PAID_PARTNERSHIP = 11;
}
}
@@ -5425,7 +5266,6 @@ message SyncActionValue {
repeated string keywords = 3;
optional int32 count = 4;
optional bool deleted = 5;
repeated string associatedLabelIds = 6;
}
message RecentEmojiWeightsAction {
@@ -5943,8 +5783,6 @@ message WebMessageInfo {
optional string hsmTag = 79;
optional uint64 ephemeralExpirationTimestamp = 80;
optional ScheduledMessageMetadata scheduledMessageMetadata = 81;
optional string decisionId = 82;
repeated string decisionSources = 83;
enum BizPrivacyStatus {
E2EE = 0;
FB = 2;
+5 -377
View File
@@ -28,8 +28,7 @@ export namespace proto {
enum ADVEncryptionType {
E2EE = 0,
HOSTED = 1,
NON_E2EE = 2
HOSTED = 1
}
interface IADVKeyIndexList {
@@ -177,8 +176,7 @@ export namespace proto {
CREATE_IMAGE = 1,
ANIMATE_PHOTO = 2,
ANALYZE_FILE = 3,
COLLABORATE = 4,
OPEN_GREETING_CARD = 5
COLLABORATE = 4
}
}
}
@@ -707,29 +705,6 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string;
}
enum AISubscriptionRequestType {
UNSPECIFIED = 0,
THINK_HARD = 1,
IMAGE_GEN = 2,
VIDEO_GEN = 3
}
interface IAISubscriptionUpsellMetadata {
requestType?: (proto.AISubscriptionRequestType|null);
}
class AISubscriptionUpsellMetadata implements IAISubscriptionUpsellMetadata {
constructor(p?: proto.IAISubscriptionUpsellMetadata);
public requestType?: (proto.AISubscriptionRequestType|null);
public static create(properties?: proto.IAISubscriptionUpsellMetadata): proto.AISubscriptionUpsellMetadata;
public static encode(m: proto.IAISubscriptionUpsellMetadata, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.AISubscriptionUpsellMetadata;
public static fromObject(d: { [k: string]: any }): proto.AISubscriptionUpsellMetadata;
public static toObject(m: proto.AISubscriptionUpsellMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IAIThreadInfo {
serverInfo?: (proto.AIThreadInfo.IAIThreadServerInfo|null);
clientInfo?: (proto.AIThreadInfo.IAIThreadClientInfo|null);
@@ -1727,8 +1702,6 @@ export namespace proto {
botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null);
aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null);
commandMetadata?: (proto.IBotCommandMetadata|null);
resolvedToolCallMetadata?: (proto.IBotResolvedToolCallMetadata|null);
subscriptionUpsellMetadata?: (proto.IAISubscriptionUpsellMetadata|null);
internalMetadata?: (Uint8Array|null);
}
@@ -1772,8 +1745,6 @@ export namespace proto {
public botInfrastructureDiagnostics?: (proto.IBotInfrastructureDiagnostics|null);
public aiMediaCollectionMetadata?: (proto.IAIMediaCollectionMetadata|null);
public commandMetadata?: (proto.IBotCommandMetadata|null);
public resolvedToolCallMetadata?: (proto.IBotResolvedToolCallMetadata|null);
public subscriptionUpsellMetadata?: (proto.IAISubscriptionUpsellMetadata|null);
public internalMetadata?: (Uint8Array|null);
public static create(properties?: proto.IBotMetadata): proto.BotMetadata;
public static encode(m: proto.IBotMetadata, w?: $protobuf.Writer): $protobuf.Writer;
@@ -2323,24 +2294,6 @@ export namespace proto {
}
}
interface IBotResolvedToolCallMetadata {
toolCallId?: (string|null);
resolutionDataSerialized?: (string|null);
}
class BotResolvedToolCallMetadata implements IBotResolvedToolCallMetadata {
constructor(p?: proto.IBotResolvedToolCallMetadata);
public toolCallId?: (string|null);
public resolutionDataSerialized?: (string|null);
public static create(properties?: proto.IBotResolvedToolCallMetadata): proto.BotResolvedToolCallMetadata;
public static encode(m: proto.IBotResolvedToolCallMetadata, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.BotResolvedToolCallMetadata;
public static fromObject(d: { [k: string]: any }): proto.BotResolvedToolCallMetadata;
public static toObject(m: proto.BotResolvedToolCallMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IBotSessionMetadata {
sessionId?: (string|null);
sessionSource?: (proto.BotSessionSource|null);
@@ -2929,7 +2882,6 @@ export namespace proto {
preacksCount?: (number|null);
processingQueueSize?: (number|null);
pairedPeripherals?: (string[]|null);
testIsolationId?: (Uint8Array|null);
}
class ClientPayload implements IClientPayload {
@@ -2969,7 +2921,6 @@ export namespace proto {
public preacksCount?: (number|null);
public processingQueueSize?: (number|null);
public pairedPeripherals: string[];
public testIsolationId?: (Uint8Array|null);
public static create(properties?: proto.IClientPayload): proto.ClientPayload;
public static encode(m: proto.IClientPayload, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ClientPayload;
@@ -3132,7 +3083,6 @@ export namespace proto {
deviceExpId?: (string|null);
deviceType?: (proto.ClientPayload.UserAgent.DeviceType|null);
deviceModelType?: (string|null);
distributionChannel?: (proto.ClientPayload.UserAgent.DistributionChannel|null);
}
class UserAgent implements IUserAgent {
@@ -3153,7 +3103,6 @@ export namespace proto {
public deviceExpId?: (string|null);
public deviceType?: (proto.ClientPayload.UserAgent.DeviceType|null);
public deviceModelType?: (string|null);
public distributionChannel?: (proto.ClientPayload.UserAgent.DistributionChannel|null);
public static create(properties?: proto.ClientPayload.IUserAgent): proto.ClientPayload.UserAgent;
public static encode(m: proto.ClientPayload.IUserAgent, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ClientPayload.UserAgent;
@@ -3197,13 +3146,6 @@ export namespace proto {
VR = 4
}
enum DistributionChannel {
APPSTORE = 0,
WEBSITE = 1,
TESTFLIGHT = 2,
INTERNAL = 3
}
enum Platform {
ANDROID = 0,
IOS = 1,
@@ -3469,9 +3411,6 @@ export namespace proto {
mediaDomainInfo?: (proto.IMediaDomainInfo|null);
partiallySelectedContent?: (proto.ContextInfo.IPartiallySelectedContent|null);
afterReadDuration?: (number|null);
crossAppSource?: (proto.ContextInfo.CrossAppSource|null);
businessInteractionPills?: (proto.ContextInfo.IBusinessInteractionPills|null);
posterStatusId?: (string|null);
}
class ContextInfo implements IContextInfo {
@@ -3535,9 +3474,6 @@ export namespace proto {
public mediaDomainInfo?: (proto.IMediaDomainInfo|null);
public partiallySelectedContent?: (proto.ContextInfo.IPartiallySelectedContent|null);
public afterReadDuration?: (number|null);
public crossAppSource?: (proto.ContextInfo.CrossAppSource|null);
public businessInteractionPills?: (proto.ContextInfo.IBusinessInteractionPills|null);
public posterStatusId?: (string|null);
public static create(properties?: proto.IContextInfo): proto.ContextInfo;
public static encode(m: proto.IContextInfo, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo;
@@ -3580,70 +3516,6 @@ export namespace proto {
}
}
interface IBusinessInteractionPills {
businessJid?: (string|null);
pills?: (proto.ContextInfo.BusinessInteractionPills.IPill[]|null);
entryPoint?: (proto.ContextInfo.BusinessInteractionPills.EntryPoint|null);
}
class BusinessInteractionPills implements IBusinessInteractionPills {
constructor(p?: proto.ContextInfo.IBusinessInteractionPills);
public businessJid?: (string|null);
public pills: proto.ContextInfo.BusinessInteractionPills.IPill[];
public entryPoint?: (proto.ContextInfo.BusinessInteractionPills.EntryPoint|null);
public static create(properties?: proto.ContextInfo.IBusinessInteractionPills): proto.ContextInfo.BusinessInteractionPills;
public static encode(m: proto.ContextInfo.IBusinessInteractionPills, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo.BusinessInteractionPills;
public static fromObject(d: { [k: string]: any }): proto.ContextInfo.BusinessInteractionPills;
public static toObject(m: proto.ContextInfo.BusinessInteractionPills, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
namespace BusinessInteractionPills {
enum EntryPoint {
ENTRY_POINT_UNKNOWN = 0,
P2P_LINK_SHARE = 1,
CONTACT_CARD_SHARING = 2,
PHONE_NUMBER = 3,
STATUS = 4,
IN_THREAD_CONTEXT_CARD = 5
}
interface IPill {
pillType?: (proto.ContextInfo.BusinessInteractionPills.PillType|null);
actionUrl?: (string|null);
}
class Pill implements IPill {
constructor(p?: proto.ContextInfo.BusinessInteractionPills.IPill);
public pillType?: (proto.ContextInfo.BusinessInteractionPills.PillType|null);
public actionUrl?: (string|null);
public static create(properties?: proto.ContextInfo.BusinessInteractionPills.IPill): proto.ContextInfo.BusinessInteractionPills.Pill;
public static encode(m: proto.ContextInfo.BusinessInteractionPills.IPill, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo.BusinessInteractionPills.Pill;
public static fromObject(d: { [k: string]: any }): proto.ContextInfo.BusinessInteractionPills.Pill;
public static toObject(m: proto.ContextInfo.BusinessInteractionPills.Pill, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
enum PillType {
UNKNOWN = 0,
VIEW_BUSINESS = 1,
CHAT = 2,
CALL = 3,
CATALOG = 4,
CHANNEL = 5,
BOOK_APPOINTMENT = 6,
OFFERS = 7,
BESTSELLERS = 8,
MENU = 9,
ABOUT = 10
}
}
interface IBusinessMessageForwardInfo {
businessOwnerJid?: (string|null);
}
@@ -3660,12 +3532,6 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string;
}
enum CrossAppSource {
CROSS_APP_SOURCE_UNKNOWN = 0,
CROSS_APP_SOURCE_INSTAGRAM = 1,
CROSS_APP_SOURCE_FACEBOOK = 2
}
interface IDataSharingContext {
showMmDisclosure?: (boolean|null);
encryptedSignalTokenConsented?: (string|null);
@@ -4055,7 +3921,6 @@ export namespace proto {
isMarketingMessageThread?: (boolean|null);
isSenderNewAccount?: (boolean|null);
afterReadDuration?: (number|null);
isSenderSuspicious?: (boolean|null);
}
class Conversation implements IConversation {
@@ -4117,7 +3982,6 @@ export namespace proto {
public isMarketingMessageThread?: (boolean|null);
public isSenderNewAccount?: (boolean|null);
public afterReadDuration?: (number|null);
public isSenderSuspicious?: (boolean|null);
public static create(properties?: proto.IConversation): proto.Conversation;
public static encode(m: proto.IConversation, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Conversation;
@@ -5861,8 +5725,6 @@ export namespace proto {
pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null);
eventInviteMessage?: (proto.Message.IEventInviteMessage|null);
groupRootKeyShare?: (proto.IGroupRootKeyShare|null);
p2PPaymentReminderNotification?: (proto.Message.IP2PPaymentReminderNotification|null);
splitPaymentMessage?: (proto.Message.ISplitPaymentMessage|null);
}
class Message implements IMessage {
@@ -5970,8 +5832,6 @@ export namespace proto {
public pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null);
public eventInviteMessage?: (proto.Message.IEventInviteMessage|null);
public groupRootKeyShare?: (proto.IGroupRootKeyShare|null);
public p2PPaymentReminderNotification?: (proto.Message.IP2PPaymentReminderNotification|null);
public splitPaymentMessage?: (proto.Message.ISplitPaymentMessage|null);
public static create(properties?: proto.IMessage): proto.Message;
public static encode(m: proto.IMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message;
@@ -6480,113 +6340,6 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IChatCustomImageWallpaper {
directPath?: (string|null);
mediaKey?: (Uint8Array|null);
fileEncSha256?: (Uint8Array|null);
fileSha256?: (Uint8Array|null);
dimLevel?: (number|null);
}
class ChatCustomImageWallpaper implements IChatCustomImageWallpaper {
constructor(p?: proto.Message.IChatCustomImageWallpaper);
public directPath?: (string|null);
public mediaKey?: (Uint8Array|null);
public fileEncSha256?: (Uint8Array|null);
public fileSha256?: (Uint8Array|null);
public dimLevel?: (number|null);
public static create(properties?: proto.Message.IChatCustomImageWallpaper): proto.Message.ChatCustomImageWallpaper;
public static encode(m: proto.Message.IChatCustomImageWallpaper, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ChatCustomImageWallpaper;
public static fromObject(d: { [k: string]: any }): proto.Message.ChatCustomImageWallpaper;
public static toObject(m: proto.Message.ChatCustomImageWallpaper, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IChatDefaultWallpaper {
isDoodleEnabled?: (boolean|null);
}
class ChatDefaultWallpaper implements IChatDefaultWallpaper {
constructor(p?: proto.Message.IChatDefaultWallpaper);
public isDoodleEnabled?: (boolean|null);
public static create(properties?: proto.Message.IChatDefaultWallpaper): proto.Message.ChatDefaultWallpaper;
public static encode(m: proto.Message.IChatDefaultWallpaper, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ChatDefaultWallpaper;
public static fromObject(d: { [k: string]: any }): proto.Message.ChatDefaultWallpaper;
public static toObject(m: proto.Message.ChatDefaultWallpaper, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IChatSolidColorWallpaper {
colorLight?: (string|null);
colorDark?: (string|null);
isDoodleEnabled?: (boolean|null);
}
class ChatSolidColorWallpaper implements IChatSolidColorWallpaper {
constructor(p?: proto.Message.IChatSolidColorWallpaper);
public colorLight?: (string|null);
public colorDark?: (string|null);
public isDoodleEnabled?: (boolean|null);
public static create(properties?: proto.Message.IChatSolidColorWallpaper): proto.Message.ChatSolidColorWallpaper;
public static encode(m: proto.Message.IChatSolidColorWallpaper, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ChatSolidColorWallpaper;
public static fromObject(d: { [k: string]: any }): proto.Message.ChatSolidColorWallpaper;
public static toObject(m: proto.Message.ChatSolidColorWallpaper, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IChatStockImageWallpaper {
stockImageId?: (string|null);
dimLevel?: (number|null);
}
class ChatStockImageWallpaper implements IChatStockImageWallpaper {
constructor(p?: proto.Message.IChatStockImageWallpaper);
public stockImageId?: (string|null);
public dimLevel?: (number|null);
public static create(properties?: proto.Message.IChatStockImageWallpaper): proto.Message.ChatStockImageWallpaper;
public static encode(m: proto.Message.IChatStockImageWallpaper, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ChatStockImageWallpaper;
public static fromObject(d: { [k: string]: any }): proto.Message.ChatStockImageWallpaper;
public static toObject(m: proto.Message.ChatStockImageWallpaper, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface IChatThemeSetting {
settingTimestampMs?: (number|Long|null);
clearTheme?: (boolean|null);
colorSchemeId?: (string|null);
defaultWallpaper?: (proto.Message.IChatDefaultWallpaper|null);
solidColor?: (proto.Message.IChatSolidColorWallpaper|null);
stockImage?: (proto.Message.IChatStockImageWallpaper|null);
customImage?: (proto.Message.IChatCustomImageWallpaper|null);
}
class ChatThemeSetting implements IChatThemeSetting {
constructor(p?: proto.Message.IChatThemeSetting);
public settingTimestampMs?: (number|Long|null);
public clearTheme?: (boolean|null);
public colorSchemeId?: (string|null);
public defaultWallpaper?: (proto.Message.IChatDefaultWallpaper|null);
public solidColor?: (proto.Message.IChatSolidColorWallpaper|null);
public stockImage?: (proto.Message.IChatStockImageWallpaper|null);
public customImage?: (proto.Message.IChatCustomImageWallpaper|null);
public wallpaper?: ("defaultWallpaper"|"solidColor"|"stockImage"|"customImage");
public static create(properties?: proto.Message.IChatThemeSetting): proto.Message.ChatThemeSetting;
public static encode(m: proto.Message.IChatThemeSetting, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ChatThemeSetting;
public static fromObject(d: { [k: string]: any }): proto.Message.ChatThemeSetting;
public static toObject(m: proto.Message.ChatThemeSetting, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface ICloudAPIThreadControlNotification {
status?: (proto.Message.CloudAPIThreadControlNotification.CloudAPIThreadControl|null);
senderNotificationTimestampMs?: (number|Long|null);
@@ -6891,7 +6644,6 @@ export namespace proto {
startTime?: (number|Long|null);
caption?: (string|null);
isCanceled?: (boolean|null);
endTime?: (number|Long|null);
}
class EventInviteMessage implements IEventInviteMessage {
@@ -6903,7 +6655,6 @@ export namespace proto {
public startTime?: (number|Long|null);
public caption?: (string|null);
public isCanceled?: (boolean|null);
public endTime?: (number|Long|null);
public static create(properties?: proto.Message.IEventInviteMessage): proto.Message.EventInviteMessage;
public static encode(m: proto.Message.IEventInviteMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.EventInviteMessage;
@@ -8424,62 +8175,6 @@ export namespace proto {
}
}
interface IP2PPaymentReminderNotification {
reminderId?: (string|null);
amount?: (proto.IMoney|null);
frequency?: (proto.Message.P2PPaymentReminderNotification.ReminderFrequency|null);
nextReminderTimestamp?: (number|Long|null);
expiryTimestamp?: (number|Long|null);
state?: (proto.Message.P2PPaymentReminderNotification.ReminderState|null);
description?: (string|null);
creatorJid?: (string|null);
receiverJid?: (string|null);
upiId?: (string|null);
createdTimestamp?: (number|Long|null);
}
class P2PPaymentReminderNotification implements IP2PPaymentReminderNotification {
constructor(p?: proto.Message.IP2PPaymentReminderNotification);
public reminderId?: (string|null);
public amount?: (proto.IMoney|null);
public frequency?: (proto.Message.P2PPaymentReminderNotification.ReminderFrequency|null);
public nextReminderTimestamp?: (number|Long|null);
public expiryTimestamp?: (number|Long|null);
public state?: (proto.Message.P2PPaymentReminderNotification.ReminderState|null);
public description?: (string|null);
public creatorJid?: (string|null);
public receiverJid?: (string|null);
public upiId?: (string|null);
public createdTimestamp?: (number|Long|null);
public static create(properties?: proto.Message.IP2PPaymentReminderNotification): proto.Message.P2PPaymentReminderNotification;
public static encode(m: proto.Message.IP2PPaymentReminderNotification, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.P2PPaymentReminderNotification;
public static fromObject(d: { [k: string]: any }): proto.Message.P2PPaymentReminderNotification;
public static toObject(m: proto.Message.P2PPaymentReminderNotification, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
namespace P2PPaymentReminderNotification {
enum ReminderFrequency {
UNKNOWN_FREQUENCY = 0,
WEEKLY = 1,
BIWEEKLY = 2,
MONTHLY = 3,
CUSTOM = 4
}
enum ReminderState {
UNKNOWN_STATE = 0,
ACTIVE = 1,
PAUSED = 2,
STOPPED = 3,
EXPIRED = 4,
CANCELLED = 5
}
}
interface IPaymentExtendedMetadata {
type?: (number|null);
platform?: (string|null);
@@ -9628,7 +9323,6 @@ export namespace proto {
memberLabel?: (proto.IMemberLabel|null);
aiMediaCollectionMessage?: (proto.IAIMediaCollectionMessage|null);
afterReadDuration?: (number|null);
chatThemeSetting?: (proto.Message.IChatThemeSetting|null);
}
class ProtocolMessage implements IProtocolMessage {
@@ -9659,7 +9353,6 @@ export namespace proto {
public memberLabel?: (proto.IMemberLabel|null);
public aiMediaCollectionMessage?: (proto.IAIMediaCollectionMessage|null);
public afterReadDuration?: (number|null);
public chatThemeSetting?: (proto.Message.IChatThemeSetting|null);
public static create(properties?: proto.Message.IProtocolMessage): proto.Message.ProtocolMessage;
public static encode(m: proto.Message.IProtocolMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.ProtocolMessage;
@@ -9699,8 +9392,7 @@ export namespace proto {
AI_QUERY_FANOUT = 29,
GROUP_MEMBER_LABEL_CHANGE = 30,
AI_MEDIA_COLLECTION_MESSAGE = 31,
MESSAGE_UNSCHEDULE = 32,
CHAT_THEME_SETTING = 34
MESSAGE_UNSCHEDULE = 32
}
}
@@ -9952,62 +9644,6 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface ISplitPaymentMessage {
splitId?: (string|null);
totalAmount?: (proto.IMoney|null);
description?: (string|null);
requesterJid?: (string|null);
participants?: (proto.Message.ISplitPaymentParticipant[]|null);
createdAtMs?: (number|Long|null);
contextInfo?: (proto.IContextInfo|null);
}
class SplitPaymentMessage implements ISplitPaymentMessage {
constructor(p?: proto.Message.ISplitPaymentMessage);
public splitId?: (string|null);
public totalAmount?: (proto.IMoney|null);
public description?: (string|null);
public requesterJid?: (string|null);
public participants: proto.Message.ISplitPaymentParticipant[];
public createdAtMs?: (number|Long|null);
public contextInfo?: (proto.IContextInfo|null);
public static create(properties?: proto.Message.ISplitPaymentMessage): proto.Message.SplitPaymentMessage;
public static encode(m: proto.Message.ISplitPaymentMessage, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.SplitPaymentMessage;
public static fromObject(d: { [k: string]: any }): proto.Message.SplitPaymentMessage;
public static toObject(m: proto.Message.SplitPaymentMessage, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
interface ISplitPaymentParticipant {
jid?: (string|null);
amount?: (proto.IMoney|null);
status?: (proto.Message.SplitPaymentParticipant.SplitPaymentStatus|null);
}
class SplitPaymentParticipant implements ISplitPaymentParticipant {
constructor(p?: proto.Message.ISplitPaymentParticipant);
public jid?: (string|null);
public amount?: (proto.IMoney|null);
public status?: (proto.Message.SplitPaymentParticipant.SplitPaymentStatus|null);
public static create(properties?: proto.Message.ISplitPaymentParticipant): proto.Message.SplitPaymentParticipant;
public static encode(m: proto.Message.ISplitPaymentParticipant, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.SplitPaymentParticipant;
public static fromObject(d: { [k: string]: any }): proto.Message.SplitPaymentParticipant;
public static toObject(m: proto.Message.SplitPaymentParticipant, o?: $protobuf.IConversionOptions): { [k: string]: any };
public toJSON(): { [k: string]: any };
public static getTypeUrl(typeUrlPrefix?: string): string;
}
namespace SplitPaymentParticipant {
enum SplitPaymentStatus {
PENDING = 0,
PAID = 1
}
}
interface IStatusNotificationMessage {
responseMessageKey?: (proto.IMessageKey|null);
originalMessageKey?: (proto.IMessageKey|null);
@@ -12362,8 +11998,7 @@ export namespace proto {
APPLE_MUSIC = 8,
SHARECHAT = 9,
GOOGLE_PHOTOS = 10,
SOUNDCLOUD = 11,
SHAZAM = 12
SOUNDCLOUD = 11
}
}
@@ -12497,8 +12132,7 @@ export namespace proto {
AI_CREATED = 7,
LAYOUTS = 8,
NEWSLETTER_STATUS = 9,
STATUS_CLOSE_SHARING = 10,
PAID_PARTNERSHIP = 11
STATUS_CLOSE_SHARING = 10
}
}
@@ -14035,7 +13669,6 @@ export namespace proto {
keywords?: (string[]|null);
count?: (number|null);
deleted?: (boolean|null);
associatedLabelIds?: (string[]|null);
}
class QuickReplyAction implements IQuickReplyAction {
@@ -14045,7 +13678,6 @@ export namespace proto {
public keywords: string[];
public count?: (number|null);
public deleted?: (boolean|null);
public associatedLabelIds: string[];
public static create(properties?: proto.SyncActionValue.IQuickReplyAction): proto.SyncActionValue.QuickReplyAction;
public static encode(m: proto.SyncActionValue.IQuickReplyAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.QuickReplyAction;
@@ -15327,8 +14959,6 @@ export namespace proto {
hsmTag?: (string|null);
ephemeralExpirationTimestamp?: (number|Long|null);
scheduledMessageMetadata?: (proto.IScheduledMessageMetadata|null);
decisionId?: (string|null);
decisionSources?: (string[]|null);
}
class WebMessageInfo implements IWebMessageInfo {
@@ -15403,8 +15033,6 @@ export namespace proto {
public hsmTag?: (string|null);
public ephemeralExpirationTimestamp?: (number|Long|null);
public scheduledMessageMetadata?: (proto.IScheduledMessageMetadata|null);
public decisionId?: (string|null);
public decisionSources: string[];
public static create(properties?: proto.IWebMessageInfo): proto.WebMessageInfo;
public static encode(m: proto.IWebMessageInfo, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.WebMessageInfo;
-2549
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
{"version":[2,3000,1038487394]}
{"version":[2,3000,1038189736]}
+1 -14
View File
@@ -2695,20 +2695,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
}
})
} catch (error) {
// Slim log: identify the message that crashed without dumping the full
// stanza XML (which contains kilobytes of inline ciphertext). Under heavy
// load — e.g. a downstream consumer that throws on every Bad MAC — the
// full XML dumps overwhelm stdout and stall the event loop. Operators
// can still pivot on msgId / from / type; full payload at debug level.
const slimNode = {
tag: node.tag,
id: node.attrs?.id,
from: node.attrs?.from,
type: node.attrs?.type,
participant: node.attrs?.participant
}
logger.error({ error, node: slimNode }, 'error in handling message')
logger.debug({ node: binaryNodeToString(node) }, 'error in handling message — full stanza')
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message')
}
}
+7 -9
View File
@@ -341,16 +341,14 @@ export const addTransactionCapability = (
return result
} catch (error) {
// SessionError / MessageCounterError are part of the normal Bad MAC
// recovery flow (retry receipt → sender resends as pkmsg → new session
// within ~1.3s). Logging them as ERROR creates 2 noise lines per
// recoverable cycle, and under WhatsApp's LID/DSM rollout these fire
// in dense bursts that saturate stdout (synchronous pm2 writes block
// the event loop). Downgrade to debug for both; keep ERROR for
// everything else. The error is still re-thrown — recovery is unchanged.
// SessionError is part of the normal Bad MAC recovery flow
// (retry receipt → sender resends as pkmsg → new session within ~1.3s).
// Logging it as ERROR creates 2 noise lines per recoverable Bad MAC cycle.
// Downgrade to debug for SessionError; keep ERROR for everything else.
// The error is still re-thrown — recovery behavior is unchanged.
const errName = (error as { name?: string })?.name
if (errName === 'SessionError' || errName === 'MessageCounterError') {
logger.debug({ error }, `transaction failed (${errName} — recoverable via retry receipt)`)
if (errName === 'SessionError') {
logger.debug({ error }, 'transaction failed (SessionError — recoverable via retry receipt)')
} else {
logger.error({ error }, 'transaction failed, rolling back')
}
+8 -29
View File
@@ -454,40 +454,19 @@ export const decryptMessageNode = (
...(isRetryExhausted && { retriesExhausted: true, attempts: err.attempts })
}
// Smart logging based on error type and retry status.
//
// PERF NOTE: under WhatsApp's LID/DSM rollout, own DSM messages flood
// the inbound pipeline with Bad MAC / "Key used already" / "No session
// record" errors *every* time the auth state has a session in the
// legacy `_1.0` format that no longer matches the LID-addressed
// envelope. Logging the full errorContext (key + jid + err) per failed
// attempt as warn-level produces tens of JSON lines/sec, which
// saturates stdout in pm2 (synchronous writes to a full pipe block the
// event loop). The clean `🔐 Bad MAC Error | JID: …` line emitted by
// the console.error interceptor in src/index.ts already gives an
// operator-visible signal — the duplicated pino line was pure noise.
//
// We now only emit warn-level when retries are exhausted (rare,
// actionable). Per-attempt detail is still available at debug level
// (BAILEYS_LOG_LEVEL=debug) for active troubleshooting.
const slimErrorContext = {
msgId: fullMessage.key?.id,
jid: fullMessage.key?.remoteJid,
err: slimErr,
attempts: isRetryExhausted ? err.attempts : 1
}
// Smart logging based on error type and retry status
if (isCorrupted) {
// Corrupted session errors are expected — Signal Protocol auto-recovers
// via retry receipt → pkmsg → new session.
// Corrupted session errors are expected and auto-recovered
// Only log as ERROR if retries exhausted, otherwise WARN on first attempt
// eslint-disable-next-line max-depth
if (isRetryExhausted) {
logger.warn(
slimErrorContext,
logger.error(
errorContext,
`⚠️ Session corrupted after ${err.attempts} attempts. Retry+pkmsg flow will recover.`
)
} else {
logger.debug(errorContext, '⚠️ Corrupted session detected - attempting auto-recovery')
// First occurrence - log as warning since auto-recovery will attempt
logger.warn(errorContext, '⚠️ Corrupted session detected - attempting auto-recovery')
}
// Session cleanup is deferred to retry exhaustion (safety net).
@@ -500,7 +479,7 @@ export const decryptMessageNode = (
// Session record errors are transient - retry should handle them
// eslint-disable-next-line max-depth
if (isRetryExhausted) {
logger.warn(slimErrorContext, `Failed to decrypt: No session record found after ${err.attempts} attempts`)
logger.error(errorContext, `Failed to decrypt: No session record found after ${err.attempts} attempts`)
} else {
logger.debug(errorContext, 'No session record - will retry')
}
+4 -31
View File
@@ -5,30 +5,11 @@
const _origConsoleError = console.error
const _origConsoleLog = console.log
const _origConsoleInfo = console.info
const _origConsoleWarn = console.warn
// Suppress libsignal session lifecycle dumps from console.log / console.info / console.warn.
// libsignal's session_record.js / session_builder.js / session_cipher.js use:
// console.info("Removing old closed session:", obj) ← ~500ms I/O dump per call
// console.info("Opening session:", obj) ← ~500ms I/O dump per call
// console.info("Migrating session to:", v) ← per migration
// console.log("Closing session:", obj) ← ~500ms I/O dump per call
// console.warn("Closing open session in favor of incoming prekey bundle") ← per pkmsg
// console.warn("Session already closed", obj) ← per stale close
// console.warn("Decrypted message with closed session.") ← per recovered decrypt
// console.warn("Unhandled bucket type (for naming):", ...) ← queue_job edge case
//
// Under WhatsApp's LID/DSM rollout these fire dozens of times per minute when
// the auth state has legacy `_1.0` sessions that don't match LID-addressed
// envelopes. Each dump is a synchronous stdout.write — pm2 buffers fill and
// the event loop blocks. Symptom: 60s inbound delivery latency, profile
// pictures don't load, queries time out.
//
// The clean operator-facing signal lives in the console.error interceptor
// below (formats Bad MAC / Counter / Decryption Failed as one emoji line).
const _SESSION_LIFECYCLE_RE =
/^(Closing session|Removing old closed session|Opening session|Migrating session|Closing open session|Session already closed|Decrypted message with closed session|Unhandled bucket type)/
// Suppress libsignal session lifecycle dumps from console.log / console.info.
// libsignal's session_record.js uses console.info("Removing old closed session:", obj)
// and console.log("Closing session:", obj) which dump full session objects (~500ms I/O each).
const _SESSION_LIFECYCLE_RE = /^(Closing session|Removing old closed session)/
console.log = function (...args: unknown[]) {
if (args.length > 0 && typeof args[0] === 'string' && _SESSION_LIFECYCLE_RE.test(args[0])) {
return
@@ -45,14 +26,6 @@ console.info = function (...args: unknown[]) {
_origConsoleInfo.apply(console, args)
}
console.warn = function (...args: unknown[]) {
if (args.length > 0 && typeof args[0] === 'string' && _SESSION_LIFECYCLE_RE.test(args[0])) {
return
}
_origConsoleWarn.apply(console, args)
}
// Track errors by type + JID to avoid duplicates (using Map for better performance)
const _errorTimestamps = new Map<string, number>()
// Dedup window for repeated decrypt-error console lines (Bad MAC / Counter / etc).