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.
- package/CHANGELOG.md +13 -0
- package/README.md +11 -0
- package/agent_docs/DESIGN.md +707 -0
- package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
- package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
- package/agent_docs/NAMING_DECISION.md +78 -0
- package/agent_docs/STRUCTURE.md +711 -0
- package/agent_docs/messages-namespace-design.md +357 -0
- package/cloud-api-docs/webhooks/endpoint.md +112 -0
- package/cloud-api-docs/webhooks/overview.md +154 -0
- package/package.json +10 -3
- package/src/client/HttpClient.ts +159 -0
- package/src/client/WhatsAppClient.ts +58 -0
- package/src/client/index.ts +2 -0
- package/src/errors.ts +58 -0
- package/src/examples/main.ts +9 -0
- package/src/examples/template.ts +134 -0
- package/src/index.ts +16 -1
- package/src/schemas/accounts/index.ts +1 -0
- package/src/schemas/accounts/phone-number.ts +20 -0
- package/src/schemas/business/account.ts +43 -0
- package/src/schemas/business/index.ts +2 -0
- package/src/schemas/client.ts +50 -0
- package/src/schemas/debug.ts +25 -0
- package/src/schemas/index.ts +6 -0
- package/src/schemas/messages/index.ts +2 -0
- package/src/schemas/messages/request.ts +82 -0
- package/src/schemas/messages/response.ts +19 -0
- package/src/schemas/templates/component.ts +145 -0
- package/src/schemas/templates/index.ts +4 -0
- package/src/schemas/templates/request.ts +78 -0
- package/src/schemas/templates/response.ts +64 -0
- package/src/services/accounts/AccountsClient.ts +34 -0
- package/src/services/accounts/AccountsService.ts +45 -0
- package/src/services/accounts/index.ts +2 -0
- package/src/services/accounts/methods/list-phone-numbers.ts +15 -0
- package/src/services/business/BusinessClient.ts +34 -0
- package/src/services/business/BusinessService.ts +45 -0
- package/src/services/business/index.ts +3 -0
- package/src/services/business/methods/list-accounts.ts +17 -0
- package/src/services/index.ts +2 -0
- package/src/services/messages/MessagesClient.ts +34 -0
- package/src/services/messages/MessagesService.ts +97 -0
- package/src/services/messages/index.ts +8 -0
- package/src/services/messages/methods/send-image.ts +33 -0
- package/src/services/messages/methods/send-location.ts +32 -0
- package/src/services/messages/methods/send-reaction.ts +33 -0
- package/src/services/messages/methods/send-text.ts +32 -0
- package/src/services/messages/utils/build-message-payload.ts +32 -0
- package/src/services/templates/TemplatesClient.ts +35 -0
- package/src/services/templates/TemplatesService.ts +117 -0
- package/src/services/templates/index.ts +3 -0
- package/src/services/templates/methods/create.ts +27 -0
- package/src/services/templates/methods/delete.ts +38 -0
- package/src/services/templates/methods/get.ts +23 -0
- package/src/services/templates/methods/list.ts +36 -0
- package/src/services/templates/methods/update.ts +35 -0
- package/src/types/accounts/index.ts +1 -0
- package/src/types/accounts/phone-number.ts +9 -0
- package/src/types/business/account.ts +10 -0
- package/src/types/business/index.ts +2 -0
- package/src/types/client.ts +8 -0
- package/src/types/debug.ts +8 -0
- package/src/types/index.ts +6 -0
- package/src/types/messages/index.ts +2 -0
- package/src/types/messages/request.ts +27 -0
- package/src/types/messages/response.ts +7 -0
- package/src/types/templates/component.ts +33 -0
- package/src/types/templates/index.ts +4 -0
- package/src/types/templates/request.ts +28 -0
- package/src/types/templates/response.ts +34 -0
- package/src/utils/zod-error.ts +28 -0
- 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,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,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,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,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";
|