zapo-js 0.1.2 → 0.2.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 +12 -4
- package/dist/appstate/WaAppStateCrypto.js +1 -1
- package/dist/appstate/WaAppStateSyncClient.js +138 -93
- package/dist/appstate/{store/sqlite.js → encoding.js} +13 -8
- package/dist/appstate/index.js +8 -6
- package/dist/appstate/utils.js +0 -5
- package/dist/auth/WaAuthClient.js +36 -47
- package/dist/auth/flow/WaAuthCredentialsFlow.js +7 -7
- package/dist/auth/index.js +1 -6
- package/dist/auth/pairing/WaPairingCodeCrypto.js +6 -4
- package/dist/auth/pairing/WaPairingFlow.js +13 -3
- package/dist/client/WaClient.js +225 -101
- package/dist/client/WaClientFactory.js +294 -44
- package/dist/client/connection/WaConnectionManager.js +19 -10
- package/dist/client/coordinators/WaBusinessCoordinator.js +241 -0
- package/dist/client/coordinators/WaGroupCoordinator.js +11 -7
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +1 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +292 -99
- package/dist/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
- package/dist/client/coordinators/WaPrivacyCoordinator.js +134 -0
- package/dist/client/coordinators/WaProfileCoordinator.js +212 -0
- package/dist/client/coordinators/WaRetryCoordinator.js +179 -27
- package/dist/client/coordinators/WaStreamControlCoordinator.js +18 -11
- package/dist/client/coordinators/WaTrustedContactTokenCoordinator.js +166 -0
- package/dist/client/dirty.js +40 -20
- package/dist/client/events/devices.js +72 -0
- package/dist/client/events/group.js +3 -11
- package/dist/client/events/identity.js +22 -0
- package/dist/client/events/privacy-token.js +39 -0
- package/dist/client/history-sync.js +50 -9
- package/dist/client/incoming.js +37 -7
- package/dist/client/mailbox.js +24 -23
- package/dist/client/messages.js +107 -31
- package/dist/client/messaging/fanout.js +21 -11
- package/dist/client/messaging/participants.js +6 -4
- package/dist/client/persistence/WriteBehindPersistence.js +129 -0
- package/dist/client/tokens/cs-token.js +50 -0
- package/dist/client/tokens/tc-token.js +25 -0
- package/dist/crypto/core/index.js +2 -2
- package/dist/crypto/core/keys.js +4 -4
- package/dist/crypto/core/nonce.js +2 -0
- package/dist/crypto/core/primitives.js +0 -8
- package/dist/crypto/core/random.js +22 -0
- package/dist/crypto/curves/X25519.js +25 -6
- package/dist/crypto/index.js +3 -0
- package/dist/crypto/math/constants.js +13 -36
- package/dist/crypto/math/edwards.js +171 -44
- package/dist/crypto/math/fe.js +706 -0
- package/dist/crypto/math/mod.js +10 -3
- package/dist/esm/appstate/WaAppStateCrypto.js +1 -1
- package/dist/esm/appstate/WaAppStateSyncClient.js +138 -93
- package/dist/esm/appstate/{store/sqlite.js → encoding.js} +13 -8
- package/dist/esm/appstate/index.js +2 -2
- package/dist/esm/appstate/utils.js +2 -5
- package/dist/esm/auth/WaAuthClient.js +36 -47
- package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +7 -7
- package/dist/esm/auth/index.js +0 -2
- package/dist/esm/auth/pairing/WaPairingCodeCrypto.js +6 -4
- package/dist/esm/auth/pairing/WaPairingFlow.js +14 -4
- package/dist/esm/client/WaClient.js +225 -101
- package/dist/esm/client/WaClientFactory.js +295 -45
- package/dist/esm/client/connection/WaConnectionManager.js +19 -10
- package/dist/esm/client/coordinators/WaBusinessCoordinator.js +238 -0
- package/dist/esm/client/coordinators/WaGroupCoordinator.js +11 -7
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +1 -0
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +295 -102
- package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +131 -0
- package/dist/esm/client/coordinators/WaProfileCoordinator.js +209 -0
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +181 -29
- package/dist/esm/client/coordinators/WaStreamControlCoordinator.js +19 -12
- package/dist/esm/client/coordinators/WaTrustedContactTokenCoordinator.js +162 -0
- package/dist/esm/client/dirty.js +40 -20
- package/dist/esm/client/events/devices.js +68 -0
- package/dist/esm/client/events/group.js +3 -11
- package/dist/esm/client/events/identity.js +19 -0
- package/dist/esm/client/events/privacy-token.js +36 -0
- package/dist/esm/client/history-sync.js +50 -9
- package/dist/esm/client/incoming.js +38 -8
- package/dist/esm/client/mailbox.js +24 -23
- package/dist/esm/client/messages.js +108 -32
- package/dist/esm/client/messaging/fanout.js +22 -12
- package/dist/esm/client/messaging/participants.js +6 -4
- package/dist/esm/client/persistence/WriteBehindPersistence.js +125 -0
- package/dist/esm/client/tokens/cs-token.js +46 -0
- package/dist/esm/client/tokens/tc-token.js +18 -0
- package/dist/esm/crypto/core/index.js +2 -2
- package/dist/esm/crypto/core/keys.js +1 -1
- package/dist/esm/crypto/core/nonce.js +2 -0
- package/dist/esm/crypto/core/primitives.js +0 -7
- package/dist/esm/crypto/core/random.js +22 -1
- package/dist/esm/crypto/curves/X25519.js +25 -6
- package/dist/esm/crypto/index.js +1 -0
- package/dist/esm/crypto/math/constants.js +12 -35
- package/dist/esm/crypto/math/edwards.js +174 -47
- package/dist/esm/crypto/math/fe.js +691 -0
- package/dist/esm/crypto/math/mod.js +10 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/infra/perf/BackgroundQueue.js +478 -0
- package/dist/esm/infra/perf/BoundedTaskQueue.js +3 -1
- package/dist/esm/infra/perf/PromiseDedup.js +20 -0
- package/dist/esm/infra/perf/SharedExclusiveGate.js +109 -0
- package/dist/esm/infra/perf/StoreLock.js +77 -0
- package/dist/esm/media/WaMediaCrypto.js +95 -13
- package/dist/esm/media/WaMediaTransferClient.js +39 -47
- package/dist/esm/media/constants.js +2 -1
- package/dist/esm/message/WaMessageClient.js +26 -19
- package/dist/esm/message/content.js +195 -9
- package/dist/esm/message/icdc.js +76 -0
- package/dist/esm/message/incoming.js +24 -12
- package/dist/esm/message/phash.js +3 -1
- package/dist/esm/message/reporting-token.js +14 -27
- package/dist/esm/protocol/appstate.js +9 -40
- package/dist/esm/protocol/browser.js +10 -18
- package/dist/esm/protocol/constants.js +5 -3
- package/dist/esm/protocol/defaults.js +6 -0
- package/dist/esm/protocol/index.js +1 -2
- package/dist/esm/protocol/jid.js +105 -36
- package/dist/esm/protocol/message.js +61 -1
- package/dist/esm/protocol/nodes.js +2 -0
- package/dist/esm/protocol/notification.js +3 -1
- package/dist/esm/protocol/privacy-token.js +17 -0
- package/dist/esm/protocol/privacy.js +55 -0
- package/dist/esm/protocol/stream.js +26 -1
- package/dist/esm/retry/codec.js +216 -0
- package/dist/esm/retry/constants.js +1 -1
- package/dist/esm/retry/index.js +2 -2
- package/dist/esm/retry/parse.js +50 -30
- package/dist/esm/retry/replay.js +11 -7
- package/dist/esm/retry/tracker.js +50 -12
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +49 -32
- package/dist/esm/signal/api/SignalDigestSyncApi.js +13 -9
- package/dist/esm/signal/api/SignalIdentitySyncApi.js +26 -11
- package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +18 -7
- package/dist/esm/signal/api/SignalRotateKeyApi.js +4 -2
- package/dist/esm/signal/api/SignalSessionSyncApi.js +16 -7
- package/dist/esm/signal/api/result-map.js +10 -0
- package/dist/esm/signal/constants.js +0 -4
- package/dist/esm/signal/crypto/WaAdvSignature.js +12 -6
- package/dist/esm/signal/{store/sqlite.js → encoding.js} +78 -24
- package/dist/esm/signal/group/SenderKeyCodec.js +3 -2
- package/dist/esm/signal/group/SenderKeyManager.js +125 -106
- package/dist/esm/signal/index.js +1 -0
- package/dist/esm/signal/registration/keygen.js +6 -2
- package/dist/esm/signal/registration/utils.js +1 -0
- package/dist/esm/signal/session/SignalProtocol.js +150 -74
- package/dist/esm/signal/session/resolver.js +137 -102
- package/dist/esm/store/contracts/privacy-token.store.js +1 -0
- package/dist/esm/store/createStore.js +101 -187
- package/dist/esm/store/index.js +1 -10
- package/dist/esm/store/locks/appstate.lock.js +26 -0
- package/dist/esm/store/locks/auth.lock.js +15 -0
- package/dist/esm/store/locks/contact.lock.js +20 -0
- package/dist/esm/store/locks/device-list.lock.js +20 -0
- package/dist/esm/store/locks/message.lock.js +21 -0
- package/dist/esm/store/locks/participants.lock.js +20 -0
- package/dist/esm/store/locks/privacy-token.lock.js +18 -0
- package/dist/esm/store/locks/retry.lock.js +29 -0
- package/dist/esm/store/locks/sender-key.lock.js +52 -0
- package/dist/esm/store/locks/signal.lock.js +63 -0
- package/dist/esm/store/locks/thread.lock.js +21 -0
- package/dist/esm/store/noop.store.js +1 -1
- package/dist/esm/store/providers/memory/appstate.store.js +22 -24
- package/dist/esm/store/providers/memory/device-list.store.js +10 -5
- package/dist/esm/store/providers/memory/privacy-token.store.js +43 -0
- package/dist/esm/store/providers/memory/retry.store.js +77 -2
- package/dist/esm/store/providers/memory/sender-key.store.js +6 -1
- package/dist/esm/store/providers/memory/signal.store.js +36 -19
- package/dist/esm/transport/WaComms.js +3 -1
- package/dist/esm/transport/WaWebSocket.js +0 -6
- package/dist/esm/transport/binary/constants.js +0 -30
- package/dist/esm/transport/binary/decoder.js +4 -4
- package/dist/esm/transport/binary/encoder.js +8 -15
- package/dist/esm/transport/binary/index.js +0 -1
- package/dist/esm/transport/node/WaNodeOrchestrator.js +25 -19
- package/dist/esm/transport/node/builders/business.js +129 -0
- package/dist/esm/transport/node/builders/global.js +370 -0
- package/dist/esm/transport/node/builders/index.js +5 -2
- package/dist/esm/transport/node/builders/message.js +63 -239
- package/dist/esm/transport/node/builders/pairing.js +0 -24
- package/dist/esm/transport/node/builders/privacy-token.js +41 -0
- package/dist/esm/transport/node/builders/privacy.js +48 -0
- package/dist/esm/transport/node/builders/profile.js +70 -0
- package/dist/esm/transport/node/builders/retry.js +10 -22
- package/dist/esm/transport/node/builders/usync.js +6 -2
- package/dist/esm/transport/node/helpers.js +19 -1
- package/dist/esm/transport/node/usync.js +3 -33
- package/dist/esm/transport/node/xml.js +35 -14
- package/dist/esm/transport/noise/WaClientPayload.js +10 -10
- package/dist/esm/transport/noise/WaNoiseCert.js +3 -3
- package/dist/esm/transport/noise/WaNoiseSession.js +64 -23
- package/dist/esm/transport/noise/WaNoiseSocket.js +8 -4
- package/dist/esm/transport/stream/parse.js +8 -4
- package/dist/esm/util/bytes.js +22 -18
- package/dist/esm/util/index.js +5 -0
- package/dist/esm/util/primitives.js +3 -2
- package/dist/index.js +7 -1
- package/dist/infra/perf/BackgroundQueue.js +482 -0
- package/dist/infra/perf/BoundedTaskQueue.js +3 -1
- package/dist/infra/perf/PromiseDedup.js +24 -0
- package/dist/infra/perf/SharedExclusiveGate.js +113 -0
- package/dist/infra/perf/StoreLock.js +81 -0
- package/dist/media/WaMediaCrypto.js +94 -12
- package/dist/media/WaMediaTransferClient.js +39 -47
- package/dist/media/constants.js +2 -1
- package/dist/message/WaMessageClient.js +26 -19
- package/dist/message/content.js +198 -9
- package/dist/message/icdc.js +81 -0
- package/dist/message/incoming.js +24 -12
- package/dist/message/phash.js +3 -1
- package/dist/message/reporting-token.js +14 -28
- package/dist/protocol/appstate.js +10 -41
- package/dist/protocol/browser.js +10 -18
- package/dist/protocol/constants.js +21 -2
- package/dist/protocol/defaults.js +6 -0
- package/dist/protocol/index.js +8 -5
- package/dist/protocol/jid.js +111 -36
- package/dist/protocol/message.js +62 -2
- package/dist/protocol/nodes.js +2 -0
- package/dist/protocol/notification.js +3 -1
- package/dist/protocol/privacy-token.js +20 -0
- package/dist/protocol/privacy.js +58 -0
- package/dist/protocol/stream.js +27 -2
- package/dist/retry/codec.js +220 -0
- package/dist/retry/constants.js +1 -1
- package/dist/retry/index.js +5 -5
- package/dist/retry/parse.js +51 -30
- package/dist/retry/replay.js +10 -6
- package/dist/retry/tracker.js +50 -12
- package/dist/signal/api/SignalDeviceSyncApi.js +48 -31
- package/dist/signal/api/SignalDigestSyncApi.js +13 -9
- package/dist/signal/api/SignalIdentitySyncApi.js +25 -10
- package/dist/signal/api/SignalMissingPreKeysSyncApi.js +17 -6
- package/dist/signal/api/SignalRotateKeyApi.js +4 -2
- package/dist/signal/api/SignalSessionSyncApi.js +16 -7
- package/dist/signal/api/result-map.js +13 -0
- package/dist/signal/constants.js +1 -5
- package/dist/signal/crypto/WaAdvSignature.js +11 -5
- package/dist/signal/{store/sqlite.js → encoding.js} +79 -25
- package/dist/signal/group/SenderKeyCodec.js +4 -3
- package/dist/signal/group/SenderKeyManager.js +125 -106
- package/dist/signal/index.js +13 -1
- package/dist/signal/registration/keygen.js +6 -2
- package/dist/signal/registration/utils.js +1 -0
- package/dist/signal/session/SignalProtocol.js +150 -74
- package/dist/signal/session/resolver.js +135 -100
- package/dist/store/contracts/privacy-token.store.js +2 -0
- package/dist/store/createStore.js +101 -187
- package/dist/store/index.js +15 -33
- package/dist/store/locks/appstate.lock.js +29 -0
- package/dist/store/locks/auth.lock.js +18 -0
- package/dist/store/locks/contact.lock.js +23 -0
- package/dist/store/locks/device-list.lock.js +23 -0
- package/dist/store/locks/message.lock.js +24 -0
- package/dist/store/locks/participants.lock.js +23 -0
- package/dist/store/locks/privacy-token.lock.js +21 -0
- package/dist/store/locks/retry.lock.js +32 -0
- package/dist/store/locks/sender-key.lock.js +55 -0
- package/dist/store/locks/signal.lock.js +66 -0
- package/dist/store/locks/thread.lock.js +24 -0
- package/dist/store/noop.store.js +1 -1
- package/dist/store/providers/memory/appstate.store.js +22 -24
- package/dist/store/providers/memory/device-list.store.js +10 -5
- package/dist/store/providers/memory/privacy-token.store.js +47 -0
- package/dist/store/providers/memory/retry.store.js +77 -2
- package/dist/store/providers/memory/sender-key.store.js +6 -1
- package/dist/store/providers/memory/signal.store.js +36 -19
- package/dist/transport/WaComms.js +3 -1
- package/dist/transport/WaWebSocket.js +0 -6
- package/dist/transport/binary/constants.js +1 -31
- package/dist/transport/binary/decoder.js +4 -4
- package/dist/transport/binary/encoder.js +8 -15
- package/dist/transport/binary/index.js +0 -4
- package/dist/transport/node/WaNodeOrchestrator.js +24 -18
- package/dist/transport/node/builders/business.js +137 -0
- package/dist/transport/node/builders/global.js +375 -0
- package/dist/transport/node/builders/index.js +18 -9
- package/dist/transport/node/builders/message.js +64 -245
- package/dist/transport/node/builders/pairing.js +0 -26
- package/dist/transport/node/builders/privacy-token.js +46 -0
- package/dist/transport/node/builders/privacy.js +55 -0
- package/dist/transport/node/builders/profile.js +78 -0
- package/dist/transport/node/builders/retry.js +9 -21
- package/dist/transport/node/builders/usync.js +6 -2
- package/dist/transport/node/helpers.js +20 -1
- package/dist/transport/node/usync.js +2 -32
- package/dist/transport/node/xml.js +35 -14
- package/dist/transport/noise/WaClientPayload.js +13 -13
- package/dist/transport/noise/WaNoiseCert.js +2 -2
- package/dist/transport/noise/WaNoiseSession.js +64 -23
- package/dist/transport/noise/WaNoiseSocket.js +8 -4
- package/dist/transport/stream/parse.js +7 -3
- package/dist/types/appstate/encoding.d.ts +7 -0
- package/dist/types/appstate/index.d.ts +3 -3
- package/dist/types/appstate/utils.d.ts +0 -2
- package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +1 -1
- package/dist/types/auth/index.d.ts +0 -2
- package/dist/types/auth/types.d.ts +1 -0
- package/dist/types/client/WaClient.d.ts +27 -12
- package/dist/types/client/WaClientFactory.d.ts +12 -4
- package/dist/types/client/connection/WaConnectionManager.d.ts +2 -0
- package/dist/types/client/coordinators/WaBusinessCoordinator.d.ts +57 -0
- package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +3 -1
- package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +14 -0
- package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +4 -0
- package/dist/types/client/coordinators/WaPrivacyCoordinator.d.ts +26 -0
- package/dist/types/client/coordinators/WaProfileCoordinator.d.ts +36 -0
- package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +6 -0
- package/dist/types/client/coordinators/WaStreamControlCoordinator.d.ts +3 -2
- package/dist/types/client/coordinators/WaTrustedContactTokenCoordinator.d.ts +45 -0
- package/dist/types/client/events/devices.d.ts +20 -0
- package/dist/types/client/events/identity.d.ts +9 -0
- package/dist/types/client/events/privacy-token.d.ts +7 -0
- package/dist/types/client/history-sync.d.ts +9 -6
- package/dist/types/client/incoming.d.ts +3 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/mailbox.d.ts +3 -5
- package/dist/types/client/messages.d.ts +1 -2
- package/dist/types/client/persistence/WriteBehindPersistence.d.ts +34 -0
- package/dist/types/client/tokens/cs-token.d.ts +10 -0
- package/dist/types/client/tokens/tc-token.d.ts +5 -0
- package/dist/types/client/types.d.ts +51 -3
- package/dist/types/crypto/core/index.d.ts +2 -2
- package/dist/types/crypto/core/nonce.d.ts +2 -0
- package/dist/types/crypto/core/primitives.d.ts +0 -1
- package/dist/types/crypto/core/random.d.ts +1 -0
- package/dist/types/crypto/index.d.ts +1 -0
- package/dist/types/crypto/math/constants.d.ts +4 -2
- package/dist/types/crypto/math/fe.d.ts +30 -0
- package/dist/types/crypto/math/mod.d.ts +0 -2
- package/dist/types/crypto/math/types.d.ts +11 -4
- package/dist/types/index.d.ts +5 -3
- package/dist/types/infra/perf/BackgroundQueue.d.ts +58 -0
- package/dist/types/infra/perf/PromiseDedup.d.ts +4 -0
- package/dist/types/infra/perf/SharedExclusiveGate.d.ts +17 -0
- package/dist/types/infra/perf/StoreLock.d.ts +10 -0
- package/dist/types/media/WaMediaCrypto.d.ts +3 -2
- package/dist/types/media/WaMediaTransferClient.d.ts +3 -12
- package/dist/types/media/constants.d.ts +1 -1
- package/dist/types/media/index.d.ts +1 -1
- package/dist/types/media/types.d.ts +10 -2
- package/dist/types/message/content.d.ts +8 -0
- package/dist/types/message/icdc.d.ts +13 -0
- package/dist/types/message/reporting-token.d.ts +0 -1
- package/dist/types/message/types.d.ts +45 -6
- package/dist/types/protocol/appstate.d.ts +0 -11
- package/dist/types/protocol/constants.d.ts +7 -3
- package/dist/types/protocol/defaults.d.ts +6 -0
- package/dist/types/protocol/index.d.ts +1 -2
- package/dist/types/protocol/jid.d.ts +19 -2
- package/dist/types/protocol/message.d.ts +60 -0
- package/dist/types/protocol/nodes.d.ts +2 -0
- package/dist/types/protocol/notification.d.ts +2 -0
- package/dist/types/protocol/privacy-token.d.ts +17 -0
- package/dist/types/protocol/privacy.d.ts +75 -0
- package/dist/types/protocol/stream.d.ts +30 -0
- package/dist/types/retry/codec.d.ts +3 -0
- package/dist/types/retry/index.d.ts +3 -3
- package/dist/types/retry/parse.d.ts +5 -2
- package/dist/types/retry/tracker.d.ts +1 -0
- package/dist/types/retry/types.d.ts +6 -1
- package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +2 -1
- package/dist/types/signal/api/SignalDigestSyncApi.d.ts +6 -0
- package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +2 -0
- package/dist/types/signal/api/SignalRotateKeyApi.d.ts +4 -5
- package/dist/types/signal/api/SignalSessionSyncApi.d.ts +8 -6
- package/dist/types/signal/api/result-map.d.ts +1 -0
- package/dist/types/signal/constants.d.ts +0 -3
- package/dist/types/signal/{store/sqlite.d.ts → encoding.d.ts} +3 -3
- package/dist/types/signal/group/SenderKeyManager.d.ts +10 -5
- package/dist/types/signal/index.d.ts +2 -0
- package/dist/types/signal/session/SignalProtocol.d.ts +10 -4
- package/dist/types/signal/session/resolver.d.ts +7 -2
- package/dist/types/store/contracts/appstate.store.d.ts +1 -1
- package/dist/types/store/contracts/privacy-token.store.d.ts +16 -0
- package/dist/types/store/contracts/retry.store.d.ts +7 -0
- package/dist/types/store/contracts/signal.store.d.ts +7 -0
- package/dist/types/store/createStore.d.ts +1 -1
- package/dist/types/store/index.d.ts +5 -13
- package/dist/types/store/locks/appstate.lock.d.ts +3 -0
- package/dist/types/store/locks/auth.lock.d.ts +3 -0
- package/dist/types/store/locks/contact.lock.d.ts +3 -0
- package/dist/types/store/locks/device-list.lock.d.ts +2 -0
- package/dist/types/store/locks/message.lock.d.ts +3 -0
- package/dist/types/store/locks/participants.lock.d.ts +2 -0
- package/dist/types/store/locks/privacy-token.lock.d.ts +2 -0
- package/dist/types/store/locks/retry.lock.d.ts +2 -0
- package/dist/types/store/locks/sender-key.lock.d.ts +3 -0
- package/dist/types/store/locks/signal.lock.d.ts +3 -0
- package/dist/types/store/locks/thread.lock.d.ts +3 -0
- package/dist/types/store/providers/memory/appstate.store.d.ts +1 -1
- package/dist/types/store/providers/memory/privacy-token.store.d.ts +13 -0
- package/dist/types/store/providers/memory/retry.store.d.ts +8 -0
- package/dist/types/store/providers/memory/signal.store.d.ts +2 -1
- package/dist/types/store/types.d.ts +49 -61
- package/dist/types/transport/WaWebSocket.d.ts +0 -1
- package/dist/types/transport/binary/constants.d.ts +0 -30
- package/dist/types/transport/binary/index.d.ts +0 -1
- package/dist/types/transport/node/WaNodeOrchestrator.d.ts +3 -4
- package/dist/types/transport/node/builders/business.d.ts +29 -0
- package/dist/types/transport/node/builders/global.d.ts +102 -0
- package/dist/types/transport/node/builders/index.d.ts +5 -2
- package/dist/types/transport/node/builders/message.d.ts +8 -7
- package/dist/types/transport/node/builders/pairing.d.ts +0 -2
- package/dist/types/transport/node/builders/privacy-token.d.ts +9 -0
- package/dist/types/transport/node/builders/privacy.d.ts +7 -0
- package/dist/types/transport/node/builders/profile.d.ts +8 -0
- package/dist/types/transport/node/builders/retry.d.ts +0 -1
- package/dist/types/transport/node/helpers.d.ts +5 -0
- package/dist/types/transport/noise/WaNoiseSession.d.ts +3 -2
- package/dist/types/transport/noise/WaNoiseSocket.d.ts +4 -2
- package/dist/types/util/bytes.d.ts +1 -1
- package/dist/types/util/index.d.ts +5 -0
- package/dist/types/util/primitives.d.ts +0 -1
- package/dist/util/bytes.js +22 -18
- package/dist/util/index.js +23 -0
- package/dist/util/primitives.js +2 -2
- package/package.json +29 -7
- package/proto/index.js +1 -1
- package/dist/crypto/core/constants.js +0 -4
- package/dist/esm/crypto/core/constants.js +0 -1
- package/dist/esm/retry/outbound.js +0 -82
- package/dist/esm/store/providers/sqlite/BaseSqliteStore.js +0 -37
- package/dist/esm/store/providers/sqlite/appstate.store.js +0 -250
- package/dist/esm/store/providers/sqlite/auth.store.js +0 -176
- package/dist/esm/store/providers/sqlite/connection.js +0 -245
- package/dist/esm/store/providers/sqlite/contact.store.js +0 -74
- package/dist/esm/store/providers/sqlite/device-list.store.js +0 -127
- package/dist/esm/store/providers/sqlite/message.store.js +0 -132
- package/dist/esm/store/providers/sqlite/migrations.js +0 -347
- package/dist/esm/store/providers/sqlite/participants.store.js +0 -77
- package/dist/esm/store/providers/sqlite/retry.store.js +0 -141
- package/dist/esm/store/providers/sqlite/sender-key.store.js +0 -198
- package/dist/esm/store/providers/sqlite/signal.store.js +0 -435
- package/dist/esm/store/providers/sqlite/table-names.js +0 -107
- package/dist/esm/store/providers/sqlite/thread.store.js +0 -85
- package/dist/retry/outbound.js +0 -87
- package/dist/store/providers/sqlite/BaseSqliteStore.js +0 -41
- package/dist/store/providers/sqlite/appstate.store.js +0 -254
- package/dist/store/providers/sqlite/auth.store.js +0 -180
- package/dist/store/providers/sqlite/connection.js +0 -281
- package/dist/store/providers/sqlite/contact.store.js +0 -78
- package/dist/store/providers/sqlite/device-list.store.js +0 -131
- package/dist/store/providers/sqlite/message.store.js +0 -136
- package/dist/store/providers/sqlite/migrations.js +0 -350
- package/dist/store/providers/sqlite/participants.store.js +0 -81
- package/dist/store/providers/sqlite/retry.store.js +0 -145
- package/dist/store/providers/sqlite/sender-key.store.js +0 -202
- package/dist/store/providers/sqlite/signal.store.js +0 -439
- package/dist/store/providers/sqlite/table-names.js +0 -113
- package/dist/store/providers/sqlite/thread.store.js +0 -89
- package/dist/types/appstate/store/sqlite.d.ts +0 -7
- package/dist/types/crypto/core/constants.d.ts +0 -1
- package/dist/types/retry/outbound.d.ts +0 -4
- package/dist/types/store/providers/sqlite/BaseSqliteStore.d.ts +0 -12
- package/dist/types/store/providers/sqlite/appstate.store.d.ts +0 -17
- package/dist/types/store/providers/sqlite/auth.store.d.ts +0 -10
- package/dist/types/store/providers/sqlite/connection.d.ts +0 -10
- package/dist/types/store/providers/sqlite/contact.store.d.ts +0 -12
- package/dist/types/store/providers/sqlite/device-list.store.d.ts +0 -15
- package/dist/types/store/providers/sqlite/message.store.d.ts +0 -13
- package/dist/types/store/providers/sqlite/migrations.d.ts +0 -3
- package/dist/types/store/providers/sqlite/participants.store.d.ts +0 -12
- package/dist/types/store/providers/sqlite/retry.store.d.ts +0 -15
- package/dist/types/store/providers/sqlite/sender-key.store.d.ts +0 -24
- package/dist/types/store/providers/sqlite/signal.store.d.ts +0 -53
- package/dist/types/store/providers/sqlite/table-names.d.ts +0 -5
- package/dist/types/store/providers/sqlite/thread.store.d.ts +0 -13
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { proto } from '../../proto.js';
|
|
2
2
|
import { WA_MESSAGE_TAGS } from '../../protocol/constants.js';
|
|
3
|
-
import { isGroupOrBroadcastJid, normalizeDeviceJid,
|
|
3
|
+
import { isGroupOrBroadcastJid, normalizeDeviceJid, parseJidFull } from '../../protocol/jid.js';
|
|
4
4
|
import { MAX_RETRY_ATTEMPTS, RETRY_KEYS_MIN_COUNT, RETRY_OUTBOUND_TTL_MS, RETRY_REASON } from '../../retry/constants.js';
|
|
5
|
-
import { pickRetryStateMax } from '../../retry/
|
|
6
|
-
import { parseRetryReceiptRequest } from '../../retry/parse.js';
|
|
5
|
+
import { parseRetryReceiptRequest, pickRetryStateMax } from '../../retry/parse.js';
|
|
7
6
|
import { mapRetryReasonFromError } from '../../retry/reason.js';
|
|
8
7
|
import { WaRetryReplayService } from '../../retry/replay.js';
|
|
9
8
|
import { generatePreKeyPair } from '../../signal/registration/keygen.js';
|
|
10
|
-
import {
|
|
9
|
+
import { buildAckNode } from '../../transport/node/builders/global.js';
|
|
11
10
|
import { buildRetryReceiptNode } from '../../transport/node/builders/retry.js';
|
|
11
|
+
import { uint8Equal } from '../../util/bytes.js';
|
|
12
|
+
import { setBoundedMapEntry } from '../../util/collections.js';
|
|
12
13
|
import { toError } from '../../util/primitives.js';
|
|
13
14
|
const RETRY_CLEANUP_INTERVAL_MS = 30000;
|
|
15
|
+
const RETRY_SESSION_BASE_KEY_CACHE_MAX_ENTRIES = 8192;
|
|
14
16
|
function getRetryReasonName(code) {
|
|
15
17
|
if (code === undefined) {
|
|
16
18
|
return undefined;
|
|
@@ -52,6 +54,7 @@ export class WaRetryCoordinator {
|
|
|
52
54
|
getCurrentSignedIdentity: this.getCurrentSignedIdentity
|
|
53
55
|
});
|
|
54
56
|
this.retryProcessingByMessageId = new Map();
|
|
57
|
+
this.retrySessionBaseKeys = new Map();
|
|
55
58
|
}
|
|
56
59
|
async onDecryptFailure(context, error) {
|
|
57
60
|
try {
|
|
@@ -76,12 +79,23 @@ export class WaRetryCoordinator {
|
|
|
76
79
|
if (!this.isRetryReceiptNode(receiptNode)) {
|
|
77
80
|
return;
|
|
78
81
|
}
|
|
82
|
+
let shouldAck = false;
|
|
79
83
|
try {
|
|
80
84
|
await this.maybeCleanupRetryStore(Date.now());
|
|
81
|
-
const
|
|
85
|
+
const expectedToJids = [];
|
|
86
|
+
const meJid = this.getCurrentMeJid()?.trim();
|
|
87
|
+
const meLid = this.getCurrentMeLid()?.trim();
|
|
88
|
+
if (meJid) {
|
|
89
|
+
expectedToJids.push(meJid);
|
|
90
|
+
}
|
|
91
|
+
if (meLid) {
|
|
92
|
+
expectedToJids.push(meLid);
|
|
93
|
+
}
|
|
94
|
+
const request = parseRetryReceiptRequest(receiptNode, expectedToJids.length > 0 ? { expectedToJids } : undefined);
|
|
82
95
|
if (!request) {
|
|
83
96
|
return;
|
|
84
97
|
}
|
|
98
|
+
shouldAck = true;
|
|
85
99
|
await this.handleParsedRetryRequest(receiptNode, request);
|
|
86
100
|
}
|
|
87
101
|
catch (error) {
|
|
@@ -93,7 +107,9 @@ export class WaRetryCoordinator {
|
|
|
93
107
|
});
|
|
94
108
|
}
|
|
95
109
|
finally {
|
|
96
|
-
|
|
110
|
+
if (shouldAck) {
|
|
111
|
+
await this.sendRetryAckSafe(receiptNode);
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
isRetryReceiptNode(node) {
|
|
@@ -129,7 +145,6 @@ export class WaRetryCoordinator {
|
|
|
129
145
|
to: context.from,
|
|
130
146
|
participant: context.participant,
|
|
131
147
|
recipient: context.recipient,
|
|
132
|
-
from: this.getCurrentMeJid() ?? undefined,
|
|
133
148
|
originalMsgId: context.stanzaId,
|
|
134
149
|
retryCount: prepared.retryCount,
|
|
135
150
|
t: prepared.timestamp,
|
|
@@ -200,8 +215,9 @@ export class WaRetryCoordinator {
|
|
|
200
215
|
let requesterAddress;
|
|
201
216
|
let requesterNormalizedDeviceJid;
|
|
202
217
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
const requesterParsed = parseJidFull(requesterJid);
|
|
219
|
+
requesterAddress = requesterParsed.address;
|
|
220
|
+
requesterNormalizedDeviceJid = requesterParsed.normalizedJid;
|
|
205
221
|
}
|
|
206
222
|
catch (error) {
|
|
207
223
|
this.logger.info('retry request rejected: invalid requester jid', {
|
|
@@ -277,20 +293,36 @@ export class WaRetryCoordinator {
|
|
|
277
293
|
if (!nextState) {
|
|
278
294
|
return;
|
|
279
295
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
await this.runRetryTaskSerialized(messageId, async () => {
|
|
297
|
+
const current = await this.retryStore.getOutboundMessage(messageId);
|
|
298
|
+
if (!current) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const nowMs = Date.now();
|
|
302
|
+
const expiresAtMs = nowMs + this.retryTtlMs;
|
|
303
|
+
const merged = pickRetryStateMax(current.state, nextState);
|
|
304
|
+
if (merged !== current.state) {
|
|
305
|
+
await this.retryStore.updateOutboundMessageState(messageId, merged, nowMs, expiresAtMs);
|
|
306
|
+
}
|
|
307
|
+
const requesterJid = receiptNode.attrs.participant ?? receiptNode.attrs.from;
|
|
308
|
+
if (!requesterJid) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
await this.retryStore.markOutboundRequesterDelivered(messageId, normalizeDeviceJid(requesterJid), nowMs, expiresAtMs);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
this.logger.warn('failed to update outbound requester delivery state', {
|
|
316
|
+
id: messageId,
|
|
317
|
+
requester: requesterJid,
|
|
318
|
+
message: toError(error).message
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
});
|
|
290
322
|
}
|
|
291
323
|
async runRetryTaskSerialized(messageId, task) {
|
|
292
324
|
const previous = this.retryProcessingByMessageId.get(messageId) ?? Promise.resolve();
|
|
293
|
-
const current = previous.then(task
|
|
325
|
+
const current = previous.then(task);
|
|
294
326
|
const tracker = current.then(() => undefined, () => undefined);
|
|
295
327
|
this.retryProcessingByMessageId.set(messageId, tracker);
|
|
296
328
|
try {
|
|
@@ -331,15 +363,45 @@ export class WaRetryCoordinator {
|
|
|
331
363
|
};
|
|
332
364
|
}
|
|
333
365
|
async updateLocalSessionFromRetryRequest(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid) {
|
|
334
|
-
await
|
|
335
|
-
|
|
336
|
-
|
|
366
|
+
const [, currentSession] = await Promise.all([
|
|
367
|
+
this.markRetryRequesterSenderKeyAsStale(request, requesterJid, requesterAddress),
|
|
368
|
+
this.signalStore.getSession(requesterAddress)
|
|
369
|
+
]);
|
|
370
|
+
const regIdMismatch = !!currentSession && request.regId > 0 && currentSession.remote.regId !== request.regId;
|
|
371
|
+
if (regIdMismatch && !request.keyBundle) {
|
|
337
372
|
await this.signalStore.deleteSession(requesterAddress);
|
|
338
373
|
}
|
|
339
374
|
if (request.keyBundle) {
|
|
340
375
|
if (!request.keyBundle.key || !request.keyBundle.skey.signature) {
|
|
341
376
|
return false;
|
|
342
377
|
}
|
|
378
|
+
if (request.offline) {
|
|
379
|
+
if (!currentSession) {
|
|
380
|
+
this.logger.info('retry request rejected: offline retry missing existing session', {
|
|
381
|
+
id: request.stanzaId,
|
|
382
|
+
originalMsgId: request.originalMsgId,
|
|
383
|
+
requester: requesterJid,
|
|
384
|
+
remoteRetryCount: request.retryCount,
|
|
385
|
+
...getRemoteRetryReasonLogFields(request.retryReason)
|
|
386
|
+
});
|
|
387
|
+
await this.signalStore.deleteSession(requesterAddress);
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (regIdMismatch) {
|
|
391
|
+
this.logger.info('retry request rejected: offline retry registration id mismatch', {
|
|
392
|
+
id: request.stanzaId,
|
|
393
|
+
originalMsgId: request.originalMsgId,
|
|
394
|
+
requester: requesterJid,
|
|
395
|
+
remoteRetryCount: request.retryCount,
|
|
396
|
+
...getRemoteRetryReasonLogFields(request.retryReason)
|
|
397
|
+
});
|
|
398
|
+
await this.signalStore.deleteSession(requesterAddress);
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (regIdMismatch) {
|
|
403
|
+
await this.signalStore.deleteSession(requesterAddress);
|
|
404
|
+
}
|
|
343
405
|
await this.signalProtocol.establishOutgoingSession(requesterAddress, {
|
|
344
406
|
regId: request.regId,
|
|
345
407
|
identity: request.keyBundle.identity,
|
|
@@ -353,12 +415,45 @@ export class WaRetryCoordinator {
|
|
|
353
415
|
publicKey: request.keyBundle.key.publicKey
|
|
354
416
|
}
|
|
355
417
|
});
|
|
418
|
+
return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
|
|
419
|
+
}
|
|
420
|
+
const sessionStillExists = currentSession !== null && !regIdMismatch;
|
|
421
|
+
if (sessionStillExists) {
|
|
422
|
+
return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
|
|
423
|
+
}
|
|
424
|
+
const fetched = await this.fetchMissingPreKeysSession(requesterJid, requesterAddress, requesterNormalizedDeviceJid, request.regId);
|
|
425
|
+
if (!fetched) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
await this.signalProtocol.establishOutgoingSession(requesterAddress, fetched);
|
|
429
|
+
return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
|
|
430
|
+
}
|
|
431
|
+
async applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid) {
|
|
432
|
+
if (request.retryCount < 2) {
|
|
356
433
|
return true;
|
|
357
434
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
435
|
+
const currentSession = await this.signalStore.getSession(requesterAddress);
|
|
436
|
+
const sessionBaseKey = currentSession?.aliceBaseKey ?? null;
|
|
437
|
+
if (!sessionBaseKey) {
|
|
360
438
|
return true;
|
|
361
439
|
}
|
|
440
|
+
const expiresAtMs = Date.now() + this.retryTtlMs;
|
|
441
|
+
if (request.retryCount === 2) {
|
|
442
|
+
this.setRetrySessionBaseKey(request.originalMsgId, requesterNormalizedDeviceJid, sessionBaseKey, expiresAtMs);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
const saved = this.getRetrySessionBaseKey(request.originalMsgId, requesterNormalizedDeviceJid);
|
|
446
|
+
if (!saved || !uint8Equal(saved.baseKey, sessionBaseKey)) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
await this.signalStore.deleteSession(requesterAddress);
|
|
450
|
+
this.logger.info('retry request forcing session refresh due to repeated base key', {
|
|
451
|
+
id: request.stanzaId,
|
|
452
|
+
originalMsgId: request.originalMsgId,
|
|
453
|
+
requester: requesterJid,
|
|
454
|
+
remoteRetryCount: request.retryCount,
|
|
455
|
+
...getRemoteRetryReasonLogFields(request.retryReason)
|
|
456
|
+
});
|
|
362
457
|
const fetched = await this.fetchMissingPreKeysSession(requesterJid, requesterAddress, requesterNormalizedDeviceJid, request.regId);
|
|
363
458
|
if (!fetched) {
|
|
364
459
|
return false;
|
|
@@ -431,13 +526,35 @@ export class WaRetryCoordinator {
|
|
|
431
526
|
if (outbound.state === 'ineligible') {
|
|
432
527
|
return { authorized: false, reason: `state_${outbound.state}` };
|
|
433
528
|
}
|
|
529
|
+
let requesterStatus = null;
|
|
530
|
+
try {
|
|
531
|
+
requesterStatus = await this.retryStore.getOutboundRequesterStatus(outbound.messageId, requesterNormalizedDeviceJid);
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
this.logger.warn('failed to resolve outbound requester status from retry store', {
|
|
535
|
+
id: request.stanzaId,
|
|
536
|
+
originalMsgId: request.originalMsgId,
|
|
537
|
+
requester: requesterJid,
|
|
538
|
+
message: toError(error).message
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (requesterStatus) {
|
|
542
|
+
if (!requesterStatus.eligible) {
|
|
543
|
+
return { authorized: false, reason: 'requester_device_not_eligible' };
|
|
544
|
+
}
|
|
545
|
+
if (requesterStatus.delivered) {
|
|
546
|
+
return { authorized: false, reason: 'requester_already_delivered' };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
434
549
|
const isGroupOutbound = isGroupOrBroadcastJid(outbound.toJid);
|
|
435
550
|
if (!isGroupOutbound && (outbound.state === 'read' || outbound.state === 'played')) {
|
|
436
551
|
return { authorized: false, reason: `state_${outbound.state}` };
|
|
437
552
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
553
|
+
if (!requesterStatus) {
|
|
554
|
+
const requesterAuthorized = await this.isRequesterAuthorizedDevice(requesterJid, requesterAddress, requesterNormalizedDeviceJid);
|
|
555
|
+
if (!requesterAuthorized) {
|
|
556
|
+
return { authorized: false, reason: 'requester_device_not_authorized' };
|
|
557
|
+
}
|
|
441
558
|
}
|
|
442
559
|
return { authorized: true };
|
|
443
560
|
}
|
|
@@ -495,7 +612,11 @@ export class WaRetryCoordinator {
|
|
|
495
612
|
return;
|
|
496
613
|
}
|
|
497
614
|
try {
|
|
498
|
-
await this.sendNode(
|
|
615
|
+
await this.sendNode(buildAckNode({
|
|
616
|
+
kind: 'receipt',
|
|
617
|
+
node: receiptNode,
|
|
618
|
+
retryType: true
|
|
619
|
+
}));
|
|
499
620
|
}
|
|
500
621
|
catch (error) {
|
|
501
622
|
this.logger.warn('failed to send retry ack', {
|
|
@@ -511,6 +632,7 @@ export class WaRetryCoordinator {
|
|
|
511
632
|
return;
|
|
512
633
|
}
|
|
513
634
|
this.nextRetryCleanupAtMs = nowMs + RETRY_CLEANUP_INTERVAL_MS;
|
|
635
|
+
this.cleanupRetrySessionBaseKeys(nowMs);
|
|
514
636
|
try {
|
|
515
637
|
await this.retryStore.cleanupExpired(nowMs);
|
|
516
638
|
}
|
|
@@ -520,4 +642,34 @@ export class WaRetryCoordinator {
|
|
|
520
642
|
});
|
|
521
643
|
}
|
|
522
644
|
}
|
|
645
|
+
retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid) {
|
|
646
|
+
return `${originalMsgId}|${requesterNormalizedDeviceJid}`;
|
|
647
|
+
}
|
|
648
|
+
setRetrySessionBaseKey(originalMsgId, requesterNormalizedDeviceJid, baseKey, expiresAtMs) {
|
|
649
|
+
const key = this.retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid);
|
|
650
|
+
setBoundedMapEntry(this.retrySessionBaseKeys, key, {
|
|
651
|
+
baseKey: Uint8Array.from(baseKey),
|
|
652
|
+
expiresAtMs
|
|
653
|
+
}, RETRY_SESSION_BASE_KEY_CACHE_MAX_ENTRIES);
|
|
654
|
+
}
|
|
655
|
+
getRetrySessionBaseKey(originalMsgId, requesterNormalizedDeviceJid) {
|
|
656
|
+
const key = this.retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid);
|
|
657
|
+
const entry = this.retrySessionBaseKeys.get(key);
|
|
658
|
+
if (!entry) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
if (entry.expiresAtMs <= Date.now()) {
|
|
662
|
+
this.retrySessionBaseKeys.delete(key);
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return entry;
|
|
666
|
+
}
|
|
667
|
+
cleanupRetrySessionBaseKeys(nowMs) {
|
|
668
|
+
for (const [key, entry] of this.retrySessionBaseKeys) {
|
|
669
|
+
if (entry.expiresAtMs > nowMs) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
this.retrySessionBaseKeys.delete(key);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
523
675
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WA_DISCONNECT_REASONS, WA_STREAM_SIGNALING } from '../../protocol/constants.js';
|
|
1
|
+
import { WA_CONNECTION_REASONS, WA_DISCONNECT_REASONS, WA_STREAM_SIGNALING } from '../../protocol/constants.js';
|
|
2
2
|
import { toError } from '../../util/primitives.js';
|
|
3
3
|
export function createStreamControlHandler(options) {
|
|
4
4
|
const { logger, getComms, clearPendingQueries, clearMediaConnCache, disconnect, clearStoredCredentials, connect } = options;
|
|
@@ -16,7 +16,7 @@ export function createStreamControlHandler(options) {
|
|
|
16
16
|
const restartBackendAfterStreamControl = async (reason) => {
|
|
17
17
|
logger.info('restarting backend after stream control', { reason });
|
|
18
18
|
try {
|
|
19
|
-
await connect();
|
|
19
|
+
await connect(WA_CONNECTION_REASONS.RECONNECTED);
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
22
|
logger.warn('failed to restart backend after stream control', {
|
|
@@ -43,29 +43,36 @@ export function createStreamControlHandler(options) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
const stopCommsImmediately = () => {
|
|
47
|
+
void getComms()?.stopComms();
|
|
48
|
+
};
|
|
46
49
|
const forceLoginDueToStreamError = async (code) => {
|
|
47
|
-
|
|
50
|
+
const reason = WA_DISCONNECT_REASONS.STREAM_ERROR_FORCE_LOGIN;
|
|
51
|
+
stopCommsImmediately();
|
|
52
|
+
await runStreamControlLifecycle(reason, async () => {
|
|
48
53
|
logger.warn('received forced login stream error; starting login lifecycle', {
|
|
49
54
|
code
|
|
50
55
|
});
|
|
51
|
-
await disconnect();
|
|
56
|
+
await disconnect(reason, true, code);
|
|
52
57
|
await clearStoredCredentials();
|
|
53
|
-
await restartBackendAfterStreamControl(
|
|
58
|
+
await restartBackendAfterStreamControl(reason);
|
|
54
59
|
});
|
|
55
60
|
};
|
|
56
|
-
const disconnectDueToStreamError = async (reason) => {
|
|
61
|
+
const disconnectDueToStreamError = async (reason, code) => {
|
|
62
|
+
stopCommsImmediately();
|
|
57
63
|
await runStreamControlLifecycle(reason, async () => {
|
|
58
64
|
logger.warn('disconnecting due to stream control node', { reason });
|
|
59
|
-
await disconnect();
|
|
65
|
+
await disconnect(reason, false, code);
|
|
60
66
|
});
|
|
61
67
|
};
|
|
62
|
-
const logoutDueToStreamError = async (reason, shouldRestartBackend) => {
|
|
68
|
+
const logoutDueToStreamError = async (reason, code, shouldRestartBackend) => {
|
|
69
|
+
stopCommsImmediately();
|
|
63
70
|
await runStreamControlLifecycle(reason, async () => {
|
|
64
71
|
logger.warn('logging out due to stream control node', {
|
|
65
72
|
reason,
|
|
66
73
|
shouldRestartBackend
|
|
67
74
|
});
|
|
68
|
-
await disconnect();
|
|
75
|
+
await disconnect(reason, true, code);
|
|
69
76
|
await clearStoredCredentials();
|
|
70
77
|
if (shouldRestartBackend) {
|
|
71
78
|
await restartBackendAfterStreamControl(reason);
|
|
@@ -86,7 +93,7 @@ export function createStreamControlHandler(options) {
|
|
|
86
93
|
return;
|
|
87
94
|
}
|
|
88
95
|
if (result.code === WA_STREAM_SIGNALING.FORCE_LOGOUT_CODE) {
|
|
89
|
-
await logoutDueToStreamError(
|
|
96
|
+
await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_FORCE_LOGOUT, result.code, true);
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -94,11 +101,11 @@ export function createStreamControlHandler(options) {
|
|
|
94
101
|
return;
|
|
95
102
|
case 'stream_error_replaced':
|
|
96
103
|
logger.warn('received stream:error replaced, stopping client');
|
|
97
|
-
await disconnectDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_REPLACED);
|
|
104
|
+
await disconnectDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_REPLACED, null);
|
|
98
105
|
return;
|
|
99
106
|
case 'stream_error_device_removed':
|
|
100
107
|
logger.warn('received stream:error device removed, logging out');
|
|
101
|
-
await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_DEVICE_REMOVED, false);
|
|
108
|
+
await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_DEVICE_REMOVED, null, false);
|
|
102
109
|
return;
|
|
103
110
|
case 'stream_error_ack':
|
|
104
111
|
logger.warn('received stream:error ack', { id: result.id });
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { CsTokenGenerator } from '../tokens/cs-token.js';
|
|
2
|
+
import { isTokenExpired, shouldSendNewToken, clampDuration } from '../tokens/tc-token.js';
|
|
3
|
+
import { PromiseDedup } from '../../infra/perf/PromiseDedup.js';
|
|
4
|
+
import { WA_PRIVACY_TOKEN_TYPES, WA_TC_TOKEN_DEFAULTS } from '../../protocol/privacy-token.js';
|
|
5
|
+
import { buildPrivacyTokenIqNode, buildTcTokenMessageNode, buildCsTokenMessageNode } from '../../transport/node/builders/privacy-token.js';
|
|
6
|
+
import { toError } from '../../util/primitives.js';
|
|
7
|
+
const NCT_SALT_SENTINEL_JID = '__nct_salt__';
|
|
8
|
+
export class WaTrustedContactTokenCoordinator {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.logger = options.logger;
|
|
11
|
+
this.store = options.store;
|
|
12
|
+
this.runtime = options.runtime;
|
|
13
|
+
const maxDurationS = options.maxDurationS ?? WA_TC_TOKEN_DEFAULTS.MAX_DURATION_S;
|
|
14
|
+
this.config = {
|
|
15
|
+
durationS: clampDuration(options.durationS ?? WA_TC_TOKEN_DEFAULTS.DURATION_S, maxDurationS),
|
|
16
|
+
numBuckets: options.numBuckets ?? WA_TC_TOKEN_DEFAULTS.NUM_BUCKETS,
|
|
17
|
+
senderDurationS: clampDuration(options.senderDurationS ?? WA_TC_TOKEN_DEFAULTS.SENDER_DURATION_S, maxDurationS),
|
|
18
|
+
senderNumBuckets: options.senderNumBuckets ?? WA_TC_TOKEN_DEFAULTS.SENDER_NUM_BUCKETS,
|
|
19
|
+
maxDurationS
|
|
20
|
+
};
|
|
21
|
+
this.csTokenGenerator = new CsTokenGenerator();
|
|
22
|
+
this.senderTokenDedup = new PromiseDedup();
|
|
23
|
+
this.cachedNctSalt = null;
|
|
24
|
+
this.nctSaltHydrated = false;
|
|
25
|
+
}
|
|
26
|
+
async resolveTokenForMessage(recipientJid) {
|
|
27
|
+
const record = await this.store.getByJid(recipientJid);
|
|
28
|
+
const nowS = Math.floor(Date.now() / 1000);
|
|
29
|
+
if (record?.tcToken &&
|
|
30
|
+
record.tcTokenTimestamp !== undefined &&
|
|
31
|
+
!isTokenExpired(record.tcTokenTimestamp, nowS, this.config.durationS, this.config.numBuckets)) {
|
|
32
|
+
return buildTcTokenMessageNode(record.tcToken);
|
|
33
|
+
}
|
|
34
|
+
const nctSalt = await this.getNctSalt();
|
|
35
|
+
if (!nctSalt) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const meLid = this.runtime.getCurrentMeLid();
|
|
39
|
+
if (!meLid) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const hash = await this.csTokenGenerator.generate(nctSalt, meLid);
|
|
43
|
+
return buildCsTokenMessageNode(hash);
|
|
44
|
+
}
|
|
45
|
+
async handleIncomingToken(fromJid, tokens) {
|
|
46
|
+
const nowMs = Date.now();
|
|
47
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
48
|
+
const token = tokens[i];
|
|
49
|
+
if (token.type !== WA_PRIVACY_TOKEN_TYPES.TRUSTED_CONTACT) {
|
|
50
|
+
this.logger.warn('ignoring unknown privacy token type', { type: token.type });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
await this.store.upsert({
|
|
54
|
+
jid: fromJid,
|
|
55
|
+
tcToken: token.tokenBytes,
|
|
56
|
+
tcTokenTimestamp: token.timestampS,
|
|
57
|
+
updatedAtMs: nowMs
|
|
58
|
+
});
|
|
59
|
+
this.runtime.emitEvent('privacy_token_update', {
|
|
60
|
+
jid: fromJid,
|
|
61
|
+
timestampS: token.timestampS,
|
|
62
|
+
type: token.type,
|
|
63
|
+
source: 'notification'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async maybeIssueSenderToken(recipientJid) {
|
|
68
|
+
return this.senderTokenDedup.run(recipientJid, async () => {
|
|
69
|
+
const nowS = Math.floor(Date.now() / 1000);
|
|
70
|
+
const record = await this.store.getByJid(recipientJid);
|
|
71
|
+
const senderTimestampS = record?.tcTokenSenderTimestamp;
|
|
72
|
+
if (senderTimestampS !== undefined && senderTimestampS > 0) {
|
|
73
|
+
if (!shouldSendNewToken(senderTimestampS, nowS, this.config.senderDurationS)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await this.issuePrivacyToken(recipientJid, nowS);
|
|
78
|
+
await this.store.upsert({
|
|
79
|
+
jid: recipientJid,
|
|
80
|
+
tcTokenSenderTimestamp: nowS,
|
|
81
|
+
updatedAtMs: Date.now()
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async reissueOnIdentityChange(jid) {
|
|
86
|
+
const record = await this.store.getByJid(jid);
|
|
87
|
+
if (!record?.tcTokenSenderTimestamp) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const nowS = Math.floor(Date.now() / 1000);
|
|
91
|
+
if (isTokenExpired(record.tcTokenSenderTimestamp, nowS, this.config.senderDurationS, this.config.senderNumBuckets)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
await this.issuePrivacyToken(jid, record.tcTokenSenderTimestamp);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.logger.warn('send-tc-token-device-identity-change-failed', {
|
|
99
|
+
jid,
|
|
100
|
+
message: toError(error).message
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async hydrateFromHistorySync(conversations) {
|
|
105
|
+
const nowMs = Date.now();
|
|
106
|
+
const records = [];
|
|
107
|
+
for (let i = 0; i < conversations.length; i += 1) {
|
|
108
|
+
const conv = conversations[i];
|
|
109
|
+
if (!conv.tcToken && !conv.tcTokenTimestamp && !conv.tcTokenSenderTimestamp) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
records[records.length] = {
|
|
113
|
+
jid: conv.jid,
|
|
114
|
+
tcToken: conv.tcToken ?? undefined,
|
|
115
|
+
tcTokenTimestamp: conv.tcTokenTimestamp ?? undefined,
|
|
116
|
+
tcTokenSenderTimestamp: conv.tcTokenSenderTimestamp ?? undefined,
|
|
117
|
+
updatedAtMs: nowMs
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (records.length > 0) {
|
|
121
|
+
await this.store.upsertBatch(records);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async handleNctSaltSync(salt) {
|
|
125
|
+
if (salt) {
|
|
126
|
+
await this.store.upsert({
|
|
127
|
+
jid: NCT_SALT_SENTINEL_JID,
|
|
128
|
+
nctSalt: salt,
|
|
129
|
+
updatedAtMs: Date.now()
|
|
130
|
+
});
|
|
131
|
+
this.cachedNctSalt = salt;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await this.store.deleteByJid(NCT_SALT_SENTINEL_JID);
|
|
135
|
+
this.cachedNctSalt = null;
|
|
136
|
+
}
|
|
137
|
+
this.nctSaltHydrated = true;
|
|
138
|
+
this.csTokenGenerator.invalidate();
|
|
139
|
+
}
|
|
140
|
+
async hydrateNctSaltFromHistorySync(salt) {
|
|
141
|
+
await this.store.upsert({
|
|
142
|
+
jid: NCT_SALT_SENTINEL_JID,
|
|
143
|
+
nctSalt: salt,
|
|
144
|
+
updatedAtMs: Date.now()
|
|
145
|
+
});
|
|
146
|
+
this.cachedNctSalt = salt;
|
|
147
|
+
this.nctSaltHydrated = true;
|
|
148
|
+
}
|
|
149
|
+
async getNctSalt() {
|
|
150
|
+
if (this.nctSaltHydrated) {
|
|
151
|
+
return this.cachedNctSalt;
|
|
152
|
+
}
|
|
153
|
+
const record = await this.store.getByJid(NCT_SALT_SENTINEL_JID);
|
|
154
|
+
this.cachedNctSalt = record?.nctSalt ?? null;
|
|
155
|
+
this.nctSaltHydrated = true;
|
|
156
|
+
return this.cachedNctSalt;
|
|
157
|
+
}
|
|
158
|
+
async issuePrivacyToken(jid, timestampS) {
|
|
159
|
+
const node = buildPrivacyTokenIqNode({ jid, timestampS });
|
|
160
|
+
await this.runtime.queryWithContext('issue-privacy-token', node);
|
|
161
|
+
}
|
|
162
|
+
}
|
package/dist/esm/client/dirty.js
CHANGED
|
@@ -24,7 +24,13 @@ function parseDirtyBitNode(node, logger) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
function resolveAccountSyncProtocols(protocols) {
|
|
27
|
-
const selected =
|
|
27
|
+
const selected = [];
|
|
28
|
+
for (let index = 0; index < protocols.length; index += 1) {
|
|
29
|
+
const protocol = protocols[index];
|
|
30
|
+
if (ACCOUNT_SYNC_PROTOCOL_SET.has(protocol)) {
|
|
31
|
+
selected.push(protocol);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
if (selected.length > 0) {
|
|
29
35
|
return selected;
|
|
30
36
|
}
|
|
@@ -56,12 +62,24 @@ export async function handleDirtyBits(runtime, dirtyBits) {
|
|
|
56
62
|
}
|
|
57
63
|
unsupported.push(dirtyBit);
|
|
58
64
|
}
|
|
65
|
+
const supportedTypes = new Array(supported.length);
|
|
66
|
+
for (let index = 0; index < supported.length; index += 1) {
|
|
67
|
+
supportedTypes[index] = supported[index].type;
|
|
68
|
+
}
|
|
69
|
+
const unsupportedTypes = new Array(unsupported.length);
|
|
70
|
+
for (let index = 0; index < unsupported.length; index += 1) {
|
|
71
|
+
unsupportedTypes[index] = unsupported[index].type;
|
|
72
|
+
}
|
|
59
73
|
runtime.logger.info('handling dirty bits from info bulletin', {
|
|
60
|
-
supported:
|
|
61
|
-
unsupported:
|
|
74
|
+
supported: supportedTypes.join(','),
|
|
75
|
+
unsupported: unsupportedTypes.join(',')
|
|
62
76
|
});
|
|
63
77
|
const clearableDirtyBits = [...unsupported];
|
|
64
|
-
const
|
|
78
|
+
const supportedPromises = new Array(supported.length);
|
|
79
|
+
for (let index = 0; index < supported.length; index += 1) {
|
|
80
|
+
supportedPromises[index] = handleDirtyBit(runtime, supported[index]);
|
|
81
|
+
}
|
|
82
|
+
const settledSupported = await Promise.allSettled(supportedPromises);
|
|
65
83
|
for (let index = 0; index < settledSupported.length; index += 1) {
|
|
66
84
|
const result = settledSupported[index];
|
|
67
85
|
if (result.status === 'fulfilled') {
|
|
@@ -101,18 +119,23 @@ async function handleAccountSyncDirtyBit(runtime, protocols) {
|
|
|
101
119
|
protocols: selectedProtocols.join(',')
|
|
102
120
|
});
|
|
103
121
|
const failures = [];
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
const protocolPromises = new Array(selectedProtocols.length);
|
|
123
|
+
for (let index = 0; index < selectedProtocols.length; index += 1) {
|
|
124
|
+
const protocol = selectedProtocols[index];
|
|
125
|
+
protocolPromises[index] = (async () => {
|
|
126
|
+
try {
|
|
127
|
+
await runAccountSyncProtocol(runtime, protocol);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
failures.push(protocol);
|
|
131
|
+
runtime.logger.warn('account_sync protocol failed', {
|
|
132
|
+
protocol,
|
|
133
|
+
message: toError(error).message
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
}
|
|
138
|
+
await Promise.all(protocolPromises);
|
|
116
139
|
if (failures.length > 0) {
|
|
117
140
|
throw new Error(`account_sync protocols failed: ${failures.join(',')}`);
|
|
118
141
|
}
|
|
@@ -132,7 +155,7 @@ async function runAccountSyncProtocol(runtime, protocol) {
|
|
|
132
155
|
await syncAccountBlocklistDirtyBit(runtime);
|
|
133
156
|
return;
|
|
134
157
|
case WA_DIRTY_PROTOCOLS.NOTICE:
|
|
135
|
-
|
|
158
|
+
runtime.logger.info('account_sync notice protocol received (no GraphQL/MEX job configured)');
|
|
136
159
|
return;
|
|
137
160
|
default:
|
|
138
161
|
runtime.logger.debug('unsupported account_sync protocol', {
|
|
@@ -207,9 +230,6 @@ async function syncAccountBlocklistDirtyBit(runtime) {
|
|
|
207
230
|
logMessage: 'account_sync blocklist synchronized'
|
|
208
231
|
});
|
|
209
232
|
}
|
|
210
|
-
async function syncAccountNoticeDirtyBit(runtime) {
|
|
211
|
-
runtime.logger.info('account_sync notice protocol received (no GraphQL/MEX job configured)');
|
|
212
|
-
}
|
|
213
233
|
async function syncGroupsDirtyBit(runtime) {
|
|
214
234
|
await runSyncQuery(runtime, {
|
|
215
235
|
queryContext: 'dirty.groups',
|