@oxidezap/baileyrs 0.0.29 → 0.0.30

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.
@@ -199,15 +199,21 @@ class ProtoCompatibilityRuntime {
199
199
  makeConstructor(schemaId, fields, sourceCodec) {
200
200
  const convertToObject = (message) => this.toObject(schemaId, message, JSON_OPTIONS);
201
201
  const path = PROTO_MESSAGE_SCHEMAS[schemaId][0];
202
+ // Indexed loops: these run for every constructed instance and stay on
203
+ // the megamorphic path at runtime, where V8 does not elide for..of
204
+ // iterator allocations.
202
205
  const constructor = function (properties) {
203
- for (const field of fields) {
206
+ for (let i = 0; i < fields.length; i++) {
207
+ const field = fields[i];
204
208
  if (field[3] & PROTO_FIELD_FLAG.repeated)
205
209
  this[field[0]] = [];
206
210
  else if (field[3] & PROTO_FIELD_FLAG.map)
207
211
  this[field[0]] = {};
208
212
  }
209
213
  if (properties) {
210
- for (const key of Object.keys(properties)) {
214
+ const keys = Object.keys(properties);
215
+ for (let i = 0; i < keys.length; i++) {
216
+ const key = keys[i];
211
217
  const value = properties[key];
212
218
  if (value !== null && value !== undefined)
213
219
  this[key] = value;
@@ -311,7 +317,12 @@ class ProtoCompatibilityRuntime {
311
317
  return input;
312
318
  const data = input;
313
319
  const instance = new constructor();
314
- for (const field of PROTO_MESSAGE_SCHEMAS[schemaId][1]) {
320
+ // Indexed loop: this walks the full schema per converted node on the
321
+ // megamorphic path, where V8 does not elide for..of iterator
322
+ // allocations (~90 iterator results per WebMessageInfo envelope).
323
+ const fields = PROTO_MESSAGE_SCHEMAS[schemaId][1];
324
+ for (let i = 0; i < fields.length; i++) {
325
+ const field = fields[i];
315
326
  const value = data[field[0]];
316
327
  if (value === null || value === undefined)
317
328
  continue;
@@ -6,7 +6,8 @@
6
6
  * mapped type so TS forces a handler per variant — a missing handler
7
7
  * is a compile error.
8
8
  */
9
- import { BinaryReader } from 'whatsapp-rust-bridge';
9
+ import Long from 'long';
10
+ import { BinaryReader, decodeMessageWireInfos, decodeReceiptWireBatch, decodeServerAckWireBatch } from 'whatsapp-rust-bridge';
10
11
  import { adaptBridgeEvent, adaptBridgeMessageWire } from '../Bridge/index.js';
11
12
  import { decodeHistorySyncWireBatch } from '../Bridge/history-sync-wire.js';
12
13
  import { bridgeGroupMetadataToBaileys } from '../Compatibility/group-metadata.js';
@@ -56,18 +57,29 @@ const emitCBEvents = (ctx, node) => {
56
57
  * bridge → canonical → Baileys pipeline. Adapters never reach for the proto.
57
58
  */
58
59
  const canonicalMessageToWAMessage = (m) => {
59
- const wm = WAProto.WebMessageInfo.fromObject({
60
- key: {
61
- remoteJid: m.chatJid,
62
- fromMe: m.isFromMe,
63
- id: m.id,
64
- participant: m.senderJid
65
- },
66
- message: m.messageProto,
67
- messageTimestamp: m.timestamp,
68
- pushName: m.pushName,
69
- status: WAProto.WebMessageInfo.Status.SERVER_ACK
70
- });
60
+ // Direct construction instead of a `WebMessageInfo.fromObject` envelope:
61
+ // every input is already in its wire-correct runtime type, so the schema
62
+ // walk would only re-derive them field by field on the hottest path. `new`
63
+ // keeps protobufjs parity (instanceof, prototype defaults and own
64
+ // repeated/map fields), and `Message.fromObject` still normalizes the
65
+ // payload while short-circuiting the already-decoded runtime instances.
66
+ const key = new WAProto.MessageKey();
67
+ key.remoteJid = m.chatJid;
68
+ key.fromMe = m.isFromMe;
69
+ key.id = m.id;
70
+ if (m.senderJid !== undefined)
71
+ key.participant = m.senderJid;
72
+ const wm = new WAProto.WebMessageInfo();
73
+ wm.key = key;
74
+ if (m.messageProto !== undefined)
75
+ wm.message = WAProto.Message.fromObject(m.messageProto);
76
+ // Source-side WAProto types 64-bit fields via the bridge shapes (plain
77
+ // number); the published facade and the previous fromObject path both
78
+ // carry a protobufjs `Long` here, so keep that runtime shape.
79
+ wm.messageTimestamp = Long.fromValue(m.timestamp);
80
+ if (m.pushName !== undefined)
81
+ wm.pushName = m.pushName;
82
+ wm.status = WAProto.WebMessageInfo.Status.SERVER_ACK;
71
83
  if (m.participantAlt)
72
84
  wm.key.participantAlt = m.participantAlt;
73
85
  if (m.remoteJidAlt)
@@ -784,8 +796,16 @@ export const makeEventHandlers = (ctx, callbacks) => {
784
796
  return;
785
797
  }
786
798
  const messageCount = batch.messageOffsets.length - MESSAGE_WIRE_OFFSET_SENTINEL_COUNT;
787
- if (batch.infos.length !== messageCount) {
788
- ctx.logger.error({ infoCount: batch.infos.length, messageCount }, 'message wire batch metadata count does not match its payload count');
799
+ let infos;
800
+ try {
801
+ infos = decodeMessageWireInfos(batch);
802
+ }
803
+ catch (err) {
804
+ ctx.logger.error({ err }, 'failed to decode message wire metadata records');
805
+ return;
806
+ }
807
+ if (infos.length !== messageCount) {
808
+ ctx.logger.error({ infoCount: infos.length, messageCount }, 'message wire batch metadata count does not match its payload count');
789
809
  return;
790
810
  }
791
811
  // Decode through the runtime facade so each payload materializes as a
@@ -808,17 +828,47 @@ export const makeEventHandlers = (ctx, callbacks) => {
808
828
  ctx.logger.warn({ err, index }, 'skipping malformed wire message');
809
829
  }
810
830
  }
811
- dispatchCanonicalBatch(ctx, dispatchCtx, messageCount, index => adaptBridgeMessageWire(messages[index], batch.infos[index], ctx.logger));
831
+ dispatchCanonicalBatch(ctx, dispatchCtx, messageCount, index => adaptBridgeMessageWire(messages[index], infos[index], ctx.logger));
812
832
  };
813
833
  const onHistorySyncBatch = (batch) => {
814
834
  const decoded = decodeHistorySyncWireBatch(batch, ctx.logger);
815
835
  onEvent(decoded.event);
816
836
  return decoded.skippedConversations;
817
837
  };
838
+ // Packed receipt/ack batches reconstruct the single-event wire shape and
839
+ // reuse the same adapter path, so transport is the only thing that changes.
840
+ const onReceiptBatch = (batch) => {
841
+ let receipts;
842
+ try {
843
+ receipts = decodeReceiptWireBatch(batch);
844
+ }
845
+ catch (err) {
846
+ ctx.logger.error({ err }, 'failed to decode receipt wire batch');
847
+ return;
848
+ }
849
+ // The decoded payload matches the single-event wire shape; the union's
850
+ // declared type is narrower than the adapter's tolerance.
851
+ for (const data of receipts)
852
+ onEvent({ type: 'receipt', data });
853
+ };
854
+ const onServerAckBatch = (batch) => {
855
+ let acks;
856
+ try {
857
+ acks = decodeServerAckWireBatch(batch);
858
+ }
859
+ catch (err) {
860
+ ctx.logger.error({ err }, 'failed to decode server-ack wire batch');
861
+ return;
862
+ }
863
+ for (const data of acks)
864
+ onEvent({ type: 'server_ack', data });
865
+ };
818
866
  return {
819
867
  onEvent,
820
868
  onMessageBatch,
821
869
  onHistorySyncBatch,
870
+ onReceiptBatch,
871
+ onServerAckBatch,
822
872
  historySyncConversationTypes: CONVERSATION_HISTORY_SYNC_TYPES
823
873
  };
824
874
  };
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from 'node:buffer';
2
+ import LongRuntime from 'long';
2
3
  import { Readable } from 'node:stream';
3
4
  import { toBridgeMediaType } from '../Compatibility/media-type.js';
4
5
  import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
@@ -261,7 +262,10 @@ export const generateWAMessageContent = async (message, options) => {
261
262
  if (hasNonNullishProperty(message, 'text')) {
262
263
  const extContent = { text: message.text };
263
264
  let urlInfo = message.linkPreview;
264
- if (typeof urlInfo === 'undefined') {
265
+ // Gate on the resolver before awaiting: without one the helper resolves
266
+ // to undefined anyway, and every plain text send would still pay for a
267
+ // promise plus a microtask turn.
268
+ if (typeof urlInfo === 'undefined' && options.getUrlInfo) {
265
269
  urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
266
270
  }
267
271
  if (urlInfo) {
@@ -546,20 +550,32 @@ export const generateWAMessageFromContent = (jid, message, options) => {
546
550
  //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
547
551
  };
548
552
  }
549
- message = WAProto.Message.create(message);
550
- const messageJSON = {
551
- key: {
552
- remoteJid: jid,
553
- fromMe: true,
554
- id: options?.messageId || '' // Rust generates the real message ID
555
- },
556
- message: message,
557
- messageTimestamp: timestamp,
558
- messageStubParameters: [],
559
- participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
560
- status: WAMessageStatus.PENDING
561
- };
562
- return WAProto.WebMessageInfo.fromObject(messageJSON);
553
+ // `create` copies into a fresh instance; content built by
554
+ // `generateWAMessageContent` already is one, and the branches above mutate
555
+ // it in place regardless, so copying it again only duplicates the tree.
556
+ if (!(message instanceof WAProto.Message))
557
+ message = WAProto.Message.create(message);
558
+ // Direct construction instead of a `WebMessageInfo.fromObject` envelope:
559
+ // every value is already in its runtime type (`message` came from
560
+ // `Message.create` above), so the schema walk would only re-derive them.
561
+ // `new` keeps protobufjs parity, including the constructor's own
562
+ // `messageStubParameters` array. The `Long` cast mirrors the published
563
+ // facade shape for 64-bit fields (source-side WAProto types them as plain
564
+ // numbers via the bridge shapes).
565
+ const messageKey = new WAProto.MessageKey();
566
+ messageKey.remoteJid = jid;
567
+ messageKey.fromMe = true;
568
+ messageKey.id = options?.messageId || ''; // Rust generates the real message ID
569
+ const wm = new WAProto.WebMessageInfo();
570
+ wm.key = messageKey;
571
+ wm.message = message;
572
+ wm.messageTimestamp = LongRuntime.fromValue(timestamp);
573
+ // TODO: Add support for LIDs
574
+ const participant = isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined;
575
+ if (participant !== undefined)
576
+ wm.participant = participant;
577
+ wm.status = WAMessageStatus.PENDING;
578
+ return wm;
563
579
  };
564
580
  export const generateWAMessage = async (jid, content, options) => {
565
581
  const contentOptions = options.messageId && options.logger
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oxidezap/baileyrs",
3
3
  "type": "module",
4
- "version": "0.0.29",
4
+ "version": "0.0.30",
5
5
  "description": "A Rust-powered WhatsApp Web library for JavaScript, with a Baileys-compatible API",
6
6
  "keywords": [
7
7
  "whatsapp",
@@ -78,7 +78,7 @@
78
78
  "long": "^5.3.2",
79
79
  "pino": "^10.3.1",
80
80
  "protobufjs": "^7.6.5",
81
- "whatsapp-rust-bridge": "0.6.0-alpha.39"
81
+ "whatsapp-rust-bridge": "0.6.0-alpha.40"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@bufbuild/protobuf": "^2.13.0",