@zapyapi/sdk 1.0.0-beta.1

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.
Files changed (44) hide show
  1. package/README.md +308 -0
  2. package/index.cjs +1176 -0
  3. package/index.js +1149 -0
  4. package/package.json +55 -0
  5. package/src/client.d.ts +98 -0
  6. package/src/client.d.ts.map +1 -0
  7. package/src/errors.d.ts +70 -0
  8. package/src/errors.d.ts.map +1 -0
  9. package/src/generated/Api.d.ts +1542 -0
  10. package/src/generated/Api.d.ts.map +1 -0
  11. package/src/index.d.ts +38 -0
  12. package/src/index.d.ts.map +1 -0
  13. package/src/resources/base.resource.d.ts +17 -0
  14. package/src/resources/base.resource.d.ts.map +1 -0
  15. package/src/resources/index.d.ts +8 -0
  16. package/src/resources/index.d.ts.map +1 -0
  17. package/src/resources/instances.resource.d.ts +75 -0
  18. package/src/resources/instances.resource.d.ts.map +1 -0
  19. package/src/resources/messages.resource.d.ts +159 -0
  20. package/src/resources/messages.resource.d.ts.map +1 -0
  21. package/src/resources/webhooks.resource.d.ts +115 -0
  22. package/src/resources/webhooks.resource.d.ts.map +1 -0
  23. package/src/types/common.types.d.ts +89 -0
  24. package/src/types/common.types.d.ts.map +1 -0
  25. package/src/types/enums.d.ts +142 -0
  26. package/src/types/enums.d.ts.map +1 -0
  27. package/src/types/index.d.ts +11 -0
  28. package/src/types/index.d.ts.map +1 -0
  29. package/src/types/instances.types.d.ts +114 -0
  30. package/src/types/instances.types.d.ts.map +1 -0
  31. package/src/types/messages.types.d.ts +166 -0
  32. package/src/types/messages.types.d.ts.map +1 -0
  33. package/src/types/webhook-config.types.d.ts +60 -0
  34. package/src/types/webhook-config.types.d.ts.map +1 -0
  35. package/src/types/webhook-events.types.d.ts +232 -0
  36. package/src/types/webhook-events.types.d.ts.map +1 -0
  37. package/src/utils/index.d.ts +6 -0
  38. package/src/utils/index.d.ts.map +1 -0
  39. package/src/utils/phone.d.ts +38 -0
  40. package/src/utils/phone.d.ts.map +1 -0
  41. package/src/utils/webhook-signature.d.ts +69 -0
  42. package/src/utils/webhook-signature.d.ts.map +1 -0
  43. package/src/version.d.ts +3 -0
  44. package/src/version.d.ts.map +1 -0
