@oxidezap/baileyrs 0.2.2 → 0.2.4

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.
@@ -179,10 +179,6 @@ const ADAPTERS = {
179
179
  };
180
180
  },
181
181
  // ── Contacts ──
182
- push_name_update: data => {
183
- const jid = asJidString(data?.jid);
184
- return jid ? { type: 'pushNameUpdate', jid, newPushName: asString(data?.new_push_name) } : null;
185
- },
186
182
  contact_update: data => adaptContactUpdate(data),
187
183
  contact_updated: data => adaptContactUpdate(data),
188
184
  picture_update: data => {
@@ -456,6 +452,7 @@ const ADAPTERS = {
456
452
  },
457
453
  // ── Acknowledged but no Baileys equivalent (noop) ──
458
454
  self_push_name_updated: () => ({ type: 'noop', bridgeType: 'self_push_name_updated' }),
455
+ client_expiration_changed: () => ({ type: 'noop', bridgeType: 'client_expiration_changed' }),
459
456
  offline_sync_completed: data => ({
460
457
  type: 'offlineSyncCompleted',
461
458
  count: asNumber(data?.count) ?? 0
@@ -173,11 +173,6 @@ export interface CanonicalReceipt {
173
173
  */
174
174
  receiptType?: 'delivered' | 'sent' | 'sender' | 'retry' | 'enc-rekey-retry' | 'read' | 'read-self' | 'played' | 'played-self' | 'inactive' | 'peer-msg' | 'history-sync' | 'server-error' | 'other';
175
175
  }
176
- export interface CanonicalPushNameUpdate {
177
- type: 'pushNameUpdate';
178
- jid: string;
179
- newPushName?: string;
180
- }
181
176
  export interface CanonicalContactUpdate {
182
177
  type: 'contactUpdate';
183
178
  jid: string;
@@ -713,5 +708,5 @@ export interface CanonicalServerAck {
713
708
  timestamp?: number;
714
709
  error?: string;
715
710
  }
716
- export type CanonicalEvent = CanonicalConnected | CanonicalDisconnected | CanonicalQR | CanonicalPairSuccess | CanonicalPairError | CanonicalLoggedOut | CanonicalConnectFailure | CanonicalStreamError | CanonicalStreamReplaced | CanonicalClientOutdated | CanonicalTemporaryBan | CanonicalQrScannedWithoutMultidevice | CanonicalMessage | CanonicalReceipt | CanonicalPushNameUpdate | CanonicalContactUpdate | CanonicalPictureUpdate | CanonicalPresence | CanonicalChatPresence | CanonicalGroupUpdate | CanonicalArchiveUpdate | CanonicalPinUpdate | CanonicalMuteUpdate | CanonicalStarUpdate | CanonicalMarkChatAsReadUpdate | CanonicalLabelEdit | CanonicalLabelAssociation | CanonicalAppStateSyncFailed | CanonicalQrCodesExhausted | CanonicalSettingUpdate | CanonicalIncomingCall | CanonicalUndecryptableMessage | CanonicalLidMappingUpdate | CanonicalNewsletterLiveUpdate | CanonicalChatDelete | CanonicalChatClear | CanonicalMessageDelete | CanonicalDisappearingModeChanged | CanonicalHistorySync | CanonicalOfflineSyncCompleted | CanonicalDirtyState | CanonicalRawNode | CanonicalNotification | CanonicalMexNotification | CanonicalServerAck | CanonicalNoop;
711
+ export type CanonicalEvent = CanonicalConnected | CanonicalDisconnected | CanonicalQR | CanonicalPairSuccess | CanonicalPairError | CanonicalLoggedOut | CanonicalConnectFailure | CanonicalStreamError | CanonicalStreamReplaced | CanonicalClientOutdated | CanonicalTemporaryBan | CanonicalQrScannedWithoutMultidevice | CanonicalMessage | CanonicalReceipt | CanonicalContactUpdate | CanonicalPictureUpdate | CanonicalPresence | CanonicalChatPresence | CanonicalGroupUpdate | CanonicalArchiveUpdate | CanonicalPinUpdate | CanonicalMuteUpdate | CanonicalStarUpdate | CanonicalMarkChatAsReadUpdate | CanonicalLabelEdit | CanonicalLabelAssociation | CanonicalAppStateSyncFailed | CanonicalQrCodesExhausted | CanonicalSettingUpdate | CanonicalIncomingCall | CanonicalUndecryptableMessage | CanonicalLidMappingUpdate | CanonicalNewsletterLiveUpdate | CanonicalChatDelete | CanonicalChatClear | CanonicalMessageDelete | CanonicalDisappearingModeChanged | CanonicalHistorySync | CanonicalOfflineSyncCompleted | CanonicalDirtyState | CanonicalRawNode | CanonicalNotification | CanonicalMexNotification | CanonicalServerAck | CanonicalNoop;
717
712
  //# sourceMappingURL=types.d.ts.map
@@ -1,9 +1,16 @@
1
1
  import type { MessageRetransmissionInput } from '@oxidezap/whatsapp-rust-bridge';
2
2
  import type { BinaryNode, MessageRelayOptions } from '../Types/index.js';
3
- type OwnRelayIdentity = {
3
+ export declare const EMPTY_RELAY_NODES: BinaryNode[];
4
+ export type OwnRelayIdentity = {
4
5
  id?: string;
5
6
  lid?: string;
6
7
  } | undefined;
8
+ /**
9
+ * The one place that decides between the caller's id and a generated one.
10
+ * Every send path resolves through here, so `messageId` means the same thing
11
+ * on `sendMessage` and on `relayMessage`.
12
+ */
13
+ export declare const resolveMessageId: (own: OwnRelayIdentity, messageId?: string) => string;
7
14
  export type MessageRelayPlan = {
8
15
  kind: 'message';
9
16
  messageId: string;
@@ -22,5 +29,4 @@ export type MessageRelayPlan = {
22
29
  input: MessageRetransmissionInput;
23
30
  };
24
31
  export declare const planMessageRelay: (jid: string, options: MessageRelayOptions, own: OwnRelayIdentity) => MessageRelayPlan;
25
- export {};
26
32
  //# sourceMappingURL=message-relay.d.ts.map
@@ -1,7 +1,13 @@
1
1
  import { Boom } from '../Utils/boom.js';
2
2
  import { generateMessageIDV2 } from '../Utils/generics.js';
3
3
  import { areJidsSameUser, isJidBroadcast, isJidGroup, isJidNewsletter } from '../WABinary/index.js';
4
- const EMPTY_RELAY_NODES = [];
4
+ export const EMPTY_RELAY_NODES = [];
5
+ /**
6
+ * The one place that decides between the caller's id and a generated one.
7
+ * Every send path resolves through here, so `messageId` means the same thing
8
+ * on `sendMessage` and on `relayMessage`.
9
+ */
10
+ export const resolveMessageId = (own, messageId) => messageId || generateMessageIDV2(own?.id);
5
11
  const assertSupportedAttributes = (attributes) => {
6
12
  if (!attributes)
7
13
  return;
@@ -24,7 +30,7 @@ export const planMessageRelay = (jid, options, own) => {
24
30
  statusCode: 400
25
31
  });
26
32
  }
27
- const id = messageId || generateMessageIDV2(own?.id);
33
+ const id = resolveMessageId(own, messageId);
28
34
  const refreshGroupMetadata = useCachedGroupMetadata === false;
29
35
  if (participant) {
30
36
  const requesterIsOwnDevice = isDirectConversation(jid) && isOwnDevice(participant.jid, own);
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Recreate a bridge rejection at the JS boundary so its stack names the caller.
3
+ *
4
+ * The engine constructs its errors inside wasm, in a microtask continuation
5
+ * that runs after the server response arrives. V8 captures the stack at
6
+ * construction, so what reaches the consumer is glue plus `wasm://` frames
7
+ * and never the code that made the call. Recreating the error inside a catch
8
+ * the caller is awaiting fixes that: V8's async stack walk names the awaiting
9
+ * frames, so a bot sees `at async itsOwnCommand` instead of function indices.
10
+ *
11
+ * What survives translation, and how:
12
+ *
13
+ * - The engine sets its payload (`kind`, `serverCode`, `serverText`,
14
+ * `errorType`, `backoffSeconds`, and whatever it adds next) and `name`
15
+ * (`WhatsAppError`) as own enumerable properties, while `message` and
16
+ * `stack` are own non-enumerable ones. `Object.assign` therefore copies
17
+ * exactly the payload and the name, never touches the fresh stack, and
18
+ * cannot invent a key the rejection did not carry.
19
+ * - The original error becomes `cause`, so the wasm-side stack stays
20
+ * reachable for whoever wants the engine's half of the story.
21
+ * - Anything that is not the bridge's shape (an `Error` carrying a string
22
+ * `kind`) passes through with the same identity it arrived with.
23
+ *
24
+ * The wrap happens once per client: `getClient()`/`getClientSync()` hand out
25
+ * a `Proxy` whose method wrappers are built on first access and cached, so
26
+ * the happy path pays one property trap and one extra promise layer, and the
27
+ * error path pays for everything else.
28
+ */
29
+ import type { WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
30
+ /** Rebuild a bridge rejection here; return anything else untouched. */
31
+ export declare const withCallerStack: (error: unknown) => unknown;
32
+ /**
33
+ * The client the socket hands out. Methods bind to the raw client (wasm
34
+ * bindings need their own `this`), sync returns pass through untouched, and
35
+ * promise returns reject through `withCallerStack`.
36
+ */
37
+ export declare const wrapBridgeClient: (client: WasmWhatsAppClient) => WasmWhatsAppClient;
38
+ //# sourceMappingURL=bridge-error-boundary.d.ts.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Recreate a bridge rejection at the JS boundary so its stack names the caller.
3
+ *
4
+ * The engine constructs its errors inside wasm, in a microtask continuation
5
+ * that runs after the server response arrives. V8 captures the stack at
6
+ * construction, so what reaches the consumer is glue plus `wasm://` frames
7
+ * and never the code that made the call. Recreating the error inside a catch
8
+ * the caller is awaiting fixes that: V8's async stack walk names the awaiting
9
+ * frames, so a bot sees `at async itsOwnCommand` instead of function indices.
10
+ *
11
+ * What survives translation, and how:
12
+ *
13
+ * - The engine sets its payload (`kind`, `serverCode`, `serverText`,
14
+ * `errorType`, `backoffSeconds`, and whatever it adds next) and `name`
15
+ * (`WhatsAppError`) as own enumerable properties, while `message` and
16
+ * `stack` are own non-enumerable ones. `Object.assign` therefore copies
17
+ * exactly the payload and the name, never touches the fresh stack, and
18
+ * cannot invent a key the rejection did not carry.
19
+ * - The original error becomes `cause`, so the wasm-side stack stays
20
+ * reachable for whoever wants the engine's half of the story.
21
+ * - Anything that is not the bridge's shape (an `Error` carrying a string
22
+ * `kind`) passes through with the same identity it arrived with.
23
+ *
24
+ * The wrap happens once per client: `getClient()`/`getClientSync()` hand out
25
+ * a `Proxy` whose method wrappers are built on first access and cached, so
26
+ * the happy path pays one property trap and one extra promise layer, and the
27
+ * error path pays for everything else.
28
+ */
29
+ /**
30
+ * The bridge's rejection shape: a real `Error` the glue named `WhatsAppError`,
31
+ * carrying a string `kind`. The name check keeps consumer-owned errors that
32
+ * also discriminate on `kind` (a custom store or cache thrown through a
33
+ * bridge call) crossing by identity instead of being rebuilt.
34
+ */
35
+ const isBridgeRejection = (error) => error instanceof Error && error.name === 'WhatsAppError' && typeof error.kind === 'string';
36
+ /** Rebuild a bridge rejection here; return anything else untouched. */
37
+ export const withCallerStack = (error) => {
38
+ if (!isBridgeRejection(error))
39
+ return error;
40
+ return Object.assign(new Error(error.message, { cause: error }), error);
41
+ };
42
+ const rethrowWithCallerStack = async (call) => {
43
+ try {
44
+ return await call;
45
+ }
46
+ catch (error) {
47
+ throw withCallerStack(error);
48
+ }
49
+ };
50
+ const wrappedClients = new WeakMap();
51
+ /**
52
+ * The client the socket hands out. Methods bind to the raw client (wasm
53
+ * bindings need their own `this`), sync returns pass through untouched, and
54
+ * promise returns reject through `withCallerStack`.
55
+ */
56
+ export const wrapBridgeClient = (client) => {
57
+ const memo = wrappedClients.get(client);
58
+ if (memo)
59
+ return memo;
60
+ const methods = new Map();
61
+ const wrapped = new Proxy(client, {
62
+ get(target, prop) {
63
+ const hit = methods.get(prop);
64
+ if (hit !== undefined)
65
+ return hit;
66
+ const value = Reflect.get(target, prop);
67
+ if (typeof value !== 'function')
68
+ return value;
69
+ const method = (...args) => {
70
+ const out = value.apply(target, args);
71
+ return out instanceof Promise ? rethrowWithCallerStack(out) : out;
72
+ };
73
+ methods.set(prop, method);
74
+ return method;
75
+ }
76
+ });
77
+ wrappedClients.set(client, wrapped);
78
+ return wrapped;
79
+ };
80
+ //# sourceMappingURL=bridge-error-boundary.js.map
@@ -17,7 +17,7 @@ import { LabelAssociationType } from '../Types/LabelAssociation.js';
17
17
  import { Boom } from '../Utils/boom.js';
18
18
  import { toNumber } from '../Utils/generics.js';
19
19
  import { CONVERSATION_HISTORY_SYNC_TYPES } from '../Utils/process-history-message.js';
20
- import { isJidGroup } from '../WABinary/jid-utils.js';
20
+ import { isJidBroadcast, isJidGroup } from '../WABinary/jid-utils.js';
21
21
  import { buildGroupCreateStubMessage, buildGroupJoinRequestEvents, buildGroupNotificationChatUpdates, buildGroupNotificationDomainEvent, buildGroupNotificationStubMessages } from '../Compatibility/group-notifications.js';
22
22
  import { emitMessageUpsert } from '../Compatibility/message-upsert.js';
23
23
  import { extractMessageCappingPayload } from './message-capping.js';
@@ -99,6 +99,20 @@ const messageUpsertMetadata = (message) => ({
99
99
  requestId: message.unavailableRequestId
100
100
  });
101
101
  const hasMessageSideEffects = (message) => message.messageProto.reactionMessage != null || message.messageProto.protocolMessage != null;
102
+ // Mirror upstream `messages-recv.ts`: every inbound envelope that carries a
103
+ // push name surfaces it as `contacts.update` with `notify`. This is also what
104
+ // replaces the bridge's dedicated `push_name_update` event, gone in 0.14.0.
105
+ const emitInboundPushName = (ctx, evt) => {
106
+ if (!evt.pushName || evt.isFromMe)
107
+ return;
108
+ const id = evt.senderJid ?? evt.chatJid;
109
+ // A broadcast envelope resolves to the pseudo-contact (the canonical layer
110
+ // drops the participant for non-groups); naming `status@broadcast` after
111
+ // whoever posted last would corrupt it, so stay silent instead.
112
+ if (isJidBroadcast(id))
113
+ return;
114
+ ctx.ev.emit('contacts.update', [{ id, notify: evt.pushName }]);
115
+ };
102
116
  const hasSameUpsertMetadata = (left, right) => left.type === right.type && left.requestId === right.requestId;
103
117
  const clearHistorySyncPausedTimeout = (state) => {
104
118
  if (state.pausedTimeout)
@@ -315,6 +329,7 @@ const DISPATCHERS = {
315
329
  message: (evt, { ctx }) => {
316
330
  if (ctx.fullConfig.shouldIgnoreJid?.(evt.chatJid))
317
331
  return;
332
+ emitInboundPushName(ctx, evt);
318
333
  // Note: `emitOwnEvents=false` is NOT applied here. Upstream Baileys
319
334
  // uses that flag to suppress the local echo when `sendMessage()`
320
335
  // succeeds, not to drop inbound `fromMe` messages from other linked
@@ -422,6 +437,13 @@ const DISPATCHERS = {
422
437
  // can request a resend (PLACEHOLDER_MESSAGE_RESEND PDO). Logging
423
438
  // happens at debug — upstream considers this routine.
424
439
  ctx.logger.debug({ id: evt.id, chat: evt.chatJid, isUnavailable: evt.isUnavailable, fail: evt.decryptFailMode }, 'undecryptable message received');
440
+ // The name is envelope metadata, independent of whether the failure
441
+ // may be surfaced, so it goes out even for `hide`. The stub below
442
+ // predates `shouldIgnoreJid` and stays unguarded; the push name
443
+ // emission is new, so it honors the predicate like the other two
444
+ // call sites do.
445
+ if (!ctx.fullConfig.shouldIgnoreJid?.(evt.chatJid))
446
+ emitInboundPushName(ctx, evt);
425
447
  // `decrypt_fail_mode === 'hide'` means the server told us to
426
448
  // silently drop — match that by NOT emitting an upsert.
427
449
  if (evt.decryptFailMode === 'hide')
@@ -445,7 +467,6 @@ const DISPATCHERS = {
445
467
  ctx.ev.emit('messages.upsert', { messages: [stubMsg], type: 'notify' });
446
468
  },
447
469
  // ── Contacts ──
448
- pushNameUpdate: (evt, { ctx }) => ctx.ev.emit('contacts.update', [{ id: evt.jid, notify: evt.newPushName }]),
449
470
  contactUpdate: (evt, { ctx }) => {
450
471
  // Promote ContactAction fields into upstream's `Partial<Contact>`
451
472
  // shape so consumers (sidebar UIs, contact pickers) see real names
@@ -869,6 +890,11 @@ const dispatchCanonicalBatch = (ctx, dispatchCtx, count, canonicalAt) => {
869
890
  if (ctx.fullConfig.shouldIgnoreJid?.(canonical.chatJid))
870
891
  continue;
871
892
  try {
893
+ // This branch bypasses the single-message dispatcher, so the push
894
+ // name has to be surfaced here too. Inside the try: a throwing
895
+ // consumer listener skips this message only, like the containment
896
+ // in dispatchCanonicalEvent.
897
+ emitInboundPushName(ctx, canonical);
872
898
  const metadata = messageUpsertMetadata(canonical);
873
899
  const message = canonicalMessageToWAMessage(canonical);
874
900
  if (pending && hasSameUpsertMetadata(pending, metadata)) {
@@ -28,6 +28,7 @@ import { makeChatActionMethods } from './chat-actions.js';
28
28
  import { makeContactMethods } from './contacts.js';
29
29
  import { makeCommunityMethods } from './communities.js';
30
30
  import { makeBridgeClientOwner } from './bridge-client-owner.js';
31
+ import { wrapBridgeClient } from './bridge-error-boundary.js';
31
32
  import { makeTerminalCloseReporter } from './terminal-close-reporter.js';
32
33
  import { makeEventHandlers } from './events.js';
33
34
  import { makeGroupMethods } from './groups.js';
@@ -224,7 +225,7 @@ const makeWASocket = (config) => {
224
225
  // Unregister before freeing: `free()` is swallowed, so ordering it
225
226
  // last would leave the module-level pointer aimed at a client that is
226
227
  // already gone if anything between them threw.
227
- _unregisterActiveBridgeClient(client);
228
+ _unregisterActiveBridgeClient(wrapBridgeClient(client));
228
229
  try {
229
230
  client.free();
230
231
  }
@@ -298,7 +299,7 @@ const makeWASocket = (config) => {
298
299
  if (initialized) {
299
300
  const ready = owner.peek();
300
301
  if (ready)
301
- return Promise.resolve(ready);
302
+ return Promise.resolve(wrapBridgeClient(ready));
302
303
  }
303
304
  return initPromise.then(() => {
304
305
  // Rechecked after the await: a close landing while startup was
@@ -319,7 +320,7 @@ const makeWASocket = (config) => {
319
320
  const built = owner.peek();
320
321
  if (!built)
321
322
  throw new Boom('Client not initialized', { statusCode: 500 });
322
- return built;
323
+ return wrapBridgeClient(built);
323
324
  });
324
325
  },
325
326
  getClientSync: () => {
@@ -330,7 +331,7 @@ const makeWASocket = (config) => {
330
331
  const built = owner.peek();
331
332
  if (!built)
332
333
  throw new Boom('Client not initialized', { statusCode: 500 });
333
- return built;
334
+ return wrapBridgeClient(built);
334
335
  }
335
336
  };
336
337
  // The native repository delegates Signal state directly to the core and does
@@ -459,8 +460,10 @@ const makeWASocket = (config) => {
459
460
  if (!owner.adopt(created))
460
461
  return owner.settled();
461
462
  // Fallback for standalone helpers like `downloadContentFromMessage`
462
- // that carry no socket reference.
463
- _registerActiveBridgeClient(created, logger);
463
+ // that carry no socket reference. Registered wrapped so those helpers
464
+ // reject with a caller stack too; the memoized wrap keeps the
465
+ // unregister identity check working.
466
+ _registerActiveBridgeClient(wrapBridgeClient(created), logger);
464
467
  // Replay a preference set before the client existed. `setAutoReconnect`
465
468
  // forwards through `client?.`, so `makeWASocket(cfg).setAutoReconnect(false)`
466
469
  // — the idiomatic first line — used to move only the JS mirror and leave
@@ -757,7 +760,8 @@ const makeWASocket = (config) => {
757
760
  };
758
761
  },
759
762
  get waClient() {
760
- return owner.peek();
763
+ const live = owner.peek();
764
+ return live && wrapBridgeClient(live);
761
765
  },
762
766
  get isConnected() {
763
767
  return owner.peek()?.isConnected() ?? false;
@@ -1,7 +1,7 @@
1
1
  import { sendReportingUpstreamFailure } from '../Compatibility/all-encryptions-failed.js';
2
2
  import { sendDroppingDerivedNodes } from '../Compatibility/derived-stanza-nodes.js';
3
3
  import { encodeProtoCompat } from '../Compatibility/encode-proto.js';
4
- import { planMessageRelay } from '../Compatibility/message-relay.js';
4
+ import { EMPTY_RELAY_NODES, planMessageRelay, resolveMessageId } from '../Compatibility/message-relay.js';
5
5
  import { receiptMessageKeys } from '../Compatibility/message-keys.js';
6
6
  import { MESSAGE_RECEIPT_TYPES, WAProto } from '../Types/index.js';
7
7
  import { assertArgumentDomain } from '../Utils/argument-domain.js';
@@ -52,6 +52,12 @@ export const makeMessageMethods = (ctx) => ({
52
52
  if (contentType === 'protocolMessage') {
53
53
  const protoMsg = msg.protocolMessage;
54
54
  if (protoMsg?.type === WAProto.Message.ProtocolMessage.Type.REVOKE && protoMsg?.key?.id) {
55
+ if (options?.messageId) {
56
+ // The core has no revoke variant that takes a stanza id, so
57
+ // the option cannot be honoured here. Saying so beats the
58
+ // silent drop this option used to get on every path.
59
+ ctx.logger.warn({ jid, messageId: options.messageId }, 'messageId option cannot be honoured for a revoke: the core assigns the stanza id itself');
60
+ }
55
61
  // Group revokes need the original sender's participant JID —
56
62
  // without it the server rejects the revoke. The bridge
57
63
  // signature is `revokeMessage(jid, message_id, participant?)`.
@@ -65,15 +71,26 @@ export const makeMessageMethods = (ctx) => ({
65
71
  protoMsg?.key?.id &&
66
72
  protoMsg?.editedMessage) {
67
73
  const editBytes = WAProto.Message.encode(protoMsg.editedMessage).finish();
68
- const newMsgId = await client.editMessageBytes(jid, protoMsg.key.id, editBytes);
74
+ // Edit-path counterpart of `options.messageId` on a plain send, and
75
+ // deliberately not `resolveMessageId`: the engine treats any supplied
76
+ // stanza id as borrowed and binds no id-keyed state to it, so an edit
77
+ // nobody asked to pin would silently lose its retry-cache entry and
78
+ // outbound secret. Absent stays absent. `||` and not `??` because an
79
+ // empty id means unspecified everywhere else in this API.
80
+ const editStanzaId = options?.messageId || undefined;
81
+ const newMsgId = await client.editMessageBytes(jid, protoMsg.key.id, editBytes, editStanzaId);
69
82
  fullMsg.key.id = newMsgId || fullMsg.key.id;
70
83
  return fullMsg;
71
84
  }
72
85
  }
73
86
  const msgBytes = encodeProtoCompat('Message', msg);
87
+ // The with-options sends are the same core send as the plain ones, with
88
+ // the stanza id supplied by the caller instead of drawn by the engine.
89
+ // The id resolves through the same place `relayMessage` resolves it.
90
+ const messageId = resolveMessageId(ctx.getUser(), options?.messageId);
74
91
  const msgId = await sendReportingUpstreamFailure(() => jid === 'status@broadcast' && options?.statusJidList?.length
75
- ? client.sendStatusMessageBytes(msgBytes, options.statusJidList)
76
- : client.sendMessageBytes(jid, msgBytes));
92
+ ? client.sendStatusMessageBytesWithOptions(msgBytes, options.statusJidList, messageId, EMPTY_RELAY_NODES, false)
93
+ : client.relayMessageBytesWithOptions(jid, msgBytes, messageId, EMPTY_RELAY_NODES, false, false));
77
94
  fullMsg.key.id = msgId || fullMsg.key.id;
78
95
  // Local echo of the message we just sent. Suppressed when
79
96
  // `emitOwnEvents=false` so callers that explicitly opted out of seeing
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oxidezap/baileyrs",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
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.13.0",
85
+ "@oxidezap/whatsapp-rust-bridge": "0.15.0",
86
86
  "long": "^5.3.2",
87
87
  "pino": "^10.3.1",
88
88
  "protobufjs": "^7.6.5"