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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # whatzapp
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 6b76203: remove pkg
8
+
3
9
  ## 0.1.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-cloud",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Work in progress. A WhatsApp client tailored for LLMs—built to actually work.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/dist/index.d.mts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
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
- };