@oxidezap/baileyrs 0.1.1 → 0.1.3

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.
@@ -48,16 +48,21 @@ function legacySignalAddress(address) {
48
48
  : `${parsed.user}${SignalAddressSyntax.DOMAIN_TYPE}${parsed.domainType}`;
49
49
  return `${user}${SignalAddressSyntax.SIGNAL_DEVICE}${parsed.device}`;
50
50
  }
51
+ /** A sender key is `<chatJid>:<signalAddress>`, and the chat is a group, status
52
+ * or a broadcast list. Which chats fan out through sender keys is the core's
53
+ * namespace, so the boundary validates the JID shape, not the domain. No JID
54
+ * carries whitespace or a control character, and letting one through would
55
+ * mint a storage key that no later lookup can match. */
56
+ const CHAT_JID = /^[^\s:@\p{Cc}]+@[^\s:@\p{Cc}]+$/u;
51
57
  function legacySenderKey(key) {
52
- const groupTerminator = `${SignalAddressSyntax.DOMAIN}${SignalDomain.GROUP}${SignalAddressSyntax.JID_DEVICE}`;
53
- const groupEnd = key.indexOf(groupTerminator);
54
- if (groupEnd < 0)
58
+ const chatDomain = key.indexOf(SignalAddressSyntax.DOMAIN);
59
+ const chatEnd = chatDomain < 0 ? -1 : key.indexOf(SignalAddressSyntax.JID_DEVICE, chatDomain);
60
+ const chat = chatEnd < 0 ? '' : key.slice(0, chatEnd);
61
+ if (!CHAT_JID.test(chat))
55
62
  throw new TypeError(`invalid native sender-key address: ${key}`);
56
- const addressStart = groupEnd + groupTerminator.length;
57
- const group = key.slice(0, addressStart - SignalAddressSyntax.JID_DEVICE.length);
58
- const address = legacySignalAddress(key.slice(addressStart));
63
+ const address = legacySignalAddress(key.slice(chatEnd + SignalAddressSyntax.JID_DEVICE.length));
59
64
  const deviceSeparator = address.lastIndexOf(SignalAddressSyntax.SIGNAL_DEVICE);
60
- return [group, address.slice(0, deviceSeparator), address.slice(deviceSeparator + 1)].join(SignalAddressSyntax.SENDER_KEY_PART);
65
+ return [chat, address.slice(0, deviceSeparator), address.slice(deviceSeparator + 1)].join(SignalAddressSyntax.SENDER_KEY_PART);
61
66
  }
62
67
  const passthroughKey = (_store, key) => key;