package/index.js ADDED
@@ -0,0 +1,1149 @@
1
+ import axios from 'axios';
2
+
3
+ /** SDK version - auto-generated from package.json */
4
+ const SDK_VERSION = '1.0.0-beta.1';
5
+
6
+ /**
7
+ * Custom error classes for @zapyapi/sdk
8
+ */
9
+ /**
10
+ * Base error class for all ZapyAPI errors
11
+ */
12
+ class ZapyError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'ZapyError';
16
+ Object.setPrototypeOf(this, ZapyError.prototype);
17
+ }
18
+ }
19
+ /**
20
+ * Error thrown when the API returns an error response
21
+ */
22
+ class ZapyApiError extends ZapyError {
23
+ /**
24
+ * HTTP status code
25
+ */
26
+ statusCode;
27
+ /**
28
+ * Error code from the API
29
+ */
30
+ code;
31
+ /**
32
+ * Request ID for debugging (if available)
33
+ */
34
+ requestId;
35
+ /**
36
+ * Additional error details
37
+ */
38
+ details;
39
+ constructor(message, statusCode, code, requestId, details) {
40
+ super(message);
41
+ this.name = 'ZapyApiError';
42
+ this.statusCode = statusCode;
43
+ this.code = code;
44
+ this.requestId = requestId;
45
+ this.details = details;
46
+ Object.setPrototypeOf(this, ZapyApiError.prototype);
47
+ }
48
+ /**
49
+ * Create a ZapyApiError from an Axios error
50
+ */
51
+ static fromAxiosError(error) {
52
+ const statusCode = error.response?.status ?? 500;
53
+ const data = error.response?.data;
54
+ const requestId = error.response?.headers?.['x-request-id'];
55
+ if (data && typeof data === 'object' && 'code' in data) {
56
+ return new ZapyApiError(data.message || error.message, statusCode, data.code, requestId || data.requestId, data.details);
57
+ }
58
+ return new ZapyApiError(error.message || 'An unexpected error occurred', statusCode, 'UNKNOWN_ERROR', requestId);
59
+ }
60
+ }
61
+ /**
62
+ * Error thrown when input validation fails
63
+ */
64
+ class ValidationError extends ZapyError {
65
+ /**
66
+ * Validation error details by field
67
+ */
68
+ details;
69
+ constructor(message, details) {
70
+ super(message);
71
+ this.name = 'ValidationError';
72
+ this.details = details;
73
+ Object.setPrototypeOf(this, ValidationError.prototype);
74
+ }
75
+ }
76
+ /**
77
+ * Error thrown when an instance is not found
78
+ */
79
+ class InstanceNotFoundError extends ZapyApiError {
80
+ constructor(instanceId, requestId) {
81
+ super(`Instance '${instanceId}' not found`, 404, 'INSTANCE_NOT_FOUND', requestId, {
82
+ instanceId
83
+ });
84
+ this.name = 'InstanceNotFoundError';
85
+ Object.setPrototypeOf(this, InstanceNotFoundError.prototype);
86
+ }
87
+ }
88
+ /**
89
+ * Error thrown when authentication fails
90
+ */
91
+ class AuthenticationError extends ZapyApiError {
92
+ constructor(message = 'Invalid or missing API key', requestId) {
93
+ super(message, 401, 'UNAUTHORIZED', requestId);
94
+ this.name = 'AuthenticationError';
95
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
96
+ }
97
+ }
98
+ /**
99
+ * Error thrown when rate limit is exceeded
100
+ */
101
+ class RateLimitError extends ZapyApiError {
102
+ /**
103
+ * Timestamp when the rate limit resets (Unix ms)
104
+ */
105
+ retryAfter;
106
+ constructor(message = 'Rate limit exceeded', retryAfter, requestId) {
107
+ super(message, 429, 'RATE_LIMIT_EXCEEDED', requestId, {
108
+ retryAfter
109
+ });
110
+ this.name = 'RateLimitError';
111
+ this.retryAfter = retryAfter;
112
+ Object.setPrototypeOf(this, RateLimitError.prototype);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Base resource class with common HTTP functionality
118
+ */
119
+ class BaseResource {
120
+ http;
121
+ constructor(http) {
122
+ this.http = http;
123
+ }
124
+ /**
125
+ * Handle API errors and convert to appropriate error types
126
+ */
127
+ handleError(error) {
128
+ if (this.isAxiosError(error)) {
129
+ const statusCode = error.response?.status;
130
+ const retryAfter = error.response?.headers?.['retry-after'];
131
+ if (statusCode === 401) {
132
+ throw new AuthenticationError(error.response?.data?.message || 'Invalid or missing API key', error.response?.headers?.['x-request-id']);
133
+ }
134
+ if (statusCode === 429) {
135
+ throw new RateLimitError(error.response?.data?.message || 'Rate limit exceeded', retryAfter ? parseInt(retryAfter, 10) * 1000 : undefined, error.response?.headers?.['x-request-id']);
136
+ }
137
+ throw ZapyApiError.fromAxiosError(error);
138
+ }
139
+ if (error instanceof Error) {
140
+ throw new ZapyApiError(error.message, 500, 'UNKNOWN_ERROR');
141
+ }
142
+ throw new ZapyApiError('An unexpected error occurred', 500, 'UNKNOWN_ERROR');
143
+ }
144
+ /**
145
+ * Type guard for Axios errors
146
+ */
147
+ isAxiosError(error) {
148
+ return typeof error === 'object' && error !== null && 'isAxiosError' in error && error.isAxiosError === true;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Messages resource for sending and managing messages
154
+ */
155
+ class MessagesResource extends BaseResource {
156
+ constructor(http) {
157
+ super(http);
158
+ }
159
+ /**
160
+ * Send a text message
161
+ *
162
+ * @param instanceId - Instance ID
163
+ * @param options - Message options
164
+ * @returns Message response with ID and status
165
+ *
166
+ * @example
167
+ * const response = await client.messages.sendText('my-instance', {
168
+ * to: '5511999999999',
169
+ * text: 'Hello from ZapyAPI!'
170
+ * });
171
+ */
172
+ async sendText(instanceId, options) {
173
+ try {
174
+ const response = await this.http.post(`/message/${instanceId}/text`, {
175
+ to: options.to,
176
+ message: options.text,
177
+ quoteMessageId: options.quotedMessageId
178
+ });
179
+ return response.data;
180
+ } catch (error) {
181
+ this.handleError(error);
182
+ }
183
+ }
184
+ /**
185
+ * Send an image message
186
+ *
187
+ * @param instanceId - Instance ID
188
+ * @param options - Image message options
189
+ * @returns Message response with ID and status
190
+ *
191
+ * @example
192
+ * await client.messages.sendImage('my-instance', {
193
+ * to: '5511999999999',
194
+ * url: 'https://example.com/image.jpg',
195
+ * caption: 'Check this out!'
196
+ * });
197
+ */
198
+ async sendImage(instanceId, options) {
199
+ try {
200
+ const response = await this.http.post(`/message/${instanceId}/image`, {
201
+ to: options.to,
202
+ url: options.url,
203
+ base64: options.base64,
204
+ caption: options.caption,
205
+ quoteMessageId: options.quotedMessageId
206
+ });
207
+ return response.data;
208
+ } catch (error) {
209
+ this.handleError(error);
210
+ }
211
+ }
212
+ /**
213
+ * Send a video message
214
+ *
215
+ * @param instanceId - Instance ID
216
+ * @param options - Video message options
217
+ * @returns Message response with ID and status
218
+ *
219
+ * @example
220
+ * await client.messages.sendVideo('my-instance', {
221
+ * to: '5511999999999',
222
+ * url: 'https://example.com/video.mp4',
223
+ * caption: 'Watch this!'
224
+ * });
225
+ */
226
+ async sendVideo(instanceId, options) {
227
+ try {
228
+ const response = await this.http.post(`/message/${instanceId}/video`, {
229
+ to: options.to,
230
+ url: options.url,
231
+ base64: options.base64,
232
+ caption: options.caption,
233
+ quoteMessageId: options.quotedMessageId
234
+ });
235
+ return response.data;
236
+ } catch (error) {
237
+ this.handleError(error);
238
+ }
239
+ }
240
+ /**
241
+ * Send an audio note (voice message)
242
+ *
243
+ * @param instanceId - Instance ID
244
+ * @param options - Audio note options
245
+ * @returns Message response with ID and status
246
+ *
247
+ * @example
248
+ * await client.messages.sendAudioNote('my-instance', {
249
+ * to: '5511999999999',
250
+ * url: 'https://example.com/audio.ogg'
251
+ * });
252
+ */
253
+ async sendAudioNote(instanceId, options) {
254
+ try {
255
+ const response = await this.http.post(`/message/${instanceId}/audio-note`, {
256
+ to: options.to,
257
+ url: options.url,
258
+ base64: options.base64,
259
+ quoteMessageId: options.quotedMessageId
260
+ });
261
+ return response.data;
262
+ } catch (error) {
263
+ this.handleError(error);
264
+ }
265
+ }
266
+ /**
267
+ * Send an audio file
268
+ *
269
+ * @param instanceId - Instance ID
270
+ * @param options - Audio file options
271
+ * @returns Message response with ID and status
272
+ *
273
+ * @example
274
+ * await client.messages.sendAudioFile('my-instance', {
275
+ * to: '5511999999999',
276
+ * url: 'https://example.com/song.mp3'
277
+ * });
278
+ */
279
+ async sendAudioFile(instanceId, options) {
280
+ try {
281
+ const response = await this.http.post(`/message/${instanceId}/audio-file`, {
282
+ to: options.to,
283
+ url: options.url,
284
+ base64: options.base64,
285
+ quoteMessageId: options.quotedMessageId
286
+ });
287
+ return response.data;
288
+ } catch (error) {
289
+ this.handleError(error);
290
+ }
291
+ }
292
+ /**
293
+ * Send a document
294
+ *
295
+ * @param instanceId - Instance ID
296
+ * @param options - Document options
297
+ * @returns Message response with ID and status
298
+ *
299
+ * @example
300
+ * await client.messages.sendDocument('my-instance', {
301
+ * to: '5511999999999',
302
+ * url: 'https://example.com/document.pdf',
303
+ * filename: 'report.pdf',
304
+ * caption: 'Here is the report'
305
+ * });
306
+ */
307
+ async sendDocument(instanceId, options) {
308
+ try {
309
+ const response = await this.http.post(`/message/${instanceId}/document`, {
310
+ to: options.to,
311
+ url: options.url,
312
+ base64: options.base64,
313
+ filename: options.filename,
314
+ caption: options.caption,
315
+ quoteMessageId: options.quotedMessageId
316
+ });
317
+ return response.data;
318
+ } catch (error) {
319
+ this.handleError(error);
320
+ }
321
+ }
322
+ /**
323
+ * Forward a message to another contact/group
324
+ *
325
+ * @param instanceId - Instance ID
326
+ * @param options - Forward options
327
+ * @returns Message response with ID and status
328
+ *
329
+ * @example
330
+ * await client.messages.forward('my-instance', {
331
+ * to: '5511888888888',
332
+ * messageId: 'ABC123...'
333
+ * });
334
+ */
335
+ async forward(instanceId, options) {
336
+ try {
337
+ const response = await this.http.post(`/message/${instanceId}/forward`, {
338
+ to: options.to,
339
+ messageId: options.messageId
340
+ });
341
+ return response.data;
342
+ } catch (error) {
343
+ this.handleError(error);
344
+ }
345
+ }
346
+ /**
347
+ * Edit a previously sent text message
348
+ *
349
+ * @param instanceId - Instance ID
350
+ * @param options - Edit options
351
+ * @returns Message response
352
+ *
353
+ * @example
354
+ * await client.messages.edit('my-instance', {
355
+ * messageId: 'ABC123...',
356
+ * text: 'Updated message text'
357
+ * });
358
+ */
359
+ async edit(instanceId, options) {
360
+ try {
361
+ const response = await this.http.post(`/message/${instanceId}/edit`, {
362
+ messageId: options.messageId,
363
+ message: options.text
364
+ });
365
+ return response.data;
366
+ } catch (error) {
367
+ this.handleError(error);
368
+ }
369
+ }
370
+ /**
371
+ * Mark a message as read
372
+ *
373
+ * @param instanceId - Instance ID
374
+ * @param messageId - Message ID to mark as read
375
+ * @returns Read confirmation
376
+ *
377
+ * @example
378
+ * await client.messages.markAsRead('my-instance', 'ABC123...');
379
+ */
380
+ async markAsRead(instanceId, messageId) {
381
+ try {
382
+ const response = await this.http.post(`/message/${instanceId}/read`, {
383
+ messageId
384
+ });
385
+ return response.data;
386
+ } catch (error) {
387
+ this.handleError(error);
388
+ }
389
+ }
390
+ /**
391
+ * Delete a message for everyone
392
+ *
393
+ * @param instanceId - Instance ID
394
+ * @param messageId - Message ID to delete
395
+ *
396
+ * @example
397
+ * await client.messages.delete('my-instance', 'ABC123...');
398
+ */
399
+ async delete(instanceId, messageId) {
400
+ try {
401
+ await this.http.delete(`/message/${instanceId}/delete`, {
402
+ data: {
403
+ messageId
404
+ }
405
+ });
406
+ } catch (error) {
407
+ this.handleError(error);
408
+ }
409
+ }
410
+ /**
411
+ * Get a download link for message media
412
+ *
413
+ * @param instanceId - Instance ID
414
+ * @param messageId - Message ID containing media
415
+ * @returns Media download link
416
+ *
417
+ * @example
418
+ * const media = await client.messages.getMediaDownloadLink('my-instance', 'ABC123...');
419
+ * console.log(media.url);
420
+ */
421
+ async getMediaDownloadLink(instanceId, messageId) {
422
+ try {
423
+ const response = await this.http.get(`/message/${instanceId}/media-download-link/${messageId}`);
424
+ return response.data;
425
+ } catch (error) {
426
+ this.handleError(error);
427
+ }
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Webhooks resource
433
+ *
434
+ * Webhooks are configured at the USER level, not per-instance.
435
+ * One webhook URL receives events from ALL your instances.
436
+ *
437
+ * Benefits of user-level webhooks:
438
+ * - One webhook URL receives events from ALL your instances
439
+ * - Simplified configuration and management
440
+ * - Built-in retry queue with pause/resume functionality
441
+ * - HMAC-SHA256 signature verification
442
+ *
443
+ * Each webhook payload includes `instanceId` to identify the source instance.
444
+ *
445
+ * @see https://docs.zapyapi.com/webhooks for documentation
446
+ */
447
+ /**
448
+ * Webhooks resource for managing user-level webhook configuration
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * import { ZapyClient } from '@zapyapi/sdk';
453
+ *
454
+ * const client = new ZapyClient({ apiKey: 'your-api-key' });
455
+ *
456
+ * // Configure webhook
457
+ * await client.webhooks.configure({
458
+ * url: 'https://your-server.com/webhook',
459
+ * secret: 'your-webhook-secret',
460
+ * isActive: true,
461
+ * });
462
+ *
463
+ * // Get current configuration
464
+ * const config = await client.webhooks.getConfig();
465
+ *
466
+ * // Check queue status
467
+ * const status = await client.webhooks.getQueueStatus();
468
+ * ```
469
+ */
470
+ class WebhooksResource extends BaseResource {
471
+ constructor(http) {
472
+ super(http);
473
+ }
474
+ /**
475
+ * Get the current webhook configuration
476
+ *
477
+ * @returns The webhook configuration or null if not configured
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * const config = await client.webhooks.getConfig();
482
+ * if (config) {
483
+ * console.log('Webhook URL:', config.url);
484
+ * console.log('Is active:', config.isActive);
485
+ * }
486
+ * ```
487
+ */
488
+ async getConfig() {
489
+ try {
490
+ const response = await this.http.get('/webhooks/config');
491
+ return response.data;
492
+ } catch (error) {
493
+ this.handleError(error);
494
+ }
495
+ }
496
+ /**
497
+ * Configure or update the webhook
498
+ *
499
+ * @param config - Webhook configuration
500
+ * @returns The saved webhook configuration
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * const config = await client.webhooks.configure({
505
+ * url: 'https://your-server.com/webhook',
506
+ * secret: 'your-webhook-secret-min-16-chars',
507
+ * isActive: true,
508
+ * });
509
+ * ```
510
+ */
511
+ async configure(config) {
512
+ try {
513
+ const response = await this.http.put('/webhooks/config', config);
514
+ return response.data;
515
+ } catch (error) {
516
+ this.handleError(error);
517
+ }
518
+ }
519
+ /**
520
+ * Delete the webhook configuration
521
+ *
522
+ * This will also delete all queued webhooks.
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * await client.webhooks.deleteConfig();
527
+ * ```
528
+ */
529
+ async deleteConfig() {
530
+ try {
531
+ await this.http.delete('/webhooks/config');
532
+ } catch (error) {
533
+ this.handleError(error);
534
+ }
535
+ }
536
+ /**
537
+ * Get the webhook queue status
538
+ *
539
+ * @returns Queue status with counts of pending, failed, paused, and delivered webhooks
540
+ *
541
+ * @example
542
+ * ```typescript
543
+ * const status = await client.webhooks.getQueueStatus();
544
+ * console.log('Pending:', status.pendingCount);
545
+ * console.log('Failed:', status.failedCount);
546
+ * ```
547
+ */
548
+ async getQueueStatus() {
549
+ try {
550
+ const response = await this.http.get('/webhooks/queue/status');
551
+ return response.data;
552
+ } catch (error) {
553
+ this.handleError(error);
554
+ }
555
+ }
556
+ /**
557
+ * Resume all paused and failed webhooks
558
+ *
559
+ * This will unpause the webhook configuration and reset all paused/failed items to pending.
560
+ *
561
+ * @returns The number of webhooks resumed
562
+ *
563
+ * @example
564
+ * ```typescript
565
+ * const result = await client.webhooks.resume();
566
+ * console.log('Resumed:', result.resumedCount, 'webhooks');
567
+ * ```
568
+ */
569
+ async resume() {
570
+ try {
571
+ const response = await this.http.post('/webhooks/queue/resume');
572
+ return response.data;
573
+ } catch (error) {
574
+ this.handleError(error);
575
+ }
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Instances resource for managing WhatsApp instances
581
+ */
582
+ class InstancesResource extends BaseResource {
583
+ constructor(http) {
584
+ super(http);
585
+ }
586
+ /**
587
+ * List all instances with pagination
588
+ *
589
+ * @param query - Pagination options
590
+ * @returns Paginated list of instances
591
+ *
592
+ * @example
593
+ * const instances = await client.instances.list({ page: 1, limit: 10 });
594
+ * console.log(`Found ${instances.total} instances`);
595
+ */
596
+ async list(query) {
597
+ try {
598
+ const response = await this.http.get('/instances', {
599
+ params: query
600
+ });
601
+ return response.data;
602
+ } catch (error) {
603
+ this.handleError(error);
604
+ }
605
+ }
606
+ /**
607
+ * Create a new WhatsApp instance (Partner only)
608
+ *
609
+ * @param options - Instance creation options
610
+ * @returns The created instance
611
+ *
612
+ * @example
613
+ * const instance = await client.instances.create({
614
+ * name: 'My Instance',
615
+ * metadata: { department: 'sales' }
616
+ * });
617
+ */
618
+ async create(options) {
619
+ try {
620
+ const response = await this.http.post('/instances', options ?? {});
621
+ return response.data;
622
+ } catch (error) {
623
+ this.handleError(error);
624
+ }
625
+ }
626
+ /**
627
+ * Get QR code for connecting an instance
628
+ *
629
+ * @param instanceId - Instance ID
630
+ * @returns QR code response with base64 image
631
+ *
632
+ * @example
633
+ * const qr = await client.instances.getQRCode('my-instance');
634
+ * // Display qr.qrCode as an image (it's base64 encoded)
635
+ */
636
+ async getQRCode(instanceId) {
637
+ try {
638
+ const response = await this.http.get(`/instances/${instanceId}/qr`);
639
+ return response.data;
640
+ } catch (error) {
641
+ this.handleError(error);
642
+ }
643
+ }
644
+ /**
645
+ * Restart an instance connection
646
+ *
647
+ * @param instanceId - Instance ID
648
+ *
649
+ * @example
650
+ * await client.instances.restart('my-instance');
651
+ */
652
+ async restart(instanceId) {
653
+ try {
654
+ await this.http.post(`/instances/${instanceId}/restart`);
655
+ } catch (error) {
656
+ this.handleError(error);
657
+ }
658
+ }
659
+ /**
660
+ * Logout from WhatsApp (disconnect the instance)
661
+ *
662
+ * @param instanceId - Instance ID
663
+ *
664
+ * @example
665
+ * await client.instances.logout('my-instance');
666
+ */
667
+ async logout(instanceId) {
668
+ try {
669
+ await this.http.post(`/instances/${instanceId}/logout`);
670
+ } catch (error) {
671
+ this.handleError(error);
672
+ }
673
+ }
674
+ /**
675
+ * Delete an instance (Partner only)
676
+ *
677
+ * This permanently deletes the instance and all associated data.
678
+ * This action is irreversible.
679
+ *
680
+ * @param instanceId - Instance ID
681
+ *
682
+ * @example
683
+ * await client.instances.delete('my-instance');
684
+ */
685
+ async delete(instanceId) {
686
+ try {
687
+ await this.http.delete(`/instances/${instanceId}`);
688
+ } catch (error) {
689
+ this.handleError(error);
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * ZapyClient - Main entry point for the ZapyAPI SDK
696
+ */
697
+ /**
698
+ * ZapyClient - Official TypeScript SDK for ZapyAPI
699
+ *
700
+ * @example
701
+ * ```typescript
702
+ * import { ZapyClient, WebhookEventType, isMessageEvent } from '@zapyapi/sdk';
703
+ *
704
+ * const client = new ZapyClient({
705
+ * apiKey: 'your-api-key',
706
+ * });
707
+ *
708
+ * // Send a text message
709
+ * await client.messages.sendText('my-instance', {
710
+ * to: '5511999999999',
711
+ * text: 'Hello from ZapyAPI SDK!'
712
+ * });
713
+ *
714
+ * // List instances
715
+ * const instances = await client.instances.list();
716
+ *
717
+ * // Get QR code for an instance
718
+ * const qr = await client.instances.getQRCode('my-instance');
719
+ *
720
+ * // Configure webhooks programmatically
721
+ * await client.webhooks.configure({
722
+ * url: 'https://your-server.com/webhook',
723
+ * secret: 'your-secret-key-min-16-chars',
724
+ * isActive: true,
725
+ * });
726
+ * ```
727
+ */
728
+ class ZapyClient {
729
+ /**
730
+ * Internal Axios HTTP client
731
+ */
732
+ http;
733
+ /**
734
+ * Instance management operations
735
+ */
736
+ instances;
737
+ /**
738
+ * Message sending and management operations
739
+ */
740
+ messages;
741
+ /**
742
+ * Webhook configuration and management operations
743
+ *
744
+ * Webhooks are configured at the user level - one webhook URL receives
745
+ * events from ALL your instances. Each payload includes `instanceId`.
746
+ */
747
+ webhooks;
748
+ /**
749
+ * Create a new ZapyClient instance
750
+ *
751
+ * @param options - Client configuration options
752
+ *
753
+ * @example
754
+ * const client = new ZapyClient({
755
+ * apiKey: 'your-api-key',
756
+ * baseUrl: 'https://api.zapyapi.com/api', // optional
757
+ * timeout: 30000, // optional, in milliseconds
758
+ * });
759
+ */
760
+ constructor(options) {
761
+ if (!options.apiKey) {
762
+ throw new Error('API key is required. Get yours at https://app.zapyapi.com/');
763
+ }
764
+ this.http = axios.create({
765
+ baseURL: options.baseUrl ?? 'https://api.zapyapi.com/api',
766
+ timeout: options.timeout ?? 30000,
767
+ headers: {
768
+ 'x-api-key': options.apiKey,
769
+ 'Content-Type': 'application/json',
770
+ 'User-Agent': `@zapyapi/sdk/${SDK_VERSION}`,
771
+ ...options.headers
772
+ }
773
+ });
774
+ // Initialize resources
775
+ this.instances = new InstancesResource(this.http);
776
+ this.messages = new MessagesResource(this.http);
777
+ this.webhooks = new WebhooksResource(this.http);
778
+ }
779
+ /**
780
+ * Get the underlying Axios instance for advanced usage
781
+ *
782
+ * @returns The configured Axios instance
783
+ *
784
+ * @example
785
+ * const httpClient = client.getHttpClient();
786
+ * httpClient.interceptors.response.use(/* ... *\/);
787
+ */
788
+ getHttpClient() {
789
+ return this.http;
790
+ }
791
+ }
792
+ /**
793
+ * Create a new ZapyClient instance
794
+ *
795
+ * @param options - Client configuration options
796
+ * @returns A configured ZapyClient instance
797
+ *
798
+ * @example
799
+ * import { createClient } from '@zapyapi/sdk';
800
+ *
801
+ * const client = createClient({
802
+ * apiKey: 'your-api-key',
803
+ * });
804
+ */
805
+ function createClient(options) {
806
+ return new ZapyClient(options);
807
+ }
808
+
809
+ /**
810
+ * SDK Enums - Type-safe enums for ZapyAPI SDK
811
+ *
812
+ * These enums can be imported and used for type-safe comparisons and
813
+ * webhook event handling.
814
+ *
815
+ * @example
816
+ * ```typescript
817
+ * import { WebhookEventType, InstanceStatus, MessageAckStatus } from '@zapyapi/sdk';
818
+ *
819
+ * // Type-safe event handling
820
+ * if (event.event === WebhookEventType.MESSAGE) {
821
+ * // Handle message event
822
+ * }
823
+ *
824
+ * // Check instance status
825
+ * if (instance.status === InstanceStatus.CONNECTED) {
826
+ * // Instance is ready
827
+ * }
828
+ * ```
829
+ */
830
+ /**
831
+ * Webhook event types sent by ZapyAPI
832
+ * These are the event types you'll receive in webhook payloads
833
+ */
834
+ const WebhookEventType = {
835
+ /** New message received */
836
+ MESSAGE: 'message',
837
+ /** Message delivery status update */
838
+ MESSAGE_STATUS: 'message-status',
839
+ /** QR code generated for authentication */
840
+ QR_CODE: 'qr-code',
841
+ /** New contact discovered */
842
+ CONTACT_CREATED: 'contact-created',
843
+ /** Contact information updated */
844
+ CONTACT_UPDATED: 'contact-updated',
845
+ /** Duplicate contacts merged */
846
+ CONTACT_DEDUPLICATED: 'contact-deduplicated'
847
+ };
848
+ /**
849
+ * Instance status values
850
+ * Represents the current state of a WhatsApp instance
851
+ */
852
+ const InstanceStatus = {
853
+ /** Instance is stopped */
854
+ STOPPED: 'stopped',
855
+ /** Instance was manually stopped by user */
856
+ MANUALLY_STOPPED: 'manually_stopped',
857
+ /** Instance is connecting to WhatsApp */
858
+ CONNECTING: 'connecting',
859
+ /** Waiting for QR code to be scanned */
860
+ PENDING_QR_CODE_SCAN: 'pending_qr_code_scan',
861
+ /** Instance is connected and ready */
862
+ CONNECTED: 'connected',
863
+ /** Instance encountered an error */
864
+ ERROR: 'error',
865
+ /** Instance was just created */
866
+ CREATED: 'created',
867
+ /** QR code scanning timed out */
868
+ QR_TIMEOUT: 'qr_timeout',
869
+ /** Payment pending for this instance */
870
+ PAYMENT_PENDING: 'payment_pending'
871
+ };
872
+ /**
873
+ * Message acknowledgment status
874
+ * Represents the delivery status of a sent message
875
+ */
876
+ const MessageAckStatus = {
877
+ /** Message is pending to be sent */
878
+ PENDING: 'pending',
879
+ /** Message was sent to server */
880
+ SENT: 'sent',
881
+ /** Message was delivered to recipient */
882
+ DELIVERED: 'delivered',
883
+ /** Message was read by recipient */
884
+ READ: 'read',
885
+ /** Audio/video message was played */
886
+ PLAYED: 'played'
887
+ };
888
+ /**
889
+ * Message types for incoming messages
890
+ * Identifies the type of content in a received message
891
+ */
892
+ const MessageType = {
893
+ /** Text message */
894
+ TEXT: 'text',
895
+ /** Image message */
896
+ IMAGE: 'image',
897
+ /** Video message */
898
+ VIDEO: 'video',
899
+ /** Audio message (voice note or file) */
900
+ AUDIO: 'audio',
901
+ /** Document/file message */
902
+ DOCUMENT: 'document',
903
+ /** Sticker message */
904
+ STICKER: 'sticker',
905
+ /** Location message */
906
+ LOCATION: 'location',
907
+ /** Contact card message */
908
+ CONTACT: 'contact',
909
+ /** Poll message */
910
+ POLL: 'poll',
911
+ /** Reaction message */
912
+ REACTION: 'reaction'
913
+ };
914
+ /**
915
+ * Webhook queue item status
916
+ * Used for monitoring webhook delivery status
917
+ */
918
+ const WebhookQueueStatus = {
919
+ /** Webhook is queued for delivery */
920
+ PENDING: 'pending',
921
+ /** Webhook is being processed */
922
+ PROCESSING: 'processing',
923
+ /** Webhook was successfully delivered */
924
+ DELIVERED: 'delivered',
925
+ /** Webhook delivery failed after retries */
926
+ FAILED: 'failed',
927
+ /** Webhook delivery is paused due to repeated failures */
928
+ PAUSED: 'paused'
929
+ };
930
+ /**
931
+ * Connection status for webhook events
932
+ * Sent in connection.update webhook events
933
+ */
934
+ const ConnectionStatus = {
935
+ /** Instance is connecting */
936
+ CONNECTING: 'connecting',
937
+ /** Instance is connected */
938
+ CONNECTED: 'connected',
939
+ /** Instance disconnected */
940
+ DISCONNECTED: 'disconnected',
941
+ /** Connection error occurred */
942
+ ERROR: 'error'
943
+ };
944
+
945
+ /**
946
+ * Webhook event type definitions
947
+ * Types for webhook payloads sent by ZapyAPI
948
+ */
949
+ /** Type guard for message events */
950
+ function isMessageEvent(event) {
951
+ return event.event === 'message';
952
+ }
953
+ /** Type guard for message status events */
954
+ function isMessageStatusEvent(event) {
955
+ return event.event === 'message-status';
956
+ }
957
+ /** Type guard for QR code events */
958
+ function isQRCodeEvent(event) {
959
+ return event.event === 'qr-code';
960
+ }
961
+ /** Type guard for contact created events */
962
+ function isContactCreatedEvent(event) {
963
+ return event.event === 'contact-created';
964
+ }
965
+ /** Type guard for contact updated events */
966
+ function isContactUpdatedEvent(event) {
967
+ return event.event === 'contact-updated';
968
+ }
969
+ /** Type guard for contact deduplicated events */
970
+ function isContactDeduplicatedEvent(event) {
971
+ return event.event === 'contact-deduplicated';
972
+ }
973
+
974
+ /**
975
+ * Phone number utilities for @zapyapi/sdk
976
+ */
977
+ /**
978
+ * Normalize a phone number to the format expected by the API
979
+ *
980
+ * @param phone - Phone number in various formats
981
+ * @returns Normalized phone number
982
+ *
983
+ * @example
984
+ * normalizePhone('11999999999') // '5511999999999'
985
+ * normalizePhone('5511999999999') // '5511999999999'
986
+ * normalizePhone('+5511999999999') // '5511999999999'
987
+ * normalizePhone('5511999999999@c.us') // '5511999999999@c.us'
988
+ */
989
+ function normalizePhone(phone) {
990
+ // If it's already a WhatsApp ID, return as-is
991
+ if (phone.includes('@')) {
992
+ return phone;
993
+ }
994
+ // Remove all non-numeric characters except +
995
+ let cleaned = phone.replace(/[^\d+]/g, '');
996
+ // Remove leading +
997
+ if (cleaned.startsWith('+')) {
998
+ cleaned = cleaned.slice(1);
999
+ }
1000
+ // If it doesn't start with 55 (Brazil), add it
1001
+ // This assumes Brazilian numbers as default - can be made configurable
1002
+ if (!cleaned.startsWith('55') && cleaned.length <= 11) {
1003
+ cleaned = '55' + cleaned;
1004
+ }
1005
+ return cleaned;
1006
+ }
1007
+ /**
1008
+ * Validate if a string is a valid phone number format
1009
+ *
1010
+ * @param phone - Phone number to validate
1011
+ * @returns Whether the phone number is valid
1012
+ */
1013
+ function isValidPhone(phone) {
1014
+ // WhatsApp IDs are valid
1015
+ if (phone.includes('@')) {
1016
+ return phone.endsWith('@c.us') || phone.endsWith('@g.us') || phone.endsWith('@lid');
1017
+ }
1018
+ // Clean and check if it's a valid number
1019
+ const cleaned = phone.replace(/[^\d]/g, '');
1020
+ // Brazilian number: 12-13 digits with country code
1021
+ // International: 10-15 digits
1022
+ return cleaned.length >= 10 && cleaned.length <= 15;
1023
+ }
1024
+ /**
1025
+ * Check if a WhatsApp ID is a group
1026
+ *
1027
+ * @param jid - WhatsApp ID (JID)
1028
+ * @returns Whether the JID is a group
1029
+ */
1030
+ function isGroup(jid) {
1031
+ return jid.endsWith('@g.us');
1032
+ }
1033
+ /**
1034
+ * Extract the phone number from a WhatsApp ID
1035
+ *
1036
+ * @param jid - WhatsApp ID (JID)
1037
+ * @returns Phone number without the @suffix
1038
+ */
1039
+ function extractPhone(jid) {
1040
+ const parts = jid.split('@');
1041
+ return parts[0] ?? jid;
1042
+ }
1043
+
1044
+ /**
1045
+ * Webhook signature verification utilities
1046
+ * HMAC-SHA256 signature verification for webhook payloads
1047
+ */
1048
+ /**
1049
+ * Verify webhook signature (synchronous)
1050
+ *
1051
+ * For best results, pass the raw request body as a string.
1052
+ *
1053
+ * @param payload - The webhook payload (raw body string recommended)
1054
+ * @param signature - The X-Webhook-Signature header value
1055
+ * @param secret - Your webhook secret
1056
+ * @returns true if the signature is valid
1057
+ *
1058
+ * @example
1059
+ * ```typescript
1060
+ * import { verifyWebhookSignature } from '@zapyapi/sdk';
1061
+ *
1062
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
1063
+ * const signature = req.headers['x-webhook-signature'] as string;
1064
+ * const isValid = verifyWebhookSignature(
1065
+ * req.body.toString('utf8'),
1066
+ * signature,
1067
+ * process.env.WEBHOOK_SECRET!
1068
+ * );
1069
+ *
1070
+ * if (!isValid) {
1071
+ * return res.status(401).send('Invalid signature');
1072
+ * }
1073
+ *
1074
+ * const event = JSON.parse(req.body.toString('utf8'));
1075
+ * res.status(200).send('OK');
1076
+ * });
1077
+ * ```
1078
+ */
1079
+ function verifyWebhookSignature(payload, signature, secret) {
1080
+ if (!signature || !secret) return false;
1081
+ try {
1082
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1083
+ const crypto = require('crypto');
1084
+ return verifySignatureWithCrypto(crypto, payload, signature, secret);
1085
+ } catch {
1086
+ if (typeof console !== 'undefined' && console.warn) {
1087
+ console.warn('@zapyapi/sdk: crypto module not available. ' + 'Use verifyWebhookSignatureAsync() or verify on your server.');
1088
+ }
1089
+ return false;
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Verify webhook signature (async - recommended for ESM)
1094
+ *
1095
+ * Uses dynamic import() for ESM compatibility.
1096
+ *
1097
+ * @param payload - The webhook payload (raw body string recommended)
1098
+ * @param signature - The X-Webhook-Signature header value
1099
+ * @param secret - Your webhook secret
1100
+ * @returns Promise resolving to true if valid
1101
+ *
1102
+ * @example
1103
+ * ```typescript
1104
+ * import { verifyWebhookSignatureAsync } from '@zapyapi/sdk';
1105
+ *
1106
+ * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
1107
+ * const signature = req.headers['x-webhook-signature'] as string;
1108
+ * const isValid = await verifyWebhookSignatureAsync(
1109
+ * req.body.toString('utf8'),
1110
+ * signature,
1111
+ * process.env.WEBHOOK_SECRET!
1112
+ * );
1113
+ *
1114
+ * if (!isValid) {
1115
+ * return res.status(401).send('Invalid signature');
1116
+ * }
1117
+ *
1118
+ * const event = JSON.parse(req.body.toString('utf8'));
1119
+ * res.status(200).send('OK');
1120
+ * });
1121
+ * ```
1122
+ */
1123
+ async function verifyWebhookSignatureAsync(payload, signature, secret) {
1124
+ if (!signature || !secret) return false;
1125
+ try {
1126
+ const crypto = await import('crypto');
1127
+ return verifySignatureWithCrypto(crypto, payload, signature, secret);
1128
+ } catch {
1129
+ if (typeof console !== 'undefined' && console.warn) {
1130
+ console.warn('@zapyapi/sdk: crypto module not available. ' + 'Verify signatures on your server instead.');
1131
+ }
1132
+ return false;
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Internal helper to verify signature with crypto module
1137
+ */
1138
+ function verifySignatureWithCrypto(crypto, payload, signature, secret) {
1139
+ const body = typeof payload === 'string' ? payload : JSON.stringify(payload);
1140
+ const expectedSignature = `sha256=${crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')}`;
1141
+ // Fast fail for wrong length
1142
+ if (signature.length !== expectedSignature.length) {
1143
+ return false;
1144
+ }
1145
+ // Timing-safe comparison to prevent timing attacks
1146
+ return crypto.timingSafeEqual(Buffer.from(signature, 'utf8'), Buffer.from(expectedSignature, 'utf8'));
1147
+ }
1148
+
1149
+ export { AuthenticationError, ConnectionStatus, InstanceNotFoundError, InstanceStatus, MessageAckStatus, MessageType, RateLimitError, ValidationError, WebhookEventType, WebhookQueueStatus, ZapyApiError, ZapyClient, ZapyError, createClient, extractPhone, isContactCreatedEvent, isContactDeduplicatedEvent, isContactUpdatedEvent, isGroup, isMessageEvent, isMessageStatusEvent, isQRCodeEvent, isValidPhone, normalizePhone, verifyWebhookSignature, verifyWebhookSignatureAsync };