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