@pontalabs/baileys 1.0.4 → 1.0.6
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/LICENSE +1 -1
- package/README.md +267 -839
- package/WAProto/GenerateStatics.sh +3 -4
- package/WAProto/WAProto.proto +1215 -511
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +14017 -55
- package/WAProto/index.js +97689 -29
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.d.ts +72 -74
- package/lib/Defaults/index.d.ts.map +1 -0
- package/lib/Defaults/index.js +94 -108
- package/lib/Defaults/index.js.map +1 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +8 -7
- package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
- package/lib/Signal/Group/ciphertext-message.js +9 -16
- package/lib/Signal/Group/ciphertext-message.js.map +1 -0
- package/lib/Signal/Group/group-session-builder.d.ts +11 -13
- package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
- package/lib/Signal/Group/group-session-builder.js +19 -61
- package/lib/Signal/Group/group-session-builder.js.map +1 -0
- package/lib/Signal/Group/group_cipher.d.ts +14 -16
- package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
- package/lib/Signal/Group/group_cipher.js +41 -70
- package/lib/Signal/Group/group_cipher.js.map +1 -0
- package/lib/Signal/Group/index.d.ts +12 -11
- package/lib/Signal/Group/index.d.ts.map +1 -0
- package/lib/Signal/Group/index.js +12 -61
- package/lib/Signal/Group/index.js.map +1 -0
- package/lib/Signal/Group/keyhelper.d.ts +9 -14
- package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
- package/lib/Signal/Group/keyhelper.js +10 -58
- package/lib/Signal/Group/keyhelper.js.map +1 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
- package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
- package/lib/Signal/Group/sender-chain-key.js +13 -34
- package/lib/Signal/Group/sender-chain-key.js.map +1 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
- package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +28 -36
- package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -18
- package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-message.js +38 -45
- package/lib/Signal/Group/sender-key-message.js.map +1 -0
- package/lib/Signal/Group/sender-key-name.d.ts +14 -15
- package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-name.js +20 -31
- package/lib/Signal/Group/sender-key-name.js.map +1 -0
- package/lib/Signal/Group/sender-key-record.d.ts +24 -25
- package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-record.js +18 -35
- package/lib/Signal/Group/sender-key-record.js.map +1 -0
- package/lib/Signal/Group/sender-key-state.d.ts +29 -34
- package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-state.js +37 -100
- package/lib/Signal/Group/sender-key-state.js.map +1 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -10
- package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
- package/lib/Signal/Group/sender-message-key.js +15 -22
- package/lib/Signal/Group/sender-message-key.js.map +1 -0
- package/lib/Signal/libsignal.d.ts +5 -8
- package/lib/Signal/libsignal.d.ts.map +1 -0
- package/lib/Signal/libsignal.js +279 -121
- package/lib/Signal/libsignal.js.map +1 -0
- package/lib/Signal/lid-mapping.d.ts +23 -28
- package/lib/Signal/lid-mapping.d.ts.map +1 -0
- package/lib/Signal/lid-mapping.js +277 -184
- package/lib/Signal/lid-mapping.js.map +1 -0
- package/lib/Socket/Client/index.d.ts +3 -2
- package/lib/Socket/Client/index.d.ts.map +1 -0
- package/lib/Socket/Client/index.js +3 -22
- package/lib/Socket/Client/index.js.map +1 -0
- package/lib/Socket/Client/types.d.ts +15 -15
- package/lib/Socket/Client/types.d.ts.map +1 -0
- package/lib/Socket/Client/types.js +8 -15
- package/lib/Socket/Client/types.js.map +1 -0
- package/lib/Socket/Client/websocket.d.ts +12 -12
- package/lib/Socket/Client/websocket.d.ts.map +1 -0
- package/lib/Socket/Client/websocket.js +29 -37
- package/lib/Socket/Client/websocket.js.map +1 -0
- package/lib/Socket/business.d.ts +208 -178
- package/lib/Socket/business.d.ts.map +1 -0
- package/lib/Socket/business.js +91 -127
- package/lib/Socket/business.js.map +1 -0
- package/lib/Socket/chats.d.ts +120 -93
- package/lib/Socket/chats.d.ts.map +1 -0
- package/lib/Socket/chats.js +727 -652
- package/lib/Socket/chats.js.map +1 -0
- package/lib/Socket/communities.d.ts +273 -0
- package/lib/Socket/communities.d.ts.map +1 -0
- package/lib/Socket/communities.js +431 -0
- package/lib/Socket/communities.js.map +1 -0
- package/lib/Socket/dugong.js +261 -0
- package/lib/Socket/dugong.js.bak +204 -0
- package/lib/Socket/groups.d.ts +143 -111
- package/lib/Socket/groups.d.ts.map +1 -0
- package/lib/Socket/groups.js +165 -177
- package/lib/Socket/groups.js.map +1 -0
- package/lib/Socket/index.d.ts +252 -183
- package/lib/Socket/index.d.ts.map +1 -0
- package/lib/Socket/index.js +38 -18
- package/lib/Socket/index.js.bak +44 -0
- package/lib/Socket/index.js.map +1 -0
- package/lib/Socket/messages-recv.d.ts +196 -167
- package/lib/Socket/messages-recv.d.ts.map +1 -0
- package/lib/Socket/messages-recv.js +1443 -1563
- package/lib/Socket/messages-recv.js.map +1 -0
- package/lib/Socket/messages-send.d.ts +192 -158
- package/lib/Socket/messages-send.d.ts.map +1 -0
- package/lib/Socket/messages-send.js +1102 -1208
- package/lib/Socket/messages-send.js.bak +1400 -0
- package/lib/Socket/messages-send.js.map +1 -0
- package/lib/Socket/mex.d.ts +3 -0
- package/lib/Socket/mex.d.ts.map +1 -0
- package/lib/Socket/mex.js +42 -0
- package/lib/Socket/mex.js.map +1 -0
- package/lib/Socket/newsletter.d.ts +163 -138
- package/lib/Socket/newsletter.d.ts.map +1 -0
- package/lib/Socket/newsletter.js +153 -249
- package/lib/Socket/newsletter.js.map +1 -0
- package/lib/Socket/socket.d.ts +50 -36
- package/lib/Socket/socket.d.ts.map +1 -0
- package/lib/Socket/socket.js +749 -820
- package/lib/Socket/socket.js.map +1 -0
- package/lib/Store/index.d.ts +4 -4
- package/lib/Store/index.d.ts.map +1 -0
- package/lib/Store/index.js +4 -24
- package/lib/Store/index.js.map +1 -0
- package/lib/Store/make-in-memory-store.d.ts +44 -104
- package/lib/Store/make-in-memory-store.d.ts.map +1 -0
- package/lib/Store/make-in-memory-store.js +199 -213
- package/lib/Store/make-in-memory-store.js.map +1 -0
- package/lib/Store/make-ordered-dictionary.d.ts +13 -12
- package/lib/Store/make-ordered-dictionary.d.ts.map +1 -0
- package/lib/Store/make-ordered-dictionary.js +38 -45
- package/lib/Store/make-ordered-dictionary.js.map +1 -0
- package/lib/Store/object-repository.d.ts +11 -10
- package/lib/Store/object-repository.d.ts.map +1 -0
- package/lib/Store/object-repository.js +9 -16
- package/lib/Store/object-repository.js.map +1 -0
- package/lib/Types/Auth.d.ts +93 -97
- package/lib/Types/Auth.d.ts.map +1 -0
- package/lib/Types/Auth.js +2 -3
- package/lib/Types/Auth.js.map +1 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Bussines.d.ts.map +1 -0
- package/lib/Types/Bussines.js +2 -3
- package/lib/Types/Bussines.js.map +1 -0
- package/lib/Types/Call.d.ts +14 -13
- package/lib/Types/Call.d.ts.map +1 -0
- package/lib/Types/Call.js +2 -3
- package/lib/Types/Call.js.map +1 -0
- package/lib/Types/Chat.d.ts +78 -97
- package/lib/Types/Chat.d.ts.map +1 -0
- package/lib/Types/Chat.js +8 -9
- package/lib/Types/Chat.js.map +1 -0
- package/lib/Types/Contact.d.ts +12 -9
- package/lib/Types/Contact.d.ts.map +1 -0
- package/lib/Types/Contact.js +2 -3
- package/lib/Types/Contact.js.map +1 -0
- package/lib/Types/Events.d.ts +206 -176
- package/lib/Types/Events.d.ts.map +1 -0
- package/lib/Types/Events.js +2 -3
- package/lib/Types/Events.js.map +1 -0
- package/lib/Types/GroupMetadata.d.ts +53 -48
- package/lib/Types/GroupMetadata.d.ts.map +1 -0
- package/lib/Types/GroupMetadata.js +2 -3
- package/lib/Types/GroupMetadata.js.map +1 -0
- package/lib/Types/Label.d.ts +13 -14
- package/lib/Types/Label.d.ts.map +1 -0
- package/lib/Types/Label.js +24 -30
- package/lib/Types/Label.js.map +1 -0
- package/lib/Types/LabelAssociation.d.ts +15 -20
- package/lib/Types/LabelAssociation.d.ts.map +1 -0
- package/lib/Types/LabelAssociation.js +6 -12
- package/lib/Types/LabelAssociation.js.map +1 -0
- package/lib/Types/Message.d.ts +248 -412
- package/lib/Types/Message.d.ts.map +1 -0
- package/lib/Types/Message.js +17 -19
- package/lib/Types/Message.js.map +1 -0
- package/lib/Types/Mex.d.ts +141 -0
- package/lib/Types/Mex.d.ts.map +1 -0
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/Mex.js.map +1 -0
- package/lib/Types/Product.d.ts +58 -71
- package/lib/Types/Product.d.ts.map +1 -0
- package/lib/Types/Product.js +2 -3
- package/lib/Types/Product.js.map +1 -0
- package/lib/Types/RichType.d.ts +3 -0
- package/lib/Types/RichType.d.ts.map +1 -0
- package/lib/Types/RichType.js +23 -0
- package/lib/Types/RichType.js.bak +23 -0
- package/lib/Types/RichType.js.map +1 -0
- package/lib/Types/Signal.d.ts +71 -82
- package/lib/Types/Signal.d.ts.map +1 -0
- package/lib/Types/Signal.js +2 -3
- package/lib/Types/Signal.js.map +1 -0
- package/lib/Types/Socket.d.ts +76 -81
- package/lib/Types/Socket.d.ts.map +1 -0
- package/lib/Types/Socket.js +3 -3
- package/lib/Types/Socket.js.map +1 -0
- package/lib/Types/State.d.ts +75 -19
- package/lib/Types/State.d.ts.map +1 -0
- package/lib/Types/State.js +56 -14
- package/lib/Types/State.js.map +1 -0
- package/lib/Types/USync.d.ts +8 -8
- package/lib/Types/USync.d.ts.map +1 -0
- package/lib/Types/USync.js +2 -3
- package/lib/Types/USync.js.map +1 -0
- package/lib/Types/index.d.ts +47 -62
- package/lib/Types/index.d.ts.map +1 -0
- package/lib/Types/index.js +26 -50
- package/lib/Types/index.js.map +1 -0
- package/lib/Utils/auth-utils.d.ts +13 -10
- package/lib/Utils/auth-utils.d.ts.map +1 -0
- package/lib/Utils/auth-utils.js +213 -454
- package/lib/Utils/auth-utils.js.map +1 -0
- package/lib/Utils/browser-utils.d.ts +4 -0
- package/lib/Utils/browser-utils.d.ts.map +1 -0
- package/lib/Utils/browser-utils.js +28 -0
- package/lib/Utils/browser-utils.js.map +1 -0
- package/lib/Utils/business.d.ts +14 -20
- package/lib/Utils/business.d.ts.map +1 -0
- package/lib/Utils/business.js +110 -134
- package/lib/Utils/business.js.map +1 -0
- package/lib/Utils/chat-utils.d.ts +88 -70
- package/lib/Utils/chat-utils.d.ts.map +1 -0
- package/lib/Utils/chat-utils.js +466 -403
- package/lib/Utils/chat-utils.js.map +1 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/companion-reg-client-utils.js.map +1 -0
- package/lib/Utils/crypto.d.ts +27 -46
- package/lib/Utils/crypto.d.ts.map +1 -0
- package/lib/Utils/crypto.js +117 -188
- package/lib/Utils/crypto.js.map +1 -0
- package/lib/Utils/decode-wa-message.d.ts +58 -45
- package/lib/Utils/decode-wa-message.d.ts.map +1 -0
- package/lib/Utils/decode-wa-message.js +213 -303
- package/lib/Utils/decode-wa-message.js.map +1 -0
- package/lib/Utils/event-buffer.d.ts +14 -17
- package/lib/Utils/event-buffer.d.ts.map +1 -0
- package/lib/Utils/event-buffer.js +321 -296
- package/lib/Utils/event-buffer.js.map +1 -0
- package/lib/Utils/generics.d.ts +66 -92
- package/lib/Utils/generics.d.ts.map +1 -0
- package/lib/Utils/generics.js +270 -436
- package/lib/Utils/generics.js.map +1 -0
- package/lib/Utils/history.d.ts +24 -23
- package/lib/Utils/history.d.ts.map +1 -0
- package/lib/Utils/history.js +106 -76
- package/lib/Utils/history.js.map +1 -0
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.d.ts.map +1 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/identity-change-handler.js.map +1 -0
- package/lib/Utils/index.d.ts +24 -21
- package/lib/Utils/index.d.ts.map +1 -0
- package/lib/Utils/index.js +24 -41
- package/lib/Utils/index.js.map +1 -0
- package/lib/Utils/link-preview.d.ts +12 -14
- package/lib/Utils/link-preview.d.ts.map +1 -0
- package/lib/Utils/link-preview.js +40 -75
- package/lib/Utils/link-preview.js.map +1 -0
- package/lib/Utils/logger.d.ts +10 -11
- package/lib/Utils/logger.d.ts.map +1 -0
- package/lib/Utils/logger.js +3 -7
- package/lib/Utils/logger.js.map +1 -0
- package/lib/Utils/lt-hash.d.ts +8 -14
- package/lib/Utils/lt-hash.d.ts.map +1 -0
- package/lib/Utils/lt-hash.js +3 -53
- package/lib/Utils/lt-hash.js.map +1 -0
- package/lib/Utils/make-mutex.d.ts +7 -7
- package/lib/Utils/make-mutex.d.ts.map +1 -0
- package/lib/Utils/make-mutex.js +28 -44
- package/lib/Utils/make-mutex.js.map +1 -0
- package/lib/Utils/message-retry-manager.d.ts +115 -88
- package/lib/Utils/message-retry-manager.d.ts.map +1 -0
- package/lib/Utils/message-retry-manager.js +265 -160
- package/lib/Utils/message-retry-manager.js.map +1 -0
- package/lib/Utils/messages-media.d.ts +105 -107
- package/lib/Utils/messages-media.d.ts.map +1 -0
- package/lib/Utils/messages-media.js +548 -619
- package/lib/Utils/messages-media.js.bak +786 -0
- package/lib/Utils/messages-media.js.map +1 -0
- package/lib/Utils/messages.d.ts +52 -66
- package/lib/Utils/messages.d.ts.map +1 -0
- package/lib/Utils/messages.js +1730 -1558
- package/lib/Utils/messages.js.backup +1768 -0
- package/lib/Utils/messages.js.bak +1809 -0
- package/lib/Utils/messages.js.bak2 +893 -0
- package/lib/Utils/messages.js.bak3 +1183 -0
- package/lib/Utils/messages.js.map +1 -0
- package/lib/Utils/noise-handler.d.ts +18 -18
- package/lib/Utils/noise-handler.d.ts.map +1 -0
- package/lib/Utils/noise-handler.js +176 -130
- package/lib/Utils/noise-handler.js.map +1 -0
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.d.ts.map +1 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/offline-node-processor.js.map +1 -0
- package/lib/Utils/pre-key-manager.d.ts +28 -0
- package/lib/Utils/pre-key-manager.d.ts.map +1 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/pre-key-manager.js.map +1 -0
- package/lib/Utils/process-message.d.ts +43 -32
- package/lib/Utils/process-message.d.ts.map +1 -0
- package/lib/Utils/process-message.js +484 -282
- package/lib/Utils/process-message.js.map +1 -0
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.d.ts.map +1 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/reporting-utils.js.map +1 -0
- package/lib/Utils/rich-message-utils.d.ts +49 -0
- package/lib/Utils/rich-message-utils.d.ts.map +1 -0
- package/lib/Utils/rich-message-utils.js +1220 -724
- package/lib/Utils/rich-message-utils.js.backup +706 -0
- package/lib/Utils/rich-message-utils.js.bak +246 -0
- package/lib/Utils/rich-message-utils.js.map +1 -0
- package/lib/Utils/rich-message-utils.js.original +1332 -0
- package/lib/Utils/signal.d.ts +37 -32
- package/lib/Utils/signal.d.ts.map +1 -0
- package/lib/Utils/signal.js +140 -105
- package/lib/Utils/signal.js.map +1 -0
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.d.ts.map +1 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/stanza-ack.js.map +1 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.d.ts.map +1 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/sync-action-utils.js.map +1 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.d.ts.map +1 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/tc-token-utils.js.map +1 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
- package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-multi-file-auth-state.js +70 -120
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +11 -13
- package/lib/Utils/use-single-file-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-single-file-auth-state.js +92 -66
- package/lib/Utils/use-single-file-auth-state.js.map +1 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-sqlite-auth-state.js +109 -0
- package/lib/Utils/use-sqlite-auth-state.js.map +1 -0
- package/lib/Utils/validate-connection.d.ts +11 -13
- package/lib/Utils/validate-connection.d.ts.map +1 -0
- package/lib/Utils/validate-connection.js +128 -113
- package/lib/Utils/validate-connection.js.map +1 -0
- package/lib/WABinary/constants.d.ts +25 -27
- package/lib/WABinary/constants.d.ts.map +1 -0
- package/lib/WABinary/constants.js +1451 -1292
- package/lib/WABinary/constants.js.bak +1373 -0
- package/lib/WABinary/constants.js.map +1 -0
- package/lib/WABinary/decode.d.ts +7 -9
- package/lib/WABinary/decode.d.ts.map +1 -0
- package/lib/WABinary/decode.js +163 -189
- package/lib/WABinary/decode.js.map +1 -0
- package/lib/WABinary/encode.d.ts +3 -3
- package/lib/WABinary/encode.d.ts.map +1 -0
- package/lib/WABinary/encode.js +110 -155
- package/lib/WABinary/encode.js.map +1 -0
- package/lib/WABinary/generic-utils.d.ts +14 -27
- package/lib/WABinary/generic-utils.d.ts.map +1 -0
- package/lib/WABinary/generic-utils.js +177 -105
- package/lib/WABinary/generic-utils.js.map +1 -0
- package/lib/WABinary/index.d.ts +6 -5
- package/lib/WABinary/index.d.ts.map +1 -0
- package/lib/WABinary/index.js +6 -25
- package/lib/WABinary/index.js.map +1 -0
- package/lib/WABinary/jid-utils.d.ts +41 -51
- package/lib/WABinary/jid-utils.d.ts.map +1 -0
- package/lib/WABinary/jid-utils.js +83 -120
- package/lib/WABinary/jid-utils.js.map +1 -0
- package/lib/WABinary/types.d.ts +10 -13
- package/lib/WABinary/types.d.ts.map +1 -0
- package/lib/WABinary/types.js +2 -3
- package/lib/WABinary/types.js.map +1 -0
- package/lib/WAM/BinaryInfo.d.ts +8 -15
- package/lib/WAM/BinaryInfo.d.ts.map +1 -0
- package/lib/WAM/BinaryInfo.js +7 -14
- package/lib/WAM/BinaryInfo.js.map +1 -0
- package/lib/WAM/constants.d.ts +30 -37
- package/lib/WAM/constants.d.ts.map +1 -0
- package/lib/WAM/constants.js +19193 -11711
- package/lib/WAM/constants.js.map +1 -0
- package/lib/WAM/encode.d.ts +3 -3
- package/lib/WAM/encode.d.ts.map +1 -0
- package/lib/WAM/encode.js +95 -109
- package/lib/WAM/encode.js.map +1 -0
- package/lib/WAM/index.d.ts +4 -3
- package/lib/WAM/index.d.ts.map +1 -0
- package/lib/WAM/index.js +4 -23
- package/lib/WAM/index.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +37 -21
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +19 -22
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +18 -26
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +11 -12
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +12 -20
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +11 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +16 -24
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/index.d.ts +6 -6
- package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/index.js +6 -26
- package/lib/WAUSync/Protocols/index.js.map +1 -0
- package/lib/WAUSync/USyncQuery.d.ts +27 -28
- package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
- package/lib/WAUSync/USyncQuery.js +68 -62
- package/lib/WAUSync/USyncQuery.js.map +1 -0
- package/lib/WAUSync/USyncUser.d.ts +15 -10
- package/lib/WAUSync/USyncUser.d.ts.map +1 -0
- package/lib/WAUSync/USyncUser.js +20 -19
- package/lib/WAUSync/USyncUser.js.map +1 -0
- package/lib/WAUSync/index.d.ts +4 -3
- package/lib/WAUSync/index.d.ts.map +1 -0
- package/lib/WAUSync/index.js +4 -23
- package/lib/WAUSync/index.js.map +1 -0
- package/lib/index.d.ts +11 -12
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +12 -39
- package/lib/index.js.map +1 -0
- package/package.json +86 -42
|
@@ -1,388 +1,465 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import NodeCache from '@cacheable/node-cache';
|
|
2
|
+
import { Boom } from '@hapi/boom';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import Long from 'long';
|
|
5
|
+
import { proto } from '../../WAProto/index.js';
|
|
6
|
+
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
|
|
7
|
+
import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
+
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
|
+
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
|
+
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
|
+
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
12
|
+
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
|
|
13
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
14
|
+
import { extractGroupMetadata } from './groups.js';
|
|
15
|
+
import { makeMessagesSocket } from './messages-send.js';
|
|
16
|
+
const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
|
|
17
|
+
function isValidEnforcementType(value) {
|
|
18
|
+
return typeof value === 'string' && ENFORCEMENT_TYPE_VALUES.has(value);
|
|
7
19
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
shouldIgnoreJid,
|
|
32
|
-
enableAutoSessionRecreation
|
|
33
|
-
} = config
|
|
34
|
-
const sock = messages_send_1.makeMessagesSocket(config)
|
|
35
|
-
const {
|
|
36
|
-
ev,
|
|
37
|
-
authState,
|
|
38
|
-
ws,
|
|
39
|
-
processingMutex,
|
|
40
|
-
signalRepository,
|
|
41
|
-
query,
|
|
42
|
-
upsertMessage,
|
|
43
|
-
resyncAppState,
|
|
44
|
-
onUnexpectedError,
|
|
45
|
-
assertSessions,
|
|
46
|
-
sendNode,
|
|
47
|
-
relayMessage,
|
|
48
|
-
sendReceipt,
|
|
49
|
-
uploadPreKeys,
|
|
50
|
-
groupMetadata,
|
|
51
|
-
getUSyncDevices,
|
|
52
|
-
createParticipantNodes,
|
|
53
|
-
messageRetryManager,
|
|
54
|
-
sendPeerDataOperationMessage
|
|
55
|
-
} = sock
|
|
56
|
-
|
|
57
|
-
const retryMutex = make_mutex_1.makeMutex()
|
|
58
|
-
// Mutex terpisah dari processingMutex (dipakai chats.js/messages-send.js) supaya
|
|
59
|
-
// handleMessage/handleReceipt/handleNotification tidak saling antre di satu antrean
|
|
60
|
-
// yang sama. Kalau satu pesan lambat diproses (nunggu plugin/group metadata), receipt
|
|
61
|
-
// & notification lain tetap bisa jalan tanpa nunggu giliran.
|
|
62
|
-
const messageMutex = make_mutex_1.makeMutex()
|
|
63
|
-
const receiptMutex = make_mutex_1.makeMutex()
|
|
64
|
-
const notificationMutex = make_mutex_1.makeMutex()
|
|
65
|
-
|
|
66
|
-
const groupDataCache = new Map()
|
|
67
|
-
global.groupMetadataCache = async (jid) => {
|
|
68
|
-
if (!groupDataCache.has(jid)) {
|
|
69
|
-
groupDataCache.set(jid, groupMetadata(jid))
|
|
70
|
-
}
|
|
71
|
-
return await groupDataCache.get(jid)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const msgRetryCache = config.msgRetryCounterCache || new node_cache_1.default({
|
|
75
|
-
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
76
|
-
useClones: false
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const callOfferCache = config.callOfferCache || new node_cache_1.default({
|
|
80
|
-
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER,
|
|
81
|
-
useClones: false
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const placeholderResendCache = config.placeholderResendCache || new node_cache_1.default({
|
|
85
|
-
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
86
|
-
useClones: false
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
let sendActiveReceipts = false
|
|
90
|
-
|
|
91
|
-
const sendMessageAck = async ({
|
|
92
|
-
tag,
|
|
93
|
-
attrs,
|
|
94
|
-
content
|
|
95
|
-
}, errorCode) => {
|
|
96
|
-
const stanza = {
|
|
97
|
-
tag: 'ack',
|
|
98
|
-
attrs: {
|
|
99
|
-
id: attrs.id,
|
|
100
|
-
to: attrs.from,
|
|
101
|
-
class: tag
|
|
102
|
-
}
|
|
20
|
+
export const makeMessagesRecvSocket = (config) => {
|
|
21
|
+
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
22
|
+
const sock = makeMessagesSocket(config);
|
|
23
|
+
const { userDevicesCache, devicesMutex, ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock, placeholderResendCache } = sock;
|
|
24
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
25
|
+
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
26
|
+
const retryMutex = makeMutex();
|
|
27
|
+
const msgRetryCache = config.msgRetryCounterCache ||
|
|
28
|
+
new NodeCache({
|
|
29
|
+
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
30
|
+
useClones: false
|
|
31
|
+
});
|
|
32
|
+
const callOfferCache = config.callOfferCache ||
|
|
33
|
+
new NodeCache({
|
|
34
|
+
stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
35
|
+
useClones: false
|
|
36
|
+
});
|
|
37
|
+
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
38
|
+
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
39
|
+
let sendActiveReceipts = false;
|
|
40
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
41
|
+
if (!authState.creds.me?.id) {
|
|
42
|
+
throw new Boom('Not authenticated');
|
|
103
43
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!!attrs.recipient) {
|
|
114
|
-
stanza.attrs.recipient = attrs.recipient
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!!attrs.type && (tag !== 'message' || WABinary_1.getBinaryNodeChild({
|
|
118
|
-
tag,
|
|
119
|
-
attrs,
|
|
120
|
-
content
|
|
121
|
-
}, 'unavailable') || errorCode !== 0)) {
|
|
122
|
-
stanza.attrs.type = attrs.type
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (tag === 'message' && WABinary_1.getBinaryNodeChild({
|
|
126
|
-
tag,
|
|
127
|
-
attrs,
|
|
128
|
-
content
|
|
129
|
-
}, 'unavailable')) {
|
|
130
|
-
stanza.attrs.from = authState.creds.me.id
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
logger.debug({
|
|
134
|
-
recv: {
|
|
135
|
-
tag,
|
|
136
|
-
attrs
|
|
137
|
-
},
|
|
138
|
-
sent: stanza.attrs
|
|
139
|
-
}, 'sent ack')
|
|
140
|
-
await sendNode(stanza)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const offerCall = async (toJid, isVideo = false) => {
|
|
144
|
-
const callId = crypto_1.randomBytes(16).toString('hex').toUpperCase().substring(0, 64)
|
|
145
|
-
const offerContent = []
|
|
146
|
-
offerContent.push({
|
|
147
|
-
tag: 'audio',
|
|
148
|
-
attrs: {
|
|
149
|
-
enc: 'opus',
|
|
150
|
-
rate: '16000'
|
|
151
|
-
},
|
|
152
|
-
content: undefined
|
|
153
|
-
})
|
|
154
|
-
offerContent.push({
|
|
155
|
-
tag: 'audio',
|
|
156
|
-
attrs: {
|
|
157
|
-
enc: 'opus',
|
|
158
|
-
rate: '8000'
|
|
44
|
+
const pdoMessage = {
|
|
45
|
+
historySyncOnDemandRequest: {
|
|
46
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
47
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
48
|
+
oldestMsgId: oldestMsgKey.id,
|
|
49
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
50
|
+
onDemandMsgCount: count
|
|
159
51
|
},
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
enc: 'vp8',
|
|
168
|
-
dec: 'vp8',
|
|
169
|
-
orientation: '0',
|
|
170
|
-
'screen_width': '1920',
|
|
171
|
-
'screen_height': '1080',
|
|
172
|
-
'device_orientation': '0'
|
|
173
|
-
},
|
|
174
|
-
content: undefined
|
|
175
|
-
})
|
|
52
|
+
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
53
|
+
};
|
|
54
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
55
|
+
};
|
|
56
|
+
const requestPlaceholderResend = async (messageKey, msgData) => {
|
|
57
|
+
if (!authState.creds.me?.id) {
|
|
58
|
+
throw new Boom('Not authenticated');
|
|
176
59
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}) => WABinary_1.jidEncode(user, 's.whatsapp.net', device))
|
|
204
|
-
await assertSessions(devices, true)
|
|
205
|
-
|
|
206
|
-
const {
|
|
207
|
-
nodes: destinations,
|
|
208
|
-
shouldIncludeDeviceIdentity
|
|
209
|
-
} = await createParticipantNodes(devices, {
|
|
210
|
-
call: {
|
|
211
|
-
callKey: new Uint8Array(encKey)
|
|
60
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
61
|
+
logger.debug({ messageKey }, 'already requested resend');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Store original message data so PDO response handler can preserve
|
|
66
|
+
// metadata (LID details, timestamps, etc.) that the phone may omit
|
|
67
|
+
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
68
|
+
}
|
|
69
|
+
await delay(2000);
|
|
70
|
+
if (!(await placeholderResendCache.get(messageKey?.id))) {
|
|
71
|
+
logger.debug({ messageKey }, 'message received while resend requested');
|
|
72
|
+
return 'RESOLVED';
|
|
73
|
+
}
|
|
74
|
+
const pdoMessage = {
|
|
75
|
+
placeholderMessageResendRequest: [
|
|
76
|
+
{
|
|
77
|
+
messageKey
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
81
|
+
};
|
|
82
|
+
setTimeout(async () => {
|
|
83
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
84
|
+
logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
|
|
85
|
+
await placeholderResendCache.del(messageKey?.id);
|
|
212
86
|
}
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
87
|
+
}, 8000);
|
|
88
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
89
|
+
};
|
|
90
|
+
const handleMexNotification = async (node) => {
|
|
91
|
+
const updateNode = getBinaryNodeChild(node, 'update');
|
|
92
|
+
if (updateNode) {
|
|
93
|
+
const opName = updateNode.attrs?.op_name;
|
|
94
|
+
if (!opName) {
|
|
95
|
+
logger.warn({ node: binaryNodeToString(node) }, 'mex notification missing op_name, fallback to legacy');
|
|
96
|
+
await handleLegacyMexNewsletterNotification(node);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let mexResponse;
|
|
100
|
+
try {
|
|
101
|
+
mexResponse = JSON.parse(updateNode.content.toString());
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger.error({ err: error, opName }, 'failed to parse mex notification JSON');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (mexResponse.errors?.length) {
|
|
108
|
+
logger.warn({ errors: mexResponse.errors, opName }, 'mex notification has GQL errors');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const data = mexResponse.data;
|
|
112
|
+
if (!data) {
|
|
113
|
+
logger.warn({ opName }, 'mex notification has null data');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
logger.debug({ opName }, 'processing mex notification');
|
|
117
|
+
switch (opName) {
|
|
118
|
+
case 'NotificationUserReachoutTimelockUpdate':
|
|
119
|
+
handleReachoutTimelockNotification(data);
|
|
120
|
+
break;
|
|
121
|
+
case 'MessageCappingInfoNotification':
|
|
122
|
+
handleMessageCappingNotification(data);
|
|
123
|
+
break;
|
|
124
|
+
// newsletter ops still use the legacy <mex> child structure
|
|
125
|
+
case 'NotificationNewsletterUpdate':
|
|
126
|
+
case 'NotificationLinkedProfilesUpdates':
|
|
127
|
+
case 'NotificationNewsletterAdminPromote':
|
|
128
|
+
case 'NotificationNewsletterAdminDemote':
|
|
129
|
+
case 'NotificationNewsletterUserSettingChange':
|
|
130
|
+
case 'NotificationNewsletterJoin':
|
|
131
|
+
case 'NotificationNewsletterLeave':
|
|
132
|
+
case 'NotificationNewsletterStateChange':
|
|
133
|
+
case 'NotificationNewsletterAdminMetadataUpdate':
|
|
134
|
+
case 'NotificationNewsletterOwnerUpdate':
|
|
135
|
+
case 'NotificationNewsletterAdminInviteRevoke':
|
|
136
|
+
case 'NotificationNewsletterWamoSubStatusChange':
|
|
137
|
+
case 'NotificationNewsletterBlockUser':
|
|
138
|
+
case 'NotificationNewsletterPaidPartnership':
|
|
139
|
+
case 'NotificationNewsletterMilestone':
|
|
140
|
+
case 'NewsletterResponseStateUpdate':
|
|
141
|
+
await handleLegacyMexNewsletterNotification(node);
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
logger.debug({ opName }, 'unhandled mex notification');
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
251
148
|
}
|
|
252
|
-
|
|
253
|
-
|
|
149
|
+
await handleLegacyMexNewsletterNotification(node);
|
|
150
|
+
};
|
|
151
|
+
const handleReachoutTimelockNotification = (data) => {
|
|
152
|
+
const payload = data.xwa2_notify_account_reachout_timelock;
|
|
153
|
+
if (!payload) {
|
|
154
|
+
logger.warn('reachout timelock notification missing payload');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!payload.is_active) {
|
|
158
|
+
logger.info('reachout timelock restriction lifted');
|
|
159
|
+
ev.emit('connection.update', {
|
|
160
|
+
reachoutTimeLock: {
|
|
161
|
+
isActive: false,
|
|
162
|
+
enforcementType: ReachoutTimelockEnforcementType.DEFAULT
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// WA Web defaults to now+60s when the server omits the expiry
|
|
168
|
+
const timeEnforcementEnds = payload.time_enforcement_ends
|
|
169
|
+
? new Date(parseInt(payload.time_enforcement_ends, 10) * 1000)
|
|
170
|
+
: new Date(Date.now() + 60000);
|
|
171
|
+
const enforcementType = isValidEnforcementType(payload.enforcement_type)
|
|
172
|
+
? payload.enforcement_type
|
|
173
|
+
: ReachoutTimelockEnforcementType.DEFAULT;
|
|
174
|
+
logger.info({ enforcementType, timeEnforcementEnds }, 'reachout timelock restriction set');
|
|
175
|
+
ev.emit('connection.update', {
|
|
176
|
+
reachoutTimeLock: {
|
|
177
|
+
isActive: true,
|
|
178
|
+
timeEnforcementEnds,
|
|
179
|
+
enforcementType
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const handleMessageCappingNotification = (data) => {
|
|
184
|
+
const payload = data.xwa2_notify_new_chat_messages_capping_info_update;
|
|
185
|
+
if (!payload) {
|
|
186
|
+
logger.warn('message capping notification missing payload');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
logger.info({ payload }, 'received message capping update');
|
|
190
|
+
ev.emit('message-capping.update', payload);
|
|
191
|
+
};
|
|
192
|
+
const handleLegacyMexNewsletterNotification = async (node) => {
|
|
193
|
+
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
194
|
+
const updateNode = mexNode?.content ? null : getBinaryNodeChild(node, 'update') || getAllBinaryNodeChildren(node)[0];
|
|
195
|
+
const payloadNode = mexNode?.content ? mexNode : updateNode;
|
|
196
|
+
if (!payloadNode?.content) {
|
|
197
|
+
logger.warn({ node: binaryNodeToString(node) }, 'invalid mex newsletter notification');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
let data;
|
|
201
|
+
try {
|
|
202
|
+
const payloadContent = payloadNode.content;
|
|
203
|
+
if (Array.isArray(payloadContent)) {
|
|
204
|
+
logger.warn({ payloadNode }, 'invalid mex newsletter notification payload format');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const contentBuf = typeof payloadContent === 'string' ? Buffer.from(payloadContent, 'binary') : Buffer.from(payloadContent);
|
|
208
|
+
data = JSON.parse(contentBuf.toString());
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
logger.error({ err: error, node: binaryNodeToString(node) }, 'failed to parse mex newsletter notification');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const operation = data?.operation ?? payloadNode?.attrs?.op_name;
|
|
215
|
+
let updates = data?.updates;
|
|
216
|
+
if (!updates) {
|
|
217
|
+
const linkedProfiles = data?.data?.xwa2_notify_linked_profiles;
|
|
218
|
+
if (linkedProfiles) {
|
|
219
|
+
updates = [linkedProfiles];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!updates || !operation) {
|
|
223
|
+
logger.warn({ data }, 'invalid mex newsletter notification content');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
227
|
+
switch (operation) {
|
|
228
|
+
case 'NotificationNewsletterUpdate':
|
|
229
|
+
for (const update of updates) {
|
|
230
|
+
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
231
|
+
ev.emit('newsletter-settings.update', {
|
|
232
|
+
id: update.jid,
|
|
233
|
+
update: update.settings
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
case 'NotificationNewsletterAdminPromote':
|
|
239
|
+
for (const update of updates) {
|
|
240
|
+
if (update.jid && update.user) {
|
|
241
|
+
ev.emit('newsletter-participants.update', {
|
|
242
|
+
id: update.jid,
|
|
243
|
+
author: node.attrs.from,
|
|
244
|
+
user: update.user,
|
|
245
|
+
new_role: 'ADMIN',
|
|
246
|
+
action: 'promote'
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case 'NotificationLinkedProfilesUpdates':
|
|
252
|
+
for (const update of updates) {
|
|
253
|
+
const lid = update?.jid;
|
|
254
|
+
const addedProfiles = Array.isArray(update?.added_profiles) ? update.added_profiles : [];
|
|
255
|
+
const mappings = [];
|
|
256
|
+
for (const profile of addedProfiles) {
|
|
257
|
+
const pn = typeof profile === 'string' ? profile : (profile?.pn ?? profile?.jid ?? null);
|
|
258
|
+
if (lid && pn) {
|
|
259
|
+
const mapping = { lid, pn };
|
|
260
|
+
ev.emit('lid-mapping.update', mapping);
|
|
261
|
+
mappings.push(mapping);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
await signalRepository.lidMapping.storeLIDPNMappings(mappings);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
default:
|
|
268
|
+
logger.info({ operation, data }, 'unhandled mex newsletter notification');
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
// Handles newsletter notifications
|
|
273
|
+
const handleNewsletterNotification = async (node) => {
|
|
274
|
+
const from = node.attrs.from;
|
|
275
|
+
const children = getAllBinaryNodeChildren(node);
|
|
276
|
+
const author = node.attrs.participant;
|
|
277
|
+
for (const child of children) {
|
|
278
|
+
logger.debug({ from, child }, 'got newsletter notification');
|
|
279
|
+
switch (child.tag) {
|
|
280
|
+
case 'reaction': {
|
|
281
|
+
const reactionUpdate = {
|
|
282
|
+
id: from,
|
|
283
|
+
server_id: child.attrs.message_id,
|
|
284
|
+
reaction: {
|
|
285
|
+
code: getBinaryNodeChildString(child, 'reaction'),
|
|
286
|
+
count: 1
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case 'view': {
|
|
293
|
+
const viewUpdate = {
|
|
294
|
+
id: from,
|
|
295
|
+
server_id: child.attrs.message_id,
|
|
296
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
297
|
+
};
|
|
298
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case 'participant': {
|
|
302
|
+
const participantUpdate = {
|
|
303
|
+
id: from,
|
|
304
|
+
author,
|
|
305
|
+
user: child.attrs.jid,
|
|
306
|
+
action: child.attrs.action,
|
|
307
|
+
new_role: child.attrs.role
|
|
308
|
+
};
|
|
309
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case 'update': {
|
|
313
|
+
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
314
|
+
if (settingsNode) {
|
|
315
|
+
const update = {};
|
|
316
|
+
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
317
|
+
if (nameNode?.content)
|
|
318
|
+
update.name = nameNode.content.toString();
|
|
319
|
+
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
320
|
+
if (descriptionNode?.content)
|
|
321
|
+
update.description = descriptionNode.content.toString();
|
|
322
|
+
ev.emit('newsletter-settings.update', {
|
|
323
|
+
id: from,
|
|
324
|
+
update
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case 'message': {
|
|
330
|
+
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
331
|
+
if (plaintextNode?.content) {
|
|
332
|
+
try {
|
|
333
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
334
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
335
|
+
: Buffer.from(plaintextNode.content);
|
|
336
|
+
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
337
|
+
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
338
|
+
key: {
|
|
339
|
+
remoteJid: from,
|
|
340
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
341
|
+
fromMe: false // TODO: is this really true though
|
|
342
|
+
},
|
|
343
|
+
message: messageProto,
|
|
344
|
+
messageTimestamp: +child.attrs.t
|
|
345
|
+
}).toJSON();
|
|
346
|
+
await upsertMessage(fullMessage, 'append');
|
|
347
|
+
logger.debug('Processed plaintext newsletter message');
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
logger.warn({ node, child }, 'Unknown newsletter notification child');
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const sendMessageAck = async (node, errorCode) => {
|
|
362
|
+
const stanza = buildAckStanza(node, errorCode, authState.creds.me.id);
|
|
363
|
+
logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
|
|
364
|
+
await sendNode(stanza);
|
|
365
|
+
};
|
|
254
366
|
const rejectCall = async (callId, callFrom) => {
|
|
255
|
-
const stanza =
|
|
367
|
+
const stanza = {
|
|
256
368
|
tag: 'call',
|
|
257
369
|
attrs: {
|
|
258
370
|
from: authState.creds.me.id,
|
|
259
|
-
to: callFrom
|
|
371
|
+
to: callFrom
|
|
260
372
|
},
|
|
261
|
-
content: [
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
373
|
+
content: [
|
|
374
|
+
{
|
|
375
|
+
tag: 'reject',
|
|
376
|
+
attrs: {
|
|
377
|
+
'call-id': callId,
|
|
378
|
+
'call-creator': callFrom,
|
|
379
|
+
count: '0'
|
|
380
|
+
},
|
|
381
|
+
content: undefined
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
};
|
|
385
|
+
await query(stanza);
|
|
386
|
+
};
|
|
275
387
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
276
|
-
const {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const {
|
|
280
|
-
key: msgKey
|
|
281
|
-
} = fullMessage
|
|
282
|
-
const msgId = msgKey.id
|
|
283
|
-
|
|
388
|
+
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
389
|
+
const { key: msgKey } = fullMessage;
|
|
390
|
+
const msgId = msgKey.id;
|
|
284
391
|
if (messageRetryManager) {
|
|
285
392
|
// Check if we've exceeded max retries using the new system
|
|
286
393
|
if (messageRetryManager.hasExceededMaxRetries(msgId)) {
|
|
287
|
-
logger.debug({
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
messageRetryManager.markRetryFailed(msgId)
|
|
291
|
-
return
|
|
394
|
+
logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
|
|
395
|
+
messageRetryManager.markRetryFailed(msgId);
|
|
396
|
+
return;
|
|
292
397
|
}
|
|
293
|
-
|
|
294
398
|
// Increment retry count using new system
|
|
295
|
-
const retryCount = messageRetryManager.incrementRetryCount(msgId)
|
|
296
|
-
|
|
399
|
+
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
297
400
|
// Use the new retry count for the rest of the logic
|
|
298
|
-
const key = `${msgId}:${msgKey?.participant}
|
|
299
|
-
msgRetryCache.set(key, retryCount)
|
|
300
|
-
}
|
|
401
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
402
|
+
await msgRetryCache.set(key, retryCount);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
301
405
|
// Fallback to old system
|
|
302
|
-
const key = `${msgId}:${msgKey?.participant}
|
|
303
|
-
let retryCount = (await msgRetryCache.get(key)) || 0
|
|
304
|
-
|
|
406
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
407
|
+
let retryCount = (await msgRetryCache.get(key)) || 0;
|
|
305
408
|
if (retryCount >= maxMsgRetryCount) {
|
|
306
|
-
logger.debug({
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}, 'reached retry limit, clearing')
|
|
310
|
-
msgRetryCache.del(key)
|
|
311
|
-
return
|
|
409
|
+
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
410
|
+
await msgRetryCache.del(key);
|
|
411
|
+
return;
|
|
312
412
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
const {
|
|
321
|
-
account,
|
|
322
|
-
signedPreKey,
|
|
323
|
-
signedIdentityKey: identityKey
|
|
324
|
-
} = authState.creds
|
|
325
|
-
const fromJid = node.attrs.from
|
|
326
|
-
|
|
413
|
+
retryCount += 1;
|
|
414
|
+
await msgRetryCache.set(key, retryCount);
|
|
415
|
+
}
|
|
416
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
417
|
+
const retryCount = (await msgRetryCache.get(key)) || 1;
|
|
418
|
+
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
419
|
+
const fromJid = node.attrs.from;
|
|
327
420
|
// Check if we should recreate the session
|
|
328
|
-
let shouldRecreateSession = false
|
|
329
|
-
let recreateReason = ''
|
|
330
|
-
|
|
331
|
-
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
421
|
+
let shouldRecreateSession = false;
|
|
422
|
+
let recreateReason = '';
|
|
423
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
332
424
|
try {
|
|
333
425
|
// Check if we have a session with this JID
|
|
334
|
-
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
|
|
335
|
-
const hasSession = await signalRepository.validateSession(fromJid)
|
|
336
|
-
const result = messageRetryManager.shouldRecreateSession(fromJid,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
recreateReason = result.reason
|
|
340
|
-
|
|
426
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
|
|
427
|
+
const hasSession = await signalRepository.validateSession(fromJid);
|
|
428
|
+
const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
|
|
429
|
+
shouldRecreateSession = result.recreate;
|
|
430
|
+
recreateReason = result.reason;
|
|
341
431
|
if (shouldRecreateSession) {
|
|
342
|
-
logger.
|
|
343
|
-
fromJid,
|
|
344
|
-
retryCount,
|
|
345
|
-
reason: recreateReason
|
|
346
|
-
}, 'recreating session for retry')
|
|
432
|
+
logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
|
|
347
433
|
// Delete existing session to force recreation
|
|
348
|
-
await authState.keys.set({
|
|
349
|
-
|
|
350
|
-
[sessionId]: null
|
|
351
|
-
}
|
|
352
|
-
})
|
|
353
|
-
forceIncludeKeys = true
|
|
434
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
435
|
+
forceIncludeKeys = true;
|
|
354
436
|
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
fromJid
|
|
359
|
-
}, 'failed to check session recreation')
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
logger.warn({ error, fromJid }, 'failed to check session recreation');
|
|
360
440
|
}
|
|
361
441
|
}
|
|
362
|
-
|
|
363
442
|
if (retryCount <= 2) {
|
|
364
443
|
// Use new retry manager for phone requests if available
|
|
365
444
|
if (messageRetryManager) {
|
|
366
445
|
// Schedule phone request with delay (like whatsmeow)
|
|
367
446
|
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
368
447
|
try {
|
|
369
|
-
const
|
|
370
|
-
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId} (scheduled)`)
|
|
371
|
-
} catch (error) {
|
|
372
|
-
logger.warn({
|
|
373
|
-
error,
|
|
374
|
-
msgId
|
|
375
|
-
}, 'failed to send scheduled phone request');
|
|
448
|
+
const requestId = await requestPlaceholderResend(msgKey);
|
|
449
|
+
logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
|
|
376
450
|
}
|
|
377
|
-
|
|
378
|
-
|
|
451
|
+
catch (error) {
|
|
452
|
+
logger.warn({ error, msgId }, 'failed to send scheduled phone request');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
379
457
|
// Fallback to immediate request
|
|
380
|
-
const msgId = await requestPlaceholderResend(msgKey)
|
|
381
|
-
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`)
|
|
458
|
+
const msgId = await requestPlaceholderResend(msgKey);
|
|
459
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
382
460
|
}
|
|
383
461
|
}
|
|
384
|
-
|
|
385
|
-
const deviceIdentity = Utils_1.encodeSignedDeviceIdentity(account, true)
|
|
462
|
+
const deviceIdentity = encodeSignedDeviceIdentity(account, true);
|
|
386
463
|
await authState.keys.transaction(async () => {
|
|
387
464
|
const receipt = {
|
|
388
465
|
tag: 'receipt',
|
|
@@ -391,1311 +468,1031 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
391
468
|
type: 'retry',
|
|
392
469
|
to: node.attrs.from
|
|
393
470
|
},
|
|
394
|
-
content: [
|
|
471
|
+
content: [
|
|
472
|
+
{
|
|
395
473
|
tag: 'retry',
|
|
396
474
|
attrs: {
|
|
397
475
|
count: retryCount.toString(),
|
|
398
476
|
id: node.attrs.id,
|
|
399
477
|
t: node.attrs.t,
|
|
400
|
-
v: '1'
|
|
478
|
+
v: '1',
|
|
479
|
+
// ADD ERROR FIELD
|
|
480
|
+
error: '0'
|
|
401
481
|
}
|
|
402
482
|
},
|
|
403
483
|
{
|
|
404
484
|
tag: 'registration',
|
|
405
485
|
attrs: {},
|
|
406
|
-
content:
|
|
486
|
+
content: encodeBigEndian(authState.creds.registrationId)
|
|
407
487
|
}
|
|
408
488
|
]
|
|
409
|
-
}
|
|
410
|
-
|
|
489
|
+
};
|
|
411
490
|
if (node.attrs.recipient) {
|
|
412
|
-
receipt.attrs.recipient = node.attrs.recipient
|
|
491
|
+
receipt.attrs.recipient = node.attrs.recipient;
|
|
413
492
|
}
|
|
414
|
-
|
|
415
493
|
if (node.attrs.participant) {
|
|
416
|
-
receipt.attrs.participant = node.attrs.participant
|
|
494
|
+
receipt.attrs.participant = node.attrs.participant;
|
|
417
495
|
}
|
|
418
|
-
|
|
419
496
|
if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
|
|
420
|
-
const {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const [keyId] = Object.keys(preKeys)
|
|
425
|
-
const key = preKeys[+keyId]
|
|
426
|
-
const content = receipt.content
|
|
427
|
-
|
|
497
|
+
const { update, preKeys } = await getNextPreKeys(authState, 1);
|
|
498
|
+
const [keyId] = Object.keys(preKeys);
|
|
499
|
+
const key = preKeys[+keyId];
|
|
500
|
+
const content = receipt.content;
|
|
428
501
|
content.push({
|
|
429
502
|
tag: 'keys',
|
|
430
503
|
attrs: {},
|
|
431
|
-
content: [
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{
|
|
437
|
-
tag: 'identity',
|
|
438
|
-
attrs: {},
|
|
439
|
-
content: identityKey.public
|
|
440
|
-
},
|
|
441
|
-
Utils_1.xmppPreKey(key, +keyId),
|
|
442
|
-
Utils_1.xmppSignedPreKey(signedPreKey),
|
|
443
|
-
{
|
|
444
|
-
tag: 'device-identity',
|
|
445
|
-
attrs: {},
|
|
446
|
-
content: deviceIdentity
|
|
447
|
-
}
|
|
504
|
+
content: [
|
|
505
|
+
{ tag: 'type', attrs: {}, content: Buffer.from(KEY_BUNDLE_TYPE) },
|
|
506
|
+
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
|
507
|
+
xmppPreKey(key, +keyId),
|
|
508
|
+
xmppSignedPreKey(signedPreKey),
|
|
509
|
+
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
|
448
510
|
]
|
|
449
511
|
});
|
|
450
|
-
ev.emit('creds.update', update)
|
|
512
|
+
ev.emit('creds.update', update);
|
|
451
513
|
}
|
|
452
|
-
await sendNode(receipt)
|
|
453
|
-
logger.info({
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
514
|
+
await sendNode(receipt);
|
|
515
|
+
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
516
|
+
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
517
|
+
};
|
|
518
|
+
// Mirrors WAWeb/Handle/PreKeyLow.js: skip a re-issued notification with the same stanza id.
|
|
519
|
+
const inFlightPreKeyLow = new Set();
|
|
520
|
+
/**
|
|
521
|
+
* Fire-and-forget tctoken re-issuance after a peer's device identity changed.
|
|
522
|
+
* Mirrors WAWebSendTcTokenWhenDeviceIdentityChange — runs in parallel with
|
|
523
|
+
* the session refresh (not after it).
|
|
524
|
+
*/
|
|
525
|
+
const reissueTcTokenAfterIdentityChange = (from) => {
|
|
526
|
+
void (async () => {
|
|
527
|
+
const normalizedJid = jidNormalizedUser(from);
|
|
528
|
+
const tcJid = await resolveTcTokenJid(normalizedJid, getLIDForPN);
|
|
529
|
+
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
530
|
+
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
531
|
+
if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
535
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
536
|
+
const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
537
|
+
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
538
|
+
await storeTcTokensFromIqResult({
|
|
539
|
+
result,
|
|
540
|
+
fallbackJid: tcJid,
|
|
541
|
+
keys: authState.keys,
|
|
542
|
+
getLIDForPN,
|
|
543
|
+
onNewJidStored: trackTcTokenJid
|
|
544
|
+
});
|
|
545
|
+
})().catch(err => {
|
|
546
|
+
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
547
|
+
});
|
|
548
|
+
};
|
|
460
549
|
const handleEncryptNotification = async (node) => {
|
|
461
|
-
const from = node.attrs.from
|
|
462
|
-
if (from ===
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
550
|
+
const from = node.attrs.from;
|
|
551
|
+
if (from === S_WHATSAPP_NET) {
|
|
552
|
+
const stanzaId = node.attrs.id;
|
|
553
|
+
if (stanzaId && inFlightPreKeyLow.has(stanzaId)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const countChild = getBinaryNodeChild(node, 'count');
|
|
557
|
+
const count = +countChild.attrs.value;
|
|
558
|
+
const shouldUploadMorePreKeys = count < MIN_PREKEY_COUNT;
|
|
559
|
+
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
471
560
|
if (shouldUploadMorePreKeys) {
|
|
472
|
-
|
|
561
|
+
if (stanzaId)
|
|
562
|
+
inFlightPreKeyLow.add(stanzaId);
|
|
563
|
+
try {
|
|
564
|
+
await uploadPreKeys();
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
if (stanzaId)
|
|
568
|
+
inFlightPreKeyLow.delete(stanzaId);
|
|
569
|
+
}
|
|
473
570
|
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const result = await handleIdentityChange(node, {
|
|
574
|
+
meId: authState.creds.me?.id,
|
|
575
|
+
meLid: authState.creds.me?.lid,
|
|
576
|
+
validateSession: signalRepository.validateSession,
|
|
577
|
+
assertSessions,
|
|
578
|
+
debounceCache: identityAssertDebounce,
|
|
579
|
+
logger,
|
|
580
|
+
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
581
|
+
});
|
|
582
|
+
if (result.action === 'no_identity_node') {
|
|
583
|
+
logger.info({ node }, 'unknown encrypt notification');
|
|
486
584
|
}
|
|
487
585
|
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
586
|
+
};
|
|
587
|
+
const handleGroupNotification = (fullNode, child, msg) => {
|
|
588
|
+
// TODO: Support PN/LID (Here is only LID now)
|
|
589
|
+
const actingParticipantLid = fullNode.attrs.participant;
|
|
590
|
+
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
591
|
+
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
592
|
+
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
593
|
+
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
594
|
+
switch (child?.tag) {
|
|
495
595
|
case 'create':
|
|
496
|
-
const metadata =
|
|
497
|
-
msg.messageStubType =
|
|
498
|
-
msg.messageStubParameters = [metadata.subject]
|
|
499
|
-
msg.key = {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
ev.emit('groups.upsert', [
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
break
|
|
596
|
+
const metadata = extractGroupMetadata(child);
|
|
597
|
+
msg.messageStubType = WAMessageStubType.GROUP_CREATE;
|
|
598
|
+
msg.messageStubParameters = [metadata.subject];
|
|
599
|
+
msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
|
|
600
|
+
ev.emit('chats.upsert', [
|
|
601
|
+
{
|
|
602
|
+
id: metadata.id,
|
|
603
|
+
name: metadata.subject,
|
|
604
|
+
conversationTimestamp: metadata.creation
|
|
605
|
+
}
|
|
606
|
+
]);
|
|
607
|
+
ev.emit('groups.upsert', [
|
|
608
|
+
{
|
|
609
|
+
...metadata,
|
|
610
|
+
author: actingParticipantLid,
|
|
611
|
+
authorPn: actingParticipantPn,
|
|
612
|
+
authorUsername: actingParticipantUsername
|
|
613
|
+
}
|
|
614
|
+
]);
|
|
615
|
+
break;
|
|
516
616
|
case 'ephemeral':
|
|
517
617
|
case 'not_ephemeral':
|
|
518
618
|
msg.message = {
|
|
519
619
|
protocolMessage: {
|
|
520
|
-
type:
|
|
620
|
+
type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
521
621
|
ephemeralExpiration: +(child.attrs.expiration || 0)
|
|
522
622
|
}
|
|
523
|
-
}
|
|
524
|
-
break
|
|
623
|
+
};
|
|
624
|
+
break;
|
|
525
625
|
case 'modify':
|
|
526
|
-
const oldNumber =
|
|
527
|
-
msg.messageStubParameters = oldNumber || []
|
|
528
|
-
msg.messageStubType =
|
|
529
|
-
break
|
|
626
|
+
const oldNumber = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid);
|
|
627
|
+
msg.messageStubParameters = oldNumber || [];
|
|
628
|
+
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
|
|
629
|
+
break;
|
|
530
630
|
case 'promote':
|
|
531
631
|
case 'demote':
|
|
532
632
|
case 'remove':
|
|
533
633
|
case 'add':
|
|
534
634
|
case 'leave':
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
635
|
+
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
636
|
+
msg.messageStubType = WAMessageStubType[stubType];
|
|
637
|
+
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
|
638
|
+
// TODO: Store LID MAPPINGS
|
|
639
|
+
return {
|
|
640
|
+
id: attrs.jid,
|
|
641
|
+
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
642
|
+
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
643
|
+
username: attrs.participant_username || attrs.username || undefined,
|
|
644
|
+
admin: (attrs.type || null)
|
|
645
|
+
};
|
|
646
|
+
});
|
|
541
647
|
if (participants.length === 1 &&
|
|
542
648
|
// if recv. "remove" message and sender removed themselves
|
|
543
649
|
// mark as left
|
|
544
|
-
|
|
650
|
+
(areJidsSameUser(participants[0].id, actingParticipantLid) ||
|
|
651
|
+
areJidsSameUser(participants[0].id, actingParticipantPn)) &&
|
|
545
652
|
child.tag === 'remove') {
|
|
546
|
-
msg.messageStubType =
|
|
653
|
+
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
547
654
|
}
|
|
548
|
-
msg.messageStubParameters = participants
|
|
549
|
-
break
|
|
655
|
+
msg.messageStubParameters = participants.map(a => JSON.stringify(a));
|
|
656
|
+
break;
|
|
550
657
|
case 'subject':
|
|
551
|
-
msg.messageStubType =
|
|
552
|
-
msg.messageStubParameters = [
|
|
553
|
-
break
|
|
658
|
+
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
659
|
+
msg.messageStubParameters = [child.attrs.subject];
|
|
660
|
+
break;
|
|
554
661
|
case 'description':
|
|
555
|
-
const description =
|
|
556
|
-
msg.messageStubType =
|
|
557
|
-
msg.messageStubParameters = description ? [description] : undefined
|
|
558
|
-
break
|
|
662
|
+
const description = getBinaryNodeChild(child, 'body')?.content?.toString();
|
|
663
|
+
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
|
|
664
|
+
msg.messageStubParameters = description ? [description] : undefined;
|
|
665
|
+
break;
|
|
559
666
|
case 'announcement':
|
|
560
667
|
case 'not_announcement':
|
|
561
|
-
msg.messageStubType =
|
|
562
|
-
msg.messageStubParameters = [
|
|
563
|
-
break
|
|
668
|
+
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
|
|
669
|
+
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
|
|
670
|
+
break;
|
|
564
671
|
case 'locked':
|
|
565
672
|
case 'unlocked':
|
|
566
|
-
msg.messageStubType =
|
|
567
|
-
msg.messageStubParameters = [
|
|
568
|
-
break
|
|
673
|
+
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT;
|
|
674
|
+
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
|
|
675
|
+
break;
|
|
569
676
|
case 'invite':
|
|
570
|
-
msg.messageStubType =
|
|
571
|
-
msg.messageStubParameters = [child.attrs.code]
|
|
572
|
-
break
|
|
677
|
+
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
|
|
678
|
+
msg.messageStubParameters = [child.attrs.code];
|
|
679
|
+
break;
|
|
573
680
|
case 'member_add_mode':
|
|
574
|
-
const addMode = child.content
|
|
681
|
+
const addMode = child.content;
|
|
575
682
|
if (addMode) {
|
|
576
|
-
msg.messageStubType =
|
|
577
|
-
msg.messageStubParameters = [addMode.toString()]
|
|
683
|
+
msg.messageStubType = WAMessageStubType.GROUP_MEMBER_ADD_MODE;
|
|
684
|
+
msg.messageStubParameters = [addMode.toString()];
|
|
578
685
|
}
|
|
579
|
-
break
|
|
686
|
+
break;
|
|
580
687
|
case 'membership_approval_mode':
|
|
581
|
-
const approvalMode =
|
|
688
|
+
const approvalMode = getBinaryNodeChild(child, 'group_join');
|
|
582
689
|
if (approvalMode) {
|
|
583
|
-
msg.messageStubType =
|
|
584
|
-
msg.messageStubParameters = [approvalMode.attrs.state]
|
|
690
|
+
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
|
|
691
|
+
msg.messageStubParameters = [approvalMode.attrs.state];
|
|
585
692
|
}
|
|
586
|
-
break
|
|
693
|
+
break;
|
|
587
694
|
case 'created_membership_requests':
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
695
|
+
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
696
|
+
msg.messageStubParameters = [
|
|
697
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
698
|
+
'created',
|
|
699
|
+
child.attrs.request_method
|
|
700
|
+
];
|
|
701
|
+
break;
|
|
593
702
|
case 'revoked_membership_requests':
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
msg.
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const type = child.attrs?.unlink_type || child.attrs?.link_type
|
|
603
|
-
const stubMap = {
|
|
604
|
-
parent_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_PARENT_GROUP`],
|
|
605
|
-
sibling_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_SIBLING_GROUP`],
|
|
606
|
-
sub_group: Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_SUB_GROUP`]
|
|
607
|
-
}
|
|
608
|
-
const groups = WABinary_1.getBinaryNodeChildren(child, 'group')
|
|
609
|
-
.map(g => g.attrs?.jid || g.attrs?.subject || '')
|
|
610
|
-
.filter(x => x)
|
|
611
|
-
msg.messageStubType = stubMap?.[type] || Types_1.WAMessageStubType[`COMMUNITY_${child.tag.toUpperCase()}_PARENT_GROUP`]
|
|
612
|
-
msg.messageStubParameters = [participantJid, child.tag, groups]
|
|
613
|
-
break
|
|
614
|
-
case 'linked_group_promote':
|
|
615
|
-
case 'linked_group_demote':
|
|
616
|
-
const stubtype = `COMMUNITY_PARTICIPANT_${child.tag.split('_')[2].toUpperCase()}`
|
|
617
|
-
const participantS = mode === 'lid' ? WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.phone_number) : WABinary_1.getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
|
618
|
-
msg.messageStubType = Types_1.WAMessageStubType[stubtype]
|
|
619
|
-
msg.messageStubParameters = participantS
|
|
620
|
-
break
|
|
621
|
-
case 'created_sub_group_suggestion':
|
|
622
|
-
msg.messageStubType = Types_1.WAMessageStubType.SUGGESTED_SUBGROUP_ANNOUNCE
|
|
623
|
-
msg.messageStubParameters = [participantJid, 'add']
|
|
624
|
-
break
|
|
625
|
-
case 'revoked_sub_group_suggestions':
|
|
626
|
-
const res = WABinary_1.getBinaryNodeChildren(child, 'sub_group_suggestions')
|
|
627
|
-
const reason = res.attrs?.reason
|
|
628
|
-
if (reason === 'approved') msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE
|
|
629
|
-
else msg.messageStubType = Types_1.WAMessageStubType.GENERIC_NOTIFICATION
|
|
630
|
-
msg.messageStubParameters = [participantJid, reason]
|
|
631
|
-
break
|
|
632
|
-
default:
|
|
633
|
-
logger.warn(child.tag, 'Unhandled group node')
|
|
634
|
-
break
|
|
703
|
+
const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
|
|
704
|
+
// TODO: LIDMAPPING SUPPORT
|
|
705
|
+
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
706
|
+
msg.messageStubParameters = [
|
|
707
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
708
|
+
isDenied ? 'revoked' : 'rejected'
|
|
709
|
+
];
|
|
710
|
+
break;
|
|
635
711
|
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const reactionsList = WABinary_1.getBinaryNodeChild(node, 'reactions')
|
|
644
|
-
const viewsList = WABinary_1.getBinaryNodeChild(node, 'views_count')
|
|
645
|
-
|
|
646
|
-
if (reactionsList) {
|
|
647
|
-
const reactions = WABinary_1.getBinaryNodeChild(reactionsList, 'reaction')
|
|
648
|
-
|
|
649
|
-
if (reactions.length === 0) {
|
|
650
|
-
ev.emit('newsletter.reaction', {
|
|
651
|
-
id,
|
|
652
|
-
newsletter_server_id: serverId,
|
|
653
|
-
reaction: {
|
|
654
|
-
removed: true
|
|
655
|
-
}
|
|
656
|
-
})
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
reactions.forEach(item => {
|
|
660
|
-
ev.emit('newsletter.reaction', {
|
|
661
|
-
id,
|
|
662
|
-
newsletter_server_id: serverId,
|
|
663
|
-
reaction: {
|
|
664
|
-
code: item.attrs?.code,
|
|
665
|
-
count: +item.attrs.count
|
|
666
|
-
}
|
|
667
|
-
})
|
|
668
|
-
})
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
if (viewsList.length) {
|
|
672
|
-
viewsList.forEach(item => {
|
|
673
|
-
ev.emit('newsletter.view', {
|
|
674
|
-
id,
|
|
675
|
-
newsletter_server_id: serverId,
|
|
676
|
-
count: +item.attrs.count
|
|
677
|
-
})
|
|
678
|
-
})
|
|
712
|
+
};
|
|
713
|
+
const handleDevicesNotification = async (node) => {
|
|
714
|
+
const [child] = getAllBinaryNodeChildren(node);
|
|
715
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
716
|
+
if (!child) {
|
|
717
|
+
logger.debug({ from }, 'devices notification missing child, skipping');
|
|
718
|
+
return;
|
|
679
719
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
id,
|
|
701
|
-
author: contentPath.updated_by?.pn ? contentPath.updated_by.pn : contentPath.updated_by.id,
|
|
702
|
-
action: `${contentPath.properties.limit_sharing.limit_sharing_enabled ? 'on' : 'off'}`,
|
|
703
|
-
trigger: contentPath.properties.limit_sharing.limit_sharing_trigger,
|
|
704
|
-
update_time: contentPath.update_time
|
|
705
|
-
})
|
|
706
|
-
} else if (operation === Types_1.MexUpdatesOperations.OWNER_COMMUNITY) {
|
|
707
|
-
contentPath = content.data[Types_1.XWAPathsMexUpdates.COMMUNITY_OWNER_CHANGE]
|
|
708
|
-
|
|
709
|
-
ev.emit('community-owner.update', {
|
|
710
|
-
id,
|
|
711
|
-
author: contentPath.updated_by?.pn ? contentPath.updated_by.pn : contentPath.updated_by.id,
|
|
712
|
-
user: contentPath.role_updates[0].user?.pn ? contentPath.role_updates[0].user.pn : contentPath.role_updates[0].user.jid,
|
|
713
|
-
new_role: contentPath.role_updates[0].new_role,
|
|
714
|
-
update_time: contentPath.update_time
|
|
715
|
-
})
|
|
716
|
-
} else {
|
|
717
|
-
|
|
718
|
-
if (operation === Types_1.MexOperations.PROMOTE) {
|
|
719
|
-
action = 'promote'
|
|
720
|
-
contentPath = content.data[Types_1.XWAPaths.PROMOTE]
|
|
721
|
-
} else {
|
|
722
|
-
action = 'demote'
|
|
723
|
-
contentPath = content.data[Types_1.XWAPaths.DEMOTE]
|
|
720
|
+
const tag = child.tag;
|
|
721
|
+
const deviceHash = child.attrs.device_hash;
|
|
722
|
+
const devices = getBinaryNodeChildren(child, 'device');
|
|
723
|
+
if (areJidsSameUser(from, authState.creds.me.id) || areJidsSameUser(from, authState.creds.me.lid)) {
|
|
724
|
+
const deviceJids = devices.map(d => d.attrs.jid);
|
|
725
|
+
logger.info({ deviceJids }, 'got my own devices');
|
|
726
|
+
}
|
|
727
|
+
if (!devices.length) {
|
|
728
|
+
logger.debug({ from, tag }, 'no devices in notification, skipping');
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const decoded = [];
|
|
732
|
+
for (const d of devices) {
|
|
733
|
+
const jid = d.attrs.jid;
|
|
734
|
+
if (!jid)
|
|
735
|
+
continue;
|
|
736
|
+
const parts = jidDecode(jid);
|
|
737
|
+
if (!parts) {
|
|
738
|
+
logger.debug({ jid }, 'failed to decode device jid, skipping');
|
|
739
|
+
continue;
|
|
724
740
|
}
|
|
725
|
-
|
|
726
|
-
ev.emit('newsletter-participants.update', {
|
|
727
|
-
id,
|
|
728
|
-
author: contentPath.actor.pn,
|
|
729
|
-
user: contentPath.user.pn,
|
|
730
|
-
new_role: contentPath.user_new_role,
|
|
731
|
-
action
|
|
732
|
-
})
|
|
741
|
+
decoded.push({ jid, user: parts.user, server: parts.server, device: parts.device });
|
|
733
742
|
}
|
|
734
|
-
|
|
735
|
-
|
|
743
|
+
if (!decoded.length)
|
|
744
|
+
return;
|
|
745
|
+
await devicesMutex.mutex(async () => {
|
|
746
|
+
const byUser = new Map();
|
|
747
|
+
for (const d of decoded) {
|
|
748
|
+
const list = byUser.get(d.user) || [];
|
|
749
|
+
list.push(d);
|
|
750
|
+
byUser.set(d.user, list);
|
|
751
|
+
}
|
|
752
|
+
for (const [user, entries] of byUser) {
|
|
753
|
+
if (tag === 'update') {
|
|
754
|
+
logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
|
|
755
|
+
await userDevicesCache?.del(user);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (tag === 'remove') {
|
|
759
|
+
await signalRepository.deleteSession(entries.map(e => e.jid));
|
|
760
|
+
}
|
|
761
|
+
const existingCache = (await userDevicesCache?.get(user)) || [];
|
|
762
|
+
if (!existingCache.length) {
|
|
763
|
+
// No baseline yet; skip applying the delta so getUSyncDevices can
|
|
764
|
+
// later fetch the full device list. Caching just the notification
|
|
765
|
+
// entries would make a partial list look authoritative.
|
|
766
|
+
logger.debug({ user, tag }, 'device list not cached, deferring to USync refresh');
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const affected = new Set(entries.map(e => e.device));
|
|
770
|
+
let updatedDevices;
|
|
771
|
+
switch (tag) {
|
|
772
|
+
case 'add':
|
|
773
|
+
logger.info({ deviceHash, count: entries.length }, 'devices added');
|
|
774
|
+
updatedDevices = [
|
|
775
|
+
...existingCache.filter(d => !affected.has(d.device)),
|
|
776
|
+
...entries.map(e => ({ user: e.user, server: e.server, device: e.device }))
|
|
777
|
+
];
|
|
778
|
+
break;
|
|
779
|
+
case 'remove':
|
|
780
|
+
logger.info({ deviceHash, count: entries.length }, 'devices removed');
|
|
781
|
+
updatedDevices = existingCache.filter(d => !affected.has(d.device));
|
|
782
|
+
break;
|
|
783
|
+
default:
|
|
784
|
+
logger.debug({ tag }, 'Unknown device list change tag');
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (updatedDevices.length === 0) {
|
|
788
|
+
await userDevicesCache?.del(user);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
await userDevicesCache?.set(user, updatedDevices);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
};
|
|
736
796
|
const processNotification = async (node) => {
|
|
737
|
-
const result = {}
|
|
738
|
-
const [child] =
|
|
739
|
-
const nodeType = node.attrs.type
|
|
740
|
-
const from =
|
|
741
|
-
|
|
797
|
+
const result = {};
|
|
798
|
+
const [child] = getAllBinaryNodeChildren(node);
|
|
799
|
+
const nodeType = node.attrs.type;
|
|
800
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
742
801
|
switch (nodeType) {
|
|
743
|
-
case 'privacy_token':
|
|
744
|
-
const tokenList = WABinary_1.getBinaryNodeChildren(child, 'token')
|
|
745
|
-
for (const {
|
|
746
|
-
attrs,
|
|
747
|
-
content
|
|
748
|
-
}
|
|
749
|
-
of tokenList) {
|
|
750
|
-
const jid = attrs.jid
|
|
751
|
-
ev.emit('chats.update', [{
|
|
752
|
-
id: jid,
|
|
753
|
-
tcToken: content
|
|
754
|
-
}])
|
|
755
|
-
logger.debug({
|
|
756
|
-
jid
|
|
757
|
-
}, 'got privacy token update')
|
|
758
|
-
}
|
|
759
|
-
break
|
|
760
|
-
case 'w:gp2':
|
|
761
|
-
const mode = node.attrs.addressing_mode
|
|
762
|
-
handleGroupNotification(mode === 'lid' ? node.attrs.participant_pn : node.attrs.participant, child, result, mode)
|
|
763
|
-
break
|
|
764
802
|
case 'newsletter':
|
|
765
|
-
handleNewsletterNotification(node
|
|
766
|
-
break
|
|
803
|
+
await handleNewsletterNotification(node);
|
|
804
|
+
break;
|
|
767
805
|
case 'mex':
|
|
768
|
-
handleMexNotification(node
|
|
769
|
-
break
|
|
806
|
+
await handleMexNotification(node);
|
|
807
|
+
break;
|
|
808
|
+
case 'w:gp2':
|
|
809
|
+
// TODO: HANDLE PARTICIPANT_PN
|
|
810
|
+
handleGroupNotification(node, child, result);
|
|
811
|
+
break;
|
|
770
812
|
case 'mediaretry':
|
|
771
|
-
const event =
|
|
772
|
-
ev.emit('messages.media-update', [event])
|
|
773
|
-
break
|
|
813
|
+
const event = decodeMediaRetryNode(node);
|
|
814
|
+
ev.emit('messages.media-update', [event]);
|
|
815
|
+
break;
|
|
774
816
|
case 'encrypt':
|
|
775
|
-
await handleEncryptNotification(node)
|
|
776
|
-
break
|
|
817
|
+
await handleEncryptNotification(node);
|
|
818
|
+
break;
|
|
777
819
|
case 'devices':
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
lid: d.attrs.lid
|
|
784
|
-
}))
|
|
785
|
-
logger.info({
|
|
786
|
-
deviceData
|
|
787
|
-
}, 'my own devices changed')
|
|
820
|
+
try {
|
|
821
|
+
await handleDevicesNotification(node);
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
logger.error({ error, node }, 'failed to handle devices notification');
|
|
788
825
|
}
|
|
789
|
-
|
|
790
|
-
break
|
|
826
|
+
break;
|
|
791
827
|
case 'server_sync':
|
|
792
|
-
const update =
|
|
828
|
+
const update = getBinaryNodeChild(node, 'collection');
|
|
793
829
|
if (update) {
|
|
794
|
-
const name = update.attrs.name
|
|
795
|
-
await resyncAppState([name], false)
|
|
830
|
+
const name = update.attrs.name;
|
|
831
|
+
await resyncAppState([name], false);
|
|
796
832
|
}
|
|
797
|
-
break
|
|
833
|
+
break;
|
|
798
834
|
case 'picture':
|
|
799
|
-
const setPicture =
|
|
800
|
-
const delPicture =
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
835
|
+
const setPicture = getBinaryNodeChild(node, 'set');
|
|
836
|
+
const delPicture = getBinaryNodeChild(node, 'delete');
|
|
837
|
+
// TODO: WAJIDHASH stuff proper support inhouse
|
|
838
|
+
ev.emit('contacts.update', [
|
|
839
|
+
{
|
|
840
|
+
id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
841
|
+
imgUrl: setPicture ? 'changed' : 'removed'
|
|
842
|
+
}
|
|
843
|
+
]);
|
|
844
|
+
if (isJidGroup(from)) {
|
|
845
|
+
const node = setPicture || delPicture;
|
|
846
|
+
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
|
|
808
847
|
if (setPicture) {
|
|
809
|
-
result.messageStubParameters = [setPicture.attrs.id]
|
|
848
|
+
result.messageStubParameters = [setPicture.attrs.id];
|
|
810
849
|
}
|
|
811
|
-
result.participant = node?.attrs
|
|
850
|
+
result.participant = node?.attrs.author;
|
|
812
851
|
result.key = {
|
|
813
|
-
...result.key || {},
|
|
814
|
-
participant: setPicture?.attrs
|
|
815
|
-
}
|
|
852
|
+
...(result.key || {}),
|
|
853
|
+
participant: setPicture?.attrs.author
|
|
854
|
+
};
|
|
816
855
|
}
|
|
817
|
-
break
|
|
856
|
+
break;
|
|
818
857
|
case 'account_sync':
|
|
819
858
|
if (child.tag === 'disappearing_mode') {
|
|
820
|
-
const newDuration = +child.attrs.duration
|
|
821
|
-
const timestamp = +child.attrs.t
|
|
822
|
-
logger.info({
|
|
823
|
-
newDuration
|
|
824
|
-
}, 'updated account disappearing mode')
|
|
859
|
+
const newDuration = +child.attrs.duration;
|
|
860
|
+
const timestamp = +child.attrs.t;
|
|
861
|
+
logger.info({ newDuration }, 'updated account disappearing mode');
|
|
825
862
|
ev.emit('creds.update', {
|
|
826
863
|
accountSettings: {
|
|
827
864
|
...authState.creds.accountSettings,
|
|
828
865
|
defaultDisappearingMode: {
|
|
829
866
|
ephemeralExpiration: newDuration,
|
|
830
|
-
ephemeralSettingTimestamp: timestamp
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
})
|
|
834
|
-
} else if (child.tag === 'blocklist') {
|
|
835
|
-
const blocklists = WABinary_1.getBinaryNodeChildren(child, 'item')
|
|
836
|
-
for (const {
|
|
837
|
-
attrs
|
|
867
|
+
ephemeralSettingTimestamp: timestamp
|
|
868
|
+
}
|
|
838
869
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
else if (child.tag === 'blocklist') {
|
|
873
|
+
const blocklists = getBinaryNodeChildren(child, 'item');
|
|
874
|
+
for (const { attrs } of blocklists) {
|
|
875
|
+
const blocklist = [attrs.jid];
|
|
876
|
+
const type = attrs.action === 'block' ? 'add' : 'remove';
|
|
877
|
+
ev.emit('blocklist.update', { blocklist, type });
|
|
846
878
|
}
|
|
847
879
|
}
|
|
848
|
-
break
|
|
880
|
+
break;
|
|
849
881
|
case 'link_code_companion_reg':
|
|
850
|
-
const linkCodeCompanionReg =
|
|
851
|
-
const ref = toRequiredBuffer(
|
|
852
|
-
const primaryIdentityPublicKey = toRequiredBuffer(
|
|
853
|
-
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(
|
|
854
|
-
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped)
|
|
855
|
-
const companionSharedKey =
|
|
856
|
-
const random =
|
|
857
|
-
const linkCodeSalt =
|
|
858
|
-
|
|
859
|
-
const linkCodePairingExpanded = await Utils_1.hkdf(companionSharedKey, 32, {
|
|
882
|
+
const linkCodeCompanionReg = getBinaryNodeChild(node, 'link_code_companion_reg');
|
|
883
|
+
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
|
|
884
|
+
const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
|
|
885
|
+
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
|
|
886
|
+
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
|
|
887
|
+
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
888
|
+
const random = randomBytes(32);
|
|
889
|
+
const linkCodeSalt = randomBytes(32);
|
|
890
|
+
const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
|
|
860
891
|
salt: linkCodeSalt,
|
|
861
892
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
862
|
-
})
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
})).toString('base64')
|
|
874
|
-
|
|
893
|
+
});
|
|
894
|
+
const encryptPayload = Buffer.concat([
|
|
895
|
+
Buffer.from(authState.creds.signedIdentityKey.public),
|
|
896
|
+
primaryIdentityPublicKey,
|
|
897
|
+
random
|
|
898
|
+
]);
|
|
899
|
+
const encryptIv = randomBytes(12);
|
|
900
|
+
const encrypted = aesEncryptGCM(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
|
|
901
|
+
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
|
|
902
|
+
const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
903
|
+
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
|
|
904
|
+
authState.creds.advSecretKey = Buffer.from(hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
|
|
875
905
|
await query({
|
|
876
906
|
tag: 'iq',
|
|
877
907
|
attrs: {
|
|
878
|
-
to:
|
|
908
|
+
to: S_WHATSAPP_NET,
|
|
879
909
|
type: 'set',
|
|
880
910
|
id: sock.generateMessageTag(),
|
|
881
911
|
xmlns: 'md'
|
|
882
912
|
},
|
|
883
|
-
content: [
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
content: [{
|
|
890
|
-
tag: 'link_code_pairing_wrapped_key_bundle',
|
|
891
|
-
attrs: {},
|
|
892
|
-
content: encryptedPayload
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
tag: 'companion_identity_public',
|
|
896
|
-
attrs: {},
|
|
897
|
-
content: authState.creds.signedIdentityKey.public
|
|
913
|
+
content: [
|
|
914
|
+
{
|
|
915
|
+
tag: 'link_code_companion_reg',
|
|
916
|
+
attrs: {
|
|
917
|
+
jid: authState.creds.me.id,
|
|
918
|
+
stage: 'companion_finish'
|
|
898
919
|
},
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
920
|
+
content: [
|
|
921
|
+
{
|
|
922
|
+
tag: 'link_code_pairing_wrapped_key_bundle',
|
|
923
|
+
attrs: {},
|
|
924
|
+
content: encryptedPayload
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
tag: 'companion_identity_public',
|
|
928
|
+
attrs: {},
|
|
929
|
+
content: authState.creds.signedIdentityKey.public
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
tag: 'link_code_pairing_ref',
|
|
933
|
+
attrs: {},
|
|
934
|
+
content: ref
|
|
935
|
+
}
|
|
936
|
+
]
|
|
937
|
+
}
|
|
938
|
+
]
|
|
939
|
+
});
|
|
940
|
+
authState.creds.registered = true;
|
|
941
|
+
ev.emit('creds.update', authState.creds);
|
|
942
|
+
break;
|
|
943
|
+
case 'privacy_token':
|
|
944
|
+
await handlePrivacyTokenNotification(node);
|
|
945
|
+
break;
|
|
910
946
|
}
|
|
911
|
-
|
|
912
947
|
if (Object.keys(result).length) {
|
|
913
|
-
return result
|
|
948
|
+
return result;
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
/**
|
|
952
|
+
* In-memory cache of storage JIDs with stored tctokens, seeded from the persisted index.
|
|
953
|
+
* Used to coalesce writes during a session; pruning always re-reads the persisted index
|
|
954
|
+
* to cover writes made by other layers (e.g. history sync).
|
|
955
|
+
*/
|
|
956
|
+
const tcTokenKnownJids = new Set();
|
|
957
|
+
const tcTokenIndexLoaded = (async () => {
|
|
958
|
+
try {
|
|
959
|
+
const jids = await readTcTokenIndex(authState.keys);
|
|
960
|
+
for (const jid of jids)
|
|
961
|
+
tcTokenKnownJids.add(jid);
|
|
962
|
+
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
963
|
+
}
|
|
964
|
+
catch (err) {
|
|
965
|
+
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
966
|
+
}
|
|
967
|
+
})();
|
|
968
|
+
let tcTokenIndexTimer;
|
|
969
|
+
async function flushTcTokenIndex() {
|
|
970
|
+
if (tcTokenIndexTimer) {
|
|
971
|
+
clearTimeout(tcTokenIndexTimer);
|
|
972
|
+
tcTokenIndexTimer = undefined;
|
|
914
973
|
}
|
|
974
|
+
// Merge with whatever is already persisted so we don't clobber writes from other
|
|
975
|
+
// paths (history sync, concurrent sessions on the same store).
|
|
976
|
+
const write = await buildMergedTcTokenIndexWrite(authState.keys, tcTokenKnownJids);
|
|
977
|
+
return authState.keys.set({ tctoken: write });
|
|
915
978
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
979
|
+
function scheduleTcTokenIndexSave() {
|
|
980
|
+
if (tcTokenIndexTimer) {
|
|
981
|
+
clearTimeout(tcTokenIndexTimer);
|
|
982
|
+
}
|
|
983
|
+
tcTokenIndexTimer = setTimeout(() => {
|
|
984
|
+
tcTokenIndexTimer = undefined;
|
|
985
|
+
flushTcTokenIndex().catch(err => {
|
|
986
|
+
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
987
|
+
});
|
|
988
|
+
}, 5000);
|
|
924
989
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
990
|
+
function trackTcTokenJid(jid) {
|
|
991
|
+
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
992
|
+
tcTokenKnownJids.add(jid);
|
|
993
|
+
scheduleTcTokenIndexSave();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const handlePrivacyTokenNotification = async (node) => {
|
|
997
|
+
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
998
|
+
if (!tokensNode)
|
|
999
|
+
return;
|
|
1000
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
1001
|
+
// WA Web uses: senderLid ?? toLid(from) for the storage key
|
|
1002
|
+
// The sender_lid attribute provides the LID directly when available
|
|
1003
|
+
const senderLid = node.attrs.sender_lid && isLidUser(jidNormalizedUser(node.attrs.sender_lid))
|
|
1004
|
+
? jidNormalizedUser(node.attrs.sender_lid)
|
|
1005
|
+
: undefined;
|
|
1006
|
+
const fallbackJid = senderLid ?? (await resolveTcTokenJid(from, getLIDForPN));
|
|
1007
|
+
logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
|
|
1008
|
+
await storeTcTokensFromIqResult({
|
|
1009
|
+
result: node,
|
|
1010
|
+
fallbackJid,
|
|
1011
|
+
keys: authState.keys,
|
|
1012
|
+
getLIDForPN,
|
|
1013
|
+
onNewJidStored: trackTcTokenJid
|
|
1014
|
+
});
|
|
1015
|
+
};
|
|
1016
|
+
async function decipherLinkPublicKey(data) {
|
|
1017
|
+
const buffer = toRequiredBuffer(data);
|
|
1018
|
+
const salt = buffer.slice(0, 32);
|
|
1019
|
+
const secretKey = await derivePairingCodeKey(authState.creds.pairingCode, salt);
|
|
1020
|
+
const iv = buffer.slice(32, 48);
|
|
1021
|
+
const payload = buffer.slice(48, 80);
|
|
1022
|
+
return aesDecryptCTR(payload, secretKey, iv);
|
|
930
1023
|
}
|
|
931
|
-
|
|
932
1024
|
function toRequiredBuffer(data) {
|
|
933
1025
|
if (data === undefined) {
|
|
934
|
-
throw new
|
|
935
|
-
statusCode: 400
|
|
936
|
-
})
|
|
1026
|
+
throw new Boom('Invalid buffer', { statusCode: 400 });
|
|
937
1027
|
}
|
|
938
|
-
return data instanceof Buffer ? data : Buffer.from(data)
|
|
1028
|
+
return data instanceof Buffer ? data : Buffer.from(data);
|
|
939
1029
|
}
|
|
940
|
-
|
|
941
1030
|
const willSendMessageAgain = async (id, participant) => {
|
|
942
|
-
const key = `${id}:${participant}
|
|
943
|
-
const retryCount = (await msgRetryCache.get(key)) || 0
|
|
944
|
-
return retryCount < maxMsgRetryCount
|
|
945
|
-
}
|
|
946
|
-
|
|
1031
|
+
const key = `${id}:${participant}`;
|
|
1032
|
+
const retryCount = (await msgRetryCache.get(key)) || 0;
|
|
1033
|
+
return retryCount < maxMsgRetryCount;
|
|
1034
|
+
};
|
|
947
1035
|
const updateSendMessageAgainCount = async (id, participant) => {
|
|
948
|
-
const key = `${id}:${participant}
|
|
949
|
-
const newValue = ((await msgRetryCache.get(key)) || 0) + 1
|
|
950
|
-
await msgRetryCache.set(key, newValue)
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
const
|
|
956
|
-
const
|
|
957
|
-
|
|
1036
|
+
const key = `${id}:${participant}`;
|
|
1037
|
+
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
1038
|
+
await msgRetryCache.set(key, newValue);
|
|
1039
|
+
};
|
|
1040
|
+
const sendMessagesAgain = async (key, ids, retryNode, receiptNode) => {
|
|
1041
|
+
const remoteJid = key.remoteJid;
|
|
1042
|
+
const participant = key.participant || remoteJid;
|
|
1043
|
+
const retryCount = +retryNode.attrs.count || 1;
|
|
1044
|
+
const msgId = ids[0];
|
|
958
1045
|
// Try to get messages from cache first, then fallback to getMessage
|
|
959
|
-
const msgs = []
|
|
960
|
-
|
|
1046
|
+
const msgs = [];
|
|
961
1047
|
for (const id of ids) {
|
|
962
|
-
let msg
|
|
963
|
-
|
|
1048
|
+
let msg;
|
|
964
1049
|
// Try to get from retry cache first if enabled
|
|
965
1050
|
if (messageRetryManager) {
|
|
966
|
-
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id)
|
|
1051
|
+
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
|
|
967
1052
|
if (cachedMsg) {
|
|
968
|
-
msg = cachedMsg.message
|
|
969
|
-
logger.debug({
|
|
970
|
-
jid: remoteJid,
|
|
971
|
-
id
|
|
972
|
-
}, 'found message in retry cache')
|
|
973
|
-
|
|
1053
|
+
msg = cachedMsg.message;
|
|
1054
|
+
logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
|
|
974
1055
|
// Mark retry as successful since we found the message
|
|
975
|
-
messageRetryManager.markRetrySuccess(id)
|
|
1056
|
+
messageRetryManager.markRetrySuccess(id);
|
|
976
1057
|
}
|
|
977
1058
|
}
|
|
978
|
-
|
|
979
1059
|
// Fallback to getMessage if not found in cache
|
|
980
1060
|
if (!msg) {
|
|
981
|
-
msg = await getMessage({
|
|
982
|
-
...key,
|
|
983
|
-
id
|
|
984
|
-
})
|
|
1061
|
+
msg = await getMessage({ ...key, id });
|
|
985
1062
|
if (msg) {
|
|
986
|
-
logger.debug({
|
|
987
|
-
jid: remoteJid,
|
|
988
|
-
id
|
|
989
|
-
}, 'found message via getMessage')
|
|
990
|
-
|
|
1063
|
+
logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
|
|
991
1064
|
// Also mark as successful if found via getMessage
|
|
992
1065
|
if (messageRetryManager) {
|
|
993
1066
|
messageRetryManager.markRetrySuccess(id);
|
|
994
1067
|
}
|
|
995
1068
|
}
|
|
996
1069
|
}
|
|
997
|
-
msgs.push(msg)
|
|
1070
|
+
msgs.push(msg);
|
|
998
1071
|
}
|
|
999
|
-
|
|
1000
1072
|
// if it's the primary jid sending the request
|
|
1001
1073
|
// just re-send the message to everyone
|
|
1002
1074
|
// prevents the first message decryption failure
|
|
1003
|
-
const sendToAll = !
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
1075
|
+
const sendToAll = !jidDecode(participant)?.device;
|
|
1076
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
1077
|
+
let injectedFromBundle = false;
|
|
1078
|
+
const bundle = extractE2ESessionFromRetryReceipt(receiptNode);
|
|
1079
|
+
if (bundle) {
|
|
1010
1080
|
try {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
[sessionId]: null
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1081
|
+
await signalRepository.injectE2ESession({ jid: participant, session: bundle });
|
|
1082
|
+
injectedFromBundle = true;
|
|
1083
|
+
logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle');
|
|
1084
|
+
}
|
|
1085
|
+
catch (error) {
|
|
1086
|
+
logger.warn({ error, participant }, 'failed to inject session from retry receipt');
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (!injectedFromBundle) {
|
|
1090
|
+
const receivedRegId = getBinaryNodeChildUInt(receiptNode, 'registration', 4);
|
|
1091
|
+
if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
|
|
1092
|
+
const info = await signalRepository.getSessionInfo(participant);
|
|
1093
|
+
if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
|
|
1094
|
+
logger.info({ participant, stored: info.registrationId, received: receivedRegId }, 'reg id mismatch on retry without bundle, deleting session');
|
|
1095
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1029
1096
|
}
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
logger.warn({
|
|
1032
|
-
error,
|
|
1033
|
-
participant
|
|
1034
|
-
}, 'failed to check session recreation for outgoing retry')
|
|
1035
1097
|
}
|
|
1036
1098
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
[remoteJid]: null
|
|
1099
|
+
const BASE_KEY_CHECK_RETRY = 2;
|
|
1100
|
+
if (msgId && messageRetryManager) {
|
|
1101
|
+
const info = await signalRepository.getSessionInfo(participant);
|
|
1102
|
+
if (info) {
|
|
1103
|
+
if (retryCount === BASE_KEY_CHECK_RETRY) {
|
|
1104
|
+
messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey);
|
|
1044
1105
|
}
|
|
1045
|
-
|
|
1106
|
+
else if (retryCount > BASE_KEY_CHECK_RETRY) {
|
|
1107
|
+
if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
|
|
1108
|
+
logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session');
|
|
1109
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1110
|
+
}
|
|
1111
|
+
messageRetryManager.deleteBaseKey(sessionId, msgId);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1046
1114
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1115
|
+
let shouldRecreateSession = false;
|
|
1116
|
+
let recreateReason = '';
|
|
1117
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
|
|
1118
|
+
try {
|
|
1119
|
+
const hasSession = await signalRepository.validateSession(participant);
|
|
1120
|
+
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
1121
|
+
shouldRecreateSession = result.recreate;
|
|
1122
|
+
recreateReason = result.reason;
|
|
1123
|
+
if (shouldRecreateSession) {
|
|
1124
|
+
logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
|
|
1125
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
catch (error) {
|
|
1129
|
+
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (!injectedFromBundle) {
|
|
1133
|
+
await assertSessions([participant], true);
|
|
1134
|
+
}
|
|
1135
|
+
if (isJidGroup(remoteJid)) {
|
|
1136
|
+
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
1137
|
+
}
|
|
1138
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason, injectedFromBundle }, 'prepared session for retry resend');
|
|
1054
1139
|
for (const [i, msg] of msgs.entries()) {
|
|
1055
1140
|
if (!ids[i])
|
|
1056
|
-
continue
|
|
1057
|
-
|
|
1141
|
+
continue;
|
|
1058
1142
|
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
1059
|
-
updateSendMessageAgainCount(ids[i], participant)
|
|
1060
|
-
const msgRelayOpts = {
|
|
1061
|
-
messageId: ids[i]
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1143
|
+
await updateSendMessageAgainCount(ids[i], participant);
|
|
1144
|
+
const msgRelayOpts = { messageId: ids[i] };
|
|
1064
1145
|
if (sendToAll) {
|
|
1065
|
-
msgRelayOpts.useUserDevicesCache = false
|
|
1066
|
-
}
|
|
1146
|
+
msgRelayOpts.useUserDevicesCache = false;
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1067
1149
|
msgRelayOpts.participant = {
|
|
1068
1150
|
jid: participant,
|
|
1069
1151
|
count: +retryNode.attrs.count
|
|
1070
|
-
}
|
|
1152
|
+
};
|
|
1071
1153
|
}
|
|
1072
|
-
await relayMessage(key.remoteJid, msg, msgRelayOpts)
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
id: ids[i]
|
|
1077
|
-
}, 'recv retry request, but message not available')
|
|
1154
|
+
await relayMessage(key.remoteJid, msg, msgRelayOpts);
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available');
|
|
1078
1158
|
}
|
|
1079
1159
|
}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1160
|
+
};
|
|
1082
1161
|
const handleReceipt = async (node) => {
|
|
1083
|
-
const {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
const isNodeFromMe = WABinary_1.areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id)
|
|
1089
|
-
const remoteJid = !isNodeFromMe || WABinary_1.isJidGroup(attrs.from) ? attrs.from : attrs.recipient
|
|
1090
|
-
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe)
|
|
1091
|
-
|
|
1162
|
+
const { attrs, content } = node;
|
|
1163
|
+
const isLid = attrs.from.includes('lid');
|
|
1164
|
+
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1165
|
+
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1166
|
+
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
|
|
1092
1167
|
const key = {
|
|
1093
1168
|
remoteJid,
|
|
1094
1169
|
id: '',
|
|
1095
1170
|
fromMe,
|
|
1096
1171
|
participant: attrs.participant
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
1100
|
-
logger.debug({
|
|
1101
|
-
remoteJid
|
|
1102
|
-
}, 'ignoring receipt from jid')
|
|
1103
|
-
await sendMessageAck(node)
|
|
1104
|
-
return
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const ids = [attrs.id]
|
|
1172
|
+
};
|
|
1173
|
+
const ids = [attrs.id];
|
|
1108
1174
|
if (Array.isArray(content)) {
|
|
1109
|
-
const items =
|
|
1110
|
-
ids.push(...items.map(i => i.attrs.id))
|
|
1175
|
+
const items = getBinaryNodeChildren(content[0], 'item');
|
|
1176
|
+
ids.push(...items.map(i => i.attrs.id));
|
|
1111
1177
|
}
|
|
1112
|
-
|
|
1113
1178
|
try {
|
|
1114
1179
|
await Promise.all([
|
|
1115
1180
|
receiptMutex.mutex(async () => {
|
|
1116
|
-
const status =
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
!isNodeFromMe)) {
|
|
1123
|
-
if (WABinary_1.isJidGroup(remoteJid) || WABinary_1.isJidStatusBroadcast(remoteJid)) {
|
|
1181
|
+
const status = getStatusFromReceiptType(attrs.type);
|
|
1182
|
+
if (typeof status !== 'undefined' &&
|
|
1183
|
+
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
|
1184
|
+
// or another device of ours has read some messages
|
|
1185
|
+
(status >= proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
|
|
1186
|
+
if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
|
|
1124
1187
|
if (attrs.participant) {
|
|
1125
|
-
const updateKey = status ===
|
|
1188
|
+
const updateKey = status === proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
|
|
1126
1189
|
ev.emit('message-receipt.update', ids.map(id => ({
|
|
1127
|
-
key: {
|
|
1128
|
-
...key,
|
|
1129
|
-
id
|
|
1130
|
-
},
|
|
1190
|
+
key: { ...key, id },
|
|
1131
1191
|
receipt: {
|
|
1132
|
-
userJid:
|
|
1192
|
+
userJid: jidNormalizedUser(attrs.participant),
|
|
1133
1193
|
[updateKey]: +attrs.t
|
|
1134
1194
|
}
|
|
1135
|
-
})))
|
|
1195
|
+
})));
|
|
1136
1196
|
}
|
|
1137
|
-
}
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1138
1199
|
ev.emit('messages.update', ids.map(id => ({
|
|
1139
|
-
key: {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
},
|
|
1143
|
-
update: {
|
|
1144
|
-
status
|
|
1145
|
-
}
|
|
1146
|
-
})))
|
|
1200
|
+
key: { ...key, id },
|
|
1201
|
+
update: { status, messageTimestamp: toNumber(+(attrs.t ?? 0)) }
|
|
1202
|
+
})));
|
|
1147
1203
|
}
|
|
1148
1204
|
}
|
|
1149
|
-
|
|
1150
1205
|
if (attrs.type === 'retry') {
|
|
1151
1206
|
// correctly set who is asking for the retry
|
|
1152
|
-
key.participant = key.participant || attrs.from
|
|
1153
|
-
const retryNode =
|
|
1154
|
-
|
|
1207
|
+
key.participant = key.participant || attrs.from;
|
|
1208
|
+
const retryNode = getBinaryNodeChild(node, 'retry');
|
|
1155
1209
|
if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
|
|
1156
1210
|
if (key.fromMe) {
|
|
1157
1211
|
try {
|
|
1158
|
-
updateSendMessageAgainCount(ids[0], key.participant)
|
|
1159
|
-
logger.debug({
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
logger.error({
|
|
1166
|
-
key,
|
|
1167
|
-
ids,
|
|
1168
|
-
trace: error instanceof Error ? error.stack : 'Unknown error'
|
|
1169
|
-
}, 'error in sending message again')
|
|
1212
|
+
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
1213
|
+
logger.debug({ attrs, key }, 'recv retry request');
|
|
1214
|
+
await sendMessagesAgain(key, ids, retryNode, node);
|
|
1215
|
+
}
|
|
1216
|
+
catch (error) {
|
|
1217
|
+
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
1170
1218
|
}
|
|
1171
|
-
} else {
|
|
1172
|
-
logger.info({
|
|
1173
|
-
attrs,
|
|
1174
|
-
key
|
|
1175
|
-
}, 'recv retry for not fromMe message')
|
|
1176
1219
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1220
|
+
else {
|
|
1221
|
+
logger.info({ attrs, key }, 'recv retry for not fromMe message');
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
logger.info({ attrs, key }, 'will not send message again, as sent too many times');
|
|
1182
1226
|
}
|
|
1183
1227
|
}
|
|
1184
1228
|
})
|
|
1185
|
-
])
|
|
1186
|
-
} finally {
|
|
1187
|
-
await sendMessageAck(node)
|
|
1229
|
+
]);
|
|
1188
1230
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1231
|
+
finally {
|
|
1232
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1191
1235
|
const handleNotification = async (node) => {
|
|
1192
|
-
const remoteJid = node.attrs.from
|
|
1193
|
-
|
|
1194
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !== '@s.whatsapp.net') {
|
|
1195
|
-
logger.debug({
|
|
1196
|
-
remoteJid,
|
|
1197
|
-
id: node.attrs.id
|
|
1198
|
-
}, 'ignored notification')
|
|
1199
|
-
await sendMessageAck(node)
|
|
1200
|
-
return
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1236
|
+
const remoteJid = node.attrs.from;
|
|
1203
1237
|
try {
|
|
1204
1238
|
await Promise.all([
|
|
1205
1239
|
notificationMutex.mutex(async () => {
|
|
1206
|
-
const msg = await processNotification(node)
|
|
1207
|
-
|
|
1240
|
+
const msg = await processNotification(node);
|
|
1208
1241
|
if (msg) {
|
|
1209
|
-
const fromMe =
|
|
1242
|
+
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
1243
|
+
const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
|
|
1210
1244
|
msg.key = {
|
|
1211
1245
|
remoteJid,
|
|
1212
1246
|
fromMe,
|
|
1213
1247
|
participant: node.attrs.participant,
|
|
1248
|
+
participantAlt,
|
|
1249
|
+
participantUsername: node.attrs.participant_username,
|
|
1250
|
+
addressingMode,
|
|
1214
1251
|
id: node.attrs.id,
|
|
1215
1252
|
...(msg.key || {})
|
|
1216
|
-
}
|
|
1217
|
-
msg.participant
|
|
1218
|
-
msg.messageTimestamp = +node.attrs.t
|
|
1219
|
-
const fullMsg =
|
|
1220
|
-
await upsertMessage(fullMsg, 'append')
|
|
1253
|
+
};
|
|
1254
|
+
msg.participant ?? (msg.participant = node.attrs.participant);
|
|
1255
|
+
msg.messageTimestamp = +node.attrs.t;
|
|
1256
|
+
const fullMsg = proto.WebMessageInfo.fromObject(msg);
|
|
1257
|
+
await upsertMessage(fullMsg, 'append');
|
|
1221
1258
|
}
|
|
1222
1259
|
})
|
|
1223
|
-
])
|
|
1224
|
-
} finally {
|
|
1225
|
-
await sendMessageAck(node)
|
|
1260
|
+
]);
|
|
1226
1261
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1262
|
+
finally {
|
|
1263
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack notification'));
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1229
1266
|
const handleMessage = async (node) => {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}, 'ignored
|
|
1234
|
-
await sendMessageAck(node)
|
|
1235
|
-
return
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
let response
|
|
1239
|
-
|
|
1240
|
-
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1241
|
-
if (encNode && encNode.attrs.type === 'msmsg') {
|
|
1242
|
-
logger.debug({
|
|
1243
|
-
key: node.attrs.key
|
|
1244
|
-
}, 'ignored msmsg')
|
|
1245
|
-
await sendMessageAck(node, Utils_1.NACK_REASONS.MissingMessageSecret)
|
|
1246
|
-
return
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
if (WABinary_1.getBinaryNodeChild(node, 'unavailable') && !encNode) {
|
|
1250
|
-
await sendMessageAck(node)
|
|
1251
|
-
let key
|
|
1252
|
-
try {
|
|
1253
|
-
key = Utils_1.decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '').fullMessage.key
|
|
1254
|
-
} catch (error) {
|
|
1255
|
-
logger.error({ error, node }, 'failed to decode unavailable message node, skipping placeholder resend')
|
|
1256
|
-
return
|
|
1257
|
-
}
|
|
1258
|
-
const unavailableNode = WABinary_1.getBinaryNodeChild(node, 'unavailable')
|
|
1259
|
-
const unavailableType = unavailableNode?.attrs?.type
|
|
1260
|
-
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1261
|
-
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1262
|
-
unavailableType === 'view_once_unavailable_fanout') {
|
|
1263
|
-
logger.debug({ msgId: node.attrs.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type')
|
|
1264
|
-
return
|
|
1265
|
-
}
|
|
1266
|
-
const messageAge = Utils_1.unixTimestampSeconds() - Utils_1.toNumber(node.attrs.t)
|
|
1267
|
-
if (messageAge > Defaults_1.PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1268
|
-
logger.debug({ msgId: node.attrs.id, messageAge }, 'skipping placeholder resend for message too old')
|
|
1269
|
-
return
|
|
1270
|
-
}
|
|
1271
|
-
response = await requestPlaceholderResend(key);
|
|
1272
|
-
if (response === 'RESOLVED') {
|
|
1273
|
-
return
|
|
1274
|
-
}
|
|
1275
|
-
logger.debug('received unavailable message, acked and requested resend from phone');
|
|
1276
|
-
} else {
|
|
1277
|
-
if (placeholderResendCache.get(node.attrs.id)) {
|
|
1278
|
-
await placeholderResendCache.del(node.attrs.id)
|
|
1279
|
-
}
|
|
1267
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1268
|
+
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
1269
|
+
if (encNode?.attrs.type === 'msmsg') {
|
|
1270
|
+
logger.debug({ key: node.attrs.key }, 'ignored msmsg');
|
|
1271
|
+
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
|
|
1272
|
+
return;
|
|
1280
1273
|
}
|
|
1281
|
-
|
|
1282
|
-
let decodeResult
|
|
1274
|
+
let acked = false;
|
|
1283
1275
|
try {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1276
|
+
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1277
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1278
|
+
// store new mappings we didn't have before
|
|
1279
|
+
if (!!alt) {
|
|
1280
|
+
const altServer = jidDecode(alt)?.server;
|
|
1281
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1282
|
+
if (altServer === 'lid') {
|
|
1283
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1284
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1285
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1290
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
1291
|
+
}
|
|
1296
1292
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
msg.messageStubParameters = [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, response]
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
if (msg.message?.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER && node.attrs.sender_pn) {
|
|
1311
|
-
const lid = WABinary_1.jidNormalizedUser(node.attrs.from), pn = WABinary_1.jidNormalizedUser(node.attrs.sender_pn)
|
|
1312
|
-
ev.emit('lid-mapping.update', {
|
|
1313
|
-
lid,
|
|
1314
|
-
pn
|
|
1315
|
-
})
|
|
1316
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{
|
|
1317
|
-
lid,
|
|
1318
|
-
pn
|
|
1319
|
-
}])
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
try {
|
|
1323
|
-
await Promise.all([
|
|
1324
|
-
messageMutex.mutex(async () => {
|
|
1325
|
-
await decrypt()
|
|
1326
|
-
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
1327
|
-
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message)
|
|
1328
|
-
logger.debug({
|
|
1329
|
-
jid: msg.key.remoteJid,
|
|
1330
|
-
id: msg.key.id
|
|
1331
|
-
}, 'Added message to recent cache for retry receipts')
|
|
1293
|
+
await messageMutex.mutex(async () => {
|
|
1294
|
+
await decrypt();
|
|
1295
|
+
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
1296
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1297
|
+
}
|
|
1298
|
+
// message failed to decrypt
|
|
1299
|
+
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
1300
|
+
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) {
|
|
1301
|
+
acked = true;
|
|
1302
|
+
return sendMessageAck(node, NACK_REASONS.ParsingError);
|
|
1332
1303
|
}
|
|
1333
|
-
if (msg.
|
|
1334
|
-
|
|
1335
|
-
|
|
1304
|
+
if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1305
|
+
// Message arrived without encryption (e.g. CTWA ads messages).
|
|
1306
|
+
// Check if this is eligible for placeholder resend (matching WA Web filters).
|
|
1307
|
+
const unavailableNode = getBinaryNodeChild(node, 'unavailable');
|
|
1308
|
+
const unavailableType = unavailableNode?.attrs?.type;
|
|
1309
|
+
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1310
|
+
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1311
|
+
unavailableType === 'view_once_unavailable_fanout') {
|
|
1312
|
+
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1313
|
+
acked = true;
|
|
1314
|
+
return sendMessageAck(node);
|
|
1336
1315
|
}
|
|
1337
|
-
|
|
1338
|
-
if (
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
return sendMessageAck(node)
|
|
1343
|
-
}
|
|
1316
|
+
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1317
|
+
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1318
|
+
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1319
|
+
acked = true;
|
|
1320
|
+
return sendMessageAck(node);
|
|
1344
1321
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
//
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
error: errorMessage
|
|
1373
|
-
}, 'PreKey error detected, uploading and retrying')
|
|
1374
|
-
try {
|
|
1375
|
-
logger.debug('Uploading pre-keys for error recovery')
|
|
1376
|
-
await uploadPreKeys(5)
|
|
1377
|
-
logger.debug('Waiting for server to process new pre-keys')
|
|
1378
|
-
await Utils_1.delay(1000)
|
|
1379
|
-
} catch (uploadErr) {
|
|
1380
|
-
logger.error({
|
|
1381
|
-
uploadErr
|
|
1382
|
-
}, 'Pre-key upload failed, proceeding with retry anyway')
|
|
1322
|
+
// Request the real content from the phone via placeholder resend PDO.
|
|
1323
|
+
// Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
|
|
1324
|
+
// and store the requestId in stubParameters[1] so users can correlate
|
|
1325
|
+
// with the incoming PDO response event.
|
|
1326
|
+
const cleanKey = {
|
|
1327
|
+
remoteJid: msg.key.remoteJid,
|
|
1328
|
+
fromMe: msg.key.fromMe,
|
|
1329
|
+
id: msg.key.id,
|
|
1330
|
+
participant: msg.key.participant
|
|
1331
|
+
};
|
|
1332
|
+
// Cache the original message metadata so the PDO response handler
|
|
1333
|
+
// can preserve key fields (LID details etc.) that the phone may omit
|
|
1334
|
+
const msgData = {
|
|
1335
|
+
key: msg.key,
|
|
1336
|
+
messageTimestamp: msg.messageTimestamp,
|
|
1337
|
+
pushName: msg.pushName,
|
|
1338
|
+
participant: msg.participant,
|
|
1339
|
+
verifiedBizName: msg.verifiedBizName
|
|
1340
|
+
};
|
|
1341
|
+
requestPlaceholderResend(cleanKey, msgData)
|
|
1342
|
+
.then(requestId => {
|
|
1343
|
+
if (requestId && requestId !== 'RESOLVED') {
|
|
1344
|
+
logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
|
|
1345
|
+
ev.emit('messages.update', [
|
|
1346
|
+
{
|
|
1347
|
+
key: msg.key,
|
|
1348
|
+
update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
|
|
1383
1349
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1387
|
-
await sendRetryRequest(node, !encNode)
|
|
1388
|
-
if (retryRequestDelayMs) {
|
|
1389
|
-
await Utils_1.delay(retryRequestDelayMs)
|
|
1390
|
-
}
|
|
1391
|
-
} catch (err) {
|
|
1392
|
-
logger.error({
|
|
1393
|
-
err,
|
|
1394
|
-
isPreKeyError
|
|
1395
|
-
}, 'Failed to handle retry, attempting basic retry')
|
|
1396
|
-
try {
|
|
1397
|
-
const encNode = WABinary_1.getBinaryNodeChild(node, 'enc')
|
|
1398
|
-
await sendRetryRequest(node, !encNode)
|
|
1399
|
-
} catch (retryErr) {
|
|
1400
|
-
logger.error({
|
|
1401
|
-
retryErr
|
|
1402
|
-
}, 'Failed to send retry after error handling')
|
|
1403
|
-
}
|
|
1350
|
+
]);
|
|
1404
1351
|
}
|
|
1405
|
-
}).catch((mutexErr) => {
|
|
1406
|
-
logger.error({ mutexErr }, 'retryMutex task failed unexpectedly')
|
|
1407
1352
|
})
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
//
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
msg.key.participant = resolvedPn
|
|
1424
|
-
? WABinary_1.jidNormalizedUser(resolvedPn)
|
|
1425
|
-
: WABinary_1.jidNormalizedUser(msg.key.remoteJid);
|
|
1426
|
-
} catch (_) {
|
|
1427
|
-
msg.key.participant = WABinary_1.jidNormalizedUser(msg.key.remoteJid);
|
|
1428
|
-
}
|
|
1353
|
+
.catch(err => {
|
|
1354
|
+
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
1355
|
+
});
|
|
1356
|
+
acked = true;
|
|
1357
|
+
await sendMessageAck(node);
|
|
1358
|
+
// Don't return — fall through to upsertMessage so the stub is emitted
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
// Skip retry for expired status messages (>24h old)
|
|
1362
|
+
if (isJidStatusBroadcast(msg.key.remoteJid)) {
|
|
1363
|
+
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp);
|
|
1364
|
+
if (messageAge > STATUS_EXPIRY_SECONDS) {
|
|
1365
|
+
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
1366
|
+
acked = true;
|
|
1367
|
+
return sendMessageAck(node);
|
|
1429
1368
|
}
|
|
1430
1369
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
if (msg.message?.[mtype]?.contextInfo) {
|
|
1439
|
-
const metadata = await global.groupMetadataCache(msg.key.remoteJid)
|
|
1440
|
-
const sender_lid = msg.message[mtype].contextInfo.participant
|
|
1441
|
-
const found = metadata.participants.find(p => p.lid === sender_lid)
|
|
1442
|
-
if (found) {
|
|
1443
|
-
msg.message[mtype].contextInfo.participant = found.id
|
|
1444
|
-
} else {
|
|
1445
|
-
const lid = WABinary_1.jidNormalizedUser(sender_lid)
|
|
1446
|
-
const sender_jid = await signalRepository.lidMapping.getPNForLID(sender_lid)
|
|
1447
|
-
if (sender_jid) {
|
|
1448
|
-
const pn = WABinary_1.jidNormalizedUser(sender_jid)
|
|
1449
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }])
|
|
1450
|
-
msg.message[mtype].contextInfo.participant = pn
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1370
|
+
logger.debug('[handleMessage] Attempting retry request for failed decryption');
|
|
1371
|
+
// WAWeb only retry-receipts here; server emits PreKeyLow if prekeys run low.
|
|
1372
|
+
await retryMutex.mutex(async () => {
|
|
1373
|
+
try {
|
|
1374
|
+
if (!ws.isOpen) {
|
|
1375
|
+
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1376
|
+
return;
|
|
1453
1377
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
1458
|
-
const updatedMentionedJid = []
|
|
1459
|
-
let needsUpdate = false
|
|
1460
|
-
for (const mentionedLid of mentionedJid) {
|
|
1461
|
-
if (typeof mentionedLid === "string" && mentionedLid.endsWith('@lid')) {
|
|
1462
|
-
const found = metadata.participants.find(p => p.lid === mentionedLid)
|
|
1463
|
-
if (found) {
|
|
1464
|
-
updatedMentionedJid.push(found.id)
|
|
1465
|
-
if (msg.message[mtype]?.text) {
|
|
1466
|
-
msg.message[mtype].text = msg.message[mtype].text.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${found.id.split("@")[0]}`);
|
|
1467
|
-
} else if (msg.message[mtype]?.caption) {
|
|
1468
|
-
msg.message[mtype].caption = msg.message[mtype].caption.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${found.id.split("@")[0]}`);
|
|
1469
|
-
}
|
|
1470
|
-
} else {
|
|
1471
|
-
const lid = WABinary_1.jidNormalizedUser(mentionedLid)
|
|
1472
|
-
const mentioned_jid = await signalRepository.lidMapping.getPNForLID(mentionedLid)
|
|
1473
|
-
if (mentioned_jid) {
|
|
1474
|
-
const pn = WABinary_1.jidNormalizedUser(mentioned_jid)
|
|
1475
|
-
await signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }])
|
|
1476
|
-
updatedMentionedJid.push(pn)
|
|
1477
|
-
if (msg.message[mtype]?.text) {
|
|
1478
|
-
msg.message[mtype].text = msg.message[mtype].text.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${pn.split("@")[0]}`);
|
|
1479
|
-
} else if (msg.message[mtype]?.caption) {
|
|
1480
|
-
msg.message[mtype].caption = msg.message[mtype].caption.replace(new RegExp(`@${mentionedLid.split("@")[0]}`, "g"), `@${pn.split("@")[0]}`);
|
|
1481
|
-
}
|
|
1482
|
-
} else {
|
|
1483
|
-
updatedMentionedJid.push(mentionedLid)
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
needsUpdate = true
|
|
1487
|
-
} else {
|
|
1488
|
-
updatedMentionedJid.push(mentionedLid)
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
if (needsUpdate) {
|
|
1492
|
-
msg.message[mtype].contextInfo.mentionedJid = updatedMentionedJid
|
|
1493
|
-
}
|
|
1378
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1379
|
+
await sendRetryRequest(node, !encNode);
|
|
1380
|
+
if (retryRequestDelayMs) {
|
|
1381
|
+
await delay(retryRequestDelayMs);
|
|
1494
1382
|
}
|
|
1495
1383
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
// FIX: private chat - resolve remoteJid dari LID ke JID
|
|
1499
|
-
const resolvedRemote = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
|
|
1500
|
-
if (resolvedRemote) {
|
|
1501
|
-
msg.key.remoteJid = WABinary_1.jidNormalizedUser(resolvedRemote);
|
|
1502
|
-
} else {
|
|
1503
|
-
// Fallback: coba resolve dari lidMapping
|
|
1504
|
-
try {
|
|
1505
|
-
const resolvedPn = await signalRepository.lidMapping.getPNForLID(msg.key.remoteJid);
|
|
1506
|
-
if (resolvedPn) {
|
|
1507
|
-
msg.key.remoteJid = WABinary_1.jidNormalizedUser(resolvedPn);
|
|
1508
|
-
}
|
|
1509
|
-
} catch (_) {}
|
|
1384
|
+
catch (err) {
|
|
1385
|
+
logger.error({ err }, 'Failed to send retry');
|
|
1510
1386
|
}
|
|
1387
|
+
acked = true;
|
|
1388
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
if (messageRetryManager && msg.key.id) {
|
|
1394
|
+
messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
|
|
1395
|
+
}
|
|
1396
|
+
const isNewsletter = isJidNewsletter(msg.key.remoteJid);
|
|
1397
|
+
if (!isNewsletter) {
|
|
1398
|
+
// no type in the receipt => message delivered
|
|
1399
|
+
let type = undefined;
|
|
1400
|
+
let participant = msg.key.participant;
|
|
1401
|
+
if (category === 'peer') {
|
|
1402
|
+
// special peer message
|
|
1403
|
+
type = 'peer_msg';
|
|
1511
1404
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
author = WABinary_1.jidNormalizedUser(resolvedAuthor);
|
|
1519
|
-
} else {
|
|
1520
|
-
try {
|
|
1521
|
-
const resolvedPn = await signalRepository.lidMapping.getPNForLID(author);
|
|
1522
|
-
if (resolvedPn) author = WABinary_1.jidNormalizedUser(resolvedPn);
|
|
1523
|
-
} catch (_) {}
|
|
1405
|
+
else if (msg.key.fromMe) {
|
|
1406
|
+
// message was sent by us from a different device
|
|
1407
|
+
type = 'sender';
|
|
1408
|
+
// need to specially handle this case
|
|
1409
|
+
if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
|
|
1410
|
+
participant = author; // TODO: investigate sending receipts to LIDs and not PNs
|
|
1524
1411
|
}
|
|
1525
1412
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
type = 'peer_msg'
|
|
1529
|
-
} else if (msg.key.fromMe) {
|
|
1530
|
-
type = 'sender'
|
|
1531
|
-
if (WABinary_1.isLidUser(msg.key.remoteJid) || WABinary_1.isLidUser(msg.key.participant_lid)) {
|
|
1532
|
-
participant = author
|
|
1533
|
-
}
|
|
1534
|
-
} else if (!sendActiveReceipts) {
|
|
1535
|
-
type = 'inactive'
|
|
1413
|
+
else if (!sendActiveReceipts) {
|
|
1414
|
+
type = 'inactive';
|
|
1536
1415
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1416
|
+
acked = true;
|
|
1417
|
+
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
1418
|
+
// send ack for history message
|
|
1419
|
+
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
1540
1420
|
if (isAnyHistoryMsg) {
|
|
1541
|
-
const jid =
|
|
1542
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync')
|
|
1421
|
+
const jid = jidNormalizedUser(msg.key.remoteJid);
|
|
1422
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
|
|
1543
1423
|
}
|
|
1544
1424
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
}, 'error in handling message')
|
|
1555
|
-
// Kalau exception terjadi sebelum sempat ack (misal error di plugin/group
|
|
1556
|
-
// metadata sebelum baris sendMessageAck), stanza ini wajib tetap di-ack.
|
|
1557
|
-
// Tanpa ini, server WA bisa terus retry/redeliver pesan yang sama berulang-ulang.
|
|
1558
|
-
try {
|
|
1559
|
-
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError)
|
|
1560
|
-
} catch (ackError) {
|
|
1561
|
-
logger.error({ ackError }, 'failed to ack message after error')
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
1567
|
-
if (!authState.creds.me?.id) {
|
|
1568
|
-
throw new boom_1.Boom('Not authenticated')
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
const pdoMessage = {
|
|
1572
|
-
historySyncOnDemandRequest: {
|
|
1573
|
-
chatJid: oldestMsgKey.remoteJid,
|
|
1574
|
-
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
1575
|
-
oldestMsgId: oldestMsgKey.id,
|
|
1576
|
-
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
1577
|
-
onDemandMsgCount: count
|
|
1578
|
-
},
|
|
1579
|
-
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
1425
|
+
else {
|
|
1426
|
+
acked = true;
|
|
1427
|
+
await sendMessageAck(node);
|
|
1428
|
+
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1432
|
+
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1433
|
+
});
|
|
1580
1434
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const requestPlaceholderResend = async (messageKey) => {
|
|
1586
|
-
if (!authState.creds.me?.id) {
|
|
1587
|
-
throw new boom_1.Boom('Not authenticated')
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
if (placeholderResendCache.get(messageKey?.id)) {
|
|
1591
|
-
logger.debug({
|
|
1592
|
-
messageKey
|
|
1593
|
-
}, 'already requested resend')
|
|
1594
|
-
return
|
|
1595
|
-
} else {
|
|
1596
|
-
placeholderResendCache.set(messageKey?.id, true)
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
await Utils_1.delay(5000)
|
|
1600
|
-
|
|
1601
|
-
if (!placeholderResendCache.get(messageKey?.id)) {
|
|
1602
|
-
logger.debug({
|
|
1603
|
-
messageKey
|
|
1604
|
-
}, 'message received while resend requested')
|
|
1605
|
-
return 'RESOLVED'
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
const pdoMessage = {
|
|
1609
|
-
placeholderMessageResendRequest: [{
|
|
1610
|
-
messageKey
|
|
1611
|
-
}],
|
|
1612
|
-
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
setTimeout(() => {
|
|
1616
|
-
if (placeholderResendCache.get(messageKey?.id)) {
|
|
1617
|
-
logger.debug({
|
|
1618
|
-
messageKey
|
|
1619
|
-
}, 'PDO message without response after 15 seconds. Phone possibly offline')
|
|
1620
|
-
placeholderResendCache.del(messageKey?.id)
|
|
1435
|
+
catch (error) {
|
|
1436
|
+
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
|
|
1437
|
+
if (!acked) {
|
|
1438
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
|
|
1621
1439
|
}
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
return sendPeerDataOperationMessage(pdoMessage)
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1627
1442
|
const handleCall = async (node) => {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1443
|
+
try {
|
|
1444
|
+
const { attrs } = node;
|
|
1445
|
+
const [infoChild] = getAllBinaryNodeChildren(node);
|
|
1446
|
+
if (!infoChild) {
|
|
1447
|
+
throw new Boom('Missing call info in call node');
|
|
1448
|
+
}
|
|
1449
|
+
const status = getCallStatusFromNode(infoChild);
|
|
1450
|
+
const callId = infoChild.attrs['call-id'];
|
|
1451
|
+
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
1452
|
+
const call = {
|
|
1453
|
+
chatId: attrs.from,
|
|
1454
|
+
from,
|
|
1455
|
+
callerPn: infoChild.attrs['caller_pn'],
|
|
1456
|
+
id: callId,
|
|
1457
|
+
date: new Date(+attrs.t * 1000),
|
|
1458
|
+
offline: !!attrs.offline,
|
|
1459
|
+
status
|
|
1460
|
+
};
|
|
1461
|
+
if (status === 'relaylatency') {
|
|
1462
|
+
const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
|
|
1463
|
+
const latencyMs = latencyValue ? Number(latencyValue) : undefined;
|
|
1464
|
+
if (Number.isFinite(latencyMs)) {
|
|
1465
|
+
call.latencyMs = latencyMs;
|
|
1651
1466
|
}
|
|
1652
|
-
await callOfferCache.set(callId, callLid)
|
|
1653
1467
|
}
|
|
1468
|
+
if (status === 'offer') {
|
|
1469
|
+
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
1470
|
+
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
1471
|
+
call.groupJid = infoChild.attrs['group-jid'];
|
|
1472
|
+
await callOfferCache.set(call.id, call);
|
|
1473
|
+
}
|
|
1474
|
+
const existingCall = await callOfferCache.get(call.id);
|
|
1475
|
+
// use existing call info to populate this event
|
|
1476
|
+
if (existingCall) {
|
|
1477
|
+
call.isVideo = existingCall.isVideo;
|
|
1478
|
+
call.isGroup = existingCall.isGroup;
|
|
1479
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
1480
|
+
}
|
|
1481
|
+
// delete data once call has ended
|
|
1482
|
+
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
1483
|
+
await callOfferCache.del(call.id);
|
|
1484
|
+
}
|
|
1485
|
+
ev.emit('call', [call]);
|
|
1654
1486
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
chatId: attrs.from,
|
|
1658
|
-
from,
|
|
1659
|
-
id: callId,
|
|
1660
|
-
date: new Date(+attrs.t * 1000),
|
|
1661
|
-
offline: !!attrs.offline,
|
|
1662
|
-
status,
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
if (status === 'offer') {
|
|
1666
|
-
call.isVideo = !!WABinary_1.getBinaryNodeChild(infoChild, 'video')
|
|
1667
|
-
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid']
|
|
1668
|
-
call.groupJid = infoChild.attrs['group-jid']
|
|
1669
|
-
await callOfferCache.set(call.id, call)
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
const existingCall = await callOfferCache.get(call.id)
|
|
1673
|
-
|
|
1674
|
-
// use existing call info to populate this event
|
|
1675
|
-
if (existingCall) {
|
|
1676
|
-
call.isVideo = existingCall.isVideo
|
|
1677
|
-
call.isGroup = existingCall.isGroup
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
// delete data once call has ended
|
|
1681
|
-
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
1682
|
-
await callOfferCache.del(call.id)
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
ev.emit('call', [call])
|
|
1686
|
-
|
|
1687
|
-
await sendMessageAck(node)
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
const handleBadAck = async ({
|
|
1691
|
-
attrs
|
|
1692
|
-
}) => {
|
|
1693
|
-
const key = {
|
|
1694
|
-
remoteJid: attrs.from,
|
|
1695
|
-
fromMe: true,
|
|
1696
|
-
id: attrs.id,
|
|
1697
|
-
newsletter_server_id: attrs?.server_id
|
|
1487
|
+
catch (error) {
|
|
1488
|
+
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling call');
|
|
1698
1489
|
}
|
|
1490
|
+
finally {
|
|
1491
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack call'));
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
const handleBadAck = async ({ attrs }) => {
|
|
1495
|
+
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
1699
1496
|
// WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
|
|
1700
1497
|
// // current hypothesis is that if pash is sent in the ack
|
|
1701
1498
|
// // it means -- the message hasn't reached all devices yet
|
|
@@ -1712,130 +1509,125 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1712
1509
|
// error in acknowledgement,
|
|
1713
1510
|
// device could not display the message
|
|
1714
1511
|
if (attrs.error) {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1512
|
+
const isReachoutTimelocked = attrs.error === String(NACK_REASONS.SenderReachoutTimelocked);
|
|
1513
|
+
if (attrs.error === SERVER_ERROR_CODES.MessageAccountRestriction) {
|
|
1514
|
+
// 463 = 1:1 message missing privacy token (tctoken). Usually means the
|
|
1515
|
+
// account is restricted: WhatsApp blocks starting new chats but preserves
|
|
1516
|
+
// existing ones, since established chats already carry a tctoken.
|
|
1517
|
+
// WA Web prevents this client-side (disables the compose bar).
|
|
1518
|
+
// No retry — retrying counts as another "reach out" and worsens the restriction.
|
|
1519
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1520
|
+
const ackFrom = attrs.from;
|
|
1521
|
+
if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
|
|
1522
|
+
inFlight463Recoveries.add(ackFrom);
|
|
1523
|
+
void (async () => {
|
|
1524
|
+
try {
|
|
1525
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
1526
|
+
const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN);
|
|
1527
|
+
const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
1528
|
+
const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds());
|
|
1529
|
+
await storeTcTokensFromIqResult({
|
|
1530
|
+
result,
|
|
1531
|
+
fallbackJid: tcStorageJid,
|
|
1532
|
+
keys: authState.keys,
|
|
1533
|
+
getLIDForPN,
|
|
1534
|
+
onNewJidStored: trackTcTokenJid
|
|
1535
|
+
});
|
|
1536
|
+
logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance');
|
|
1537
|
+
}
|
|
1538
|
+
catch (err) {
|
|
1539
|
+
logger.debug({ from: ackFrom, err: err?.message }, 'failed 463 token recovery issuance');
|
|
1540
|
+
}
|
|
1541
|
+
finally {
|
|
1542
|
+
inFlight463Recoveries.delete(ackFrom);
|
|
1543
|
+
}
|
|
1544
|
+
})();
|
|
1725
1545
|
}
|
|
1726
|
-
}
|
|
1546
|
+
}
|
|
1547
|
+
else if (attrs.error === SERVER_ERROR_CODES.SmaxInvalid) {
|
|
1548
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
1549
|
+
}
|
|
1550
|
+
else if (isReachoutTimelocked) {
|
|
1551
|
+
// user is temporarily restricted, fetch current restriction details
|
|
1552
|
+
await fetchAccountReachoutTimelock().catch(err => logger.warn({ err }, 'failed to fetch reachout timelock'));
|
|
1553
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
1557
|
+
}
|
|
1558
|
+
ev.emit('messages.update', [
|
|
1559
|
+
{
|
|
1560
|
+
key,
|
|
1561
|
+
update: {
|
|
1562
|
+
status: WAMessageStatus.ERROR,
|
|
1563
|
+
messageStubParameters: isReachoutTimelocked ? [attrs.error, ACCOUNT_RESTRICTED_TEXT] : [attrs.error]
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
]);
|
|
1727
1567
|
}
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1568
|
+
};
|
|
1730
1569
|
/// processes a node with the given function
|
|
1731
1570
|
/// and adds the task to the existing buffer if we're buffering events
|
|
1732
1571
|
const processNodeWithBuffer = async (node, identifier, exec) => {
|
|
1733
|
-
ev.buffer()
|
|
1734
|
-
await execTask()
|
|
1735
|
-
ev.flush()
|
|
1736
|
-
|
|
1572
|
+
ev.buffer();
|
|
1573
|
+
await execTask();
|
|
1574
|
+
ev.flush();
|
|
1737
1575
|
function execTask() {
|
|
1738
|
-
return exec(node, false)
|
|
1739
|
-
.catch(err => onUnexpectedError(err, identifier))
|
|
1576
|
+
return exec(node, false).catch(err => onUnexpectedError(err, identifier));
|
|
1740
1577
|
}
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
return
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
isProcessing = true
|
|
1765
|
-
|
|
1766
|
-
const promise = async () => {
|
|
1767
|
-
// PENTING: sebelumnya kalau nodeProcessor(node) throw (misal bug upstream
|
|
1768
|
-
// baileys "No participant in group message" pas decrypt node offline yang
|
|
1769
|
-
// malformed), exception itu keluar dari while-loop ini SEBELUM sempat
|
|
1770
|
-
// `isProcessing = false`. Akibatnya isProcessing kepentok true SELAMANYA,
|
|
1771
|
-
// dan enqueue() di bawah selalu langsung `return` tanpa pernah mulai
|
|
1772
|
-
// proses lagi -> semua pesan sesudahnya cuma numpuk di array `nodes` dan
|
|
1773
|
-
// gak pernah diproses lagi sampai bot di-restart total. Ini kemungkinan
|
|
1774
|
-
// besar penyebab "reconnect tapi gak pernah nerima pesan lagi".
|
|
1775
|
-
try {
|
|
1776
|
-
while (nodes.length && ws.isOpen) {
|
|
1777
|
-
const {
|
|
1778
|
-
type,
|
|
1779
|
-
node
|
|
1780
|
-
} = nodes.shift()
|
|
1781
|
-
const nodeProcessor = nodeProcessorMap.get(type)
|
|
1782
|
-
if (!nodeProcessor) {
|
|
1783
|
-
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node')
|
|
1784
|
-
continue
|
|
1785
|
-
}
|
|
1786
|
-
try {
|
|
1787
|
-
await nodeProcessor(node)
|
|
1788
|
-
} catch (error) {
|
|
1789
|
-
// Satu node gagal (misal pesan corrupt/participant kosong) TIDAK
|
|
1790
|
-
// boleh ngeblok node-node lain di belakangnya dalam antrian.
|
|
1791
|
-
onUnexpectedError(error, 'processing offline node')
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
} finally {
|
|
1795
|
-
isProcessing = false
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
promise().catch(error => onUnexpectedError(error, 'processing offline nodes'))
|
|
1578
|
+
};
|
|
1579
|
+
const offlineNodeProcessor = makeOfflineNodeProcessor(new Map([
|
|
1580
|
+
['message', handleMessage],
|
|
1581
|
+
['call', handleCall],
|
|
1582
|
+
['receipt', handleReceipt],
|
|
1583
|
+
['notification', handleNotification]
|
|
1584
|
+
]), {
|
|
1585
|
+
isWsOpen: () => ws.isOpen,
|
|
1586
|
+
onUnexpectedError,
|
|
1587
|
+
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1588
|
+
});
|
|
1589
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
1590
|
+
// Fast path: ack and drop ignored JIDs before entering the buffer/queue
|
|
1591
|
+
const from = node.attrs.from;
|
|
1592
|
+
let ignoreJid = from;
|
|
1593
|
+
if (type === 'receipt' && from) {
|
|
1594
|
+
const attrs = node.attrs;
|
|
1595
|
+
const isLid = attrs.from.includes('lid');
|
|
1596
|
+
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1597
|
+
ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1799
1598
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1599
|
+
if (ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
|
|
1600
|
+
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
|
|
1601
|
+
return;
|
|
1803
1602
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
|
1807
|
-
|
|
1808
|
-
const processNode = async (type, node, identifier, exec) => {
|
|
1809
|
-
const isOffline = !!node.attrs.offline
|
|
1810
|
-
|
|
1603
|
+
const isOffline = !!node.attrs.offline;
|
|
1811
1604
|
if (isOffline) {
|
|
1812
|
-
offlineNodeProcessor.enqueue(type, node)
|
|
1813
|
-
} else {
|
|
1814
|
-
await processNodeWithBuffer(node, identifier, exec)
|
|
1605
|
+
offlineNodeProcessor.enqueue(type, node);
|
|
1815
1606
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1607
|
+
else {
|
|
1608
|
+
await processNodeWithBuffer(node, identifier, exec);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1818
1611
|
// recv a message
|
|
1819
|
-
// Semua handler di-await supaya event 'CB:message'/'CB:call'/'CB:receipt'/'CB:notification'
|
|
1820
|
-
// yang datang beruntun tidak diproses bersamaan tanpa urutan (race condition di state
|
|
1821
|
-
// bersama seperti session & cache).
|
|
1822
1612
|
ws.on('CB:message', async (node) => {
|
|
1823
|
-
await processNode('message', node, 'processing message', handleMessage)
|
|
1824
|
-
})
|
|
1613
|
+
await processNode('message', node, 'processing message', handleMessage);
|
|
1614
|
+
});
|
|
1825
1615
|
ws.on('CB:call', async (node) => {
|
|
1826
|
-
await processNode('call', node, 'handling call', handleCall)
|
|
1827
|
-
})
|
|
1616
|
+
await processNode('call', node, 'handling call', handleCall);
|
|
1617
|
+
});
|
|
1828
1618
|
ws.on('CB:receipt', async (node) => {
|
|
1829
|
-
await processNode('receipt', node, 'handling receipt', handleReceipt)
|
|
1830
|
-
})
|
|
1619
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1620
|
+
});
|
|
1831
1621
|
ws.on('CB:notification', async (node) => {
|
|
1832
|
-
await processNode('notification', node, 'handling notification', handleNotification)
|
|
1833
|
-
})
|
|
1622
|
+
await processNode('notification', node, 'handling notification', handleNotification);
|
|
1623
|
+
});
|
|
1834
1624
|
ws.on('CB:ack,class:message', (node) => {
|
|
1835
|
-
handleBadAck(node)
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1625
|
+
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
1626
|
+
});
|
|
1627
|
+
ev.on('call', async ([call]) => {
|
|
1628
|
+
if (!call) {
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1839
1631
|
// missed call + group call notification message generation
|
|
1840
1632
|
if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
|
|
1841
1633
|
const msg = {
|
|
@@ -1844,49 +1636,137 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1844
1636
|
id: call.id,
|
|
1845
1637
|
fromMe: false
|
|
1846
1638
|
},
|
|
1847
|
-
messageTimestamp:
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1639
|
+
messageTimestamp: unixTimestampSeconds(call.date)
|
|
1640
|
+
};
|
|
1850
1641
|
if (call.status === 'timeout') {
|
|
1851
1642
|
if (call.isGroup) {
|
|
1852
|
-
msg.messageStubType = call.isVideo
|
|
1853
|
-
|
|
1854
|
-
|
|
1643
|
+
msg.messageStubType = call.isVideo
|
|
1644
|
+
? WAMessageStubType.CALL_MISSED_GROUP_VIDEO
|
|
1645
|
+
: WAMessageStubType.CALL_MISSED_GROUP_VOICE;
|
|
1855
1646
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
call: {
|
|
1859
|
-
callKey: Buffer.from(call.id)
|
|
1860
|
-
}
|
|
1647
|
+
else {
|
|
1648
|
+
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
|
|
1861
1649
|
}
|
|
1862
1650
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1651
|
+
else {
|
|
1652
|
+
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
1653
|
+
}
|
|
1654
|
+
const protoMsg = proto.WebMessageInfo.fromObject(msg);
|
|
1655
|
+
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
/** timestamp of last tctoken prune run — throttles to once per 24h */
|
|
1659
|
+
let lastTcTokenPruneTs = 0;
|
|
1660
|
+
/** dedupe in-flight 463 recovery token issuance by target JID */
|
|
1661
|
+
const inFlight463Recoveries = new Set();
|
|
1662
|
+
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
1872
1663
|
if (typeof isOnline !== 'undefined') {
|
|
1873
|
-
sendActiveReceipts = isOnline
|
|
1874
|
-
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`)
|
|
1664
|
+
sendActiveReceipts = isOnline;
|
|
1665
|
+
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
1666
|
+
}
|
|
1667
|
+
// Flush pending tctoken index save on disconnect to avoid writing after close
|
|
1668
|
+
if (connection === 'close' && tcTokenIndexTimer) {
|
|
1669
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1670
|
+
tcTokenIndexTimer = undefined;
|
|
1671
|
+
// Best-effort flush — may fail if store is already closed
|
|
1672
|
+
try {
|
|
1673
|
+
void Promise.resolve(flushTcTokenIndex()).catch(() => { });
|
|
1674
|
+
}
|
|
1675
|
+
catch {
|
|
1676
|
+
/* ignore sync errors */
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
// Prune expired tctokens when coming online, at most once per 24 hours
|
|
1680
|
+
// Matches WA Web's CLEAN_TC_TOKENS task
|
|
1681
|
+
// Note: don't gate on tcTokenKnownJids.size — the index may still be loading
|
|
1682
|
+
if (isOnline) {
|
|
1683
|
+
const now = Date.now();
|
|
1684
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
1685
|
+
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
1686
|
+
lastTcTokenPruneTs = now;
|
|
1687
|
+
void pruneExpiredTcTokens();
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
registerSocketEndHandler(() => {
|
|
1692
|
+
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
1693
|
+
msgRetryCache.close();
|
|
1694
|
+
}
|
|
1695
|
+
if (!config.callOfferCache && callOfferCache.close) {
|
|
1696
|
+
callOfferCache.close();
|
|
1697
|
+
}
|
|
1698
|
+
identityAssertDebounce.close();
|
|
1699
|
+
sendActiveReceipts = false;
|
|
1700
|
+
});
|
|
1701
|
+
async function pruneExpiredTcTokens() {
|
|
1702
|
+
try {
|
|
1703
|
+
await tcTokenIndexLoaded;
|
|
1704
|
+
// Union with the persisted index picks up JIDs added by other layers
|
|
1705
|
+
// (history sync) without needing inter-module wiring.
|
|
1706
|
+
const persisted = await readTcTokenIndex(authState.keys);
|
|
1707
|
+
const allJids = new Set(tcTokenKnownJids);
|
|
1708
|
+
for (const jid of persisted)
|
|
1709
|
+
allJids.add(jid);
|
|
1710
|
+
if (!allJids.size)
|
|
1711
|
+
return;
|
|
1712
|
+
const jids = [...allJids];
|
|
1713
|
+
const allTokens = await authState.keys.get('tctoken', jids);
|
|
1714
|
+
const writes = {};
|
|
1715
|
+
const survivors = new Set();
|
|
1716
|
+
let mutated = 0;
|
|
1717
|
+
for (const jid of jids) {
|
|
1718
|
+
const entry = allTokens[jid];
|
|
1719
|
+
if (!entry) {
|
|
1720
|
+
// Tracked but nothing in store — drop from index.
|
|
1721
|
+
mutated++;
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
const hasPeerToken = !!entry.token?.length;
|
|
1725
|
+
const peerTokenExpired = hasPeerToken && isTcTokenExpired(entry.timestamp);
|
|
1726
|
+
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
1727
|
+
const senderTsExpired = hasSenderTs && isTcTokenExpired(entry.senderTimestamp);
|
|
1728
|
+
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
1729
|
+
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
1730
|
+
if (!keepPeerToken && !keepSenderTs) {
|
|
1731
|
+
writes[jid] = null;
|
|
1732
|
+
mutated++;
|
|
1733
|
+
}
|
|
1734
|
+
else if (peerTokenExpired && keepSenderTs) {
|
|
1735
|
+
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
1736
|
+
survivors.add(jid);
|
|
1737
|
+
mutated++;
|
|
1738
|
+
}
|
|
1739
|
+
else {
|
|
1740
|
+
survivors.add(jid);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (mutated === 0)
|
|
1744
|
+
return;
|
|
1745
|
+
await authState.keys.set({
|
|
1746
|
+
tctoken: {
|
|
1747
|
+
...writes,
|
|
1748
|
+
[TC_TOKEN_INDEX_KEY]: {
|
|
1749
|
+
token: Buffer.from(JSON.stringify([...survivors]))
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
tcTokenKnownJids.clear();
|
|
1754
|
+
for (const jid of survivors)
|
|
1755
|
+
tcTokenKnownJids.add(jid);
|
|
1756
|
+
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
1757
|
+
}
|
|
1758
|
+
catch (err) {
|
|
1759
|
+
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
1875
1760
|
}
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1761
|
+
}
|
|
1878
1762
|
return {
|
|
1879
1763
|
...sock,
|
|
1880
1764
|
sendMessageAck,
|
|
1881
1765
|
sendRetryRequest,
|
|
1882
|
-
offerCall,
|
|
1883
1766
|
rejectCall,
|
|
1884
1767
|
fetchMessageHistory,
|
|
1885
1768
|
requestPlaceholderResend,
|
|
1886
1769
|
messageRetryManager
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
module.exports = {
|
|
1891
|
-
makeMessagesRecvSocket
|
|
1892
|
-
}
|
|
1770
|
+
};
|
|
1771
|
+
};
|
|
1772
|
+
//# sourceMappingURL=messages-recv.js.map
|