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,159 @@
|
|
|
1
|
+
import type { ClientConfig } from "../types/client";
|
|
2
|
+
|
|
3
|
+
interface APIErrorResponse {
|
|
4
|
+
error?: {
|
|
5
|
+
message?: string;
|
|
6
|
+
code?: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HTTP client for making requests to the WhatsApp Cloud API
|
|
12
|
+
*/
|
|
13
|
+
export class HttpClient {
|
|
14
|
+
private readonly baseURL: string;
|
|
15
|
+
public readonly accessToken: string;
|
|
16
|
+
public readonly phoneNumberId?: string;
|
|
17
|
+
public readonly businessAccountId?: string;
|
|
18
|
+
public readonly businessId?: string;
|
|
19
|
+
public readonly apiVersion: string;
|
|
20
|
+
|
|
21
|
+
constructor(config: ClientConfig) {
|
|
22
|
+
this.accessToken = config.accessToken;
|
|
23
|
+
if (config.phoneNumberId !== undefined) {
|
|
24
|
+
this.phoneNumberId = config.phoneNumberId;
|
|
25
|
+
}
|
|
26
|
+
if (config.businessAccountId !== undefined) {
|
|
27
|
+
this.businessAccountId = config.businessAccountId;
|
|
28
|
+
}
|
|
29
|
+
if (config.businessId !== undefined) {
|
|
30
|
+
this.businessId = config.businessId;
|
|
31
|
+
}
|
|
32
|
+
this.apiVersion = config.apiVersion ?? "v18.0";
|
|
33
|
+
this.baseURL = config.baseURL ?? "https://graph.facebook.com";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Make a POST request
|
|
38
|
+
*/
|
|
39
|
+
async post<T>(path: string, body: unknown): Promise<T> {
|
|
40
|
+
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
41
|
+
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const error = (await response.json().catch(() => ({
|
|
53
|
+
error: {
|
|
54
|
+
message: response.statusText,
|
|
55
|
+
code: response.status,
|
|
56
|
+
},
|
|
57
|
+
}))) as APIErrorResponse;
|
|
58
|
+
throw new Error(
|
|
59
|
+
`API Error: ${error.error?.message || response.statusText} (${
|
|
60
|
+
error.error?.code || response.status
|
|
61
|
+
})`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return response.json() as Promise<T>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Make a GET request
|
|
70
|
+
*/
|
|
71
|
+
async get<T>(path: string): Promise<T> {
|
|
72
|
+
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
73
|
+
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
method: "GET",
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const error = (await response.json().catch(() => ({
|
|
83
|
+
error: {
|
|
84
|
+
message: response.statusText,
|
|
85
|
+
code: response.status,
|
|
86
|
+
},
|
|
87
|
+
}))) as APIErrorResponse;
|
|
88
|
+
throw new Error(
|
|
89
|
+
`API Error: ${error.error?.message || response.statusText} (${
|
|
90
|
+
error.error?.code || response.status
|
|
91
|
+
})`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return response.json() as Promise<T>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Make a PATCH request
|
|
100
|
+
*/
|
|
101
|
+
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
102
|
+
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
103
|
+
|
|
104
|
+
const response = await fetch(url, {
|
|
105
|
+
method: "PATCH",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const error = (await response.json().catch(() => ({
|
|
115
|
+
error: {
|
|
116
|
+
message: response.statusText,
|
|
117
|
+
code: response.status,
|
|
118
|
+
},
|
|
119
|
+
}))) as APIErrorResponse;
|
|
120
|
+
throw new Error(
|
|
121
|
+
`API Error: ${error.error?.message || response.statusText} (${
|
|
122
|
+
error.error?.code || response.status
|
|
123
|
+
})`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return response.json() as Promise<T>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Make a DELETE request
|
|
132
|
+
*/
|
|
133
|
+
async delete<T>(path: string): Promise<T> {
|
|
134
|
+
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
135
|
+
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
method: "DELETE",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const error = (await response.json().catch(() => ({
|
|
145
|
+
error: {
|
|
146
|
+
message: response.statusText,
|
|
147
|
+
code: response.status,
|
|
148
|
+
},
|
|
149
|
+
}))) as APIErrorResponse;
|
|
150
|
+
throw new Error(
|
|
151
|
+
`API Error: ${error.error?.message || response.statusText} (${
|
|
152
|
+
error.error?.code || response.status
|
|
153
|
+
})`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return response.json() as Promise<T>;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { clientConfigSchema } from "../schemas/client";
|
|
2
|
+
import type { ClientConfig } from "../types/client";
|
|
3
|
+
import { HttpClient } from "./HttpClient";
|
|
4
|
+
import { MessagesService } from "../services/messages/index";
|
|
5
|
+
import { AccountsService } from "../services/accounts/index";
|
|
6
|
+
import { BusinessService } from "../services/business/index";
|
|
7
|
+
import { TemplatesService } from "../services/templates/index";
|
|
8
|
+
import { ZodError } from "zod";
|
|
9
|
+
import { transformZodError } from "../utils/zod-error";
|
|
10
|
+
import type { DebugTokenResponse } from "../types/debug";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* WhatsApp Cloud API client
|
|
14
|
+
*/
|
|
15
|
+
export class WhatsAppClient {
|
|
16
|
+
public readonly messages: MessagesService;
|
|
17
|
+
public readonly accounts: AccountsService;
|
|
18
|
+
public readonly business: BusinessService;
|
|
19
|
+
public readonly templates: TemplatesService;
|
|
20
|
+
|
|
21
|
+
private readonly httpClient: HttpClient;
|
|
22
|
+
|
|
23
|
+
constructor(config: ClientConfig) {
|
|
24
|
+
// Validate config with schema - Zod provides detailed error messages
|
|
25
|
+
let validated: ClientConfig;
|
|
26
|
+
try {
|
|
27
|
+
validated = clientConfigSchema.parse(config);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof ZodError) {
|
|
30
|
+
throw transformZodError(error);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Initialize HTTP client
|
|
36
|
+
this.httpClient = new HttpClient(validated);
|
|
37
|
+
|
|
38
|
+
// Initialize services (namespaces)
|
|
39
|
+
this.messages = new MessagesService(this.httpClient);
|
|
40
|
+
this.accounts = new AccountsService(this.httpClient);
|
|
41
|
+
this.business = new BusinessService(this.httpClient);
|
|
42
|
+
this.templates = new TemplatesService(this.httpClient);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Debug the current access token
|
|
47
|
+
*
|
|
48
|
+
* This method calls the Graph API debug_token endpoint to inspect the access token
|
|
49
|
+
* used by this client. Useful for understanding token permissions, expiration, and validity.
|
|
50
|
+
*
|
|
51
|
+
* @returns Debug information about the access token
|
|
52
|
+
*/
|
|
53
|
+
async debugToken(): Promise<DebugTokenResponse> {
|
|
54
|
+
return this.httpClient.get<DebugTokenResponse>(
|
|
55
|
+
`/debug_token?input_token=${this.httpClient.accessToken}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for WhatsApp API errors
|
|
3
|
+
*/
|
|
4
|
+
export class WhatsAppError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
9
|
+
const captureStackTrace = (Error as any).captureStackTrace;
|
|
10
|
+
if (typeof captureStackTrace === "function") {
|
|
11
|
+
captureStackTrace(this, this.constructor);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when validation fails (configuration, requests, etc.)
|
|
18
|
+
* Can be used for any Zod validation error
|
|
19
|
+
*/
|
|
20
|
+
export class WhatsAppValidationError extends WhatsAppError {
|
|
21
|
+
constructor(
|
|
22
|
+
message: string,
|
|
23
|
+
public readonly field?: string,
|
|
24
|
+
public readonly issues?: Array<{
|
|
25
|
+
path: readonly (string | number)[];
|
|
26
|
+
message: string;
|
|
27
|
+
}>
|
|
28
|
+
) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "WhatsAppValidationError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Error thrown when an API request fails
|
|
36
|
+
*/
|
|
37
|
+
export class WhatsAppAPIError extends WhatsAppError {
|
|
38
|
+
constructor(
|
|
39
|
+
public readonly code: number,
|
|
40
|
+
public readonly type: string,
|
|
41
|
+
message: string,
|
|
42
|
+
public readonly statusCode?: number,
|
|
43
|
+
public readonly details?: unknown
|
|
44
|
+
) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "WhatsAppAPIError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Error thrown when rate limit is exceeded
|
|
52
|
+
*/
|
|
53
|
+
export class WhatsAppRateLimitError extends WhatsAppAPIError {
|
|
54
|
+
constructor(message: string, public readonly retryAfter?: number) {
|
|
55
|
+
super(131056, "rate_limit", message, 429, { retryAfter });
|
|
56
|
+
this.name = "WhatsAppRateLimitError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { WhatsAppClient } from "../client";
|
|
3
|
+
|
|
4
|
+
const client = new WhatsAppClient({
|
|
5
|
+
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
|
|
6
|
+
businessAccountId: process.env.WHATSAPP_BUSINESS_ACCOUNT_ID!,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
async function testTemplates() {
|
|
10
|
+
try {
|
|
11
|
+
console.log("🧪 Testing Templates API...\n");
|
|
12
|
+
|
|
13
|
+
// Test 1: Create a template
|
|
14
|
+
console.log("1️⃣ Creating template...");
|
|
15
|
+
const createResponse = await client.templates.create({
|
|
16
|
+
name: `test_template_${Date.now()}`, // Unique name to avoid conflicts
|
|
17
|
+
language: "en",
|
|
18
|
+
category: "UTILITY",
|
|
19
|
+
components: [
|
|
20
|
+
{
|
|
21
|
+
type: "BODY",
|
|
22
|
+
text: "Hello! This is a test template. Thank you for testing our WhatsApp Cloud API SDK.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "FOOTER",
|
|
26
|
+
text: "This is a test footer",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "BUTTONS",
|
|
30
|
+
buttons: [
|
|
31
|
+
{
|
|
32
|
+
type: "QUICK_REPLY",
|
|
33
|
+
text: "Get Started",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "QUICK_REPLY",
|
|
37
|
+
text: "Learn More",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log("✅ Template created successfully!");
|
|
45
|
+
console.log("Response:", JSON.stringify(createResponse, null, 2));
|
|
46
|
+
console.log("\n");
|
|
47
|
+
|
|
48
|
+
// Verify create response structure
|
|
49
|
+
if (!createResponse.id) {
|
|
50
|
+
throw new Error("Create response missing 'id' field");
|
|
51
|
+
}
|
|
52
|
+
if (!createResponse.status) {
|
|
53
|
+
throw new Error("Create response missing 'status' field");
|
|
54
|
+
}
|
|
55
|
+
if (!createResponse.category) {
|
|
56
|
+
throw new Error("Create response missing 'category' field");
|
|
57
|
+
}
|
|
58
|
+
console.log("✅ Create response structure verified\n");
|
|
59
|
+
|
|
60
|
+
// Test 2: Get template by ID
|
|
61
|
+
console.log("2️⃣ Getting template by ID...");
|
|
62
|
+
const templateId = createResponse.id;
|
|
63
|
+
const getResponse = await client.templates.get(templateId);
|
|
64
|
+
|
|
65
|
+
console.log("✅ Template retrieved successfully!");
|
|
66
|
+
console.log("Response:", JSON.stringify(getResponse, null, 2));
|
|
67
|
+
console.log("\n");
|
|
68
|
+
|
|
69
|
+
// Verify get response structure
|
|
70
|
+
if (!getResponse.id) {
|
|
71
|
+
throw new Error("Get response missing 'id' field");
|
|
72
|
+
}
|
|
73
|
+
if (!getResponse.name) {
|
|
74
|
+
throw new Error("Get response missing 'name' field");
|
|
75
|
+
}
|
|
76
|
+
if (!getResponse.language) {
|
|
77
|
+
throw new Error("Get response missing 'language' field");
|
|
78
|
+
}
|
|
79
|
+
if (!getResponse.status) {
|
|
80
|
+
throw new Error("Get response missing 'status' field");
|
|
81
|
+
}
|
|
82
|
+
if (!getResponse.category) {
|
|
83
|
+
throw new Error("Get response missing 'category' field");
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(getResponse.components)) {
|
|
86
|
+
throw new Error("Get response missing 'components' array");
|
|
87
|
+
}
|
|
88
|
+
console.log("✅ Get response structure verified\n");
|
|
89
|
+
|
|
90
|
+
// Verify components
|
|
91
|
+
const bodyComponent = getResponse.components.find((c) => c.type === "BODY");
|
|
92
|
+
if (!bodyComponent) {
|
|
93
|
+
throw new Error("BODY component not found in response");
|
|
94
|
+
}
|
|
95
|
+
if (bodyComponent.type !== "BODY") {
|
|
96
|
+
throw new Error("BODY component type mismatch");
|
|
97
|
+
}
|
|
98
|
+
console.log("✅ Components verified\n");
|
|
99
|
+
|
|
100
|
+
// Test 3: List templates
|
|
101
|
+
console.log("3️⃣ Listing templates...");
|
|
102
|
+
const listResponse = await client.templates.list();
|
|
103
|
+
console.log(`✅ Found ${listResponse.data.length} template(s)`);
|
|
104
|
+
console.log("Response:", JSON.stringify(listResponse, null, 2));
|
|
105
|
+
console.log("\n");
|
|
106
|
+
|
|
107
|
+
// Verify list response structure
|
|
108
|
+
if (!Array.isArray(listResponse.data)) {
|
|
109
|
+
throw new Error("List response missing 'data' array");
|
|
110
|
+
}
|
|
111
|
+
console.log("✅ List response structure verified\n");
|
|
112
|
+
|
|
113
|
+
// Verify our created template is in the list
|
|
114
|
+
const foundTemplate = listResponse.data.find((t) => t.id === templateId);
|
|
115
|
+
if (!foundTemplate) {
|
|
116
|
+
console.log(
|
|
117
|
+
"⚠️ Note: Created template may not appear in list immediately (async processing)"
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
console.log("✅ Created template found in list\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log("🎉 All tests passed!");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("❌ Test failed:", error);
|
|
126
|
+
if (error instanceof Error) {
|
|
127
|
+
console.error("Error message:", error.message);
|
|
128
|
+
console.error("Error stack:", error.stack);
|
|
129
|
+
}
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
testTemplates();
|
package/src/index.ts
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
// WhatsApp Cloud API SDK
|
|
2
|
+
export { WhatsAppClient } from "./client/index";
|
|
3
|
+
|
|
4
|
+
// Export schemas (AI-ready)
|
|
5
|
+
export * from "./schemas/index";
|
|
6
|
+
|
|
7
|
+
// Export types (primary export point)
|
|
8
|
+
export type * from "./types/index";
|
|
9
|
+
|
|
10
|
+
// Export errors for error handling
|
|
11
|
+
export {
|
|
12
|
+
WhatsAppError,
|
|
13
|
+
WhatsAppValidationError,
|
|
14
|
+
WhatsAppAPIError,
|
|
15
|
+
WhatsAppRateLimitError,
|
|
16
|
+
} from "./errors";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./phone-number";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for phone number response
|
|
5
|
+
* Matches WhatsApp API structure for phone number objects
|
|
6
|
+
*/
|
|
7
|
+
export const phoneNumberResponseSchema = z.object({
|
|
8
|
+
verified_name: z.string(),
|
|
9
|
+
display_phone_number: z.string(),
|
|
10
|
+
id: z.string(),
|
|
11
|
+
quality_rating: z.string(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Schema for phone number list response
|
|
16
|
+
* Matches WhatsApp API structure for GET /phone_numbers endpoint
|
|
17
|
+
*/
|
|
18
|
+
export const phoneNumberListResponseSchema = z.object({
|
|
19
|
+
data: z.array(phoneNumberResponseSchema),
|
|
20
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for WhatsApp Business Account (WABA) response
|
|
5
|
+
* Matches WhatsApp API structure for WABA objects
|
|
6
|
+
*/
|
|
7
|
+
export const businessAccountResponseSchema = z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
name: z.string().optional(),
|
|
10
|
+
account_review_status: z.string().optional(),
|
|
11
|
+
currency: z.string().optional(),
|
|
12
|
+
country: z.string().optional(),
|
|
13
|
+
timezone_id: z.string().optional(),
|
|
14
|
+
business_verification_status: z.string().optional(),
|
|
15
|
+
is_enabled_for_insights: z.boolean().optional(),
|
|
16
|
+
message_template_namespace: z.string().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Schema for WhatsApp Business Accounts list response
|
|
21
|
+
* Matches WhatsApp API structure for GET /whatsapp_business_accounts endpoint
|
|
22
|
+
*
|
|
23
|
+
* Note: The API returns data as an object with numeric string keys (e.g., "0", "1")
|
|
24
|
+
* or as an array, plus optional paging information
|
|
25
|
+
*/
|
|
26
|
+
export const businessAccountsListResponseSchema = z.object({
|
|
27
|
+
data: z.record(z.string(), businessAccountResponseSchema).or(
|
|
28
|
+
z.array(businessAccountResponseSchema)
|
|
29
|
+
),
|
|
30
|
+
paging: z
|
|
31
|
+
.object({
|
|
32
|
+
cursors: z
|
|
33
|
+
.object({
|
|
34
|
+
before: z.string().optional(),
|
|
35
|
+
after: z.string().optional(),
|
|
36
|
+
})
|
|
37
|
+
.optional(),
|
|
38
|
+
next: z.string().url().optional(),
|
|
39
|
+
previous: z.string().url().optional(),
|
|
40
|
+
})
|
|
41
|
+
.optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper message for access token errors
|
|
5
|
+
*/
|
|
6
|
+
const ACCESS_TOKEN_HELP_MESSAGE =
|
|
7
|
+
"Get your access token from Meta for Developers: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Schema for access token with validation and helpful error messages
|
|
11
|
+
*/
|
|
12
|
+
const accessTokenSchema = z
|
|
13
|
+
.string({
|
|
14
|
+
message: `accessToken is required. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
15
|
+
})
|
|
16
|
+
.min(1, {
|
|
17
|
+
message: `accessToken cannot be empty. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
18
|
+
})
|
|
19
|
+
.trim()
|
|
20
|
+
.refine((val) => val.length > 0, {
|
|
21
|
+
message: `accessToken cannot be whitespace only. ${ACCESS_TOKEN_HELP_MESSAGE}`,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Client configuration schema
|
|
26
|
+
*/
|
|
27
|
+
export const clientConfigSchema = z.object({
|
|
28
|
+
accessToken: accessTokenSchema,
|
|
29
|
+
phoneNumberId: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
33
|
+
message: "phoneNumberId cannot be empty or whitespace only",
|
|
34
|
+
}),
|
|
35
|
+
businessAccountId: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
39
|
+
message: "businessAccountId cannot be empty or whitespace only",
|
|
40
|
+
}),
|
|
41
|
+
businessId: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.refine((val) => val === undefined || val.trim().length > 0, {
|
|
45
|
+
message: "businessId cannot be empty or whitespace only",
|
|
46
|
+
}),
|
|
47
|
+
apiVersion: z.string().default("v18.0").optional(),
|
|
48
|
+
baseURL: z.string().url().default("https://graph.facebook.com").optional(),
|
|
49
|
+
timeout: z.number().positive().optional(),
|
|
50
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Schema for debug token response
|
|
5
|
+
* Matches Graph API debug_token endpoint response structure
|
|
6
|
+
*/
|
|
7
|
+
export const debugTokenResponseSchema = z.object({
|
|
8
|
+
data: z.object({
|
|
9
|
+
app_id: z.string().optional(),
|
|
10
|
+
type: z.string().optional(),
|
|
11
|
+
application: z.string().optional(),
|
|
12
|
+
data_access_expires_at: z.number().optional(),
|
|
13
|
+
expires_at: z.number().optional(),
|
|
14
|
+
is_valid: z.boolean().optional(),
|
|
15
|
+
issued_at: z.number().optional(),
|
|
16
|
+
metadata: z
|
|
17
|
+
.object({
|
|
18
|
+
auth_type: z.string().optional(),
|
|
19
|
+
sso: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
scopes: z.array(z.string()).optional(),
|
|
23
|
+
user_id: z.string().optional(),
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base schema for all message requests
|
|
5
|
+
* phoneNumberId is handled at the client level, not in the request object
|
|
6
|
+
*/
|
|
7
|
+
const baseMessageRequestSchema = z.object({
|
|
8
|
+
to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Schema for image object in image messages
|
|
13
|
+
* Matches WhatsApp API structure
|
|
14
|
+
*/
|
|
15
|
+
const imageSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
id: z.string().optional(),
|
|
18
|
+
link: z.string().url().optional(),
|
|
19
|
+
caption: z.string().max(1024).optional(),
|
|
20
|
+
})
|
|
21
|
+
.refine((data) => data.link || data.id, "Either link or id must be provided");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Schema for sending an image message
|
|
25
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
26
|
+
*/
|
|
27
|
+
export const sendImageRequestSchema = baseMessageRequestSchema.extend({
|
|
28
|
+
image: imageSchema,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Schema for text object in text messages
|
|
33
|
+
* Matches WhatsApp API structure
|
|
34
|
+
*/
|
|
35
|
+
const textSchema = z.object({
|
|
36
|
+
body: z.string().min(1).max(4096),
|
|
37
|
+
preview_url: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Schema for sending a text message
|
|
42
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
43
|
+
*/
|
|
44
|
+
export const sendTextRequestSchema = baseMessageRequestSchema.extend({
|
|
45
|
+
text: textSchema,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Schema for location object in location messages
|
|
50
|
+
* Matches WhatsApp API structure
|
|
51
|
+
*/
|
|
52
|
+
const locationSchema = z.object({
|
|
53
|
+
longitude: z.number().min(-180).max(180),
|
|
54
|
+
latitude: z.number().min(-90).max(90),
|
|
55
|
+
name: z.string().optional(),
|
|
56
|
+
address: z.string().optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Schema for sending a location message
|
|
61
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
62
|
+
*/
|
|
63
|
+
export const sendLocationRequestSchema = baseMessageRequestSchema.extend({
|
|
64
|
+
location: locationSchema,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Schema for reaction object in reaction messages
|
|
69
|
+
* Matches WhatsApp API structure
|
|
70
|
+
*/
|
|
71
|
+
const reactionSchema = z.object({
|
|
72
|
+
message_id: z.string().min(1),
|
|
73
|
+
emoji: z.string().min(1).max(1), // Single emoji character
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Schema for sending a reaction message
|
|
78
|
+
* Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
|
|
79
|
+
*/
|
|
80
|
+
export const sendReactionRequestSchema = baseMessageRequestSchema.extend({
|
|
81
|
+
reaction: reactionSchema,
|
|
82
|
+
});
|