@oxidezap/baileyrs 0.2.13 → 0.3.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.
@@ -1,7 +1,7 @@
1
1
  import type { LIDMapping, SignalAuthState, SignalRepositoryWithLIDStore } from '../Types/index.js';
2
2
  import type { ILogger } from '../Utils/logger.js';
3
3
  import type { SocketContext } from '../Socket/types.js';
4
- type SignalRepositoryContext = Pick<SocketContext, 'getClient' | 'logger'>;
4
+ type SignalRepositoryContext = Pick<SocketContext, 'logger' | 'withClient'>;
5
5
  export declare const jidToSignalProtocolAddressCompat: (jid: string) => string;
6
6
  export declare const bindSignalRepositoryContext: (auth: SignalAuthState, ctx: SignalRepositoryContext) => void;
7
7
  /** Default public factory; the socket binds its native context before invoking it. */
@@ -34,12 +34,12 @@ export const bindSignalRepositoryContext = (auth, ctx) => {
34
34
  /** Default public factory; the socket binds its native context before invoking it. */
35
35
  export const makeDefaultSignalRepository = (auth, logger, _pnToLIDFunc) => makeSignalRepository({
36
36
  logger,
37
- getClient: () => {
37
+ withClient: operation => {
38
38
  const ctx = nativeContexts.get(auth);
39
39
  if (!ctx) {
40
40
  return Promise.reject(new Error('Signal repository is not bound to an active socket; pass it through SocketConfig'));
41
41
  }
42
- return ctx.getClient();
42
+ return ctx.withClient(operation);
43
43
  }
44
44
  });
45
45
  const resolveMappings = async (client, inputs, direction, ctx) => {
@@ -69,63 +69,67 @@ const resolveMappings = async (client, inputs, direction, ctx) => {
69
69
  return mappings.length ? mappings : null;
70
70
  };
71
71
  /** Translate the neutral bridge Signal surface into the public repository contract. */
72
- export const makeSignalRepository = (ctx) => ({
73
- decryptMessage: async (opts) => (await ctx.getClient()).signalDecryptMessage(opts.jid, opts.type, opts.ciphertext),
74
- encryptMessage: async (opts) => (await ctx.getClient()).signalEncryptMessage(opts.jid, opts.data),
75
- decryptGroupMessage: async (opts) => (await ctx.getClient()).signalDecryptGroupMessage(opts.group, opts.authorJid, opts.msg),
76
- encryptGroupMessage: async (opts) => (await ctx.getClient()).signalEncryptGroupMessage(opts.group, opts.data, opts.meId),
77
- processSenderKeyDistributionMessage: async ({ item, authorJid }) => {
78
- if (!item.groupId) {
79
- throw new Error('Group ID is required for sender key distribution message');
80
- }
81
- const distribution = item.axolotlSenderKeyDistributionMessage;
82
- if (!distribution) {
83
- throw new Error('Sender key distribution payload is required');
84
- }
85
- await (await ctx.getClient()).signalProcessSenderKeyDistribution(item.groupId, authorJid, distribution);
86
- },
87
- getSenderKeyDistributionMessage: async ({ group, meId }) => (await ctx.getClient()).signalGetSenderKeyDistribution(group, meId),
88
- hasSenderKey: async ({ group, meId }) => (await ctx.getClient()).signalHasSenderKey(group, meId),
89
- getSessionInfo: async (jid) => (await (await ctx.getClient()).signalGetSessionInfo(jid)) ?? null,
90
- injectE2ESession: async ({ jid, session }) => {
91
- await (await ctx.getClient()).signalInstallPreKeyBundle(jid, session);
92
- },
93
- validateSession: async (jid) => ({ exists: await (await ctx.getClient()).signalValidateSession(jid) }),
94
- jidToSignalProtocolAddress: jidToSignalProtocolAddressCompat,
95
- migrateSession: async (fromJid, toJid) => {
96
- if (!fromJid || !isLidNamespace(toJid))
97
- return { migrated: 0, skipped: 0, total: 0 };
98
- if (!isPnNamespace(fromJid))
99
- return { migrated: 0, skipped: 0, total: 1 };
100
- return (await ctx.getClient()).signalMigrateSessions(fromJid, toJid);
101
- },
102
- deleteSession: async (jids) => {
103
- if (jids.length)
104
- await (await ctx.getClient()).signalDeleteSessions(jids);
105
- },
106
- lidMapping: {
107
- storeLIDPNMappings: async (pairs) => {
108
- const valid = pairs.filter(({ lid, pn }) => {
109
- const accepted = isLidNamespace(lid) && isPnNamespace(pn);
110
- if (!accepted)
111
- ctx.logger.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
112
- return accepted;
113
- });
114
- if (valid.length)
115
- await (await ctx.getClient()).addLidPnMappings(valid);
72
+ export const makeSignalRepository = (ctx) => {
73
+ return {
74
+ decryptMessage: async (opts) => ctx.withClient(client => client.signalDecryptMessage(opts.jid, opts.type, opts.ciphertext)),
75
+ encryptMessage: async (opts) => ctx.withClient(client => client.signalEncryptMessage(opts.jid, opts.data)),
76
+ decryptGroupMessage: async (opts) => ctx.withClient(client => client.signalDecryptGroupMessage(opts.group, opts.authorJid, opts.msg)),
77
+ encryptGroupMessage: async (opts) => ctx.withClient(client => client.signalEncryptGroupMessage(opts.group, opts.data, opts.meId)),
78
+ processSenderKeyDistributionMessage: async ({ item, authorJid }) => {
79
+ if (!item.groupId) {
80
+ throw new Error('Group ID is required for sender key distribution message');
81
+ }
82
+ const distribution = item.axolotlSenderKeyDistributionMessage;
83
+ if (!distribution) {
84
+ throw new Error('Sender key distribution payload is required');
85
+ }
86
+ await ctx.withClient(client => client.signalProcessSenderKeyDistribution(item.groupId, authorJid, distribution));
87
+ },
88
+ getSenderKeyDistributionMessage: async ({ group, meId }) => ctx.withClient(client => client.signalGetSenderKeyDistribution(group, meId)),
89
+ hasSenderKey: async ({ group, meId }) => ctx.withClient(client => client.signalHasSenderKey(group, meId)),
90
+ getSessionInfo: async (jid) => (await ctx.withClient(client => client.signalGetSessionInfo(jid))) ?? null,
91
+ injectE2ESession: async ({ jid, session }) => {
92
+ await ctx.withClient(client => client.signalInstallPreKeyBundle(jid, session));
116
93
  },
117
- getLIDForPN: async (pn) => {
118
- const client = await ctx.getClient();
119
- return (await resolveMappings(client, [pn], 'pn-to-lid', ctx))?.[0]?.lid ?? null;
94
+ validateSession: async (jid) => ({ exists: await ctx.withClient(client => client.signalValidateSession(jid)) }),
95
+ jidToSignalProtocolAddress: jidToSignalProtocolAddressCompat,
96
+ migrateSession: async (fromJid, toJid) => {
97
+ if (!fromJid || !isLidNamespace(toJid))
98
+ return { migrated: 0, skipped: 0, total: 0 };
99
+ if (!isPnNamespace(fromJid))
100
+ return { migrated: 0, skipped: 0, total: 1 };
101
+ return ctx.withClient(client => client.signalMigrateSessions(fromJid, toJid));
120
102
  },
121
- getLIDsForPNs: async (pns) => resolveMappings(await ctx.getClient(), pns, 'pn-to-lid', ctx),
122
- getPNForLID: async (lid) => {
123
- const client = await ctx.getClient();
124
- return (await resolveMappings(client, [lid], 'lid-to-pn', ctx))?.[0]?.pn ?? null;
103
+ deleteSession: async (jids) => {
104
+ if (jids.length)
105
+ await ctx.withClient(client => client.signalDeleteSessions(jids));
106
+ },
107
+ lidMapping: {
108
+ storeLIDPNMappings: async (pairs) => {
109
+ const valid = pairs.filter(({ lid, pn }) => {
110
+ const accepted = isLidNamespace(lid) && isPnNamespace(pn);
111
+ if (!accepted)
112
+ ctx.logger.warn(`Invalid LID-PN mapping: ${lid}, ${pn}`);
113
+ return accepted;
114
+ });
115
+ if (valid.length)
116
+ await ctx.withClient(client => client.addLidPnMappings(valid));
117
+ },
118
+ getLIDForPN: async (pn) => {
119
+ return ctx.withClient(async (client) => {
120
+ return (await resolveMappings(client, [pn], 'pn-to-lid', ctx))?.[0]?.lid ?? null;
121
+ });
122
+ },
123
+ getLIDsForPNs: async (pns) => ctx.withClient(client => resolveMappings(client, pns, 'pn-to-lid', ctx)),
124
+ getPNForLID: async (lid) => {
125
+ return ctx.withClient(async (client) => {
126
+ return (await resolveMappings(client, [lid], 'lid-to-pn', ctx))?.[0]?.pn ?? null;
127
+ });
128
+ },
129
+ getPNsForLIDs: async (lids) => ctx.withClient(client => resolveMappings(client, lids, 'lid-to-pn', ctx)),
130
+ close: () => undefined
125
131
  },
126
- getPNsForLIDs: async (lids) => resolveMappings(await ctx.getClient(), lids, 'lid-to-pn', ctx),
127
132
  close: () => undefined
128
- },
129
- close: () => undefined
130
- });
133
+ };
134
+ };
131
135
  //# sourceMappingURL=signal-repository.js.map
@@ -1,11 +1,11 @@
1
1
  import type { WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
2
2
  import type { BinaryNode } from '../Types/index.js';
3
+ import type { ClientOperations } from '../Socket/client-operations.js';
3
4
  type StanzaResponseClient = Pick<WasmWhatsAppClient, 'acknowledgeStanza' | 'rejectStanza' | 'requestMessageRetry'>;
4
- export interface StanzaResponseContext {
5
- getClient: () => Promise<StanzaResponseClient>;
5
+ export interface StanzaResponseContext extends ClientOperations<StanzaResponseClient> {
6
6
  }
7
7
  /** Translate the public socket calls into the core-owned response operations. */
8
- export declare const makeStanzaResponseMethods: ({ getClient }: StanzaResponseContext) => {
8
+ export declare const makeStanzaResponseMethods: (ctx: StanzaResponseContext) => {
9
9
  sendMessageAck: (node: BinaryNode, errorCode?: number) => Promise<void>;
10
10
  sendRetryRequest: (node: BinaryNode, forceIncludeKeys?: boolean) => Promise<void>;
11
11
  };
@@ -1,16 +1,19 @@
1
1
  /** Translate the public socket calls into the core-owned response operations. */
2
- export const makeStanzaResponseMethods = ({ getClient }) => ({
3
- sendMessageAck: async (node, errorCode) => {
4
- const client = await getClient();
5
- if (errorCode) {
6
- await client.rejectStanza(node, errorCode);
2
+ export const makeStanzaResponseMethods = (ctx) => {
3
+ return {
4
+ sendMessageAck: async (node, errorCode) => {
5
+ return ctx.withClient(async (client) => {
6
+ if (errorCode) {
7
+ await client.rejectStanza(node, errorCode);
8
+ }
9
+ else {
10
+ await client.acknowledgeStanza(node);
11
+ }
12
+ });
13
+ },
14
+ sendRetryRequest: async (node, forceIncludeKeys = false) => {
15
+ await ctx.withClient(client => client.requestMessageRetry(node, forceIncludeKeys));
7
16
  }
8
- else {
9
- await client.acknowledgeStanza(node);
10
- }
11
- },
12
- sendRetryRequest: async (node, forceIncludeKeys = false) => {
13
- await (await getClient()).requestMessageRetry(node, forceIncludeKeys);
14
- }
15
- });
17
+ };
18
+ };
16
19
  //# sourceMappingURL=stanza-responses.js.map
@@ -1,13 +1,15 @@
1
1
  import { bridgeBlocklistToBaileys } from '../Compatibility/socket-results.js';
2
2
  import { assertArgumentDomain } from '../Utils/argument-domain.js';
3
3
  export const BLOCK_ACTIONS = ['block', 'unblock'];
4
- export const makeBlockingMethods = (ctx) => ({
5
- updateBlockStatus: async (jid, action) => {
6
- assertArgumentDomain('updateBlockStatus', 'action', action, BLOCK_ACTIONS);
7
- await (await ctx.getClient()).updateBlockStatus(jid, action);
8
- },
9
- fetchBlocklist: async () => {
10
- return bridgeBlocklistToBaileys(await (await ctx.getClient()).fetchBlocklist());
11
- }
12
- });
4
+ export const makeBlockingMethods = (ctx) => {
5
+ return {
6
+ updateBlockStatus: async (jid, action) => {
7
+ assertArgumentDomain('updateBlockStatus', 'action', action, BLOCK_ACTIONS);
8
+ await ctx.withClient(client => client.updateBlockStatus(jid, action));
9
+ },
10
+ fetchBlocklist: async () => {
11
+ return bridgeBlocklistToBaileys(await ctx.withClient(client => client.fetchBlocklist()));
12
+ }
13
+ };
14
+ };
13
15
  //# sourceMappingURL=blocking.js.map
@@ -21,8 +21,8 @@
21
21
  * - Anything that is not the bridge's shape (an `Error` carrying a string
22
22
  * `kind`) passes through with the same identity it arrived with.
23
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
24
+ * The private `getClient()` getter returns a cached `Proxy` for each client.
25
+ * Its method wrappers are built on first access and cached, so
26
26
  * the happy path pays one property trap and one extra promise layer, and the
27
27
  * error path pays for everything else.
28
28
  */
@@ -21,8 +21,8 @@
21
21
  * - Anything that is not the bridge's shape (an `Error` carrying a string
22
22
  * `kind`) passes through with the same identity it arrived with.
23
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
24
+ * The private `getClient()` getter returns a cached `Proxy` for each client.
25
+ * Its method wrappers are built on first access and cached, so
26
26
  * the happy path pays one property trap and one extra promise layer, and the
27
27
  * error path pays for everything else.
28
28
  */
@@ -39,66 +39,68 @@ const catalogSubject = (method, ctx, jid) => {
39
39
  }
40
40
  return subject;
41
41
  };
42
- export const makeBusinessMethods = (ctx) => ({
43
- getCatalog: async ({ jid, limit, cursor }) => {
44
- const subject = catalogSubject('getCatalog', ctx, jid);
45
- return await (await ctx.getClient()).getCatalog(subject, { limit, after: cursor });
46
- },
47
- getCollections: async (jid, limit) => {
48
- const subject = catalogSubject('getCollections', ctx, jid);
49
- return await (await ctx.getClient()).getCollections(subject, { collectionLimit: limit });
50
- },
51
- /**
52
- * `sellerJid` is not in upstream's signature because upstream reads orders
53
- * over the legacy `fb:thrift_iq` route, which addresses the server. The
54
- * route the real client uses is a MEX query keyed by the seller, so the JID
55
- * is required here. It is on the order message the token came from.
56
- */
57
- getOrderDetails: async (orderId, tokenBase64, sellerJid) => {
58
- if (!sellerJid) {
59
- throw new Boom('getOrderDetails: a third argument with the seller jid is required, because orders are read through a query keyed by the business rather than the legacy server-addressed one', { statusCode: 400 });
60
- }
61
- return await (await ctx.getClient()).getOrder(sellerJid, orderId, tokenBase64);
62
- },
63
- updateBussinesProfile: async (args) => {
64
- const update = {
65
- address: args.address,
66
- description: args.description,
67
- email: args.email,
68
- websites: args.websites,
69
- ...(args.hours !== undefined
70
- ? {
71
- businessHours: {
72
- timezone: args.hours.timezone,
73
- // Minutes past midnight as a number; upstream types the two
74
- // as strings and the core rejects them on the other modes.
75
- config: args.hours.days.map(day => ({
76
- dayOfWeek: day.day,
77
- mode: day.mode,
78
- openTime: day.mode === 'specific_hours' ? minutesPastMidnight(day.openTimeInMinutes, 'open') : undefined,
79
- closeTime: day.mode === 'specific_hours' ? minutesPastMidnight(day.closeTimeInMinutes, 'close') : undefined
80
- }))
42
+ export const makeBusinessMethods = (ctx) => {
43
+ return {
44
+ getCatalog: async ({ jid, limit, cursor }) => {
45
+ const subject = catalogSubject('getCatalog', ctx, jid);
46
+ return await ctx.withClient(client => client.getCatalog(subject, { limit, after: cursor }));
47
+ },
48
+ getCollections: async (jid, limit) => {
49
+ const subject = catalogSubject('getCollections', ctx, jid);
50
+ return await ctx.withClient(client => client.getCollections(subject, { collectionLimit: limit }));
51
+ },
52
+ /**
53
+ * `sellerJid` is not in upstream's signature because upstream reads orders
54
+ * over the legacy `fb:thrift_iq` route, which addresses the server. The
55
+ * route the real client uses is a MEX query keyed by the seller, so the JID
56
+ * is required here. It is on the order message the token came from.
57
+ */
58
+ getOrderDetails: async (orderId, tokenBase64, sellerJid) => {
59
+ if (!sellerJid) {
60
+ throw new Boom('getOrderDetails: a third argument with the seller jid is required, because orders are read through a query keyed by the business rather than the legacy server-addressed one', { statusCode: 400 });
61
+ }
62
+ return await ctx.withClient(client => client.getOrder(sellerJid, orderId, tokenBase64));
63
+ },
64
+ updateBussinesProfile: async (args) => {
65
+ const update = {
66
+ address: args.address,
67
+ description: args.description,
68
+ email: args.email,
69
+ websites: args.websites,
70
+ ...(args.hours !== undefined
71
+ ? {
72
+ businessHours: {
73
+ timezone: args.hours.timezone,
74
+ // Minutes past midnight as a number; upstream types the two
75
+ // as strings and the core rejects them on the other modes.
76
+ config: args.hours.days.map(day => ({
77
+ dayOfWeek: day.day,
78
+ mode: day.mode,
79
+ openTime: day.mode === 'specific_hours' ? minutesPastMidnight(day.openTimeInMinutes, 'open') : undefined,
80
+ closeTime: day.mode === 'specific_hours' ? minutesPastMidnight(day.closeTimeInMinutes, 'close') : undefined
81
+ }))
82
+ }
81
83
  }
82
- }
83
- : {})
84
- };
85
- await (await ctx.getClient()).updateBusinessProfile(update);
86
- },
87
- /**
88
- * Declared but not callable end to end. The core and the bridge take the
89
- * `{fbid, meta_hmac, ts}` receipt of a cover photo upload, and this
90
- * package's upload path cannot produce one: it requires a url and a
91
- * direct path, which that endpoint does not return.
92
- */
93
- updateCoverPhoto: async (photo) => {
94
- void photo;
95
- throw new Boom('updateCoverPhoto is not available yet: the cover photo upload returns an {fbid, meta_hmac, ts} receipt that this package cannot obtain. removeCoverPhoto works.', { statusCode: 501 });
96
- },
97
- removeCoverPhoto: async (id) => {
98
- await (await ctx.getClient()).removeBusinessCoverPhoto(id);
99
- },
100
- productCreate: async (create) => noProductWriteRoute('productCreate', create),
101
- productUpdate: async (productId, update) => noProductWriteRoute('productUpdate', productId, update),
102
- productDelete: async (productIds) => noProductWriteRoute('productDelete', productIds)
103
- });
84
+ : {})
85
+ };
86
+ await ctx.withClient(client => client.updateBusinessProfile(update));
87
+ },
88
+ /**
89
+ * Declared but not callable end to end. The core and the bridge take the
90
+ * `{fbid, meta_hmac, ts}` receipt of a cover photo upload, and this
91
+ * package's upload path cannot produce one: it requires a url and a
92
+ * direct path, which that endpoint does not return.
93
+ */
94
+ updateCoverPhoto: async (photo) => {
95
+ void photo;
96
+ throw new Boom('updateCoverPhoto is not available yet: the cover photo upload returns an {fbid, meta_hmac, ts} receipt that this package cannot obtain. removeCoverPhoto works.', { statusCode: 501 });
97
+ },
98
+ removeCoverPhoto: async (id) => {
99
+ await ctx.withClient(client => client.removeBusinessCoverPhoto(id));
100
+ },
101
+ productCreate: async (create) => noProductWriteRoute('productCreate', create),
102
+ productUpdate: async (productId, update) => noProductWriteRoute('productUpdate', productId, update),
103
+ productDelete: async (productIds) => noProductWriteRoute('productDelete', productIds)
104
+ };
105
+ };
104
106
  //# sourceMappingURL=business.js.map
@@ -16,16 +16,16 @@ const rejectUnsupportedFields = (where, value, fields) => {
16
16
  export const makeChatActionMethods = (ctx) => {
17
17
  const methods = {
18
18
  pinChat: async (jid, pin) => {
19
- await (await ctx.getClient()).pinChat(jid, pin);
19
+ await ctx.withClient(client => client.pinChat(jid, pin));
20
20
  },
21
21
  muteChat: async (jid, muteUntil) => {
22
- await (await ctx.getClient()).muteChat(jid, muteUntil);
22
+ await ctx.withClient(client => client.muteChat(jid, muteUntil));
23
23
  },
24
24
  archiveChat: async (jid, archive) => {
25
- await (await ctx.getClient()).archiveChat(jid, archive);
25
+ await ctx.withClient(client => client.archiveChat(jid, archive));
26
26
  },
27
27
  starMessage: async (jid, messageId, star) => {
28
- await (await ctx.getClient()).starMessage(jid, messageId, star);
28
+ await ctx.withClient(client => client.starMessage(jid, messageId, star));
29
29
  },
30
30
  /**
31
31
  * Compatibility wrapper for original Baileys chatModify API.
@@ -36,106 +36,110 @@ export const makeChatActionMethods = (ctx) => {
36
36
  * synced, and no signature or type catches that.
37
37
  */
38
38
  chatModify: async (mod, jid) => {
39
- const client = await ctx.getClient();
40
- if ('archive' in mod) {
41
- await client.archiveChat(jid, mod.archive);
42
- }
43
- else if ('pin' in mod) {
44
- await client.pinChat(jid, mod.pin);
45
- }
46
- else if ('mute' in mod) {
47
- await client.muteChat(jid, mod.mute);
48
- }
49
- else if ('star' in mod) {
50
- for (const msg of mod.star.messages) {
51
- await client.starMessage(jid, msg.id, mod.star.star);
52
- }
53
- }
54
- else if ('markRead' in mod) {
55
- await client.markChatAsRead(jid, mod.markRead);
56
- }
57
- else if ('delete' in mod) {
58
- await client.deleteChat(jid);
59
- }
60
- else if ('deleteForMe' in mod) {
61
- await client.deleteMessageForMe(jid, mod.deleteForMe.key.id, !!mod.deleteForMe.key.fromMe);
62
- }
63
- else if ('pushNameSetting' in mod) {
64
- await client.setPushName(mod.pushNameSetting);
65
- }
66
- else if ('contact' in mod) {
67
- // Save/rename a contact (syncs the name to linked devices). `jid` is the
68
- // contact's bare PN jid.
69
- if (mod.contact) {
70
- rejectUnsupportedFields('chatModify contact', mod.contact, ['lidJid', 'pnJid', 'username']);
71
- await client.saveContact(jid, mod.contact.fullName ?? undefined, mod.contact.firstName ?? undefined, mod.contact.saveOnPrimaryAddressbook ?? true);
39
+ return ctx.withClient(async (client) => {
40
+ if ('archive' in mod) {
41
+ await client.archiveChat(jid, mod.archive);
72
42
  }
73
- else {
74
- // Its own method, not `saveContact` with empty fields: removal is
75
- // the one contact mutation the wire models as a `Remove`, and a
76
- // `Set` carrying empty values renames the contact to "".
77
- await client.removeContact(jid);
78
- }
79
- }
80
- else if ('clear' in mod) {
81
- // Clear a chat's messages while keeping the chat. `lastMessages` (the
82
- // message range) is ignored, same as the `delete` branch — the bridge
83
- // clears the whole chat. deleteStarred/deleteMedia aren't part of the
84
- // Baileys `clear` shape, so default both to false (keep starred + media).
85
- if (mod.clear) {
86
- await client.clearChat(jid, false, false);
87
- }
88
- }
89
- else if ('disableLinkPreviews' in mod) {
90
- await client.setLinkPreviewsDisabled(mod.disableLinkPreviews.isPreviewsDisabled ?? false);
91
- }
92
- else if ('addLabel' in mod) {
93
- // One upstream variant, two wire actions: an edit carrying
94
- // `deleted` is the delete, not a separate modification.
95
- if (mod.addLabel.deleted) {
96
- await client.deleteLabel(mod.addLabel.id);
43
+ else if ('pin' in mod) {
44
+ await client.pinChat(jid, mod.pin);
97
45
  }
98
- else {
99
- rejectUnsupportedFields('chatModify addLabel', mod.addLabel, ['predefinedId']);
100
- // The mutation replaces the whole label, so a missing field is
101
- // not "leave it alone", it is "set it to nothing". Upstream can
102
- // omit one because it builds the proto directly; this call
103
- // cannot, so both are required rather than defaulted.
104
- if (mod.addLabel.name === undefined || mod.addLabel.color === undefined) {
105
- throw new Boom('chatModify addLabel: name and color are both required, because the label mutation is a full replace and an omitted field would reset it', { statusCode: 400 });
46
+ else if ('mute' in mod) {
47
+ await client.muteChat(jid, mod.mute);
48
+ }
49
+ else if ('star' in mod) {
50
+ for (const msg of mod.star.messages) {
51
+ await client.starMessage(jid, msg.id, mod.star.star);
52
+ }
53
+ }
54
+ else if ('markRead' in mod) {
55
+ await client.markChatAsRead(jid, mod.markRead);
56
+ }
57
+ else if ('delete' in mod) {
58
+ await client.deleteChat(jid);
59
+ }
60
+ else if ('deleteForMe' in mod) {
61
+ await client.deleteMessageForMe(jid, mod.deleteForMe.key.id, !!mod.deleteForMe.key.fromMe);
62
+ }
63
+ else if ('pushNameSetting' in mod) {
64
+ await client.setPushName(mod.pushNameSetting);
65
+ }
66
+ else if ('contact' in mod) {
67
+ // Save/rename a contact (syncs the name to linked devices). `jid` is the
68
+ // contact's bare PN jid.
69
+ if (mod.contact) {
70
+ rejectUnsupportedFields('chatModify contact', mod.contact, ['lidJid', 'pnJid', 'username']);
71
+ await client.saveContact(jid, mod.contact.fullName ?? undefined, mod.contact.firstName ?? undefined, mod.contact.saveOnPrimaryAddressbook ?? true);
72
+ }
73
+ else {
74
+ // Its own method, not `saveContact` with empty fields: removal is
75
+ // the one contact mutation the wire models as a `Remove`, and a
76
+ // `Set` carrying empty values renames the contact to "".
77
+ await client.removeContact(jid);
78
+ }
79
+ }
80
+ else if ('clear' in mod) {
81
+ // Clear a chat's messages while keeping the chat. `lastMessages` (the
82
+ // message range) is ignored, same as the `delete` branch — the bridge
83
+ // clears the whole chat. deleteStarred/deleteMedia aren't part of the
84
+ // Baileys `clear` shape, so default both to false (keep starred + media).
85
+ if (mod.clear) {
86
+ await client.clearChat(jid, false, false);
87
+ }
88
+ }
89
+ else if ('disableLinkPreviews' in mod) {
90
+ await client.setLinkPreviewsDisabled(mod.disableLinkPreviews.isPreviewsDisabled ?? false);
91
+ }
92
+ else if ('addLabel' in mod) {
93
+ // One upstream variant, two wire actions: an edit carrying
94
+ // `deleted` is the delete, not a separate modification.
95
+ if (mod.addLabel.deleted) {
96
+ await client.deleteLabel(mod.addLabel.id);
97
+ }
98
+ else {
99
+ rejectUnsupportedFields('chatModify addLabel', mod.addLabel, ['predefinedId']);
100
+ // The mutation replaces the whole label, so a missing field is
101
+ // not "leave it alone", it is "set it to nothing". Upstream can
102
+ // omit one because it builds the proto directly; this call
103
+ // cannot, so both are required rather than defaulted.
104
+ if (mod.addLabel.name === undefined || mod.addLabel.color === undefined) {
105
+ throw new Boom('chatModify addLabel: name and color are both required, because the label mutation is a full replace and an omitted field would reset it', { statusCode: 400 });
106
+ }
107
+ await client.createLabel(mod.addLabel.id, mod.addLabel.name, mod.addLabel.color);
108
+ }
109
+ }
110
+ else if ('addChatLabel' in mod) {
111
+ await client.addChatLabel(mod.addChatLabel.labelId, jid);
112
+ }
113
+ else if ('removeChatLabel' in mod) {
114
+ await client.removeChatLabel(mod.removeChatLabel.labelId, jid);
115
+ }
116
+ else if ('addMessageLabel' in mod) {
117
+ await client.addMessageLabel(mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId);
118
+ }
119
+ else if ('removeMessageLabel' in mod) {
120
+ await client.removeMessageLabel(mod.removeMessageLabel.labelId, jid, mod.removeMessageLabel.messageId);
121
+ }
122
+ else if ('quickReply' in mod) {
123
+ // Upstream's `timestamp` is the app-state index key, the same slot
124
+ // the core calls `id`. Deleting is the same upsert with `deleted`.
125
+ // `||`, not `??`: upstream treats an empty timestamp as absent and
126
+ // mints a key, and the core rejects an empty index outright.
127
+ const id = mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000));
128
+ if (mod.quickReply.deleted) {
129
+ await client.deleteQuickReply(id);
130
+ }
131
+ else {
132
+ await client.setQuickReply(id, mod.quickReply.shortcut ?? '', mod.quickReply.message ?? '', mod.quickReply.keywords ?? [], mod.quickReply.count ?? 0);
106
133
  }
107
- await client.createLabel(mod.addLabel.id, mod.addLabel.name, mod.addLabel.color);
108
- }
109
- }
110
- else if ('addChatLabel' in mod) {
111
- await client.addChatLabel(mod.addChatLabel.labelId, jid);
112
- }
113
- else if ('removeChatLabel' in mod) {
114
- await client.removeChatLabel(mod.removeChatLabel.labelId, jid);
115
- }
116
- else if ('addMessageLabel' in mod) {
117
- await client.addMessageLabel(mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId);
118
- }
119
- else if ('removeMessageLabel' in mod) {
120
- await client.removeMessageLabel(mod.removeMessageLabel.labelId, jid, mod.removeMessageLabel.messageId);
121
- }
122
- else if ('quickReply' in mod) {
123
- // Upstream's `timestamp` is the app-state index key, the same slot
124
- // the core calls `id`. Deleting is the same upsert with `deleted`.
125
- // `||`, not `??`: upstream treats an empty timestamp as absent and
126
- // mints a key, and the core rejects an empty index outright.
127
- const id = mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000));
128
- if (mod.quickReply.deleted) {
129
- await client.deleteQuickReply(id);
130
134
  }
131
135
  else {
132
- await client.setQuickReply(id, mod.quickReply.shortcut ?? '', mod.quickReply.message ?? '', mod.quickReply.keywords ?? [], mod.quickReply.count ?? 0);
136
+ const variant = Object.keys(mod)[0] ?? '(empty)';
137
+ throw new Boom(`chatModify: unsupported modification '${variant}'`, {
138
+ statusCode: 400,
139
+ data: { variant, jid }
140
+ });
133
141
  }
134
- }
135
- else {
136
- const variant = Object.keys(mod)[0] ?? '(empty)';
137
- throw new Boom(`chatModify: unsupported modification '${variant}'`, { statusCode: 400, data: { variant, jid } });
138
- }
142
+ });
139
143
  },
140
144
  // Upstream models all of these as sugar over `chatModify`, and so do we:
141
145
  // one place decides which bridge call a modification becomes, so the
@@ -0,0 +1,6 @@
1
+ import type { WasmWhatsAppClient } from '@oxidezap/whatsapp-rust-bridge';
2
+ export interface ClientOperations<Client = WasmWhatsAppClient> {
3
+ withClient<T>(operation: (client: Client) => T | Promise<T>): Promise<T>;
4
+ }
5
+ export declare const makeWithClient: <Client>(getClient: () => Promise<Client>) => ClientOperations<Client>['withClient'];
6
+ //# sourceMappingURL=client-operations.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const makeWithClient = (getClient) => async (operation) => operation(await getClient());
2
+ //# sourceMappingURL=client-operations.js.map