zapo-js 0.1.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/README.md +235 -0
- package/dist/appstate/WaAppStateCrypto.js +202 -0
- package/dist/appstate/WaAppStateSyncClient.js +808 -0
- package/dist/appstate/WaAppStateSyncResponseParser.js +71 -0
- package/dist/appstate/constants.js +23 -0
- package/dist/appstate/index.js +28 -0
- package/dist/appstate/store/sqlite.js +55 -0
- package/dist/appstate/types.js +2 -0
- package/dist/appstate/utils.js +84 -0
- package/dist/auth/WaAuthClient.js +266 -0
- package/dist/auth/flow/WaAuthCredentialsFlow.js +123 -0
- package/dist/auth/index.js +27 -0
- package/dist/auth/pairing/WaPairingCodeCrypto.js +75 -0
- package/dist/auth/pairing/WaPairingFlow.js +328 -0
- package/dist/auth/pairing/WaQrFlow.js +86 -0
- package/dist/auth/pairing/constants.js +5 -0
- package/dist/auth/types.js +2 -0
- package/dist/client/WaClient.js +749 -0
- package/dist/client/WaClientFactory.js +381 -0
- package/dist/client/coordinators/WaGroupCoordinator.js +191 -0
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +315 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +1061 -0
- package/dist/client/coordinators/WaPassiveTasksCoordinator.js +200 -0
- package/dist/client/coordinators/WaRetryCoordinator.js +494 -0
- package/dist/client/coordinators/WaStreamControlCoordinator.js +123 -0
- package/dist/client/dirty.js +254 -0
- package/dist/client/events/chat.js +226 -0
- package/dist/client/events/group.js +410 -0
- package/dist/client/history-sync.js +122 -0
- package/dist/client/incoming.js +236 -0
- package/dist/client/index.js +5 -0
- package/dist/client/mailbox.js +49 -0
- package/dist/client/messages.js +152 -0
- package/dist/client/types.js +2 -0
- package/dist/crypto/core/constants.js +4 -0
- package/dist/crypto/core/encoding.js +29 -0
- package/dist/crypto/core/hkdf.js +26 -0
- package/dist/crypto/core/index.js +43 -0
- package/dist/crypto/core/keys.js +73 -0
- package/dist/crypto/core/nonce.js +18 -0
- package/dist/crypto/core/primitives.js +121 -0
- package/dist/crypto/core/random.js +32 -0
- package/dist/crypto/curves/Ed25519.js +42 -0
- package/dist/crypto/curves/X25519.js +64 -0
- package/dist/crypto/curves/constants.js +6 -0
- package/dist/crypto/curves/types.js +9 -0
- package/dist/crypto/index.js +22 -0
- package/dist/crypto/math/constants.js +44 -0
- package/dist/crypto/math/edwards.js +64 -0
- package/dist/crypto/math/le.js +20 -0
- package/dist/crypto/math/mod.js +38 -0
- package/dist/crypto/math/types.js +2 -0
- package/dist/esm/appstate/WaAppStateCrypto.js +198 -0
- package/dist/esm/appstate/WaAppStateSyncClient.js +803 -0
- package/dist/esm/appstate/WaAppStateSyncResponseParser.js +67 -0
- package/dist/esm/appstate/constants.js +20 -0
- package/dist/esm/appstate/index.js +6 -0
- package/dist/esm/appstate/store/sqlite.js +49 -0
- package/dist/esm/appstate/types.js +1 -0
- package/dist/esm/appstate/utils.js +75 -0
- package/dist/esm/auth/WaAuthClient.js +262 -0
- package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +118 -0
- package/dist/esm/auth/index.js +5 -0
- package/dist/esm/auth/pairing/WaPairingCodeCrypto.js +71 -0
- package/dist/esm/auth/pairing/WaPairingFlow.js +324 -0
- package/dist/esm/auth/pairing/WaQrFlow.js +82 -0
- package/dist/esm/auth/pairing/constants.js +2 -0
- package/dist/esm/auth/types.js +1 -0
- package/dist/esm/client/WaClient.js +745 -0
- package/dist/esm/client/WaClientFactory.js +377 -0
- package/dist/esm/client/coordinators/WaGroupCoordinator.js +188 -0
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +311 -0
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +1057 -0
- package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +196 -0
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +490 -0
- package/dist/esm/client/coordinators/WaStreamControlCoordinator.js +120 -0
- package/dist/esm/client/dirty.js +250 -0
- package/dist/esm/client/events/chat.js +223 -0
- package/dist/esm/client/events/group.js +407 -0
- package/dist/esm/client/history-sync.js +119 -0
- package/dist/esm/client/incoming.js +227 -0
- package/dist/esm/client/index.js +1 -0
- package/dist/esm/client/mailbox.js +46 -0
- package/dist/esm/client/messages.js +148 -0
- package/dist/esm/client/types.js +1 -0
- package/dist/esm/crypto/core/constants.js +1 -0
- package/dist/esm/crypto/core/encoding.js +25 -0
- package/dist/esm/crypto/core/hkdf.js +22 -0
- package/dist/esm/crypto/core/index.js +11 -0
- package/dist/esm/crypto/core/keys.js +66 -0
- package/dist/esm/crypto/core/nonce.js +15 -0
- package/dist/esm/crypto/core/primitives.js +102 -0
- package/dist/esm/crypto/core/random.js +28 -0
- package/dist/esm/crypto/curves/Ed25519.js +38 -0
- package/dist/esm/crypto/curves/X25519.js +58 -0
- package/dist/esm/crypto/curves/constants.js +3 -0
- package/dist/esm/crypto/curves/types.js +6 -0
- package/dist/esm/crypto/index.js +3 -0
- package/dist/esm/crypto/math/constants.js +41 -0
- package/dist/esm/crypto/math/edwards.js +60 -0
- package/dist/esm/crypto/math/le.js +16 -0
- package/dist/esm/crypto/math/mod.js +31 -0
- package/dist/esm/crypto/math/types.js +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/infra/log/ConsoleLogger.js +40 -0
- package/dist/esm/infra/log/PinoLogger.js +73 -0
- package/dist/esm/infra/log/types.js +1 -0
- package/dist/esm/infra/perf/BoundedTaskQueue.js +62 -0
- package/dist/esm/media/WaMediaCrypto.js +224 -0
- package/dist/esm/media/WaMediaTransferClient.js +361 -0
- package/dist/esm/media/conn.js +33 -0
- package/dist/esm/media/constants.js +18 -0
- package/dist/esm/media/index.js +3 -0
- package/dist/esm/media/types.js +1 -0
- package/dist/esm/message/WaMessageClient.js +210 -0
- package/dist/esm/message/ack.js +46 -0
- package/dist/esm/message/content.js +20 -0
- package/dist/esm/message/device-sent.js +49 -0
- package/dist/esm/message/incoming.js +318 -0
- package/dist/esm/message/index.js +2 -0
- package/dist/esm/message/padding.js +20 -0
- package/dist/esm/message/phash.js +25 -0
- package/dist/esm/message/types.js +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proto.js +3 -0
- package/dist/esm/protocol/appstate.js +34 -0
- package/dist/esm/protocol/auth.js +12 -0
- package/dist/esm/protocol/browser.js +41 -0
- package/dist/esm/protocol/constants.js +11 -0
- package/dist/esm/protocol/defaults.js +27 -0
- package/dist/esm/protocol/dirty.js +26 -0
- package/dist/esm/protocol/group.js +5 -0
- package/dist/esm/protocol/index.js +11 -0
- package/dist/esm/protocol/jid.js +94 -0
- package/dist/esm/protocol/media.js +20 -0
- package/dist/esm/protocol/message.js +16 -0
- package/dist/esm/protocol/nodes.js +83 -0
- package/dist/esm/protocol/notification.js +50 -0
- package/dist/esm/protocol/stream.js +60 -0
- package/dist/esm/retry/constants.js +20 -0
- package/dist/esm/retry/index.js +5 -0
- package/dist/esm/retry/outbound.js +83 -0
- package/dist/esm/retry/parse.js +130 -0
- package/dist/esm/retry/reason.js +50 -0
- package/dist/esm/retry/replay.js +177 -0
- package/dist/esm/retry/types.js +1 -0
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +185 -0
- package/dist/esm/signal/api/SignalDigestSyncApi.js +179 -0
- package/dist/esm/signal/api/SignalIdentitySyncApi.js +111 -0
- package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +141 -0
- package/dist/esm/signal/api/SignalRotateKeyApi.js +59 -0
- package/dist/esm/signal/api/SignalSessionSyncApi.js +187 -0
- package/dist/esm/signal/api/codec.js +23 -0
- package/dist/esm/signal/api/constants.js +9 -0
- package/dist/esm/signal/api/prekeys.js +9 -0
- package/dist/esm/signal/constants.js +16 -0
- package/dist/esm/signal/crypto/WaAdvSignature.js +60 -0
- package/dist/esm/signal/crypto/constants.js +8 -0
- package/dist/esm/signal/group/SenderKeyChain.js +97 -0
- package/dist/esm/signal/group/SenderKeyCodec.js +46 -0
- package/dist/esm/signal/group/SenderKeyManager.js +176 -0
- package/dist/esm/signal/index.js +11 -0
- package/dist/esm/signal/registration/keygen.js +31 -0
- package/dist/esm/signal/registration/utils.js +16 -0
- package/dist/esm/signal/session/SignalProtocol.js +122 -0
- package/dist/esm/signal/session/SignalRatchet.js +260 -0
- package/dist/esm/signal/session/SignalSerializer.js +63 -0
- package/dist/esm/signal/session/SignalSession.js +153 -0
- package/dist/esm/signal/store/sqlite.js +310 -0
- package/dist/esm/signal/types.js +1 -0
- package/dist/esm/store/contracts/appstate.store.js +1 -0
- package/dist/esm/store/contracts/auth.store.js +1 -0
- package/dist/esm/store/contracts/contact.store.js +1 -0
- package/dist/esm/store/contracts/device-list.store.js +1 -0
- package/dist/esm/store/contracts/message.store.js +1 -0
- package/dist/esm/store/contracts/participants.store.js +1 -0
- package/dist/esm/store/contracts/retry.store.js +1 -0
- package/dist/esm/store/contracts/sender-key.store.js +1 -0
- package/dist/esm/store/contracts/signal.store.js +1 -0
- package/dist/esm/store/contracts/thread.store.js +1 -0
- package/dist/esm/store/createStore.js +278 -0
- package/dist/esm/store/index.js +20 -0
- package/dist/esm/store/noop.store.js +43 -0
- package/dist/esm/store/providers/memory/appstate.store.js +101 -0
- package/dist/esm/store/providers/memory/contact.store.js +23 -0
- package/dist/esm/store/providers/memory/device-list.store.js +86 -0
- package/dist/esm/store/providers/memory/message.store.js +40 -0
- package/dist/esm/store/providers/memory/participants.store.js +61 -0
- package/dist/esm/store/providers/memory/retry.store.js +71 -0
- package/dist/esm/store/providers/memory/sender-key.store.js +88 -0
- package/dist/esm/store/providers/memory/signal.store.js +170 -0
- package/dist/esm/store/providers/memory/thread.store.js +34 -0
- package/dist/esm/store/providers/sqlite/BaseSqliteStore.js +37 -0
- package/dist/esm/store/providers/sqlite/appstate.store.js +169 -0
- package/dist/esm/store/providers/sqlite/auth.store.js +176 -0
- package/dist/esm/store/providers/sqlite/connection.js +240 -0
- package/dist/esm/store/providers/sqlite/contact.store.js +61 -0
- package/dist/esm/store/providers/sqlite/device-list.store.js +155 -0
- package/dist/esm/store/providers/sqlite/message.store.js +119 -0
- package/dist/esm/store/providers/sqlite/migrations.js +347 -0
- package/dist/esm/store/providers/sqlite/participants.store.js +85 -0
- package/dist/esm/store/providers/sqlite/retry.store.js +144 -0
- package/dist/esm/store/providers/sqlite/sender-key.store.js +203 -0
- package/dist/esm/store/providers/sqlite/signal.store.js +353 -0
- package/dist/esm/store/providers/sqlite/thread.store.js +72 -0
- package/dist/esm/store/types.js +1 -0
- package/dist/esm/transport/WaComms.js +527 -0
- package/dist/esm/transport/WaWebSocket.js +361 -0
- package/dist/esm/transport/binary/constants.js +96 -0
- package/dist/esm/transport/binary/decoder.js +275 -0
- package/dist/esm/transport/binary/encoder.js +210 -0
- package/dist/esm/transport/binary/index.js +4 -0
- package/dist/esm/transport/binary/tokens.js +1280 -0
- package/dist/esm/transport/index.js +6 -0
- package/dist/esm/transport/keepalive/WaKeepAlive.js +141 -0
- package/dist/esm/transport/node/WaNodeOrchestrator.js +143 -0
- package/dist/esm/transport/node/WaNodeTransport.js +64 -0
- package/dist/esm/transport/node/builders/accountSync.js +101 -0
- package/dist/esm/transport/node/builders/group.js +47 -0
- package/dist/esm/transport/node/builders/index.js +7 -0
- package/dist/esm/transport/node/builders/media.js +10 -0
- package/dist/esm/transport/node/builders/message.js +317 -0
- package/dist/esm/transport/node/builders/pairing.js +130 -0
- package/dist/esm/transport/node/builders/prekeys.js +102 -0
- package/dist/esm/transport/node/builders/retry.js +116 -0
- package/dist/esm/transport/node/helpers.js +37 -0
- package/dist/esm/transport/node/query.js +53 -0
- package/dist/esm/transport/node/xml.js +39 -0
- package/dist/esm/transport/noise/WaClientPayload.js +162 -0
- package/dist/esm/transport/noise/WaFrameCodec.js +121 -0
- package/dist/esm/transport/noise/WaNoiseCert.js +74 -0
- package/dist/esm/transport/noise/WaNoiseHandshake.js +57 -0
- package/dist/esm/transport/noise/WaNoiseSession.js +322 -0
- package/dist/esm/transport/noise/WaNoiseSocket.js +17 -0
- package/dist/esm/transport/noise/constants.js +8 -0
- package/dist/esm/transport/noise/types.js +1 -0
- package/dist/esm/transport/stream/parse.js +91 -0
- package/dist/esm/transport/types.js +1 -0
- package/dist/esm/util/async.js +5 -0
- package/dist/esm/util/base64.js +18 -0
- package/dist/esm/util/bytes.js +275 -0
- package/dist/esm/util/coercion.js +56 -0
- package/dist/esm/util/collections.js +27 -0
- package/dist/esm/util/primitives.js +32 -0
- package/dist/esm/util/runtime.js +15 -0
- package/dist/esm/util/signal-address.js +5 -0
- package/dist/index.js +52 -0
- package/dist/infra/log/ConsoleLogger.js +44 -0
- package/dist/infra/log/PinoLogger.js +111 -0
- package/dist/infra/log/types.js +2 -0
- package/dist/infra/perf/BoundedTaskQueue.js +67 -0
- package/dist/media/WaMediaCrypto.js +228 -0
- package/dist/media/WaMediaTransferClient.js +365 -0
- package/dist/media/conn.js +36 -0
- package/dist/media/constants.js +21 -0
- package/dist/media/index.js +9 -0
- package/dist/media/types.js +2 -0
- package/dist/message/WaMessageClient.js +214 -0
- package/dist/message/ack.js +52 -0
- package/dist/message/content.js +24 -0
- package/dist/message/device-sent.js +53 -0
- package/dist/message/incoming.js +321 -0
- package/dist/message/index.js +20 -0
- package/dist/message/padding.js +24 -0
- package/dist/message/phash.js +28 -0
- package/dist/message/types.js +2 -0
- package/dist/proto.js +5 -0
- package/dist/protocol/appstate.js +37 -0
- package/dist/protocol/auth.js +15 -0
- package/dist/protocol/browser.js +45 -0
- package/dist/protocol/constants.js +46 -0
- package/dist/protocol/defaults.js +30 -0
- package/dist/protocol/dirty.js +29 -0
- package/dist/protocol/group.js +8 -0
- package/dist/protocol/index.js +53 -0
- package/dist/protocol/jid.js +107 -0
- package/dist/protocol/media.js +24 -0
- package/dist/protocol/message.js +19 -0
- package/dist/protocol/nodes.js +86 -0
- package/dist/protocol/notification.js +53 -0
- package/dist/protocol/stream.js +63 -0
- package/dist/retry/constants.js +23 -0
- package/dist/retry/index.js +19 -0
- package/dist/retry/outbound.js +88 -0
- package/dist/retry/parse.js +133 -0
- package/dist/retry/reason.js +53 -0
- package/dist/retry/replay.js +181 -0
- package/dist/retry/types.js +2 -0
- package/dist/signal/api/SignalDeviceSyncApi.js +189 -0
- package/dist/signal/api/SignalDigestSyncApi.js +183 -0
- package/dist/signal/api/SignalIdentitySyncApi.js +115 -0
- package/dist/signal/api/SignalMissingPreKeysSyncApi.js +145 -0
- package/dist/signal/api/SignalRotateKeyApi.js +63 -0
- package/dist/signal/api/SignalSessionSyncApi.js +191 -0
- package/dist/signal/api/codec.js +27 -0
- package/dist/signal/api/constants.js +12 -0
- package/dist/signal/api/prekeys.js +16 -0
- package/dist/signal/constants.js +19 -0
- package/dist/signal/crypto/WaAdvSignature.js +72 -0
- package/dist/signal/crypto/constants.js +11 -0
- package/dist/signal/group/SenderKeyChain.js +101 -0
- package/dist/signal/group/SenderKeyCodec.js +50 -0
- package/dist/signal/group/SenderKeyManager.js +180 -0
- package/dist/signal/index.js +29 -0
- package/dist/signal/registration/keygen.js +37 -0
- package/dist/signal/registration/utils.js +19 -0
- package/dist/signal/session/SignalProtocol.js +126 -0
- package/dist/signal/session/SignalRatchet.js +268 -0
- package/dist/signal/session/SignalSerializer.js +69 -0
- package/dist/signal/session/SignalSession.js +165 -0
- package/dist/signal/store/sqlite.js +324 -0
- package/dist/signal/types.js +2 -0
- package/dist/store/contracts/appstate.store.js +2 -0
- package/dist/store/contracts/auth.store.js +2 -0
- package/dist/store/contracts/contact.store.js +2 -0
- package/dist/store/contracts/device-list.store.js +2 -0
- package/dist/store/contracts/message.store.js +2 -0
- package/dist/store/contracts/participants.store.js +2 -0
- package/dist/store/contracts/retry.store.js +2 -0
- package/dist/store/contracts/sender-key.store.js +2 -0
- package/dist/store/contracts/signal.store.js +2 -0
- package/dist/store/contracts/thread.store.js +2 -0
- package/dist/store/createStore.js +281 -0
- package/dist/store/index.js +43 -0
- package/dist/store/noop.store.js +46 -0
- package/dist/store/providers/memory/appstate.store.js +105 -0
- package/dist/store/providers/memory/contact.store.js +27 -0
- package/dist/store/providers/memory/device-list.store.js +90 -0
- package/dist/store/providers/memory/message.store.js +44 -0
- package/dist/store/providers/memory/participants.store.js +65 -0
- package/dist/store/providers/memory/retry.store.js +75 -0
- package/dist/store/providers/memory/sender-key.store.js +92 -0
- package/dist/store/providers/memory/signal.store.js +174 -0
- package/dist/store/providers/memory/thread.store.js +38 -0
- package/dist/store/providers/sqlite/BaseSqliteStore.js +41 -0
- package/dist/store/providers/sqlite/appstate.store.js +173 -0
- package/dist/store/providers/sqlite/auth.store.js +180 -0
- package/dist/store/providers/sqlite/connection.js +276 -0
- package/dist/store/providers/sqlite/contact.store.js +65 -0
- package/dist/store/providers/sqlite/device-list.store.js +159 -0
- package/dist/store/providers/sqlite/message.store.js +123 -0
- package/dist/store/providers/sqlite/migrations.js +350 -0
- package/dist/store/providers/sqlite/participants.store.js +89 -0
- package/dist/store/providers/sqlite/retry.store.js +148 -0
- package/dist/store/providers/sqlite/sender-key.store.js +207 -0
- package/dist/store/providers/sqlite/signal.store.js +357 -0
- package/dist/store/providers/sqlite/thread.store.js +76 -0
- package/dist/store/types.js +2 -0
- package/dist/transport/WaComms.js +531 -0
- package/dist/transport/WaWebSocket.js +365 -0
- package/dist/transport/binary/constants.js +99 -0
- package/dist/transport/binary/decoder.js +279 -0
- package/dist/transport/binary/encoder.js +214 -0
- package/dist/transport/binary/index.js +23 -0
- package/dist/transport/binary/tokens.js +1283 -0
- package/dist/transport/index.js +18 -0
- package/dist/transport/keepalive/WaKeepAlive.js +145 -0
- package/dist/transport/node/WaNodeOrchestrator.js +147 -0
- package/dist/transport/node/WaNodeTransport.js +68 -0
- package/dist/transport/node/builders/accountSync.js +110 -0
- package/dist/transport/node/builders/group.js +52 -0
- package/dist/transport/node/builders/index.js +39 -0
- package/dist/transport/node/builders/media.js +13 -0
- package/dist/transport/node/builders/message.js +328 -0
- package/dist/transport/node/builders/pairing.js +137 -0
- package/dist/transport/node/builders/prekeys.js +107 -0
- package/dist/transport/node/builders/retry.js +119 -0
- package/dist/transport/node/helpers.js +46 -0
- package/dist/transport/node/query.js +59 -0
- package/dist/transport/node/xml.js +42 -0
- package/dist/transport/noise/WaClientPayload.js +166 -0
- package/dist/transport/noise/WaFrameCodec.js +125 -0
- package/dist/transport/noise/WaNoiseCert.js +77 -0
- package/dist/transport/noise/WaNoiseHandshake.js +61 -0
- package/dist/transport/noise/WaNoiseSession.js +326 -0
- package/dist/transport/noise/WaNoiseSocket.js +21 -0
- package/dist/transport/noise/constants.js +11 -0
- package/dist/transport/noise/types.js +2 -0
- package/dist/transport/stream/parse.js +97 -0
- package/dist/transport/types.js +2 -0
- package/dist/types/appstate/WaAppStateCrypto.d.ts +59 -0
- package/dist/types/appstate/WaAppStateSyncClient.d.ts +63 -0
- package/dist/types/appstate/WaAppStateSyncResponseParser.d.ts +12 -0
- package/dist/types/appstate/constants.d.ts +14 -0
- package/dist/types/appstate/index.d.ts +7 -0
- package/dist/types/appstate/store/sqlite.d.ts +21 -0
- package/dist/types/appstate/types.d.ts +66 -0
- package/dist/types/appstate/utils.d.ts +10 -0
- package/dist/types/auth/WaAuthClient.d.ts +61 -0
- package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +14 -0
- package/dist/types/auth/index.d.ts +6 -0
- package/dist/types/auth/pairing/WaPairingCodeCrypto.d.ts +17 -0
- package/dist/types/auth/pairing/WaPairingFlow.d.ts +48 -0
- package/dist/types/auth/pairing/WaQrFlow.d.ts +23 -0
- package/dist/types/auth/pairing/constants.d.ts +2 -0
- package/dist/types/auth/types.d.ts +48 -0
- package/dist/types/client/WaClient.d.ts +97 -0
- package/dist/types/client/WaClientFactory.d.ts +83 -0
- package/dist/types/client/coordinators/WaGroupCoordinator.d.ts +48 -0
- package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +60 -0
- package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +90 -0
- package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +43 -0
- package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +61 -0
- package/dist/types/client/coordinators/WaStreamControlCoordinator.d.ts +17 -0
- package/dist/types/client/dirty.d.ts +17 -0
- package/dist/types/client/events/chat.d.ts +3 -0
- package/dist/types/client/events/group.d.ts +7 -0
- package/dist/types/client/history-sync.d.ts +17 -0
- package/dist/types/client/incoming.d.ts +35 -0
- package/dist/types/client/index.d.ts +2 -0
- package/dist/types/client/mailbox.d.ts +12 -0
- package/dist/types/client/messages.d.ts +17 -0
- package/dist/types/client/types.d.ts +235 -0
- package/dist/types/crypto/core/constants.d.ts +1 -0
- package/dist/types/crypto/core/encoding.d.ts +11 -0
- package/dist/types/crypto/core/hkdf.d.ts +8 -0
- package/dist/types/crypto/core/index.d.ts +11 -0
- package/dist/types/crypto/core/keys.d.ts +20 -0
- package/dist/types/crypto/core/nonce.d.ts +5 -0
- package/dist/types/crypto/core/primitives.d.ts +25 -0
- package/dist/types/crypto/core/random.d.ts +8 -0
- package/dist/types/crypto/curves/Ed25519.d.ts +7 -0
- package/dist/types/crypto/curves/X25519.d.ts +8 -0
- package/dist/types/crypto/curves/constants.d.ts +2 -0
- package/dist/types/crypto/curves/types.d.ts +10 -0
- package/dist/types/crypto/index.d.ts +3 -0
- package/dist/types/crypto/math/constants.d.ts +7 -0
- package/dist/types/crypto/math/edwards.d.ts +3 -0
- package/dist/types/crypto/math/le.d.ts +2 -0
- package/dist/types/crypto/math/mod.d.ts +5 -0
- package/dist/types/crypto/math/types.d.ts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/infra/log/ConsoleLogger.d.ts +11 -0
- package/dist/types/infra/log/PinoLogger.d.ts +30 -0
- package/dist/types/infra/log/types.d.ts +9 -0
- package/dist/types/infra/perf/BoundedTaskQueue.d.ts +19 -0
- package/dist/types/media/WaMediaCrypto.d.ts +12 -0
- package/dist/types/media/WaMediaTransferClient.d.ts +81 -0
- package/dist/types/media/conn.d.ts +3 -0
- package/dist/types/media/constants.d.ts +10 -0
- package/dist/types/media/index.d.ts +4 -0
- package/dist/types/media/types.d.ts +56 -0
- package/dist/types/message/WaMessageClient.d.ts +29 -0
- package/dist/types/message/ack.d.ts +5 -0
- package/dist/types/message/content.d.ts +4 -0
- package/dist/types/message/device-sent.d.ts +3 -0
- package/dist/types/message/incoming.d.ts +18 -0
- package/dist/types/message/index.d.ts +2 -0
- package/dist/types/message/padding.d.ts +2 -0
- package/dist/types/message/phash.d.ts +1 -0
- package/dist/types/message/types.d.ts +58 -0
- package/dist/types/proto.d.ts +2 -0
- package/dist/types/protocol/appstate.d.ts +34 -0
- package/dist/types/protocol/auth.d.ts +12 -0
- package/dist/types/protocol/browser.d.ts +22 -0
- package/dist/types/protocol/constants.d.ts +11 -0
- package/dist/types/protocol/defaults.d.ts +26 -0
- package/dist/types/protocol/dirty.d.ts +15 -0
- package/dist/types/protocol/group.d.ts +6 -0
- package/dist/types/protocol/index.d.ts +11 -0
- package/dist/types/protocol/jid.d.ts +19 -0
- package/dist/types/protocol/media.d.ts +15 -0
- package/dist/types/protocol/message.d.ts +16 -0
- package/dist/types/protocol/nodes.d.ts +83 -0
- package/dist/types/protocol/notification.d.ts +50 -0
- package/dist/types/protocol/stream.d.ts +60 -0
- package/dist/types/retry/constants.d.ts +21 -0
- package/dist/types/retry/index.d.ts +7 -0
- package/dist/types/retry/outbound.d.ts +4 -0
- package/dist/types/retry/parse.d.ts +3 -0
- package/dist/types/retry/reason.d.ts +2 -0
- package/dist/types/retry/replay.d.ts +30 -0
- package/dist/types/retry/types.d.ts +70 -0
- package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +31 -0
- package/dist/types/signal/api/SignalDigestSyncApi.d.ts +27 -0
- package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +26 -0
- package/dist/types/signal/api/SignalMissingPreKeysSyncApi.d.ts +39 -0
- package/dist/types/signal/api/SignalRotateKeyApi.d.ts +22 -0
- package/dist/types/signal/api/SignalSessionSyncApi.d.ts +38 -0
- package/dist/types/signal/api/codec.d.ts +3 -0
- package/dist/types/signal/api/constants.d.ts +9 -0
- package/dist/types/signal/api/prekeys.d.ts +6 -0
- package/dist/types/signal/constants.d.ts +14 -0
- package/dist/types/signal/crypto/WaAdvSignature.d.ts +7 -0
- package/dist/types/signal/crypto/constants.d.ts +5 -0
- package/dist/types/signal/group/SenderKeyChain.d.ts +11 -0
- package/dist/types/signal/group/SenderKeyCodec.d.ts +14 -0
- package/dist/types/signal/group/SenderKeyManager.d.ts +22 -0
- package/dist/types/signal/index.d.ts +12 -0
- package/dist/types/signal/registration/keygen.d.ts +5 -0
- package/dist/types/signal/registration/utils.d.ts +9 -0
- package/dist/types/signal/session/SignalProtocol.d.ts +22 -0
- package/dist/types/signal/session/SignalRatchet.d.ts +25 -0
- package/dist/types/signal/session/SignalSerializer.d.ts +6 -0
- package/dist/types/signal/session/SignalSession.d.ts +43 -0
- package/dist/types/signal/store/sqlite.d.ts +72 -0
- package/dist/types/signal/types.d.ts +110 -0
- package/dist/types/store/contracts/appstate.store.d.ts +22 -0
- package/dist/types/store/contracts/auth.store.d.ts +6 -0
- package/dist/types/store/contracts/contact.store.d.ts +14 -0
- package/dist/types/store/contracts/device-list.store.d.ts +16 -0
- package/dist/types/store/contracts/message.store.d.ts +18 -0
- package/dist/types/store/contracts/participants.store.d.ts +14 -0
- package/dist/types/store/contracts/retry.store.d.ts +11 -0
- package/dist/types/store/contracts/sender-key.store.d.ts +16 -0
- package/dist/types/store/contracts/signal.store.d.ts +31 -0
- package/dist/types/store/contracts/thread.store.d.ts +17 -0
- package/dist/types/store/createStore.d.ts +2 -0
- package/dist/types/store/index.d.ts +31 -0
- package/dist/types/store/noop.store.d.ts +10 -0
- package/dist/types/store/providers/memory/appstate.store.d.ts +21 -0
- package/dist/types/store/providers/memory/contact.store.d.ts +13 -0
- package/dist/types/store/providers/memory/device-list.store.d.ts +20 -0
- package/dist/types/store/providers/memory/message.store.d.ts +14 -0
- package/dist/types/store/providers/memory/participants.store.d.ts +18 -0
- package/dist/types/store/providers/memory/retry.store.d.ts +18 -0
- package/dist/types/store/providers/memory/sender-key.store.d.ts +28 -0
- package/dist/types/store/providers/memory/signal.store.d.ts +51 -0
- package/dist/types/store/providers/memory/thread.store.d.ts +14 -0
- package/dist/types/store/providers/sqlite/BaseSqliteStore.d.ts +12 -0
- package/dist/types/store/providers/sqlite/appstate.store.d.ts +15 -0
- package/dist/types/store/providers/sqlite/auth.store.d.ts +10 -0
- package/dist/types/store/providers/sqlite/connection.d.ts +10 -0
- package/dist/types/store/providers/sqlite/contact.store.d.ts +10 -0
- package/dist/types/store/providers/sqlite/device-list.store.d.ts +18 -0
- package/dist/types/store/providers/sqlite/message.store.d.ts +11 -0
- package/dist/types/store/providers/sqlite/migrations.d.ts +3 -0
- package/dist/types/store/providers/sqlite/participants.store.d.ts +13 -0
- package/dist/types/store/providers/sqlite/retry.store.d.ts +16 -0
- package/dist/types/store/providers/sqlite/sender-key.store.d.ts +25 -0
- package/dist/types/store/providers/sqlite/signal.store.d.ts +46 -0
- package/dist/types/store/providers/sqlite/thread.store.d.ts +11 -0
- package/dist/types/store/types.d.ts +103 -0
- package/dist/types/transport/WaComms.d.ts +61 -0
- package/dist/types/transport/WaWebSocket.d.ts +36 -0
- package/dist/types/transport/binary/constants.d.ts +49 -0
- package/dist/types/transport/binary/decoder.d.ts +3 -0
- package/dist/types/transport/binary/encoder.d.ts +3 -0
- package/dist/types/transport/binary/index.d.ts +4 -0
- package/dist/types/transport/binary/tokens.d.ts +11 -0
- package/dist/types/transport/index.d.ts +7 -0
- package/dist/types/transport/keepalive/WaKeepAlive.d.ts +39 -0
- package/dist/types/transport/node/WaNodeOrchestrator.d.ts +28 -0
- package/dist/types/transport/node/WaNodeTransport.d.ts +22 -0
- package/dist/types/transport/node/builders/accountSync.d.ts +11 -0
- package/dist/types/transport/node/builders/group.d.ts +16 -0
- package/dist/types/transport/node/builders/index.d.ts +7 -0
- package/dist/types/transport/node/builders/media.d.ts +2 -0
- package/dist/types/transport/node/builders/message.d.ts +52 -0
- package/dist/types/transport/node/builders/pairing.d.ts +18 -0
- package/dist/types/transport/node/builders/prekeys.d.ts +5 -0
- package/dist/types/transport/node/builders/retry.d.ts +18 -0
- package/dist/types/transport/node/helpers.d.ts +8 -0
- package/dist/types/transport/node/query.d.ts +10 -0
- package/dist/types/transport/node/xml.d.ts +2 -0
- package/dist/types/transport/noise/WaClientPayload.d.ts +3 -0
- package/dist/types/transport/noise/WaFrameCodec.d.ts +9 -0
- package/dist/types/transport/noise/WaNoiseCert.d.ts +1 -0
- package/dist/types/transport/noise/WaNoiseHandshake.d.ts +14 -0
- package/dist/types/transport/noise/WaNoiseSession.d.ts +33 -0
- package/dist/types/transport/noise/WaNoiseSocket.d.ts +10 -0
- package/dist/types/transport/noise/constants.d.ts +7 -0
- package/dist/types/transport/noise/types.d.ts +23 -0
- package/dist/types/transport/stream/parse.d.ts +23 -0
- package/dist/types/transport/types.d.ts +71 -0
- package/dist/types/util/async.d.ts +1 -0
- package/dist/types/util/base64.d.ts +4 -0
- package/dist/types/util/bytes.d.ts +28 -0
- package/dist/types/util/coercion.d.ts +8 -0
- package/dist/types/util/collections.d.ts +3 -0
- package/dist/types/util/primitives.d.ts +7 -0
- package/dist/types/util/runtime.d.ts +2 -0
- package/dist/types/util/signal-address.d.ts +2 -0
- package/dist/util/async.js +8 -0
- package/dist/util/base64.js +24 -0
- package/dist/util/bytes.js +291 -0
- package/dist/util/coercion.js +66 -0
- package/dist/util/collections.js +32 -0
- package/dist/util/primitives.js +37 -0
- package/dist/util/runtime.js +19 -0
- package/dist/util/signal-address.js +8 -0
- package/package.json +150 -0
- package/proto/index.d.ts +10861 -0
- package/proto/index.js +1 -0
- package/scripts/check-node-version.cjs +55 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
import { toSerializedPubKey } from '../../crypto/core/keys.js';
|
|
2
|
+
import { resolveMessageTypeAttr } from '../../message/content.js';
|
|
3
|
+
import { wrapDeviceSentMessage } from '../../message/device-sent.js';
|
|
4
|
+
import { writeRandomPadMax16 } from '../../message/padding.js';
|
|
5
|
+
import { computePhashV2 } from '../../message/phash.js';
|
|
6
|
+
import { proto } from '../../proto.js';
|
|
7
|
+
import { WA_DEFAULTS } from '../../protocol/constants.js';
|
|
8
|
+
import { isGroupJid, normalizeDeviceJid, normalizeRecipientJid, parseSignalAddressFromJid, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
9
|
+
import { RETRY_OUTBOUND_TTL_MS } from '../../retry/constants.js';
|
|
10
|
+
import { encodeRetryReplayPayload } from '../../retry/outbound.js';
|
|
11
|
+
import { encodeBinaryNode } from '../../transport/binary/index.js';
|
|
12
|
+
import { buildDirectMessageFanoutNode, buildGroupDirectMessageNode, buildGroupSenderKeyMessageNode } from '../../transport/node/builders/message.js';
|
|
13
|
+
import { bytesToHex, uint8Equal } from '../../util/bytes.js';
|
|
14
|
+
import { toError } from '../../util/primitives.js';
|
|
15
|
+
import { signalAddressKey } from '../../util/signal-address.js';
|
|
16
|
+
export class WaMessageDispatchCoordinator {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.logger = options.logger;
|
|
19
|
+
this.messageClient = options.messageClient;
|
|
20
|
+
this.retryStore = options.retryStore;
|
|
21
|
+
this.participantsStore = options.participantsStore;
|
|
22
|
+
this.retryTtlMs = this.retryStore.getTtlMs?.() ?? RETRY_OUTBOUND_TTL_MS;
|
|
23
|
+
this.buildMessageContent = options.buildMessageContent;
|
|
24
|
+
this.queryGroupParticipantJids = options.queryGroupParticipantJids;
|
|
25
|
+
this.senderKeyManager = options.senderKeyManager;
|
|
26
|
+
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
|
+
this.getCurrentMeJid = options.getCurrentMeJid;
|
|
32
|
+
this.getCurrentMeLid = options.getCurrentMeLid;
|
|
33
|
+
this.getCurrentSignedIdentity = options.getCurrentSignedIdentity;
|
|
34
|
+
}
|
|
35
|
+
async publishMessageNode(node, options = {}) {
|
|
36
|
+
this.logger.debug('wa client publish message node', {
|
|
37
|
+
tag: node.tag,
|
|
38
|
+
type: node.attrs.type,
|
|
39
|
+
to: node.attrs.to
|
|
40
|
+
});
|
|
41
|
+
const messageType = node.attrs.type ?? 'text';
|
|
42
|
+
const replayPayload = {
|
|
43
|
+
mode: 'opaque_node',
|
|
44
|
+
node: encodeBinaryNode(node)
|
|
45
|
+
};
|
|
46
|
+
return this.publishWithRetryTracking({
|
|
47
|
+
messageIdHint: node.attrs.id,
|
|
48
|
+
toJid: node.attrs.to,
|
|
49
|
+
participantJid: node.attrs.participant,
|
|
50
|
+
recipientJid: node.attrs.recipient,
|
|
51
|
+
messageType,
|
|
52
|
+
replayPayload
|
|
53
|
+
}, async () => this.messageClient.publishNode(node, options));
|
|
54
|
+
}
|
|
55
|
+
async publishEncryptedMessage(input, options = {}) {
|
|
56
|
+
this.logger.debug('wa client publish encrypted message', {
|
|
57
|
+
to: input.to,
|
|
58
|
+
type: input.type,
|
|
59
|
+
encType: input.encType
|
|
60
|
+
});
|
|
61
|
+
const replayPayload = {
|
|
62
|
+
mode: 'encrypted',
|
|
63
|
+
to: input.to,
|
|
64
|
+
type: input.type ?? 'text',
|
|
65
|
+
encType: input.encType,
|
|
66
|
+
ciphertext: input.ciphertext,
|
|
67
|
+
participant: input.participant
|
|
68
|
+
};
|
|
69
|
+
return this.publishWithRetryTracking({
|
|
70
|
+
messageIdHint: input.id,
|
|
71
|
+
toJid: input.to,
|
|
72
|
+
participantJid: input.participant,
|
|
73
|
+
messageType: input.type ?? 'text',
|
|
74
|
+
replayPayload
|
|
75
|
+
}, async () => this.messageClient.publishEncrypted(input, options));
|
|
76
|
+
}
|
|
77
|
+
async publishSignalMessage(input, options = {}) {
|
|
78
|
+
this.requireCurrentMeJid('publishSignalMessage');
|
|
79
|
+
const address = parseSignalAddressFromJid(input.to);
|
|
80
|
+
if (address.server === WA_DEFAULTS.GROUP_SERVER) {
|
|
81
|
+
throw new Error('publishSignalMessage currently supports only direct chats; use sender-key flow for groups');
|
|
82
|
+
}
|
|
83
|
+
this.logger.debug('wa client publish signal message', {
|
|
84
|
+
to: input.to,
|
|
85
|
+
type: input.type
|
|
86
|
+
});
|
|
87
|
+
const [paddedPlaintext] = await Promise.all([
|
|
88
|
+
writeRandomPadMax16(input.plaintext),
|
|
89
|
+
this.ensureSignalSession(address, input.to, input.expectedIdentity)
|
|
90
|
+
]);
|
|
91
|
+
const encrypted = await this.signalProtocol.encryptMessage(address, paddedPlaintext, input.expectedIdentity);
|
|
92
|
+
const messageType = input.type ?? 'text';
|
|
93
|
+
const replayPayload = {
|
|
94
|
+
mode: 'plaintext',
|
|
95
|
+
to: input.to,
|
|
96
|
+
type: messageType,
|
|
97
|
+
plaintext: paddedPlaintext
|
|
98
|
+
};
|
|
99
|
+
return this.publishWithRetryTracking({
|
|
100
|
+
messageIdHint: input.id,
|
|
101
|
+
toJid: input.to,
|
|
102
|
+
participantJid: input.participant,
|
|
103
|
+
messageType,
|
|
104
|
+
replayPayload
|
|
105
|
+
}, async () => this.messageClient.publishEncrypted({
|
|
106
|
+
to: input.to,
|
|
107
|
+
encType: encrypted.type,
|
|
108
|
+
ciphertext: encrypted.ciphertext,
|
|
109
|
+
id: input.id,
|
|
110
|
+
type: input.type,
|
|
111
|
+
category: input.category,
|
|
112
|
+
pushPriority: input.pushPriority,
|
|
113
|
+
participant: input.participant,
|
|
114
|
+
deviceFanout: input.deviceFanout
|
|
115
|
+
}, options));
|
|
116
|
+
}
|
|
117
|
+
async sendMessage(to, content, options = {}) {
|
|
118
|
+
const recipientJid = normalizeRecipientJid(to);
|
|
119
|
+
const message = await this.buildMessageContent(content);
|
|
120
|
+
const plaintext = await writeRandomPadMax16(proto.Message.encode(message).finish());
|
|
121
|
+
const type = resolveMessageTypeAttr(message);
|
|
122
|
+
if (isGroupJid(recipientJid)) {
|
|
123
|
+
if (this.shouldUseGroupDirectPath(message)) {
|
|
124
|
+
return this.publishGroupDirectMessage(recipientJid, plaintext, type, options);
|
|
125
|
+
}
|
|
126
|
+
return this.publishGroupSenderKeyMessage(recipientJid, plaintext, type, options);
|
|
127
|
+
}
|
|
128
|
+
const directRecipientJid = toUserJid(recipientJid);
|
|
129
|
+
return this.publishDirectSignalMessageWithFanout(directRecipientJid, message, plaintext, type, options);
|
|
130
|
+
}
|
|
131
|
+
async syncSignalSession(jid, reasonIdentity = false) {
|
|
132
|
+
const address = parseSignalAddressFromJid(jid);
|
|
133
|
+
if (address.server === WA_DEFAULTS.GROUP_SERVER) {
|
|
134
|
+
throw new Error('syncSignalSession supports only direct chats');
|
|
135
|
+
}
|
|
136
|
+
await this.ensureSignalSession(address, jid, undefined, reasonIdentity);
|
|
137
|
+
}
|
|
138
|
+
async sendReceipt(input) {
|
|
139
|
+
await this.messageClient.sendReceipt(input);
|
|
140
|
+
}
|
|
141
|
+
async requestAppStateSyncKeys(keyIds) {
|
|
142
|
+
const normalizedKeyIds = this.normalizeKeyIds(keyIds);
|
|
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;
|
|
168
|
+
}
|
|
169
|
+
async sendAppStateSyncKeyShare(toDeviceJid, keys, missingKeyIds = []) {
|
|
170
|
+
const normalizedTo = normalizeDeviceJid(toDeviceJid);
|
|
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
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async mutateParticipantsCacheFromGroupEvent(event) {
|
|
204
|
+
const groupJid = this.resolveGroupJidForParticipantCacheEvent(event);
|
|
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;
|
|
399
|
+
}
|
|
400
|
+
shouldUseGroupDirectPath(message) {
|
|
401
|
+
const protocolType = message.protocolMessage?.type;
|
|
402
|
+
if (protocolType === proto.Message.ProtocolMessage.Type.REVOKE ||
|
|
403
|
+
protocolType === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
return message.keepInChatMessage?.keepType === proto.KeepType.UNDO_KEEP_FOR_ALL;
|
|
407
|
+
}
|
|
408
|
+
async publishGroupDirectMessage(groupJid, plaintext, type, options, retryContext = {}) {
|
|
409
|
+
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
410
|
+
const participantUserJids = retryContext.forceRefreshParticipants
|
|
411
|
+
? await this.refreshGroupParticipantUsers(groupJid)
|
|
412
|
+
: await this.resolveGroupParticipantUsers(groupJid);
|
|
413
|
+
const addressingMode = retryContext.forceAddressingMode ??
|
|
414
|
+
this.resolveGroupAddressingMode(participantUserJids, groupJid);
|
|
415
|
+
const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
|
|
416
|
+
const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
417
|
+
if (fanoutDeviceJids.length === 0) {
|
|
418
|
+
throw new Error('group direct send resolved no target devices');
|
|
419
|
+
}
|
|
420
|
+
await this.ensureSignalSessionsBatch(fanoutDeviceJids);
|
|
421
|
+
const participants = await Promise.all(fanoutDeviceJids.map(async (targetJid) => {
|
|
422
|
+
const address = parseSignalAddressFromJid(targetJid);
|
|
423
|
+
await this.ensureSignalSession(address, targetJid);
|
|
424
|
+
const encrypted = await this.signalProtocol.encryptMessage(address, plaintext);
|
|
425
|
+
return {
|
|
426
|
+
jid: targetJid,
|
|
427
|
+
encType: encrypted.type,
|
|
428
|
+
ciphertext: encrypted.ciphertext
|
|
429
|
+
};
|
|
430
|
+
}));
|
|
431
|
+
const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
|
|
432
|
+
const localPhash = await computePhashV2([...fanoutDeviceJids, senderForPhash]);
|
|
433
|
+
const messageNode = buildGroupDirectMessageNode({
|
|
434
|
+
to: groupJid,
|
|
435
|
+
type,
|
|
436
|
+
id: options.id,
|
|
437
|
+
phash: localPhash,
|
|
438
|
+
addressingMode,
|
|
439
|
+
participants,
|
|
440
|
+
deviceIdentity: shouldAttachDeviceIdentity
|
|
441
|
+
? this.getEncodedSignedDeviceIdentity()
|
|
442
|
+
: undefined
|
|
443
|
+
});
|
|
444
|
+
const replayPayload = {
|
|
445
|
+
mode: 'plaintext',
|
|
446
|
+
to: groupJid,
|
|
447
|
+
type,
|
|
448
|
+
plaintext
|
|
449
|
+
};
|
|
450
|
+
const result = await this.publishWithRetryTracking({
|
|
451
|
+
messageIdHint: options.id ?? messageNode.attrs.id,
|
|
452
|
+
toJid: groupJid,
|
|
453
|
+
messageType: type,
|
|
454
|
+
replayPayload
|
|
455
|
+
}, async () => this.messageClient.publishNode(messageNode, options));
|
|
456
|
+
const ackError = result.ack.error;
|
|
457
|
+
const serverPhash = result.ack.phash;
|
|
458
|
+
const serverAddressingMode = result.ack.addressingMode;
|
|
459
|
+
const hasPhashMismatch = !!serverPhash && serverPhash !== localPhash;
|
|
460
|
+
const hasAddressingMismatch = !!serverAddressingMode && serverAddressingMode !== addressingMode;
|
|
461
|
+
const hasAddressingError = ackError === 421;
|
|
462
|
+
if (!retryContext.retried &&
|
|
463
|
+
(hasPhashMismatch || hasAddressingMismatch || hasAddressingError)) {
|
|
464
|
+
this.logger.warn('group direct publish acknowledged with mismatch metadata', {
|
|
465
|
+
id: result.id,
|
|
466
|
+
groupJid,
|
|
467
|
+
localPhash,
|
|
468
|
+
serverPhash,
|
|
469
|
+
localAddressingMode: addressingMode,
|
|
470
|
+
serverAddressingMode,
|
|
471
|
+
ackError
|
|
472
|
+
});
|
|
473
|
+
return this.publishGroupDirectMessage(groupJid, plaintext, type, {
|
|
474
|
+
...options,
|
|
475
|
+
id: result.id
|
|
476
|
+
}, {
|
|
477
|
+
retried: true,
|
|
478
|
+
forceRefreshParticipants: true,
|
|
479
|
+
forceAddressingMode: serverAddressingMode
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
async publishGroupSenderKeyMessage(groupJid, plaintext, type, options, retryContext = {}) {
|
|
485
|
+
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
486
|
+
const participantUserJids = retryContext.forceRefreshParticipants
|
|
487
|
+
? await this.refreshGroupParticipantUsers(groupJid)
|
|
488
|
+
: await this.resolveGroupParticipantUsers(groupJid);
|
|
489
|
+
const addressingMode = retryContext.forceAddressingMode ??
|
|
490
|
+
this.resolveGroupAddressingMode(participantUserJids, groupJid);
|
|
491
|
+
const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
|
|
492
|
+
const sender = parseSignalAddressFromJid(senderJid);
|
|
493
|
+
const senderKeyDistributionMessage = await this.senderKeyManager.createSenderKeyDistributionMessage(groupJid, sender);
|
|
494
|
+
const groupCiphertext = await this.senderKeyManager.encryptGroupMessage(groupJid, sender, plaintext);
|
|
495
|
+
const distributionData = await this.encryptGroupDistributionParticipants(groupJid, sender, senderKeyDistributionMessage, participantUserJids);
|
|
496
|
+
const { fanoutDeviceJids, distributionParticipants } = distributionData;
|
|
497
|
+
const shouldAttachDeviceIdentity = distributionParticipants.some((participant) => participant.encType === 'pkmsg');
|
|
498
|
+
const localPhash = await computePhashV2([...fanoutDeviceJids, senderJid]);
|
|
499
|
+
const messageNode = buildGroupSenderKeyMessageNode({
|
|
500
|
+
to: groupJid,
|
|
501
|
+
type,
|
|
502
|
+
id: options.id,
|
|
503
|
+
phash: localPhash,
|
|
504
|
+
addressingMode,
|
|
505
|
+
groupCiphertext: groupCiphertext.ciphertext,
|
|
506
|
+
participants: distributionParticipants,
|
|
507
|
+
deviceIdentity: shouldAttachDeviceIdentity
|
|
508
|
+
? this.getEncodedSignedDeviceIdentity()
|
|
509
|
+
: undefined
|
|
510
|
+
});
|
|
511
|
+
const replayPayload = {
|
|
512
|
+
mode: 'plaintext',
|
|
513
|
+
to: groupJid,
|
|
514
|
+
type,
|
|
515
|
+
plaintext
|
|
516
|
+
};
|
|
517
|
+
const result = await this.publishWithRetryTracking({
|
|
518
|
+
messageIdHint: options.id ?? messageNode.attrs.id,
|
|
519
|
+
toJid: groupJid,
|
|
520
|
+
messageType: type,
|
|
521
|
+
replayPayload
|
|
522
|
+
}, async () => this.messageClient.publishNode(messageNode, options));
|
|
523
|
+
const distributedAddresses = distributionParticipants.map((participant) => participant.address);
|
|
524
|
+
try {
|
|
525
|
+
await this.senderKeyManager.markSenderKeyDistributed(groupJid, sender, distributedAddresses);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
this.logger.warn('failed to mark sender key distribution targets', {
|
|
529
|
+
groupJid,
|
|
530
|
+
participants: distributedAddresses.length,
|
|
531
|
+
message: toError(error).message
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const ackError = result.ack.error;
|
|
535
|
+
const serverPhash = result.ack.phash;
|
|
536
|
+
const serverAddressingMode = result.ack.addressingMode;
|
|
537
|
+
const hasPhashMismatch = !!serverPhash && serverPhash !== localPhash;
|
|
538
|
+
const hasAddressingMismatch = !!serverAddressingMode && serverAddressingMode !== addressingMode;
|
|
539
|
+
const hasAddressingError = ackError === 421;
|
|
540
|
+
if (!retryContext.retried &&
|
|
541
|
+
(hasPhashMismatch || hasAddressingMismatch || hasAddressingError)) {
|
|
542
|
+
this.logger.warn('group message publish acknowledged with mismatch metadata', {
|
|
543
|
+
id: result.id,
|
|
544
|
+
groupJid,
|
|
545
|
+
localPhash,
|
|
546
|
+
serverPhash,
|
|
547
|
+
localAddressingMode: addressingMode,
|
|
548
|
+
serverAddressingMode,
|
|
549
|
+
ackError
|
|
550
|
+
});
|
|
551
|
+
return this.publishGroupSenderKeyMessage(groupJid, plaintext, type, {
|
|
552
|
+
...options,
|
|
553
|
+
id: result.id
|
|
554
|
+
}, {
|
|
555
|
+
retried: true,
|
|
556
|
+
forceRefreshParticipants: true,
|
|
557
|
+
forceAddressingMode: serverAddressingMode
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
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
|
+
resolveGroupAddressingMode(participantUserJids, groupJid) {
|
|
694
|
+
for (const participantJid of participantUserJids) {
|
|
695
|
+
try {
|
|
696
|
+
if (splitJid(participantJid).server === 'lid') {
|
|
697
|
+
return 'lid';
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
this.logger.trace('ignoring malformed participant jid in addressing mode resolution', { participantJid, message: toError(error).message });
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
this.logger.trace('group addressing mode resolved to pn (default)', {
|
|
705
|
+
groupJid,
|
|
706
|
+
participants: participantUserJids.length
|
|
707
|
+
});
|
|
708
|
+
return 'pn';
|
|
709
|
+
}
|
|
710
|
+
resolveSenderForAddressingMode(addressingMode, meJid) {
|
|
711
|
+
if (addressingMode === 'lid') {
|
|
712
|
+
const meLid = this.getCurrentMeLid();
|
|
713
|
+
if (meLid && meLid.includes('@')) {
|
|
714
|
+
try {
|
|
715
|
+
return normalizeDeviceJid(meLid);
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
this.logger.trace('ignoring malformed me lid jid', {
|
|
719
|
+
meLid,
|
|
720
|
+
message: toError(error).message
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return normalizeDeviceJid(meJid);
|
|
726
|
+
}
|
|
727
|
+
async encryptGroupDistributionParticipants(groupJid, sender, senderKeyDistributionMessage, participantUserJids) {
|
|
728
|
+
const distributionPayload = await writeRandomPadMax16(proto.Message.encode({
|
|
729
|
+
senderKeyDistributionMessage
|
|
730
|
+
}).finish());
|
|
731
|
+
const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
|
|
732
|
+
if (fanoutDeviceJids.length === 0) {
|
|
733
|
+
return {
|
|
734
|
+
fanoutDeviceJids,
|
|
735
|
+
distributionParticipants: []
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const fanoutTargets = fanoutDeviceJids.map((jid) => ({
|
|
739
|
+
jid,
|
|
740
|
+
address: parseSignalAddressFromJid(jid)
|
|
741
|
+
}));
|
|
742
|
+
const pendingAddresses = await this.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, sender, fanoutTargets.map((target) => target.address));
|
|
743
|
+
if (pendingAddresses.length === 0) {
|
|
744
|
+
return {
|
|
745
|
+
fanoutDeviceJids,
|
|
746
|
+
distributionParticipants: []
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const pendingAddressKeys = new Set(pendingAddresses.map(signalAddressKey));
|
|
750
|
+
const pendingTargets = fanoutTargets.filter((target) => pendingAddressKeys.has(signalAddressKey(target.address)));
|
|
751
|
+
if (pendingTargets.length === 0) {
|
|
752
|
+
return {
|
|
753
|
+
fanoutDeviceJids,
|
|
754
|
+
distributionParticipants: []
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
await this.ensureSignalSessionsBatch(pendingTargets.map((target) => target.jid));
|
|
758
|
+
const distributionParticipants = await Promise.all(pendingTargets.map(async (target) => {
|
|
759
|
+
await this.ensureSignalSession(target.address, target.jid);
|
|
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 [];
|
|
802
|
+
}
|
|
803
|
+
try {
|
|
804
|
+
const synced = await this.signalDeviceSync.syncDeviceList(candidateUsers);
|
|
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];
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
this.logger.warn('group participant device sync failed, falling back to participant user jids', {
|
|
825
|
+
participants: candidateUsers.length,
|
|
826
|
+
message: toError(error).message
|
|
827
|
+
});
|
|
828
|
+
return [...new Set(candidateUsers.map((jid) => normalizeDeviceJid(jid)))].filter((jid) => !meDeviceJids.has(jid));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async publishDirectSignalMessageWithFanout(recipientJid, message, plaintext, type, options) {
|
|
832
|
+
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
833
|
+
const meLid = this.getCurrentMeLid();
|
|
834
|
+
const selfDeviceJidForRecipient = this.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
|
|
835
|
+
const deviceJids = await this.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
|
|
836
|
+
const recipientUserJid = toUserJid(recipientJid);
|
|
837
|
+
const meUserJid = toUserJid(selfDeviceJidForRecipient);
|
|
838
|
+
this.logger.debug('wa client publish signal fanout', {
|
|
839
|
+
to: recipientJid,
|
|
840
|
+
devices: deviceJids.length,
|
|
841
|
+
type
|
|
842
|
+
});
|
|
843
|
+
const expectedIdentityByJid = new Map();
|
|
844
|
+
if (options.expectedIdentity) {
|
|
845
|
+
for (let index = 0; index < deviceJids.length; index += 1) {
|
|
846
|
+
const targetJid = deviceJids[index];
|
|
847
|
+
if (toUserJid(targetJid) === recipientUserJid) {
|
|
848
|
+
expectedIdentityByJid.set(normalizeDeviceJid(targetJid), options.expectedIdentity);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
await this.ensureSignalSessionsBatch(deviceJids, expectedIdentityByJid);
|
|
853
|
+
const hasSelfDeviceFanout = deviceJids.some((targetJid) => toUserJid(targetJid) === meUserJid);
|
|
854
|
+
const selfDevicePlaintext = hasSelfDeviceFanout
|
|
855
|
+
? await writeRandomPadMax16(proto.Message.encode(wrapDeviceSentMessage(message, recipientUserJid)).finish())
|
|
856
|
+
: null;
|
|
857
|
+
const participants = await Promise.all(deviceJids.map(async (targetJid) => {
|
|
858
|
+
const address = parseSignalAddressFromJid(targetJid);
|
|
859
|
+
const targetUserJid = toUserJid(targetJid);
|
|
860
|
+
const expectedIdentity = targetUserJid === recipientUserJid ? options.expectedIdentity : undefined;
|
|
861
|
+
const plaintextForTarget = selfDevicePlaintext && targetUserJid === meUserJid
|
|
862
|
+
? selfDevicePlaintext
|
|
863
|
+
: plaintext;
|
|
864
|
+
await this.ensureSignalSession(address, targetJid, expectedIdentity);
|
|
865
|
+
const encrypted = await this.signalProtocol.encryptMessage(address, plaintextForTarget, expectedIdentity);
|
|
866
|
+
return {
|
|
867
|
+
jid: targetJid,
|
|
868
|
+
encType: encrypted.type,
|
|
869
|
+
ciphertext: encrypted.ciphertext
|
|
870
|
+
};
|
|
871
|
+
}));
|
|
872
|
+
const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
|
|
873
|
+
const deviceIdentity = shouldAttachDeviceIdentity
|
|
874
|
+
? this.getEncodedSignedDeviceIdentity()
|
|
875
|
+
: undefined;
|
|
876
|
+
const messageNode = buildDirectMessageFanoutNode({
|
|
877
|
+
to: recipientJid,
|
|
878
|
+
type,
|
|
879
|
+
id: options.id,
|
|
880
|
+
participants,
|
|
881
|
+
deviceIdentity
|
|
882
|
+
});
|
|
883
|
+
const replayPayload = {
|
|
884
|
+
mode: 'plaintext',
|
|
885
|
+
to: recipientJid,
|
|
886
|
+
type,
|
|
887
|
+
plaintext
|
|
888
|
+
};
|
|
889
|
+
return this.publishWithRetryTracking({
|
|
890
|
+
messageIdHint: options.id ?? messageNode.attrs.id,
|
|
891
|
+
toJid: recipientJid,
|
|
892
|
+
messageType: type,
|
|
893
|
+
replayPayload
|
|
894
|
+
}, async () => this.messageClient.publishNode(messageNode, options));
|
|
895
|
+
}
|
|
896
|
+
async ensureSignalSessionsBatch(targetJids, expectedIdentityByJid = new Map()) {
|
|
897
|
+
const normalizedTargetJids = [...new Set(targetJids.map((jid) => normalizeDeviceJid(jid)))];
|
|
898
|
+
if (normalizedTargetJids.length === 0) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const normalizedTargets = normalizedTargetJids.map((jid) => ({
|
|
902
|
+
jid,
|
|
903
|
+
address: parseSignalAddressFromJid(jid)
|
|
904
|
+
}));
|
|
905
|
+
const hasSessions = await this.signalProtocol.hasSessions(normalizedTargets.map((target) => target.address));
|
|
906
|
+
const missingTargets = normalizedTargets.filter((_, index) => !hasSessions[index]);
|
|
907
|
+
if (missingTargets.length === 0) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
const batchResults = await this.signalSessionSync.fetchKeyBundles(missingTargets.map((target) => ({ jid: target.jid })));
|
|
912
|
+
const resultByJid = new Map(batchResults.map((result) => [normalizeDeviceJid(result.jid), result]));
|
|
913
|
+
const fallbackJids = [];
|
|
914
|
+
const establishPromises = [];
|
|
915
|
+
for (let index = 0; index < missingTargets.length; index += 1) {
|
|
916
|
+
const target = missingTargets[index];
|
|
917
|
+
const result = resultByJid.get(target.jid);
|
|
918
|
+
if (!result || !('bundle' in result)) {
|
|
919
|
+
fallbackJids.push(target.jid);
|
|
920
|
+
continue;
|
|
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
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
const normalized = toError(error);
|
|
954
|
+
if (normalized.message === 'identity mismatch') {
|
|
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
|
|
960
|
+
});
|
|
961
|
+
for (let index = 0; index < missingTargets.length; index += 1) {
|
|
962
|
+
const target = missingTargets[index];
|
|
963
|
+
await this.ensureSignalSession(target.address, target.jid, expectedIdentityByJid.get(target.jid));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
async resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient) {
|
|
968
|
+
const recipientUserJid = toUserJid(recipientJid);
|
|
969
|
+
const meUserJid = toUserJid(selfDeviceJidForRecipient);
|
|
970
|
+
const targets = recipientUserJid === meUserJid ? [recipientUserJid] : [recipientUserJid, meUserJid];
|
|
971
|
+
try {
|
|
972
|
+
const synced = await this.signalDeviceSync.syncDeviceList(targets);
|
|
973
|
+
const byUser = new Map(synced.map((entry) => [toUserJid(entry.jid), entry.deviceJids]));
|
|
974
|
+
const fanout = new Set();
|
|
975
|
+
const recipientDevices = byUser.get(recipientUserJid) ?? [];
|
|
976
|
+
if (recipientDevices.length === 0) {
|
|
977
|
+
fanout.add(recipientUserJid);
|
|
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];
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
997
|
+
this.logger.warn('signal device fanout sync failed, falling back to direct recipient', {
|
|
998
|
+
to: recipientJid,
|
|
999
|
+
message
|
|
1000
|
+
});
|
|
1001
|
+
return [recipientUserJid];
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
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
|
+
getEncodedSignedDeviceIdentity() {
|
|
1014
|
+
const signedIdentity = this.getCurrentSignedIdentity();
|
|
1015
|
+
if (!signedIdentity) {
|
|
1016
|
+
throw new Error('missing signed identity for pkmsg fanout');
|
|
1017
|
+
}
|
|
1018
|
+
return proto.ADVSignedDeviceIdentity.encode(signedIdentity).finish();
|
|
1019
|
+
}
|
|
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
|
+
requireCurrentMeJid(context) {
|
|
1051
|
+
const meJid = this.getCurrentMeJid();
|
|
1052
|
+
if (meJid) {
|
|
1053
|
+
return meJid;
|
|
1054
|
+
}
|
|
1055
|
+
throw new Error(`${context} requires registered meJid`);
|
|
1056
|
+
}
|
|
1057
|
+
}
|