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