zapo-js 0.1.0 → 0.1.2
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/README.md +12 -7
- package/dist/appstate/WaAppStateCrypto.js +18 -25
- package/dist/appstate/WaAppStateSyncClient.js +181 -114
- package/dist/appstate/WaAppStateSyncResponseParser.js +16 -5
- package/dist/appstate/constants.js +4 -3
- package/dist/appstate/utils.js +10 -30
- package/dist/auth/WaAuthClient.js +48 -55
- package/dist/auth/flow/WaAuthCredentialsFlow.js +21 -14
- package/dist/auth/index.js +1 -3
- package/dist/auth/pairing/WaPairingFlow.js +21 -23
- package/dist/auth/pairing/WaQrFlow.js +37 -24
- package/dist/client/WaClient.js +103 -276
- package/dist/client/WaClientFactory.js +227 -110
- package/dist/client/connection/WaConnectionManager.js +292 -0
- package/dist/client/connection/WaKeyShareCoordinator.js +63 -0
- package/dist/client/connection/WaReceiptQueue.js +51 -0
- package/dist/client/coordinators/WaAppStateMutationCoordinator.js +471 -0
- package/dist/client/coordinators/WaGroupCoordinator.js +27 -17
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +20 -27
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +231 -686
- package/dist/client/coordinators/WaRetryCoordinator.js +70 -37
- package/dist/client/dirty.js +35 -29
- package/dist/client/events/chat.js +4 -3
- package/dist/client/events/group.js +59 -36
- package/dist/client/history-sync.js +53 -63
- package/dist/client/incoming.js +23 -20
- package/dist/client/mailbox.js +8 -8
- package/dist/client/messages.js +4 -4
- package/dist/client/messaging/fanout.js +189 -0
- package/dist/client/messaging/key-protocol.js +130 -0
- package/dist/client/messaging/participants.js +191 -0
- package/dist/crypto/core/hkdf.js +3 -8
- package/dist/crypto/core/index.js +1 -4
- package/dist/crypto/core/keys.js +2 -3
- package/dist/crypto/core/primitives.js +12 -15
- package/dist/crypto/core/random.js +7 -26
- package/dist/crypto/curves/Ed25519.js +7 -8
- package/dist/crypto/curves/X25519.js +13 -16
- package/dist/crypto/index.js +0 -5
- package/dist/esm/appstate/WaAppStateCrypto.js +6 -13
- package/dist/esm/appstate/WaAppStateSyncClient.js +174 -107
- package/dist/esm/appstate/WaAppStateSyncResponseParser.js +17 -6
- package/dist/esm/appstate/constants.js +3 -2
- package/dist/esm/appstate/utils.js +8 -27
- package/dist/esm/auth/WaAuthClient.js +48 -55
- package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +21 -14
- package/dist/esm/auth/index.js +0 -1
- package/dist/esm/auth/pairing/WaPairingFlow.js +14 -16
- package/dist/esm/auth/pairing/WaQrFlow.js +37 -24
- package/dist/esm/client/WaClient.js +103 -276
- package/dist/esm/client/WaClientFactory.js +227 -110
- package/dist/esm/client/connection/WaConnectionManager.js +288 -0
- package/dist/esm/client/connection/WaKeyShareCoordinator.js +59 -0
- package/dist/esm/client/connection/WaReceiptQueue.js +47 -0
- package/dist/esm/client/coordinators/WaAppStateMutationCoordinator.js +467 -0
- package/dist/esm/client/coordinators/WaGroupCoordinator.js +20 -10
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +20 -27
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +232 -687
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +71 -38
- package/dist/esm/client/dirty.js +30 -24
- package/dist/esm/client/events/chat.js +4 -3
- package/dist/esm/client/events/group.js +50 -28
- package/dist/esm/client/history-sync.js +50 -60
- package/dist/esm/client/incoming.js +23 -20
- package/dist/esm/client/mailbox.js +8 -8
- package/dist/esm/client/messages.js +1 -1
- package/dist/esm/client/messaging/fanout.js +186 -0
- package/dist/esm/client/messaging/key-protocol.js +127 -0
- package/dist/esm/client/messaging/participants.js +188 -0
- package/dist/esm/crypto/core/hkdf.js +3 -8
- package/dist/esm/crypto/core/index.js +0 -1
- package/dist/esm/crypto/core/keys.js +2 -3
- package/dist/esm/crypto/core/primitives.js +12 -15
- package/dist/esm/crypto/core/random.js +6 -25
- package/dist/esm/crypto/curves/Ed25519.js +4 -5
- package/dist/esm/crypto/curves/X25519.js +10 -13
- package/dist/esm/crypto/index.js +0 -2
- package/dist/esm/infra/log/ConsoleLogger.js +18 -17
- package/dist/esm/infra/log/PinoLogger.js +15 -9
- package/dist/esm/infra/log/types.js +11 -1
- package/dist/esm/infra/perf/BoundedTaskQueue.js +13 -17
- package/dist/esm/media/WaMediaCrypto.js +2 -4
- package/dist/esm/media/WaMediaTransferClient.js +226 -58
- package/dist/esm/media/conn.js +10 -6
- package/dist/esm/media/constants.js +4 -1
- package/dist/esm/message/WaMessageClient.js +4 -13
- package/dist/esm/message/ack.js +6 -6
- package/dist/esm/message/addon-crypto.js +59 -0
- package/dist/esm/message/incoming.js +106 -111
- package/dist/esm/message/index.js +2 -0
- package/dist/esm/message/reporting-token.js +438 -0
- package/dist/esm/message/use-case-secret.js +49 -0
- package/dist/esm/protocol/appstate.js +58 -0
- package/dist/esm/protocol/constants.js +2 -1
- package/dist/esm/protocol/index.js +2 -10
- package/dist/esm/protocol/jid.js +63 -51
- package/dist/esm/protocol/media.js +3 -3
- package/dist/esm/protocol/nodes.js +2 -0
- package/dist/esm/protocol/usync.js +11 -0
- package/dist/esm/retry/index.js +1 -0
- package/dist/esm/retry/outbound.js +4 -5
- package/dist/esm/retry/parse.js +58 -76
- package/dist/esm/retry/replay.js +48 -49
- package/dist/esm/retry/tracker.js +56 -0
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +249 -82
- package/dist/esm/signal/api/SignalDigestSyncApi.js +6 -1
- package/dist/esm/signal/api/SignalIdentitySyncApi.js +49 -34
- package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +70 -62
- package/dist/esm/signal/api/SignalSessionSyncApi.js +23 -30
- package/dist/esm/signal/crypto/WaAdvSignature.js +3 -5
- package/dist/esm/signal/group/SenderKeyChain.js +28 -23
- package/dist/esm/signal/group/SenderKeyCodec.js +2 -4
- package/dist/esm/signal/group/SenderKeyManager.js +26 -16
- package/dist/esm/signal/index.js +1 -0
- package/dist/esm/signal/session/SignalProtocol.js +49 -14
- package/dist/esm/signal/session/SignalRatchet.js +24 -15
- package/dist/esm/signal/session/SignalSession.js +14 -9
- package/dist/esm/signal/session/resolver.js +186 -0
- package/dist/esm/signal/store/sqlite.js +16 -37
- package/dist/esm/store/createStore.js +16 -18
- package/dist/esm/store/noop.store.js +3 -6
- package/dist/esm/store/providers/memory/appstate.store.js +30 -6
- package/dist/esm/store/providers/memory/contact.store.js +5 -0
- package/dist/esm/store/providers/memory/device-list.store.js +3 -30
- package/dist/esm/store/providers/memory/message.store.js +11 -5
- package/dist/esm/store/providers/memory/participants.store.js +1 -8
- package/dist/esm/store/providers/memory/sender-key.store.js +5 -7
- package/dist/esm/store/providers/memory/signal.store.js +13 -1
- package/dist/esm/store/providers/memory/thread.store.js +5 -0
- package/dist/esm/store/providers/sqlite/appstate.store.js +82 -1
- package/dist/esm/store/providers/sqlite/connection.js +18 -13
- package/dist/esm/store/providers/sqlite/contact.store.js +31 -18
- package/dist/esm/store/providers/sqlite/device-list.store.js +7 -35
- package/dist/esm/store/providers/sqlite/message.store.js +45 -32
- package/dist/esm/store/providers/sqlite/migrations.js +1 -1
- package/dist/esm/store/providers/sqlite/participants.store.js +1 -9
- package/dist/esm/store/providers/sqlite/retry.store.js +8 -11
- package/dist/esm/store/providers/sqlite/sender-key.store.js +25 -30
- package/dist/esm/store/providers/sqlite/signal.store.js +104 -22
- package/dist/esm/store/providers/sqlite/table-names.js +107 -0
- package/dist/esm/store/providers/sqlite/thread.store.js +35 -22
- package/dist/esm/transport/WaComms.js +25 -23
- package/dist/esm/transport/WaWebSocket.js +115 -12
- package/dist/esm/transport/binary/decoder.js +4 -4
- package/dist/esm/transport/binary/encoder.js +12 -4
- package/dist/esm/transport/index.js +1 -0
- package/dist/esm/transport/keepalive/WaKeepAlive.js +2 -8
- package/dist/esm/transport/node/WaNodeOrchestrator.js +2 -4
- package/dist/esm/transport/node/WaNodeTransport.js +0 -3
- package/dist/esm/transport/node/builders/{accountSync.js → account-sync.js} +16 -36
- package/dist/esm/transport/node/builders/index.js +2 -1
- package/dist/esm/transport/node/builders/message.js +9 -0
- package/dist/esm/transport/node/builders/pairing.js +4 -5
- package/dist/esm/transport/node/builders/usync.js +41 -0
- package/dist/esm/transport/node/helpers.js +107 -5
- package/dist/esm/transport/node/usync.js +35 -0
- package/dist/esm/transport/noise/WaFrameCodec.js +48 -33
- package/dist/esm/transport/noise/WaNoiseCert.js +3 -6
- package/dist/esm/transport/noise/WaNoiseSession.js +17 -10
- package/dist/esm/transport/proxy.js +27 -0
- package/dist/esm/transport/stream/parse.js +13 -48
- package/dist/esm/util/bytes.js +50 -32
- package/dist/esm/util/coercion.js +6 -14
- package/dist/esm/util/primitives.js +39 -14
- package/dist/infra/log/ConsoleLogger.js +18 -17
- package/dist/infra/log/PinoLogger.js +15 -9
- package/dist/infra/log/types.js +12 -0
- package/dist/infra/perf/BoundedTaskQueue.js +13 -17
- package/dist/media/WaMediaCrypto.js +1 -3
- package/dist/media/WaMediaTransferClient.js +259 -58
- package/dist/media/conn.js +10 -6
- package/dist/media/constants.js +4 -1
- package/dist/message/WaMessageClient.js +5 -14
- package/dist/message/ack.js +6 -6
- package/dist/message/addon-crypto.js +65 -0
- package/dist/message/incoming.js +104 -109
- package/dist/message/index.js +2 -0
- package/dist/message/reporting-token.js +443 -0
- package/dist/message/use-case-secret.js +55 -0
- package/dist/protocol/appstate.js +59 -1
- package/dist/protocol/constants.js +7 -1
- package/dist/protocol/index.js +20 -42
- package/dist/protocol/jid.js +64 -51
- package/dist/protocol/media.js +3 -3
- package/dist/protocol/nodes.js +2 -0
- package/dist/protocol/usync.js +14 -0
- package/dist/retry/index.js +3 -1
- package/dist/retry/outbound.js +6 -7
- package/dist/retry/parse.js +57 -75
- package/dist/retry/replay.js +46 -47
- package/dist/retry/tracker.js +59 -0
- package/dist/signal/api/SignalDeviceSyncApi.js +247 -80
- package/dist/signal/api/SignalDigestSyncApi.js +6 -1
- package/dist/signal/api/SignalIdentitySyncApi.js +49 -34
- package/dist/signal/api/SignalMissingPreKeysSyncApi.js +67 -59
- package/dist/signal/api/SignalSessionSyncApi.js +23 -30
- package/dist/signal/crypto/WaAdvSignature.js +2 -4
- package/dist/signal/group/SenderKeyChain.js +27 -22
- package/dist/signal/group/SenderKeyCodec.js +1 -3
- package/dist/signal/group/SenderKeyManager.js +26 -16
- package/dist/signal/index.js +3 -1
- package/dist/signal/session/SignalProtocol.js +49 -14
- package/dist/signal/session/SignalRatchet.js +24 -15
- package/dist/signal/session/SignalSession.js +14 -9
- package/dist/signal/session/resolver.js +189 -0
- package/dist/signal/store/sqlite.js +16 -37
- package/dist/store/createStore.js +16 -18
- package/dist/store/noop.store.js +3 -6
- package/dist/store/providers/memory/appstate.store.js +28 -4
- package/dist/store/providers/memory/contact.store.js +5 -0
- package/dist/store/providers/memory/device-list.store.js +3 -30
- package/dist/store/providers/memory/message.store.js +11 -5
- package/dist/store/providers/memory/participants.store.js +1 -8
- package/dist/store/providers/memory/sender-key.store.js +8 -10
- package/dist/store/providers/memory/signal.store.js +21 -9
- package/dist/store/providers/memory/thread.store.js +5 -0
- package/dist/store/providers/sqlite/appstate.store.js +81 -0
- package/dist/store/providers/sqlite/connection.js +18 -13
- package/dist/store/providers/sqlite/contact.store.js +31 -18
- package/dist/store/providers/sqlite/device-list.store.js +7 -35
- package/dist/store/providers/sqlite/message.store.js +45 -32
- package/dist/store/providers/sqlite/migrations.js +1 -1
- package/dist/store/providers/sqlite/participants.store.js +1 -9
- package/dist/store/providers/sqlite/retry.store.js +8 -11
- package/dist/store/providers/sqlite/sender-key.store.js +24 -29
- package/dist/store/providers/sqlite/signal.store.js +105 -23
- package/dist/store/providers/sqlite/table-names.js +113 -0
- package/dist/store/providers/sqlite/thread.store.js +35 -22
- package/dist/transport/WaComms.js +27 -25
- package/dist/transport/WaWebSocket.js +148 -12
- package/dist/transport/binary/decoder.js +4 -4
- package/dist/transport/binary/encoder.js +12 -4
- package/dist/transport/index.js +7 -1
- package/dist/transport/keepalive/WaKeepAlive.js +1 -7
- package/dist/transport/node/WaNodeOrchestrator.js +2 -4
- package/dist/transport/node/WaNodeTransport.js +0 -3
- package/dist/transport/node/builders/{accountSync.js → account-sync.js} +15 -35
- package/dist/transport/node/builders/index.js +12 -9
- package/dist/transport/node/builders/message.js +9 -0
- package/dist/transport/node/builders/pairing.js +4 -5
- package/dist/transport/node/builders/usync.js +45 -0
- package/dist/transport/node/helpers.js +112 -4
- package/dist/transport/node/usync.js +38 -0
- package/dist/transport/noise/WaFrameCodec.js +47 -32
- package/dist/transport/noise/WaNoiseCert.js +5 -8
- package/dist/transport/noise/WaNoiseSession.js +17 -10
- package/dist/transport/proxy.js +34 -0
- package/dist/transport/stream/parse.js +17 -53
- package/dist/types/appstate/WaAppStateCrypto.d.ts +0 -1
- package/dist/types/appstate/WaAppStateSyncClient.d.ts +5 -2
- package/dist/types/appstate/constants.d.ts +1 -0
- package/dist/types/appstate/store/sqlite.d.ts +4 -18
- package/dist/types/appstate/utils.d.ts +0 -1
- package/dist/types/auth/WaAuthClient.d.ts +10 -12
- package/dist/types/auth/index.d.ts +0 -2
- package/dist/types/auth/pairing/WaQrFlow.d.ts +1 -1
- package/dist/types/auth/types.d.ts +6 -9
- package/dist/types/client/WaClient.d.ts +27 -25
- package/dist/types/client/WaClientFactory.d.ts +22 -23
- package/dist/types/client/connection/WaConnectionManager.d.ts +64 -0
- package/dist/types/client/connection/WaKeyShareCoordinator.d.ts +14 -0
- package/dist/types/client/connection/WaReceiptQueue.d.ts +13 -0
- package/dist/types/client/coordinators/WaAppStateMutationCoordinator.d.ts +46 -0
- package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +0 -1
- package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +18 -41
- package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +2 -0
- package/dist/types/client/dirty.d.ts +1 -0
- package/dist/types/client/events/group.d.ts +2 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/messaging/fanout.d.ts +14 -0
- package/dist/types/client/messaging/key-protocol.d.ts +18 -0
- package/dist/types/client/messaging/participants.d.ts +13 -0
- package/dist/types/client/types.d.ts +24 -1
- package/dist/types/crypto/core/hkdf.d.ts +0 -6
- package/dist/types/crypto/core/index.d.ts +0 -1
- package/dist/types/crypto/core/random.d.ts +1 -7
- package/dist/types/crypto/index.d.ts +0 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/infra/log/ConsoleLogger.d.ts +2 -1
- package/dist/types/infra/log/PinoLogger.d.ts +1 -1
- package/dist/types/infra/log/types.d.ts +1 -0
- package/dist/types/infra/perf/BoundedTaskQueue.d.ts +1 -1
- package/dist/types/media/WaMediaTransferClient.d.ts +13 -3
- package/dist/types/media/types.d.ts +5 -0
- package/dist/types/message/addon-crypto.d.ts +25 -0
- package/dist/types/message/index.d.ts +2 -0
- package/dist/types/message/reporting-token.d.ts +19 -0
- package/dist/types/message/use-case-secret.d.ts +20 -0
- package/dist/types/protocol/appstate.d.ts +58 -0
- package/dist/types/protocol/constants.d.ts +2 -1
- package/dist/types/protocol/index.d.ts +2 -10
- package/dist/types/protocol/jid.d.ts +3 -3
- package/dist/types/protocol/nodes.d.ts +2 -0
- package/dist/types/protocol/usync.d.ts +11 -0
- package/dist/types/retry/index.d.ts +1 -0
- package/dist/types/retry/replay.d.ts +0 -4
- package/dist/types/retry/tracker.d.ts +19 -0
- package/dist/types/retry/types.d.ts +4 -3
- package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +13 -1
- package/dist/types/signal/group/SenderKeyCodec.d.ts +4 -6
- package/dist/types/signal/index.d.ts +1 -0
- package/dist/types/signal/session/SignalProtocol.d.ts +9 -0
- package/dist/types/signal/session/resolver.d.ts +17 -0
- package/dist/types/store/contracts/appstate.store.d.ts +3 -0
- package/dist/types/store/contracts/contact.store.d.ts +1 -0
- package/dist/types/store/contracts/device-list.store.d.ts +0 -3
- package/dist/types/store/contracts/message.store.d.ts +1 -0
- package/dist/types/store/contracts/participants.store.d.ts +0 -1
- package/dist/types/store/contracts/sender-key.store.d.ts +0 -1
- package/dist/types/store/contracts/signal.store.d.ts +6 -0
- package/dist/types/store/contracts/thread.store.d.ts +1 -0
- package/dist/types/store/index.d.ts +1 -1
- package/dist/types/store/providers/memory/appstate.store.d.ts +2 -0
- package/dist/types/store/providers/memory/contact.store.d.ts +1 -0
- package/dist/types/store/providers/memory/device-list.store.d.ts +0 -3
- package/dist/types/store/providers/memory/message.store.d.ts +1 -0
- package/dist/types/store/providers/memory/participants.store.d.ts +0 -1
- package/dist/types/store/providers/memory/sender-key.store.d.ts +0 -1
- package/dist/types/store/providers/memory/signal.store.d.ts +6 -0
- package/dist/types/store/providers/memory/thread.store.d.ts +1 -0
- package/dist/types/store/providers/sqlite/appstate.store.d.ts +2 -0
- package/dist/types/store/providers/sqlite/contact.store.d.ts +2 -0
- package/dist/types/store/providers/sqlite/device-list.store.d.ts +0 -3
- package/dist/types/store/providers/sqlite/message.store.d.ts +2 -0
- package/dist/types/store/providers/sqlite/participants.store.d.ts +0 -1
- package/dist/types/store/providers/sqlite/retry.store.d.ts +0 -1
- package/dist/types/store/providers/sqlite/sender-key.store.d.ts +0 -1
- package/dist/types/store/providers/sqlite/signal.store.d.ts +7 -0
- package/dist/types/store/providers/sqlite/table-names.d.ts +5 -0
- package/dist/types/store/providers/sqlite/thread.store.d.ts +2 -0
- package/dist/types/store/types.d.ts +3 -0
- package/dist/types/transport/WaWebSocket.d.ts +3 -0
- package/dist/types/transport/index.d.ts +2 -1
- package/dist/types/transport/keepalive/WaKeepAlive.d.ts +0 -1
- package/dist/types/transport/node/WaNodeTransport.d.ts +0 -9
- package/dist/types/transport/node/builders/group.d.ts +4 -6
- package/dist/types/transport/node/builders/index.d.ts +2 -1
- package/dist/types/transport/node/builders/message.d.ts +14 -25
- package/dist/types/transport/node/builders/retry.d.ts +2 -4
- package/dist/types/transport/node/builders/usync.d.ts +21 -0
- package/dist/types/transport/node/helpers.d.ts +8 -0
- package/dist/types/transport/node/usync.d.ts +2 -0
- package/dist/types/transport/noise/WaFrameCodec.d.ts +3 -0
- package/dist/types/transport/noise/WaNoiseSession.d.ts +1 -0
- package/dist/types/transport/proxy.d.ts +6 -0
- package/dist/types/transport/stream/parse.d.ts +0 -1
- package/dist/types/transport/types.d.ts +18 -1
- package/dist/types/util/bytes.d.ts +5 -0
- package/dist/types/util/primitives.d.ts +3 -0
- package/dist/util/bytes.js +55 -33
- package/dist/util/coercion.js +6 -14
- package/dist/util/primitives.js +42 -14
- package/package.json +27 -9
- package/proto/index.d.ts +1090 -1048
- package/proto/index.js +1 -1
- package/scripts/check-node-version.cjs +0 -1
- package/dist/crypto/core/encoding.js +0 -29
- package/dist/esm/crypto/core/encoding.js +0 -25
- package/dist/esm/util/base64.js +0 -18
- package/dist/esm/util/signal-address.js +0 -5
- package/dist/types/crypto/core/encoding.d.ts +0 -11
- package/dist/types/util/base64.d.ts +0 -4
- package/dist/types/util/signal-address.d.ts +0 -2
- package/dist/util/base64.js +0 -24
- package/dist/util/signal-address.js +0 -8
- /package/dist/types/transport/node/builders/{accountSync.d.ts → account-sync.d.ts} +0 -0
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomBytesAsync, sha256 } from '../../crypto/index.js';
|
|
2
|
+
import { ensureMessageSecret } from '../../message/index.js';
|
|
2
3
|
import { resolveMessageTypeAttr } from '../../message/content.js';
|
|
3
4
|
import { wrapDeviceSentMessage } from '../../message/device-sent.js';
|
|
4
5
|
import { writeRandomPadMax16 } from '../../message/padding.js';
|
|
5
6
|
import { computePhashV2 } from '../../message/phash.js';
|
|
7
|
+
import { buildReportingTokenArtifacts } from '../../message/reporting-token.js';
|
|
6
8
|
import { proto } from '../../proto.js';
|
|
7
9
|
import { WA_DEFAULTS } from '../../protocol/constants.js';
|
|
8
10
|
import { isGroupJid, normalizeDeviceJid, normalizeRecipientJid, parseSignalAddressFromJid, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
9
|
-
import {
|
|
10
|
-
import { encodeRetryReplayPayload } from '../../retry/outbound.js';
|
|
11
|
+
import { signalAddressKey } from '../../protocol/jid.js';
|
|
11
12
|
import { encodeBinaryNode } from '../../transport/binary/index.js';
|
|
12
13
|
import { buildDirectMessageFanoutNode, buildGroupDirectMessageNode, buildGroupSenderKeyMessageNode } from '../../transport/node/builders/message.js';
|
|
13
|
-
import { bytesToHex,
|
|
14
|
+
import { bytesToHex, concatBytes, TEXT_ENCODER } from '../../util/bytes.js';
|
|
14
15
|
import { toError } from '../../util/primitives.js';
|
|
15
|
-
import { signalAddressKey } from '../../util/signal-address.js';
|
|
16
16
|
export class WaMessageDispatchCoordinator {
|
|
17
17
|
constructor(options) {
|
|
18
18
|
this.logger = options.logger;
|
|
19
19
|
this.messageClient = options.messageClient;
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
20
|
+
this.retryTracker = options.retryTracker;
|
|
21
|
+
this.sessionResolver = options.sessionResolver;
|
|
22
|
+
this.fanoutResolver = options.fanoutResolver;
|
|
23
|
+
this.participantsCache = options.participantsCache;
|
|
24
|
+
this.appStateSyncKeyProtocol = options.appStateSyncKeyProtocol;
|
|
23
25
|
this.buildMessageContent = options.buildMessageContent;
|
|
24
|
-
this.queryGroupParticipantJids = options.queryGroupParticipantJids;
|
|
25
26
|
this.senderKeyManager = options.senderKeyManager;
|
|
26
27
|
this.signalProtocol = options.signalProtocol;
|
|
27
|
-
this.signalStore = options.signalStore;
|
|
28
|
-
this.signalDeviceSync = options.signalDeviceSync;
|
|
29
|
-
this.signalIdentitySync = options.signalIdentitySync;
|
|
30
|
-
this.signalSessionSync = options.signalSessionSync;
|
|
31
28
|
this.getCurrentMeJid = options.getCurrentMeJid;
|
|
32
29
|
this.getCurrentMeLid = options.getCurrentMeLid;
|
|
33
30
|
this.getCurrentSignedIdentity = options.getCurrentSignedIdentity;
|
|
@@ -43,13 +40,13 @@ export class WaMessageDispatchCoordinator {
|
|
|
43
40
|
mode: 'opaque_node',
|
|
44
41
|
node: encodeBinaryNode(node)
|
|
45
42
|
};
|
|
46
|
-
return this.
|
|
43
|
+
return this.retryTracker.track({
|
|
47
44
|
messageIdHint: node.attrs.id,
|
|
48
45
|
toJid: node.attrs.to,
|
|
46
|
+
type: messageType,
|
|
47
|
+
replayPayload,
|
|
49
48
|
participantJid: node.attrs.participant,
|
|
50
|
-
recipientJid: node.attrs.recipient
|
|
51
|
-
messageType,
|
|
52
|
-
replayPayload
|
|
49
|
+
recipientJid: node.attrs.recipient
|
|
53
50
|
}, async () => this.messageClient.publishNode(node, options));
|
|
54
51
|
}
|
|
55
52
|
async publishEncryptedMessage(input, options = {}) {
|
|
@@ -66,12 +63,12 @@ export class WaMessageDispatchCoordinator {
|
|
|
66
63
|
ciphertext: input.ciphertext,
|
|
67
64
|
participant: input.participant
|
|
68
65
|
};
|
|
69
|
-
return this.
|
|
66
|
+
return this.retryTracker.track({
|
|
70
67
|
messageIdHint: input.id,
|
|
71
68
|
toJid: input.to,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
type: input.type ?? 'text',
|
|
70
|
+
replayPayload,
|
|
71
|
+
participantJid: input.participant
|
|
75
72
|
}, async () => this.messageClient.publishEncrypted(input, options));
|
|
76
73
|
}
|
|
77
74
|
async publishSignalMessage(input, options = {}) {
|
|
@@ -86,7 +83,7 @@ export class WaMessageDispatchCoordinator {
|
|
|
86
83
|
});
|
|
87
84
|
const [paddedPlaintext] = await Promise.all([
|
|
88
85
|
writeRandomPadMax16(input.plaintext),
|
|
89
|
-
this.
|
|
86
|
+
this.sessionResolver.ensureSession(address, input.to, input.expectedIdentity)
|
|
90
87
|
]);
|
|
91
88
|
const encrypted = await this.signalProtocol.encryptMessage(address, paddedPlaintext, input.expectedIdentity);
|
|
92
89
|
const messageType = input.type ?? 'text';
|
|
@@ -96,12 +93,12 @@ export class WaMessageDispatchCoordinator {
|
|
|
96
93
|
type: messageType,
|
|
97
94
|
plaintext: paddedPlaintext
|
|
98
95
|
};
|
|
99
|
-
return this.
|
|
96
|
+
return this.retryTracker.track({
|
|
100
97
|
messageIdHint: input.id,
|
|
101
98
|
toJid: input.to,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
type: messageType,
|
|
100
|
+
replayPayload,
|
|
101
|
+
participantJid: input.participant
|
|
105
102
|
}, async () => this.messageClient.publishEncrypted({
|
|
106
103
|
to: input.to,
|
|
107
104
|
encType: encrypted.type,
|
|
@@ -116,286 +113,40 @@ export class WaMessageDispatchCoordinator {
|
|
|
116
113
|
}
|
|
117
114
|
async sendMessage(to, content, options = {}) {
|
|
118
115
|
const recipientJid = normalizeRecipientJid(to);
|
|
119
|
-
const message = await
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
const [message, sendOptions] = await Promise.all([
|
|
117
|
+
this.buildMessageContent(content),
|
|
118
|
+
this.withResolvedMessageId(options)
|
|
119
|
+
]);
|
|
120
|
+
const messageWithSecret = await ensureMessageSecret(message);
|
|
121
|
+
const plaintext = await writeRandomPadMax16(proto.Message.encode(messageWithSecret).finish());
|
|
122
|
+
const type = resolveMessageTypeAttr(messageWithSecret);
|
|
122
123
|
if (isGroupJid(recipientJid)) {
|
|
123
|
-
if (this.shouldUseGroupDirectPath(
|
|
124
|
-
return this.publishGroupDirectMessage(recipientJid, plaintext, type,
|
|
124
|
+
if (this.shouldUseGroupDirectPath(messageWithSecret)) {
|
|
125
|
+
return this.publishGroupDirectMessage(recipientJid, messageWithSecret, plaintext, type, sendOptions);
|
|
125
126
|
}
|
|
126
|
-
return this.publishGroupSenderKeyMessage(recipientJid, plaintext, type,
|
|
127
|
+
return this.publishGroupSenderKeyMessage(recipientJid, messageWithSecret, plaintext, type, sendOptions);
|
|
127
128
|
}
|
|
128
129
|
const directRecipientJid = toUserJid(recipientJid);
|
|
129
|
-
return this.publishDirectSignalMessageWithFanout(directRecipientJid,
|
|
130
|
+
return this.publishDirectSignalMessageWithFanout(directRecipientJid, messageWithSecret, plaintext, type, sendOptions);
|
|
130
131
|
}
|
|
131
132
|
async syncSignalSession(jid, reasonIdentity = false) {
|
|
132
133
|
const address = parseSignalAddressFromJid(jid);
|
|
133
134
|
if (address.server === WA_DEFAULTS.GROUP_SERVER) {
|
|
134
135
|
throw new Error('syncSignalSession supports only direct chats');
|
|
135
136
|
}
|
|
136
|
-
await this.
|
|
137
|
+
await this.sessionResolver.ensureSession(address, jid, undefined, reasonIdentity);
|
|
137
138
|
}
|
|
138
139
|
async sendReceipt(input) {
|
|
139
140
|
await this.messageClient.sendReceipt(input);
|
|
140
141
|
}
|
|
141
142
|
async requestAppStateSyncKeys(keyIds) {
|
|
142
|
-
|
|
143
|
-
if (normalizedKeyIds.length === 0) {
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
const peerDeviceJids = await this.resolveOwnPeerDeviceJids();
|
|
147
|
-
if (peerDeviceJids.length === 0) {
|
|
148
|
-
this.logger.warn('app-state sync key request skipped: no peer devices available', {
|
|
149
|
-
keys: normalizedKeyIds.length
|
|
150
|
-
});
|
|
151
|
-
return [];
|
|
152
|
-
}
|
|
153
|
-
const protocolMessage = {
|
|
154
|
-
type: proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_REQUEST,
|
|
155
|
-
appStateSyncKeyRequest: {
|
|
156
|
-
keyIds: normalizedKeyIds.map((keyId) => ({
|
|
157
|
-
keyId
|
|
158
|
-
}))
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
await Promise.all(peerDeviceJids.map((deviceJid) => this.publishProtocolMessageToDevice(deviceJid, protocolMessage)));
|
|
162
|
-
this.logger.info('app-state sync key request sent to peer devices', {
|
|
163
|
-
devices: peerDeviceJids.length,
|
|
164
|
-
keys: normalizedKeyIds.length,
|
|
165
|
-
keyIds: normalizedKeyIds.map((keyId) => bytesToHex(keyId)).join(',')
|
|
166
|
-
});
|
|
167
|
-
return peerDeviceJids;
|
|
143
|
+
return this.appStateSyncKeyProtocol.requestKeys(keyIds);
|
|
168
144
|
}
|
|
169
145
|
async sendAppStateSyncKeyShare(toDeviceJid, keys, missingKeyIds = []) {
|
|
170
|
-
|
|
171
|
-
const dedupedKeysById = new Map();
|
|
172
|
-
for (const key of keys) {
|
|
173
|
-
dedupedKeysById.set(bytesToHex(key.keyId), key);
|
|
174
|
-
}
|
|
175
|
-
const dedupedKeys = [...dedupedKeysById.values()];
|
|
176
|
-
const dedupedMissingKeyIds = this.normalizeKeyIds(missingKeyIds).filter((keyId) => !dedupedKeysById.has(bytesToHex(keyId)));
|
|
177
|
-
const keyShareEntries = [
|
|
178
|
-
...dedupedKeys.map((key) => ({
|
|
179
|
-
keyId: { keyId: key.keyId },
|
|
180
|
-
keyData: {
|
|
181
|
-
keyData: key.keyData,
|
|
182
|
-
timestamp: key.timestamp,
|
|
183
|
-
...(key.fingerprint ? { fingerprint: key.fingerprint } : {})
|
|
184
|
-
}
|
|
185
|
-
})),
|
|
186
|
-
...dedupedMissingKeyIds.map((keyId) => ({
|
|
187
|
-
keyId: { keyId }
|
|
188
|
-
}))
|
|
189
|
-
];
|
|
190
|
-
const protocolMessage = {
|
|
191
|
-
type: proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE,
|
|
192
|
-
appStateSyncKeyShare: {
|
|
193
|
-
keys: keyShareEntries
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
await this.publishProtocolMessageToDevice(normalizedTo, protocolMessage);
|
|
197
|
-
this.logger.info('app-state sync key share sent', {
|
|
198
|
-
to: normalizedTo,
|
|
199
|
-
keys: dedupedKeys.length,
|
|
200
|
-
orphanKeys: dedupedMissingKeyIds.length
|
|
201
|
-
});
|
|
146
|
+
await this.appStateSyncKeyProtocol.sendKeyShare(toDeviceJid, keys, missingKeyIds);
|
|
202
147
|
}
|
|
203
148
|
async mutateParticipantsCacheFromGroupEvent(event) {
|
|
204
|
-
|
|
205
|
-
if (!groupJid) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (event.action === 'delete') {
|
|
209
|
-
await this.participantsStore.deleteGroupParticipants(groupJid);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const participantUsers = this.extractParticipantUsersFromGroupEvent(event);
|
|
213
|
-
if (event.action === 'create') {
|
|
214
|
-
if (participantUsers.length === 0) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
await this.participantsStore.upsertGroupParticipants({
|
|
218
|
-
groupJid,
|
|
219
|
-
participants: participantUsers,
|
|
220
|
-
updatedAtMs: Date.now()
|
|
221
|
-
});
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const cached = await this.participantsStore.getGroupParticipants(groupJid);
|
|
225
|
-
if (!cached || cached.participants.length === 0) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const cachedParticipants = this.sanitizeParticipantUsers(cached.participants);
|
|
229
|
-
if (cachedParticipants.length === 0) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (event.action === 'add' ||
|
|
233
|
-
event.action === 'promote' ||
|
|
234
|
-
event.action === 'demote' ||
|
|
235
|
-
event.action === 'linked_group_promote' ||
|
|
236
|
-
event.action === 'linked_group_demote') {
|
|
237
|
-
await this.mergeParticipantUsersIntoCache(groupJid, cachedParticipants, participantUsers);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (event.action === 'remove') {
|
|
241
|
-
await this.removeParticipantUsersFromCache(groupJid, cachedParticipants, participantUsers);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (event.action === 'modify') {
|
|
245
|
-
const authorUsers = event.authorJid
|
|
246
|
-
? this.sanitizeParticipantUsers([event.authorJid])
|
|
247
|
-
: [];
|
|
248
|
-
await this.replaceParticipantUsersInCache(groupJid, cachedParticipants, authorUsers, participantUsers);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
async publishProtocolMessageToDevice(deviceJid, protocolMessage) {
|
|
252
|
-
const plaintext = await writeRandomPadMax16(proto.Message.encode({
|
|
253
|
-
protocolMessage
|
|
254
|
-
}).finish());
|
|
255
|
-
await this.publishSignalMessage({
|
|
256
|
-
to: deviceJid,
|
|
257
|
-
plaintext,
|
|
258
|
-
type: 'protocol',
|
|
259
|
-
category: 'peer',
|
|
260
|
-
pushPriority: 'high'
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
async resolveOwnPeerDeviceJids() {
|
|
264
|
-
const meJid = this.requireCurrentMeJid('resolveOwnPeerDeviceJids');
|
|
265
|
-
const meUserJid = toUserJid(meJid);
|
|
266
|
-
const meDevices = new Set();
|
|
267
|
-
meDevices.add(normalizeDeviceJid(meJid));
|
|
268
|
-
const meLid = this.getCurrentMeLid();
|
|
269
|
-
if (meLid && meLid.includes('@')) {
|
|
270
|
-
try {
|
|
271
|
-
meDevices.add(normalizeDeviceJid(meLid));
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
this.logger.trace('ignoring malformed me lid jid while resolving peer devices', {
|
|
275
|
-
meLid,
|
|
276
|
-
message: toError(error).message
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
const synced = await this.signalDeviceSync.syncDeviceList([meUserJid]);
|
|
282
|
-
const peerDevices = new Set();
|
|
283
|
-
for (const entry of synced) {
|
|
284
|
-
const sourceDevices = entry.deviceJids.length > 0 ? entry.deviceJids : [entry.jid];
|
|
285
|
-
for (const deviceJid of sourceDevices) {
|
|
286
|
-
try {
|
|
287
|
-
const normalized = normalizeDeviceJid(deviceJid);
|
|
288
|
-
if (meDevices.has(normalized)) {
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
peerDevices.add(normalized);
|
|
292
|
-
}
|
|
293
|
-
catch (error) {
|
|
294
|
-
this.logger.trace('ignoring malformed peer device jid while resolving app-state peers', {
|
|
295
|
-
deviceJid,
|
|
296
|
-
message: toError(error).message
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return [...peerDevices];
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
this.logger.warn('failed to resolve peer devices for app-state key request', {
|
|
305
|
-
message: toError(error).message
|
|
306
|
-
});
|
|
307
|
-
return [];
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
normalizeKeyIds(keyIds) {
|
|
311
|
-
const deduped = new Map();
|
|
312
|
-
for (const keyId of keyIds) {
|
|
313
|
-
if (keyId.byteLength === 0) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const keyHex = bytesToHex(keyId);
|
|
317
|
-
if (deduped.has(keyHex)) {
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
deduped.set(keyHex, keyId);
|
|
321
|
-
}
|
|
322
|
-
return [...deduped.values()];
|
|
323
|
-
}
|
|
324
|
-
async publishWithRetryTracking(args, publish) {
|
|
325
|
-
const nowMs = Date.now();
|
|
326
|
-
const expiresAtMs = nowMs + this.retryTtlMs;
|
|
327
|
-
const hintedMessageId = args.messageIdHint?.trim();
|
|
328
|
-
const resolvedToJid = args.toJid ?? (args.replayPayload.mode === 'opaque_node' ? '' : args.replayPayload.to);
|
|
329
|
-
let hintedPersisted = false;
|
|
330
|
-
if (hintedMessageId) {
|
|
331
|
-
hintedPersisted = await this.safeUpsertRetryOutboundRecord(this.createRetryOutboundRecord({
|
|
332
|
-
messageId: hintedMessageId,
|
|
333
|
-
toJid: resolvedToJid,
|
|
334
|
-
participantJid: args.participantJid,
|
|
335
|
-
recipientJid: args.recipientJid,
|
|
336
|
-
messageType: args.messageType,
|
|
337
|
-
replayPayload: args.replayPayload,
|
|
338
|
-
createdAtMs: nowMs,
|
|
339
|
-
updatedAtMs: nowMs,
|
|
340
|
-
expiresAtMs
|
|
341
|
-
}));
|
|
342
|
-
}
|
|
343
|
-
const result = await publish();
|
|
344
|
-
if (hintedPersisted && hintedMessageId && result.id === hintedMessageId) {
|
|
345
|
-
// Hint and final message id matched; avoid a second equivalent upsert on the hot path.
|
|
346
|
-
return result;
|
|
347
|
-
}
|
|
348
|
-
const persistedNowMs = Date.now();
|
|
349
|
-
await this.safeUpsertRetryOutboundRecord(this.createRetryOutboundRecord({
|
|
350
|
-
messageId: result.id,
|
|
351
|
-
toJid: resolvedToJid,
|
|
352
|
-
participantJid: args.participantJid,
|
|
353
|
-
recipientJid: args.recipientJid,
|
|
354
|
-
messageType: args.messageType,
|
|
355
|
-
replayPayload: args.replayPayload,
|
|
356
|
-
createdAtMs: hintedMessageId ? nowMs : persistedNowMs,
|
|
357
|
-
updatedAtMs: persistedNowMs,
|
|
358
|
-
expiresAtMs: persistedNowMs + this.retryTtlMs
|
|
359
|
-
}));
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
createRetryOutboundRecord(input) {
|
|
363
|
-
return {
|
|
364
|
-
messageId: input.messageId,
|
|
365
|
-
toJid: input.toJid,
|
|
366
|
-
participantJid: input.participantJid,
|
|
367
|
-
recipientJid: input.recipientJid,
|
|
368
|
-
messageType: input.messageType,
|
|
369
|
-
replayMode: input.replayPayload.mode,
|
|
370
|
-
replayPayload: encodeRetryReplayPayload(input.replayPayload),
|
|
371
|
-
state: 'pending',
|
|
372
|
-
createdAtMs: input.createdAtMs,
|
|
373
|
-
updatedAtMs: input.updatedAtMs,
|
|
374
|
-
expiresAtMs: input.expiresAtMs
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
async safeUpsertRetryOutboundRecord(record) {
|
|
378
|
-
try {
|
|
379
|
-
await this.retryStore.upsertOutboundMessage(record);
|
|
380
|
-
}
|
|
381
|
-
catch (error) {
|
|
382
|
-
this.logger.warn('failed to persist retry outbound message record', {
|
|
383
|
-
messageId: record.messageId,
|
|
384
|
-
to: record.toJid,
|
|
385
|
-
mode: record.replayMode,
|
|
386
|
-
message: toError(error).message
|
|
387
|
-
});
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
try {
|
|
391
|
-
await this.retryStore.cleanupExpired(Date.now());
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
this.logger.warn('failed to cleanup retry records after outbound persist', {
|
|
395
|
-
message: toError(error).message
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
return true;
|
|
149
|
+
await this.participantsCache.mutateFromGroupEvent(event);
|
|
399
150
|
}
|
|
400
151
|
shouldUseGroupDirectPath(message) {
|
|
401
152
|
const protocolType = message.protocolMessage?.type;
|
|
@@ -405,41 +156,50 @@ export class WaMessageDispatchCoordinator {
|
|
|
405
156
|
}
|
|
406
157
|
return message.keepInChatMessage?.keepType === proto.KeepType.UNDO_KEEP_FOR_ALL;
|
|
407
158
|
}
|
|
408
|
-
async publishGroupDirectMessage(groupJid, plaintext, type, options, retryContext = {}) {
|
|
159
|
+
async publishGroupDirectMessage(groupJid, message, plaintext, type, options, retryContext = {}) {
|
|
160
|
+
const sendOptions = await this.withResolvedMessageId(options);
|
|
409
161
|
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
410
162
|
const participantUserJids = retryContext.forceRefreshParticipants
|
|
411
|
-
? await this.
|
|
412
|
-
: await this.
|
|
163
|
+
? await this.participantsCache.refreshParticipantUsers(groupJid)
|
|
164
|
+
: await this.participantsCache.resolveParticipantUsers(groupJid);
|
|
413
165
|
const addressingMode = retryContext.forceAddressingMode ??
|
|
414
166
|
this.resolveGroupAddressingMode(participantUserJids, groupJid);
|
|
415
167
|
const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
|
|
416
|
-
const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
168
|
+
const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
417
169
|
if (fanoutDeviceJids.length === 0) {
|
|
418
170
|
throw new Error('group direct send resolved no target devices');
|
|
419
171
|
}
|
|
420
|
-
await this.
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
172
|
+
await this.sessionResolver.ensureSessionsBatch(fanoutDeviceJids);
|
|
173
|
+
const participantAddresses = fanoutDeviceJids.map((targetJid) => parseSignalAddressFromJid(targetJid));
|
|
174
|
+
const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(participantAddresses.map((address) => ({
|
|
175
|
+
address,
|
|
176
|
+
plaintext
|
|
177
|
+
})));
|
|
178
|
+
const participants = fanoutDeviceJids.map((targetJid, index) => ({
|
|
179
|
+
jid: targetJid,
|
|
180
|
+
encType: encryptedParticipants[index].type,
|
|
181
|
+
ciphertext: encryptedParticipants[index].ciphertext
|
|
430
182
|
}));
|
|
431
183
|
const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
|
|
432
184
|
const localPhash = await computePhashV2([...fanoutDeviceJids, senderForPhash]);
|
|
185
|
+
const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
|
|
186
|
+
message,
|
|
187
|
+
stanzaId: sendOptions.id,
|
|
188
|
+
senderUserJid: toUserJid(senderForPhash),
|
|
189
|
+
remoteJid: groupJid,
|
|
190
|
+
context: 'group_direct'
|
|
191
|
+
});
|
|
433
192
|
const messageNode = buildGroupDirectMessageNode({
|
|
434
193
|
to: groupJid,
|
|
435
194
|
type,
|
|
436
|
-
id:
|
|
195
|
+
id: sendOptions.id,
|
|
437
196
|
phash: localPhash,
|
|
438
197
|
addressingMode,
|
|
439
198
|
participants,
|
|
440
199
|
deviceIdentity: shouldAttachDeviceIdentity
|
|
441
200
|
? this.getEncodedSignedDeviceIdentity()
|
|
442
|
-
: undefined
|
|
201
|
+
: undefined,
|
|
202
|
+
reportingNode: reportingArtifacts?.node ?? undefined
|
|
443
203
|
});
|
|
444
204
|
const replayPayload = {
|
|
445
205
|
mode: 'plaintext',
|
|
@@ -447,12 +207,12 @@ export class WaMessageDispatchCoordinator {
|
|
|
447
207
|
type,
|
|
448
208
|
plaintext
|
|
449
209
|
};
|
|
450
|
-
const result = await this.
|
|
451
|
-
messageIdHint:
|
|
210
|
+
const result = await this.retryTracker.track({
|
|
211
|
+
messageIdHint: sendOptions.id ?? messageNode.attrs.id,
|
|
452
212
|
toJid: groupJid,
|
|
453
|
-
|
|
213
|
+
type,
|
|
454
214
|
replayPayload
|
|
455
|
-
}, async () => this.messageClient.publishNode(messageNode,
|
|
215
|
+
}, async () => this.messageClient.publishNode(messageNode, sendOptions));
|
|
456
216
|
const ackError = result.ack.error;
|
|
457
217
|
const serverPhash = result.ack.phash;
|
|
458
218
|
const serverAddressingMode = result.ack.addressingMode;
|
|
@@ -470,8 +230,8 @@ export class WaMessageDispatchCoordinator {
|
|
|
470
230
|
serverAddressingMode,
|
|
471
231
|
ackError
|
|
472
232
|
});
|
|
473
|
-
return this.publishGroupDirectMessage(groupJid, plaintext, type, {
|
|
474
|
-
...
|
|
233
|
+
return this.publishGroupDirectMessage(groupJid, message, plaintext, type, {
|
|
234
|
+
...sendOptions,
|
|
475
235
|
id: result.id
|
|
476
236
|
}, {
|
|
477
237
|
retried: true,
|
|
@@ -481,11 +241,12 @@ export class WaMessageDispatchCoordinator {
|
|
|
481
241
|
}
|
|
482
242
|
return result;
|
|
483
243
|
}
|
|
484
|
-
async publishGroupSenderKeyMessage(groupJid, plaintext, type, options, retryContext = {}) {
|
|
244
|
+
async publishGroupSenderKeyMessage(groupJid, message, plaintext, type, options, retryContext = {}) {
|
|
245
|
+
const sendOptions = await this.withResolvedMessageId(options);
|
|
485
246
|
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
486
247
|
const participantUserJids = retryContext.forceRefreshParticipants
|
|
487
|
-
? await this.
|
|
488
|
-
: await this.
|
|
248
|
+
? await this.participantsCache.refreshParticipantUsers(groupJid)
|
|
249
|
+
: await this.participantsCache.resolveParticipantUsers(groupJid);
|
|
489
250
|
const addressingMode = retryContext.forceAddressingMode ??
|
|
490
251
|
this.resolveGroupAddressingMode(participantUserJids, groupJid);
|
|
491
252
|
const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
|
|
@@ -496,17 +257,25 @@ export class WaMessageDispatchCoordinator {
|
|
|
496
257
|
const { fanoutDeviceJids, distributionParticipants } = distributionData;
|
|
497
258
|
const shouldAttachDeviceIdentity = distributionParticipants.some((participant) => participant.encType === 'pkmsg');
|
|
498
259
|
const localPhash = await computePhashV2([...fanoutDeviceJids, senderJid]);
|
|
260
|
+
const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
|
|
261
|
+
message,
|
|
262
|
+
stanzaId: sendOptions.id,
|
|
263
|
+
senderUserJid: toUserJid(senderJid),
|
|
264
|
+
remoteJid: groupJid,
|
|
265
|
+
context: 'group_sender_key'
|
|
266
|
+
});
|
|
499
267
|
const messageNode = buildGroupSenderKeyMessageNode({
|
|
500
268
|
to: groupJid,
|
|
501
269
|
type,
|
|
502
|
-
id:
|
|
270
|
+
id: sendOptions.id,
|
|
503
271
|
phash: localPhash,
|
|
504
272
|
addressingMode,
|
|
505
273
|
groupCiphertext: groupCiphertext.ciphertext,
|
|
506
274
|
participants: distributionParticipants,
|
|
507
275
|
deviceIdentity: shouldAttachDeviceIdentity
|
|
508
276
|
? this.getEncodedSignedDeviceIdentity()
|
|
509
|
-
: undefined
|
|
277
|
+
: undefined,
|
|
278
|
+
reportingNode: reportingArtifacts?.node ?? undefined
|
|
510
279
|
});
|
|
511
280
|
const replayPayload = {
|
|
512
281
|
mode: 'plaintext',
|
|
@@ -514,12 +283,12 @@ export class WaMessageDispatchCoordinator {
|
|
|
514
283
|
type,
|
|
515
284
|
plaintext
|
|
516
285
|
};
|
|
517
|
-
const result = await this.
|
|
518
|
-
messageIdHint:
|
|
286
|
+
const result = await this.retryTracker.track({
|
|
287
|
+
messageIdHint: sendOptions.id ?? messageNode.attrs.id,
|
|
519
288
|
toJid: groupJid,
|
|
520
|
-
|
|
289
|
+
type,
|
|
521
290
|
replayPayload
|
|
522
|
-
}, async () => this.messageClient.publishNode(messageNode,
|
|
291
|
+
}, async () => this.messageClient.publishNode(messageNode, sendOptions));
|
|
523
292
|
const distributedAddresses = distributionParticipants.map((participant) => participant.address);
|
|
524
293
|
try {
|
|
525
294
|
await this.senderKeyManager.markSenderKeyDistributed(groupJid, sender, distributedAddresses);
|
|
@@ -548,8 +317,8 @@ export class WaMessageDispatchCoordinator {
|
|
|
548
317
|
serverAddressingMode,
|
|
549
318
|
ackError
|
|
550
319
|
});
|
|
551
|
-
return this.publishGroupSenderKeyMessage(groupJid, plaintext, type, {
|
|
552
|
-
...
|
|
320
|
+
return this.publishGroupSenderKeyMessage(groupJid, message, plaintext, type, {
|
|
321
|
+
...sendOptions,
|
|
553
322
|
id: result.id
|
|
554
323
|
}, {
|
|
555
324
|
retried: true,
|
|
@@ -559,137 +328,6 @@ export class WaMessageDispatchCoordinator {
|
|
|
559
328
|
}
|
|
560
329
|
return result;
|
|
561
330
|
}
|
|
562
|
-
async resolveGroupParticipantUsers(groupJid) {
|
|
563
|
-
const cached = await this.participantsStore.getGroupParticipants(groupJid);
|
|
564
|
-
if (cached && cached.participants.length > 0) {
|
|
565
|
-
return this.sanitizeParticipantUsers(cached.participants);
|
|
566
|
-
}
|
|
567
|
-
return this.refreshGroupParticipantUsers(groupJid);
|
|
568
|
-
}
|
|
569
|
-
resolveGroupJidForParticipantCacheEvent(event) {
|
|
570
|
-
if (event.action === 'linked_group_promote' || event.action === 'linked_group_demote') {
|
|
571
|
-
return event.contextGroupJid ?? event.groupJid ?? null;
|
|
572
|
-
}
|
|
573
|
-
return event.groupJid ?? null;
|
|
574
|
-
}
|
|
575
|
-
extractParticipantUsersFromGroupEvent(event) {
|
|
576
|
-
const candidates = [];
|
|
577
|
-
for (const participant of event.participants ?? []) {
|
|
578
|
-
if (participant.jid) {
|
|
579
|
-
candidates.push(participant.jid);
|
|
580
|
-
}
|
|
581
|
-
if (participant.lidJid) {
|
|
582
|
-
candidates.push(participant.lidJid);
|
|
583
|
-
}
|
|
584
|
-
if (participant.phoneJid) {
|
|
585
|
-
candidates.push(participant.phoneJid);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return this.sanitizeParticipantUsers(candidates);
|
|
589
|
-
}
|
|
590
|
-
async mergeParticipantUsersIntoCache(groupJid, cachedParticipants, participantsToAdd) {
|
|
591
|
-
if (participantsToAdd.length === 0) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
const nextParticipants = [...cachedParticipants];
|
|
595
|
-
const existing = new Set(cachedParticipants);
|
|
596
|
-
for (const participant of participantsToAdd) {
|
|
597
|
-
if (existing.has(participant)) {
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
existing.add(participant);
|
|
601
|
-
nextParticipants.push(participant);
|
|
602
|
-
}
|
|
603
|
-
if (nextParticipants.length === cachedParticipants.length) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
await this.participantsStore.upsertGroupParticipants({
|
|
607
|
-
groupJid,
|
|
608
|
-
participants: nextParticipants,
|
|
609
|
-
updatedAtMs: Date.now()
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
async removeParticipantUsersFromCache(groupJid, cachedParticipants, participantsToRemove) {
|
|
613
|
-
if (participantsToRemove.length === 0) {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
const removed = new Set(participantsToRemove);
|
|
617
|
-
const nextParticipants = cachedParticipants.filter((participant) => !removed.has(participant));
|
|
618
|
-
if (nextParticipants.length === cachedParticipants.length) {
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
if (nextParticipants.length === 0) {
|
|
622
|
-
await this.participantsStore.deleteGroupParticipants(groupJid);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
await this.participantsStore.upsertGroupParticipants({
|
|
626
|
-
groupJid,
|
|
627
|
-
participants: nextParticipants,
|
|
628
|
-
updatedAtMs: Date.now()
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
async replaceParticipantUsersInCache(groupJid, cachedParticipants, participantsToReplace, replacementParticipants) {
|
|
632
|
-
const toReplace = new Set(participantsToReplace);
|
|
633
|
-
const nextParticipants = cachedParticipants.filter((participant) => !toReplace.has(participant));
|
|
634
|
-
const existing = new Set(nextParticipants);
|
|
635
|
-
for (const participant of replacementParticipants) {
|
|
636
|
-
if (existing.has(participant)) {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
existing.add(participant);
|
|
640
|
-
nextParticipants.push(participant);
|
|
641
|
-
}
|
|
642
|
-
if (this.areParticipantListsEqual(cachedParticipants, nextParticipants)) {
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
if (nextParticipants.length === 0) {
|
|
646
|
-
await this.participantsStore.deleteGroupParticipants(groupJid);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
await this.participantsStore.upsertGroupParticipants({
|
|
650
|
-
groupJid,
|
|
651
|
-
participants: nextParticipants,
|
|
652
|
-
updatedAtMs: Date.now()
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
areParticipantListsEqual(left, right) {
|
|
656
|
-
if (left.length !== right.length) {
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
660
|
-
if (left[index] !== right[index]) {
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
return true;
|
|
665
|
-
}
|
|
666
|
-
async refreshGroupParticipantUsers(groupJid) {
|
|
667
|
-
const queried = await this.queryGroupParticipantJids(groupJid);
|
|
668
|
-
const participants = this.sanitizeParticipantUsers(queried);
|
|
669
|
-
await this.participantsStore.upsertGroupParticipants({
|
|
670
|
-
groupJid,
|
|
671
|
-
participants,
|
|
672
|
-
updatedAtMs: Date.now()
|
|
673
|
-
});
|
|
674
|
-
return participants;
|
|
675
|
-
}
|
|
676
|
-
sanitizeParticipantUsers(participants) {
|
|
677
|
-
const deduped = new Set();
|
|
678
|
-
for (const participant of participants) {
|
|
679
|
-
if (!participant || !participant.includes('@'))
|
|
680
|
-
continue;
|
|
681
|
-
try {
|
|
682
|
-
deduped.add(toUserJid(participant));
|
|
683
|
-
}
|
|
684
|
-
catch (error) {
|
|
685
|
-
this.logger.trace('ignoring malformed participant jid', {
|
|
686
|
-
participant,
|
|
687
|
-
message: toError(error).message
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return [...deduped];
|
|
692
|
-
}
|
|
693
331
|
resolveGroupAddressingMode(participantUserJids, groupJid) {
|
|
694
332
|
for (const participantJid of participantUserJids) {
|
|
695
333
|
try {
|
|
@@ -728,111 +366,95 @@ export class WaMessageDispatchCoordinator {
|
|
|
728
366
|
const distributionPayload = await writeRandomPadMax16(proto.Message.encode({
|
|
729
367
|
senderKeyDistributionMessage
|
|
730
368
|
}).finish());
|
|
731
|
-
const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
369
|
+
const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
732
370
|
if (fanoutDeviceJids.length === 0) {
|
|
733
371
|
return {
|
|
734
372
|
fanoutDeviceJids,
|
|
735
373
|
distributionParticipants: []
|
|
736
374
|
};
|
|
737
375
|
}
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
376
|
+
const fanoutTargetsByAddressKey = new Map();
|
|
377
|
+
const fanoutAddresses = new Array(fanoutDeviceJids.length);
|
|
378
|
+
for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
|
|
379
|
+
const jid = fanoutDeviceJids[index];
|
|
380
|
+
const address = parseSignalAddressFromJid(jid);
|
|
381
|
+
fanoutAddresses[index] = address;
|
|
382
|
+
fanoutTargetsByAddressKey.set(signalAddressKey(address), { jid, address });
|
|
383
|
+
}
|
|
384
|
+
const pendingAddresses = await this.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, sender, fanoutAddresses);
|
|
743
385
|
if (pendingAddresses.length === 0) {
|
|
744
386
|
return {
|
|
745
387
|
fanoutDeviceJids,
|
|
746
388
|
distributionParticipants: []
|
|
747
389
|
};
|
|
748
390
|
}
|
|
749
|
-
const pendingAddressKeys = new Set(
|
|
750
|
-
const pendingTargets =
|
|
391
|
+
const pendingAddressKeys = new Set();
|
|
392
|
+
const pendingTargets = [];
|
|
393
|
+
for (let index = 0; index < pendingAddresses.length; index += 1) {
|
|
394
|
+
const key = signalAddressKey(pendingAddresses[index]);
|
|
395
|
+
if (pendingAddressKeys.has(key)) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
pendingAddressKeys.add(key);
|
|
399
|
+
const target = fanoutTargetsByAddressKey.get(key);
|
|
400
|
+
if (target) {
|
|
401
|
+
pendingTargets.push(target);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
751
404
|
if (pendingTargets.length === 0) {
|
|
752
405
|
return {
|
|
753
406
|
fanoutDeviceJids,
|
|
754
407
|
distributionParticipants: []
|
|
755
408
|
};
|
|
756
409
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const encrypted = await this.signalProtocol.encryptMessage(target.address, distributionPayload);
|
|
761
|
-
return {
|
|
762
|
-
jid: target.jid,
|
|
763
|
-
address: target.address,
|
|
764
|
-
encType: encrypted.type,
|
|
765
|
-
ciphertext: encrypted.ciphertext
|
|
766
|
-
};
|
|
767
|
-
}));
|
|
768
|
-
return {
|
|
769
|
-
fanoutDeviceJids,
|
|
770
|
-
distributionParticipants
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
async resolveGroupParticipantDeviceJids(participantUserJids) {
|
|
774
|
-
const meDeviceJids = new Set();
|
|
775
|
-
const meJid = this.getCurrentMeJid();
|
|
776
|
-
if (meJid) {
|
|
777
|
-
try {
|
|
778
|
-
meDeviceJids.add(normalizeDeviceJid(meJid));
|
|
779
|
-
}
|
|
780
|
-
catch (error) {
|
|
781
|
-
this.logger.trace('ignoring malformed me jid', {
|
|
782
|
-
meJid,
|
|
783
|
-
message: toError(error).message
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
const meLid = this.getCurrentMeLid();
|
|
788
|
-
if (meLid && meLid.includes('@')) {
|
|
789
|
-
try {
|
|
790
|
-
meDeviceJids.add(normalizeDeviceJid(meLid));
|
|
791
|
-
}
|
|
792
|
-
catch (error) {
|
|
793
|
-
this.logger.trace('ignoring malformed me lid jid', {
|
|
794
|
-
meLid,
|
|
795
|
-
message: toError(error).message
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
const candidateUsers = [...new Set(participantUserJids)];
|
|
800
|
-
if (candidateUsers.length === 0) {
|
|
801
|
-
return [];
|
|
410
|
+
const pendingTargetJids = new Array(pendingTargets.length);
|
|
411
|
+
for (let index = 0; index < pendingTargets.length; index += 1) {
|
|
412
|
+
pendingTargetJids[index] = pendingTargets[index].jid;
|
|
802
413
|
}
|
|
803
414
|
try {
|
|
804
|
-
|
|
805
|
-
const fanout = new Set();
|
|
806
|
-
for (const entry of synced) {
|
|
807
|
-
if (entry.deviceJids.length === 0) {
|
|
808
|
-
const normalizedEntryJid = normalizeDeviceJid(entry.jid);
|
|
809
|
-
if (meDeviceJids.has(normalizedEntryJid))
|
|
810
|
-
continue;
|
|
811
|
-
fanout.add(normalizedEntryJid);
|
|
812
|
-
continue;
|
|
813
|
-
}
|
|
814
|
-
for (const deviceJid of entry.deviceJids) {
|
|
815
|
-
const normalizedDeviceJid = normalizeDeviceJid(deviceJid);
|
|
816
|
-
if (meDeviceJids.has(normalizedDeviceJid))
|
|
817
|
-
continue;
|
|
818
|
-
fanout.add(normalizedDeviceJid);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return [...fanout];
|
|
415
|
+
await this.sessionResolver.ensureSessionsBatch(pendingTargetJids);
|
|
822
416
|
}
|
|
823
417
|
catch (error) {
|
|
824
|
-
this.logger.warn('group
|
|
825
|
-
|
|
418
|
+
this.logger.warn('group sender-key distribution session sync failed, continuing with available sessions', {
|
|
419
|
+
groupJid,
|
|
420
|
+
requested: pendingTargetJids.length,
|
|
826
421
|
message: toError(error).message
|
|
827
422
|
});
|
|
828
|
-
return [...new Set(candidateUsers.map((jid) => normalizeDeviceJid(jid)))].filter((jid) => !meDeviceJids.has(jid));
|
|
829
423
|
}
|
|
424
|
+
const hasPendingSessions = await this.signalProtocol.hasSessions(pendingTargets.map((target) => target.address));
|
|
425
|
+
const availableTargets = pendingTargets.filter((_target, index) => hasPendingSessions[index]);
|
|
426
|
+
if (availableTargets.length === 0) {
|
|
427
|
+
return {
|
|
428
|
+
fanoutDeviceJids,
|
|
429
|
+
distributionParticipants: []
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const encryptedDistributionParticipants = await this.signalProtocol.encryptMessagesBatch(availableTargets.map((target) => ({
|
|
433
|
+
address: target.address,
|
|
434
|
+
plaintext: distributionPayload
|
|
435
|
+
})));
|
|
436
|
+
const distributionParticipants = availableTargets.map((target, index) => ({
|
|
437
|
+
jid: target.jid,
|
|
438
|
+
address: target.address,
|
|
439
|
+
encType: encryptedDistributionParticipants[index].type,
|
|
440
|
+
ciphertext: encryptedDistributionParticipants[index].ciphertext
|
|
441
|
+
}));
|
|
442
|
+
return {
|
|
443
|
+
fanoutDeviceJids,
|
|
444
|
+
distributionParticipants
|
|
445
|
+
};
|
|
830
446
|
}
|
|
831
447
|
async publishDirectSignalMessageWithFanout(recipientJid, message, plaintext, type, options) {
|
|
448
|
+
const sendOptions = await this.withResolvedMessageId(options);
|
|
832
449
|
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
833
450
|
const meLid = this.getCurrentMeLid();
|
|
834
|
-
const selfDeviceJidForRecipient = this.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
|
|
835
|
-
const deviceJids = await this.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
|
|
451
|
+
const selfDeviceJidForRecipient = this.fanoutResolver.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
|
|
452
|
+
const deviceJids = await this.fanoutResolver.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
|
|
453
|
+
const targets = deviceJids.map((jid) => ({
|
|
454
|
+
jid,
|
|
455
|
+
normalizedJid: normalizeDeviceJid(jid),
|
|
456
|
+
userJid: toUserJid(jid)
|
|
457
|
+
}));
|
|
836
458
|
const recipientUserJid = toUserJid(recipientJid);
|
|
837
459
|
const meUserJid = toUserJid(selfDeviceJidForRecipient);
|
|
838
460
|
this.logger.debug('wa client publish signal fanout', {
|
|
@@ -841,44 +463,55 @@ export class WaMessageDispatchCoordinator {
|
|
|
841
463
|
type
|
|
842
464
|
});
|
|
843
465
|
const expectedIdentityByJid = new Map();
|
|
844
|
-
if (
|
|
845
|
-
for (let index = 0; index <
|
|
846
|
-
const
|
|
847
|
-
if (
|
|
848
|
-
expectedIdentityByJid.set(
|
|
466
|
+
if (sendOptions.expectedIdentity) {
|
|
467
|
+
for (let index = 0; index < targets.length; index += 1) {
|
|
468
|
+
const target = targets[index];
|
|
469
|
+
if (target.userJid === recipientUserJid) {
|
|
470
|
+
expectedIdentityByJid.set(target.normalizedJid, sendOptions.expectedIdentity);
|
|
849
471
|
}
|
|
850
472
|
}
|
|
851
473
|
}
|
|
852
|
-
await this.
|
|
853
|
-
const hasSelfDeviceFanout =
|
|
474
|
+
await this.sessionResolver.ensureSessionsBatch(deviceJids, expectedIdentityByJid);
|
|
475
|
+
const hasSelfDeviceFanout = targets.some((target) => target.userJid === meUserJid);
|
|
854
476
|
const selfDevicePlaintext = hasSelfDeviceFanout
|
|
855
477
|
? await writeRandomPadMax16(proto.Message.encode(wrapDeviceSentMessage(message, recipientUserJid)).finish())
|
|
856
478
|
: null;
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
479
|
+
const participantRequests = targets.map((target) => ({
|
|
480
|
+
target,
|
|
481
|
+
address: parseSignalAddressFromJid(target.jid),
|
|
482
|
+
expectedIdentity: target.userJid === recipientUserJid ? sendOptions.expectedIdentity : undefined,
|
|
483
|
+
plaintext: selfDevicePlaintext && target.userJid === meUserJid
|
|
862
484
|
? selfDevicePlaintext
|
|
863
|
-
: plaintext
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
485
|
+
: plaintext
|
|
486
|
+
}));
|
|
487
|
+
const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(participantRequests.map((request) => ({
|
|
488
|
+
address: request.address,
|
|
489
|
+
plaintext: request.plaintext,
|
|
490
|
+
expectedIdentity: request.expectedIdentity
|
|
491
|
+
})));
|
|
492
|
+
const participants = participantRequests.map((request, index) => ({
|
|
493
|
+
jid: request.target.jid,
|
|
494
|
+
encType: encryptedParticipants[index].type,
|
|
495
|
+
ciphertext: encryptedParticipants[index].ciphertext
|
|
871
496
|
}));
|
|
872
497
|
const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
|
|
873
498
|
const deviceIdentity = shouldAttachDeviceIdentity
|
|
874
499
|
? this.getEncodedSignedDeviceIdentity()
|
|
875
500
|
: undefined;
|
|
501
|
+
const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
|
|
502
|
+
message,
|
|
503
|
+
stanzaId: sendOptions.id,
|
|
504
|
+
senderUserJid: meUserJid,
|
|
505
|
+
remoteJid: recipientUserJid,
|
|
506
|
+
context: 'direct_fanout'
|
|
507
|
+
});
|
|
876
508
|
const messageNode = buildDirectMessageFanoutNode({
|
|
877
509
|
to: recipientJid,
|
|
878
510
|
type,
|
|
879
|
-
id:
|
|
511
|
+
id: sendOptions.id,
|
|
880
512
|
participants,
|
|
881
|
-
deviceIdentity
|
|
513
|
+
deviceIdentity,
|
|
514
|
+
reportingNode: reportingArtifacts?.node ?? undefined
|
|
882
515
|
});
|
|
883
516
|
const replayPayload = {
|
|
884
517
|
mode: 'plaintext',
|
|
@@ -886,130 +519,72 @@ export class WaMessageDispatchCoordinator {
|
|
|
886
519
|
type,
|
|
887
520
|
plaintext
|
|
888
521
|
};
|
|
889
|
-
return this.
|
|
890
|
-
messageIdHint:
|
|
522
|
+
return this.retryTracker.track({
|
|
523
|
+
messageIdHint: sendOptions.id ?? messageNode.attrs.id,
|
|
891
524
|
toJid: recipientJid,
|
|
892
|
-
|
|
525
|
+
type,
|
|
893
526
|
replayPayload
|
|
894
|
-
}, async () => this.messageClient.publishNode(messageNode,
|
|
527
|
+
}, async () => this.messageClient.publishNode(messageNode, sendOptions));
|
|
895
528
|
}
|
|
896
|
-
async
|
|
897
|
-
const
|
|
898
|
-
if (
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const missingTargets = normalizedTargets.filter((_, index) => !hasSessions[index]);
|
|
907
|
-
if (missingTargets.length === 0) {
|
|
908
|
-
return;
|
|
529
|
+
async withResolvedMessageId(options) {
|
|
530
|
+
const normalizedId = options.id?.trim();
|
|
531
|
+
if (normalizedId) {
|
|
532
|
+
if (normalizedId === options.id) {
|
|
533
|
+
return options;
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
...options,
|
|
537
|
+
id: normalizedId
|
|
538
|
+
};
|
|
909
539
|
}
|
|
540
|
+
return {
|
|
541
|
+
...options,
|
|
542
|
+
id: await this.generateOutgoingMessageId()
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
async generateOutgoingMessageId() {
|
|
910
546
|
try {
|
|
911
|
-
const
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const expectedIdentity = expectedIdentityByJid.get(target.jid);
|
|
923
|
-
const remoteIdentity = toSerializedPubKey(result.bundle.identity);
|
|
924
|
-
if (expectedIdentity &&
|
|
925
|
-
!uint8Equal(remoteIdentity, toSerializedPubKey(expectedIdentity))) {
|
|
926
|
-
throw new Error('identity mismatch');
|
|
927
|
-
}
|
|
928
|
-
establishPromises.push(this.signalProtocol
|
|
929
|
-
.establishOutgoingSession(target.address, result.bundle)
|
|
930
|
-
.then(() => {
|
|
931
|
-
this.logger.debug('signal session synchronized from batch key fetch', {
|
|
932
|
-
jid: target.jid,
|
|
933
|
-
regId: result.bundle.regId,
|
|
934
|
-
hasOneTimeKey: result.bundle.oneTimeKey !== undefined
|
|
935
|
-
});
|
|
936
|
-
}));
|
|
937
|
-
}
|
|
938
|
-
await Promise.all(establishPromises);
|
|
939
|
-
if (fallbackJids.length === 0) {
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
this.logger.warn('signal batch key fetch returned partial errors, falling back to single requests', {
|
|
943
|
-
requested: missingTargets.length,
|
|
944
|
-
fallbackTargets: fallbackJids.length
|
|
945
|
-
});
|
|
946
|
-
for (let index = 0; index < fallbackJids.length; index += 1) {
|
|
947
|
-
const jid = fallbackJids[index];
|
|
948
|
-
const address = parseSignalAddressFromJid(jid);
|
|
949
|
-
await this.ensureSignalSession(address, jid, expectedIdentityByJid.get(jid));
|
|
950
|
-
}
|
|
547
|
+
const meUserJid = toUserJid(this.requireCurrentMeJid('sendMessage'));
|
|
548
|
+
const timestampSeconds = Math.floor(Date.now() / 1000);
|
|
549
|
+
const timestampBytes = new Uint8Array(8);
|
|
550
|
+
new DataView(timestampBytes.buffer, timestampBytes.byteOffset, timestampBytes.byteLength).setBigUint64(0, BigInt(timestampSeconds), false);
|
|
551
|
+
const entropy = concatBytes([
|
|
552
|
+
timestampBytes,
|
|
553
|
+
TEXT_ENCODER.encode(meUserJid),
|
|
554
|
+
await randomBytesAsync(8)
|
|
555
|
+
]);
|
|
556
|
+
const digest = await sha256(entropy);
|
|
557
|
+
return `3EB0${bytesToHex(digest.subarray(0, 9)).toUpperCase()}`;
|
|
951
558
|
}
|
|
952
559
|
catch (error) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
throw normalized;
|
|
956
|
-
}
|
|
957
|
-
this.logger.warn('signal batch key fetch failed, falling back to single requests', {
|
|
958
|
-
requested: missingTargets.length,
|
|
959
|
-
message: normalized.message
|
|
560
|
+
this.logger.warn('failed to generate sha256 message id, falling back to random id', {
|
|
561
|
+
message: toError(error).message
|
|
960
562
|
});
|
|
961
|
-
|
|
962
|
-
const target = missingTargets[index];
|
|
963
|
-
await this.ensureSignalSession(target.address, target.jid, expectedIdentityByJid.get(target.jid));
|
|
964
|
-
}
|
|
563
|
+
return `3EB0${bytesToHex(await randomBytesAsync(8)).toUpperCase()}`;
|
|
965
564
|
}
|
|
966
565
|
}
|
|
967
|
-
async
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
566
|
+
async tryBuildReportingTokenArtifacts(input) {
|
|
567
|
+
if (!input.stanzaId) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
971
570
|
try {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
for (let index = 0; index < recipientDevices.length; index += 1) {
|
|
981
|
-
fanout.add(recipientDevices[index]);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
const meDevices = byUser.get(meUserJid) ?? [];
|
|
985
|
-
const normalizedMeJid = normalizeDeviceJid(selfDeviceJidForRecipient);
|
|
986
|
-
for (let index = 0; index < meDevices.length; index += 1) {
|
|
987
|
-
const deviceJid = meDevices[index];
|
|
988
|
-
if (normalizeDeviceJid(deviceJid) === normalizedMeJid) {
|
|
989
|
-
continue;
|
|
990
|
-
}
|
|
991
|
-
fanout.add(deviceJid);
|
|
992
|
-
}
|
|
993
|
-
return [...fanout];
|
|
571
|
+
return await buildReportingTokenArtifacts({
|
|
572
|
+
message: input.message,
|
|
573
|
+
stanzaId: input.stanzaId,
|
|
574
|
+
senderUserJid: input.senderUserJid,
|
|
575
|
+
remoteJid: input.remoteJid
|
|
576
|
+
});
|
|
994
577
|
}
|
|
995
578
|
catch (error) {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
579
|
+
this.logger.warn('failed to generate reporting token', {
|
|
580
|
+
context: input.context,
|
|
581
|
+
id: input.stanzaId,
|
|
582
|
+
remoteJid: input.remoteJid,
|
|
583
|
+
message: toError(error).message
|
|
1000
584
|
});
|
|
1001
|
-
return
|
|
585
|
+
return null;
|
|
1002
586
|
}
|
|
1003
587
|
}
|
|
1004
|
-
resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid) {
|
|
1005
|
-
if (splitJid(recipientJid).server !== 'lid') {
|
|
1006
|
-
return meJid;
|
|
1007
|
-
}
|
|
1008
|
-
if (!meLid || !meLid.includes('@')) {
|
|
1009
|
-
return meJid;
|
|
1010
|
-
}
|
|
1011
|
-
return meLid;
|
|
1012
|
-
}
|
|
1013
588
|
getEncodedSignedDeviceIdentity() {
|
|
1014
589
|
const signedIdentity = this.getCurrentSignedIdentity();
|
|
1015
590
|
if (!signedIdentity) {
|
|
@@ -1017,36 +592,6 @@ export class WaMessageDispatchCoordinator {
|
|
|
1017
592
|
}
|
|
1018
593
|
return proto.ADVSignedDeviceIdentity.encode(signedIdentity).finish();
|
|
1019
594
|
}
|
|
1020
|
-
async ensureSignalSession(address, jid, expectedIdentity, reasonIdentity = false) {
|
|
1021
|
-
this.requireCurrentMeJid('ensureSignalSession');
|
|
1022
|
-
if (reasonIdentity) {
|
|
1023
|
-
await this.signalIdentitySync.syncIdentityKeys([jid]);
|
|
1024
|
-
}
|
|
1025
|
-
if (await this.signalProtocol.hasSession(address)) {
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
this.logger.info('signal session missing, fetching remote key bundle', { jid });
|
|
1029
|
-
const fetched = await this.signalSessionSync.fetchKeyBundle({
|
|
1030
|
-
jid,
|
|
1031
|
-
reasonIdentity
|
|
1032
|
-
});
|
|
1033
|
-
const remoteIdentity = toSerializedPubKey(fetched.bundle.identity);
|
|
1034
|
-
if (reasonIdentity) {
|
|
1035
|
-
const storedIdentity = await this.signalStore.getRemoteIdentity(address);
|
|
1036
|
-
if (storedIdentity && !uint8Equal(remoteIdentity, storedIdentity)) {
|
|
1037
|
-
throw new Error('identity mismatch');
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
if (expectedIdentity && !uint8Equal(remoteIdentity, toSerializedPubKey(expectedIdentity))) {
|
|
1041
|
-
throw new Error('identity mismatch');
|
|
1042
|
-
}
|
|
1043
|
-
await this.signalProtocol.establishOutgoingSession(address, fetched.bundle);
|
|
1044
|
-
this.logger.info('signal session synchronized', {
|
|
1045
|
-
jid,
|
|
1046
|
-
regId: fetched.bundle.regId,
|
|
1047
|
-
hasOneTimeKey: fetched.bundle.oneTimeKey !== undefined
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
595
|
requireCurrentMeJid(context) {
|
|
1051
596
|
const meJid = this.getCurrentMeJid();
|
|
1052
597
|
if (meJid) {
|