@semboja/connect 0.1.0
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/LICENSE +21 -0
- package/README.md +304 -0
- package/dist/index.d.mts +739 -0
- package/dist/index.d.ts +739 -0
- package/dist/index.js +635 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +599 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
NetworkError: () => NetworkError,
|
|
25
|
+
NotFoundError: () => NotFoundError,
|
|
26
|
+
RateLimitError: () => RateLimitError,
|
|
27
|
+
SembojaClient: () => SembojaClient,
|
|
28
|
+
SembojaError: () => SembojaError,
|
|
29
|
+
ServerError: () => ServerError,
|
|
30
|
+
ValidationError: () => ValidationError,
|
|
31
|
+
parseWebhookPayload: () => parseWebhookPayload,
|
|
32
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/errors.ts
|
|
37
|
+
var SembojaError = class _SembojaError extends Error {
|
|
38
|
+
/** Error code from the API */
|
|
39
|
+
code;
|
|
40
|
+
/** HTTP status code */
|
|
41
|
+
statusCode;
|
|
42
|
+
/** Request ID for debugging */
|
|
43
|
+
requestId;
|
|
44
|
+
constructor(message, code, statusCode, requestId) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "SembojaError";
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.statusCode = statusCode;
|
|
49
|
+
this.requestId = requestId;
|
|
50
|
+
Object.setPrototypeOf(this, _SembojaError.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var AuthenticationError = class _AuthenticationError extends SembojaError {
|
|
54
|
+
constructor(message, requestId) {
|
|
55
|
+
super(message, "INVALID_API_KEY", 401, requestId);
|
|
56
|
+
this.name = "AuthenticationError";
|
|
57
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var RateLimitError = class _RateLimitError extends SembojaError {
|
|
61
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
62
|
+
resetAt;
|
|
63
|
+
constructor(message, requestId, resetAt) {
|
|
64
|
+
super(message, "RATE_LIMITED", 429, requestId);
|
|
65
|
+
this.name = "RateLimitError";
|
|
66
|
+
this.resetAt = resetAt;
|
|
67
|
+
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var ValidationError = class _ValidationError extends SembojaError {
|
|
71
|
+
constructor(message, requestId) {
|
|
72
|
+
super(message, "VALIDATION_ERROR", 400, requestId);
|
|
73
|
+
this.name = "ValidationError";
|
|
74
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var NotFoundError = class _NotFoundError extends SembojaError {
|
|
78
|
+
constructor(message, code, requestId) {
|
|
79
|
+
super(message, code, 404, requestId);
|
|
80
|
+
this.name = "NotFoundError";
|
|
81
|
+
Object.setPrototypeOf(this, _NotFoundError.prototype);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var ServerError = class _ServerError extends SembojaError {
|
|
85
|
+
constructor(message, requestId) {
|
|
86
|
+
super(message, "INTERNAL_ERROR", 500, requestId);
|
|
87
|
+
this.name = "ServerError";
|
|
88
|
+
Object.setPrototypeOf(this, _ServerError.prototype);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var NetworkError = class _NetworkError extends SembojaError {
|
|
92
|
+
constructor(message) {
|
|
93
|
+
super(message, "NETWORK_ERROR", 0);
|
|
94
|
+
this.name = "NetworkError";
|
|
95
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/lib/http.ts
|
|
100
|
+
function sleep(ms) {
|
|
101
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
|
+
}
|
|
103
|
+
function getBackoffDelay(attempt, baseDelay = 1e3) {
|
|
104
|
+
return Math.min(baseDelay * Math.pow(2, attempt), 3e4);
|
|
105
|
+
}
|
|
106
|
+
var HttpClient = class {
|
|
107
|
+
baseUrl;
|
|
108
|
+
apiKey;
|
|
109
|
+
timeout;
|
|
110
|
+
retries;
|
|
111
|
+
constructor(options) {
|
|
112
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
113
|
+
this.apiKey = options.apiKey;
|
|
114
|
+
this.timeout = options.timeout;
|
|
115
|
+
this.retries = options.retries;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Make an HTTP request with retry logic
|
|
119
|
+
*/
|
|
120
|
+
async request(options) {
|
|
121
|
+
const url = `${this.baseUrl}${options.path}`;
|
|
122
|
+
const headers = {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
"X-API-Key": this.apiKey,
|
|
125
|
+
...options.headers
|
|
126
|
+
};
|
|
127
|
+
let lastError = null;
|
|
128
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
132
|
+
const response = await fetch(url, {
|
|
133
|
+
method: options.method,
|
|
134
|
+
headers,
|
|
135
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
136
|
+
signal: controller.signal
|
|
137
|
+
});
|
|
138
|
+
clearTimeout(timeoutId);
|
|
139
|
+
const data = await response.json();
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
throw this.parseError(response.status, data);
|
|
142
|
+
}
|
|
143
|
+
return data;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
lastError = error;
|
|
146
|
+
if (error instanceof SembojaError) {
|
|
147
|
+
if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
152
|
+
throw new NetworkError(`Request timeout after ${this.timeout}ms`);
|
|
153
|
+
}
|
|
154
|
+
if (attempt < this.retries) {
|
|
155
|
+
const delay = getBackoffDelay(attempt);
|
|
156
|
+
await sleep(delay);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (lastError instanceof SembojaError) {
|
|
162
|
+
throw lastError;
|
|
163
|
+
}
|
|
164
|
+
throw new NetworkError(lastError?.message || "Request failed");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Parse error response into appropriate error class
|
|
168
|
+
*/
|
|
169
|
+
parseError(statusCode, data) {
|
|
170
|
+
const message = data.error?.message || "Unknown error";
|
|
171
|
+
const code = data.error?.code || "UNKNOWN_ERROR";
|
|
172
|
+
const requestId = data.meta?.request_id;
|
|
173
|
+
switch (statusCode) {
|
|
174
|
+
case 401:
|
|
175
|
+
return new AuthenticationError(message, requestId);
|
|
176
|
+
case 429:
|
|
177
|
+
return new RateLimitError(message, requestId);
|
|
178
|
+
case 400:
|
|
179
|
+
return new ValidationError(message, requestId);
|
|
180
|
+
case 404:
|
|
181
|
+
return new NotFoundError(message, code, requestId);
|
|
182
|
+
case 500:
|
|
183
|
+
case 502:
|
|
184
|
+
case 503:
|
|
185
|
+
return new ServerError(message, requestId);
|
|
186
|
+
default:
|
|
187
|
+
return new SembojaError(message, code, statusCode, requestId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* GET request
|
|
192
|
+
*/
|
|
193
|
+
async get(path) {
|
|
194
|
+
return this.request({ method: "GET", path });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* POST request
|
|
198
|
+
*/
|
|
199
|
+
async post(path, body) {
|
|
200
|
+
return this.request({ method: "POST", path, body });
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/resources/messages.ts
|
|
205
|
+
var Messages = class {
|
|
206
|
+
constructor(http) {
|
|
207
|
+
this.http = http;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Send a text message
|
|
211
|
+
*
|
|
212
|
+
* @param options - Text message options
|
|
213
|
+
* @returns Message response with message ID
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const result = await client.messages.sendText({
|
|
218
|
+
* phoneNumberId: '123456789',
|
|
219
|
+
* to: '+6281234567890',
|
|
220
|
+
* text: 'Hello, World!',
|
|
221
|
+
* previewUrl: true,
|
|
222
|
+
* });
|
|
223
|
+
* console.log('Message ID:', result.data.messages[0].id);
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
async sendText(options) {
|
|
227
|
+
return this.http.post("/api/v1/messages/text", {
|
|
228
|
+
phone_number_id: options.phoneNumberId,
|
|
229
|
+
to: options.to,
|
|
230
|
+
text: options.text,
|
|
231
|
+
preview_url: options.previewUrl,
|
|
232
|
+
reply_to: options.replyTo
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Send a template message
|
|
237
|
+
*
|
|
238
|
+
* @param options - Template message options
|
|
239
|
+
* @returns Message response with message ID
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const result = await client.messages.sendTemplate({
|
|
244
|
+
* phoneNumberId: '123456789',
|
|
245
|
+
* to: '+6281234567890',
|
|
246
|
+
* template: {
|
|
247
|
+
* name: 'hello_world',
|
|
248
|
+
* language: { code: 'en' },
|
|
249
|
+
* },
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
async sendTemplate(options) {
|
|
254
|
+
return this.http.post("/api/v1/messages/template", {
|
|
255
|
+
phone_number_id: options.phoneNumberId,
|
|
256
|
+
to: options.to,
|
|
257
|
+
template: options.template
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Send an image message
|
|
262
|
+
*
|
|
263
|
+
* @param options - Image message options
|
|
264
|
+
* @returns Message response with message ID
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* const result = await client.messages.sendImage({
|
|
269
|
+
* phoneNumberId: '123456789',
|
|
270
|
+
* to: '+6281234567890',
|
|
271
|
+
* image: {
|
|
272
|
+
* link: 'https://example.com/image.jpg',
|
|
273
|
+
* caption: 'Check this out!',
|
|
274
|
+
* },
|
|
275
|
+
* });
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
async sendImage(options) {
|
|
279
|
+
return this.http.post("/api/v1/messages", {
|
|
280
|
+
phone_number_id: options.phoneNumberId,
|
|
281
|
+
to: options.to,
|
|
282
|
+
type: "image",
|
|
283
|
+
image: options.image,
|
|
284
|
+
context: options.replyTo ? { message_id: options.replyTo } : void 0
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Send a video message
|
|
289
|
+
*
|
|
290
|
+
* @param options - Video message options
|
|
291
|
+
* @returns Message response with message ID
|
|
292
|
+
*/
|
|
293
|
+
async sendVideo(options) {
|
|
294
|
+
return this.http.post("/api/v1/messages", {
|
|
295
|
+
phone_number_id: options.phoneNumberId,
|
|
296
|
+
to: options.to,
|
|
297
|
+
type: "video",
|
|
298
|
+
video: options.video,
|
|
299
|
+
context: options.replyTo ? { message_id: options.replyTo } : void 0
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Send an audio message
|
|
304
|
+
*
|
|
305
|
+
* @param options - Audio message options
|
|
306
|
+
* @returns Message response with message ID
|
|
307
|
+
*/
|
|
308
|
+
async sendAudio(options) {
|
|
309
|
+
return this.http.post("/api/v1/messages", {
|
|
310
|
+
phone_number_id: options.phoneNumberId,
|
|
311
|
+
to: options.to,
|
|
312
|
+
type: "audio",
|
|
313
|
+
audio: options.audio,
|
|
314
|
+
context: options.replyTo ? { message_id: options.replyTo } : void 0
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Send a document message
|
|
319
|
+
*
|
|
320
|
+
* @param options - Document message options
|
|
321
|
+
* @returns Message response with message ID
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const result = await client.messages.sendDocument({
|
|
326
|
+
* phoneNumberId: '123456789',
|
|
327
|
+
* to: '+6281234567890',
|
|
328
|
+
* document: {
|
|
329
|
+
* link: 'https://example.com/invoice.pdf',
|
|
330
|
+
* filename: 'invoice.pdf',
|
|
331
|
+
* caption: 'Your invoice',
|
|
332
|
+
* },
|
|
333
|
+
* });
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
async sendDocument(options) {
|
|
337
|
+
return this.http.post("/api/v1/messages", {
|
|
338
|
+
phone_number_id: options.phoneNumberId,
|
|
339
|
+
to: options.to,
|
|
340
|
+
type: "document",
|
|
341
|
+
document: options.document,
|
|
342
|
+
context: options.replyTo ? { message_id: options.replyTo } : void 0
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Send a reaction to a message
|
|
347
|
+
*
|
|
348
|
+
* @param options - Reaction options
|
|
349
|
+
* @returns Message response
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // Add reaction
|
|
354
|
+
* await client.messages.sendReaction({
|
|
355
|
+
* phoneNumberId: '123456789',
|
|
356
|
+
* to: '+6281234567890',
|
|
357
|
+
* reaction: {
|
|
358
|
+
* messageId: 'wamid.xxx',
|
|
359
|
+
* emoji: '👍',
|
|
360
|
+
* },
|
|
361
|
+
* });
|
|
362
|
+
*
|
|
363
|
+
* // Remove reaction
|
|
364
|
+
* await client.messages.sendReaction({
|
|
365
|
+
* phoneNumberId: '123456789',
|
|
366
|
+
* to: '+6281234567890',
|
|
367
|
+
* reaction: {
|
|
368
|
+
* messageId: 'wamid.xxx',
|
|
369
|
+
* emoji: '',
|
|
370
|
+
* },
|
|
371
|
+
* });
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
async sendReaction(options) {
|
|
375
|
+
return this.http.post("/api/v1/messages", {
|
|
376
|
+
phone_number_id: options.phoneNumberId,
|
|
377
|
+
to: options.to,
|
|
378
|
+
type: "reaction",
|
|
379
|
+
reaction: {
|
|
380
|
+
message_id: options.reaction.messageId,
|
|
381
|
+
emoji: options.reaction.emoji
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Send an interactive message (buttons, lists, etc.)
|
|
387
|
+
*
|
|
388
|
+
* @param options - Interactive message options
|
|
389
|
+
* @returns Message response with message ID
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* // Button message
|
|
394
|
+
* await client.messages.sendInteractive({
|
|
395
|
+
* phoneNumberId: '123456789',
|
|
396
|
+
* to: '+6281234567890',
|
|
397
|
+
* interactive: {
|
|
398
|
+
* type: 'button',
|
|
399
|
+
* body: { text: 'Choose an option:' },
|
|
400
|
+
* action: {
|
|
401
|
+
* buttons: [
|
|
402
|
+
* { type: 'reply', reply: { id: 'yes', title: 'Yes' } },
|
|
403
|
+
* { type: 'reply', reply: { id: 'no', title: 'No' } },
|
|
404
|
+
* ],
|
|
405
|
+
* },
|
|
406
|
+
* },
|
|
407
|
+
* });
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
async sendInteractive(options) {
|
|
411
|
+
return this.http.post("/api/v1/messages", {
|
|
412
|
+
phone_number_id: options.phoneNumberId,
|
|
413
|
+
to: options.to,
|
|
414
|
+
type: "interactive",
|
|
415
|
+
interactive: options.interactive,
|
|
416
|
+
context: options.replyTo ? { message_id: options.replyTo } : void 0
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/resources/templates.ts
|
|
422
|
+
var Templates = class {
|
|
423
|
+
constructor(http) {
|
|
424
|
+
this.http = http;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* List all message templates
|
|
428
|
+
*
|
|
429
|
+
* @param options - Optional filters
|
|
430
|
+
* @returns List of templates
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* ```typescript
|
|
434
|
+
* const templates = await client.templates.list();
|
|
435
|
+
* console.log('Templates:', templates.data);
|
|
436
|
+
*
|
|
437
|
+
* // Filter by status
|
|
438
|
+
* const approved = await client.templates.list({ status: 'APPROVED' });
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
async list(options) {
|
|
442
|
+
let path = "/api/v1/templates";
|
|
443
|
+
if (options?.status) {
|
|
444
|
+
path += `?status=${encodeURIComponent(options.status)}`;
|
|
445
|
+
}
|
|
446
|
+
return this.http.get(path);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// src/resources/phone-numbers.ts
|
|
451
|
+
var PhoneNumbers = class {
|
|
452
|
+
constructor(http) {
|
|
453
|
+
this.http = http;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* List all phone numbers configured for your account
|
|
457
|
+
*
|
|
458
|
+
* @returns List of phone numbers
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* const phoneNumbers = await client.phoneNumbers.list();
|
|
463
|
+
* for (const phone of phoneNumbers.data) {
|
|
464
|
+
* console.log(`${phone.verified_name}: ${phone.display_phone_number}`);
|
|
465
|
+
* }
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
async list() {
|
|
469
|
+
return this.http.get("/api/v1/phone-numbers");
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/resources/usage.ts
|
|
474
|
+
var Usage = class {
|
|
475
|
+
constructor(http) {
|
|
476
|
+
this.http = http;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get current usage statistics for the billing period
|
|
480
|
+
*
|
|
481
|
+
* @returns Usage statistics
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```typescript
|
|
485
|
+
* const usage = await client.usage.get();
|
|
486
|
+
* console.log(`Period: ${usage.data.period.start} - ${usage.data.period.end}`);
|
|
487
|
+
* console.log(`Messages sent: ${usage.data.messages.sent}`);
|
|
488
|
+
* console.log(`Messages received: ${usage.data.messages.received}`);
|
|
489
|
+
* console.log(`API calls: ${usage.data.api_calls}`);
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
async get() {
|
|
493
|
+
return this.http.get("/api/v1/usage");
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/resources/test.ts
|
|
498
|
+
var Test = class {
|
|
499
|
+
constructor(http) {
|
|
500
|
+
this.http = http;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Trigger a test webhook to simulate an incoming message
|
|
504
|
+
*
|
|
505
|
+
* **Note:** This only works with test API keys (sk_test_*)
|
|
506
|
+
*
|
|
507
|
+
* @param options - Webhook trigger options
|
|
508
|
+
* @returns Success response
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* ```typescript
|
|
512
|
+
* await client.test.triggerWebhook({
|
|
513
|
+
* phoneNumberId: '123456789',
|
|
514
|
+
* type: 'text',
|
|
515
|
+
* from: '+6281234567890',
|
|
516
|
+
* text: 'Hello, this is a test incoming message!',
|
|
517
|
+
* });
|
|
518
|
+
* ```
|
|
519
|
+
*/
|
|
520
|
+
async triggerWebhook(options) {
|
|
521
|
+
return this.http.post("/api/v1/test/webhooks/trigger", {
|
|
522
|
+
phone_number_id: options.phoneNumberId,
|
|
523
|
+
type: options.type || "text",
|
|
524
|
+
from: options.from,
|
|
525
|
+
text: options.text
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/client.ts
|
|
531
|
+
var DEFAULT_BASE_URL = "https://connect.semboja.tech";
|
|
532
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
533
|
+
var DEFAULT_RETRIES = 3;
|
|
534
|
+
var SembojaClient = class {
|
|
535
|
+
/** Messages API */
|
|
536
|
+
messages;
|
|
537
|
+
/** Templates API */
|
|
538
|
+
templates;
|
|
539
|
+
/** Phone Numbers API */
|
|
540
|
+
phoneNumbers;
|
|
541
|
+
/** Usage API */
|
|
542
|
+
usage;
|
|
543
|
+
/** Test API (only works with sk_test_* keys) */
|
|
544
|
+
test;
|
|
545
|
+
/** Whether the client is in test mode */
|
|
546
|
+
isTestMode;
|
|
547
|
+
http;
|
|
548
|
+
/**
|
|
549
|
+
* Create a new Semboja client
|
|
550
|
+
*
|
|
551
|
+
* @param optionsOrApiKey - API key string or client options object
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```typescript
|
|
555
|
+
* // Using API key directly
|
|
556
|
+
* const client = new SembojaClient('sk_live_xxx');
|
|
557
|
+
*
|
|
558
|
+
* // Using options object
|
|
559
|
+
* const client = new SembojaClient({
|
|
560
|
+
* apiKey: 'sk_live_xxx',
|
|
561
|
+
* timeout: 60000,
|
|
562
|
+
* retries: 5,
|
|
563
|
+
* });
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
constructor(optionsOrApiKey) {
|
|
567
|
+
const options = typeof optionsOrApiKey === "string" ? { apiKey: optionsOrApiKey } : optionsOrApiKey;
|
|
568
|
+
if (!options.apiKey) {
|
|
569
|
+
throw new Error("API key is required");
|
|
570
|
+
}
|
|
571
|
+
if (!options.apiKey.startsWith("sk_live_") && !options.apiKey.startsWith("sk_test_")) {
|
|
572
|
+
throw new Error("Invalid API key format. Must start with sk_live_ or sk_test_");
|
|
573
|
+
}
|
|
574
|
+
this.isTestMode = options.apiKey.startsWith("sk_test_");
|
|
575
|
+
this.http = new HttpClient({
|
|
576
|
+
baseUrl: options.baseUrl || DEFAULT_BASE_URL,
|
|
577
|
+
apiKey: options.apiKey,
|
|
578
|
+
timeout: options.timeout || DEFAULT_TIMEOUT,
|
|
579
|
+
retries: options.retries ?? DEFAULT_RETRIES
|
|
580
|
+
});
|
|
581
|
+
this.messages = new Messages(this.http);
|
|
582
|
+
this.templates = new Templates(this.http);
|
|
583
|
+
this.phoneNumbers = new PhoneNumbers(this.http);
|
|
584
|
+
this.usage = new Usage(this.http);
|
|
585
|
+
this.test = new Test(this.http);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/webhooks/verify.ts
|
|
590
|
+
var import_crypto = require("crypto");
|
|
591
|
+
function verifyWebhookSignature(options) {
|
|
592
|
+
const { payload, signature, timestamp, secret } = options;
|
|
593
|
+
if (!payload || !signature || !timestamp || !secret) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
597
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
598
|
+
const tolerance = 5 * 60;
|
|
599
|
+
if (isNaN(timestampNum) || Math.abs(now - timestampNum) > tolerance) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const payloadString = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
603
|
+
const signatureData = timestamp + payloadString;
|
|
604
|
+
const expectedSignature = "sha256=" + (0, import_crypto.createHmac)("sha256", secret).update(signatureData).digest("hex");
|
|
605
|
+
try {
|
|
606
|
+
const sigBuffer = Buffer.from(signature);
|
|
607
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
608
|
+
if (sigBuffer.length !== expectedBuffer.length) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
return (0, import_crypto.timingSafeEqual)(sigBuffer, expectedBuffer);
|
|
612
|
+
} catch {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function parseWebhookPayload(payload) {
|
|
617
|
+
if (typeof payload === "string") {
|
|
618
|
+
return JSON.parse(payload);
|
|
619
|
+
}
|
|
620
|
+
return payload;
|
|
621
|
+
}
|
|
622
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
623
|
+
0 && (module.exports = {
|
|
624
|
+
AuthenticationError,
|
|
625
|
+
NetworkError,
|
|
626
|
+
NotFoundError,
|
|
627
|
+
RateLimitError,
|
|
628
|
+
SembojaClient,
|
|
629
|
+
SembojaError,
|
|
630
|
+
ServerError,
|
|
631
|
+
ValidationError,
|
|
632
|
+
parseWebhookPayload,
|
|
633
|
+
verifyWebhookSignature
|
|
634
|
+
});
|
|
635
|
+
//# sourceMappingURL=index.js.map
|