whalibmob 5.25.0 → 5.26.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/index.d.ts +10 -1
- package/lib/Client.js +158 -28
- package/lib/appstate/AppStateSync.js +15 -4
- package/lib/appstate/LTHash.js +10 -2
- package/lib/proto/MessageProto.js +27 -3
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -665,7 +665,7 @@ export interface WhalibmobEvents {
|
|
|
665
665
|
privacy_settings: (p: { changes: any; settings: PrivacySettings }) => void;
|
|
666
666
|
|
|
667
667
|
app_state_sync: (r: AppStateSyncResult) => void;
|
|
668
|
-
app_state_mutation: (m: { collection: string; index: any; action: any; removed: boolean }) => void;
|
|
668
|
+
app_state_mutation: (m: { collection: string; index: any; action: any; removed: boolean; version?: number }) => void;
|
|
669
669
|
app_state_key_missing: (m: { collection: string; keyId: string }) => void;
|
|
670
670
|
app_state_keys: (m: { keys: any[] }) => void;
|
|
671
671
|
|
|
@@ -821,6 +821,15 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
821
821
|
syncAppState(names?: string[] | null, opts?: { snapshot?: boolean }): Promise<AppStateSyncResult>;
|
|
822
822
|
/** Whether an app-state key is available — `false` on an SMS session with no companion. */
|
|
823
823
|
canSyncAppState(): boolean;
|
|
824
|
+
/**
|
|
825
|
+
* Ask the primary device for app-state keys it never shared.
|
|
826
|
+
*
|
|
827
|
+
* Sent automatically when a sync runs into a key it does not have, so calling
|
|
828
|
+
* it is rarely necessary. Ids may be base64 or hex. The same id is not asked
|
|
829
|
+
* for twice within 24 hours; resolves to `null` when everything passed was
|
|
830
|
+
* held down, or when there is no connection to send it over.
|
|
831
|
+
*/
|
|
832
|
+
requestAppStateKeys(keyIds: string[]): Promise<string | null>;
|
|
824
833
|
|
|
825
834
|
// ─── Account restriction ─────────────────────────────────────────────────
|
|
826
835
|
fetchReachoutTimelock(): Promise<ReachoutTimelockState>;
|
package/lib/Client.js
CHANGED
|
@@ -79,6 +79,17 @@ const MEX_QUERY_REACHOUT_TIMELOCK = '23983697327930364';
|
|
|
79
79
|
// server takes a longer one, answers <iq type="result"/> and keeps the old text.
|
|
80
80
|
const ABOUT_MAX_LENGTH = 139;
|
|
81
81
|
|
|
82
|
+
// How many times a single syncAppState call will go back for more. A busy
|
|
83
|
+
// account really does need several rounds, so this is high enough not to cut a
|
|
84
|
+
// genuine sync short; it exists only so a server that never stops saying
|
|
85
|
+
// has_more_patches cannot keep us here indefinitely.
|
|
86
|
+
const MAX_APP_STATE_ROUNDS = 30;
|
|
87
|
+
|
|
88
|
+
// How long to wait before asking the primary device for the same app-state key
|
|
89
|
+
// again. A key it has not shared is usually one it will not share; asking on
|
|
90
|
+
// every reconnect would be a message to your own phone every few seconds.
|
|
91
|
+
const APP_STATE_KEY_REQUEST_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
92
|
+
|
|
82
93
|
// How long the first message to a contact waits for its trusted-contact token.
|
|
83
94
|
// One round-trip to the server, no more: past this the message goes out without
|
|
84
95
|
// a token rather than sitting in the queue, and the issuance finishes in the
|
|
@@ -1999,11 +2010,13 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1999
2010
|
const nodes = wanted.map(name => {
|
|
2000
2011
|
const st = this._appState.get(name);
|
|
2001
2012
|
const wantSnapshot = !!opts.snapshot || !st.version;
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2013
|
+
// Asking for a snapshot means asking for the collection from nothing, so
|
|
2014
|
+
// there is no version to ask from. The attribute is left off entirely
|
|
2015
|
+
// rather than sent as 0 — that is what the official client puts on the
|
|
2016
|
+
// wire, and a version on a snapshot request is a contradiction.
|
|
2017
|
+
const attrs = { name, return_snapshot: wantSnapshot ? 'true' : 'false' };
|
|
2018
|
+
if (!wantSnapshot) attrs.version = String(st.version);
|
|
2019
|
+
return new BinaryNode('collection', attrs, null);
|
|
2007
2020
|
});
|
|
2008
2021
|
|
|
2009
2022
|
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
@@ -2043,6 +2056,79 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2043
2056
|
return out;
|
|
2044
2057
|
}
|
|
2045
2058
|
|
|
2059
|
+
/**
|
|
2060
|
+
* Ask the primary device for app-state keys it never shared.
|
|
2061
|
+
*
|
|
2062
|
+
* App-state mutations are encrypted under keys the phone hands out in an
|
|
2063
|
+
* encrypted message of its own. Miss that message — linked while the phone
|
|
2064
|
+
* was off, a session that had to be rebuilt — and every patch signed with
|
|
2065
|
+
* that key is unreadable, and stays unreadable: the key is not re-offered,
|
|
2066
|
+
* and nothing later in the collection can be decoded past it. The request
|
|
2067
|
+
* goes out as a peer message, the same channel a placeholder resend uses, so
|
|
2068
|
+
* the server routes it to your own devices rather than delivering it as a
|
|
2069
|
+
* chat message to yourself.
|
|
2070
|
+
*
|
|
2071
|
+
* Asking twice for the same key inside a day achieves nothing, so each id is
|
|
2072
|
+
* remembered and held down for that long. This is what whatsmeow does with
|
|
2073
|
+
* `requestAppStateKeys`, and for the same reason.
|
|
2074
|
+
*
|
|
2075
|
+
* @param {string[]} keyIds base64 (or hex) key ids, as the decoder reports
|
|
2076
|
+
* @returns {Promise<string|null>} the id of the message that carried it
|
|
2077
|
+
*/
|
|
2078
|
+
async requestAppStateKeys(keyIds) {
|
|
2079
|
+
if (!Array.isArray(keyIds) || !keyIds.length) return null;
|
|
2080
|
+
if (!this._sender || !this._connected) return null;
|
|
2081
|
+
const me = this._store && this._store.me && this._store.me.id;
|
|
2082
|
+
if (!me) return null;
|
|
2083
|
+
|
|
2084
|
+
this._appStateKeyRequests = this._appStateKeyRequests || new Map();
|
|
2085
|
+
const now = Date.now();
|
|
2086
|
+
const want = [];
|
|
2087
|
+
|
|
2088
|
+
for (const id of keyIds) {
|
|
2089
|
+
if (!id) continue;
|
|
2090
|
+
// The decoder reports base64; a caller with the stored spelling has hex.
|
|
2091
|
+
// Both name the same bytes, so normalise before deduplicating.
|
|
2092
|
+
let raw;
|
|
2093
|
+
try {
|
|
2094
|
+
raw = /^[0-9a-fA-F]+$/.test(id) && id.length % 2 === 0
|
|
2095
|
+
? Buffer.from(id, 'hex')
|
|
2096
|
+
: Buffer.from(id, 'base64');
|
|
2097
|
+
} catch (_) { continue; }
|
|
2098
|
+
if (!raw || !raw.length) continue;
|
|
2099
|
+
|
|
2100
|
+
const key = raw.toString('base64');
|
|
2101
|
+
const last = this._appStateKeyRequests.get(key);
|
|
2102
|
+
if (last && now - last < APP_STATE_KEY_REQUEST_INTERVAL_MS) continue;
|
|
2103
|
+
this._appStateKeyRequests.set(key, now);
|
|
2104
|
+
want.push(raw);
|
|
2105
|
+
}
|
|
2106
|
+
if (!want.length) return null;
|
|
2107
|
+
|
|
2108
|
+
const {
|
|
2109
|
+
encodeMessage, encodeProtocolMessage, encodeAppStateSyncKeyRequest,
|
|
2110
|
+
PROTOCOL_TYPE_APP_STATE_SYNC_KEY_REQUEST
|
|
2111
|
+
} = require('./proto/MessageProto');
|
|
2112
|
+
|
|
2113
|
+
const payload = encodeProtocolMessage({
|
|
2114
|
+
type: PROTOCOL_TYPE_APP_STATE_SYNC_KEY_REQUEST,
|
|
2115
|
+
appStateSyncKeyRequest: encodeAppStateSyncKeyRequest(want)
|
|
2116
|
+
});
|
|
2117
|
+
const msgBuf = encodeMessage('protocol', payload);
|
|
2118
|
+
const msgId = this._genMsgId();
|
|
2119
|
+
|
|
2120
|
+
_whaDbg('[DBG] APP_STATE_KEY_REQUEST ' +
|
|
2121
|
+
want.map(b => b.toString('hex')).join(', '));
|
|
2122
|
+
|
|
2123
|
+
await this._sender._sendMessage(String(me), msgId, msgBuf, 'text', {
|
|
2124
|
+
_protocol: true, // not a reach-out; keeps it off the tcToken gate
|
|
2125
|
+
category: 'peer',
|
|
2126
|
+
pushPriority: 'high_force',
|
|
2127
|
+
additionalNodes: [new BinaryNode('meta', { appdata: 'default' }, null)]
|
|
2128
|
+
});
|
|
2129
|
+
return msgId;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2046
2132
|
async _applyAppStateResponse(resp, opts) {
|
|
2047
2133
|
const syncNode = findChild(resp, 'sync');
|
|
2048
2134
|
const cols = (syncNode && Array.isArray(syncNode.content))
|
|
@@ -2089,11 +2175,13 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2089
2175
|
}
|
|
2090
2176
|
} catch (err) {
|
|
2091
2177
|
if (err && err.isMissingKey) {
|
|
2092
|
-
// Nothing can be read until the primary shares
|
|
2178
|
+
// Nothing in this collection can be read until the primary shares
|
|
2179
|
+
// that key, and it will not offer one unasked — so ask, and leave the
|
|
2093
2180
|
// collection where it is so the next sync picks up from here.
|
|
2094
2181
|
_whaDbg('[DBG] APP_STATE ' + name + ' waiting for key ' + err.keyId);
|
|
2095
2182
|
summary[name] = { waitingForKey: err.keyId };
|
|
2096
2183
|
this.emit('app_state_key_missing', { collection: name, keyId: err.keyId });
|
|
2184
|
+
this.requestAppStateKeys([err.keyId]).catch(() => {});
|
|
2097
2185
|
continue;
|
|
2098
2186
|
}
|
|
2099
2187
|
throw err;
|
|
@@ -2123,10 +2211,23 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2123
2211
|
if (col.attrs && col.attrs.has_more_patches === 'true') more.push(name);
|
|
2124
2212
|
}
|
|
2125
2213
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2214
|
+
// The server hands a long collection over in pieces and says so with
|
|
2215
|
+
// has_more_patches; a hash that stopped agreeing puts the collection back
|
|
2216
|
+
// here too, once, as a snapshot. Either way there is more to fetch, and
|
|
2217
|
+
// stopping after one more round would leave the collection half-read
|
|
2218
|
+
// without anything saying so. Keep going until the server stops asking —
|
|
2219
|
+
// with a ceiling, because a server that always says "more" would otherwise
|
|
2220
|
+
// spin here forever.
|
|
2221
|
+
if (more.length) {
|
|
2222
|
+
const rounds = (opts._round || 0) + 1;
|
|
2223
|
+
if (rounds > MAX_APP_STATE_ROUNDS) {
|
|
2224
|
+
_whaDbg('[DBG] APP_STATE giving up after ' + rounds + ' rounds: ' + more.join(', '));
|
|
2225
|
+
} else {
|
|
2226
|
+
const again = await this.syncAppState(more,
|
|
2227
|
+
Object.assign({}, opts, { _round: rounds, snapshot: false }));
|
|
2228
|
+
applied += again.applied;
|
|
2229
|
+
Object.assign(summary, again.collections);
|
|
2230
|
+
}
|
|
2130
2231
|
}
|
|
2131
2232
|
|
|
2132
2233
|
this.emit('app_state_sync', { collections: summary, applied });
|
|
@@ -2254,7 +2355,7 @@ class WhalibmobClient extends EventEmitter {
|
|
|
2254
2355
|
// Something the server tracks that this library does not model yet.
|
|
2255
2356
|
// Reported rather than dropped, so it is visible that it happened.
|
|
2256
2357
|
this.emit('app_state_mutation', {
|
|
2257
|
-
collection, index: m.index, action: a, removed
|
|
2358
|
+
collection, index: m.index, action: a, removed, version: m.version
|
|
2258
2359
|
});
|
|
2259
2360
|
break;
|
|
2260
2361
|
}
|
|
@@ -5360,9 +5461,9 @@ class WhalibmobClient extends EventEmitter {
|
|
|
5360
5461
|
// Web-mode history lands in its own files. A number can be registered over
|
|
5361
5462
|
// SMS and separately linked as a companion, and only the companion ever
|
|
5362
5463
|
// receives history — merging the two would be merging different accounts'
|
|
5363
|
-
// views of the same number.
|
|
5364
|
-
const phone =
|
|
5365
|
-
|
|
5464
|
+
// views of the same number. Same prefix the readers use.
|
|
5465
|
+
const phone = this._sessionFilePrefix();
|
|
5466
|
+
if (!phone) return;
|
|
5366
5467
|
const sessionDir = this._sessionDir;
|
|
5367
5468
|
|
|
5368
5469
|
_whaDbg('[DBG] HIST_SYNC start syncType=' + notification.syncType +
|
|
@@ -5510,6 +5611,19 @@ class WhalibmobClient extends EventEmitter {
|
|
|
5510
5611
|
_whaDbg('[DBG] APP_STATE_KEY_SHARE_SAVE_ERR ' + e.message);
|
|
5511
5612
|
}
|
|
5512
5613
|
|
|
5614
|
+
// A key that has arrived is no longer one we are holding a request down
|
|
5615
|
+
// for; drop the hold so that if it is ever missing again the ask goes out
|
|
5616
|
+
// at once rather than a day later.
|
|
5617
|
+
if (this._appStateKeyRequests) {
|
|
5618
|
+
for (const k of keyShare.keys) {
|
|
5619
|
+
if (!k || !k.keyId) continue;
|
|
5620
|
+
try {
|
|
5621
|
+
this._appStateKeyRequests.delete(
|
|
5622
|
+
Buffer.from(k.keyId, 'hex').toString('base64'));
|
|
5623
|
+
} catch (_) {}
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5513
5627
|
this.emit('app_state_keys', { keys: keyShare.keys });
|
|
5514
5628
|
|
|
5515
5629
|
// The key is what app state was waiting on. Anything the server has been
|
|
@@ -5566,15 +5680,25 @@ class WhalibmobClient extends EventEmitter {
|
|
|
5566
5680
|
}
|
|
5567
5681
|
|
|
5568
5682
|
/**
|
|
5569
|
-
*
|
|
5570
|
-
* Returns the parsed {phone}.history.json content or null if not yet synced.
|
|
5683
|
+
* The name every file of this session is built from.
|
|
5571
5684
|
*
|
|
5572
|
-
*
|
|
5685
|
+
* A number can be registered over SMS and separately linked as a companion,
|
|
5686
|
+
* and the two are different accounts' views of it, so the companion's files
|
|
5687
|
+
* carry a `.web` in the middle: 40712345678.web.history.json. Everything that
|
|
5688
|
+
* reads or writes one has to agree on that — a writer that adds the `.web`
|
|
5689
|
+
* and a reader that does not will not error, it will simply never find the
|
|
5690
|
+
* file it just wrote.
|
|
5573
5691
|
*/
|
|
5574
|
-
|
|
5692
|
+
_sessionFilePrefix() {
|
|
5575
5693
|
if (!this._store || !this._store.phoneNumber) return null;
|
|
5694
|
+
return this._store.phoneNumber + (this._mode === 'web' ? '.web' : '');
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
_readSessionJson(suffix) {
|
|
5698
|
+
const prefix = this._sessionFilePrefix();
|
|
5699
|
+
if (!prefix) return null;
|
|
5576
5700
|
const fs = require('fs');
|
|
5577
|
-
const file = require('path').join(this._sessionDir,
|
|
5701
|
+
const file = require('path').join(this._sessionDir, prefix + suffix);
|
|
5578
5702
|
try {
|
|
5579
5703
|
if (!fs.existsSync(file)) return null;
|
|
5580
5704
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -5582,19 +5706,25 @@ class WhalibmobClient extends EventEmitter {
|
|
|
5582
5706
|
}
|
|
5583
5707
|
|
|
5584
5708
|
/**
|
|
5585
|
-
*
|
|
5586
|
-
* Returns the parsed {phone}.
|
|
5709
|
+
* getHistoryStore — load the persisted history store for this session.
|
|
5710
|
+
* Returns the parsed {phone}.history.json content — {phone}.web.history.json
|
|
5711
|
+
* on a companion — or null if not yet synced.
|
|
5712
|
+
*
|
|
5713
|
+
* @returns {object|null}
|
|
5714
|
+
*/
|
|
5715
|
+
getHistoryStore() {
|
|
5716
|
+
return this._readSessionJson('.history.json');
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5719
|
+
/**
|
|
5720
|
+
* getMessagesStore — load the persisted messages for this session.
|
|
5721
|
+
* Returns the parsed {phone}.messages.json content — {phone}.web.messages.json
|
|
5722
|
+
* on a companion — or null if not yet synced.
|
|
5587
5723
|
*
|
|
5588
5724
|
* @returns {object|null}
|
|
5589
5725
|
*/
|
|
5590
5726
|
getMessagesStore() {
|
|
5591
|
-
|
|
5592
|
-
const fs = require('fs');
|
|
5593
|
-
const file = require('path').join(this._sessionDir, this._store.phoneNumber + '.messages.json');
|
|
5594
|
-
try {
|
|
5595
|
-
if (!fs.existsSync(file)) return null;
|
|
5596
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
5597
|
-
} catch (_) { return null; }
|
|
5727
|
+
return this._readSessionJson('.messages.json');
|
|
5598
5728
|
}
|
|
5599
5729
|
|
|
5600
5730
|
// ─── Public API ───────────────────────────────────────────────────────────
|
|
@@ -95,8 +95,8 @@ class MissingAppStateKeyError extends Error {
|
|
|
95
95
|
* missing key is different — nothing after it can be read either, and the fix
|
|
96
96
|
* is to wait for the key rather than to carry on — so that one is thrown.
|
|
97
97
|
*/
|
|
98
|
-
function decodeMutations(mutations, state, getKey, onMutation, validateMacs) {
|
|
99
|
-
const gen = makeGenerator(state);
|
|
98
|
+
function decodeMutations(mutations, state, getKey, onMutation, validateMacs, opts) {
|
|
99
|
+
const gen = makeGenerator(state, opts);
|
|
100
100
|
const cache = new Map();
|
|
101
101
|
let skipped = 0;
|
|
102
102
|
|
|
@@ -145,7 +145,17 @@ function decodeMutations(mutations, state, getKey, onMutation, validateMacs) {
|
|
|
145
145
|
try { index = JSON.parse(data.index.toString('utf8')); }
|
|
146
146
|
catch (_) { skipped++; continue; }
|
|
147
147
|
|
|
148
|
-
onMutation({
|
|
148
|
+
onMutation({
|
|
149
|
+
index,
|
|
150
|
+
action: data.value,
|
|
151
|
+
operation: m.operation,
|
|
152
|
+
// The api version the mutation was written under. The server sends it, so
|
|
153
|
+
// report it: a caller that has to tell an old write from a new one has
|
|
154
|
+
// nothing else to go on.
|
|
155
|
+
version: data.version,
|
|
156
|
+
indexMac: m.indexBlob,
|
|
157
|
+
valueMac: theirMac
|
|
158
|
+
});
|
|
149
159
|
gen.mix({
|
|
150
160
|
indexMac: m.indexBlob,
|
|
151
161
|
valueMac: theirMac,
|
|
@@ -168,7 +178,8 @@ function decodeSnapshot(name, snapshot, getKey, getMutation, validateMacs) {
|
|
|
168
178
|
const state = newState();
|
|
169
179
|
state.version = snapshot.version;
|
|
170
180
|
|
|
171
|
-
const res = decodeMutations(snapshot.records, state, getKey, getMutation,
|
|
181
|
+
const res = decodeMutations(snapshot.records, state, getKey, getMutation,
|
|
182
|
+
validateMacs, { snapshot: true });
|
|
172
183
|
state.hash = res.hash;
|
|
173
184
|
state.indexValueMap = res.indexValueMap;
|
|
174
185
|
|
package/lib/appstate/LTHash.js
CHANGED
|
@@ -79,7 +79,15 @@ const PATCH_INTEGRITY = new LTHash(PATCH_SALT);
|
|
|
79
79
|
// indexValueMap is index MAC (base64) → { valueMac }. It is the reason a
|
|
80
80
|
// mutation can be replaced at all: without knowing the value an index used to
|
|
81
81
|
// carry, there is nothing to subtract back out.
|
|
82
|
-
|
|
82
|
+
//
|
|
83
|
+
// `snapshot` turns the subtraction off. A snapshot is the whole collection
|
|
84
|
+
// stated from nothing, so the server computed its hash by adding every record
|
|
85
|
+
// and subtracting none — including two records that happen to share an index.
|
|
86
|
+
// Displacing the first would leave us one value short of the hash the snapshot
|
|
87
|
+
// is signed against, and the collection would be thrown away for a mismatch
|
|
88
|
+
// that was ours.
|
|
89
|
+
function makeGenerator(state, opts) {
|
|
90
|
+
const snapshot = !!(opts && opts.snapshot);
|
|
83
91
|
const indexValueMap = Object.assign({}, state.indexValueMap || {});
|
|
84
92
|
const hash = Buffer.isBuffer(state.hash) ? state.hash : Buffer.alloc(HASH_BYTES);
|
|
85
93
|
const addBufs = [];
|
|
@@ -88,7 +96,7 @@ function makeGenerator(state) {
|
|
|
88
96
|
return {
|
|
89
97
|
mix({ indexMac, valueMac, remove }) {
|
|
90
98
|
const key = Buffer.from(indexMac).toString('base64');
|
|
91
|
-
const prev = indexValueMap[key];
|
|
99
|
+
const prev = snapshot ? null : indexValueMap[key];
|
|
92
100
|
if (remove) {
|
|
93
101
|
// A remove for something we never had is the server telling us about a
|
|
94
102
|
// mutation we missed. There is nothing to subtract; the hash will not
|
|
@@ -364,18 +364,25 @@ function encodeDeviceSentMessage(destinationJid, messageBuf, phash) {
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// ─── ProtocolMessage (field 12 of Message) ───────────────────────────────────
|
|
367
|
-
// ProtocolMessage { key=1, type=2, ephemeralExpiration=4,
|
|
368
|
-
//
|
|
367
|
+
// ProtocolMessage { key=1, type=2, ephemeralExpiration=4,
|
|
368
|
+
// appStateSyncKeyShare=6, appStateSyncKeyRequest=8, ... }
|
|
369
|
+
// Type enum: REVOKE=0, EPHEMERAL_SETTING=3, APP_STATE_SYNC_KEY_REQUEST=7
|
|
369
370
|
const PROTOCOL_MSG_REVOKE = 0;
|
|
370
371
|
const PROTOCOL_MSG_EPHEMERAL = 3;
|
|
371
372
|
|
|
373
|
+
/** ProtocolMessage.Type.APP_STATE_SYNC_KEY_REQUEST */
|
|
374
|
+
const PROTOCOL_TYPE_APP_STATE_SYNC_KEY_REQUEST = 7;
|
|
375
|
+
|
|
372
376
|
function encodeProtocolMessage(opts) {
|
|
373
377
|
// opts: { type, key: { remoteJid, fromMe, id }, ephemeralExpiration,
|
|
374
|
-
// peerDataOperationRequestMessage }
|
|
378
|
+
// appStateSyncKeyRequest, peerDataOperationRequestMessage }
|
|
375
379
|
const parts = [];
|
|
376
380
|
if (opts.key) parts.push(field(1, WIRE_LEN, encodeMessageKey(opts.key)));
|
|
377
381
|
if (opts.type !== undefined) parts.push(field(2, WIRE_VARINT, varint(opts.type)));
|
|
378
382
|
if (opts.ephemeralExpiration !== undefined) parts.push(field(4, WIRE_VARINT, varint(opts.ephemeralExpiration)));
|
|
383
|
+
if (opts.appStateSyncKeyRequest) {
|
|
384
|
+
parts.push(field(8, WIRE_LEN, opts.appStateSyncKeyRequest));
|
|
385
|
+
}
|
|
379
386
|
// Field 16, and the Type enum value that goes with it is also 16 — a
|
|
380
387
|
// coincidence in the schema, not a mistake here.
|
|
381
388
|
if (opts.peerDataOperationRequestMessage) {
|
|
@@ -384,6 +391,21 @@ function encodeProtocolMessage(opts) {
|
|
|
384
391
|
return Buffer.concat(parts);
|
|
385
392
|
}
|
|
386
393
|
|
|
394
|
+
// ─── AppStateSyncKeyRequest ──────────────────────────────────────────────────
|
|
395
|
+
//
|
|
396
|
+
// Asking the primary device for an app-state key it never shared. Mutations are
|
|
397
|
+
// encrypted under a key the phone hands out over an encrypted message; if that
|
|
398
|
+
// message was missed, every patch signed with that key is unreadable and stays
|
|
399
|
+
// unreadable — nothing later in the collection can be decoded either. The only
|
|
400
|
+
// way out is to ask, which is what this carries.
|
|
401
|
+
//
|
|
402
|
+
// AppStateSyncKeyRequest { keyIds = 1 (repeated) }
|
|
403
|
+
// AppStateSyncKeyId { keyId = 1 }
|
|
404
|
+
function encodeAppStateSyncKeyRequest(keyIds) {
|
|
405
|
+
return Buffer.concat((keyIds || []).map(
|
|
406
|
+
id => field(1, WIRE_LEN, field(1, WIRE_LEN, bytes(id)))));
|
|
407
|
+
}
|
|
408
|
+
|
|
387
409
|
// ─── PeerDataOperationRequestMessage ─────────────────────────────────────────
|
|
388
410
|
//
|
|
389
411
|
// The channel a companion asks its own phone for something over. It travels as
|
|
@@ -980,6 +1002,8 @@ module.exports = {
|
|
|
980
1002
|
encodePeerDataOperationRequestMessage,
|
|
981
1003
|
PeerDataOperationRequestType,
|
|
982
1004
|
PROTOCOL_TYPE_PEER_DATA_OPERATION_REQUEST,
|
|
1005
|
+
PROTOCOL_TYPE_APP_STATE_SYNC_KEY_REQUEST,
|
|
1006
|
+
encodeAppStateSyncKeyRequest,
|
|
983
1007
|
encodeExtendedText,
|
|
984
1008
|
encodeContextInfo,
|
|
985
1009
|
encodeSenderKeyDistributionMessage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.26.1",
|
|
4
4
|
"description": "Node.js library for WhatsApp — register a number over SMS, or link as a companion by QR. Signal E2E encryption, media, groups, channels.",
|
|
5
5
|
"author": "Kunboruto20",
|
|
6
6
|
"main": "index.js",
|