whatsapp-cloud 0.0.3 → 0.0.5

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +11 -0
  3. package/agent_docs/DESIGN.md +707 -0
  4. package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
  5. package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
  6. package/agent_docs/NAMING_DECISION.md +78 -0
  7. package/agent_docs/STRUCTURE.md +711 -0
  8. package/agent_docs/messages-namespace-design.md +357 -0
  9. package/cloud-api-docs/webhooks/endpoint.md +112 -0
  10. package/cloud-api-docs/webhooks/overview.md +154 -0
  11. package/package.json +10 -3
  12. package/src/client/HttpClient.ts +159 -0
  13. package/src/client/WhatsAppClient.ts +58 -0
  14. package/src/client/index.ts +2 -0
  15. package/src/errors.ts +58 -0
  16. package/src/examples/main.ts +9 -0
  17. package/src/examples/template.ts +134 -0
  18. package/src/index.ts +16 -1
  19. package/src/schemas/accounts/index.ts +1 -0
  20. package/src/schemas/accounts/phone-number.ts +20 -0
  21. package/src/schemas/business/account.ts +43 -0
  22. package/src/schemas/business/index.ts +2 -0
  23. package/src/schemas/client.ts +50 -0
  24. package/src/schemas/debug.ts +25 -0
  25. package/src/schemas/index.ts +6 -0
  26. package/src/schemas/messages/index.ts +2 -0
  27. package/src/schemas/messages/request.ts +82 -0
  28. package/src/schemas/messages/response.ts +19 -0
  29. package/src/schemas/templates/component.ts +145 -0
  30. package/src/schemas/templates/index.ts +4 -0
  31. package/src/schemas/templates/request.ts +78 -0
  32. package/src/schemas/templates/response.ts +64 -0
  33. package/src/services/accounts/AccountsClient.ts +34 -0
  34. package/src/services/accounts/AccountsService.ts +45 -0
  35. package/src/services/accounts/index.ts +2 -0
  36. package/src/services/accounts/methods/list-phone-numbers.ts +15 -0
  37. package/src/services/business/BusinessClient.ts +34 -0
  38. package/src/services/business/BusinessService.ts +45 -0
  39. package/src/services/business/index.ts +3 -0
  40. package/src/services/business/methods/list-accounts.ts +17 -0
  41. package/src/services/index.ts +2 -0
  42. package/src/services/messages/MessagesClient.ts +34 -0
  43. package/src/services/messages/MessagesService.ts +97 -0
  44. package/src/services/messages/index.ts +8 -0
  45. package/src/services/messages/methods/send-image.ts +33 -0
  46. package/src/services/messages/methods/send-location.ts +32 -0
  47. package/src/services/messages/methods/send-reaction.ts +33 -0
  48. package/src/services/messages/methods/send-text.ts +32 -0
  49. package/src/services/messages/utils/build-message-payload.ts +32 -0
  50. package/src/services/templates/TemplatesClient.ts +35 -0
  51. package/src/services/templates/TemplatesService.ts +117 -0
  52. package/src/services/templates/index.ts +3 -0
  53. package/src/services/templates/methods/create.ts +27 -0
  54. package/src/services/templates/methods/delete.ts +38 -0
  55. package/src/services/templates/methods/get.ts +23 -0
  56. package/src/services/templates/methods/list.ts +36 -0
  57. package/src/services/templates/methods/update.ts +35 -0
  58. package/src/types/accounts/index.ts +1 -0
  59. package/src/types/accounts/phone-number.ts +9 -0
  60. package/src/types/business/account.ts +10 -0
  61. package/src/types/business/index.ts +2 -0
  62. package/src/types/client.ts +8 -0
  63. package/src/types/debug.ts +8 -0
  64. package/src/types/index.ts +6 -0
  65. package/src/types/messages/index.ts +2 -0
  66. package/src/types/messages/request.ts +27 -0
  67. package/src/types/messages/response.ts +7 -0
  68. package/src/types/templates/component.ts +33 -0
  69. package/src/types/templates/index.ts +4 -0
  70. package/src/types/templates/request.ts +28 -0
  71. package/src/types/templates/response.ts +34 -0
  72. package/src/utils/zod-error.ts +28 -0
  73. package/tsconfig.json +6 -4
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for message response
5
+ */
6
+ export const messageResponseSchema = z.object({
7
+ messaging_product: z.literal("whatsapp"),
8
+ contacts: z.array(
9
+ z.object({
10
+ input: z.string(),
11
+ wa_id: z.string(),
12
+ })
13
+ ),
14
+ messages: z.array(
15
+ z.object({
16
+ id: z.string(),
17
+ })
18
+ ),
19
+ });
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Button schemas for template components
5
+ * Simplified version without variables for now
6
+ */
7
+
8
+ /**
9
+ * Quick reply button schema
10
+ */
11
+ export const quickReplyButtonSchema = z.object({
12
+ type: z.literal("QUICK_REPLY"),
13
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
14
+ });
15
+
16
+ /**
17
+ * URL button schema
18
+ * Note: example field will be added later when we support variables
19
+ */
20
+ export const urlButtonSchema = z.object({
21
+ type: z.literal("URL"),
22
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
23
+ url: z.string().url().max(2000, "URL must be 2000 characters or less"),
24
+ // example: z.array(z.string()).optional(), // For later: when URL contains variables
25
+ });
26
+
27
+ /**
28
+ * Phone number button schema
29
+ */
30
+ export const phoneNumberButtonSchema = z.object({
31
+ type: z.literal("PHONE_NUMBER"),
32
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
33
+ phone_number: z
34
+ .string()
35
+ .min(1)
36
+ .max(20, "Phone number must be 20 characters or less"),
37
+ });
38
+
39
+ /**
40
+ * Copy code button schema
41
+ * Note: example field will be added later
42
+ */
43
+ export const copyCodeButtonSchema = z.object({
44
+ type: z.literal("COPY_CODE"),
45
+ // example: z.string().max(15).optional(), // For later: example value to copy
46
+ });
47
+
48
+ /**
49
+ * Flow button schema (for authentication templates)
50
+ * Note: Will be expanded later when we support flow templates
51
+ */
52
+ export const flowButtonSchema = z.object({
53
+ type: z.literal("FLOW"),
54
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
55
+ flow_action: z.string().optional(),
56
+ flow_id: z.string().optional(),
57
+ navigate_screen: z.string().optional(),
58
+ });
59
+
60
+ /**
61
+ * Union of all button types
62
+ */
63
+ export const buttonSchema = z.discriminatedUnion("type", [
64
+ quickReplyButtonSchema,
65
+ urlButtonSchema,
66
+ phoneNumberButtonSchema,
67
+ copyCodeButtonSchema,
68
+ flowButtonSchema,
69
+ ]);
70
+
71
+ /**
72
+ * Header component schema
73
+ * Simplified - no variables/examples for now
74
+ *
75
+ * Note:
76
+ * - TEXT format requires text field
77
+ * - IMAGE/VIDEO/DOCUMENT formats require example.header_handle (for later)
78
+ * - LOCATION format requires neither text nor example
79
+ */
80
+ export const headerComponentSchema = z
81
+ .object({
82
+ type: z.literal("HEADER"),
83
+ format: z.enum(["TEXT", "IMAGE", "VIDEO", "DOCUMENT", "LOCATION"]),
84
+ text: z
85
+ .string()
86
+ .max(60, "Header text must be 60 characters or less")
87
+ .optional(),
88
+ // example: z.object({...}).optional(), // For later: when using variables or media
89
+ })
90
+ .refine(
91
+ (data) => {
92
+ // TEXT format requires text
93
+ if (data.format === "TEXT" && !data.text) {
94
+ return false;
95
+ }
96
+ // LOCATION format doesn't need text
97
+ if (data.format === "LOCATION") {
98
+ return true;
99
+ }
100
+ // IMAGE/VIDEO/DOCUMENT will need example.header_handle (for later)
101
+ return true;
102
+ },
103
+ {
104
+ message: "TEXT format header requires text field",
105
+ }
106
+ );
107
+
108
+ /**
109
+ * Body component schema
110
+ * Required component - no variables for now
111
+ */
112
+ export const bodyComponentSchema = z.object({
113
+ type: z.literal("BODY"),
114
+ text: z
115
+ .string()
116
+ .min(1)
117
+ .max(1024, "Body text must be 1024 characters or less"),
118
+ // example: z.object({...}).optional(), // For later: when using variables
119
+ });
120
+
121
+ /**
122
+ * Footer component schema
123
+ */
124
+ export const footerComponentSchema = z.object({
125
+ type: z.literal("FOOTER"),
126
+ text: z.string().min(1).max(60, "Footer text must be 60 characters or less"),
127
+ });
128
+
129
+ /**
130
+ * Buttons component schema
131
+ */
132
+ export const buttonsComponentSchema = z.object({
133
+ type: z.literal("BUTTONS"),
134
+ buttons: z.array(buttonSchema).min(1).max(10, "Maximum 10 buttons allowed"),
135
+ });
136
+
137
+ /**
138
+ * Union of all component types
139
+ */
140
+ export const componentSchema = z.discriminatedUnion("type", [
141
+ headerComponentSchema,
142
+ bodyComponentSchema,
143
+ footerComponentSchema,
144
+ buttonsComponentSchema,
145
+ ]);
@@ -0,0 +1,4 @@
1
+ export * from "./component";
2
+ export * from "./request";
3
+ export * from "./response";
4
+
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import { componentSchema } from "./component";
3
+
4
+ /**
5
+ * Schema for creating a template
6
+ * Simplified - no variables/examples for now
7
+ */
8
+ export const createTemplateRequestSchema = z.object({
9
+ name: z.string().min(1).max(512, "Template name must be 512 characters or less"),
10
+ language: z.string().min(2).max(5, "Language code must be 2-5 characters (e.g., 'en' or 'en_US')"),
11
+ category: z.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]),
12
+ components: z
13
+ .array(componentSchema)
14
+ .min(1, "At least one component is required")
15
+ .refine(
16
+ (components) => {
17
+ // Body component is required
18
+ return components.some((c) => c.type === "BODY");
19
+ },
20
+ { message: "BODY component is required" }
21
+ )
22
+ .refine(
23
+ (components) => {
24
+ // Only one header allowed
25
+ const headers = components.filter((c) => c.type === "HEADER");
26
+ return headers.length <= 1;
27
+ },
28
+ { message: "Only one HEADER component is allowed" }
29
+ )
30
+ .refine(
31
+ (components) => {
32
+ // Only one footer allowed
33
+ const footers = components.filter((c) => c.type === "FOOTER");
34
+ return footers.length <= 1;
35
+ },
36
+ { message: "Only one FOOTER component is allowed" }
37
+ )
38
+ .refine(
39
+ (components) => {
40
+ // Only one buttons component allowed
41
+ const buttons = components.filter((c) => c.type === "BUTTONS");
42
+ return buttons.length <= 1;
43
+ },
44
+ { message: "Only one BUTTONS component is allowed" }
45
+ ),
46
+ });
47
+
48
+ /**
49
+ * Schema for updating a template
50
+ * All fields optional - only update what's provided
51
+ */
52
+ export const updateTemplateRequestSchema = z.object({
53
+ category: z.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]).optional(),
54
+ components: z.array(componentSchema).optional(),
55
+ language: z.string().min(2).max(5).optional(),
56
+ name: z.string().min(1).max(512).optional(),
57
+ });
58
+
59
+ /**
60
+ * Schema for listing templates
61
+ */
62
+ export const listTemplatesRequestSchema = z.object({
63
+ name: z.string().optional(), // Filter by template name
64
+ });
65
+
66
+ /**
67
+ * Schema for deleting a template
68
+ * Either name or hsm_id must be provided
69
+ */
70
+ export const deleteTemplateRequestSchema = z
71
+ .object({
72
+ name: z.string().optional(),
73
+ hsm_id: z.string().optional(),
74
+ })
75
+ .refine((data) => data.name || data.hsm_id, {
76
+ message: "Either name or hsm_id must be provided",
77
+ });
78
+
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { componentSchema } from "./component";
3
+
4
+ /**
5
+ * Schema for template response (single template)
6
+ */
7
+ export const templateResponseSchema = z.object({
8
+ id: z.string(),
9
+ name: z.string(),
10
+ language: z.string(),
11
+ status: z.string(),
12
+ category: z.string(),
13
+ components: z.array(componentSchema),
14
+ });
15
+
16
+ /**
17
+ * Schema for create template response
18
+ */
19
+ export const createTemplateResponseSchema = z.object({
20
+ id: z.string(),
21
+ status: z.string(),
22
+ category: z.string(),
23
+ });
24
+
25
+ /**
26
+ * Schema for list templates response
27
+ */
28
+ export const templateListItemSchema = z.object({
29
+ id: z.string(),
30
+ name: z.string(),
31
+ language: z.string(),
32
+ status: z.string(),
33
+ category: z.string(),
34
+ components: z.array(componentSchema),
35
+ });
36
+
37
+ export const listTemplatesResponseSchema = z.object({
38
+ data: z.array(templateListItemSchema),
39
+ paging: z
40
+ .object({
41
+ cursors: z
42
+ .object({
43
+ before: z.string().optional(),
44
+ after: z.string().optional(),
45
+ })
46
+ .optional(),
47
+ })
48
+ .optional(),
49
+ });
50
+
51
+ /**
52
+ * Schema for update template response
53
+ */
54
+ export const updateTemplateResponseSchema = z.object({
55
+ success: z.boolean(),
56
+ });
57
+
58
+ /**
59
+ * Schema for delete template response
60
+ */
61
+ export const deleteTemplateResponseSchema = z.object({
62
+ success: z.boolean(),
63
+ });
64
+
@@ -0,0 +1,34 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Accounts client - wraps HttpClient with WABA ID as base endpoint
5
+ *
6
+ * This client automatically prepends `/${businessAccountId}` to all request paths.
7
+ */
8
+ export class AccountsClient {
9
+ constructor(
10
+ private readonly httpClient: HttpClient,
11
+ private readonly businessAccountId: string
12
+ ) {}
13
+
14
+ /**
15
+ * Make a GET request with WABA ID prefix
16
+ */
17
+ async get<T>(path: string): Promise<T> {
18
+ return this.httpClient.get<T>(`/${this.businessAccountId}${path}`);
19
+ }
20
+
21
+ /**
22
+ * Make a POST request with WABA ID prefix
23
+ */
24
+ async post<T>(path: string, body: unknown): Promise<T> {
25
+ return this.httpClient.post<T>(`/${this.businessAccountId}${path}`, body);
26
+ }
27
+
28
+ /**
29
+ * Make a PATCH request with WABA ID prefix
30
+ */
31
+ async patch<T>(path: string, body: unknown): Promise<T> {
32
+ return this.httpClient.patch<T>(`/${this.businessAccountId}${path}`, body);
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+ import { AccountsClient } from "./AccountsClient";
3
+ import { listPhoneNumbers } from "./methods/list-phone-numbers";
4
+ import { WhatsAppValidationError } from "../../errors";
5
+ import type { PhoneNumberListResponse } from "../../types/accounts/phone-number";
6
+
7
+ /**
8
+ * Accounts service for managing WhatsApp Business Accounts
9
+ *
10
+ * This service handles WABA operations like listing phone numbers.
11
+ * It supports both a globally configured businessAccountId (in WhatsAppClient)
12
+ * and per-request businessAccountId overrides.
13
+ */
14
+ export class AccountsService {
15
+ constructor(private readonly httpClient: HttpClient) {}
16
+
17
+ /**
18
+ * Helper to create a Scoped Client (prefer override, fallback to config)
19
+ */
20
+ private getClient(overrideId?: string): AccountsClient {
21
+ const id = overrideId || this.httpClient.businessAccountId;
22
+ if (!id) {
23
+ throw new WhatsAppValidationError(
24
+ "businessAccountId (WABA ID) is required. Provide it in WhatsAppClient config or as a parameter.",
25
+ "businessAccountId"
26
+ );
27
+ }
28
+
29
+ // Just wrap the existing httpClient
30
+ return new AccountsClient(this.httpClient, id);
31
+ }
32
+
33
+ /**
34
+ * List phone numbers for a WhatsApp Business Account
35
+ *
36
+ * @param businessAccountId - Optional WABA ID (overrides client config)
37
+ * @returns List of phone numbers associated with the WABA
38
+ */
39
+ async listPhoneNumbers(
40
+ businessAccountId?: string
41
+ ): Promise<PhoneNumberListResponse> {
42
+ const client = this.getClient(businessAccountId);
43
+ return listPhoneNumbers(client);
44
+ }
45
+ }
@@ -0,0 +1,2 @@
1
+ export { AccountsService } from "./AccountsService";
2
+ export type { PhoneNumberListResponse } from "../../types/accounts/phone-number";
@@ -0,0 +1,15 @@
1
+ import type { AccountsClient } from "../AccountsClient";
2
+ import type { PhoneNumberListResponse } from "../../../types/accounts/phone-number";
3
+
4
+ /**
5
+ * List phone numbers for a WhatsApp Business Account
6
+ *
7
+ * @param accountsClient - Scoped accounts client
8
+ * @returns List of phone numbers associated with the WABA
9
+ */
10
+ export async function listPhoneNumbers(
11
+ accountsClient: AccountsClient
12
+ ): Promise<PhoneNumberListResponse> {
13
+ // Make API request - accountsClient handles the WABA ID prefix automatically
14
+ return accountsClient.get<PhoneNumberListResponse>("/phone_numbers");
15
+ }
@@ -0,0 +1,34 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Business client - wraps HttpClient with Business Portfolio ID as base endpoint
5
+ *
6
+ * This client automatically prepends `/${businessId}` to all request paths.
7
+ */
8
+ export class BusinessClient {
9
+ constructor(
10
+ private readonly httpClient: HttpClient,
11
+ private readonly businessId: string
12
+ ) {}
13
+
14
+ /**
15
+ * Make a GET request with Business Portfolio ID prefix
16
+ */
17
+ async get<T>(path: string): Promise<T> {
18
+ return this.httpClient.get<T>(`/${this.businessId}${path}`);
19
+ }
20
+
21
+ /**
22
+ * Make a POST request with Business Portfolio ID prefix
23
+ */
24
+ async post<T>(path: string, body: unknown): Promise<T> {
25
+ return this.httpClient.post<T>(`/${this.businessId}${path}`, body);
26
+ }
27
+
28
+ /**
29
+ * Make a PATCH request with Business Portfolio ID prefix
30
+ */
31
+ async patch<T>(path: string, body: unknown): Promise<T> {
32
+ return this.httpClient.patch<T>(`/${this.businessId}${path}`, body);
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+ import { BusinessClient } from "./BusinessClient";
3
+ import { listAccounts } from "./methods/list-accounts";
4
+ import { WhatsAppValidationError } from "../../errors";
5
+ import type { BusinessAccountsListResponse } from "../../types/business/account";
6
+
7
+ /**
8
+ * Business service for managing Business Portfolios
9
+ *
10
+ * This service handles Business Portfolio operations like listing WABAs.
11
+ * It supports both a globally configured businessId (in WhatsAppClient)
12
+ * and per-request businessId overrides.
13
+ */
14
+ export class BusinessService {
15
+ constructor(private readonly httpClient: HttpClient) {}
16
+
17
+ /**
18
+ * Helper to create a Scoped Client (prefer override, fallback to config)
19
+ */
20
+ private getClient(overrideId?: string): BusinessClient {
21
+ const id = overrideId || this.httpClient.businessId;
22
+ if (!id) {
23
+ throw new WhatsAppValidationError(
24
+ "businessId (Business Portfolio ID) is required. Provide it in WhatsAppClient config or as a parameter.",
25
+ "businessId"
26
+ );
27
+ }
28
+
29
+ // Just wrap the existing httpClient
30
+ return new BusinessClient(this.httpClient, id);
31
+ }
32
+
33
+ /**
34
+ * List WhatsApp Business Accounts (WABAs) for a Business Portfolio
35
+ *
36
+ * @param businessId - Optional Business Portfolio ID (overrides client config)
37
+ * @returns List of WABAs associated with the Business Portfolio
38
+ */
39
+ async listAccounts(
40
+ businessId?: string
41
+ ): Promise<BusinessAccountsListResponse> {
42
+ const client = this.getClient(businessId);
43
+ return listAccounts(client);
44
+ }
45
+ }
@@ -0,0 +1,3 @@
1
+ export { BusinessService } from "./BusinessService";
2
+ export type { BusinessAccountsListResponse } from "../../types/business/account";
3
+
@@ -0,0 +1,17 @@
1
+ import type { BusinessClient } from "../BusinessClient";
2
+ import type { BusinessAccountsListResponse } from "../../../types/business/account";
3
+
4
+ /**
5
+ * List WhatsApp Business Accounts (WABAs) for a Business Portfolio
6
+ *
7
+ * @param businessClient - Scoped business client
8
+ * @returns List of WABAs associated with the Business Portfolio
9
+ */
10
+ export async function listAccounts(
11
+ businessClient: BusinessClient
12
+ ): Promise<BusinessAccountsListResponse> {
13
+ // Make API request - businessClient handles the Business Portfolio ID prefix automatically
14
+ return businessClient.get<BusinessAccountsListResponse>(
15
+ "/whatsapp_business_accounts"
16
+ );
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./messages/index";
2
+ export * from "./accounts/index";
@@ -0,0 +1,34 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Messages client - wraps HttpClient with phone number ID as base endpoint
5
+ *
6
+ * This client automatically prepends `/${phoneNumberId}` to all request paths.
7
+ */
8
+ export class MessagesClient {
9
+ constructor(
10
+ private readonly httpClient: HttpClient,
11
+ private readonly phoneNumberId: string
12
+ ) {}
13
+
14
+ /**
15
+ * Make a POST request with phone number ID prefix
16
+ */
17
+ async post<T>(path: string, body: unknown): Promise<T> {
18
+ return this.httpClient.post<T>(`/${this.phoneNumberId}${path}`, body);
19
+ }
20
+
21
+ /**
22
+ * Make a GET request with phone number ID prefix
23
+ */
24
+ async get<T>(path: string): Promise<T> {
25
+ return this.httpClient.get<T>(`/${this.phoneNumberId}${path}`);
26
+ }
27
+
28
+ /**
29
+ * Make a PATCH request with phone number ID prefix
30
+ */
31
+ async patch<T>(path: string, body: unknown): Promise<T> {
32
+ return this.httpClient.patch<T>(`/${this.phoneNumberId}${path}`, body);
33
+ }
34
+ }
@@ -0,0 +1,97 @@
1
+ import { HttpClient } from "../../client/HttpClient";
2
+ import { sendText } from "./methods/send-text";
3
+ import { sendImage } from "./methods/send-image";
4
+ import { sendLocation } from "./methods/send-location";
5
+ import { sendReaction } from "./methods/send-reaction";
6
+ import { MessagesClient } from "./MessagesClient";
7
+ import { WhatsAppValidationError } from "../../errors";
8
+ import type {
9
+ SendTextRequest,
10
+ SendImageRequest,
11
+ SendLocationRequest,
12
+ SendReactionRequest,
13
+ } from "../../types/messages/request";
14
+ import type { MessageResponse } from "../../types/messages/response";
15
+
16
+ /**
17
+ * Messages service for sending WhatsApp messages
18
+ *
19
+ * This service handles message operations.
20
+ * It supports both a globally configured phoneNumberId (in WhatsAppClient)
21
+ * and per-request phoneNumberId overrides.
22
+ */
23
+ export class MessagesService {
24
+ constructor(private readonly httpClient: HttpClient) {}
25
+
26
+ /**
27
+ * Helper to create a Scoped Client (prefer override, fallback to config)
28
+ */
29
+ private getClient(overrideId?: string): MessagesClient {
30
+ const id = overrideId || this.httpClient.phoneNumberId;
31
+ if (!id) {
32
+ throw new WhatsAppValidationError(
33
+ "phoneNumberId is required. Provide it in WhatsAppClient config or as a parameter.",
34
+ "phoneNumberId"
35
+ );
36
+ }
37
+
38
+ // Just wrap the existing httpClient
39
+ return new MessagesClient(this.httpClient, id);
40
+ }
41
+
42
+ /**
43
+ * Send a text message
44
+ *
45
+ * @param request - Text message request (to, text)
46
+ * @param phoneNumberId - Optional phone number ID (overrides client config)
47
+ */
48
+ async sendText(
49
+ request: SendTextRequest,
50
+ phoneNumberId?: string
51
+ ): Promise<MessageResponse> {
52
+ const client = this.getClient(phoneNumberId);
53
+ return sendText(client, request);
54
+ }
55
+
56
+ /**
57
+ * Send an image message
58
+ *
59
+ * @param request - Image message request (to, image)
60
+ * @param phoneNumberId - Optional phone number ID (overrides client config)
61
+ */
62
+ async sendImage(
63
+ request: SendImageRequest,
64
+ phoneNumberId?: string
65
+ ): Promise<MessageResponse> {
66
+ const client = this.getClient(phoneNumberId);
67
+ return sendImage(client, request);
68
+ }
69
+
70
+ /**
71
+ * Send a location message
72
+ *
73
+ * @param request - Location message request (to, location)
74
+ * @param phoneNumberId - Optional phone number ID (overrides client config)
75
+ */
76
+ async sendLocation(
77
+ request: SendLocationRequest,
78
+ phoneNumberId?: string
79
+ ): Promise<MessageResponse> {
80
+ const client = this.getClient(phoneNumberId);
81
+ return sendLocation(client, request);
82
+ }
83
+
84
+ /**
85
+ * Send a reaction message
86
+ *
87
+ * @param request - Reaction message request (to, reaction)
88
+ * @param phoneNumberId - Optional phone number ID (overrides client config)
89
+ */
90
+ async sendReaction(
91
+ request: SendReactionRequest,
92
+ phoneNumberId?: string
93
+ ): Promise<MessageResponse> {
94
+ const client = this.getClient(phoneNumberId);
95
+ return sendReaction(client, request);
96
+ }
97
+ }
@@ -0,0 +1,8 @@
1
+ export { MessagesService } from "./MessagesService";
2
+ export type {
3
+ SendTextRequest,
4
+ SendImageRequest,
5
+ SendLocationRequest,
6
+ SendReactionRequest,
7
+ } from "../../types/messages/request";
8
+ export type { MessageResponse } from "../../types/messages/response";