@oxidezap/baileyrs 0.2.7 → 0.2.8
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 +23 -0
- package/lib/Socket/events.js +11 -1
- package/lib/Utils/messages.js +45 -4
- package/lib/WABinary/jid-utils.js +62 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -374,6 +374,29 @@ baileyrs auto-wraps, including `useLegacyMultiFileAuthState`. It does not apply
|
|
|
374
374
|
to `useMultiFileAuthState`, whose `keys` is a projection over the engine's own
|
|
375
375
|
store; the `bridge-` namespaces never surface through it.
|
|
376
376
|
|
|
377
|
+
The move back down is not symmetric either. From 0.2.8 on, the core buffers a
|
|
378
|
+
skipped-message key as its seed alone, where an earlier release wrote the
|
|
379
|
+
derived cipher/mac/iv triple beside it. Forward is fine — a record written
|
|
380
|
+
before this carries both, and the newer engine reads it unchanged. Backward is
|
|
381
|
+
not: an older engine cannot parse a record whose skipped keys carry no triple,
|
|
382
|
+
and it fails on **the whole record**, not on the one key.
|
|
383
|
+
|
|
384
|
+
Where that bites depends on which bytes the engine is handed, and a wrapped
|
|
385
|
+
store is not automatically on the safe side of it. It holds the Baileys
|
|
386
|
+
projection, which has only ever held seeds — but it also mirrors the core's
|
|
387
|
+
exact bytes under `bridge-native-session`, and a read prefers that mirror for as
|
|
388
|
+
long as its fingerprint still matches the projection. A downgrade can therefore
|
|
389
|
+
be handed the newer record with a perfectly readable projection sitting right
|
|
390
|
+
beside it.
|
|
391
|
+
|
|
392
|
+
Dropping a mirror row makes the next read rebuild it from the projection, which
|
|
393
|
+
is the format both versions agree on — but only for a session that has one. A
|
|
394
|
+
session that turned native-only lives in that row alone, so dropping *that* one
|
|
395
|
+
loses the session outright, which is the case the warning above is about. A
|
|
396
|
+
session with no skipped keys buffered at the time is unaffected either way: the
|
|
397
|
+
difference only exists while a chain is holding keys for messages that arrived
|
|
398
|
+
out of order.
|
|
399
|
+
|
|
377
400
|
## Disclaimer
|
|
378
401
|
|
|
379
402
|
This project is not affiliated with, endorsed by, or in any way officially connected to
|
package/lib/Socket/events.js
CHANGED
|
@@ -35,7 +35,17 @@ const emitCBEvents = (ctx, node) => {
|
|
|
35
35
|
const id = l1.id;
|
|
36
36
|
if (id)
|
|
37
37
|
ws.emit(`${DEF_TAG_PREFIX}${id}`, node);
|
|
38
|
-
for
|
|
38
|
+
// `for..in` over the attrs rather than `Object.entries`: this runs for every
|
|
39
|
+
// stanza the socket sees and the pair array it built was thrown away
|
|
40
|
+
// immediately. `Object.entries` was own-only by construction though, and
|
|
41
|
+
// `BinaryNode` is public, so the walk is guarded rather than trusting every
|
|
42
|
+
// caller to hand over an attrs map with a bare prototype. An inherited
|
|
43
|
+
// enumerable would otherwise fire CB events for an attr the stanza does not
|
|
44
|
+
// carry; the check costs nothing next to the emits below.
|
|
45
|
+
for (const key in l1) {
|
|
46
|
+
if (!Object.hasOwn(l1, key))
|
|
47
|
+
continue;
|
|
48
|
+
const val = l1[key];
|
|
39
49
|
if (l2)
|
|
40
50
|
ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${val},${l2}`, node);
|
|
41
51
|
ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${val}`, node);
|
package/lib/Utils/messages.js
CHANGED
|
@@ -612,13 +612,54 @@ export const generateWAMessage = async (jid, content, options) => {
|
|
|
612
612
|
: options;
|
|
613
613
|
return generateWAMessageFromContent(jid, await generateWAMessageContent(content, contentOptions), options);
|
|
614
614
|
};
|
|
615
|
+
/**
|
|
616
|
+
* The content keys that carry almost every message on a live socket, settled by
|
|
617
|
+
* identity before the structural predicate runs. Every name here already
|
|
618
|
+
* satisfies that predicate, so this only shortcuts the substring scan; it never
|
|
619
|
+
* changes which key wins.
|
|
620
|
+
*/
|
|
621
|
+
const isCommonContentKey = (key) => {
|
|
622
|
+
switch (key) {
|
|
623
|
+
case 'conversation':
|
|
624
|
+
case 'extendedTextMessage':
|
|
625
|
+
case 'imageMessage':
|
|
626
|
+
case 'videoMessage':
|
|
627
|
+
case 'audioMessage':
|
|
628
|
+
case 'stickerMessage':
|
|
629
|
+
case 'documentMessage':
|
|
630
|
+
case 'reactionMessage':
|
|
631
|
+
case 'protocolMessage':
|
|
632
|
+
case 'pollCreationMessage':
|
|
633
|
+
return true;
|
|
634
|
+
default:
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
615
638
|
/** Get the key to access the true type of content */
|
|
616
639
|
export const getContentType = (content) => {
|
|
617
|
-
if (content) {
|
|
618
|
-
|
|
619
|
-
const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
|
|
620
|
-
return key;
|
|
640
|
+
if (!content) {
|
|
641
|
+
return undefined;
|
|
621
642
|
}
|
|
643
|
+
// The answer stays positional (the first own key naming content wins), but
|
|
644
|
+
// the scan is an indexed loop with an early return instead of `Array.find`,
|
|
645
|
+
// so no closure is allocated and the common keys never reach `includes`.
|
|
646
|
+
//
|
|
647
|
+
// Deliberately still `Object.keys` and not `for..in`: a decoded
|
|
648
|
+
// `WAProto.Message` inherits a default for every field of the schema, so
|
|
649
|
+
// `for..in` would enumerate ~100 prototype keys per call (and would have to
|
|
650
|
+
// filter them back out with `hasOwn` to stay positional) where `Object.keys`
|
|
651
|
+
// yields only the handful that were actually set.
|
|
652
|
+
const keys = Object.keys(content);
|
|
653
|
+
for (let i = 0; i < keys.length; i++) {
|
|
654
|
+
const key = keys[i];
|
|
655
|
+
if (isCommonContentKey(key)) {
|
|
656
|
+
return key;
|
|
657
|
+
}
|
|
658
|
+
if (key.includes('Message') && key !== 'senderKeyDistributionMessage') {
|
|
659
|
+
return key;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return undefined;
|
|
622
663
|
};
|
|
623
664
|
const getFutureProofMessage = (message) => message?.ephemeralMessage ||
|
|
624
665
|
message?.viewOnceMessage ||
|
|
@@ -57,8 +57,52 @@ export const jidDecode = (jid) => {
|
|
|
57
57
|
device: device ? +device : undefined
|
|
58
58
|
};
|
|
59
59
|
};
|
|
60
|
+
const CHAR_DEVICE_SEP = 58; // ':'
|
|
61
|
+
const CHAR_AGENT_SEP = 95; // '_'
|
|
62
|
+
/**
|
|
63
|
+
* Index at which the user component ends, given the index of the '@'.
|
|
64
|
+
*
|
|
65
|
+
* `jidDecode` derives the user by splitting the pre-`@` half on ':' and then on
|
|
66
|
+
* '_', so the user always runs to whichever of ':' or '_' comes first, and to
|
|
67
|
+
* the '@' when neither is there. Locating that boundary answers both callers
|
|
68
|
+
* below without the intermediate arrays and result object `jidDecode` allocates.
|
|
69
|
+
*/
|
|
70
|
+
const userEndBefore = (jid, sepIdx) => {
|
|
71
|
+
for (let i = 0; i < sepIdx; i++) {
|
|
72
|
+
const code = jid.charCodeAt(i);
|
|
73
|
+
if (code === CHAR_DEVICE_SEP || code === CHAR_AGENT_SEP) {
|
|
74
|
+
return i;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return sepIdx;
|
|
78
|
+
};
|
|
79
|
+
/** As above, or -1 when the JID has no server part: the case `jidDecode` reports as `undefined`. */
|
|
80
|
+
const userEnd = (jid) => {
|
|
81
|
+
const sepIdx = jid.indexOf('@');
|
|
82
|
+
return sepIdx < 0 ? -1 : userEndBefore(jid, sepIdx);
|
|
83
|
+
};
|
|
60
84
|
/** Compare the user component of two JIDs, matching upstream Baileys. */
|
|
61
|
-
export const areJidsSameUser = (jid1, jid2) =>
|
|
85
|
+
export const areJidsSameUser = (jid1, jid2) => {
|
|
86
|
+
// Same answer as comparing `jidDecode(...)?.user`, compared in place: this
|
|
87
|
+
// runs once per participant check on the message pipeline, and decoding both
|
|
88
|
+
// sides allocated four arrays and two objects only to throw them away.
|
|
89
|
+
const end = typeof jid1 === 'string' ? userEnd(jid1) : -1;
|
|
90
|
+
if (end !== (typeof jid2 === 'string' ? userEnd(jid2) : -1)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Neither side decodes, so upstream compares `undefined === undefined`.
|
|
94
|
+
if (end < 0) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const left = jid1;
|
|
98
|
+
const right = jid2;
|
|
99
|
+
for (let i = 0; i < end; i++) {
|
|
100
|
+
if (left.charCodeAt(i) !== right.charCodeAt(i)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
62
106
|
export const isJidMetaAI = (jid) => jid?.endsWith('@bot');
|
|
63
107
|
export const isPnUser = (jid) => jid?.endsWith('@s.whatsapp.net');
|
|
64
108
|
export const isLidUser = (jid) => jid?.endsWith('@lid');
|
|
@@ -71,12 +115,25 @@ export const isHostedPnUser = (jid) => jid?.endsWith('@hosted');
|
|
|
71
115
|
const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/;
|
|
72
116
|
export const isJidBot = (jid) => jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us');
|
|
73
117
|
export const jidNormalizedUser = (jid) => {
|
|
74
|
-
|
|
75
|
-
|
|
118
|
+
if (typeof jid !== 'string') {
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
const sepIdx = jid.indexOf('@');
|
|
122
|
+
if (sepIdx < 0) {
|
|
76
123
|
return '';
|
|
77
124
|
}
|
|
78
|
-
|
|
79
|
-
|
|
125
|
+
// Everything between the user and the '@' is exactly what normalization
|
|
126
|
+
// drops, so the boundary is all this needs. `jidDecode` would split the same
|
|
127
|
+
// half twice and box the pieces in an object only for them to be re-joined.
|
|
128
|
+
const end = userEndBefore(jid, sepIdx);
|
|
129
|
+
// Fast path for the shape that dominates the pipeline: nothing to strip and a
|
|
130
|
+
// server `jidEncode` re-emits verbatim, so the JID already is its own normal
|
|
131
|
+
// form and no new string has to be built at all.
|
|
132
|
+
if (end === sepIdx && !jid.endsWith('@c.us')) {
|
|
133
|
+
return jid;
|
|
134
|
+
}
|
|
135
|
+
const server = jid.slice(sepIdx + 1);
|
|
136
|
+
return jidEncode(jid.slice(0, end), server === 'c.us' ? 's.whatsapp.net' : server);
|
|
80
137
|
};
|
|
81
138
|
export const transferDevice = (fromJid, toJid) => {
|
|
82
139
|
const deviceId = jidDecode(fromJid)?.device || 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxidezap/baileyrs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"description": "A Rust-powered WhatsApp Web library for JavaScript, with a Baileys-compatible API",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@hapi/boom": "^9.1.4",
|
|
85
|
-
"@oxidezap/whatsapp-rust-bridge": "0.
|
|
85
|
+
"@oxidezap/whatsapp-rust-bridge": "0.18.0",
|
|
86
86
|
"long": "^5.3.2",
|
|
87
87
|
"pino": "^10.3.1",
|
|
88
88
|
"protobufjs": "^7.6.5"
|