whatsapp-cloud 0.0.4 → 0.0.6
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/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
- package/cloud-api-docs/webhooks/endpoint.md +112 -0
- package/cloud-api-docs/webhooks/overview.md +154 -0
- package/docs/DEVELOPMENT.md +154 -0
- package/docs/webhooks.md +76 -0
- package/package.json +6 -2
- package/src/client/HttpClient.ts +43 -6
- package/src/client/WhatsAppClient.ts +6 -0
- package/src/examples/main.ts +9 -0
- package/src/examples/template.ts +134 -0
- package/src/index.ts +7 -0
- package/src/schemas/client.ts +2 -2
- package/src/schemas/index.ts +2 -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/schemas/webhooks/incoming-message.ts +38 -0
- package/src/schemas/webhooks/index.ts +3 -0
- package/src/schemas/webhooks/payload.ts +56 -0
- package/src/services/accounts/AccountsClient.ts +6 -14
- package/src/services/accounts/AccountsService.ts +19 -21
- package/src/services/accounts/methods/list-phone-numbers.ts +1 -2
- package/src/services/business/BusinessClient.ts +1 -9
- package/src/services/business/BusinessService.ts +19 -21
- package/src/services/business/methods/list-accounts.ts +1 -2
- package/src/services/messages/MessagesClient.ts +2 -6
- package/src/services/messages/MessagesService.ts +42 -22
- 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/services/webhooks/WebhooksService.ts +214 -0
- package/src/services/webhooks/index.ts +3 -0
- package/src/services/webhooks/utils/extract-messages.ts +25 -0
- package/src/services/webhooks/utils/extract-statuses.ts +25 -0
- package/src/services/webhooks/utils/verify.ts +29 -0
- package/src/types/index.ts +2 -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/types/webhooks/incoming-message.ts +16 -0
- package/src/types/webhooks/index.ts +3 -0
- package/src/types/webhooks/payload.ts +8 -0
- package/tsconfig.json +2 -3
package/src/schemas/client.ts
CHANGED
|
@@ -44,7 +44,7 @@ export const clientConfigSchema = z.object({
|
|
|
44
44
|
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
45
45
|
message: "businessId cannot be empty or whitespace only",
|
|
46
46
|
}),
|
|
47
|
-
apiVersion: z.string().default("v18.0"),
|
|
48
|
-
baseURL: z.string().url().default("https://graph.facebook.com"),
|
|
47
|
+
apiVersion: z.string().default("v18.0").optional(),
|
|
48
|
+
baseURL: z.string().url().default("https://graph.facebook.com").optional(),
|
|
49
49
|
timeout: z.number().positive().optional(),
|
|
50
50
|
});
|
package/src/schemas/index.ts
CHANGED
|
@@ -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,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base fields present in ALL incoming messages
|
|
5
|
+
*/
|
|
6
|
+
const baseIncomingMessageSchema = z.object({
|
|
7
|
+
from: z.string(), // WhatsApp ID (phone number without +)
|
|
8
|
+
id: z.string(), // Message ID (wamid.*)
|
|
9
|
+
timestamp: z.string(), // Unix timestamp as string
|
|
10
|
+
type: z.string(), // Message type discriminator
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Text content in incoming text messages
|
|
15
|
+
* Note: Incoming messages don't have preview_url like outgoing
|
|
16
|
+
*/
|
|
17
|
+
const incomingTextContentSchema = z.object({
|
|
18
|
+
body: z.string(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Incoming text message schema
|
|
23
|
+
* Uses discriminated union pattern (type: "text")
|
|
24
|
+
*/
|
|
25
|
+
export const incomingTextMessageSchema = baseIncomingMessageSchema.extend({
|
|
26
|
+
type: z.literal("text"),
|
|
27
|
+
text: incomingTextContentSchema,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Union of all incoming message types
|
|
32
|
+
* For now: just text. Others (image, audio, etc.) will be added later
|
|
33
|
+
*/
|
|
34
|
+
export const incomingMessageSchema = z.discriminatedUnion("type", [
|
|
35
|
+
incomingTextMessageSchema,
|
|
36
|
+
// Future: incomingImageMessageSchema, incomingAudioMessageSchema, etc.
|
|
37
|
+
]);
|
|
38
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { incomingMessageSchema } from "./incoming-message";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Contact information in webhook
|
|
6
|
+
*/
|
|
7
|
+
const contactSchema = z.object({
|
|
8
|
+
profile: z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
wa_id: z.string(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Metadata in webhook value
|
|
16
|
+
*/
|
|
17
|
+
const webhookMetadataSchema = z.object({
|
|
18
|
+
display_phone_number: z.string(),
|
|
19
|
+
phone_number_id: z.string(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Webhook value (the actual data)
|
|
24
|
+
*/
|
|
25
|
+
const webhookValueSchema = z.object({
|
|
26
|
+
messaging_product: z.literal("whatsapp"),
|
|
27
|
+
metadata: webhookMetadataSchema,
|
|
28
|
+
contacts: z.array(contactSchema).optional(),
|
|
29
|
+
messages: z.array(incomingMessageSchema).optional(), // Incoming messages
|
|
30
|
+
statuses: z.array(z.any()).optional(), // Status updates (for later)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Webhook change entry
|
|
35
|
+
*/
|
|
36
|
+
const webhookChangeSchema = z.object({
|
|
37
|
+
value: webhookValueSchema,
|
|
38
|
+
field: z.literal("messages"), // For now: only messages field
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Webhook entry
|
|
43
|
+
*/
|
|
44
|
+
const webhookEntrySchema = z.object({
|
|
45
|
+
id: z.string(), // WABA ID
|
|
46
|
+
changes: z.array(webhookChangeSchema),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Full webhook payload schema
|
|
51
|
+
*/
|
|
52
|
+
export const webhookPayloadSchema = z.object({
|
|
53
|
+
object: z.literal("whatsapp_business_account"),
|
|
54
|
+
entry: z.array(webhookEntrySchema),
|
|
55
|
+
});
|
|
56
|
+
|
|
@@ -1,42 +1,34 @@
|
|
|
1
1
|
import type { HttpClient } from "../../client/HttpClient";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Accounts client - wraps HttpClient with WABA ID
|
|
4
|
+
* Accounts client - wraps HttpClient with WABA ID as base endpoint
|
|
5
5
|
*
|
|
6
|
-
* This client automatically prepends `/${
|
|
7
|
-
* so methods can use relative paths like `/phone_numbers` instead of `/${wabaId}/phone_numbers`.
|
|
8
|
-
*
|
|
9
|
-
* Note: The wabaId is the WhatsApp Business Account ID (not the Business Portfolio ID).
|
|
10
|
-
* This is used in endpoints like GET /<WABA_ID>/phone_numbers.
|
|
11
|
-
*
|
|
12
|
-
* This treats wabaId as a "client" for the accounts namespace - different
|
|
13
|
-
* wabaIds represent different WhatsApp Business Account endpoints.
|
|
6
|
+
* This client automatically prepends `/${businessAccountId}` to all request paths.
|
|
14
7
|
*/
|
|
15
8
|
export class AccountsClient {
|
|
16
9
|
constructor(
|
|
17
10
|
private readonly httpClient: HttpClient,
|
|
18
|
-
private readonly
|
|
11
|
+
private readonly businessAccountId: string
|
|
19
12
|
) {}
|
|
20
13
|
|
|
21
14
|
/**
|
|
22
15
|
* Make a GET request with WABA ID prefix
|
|
23
16
|
*/
|
|
24
17
|
async get<T>(path: string): Promise<T> {
|
|
25
|
-
return this.httpClient.get<T>(`/${this.
|
|
18
|
+
return this.httpClient.get<T>(`/${this.businessAccountId}${path}`);
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
/**
|
|
29
22
|
* Make a POST request with WABA ID prefix
|
|
30
23
|
*/
|
|
31
24
|
async post<T>(path: string, body: unknown): Promise<T> {
|
|
32
|
-
return this.httpClient.post<T>(`/${this.
|
|
25
|
+
return this.httpClient.post<T>(`/${this.businessAccountId}${path}`, body);
|
|
33
26
|
}
|
|
34
27
|
|
|
35
28
|
/**
|
|
36
29
|
* Make a PATCH request with WABA ID prefix
|
|
37
30
|
*/
|
|
38
31
|
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
39
|
-
return this.httpClient.patch<T>(`/${this.
|
|
32
|
+
return this.httpClient.patch<T>(`/${this.businessAccountId}${path}`, body);
|
|
40
33
|
}
|
|
41
34
|
}
|
|
42
|
-
|
|
@@ -7,41 +7,39 @@ import type { PhoneNumberListResponse } from "../../types/accounts/phone-number"
|
|
|
7
7
|
/**
|
|
8
8
|
* Accounts service for managing WhatsApp Business Accounts
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Note: businessAccountId in the client config represents the WABA ID, not the Business Portfolio ID.
|
|
14
|
-
* The WABA ID is used in endpoints like GET /<WABA_ID>/phone_numbers.
|
|
15
|
-
*
|
|
16
|
-
* AccountsClient treats wabaId as a "client" for the accounts namespace - different
|
|
17
|
-
* wabaIds represent different WhatsApp Business Account endpoints.
|
|
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.
|
|
18
13
|
*/
|
|
19
14
|
export class AccountsService {
|
|
20
|
-
private readonly
|
|
15
|
+
constructor(private readonly httpClient: HttpClient) {}
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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) {
|
|
26
23
|
throw new WhatsAppValidationError(
|
|
27
|
-
"businessAccountId (WABA ID
|
|
24
|
+
"businessAccountId (WABA ID) is required. Provide it in WhatsAppClient config or as a parameter.",
|
|
28
25
|
"businessAccountId"
|
|
29
26
|
);
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
httpClient,
|
|
35
|
-
httpClient.businessAccountId
|
|
36
|
-
);
|
|
29
|
+
// Just wrap the existing httpClient
|
|
30
|
+
return new AccountsClient(this.httpClient, id);
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
/**
|
|
40
34
|
* List phone numbers for a WhatsApp Business Account
|
|
41
35
|
*
|
|
36
|
+
* @param businessAccountId - Optional WABA ID (overrides client config)
|
|
42
37
|
* @returns List of phone numbers associated with the WABA
|
|
43
38
|
*/
|
|
44
|
-
async listPhoneNumbers(
|
|
45
|
-
|
|
39
|
+
async listPhoneNumbers(
|
|
40
|
+
businessAccountId?: string
|
|
41
|
+
): Promise<PhoneNumberListResponse> {
|
|
42
|
+
const client = this.getClient(businessAccountId);
|
|
43
|
+
return listPhoneNumbers(client);
|
|
46
44
|
}
|
|
47
45
|
}
|
|
@@ -4,7 +4,7 @@ import type { PhoneNumberListResponse } from "../../../types/accounts/phone-numb
|
|
|
4
4
|
/**
|
|
5
5
|
* List phone numbers for a WhatsApp Business Account
|
|
6
6
|
*
|
|
7
|
-
* @param accountsClient -
|
|
7
|
+
* @param accountsClient - Scoped accounts client
|
|
8
8
|
* @returns List of phone numbers associated with the WABA
|
|
9
9
|
*/
|
|
10
10
|
export async function listPhoneNumbers(
|
|
@@ -13,4 +13,3 @@ export async function listPhoneNumbers(
|
|
|
13
13
|
// Make API request - accountsClient handles the WABA ID prefix automatically
|
|
14
14
|
return accountsClient.get<PhoneNumberListResponse>("/phone_numbers");
|
|
15
15
|
}
|
|
16
|
-
|
|
@@ -3,14 +3,7 @@ import type { HttpClient } from "../../client/HttpClient";
|
|
|
3
3
|
/**
|
|
4
4
|
* Business client - wraps HttpClient with Business Portfolio ID as base endpoint
|
|
5
5
|
*
|
|
6
|
-
* This client automatically prepends `/${businessId}` to all request paths
|
|
7
|
-
* so methods can use relative paths like `/whatsapp_business_accounts` instead of `/${businessId}/whatsapp_business_accounts`.
|
|
8
|
-
*
|
|
9
|
-
* Note: The businessId is the Business Portfolio ID (not the WABA ID).
|
|
10
|
-
* This is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
|
|
11
|
-
*
|
|
12
|
-
* This treats businessId as a "client" for the business namespace - different
|
|
13
|
-
* businessIds represent different Business Portfolio endpoints.
|
|
6
|
+
* This client automatically prepends `/${businessId}` to all request paths.
|
|
14
7
|
*/
|
|
15
8
|
export class BusinessClient {
|
|
16
9
|
constructor(
|
|
@@ -39,4 +32,3 @@ export class BusinessClient {
|
|
|
39
32
|
return this.httpClient.patch<T>(`/${this.businessId}${path}`, body);
|
|
40
33
|
}
|
|
41
34
|
}
|
|
42
|
-
|
|
@@ -7,41 +7,39 @@ import type { BusinessAccountsListResponse } from "../../types/business/account"
|
|
|
7
7
|
/**
|
|
8
8
|
* Business service for managing Business Portfolios
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Note: businessId in the client config represents the Business Portfolio ID.
|
|
14
|
-
* The Business Portfolio ID is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
|
|
15
|
-
*
|
|
16
|
-
* BusinessClient treats businessId as a "client" for the business namespace - different
|
|
17
|
-
* businessIds represent different Business Portfolio endpoints.
|
|
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.
|
|
18
13
|
*/
|
|
19
14
|
export class BusinessService {
|
|
20
|
-
private readonly
|
|
15
|
+
constructor(private readonly httpClient: HttpClient) {}
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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) {
|
|
25
23
|
throw new WhatsAppValidationError(
|
|
26
|
-
"businessId (Business Portfolio ID) is required
|
|
24
|
+
"businessId (Business Portfolio ID) is required. Provide it in WhatsAppClient config or as a parameter.",
|
|
27
25
|
"businessId"
|
|
28
26
|
);
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
httpClient,
|
|
34
|
-
httpClient.businessId
|
|
35
|
-
);
|
|
29
|
+
// Just wrap the existing httpClient
|
|
30
|
+
return new BusinessClient(this.httpClient, id);
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
/**
|
|
39
34
|
* List WhatsApp Business Accounts (WABAs) for a Business Portfolio
|
|
40
35
|
*
|
|
36
|
+
* @param businessId - Optional Business Portfolio ID (overrides client config)
|
|
41
37
|
* @returns List of WABAs associated with the Business Portfolio
|
|
42
38
|
*/
|
|
43
|
-
async listAccounts(
|
|
44
|
-
|
|
39
|
+
async listAccounts(
|
|
40
|
+
businessId?: string
|
|
41
|
+
): Promise<BusinessAccountsListResponse> {
|
|
42
|
+
const client = this.getClient(businessId);
|
|
43
|
+
return listAccounts(client);
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
|
|
@@ -4,7 +4,7 @@ import type { BusinessAccountsListResponse } from "../../../types/business/accou
|
|
|
4
4
|
/**
|
|
5
5
|
* List WhatsApp Business Accounts (WABAs) for a Business Portfolio
|
|
6
6
|
*
|
|
7
|
-
* @param businessClient -
|
|
7
|
+
* @param businessClient - Scoped business client
|
|
8
8
|
* @returns List of WABAs associated with the Business Portfolio
|
|
9
9
|
*/
|
|
10
10
|
export async function listAccounts(
|
|
@@ -15,4 +15,3 @@ export async function listAccounts(
|
|
|
15
15
|
"/whatsapp_business_accounts"
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
|
-
|
|
@@ -2,12 +2,8 @@ import type { HttpClient } from "../../client/HttpClient";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Messages client - wraps HttpClient with phone number ID as base endpoint
|
|
5
|
-
*
|
|
6
|
-
* This client automatically prepends `/${phoneNumberId}` to all request paths
|
|
7
|
-
* so methods can use relative paths like `/messages` instead of `/${phoneNumberId}/messages`.
|
|
8
|
-
*
|
|
9
|
-
* This treats phoneNumberId as a "client" for the messaging namespace - different
|
|
10
|
-
* phoneNumberIds represent different messaging endpoints.
|
|
5
|
+
*
|
|
6
|
+
* This client automatically prepends `/${phoneNumberId}` to all request paths.
|
|
11
7
|
*/
|
|
12
8
|
export class MessagesClient {
|
|
13
9
|
constructor(
|