@oxidezap/baileyrs 0.0.7 → 0.0.9
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 +119 -5
- package/lib/Bridge/__tests__/adapt.test.d.ts +2 -0
- package/lib/Bridge/__tests__/adapt.test.d.ts.map +1 -0
- package/lib/Bridge/__tests__/adapt.test.js +355 -0
- package/lib/Bridge/__tests__/adapt.test.js.map +1 -0
- package/lib/Bridge/__tests__/dts-drift.test.d.ts +13 -0
- package/lib/Bridge/__tests__/dts-drift.test.d.ts.map +1 -0
- package/lib/Bridge/__tests__/dts-drift.test.js +92 -0
- package/lib/Bridge/__tests__/dts-drift.test.js.map +1 -0
- package/lib/Bridge/__tests__/fixtures.d.ts +192 -0
- package/lib/Bridge/__tests__/fixtures.d.ts.map +1 -0
- package/lib/Bridge/__tests__/fixtures.js +99 -0
- package/lib/Bridge/__tests__/fixtures.js.map +1 -0
- package/lib/Bridge/adapt.d.ts +28 -0
- package/lib/Bridge/adapt.d.ts.map +1 -0
- package/lib/Bridge/adapt.js +504 -0
- package/lib/Bridge/adapt.js.map +1 -0
- package/lib/Bridge/index.d.ts +12 -0
- package/lib/Bridge/index.d.ts.map +1 -0
- package/lib/Bridge/index.js +12 -0
- package/lib/Bridge/index.js.map +1 -0
- package/lib/Bridge/primitives.d.ts +56 -0
- package/lib/Bridge/primitives.d.ts.map +1 -0
- package/lib/Bridge/primitives.js +58 -0
- package/lib/Bridge/primitives.js.map +1 -0
- package/lib/Bridge/types.d.ts +273 -0
- package/lib/Bridge/types.d.ts.map +1 -0
- package/lib/Bridge/types.js +17 -0
- package/lib/Bridge/types.js.map +1 -0
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +2 -2
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Socket/blocking.d.ts.map +1 -1
- package/lib/Socket/chat-actions.d.ts.map +1 -1
- package/lib/Socket/contacts.d.ts +9 -4
- package/lib/Socket/contacts.d.ts.map +1 -1
- package/lib/Socket/contacts.js +11 -8
- package/lib/Socket/contacts.js.map +1 -1
- package/lib/Socket/events.d.ts +7 -1
- package/lib/Socket/events.d.ts.map +1 -1
- package/lib/Socket/events.js +212 -243
- package/lib/Socket/events.js.map +1 -1
- package/lib/Socket/group-notifications.d.ts +25 -0
- package/lib/Socket/group-notifications.d.ts.map +1 -0
- package/lib/Socket/group-notifications.js +162 -0
- package/lib/Socket/group-notifications.js.map +1 -0
- package/lib/Socket/groups.d.ts +12 -1
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +44 -1
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +47 -13
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/index.js +158 -24
- package/lib/Socket/index.js.map +1 -1
- package/lib/Socket/messages.d.ts +5 -2
- package/lib/Socket/messages.d.ts.map +1 -1
- package/lib/Socket/messages.js +42 -21
- package/lib/Socket/messages.js.map +1 -1
- package/lib/Socket/newsletter.d.ts.map +1 -1
- package/lib/Socket/presence.d.ts.map +1 -1
- package/lib/Socket/profile.d.ts +7 -1
- package/lib/Socket/profile.d.ts.map +1 -1
- package/lib/Socket/profile.js +20 -5
- package/lib/Socket/profile.js.map +1 -1
- package/lib/Socket/transport.d.ts.map +1 -1
- package/lib/Socket/transport.js +14 -13
- package/lib/Socket/transport.js.map +1 -1
- package/lib/Socket/types.d.ts.map +1 -1
- package/lib/Types/Auth.d.ts +1 -1
- package/lib/Types/Auth.d.ts.map +1 -1
- package/lib/Types/Bussines.d.ts +1 -1
- package/lib/Types/Bussines.d.ts.map +1 -1
- package/lib/Types/Chat.d.ts +1 -1
- package/lib/Types/Chat.d.ts.map +1 -1
- package/lib/Types/Events.d.ts +12 -3
- package/lib/Types/Events.d.ts.map +1 -1
- package/lib/Types/Events.js +1 -1
- package/lib/Types/Events.js.map +1 -1
- package/lib/Types/GroupMetadata.d.ts.map +1 -1
- package/lib/Types/Message.d.ts +16 -1
- package/lib/Types/Message.d.ts.map +1 -1
- package/lib/Types/Message.js +1 -1
- package/lib/Types/Message.js.map +1 -1
- package/lib/Types/Socket.d.ts +42 -1
- package/lib/Types/Socket.d.ts.map +1 -1
- package/lib/Types/State.d.ts +1 -1
- package/lib/Types/State.d.ts.map +1 -1
- package/lib/Types/index.d.ts +1 -1
- package/lib/Types/index.d.ts.map +1 -1
- package/lib/Types/index.js +17 -12
- package/lib/Types/index.js.map +1 -1
- package/lib/Utils/__tests__/_legacy-store-fixtures.d.ts +65 -0
- package/lib/Utils/__tests__/_legacy-store-fixtures.d.ts.map +1 -0
- package/lib/Utils/__tests__/_legacy-store-fixtures.js +107 -0
- package/lib/Utils/__tests__/_legacy-store-fixtures.js.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-coverage.test.d.ts +10 -0
- package/lib/Utils/__tests__/wrap-legacy-store-coverage.test.d.ts.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-coverage.test.js +298 -0
- package/lib/Utils/__tests__/wrap-legacy-store-coverage.test.js.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-lid-mapping.test.d.ts +8 -0
- package/lib/Utils/__tests__/wrap-legacy-store-lid-mapping.test.d.ts.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-lid-mapping.test.js +91 -0
- package/lib/Utils/__tests__/wrap-legacy-store-lid-mapping.test.js.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-sender-key.test.d.ts +10 -0
- package/lib/Utils/__tests__/wrap-legacy-store-sender-key.test.d.ts.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-sender-key.test.js +126 -0
- package/lib/Utils/__tests__/wrap-legacy-store-sender-key.test.js.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-session.test.d.ts +14 -0
- package/lib/Utils/__tests__/wrap-legacy-store-session.test.d.ts.map +1 -0
- package/lib/Utils/__tests__/wrap-legacy-store-session.test.js +309 -0
- package/lib/Utils/__tests__/wrap-legacy-store-session.test.js.map +1 -0
- package/lib/Utils/boom.d.ts +33 -0
- package/lib/Utils/boom.d.ts.map +1 -0
- package/lib/Utils/boom.js +63 -0
- package/lib/Utils/boom.js.map +1 -0
- package/lib/Utils/browser-utils.d.ts.map +1 -1
- package/lib/Utils/event-buffer.d.ts.map +1 -1
- package/lib/Utils/generics.d.ts +37 -1
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +60 -2
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/index.d.ts +1 -0
- package/lib/Utils/index.d.ts.map +1 -1
- package/lib/Utils/index.js +10 -9
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/lid-mapping-store.d.ts +81 -0
- package/lib/Utils/lid-mapping-store.d.ts.map +1 -0
- package/lib/Utils/lid-mapping-store.js +126 -0
- package/lib/Utils/lid-mapping-store.js.map +1 -0
- package/lib/Utils/logger.d.ts.map +1 -1
- package/lib/Utils/logger.js +25 -1
- package/lib/Utils/logger.js.map +1 -1
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +1 -1
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts +30 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +115 -41
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/native-crypto-provider.d.ts +18 -0
- package/lib/Utils/native-crypto-provider.d.ts.map +1 -0
- package/lib/Utils/native-crypto-provider.js +67 -0
- package/lib/Utils/native-crypto-provider.js.map +1 -0
- package/lib/Utils/use-bridge-store.d.ts.map +1 -1
- package/lib/Utils/use-memory-store.d.ts.map +1 -1
- package/lib/Utils/use-memory-store.js +1 -1
- package/lib/Utils/use-memory-store.js.map +1 -1
- package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -1
- package/lib/Utils/use-multi-file-auth-state.js +1 -1
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
- package/lib/Utils/wrap-legacy-store.d.ts.map +1 -1
- package/lib/Utils/wrap-legacy-store.js +505 -42
- package/lib/Utils/wrap-legacy-store.js.map +1 -1
- package/lib/WABinary/index.d.ts.map +1 -1
- package/lib/WABinary/index.js +1 -1
- package/lib/WABinary/index.js.map +1 -1
- package/lib/__tests__/expect.d.ts +30 -0
- package/lib/__tests__/expect.d.ts.map +1 -0
- package/lib/__tests__/expect.js +60 -0
- package/lib/__tests__/expect.js.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +9 -6
- package/lib/index.js.map +1 -1
- package/package.json +33 -25
- package/WAProto/index.d.ts +0 -13875
- package/WAProto/index.js +0 -175
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
import { Buffer } from 'node:buffer';
|
|
14
|
+
import { createHmac } from 'node:crypto';
|
|
14
15
|
import { mkdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
|
|
15
16
|
import { join } from 'node:path';
|
|
16
|
-
import { proto } from '
|
|
17
|
+
import { proto } from 'whatsapp-rust-bridge/proto-types';
|
|
17
18
|
const protoLookup = proto;
|
|
18
19
|
const ADVSignedDeviceIdentity = protoLookup.ADVSignedDeviceIdentity ?? protoLookup.AdvSignedDeviceIdentity;
|
|
19
20
|
function warn(msg, err) {
|
|
@@ -46,11 +47,22 @@ const STORE_MAP = {
|
|
|
46
47
|
mutation_mac: 'bridge-mutation-mac',
|
|
47
48
|
meta: 'bridge-meta'
|
|
48
49
|
};
|
|
49
|
-
// Stores where bridge data is passthrough binary (no conversion)
|
|
50
|
-
|
|
51
|
-
//
|
|
50
|
+
// Stores where bridge data is passthrough binary (no conversion).
|
|
51
|
+
// All Signal-protocol records (`identity`, `session`, `sender_key`) now
|
|
52
|
+
// route through converters because every one of them either:
|
|
53
|
+
// • has a different key shape between bridge and upstream, OR
|
|
54
|
+
// • has a different value-byte encoding (proto / JSON / 32-vs-33-byte
|
|
55
|
+
// prefix), OR
|
|
56
|
+
// • both.
|
|
57
|
+
// Keeping the empty set as an extension point — future bridge stores that
|
|
58
|
+
// are byte-compatible with upstream can land here without a converter.
|
|
59
|
+
const BINARY_STORES = new Set();
|
|
60
|
+
// Bridge-only stores — raw binary persisted through keys interface.
|
|
61
|
+
// `lid_mapping` is intentionally excluded — it has a converter so the
|
|
62
|
+
// `pn:`/`lid:` prefixed bridge keys translate to upstream's bare-userpart
|
|
63
|
+
// `{pnUser}` + `{lidUser}_reverse` shape, letting both libraries share
|
|
64
|
+
// the same on-disk LID↔PN mapping table.
|
|
52
65
|
const BRIDGE_ONLY_STORES = new Set([
|
|
53
|
-
'lid_mapping',
|
|
54
66
|
'signed_prekey',
|
|
55
67
|
'sender_key_devices',
|
|
56
68
|
'base_key',
|
|
@@ -203,8 +215,397 @@ const converters = {
|
|
|
203
215
|
return [];
|
|
204
216
|
}
|
|
205
217
|
}
|
|
218
|
+
},
|
|
219
|
+
// identity: bridge holds 32-byte raw DJB pubkey, upstream holds 33 bytes
|
|
220
|
+
// (0x05 || 32-byte) — the curve25519-XEdDSA wire form.
|
|
221
|
+
identity: {
|
|
222
|
+
toBridge(_key, value) {
|
|
223
|
+
const buf = toBuf(value);
|
|
224
|
+
if (!buf)
|
|
225
|
+
return null;
|
|
226
|
+
return buf.length === 33 && buf[0] === 0x05 ? new Uint8Array(buf.slice(1)) : buf;
|
|
227
|
+
},
|
|
228
|
+
fromBridge(_key, value) {
|
|
229
|
+
const v = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
230
|
+
return v.length === 32 ? Buffer.concat([Buffer.from([0x05]), v]) : Buffer.from(v);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
// lid_mapping: bridge uses prefixed keys (`lid:{X}` JSON entry,
|
|
234
|
+
// `pn:{X}` raw LID bytes); upstream uses bare keys (`{pnUser}` string LID,
|
|
235
|
+
// `{lidUser}_reverse` string PN). No upstream timestamps; we synthesise.
|
|
236
|
+
lid_mapping: {
|
|
237
|
+
toBridge(key, value) {
|
|
238
|
+
try {
|
|
239
|
+
const asString = typeof value === 'string' ? value : Buffer.from(value).toString('utf-8');
|
|
240
|
+
if (!asString)
|
|
241
|
+
return null;
|
|
242
|
+
if (key.startsWith('pn:'))
|
|
243
|
+
return new TextEncoder().encode(asString);
|
|
244
|
+
if (key.startsWith('lid:')) {
|
|
245
|
+
const now = Math.floor(Date.now() / 1000);
|
|
246
|
+
return toJson({
|
|
247
|
+
lid: key.slice(4),
|
|
248
|
+
phone_number: asString,
|
|
249
|
+
created_at: now,
|
|
250
|
+
updated_at: now,
|
|
251
|
+
learning_source: 'wrap-legacy-store'
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
warn(`lid_mapping.toBridge: unknown key prefix in "${key}"`);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
warn('lid_mapping.toBridge failed:', e);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
fromBridge(key, value) {
|
|
263
|
+
try {
|
|
264
|
+
if (key.startsWith('pn:'))
|
|
265
|
+
return Buffer.from(value).toString('utf-8');
|
|
266
|
+
if (key.startsWith('lid:')) {
|
|
267
|
+
const entry = fromJson(value);
|
|
268
|
+
return entry.phone_number ?? '';
|
|
269
|
+
}
|
|
270
|
+
warn(`lid_mapping.fromBridge: unknown key prefix in "${key}"`);
|
|
271
|
+
return Buffer.from(value);
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
warn('lid_mapping.fromBridge failed:', e);
|
|
275
|
+
return Buffer.from(value);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
// session: bridge=protobuf RecordStructure, upstream=JS object
|
|
280
|
+
// `{_sessions: {[b64BaseKey]: SessionEntry}, version: 'v1'}` with byte
|
|
281
|
+
// fields stored as base64 strings inside the entry. messageKeys cache
|
|
282
|
+
// is lossy in the Rust→JS direction (HKDF is one-way) — see
|
|
283
|
+
// `bridgeSessionProtoToUpstreamRecord`.
|
|
284
|
+
session: {
|
|
285
|
+
toBridge(_key, value) {
|
|
286
|
+
try {
|
|
287
|
+
if (value == null)
|
|
288
|
+
return null; // eslint-disable-line eqeqeq
|
|
289
|
+
return upstreamSessionRecordToProto(value);
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
warn('session.toBridge encode failed:', e);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
fromBridge(_key, value) {
|
|
297
|
+
try {
|
|
298
|
+
return bridgeSessionProtoToUpstreamRecord(value);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
warn('session.fromBridge decode failed:', e);
|
|
302
|
+
return Buffer.from(value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
// sender_key: bridge=protobuf SenderKeyRecordStructure, upstream=
|
|
307
|
+
// `Buffer.from(JSON.stringify(states, BufferJSON.replacer), 'utf-8')`
|
|
308
|
+
// — Buffer fields wrapped as `{type:'Buffer', data:'<base64>'}`.
|
|
309
|
+
sender_key: {
|
|
310
|
+
toBridge(_key, value) {
|
|
311
|
+
try {
|
|
312
|
+
const buf = toBuf(value);
|
|
313
|
+
if (!buf)
|
|
314
|
+
return null;
|
|
315
|
+
return upstreamSenderKeyJsonToProto(buf);
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
warn('sender_key.toBridge decode failed:', e);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
fromBridge(_key, value) {
|
|
323
|
+
try {
|
|
324
|
+
return bridgeSenderKeyProtoToJson(value);
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
warn('sender_key.fromBridge encode failed:', e);
|
|
328
|
+
return Buffer.from(value);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
// BufferJSON-compatible replacer/reviver matching upstream `Utils/generics`.
|
|
334
|
+
const bufferJsonReplacer = (_k, v) => {
|
|
335
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array || v?.type === 'Buffer') {
|
|
336
|
+
const data = v?.data ?? v;
|
|
337
|
+
return { type: 'Buffer', data: Buffer.from(data).toString('base64') };
|
|
206
338
|
}
|
|
339
|
+
return v;
|
|
207
340
|
};
|
|
341
|
+
const bufferJsonReviver = (_k, v) => {
|
|
342
|
+
const obj = v;
|
|
343
|
+
if (obj && typeof obj === 'object' && obj.type === 'Buffer' && typeof obj.data === 'string') {
|
|
344
|
+
return Buffer.from(obj.data, 'base64');
|
|
345
|
+
}
|
|
346
|
+
return v;
|
|
347
|
+
};
|
|
348
|
+
const sk_toBytes = (v) => v ? new Uint8Array(Buffer.from(v)) : new Uint8Array();
|
|
349
|
+
const sk_toBuffer = (v) => Buffer.from(v ?? new Uint8Array());
|
|
350
|
+
function bridgeSenderKeyProtoToJson(protoBytes) {
|
|
351
|
+
const struct = proto.SenderKeyRecordStructure.decode(Buffer.from(protoBytes));
|
|
352
|
+
const states = (struct.senderKeyStates ?? []).map(s => ({
|
|
353
|
+
senderKeyId: s.senderKeyId ?? 0,
|
|
354
|
+
senderChainKey: { iteration: s.senderChainKey?.iteration ?? 0, seed: sk_toBuffer(s.senderChainKey?.seed) },
|
|
355
|
+
senderSigningKey: {
|
|
356
|
+
public: sk_toBuffer(s.senderSigningKey?.public),
|
|
357
|
+
private: sk_toBuffer(s.senderSigningKey?.private)
|
|
358
|
+
},
|
|
359
|
+
senderMessageKeys: (s.senderMessageKeys ?? []).map(mk => ({
|
|
360
|
+
iteration: mk.iteration ?? 0,
|
|
361
|
+
seed: sk_toBuffer(mk.seed)
|
|
362
|
+
}))
|
|
363
|
+
}));
|
|
364
|
+
return Buffer.from(JSON.stringify(states, bufferJsonReplacer), 'utf-8');
|
|
365
|
+
}
|
|
366
|
+
function upstreamSenderKeyJsonToProto(jsonBuf) {
|
|
367
|
+
const states = JSON.parse(Buffer.from(jsonBuf).toString('utf-8'), bufferJsonReviver);
|
|
368
|
+
const senderKeyStates = states.map(s => ({
|
|
369
|
+
senderKeyId: s.senderKeyId ?? 0,
|
|
370
|
+
senderChainKey: { iteration: s.senderChainKey?.iteration ?? 0, seed: sk_toBytes(s.senderChainKey?.seed) },
|
|
371
|
+
senderSigningKey: {
|
|
372
|
+
public: sk_toBytes(s.senderSigningKey?.public),
|
|
373
|
+
private: sk_toBytes(s.senderSigningKey?.private)
|
|
374
|
+
},
|
|
375
|
+
senderMessageKeys: (s.senderMessageKeys ?? []).map(mk => ({
|
|
376
|
+
iteration: mk.iteration ?? 0,
|
|
377
|
+
seed: sk_toBytes(mk.seed)
|
|
378
|
+
}))
|
|
379
|
+
}));
|
|
380
|
+
return proto.SenderKeyRecordStructure.encode(proto.SenderKeyRecordStructure.create({ senderKeyStates })).finish();
|
|
381
|
+
}
|
|
382
|
+
// sender_key key translation: bridge `{group}:{user}[:dev]@{server}.{sig}`
|
|
383
|
+
// → upstream `{group}::{signalUser}::{deviceId}`. Group JIDs never contain
|
|
384
|
+
// `:`, so the first `:` separates group from sender address.
|
|
385
|
+
function bridgeSenderKeyToUpstream(bridgeKey) {
|
|
386
|
+
const sep = bridgeKey.indexOf(':');
|
|
387
|
+
if (sep < 0)
|
|
388
|
+
return null;
|
|
389
|
+
const groupJid = bridgeKey.slice(0, sep);
|
|
390
|
+
const addrStr = bridgeKey.slice(sep + 1);
|
|
391
|
+
const dotIdx = addrStr.lastIndexOf('.');
|
|
392
|
+
if (dotIdx < 0)
|
|
393
|
+
return null;
|
|
394
|
+
const jidPart = addrStr.slice(0, dotIdx);
|
|
395
|
+
const atIdx = jidPart.indexOf('@');
|
|
396
|
+
if (atIdx < 0)
|
|
397
|
+
return null;
|
|
398
|
+
const userPart = jidPart.slice(0, atIdx);
|
|
399
|
+
const server = jidPart.slice(atIdx + 1);
|
|
400
|
+
const [user, jidDev] = userPart.includes(':')
|
|
401
|
+
? [userPart.slice(0, userPart.indexOf(':')), parseInt(userPart.slice(userPart.indexOf(':') + 1), 10) || 0]
|
|
402
|
+
: [userPart, 0];
|
|
403
|
+
const domainType = DOMAIN_TYPE_MAP[server];
|
|
404
|
+
if (domainType === undefined)
|
|
405
|
+
return null;
|
|
406
|
+
return `${groupJid}::${domainType !== 0 ? `${user}_${domainType}` : user}::${jidDev}`;
|
|
407
|
+
}
|
|
408
|
+
const CHAIN_TYPE_SENDING = 1;
|
|
409
|
+
const CHAIN_TYPE_RECEIVING = 2;
|
|
410
|
+
const BASE_KEY_TYPE_OURS = 1;
|
|
411
|
+
const BASE_KEY_TYPE_THEIRS = 2;
|
|
412
|
+
const b64 = (b) => b ? Buffer.from(b).toString('base64') : Buffer.alloc(0).toString('base64');
|
|
413
|
+
const fromB64 = (s) => (s ? Buffer.from(s, 'base64') : Buffer.alloc(0));
|
|
414
|
+
/**
|
|
415
|
+
* Derive Rust's per-message split (cipher 32 / mac 32 / iv 16) from JS's
|
|
416
|
+
* 32-byte `messageKey` seed. Both impls compute HKDF-SHA256 with
|
|
417
|
+
* salt=[0u8;32] and info="WhisperMessageKeys" — Rust's `None` salt
|
|
418
|
+
* defaults to a zero-byte HashLen string, so the output is byte-identical.
|
|
419
|
+
* Reverse direction (Rust→JS) is impossible: HKDF is one-way.
|
|
420
|
+
*/
|
|
421
|
+
function deriveProtoMessageKey(seed, counter) {
|
|
422
|
+
const prk = createHmac('sha256', Buffer.alloc(32)).update(seed).digest();
|
|
423
|
+
const info = Buffer.from('WhisperMessageKeys');
|
|
424
|
+
const expand = (prev, n) => createHmac('sha256', prk)
|
|
425
|
+
.update(Buffer.concat([prev, info, Buffer.from([n])]))
|
|
426
|
+
.digest();
|
|
427
|
+
const t1 = expand(Buffer.alloc(0), 0x01);
|
|
428
|
+
const t2 = expand(t1, 0x02);
|
|
429
|
+
const t3 = expand(t2, 0x03);
|
|
430
|
+
return {
|
|
431
|
+
index: counter,
|
|
432
|
+
cipherKey: new Uint8Array(t1),
|
|
433
|
+
macKey: new Uint8Array(t2),
|
|
434
|
+
iv: new Uint8Array(t3.subarray(0, 16))
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function jsChainMessageKeysToProto(messageKeys) {
|
|
438
|
+
if (!messageKeys)
|
|
439
|
+
return [];
|
|
440
|
+
const out = [];
|
|
441
|
+
for (const [counterStr, seedB64] of Object.entries(messageKeys)) {
|
|
442
|
+
const counter = parseInt(counterStr, 10);
|
|
443
|
+
if (!Number.isFinite(counter))
|
|
444
|
+
continue;
|
|
445
|
+
const seed = fromB64(seedB64);
|
|
446
|
+
if (seed.length > 0)
|
|
447
|
+
out.push(deriveProtoMessageKey(seed, counter));
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
function sessionStructureToEntry(session, closedTs) {
|
|
452
|
+
if (!session.senderChain || !session.rootKey)
|
|
453
|
+
return null;
|
|
454
|
+
// aliceBaseKey is set by Rust on BOTH sides (alice=our ephemeral, bob=
|
|
455
|
+
// peer's ephemeral). pendingPreKey is the side discriminator (alice
|
|
456
|
+
// only, cleared on first reply).
|
|
457
|
+
const baseKeyBytes = session.aliceBaseKey ?? session.senderChain.senderRatchetKey;
|
|
458
|
+
if (!baseKeyBytes)
|
|
459
|
+
return null;
|
|
460
|
+
const weAreAlice = !!session.pendingPreKey?.baseKey;
|
|
461
|
+
const baseKeyType = weAreAlice ? BASE_KEY_TYPE_OURS : BASE_KEY_TYPE_THEIRS;
|
|
462
|
+
// lastRemoteEphemeralKey precedence: tail receiverChain → aliceBaseKey
|
|
463
|
+
// (when bob, matches JS init) → empty. NEVER senderRatchetKey: that
|
|
464
|
+
// collides with the SENDER chain in `_chains`, and upstream's
|
|
465
|
+
// `maybeStepRatchet` would `delete previousRatchet.chainKey.key` on
|
|
466
|
+
// the sender chain when the peer ratchets, corrupting outbound encryption.
|
|
467
|
+
const receiverChains = session.receiverChains ?? [];
|
|
468
|
+
const lastReceiver = receiverChains[receiverChains.length - 1];
|
|
469
|
+
const lastRemoteEph = lastReceiver?.senderRatchetKey && lastReceiver.senderRatchetKey.length > 0
|
|
470
|
+
? lastReceiver.senderRatchetKey
|
|
471
|
+
: !weAreAlice && session.aliceBaseKey && session.aliceBaseKey.length > 0
|
|
472
|
+
? session.aliceBaseKey
|
|
473
|
+
: new Uint8Array();
|
|
474
|
+
const senderRatchetPub = session.senderChain.senderRatchetKey ?? new Uint8Array();
|
|
475
|
+
const senderRatchetPriv = session.senderChain.senderRatchetKeyPrivate ?? new Uint8Array();
|
|
476
|
+
const _chains = {};
|
|
477
|
+
if (session.senderChain.chainKey?.key && senderRatchetPub.length > 0) {
|
|
478
|
+
_chains[b64(senderRatchetPub)] = {
|
|
479
|
+
chainKey: { counter: session.senderChain.chainKey.index ?? 0, key: b64(session.senderChain.chainKey.key) },
|
|
480
|
+
chainType: CHAIN_TYPE_SENDING,
|
|
481
|
+
messageKeys: {}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
for (const rc of receiverChains) {
|
|
485
|
+
if (!rc.chainKey?.key || !rc.senderRatchetKey || rc.senderRatchetKey.length === 0)
|
|
486
|
+
continue;
|
|
487
|
+
_chains[b64(rc.senderRatchetKey)] = {
|
|
488
|
+
chainKey: { counter: rc.chainKey.index ?? 0, key: b64(rc.chainKey.key) },
|
|
489
|
+
chainType: CHAIN_TYPE_RECEIVING,
|
|
490
|
+
messageKeys: {}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const now = Date.now();
|
|
494
|
+
const entry = {
|
|
495
|
+
registrationId: session.remoteRegistrationId ?? 0,
|
|
496
|
+
currentRatchet: {
|
|
497
|
+
ephemeralKeyPair: { pubKey: b64(senderRatchetPub), privKey: b64(senderRatchetPriv) },
|
|
498
|
+
lastRemoteEphemeralKey: b64(lastRemoteEph),
|
|
499
|
+
previousCounter: session.previousCounter ?? 0,
|
|
500
|
+
rootKey: b64(session.rootKey)
|
|
501
|
+
},
|
|
502
|
+
indexInfo: {
|
|
503
|
+
baseKey: b64(baseKeyBytes),
|
|
504
|
+
baseKeyType,
|
|
505
|
+
closed: closedTs,
|
|
506
|
+
used: now,
|
|
507
|
+
created: now,
|
|
508
|
+
remoteIdentityKey: b64(session.remoteIdentityPublic ?? new Uint8Array())
|
|
509
|
+
},
|
|
510
|
+
_chains
|
|
511
|
+
};
|
|
512
|
+
if (session.pendingPreKey?.baseKey) {
|
|
513
|
+
entry.pendingPreKey = {
|
|
514
|
+
preKeyId: session.pendingPreKey.preKeyId ?? undefined,
|
|
515
|
+
signedKeyId: session.pendingPreKey.signedPreKeyId ?? undefined,
|
|
516
|
+
baseKey: b64(session.pendingPreKey.baseKey)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return entry;
|
|
520
|
+
}
|
|
521
|
+
function bridgeSessionProtoToUpstreamRecord(protoBytes) {
|
|
522
|
+
const record = proto.RecordStructure.decode(Buffer.from(protoBytes));
|
|
523
|
+
const _sessions = {};
|
|
524
|
+
const current = record.currentSession ? sessionStructureToEntry(record.currentSession, -1) : null;
|
|
525
|
+
if (current)
|
|
526
|
+
_sessions[current.indexInfo.baseKey] = current;
|
|
527
|
+
// Synthesize descending close timestamps so removeOldSessions can sort:
|
|
528
|
+
// front of Rust `previous_sessions` is the most recently archived.
|
|
529
|
+
const previous = record.previousSessions ?? [];
|
|
530
|
+
for (let i = 0; i < previous.length; i++) {
|
|
531
|
+
const entry = sessionStructureToEntry(previous[i], Math.max(1, Date.now() - i));
|
|
532
|
+
if (entry && !_sessions[entry.indexInfo.baseKey])
|
|
533
|
+
_sessions[entry.indexInfo.baseKey] = entry;
|
|
534
|
+
}
|
|
535
|
+
return { _sessions, version: 'v1' };
|
|
536
|
+
}
|
|
537
|
+
function entryToSessionStructure(entry) {
|
|
538
|
+
const ratchet = entry.currentRatchet;
|
|
539
|
+
const senderRatchetPub = fromB64(ratchet.ephemeralKeyPair.pubKey);
|
|
540
|
+
const senderRatchetPriv = fromB64(ratchet.ephemeralKeyPair.privKey);
|
|
541
|
+
const senderKeyB64 = senderRatchetPub.toString('base64');
|
|
542
|
+
const senderChainEntry = entry._chains[senderKeyB64];
|
|
543
|
+
const senderChain = {
|
|
544
|
+
senderRatchetKey: new Uint8Array(senderRatchetPub),
|
|
545
|
+
senderRatchetKeyPrivate: new Uint8Array(senderRatchetPriv),
|
|
546
|
+
chainKey: senderChainEntry
|
|
547
|
+
? { index: senderChainEntry.chainKey.counter, key: new Uint8Array(fromB64(senderChainEntry.chainKey.key)) }
|
|
548
|
+
: { index: 0, key: new Uint8Array() },
|
|
549
|
+
messageKeys: [] // sender chain is sequential — no skipped cache either side
|
|
550
|
+
};
|
|
551
|
+
const receiverChains = [];
|
|
552
|
+
for (const [k, ch] of Object.entries(entry._chains)) {
|
|
553
|
+
if (ch.chainType !== CHAIN_TYPE_RECEIVING)
|
|
554
|
+
continue;
|
|
555
|
+
receiverChains.push({
|
|
556
|
+
senderRatchetKey: new Uint8Array(fromB64(k)),
|
|
557
|
+
chainKey: { index: ch.chainKey.counter, key: new Uint8Array(fromB64(ch.chainKey.key)) },
|
|
558
|
+
// JS holds 32-byte HMAC seeds; Rust holds the post-HKDF split.
|
|
559
|
+
// Lossless JS→Rust derivation via deriveProtoMessageKey.
|
|
560
|
+
messageKeys: jsChainMessageKeysToProto(ch.messageKeys)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
// `indexInfo.baseKey` IS the alice_base_key in either direction
|
|
564
|
+
// (alice=our ephemeral, bob=peer's ephemeral). Rust populates it
|
|
565
|
+
// symmetrically; dropping it would break find_matching_previous_session_index.
|
|
566
|
+
const session = {
|
|
567
|
+
rootKey: new Uint8Array(fromB64(ratchet.rootKey)),
|
|
568
|
+
previousCounter: ratchet.previousCounter,
|
|
569
|
+
senderChain,
|
|
570
|
+
receiverChains,
|
|
571
|
+
remoteIdentityPublic: new Uint8Array(fromB64(entry.indexInfo.remoteIdentityKey)),
|
|
572
|
+
remoteRegistrationId: entry.registrationId ?? 0,
|
|
573
|
+
aliceBaseKey: new Uint8Array(fromB64(entry.indexInfo.baseKey))
|
|
574
|
+
};
|
|
575
|
+
if (entry.pendingPreKey?.baseKey) {
|
|
576
|
+
session.pendingPreKey = {
|
|
577
|
+
preKeyId: entry.pendingPreKey.preKeyId ?? null,
|
|
578
|
+
signedPreKeyId: entry.pendingPreKey.signedKeyId ?? null,
|
|
579
|
+
baseKey: new Uint8Array(fromB64(entry.pendingPreKey.baseKey))
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return session;
|
|
583
|
+
}
|
|
584
|
+
function upstreamSessionRecordToProto(record) {
|
|
585
|
+
const sessions = record?._sessions ?? {};
|
|
586
|
+
let currentSession = null;
|
|
587
|
+
const previousSessions = [];
|
|
588
|
+
for (const entry of Object.values(sessions)) {
|
|
589
|
+
const ss = entryToSessionStructure(entry);
|
|
590
|
+
if (!currentSession && entry.indexInfo.closed === -1)
|
|
591
|
+
currentSession = ss;
|
|
592
|
+
else
|
|
593
|
+
previousSessions.push(ss);
|
|
594
|
+
}
|
|
595
|
+
// `BridgeSessionProto` is a structural mirror of `ISessionStructure`
|
|
596
|
+
// from `whatsapp-rust-bridge/proto-types`; the differences are nullable
|
|
597
|
+
// vs optional fields. We cast at the boundary to avoid pulling the full
|
|
598
|
+
// proto class types into our internal shape.
|
|
599
|
+
const recordOut = proto.RecordStructure.create({
|
|
600
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
601
|
+
currentSession: (currentSession ?? undefined),
|
|
602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
603
|
+
previousSessions: previousSessions
|
|
604
|
+
});
|
|
605
|
+
return proto.RecordStructure.encode(recordOut).finish();
|
|
606
|
+
}
|
|
607
|
+
// session/identity key translation: bridge `{user}[:dev]@{server}.{sig}`
|
|
608
|
+
// → upstream `{signalUser}.{deviceId}` via `bridgeAddrToBaileysAddr`.
|
|
208
609
|
// ---- Device/Creds ----
|
|
209
610
|
/** Parse a JID string into { user, device, server } */
|
|
210
611
|
function parseJid(jid) {
|
|
@@ -259,6 +660,21 @@ function credsToDeviceJson(creds) {
|
|
|
259
660
|
next_pre_key_id: creds.nextPreKeyId ?? 1
|
|
260
661
|
});
|
|
261
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Decode a `[priv(32)…pub(32)]` byte array (the bridge's `kp` shape) back into
|
|
665
|
+
* the upstream Baileys `{public, private}` keypair. Returns `undefined` for
|
|
666
|
+
* absent / all-zero arrays so callers can use `??` to keep prior values.
|
|
667
|
+
*/
|
|
668
|
+
function numArrayToKeyPair(arr) {
|
|
669
|
+
if (!Array.isArray(arr) || arr.length < 64)
|
|
670
|
+
return undefined;
|
|
671
|
+
if (arr.every(b => b === 0))
|
|
672
|
+
return undefined;
|
|
673
|
+
return {
|
|
674
|
+
private: new Uint8Array(arr.slice(0, 32)),
|
|
675
|
+
public: new Uint8Array(arr.slice(32, 64))
|
|
676
|
+
};
|
|
677
|
+
}
|
|
262
678
|
function updateCredsFromDevice(bytes, creds) {
|
|
263
679
|
try {
|
|
264
680
|
const d = fromJson(bytes);
|
|
@@ -293,6 +709,29 @@ function updateCredsFromDevice(bytes, creds) {
|
|
|
293
709
|
creds.advSecretKey = buf.toString('base64');
|
|
294
710
|
}
|
|
295
711
|
}
|
|
712
|
+
// Hydrate the noise / identity / signed pre-key material so the next
|
|
713
|
+
// `credsToDeviceJson(creds)` round-trip emits real bytes instead of
|
|
714
|
+
// zero-filled placeholders. Without this, a paired session looks
|
|
715
|
+
// half-valid on the next process start (`me.id` set, but key slots
|
|
716
|
+
// all zero) and the bridge's noise handshake gets rejected by the
|
|
717
|
+
// server with stream-error precondition-required.
|
|
718
|
+
const noise = numArrayToKeyPair(d.noise_key);
|
|
719
|
+
if (noise)
|
|
720
|
+
creds.noiseKey = noise;
|
|
721
|
+
const identity = numArrayToKeyPair(d.identity_key);
|
|
722
|
+
if (identity)
|
|
723
|
+
creds.signedIdentityKey = identity;
|
|
724
|
+
const signedPreKeyPair = numArrayToKeyPair(d.signed_pre_key);
|
|
725
|
+
if (signedPreKeyPair &&
|
|
726
|
+
Array.isArray(d.signed_pre_key_signature) &&
|
|
727
|
+
d.signed_pre_key_signature.length === 64 &&
|
|
728
|
+
d.signed_pre_key_signature.some(b => b !== 0)) {
|
|
729
|
+
creds.signedPreKey = {
|
|
730
|
+
keyPair: signedPreKeyPair,
|
|
731
|
+
keyId: d.signed_pre_key_id ?? 1,
|
|
732
|
+
signature: new Uint8Array(d.signed_pre_key_signature)
|
|
733
|
+
};
|
|
734
|
+
}
|
|
296
735
|
}
|
|
297
736
|
catch (e) {
|
|
298
737
|
warn('updateCredsFromDevice failed:', e);
|
|
@@ -444,22 +883,58 @@ export async function wrapLegacyStore(state, saveCreds, logger) {
|
|
|
444
883
|
}, 100);
|
|
445
884
|
};
|
|
446
885
|
/** Route a store to its handler */
|
|
447
|
-
const resolve = (
|
|
448
|
-
if (
|
|
886
|
+
const resolve = (storeName) => {
|
|
887
|
+
if (storeName === 'device')
|
|
449
888
|
return 'device';
|
|
450
|
-
if (BINARY_STORES.has(
|
|
889
|
+
if (BINARY_STORES.has(storeName))
|
|
451
890
|
return 'signal';
|
|
452
|
-
if (BRIDGE_ONLY_STORES.has(
|
|
891
|
+
if (BRIDGE_ONLY_STORES.has(storeName))
|
|
453
892
|
return 'bridge-only';
|
|
454
|
-
if (STORE_MAP[
|
|
893
|
+
if (STORE_MAP[storeName])
|
|
455
894
|
return 'converter';
|
|
456
895
|
return 'unknown';
|
|
457
896
|
};
|
|
458
897
|
/** Translate bridge key format to upstream Baileys key format */
|
|
459
|
-
const translateKey = (
|
|
898
|
+
const translateKey = (storeName, key) => {
|
|
460
899
|
// Bridge encodes sync_key IDs as hex; upstream stores use base64
|
|
461
|
-
if (
|
|
900
|
+
if (storeName === 'sync_key')
|
|
462
901
|
return hexToBase64(key);
|
|
902
|
+
// Bridge sender_key cache_key uses single-colon separator + signal-
|
|
903
|
+
// device suffix; upstream's `SenderKeyName.serialize()` uses double
|
|
904
|
+
// colons and drops the signal device — see bridgeSenderKeyToUpstream.
|
|
905
|
+
if (storeName === 'sender_key') {
|
|
906
|
+
const translated = bridgeSenderKeyToUpstream(key);
|
|
907
|
+
if (translated == null) {
|
|
908
|
+
// eslint-disable-line eqeqeq -- intentional null+undefined check
|
|
909
|
+
warn(`sender_key key translation failed for "${key}", falling back to passthrough`);
|
|
910
|
+
return key;
|
|
911
|
+
}
|
|
912
|
+
return translated;
|
|
913
|
+
}
|
|
914
|
+
// Bridge session/identity keys are the protocol address
|
|
915
|
+
// (`user[:dev]@server.sig`); upstream's libsignal looks under
|
|
916
|
+
// `signalUser.deviceId`. `bridgeAddrToBaileysAddr` does the LID/PN
|
|
917
|
+
// → signalUser rewrite + JID-device extraction.
|
|
918
|
+
if (storeName === 'session' || storeName === 'identity') {
|
|
919
|
+
const translated = bridgeAddrToBaileysAddr(key);
|
|
920
|
+
if (translated == null) {
|
|
921
|
+
// eslint-disable-line eqeqeq -- intentional null+undefined check
|
|
922
|
+
warn(`${storeName} key translation failed for "${key}", falling back to passthrough`);
|
|
923
|
+
return key;
|
|
924
|
+
}
|
|
925
|
+
return translated;
|
|
926
|
+
}
|
|
927
|
+
// Bridge lid_mapping uses prefixed keys (`lid:{X}`, `pn:{X}`) that
|
|
928
|
+
// upstream's `Signal/lid-mapping.js` doesn't recognize. Map them to
|
|
929
|
+
// upstream's bare-userpart shape: `pn:{X}` → `{X}`, `lid:{X}` →
|
|
930
|
+
// `{X}_reverse`.
|
|
931
|
+
if (storeName === 'lid_mapping') {
|
|
932
|
+
if (key.startsWith('pn:'))
|
|
933
|
+
return key.slice(3);
|
|
934
|
+
if (key.startsWith('lid:'))
|
|
935
|
+
return `${key.slice(4)}_reverse`;
|
|
936
|
+
return key;
|
|
937
|
+
}
|
|
463
938
|
return key;
|
|
464
939
|
};
|
|
465
940
|
/** Read a binary value from the keys store */
|
|
@@ -496,8 +971,19 @@ export async function wrapLegacyStore(state, saveCreds, logger) {
|
|
|
496
971
|
const route = resolve(bridgeStore);
|
|
497
972
|
const type = STORE_MAP[bridgeStore];
|
|
498
973
|
if (route === 'device') {
|
|
499
|
-
if (key === 'device')
|
|
974
|
+
if (key === 'device') {
|
|
975
|
+
// Fresh/stub creds (baileyrs' `initAuthCreds()` shim, or an
|
|
976
|
+
// upstream flow that hadn't finished its first pair) carry
|
|
977
|
+
// neither a noise key nor a paired `me.id`. Returning a
|
|
978
|
+
// zeroed `credsToDeviceJson` in that state poisons the
|
|
979
|
+
// bridge's noise handshake (server responds 428 Precondition
|
|
980
|
+
// Required). Signaling `null` instead lets the bridge
|
|
981
|
+
// bootstrap its own device identity and write it back
|
|
982
|
+
// through `set('device', 'device', …)` on pair-success.
|
|
983
|
+
if (!creds.noiseKey && !creds.me?.id)
|
|
984
|
+
return null;
|
|
500
985
|
return credsToDeviceJson(creds);
|
|
986
|
+
}
|
|
501
987
|
if (key === 'account') {
|
|
502
988
|
if (!creds.account)
|
|
503
989
|
return null;
|
|
@@ -506,13 +992,12 @@ export async function wrapLegacyStore(state, saveCreds, logger) {
|
|
|
506
992
|
return readBinary(type, key);
|
|
507
993
|
}
|
|
508
994
|
if (route === 'signal') {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return null;
|
|
995
|
+
// `BINARY_STORES` is empty as of the move-to-converter
|
|
996
|
+
// migration — keeping the branch for future passthrough
|
|
997
|
+
// stores. No identity fallback needed: identity now goes
|
|
998
|
+
// through the converter, which handles both the address
|
|
999
|
+
// rewrite and the 32↔33-byte prefix.
|
|
1000
|
+
return readBinary(type, key);
|
|
516
1001
|
}
|
|
517
1002
|
if (route === 'bridge-only')
|
|
518
1003
|
return readBinary(type, key);
|
|
@@ -594,28 +1079,6 @@ export async function wrapLegacyStore(state, saveCreds, logger) {
|
|
|
594
1079
|
}
|
|
595
1080
|
}
|
|
596
1081
|
};
|
|
597
|
-
// Identity key fallback: upstream Baileys uses different address format
|
|
598
|
-
// (user_domainType.device) and stores 33 bytes (0x05 + 32-byte DJB key)
|
|
599
|
-
async function identityFallback(key, type) {
|
|
600
|
-
try {
|
|
601
|
-
const baileysAddr = bridgeAddrToBaileysAddr(key);
|
|
602
|
-
if (!baileysAddr)
|
|
603
|
-
return null;
|
|
604
|
-
const raw = await storeGetOne('identity-key', baileysAddr);
|
|
605
|
-
if (!raw || (!Buffer.isBuffer(raw) && !(raw instanceof Uint8Array)))
|
|
606
|
-
return null;
|
|
607
|
-
let buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
|
|
608
|
-
if (buf.length === 33 && buf[0] === 0x05)
|
|
609
|
-
buf = buf.slice(1);
|
|
610
|
-
const arr = new Uint8Array(buf);
|
|
611
|
-
await writeBinary(type, key, arr); // persist under bridge key for future reads
|
|
612
|
-
return arr;
|
|
613
|
-
}
|
|
614
|
-
catch (e) {
|
|
615
|
-
warn(`identity fallback failed for ${key}:`, e);
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
1082
|
}
|
|
620
1083
|
const bufferReviver = (_, value) => {
|
|
621
1084
|
if (value !== null &&
|