zapo-js 1.7.0 → 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/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/WaClientFactory.js +3 -1
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +35 -27
- package/dist/client/messaging/messages.js +16 -12
- package/dist/client/newsletter/discovery.js +4 -2
- package/dist/client/persistence/history-sync.d.ts +18 -0
- package/dist/client/persistence/history-sync.js +134 -42
- package/dist/client/plugins/types.d.ts +10 -2
- 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 +19 -4
- package/dist/crypto/curves/X25519.js +16 -2
- 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/WaClientFactory.js +3 -1
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +37 -29
- package/dist/esm/client/messaging/messages.js +16 -12
- package/dist/esm/client/newsletter/discovery.js +4 -2
- package/dist/esm/client/persistence/history-sync.js +133 -43
- 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 +19 -4
- package/dist/esm/crypto/curves/X25519.js +16 -2
- package/dist/esm/media/crypto/WaMediaCrypto.js +38 -19
- package/dist/esm/message/crypto/reporting-token.js +38 -216
- package/dist/esm/message/encode/content.js +53 -8
- package/dist/esm/message/primitives/incoming.js +14 -7
- package/dist/esm/protocol/defaults.js +1 -0
- package/dist/esm/protocol/device-identity.js +10 -5
- package/dist/esm/protocol/jid.js +83 -36
- 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/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/noise/WaClientPayload.js +43 -22
- package/dist/esm/transport/noise/WaFrameCodec.js +14 -4
- package/dist/esm/util/protoscan.js +181 -0
- package/dist/esm/util/runtime.js +34 -0
- package/dist/media/crypto/WaMediaCrypto.js +36 -17
- 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 +17 -1
- package/dist/message/encode/content.js +54 -8
- package/dist/message/primitives/incoming.js +14 -7
- package/dist/message/types.d.ts +10 -0
- package/dist/protocol/defaults.d.ts +1 -0
- package/dist/protocol/defaults.js +1 -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/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/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/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/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 +1 -1
|
@@ -105,19 +105,23 @@ function buildPollCreationMessage(content) {
|
|
|
105
105
|
const options = content.options.map((opt) => ({
|
|
106
106
|
optionName: typeof opt === 'string' ? opt : opt.name
|
|
107
107
|
}));
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
? { hideParticipantName: content.hideParticipantName }
|
|
118
|
-
: {})
|
|
119
|
-
}
|
|
108
|
+
const selectableOptionsCount = content.selectableCount ?? 1;
|
|
109
|
+
const allowAddOption = content.allowAddOption === true;
|
|
110
|
+
const hideParticipantName = content.hideParticipantName === true;
|
|
111
|
+
const pollBody = {
|
|
112
|
+
name: content.name,
|
|
113
|
+
options,
|
|
114
|
+
selectableOptionsCount,
|
|
115
|
+
...(allowAddOption ? { allowAddOption: true } : {}),
|
|
116
|
+
...(hideParticipantName ? { hideParticipantName: true } : {})
|
|
120
117
|
};
|
|
118
|
+
if (allowAddOption || hideParticipantName) {
|
|
119
|
+
return { pollCreationMessageV6: pollBody };
|
|
120
|
+
}
|
|
121
|
+
if (selectableOptionsCount === 1) {
|
|
122
|
+
return { pollCreationMessageV3: pollBody };
|
|
123
|
+
}
|
|
124
|
+
return { pollCreationMessage: pollBody };
|
|
121
125
|
}
|
|
122
126
|
function buildEventMessage(content) {
|
|
123
127
|
const eventMessage = {
|
|
@@ -17,7 +17,8 @@ function createDiscoveryOps(deps) {
|
|
|
17
17
|
fetch_full_image: opts?.fetchFullImage ?? keyType !== 'INVITE',
|
|
18
18
|
fetch_creation_time: opts?.fetchCreationTime ?? true,
|
|
19
19
|
fetch_wamo_sub: opts?.fetchWamoSub ?? false,
|
|
20
|
-
fetch_status_metadata: false
|
|
20
|
+
fetch_status_metadata: false,
|
|
21
|
+
fetch_pinned_messages: true
|
|
21
22
|
});
|
|
22
23
|
if (!data?.xwa2_newsletter) {
|
|
23
24
|
throw new Error('newsletter fetch returned no envelope');
|
|
@@ -107,7 +108,8 @@ function createDiscoveryOps(deps) {
|
|
|
107
108
|
type: isJid ? 'JID' : 'INVITE',
|
|
108
109
|
view_role: opts?.viewRole ?? newsletter_1.WA_NEWSLETTER_VIEW_ROLES.SUBSCRIBER
|
|
109
110
|
},
|
|
110
|
-
fetch_wamo_sub: opts?.fetchWamoSub ?? false
|
|
111
|
+
fetch_wamo_sub: opts?.fetchWamoSub ?? false,
|
|
112
|
+
fetch_pinned_messages: true
|
|
111
113
|
});
|
|
112
114
|
return (0, parse_1.parseDehydratedMetadata)(data);
|
|
113
115
|
}
|
|
@@ -4,6 +4,24 @@ import type { Logger } from '../../infra/log/types';
|
|
|
4
4
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
5
5
|
import { type Proto } from '../../proto';
|
|
6
6
|
import type { WaChatMetadataStore } from '../../store/contracts/chat-metadata.store';
|
|
7
|
+
interface ByteRange {
|
|
8
|
+
readonly start: number;
|
|
9
|
+
readonly end: number;
|
|
10
|
+
}
|
|
11
|
+
interface HistorySyncScan {
|
|
12
|
+
readonly conversationRanges: readonly ByteRange[];
|
|
13
|
+
readonly pushnames: readonly Proto.IPushname[];
|
|
14
|
+
readonly inlineContacts: readonly Proto.IInlineContact[];
|
|
15
|
+
readonly phoneNumberToLidMappings: readonly Proto.IPhoneNumberToLIDMapping[];
|
|
16
|
+
readonly nctSalt: Uint8Array | null;
|
|
17
|
+
readonly chunkOrder: number | null;
|
|
18
|
+
readonly progress: number | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function scanHistorySyncBlob(bytes: Uint8Array): HistorySyncScan;
|
|
21
|
+
export declare function scanConversationJidPair(bytes: Uint8Array, range: ByteRange): {
|
|
22
|
+
readonly pnJid: string | null;
|
|
23
|
+
readonly lidJid: string | null;
|
|
24
|
+
};
|
|
7
25
|
interface WaHistorySyncDeps {
|
|
8
26
|
readonly logger: Logger;
|
|
9
27
|
readonly mediaTransfer: WaMediaTransferClient;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scanHistorySyncBlob = scanHistorySyncBlob;
|
|
4
|
+
exports.scanConversationJidPair = scanConversationJidPair;
|
|
3
5
|
exports.runHistorySyncNotification = runHistorySyncNotification;
|
|
4
6
|
exports.processHistorySyncNotification = processHistorySyncNotification;
|
|
5
7
|
const node_util_1 = require("node:util");
|
|
@@ -10,6 +12,7 @@ const jid_1 = require("../../protocol/jid");
|
|
|
10
12
|
const message_1 = require("../../protocol/message");
|
|
11
13
|
const bytes_1 = require("../../util/bytes");
|
|
12
14
|
const primitives_1 = require("../../util/primitives");
|
|
15
|
+
const protoscan_1 = require("../../util/protoscan");
|
|
13
16
|
const unzipAsync = (0, node_util_1.promisify)(node_zlib_1.unzip);
|
|
14
17
|
const HANDLED_SYNC_TYPES = new Set([
|
|
15
18
|
_proto_1.proto.Message.HistorySyncType.INITIAL_BOOTSTRAP,
|
|
@@ -20,6 +23,90 @@ const HANDLED_SYNC_TYPES = new Set([
|
|
|
20
23
|
_proto_1.proto.Message.HistorySyncType.NON_BLOCKING_DATA
|
|
21
24
|
]);
|
|
22
25
|
const HISTORY_SYNC_MAX_PENDING_WRITES = 1024;
|
|
26
|
+
const HISTORY_SYNC_FIELDS = Object.freeze({
|
|
27
|
+
CONVERSATIONS: 2,
|
|
28
|
+
CHUNK_ORDER: 5,
|
|
29
|
+
PROGRESS: 6,
|
|
30
|
+
PUSHNAMES: 7,
|
|
31
|
+
PHONE_NUMBER_TO_LID_MAPPINGS: 15,
|
|
32
|
+
NCT_SALT: 19,
|
|
33
|
+
INLINE_CONTACTS: 20
|
|
34
|
+
});
|
|
35
|
+
const CONVERSATION_JID_FIELDS = Object.freeze({
|
|
36
|
+
PN_JID: 39,
|
|
37
|
+
LID_JID: 42,
|
|
38
|
+
ACCOUNT_LID: 49
|
|
39
|
+
});
|
|
40
|
+
function scanHistorySyncBlob(bytes) {
|
|
41
|
+
const conversationRanges = [];
|
|
42
|
+
const pushnames = [];
|
|
43
|
+
const inlineContacts = [];
|
|
44
|
+
const phoneNumberToLidMappings = [];
|
|
45
|
+
let nctSalt = null;
|
|
46
|
+
let chunkOrder = null;
|
|
47
|
+
let progress = null;
|
|
48
|
+
(0, protoscan_1.scanProtoFields)(bytes, 0, bytes.length, (field) => {
|
|
49
|
+
if (field.wireType === protoscan_1.PROTO_WIRE_TYPES.LEN) {
|
|
50
|
+
if (field.fieldNumber === HISTORY_SYNC_FIELDS.CONVERSATIONS) {
|
|
51
|
+
conversationRanges[conversationRanges.length] = {
|
|
52
|
+
start: field.valueStart,
|
|
53
|
+
end: field.valueEnd
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
else if (field.fieldNumber === HISTORY_SYNC_FIELDS.PUSHNAMES) {
|
|
57
|
+
pushnames[pushnames.length] = _proto_1.proto.Pushname.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
58
|
+
}
|
|
59
|
+
else if (field.fieldNumber === HISTORY_SYNC_FIELDS.PHONE_NUMBER_TO_LID_MAPPINGS) {
|
|
60
|
+
phoneNumberToLidMappings[phoneNumberToLidMappings.length] =
|
|
61
|
+
_proto_1.proto.PhoneNumberToLIDMapping.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
62
|
+
}
|
|
63
|
+
else if (field.fieldNumber === HISTORY_SYNC_FIELDS.INLINE_CONTACTS) {
|
|
64
|
+
inlineContacts[inlineContacts.length] = _proto_1.proto.InlineContact.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
65
|
+
}
|
|
66
|
+
else if (field.fieldNumber === HISTORY_SYNC_FIELDS.NCT_SALT) {
|
|
67
|
+
nctSalt = bytes.slice(field.valueStart, field.valueEnd);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (field.wireType === protoscan_1.PROTO_WIRE_TYPES.VARINT) {
|
|
72
|
+
if (field.fieldNumber === HISTORY_SYNC_FIELDS.CHUNK_ORDER) {
|
|
73
|
+
chunkOrder = field.varintValue;
|
|
74
|
+
}
|
|
75
|
+
else if (field.fieldNumber === HISTORY_SYNC_FIELDS.PROGRESS) {
|
|
76
|
+
progress = field.varintValue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
conversationRanges,
|
|
82
|
+
pushnames,
|
|
83
|
+
inlineContacts,
|
|
84
|
+
phoneNumberToLidMappings,
|
|
85
|
+
nctSalt,
|
|
86
|
+
chunkOrder,
|
|
87
|
+
progress
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function scanConversationJidPair(bytes, range) {
|
|
91
|
+
let pnJid = null;
|
|
92
|
+
let lidJid = null;
|
|
93
|
+
let accountLid = null;
|
|
94
|
+
(0, protoscan_1.scanProtoFields)(bytes, range.start, range.end, (field) => {
|
|
95
|
+
if (field.wireType !== protoscan_1.PROTO_WIRE_TYPES.LEN) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (field.fieldNumber === CONVERSATION_JID_FIELDS.PN_JID) {
|
|
99
|
+
pnJid = bytes_1.TEXT_DECODER.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
100
|
+
}
|
|
101
|
+
else if (field.fieldNumber === CONVERSATION_JID_FIELDS.LID_JID) {
|
|
102
|
+
lidJid = bytes_1.TEXT_DECODER.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
103
|
+
}
|
|
104
|
+
else if (field.fieldNumber === CONVERSATION_JID_FIELDS.ACCOUNT_LID) {
|
|
105
|
+
accountLid = bytes_1.TEXT_DECODER.decode(bytes.subarray(field.valueStart, field.valueEnd));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return { pnJid, lidJid: lidJid ?? accountLid };
|
|
109
|
+
}
|
|
23
110
|
async function runHistorySyncNotification(deps, notification) {
|
|
24
111
|
try {
|
|
25
112
|
await processHistorySyncNotification(deps, notification);
|
|
@@ -49,14 +136,17 @@ async function processHistorySyncNotification(deps, notification) {
|
|
|
49
136
|
}
|
|
50
137
|
const blob = await downloadHistorySyncBlob(deps, notification);
|
|
51
138
|
const decompressed = (0, bytes_1.toBytesView)(await unzipAsync(blob));
|
|
52
|
-
|
|
139
|
+
// Conversations are decoded one record at a time in the loop below so the
|
|
140
|
+
// full message graph never coexists in memory; the scan only records their
|
|
141
|
+
// byte ranges and decodes the small metadata fields.
|
|
142
|
+
const scan = scanHistorySyncBlob(decompressed);
|
|
53
143
|
deps.logger.info('decoded history sync chunk', {
|
|
54
144
|
syncType,
|
|
55
|
-
chunkOrder:
|
|
56
|
-
progress:
|
|
57
|
-
conversations:
|
|
58
|
-
pushnames:
|
|
59
|
-
inlineContacts:
|
|
145
|
+
chunkOrder: scan.chunkOrder,
|
|
146
|
+
progress: scan.progress,
|
|
147
|
+
conversations: scan.conversationRanges.length,
|
|
148
|
+
pushnames: scan.pushnames.length,
|
|
149
|
+
inlineContacts: scan.inlineContacts.length
|
|
60
150
|
});
|
|
61
151
|
const nowMs = Date.now();
|
|
62
152
|
const pendingWrites = [];
|
|
@@ -65,24 +155,23 @@ async function processHistorySyncNotification(deps, notification) {
|
|
|
65
155
|
// pushnames and mappings land on a single canonical (LID-form) contact
|
|
66
156
|
// row instead of two mirror rows (one keyed by PN, one keyed by LID).
|
|
67
157
|
const pnToLid = new Map();
|
|
68
|
-
for (const map of
|
|
158
|
+
for (const map of scan.phoneNumberToLidMappings) {
|
|
69
159
|
if (map.pnJid && map.lidJid) {
|
|
70
160
|
pnToLid.set(map.pnJid, map.lidJid);
|
|
71
161
|
}
|
|
72
162
|
}
|
|
73
|
-
for (const c of
|
|
163
|
+
for (const c of scan.inlineContacts) {
|
|
74
164
|
if (c.pnJid && c.lidJid) {
|
|
75
165
|
pnToLid.set(c.pnJid, c.lidJid);
|
|
76
166
|
}
|
|
77
167
|
}
|
|
78
|
-
for (const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
pnToLid.set(pnJid, lidJid);
|
|
168
|
+
for (const range of scan.conversationRanges) {
|
|
169
|
+
const pair = scanConversationJidPair(decompressed, range);
|
|
170
|
+
if (pair.pnJid && pair.lidJid) {
|
|
171
|
+
pnToLid.set(pair.pnJid, pair.lidJid);
|
|
83
172
|
}
|
|
84
173
|
}
|
|
85
|
-
for (const pn of
|
|
174
|
+
for (const pn of scan.pushnames) {
|
|
86
175
|
if (!pn.id) {
|
|
87
176
|
continue;
|
|
88
177
|
}
|
|
@@ -103,7 +192,7 @@ async function processHistorySyncNotification(deps, notification) {
|
|
|
103
192
|
await (0, history_blob_1.flushPendingWrites)(pendingWrites);
|
|
104
193
|
}
|
|
105
194
|
}
|
|
106
|
-
for (const c of
|
|
195
|
+
for (const c of scan.inlineContacts) {
|
|
107
196
|
const jid = c.lidJid ?? c.pnJid;
|
|
108
197
|
if (!jid) {
|
|
109
198
|
continue;
|
|
@@ -123,12 +212,32 @@ async function processHistorySyncNotification(deps, notification) {
|
|
|
123
212
|
}
|
|
124
213
|
}
|
|
125
214
|
let messagesCount = 0;
|
|
126
|
-
|
|
215
|
+
const tokenConversations = [];
|
|
216
|
+
for (const range of scan.conversationRanges) {
|
|
217
|
+
let conversation;
|
|
218
|
+
try {
|
|
219
|
+
conversation = _proto_1.proto.Conversation.decode(decompressed.subarray(range.start, range.end));
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
await Promise.allSettled(pendingWrites);
|
|
223
|
+
throw (0, primitives_1.toError)(error);
|
|
224
|
+
}
|
|
127
225
|
const threadJid = conversation.id;
|
|
128
226
|
if (!threadJid) {
|
|
129
227
|
deps.logger.debug('skipping history sync conversation without thread jid');
|
|
130
228
|
continue;
|
|
131
229
|
}
|
|
230
|
+
if (deps.onPrivacyTokens &&
|
|
231
|
+
(conversation.tcToken ||
|
|
232
|
+
conversation.tcTokenTimestamp ||
|
|
233
|
+
conversation.tcTokenSenderTimestamp)) {
|
|
234
|
+
tokenConversations[tokenConversations.length] = {
|
|
235
|
+
jid: threadJid,
|
|
236
|
+
tcToken: conversation.tcToken ? new Uint8Array(conversation.tcToken) : undefined,
|
|
237
|
+
tcTokenTimestamp: (0, primitives_1.longToNumber)(conversation.tcTokenTimestamp) || undefined,
|
|
238
|
+
tcTokenSenderTimestamp: (0, primitives_1.longToNumber)(conversation.tcTokenSenderTimestamp) || undefined
|
|
239
|
+
};
|
|
240
|
+
}
|
|
132
241
|
const ephemeralExpiration = conversation.ephemeralExpiration ?? undefined;
|
|
133
242
|
const ephemeralSettingTimestamp = (0, message_1.normalizeEphemeralSettingSeconds)((0, primitives_1.longToNumber)(conversation.ephemeralSettingTimestamp)) || undefined;
|
|
134
243
|
if (deps.chatMetadataStore && ephemeralExpiration !== undefined) {
|
|
@@ -216,37 +325,20 @@ async function processHistorySyncNotification(deps, notification) {
|
|
|
216
325
|
await (0, history_blob_1.flushPendingWrites)(pendingWrites);
|
|
217
326
|
}
|
|
218
327
|
}
|
|
219
|
-
if (deps.onPrivacyTokens) {
|
|
220
|
-
|
|
221
|
-
for (const conversation of historySync.conversations) {
|
|
222
|
-
if (!conversation.id)
|
|
223
|
-
continue;
|
|
224
|
-
if (conversation.tcToken ||
|
|
225
|
-
conversation.tcTokenTimestamp ||
|
|
226
|
-
conversation.tcTokenSenderTimestamp) {
|
|
227
|
-
tokenConversations[tokenConversations.length] = {
|
|
228
|
-
jid: conversation.id,
|
|
229
|
-
tcToken: conversation.tcToken,
|
|
230
|
-
tcTokenTimestamp: (0, primitives_1.longToNumber)(conversation.tcTokenTimestamp) || undefined,
|
|
231
|
-
tcTokenSenderTimestamp: (0, primitives_1.longToNumber)(conversation.tcTokenSenderTimestamp) || undefined
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (tokenConversations.length > 0) {
|
|
236
|
-
pendingWrites[pendingWrites.length] = deps.onPrivacyTokens(tokenConversations);
|
|
237
|
-
}
|
|
328
|
+
if (deps.onPrivacyTokens && tokenConversations.length > 0) {
|
|
329
|
+
pendingWrites[pendingWrites.length] = deps.onPrivacyTokens(tokenConversations);
|
|
238
330
|
}
|
|
239
|
-
if (deps.onNctSalt &&
|
|
240
|
-
pendingWrites[pendingWrites.length] = deps.onNctSalt(
|
|
331
|
+
if (deps.onNctSalt && scan.nctSalt) {
|
|
332
|
+
pendingWrites[pendingWrites.length] = deps.onNctSalt(scan.nctSalt);
|
|
241
333
|
}
|
|
242
334
|
const event = {
|
|
243
335
|
syncType,
|
|
244
336
|
messagesCount,
|
|
245
|
-
conversationsCount:
|
|
246
|
-
pushnamesCount:
|
|
247
|
-
inlineContactsCount:
|
|
248
|
-
chunkOrder:
|
|
249
|
-
progress:
|
|
337
|
+
conversationsCount: scan.conversationRanges.length,
|
|
338
|
+
pushnamesCount: scan.pushnames.length,
|
|
339
|
+
inlineContactsCount: scan.inlineContacts.length,
|
|
340
|
+
chunkOrder: scan.chunkOrder ?? undefined,
|
|
341
|
+
progress: scan.progress ?? undefined
|
|
250
342
|
};
|
|
251
343
|
await (0, history_blob_1.flushPendingWrites)(pendingWrites);
|
|
252
344
|
deps.emitEvent('history_sync_chunk', event);
|
|
@@ -63,10 +63,18 @@ type ExposedOf<P> = P extends {
|
|
|
63
63
|
* `{ readonly voip: WaVoipCoordinator }`.
|
|
64
64
|
*/
|
|
65
65
|
export type WaClientExposedFromPlugins<P extends readonly unknown[]> = UnionToIntersection<ExposedOf<P[number]>>;
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Event map a plugin contributes, carried as a phantom marker on its definition.
|
|
68
|
+
*
|
|
69
|
+
* The marker is optional (it never exists at runtime), so under
|
|
70
|
+
* `exactOptionalPropertyTypes` the inferred `E` keeps the `| undefined` the
|
|
71
|
+
* declaration emits. `Exclude` drops it before the union reaches
|
|
72
|
+
* {@link UnionToIntersection}, which would otherwise collapse
|
|
73
|
+
* `Events | undefined` to `never` and type every client listener as `never`.
|
|
74
|
+
*/
|
|
67
75
|
type EventsOf<P> = P extends {
|
|
68
76
|
readonly __pluginEvents?: infer E;
|
|
69
|
-
} ? unknown extends E ? {} : E : {};
|
|
77
|
+
} ? unknown extends E ? {} : Exclude<E, undefined> : {};
|
|
70
78
|
/**
|
|
71
79
|
* Client events contributed by a tuple of plugin definitions, derived from the
|
|
72
80
|
* plugin values (no global augmentation). Threaded into the client's
|
|
@@ -7,5 +7,5 @@ export { hkdf, hkdfSplit } from '../core/hkdf';
|
|
|
7
7
|
export { toSerializedPubKey, toRawPubKey, prependVersion, readVersionedContent } from '../core/keys';
|
|
8
8
|
export { writeNonceCounter } from '../core/nonce';
|
|
9
9
|
export { randomBytesAsync, randomFillAsync, randomIntAsync } from '../core/random';
|
|
10
|
-
export { sha1, sha256, sha512, md5Bytes, aesGcmEncrypt, aesGcmDecrypt, aesCbcEncrypt, aesCbcDecrypt, aesCtrEncrypt, aesCtrDecrypt, hmacSha256Sign, hmacSha512Sign, pbkdf2Sha256 } from '../core/primitives';
|
|
10
|
+
export { sha1, sha256, sha512, md5Bytes, aesGcmEncrypt, aesGcmDecrypt, aesCbcEncrypt, aesCbcEncryptWithTrailer, aesCbcDecrypt, aesCtrEncrypt, aesCtrDecrypt, hmacSha256Sign, hmacSha512Sign, pbkdf2Sha256 } from '../core/primitives';
|
|
11
11
|
export { xeddsaSign, xeddsaVerify } from '../core/xeddsa';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Cryptographic utilities
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.xeddsaVerify = exports.xeddsaSign = exports.pbkdf2Sha256 = exports.hmacSha512Sign = exports.hmacSha256Sign = exports.aesCtrDecrypt = exports.aesCtrEncrypt = exports.aesCbcDecrypt = exports.aesCbcEncrypt = exports.aesGcmDecrypt = exports.aesGcmEncrypt = exports.md5Bytes = exports.sha512 = exports.sha256 = exports.sha1 = exports.randomIntAsync = exports.randomFillAsync = exports.randomBytesAsync = exports.writeNonceCounter = exports.readVersionedContent = exports.prependVersion = exports.toRawPubKey = exports.toSerializedPubKey = exports.hkdfSplit = exports.hkdf = exports.X25519 = exports.Ed25519 = void 0;
|
|
6
|
+
exports.xeddsaVerify = exports.xeddsaSign = exports.pbkdf2Sha256 = exports.hmacSha512Sign = exports.hmacSha256Sign = exports.aesCtrDecrypt = exports.aesCtrEncrypt = exports.aesCbcDecrypt = exports.aesCbcEncryptWithTrailer = exports.aesCbcEncrypt = exports.aesGcmDecrypt = exports.aesGcmEncrypt = exports.md5Bytes = exports.sha512 = exports.sha256 = exports.sha1 = exports.randomIntAsync = exports.randomFillAsync = exports.randomBytesAsync = exports.writeNonceCounter = exports.readVersionedContent = exports.prependVersion = exports.toRawPubKey = exports.toSerializedPubKey = exports.hkdfSplit = exports.hkdf = exports.X25519 = exports.Ed25519 = void 0;
|
|
7
7
|
var Ed25519_1 = require("../curves/Ed25519");
|
|
8
8
|
Object.defineProperty(exports, "Ed25519", { enumerable: true, get: function () { return Ed25519_1.Ed25519; } });
|
|
9
9
|
var X25519_1 = require("../curves/X25519");
|
|
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "md5Bytes", { enumerable: true, get: function ()
|
|
|
30
30
|
Object.defineProperty(exports, "aesGcmEncrypt", { enumerable: true, get: function () { return primitives_1.aesGcmEncrypt; } });
|
|
31
31
|
Object.defineProperty(exports, "aesGcmDecrypt", { enumerable: true, get: function () { return primitives_1.aesGcmDecrypt; } });
|
|
32
32
|
Object.defineProperty(exports, "aesCbcEncrypt", { enumerable: true, get: function () { return primitives_1.aesCbcEncrypt; } });
|
|
33
|
+
Object.defineProperty(exports, "aesCbcEncryptWithTrailer", { enumerable: true, get: function () { return primitives_1.aesCbcEncryptWithTrailer; } });
|
|
33
34
|
Object.defineProperty(exports, "aesCbcDecrypt", { enumerable: true, get: function () { return primitives_1.aesCbcDecrypt; } });
|
|
34
35
|
Object.defineProperty(exports, "aesCtrEncrypt", { enumerable: true, get: function () { return primitives_1.aesCtrEncrypt; } });
|
|
35
36
|
Object.defineProperty(exports, "aesCtrDecrypt", { enumerable: true, get: function () { return primitives_1.aesCtrDecrypt; } });
|
|
@@ -21,6 +21,25 @@ export declare function aesGcmEncrypt(key: AesKey, nonce: Uint8Array, plaintext:
|
|
|
21
21
|
export declare function aesGcmDecrypt(key: AesKey, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array;
|
|
22
22
|
/** AES-256-CBC encrypt with PKCS#7 padding (default Node behavior). */
|
|
23
23
|
export declare function aesCbcEncrypt(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
24
|
+
/**
|
|
25
|
+
* AES-256-CBC encrypt into a buffer that reserves `trailerBytes` of spare room
|
|
26
|
+
* after the ciphertext, for a caller that has to append a MAC to it.
|
|
27
|
+
*
|
|
28
|
+
* Appending to the result of {@link aesCbcEncrypt} means copying the whole
|
|
29
|
+
* ciphertext a second time, which on a media payload is a copy of the entire
|
|
30
|
+
* file to make room for ten bytes. Sizing the destination from the PKCS#7
|
|
31
|
+
* output length up front removes that copy, and feeding the cipher in bounded
|
|
32
|
+
* chunks keeps the transient buffer Node hands back to a chunk rather than a
|
|
33
|
+
* second full-size ciphertext, so peak memory stays near one copy of the
|
|
34
|
+
* payload instead of two.
|
|
35
|
+
*
|
|
36
|
+
* The ciphertext occupies `result.length - trailerBytes`; the reserved tail is
|
|
37
|
+
* left zeroed for the caller to fill.
|
|
38
|
+
*
|
|
39
|
+
* @throws when `trailerBytes` is not a non-negative safe integer, or when the
|
|
40
|
+
* cipher yields a length other than the PKCS#7 size the destination was cut to.
|
|
41
|
+
*/
|
|
42
|
+
export declare function aesCbcEncryptWithTrailer(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array, trailerBytes: number): Uint8Array;
|
|
24
43
|
/** AES-256-CBC decrypt with PKCS#7 padding. */
|
|
25
44
|
export declare function aesCbcDecrypt(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array;
|
|
26
45
|
/** AES-256-CTR encrypt with the given 16-byte counter. */
|
|
@@ -7,6 +7,7 @@ exports.md5Bytes = md5Bytes;
|
|
|
7
7
|
exports.aesGcmEncrypt = aesGcmEncrypt;
|
|
8
8
|
exports.aesGcmDecrypt = aesGcmDecrypt;
|
|
9
9
|
exports.aesCbcEncrypt = aesCbcEncrypt;
|
|
10
|
+
exports.aesCbcEncryptWithTrailer = aesCbcEncryptWithTrailer;
|
|
10
11
|
exports.aesCbcDecrypt = aesCbcDecrypt;
|
|
11
12
|
exports.aesCtrEncrypt = aesCtrEncrypt;
|
|
12
13
|
exports.aesCtrDecrypt = aesCtrDecrypt;
|
|
@@ -17,6 +18,9 @@ const node_crypto_1 = require("node:crypto");
|
|
|
17
18
|
const node_util_1 = require("node:util");
|
|
18
19
|
const bytes_1 = require("../../util/bytes");
|
|
19
20
|
const AES_GCM_TAG_LENGTH = 16;
|
|
21
|
+
const AES_CBC_BLOCK_SIZE = 16;
|
|
22
|
+
/** Bounds the transient buffer `cipher.update` returns per call. */
|
|
23
|
+
const AES_CBC_CHUNK_SIZE = 262144;
|
|
20
24
|
const pbkdf2Async = (0, node_util_1.promisify)(node_crypto_1.pbkdf2);
|
|
21
25
|
function feed(target, input) {
|
|
22
26
|
if (Array.isArray(input)) {
|
|
@@ -96,6 +100,50 @@ function aesCbcEncrypt(key, iv, plaintext) {
|
|
|
96
100
|
}
|
|
97
101
|
return (0, bytes_1.concatBytes)([head, tail]);
|
|
98
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* AES-256-CBC encrypt into a buffer that reserves `trailerBytes` of spare room
|
|
105
|
+
* after the ciphertext, for a caller that has to append a MAC to it.
|
|
106
|
+
*
|
|
107
|
+
* Appending to the result of {@link aesCbcEncrypt} means copying the whole
|
|
108
|
+
* ciphertext a second time, which on a media payload is a copy of the entire
|
|
109
|
+
* file to make room for ten bytes. Sizing the destination from the PKCS#7
|
|
110
|
+
* output length up front removes that copy, and feeding the cipher in bounded
|
|
111
|
+
* chunks keeps the transient buffer Node hands back to a chunk rather than a
|
|
112
|
+
* second full-size ciphertext, so peak memory stays near one copy of the
|
|
113
|
+
* payload instead of two.
|
|
114
|
+
*
|
|
115
|
+
* The ciphertext occupies `result.length - trailerBytes`; the reserved tail is
|
|
116
|
+
* left zeroed for the caller to fill.
|
|
117
|
+
*
|
|
118
|
+
* @throws when `trailerBytes` is not a non-negative safe integer, or when the
|
|
119
|
+
* cipher yields a length other than the PKCS#7 size the destination was cut to.
|
|
120
|
+
*/
|
|
121
|
+
function aesCbcEncryptWithTrailer(key, iv, plaintext, trailerBytes) {
|
|
122
|
+
if (!Number.isSafeInteger(trailerBytes) || trailerBytes < 0) {
|
|
123
|
+
throw new Error(`invalid trailer length ${trailerBytes}`);
|
|
124
|
+
}
|
|
125
|
+
const cipherLength = (Math.floor(plaintext.length / AES_CBC_BLOCK_SIZE) + 1) * AES_CBC_BLOCK_SIZE;
|
|
126
|
+
const out = new Uint8Array(cipherLength + trailerBytes);
|
|
127
|
+
const cipher = (0, node_crypto_1.createCipheriv)('aes-256-cbc', key, iv);
|
|
128
|
+
let offset = 0;
|
|
129
|
+
for (let start = 0; start < plaintext.length; start += AES_CBC_CHUNK_SIZE) {
|
|
130
|
+
const end = Math.min(start + AES_CBC_CHUNK_SIZE, plaintext.length);
|
|
131
|
+
const block = cipher.update(plaintext.subarray(start, end));
|
|
132
|
+
if (block.length > 0) {
|
|
133
|
+
out.set(block, offset);
|
|
134
|
+
offset += block.length;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const tail = cipher.final();
|
|
138
|
+
if (tail.length > 0) {
|
|
139
|
+
out.set(tail, offset);
|
|
140
|
+
offset += tail.length;
|
|
141
|
+
}
|
|
142
|
+
if (offset !== cipherLength) {
|
|
143
|
+
throw new Error(`aes-cbc produced ${offset} bytes, expected ${cipherLength}`);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
99
147
|
/** AES-256-CBC decrypt with PKCS#7 padding. */
|
|
100
148
|
function aesCbcDecrypt(key, iv, ciphertext) {
|
|
101
149
|
const decipher = (0, node_crypto_1.createDecipheriv)('aes-256-cbc', key, iv);
|
|
@@ -51,6 +51,23 @@ async function xeddsaVerify(curvePublicKey, message, signature) {
|
|
|
51
51
|
signature[lastByteIndex] = originalLastByte;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
const publicDerivationCache = new WeakMap();
|
|
55
|
+
function derivePublicForSigning(privateKey) {
|
|
56
|
+
const cached = publicDerivationCache.get(privateKey);
|
|
57
|
+
if (cached) {
|
|
58
|
+
return cached;
|
|
59
|
+
}
|
|
60
|
+
const clampedPrivateKey = (0, X25519_1.clampCurvePrivateKeyInPlace)(privateKey);
|
|
61
|
+
const privateScalar = (0, le_1.bytesToBigIntLE)(clampedPrivateKey);
|
|
62
|
+
const encodedPublic = (0, edwards_1.encodeExtendedPoint)((0, edwards_1.scalarMultBase)(privateScalar));
|
|
63
|
+
const derivation = {
|
|
64
|
+
privateScalar,
|
|
65
|
+
encodedPublic,
|
|
66
|
+
pubKeySignBit: encodedPublic[31] & 0x80
|
|
67
|
+
};
|
|
68
|
+
publicDerivationCache.set(privateKey, derivation);
|
|
69
|
+
return derivation;
|
|
70
|
+
}
|
|
54
71
|
/**
|
|
55
72
|
* Signs `message` with an X25519 (Montgomery) private key using the XEdDSA
|
|
56
73
|
* construction. Returns a 64-byte signature.
|
|
@@ -60,10 +77,8 @@ async function xeddsaSign(privateKey, message) {
|
|
|
60
77
|
if (nativeBinding) {
|
|
61
78
|
return (0, bytes_1.toBytesView)(nativeBinding.xeddsaSign(privateKey, message));
|
|
62
79
|
}
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const encodedPublic = (0, edwards_1.encodeExtendedPoint)((0, edwards_1.scalarMultBase)(privateScalar));
|
|
66
|
-
const pubKeySignBit = encodedPublic[31] & 0x80;
|
|
80
|
+
const { privateScalar, encodedPublic, pubKeySignBit } = derivePublicForSigning(privateKey);
|
|
81
|
+
const clampedPrivateKey = privateKey;
|
|
67
82
|
const randomSuffix = await (0, random_1.randomBytesAsync)(64);
|
|
68
83
|
const r = (0, mod_1.modGroup)((0, le_1.bytesToBigIntLE)((0, primitives_1.sha512)([PREFIX_SIGNATURE_RANDOM, clampedPrivateKey, message, randomSuffix])));
|
|
69
84
|
const encodedR = (0, edwards_1.encodeExtendedPoint)((0, edwards_1.scalarMultBase)(r));
|
|
@@ -86,19 +86,33 @@ function montgomeryToEdwardsPublic(curvePublicKey, signBit) {
|
|
|
86
86
|
encoded[31] = (encoded[31] & 0x7f) | (signBit & 0x80);
|
|
87
87
|
return encoded;
|
|
88
88
|
}
|
|
89
|
+
const privateKeyObjectCache = new WeakMap();
|
|
90
|
+
const publicKeyObjectCache = new WeakMap();
|
|
89
91
|
function x25519PrivateKeyObject(privKey) {
|
|
90
|
-
|
|
92
|
+
const cached = privateKeyObjectCache.get(privKey);
|
|
93
|
+
if (cached) {
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
const keyObject = (0, node_crypto_1.createPrivateKey)({
|
|
91
97
|
key: (0, types_1.pkcs8FromRawPrivate)(constants_1.X25519_PKCS8_PREFIX, privKey),
|
|
92
98
|
format: 'der',
|
|
93
99
|
type: 'pkcs8'
|
|
94
100
|
});
|
|
101
|
+
privateKeyObjectCache.set(privKey, keyObject);
|
|
102
|
+
return keyObject;
|
|
95
103
|
}
|
|
96
104
|
function x25519PublicKeyObject(pubKey) {
|
|
97
|
-
|
|
105
|
+
const cached = publicKeyObjectCache.get(pubKey);
|
|
106
|
+
if (cached) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const keyObject = (0, node_crypto_1.createPublicKey)({
|
|
98
110
|
key: (0, bytes_1.concatBytes)([constants_1.X25519_SPKI_PREFIX, pubKey]),
|
|
99
111
|
format: 'der',
|
|
100
112
|
type: 'spki'
|
|
101
113
|
});
|
|
114
|
+
publicKeyObjectCache.set(pubKey, keyObject);
|
|
115
|
+
return keyObject;
|
|
102
116
|
}
|
|
103
117
|
/**
|
|
104
118
|
* X25519 key-pair generation and Diffie-Hellman scalar multiplication
|
|
@@ -174,12 +174,10 @@ export class WaAppStateCrypto {
|
|
|
174
174
|
if (out.byteLength !== left.byteLength) {
|
|
175
175
|
throw new Error('lt hash output length mismatch');
|
|
176
176
|
}
|
|
177
|
-
const leftView = new DataView(left.buffer, left.byteOffset, left.byteLength);
|
|
178
|
-
const rightView = new DataView(right.buffer, right.byteOffset, right.byteLength);
|
|
179
|
-
const outView = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
180
177
|
for (let offset = 0; offset < left.byteLength; offset += APP_STATE_POINT_SIZE) {
|
|
181
|
-
const value = combine(
|
|
182
|
-
|
|
178
|
+
const value = combine(left[offset] | (left[offset + 1] << 8), right[offset] | (right[offset + 1] << 8));
|
|
179
|
+
out[offset] = value & 0xff;
|
|
180
|
+
out[offset + 1] = (value >>> 8) & 0xff;
|
|
183
181
|
}
|
|
184
182
|
return out;
|
|
185
183
|
}
|
|
@@ -196,7 +194,7 @@ export class WaAppStateCrypto {
|
|
|
196
194
|
generateValueMac(valueMacHmacKey, associatedData, iv, cipherText) {
|
|
197
195
|
const octetLength = new Uint8Array(APP_STATE_MAC_OCTET_LENGTH);
|
|
198
196
|
octetLength[octetLength.length - 1] = associatedData.byteLength & 0xff;
|
|
199
|
-
const full = hmacSha512Sign(valueMacHmacKey,
|
|
197
|
+
const full = hmacSha512Sign(valueMacHmacKey, [associatedData, iv, cipherText, octetLength]);
|
|
200
198
|
return full.subarray(0, APP_STATE_VALUE_MAC_LENGTH);
|
|
201
199
|
}
|
|
202
200
|
touchDerivedKeysCacheEntry(cacheKey, keys) {
|
|
@@ -20,11 +20,18 @@ export class WaAuthClient {
|
|
|
20
20
|
constructor(options, deps) {
|
|
21
21
|
this.versionOverride = null;
|
|
22
22
|
this.mobileAppVersionOverride = null;
|
|
23
|
+
/**
|
|
24
|
+
* Successful logins made by this instance, advertised as
|
|
25
|
+
* `connectAttemptCount`. Process-local by design – the persisted
|
|
26
|
+
* counterpart is `credentials.loginCounter`.
|
|
27
|
+
*/
|
|
28
|
+
this.connectAttemptCount = 0;
|
|
23
29
|
const device = resolveWaDeviceIdentity(options);
|
|
24
30
|
this.options = Object.freeze({
|
|
25
31
|
...options,
|
|
26
32
|
deviceBrowser: device.browser,
|
|
27
33
|
deviceOsDisplayName: device.osDisplayName,
|
|
34
|
+
deviceOsVersion: device.osVersion ?? undefined,
|
|
28
35
|
devicePlatform: device.platform,
|
|
29
36
|
requireFullSync: options.requireFullSync
|
|
30
37
|
});
|
|
@@ -122,8 +129,10 @@ export class WaAuthClient {
|
|
|
122
129
|
const mobileAppVersionOverride = this.mobileAppVersionOverride;
|
|
123
130
|
this.mobileAppVersionOverride = null;
|
|
124
131
|
return buildCommsConfig(this.logger, this.requireCredentials(), socketOptions, {
|
|
132
|
+
connectAttemptCount: this.connectAttemptCount,
|
|
125
133
|
deviceBrowser: this.options.deviceBrowser,
|
|
126
134
|
deviceOsDisplayName: this.options.deviceOsDisplayName,
|
|
135
|
+
deviceOsVersion: this.options.deviceOsVersion,
|
|
127
136
|
requireFullSync: this.options.requireFullSync,
|
|
128
137
|
version: override ?? this.options.version,
|
|
129
138
|
mobileTransport: this.options.mobileTransport,
|
|
@@ -236,10 +245,19 @@ export class WaAuthClient {
|
|
|
236
245
|
* Persists the per-connection success attributes from the server (LID,
|
|
237
246
|
* display name, companion key, last-success ts, props versions, ...). Only
|
|
238
247
|
* persists when at least one attribute actually changed.
|
|
248
|
+
*
|
|
249
|
+
* Pass `countsAsLogin` only for the server `success` node: it advances the
|
|
250
|
+
* login counters advertised as `lc` / `connectAttemptCount`. Callers that
|
|
251
|
+
* merely reuse this to persist a single attribute (a push-name change, for
|
|
252
|
+
* instance) must leave it unset.
|
|
239
253
|
*/
|
|
240
|
-
async persistSuccessAttributes(attributes) {
|
|
254
|
+
async persistSuccessAttributes(attributes, { countsAsLogin = false } = {}) {
|
|
255
|
+
if (countsAsLogin) {
|
|
256
|
+
this.connectAttemptCount += 1;
|
|
257
|
+
}
|
|
241
258
|
let persistDiff;
|
|
242
259
|
const computeDiff = (current, next) => ({
|
|
260
|
+
loginCounterChanged: next.loginCounter !== current.loginCounter,
|
|
243
261
|
lidChanged: next.meLid !== current.meLid,
|
|
244
262
|
displayNameChanged: next.meDisplayName !== current.meDisplayName,
|
|
245
263
|
companionChanged: (current.companionEncStatic === undefined) !==
|
|
@@ -259,6 +277,9 @@ export class WaAuthClient {
|
|
|
259
277
|
meDisplayName: attributes.meDisplayName ?? credentials.meDisplayName,
|
|
260
278
|
companionEncStatic: attributes.companionEncStatic ?? credentials.companionEncStatic,
|
|
261
279
|
lastSuccessTs: attributes.lastSuccessTs ?? credentials.lastSuccessTs,
|
|
280
|
+
loginCounter: countsAsLogin
|
|
281
|
+
? (credentials.loginCounter ?? 0) + 1
|
|
282
|
+
: credentials.loginCounter,
|
|
262
283
|
propsVersion: attributes.propsVersion ?? credentials.propsVersion,
|
|
263
284
|
abPropsVersion: attributes.abPropsVersion ?? credentials.abPropsVersion,
|
|
264
285
|
connectionLocation: attributes.connectionLocation ?? credentials.connectionLocation,
|
|
@@ -174,6 +174,8 @@ export async function buildCommsConfig(logger, credentials, socketOptions, clien
|
|
|
174
174
|
? {
|
|
175
175
|
username: loginIdentity.username,
|
|
176
176
|
device: loginIdentity.device,
|
|
177
|
+
loginCounter: credentials.loginCounter ?? 0,
|
|
178
|
+
connectAttemptCount: clientOptions.connectAttemptCount ?? 0,
|
|
177
179
|
deviceBrowser: clientOptions.deviceBrowser,
|
|
178
180
|
deviceOsDisplayName: clientOptions.deviceOsDisplayName,
|
|
179
181
|
versionBase
|
|
@@ -185,6 +187,7 @@ export async function buildCommsConfig(logger, credentials, socketOptions, clien
|
|
|
185
187
|
signedPreKey: credentials.signedPreKey,
|
|
186
188
|
deviceBrowser: clientOptions.deviceBrowser,
|
|
187
189
|
deviceOsDisplayName: clientOptions.deviceOsDisplayName,
|
|
190
|
+
deviceOsVersion: clientOptions.deviceOsVersion,
|
|
188
191
|
requireFullSync: clientOptions.requireFullSync,
|
|
189
192
|
versionBase
|
|
190
193
|
}
|