63
68
  const keyTranslators = Object.freeze({
@@ -114,7 +119,7 @@ function nativeSignalAddress(address) {
114
119
  }
115
120
  function nativeSenderKey(key) {
116
121
  const parts = key.split(SignalAddressSyntax.SENDER_KEY_PART);
117
- if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
122
+ if (parts.length !== 3 || !CHAT_JID.test(parts[0]) || !parts[1] || !parts[2]) {
118
123
  throw new TypeError(`invalid legacy sender-key address: ${key}`);
119
124
  }
120
125
  return `${parts[0]}${SignalAddressSyntax.JID_DEVICE}${nativeSignalAddress(`${parts[1]}.${parts[2]}`)}`;
@@ -62,6 +62,12 @@ export declare const MEDIA_HKDF_KEY_MAPPING: {
62
62
  };
63
63
  export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING;
64
64
  export declare const MEDIA_KEYS: MediaType[];
65
+ /**
66
+ * The type's own members, for checking one that arrives untyped. Readonly
67
+ * because it is public and the upload guard reads it: editing it would change
68
+ * what every caller is allowed to send.
69
+ */
70
+ export declare const MEDIA_TYPES: readonly MediaType[];
65
71
  /** 120s timeout for history sync stall detection, matching upstream Baileys. */
66
72
  export declare const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120000;
67
73
  export declare const MIN_PREKEY_COUNT = 5;
@@ -110,6 +110,12 @@ export const MEDIA_HKDF_KEY_MAPPING = {
110
110
  'biz-cover-photo': 'Image'
111
111
  };
112
112
  export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
113
+ /**
114
+ * The type's own members, for checking one that arrives untyped. Readonly
115
+ * because it is public and the upload guard reads it: editing it would change
116
+ * what every caller is allowed to send.
117
+ */
118
+ export const MEDIA_TYPES = Object.keys(MEDIA_HKDF_KEY_MAPPING);
113
119
  /** 120s timeout for history sync stall detection, matching upstream Baileys. */
114
120
  export const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120000;
115
121
  export const MIN_PREKEY_COUNT = 5;
@@ -1,6 +1,8 @@
1
1
  import type { SocketContext } from './types.js';
2
+ export declare const BLOCK_ACTIONS: readonly ['block', 'unblock'];
3
+ export type BlockAction = (typeof BLOCK_ACTIONS)[number];
2
4
  export declare const makeBlockingMethods: (ctx: SocketContext) => {
3
- updateBlockStatus: (jid: string, action: 'block' | 'unblock') => Promise<void>;
5
+ updateBlockStatus: (jid: string, action: BlockAction) => Promise<void>;
4
6
  fetchBlocklist: () => Promise<Array<string | undefined>>;
5
7
  };
6
8
  //# sourceMappingURL=blocking.d.ts.map
@@ -1,6 +1,9 @@
1
1
  import { bridgeBlocklistToBaileys } from '../Compatibility/socket-results.js';
2
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
3
+ export const BLOCK_ACTIONS = ['block', 'unblock'];
2
4
  export const makeBlockingMethods = (ctx) => ({
3
5
  updateBlockStatus: async (jid, action) => {
6
+ assertArgumentDomain('updateBlockStatus', 'action', action, BLOCK_ACTIONS);
4
7
  await (await ctx.getClient()).updateBlockStatus(jid, action);
5
8
  },
6
9
  fetchBlocklist: async () => {
@@ -1,4 +1,5 @@
1
- import type { GroupMetadata, ParticipantAction } from '../Types/index.js';
1
+ import { type GroupMetadata, type ParticipantAction } from '../Types/index.js';
2
+ import { type GroupRequestAction, type GroupSetting, type JoinApprovalMode, type MemberAddMode } from './groups.js';
2
3
  import type { SocketContext } from './types.js';
3
4
  type LinkedGroup = {
4
5
  id: string | undefined;
@@ -13,7 +14,7 @@ export declare const makeCommunityMethods: (ctx: SocketContext, groups?: {
13
14
  groupLeave: (id: string) => Promise<void>;
14
15
  groupUpdateSubject: (jid: string, subject: string) => Promise<void>;
15
16
  groupRequestParticipantsList: (jid: string) => Promise<Array<Record<string, string>>>;
16
- groupRequestParticipantsUpdate: (jid: string, participants: string[], action: 'approve' | 'reject') => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
17
+ groupRequestParticipantsUpdate: (jid: string, participants: string[], action: GroupRequestAction) => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
17
18
  groupParticipantsUpdate: (jid: string, participants: string[], action: ParticipantAction) => Promise<import("../Compatibility/socket-results.js").ParticipantUpdateResult[]>;
18
19
  groupUpdateDescription: (jid: string, description?: string) => Promise<void>;
19
20
  groupInviteCode: (jid: string) => Promise<string | undefined>;
@@ -23,9 +24,9 @@ export declare const makeCommunityMethods: (ctx: SocketContext, groups?: {
23
24
  groupAcceptInviteV4: (key: string | import("../index.js").WAMessageKey, inviteMessage: import("@oxidezap/whatsapp-rust-bridge/proto-types").proto.Message.IGroupInviteMessage) => Promise<any>;
24
25
  groupGetInviteInfo: (code: string) => Promise<GroupMetadata>;
25
26
  groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>;
26
- groupSettingUpdate: (jid: string, setting: "announcement" | "locked" | "not_announcement" | "unlocked") => Promise<void>;
27
- groupMemberAddMode: (jid: string, mode: 'admin_add' | 'all_member_add') => Promise<void>;
28
- groupJoinApprovalMode: (jid: string, mode: 'on' | 'off') => Promise<void>;
27
+ groupSettingUpdate: (jid: string, setting: GroupSetting) => Promise<void>;
28
+ groupMemberAddMode: (jid: string, mode: MemberAddMode) => Promise<void>;
29
+ groupJoinApprovalMode: (jid: string, mode: JoinApprovalMode) => Promise<void>;
29
30
  groupFetchAllParticipating: () => Promise<Record<string, GroupMetadata>>;
30
31
  updateMemberLabel: (jid: string, memberLabel: string) => Promise<string>;
31
32
  }) => {
@@ -42,7 +43,11 @@ export declare const makeCommunityMethods: (ctx: SocketContext, groups?: {
42
43
  linkedGroups: LinkedGroup[];
43
44
  }>;
44
45
  communityRequestParticipantsList: (jid: string) => Promise<Array<Record<string, string>>>;
45
- communityRequestParticipantsUpdate: (jid: string, participants: string[], action: 'approve' | 'reject') => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
46
+ /**
47
+ * The community methods that are the group operation check under their own
48
+ * name before delegating: a refusal has to name the method that was called.
49
+ */
50
+ communityRequestParticipantsUpdate: (jid: string, participants: string[], action: GroupRequestAction) => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
46
51
  communityParticipantsUpdate: (jid: string, participants: string[], action: ParticipantAction) => Promise<import("../Compatibility/socket-results.js").ParticipantUpdateResult[]>;
47
52
  communityUpdateDescription: (jid: string, description?: string) => Promise<void>;
48
53
  communityInviteCode: (jid: string) => Promise<string | undefined>;
@@ -52,9 +57,9 @@ export declare const makeCommunityMethods: (ctx: SocketContext, groups?: {
52
57
  communityAcceptInviteV4: (key: string | import("../index.js").WAMessageKey, inviteMessage: import("@oxidezap/whatsapp-rust-bridge/proto-types").proto.Message.IGroupInviteMessage) => Promise<any>;
53
58
  communityGetInviteInfo: (code: string) => Promise<GroupMetadata>;
54
59
  communityToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>;
55
- communitySettingUpdate: (jid: string, setting: "announcement" | "locked" | "not_announcement" | "unlocked") => Promise<void>;
56
- communityMemberAddMode: (jid: string, mode: 'admin_add' | 'all_member_add') => Promise<void>;
57
- communityJoinApprovalMode: (jid: string, mode: 'on' | 'off') => Promise<void>;
60
+ communitySettingUpdate: (jid: string, setting: GroupSetting) => Promise<void>;
61
+ communityMemberAddMode: (jid: string, mode: MemberAddMode) => Promise<void>;
62
+ communityJoinApprovalMode: (jid: string, mode: JoinApprovalMode) => Promise<void>;
58
63
  communityFetchAllParticipating: () => Promise<Record<string, GroupMetadata>>;
59
64
  };
60
65
  export {};
@@ -1,6 +1,8 @@
1
1
  import { bridgeGroupMetadataToBaileys } from '../Compatibility/group-metadata.js';
2
2
  import { bridgeParticipantChangesToBaileys } from '../Compatibility/socket-results.js';
3
- import { makeGroupMethods } from './groups.js';
3
+ import { PARTICIPANT_ACTIONS } from '../Types/index.js';
4
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
5
+ import { GROUP_REQUEST_ACTIONS, GROUP_SETTINGS, JOIN_APPROVAL_MODES, makeGroupMethods, MEMBER_ADD_MODES } from './groups.js';
4
6
  export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
5
7
  const communityMetadata = async (jid) => groups.groupMetadata(jid);
6
8
  const communityFetchAllParticipating = async () => {
@@ -49,8 +51,16 @@ export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
49
51
  return { communityJid, isCommunity, linkedGroups };
50
52
  },
51
53
  communityRequestParticipantsList: groups.groupRequestParticipantsList,
52
- communityRequestParticipantsUpdate: groups.groupRequestParticipantsUpdate,
54
+ /**
55
+ * The community methods that are the group operation check under their own
56
+ * name before delegating: a refusal has to name the method that was called.
57
+ */
58
+ communityRequestParticipantsUpdate: async (jid, participants, action) => {
59
+ assertArgumentDomain('communityRequestParticipantsUpdate', 'action', action, GROUP_REQUEST_ACTIONS);
60
+ return groups.groupRequestParticipantsUpdate(jid, participants, action);
61
+ },
53
62
  communityParticipantsUpdate: async (jid, participants, action) => {
63
+ assertArgumentDomain('communityParticipantsUpdate', 'action', action, PARTICIPANT_ACTIONS);
54
64
  return bridgeParticipantChangesToBaileys(await (await ctx.getClient()).communityParticipantsUpdate(jid, participants, action));
55
65
  },
56
66
  communityUpdateDescription: groups.groupUpdateDescription,
@@ -63,9 +73,18 @@ export const makeCommunityMethods = (ctx, groups = makeGroupMethods(ctx)) => {
63
73
  communityAcceptInviteV4: groups.groupAcceptInviteV4,
64
74
  communityGetInviteInfo: groups.groupGetInviteInfo,
65
75
  communityToggleEphemeral: groups.groupToggleEphemeral,
66
- communitySettingUpdate: groups.groupSettingUpdate,
67
- communityMemberAddMode: groups.groupMemberAddMode,
68
- communityJoinApprovalMode: groups.groupJoinApprovalMode,
76
+ communitySettingUpdate: async (jid, setting) => {
77
+ assertArgumentDomain('communitySettingUpdate', 'setting', setting, GROUP_SETTINGS);
78
+ return groups.groupSettingUpdate(jid, setting);
79
+ },
80
+ communityMemberAddMode: async (jid, mode) => {
81
+ assertArgumentDomain('communityMemberAddMode', 'mode', mode, MEMBER_ADD_MODES);
82
+ return groups.groupMemberAddMode(jid, mode);
83
+ },
84
+ communityJoinApprovalMode: async (jid, mode) => {
85
+ assertArgumentDomain('communityJoinApprovalMode', 'mode', mode, JOIN_APPROVAL_MODES);
86
+ return groups.groupJoinApprovalMode(jid, mode);
87
+ },
69
88
  communityFetchAllParticipating
70
89
  };
71
90
  };
@@ -1,4 +1,6 @@
1
1
  import type { SocketContext } from './types.js';
2
+ export declare const PROFILE_PICTURE_TYPES: readonly ['preview', 'image'];
3
+ export type ProfilePictureType = (typeof PROFILE_PICTURE_TYPES)[number];
2
4
  export type OnWhatsAppResult = {
3
5
  exists: boolean;
4
6
  jid: string;
@@ -11,7 +13,7 @@ export type OnWhatsAppResult = {
11
13
  };
12
14
  export declare const makeContactMethods: (ctx: SocketContext) => {
13
15
  onWhatsApp: (...phoneNumber: string[]) => Promise<OnWhatsAppResult[] | undefined>;
14
- profilePictureUrl: (jid: string, type?: 'preview' | 'image', timeoutMs?: number) => Promise<string | undefined>;
16
+ profilePictureUrl: (jid: string, type?: ProfilePictureType, timeoutMs?: number) => Promise<string | undefined>;
15
17
  fetchUserInfo: (...jids: string[]) => Promise<Record<string, import("@oxidezap/whatsapp-rust-bridge").UserInfoResult>>;
16
18
  };
17
19
  //# sourceMappingURL=contacts.d.ts.map
@@ -1,3 +1,5 @@
1
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
2
+ export const PROFILE_PICTURE_TYPES = ['preview', 'image'];
1
3
  export const makeContactMethods = (ctx) => ({
2
4
  onWhatsApp: async (...phoneNumber) => {
3
5
  const client = await ctx.getClient();
@@ -16,6 +18,7 @@ export const makeContactMethods = (ctx) => ({
16
18
  });
17
19
  },
18
20
  profilePictureUrl: async (jid, type = 'preview', timeoutMs) => {
21
+ assertArgumentDomain('profilePictureUrl', 'type', type, PROFILE_PICTURE_TYPES);
19
22
  const result = await (await ctx.getClient()).profilePictureUrl(jid, type, timeoutMs);
20
23
  return result?.url;
21
24
  },
@@ -1,14 +1,40 @@
1
1
  import { type GroupMetadata, type ParticipantAction, type WAMessageKey } from '../Types/index.js';
2
2
  import { proto } from '../WAProto/runtime.js';
3
3
  import type { SocketContext } from './types.js';
4
- type GroupSetting = 'announcement' | 'not_announcement' | 'locked' | 'unlocked';
4
+ declare const GROUP_SETTING_ALIASES: {
5
+ readonly announcement: {
6
+ readonly setting: 'announce';
7
+ readonly value: true;
8
+ };
9
+ readonly not_announcement: {
10
+ readonly setting: 'announce';
11
+ readonly value: false;
12
+ };
13
+ readonly locked: {
14
+ readonly setting: 'locked';
15
+ readonly value: true;
16
+ };
17
+ readonly unlocked: {
18
+ readonly setting: 'locked';
19
+ readonly value: false;
20
+ };
21
+ };
22
+ /** The table is the definition: both the type and the accepted set come off it. */
23
+ export type GroupSetting = keyof typeof GROUP_SETTING_ALIASES;
24
+ export declare const GROUP_SETTINGS: readonly GroupSetting[];
25
+ export declare const GROUP_REQUEST_ACTIONS: readonly ['approve', 'reject'];
26
+ export type GroupRequestAction = (typeof GROUP_REQUEST_ACTIONS)[number];
27
+ export declare const MEMBER_ADD_MODES: readonly ['admin_add', 'all_member_add'];
28
+ export type MemberAddMode = (typeof MEMBER_ADD_MODES)[number];
29
+ export declare const JOIN_APPROVAL_MODES: readonly ['on', 'off'];
30
+ export type JoinApprovalMode = (typeof JOIN_APPROVAL_MODES)[number];
5
31
  export declare const makeGroupMethods: (ctx: SocketContext) => {
6
32
  groupMetadata: (jid: string) => Promise<GroupMetadata>;
7
33
  groupCreate: (subject: string, participants: string[]) => Promise<GroupMetadata>;
8
34
  groupLeave: (id: string) => Promise<void>;
9
35
  groupUpdateSubject: (jid: string, subject: string) => Promise<void>;
10
36
  groupRequestParticipantsList: (jid: string) => Promise<Array<Record<string, string>>>;
11
- groupRequestParticipantsUpdate: (jid: string, participants: string[], action: 'approve' | 'reject') => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
37
+ groupRequestParticipantsUpdate: (jid: string, participants: string[], action: GroupRequestAction) => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
12
38
  groupParticipantsUpdate: (jid: string, participants: string[], action: ParticipantAction) => Promise<import("../Compatibility/socket-results.js").ParticipantUpdateResult[]>;
13
39
  groupUpdateDescription: (jid: string, description?: string) => Promise<void>;
14
40
  groupInviteCode: (jid: string) => Promise<string | undefined>;
@@ -19,8 +45,8 @@ export declare const makeGroupMethods: (ctx: SocketContext) => {
19
45
  groupGetInviteInfo: (code: string) => Promise<GroupMetadata>;
20
46
  groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>;
21
47
  groupSettingUpdate: (jid: string, setting: GroupSetting) => Promise<void>;
22
- groupMemberAddMode: (jid: string, mode: 'admin_add' | 'all_member_add') => Promise<void>;
23
- groupJoinApprovalMode: (jid: string, mode: 'on' | 'off') => Promise<void>;
48
+ groupMemberAddMode: (jid: string, mode: MemberAddMode) => Promise<void>;
49
+ groupJoinApprovalMode: (jid: string, mode: JoinApprovalMode) => Promise<void>;
24
50
  groupFetchAllParticipating: () => Promise<Record<string, GroupMetadata>>;
25
51
  updateMemberLabel: (jid: string, memberLabel: string) => Promise<string>;
26
52
  };
@@ -1,6 +1,7 @@
1
1
  import { bridgeInviteLinkToCode, bridgeMembershipRequestsToBaileys, bridgeMembershipRequestUpdatesToBaileys, bridgeParticipantChangesToBaileys } from '../Compatibility/socket-results.js';
2
2
  import { emitMessageUpsert } from '../Compatibility/message-upsert.js';
3
- import { WAMessageStubType } from '../Types/index.js';
3
+ import { PARTICIPANT_ACTIONS, WAMessageStubType } from '../Types/index.js';
4
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
4
5
  import { generateMessageIDV2, unixTimestampSeconds } from '../Utils/generics.js';
5
6
  import { proto } from '../WAProto/runtime.js';
6
7
  import { bridgeGroupMetadataToBaileys } from '../Compatibility/group-metadata.js';
@@ -10,6 +11,10 @@ const GROUP_SETTING_ALIASES = {
10
11
  locked: { setting: 'locked', value: true },
11
12
  unlocked: { setting: 'locked', value: false }
12
13
  };
14
+ export const GROUP_SETTINGS = Object.keys(GROUP_SETTING_ALIASES);
15
+ export const GROUP_REQUEST_ACTIONS = ['approve', 'reject'];
16
+ export const MEMBER_ADD_MODES = ['admin_add', 'all_member_add'];
17
+ export const JOIN_APPROVAL_MODES = ['on', 'off'];
13
18
  const inviteExpirationNumber = (value) => typeof value === 'number' ? value : (value?.toNumber() ?? 0);
14
19
  export const makeGroupMethods = (ctx) => {
15
20
  const groupMetadata = async (jid) => {
@@ -17,7 +22,8 @@ export const makeGroupMethods = (ctx) => {
17
22
  return bridgeGroupMetadataToBaileys(metadata);
18
23
  };
19
24
  const groupSettingUpdate = async (jid, setting) => {
20
- const mapped = GROUP_SETTING_ALIASES[setting];
25
+ const checked = assertArgumentDomain('groupSettingUpdate', 'setting', setting, GROUP_SETTINGS);
26
+ const mapped = GROUP_SETTING_ALIASES[checked];
21
27
  await (await ctx.getClient()).groupSettingUpdate(jid, mapped.setting, mapped.value);
22
28
  };
23
29
  const groupAcceptInviteV4 = ctx.ev.createBufferedFunction(async (key, inviteMessage
@@ -71,9 +77,11 @@ export const makeGroupMethods = (ctx) => {
71
77
  return bridgeMembershipRequestsToBaileys(await (await ctx.getClient()).groupRequestParticipantsList(jid));
72
78
  },
73
79
  groupRequestParticipantsUpdate: async (jid, participants, action) => {
80
+ assertArgumentDomain('groupRequestParticipantsUpdate', 'action', action, GROUP_REQUEST_ACTIONS);
74
81
  return bridgeMembershipRequestUpdatesToBaileys(await (await ctx.getClient()).groupRequestParticipantsUpdate(jid, participants, action));
75
82
  },
76
83
  groupParticipantsUpdate: async (jid, participants, action) => {
84
+ assertArgumentDomain('groupParticipantsUpdate', 'action', action, PARTICIPANT_ACTIONS);
77
85
  return bridgeParticipantChangesToBaileys(await (await ctx.getClient()).groupParticipantsUpdate(jid, participants, action));
78
86
  },
79
87
  groupUpdateDescription: async (jid, description) => {
@@ -100,9 +108,13 @@ export const makeGroupMethods = (ctx) => {
100
108
  },
101
109
  groupSettingUpdate,
102
110
  groupMemberAddMode: async (jid, mode) => {
111
+ assertArgumentDomain('groupMemberAddMode', 'mode', mode, MEMBER_ADD_MODES);
103
112
  await (await ctx.getClient()).groupMemberAddMode(jid, mode);
104
113
  },
105
114
  groupJoinApprovalMode: async (jid, mode) => {
115
+ // Anything but 'on' used to mean off, so a typo turned approvals off
116
+ // and reported success.
117
+ assertArgumentDomain('groupJoinApprovalMode', 'mode', mode, JOIN_APPROVAL_MODES);
106
118
  await (await ctx.getClient()).groupSettingUpdate(jid, 'membership_approval', mode === 'on');
107
119
  },
108
120
  groupFetchAllParticipating: async () => {
@@ -2,15 +2,16 @@ import { Buffer } from 'node:buffer';
2
2
  import { type UploadMediaResult } from '@oxidezap/whatsapp-rust-bridge';
3
3
  import { WebSocketClient } from '../Compatibility/websocket-client.js';
4
4
  import { type MediaType } from '../Defaults/index.js';
5
- import type { BinaryNode, AuthenticationCreds, ConnectionState, Contact, ReachoutTimelockState, SignalKeyStoreWithTransaction, UserFacingSocketConfig, WABusinessProfile, WAMessage, WAMessageKey } from '../Types/index.js';
5
+ import type { BinaryNode, AuthenticationCreds, ConnectionState, Contact, ReachoutTimelockState, SignalKeyStoreWithTransaction, UserFacingSocketConfig, WABusinessProfile, WAMessage, WAMessageKey, WAPresence } from '../Types/index.js';
6
6
  import type Long from 'long';
7
+ import { type MediaDownloadType } from '../Utils/messages.js';
7
8
  import type { MediaDownloadOptions } from '../Utils/messages-media.js';
8
9
  import type { proto } from '../WAProto/runtime.js';
9
10
  /** Build the ws EventEmitter with auto-enable raw node forwarding */
10
11
  declare const makeWASocket: (config: UserFacingSocketConfig) => {
11
12
  sendMessageAck: (node: BinaryNode, errorCode?: number) => Promise<void>;
12
13
  sendRetryRequest: (node: BinaryNode, forceIncludeKeys?: boolean) => Promise<void>;
13
- updateBlockStatus: (jid: string, action: 'block' | 'unblock') => Promise<void>;
14
+ updateBlockStatus: (jid: string, action: import("./blocking.js").BlockAction) => Promise<void>;
14
15
  fetchBlocklist: () => Promise<Array<string | undefined>>;
15
16
  getCatalog: ({ jid, limit, cursor }: import("../index.js").GetCatalogOptions) => Promise<import("../index.js").CatalogPage>;
16
17
  getCollections: (jid?: string, limit?: number) => Promise<import("../index.js").CollectionsPage>;
@@ -40,14 +41,14 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
40
41
  addOrEditQuickReply: (quickReply: import("../Types/Bussines.js").QuickReplyAction) => Promise<void>;
41
42
  removeQuickReply: (timestamp: string) => Promise<void>;
42
43
  onWhatsApp: (...phoneNumber: string[]) => Promise<import("./contacts.js").OnWhatsAppResult[] | undefined>;
43
- profilePictureUrl: (jid: string, type?: 'preview' | 'image', timeoutMs?: number) => Promise<string | undefined>;
44
+ profilePictureUrl: (jid: string, type?: import("./contacts.js").ProfilePictureType, timeoutMs?: number) => Promise<string | undefined>;
44
45
  fetchUserInfo: (...jids: string[]) => Promise<Record<string, import("@oxidezap/whatsapp-rust-bridge").UserInfoResult>>;
45
46
  groupMetadata: (jid: string) => Promise<import("../index.js").GroupMetadata>;
46
47
  groupCreate: (subject: string, participants: string[]) => Promise<import("../index.js").GroupMetadata>;
47
48
  groupLeave: (id: string) => Promise<void>;
48
49
  groupUpdateSubject: (jid: string, subject: string) => Promise<void>;
49
50
  groupRequestParticipantsList: (jid: string) => Promise<Array<Record<string, string>>>;
50
- groupRequestParticipantsUpdate: (jid: string, participants: string[], action: 'approve' | 'reject') => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
51
+ groupRequestParticipantsUpdate: (jid: string, participants: string[], action: import("./groups.js").GroupRequestAction) => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
51
52
  groupParticipantsUpdate: (jid: string, participants: string[], action: import("../index.js").ParticipantAction) => Promise<import("../Compatibility/socket-results.js").ParticipantUpdateResult[]>;
52
53
  groupUpdateDescription: (jid: string, description?: string) => Promise<void>;
53
54
  groupInviteCode: (jid: string) => Promise<string | undefined>;
@@ -57,9 +58,9 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
57
58
  groupAcceptInviteV4: (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => Promise<any>;
58
59
  groupGetInviteInfo: (code: string) => Promise<import("../index.js").GroupMetadata>;
59
60
  groupToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>;
60
- groupSettingUpdate: (jid: string, setting: "announcement" | "locked" | "not_announcement" | "unlocked") => Promise<void>;
61
- groupMemberAddMode: (jid: string, mode: 'admin_add' | 'all_member_add') => Promise<void>;
62
- groupJoinApprovalMode: (jid: string, mode: 'on' | 'off') => Promise<void>;
61
+ groupSettingUpdate: (jid: string, setting: import("./groups.js").GroupSetting) => Promise<void>;
62
+ groupMemberAddMode: (jid: string, mode: import("./groups.js").MemberAddMode) => Promise<void>;
63
+ groupJoinApprovalMode: (jid: string, mode: import("./groups.js").JoinApprovalMode) => Promise<void>;
63
64
  groupFetchAllParticipating: () => Promise<Record<string, import("../index.js").GroupMetadata>>;
64
65
  updateMemberLabel: (jid: string, memberLabel: string) => Promise<string>;
65
66
  communityMetadata: (jid: string) => Promise<import("../index.js").GroupMetadata>;
@@ -81,7 +82,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
81
82
  }[];
82
83
  }>;
83
84
  communityRequestParticipantsList: (jid: string) => Promise<Array<Record<string, string>>>;
84
- communityRequestParticipantsUpdate: (jid: string, participants: string[], action: 'approve' | 'reject') => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
85
+ communityRequestParticipantsUpdate: (jid: string, participants: string[], action: import("./groups.js").GroupRequestAction) => Promise<import("../Compatibility/socket-results.js").MembershipRequestUpdateResult[]>;
85
86
  communityParticipantsUpdate: (jid: string, participants: string[], action: import("../index.js").ParticipantAction) => Promise<import("../Compatibility/socket-results.js").ParticipantUpdateResult[]>;
86
87
  communityUpdateDescription: (jid: string, description?: string) => Promise<void>;
87
88
  communityInviteCode: (jid: string) => Promise<string | undefined>;
@@ -91,9 +92,9 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
91
92
  communityAcceptInviteV4: (key: string | WAMessageKey, inviteMessage: proto.Message.IGroupInviteMessage) => Promise<any>;
92
93
  communityGetInviteInfo: (code: string) => Promise<import("../index.js").GroupMetadata>;
93
94
  communityToggleEphemeral: (jid: string, ephemeralExpiration: number) => Promise<void>;
94
- communitySettingUpdate: (jid: string, setting: "announcement" | "locked" | "not_announcement" | "unlocked") => Promise<void>;
95
- communityMemberAddMode: (jid: string, mode: 'admin_add' | 'all_member_add') => Promise<void>;
96
- communityJoinApprovalMode: (jid: string, mode: 'on' | 'off') => Promise<void>;
95
+ communitySettingUpdate: (jid: string, setting: import("./groups.js").GroupSetting) => Promise<void>;
96
+ communityMemberAddMode: (jid: string, mode: import("./groups.js").MemberAddMode) => Promise<void>;
97
+ communityJoinApprovalMode: (jid: string, mode: import("./groups.js").JoinApprovalMode) => Promise<void>;
97
98
  communityFetchAllParticipating: () => Promise<Record<string, import("../index.js").GroupMetadata>>;
98
99
  waitForSocketOpen: () => Promise<void>;
99
100
  upsertMessage: (msg: WAMessage, type: import("../index.js").MessageUpsertType) => Promise<void>;
@@ -111,7 +112,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
111
112
  sendReceipts: (keys: WAMessageKey[], type: import("../index.js").MessageReceiptType) => Promise<void>;
112
113
  requestPlaceholderResend: (messageKey: WAMessageKey, msgData?: Partial<WAMessage>) => Promise<string | undefined>;
113
114
  newsletterCreate: (name: string, description?: string) => Promise<import("../index.js").NewsletterMetadata>;
114
- newsletterMetadata: (type: 'invite' | 'jid', key: string) => Promise<import("../index.js").NewsletterMetadata | null>;
115
+ newsletterMetadata: (type: import("./newsletter.js").NewsletterKeyType, key: string) => Promise<import("../index.js").NewsletterMetadata | null>;
115
116
  newsletterUpdate: (jid: string, updates: import("../index.js").NewsletterUpdate) => Promise<import("../index.js").NewsletterMetadata>;
116
117
  newsletterUpdateName: (jid: string, name: string) => Promise<import("../index.js").NewsletterMetadata>;
117
118
  newsletterUpdateDescription: (jid: string, description: string) => Promise<import("../index.js").NewsletterMetadata>;
@@ -139,9 +140,9 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
139
140
  uploadPreKeysToServerIfRequired: () => Promise<void>;
140
141
  digestKeyBundle: () => Promise<void>;
141
142
  rotateSignedPreKey: () => Promise<void>;
142
- sendPresence: (status: 'available' | 'unavailable') => Promise<void>;
143
+ sendPresence: (status: import("../index.js").WAPresenceStatus) => Promise<void>;
143
144
  presenceSubscribe: (toJid: string) => Promise<void>;
144
- sendChatState: (jid: string, state: 'composing' | 'recording' | 'paused') => Promise<void>;
145
+ sendChatState: (jid: string, state: import("../index.js").WAChatState) => Promise<void>;
145
146
  fetchPrivacySettings: (force?: boolean) => Promise<any>;
146
147
  updatePrivacySetting: (category: string, value: string) => Promise<void>;
147
148
  updateLastSeenPrivacy: (value: import("../index.js").WAPrivacyValue) => Promise<void>;
@@ -163,7 +164,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
163
164
  fetchNewChatMessageCap: () => Promise<import("../index.js").NewChatMessageCapInfo & {
164
165
  remaining_quota?: number;
165
166
  }>;
166
- cleanDirtyBits: (type: 'account_sync' | 'groups', fromTimestamp?: number | string) => Promise<void>;
167
+ cleanDirtyBits: (type: import("./server-queries.js").DirtyBitType, fromTimestamp?: number | string) => Promise<void>;
167
168
  sendPeerDataOperationMessage: (_pdoMessage: unknown) => Promise<never>;
168
169
  createCallLink: (_type: 'audio' | 'video', _event?: unknown, _timeoutMs?: number) => Promise<never>;
169
170
  requestPairingCode: (phoneNumber: string, customPairingCode?: string) => Promise<string>;
@@ -272,7 +273,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
272
273
  * caller hears about the protocol mistake instead of the bridge silently
273
274
  * sending nothing.
274
275
  */
275
- sendPresenceUpdate: (type: 'available' | 'unavailable' | 'composing' | 'recording' | 'paused', toJid?: string) => Promise<void>;
276
+ sendPresenceUpdate: (type: WAPresence, toJid?: string) => Promise<void>;
276
277
  /**
277
278
  * Plaintext media upload helper, source-compatible with the upstream
278
279
  * Baileys `sock.waUploadToServer(buf, { mediaType })` shape so existing
@@ -303,7 +304,7 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
303
304
  getBusinessProfile: (jid: string) => Promise<WABusinessProfile | void>;
304
305
  fetchMessageHistory: (count: number, oldestMsgKey: WAMessageKey, oldestMsgTimestamp: number | Long) => Promise<string>;
305
306
  sendStatusMessage: (message: Record<string, unknown>, recipients: string[]) => Promise<string>;
306
- downloadMedia: <T extends 'buffer' | 'stream'>(message: WAMessage, type: T, options?: MediaDownloadOptions) => Promise<T extends "buffer" ? Buffer<ArrayBufferLike> : import("node:stream").Readable>;
307
+ downloadMedia: <T extends MediaDownloadType>(message: WAMessage, type: T, options?: MediaDownloadOptions) => Promise<T extends "buffer" ? Buffer<ArrayBufferLike> : import("node:stream").Readable>;
307
308
  };
308
309
  export default makeWASocket;
309
310
  //# sourceMappingURL=index.d.ts.map
@@ -12,11 +12,12 @@ import { makeStanzaResponseMethods } from '../Compatibility/stanza-responses.js'
12
12
  import { makeParticipatingRefreshHandler } from '../Compatibility/participating-refresh.js';
13
13
  import { makeTaggedMessageWaiter } from '../Compatibility/tagged-message-waiter.js';
14
14
  import { isRawNodeForwardingEnabled, WebSocketClient } from '../Compatibility/websocket-client.js';
15
- import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
16
- import { DisconnectReason } from '../Types/index.js';
15
+ import { DEFAULT_CONNECTION_CONFIG, MEDIA_TYPES } from '../Defaults/index.js';
16
+ import { DisconnectReason, WA_PRESENCES } from '../Types/index.js';
17
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
17
18
  import { Boom } from '../Utils/boom.js';
18
19
  import { makeEventBuffer } from '../Utils/event-buffer.js';
19
- import { _registerActiveBridgeClient, _unregisterActiveBridgeClient, downloadMediaMessage } from '../Utils/messages.js';
20
+ import { _registerActiveBridgeClient, _unregisterActiveBridgeClient, downloadMediaMessage, MEDIA_DOWNLOAD_TYPES } from '../Utils/messages.js';
20
21
  import { makeNativeCryptoProvider } from '../Utils/native-crypto-provider.js';
21
22
  import { wrapLegacyStore } from '../Utils/wrap-legacy-store.js';
22
23
  import { assertNodeErrorFree } from '../WABinary/generic-utils.js';
@@ -828,6 +829,9 @@ const makeWASocket = (config) => {
828
829
  * sending nothing.
829
830
  */
830
831
  sendPresenceUpdate: async (type, toJid) => {
832
+ // Ahead of the client: an off-union value used to fall through to the
833
+ // chat-state branch and be reported as a missing jid.
834
+ assertArgumentDomain('sendPresenceUpdate', 'type', type, WA_PRESENCES);
831
835
  const c = await ctx.getClient();
832
836
  if (type === 'available' || type === 'unavailable') {
833
837
  return c.sendPresence(type);
@@ -844,6 +848,9 @@ const makeWASocket = (config) => {
844
848
  * keep working. Delegates to the bridge's encrypt + CDN-failover upload.
845
849
  */
846
850
  waUploadToServer: async (data, opts) => {
851
+ // The upstream set, which is wider than what the bridge uploads:
852
+ // `toBridgeMediaType` below still refuses the ones it cannot map.
853
+ assertArgumentDomain('waUploadToServer', 'mediaType', opts?.mediaType, MEDIA_TYPES);
847
854
  const bytes = data instanceof Uint8Array && !Buffer.isBuffer(data) ? data : new Uint8Array(data);
848
855
  return (await ctx.getClient()).uploadMedia(bytes, toBridgeMediaType(opts.mediaType));
849
856
  },
@@ -897,6 +904,10 @@ const makeWASocket = (config) => {
897
904
  ...makeBusinessMethods(ctx),
898
905
  ...makeServerQueryMethods(ctx),
899
906
  downloadMedia: async (message, type, options = {}) => {
907
+ // Checked here as well as in the helper: the client is awaited while
908
+ // the context below is built, and a check past that await reports a
909
+ // stack without the caller in it.
910
+ assertArgumentDomain('downloadMedia', 'type', type, MEDIA_DOWNLOAD_TYPES);
900
911
  return downloadMediaMessage(message, type, options, {
901
912
  logger,
902
913
  reuploadRequest: (m) => sock.updateMediaMessage(m),
@@ -1,7 +1,8 @@
1
1
  import { encodeProto } from '@oxidezap/whatsapp-rust-bridge';
2
2
  import { planMessageRelay } from '../Compatibility/message-relay.js';
3
3
  import { receiptMessageKeys } from '../Compatibility/message-keys.js';
4
- import { WAProto } from '../Types/index.js';
4
+ import { MESSAGE_RECEIPT_TYPES, WAProto } from '../Types/index.js';
5
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
5
6
  import { Boom } from '../Utils/boom.js';
6
7
  import { generateWAMessage, getContentType, normalizeMessageContent } from '../Utils/messages.js';
7
8
  import { jidNormalizedUser } from '../WABinary/index.js';
@@ -161,6 +162,9 @@ export const makeMessageMethods = (ctx) => ({
161
162
  * Not supported: 'hist_sync', 'peer_msg' (logged as warning)
162
163
  */
163
164
  sendReceipt: async (jid, participant, messageIds, type) => {
165
+ // Ahead of the empty-list exit: the types this method handles elsewhere
166
+ // resolve without sending anything, so a typo looked like a no-op.
167
+ assertArgumentDomain('sendReceipt', 'type', type, MESSAGE_RECEIPT_TYPES);
164
168
  if (!messageIds.length)
165
169
  return;
166
170
  if (type === 'read' || type === 'read-self') {
@@ -193,6 +197,7 @@ export const makeMessageMethods = (ctx) => ({
193
197
  * Send receipts for multiple message keys, grouped by JID and participant.
194
198
  */
195
199
  sendReceipts: async (keys, type) => {
200
+ assertArgumentDomain('sendReceipts', 'type', type, MESSAGE_RECEIPT_TYPES);
196
201
  const client = await ctx.getClient();
197
202
  const receiptKeys = receiptMessageKeys(keys);
198
203
  if (type === 'read' || type === 'read-self') {
@@ -2,6 +2,8 @@ import type { NewsletterMetadataResult } from '@oxidezap/whatsapp-rust-bridge';
2
2
  import type { NewsletterMetadata, NewsletterUpdate } from '../Types/Newsletter.js';
3
3
  import type { WAMediaUpload } from '../Types/index.js';
4
4
  import type { SocketContext } from './types.js';
5
+ export declare const NEWSLETTER_KEY_TYPES: readonly ['invite', 'jid'];
6
+ export type NewsletterKeyType = (typeof NEWSLETTER_KEY_TYPES)[number];
5
7
  export declare const makeNewsletterMethods: (ctx: SocketContext) => {
6
8
  newsletterCreate: (name: string, description?: string) => Promise<NewsletterMetadata>;
7
9
  /**
@@ -9,7 +11,7 @@ export declare const makeNewsletterMethods: (ctx: SocketContext) => {
9
11
  * rather than one that inspects the key. Resolves to null when the
10
12
  * newsletter does not exist, matching upstream.
11
13
  */
12
- newsletterMetadata: (type: 'invite' | 'jid', key: string) => Promise<NewsletterMetadata | null>;
14
+ newsletterMetadata: (type: NewsletterKeyType, key: string) => Promise<NewsletterMetadata | null>;
13
15
  /**
14
16
  * `picture` is base64 of an already-generated image, and the empty string
15
17
  * means remove, as upstream builds it. The core splits those into two
@@ -1,7 +1,9 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { bridgeNewsletterMetadataToBaileys } from '../Compatibility/newsletter-results.js';
3
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
3
4
  import { Boom } from '../Utils/boom.js';
4
5
  import { generateProfilePicture } from '../Utils/messages-media.js';
6
+ export const NEWSLETTER_KEY_TYPES = ['invite', 'jid'];
5
7
  export const makeNewsletterMethods = (ctx) => ({
6
8
  newsletterCreate: async (name, description) => {
7
9
  return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterCreate(name, description ?? null));
@@ -12,9 +14,7 @@ export const makeNewsletterMethods = (ctx) => ({
12
14
  * newsletter does not exist, matching upstream.
13
15
  */
14
16
  newsletterMetadata: async (type, key) => {
15
- if (type !== 'invite' && type !== 'jid') {
16
- throw new Boom(`newsletterMetadata: unknown key type '${type}'`, { statusCode: 400 });
17
- }
17
+ assertArgumentDomain('newsletterMetadata', 'type', type, NEWSLETTER_KEY_TYPES);
18
18
  const client = await ctx.getClient();
19
19
  const result = type === 'invite' ? await client.newsletterMetadataByInvite(key) : await client.newsletterMetadata(key);
20
20
  return result ? bridgeNewsletterMetadataToBaileys(result) : null;
@@ -1,7 +1,8 @@
1
+ import { type WAChatState, type WAPresenceStatus } from '../Types/index.js';
1
2
  import type { SocketContext } from './types.js';
2
3
  export declare const makePresenceMethods: (ctx: SocketContext) => {
3
- sendPresence: (status: 'available' | 'unavailable') => Promise<void>;
4
+ sendPresence: (status: WAPresenceStatus) => Promise<void>;
4
5
  presenceSubscribe: (toJid: string) => Promise<void>;
5
- sendChatState: (jid: string, state: 'composing' | 'recording' | 'paused') => Promise<void>;
6
+ sendChatState: (jid: string, state: WAChatState) => Promise<void>;
6
7
  };
7
8
  //# sourceMappingURL=presence.d.ts.map
@@ -1,11 +1,15 @@
1
+ import { WA_CHAT_STATES, WA_PRESENCE_STATUSES } from '../Types/index.js';
2
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
1
3
  export const makePresenceMethods = (ctx) => ({
2
4
  sendPresence: async (status) => {
5
+ assertArgumentDomain('sendPresence', 'status', status, WA_PRESENCE_STATUSES);
3
6
  await (await ctx.getClient()).sendPresence(status);
4
7
  },
5
8
  presenceSubscribe: async (toJid) => {
6
9
  await (await ctx.getClient()).presenceSubscribe(toJid);
7
10
  },
8
11
  sendChatState: async (jid, state) => {
12
+ assertArgumentDomain('sendChatState', 'state', state, WA_CHAT_STATES);
9
13
  await (await ctx.getClient()).sendChatState(jid, state);
10
14
  }
11
15
  });
@@ -1,7 +1,13 @@
1
- import type { WAPrivacyCallValue, WAPrivacyGroupAddValue, WAPrivacyMessagesValue, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '../Types/index.js';
1
+ import { type WAPrivacyCallValue, type WAPrivacyGroupAddValue, type WAPrivacyMessagesValue, type WAPrivacyOnlineValue, type WAPrivacyValue, type WAReadReceiptsValue } from '../Types/index.js';
2
2
  import type { SocketContext } from './types.js';
3
3
  export declare const makePrivacyMethods: (ctx: SocketContext) => {
4
4
  fetchPrivacySettings: (force?: boolean) => Promise<any>;
5
+ /**
6
+ * The untyped escape hatch, left open on purpose: the bridge takes both
7
+ * halves as plain strings and the core's wire enums carry a fallback, so
8
+ * this is the way to reach a category or value the wrappers below do not
9
+ * name yet. The wrappers are the checked path.
10
+ */
5
11
  updatePrivacySetting: (category: string, value: string) => Promise<void>;
6
12
  updateLastSeenPrivacy: (value: WAPrivacyValue) => Promise<void>;
7
13
  updateOnlinePrivacy: (value: WAPrivacyOnlineValue) => Promise<void>;
@@ -1,3 +1,5 @@
1
+ import { WA_PRIVACY_CALL_VALUES, WA_PRIVACY_GROUP_ADD_VALUES, WA_PRIVACY_MESSAGES_VALUES, WA_PRIVACY_ONLINE_VALUES, WA_PRIVACY_VALUES, WA_READ_RECEIPTS_VALUES } from '../Types/index.js';
2
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
1
3
  export const makePrivacyMethods = (ctx) => {
2
4
  /** Per socket, so a send loop calling this does not flood the log. */
3
5
  let warnedAboutPrivacyTokens = false;
@@ -6,31 +8,45 @@ export const makePrivacyMethods = (ctx) => {
6
8
  void force;
7
9
  return (await ctx.getClient()).fetchPrivacySettings();
8
10
  },
11
+ /**
12
+ * The untyped escape hatch, left open on purpose: the bridge takes both
13
+ * halves as plain strings and the core's wire enums carry a fallback, so
14
+ * this is the way to reach a category or value the wrappers below do not
15
+ * name yet. The wrappers are the checked path.
16
+ */
9
17
  updatePrivacySetting: async (category, value) => {
10
18
  await (await ctx.getClient()).updatePrivacySetting(category, value);
11
19
  },
12
20
  updateLastSeenPrivacy: async (value) => {
21
+ assertArgumentDomain('updateLastSeenPrivacy', 'value', value, WA_PRIVACY_VALUES);
13
22
  await (await ctx.getClient()).updatePrivacySetting('last', value);
14
23
  },
15
24
  updateOnlinePrivacy: async (value) => {
25
+ assertArgumentDomain('updateOnlinePrivacy', 'value', value, WA_PRIVACY_ONLINE_VALUES);
16
26
  await (await ctx.getClient()).updatePrivacySetting('online', value);
17
27
  },
18
28
  updateProfilePicturePrivacy: async (value) => {
29
+ assertArgumentDomain('updateProfilePicturePrivacy', 'value', value, WA_PRIVACY_VALUES);
19
30
  await (await ctx.getClient()).updatePrivacySetting('profile', value);
20
31
  },
21
32
  updateStatusPrivacy: async (value) => {
33
+ assertArgumentDomain('updateStatusPrivacy', 'value', value, WA_PRIVACY_VALUES);
22
34
  await (await ctx.getClient()).updatePrivacySetting('status', value);
23
35
  },
24
36
  updateReadReceiptsPrivacy: async (value) => {
37
+ assertArgumentDomain('updateReadReceiptsPrivacy', 'value', value, WA_READ_RECEIPTS_VALUES);
25
38
  await (await ctx.getClient()).updatePrivacySetting('readreceipts', value);
26
39
  },
27
40
  updateGroupsAddPrivacy: async (value) => {
41
+ assertArgumentDomain('updateGroupsAddPrivacy', 'value', value, WA_PRIVACY_GROUP_ADD_VALUES);
28
42
  await (await ctx.getClient()).updatePrivacySetting('groupadd', value);
29
43
  },
30
44
  updateCallPrivacy: async (value) => {
45
+ assertArgumentDomain('updateCallPrivacy', 'value', value, WA_PRIVACY_CALL_VALUES);
31
46
  await (await ctx.getClient()).updatePrivacySetting('calladd', value);
32
47
  },
33
48
  updateMessagesPrivacy: async (value) => {
49
+ assertArgumentDomain('updateMessagesPrivacy', 'value', value, WA_PRIVACY_MESSAGES_VALUES);
34
50
  await (await ctx.getClient()).updatePrivacySetting('messages', value);
35
51
  },
36
52
  /**
@@ -2,6 +2,8 @@ import type { BotListInfo } from '../Types/Chat.js';
2
2
  import type { NewChatMessageCapInfo } from '../Types/State.js';
3
3
  import type { MediaConnInfo } from '../Types/Message.js';
4
4
  import type { SocketContext } from './types.js';
5
+ export declare const DIRTY_BIT_TYPES: readonly ['account_sync', 'groups'];
6
+ export type DirtyBitType = (typeof DIRTY_BIT_TYPES)[number];
5
7
  export declare const makeServerQueryMethods: (ctx: SocketContext) => {
6
8
  /**
7
9
  * `maxContentLengthBytes` is absent by design: the core's hosts carry
@@ -19,7 +21,7 @@ export declare const makeServerQueryMethods: (ctx: SocketContext) => {
19
21
  fetchNewChatMessageCap: () => Promise<NewChatMessageCapInfo & {
20
22
  remaining_quota?: number;
21
23
  }>;
22
- cleanDirtyBits: (type: 'account_sync' | 'groups', fromTimestamp?: number | string) => Promise<void>;
24
+ cleanDirtyBits: (type: DirtyBitType, fromTimestamp?: number | string) => Promise<void>;
23
25
  /**
24
26
  * Refused rather than wired up. The core already fires a peer data
25
27
  * request itself when a message fails to decrypt, with its own age
@@ -1,4 +1,6 @@
1
+ import { assertArgumentDomain } from '../Utils/argument-domain.js';
1
2
  import { Boom } from '../Utils/boom.js';
3
+ export const DIRTY_BIT_TYPES = ['account_sync', 'groups'];
2
4
  /**
3
5
  * Every section, flattened and deduplicated, rather than only the section the
4
6
  * server types `all`.
@@ -85,6 +87,7 @@ export const makeServerQueryMethods = (ctx) => {
85
87
  return toCapInfo(await (await ctx.getClient()).fetchNewChatMessageCappingInfo());
86
88
  },
87
89
  cleanDirtyBits: async (type, fromTimestamp) => {
90
+ assertArgumentDomain('cleanDirtyBits', 'type', type, DIRTY_BIT_TYPES);
88
91
  let timestamp = null;
89
92
  if (fromTimestamp !== undefined) {
90
93
  // A blank string is not a timestamp, and `Number('')` is the epoch,
@@ -6,15 +6,33 @@ import type { LabelActionBody } from './Label.js';
6
6
  import type { ChatLabelAssociationActionBody } from './LabelAssociation.js';
7
7
  import type { MessageLabelAssociationActionBody } from './LabelAssociation.js';
8
8
  import type { MinimalMessage, WAMessageKey } from './Message.js';
9
- /** privacy settings in WhatsApp Web */
10
- export type WAPrivacyValue = 'all' | 'contacts' | 'contact_blacklist' | 'none';
11
- export type WAPrivacyOnlineValue = 'all' | 'match_last_seen';
12
- export type WAPrivacyGroupAddValue = 'all' | 'contacts' | 'contact_blacklist';
13
- export type WAReadReceiptsValue = 'all' | 'none';
14
- export type WAPrivacyCallValue = 'all' | 'known';
15
- export type WAPrivacyMessagesValue = 'all' | 'contacts';
9
+ /**
10
+ * privacy settings in WhatsApp Web
11
+ *
12
+ * Each of these is a set first and a type second: the wrappers that take one
13
+ * check the value against the same array the type is derived from, so a value
14
+ * cannot be accepted by the compiler and refused at runtime, or the reverse.
15
+ */
16
+ export declare const WA_PRIVACY_VALUES: readonly ['all', 'contacts', 'contact_blacklist', 'none'];
17
+ export type WAPrivacyValue = (typeof WA_PRIVACY_VALUES)[number];
18
+ export declare const WA_PRIVACY_ONLINE_VALUES: readonly ['all', 'match_last_seen'];
19
+ export type WAPrivacyOnlineValue = (typeof WA_PRIVACY_ONLINE_VALUES)[number];
20
+ export declare const WA_PRIVACY_GROUP_ADD_VALUES: readonly ['all', 'contacts', 'contact_blacklist'];
21
+ export type WAPrivacyGroupAddValue = (typeof WA_PRIVACY_GROUP_ADD_VALUES)[number];
22
+ export declare const WA_READ_RECEIPTS_VALUES: readonly ['all', 'none'];
23
+ export type WAReadReceiptsValue = (typeof WA_READ_RECEIPTS_VALUES)[number];
24
+ export declare const WA_PRIVACY_CALL_VALUES: readonly ['all', 'known'];
25
+ export type WAPrivacyCallValue = (typeof WA_PRIVACY_CALL_VALUES)[number];
26
+ export declare const WA_PRIVACY_MESSAGES_VALUES: readonly ['all', 'contacts'];
27
+ export type WAPrivacyMessagesValue = (typeof WA_PRIVACY_MESSAGES_VALUES)[number];
28
+ /** the two the account itself broadcasts, as opposed to the per-chat states */
29
+ export declare const WA_PRESENCE_STATUSES: readonly ['unavailable', 'available'];
30
+ export type WAPresenceStatus = (typeof WA_PRESENCE_STATUSES)[number];
31
+ export declare const WA_CHAT_STATES: readonly ['composing', 'recording', 'paused'];
32
+ export type WAChatState = (typeof WA_CHAT_STATES)[number];
16
33
  /** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
17
- export type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';
34
+ export declare const WA_PRESENCES: readonly ["unavailable", "available", "composing", "recording", "paused"];
35
+ export type WAPresence = (typeof WA_PRESENCES)[number];
18
36
  export declare const ALL_WA_PATCH_NAMES: readonly ['critical_block', 'critical_unblock_low', 'regular_high', 'regular_low', 'regular'];
19
37
  export type WAPatchName = (typeof ALL_WA_PATCH_NAMES)[number];
20
38
  export interface PresenceData {
package/lib/Types/Chat.js CHANGED
@@ -1,3 +1,21 @@
1
+ /**
2
+ * privacy settings in WhatsApp Web
3
+ *
4
+ * Each of these is a set first and a type second: the wrappers that take one
5
+ * check the value against the same array the type is derived from, so a value
6
+ * cannot be accepted by the compiler and refused at runtime, or the reverse.
7
+ */
8
+ export const WA_PRIVACY_VALUES = ['all', 'contacts', 'contact_blacklist', 'none'];
9
+ export const WA_PRIVACY_ONLINE_VALUES = ['all', 'match_last_seen'];
10
+ export const WA_PRIVACY_GROUP_ADD_VALUES = ['all', 'contacts', 'contact_blacklist'];
11
+ export const WA_READ_RECEIPTS_VALUES = ['all', 'none'];
12
+ export const WA_PRIVACY_CALL_VALUES = ['all', 'known'];
13
+ export const WA_PRIVACY_MESSAGES_VALUES = ['all', 'contacts'];
14
+ /** the two the account itself broadcasts, as opposed to the per-chat states */
15
+ export const WA_PRESENCE_STATUSES = ['unavailable', 'available'];
16
+ export const WA_CHAT_STATES = ['composing', 'recording', 'paused'];
17
+ /** set of statuses visible to other people; see updatePresence() in WhatsAppWeb.Send */
18
+ export const WA_PRESENCES = [...WA_PRESENCE_STATUSES, ...WA_CHAT_STATES];
1
19
  export const ALL_WA_PATCH_NAMES = [
2
20
  'critical_block',
3
21
  'critical_unblock_low',
@@ -5,7 +5,9 @@ export type GroupParticipant = Contact & {
5
5
  isSuperAdmin?: boolean;
6
6
  admin?: 'admin' | 'superadmin' | null;
7
7
  };
8
- export type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote' | 'modify';
8
+ export declare const PARTICIPANT_ACTIONS: readonly ['add', 'remove', 'promote', 'demote', 'modify'];
9
+ /** Derived from the values, so the runtime check and the type cannot drift. */
10
+ export type ParticipantAction = (typeof PARTICIPANT_ACTIONS)[number];
9
11
  export type RequestJoinAction = 'created' | 'revoked' | 'rejected';
10
12
  export type RequestJoinMethod = 'invite_link' | 'linked_group_join' | 'non_admin_add' | undefined;
11
13
  export interface GroupMetadata {
@@ -1,2 +1,2 @@
1
- export {};
1
+ export const PARTICIPANT_ACTIONS = ['add', 'remove', 'promote', 'demote', 'modify'];
2
2
  //# sourceMappingURL=GroupMetadata.js.map
@@ -51,7 +51,9 @@ export type MessageType = keyof proto.Message;
51
51
  export declare const WAMessageAddressingMode: typeof WAMessageAddressingModeType;
52
52
  export type WAMessageAddressingMode = WAMessageAddressingModeType;
53
53
  export type MessageWithContextInfo = 'imageMessage' | 'contactMessage' | 'locationMessage' | 'extendedTextMessage' | 'documentMessage' | 'audioMessage' | 'videoMessage' | 'call' | 'contactsArrayMessage' | 'liveLocationMessage' | 'templateMessage' | 'stickerMessage' | 'groupInviteMessage' | 'templateButtonReplyMessage' | 'productMessage' | 'listMessage' | 'orderMessage' | 'listResponseMessage' | 'buttonsMessage' | 'buttonsResponseMessage' | 'interactiveMessage' | 'interactiveResponseMessage' | 'pollCreationMessage' | 'requestPhoneNumberMessage' | 'messageHistoryBundle' | 'eventMessage' | 'newsletterAdminInviteMessage' | 'albumMessage' | 'stickerPackMessage' | 'pollResultSnapshotMessage' | 'messageHistoryNotice';
54
- export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | 'played' | undefined;
54
+ /** `undefined` is a member: it is how upstream spells a delivery receipt. */
55
+ export declare const MESSAGE_RECEIPT_TYPES: readonly ['read', 'read-self', 'hist_sync', 'peer_msg', 'sender', 'inactive', 'played', undefined];
56
+ export type MessageReceiptType = (typeof MESSAGE_RECEIPT_TYPES)[number];
55
57
  export type MediaConnInfo = {
56
58
  auth: string;
57
59
  ttl: number;
@@ -7,4 +7,15 @@ export const WAMessageAddressingMode = Object.freeze({
7
7
  PN: 'pn',
8
8
  LID: 'lid'
9
9
  });
10
+ /** `undefined` is a member: it is how upstream spells a delivery receipt. */
11
+ export const MESSAGE_RECEIPT_TYPES = [
12
+ 'read',
13
+ 'read-self',
14
+ 'hist_sync',
15
+ 'peer_msg',
16
+ 'sender',
17
+ 'inactive',
18
+ 'played',
19
+ undefined
20
+ ];
10
21
  //# sourceMappingURL=Message.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Refuse a value outside a closed set, naming the method, the parameter, what
3
+ * arrived and everything that is accepted.
4
+ *
5
+ * Call it in the synchronous prefix of the public method, ahead of the first
6
+ * `await`. That is what puts the caller's own frame in the stack: a
7
+ * fire-and-forget call leaves no awaiting frame for V8 to stitch an async
8
+ * stack onto, and past the bridge boundary the rejection is built inside wasm
9
+ * and carries nothing but wasm frames.
10
+ *
11
+ * The domain is always the values that define the parameter's type, never a
12
+ * second list written beside it.
13
+ */
14
+ export declare const assertArgumentDomain: <Value extends string | undefined>(method: string, parameter: string, value: unknown, domain: readonly Value[]) => Value;
15
+ //# sourceMappingURL=argument-domain.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { Boom } from './boom.js';
2
+ /** Quoted when it is a string, bare otherwise, so `""` and `undefined` read apart. */
3
+ const shown = (value) => {
4
+ if (typeof value === 'string')
5
+ return JSON.stringify(value);
6
+ try {
7
+ return String(value);
8
+ }
9
+ catch {
10
+ // A null-prototype object, a revoked proxy, a trap that throws. `typeof`
11
+ // reads nothing off the value, so reporting it cannot fail on it.
12
+ return `[${typeof value}]`;
13
+ }
14
+ };
15
+ /**
16
+ * Refuse a value outside a closed set, naming the method, the parameter, what
17
+ * arrived and everything that is accepted.
18
+ *
19
+ * Call it in the synchronous prefix of the public method, ahead of the first
20
+ * `await`. That is what puts the caller's own frame in the stack: a
21
+ * fire-and-forget call leaves no awaiting frame for V8 to stitch an async
22
+ * stack onto, and past the bridge boundary the rejection is built inside wasm
23
+ * and carries nothing but wasm frames.
24
+ *
25
+ * The domain is always the values that define the parameter's type, never a
26
+ * second list written beside it.
27
+ */
28
+ export const assertArgumentDomain = (method, parameter, value, domain) => {
29
+ if (domain.includes(value))
30
+ return value;
31
+ const error = new Boom(`${method}: ${JSON.stringify(parameter)} must be one of ${domain.map(shown).join(', ')}, received ${shown(value)}`, { statusCode: 400, data: { parameter, value, accepted: [...domain] } });
32
+ // Drop this frame: the method the consumer called is the useful top.
33
+ Error.captureStackTrace(error, assertArgumentDomain);
34
+ throw error;
35
+ };
36
+ //# sourceMappingURL=argument-domain.js.map
@@ -55,6 +55,8 @@ export type DownloadMediaMessageContext = {
55
55
  /** Bridge client for media download. Falls back to the registered one. */
56
56
  waClient?: Pick<WasmWhatsAppClient, 'downloadMedia' | 'downloadMediaStream'>;
57
57
  };
58
+ export declare const MEDIA_DOWNLOAD_TYPES: readonly ['buffer', 'stream'];
59
+ export type MediaDownloadType = (typeof MEDIA_DOWNLOAD_TYPES)[number];
58
60
  /**
59
61
  * Downloads the given message. Throws an error if it's not a media message.
60
62
  *
@@ -68,7 +70,7 @@ export type DownloadMediaMessageContext = {
68
70
  * several sockets should pass `ctx` explicitly, because the registration points
69
71
  * at whichever client was created last.
70
72
  */
71
- export declare const downloadMediaMessage: <Type extends 'buffer' | 'stream'>(message: WAMessage, type: Type, options: MediaDownloadOptions, ctx?: DownloadMediaMessageContext) => Promise<Type extends "buffer" ? Buffer<ArrayBufferLike> : Readable>;
73
+ export declare const downloadMediaMessage: <Type extends MediaDownloadType>(message: WAMessage, type: Type, options: MediaDownloadOptions, ctx?: DownloadMediaMessageContext) => Promise<Type extends "buffer" ? Buffer<ArrayBufferLike> : Readable>;
72
74
  export declare const _registerActiveBridgeClient: (client: WasmWhatsAppClient, logger?: ILogger) => void;
73
75
  /**
74
76
  * Drop the module-level pointer when `sock.end()` frees the client it points
@@ -6,6 +6,7 @@ import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT
6
6
  import { WAMessageStatus, WAProto } from '../Types/index.js';
7
7
  import { proto } from '../WAProto/runtime.js';
8
8
  import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
9
+ import { assertArgumentDomain } from './argument-domain.js';
9
10
  import { Boom } from './boom.js';
10
11
  import { sha256 } from './crypto.js';
11
12
  import { getKeyAuthor, toNumber, unixTimestampSeconds } from './generics.js';
@@ -664,6 +665,7 @@ export const extractMessageContent = (content) => {
664
665
  }
665
666
  return content;
666
667
  };
668
+ export const MEDIA_DOWNLOAD_TYPES = ['buffer', 'stream'];
667
669
  /**
668
670
  * Downloads the given message. Throws an error if it's not a media message.
669
671
  *
@@ -678,6 +680,9 @@ export const extractMessageContent = (content) => {
678
680
  * at whichever client was created last.
679
681
  */
680
682
  export const downloadMediaMessage = async (message, type, options, ctx) => {
683
+ // Anything but 'buffer' used to take the stream branch, so a typo returned
684
+ // a Readable to a caller holding it as a Buffer.
685
+ assertArgumentDomain('downloadMediaMessage', 'type', type, MEDIA_DOWNLOAD_TYPES);
681
686
  const waClient = ctx?.waClient ?? activeBridgeClient;
682
687
  if (!waClient) {
683
688
  throw new Boom('downloadMediaMessage: no bridge client available, and the download, its CDN failover and its decryption all happen in the engine. Pass `{ waClient: sock.waClient }`, use `sock.downloadMedia(message, type, options)`, or call after `makeWASocket()` has initialized.', { statusCode: 500 });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oxidezap/baileyrs",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "description": "A Rust-powered WhatsApp Web library for JavaScript, with a Baileys-compatible API",
6
6
  "keywords": [
7
7
  "whatsapp",
@@ -76,7 +76,7 @@
76
76
  },
77
77
  "dependencies": {
78
78
  "@hapi/boom": "^9.1.4",
79
- "@oxidezap/whatsapp-rust-bridge": "0.7.0",
79
+ "@oxidezap/whatsapp-rust-bridge": "0.7.1",
80
80
  "long": "^5.3.2",
81
81
  "pino": "^10.3.1",
82
82
  "protobufjs": "^7.6.5"