whatsapp-cloud 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1332
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/dist/index.d.mts
DELETED
package/dist/index.mjs
DELETED
|
@@ -1,1332 +0,0 @@
|
|
|
1
|
-
// src/schemas/client.ts
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
var ACCESS_TOKEN_HELP_MESSAGE = "Get your access token from Meta for Developers: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started";
|
|
4
|
-
var accessTokenSchema = z.string({
|
|
5
|
-
message: `accessToken is required. ${ACCESS_TOKEN_HELP_MESSAGE}`
|
|
6
|
-
}).min(1, {
|
|
7
|
-
message: `accessToken cannot be empty. ${ACCESS_TOKEN_HELP_MESSAGE}`
|
|
8
|
-
}).trim().refine((val) => val.length > 0, {
|
|
9
|
-
message: `accessToken cannot be whitespace only. ${ACCESS_TOKEN_HELP_MESSAGE}`
|
|
10
|
-
});
|
|
11
|
-
var clientConfigSchema = z.object({
|
|
12
|
-
accessToken: accessTokenSchema,
|
|
13
|
-
phoneNumberId: z.string().optional().refine((val) => val === void 0 || val.trim().length > 0, {
|
|
14
|
-
message: "phoneNumberId cannot be empty or whitespace only"
|
|
15
|
-
}),
|
|
16
|
-
businessAccountId: z.string().optional().refine((val) => val === void 0 || val.trim().length > 0, {
|
|
17
|
-
message: "businessAccountId cannot be empty or whitespace only"
|
|
18
|
-
}),
|
|
19
|
-
businessId: z.string().optional().refine((val) => val === void 0 || val.trim().length > 0, {
|
|
20
|
-
message: "businessId cannot be empty or whitespace only"
|
|
21
|
-
}),
|
|
22
|
-
apiVersion: z.string().default("v18.0").optional(),
|
|
23
|
-
baseURL: z.string().url().default("https://graph.facebook.com").optional(),
|
|
24
|
-
timeout: z.number().positive().optional()
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// src/client/HttpClient.ts
|
|
28
|
-
var HttpClient = class {
|
|
29
|
-
baseURL;
|
|
30
|
-
accessToken;
|
|
31
|
-
phoneNumberId;
|
|
32
|
-
businessAccountId;
|
|
33
|
-
businessId;
|
|
34
|
-
apiVersion;
|
|
35
|
-
constructor(config) {
|
|
36
|
-
this.accessToken = config.accessToken;
|
|
37
|
-
if (config.phoneNumberId !== void 0) {
|
|
38
|
-
this.phoneNumberId = config.phoneNumberId;
|
|
39
|
-
}
|
|
40
|
-
if (config.businessAccountId !== void 0) {
|
|
41
|
-
this.businessAccountId = config.businessAccountId;
|
|
42
|
-
}
|
|
43
|
-
if (config.businessId !== void 0) {
|
|
44
|
-
this.businessId = config.businessId;
|
|
45
|
-
}
|
|
46
|
-
this.apiVersion = config.apiVersion ?? "v18.0";
|
|
47
|
-
this.baseURL = config.baseURL ?? "https://graph.facebook.com";
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Make a POST request
|
|
51
|
-
*/
|
|
52
|
-
async post(path, body) {
|
|
53
|
-
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
54
|
-
const response = await fetch(url, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: {
|
|
57
|
-
"Content-Type": "application/json",
|
|
58
|
-
Authorization: `Bearer ${this.accessToken}`
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify(body)
|
|
61
|
-
});
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
const error = await response.json().catch(() => ({
|
|
64
|
-
error: {
|
|
65
|
-
message: response.statusText,
|
|
66
|
-
code: response.status
|
|
67
|
-
}
|
|
68
|
-
}));
|
|
69
|
-
throw new Error(
|
|
70
|
-
`API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
return response.json();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Make a GET request
|
|
77
|
-
*/
|
|
78
|
-
async get(path) {
|
|
79
|
-
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
80
|
-
const response = await fetch(url, {
|
|
81
|
-
method: "GET",
|
|
82
|
-
headers: {
|
|
83
|
-
Authorization: `Bearer ${this.accessToken}`
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
const error = await response.json().catch(() => ({
|
|
88
|
-
error: {
|
|
89
|
-
message: response.statusText,
|
|
90
|
-
code: response.status
|
|
91
|
-
}
|
|
92
|
-
}));
|
|
93
|
-
throw new Error(
|
|
94
|
-
`API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return response.json();
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Make a GET request and return binary data (ArrayBuffer)
|
|
101
|
-
* Useful for downloading media files
|
|
102
|
-
*/
|
|
103
|
-
async getBinary(path) {
|
|
104
|
-
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
105
|
-
const response = await fetch(url, {
|
|
106
|
-
method: "GET",
|
|
107
|
-
headers: {
|
|
108
|
-
Authorization: `Bearer ${this.accessToken}`
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
if (!response.ok) {
|
|
112
|
-
let errorMessage = `API Error: ${response.statusText}`;
|
|
113
|
-
try {
|
|
114
|
-
const error = await response.json();
|
|
115
|
-
errorMessage = `API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`;
|
|
116
|
-
} catch {
|
|
117
|
-
}
|
|
118
|
-
throw new Error(errorMessage);
|
|
119
|
-
}
|
|
120
|
-
return response.arrayBuffer();
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Make a PATCH request
|
|
124
|
-
*/
|
|
125
|
-
async patch(path, body) {
|
|
126
|
-
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
127
|
-
const response = await fetch(url, {
|
|
128
|
-
method: "PATCH",
|
|
129
|
-
headers: {
|
|
130
|
-
"Content-Type": "application/json",
|
|
131
|
-
Authorization: `Bearer ${this.accessToken}`
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify(body)
|
|
134
|
-
});
|
|
135
|
-
if (!response.ok) {
|
|
136
|
-
const error = await response.json().catch(() => ({
|
|
137
|
-
error: {
|
|
138
|
-
message: response.statusText,
|
|
139
|
-
code: response.status
|
|
140
|
-
}
|
|
141
|
-
}));
|
|
142
|
-
throw new Error(
|
|
143
|
-
`API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
return response.json();
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Make a DELETE request
|
|
150
|
-
*/
|
|
151
|
-
async delete(path) {
|
|
152
|
-
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
153
|
-
const response = await fetch(url, {
|
|
154
|
-
method: "DELETE",
|
|
155
|
-
headers: {
|
|
156
|
-
Authorization: `Bearer ${this.accessToken}`
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
const error = await response.json().catch(() => ({
|
|
161
|
-
error: {
|
|
162
|
-
message: response.statusText,
|
|
163
|
-
code: response.status
|
|
164
|
-
}
|
|
165
|
-
}));
|
|
166
|
-
throw new Error(
|
|
167
|
-
`API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
return response.json();
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// src/schemas/messages/request.ts
|
|
175
|
-
import { z as z2 } from "zod";
|
|
176
|
-
var baseMessageRequestSchema = z2.object({
|
|
177
|
-
to: z2.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format")
|
|
178
|
-
});
|
|
179
|
-
var imageSchema = z2.object({
|
|
180
|
-
id: z2.string().optional(),
|
|
181
|
-
link: z2.string().url().optional(),
|
|
182
|
-
caption: z2.string().max(1024).optional()
|
|
183
|
-
}).refine((data) => data.link || data.id, "Either link or id must be provided");
|
|
184
|
-
var sendImageRequestSchema = baseMessageRequestSchema.extend({
|
|
185
|
-
image: imageSchema
|
|
186
|
-
});
|
|
187
|
-
var textSchema = z2.object({
|
|
188
|
-
body: z2.string().min(1).max(4096),
|
|
189
|
-
preview_url: z2.boolean().optional()
|
|
190
|
-
});
|
|
191
|
-
var sendTextRequestSchema = baseMessageRequestSchema.extend({
|
|
192
|
-
text: textSchema
|
|
193
|
-
});
|
|
194
|
-
var locationSchema = z2.object({
|
|
195
|
-
longitude: z2.number().min(-180).max(180),
|
|
196
|
-
latitude: z2.number().min(-90).max(90),
|
|
197
|
-
name: z2.string().optional(),
|
|
198
|
-
address: z2.string().optional()
|
|
199
|
-
});
|
|
200
|
-
var sendLocationRequestSchema = baseMessageRequestSchema.extend({
|
|
201
|
-
location: locationSchema
|
|
202
|
-
});
|
|
203
|
-
var reactionSchema = z2.object({
|
|
204
|
-
message_id: z2.string().min(1),
|
|
205
|
-
emoji: z2.string().min(1).max(1)
|
|
206
|
-
// Single emoji character
|
|
207
|
-
});
|
|
208
|
-
var sendReactionRequestSchema = baseMessageRequestSchema.extend({
|
|
209
|
-
reaction: reactionSchema
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// src/services/messages/utils/build-message-payload.ts
|
|
213
|
-
function buildMessagePayload(to, type, content) {
|
|
214
|
-
return {
|
|
215
|
-
messaging_product: "whatsapp",
|
|
216
|
-
recipient_type: "individual",
|
|
217
|
-
to,
|
|
218
|
-
type,
|
|
219
|
-
...content
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// src/utils/zod-error.ts
|
|
224
|
-
import "zod";
|
|
225
|
-
|
|
226
|
-
// src/errors.ts
|
|
227
|
-
var WhatsAppError = class extends Error {
|
|
228
|
-
constructor(message) {
|
|
229
|
-
super(message);
|
|
230
|
-
this.name = this.constructor.name;
|
|
231
|
-
const captureStackTrace = Error.captureStackTrace;
|
|
232
|
-
if (typeof captureStackTrace === "function") {
|
|
233
|
-
captureStackTrace(this, this.constructor);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
var WhatsAppValidationError = class extends WhatsAppError {
|
|
238
|
-
constructor(message, field, issues) {
|
|
239
|
-
super(message);
|
|
240
|
-
this.field = field;
|
|
241
|
-
this.issues = issues;
|
|
242
|
-
this.name = "WhatsAppValidationError";
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
var WhatsAppAPIError = class extends WhatsAppError {
|
|
246
|
-
constructor(code, type, message, statusCode, details) {
|
|
247
|
-
super(message);
|
|
248
|
-
this.code = code;
|
|
249
|
-
this.type = type;
|
|
250
|
-
this.statusCode = statusCode;
|
|
251
|
-
this.details = details;
|
|
252
|
-
this.name = "WhatsAppAPIError";
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
var WhatsAppRateLimitError = class extends WhatsAppAPIError {
|
|
256
|
-
constructor(message, retryAfter) {
|
|
257
|
-
super(131056, "rate_limit", message, 429, { retryAfter });
|
|
258
|
-
this.retryAfter = retryAfter;
|
|
259
|
-
this.name = "WhatsAppRateLimitError";
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// src/utils/zod-error.ts
|
|
264
|
-
function transformZodError(error) {
|
|
265
|
-
const issues = error.issues.map((err) => ({
|
|
266
|
-
path: err.path,
|
|
267
|
-
message: err.message
|
|
268
|
-
}));
|
|
269
|
-
const firstError = error.issues[0];
|
|
270
|
-
if (firstError) {
|
|
271
|
-
return new WhatsAppValidationError(
|
|
272
|
-
firstError.message,
|
|
273
|
-
typeof firstError.path[0] === "string" ? firstError.path[0] : void 0,
|
|
274
|
-
issues
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
return new WhatsAppValidationError("Validation failed", void 0, issues);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// src/services/messages/methods/send-text.ts
|
|
281
|
-
async function sendText(messagesClient, request) {
|
|
282
|
-
const result = sendTextRequestSchema.safeParse(request);
|
|
283
|
-
if (!result.success) {
|
|
284
|
-
throw transformZodError(result.error);
|
|
285
|
-
}
|
|
286
|
-
const data = result.data;
|
|
287
|
-
const payload = buildMessagePayload(data.to, "text", {
|
|
288
|
-
text: data.text
|
|
289
|
-
});
|
|
290
|
-
return messagesClient.post("/messages", payload);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// src/services/messages/methods/send-image.ts
|
|
294
|
-
async function sendImage(messagesClient, request) {
|
|
295
|
-
const result = sendImageRequestSchema.safeParse(request);
|
|
296
|
-
if (!result.success) {
|
|
297
|
-
throw transformZodError(result.error);
|
|
298
|
-
}
|
|
299
|
-
const data = result.data;
|
|
300
|
-
const payload = buildMessagePayload(data.to, "image", {
|
|
301
|
-
image: data.image
|
|
302
|
-
});
|
|
303
|
-
return messagesClient.post("/messages", payload);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/services/messages/methods/send-location.ts
|
|
307
|
-
async function sendLocation(messagesClient, request) {
|
|
308
|
-
const result = sendLocationRequestSchema.safeParse(request);
|
|
309
|
-
if (!result.success) {
|
|
310
|
-
throw transformZodError(result.error);
|
|
311
|
-
}
|
|
312
|
-
const data = result.data;
|
|
313
|
-
const payload = buildMessagePayload(data.to, "location", {
|
|
314
|
-
location: data.location
|
|
315
|
-
});
|
|
316
|
-
return messagesClient.post("/messages", payload);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// src/services/messages/methods/send-reaction.ts
|
|
320
|
-
async function sendReaction(messagesClient, request) {
|
|
321
|
-
const result = sendReactionRequestSchema.safeParse(request);
|
|
322
|
-
if (!result.success) {
|
|
323
|
-
throw transformZodError(result.error);
|
|
324
|
-
}
|
|
325
|
-
const data = result.data;
|
|
326
|
-
const payload = buildMessagePayload(data.to, "reaction", {
|
|
327
|
-
reaction: data.reaction
|
|
328
|
-
});
|
|
329
|
-
return messagesClient.post("/messages", payload);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// src/services/messages/MessagesClient.ts
|
|
333
|
-
var MessagesClient = class {
|
|
334
|
-
constructor(httpClient, phoneNumberId) {
|
|
335
|
-
this.httpClient = httpClient;
|
|
336
|
-
this.phoneNumberId = phoneNumberId;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Make a POST request with phone number ID prefix
|
|
340
|
-
*/
|
|
341
|
-
async post(path, body) {
|
|
342
|
-
return this.httpClient.post(`/${this.phoneNumberId}${path}`, body);
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Make a GET request with phone number ID prefix
|
|
346
|
-
*/
|
|
347
|
-
async get(path) {
|
|
348
|
-
return this.httpClient.get(`/${this.phoneNumberId}${path}`);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Make a PATCH request with phone number ID prefix
|
|
352
|
-
*/
|
|
353
|
-
async patch(path, body) {
|
|
354
|
-
return this.httpClient.patch(`/${this.phoneNumberId}${path}`, body);
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
// src/services/messages/MessagesService.ts
|
|
359
|
-
var MessagesService = class {
|
|
360
|
-
constructor(httpClient) {
|
|
361
|
-
this.httpClient = httpClient;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Helper to create a Scoped Client (prefer override, fallback to config)
|
|
365
|
-
*/
|
|
366
|
-
getClient(overrideId) {
|
|
367
|
-
const id = overrideId || this.httpClient.phoneNumberId;
|
|
368
|
-
if (!id) {
|
|
369
|
-
throw new WhatsAppValidationError(
|
|
370
|
-
"phoneNumberId is required. Provide it in WhatsAppClient config or as a parameter.",
|
|
371
|
-
"phoneNumberId"
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
return new MessagesClient(this.httpClient, id);
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Send a text message
|
|
378
|
-
*
|
|
379
|
-
* @param request - Text message request (to, text)
|
|
380
|
-
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
381
|
-
*/
|
|
382
|
-
async sendText(request, phoneNumberId) {
|
|
383
|
-
const client = this.getClient(phoneNumberId);
|
|
384
|
-
return sendText(client, request);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Send an image message
|
|
388
|
-
*
|
|
389
|
-
* @param request - Image message request (to, image)
|
|
390
|
-
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
391
|
-
*/
|
|
392
|
-
async sendImage(request, phoneNumberId) {
|
|
393
|
-
const client = this.getClient(phoneNumberId);
|
|
394
|
-
return sendImage(client, request);
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Send a location message
|
|
398
|
-
*
|
|
399
|
-
* @param request - Location message request (to, location)
|
|
400
|
-
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
401
|
-
*/
|
|
402
|
-
async sendLocation(request, phoneNumberId) {
|
|
403
|
-
const client = this.getClient(phoneNumberId);
|
|
404
|
-
return sendLocation(client, request);
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Send a reaction message
|
|
408
|
-
*
|
|
409
|
-
* @param request - Reaction message request (to, reaction)
|
|
410
|
-
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
411
|
-
*/
|
|
412
|
-
async sendReaction(request, phoneNumberId) {
|
|
413
|
-
const client = this.getClient(phoneNumberId);
|
|
414
|
-
return sendReaction(client, request);
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
// src/services/accounts/AccountsClient.ts
|
|
419
|
-
var AccountsClient = class {
|
|
420
|
-
constructor(httpClient, businessAccountId) {
|
|
421
|
-
this.httpClient = httpClient;
|
|
422
|
-
this.businessAccountId = businessAccountId;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Make a GET request with WABA ID prefix
|
|
426
|
-
*/
|
|
427
|
-
async get(path) {
|
|
428
|
-
return this.httpClient.get(`/${this.businessAccountId}${path}`);
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Make a POST request with WABA ID prefix
|
|
432
|
-
*/
|
|
433
|
-
async post(path, body) {
|
|
434
|
-
return this.httpClient.post(`/${this.businessAccountId}${path}`, body);
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Make a PATCH request with WABA ID prefix
|
|
438
|
-
*/
|
|
439
|
-
async patch(path, body) {
|
|
440
|
-
return this.httpClient.patch(`/${this.businessAccountId}${path}`, body);
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// src/services/accounts/methods/list-phone-numbers.ts
|
|
445
|
-
async function listPhoneNumbers(accountsClient) {
|
|
446
|
-
return accountsClient.get("/phone_numbers");
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// src/services/accounts/AccountsService.ts
|
|
450
|
-
var AccountsService = class {
|
|
451
|
-
constructor(httpClient) {
|
|
452
|
-
this.httpClient = httpClient;
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Helper to create a Scoped Client (prefer override, fallback to config)
|
|
456
|
-
*/
|
|
457
|
-
getClient(overrideId) {
|
|
458
|
-
const id = overrideId || this.httpClient.businessAccountId;
|
|
459
|
-
if (!id) {
|
|
460
|
-
throw new WhatsAppValidationError(
|
|
461
|
-
"businessAccountId (WABA ID) is required. Provide it in WhatsAppClient config or as a parameter.",
|
|
462
|
-
"businessAccountId"
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
return new AccountsClient(this.httpClient, id);
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* List phone numbers for a WhatsApp Business Account
|
|
469
|
-
*
|
|
470
|
-
* @param businessAccountId - Optional WABA ID (overrides client config)
|
|
471
|
-
* @returns List of phone numbers associated with the WABA
|
|
472
|
-
*/
|
|
473
|
-
async listPhoneNumbers(businessAccountId) {
|
|
474
|
-
const client = this.getClient(businessAccountId);
|
|
475
|
-
return listPhoneNumbers(client);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
// src/services/business/BusinessClient.ts
|
|
480
|
-
var BusinessClient = class {
|
|
481
|
-
constructor(httpClient, businessId) {
|
|
482
|
-
this.httpClient = httpClient;
|
|
483
|
-
this.businessId = businessId;
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Make a GET request with Business Portfolio ID prefix
|
|
487
|
-
*/
|
|
488
|
-
async get(path) {
|
|
489
|
-
return this.httpClient.get(`/${this.businessId}${path}`);
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Make a POST request with Business Portfolio ID prefix
|
|
493
|
-
*/
|
|
494
|
-
async post(path, body) {
|
|
495
|
-
return this.httpClient.post(`/${this.businessId}${path}`, body);
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Make a PATCH request with Business Portfolio ID prefix
|
|
499
|
-
*/
|
|
500
|
-
async patch(path, body) {
|
|
501
|
-
return this.httpClient.patch(`/${this.businessId}${path}`, body);
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
// src/services/business/methods/list-accounts.ts
|
|
506
|
-
async function listAccounts(businessClient) {
|
|
507
|
-
return businessClient.get(
|
|
508
|
-
"/whatsapp_business_accounts"
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/services/business/BusinessService.ts
|
|
513
|
-
var BusinessService = class {
|
|
514
|
-
constructor(httpClient) {
|
|
515
|
-
this.httpClient = httpClient;
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Helper to create a Scoped Client (prefer override, fallback to config)
|
|
519
|
-
*/
|
|
520
|
-
getClient(overrideId) {
|
|
521
|
-
const id = overrideId || this.httpClient.businessId;
|
|
522
|
-
if (!id) {
|
|
523
|
-
throw new WhatsAppValidationError(
|
|
524
|
-
"businessId (Business Portfolio ID) is required. Provide it in WhatsAppClient config or as a parameter.",
|
|
525
|
-
"businessId"
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
return new BusinessClient(this.httpClient, id);
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* List WhatsApp Business Accounts (WABAs) for a Business Portfolio
|
|
532
|
-
*
|
|
533
|
-
* @param businessId - Optional Business Portfolio ID (overrides client config)
|
|
534
|
-
* @returns List of WABAs associated with the Business Portfolio
|
|
535
|
-
*/
|
|
536
|
-
async listAccounts(businessId) {
|
|
537
|
-
const client = this.getClient(businessId);
|
|
538
|
-
return listAccounts(client);
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
// src/services/templates/TemplatesClient.ts
|
|
543
|
-
var TemplatesClient = class {
|
|
544
|
-
constructor(httpClient, businessAccountId) {
|
|
545
|
-
this.httpClient = httpClient;
|
|
546
|
-
this.businessAccountId = businessAccountId;
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Make a GET request with WABA ID prefix
|
|
550
|
-
*/
|
|
551
|
-
async get(path) {
|
|
552
|
-
return this.httpClient.get(`/${this.businessAccountId}${path}`);
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Make a POST request with WABA ID prefix
|
|
556
|
-
*/
|
|
557
|
-
async post(path, body) {
|
|
558
|
-
return this.httpClient.post(`/${this.businessAccountId}${path}`, body);
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Make a DELETE request with WABA ID prefix
|
|
562
|
-
*/
|
|
563
|
-
async delete(path) {
|
|
564
|
-
return this.httpClient.delete(`/${this.businessAccountId}${path}`);
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
// src/schemas/templates/request.ts
|
|
569
|
-
import { z as z4 } from "zod";
|
|
570
|
-
|
|
571
|
-
// src/schemas/templates/component.ts
|
|
572
|
-
import { z as z3 } from "zod";
|
|
573
|
-
var templateQuickReplyButtonSchema = z3.object({
|
|
574
|
-
type: z3.literal("QUICK_REPLY"),
|
|
575
|
-
text: z3.string().min(1).max(25, "Button text must be 25 characters or less")
|
|
576
|
-
});
|
|
577
|
-
var templateUrlButtonSchema = z3.object({
|
|
578
|
-
type: z3.literal("URL"),
|
|
579
|
-
text: z3.string().min(1).max(25, "Button text must be 25 characters or less"),
|
|
580
|
-
url: z3.string().url().max(2e3, "URL must be 2000 characters or less")
|
|
581
|
-
// example: z.array(z.string()).optional(), // For later: when URL contains variables
|
|
582
|
-
});
|
|
583
|
-
var templatePhoneNumberButtonSchema = z3.object({
|
|
584
|
-
type: z3.literal("PHONE_NUMBER"),
|
|
585
|
-
text: z3.string().min(1).max(25, "Button text must be 25 characters or less"),
|
|
586
|
-
phone_number: z3.string().min(1).max(20, "Phone number must be 20 characters or less")
|
|
587
|
-
});
|
|
588
|
-
var templateCopyCodeButtonSchema = z3.object({
|
|
589
|
-
type: z3.literal("COPY_CODE")
|
|
590
|
-
// example: z.string().max(15).optional(), // For later: example value to copy
|
|
591
|
-
});
|
|
592
|
-
var templateFlowButtonSchema = z3.object({
|
|
593
|
-
type: z3.literal("FLOW"),
|
|
594
|
-
text: z3.string().min(1).max(25, "Button text must be 25 characters or less"),
|
|
595
|
-
flow_action: z3.string().optional(),
|
|
596
|
-
flow_id: z3.string().optional(),
|
|
597
|
-
navigate_screen: z3.string().optional()
|
|
598
|
-
});
|
|
599
|
-
var templateButtonSchema = z3.discriminatedUnion("type", [
|
|
600
|
-
templateQuickReplyButtonSchema,
|
|
601
|
-
templateUrlButtonSchema,
|
|
602
|
-
templatePhoneNumberButtonSchema,
|
|
603
|
-
templateCopyCodeButtonSchema,
|
|
604
|
-
templateFlowButtonSchema
|
|
605
|
-
]);
|
|
606
|
-
var templateHeaderComponentSchema = z3.object({
|
|
607
|
-
type: z3.literal("HEADER"),
|
|
608
|
-
format: z3.enum(["TEXT", "IMAGE", "VIDEO", "DOCUMENT", "LOCATION"]),
|
|
609
|
-
text: z3.string().max(60, "Header text must be 60 characters or less").optional(),
|
|
610
|
-
example: z3.object({
|
|
611
|
-
header_handle: z3.array(z3.string()).min(1, "At least one header_handle is required")
|
|
612
|
-
}).optional()
|
|
613
|
-
}).refine(
|
|
614
|
-
(data) => {
|
|
615
|
-
if (data.format === "TEXT" && !data.text) {
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
618
|
-
if (data.format === "LOCATION") {
|
|
619
|
-
return true;
|
|
620
|
-
}
|
|
621
|
-
if (["IMAGE", "VIDEO", "DOCUMENT"].includes(data.format)) {
|
|
622
|
-
if (!data.example || !data.example.header_handle || data.example.header_handle.length === 0) {
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return true;
|
|
627
|
-
},
|
|
628
|
-
{
|
|
629
|
-
message: "TEXT format requires text field; IMAGE/VIDEO/DOCUMENT formats require example.header_handle"
|
|
630
|
-
}
|
|
631
|
-
).refine(
|
|
632
|
-
(data) => {
|
|
633
|
-
if (data.format === "TEXT") {
|
|
634
|
-
return !!data.text;
|
|
635
|
-
}
|
|
636
|
-
return true;
|
|
637
|
-
},
|
|
638
|
-
{
|
|
639
|
-
message: "TEXT format header requires text field"
|
|
640
|
-
}
|
|
641
|
-
).refine(
|
|
642
|
-
(data) => {
|
|
643
|
-
if (["IMAGE", "VIDEO", "DOCUMENT"].includes(data.format)) {
|
|
644
|
-
return !!(data.example?.header_handle && data.example.header_handle.length > 0);
|
|
645
|
-
}
|
|
646
|
-
return true;
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
message: "IMAGE/VIDEO/DOCUMENT format header requires example.header_handle (asset handle from Resumable Upload API)"
|
|
650
|
-
}
|
|
651
|
-
);
|
|
652
|
-
var templateBodyComponentSchema = z3.object({
|
|
653
|
-
type: z3.literal("BODY"),
|
|
654
|
-
text: z3.string().min(1).max(1024, "Body text must be 1024 characters or less")
|
|
655
|
-
// example: z.object({...}).optional(), // For later: when using variables
|
|
656
|
-
});
|
|
657
|
-
var templateFooterComponentSchema = z3.object({
|
|
658
|
-
type: z3.literal("FOOTER"),
|
|
659
|
-
text: z3.string().min(1).max(60, "Footer text must be 60 characters or less")
|
|
660
|
-
});
|
|
661
|
-
var templateButtonsComponentSchema = z3.object({
|
|
662
|
-
type: z3.literal("BUTTONS"),
|
|
663
|
-
buttons: z3.array(templateButtonSchema).min(1).max(10, "Maximum 10 buttons allowed")
|
|
664
|
-
});
|
|
665
|
-
var templateComponentSchema = z3.discriminatedUnion("type", [
|
|
666
|
-
templateHeaderComponentSchema,
|
|
667
|
-
templateBodyComponentSchema,
|
|
668
|
-
templateFooterComponentSchema,
|
|
669
|
-
templateButtonsComponentSchema
|
|
670
|
-
]);
|
|
671
|
-
|
|
672
|
-
// src/schemas/templates/request.ts
|
|
673
|
-
var templateCreateSchema = z4.object({
|
|
674
|
-
name: z4.string().min(1).max(512, "Template name must be 512 characters or less"),
|
|
675
|
-
language: z4.string().min(2).max(5, "Language code must be 2-5 characters (e.g., 'en' or 'en_US')"),
|
|
676
|
-
category: z4.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]),
|
|
677
|
-
components: z4.array(templateComponentSchema).min(1, "At least one component is required").refine(
|
|
678
|
-
(components) => {
|
|
679
|
-
return components.some((c) => c.type === "BODY");
|
|
680
|
-
},
|
|
681
|
-
{ message: "BODY component is required" }
|
|
682
|
-
).refine(
|
|
683
|
-
(components) => {
|
|
684
|
-
const headers = components.filter((c) => c.type === "HEADER");
|
|
685
|
-
return headers.length <= 1;
|
|
686
|
-
},
|
|
687
|
-
{ message: "Only one HEADER component is allowed" }
|
|
688
|
-
).refine(
|
|
689
|
-
(components) => {
|
|
690
|
-
const footers = components.filter((c) => c.type === "FOOTER");
|
|
691
|
-
return footers.length <= 1;
|
|
692
|
-
},
|
|
693
|
-
{ message: "Only one FOOTER component is allowed" }
|
|
694
|
-
).refine(
|
|
695
|
-
(components) => {
|
|
696
|
-
const buttons = components.filter((c) => c.type === "BUTTONS");
|
|
697
|
-
return buttons.length <= 1;
|
|
698
|
-
},
|
|
699
|
-
{ message: "Only one BUTTONS component is allowed" }
|
|
700
|
-
)
|
|
701
|
-
});
|
|
702
|
-
var templateUpdateSchema = z4.object({
|
|
703
|
-
category: z4.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]).optional(),
|
|
704
|
-
components: z4.array(templateComponentSchema).optional(),
|
|
705
|
-
language: z4.string().min(2).max(5).optional(),
|
|
706
|
-
name: z4.string().min(1).max(512).optional()
|
|
707
|
-
});
|
|
708
|
-
var templateListSchema = z4.object({
|
|
709
|
-
name: z4.string().optional()
|
|
710
|
-
// Filter by template name
|
|
711
|
-
});
|
|
712
|
-
var templateDeleteSchema = z4.object({
|
|
713
|
-
name: z4.string().optional(),
|
|
714
|
-
hsm_id: z4.string().optional()
|
|
715
|
-
}).refine((data) => data.name || data.hsm_id, {
|
|
716
|
-
message: "Either name or hsm_id must be provided"
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// src/services/templates/methods/create.ts
|
|
720
|
-
async function createTemplate(templatesClient, request) {
|
|
721
|
-
const result = templateCreateSchema.safeParse(request);
|
|
722
|
-
if (!result.success) {
|
|
723
|
-
throw transformZodError(result.error);
|
|
724
|
-
}
|
|
725
|
-
const data = result.data;
|
|
726
|
-
return templatesClient.post(
|
|
727
|
-
"/message_templates",
|
|
728
|
-
data
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// src/services/templates/methods/list.ts
|
|
733
|
-
async function listTemplates(templatesClient, options) {
|
|
734
|
-
if (options) {
|
|
735
|
-
const result = templateListSchema.safeParse(options);
|
|
736
|
-
if (!result.success) {
|
|
737
|
-
throw transformZodError(result.error);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
const params = new URLSearchParams();
|
|
741
|
-
if (options?.name) {
|
|
742
|
-
params.append("name", options.name);
|
|
743
|
-
}
|
|
744
|
-
const queryString = params.toString();
|
|
745
|
-
const path = queryString ? `/message_templates?${queryString}` : "/message_templates";
|
|
746
|
-
return templatesClient.get(path);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// src/services/templates/methods/get.ts
|
|
750
|
-
async function getTemplate(httpClient, templateId) {
|
|
751
|
-
if (!templateId || templateId.trim().length === 0) {
|
|
752
|
-
throw new Error("Template ID is required");
|
|
753
|
-
}
|
|
754
|
-
return httpClient.get(`/${templateId}`);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// src/services/templates/methods/update.ts
|
|
758
|
-
async function updateTemplate(httpClient, templateId, request) {
|
|
759
|
-
if (!templateId || templateId.trim().length === 0) {
|
|
760
|
-
throw new Error("Template ID is required");
|
|
761
|
-
}
|
|
762
|
-
const result = templateUpdateSchema.safeParse(request);
|
|
763
|
-
if (!result.success) {
|
|
764
|
-
throw transformZodError(result.error);
|
|
765
|
-
}
|
|
766
|
-
const data = result.data;
|
|
767
|
-
return httpClient.post(`/${templateId}`, data);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// src/services/templates/methods/delete.ts
|
|
771
|
-
async function deleteTemplate(templatesClient, options) {
|
|
772
|
-
const result = templateDeleteSchema.safeParse(options);
|
|
773
|
-
if (!result.success) {
|
|
774
|
-
throw transformZodError(result.error);
|
|
775
|
-
}
|
|
776
|
-
const data = result.data;
|
|
777
|
-
const params = new URLSearchParams();
|
|
778
|
-
if (data.name) {
|
|
779
|
-
params.append("name", data.name);
|
|
780
|
-
}
|
|
781
|
-
if (data.hsm_id) {
|
|
782
|
-
params.append("hsm_id", data.hsm_id);
|
|
783
|
-
}
|
|
784
|
-
const queryString = params.toString();
|
|
785
|
-
const path = `/message_templates?${queryString}`;
|
|
786
|
-
return templatesClient.delete(path);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// src/services/templates/TemplatesService.ts
|
|
790
|
-
var TemplatesService = class {
|
|
791
|
-
constructor(httpClient) {
|
|
792
|
-
this.httpClient = httpClient;
|
|
793
|
-
}
|
|
794
|
-
/**
|
|
795
|
-
* Helper to create a Scoped Client (prefer override, fallback to config)
|
|
796
|
-
*/
|
|
797
|
-
getClient(overrideId) {
|
|
798
|
-
const id = overrideId || this.httpClient.businessAccountId;
|
|
799
|
-
if (!id) {
|
|
800
|
-
throw new WhatsAppValidationError(
|
|
801
|
-
"businessAccountId (WABA ID) is required for templates. Provide it in WhatsAppClient config or as a parameter.",
|
|
802
|
-
"businessAccountId"
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
return new TemplatesClient(this.httpClient, id);
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Create a message template
|
|
809
|
-
*
|
|
810
|
-
* @param request - Template creation request
|
|
811
|
-
* @param businessAccountId - Optional WABA ID (overrides client config)
|
|
812
|
-
*/
|
|
813
|
-
async create(request, businessAccountId) {
|
|
814
|
-
const client = this.getClient(businessAccountId);
|
|
815
|
-
return createTemplate(client, request);
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* List message templates
|
|
819
|
-
*
|
|
820
|
-
* @param options - Optional filter options (name)
|
|
821
|
-
* @param businessAccountId - Optional WABA ID (overrides client config)
|
|
822
|
-
*/
|
|
823
|
-
async list(options, businessAccountId) {
|
|
824
|
-
const client = this.getClient(businessAccountId);
|
|
825
|
-
return listTemplates(client, options);
|
|
826
|
-
}
|
|
827
|
-
/**
|
|
828
|
-
* Get a template by ID
|
|
829
|
-
*
|
|
830
|
-
* Note: This uses the template ID directly (no WABA prefix needed)
|
|
831
|
-
*
|
|
832
|
-
* @param templateId - Template ID
|
|
833
|
-
*/
|
|
834
|
-
async get(templateId) {
|
|
835
|
-
return getTemplate(this.httpClient, templateId);
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Update a template
|
|
839
|
-
*
|
|
840
|
-
* Note: This uses the template ID directly (no WABA prefix needed)
|
|
841
|
-
*
|
|
842
|
-
* @param templateId - Template ID
|
|
843
|
-
* @param request - Template update request
|
|
844
|
-
*/
|
|
845
|
-
async update(templateId, request) {
|
|
846
|
-
return updateTemplate(this.httpClient, templateId, request);
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Delete a template
|
|
850
|
-
*
|
|
851
|
-
* @param options - Delete options (name or hsm_id)
|
|
852
|
-
* @param businessAccountId - Optional WABA ID (overrides client config)
|
|
853
|
-
*/
|
|
854
|
-
async delete(options, businessAccountId) {
|
|
855
|
-
const client = this.getClient(businessAccountId);
|
|
856
|
-
return deleteTemplate(client, options);
|
|
857
|
-
}
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
// src/services/webhooks/utils/extract-messages.ts
|
|
861
|
-
function extractMessages(payload) {
|
|
862
|
-
const messages = [];
|
|
863
|
-
for (const entry of payload.entry) {
|
|
864
|
-
for (const change of entry.changes) {
|
|
865
|
-
if (change.field === "messages" && change.value.messages) {
|
|
866
|
-
messages.push(...change.value.messages);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
return messages;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// src/services/webhooks/utils/extract-statuses.ts
|
|
874
|
-
function extractStatuses(payload) {
|
|
875
|
-
const statuses = [];
|
|
876
|
-
for (const entry of payload.entry) {
|
|
877
|
-
for (const change of entry.changes) {
|
|
878
|
-
if (change.field === "messages" && change.value.statuses) {
|
|
879
|
-
statuses.push(...change.value.statuses);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return statuses;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// src/services/webhooks/utils/verify.ts
|
|
887
|
-
function verifyWebhook(query, verifyToken) {
|
|
888
|
-
const mode = query["hub.mode"];
|
|
889
|
-
const token = query["hub.verify_token"];
|
|
890
|
-
const challenge = query["hub.challenge"];
|
|
891
|
-
if (mode === "subscribe" && token === verifyToken && challenge) {
|
|
892
|
-
return challenge;
|
|
893
|
-
}
|
|
894
|
-
return null;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// src/schemas/webhooks/payload.ts
|
|
898
|
-
import { z as z6 } from "zod";
|
|
899
|
-
|
|
900
|
-
// src/schemas/webhooks/incoming-message.ts
|
|
901
|
-
import { z as z5 } from "zod";
|
|
902
|
-
var baseIncomingMessageSchema = z5.object({
|
|
903
|
-
from: z5.string(),
|
|
904
|
-
// WhatsApp ID (phone number without +)
|
|
905
|
-
id: z5.string(),
|
|
906
|
-
// Message ID (wamid.*)
|
|
907
|
-
timestamp: z5.string(),
|
|
908
|
-
// Unix timestamp as string
|
|
909
|
-
type: z5.string()
|
|
910
|
-
// Message type discriminator
|
|
911
|
-
});
|
|
912
|
-
var incomingTextContentSchema = z5.object({
|
|
913
|
-
body: z5.string()
|
|
914
|
-
});
|
|
915
|
-
var incomingAudioContentSchema = z5.object({
|
|
916
|
-
id: z5.string(),
|
|
917
|
-
// Media ID for downloading
|
|
918
|
-
mime_type: z5.string().optional()
|
|
919
|
-
// e.g., "audio/ogg; codecs=opus"
|
|
920
|
-
});
|
|
921
|
-
var incomingImageContentSchema = z5.object({
|
|
922
|
-
id: z5.string(),
|
|
923
|
-
// Media ID for downloading
|
|
924
|
-
mime_type: z5.string().optional(),
|
|
925
|
-
// e.g., "image/jpeg"
|
|
926
|
-
caption: z5.string().optional()
|
|
927
|
-
// Optional caption text
|
|
928
|
-
});
|
|
929
|
-
var incomingTextMessageSchema = baseIncomingMessageSchema.extend({
|
|
930
|
-
type: z5.literal("text"),
|
|
931
|
-
text: incomingTextContentSchema
|
|
932
|
-
});
|
|
933
|
-
var incomingAudioMessageSchema = baseIncomingMessageSchema.extend({
|
|
934
|
-
type: z5.literal("audio"),
|
|
935
|
-
audio: incomingAudioContentSchema
|
|
936
|
-
});
|
|
937
|
-
var incomingImageMessageSchema = baseIncomingMessageSchema.extend({
|
|
938
|
-
type: z5.literal("image"),
|
|
939
|
-
image: incomingImageContentSchema
|
|
940
|
-
});
|
|
941
|
-
var incomingMessageSchema = z5.discriminatedUnion("type", [
|
|
942
|
-
incomingTextMessageSchema,
|
|
943
|
-
incomingAudioMessageSchema,
|
|
944
|
-
incomingImageMessageSchema
|
|
945
|
-
]);
|
|
946
|
-
|
|
947
|
-
// src/schemas/webhooks/payload.ts
|
|
948
|
-
var contactSchema = z6.object({
|
|
949
|
-
profile: z6.object({
|
|
950
|
-
name: z6.string()
|
|
951
|
-
}),
|
|
952
|
-
wa_id: z6.string()
|
|
953
|
-
});
|
|
954
|
-
var webhookMetadataSchema = z6.object({
|
|
955
|
-
display_phone_number: z6.string(),
|
|
956
|
-
phone_number_id: z6.string()
|
|
957
|
-
});
|
|
958
|
-
var webhookValueSchema = z6.object({
|
|
959
|
-
messaging_product: z6.literal("whatsapp"),
|
|
960
|
-
metadata: webhookMetadataSchema,
|
|
961
|
-
contacts: z6.array(contactSchema).optional(),
|
|
962
|
-
messages: z6.array(incomingMessageSchema).optional(),
|
|
963
|
-
// Incoming messages
|
|
964
|
-
statuses: z6.array(z6.any()).optional()
|
|
965
|
-
// Status updates (for later)
|
|
966
|
-
});
|
|
967
|
-
var webhookChangeSchema = z6.object({
|
|
968
|
-
value: webhookValueSchema,
|
|
969
|
-
field: z6.literal("messages")
|
|
970
|
-
// For now: only messages field
|
|
971
|
-
});
|
|
972
|
-
var webhookEntrySchema = z6.object({
|
|
973
|
-
id: z6.string(),
|
|
974
|
-
// WABA ID
|
|
975
|
-
changes: z6.array(webhookChangeSchema)
|
|
976
|
-
});
|
|
977
|
-
var webhookPayloadSchema = z6.object({
|
|
978
|
-
object: z6.literal("whatsapp_business_account"),
|
|
979
|
-
entry: z6.array(webhookEntrySchema)
|
|
980
|
-
});
|
|
981
|
-
|
|
982
|
-
// src/services/webhooks/WebhooksService.ts
|
|
983
|
-
var WebhooksService = class {
|
|
984
|
-
constructor(httpClient) {
|
|
985
|
-
this.httpClient = httpClient;
|
|
986
|
-
}
|
|
987
|
-
/**
|
|
988
|
-
* Verify webhook GET request from Meta
|
|
989
|
-
*
|
|
990
|
-
* Meta sends GET requests to verify webhook endpoints during setup.
|
|
991
|
-
* Returns the challenge string if valid, null if invalid.
|
|
992
|
-
*
|
|
993
|
-
* @param query - Query parameters from GET request
|
|
994
|
-
* @param verifyToken - Your verification token (stored on your server)
|
|
995
|
-
* @returns Challenge string if valid, null if invalid
|
|
996
|
-
*/
|
|
997
|
-
verify(query, verifyToken) {
|
|
998
|
-
return verifyWebhook(query, verifyToken);
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Extract all incoming messages from webhook payload
|
|
1002
|
-
*
|
|
1003
|
-
* Low-level utility that flattens the nested webhook structure
|
|
1004
|
-
* and returns messages directly.
|
|
1005
|
-
*
|
|
1006
|
-
* @param payload - Webhook payload from Meta
|
|
1007
|
-
* @returns Flat array of incoming messages
|
|
1008
|
-
*/
|
|
1009
|
-
extractMessages(payload) {
|
|
1010
|
-
return extractMessages(payload);
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Extract status updates from webhook payload
|
|
1014
|
-
*
|
|
1015
|
-
* Low-level utility for extracting status updates for outgoing messages.
|
|
1016
|
-
*
|
|
1017
|
-
* @param payload - Webhook payload from Meta
|
|
1018
|
-
* @returns Flat array of status updates
|
|
1019
|
-
*/
|
|
1020
|
-
extractStatuses(payload) {
|
|
1021
|
-
return extractStatuses(payload);
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Download media file by media ID
|
|
1025
|
-
*
|
|
1026
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1027
|
-
* Uses the access token from the client configuration automatically.
|
|
1028
|
-
*
|
|
1029
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1030
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1031
|
-
* @throws Error if download fails or media ID is invalid
|
|
1032
|
-
*
|
|
1033
|
-
* @example
|
|
1034
|
-
* ```typescript
|
|
1035
|
-
* client.webhooks.handle(req.body, {
|
|
1036
|
-
* image: async (message, context) => {
|
|
1037
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
1038
|
-
* // Upload to S3, save to disk, etc.
|
|
1039
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1040
|
-
* },
|
|
1041
|
-
* });
|
|
1042
|
-
* ```
|
|
1043
|
-
*/
|
|
1044
|
-
async downloadMedia(mediaId) {
|
|
1045
|
-
if (!mediaId || mediaId.trim().length === 0) {
|
|
1046
|
-
throw new Error("Media ID is required");
|
|
1047
|
-
}
|
|
1048
|
-
return this.httpClient.getBinary(`/${mediaId}`);
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Validate webhook payload structure
|
|
1052
|
-
*
|
|
1053
|
-
* Validates the payload against the schema. Logs errors if malformed
|
|
1054
|
-
* but doesn't throw, allowing processing to continue.
|
|
1055
|
-
*
|
|
1056
|
-
* @param payload - Raw payload to validate
|
|
1057
|
-
* @returns Validated payload if valid, original payload if invalid (with logged error)
|
|
1058
|
-
*/
|
|
1059
|
-
validatePayload(payload) {
|
|
1060
|
-
const result = webhookPayloadSchema.safeParse(payload);
|
|
1061
|
-
if (!result.success) {
|
|
1062
|
-
console.error(
|
|
1063
|
-
"Webhook payload validation failed:",
|
|
1064
|
-
result.error.format()
|
|
1065
|
-
);
|
|
1066
|
-
return payload;
|
|
1067
|
-
}
|
|
1068
|
-
return result.data;
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Handle webhook payload with type-safe callbacks
|
|
1072
|
-
*
|
|
1073
|
-
* High-level convenience method that extracts messages and dispatches
|
|
1074
|
-
* them to appropriate handlers based on message type.
|
|
1075
|
-
*
|
|
1076
|
-
* **Important**: This method returns quickly to allow fast webhook responses.
|
|
1077
|
-
* Handlers are processed asynchronously. If you need to await handler completion,
|
|
1078
|
-
* use the low-level `extractMessages()` method instead.
|
|
1079
|
-
*
|
|
1080
|
-
* @param payload - Webhook payload from Meta (will be validated)
|
|
1081
|
-
* @param handlers - Object with handler functions for each message type
|
|
1082
|
-
* @param options - Optional error handling configuration
|
|
1083
|
-
*/
|
|
1084
|
-
handle(payload, handlers, options) {
|
|
1085
|
-
const validatedPayload = this.validatePayload(payload);
|
|
1086
|
-
for (const entry of validatedPayload.entry) {
|
|
1087
|
-
for (const change of entry.changes) {
|
|
1088
|
-
if (change.field === "messages" && change.value.messages) {
|
|
1089
|
-
const metadata = {
|
|
1090
|
-
phoneNumberId: change.value.metadata.phone_number_id,
|
|
1091
|
-
displayPhoneNumber: change.value.metadata.display_phone_number,
|
|
1092
|
-
wabaId: entry.id
|
|
1093
|
-
};
|
|
1094
|
-
const contacts = change.value.contacts || [];
|
|
1095
|
-
for (const message of change.value.messages) {
|
|
1096
|
-
const contact = contacts.find((c) => c.wa_id === message.from);
|
|
1097
|
-
const context = {
|
|
1098
|
-
metadata,
|
|
1099
|
-
...contact && {
|
|
1100
|
-
contact: {
|
|
1101
|
-
name: contact.profile.name,
|
|
1102
|
-
waId: contact.wa_id
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1106
|
-
Promise.resolve().then(async () => {
|
|
1107
|
-
switch (message.type) {
|
|
1108
|
-
case "text":
|
|
1109
|
-
if (handlers.text) {
|
|
1110
|
-
await handlers.text(message, context);
|
|
1111
|
-
}
|
|
1112
|
-
break;
|
|
1113
|
-
case "audio":
|
|
1114
|
-
if (handlers.audio) {
|
|
1115
|
-
await handlers.audio(message, context);
|
|
1116
|
-
}
|
|
1117
|
-
break;
|
|
1118
|
-
case "image":
|
|
1119
|
-
if (handlers.image) {
|
|
1120
|
-
await handlers.image(message, context);
|
|
1121
|
-
}
|
|
1122
|
-
break;
|
|
1123
|
-
default:
|
|
1124
|
-
break;
|
|
1125
|
-
}
|
|
1126
|
-
}).catch((error) => {
|
|
1127
|
-
if (options?.onError) {
|
|
1128
|
-
options.onError(error, message);
|
|
1129
|
-
} else {
|
|
1130
|
-
console.error(
|
|
1131
|
-
`Error handling ${message.type} message ${message.id}:`,
|
|
1132
|
-
error
|
|
1133
|
-
);
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
};
|
|
1142
|
-
|
|
1143
|
-
// src/client/WhatsAppClient.ts
|
|
1144
|
-
import { ZodError as ZodError2 } from "zod";
|
|
1145
|
-
var WhatsAppClient = class {
|
|
1146
|
-
messages;
|
|
1147
|
-
accounts;
|
|
1148
|
-
business;
|
|
1149
|
-
templates;
|
|
1150
|
-
webhooks;
|
|
1151
|
-
httpClient;
|
|
1152
|
-
constructor(config) {
|
|
1153
|
-
let validated;
|
|
1154
|
-
try {
|
|
1155
|
-
validated = clientConfigSchema.parse(config);
|
|
1156
|
-
} catch (error) {
|
|
1157
|
-
if (error instanceof ZodError2) {
|
|
1158
|
-
throw transformZodError(error);
|
|
1159
|
-
}
|
|
1160
|
-
throw error;
|
|
1161
|
-
}
|
|
1162
|
-
this.httpClient = new HttpClient(validated);
|
|
1163
|
-
this.messages = new MessagesService(this.httpClient);
|
|
1164
|
-
this.accounts = new AccountsService(this.httpClient);
|
|
1165
|
-
this.business = new BusinessService(this.httpClient);
|
|
1166
|
-
this.templates = new TemplatesService(this.httpClient);
|
|
1167
|
-
this.webhooks = new WebhooksService(this.httpClient);
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Debug the current access token
|
|
1171
|
-
*
|
|
1172
|
-
* This method calls the Graph API debug_token endpoint to inspect the access token
|
|
1173
|
-
* used by this client. Useful for understanding token permissions, expiration, and validity.
|
|
1174
|
-
*
|
|
1175
|
-
* @returns Debug information about the access token
|
|
1176
|
-
*/
|
|
1177
|
-
async debugToken() {
|
|
1178
|
-
return this.httpClient.get(
|
|
1179
|
-
`/debug_token?input_token=${this.httpClient.accessToken}`
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
// src/schemas/messages/response.ts
|
|
1185
|
-
import { z as z7 } from "zod";
|
|
1186
|
-
var messageResponseSchema = z7.object({
|
|
1187
|
-
messaging_product: z7.literal("whatsapp"),
|
|
1188
|
-
contacts: z7.array(
|
|
1189
|
-
z7.object({
|
|
1190
|
-
input: z7.string(),
|
|
1191
|
-
wa_id: z7.string()
|
|
1192
|
-
})
|
|
1193
|
-
),
|
|
1194
|
-
messages: z7.array(
|
|
1195
|
-
z7.object({
|
|
1196
|
-
id: z7.string()
|
|
1197
|
-
})
|
|
1198
|
-
)
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
// src/schemas/accounts/phone-number.ts
|
|
1202
|
-
import { z as z8 } from "zod";
|
|
1203
|
-
var phoneNumberResponseSchema = z8.object({
|
|
1204
|
-
verified_name: z8.string(),
|
|
1205
|
-
display_phone_number: z8.string(),
|
|
1206
|
-
id: z8.string(),
|
|
1207
|
-
quality_rating: z8.string()
|
|
1208
|
-
});
|
|
1209
|
-
var phoneNumberListResponseSchema = z8.object({
|
|
1210
|
-
data: z8.array(phoneNumberResponseSchema)
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
// src/schemas/business/account.ts
|
|
1214
|
-
import { z as z9 } from "zod";
|
|
1215
|
-
var businessAccountResponseSchema = z9.object({
|
|
1216
|
-
id: z9.string(),
|
|
1217
|
-
name: z9.string().optional(),
|
|
1218
|
-
account_review_status: z9.string().optional(),
|
|
1219
|
-
currency: z9.string().optional(),
|
|
1220
|
-
country: z9.string().optional(),
|
|
1221
|
-
timezone_id: z9.string().optional(),
|
|
1222
|
-
business_verification_status: z9.string().optional(),
|
|
1223
|
-
is_enabled_for_insights: z9.boolean().optional(),
|
|
1224
|
-
message_template_namespace: z9.string().optional()
|
|
1225
|
-
});
|
|
1226
|
-
var businessAccountsListResponseSchema = z9.object({
|
|
1227
|
-
data: z9.record(z9.string(), businessAccountResponseSchema).or(
|
|
1228
|
-
z9.array(businessAccountResponseSchema)
|
|
1229
|
-
),
|
|
1230
|
-
paging: z9.object({
|
|
1231
|
-
cursors: z9.object({
|
|
1232
|
-
before: z9.string().optional(),
|
|
1233
|
-
after: z9.string().optional()
|
|
1234
|
-
}).optional(),
|
|
1235
|
-
next: z9.string().url().optional(),
|
|
1236
|
-
previous: z9.string().url().optional()
|
|
1237
|
-
}).optional()
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
// src/schemas/templates/response.ts
|
|
1241
|
-
import { z as z10 } from "zod";
|
|
1242
|
-
var templateSchema = z10.object({
|
|
1243
|
-
id: z10.string(),
|
|
1244
|
-
name: z10.string(),
|
|
1245
|
-
language: z10.string(),
|
|
1246
|
-
status: z10.string(),
|
|
1247
|
-
category: z10.string(),
|
|
1248
|
-
components: z10.array(templateComponentSchema)
|
|
1249
|
-
});
|
|
1250
|
-
var templateCreateResponseSchema = z10.object({
|
|
1251
|
-
id: z10.string(),
|
|
1252
|
-
status: z10.string(),
|
|
1253
|
-
category: z10.string()
|
|
1254
|
-
});
|
|
1255
|
-
var templateListResponseSchema = z10.object({
|
|
1256
|
-
data: z10.array(templateSchema),
|
|
1257
|
-
paging: z10.object({
|
|
1258
|
-
cursors: z10.object({
|
|
1259
|
-
before: z10.string().optional(),
|
|
1260
|
-
after: z10.string().optional()
|
|
1261
|
-
}).optional()
|
|
1262
|
-
}).optional()
|
|
1263
|
-
});
|
|
1264
|
-
var templateUpdateResponseSchema = z10.object({
|
|
1265
|
-
success: z10.boolean()
|
|
1266
|
-
});
|
|
1267
|
-
var templateDeleteResponseSchema = z10.object({
|
|
1268
|
-
success: z10.boolean()
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
// src/schemas/debug.ts
|
|
1272
|
-
import { z as z11 } from "zod";
|
|
1273
|
-
var debugTokenResponseSchema = z11.object({
|
|
1274
|
-
data: z11.object({
|
|
1275
|
-
app_id: z11.string().optional(),
|
|
1276
|
-
type: z11.string().optional(),
|
|
1277
|
-
application: z11.string().optional(),
|
|
1278
|
-
data_access_expires_at: z11.number().optional(),
|
|
1279
|
-
expires_at: z11.number().optional(),
|
|
1280
|
-
is_valid: z11.boolean().optional(),
|
|
1281
|
-
issued_at: z11.number().optional(),
|
|
1282
|
-
metadata: z11.object({
|
|
1283
|
-
auth_type: z11.string().optional(),
|
|
1284
|
-
sso: z11.string().optional()
|
|
1285
|
-
}).optional(),
|
|
1286
|
-
scopes: z11.array(z11.string()).optional(),
|
|
1287
|
-
user_id: z11.string().optional()
|
|
1288
|
-
})
|
|
1289
|
-
});
|
|
1290
|
-
export {
|
|
1291
|
-
WhatsAppAPIError,
|
|
1292
|
-
WhatsAppClient,
|
|
1293
|
-
WhatsAppError,
|
|
1294
|
-
WhatsAppRateLimitError,
|
|
1295
|
-
WhatsAppValidationError,
|
|
1296
|
-
businessAccountResponseSchema,
|
|
1297
|
-
businessAccountsListResponseSchema,
|
|
1298
|
-
clientConfigSchema,
|
|
1299
|
-
debugTokenResponseSchema,
|
|
1300
|
-
incomingAudioMessageSchema,
|
|
1301
|
-
incomingImageMessageSchema,
|
|
1302
|
-
incomingMessageSchema,
|
|
1303
|
-
incomingTextMessageSchema,
|
|
1304
|
-
messageResponseSchema,
|
|
1305
|
-
phoneNumberListResponseSchema,
|
|
1306
|
-
phoneNumberResponseSchema,
|
|
1307
|
-
sendImageRequestSchema,
|
|
1308
|
-
sendLocationRequestSchema,
|
|
1309
|
-
sendReactionRequestSchema,
|
|
1310
|
-
sendTextRequestSchema,
|
|
1311
|
-
templateBodyComponentSchema,
|
|
1312
|
-
templateButtonSchema,
|
|
1313
|
-
templateButtonsComponentSchema,
|
|
1314
|
-
templateComponentSchema,
|
|
1315
|
-
templateCopyCodeButtonSchema,
|
|
1316
|
-
templateCreateResponseSchema,
|
|
1317
|
-
templateCreateSchema,
|
|
1318
|
-
templateDeleteResponseSchema,
|
|
1319
|
-
templateDeleteSchema,
|
|
1320
|
-
templateFlowButtonSchema,
|
|
1321
|
-
templateFooterComponentSchema,
|
|
1322
|
-
templateHeaderComponentSchema,
|
|
1323
|
-
templateListResponseSchema,
|
|
1324
|
-
templateListSchema,
|
|
1325
|
-
templatePhoneNumberButtonSchema,
|
|
1326
|
-
templateQuickReplyButtonSchema,
|
|
1327
|
-
templateSchema,
|
|
1328
|
-
templateUpdateResponseSchema,
|
|
1329
|
-
templateUpdateSchema,
|
|
1330
|
-
templateUrlButtonSchema,
|
|
1331
|
-
webhookPayloadSchema
|
|
1332
|
-
};
|