@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.
- package/README.md +308 -0
- package/index.cjs +1176 -0
- package/index.js +1149 -0
- package/package.json +55 -0
- package/src/client.d.ts +98 -0
- package/src/client.d.ts.map +1 -0
- package/src/errors.d.ts +70 -0
- package/src/errors.d.ts.map +1 -0
- package/src/generated/Api.d.ts +1542 -0
- package/src/generated/Api.d.ts.map +1 -0
- package/src/index.d.ts +38 -0
- package/src/index.d.ts.map +1 -0
- package/src/resources/base.resource.d.ts +17 -0
- package/src/resources/base.resource.d.ts.map +1 -0
- package/src/resources/index.d.ts +8 -0
- package/src/resources/index.d.ts.map +1 -0
- package/src/resources/instances.resource.d.ts +75 -0
- package/src/resources/instances.resource.d.ts.map +1 -0
- package/src/resources/messages.resource.d.ts +159 -0
- package/src/resources/messages.resource.d.ts.map +1 -0
- package/src/resources/webhooks.resource.d.ts +115 -0
- package/src/resources/webhooks.resource.d.ts.map +1 -0
- package/src/types/common.types.d.ts +89 -0
- package/src/types/common.types.d.ts.map +1 -0
- package/src/types/enums.d.ts +142 -0
- package/src/types/enums.d.ts.map +1 -0
- package/src/types/index.d.ts +11 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/instances.types.d.ts +114 -0
- package/src/types/instances.types.d.ts.map +1 -0
- package/src/types/messages.types.d.ts +166 -0
- package/src/types/messages.types.d.ts.map +1 -0
- package/src/types/webhook-config.types.d.ts +60 -0
- package/src/types/webhook-config.types.d.ts.map +1 -0
- package/src/types/webhook-events.types.d.ts +232 -0
- package/src/types/webhook-events.types.d.ts.map +1 -0
- package/src/utils/index.d.ts +6 -0
- package/src/utils/index.d.ts.map +1 -0
- package/src/utils/phone.d.ts +38 -0
- package/src/utils/phone.d.ts.map +1 -0
- package/src/utils/webhook-signature.d.ts +69 -0
- package/src/utils/webhook-signature.d.ts.map +1 -0
- package/src/version.d.ts +3 -0
- 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;
|