zapo-js 1.6.2 → 1.7.1
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 +26 -12
- package/dist/abprops-spec.d.ts +10 -0
- package/dist/abprops-spec.js +25 -0
- package/dist/appstate/crypto/WaAppStateCrypto.js +4 -6
- package/dist/auth/WaAuthClient.d.ts +14 -1
- package/dist/auth/WaAuthClient.js +22 -1
- package/dist/auth/credentials-flow.d.ts +3 -1
- package/dist/auth/credentials-flow.js +3 -0
- package/dist/auth/pairing/WaPairingFlow.d.ts +5 -0
- package/dist/auth/pairing/WaPairingFlow.js +66 -14
- package/dist/auth/types.d.ts +13 -0
- package/dist/client/WaClient.d.ts +11 -0
- package/dist/client/WaClient.js +60 -0
- package/dist/client/WaClientFactory.d.ts +2 -2
- package/dist/client/WaClientFactory.js +53 -13
- package/dist/client/coordinators/WaAbPropsCoordinator.d.ts +2 -2
- package/dist/client/coordinators/WaAbPropsCoordinator.js +16 -4
- package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +2 -1
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
- package/dist/client/coordinators/WaMessageCoordinator.d.ts +96 -0
- package/dist/client/coordinators/WaMessageCoordinator.js +163 -8
- package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +64 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +252 -57
- package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +97 -19
- package/dist/client/coordinators/WaPrivacyCoordinator.js +152 -101
- package/dist/client/coordinators/WaRetryCoordinator.d.ts +22 -0
- package/dist/client/coordinators/WaRetryCoordinator.js +74 -11
- package/dist/client/events/dirty.d.ts +3 -0
- package/dist/client/events/dirty.js +12 -7
- package/dist/client/events/offline.d.ts +9 -0
- package/dist/client/events/offline.js +95 -0
- package/dist/client/events/privacy.d.ts +45 -0
- package/dist/client/events/privacy.js +130 -0
- package/dist/client/messaging/group-metadata.d.ts +12 -0
- package/dist/client/messaging/group-metadata.js +24 -8
- package/dist/client/messaging/link-preview.d.ts +7 -0
- package/dist/client/messaging/link-preview.js +27 -0
- package/dist/client/messaging/messages.js +16 -12
- package/dist/client/newsletter/content.d.ts +4 -1
- package/dist/client/newsletter/content.js +43 -18
- package/dist/client/newsletter/discovery.js +4 -2
- package/dist/client/newsletter/messaging.d.ts +2 -0
- package/dist/client/newsletter/messaging.js +2 -1
- package/dist/client/newsletter/mex.d.ts +2 -2
- package/dist/client/persistence/WriteBehindPersistence.js +2 -1
- package/dist/client/persistence/ephemeral-setting.d.ts +42 -0
- package/dist/client/persistence/ephemeral-setting.js +120 -0
- package/dist/client/persistence/group-history.d.ts +29 -0
- package/dist/client/persistence/group-history.js +149 -0
- package/dist/client/persistence/history-blob.d.ts +25 -0
- package/dist/client/persistence/history-blob.js +38 -0
- package/dist/client/persistence/history-sync.d.ts +20 -0
- package/dist/client/persistence/history-sync.js +165 -71
- package/dist/client/plugins/types.d.ts +10 -2
- package/dist/client/types.d.ts +150 -16
- package/dist/crypto/core/index.d.ts +1 -1
- package/dist/crypto/core/index.js +2 -1
- package/dist/crypto/core/primitives.d.ts +19 -0
- package/dist/crypto/core/primitives.js +48 -0
- package/dist/crypto/core/xeddsa.js +35 -4
- package/dist/crypto/curves/X25519.js +29 -2
- package/dist/crypto/nativeBackend.d.ts +20 -0
- package/dist/crypto/nativeBackend.js +95 -0
- package/dist/esm/abprops-spec.js +15 -0
- package/dist/esm/appstate/crypto/WaAppStateCrypto.js +4 -6
- package/dist/esm/auth/WaAuthClient.js +22 -1
- package/dist/esm/auth/credentials-flow.js +3 -0
- package/dist/esm/auth/pairing/WaPairingFlow.js +67 -15
- package/dist/esm/client/WaClient.js +60 -0
- package/dist/esm/client/WaClientFactory.js +54 -14
- package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +17 -5
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
- package/dist/esm/client/coordinators/WaMessageCoordinator.js +166 -11
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +254 -60
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +145 -94
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +74 -11
- package/dist/esm/client/events/dirty.js +12 -7
- package/dist/esm/client/events/offline.js +92 -0
- package/dist/esm/client/events/privacy.js +123 -0
- package/dist/esm/client/messaging/group-metadata.js +25 -9
- package/dist/esm/client/messaging/link-preview.js +29 -2
- package/dist/esm/client/messaging/messages.js +16 -12
- package/dist/esm/client/newsletter/content.js +44 -19
- package/dist/esm/client/newsletter/discovery.js +4 -2
- package/dist/esm/client/newsletter/messaging.js +2 -1
- package/dist/esm/client/persistence/WriteBehindPersistence.js +2 -1
- package/dist/esm/client/persistence/ephemeral-setting.js +116 -0
- package/dist/esm/client/persistence/group-history.js +145 -0
- package/dist/esm/client/persistence/history-blob.js +34 -0
- package/dist/esm/client/persistence/history-sync.js +157 -65
- package/dist/esm/crypto/core/index.js +1 -1
- package/dist/esm/crypto/core/primitives.js +47 -0
- package/dist/esm/crypto/core/xeddsa.js +36 -5
- package/dist/esm/crypto/curves/X25519.js +29 -2
- package/dist/esm/crypto/nativeBackend.js +93 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/media/crypto/WaMediaCrypto.js +38 -19
- package/dist/esm/message/context-info.js +39 -0
- package/dist/esm/message/crypto/reporting-token.js +38 -216
- package/dist/esm/message/encode/content.js +73 -12
- package/dist/esm/message/encode/media-payload.js +3 -1
- package/dist/esm/message/kinds/group-history.js +36 -0
- package/dist/esm/message/primitives/incoming.js +32 -13
- package/dist/esm/protocol/abprops.js +18 -162
- package/dist/esm/protocol/constants.js +4 -3
- package/dist/esm/protocol/defaults.js +7 -0
- package/dist/esm/protocol/device-identity.js +10 -5
- package/dist/esm/protocol/jid.js +83 -36
- package/dist/esm/protocol/media.js +1 -0
- package/dist/esm/protocol/message.js +8 -0
- package/dist/esm/protocol/notification.js +1 -0
- package/dist/esm/protocol/privacy.js +70 -4
- package/dist/esm/signal/group/SenderKeyChain.js +1 -2
- package/dist/esm/signal/group/SenderKeyManager.js +1 -7
- package/dist/esm/signal/session/SignalProtocol.js +22 -21
- package/dist/esm/signal/session/SignalRatchet.js +20 -17
- package/dist/esm/signal/session/encoding.js +25 -3
- package/dist/esm/signal/session/resolver.js +11 -5
- package/dist/esm/store/contracts/chat-metadata.store.js +1 -0
- package/dist/esm/store/createStore.js +19 -2
- package/dist/esm/store/index.js +1 -0
- package/dist/esm/store/memory/chat-metadata.store.js +66 -0
- package/dist/esm/store/noop.store.js +8 -0
- package/dist/esm/transport/binary/constants.js +19 -0
- package/dist/esm/transport/binary/decoder.js +17 -19
- package/dist/esm/transport/binary/encoder.js +61 -18
- package/dist/esm/transport/node/builders/global.js +20 -0
- package/dist/esm/transport/node/builders/message.js +32 -15
- package/dist/esm/transport/node/builders/privacy.js +63 -4
- package/dist/esm/transport/noise/WaClientPayload.js +43 -22
- package/dist/esm/transport/noise/WaFrameCodec.js +14 -4
- package/dist/esm/util/coercion.js +12 -4
- package/dist/esm/util/index.js +1 -1
- package/dist/esm/util/protoscan.js +181 -0
- package/dist/esm/util/runtime.js +34 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +15 -2
- package/dist/media/crypto/WaMediaCrypto.js +36 -17
- package/dist/media/types.d.ts +1 -1
- package/dist/message/context-info.d.ts +10 -0
- package/dist/message/context-info.js +40 -0
- package/dist/message/crypto/reporting-token.d.ts +3 -1
- package/dist/message/crypto/reporting-token.js +38 -216
- package/dist/message/encode/content.d.ts +24 -4
- package/dist/message/encode/content.js +74 -12
- package/dist/message/encode/media-payload.d.ts +5 -2
- package/dist/message/encode/media-payload.js +3 -1
- package/dist/message/kinds/group-history.d.ts +30 -0
- package/dist/message/kinds/group-history.js +40 -0
- package/dist/message/primitives/incoming.d.ts +5 -0
- package/dist/message/primitives/incoming.js +32 -13
- package/dist/message/types.d.ts +10 -0
- package/dist/protocol/abprops.d.ts +27 -140
- package/dist/protocol/abprops.js +18 -163
- package/dist/protocol/constants.d.ts +4 -3
- package/dist/protocol/constants.js +10 -3
- package/dist/protocol/defaults.d.ts +7 -0
- package/dist/protocol/defaults.js +7 -0
- package/dist/protocol/device-identity.d.ts +14 -4
- package/dist/protocol/device-identity.js +9 -4
- package/dist/protocol/jid.d.ts +4 -1
- package/dist/protocol/jid.js +83 -36
- package/dist/protocol/media.d.ts +1 -0
- package/dist/protocol/media.js +1 -0
- package/dist/protocol/message.d.ts +6 -0
- package/dist/protocol/message.js +9 -0
- package/dist/protocol/notification.d.ts +1 -0
- package/dist/protocol/notification.js +1 -0
- package/dist/protocol/privacy.d.ts +52 -0
- package/dist/protocol/privacy.js +71 -5
- package/dist/signal/group/SenderKeyChain.d.ts +1 -1
- package/dist/signal/group/SenderKeyChain.js +1 -2
- package/dist/signal/group/SenderKeyManager.js +1 -7
- package/dist/signal/session/SignalProtocol.d.ts +13 -0
- package/dist/signal/session/SignalProtocol.js +22 -21
- package/dist/signal/session/SignalRatchet.d.ts +4 -4
- package/dist/signal/session/SignalRatchet.js +19 -16
- package/dist/signal/session/SignalSession.d.ts +8 -1
- package/dist/signal/session/encoding.d.ts +5 -0
- package/dist/signal/session/encoding.js +25 -3
- package/dist/signal/session/resolver.js +11 -5
- package/dist/store/contracts/chat-metadata.store.d.ts +34 -0
- package/dist/store/contracts/chat-metadata.store.js +2 -0
- package/dist/store/contracts/group-metadata.store.d.ts +7 -0
- package/dist/store/contracts/thread.store.d.ts +6 -0
- package/dist/store/createStore.d.ts +10 -2
- package/dist/store/createStore.js +18 -1
- package/dist/store/index.d.ts +3 -1
- package/dist/store/index.js +3 -1
- package/dist/store/memory/chat-metadata.store.d.ts +26 -0
- package/dist/store/memory/chat-metadata.store.js +70 -0
- package/dist/store/noop.store.d.ts +2 -0
- package/dist/store/noop.store.js +9 -1
- package/dist/store/types.d.ts +190 -39
- package/dist/transport/binary/constants.d.ts +8 -0
- package/dist/transport/binary/constants.js +20 -1
- package/dist/transport/binary/decoder.js +17 -19
- package/dist/transport/binary/encoder.js +60 -17
- package/dist/transport/node/builders/global.d.ts +8 -0
- package/dist/transport/node/builders/global.js +21 -0
- package/dist/transport/node/builders/message.js +32 -15
- package/dist/transport/node/builders/privacy.d.ts +26 -2
- package/dist/transport/node/builders/privacy.js +63 -3
- package/dist/transport/noise/WaClientPayload.js +43 -22
- package/dist/transport/noise/WaFrameCodec.d.ts +1 -0
- package/dist/transport/noise/WaFrameCodec.js +14 -4
- package/dist/transport/noise/types.d.ts +17 -0
- package/dist/util/coercion.d.ts +6 -0
- package/dist/util/coercion.js +13 -4
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +2 -1
- package/dist/util/protoscan.d.ts +33 -0
- package/dist/util/protoscan.js +186 -0
- package/dist/util/runtime.d.ts +12 -0
- package/dist/util/runtime.js +35 -0
- package/package.json +3 -1
- package/spec/abprops/index.d.ts +4402 -0
- package/spec/abprops/index.js +4372 -0
package/README.md
CHANGED
|
@@ -41,8 +41,8 @@ npm install zapo-js
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Zero mandatory runtime dependencies. Pick the optional packages you need
|
|
44
|
-
on top: a persistent store
|
|
45
|
-
|
|
44
|
+
on top: a persistent store, the media processor for thumbnails /
|
|
45
|
+
voice-note metadata, and the native crypto accelerator.
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# Persistent store - choose one
|
|
@@ -56,10 +56,21 @@ npm install @zapo-js/store-sqlite better-sqlite3
|
|
|
56
56
|
npm install @zapo-js/media-utils sharp
|
|
57
57
|
# plus a system `ffmpeg` + `ffprobe` on PATH (see media-utils README)
|
|
58
58
|
|
|
59
|
+
# Optional - Rust crypto accelerator, no code change required
|
|
60
|
+
npm install @zapo-js/native
|
|
61
|
+
|
|
59
62
|
# Optional - structured logging
|
|
60
63
|
npm install pino pino-pretty
|
|
61
64
|
```
|
|
62
65
|
|
|
66
|
+
`@zapo-js/native` is picked up automatically once installed: the core
|
|
67
|
+
resolves it on first use and falls back to the JS implementation when it is
|
|
68
|
+
absent, so nothing else changes. It accelerates the two primitives that
|
|
69
|
+
dominate the messaging hot path, X25519 ECDH and XEdDSA sign / verify. The
|
|
70
|
+
published build is the WASM one (`ZAPO_NATIVE_BACKEND=auto` tries the NAPI
|
|
71
|
+
addon first, then WASM); the compiled NAPI addon is not on npm yet and has
|
|
72
|
+
to be built from `packages/native`. Set `ZAPO_NATIVE_BACKEND=js` to opt out.
|
|
73
|
+
|
|
63
74
|
## Quick Start
|
|
64
75
|
|
|
65
76
|
```ts
|
|
@@ -116,16 +127,19 @@ The core lives at the repo root (`zapo-js`). Optional packages live in
|
|
|
116
127
|
[`packages/`](packages/) and ship under the `@zapo-js/*` scope. Install
|
|
117
128
|
only what you need.
|
|
118
129
|
|
|
119
|
-
| Package | Peer dependency
|
|
120
|
-
| ---------------------------------------------------- |
|
|
121
|
-
| [`@zapo-js/store-sqlite`](packages/store-sqlite) | `better-sqlite3`
|
|
122
|
-
| [`@zapo-js/store-redis`](packages/store-redis) | `ioredis`
|
|
123
|
-
| [`@zapo-js/store-mongo`](packages/store-mongo) | `mongodb`
|
|
124
|
-
| [`@zapo-js/store-mysql`](packages/store-mysql) | `mysql2`
|
|
125
|
-
| [`@zapo-js/store-postgres`](packages/store-postgres) | `pg`
|
|
126
|
-
| [`@zapo-js/media-utils`](packages/media-utils) | `sharp` + `ffmpeg`
|
|
127
|
-
| [`@zapo-js/
|
|
128
|
-
| [`@zapo-js/
|
|
130
|
+
| Package | Peer dependency | Purpose |
|
|
131
|
+
| ---------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
132
|
+
| [`@zapo-js/store-sqlite`](packages/store-sqlite) | `better-sqlite3` | SQLite persistent store (single-process bots, dev sessions, small-to-medium prod). |
|
|
133
|
+
| [`@zapo-js/store-redis`](packages/store-redis) | `ioredis` | Redis-backed store with native TTL eviction. |
|
|
134
|
+
| [`@zapo-js/store-mongo`](packages/store-mongo) | `mongodb` | MongoDB-backed store with TTL-index eviction. |
|
|
135
|
+
| [`@zapo-js/store-mysql`](packages/store-mysql) | `mysql2` | MySQL / MariaDB-backed store with background cleanup poller. |
|
|
136
|
+
| [`@zapo-js/store-postgres`](packages/store-postgres) | `pg` | PostgreSQL-backed store with background cleanup poller. |
|
|
137
|
+
| [`@zapo-js/media-utils`](packages/media-utils) | `sharp` + `file-type` + `ffmpeg` | `WaMediaProcessor`: thumbnails, waveforms, voice-note normalization. |
|
|
138
|
+
| [`@zapo-js/native`](packages/native) | (none) | Rust crypto accelerator for the messaging hot path (X25519 ECDH, XEdDSA sign / verify). Auto-detected once installed; ships as a WASM build. |
|
|
139
|
+
| [`@zapo-js/voip`](packages/voip) | `@roamhq/wrtc` + `libmlow-wasm` | `client.voip` plugin for calls: MLow/WASM codec, SRTP, STUN, WebRTC/SCTP relay transport. |
|
|
140
|
+
| [`@zapo-js/wam`](packages/wam) | (none) | `client.wam` plugin emitting the client-side WAM telemetry batches WA Web sends, for wire parity and anti-fingerprinting. |
|
|
141
|
+
| [`@zapo-js/fake-server`](packages/fake-server) | (none) | In-process fake WhatsApp Web server for end-to-end testing. |
|
|
142
|
+
| [`@zapo-js/mcp-server`](packages/mcp-server) | `@modelcontextprotocol/sdk` | **Dev-only.** MCP server exposing multi-session `WaClient`s as dynamic tools for an LLM agent (Claude Code / Cursor / etc.). Not for production. |
|
|
129
143
|
|
|
130
144
|
Each package's README has the install + config + integration notes.
|
|
131
145
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type WaAbPropName } from '../spec/abprops';
|
|
2
|
+
export { WA_ABPROPS, WA_ABPROPS_BY_CODE, WA_ABPROPS_SPECIAL_EARLY, WA_ABPROPS_USED_BEFORE_INIT, WA_GROUP_ABPROPS, WA_GROUP_ABPROPS_BY_CODE } from '../spec/abprops';
|
|
3
|
+
export type { WaAbProp, WaAbPropName, WaAbPropType, WaAbPropValue, WaAbPropValueByName, WaAbPropValueOf, WaGroupAbPropName, WaGroupAbPropValueByName } from '../spec/abprops';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the client-side prop name for a wire config code.
|
|
6
|
+
*
|
|
7
|
+
* Returns `undefined` for codes this build does not know about - the server
|
|
8
|
+
* advertises props from newer bundles, and WA Web drops those rows too.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveAbPropNameByCode(configCode: number): WaAbPropName | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WA_GROUP_ABPROPS_BY_CODE = exports.WA_GROUP_ABPROPS = exports.WA_ABPROPS_USED_BEFORE_INIT = exports.WA_ABPROPS_SPECIAL_EARLY = exports.WA_ABPROPS_BY_CODE = exports.WA_ABPROPS = void 0;
|
|
4
|
+
exports.resolveAbPropNameByCode = resolveAbPropNameByCode;
|
|
5
|
+
const abprops_1 = require("../spec/abprops");
|
|
6
|
+
var abprops_2 = require("../spec/abprops");
|
|
7
|
+
Object.defineProperty(exports, "WA_ABPROPS", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS; } });
|
|
8
|
+
Object.defineProperty(exports, "WA_ABPROPS_BY_CODE", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_BY_CODE; } });
|
|
9
|
+
Object.defineProperty(exports, "WA_ABPROPS_SPECIAL_EARLY", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_SPECIAL_EARLY; } });
|
|
10
|
+
Object.defineProperty(exports, "WA_ABPROPS_USED_BEFORE_INIT", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_USED_BEFORE_INIT; } });
|
|
11
|
+
Object.defineProperty(exports, "WA_GROUP_ABPROPS", { enumerable: true, get: function () { return abprops_2.WA_GROUP_ABPROPS; } });
|
|
12
|
+
Object.defineProperty(exports, "WA_GROUP_ABPROPS_BY_CODE", { enumerable: true, get: function () { return abprops_2.WA_GROUP_ABPROPS_BY_CODE; } });
|
|
13
|
+
// The reverse map is emitted with numeric literal keys, so it cannot be indexed
|
|
14
|
+
// with a plain `number`. Widening it keeps the lookup a single property read and
|
|
15
|
+
// avoids allocating a parallel Map with 2000+ entries at module init.
|
|
16
|
+
const AB_PROP_NAME_BY_CODE = abprops_1.WA_ABPROPS_BY_CODE;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the client-side prop name for a wire config code.
|
|
19
|
+
*
|
|
20
|
+
* Returns `undefined` for codes this build does not know about - the server
|
|
21
|
+
* advertises props from newer bundles, and WA Web drops those rows too.
|
|
22
|
+
*/
|
|
23
|
+
function resolveAbPropNameByCode(configCode) {
|
|
24
|
+
return AB_PROP_NAME_BY_CODE[configCode];
|
|
25
|
+
}
|
|
@@ -177,12 +177,10 @@ class WaAppStateCrypto {
|
|
|
177
177
|
if (out.byteLength !== left.byteLength) {
|
|
178
178
|
throw new Error('lt hash output length mismatch');
|
|
179
179
|
}
|
|
180
|
-
const leftView = new DataView(left.buffer, left.byteOffset, left.byteLength);
|
|
181
|
-
const rightView = new DataView(right.buffer, right.byteOffset, right.byteLength);
|
|
182
|
-
const outView = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
183
180
|
for (let offset = 0; offset < left.byteLength; offset += constants_1.APP_STATE_POINT_SIZE) {
|
|
184
|
-
const value = combine(
|
|
185
|
-
|
|
181
|
+
const value = combine(left[offset] | (left[offset + 1] << 8), right[offset] | (right[offset + 1] << 8));
|
|
182
|
+
out[offset] = value & 0xff;
|
|
183
|
+
out[offset + 1] = (value >>> 8) & 0xff;
|
|
186
184
|
}
|
|
187
185
|
return out;
|
|
188
186
|
}
|
|
@@ -199,7 +197,7 @@ class WaAppStateCrypto {
|
|
|
199
197
|
generateValueMac(valueMacHmacKey, associatedData, iv, cipherText) {
|
|
200
198
|
const octetLength = new Uint8Array(constants_1.APP_STATE_MAC_OCTET_LENGTH);
|
|
201
199
|
octetLength[octetLength.length - 1] = associatedData.byteLength & 0xff;
|
|
202
|
-
const full = (0, primitives_1.hmacSha512Sign)(valueMacHmacKey,
|
|
200
|
+
const full = (0, primitives_1.hmacSha512Sign)(valueMacHmacKey, [associatedData, iv, cipherText, octetLength]);
|
|
203
201
|
return full.subarray(0, constants_1.APP_STATE_VALUE_MAC_LENGTH);
|
|
204
202
|
}
|
|
205
203
|
touchDerivedKeysCacheEntry(cacheKey, keys) {
|
|
@@ -52,6 +52,12 @@ export declare class WaAuthClient {
|
|
|
52
52
|
private credentials;
|
|
53
53
|
private versionOverride;
|
|
54
54
|
private mobileAppVersionOverride;
|
|
55
|
+
/**
|
|
56
|
+
* Successful logins made by this instance, advertised as
|
|
57
|
+
* `connectAttemptCount`. Process-local by design – the persisted
|
|
58
|
+
* counterpart is `credentials.loginCounter`.
|
|
59
|
+
*/
|
|
60
|
+
private connectAttemptCount;
|
|
55
61
|
constructor(options: WaAuthClientOptions, deps: WaAuthClientDeps);
|
|
56
62
|
/**
|
|
57
63
|
* Returns a snapshot of auth readiness flags (connection, registration,
|
|
@@ -121,8 +127,15 @@ export declare class WaAuthClient {
|
|
|
121
127
|
* Persists the per-connection success attributes from the server (LID,
|
|
122
128
|
* display name, companion key, last-success ts, props versions, ...). Only
|
|
123
129
|
* persists when at least one attribute actually changed.
|
|
130
|
+
*
|
|
131
|
+
* Pass `countsAsLogin` only for the server `success` node: it advances the
|
|
132
|
+
* login counters advertised as `lc` / `connectAttemptCount`. Callers that
|
|
133
|
+
* merely reuse this to persist a single attribute (a push-name change, for
|
|
134
|
+
* instance) must leave it unset.
|
|
124
135
|
*/
|
|
125
|
-
persistSuccessAttributes(attributes: WaSuccessPersistAttributes
|
|
136
|
+
persistSuccessAttributes(attributes: WaSuccessPersistAttributes, { countsAsLogin }?: {
|
|
137
|
+
readonly countsAsLogin?: boolean;
|
|
138
|
+
}): Promise<void>;
|
|
126
139
|
/**
|
|
127
140
|
* Requests an 8-character pairing code for `phoneNumber` (link-code flow).
|
|
128
141
|
* Pass `customCode` to suggest a specific code; the server still validates
|
|
@@ -23,11 +23,18 @@ class WaAuthClient {
|
|
|
23
23
|
constructor(options, deps) {
|
|
24
24
|
this.versionOverride = null;
|
|
25
25
|
this.mobileAppVersionOverride = null;
|
|
26
|
+
/**
|
|
27
|
+
* Successful logins made by this instance, advertised as
|
|
28
|
+
* `connectAttemptCount`. Process-local by design – the persisted
|
|
29
|
+
* counterpart is `credentials.loginCounter`.
|
|
30
|
+
*/
|
|
31
|
+
this.connectAttemptCount = 0;
|
|
26
32
|
const device = (0, constants_1.resolveWaDeviceIdentity)(options);
|
|
27
33
|
this.options = Object.freeze({
|
|
28
34
|
...options,
|
|
29
35
|
deviceBrowser: device.browser,
|
|
30
36
|
deviceOsDisplayName: device.osDisplayName,
|
|
37
|
+
deviceOsVersion: device.osVersion ?? undefined,
|
|
31
38
|
devicePlatform: device.platform,
|
|
32
39
|
requireFullSync: options.requireFullSync
|
|
33
40
|
});
|
|
@@ -125,8 +132,10 @@ class WaAuthClient {
|
|
|
125
132
|
const mobileAppVersionOverride = this.mobileAppVersionOverride;
|
|
126
133
|
this.mobileAppVersionOverride = null;
|
|
127
134
|
return (0, credentials_flow_1.buildCommsConfig)(this.logger, this.requireCredentials(), socketOptions, {
|
|
135
|
+
connectAttemptCount: this.connectAttemptCount,
|
|
128
136
|
deviceBrowser: this.options.deviceBrowser,
|
|
129
137
|
deviceOsDisplayName: this.options.deviceOsDisplayName,
|
|
138
|
+
deviceOsVersion: this.options.deviceOsVersion,
|
|
130
139
|
requireFullSync: this.options.requireFullSync,
|
|
131
140
|
version: override ?? this.options.version,
|
|
132
141
|
mobileTransport: this.options.mobileTransport,
|
|
@@ -239,10 +248,19 @@ class WaAuthClient {
|
|
|
239
248
|
* Persists the per-connection success attributes from the server (LID,
|
|
240
249
|
* display name, companion key, last-success ts, props versions, ...). Only
|
|
241
250
|
* persists when at least one attribute actually changed.
|
|
251
|
+
*
|
|
252
|
+
* Pass `countsAsLogin` only for the server `success` node: it advances the
|
|
253
|
+
* login counters advertised as `lc` / `connectAttemptCount`. Callers that
|
|
254
|
+
* merely reuse this to persist a single attribute (a push-name change, for
|
|
255
|
+
* instance) must leave it unset.
|
|
242
256
|
*/
|
|
243
|
-
async persistSuccessAttributes(attributes) {
|
|
257
|
+
async persistSuccessAttributes(attributes, { countsAsLogin = false } = {}) {
|
|
258
|
+
if (countsAsLogin) {
|
|
259
|
+
this.connectAttemptCount += 1;
|
|
260
|
+
}
|
|
244
261
|
let persistDiff;
|
|
245
262
|
const computeDiff = (current, next) => ({
|
|
263
|
+
loginCounterChanged: next.loginCounter !== current.loginCounter,
|
|
246
264
|
lidChanged: next.meLid !== current.meLid,
|
|
247
265
|
displayNameChanged: next.meDisplayName !== current.meDisplayName,
|
|
248
266
|
companionChanged: (current.companionEncStatic === undefined) !==
|
|
@@ -262,6 +280,9 @@ class WaAuthClient {
|
|
|
262
280
|
meDisplayName: attributes.meDisplayName ?? credentials.meDisplayName,
|
|
263
281
|
companionEncStatic: attributes.companionEncStatic ?? credentials.companionEncStatic,
|
|
264
282
|
lastSuccessTs: attributes.lastSuccessTs ?? credentials.lastSuccessTs,
|
|
283
|
+
loginCounter: countsAsLogin
|
|
284
|
+
? (credentials.loginCounter ?? 0) + 1
|
|
285
|
+
: credentials.loginCounter,
|
|
265
286
|
propsVersion: attributes.propsVersion ?? credentials.propsVersion,
|
|
266
287
|
abPropsVersion: attributes.abPropsVersion ?? credentials.abPropsVersion,
|
|
267
288
|
connectionLocation: attributes.connectionLocation ?? credentials.connectionLocation,
|
|
@@ -14,7 +14,7 @@ interface WaAuthCredentialsFlowArgs {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function loadOrCreateCredentials(args: WaAuthCredentialsFlowArgs): Promise<WaAuthCredentials>;
|
|
16
16
|
export declare function persistCredentials(args: WaAuthCredentialsFlowArgs, credentials: WaAuthCredentials): Promise<void>;
|
|
17
|
-
export declare function buildCommsConfig(logger: Logger, credentials: WaAuthCredentials, socketOptions: WaAuthSocketOptions, clientOptions: Pick<WaAuthClientOptions, 'deviceBrowser' | 'deviceOsDisplayName' | 'requireFullSync' | 'version' | 'mobileTransport'> & {
|
|
17
|
+
export declare function buildCommsConfig(logger: Logger, credentials: WaAuthCredentials, socketOptions: WaAuthSocketOptions, clientOptions: Pick<WaAuthClientOptions, 'deviceBrowser' | 'deviceOsDisplayName' | 'deviceOsVersion' | 'requireFullSync' | 'version' | 'mobileTransport'> & {
|
|
18
18
|
readonly noiseTrustedRootCa?: WaNoiseRootCa;
|
|
19
19
|
readonly disableNoiseCertificateChainVerification?: boolean;
|
|
20
20
|
/**
|
|
@@ -25,5 +25,7 @@ export declare function buildCommsConfig(logger: Logger, credentials: WaAuthCred
|
|
|
25
25
|
* sessions.
|
|
26
26
|
*/
|
|
27
27
|
readonly mobileAppVersionOverride?: string;
|
|
28
|
+
/** Successful logins made by the calling client instance so far. */
|
|
29
|
+
readonly connectAttemptCount?: number;
|
|
28
30
|
}): Promise<WaCommsConfig>;
|
|
29
31
|
export {};
|
|
@@ -179,6 +179,8 @@ async function buildCommsConfig(logger, credentials, socketOptions, clientOption
|
|
|
179
179
|
? {
|
|
180
180
|
username: loginIdentity.username,
|
|
181
181
|
device: loginIdentity.device,
|
|
182
|
+
loginCounter: credentials.loginCounter ?? 0,
|
|
183
|
+
connectAttemptCount: clientOptions.connectAttemptCount ?? 0,
|
|
182
184
|
deviceBrowser: clientOptions.deviceBrowser,
|
|
183
185
|
deviceOsDisplayName: clientOptions.deviceOsDisplayName,
|
|
184
186
|
versionBase
|
|
@@ -190,6 +192,7 @@ async function buildCommsConfig(logger, credentials, socketOptions, clientOption
|
|
|
190
192
|
signedPreKey: credentials.signedPreKey,
|
|
191
193
|
deviceBrowser: clientOptions.deviceBrowser,
|
|
192
194
|
deviceOsDisplayName: clientOptions.deviceOsDisplayName,
|
|
195
|
+
deviceOsVersion: clientOptions.deviceOsVersion,
|
|
193
196
|
requireFullSync: clientOptions.requireFullSync,
|
|
194
197
|
versionBase
|
|
195
198
|
}
|
|
@@ -31,6 +31,7 @@ interface WaPairingFlowOptions {
|
|
|
31
31
|
export declare class WaPairingFlow {
|
|
32
32
|
private readonly opts;
|
|
33
33
|
private pairingSession;
|
|
34
|
+
private pairSuccessInFlight;
|
|
34
35
|
constructor(options: WaPairingFlowOptions);
|
|
35
36
|
hasPairingSession(): boolean;
|
|
36
37
|
clearSession(): void;
|
|
@@ -41,7 +42,11 @@ export declare class WaPairingFlow {
|
|
|
41
42
|
handleCompanionRegRefreshNotification(node: BinaryNode): Promise<boolean>;
|
|
42
43
|
private handlePairDevice;
|
|
43
44
|
private handlePairSuccess;
|
|
45
|
+
private completePairSuccess;
|
|
46
|
+
/** Returns `null` when the primary's account signature does not verify. */
|
|
44
47
|
private buildPairSuccessResponseIdentity;
|
|
48
|
+
/** Mirrors the `not-authorized` IQ error WhatsApp Web returns on a failed pair-success. */
|
|
49
|
+
private sendPairSuccessValidationError;
|
|
45
50
|
private handlePrimaryHello;
|
|
46
51
|
private rotateAdvSecret;
|
|
47
52
|
private requireCredentials;
|
|
@@ -13,8 +13,13 @@ const pairing_1 = require("../../transport/node/builders/pairing");
|
|
|
13
13
|
const helpers_1 = require("../../transport/node/helpers");
|
|
14
14
|
const query_1 = require("../../transport/node/query");
|
|
15
15
|
const bytes_1 = require("../../util/bytes");
|
|
16
|
+
const primitives_1 = require("../../util/primitives");
|
|
17
|
+
/** Server-enforced bounds on the `pair-success` `device-identity` payload. */
|
|
18
|
+
const PAIR_SUCCESS_DEVICE_IDENTITY_MIN_BYTES = 1;
|
|
19
|
+
const PAIR_SUCCESS_DEVICE_IDENTITY_MAX_BYTES = 500;
|
|
16
20
|
class WaPairingFlow {
|
|
17
21
|
constructor(options) {
|
|
22
|
+
this.pairSuccessInFlight = false;
|
|
18
23
|
this.opts = options;
|
|
19
24
|
this.pairingSession = null;
|
|
20
25
|
}
|
|
@@ -33,6 +38,7 @@ class WaPairingFlow {
|
|
|
33
38
|
const normalizedCustomCode = customCode !== undefined ? (0, pairing_code_crypto_1.normalizeCustomPairingCode)(customCode) : undefined;
|
|
34
39
|
const credentials = this.requireCredentials();
|
|
35
40
|
const phoneJid = (0, jid_1.parsePhoneJid)(phoneNumber);
|
|
41
|
+
const createdAtSeconds = Math.floor(Date.now() / 1000);
|
|
36
42
|
const [companionHello, refreshedCredentials] = await Promise.all([
|
|
37
43
|
(0, pairing_code_crypto_1.createCompanionHello)({ customCode: normalizedCustomCode }),
|
|
38
44
|
this.rotateAdvSecret(credentials)
|
|
@@ -64,7 +70,7 @@ class WaPairingFlow {
|
|
|
64
70
|
pairingCode: companionHello.pairingCode,
|
|
65
71
|
phoneJid,
|
|
66
72
|
ref,
|
|
67
|
-
createdAtSeconds
|
|
73
|
+
createdAtSeconds,
|
|
68
74
|
companionEphemeralKeyPair: companionHello.companionEphemeralKeyPair,
|
|
69
75
|
attempts: 0,
|
|
70
76
|
finished: false
|
|
@@ -166,6 +172,23 @@ class WaPairingFlow {
|
|
|
166
172
|
this.opts.logger.debug('pair-device refs updated', { refsCount: refs.length });
|
|
167
173
|
}
|
|
168
174
|
async handlePairSuccess(iqNode, pairSuccessNode) {
|
|
175
|
+
if (this.pairSuccessInFlight) {
|
|
176
|
+
this.opts.logger.debug('pair-success ignored: already processing');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (this.opts.auth.getCredentials()?.meJid) {
|
|
180
|
+
this.opts.logger.debug('pair-success ignored: session already registered');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.pairSuccessInFlight = true;
|
|
184
|
+
try {
|
|
185
|
+
await this.completePairSuccess(iqNode, pairSuccessNode);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
this.pairSuccessInFlight = false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async completePairSuccess(iqNode, pairSuccessNode) {
|
|
169
192
|
this.opts.logger.debug('processing pair-success node');
|
|
170
193
|
const credentials = this.requireCredentials();
|
|
171
194
|
const [deviceIdentityNode, deviceNode, platformNode] = (0, helpers_1.findNodeChildrenByTags)(pairSuccessNode, [constants_1.WA_NODE_TAGS.DEVICE_IDENTITY, 'device', constants_1.WA_NODE_TAGS.PLATFORM]);
|
|
@@ -177,7 +200,12 @@ class WaPairingFlow {
|
|
|
177
200
|
});
|
|
178
201
|
throw new Error('pair-success stanza is missing required nodes');
|
|
179
202
|
}
|
|
180
|
-
const
|
|
203
|
+
const deviceIdentityBytes = (0, helpers_1.decodeNodeContentUtf8OrBytes)(deviceIdentityNode.content, 'pair-success.device-identity');
|
|
204
|
+
if (deviceIdentityBytes.length < PAIR_SUCCESS_DEVICE_IDENTITY_MIN_BYTES ||
|
|
205
|
+
deviceIdentityBytes.length > PAIR_SUCCESS_DEVICE_IDENTITY_MAX_BYTES) {
|
|
206
|
+
throw new Error(`pair-success device-identity must be ${PAIR_SUCCESS_DEVICE_IDENTITY_MIN_BYTES}-${PAIR_SUCCESS_DEVICE_IDENTITY_MAX_BYTES} bytes, got ${deviceIdentityBytes.length}`);
|
|
207
|
+
}
|
|
208
|
+
const wrappedIdentity = _proto_1.proto.ADVSignedDeviceIdentityHMAC.decode(deviceIdentityBytes);
|
|
181
209
|
const wrappedDetails = (0, bytes_1.decodeProtoBytes)(wrappedIdentity.details, 'ADVSignedDeviceIdentityHMAC.details');
|
|
182
210
|
const wrappedHmac = (0, bytes_1.decodeProtoBytes)(wrappedIdentity.hmac, 'ADVSignedDeviceIdentityHMAC.hmac');
|
|
183
211
|
const accountType = wrappedIdentity.accountType ?? _proto_1.proto.ADVEncryptionType.E2EE;
|
|
@@ -194,16 +222,23 @@ class WaPairingFlow {
|
|
|
194
222
|
platform: platformNode.attrs.name,
|
|
195
223
|
isHosted
|
|
196
224
|
});
|
|
225
|
+
await this.sendPairSuccessValidationError(iqNode);
|
|
197
226
|
throw new Error('pair-success HMAC validation failed');
|
|
198
227
|
}
|
|
199
228
|
}
|
|
200
|
-
const
|
|
229
|
+
const built = await this.buildPairSuccessResponseIdentity(credentials, wrappedDetails);
|
|
230
|
+
if (!built) {
|
|
231
|
+
await this.sendPairSuccessValidationError(iqNode);
|
|
232
|
+
throw new Error('pair-success account signature validation failed');
|
|
233
|
+
}
|
|
234
|
+
const { signedIdentity, keyIndex, responseIdentityBytes } = built;
|
|
201
235
|
const nextCredentials = {
|
|
202
236
|
...credentials,
|
|
203
237
|
signedIdentity,
|
|
204
238
|
meJid: deviceNode.attrs.jid,
|
|
205
239
|
meLid: deviceNode.attrs.lid,
|
|
206
|
-
platform: platformNode.attrs.name
|
|
240
|
+
platform: platformNode.attrs.name,
|
|
241
|
+
loginCounter: 0
|
|
207
242
|
};
|
|
208
243
|
await this.opts.auth.updateCredentials(nextCredentials);
|
|
209
244
|
this.opts.logger.info('pair-success credentials updated', {
|
|
@@ -238,6 +273,7 @@ class WaPairingFlow {
|
|
|
238
273
|
this.opts.callbacks.emitPaired(nextCredentials);
|
|
239
274
|
this.opts.logger.debug('pair-success completed and paired event emitted');
|
|
240
275
|
}
|
|
276
|
+
/** Returns `null` when the primary's account signature does not verify. */
|
|
241
277
|
async buildPairSuccessResponseIdentity(credentials, wrappedDetails) {
|
|
242
278
|
const signedIdentity = _proto_1.proto.ADVSignedDeviceIdentity.decode(wrappedDetails);
|
|
243
279
|
const details = (0, bytes_1.decodeProtoBytes)(signedIdentity.details, 'ADVSignedDeviceIdentity.details');
|
|
@@ -253,7 +289,7 @@ class WaPairingFlow {
|
|
|
253
289
|
keyIndex: advDeviceIdentity.keyIndex ?? 0,
|
|
254
290
|
isDeviceHosted
|
|
255
291
|
});
|
|
256
|
-
|
|
292
|
+
return null;
|
|
257
293
|
}
|
|
258
294
|
}
|
|
259
295
|
signedIdentity.deviceSignature = await (0, WaAdvSignature_1.generateDeviceSignature)(details, localIdentity, accountSignatureKey, false);
|
|
@@ -268,19 +304,35 @@ class WaPairingFlow {
|
|
|
268
304
|
responseIdentityBytes
|
|
269
305
|
};
|
|
270
306
|
}
|
|
307
|
+
/** Mirrors the `not-authorized` IQ error WhatsApp Web returns on a failed pair-success. */
|
|
308
|
+
async sendPairSuccessValidationError(iqNode) {
|
|
309
|
+
try {
|
|
310
|
+
await this.opts.socket.sendNode((0, global_1.buildIqErrorNode)(iqNode, { code: 401, text: 'not-authorized' }));
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
this.opts.logger.warn('failed to send pair-success error response', {
|
|
314
|
+
message: (0, primitives_1.toError)(error).message
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
271
318
|
async handlePrimaryHello(linkCodeNode) {
|
|
272
|
-
|
|
319
|
+
let credentials = this.requireCredentials();
|
|
273
320
|
const pairingSession = this.pairingSession;
|
|
274
|
-
if (!pairingSession
|
|
321
|
+
if (!pairingSession) {
|
|
275
322
|
this.opts.logger.trace('primary_hello ignored: no active session');
|
|
276
323
|
return;
|
|
277
324
|
}
|
|
278
325
|
pairingSession.attempts += 1;
|
|
279
326
|
this.opts.logger.debug('processing primary_hello', {
|
|
280
|
-
attempts: pairingSession.attempts
|
|
327
|
+
attempts: pairingSession.attempts,
|
|
328
|
+
finished: pairingSession.finished
|
|
281
329
|
});
|
|
282
|
-
if (pairingSession.
|
|
283
|
-
|
|
330
|
+
if (pairingSession.finished) {
|
|
331
|
+
if (pairingSession.attempts > constants_1.WA_DEFAULTS.PAIRING_CODE_MAX_PRIMARY_HELLOS) {
|
|
332
|
+
throw new Error('pairing code exceeded maximum primary hello attempts');
|
|
333
|
+
}
|
|
334
|
+
credentials = await this.rotateAdvSecret(credentials);
|
|
335
|
+
pairingSession.finished = false;
|
|
284
336
|
}
|
|
285
337
|
const [refNode, wrappedPrimaryNode, primaryIdentityNode] = (0, helpers_1.findNodeChildrenByTags)(linkCodeNode, [
|
|
286
338
|
constants_1.WA_NODE_TAGS.LINK_CODE_PAIRING_REF,
|
|
@@ -309,6 +361,10 @@ class WaPairingFlow {
|
|
|
309
361
|
companionEphemeralPrivKey: pairingSession.companionEphemeralKeyPair.privKey,
|
|
310
362
|
registrationIdentityKeyPair: credentials.registrationInfo.identityKeyPair
|
|
311
363
|
});
|
|
364
|
+
await this.opts.auth.updateCredentials({
|
|
365
|
+
...credentials,
|
|
366
|
+
advSecretKey: finish.advSecret
|
|
367
|
+
});
|
|
312
368
|
const result = await this.opts.socket.query((0, pairing_1.buildCompanionFinishRequestNode)({
|
|
313
369
|
phoneJid: pairingSession.phoneJid,
|
|
314
370
|
wrappedKeyBundle: finish.wrappedKeyBundle,
|
|
@@ -318,10 +374,6 @@ class WaPairingFlow {
|
|
|
318
374
|
if (result.attrs.type === constants_1.WA_IQ_TYPES.ERROR) {
|
|
319
375
|
throw new Error('companion_finish returned error');
|
|
320
376
|
}
|
|
321
|
-
await this.opts.auth.updateCredentials({
|
|
322
|
-
...credentials,
|
|
323
|
-
advSecretKey: finish.advSecret
|
|
324
|
-
});
|
|
325
377
|
pairingSession.finished = true;
|
|
326
378
|
this.opts.logger.debug('primary_hello completed with companion_finish success');
|
|
327
379
|
}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface WaAuthCredentials {
|
|
|
24
24
|
readonly serverHasPreKeys?: boolean;
|
|
25
25
|
readonly routingInfo?: Uint8Array;
|
|
26
26
|
readonly lastSuccessTs?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Successful logins since this device was paired, advertised as `lc` in the
|
|
29
|
+
* noise login payload. Reset to `0` when a new pairing completes.
|
|
30
|
+
*/
|
|
31
|
+
readonly loginCounter?: number;
|
|
27
32
|
readonly propsVersion?: number;
|
|
28
33
|
readonly abPropsVersion?: number;
|
|
29
34
|
readonly connectionLocation?: string;
|
|
@@ -65,6 +70,14 @@ export interface WaAuthClientOptions {
|
|
|
65
70
|
* `'Mac OS'`, `'Linux'`). Defaults to the current runtime's OS.
|
|
66
71
|
*/
|
|
67
72
|
readonly deviceOsDisplayName?: string;
|
|
73
|
+
/**
|
|
74
|
+
* OS version advertised in `DeviceProps.version` (`'10'`, `'14.6'`, ...).
|
|
75
|
+
* Defaults to the detected runtime OS version. Set this alongside
|
|
76
|
+
* {@link deviceOsDisplayName} when advertising an OS the process is not
|
|
77
|
+
* running on, so the advertised name and version agree. Values that are
|
|
78
|
+
* not dotted-numeric leave the field unset.
|
|
79
|
+
*/
|
|
80
|
+
readonly deviceOsVersion?: string;
|
|
68
81
|
/**
|
|
69
82
|
* When `true`, request a full history download from the primary device on
|
|
70
83
|
* pairing instead of just recent messages. Off by default.
|
|
@@ -27,6 +27,7 @@ declare class WaClientImpl extends EventEmitter {
|
|
|
27
27
|
private readonly appStateSync;
|
|
28
28
|
private readonly mediaTransfer;
|
|
29
29
|
private readonly writeBehind;
|
|
30
|
+
private readonly observeEphemeralSetting;
|
|
30
31
|
private connectPromise;
|
|
31
32
|
private acceptingIncomingEvents;
|
|
32
33
|
private activeIncomingHandlers;
|
|
@@ -74,6 +75,16 @@ declare class WaClientImpl extends EventEmitter {
|
|
|
74
75
|
getClockSkewMs(): number | null;
|
|
75
76
|
private bindNodeTransportEvents;
|
|
76
77
|
private handleIncomingMessageEvent;
|
|
78
|
+
/**
|
|
79
|
+
* Kicks off the group-history bundle download when the incoming message
|
|
80
|
+
* carries one. Fire-and-forget: the blob can be large, and the incoming
|
|
81
|
+
* handler must not stall behind a CDN fetch.
|
|
82
|
+
*
|
|
83
|
+
* The download outlives the handler that started it, so it takes a slot of
|
|
84
|
+
* its own in the drain accounting - `disconnect()` and `clearStoredState()`
|
|
85
|
+
* must not flush or wipe the stores while a bundle is still writing.
|
|
86
|
+
*/
|
|
87
|
+
private tryProcessGroupHistoryBundle;
|
|
77
88
|
private queryWithContext;
|
|
78
89
|
private handleIncomingFrame;
|
|
79
90
|
/**
|
package/dist/client/WaClient.js
CHANGED
|
@@ -3,12 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WaClient = void 0;
|
|
4
4
|
const node_events_1 = require("node:events");
|
|
5
5
|
const ignore_key_1 = require("./messaging/ignore-key");
|
|
6
|
+
const ephemeral_setting_1 = require("./persistence/ephemeral-setting");
|
|
7
|
+
const group_history_1 = require("./persistence/group-history");
|
|
6
8
|
const history_sync_1 = require("./persistence/history-sync");
|
|
7
9
|
const mailbox_1 = require("./persistence/mailbox");
|
|
8
10
|
const WriteBehindPersistence_1 = require("./persistence/WriteBehindPersistence");
|
|
9
11
|
const install_1 = require("./plugins/install");
|
|
10
12
|
const WaClientFactory_1 = require("./WaClientFactory");
|
|
11
13
|
const ConsoleLogger_1 = require("../infra/log/ConsoleLogger");
|
|
14
|
+
const content_1 = require("../message/encode/content");
|
|
12
15
|
const _proto_1 = require("../proto");
|
|
13
16
|
const constants_1 = require("../protocol/constants");
|
|
14
17
|
const jid_1 = require("../protocol/jid");
|
|
@@ -45,6 +48,10 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
45
48
|
threadStore: this.stores.threads,
|
|
46
49
|
contactStore: this.stores.contacts
|
|
47
50
|
}, this.logger, this.options.writeBehind);
|
|
51
|
+
this.observeEphemeralSetting = (0, ephemeral_setting_1.createEphemeralObserver)({
|
|
52
|
+
logger: this.logger,
|
|
53
|
+
chatMetadataStore: this.stores.chatMetadata
|
|
54
|
+
});
|
|
48
55
|
const dependencies = (0, WaClientFactory_1.buildWaClientDependencies)({
|
|
49
56
|
base,
|
|
50
57
|
runtime: {
|
|
@@ -185,6 +192,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
185
192
|
}
|
|
186
193
|
try {
|
|
187
194
|
this.emit('message', event);
|
|
195
|
+
this.observeEphemeralSetting(event);
|
|
188
196
|
void (0, mailbox_1.persistIncomingMailboxEntities)({
|
|
189
197
|
logger: this.logger,
|
|
190
198
|
writeBehind: this.writeBehind,
|
|
@@ -210,6 +218,9 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
210
218
|
});
|
|
211
219
|
});
|
|
212
220
|
}
|
|
221
|
+
if (this.options.history?.groupBundles === true && event.message && !event.key.fromMe) {
|
|
222
|
+
this.tryProcessGroupHistoryBundle(event);
|
|
223
|
+
}
|
|
213
224
|
const protocolMessage = event.message?.protocolMessage;
|
|
214
225
|
if (!protocolMessage) {
|
|
215
226
|
return;
|
|
@@ -261,6 +272,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
261
272
|
await (0, history_sync_1.runHistorySyncNotification)({
|
|
262
273
|
logger: this.logger,
|
|
263
274
|
mediaTransfer: this.mediaTransfer,
|
|
275
|
+
chatMetadataStore: this.stores.chatMetadata,
|
|
264
276
|
writeBehind: this.writeBehind,
|
|
265
277
|
emitEvent: this.emit.bind(this),
|
|
266
278
|
onPrivacyTokens: (conversations) => this.deps.trustedContactToken.hydrateFromHistorySync(conversations),
|
|
@@ -273,6 +285,16 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
273
285
|
}
|
|
274
286
|
return;
|
|
275
287
|
}
|
|
288
|
+
if (protocolType === _proto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING) {
|
|
289
|
+
(0, ephemeral_setting_1.persistIncomingEphemeralSetting)({
|
|
290
|
+
logger: this.logger,
|
|
291
|
+
writeBehind: this.writeBehind,
|
|
292
|
+
chatMetadataStore: this.stores.chatMetadata,
|
|
293
|
+
event,
|
|
294
|
+
protocolMessage
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
276
298
|
if (SYNC_RELATED_PROTOCOL_TYPES.has(protocolType)) {
|
|
277
299
|
this.logger.info('incoming sync-related protocol message', {
|
|
278
300
|
id: event.key.id,
|
|
@@ -291,6 +313,43 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
291
313
|
this.leaveIncomingHandler();
|
|
292
314
|
}
|
|
293
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Kicks off the group-history bundle download when the incoming message
|
|
318
|
+
* carries one. Fire-and-forget: the blob can be large, and the incoming
|
|
319
|
+
* handler must not stall behind a CDN fetch.
|
|
320
|
+
*
|
|
321
|
+
* The download outlives the handler that started it, so it takes a slot of
|
|
322
|
+
* its own in the drain accounting - `disconnect()` and `clearStoredState()`
|
|
323
|
+
* must not flush or wipe the stores while a bundle is still writing.
|
|
324
|
+
*/
|
|
325
|
+
tryProcessGroupHistoryBundle(event) {
|
|
326
|
+
const bundle = (0, content_1.unwrapMessage)(event.message ?? {}).messageHistoryBundle;
|
|
327
|
+
const groupJid = event.key.remoteJid;
|
|
328
|
+
if (!bundle || !groupJid) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!this.tryEnterIncomingHandler()) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const credentials = this.deps.authClient.getCurrentCredentials();
|
|
335
|
+
void (0, group_history_1.runGroupHistoryBundle)({
|
|
336
|
+
logger: this.logger,
|
|
337
|
+
mediaTransfer: this.mediaTransfer,
|
|
338
|
+
writeBehind: this.writeBehind,
|
|
339
|
+
emitEvent: this.emit.bind(this),
|
|
340
|
+
meJid: credentials?.meJid,
|
|
341
|
+
meLid: credentials?.meLid,
|
|
342
|
+
getAbPropNumber: (name) => this.deps.abPropsCoordinator.getConfigValue(name)
|
|
343
|
+
}, {
|
|
344
|
+
bundle,
|
|
345
|
+
groupJid,
|
|
346
|
+
senderJid: event.key.participant ?? undefined,
|
|
347
|
+
bundleMessageId: event.key.id,
|
|
348
|
+
sentAtSeconds: event.timestampSeconds
|
|
349
|
+
}).finally(() => {
|
|
350
|
+
this.leaveIncomingHandler();
|
|
351
|
+
});
|
|
352
|
+
}
|
|
294
353
|
async queryWithContext(context, node, timeoutMs = this.options.iqTimeoutMs ?? constants_1.WA_DEFAULTS.IQ_TIMEOUT_MS, contextData = {}, options = {}) {
|
|
295
354
|
return (0, query_1.queryWithContext)(async (queryNode, queryTimeoutMs) => this.deps.lowLevelCoordinator.query(queryNode, queryTimeoutMs, options), this.logger, context, node, timeoutMs, contextData);
|
|
296
355
|
}
|
|
@@ -365,6 +424,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
365
424
|
* your own backoff.
|
|
366
425
|
*/
|
|
367
426
|
async disconnect() {
|
|
427
|
+
this.deps.privacyCoordinator.stopAccountSyncRefresh();
|
|
368
428
|
await this.pauseIncomingEventsAndWaitDrain();
|
|
369
429
|
const writeBehindFlush = await this.writeBehind.flush(this.options.writeBehind?.flushTimeoutMs);
|
|
370
430
|
if (writeBehindFlush.remaining > 0) {
|
|
@@ -18,7 +18,7 @@ import { WaMobileCoordinator } from './coordinators/WaMobileCoordinator';
|
|
|
18
18
|
import { type WaNewsletterCoordinator } from './coordinators/WaNewsletterCoordinator';
|
|
19
19
|
import { WaPassiveTasksCoordinator } from './coordinators/WaPassiveTasksCoordinator';
|
|
20
20
|
import { type WaPresenceCoordinator } from './coordinators/WaPresenceCoordinator';
|
|
21
|
-
import { type
|
|
21
|
+
import { type WaPrivacyCoordinatorRuntime } from './coordinators/WaPrivacyCoordinator';
|
|
22
22
|
import { type WaProfileCoordinator } from './coordinators/WaProfileCoordinator';
|
|
23
23
|
import { WaRetryCoordinator } from './coordinators/WaRetryCoordinator';
|
|
24
24
|
import { type WaStatusCoordinator } from './coordinators/WaStatusCoordinator';
|
|
@@ -110,7 +110,7 @@ export interface WaClientDependencies {
|
|
|
110
110
|
readonly statusCoordinator: WaStatusCoordinator;
|
|
111
111
|
readonly broadcastListCoordinator: WaBroadcastListCoordinator;
|
|
112
112
|
readonly newsletterCoordinator: WaNewsletterCoordinator;
|
|
113
|
-
readonly privacyCoordinator:
|
|
113
|
+
readonly privacyCoordinator: WaPrivacyCoordinatorRuntime;
|
|
114
114
|
readonly profileCoordinator: WaProfileCoordinator;
|
|
115
115
|
readonly businessCoordinator: WaBusinessCoordinator;
|
|
116
116
|
readonly botCoordinator: WaBotCoordinator;
|