semaphoreco 1.0.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/README.md +435 -0
- package/dist/index.cjs +520 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +507 -0
- package/dist/index.d.ts +507 -0
- package/dist/index.js +508 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# semaphoreco
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/semaphoreco)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Official TypeScript/JavaScript client for the [Semaphore.co](https://semaphore.co) SMS Gateway API (Philippines).
|
|
7
|
+
|
|
8
|
+
> **Note:** This package was previously published as `semaphore-client-js`. If you're migrating, simply update your package name - the API is unchanged.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install semaphoreco
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or with yarn:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yarn add semaphoreco
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or with pnpm:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add semaphoreco
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { SemaphoreClient } from 'semaphoreco';
|
|
32
|
+
|
|
33
|
+
const client = new SemaphoreClient({ apiKey: 'your-api-key' });
|
|
34
|
+
|
|
35
|
+
// Send an SMS
|
|
36
|
+
const result = await client.messages.send({
|
|
37
|
+
number: '639171234567',
|
|
38
|
+
message: 'Hello from Semaphore!',
|
|
39
|
+
sendername: 'SEMAPHORE',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log('Message ID:', result.message_id);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
Create a client instance with your API key:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { SemaphoreClient } from 'semaphoreco';
|
|
51
|
+
|
|
52
|
+
const client = new SemaphoreClient({
|
|
53
|
+
apiKey: 'your-api-key', // Required (or set SEMAPHORE_API_KEY env var)
|
|
54
|
+
baseUrl: 'https://api.semaphore.co/api/v4', // Optional, this is the default
|
|
55
|
+
timeout: 30000, // Optional, request timeout in ms (default: 30000)
|
|
56
|
+
onRetry: (attempt, delayMs) => {
|
|
57
|
+
console.log(`Retry attempt ${attempt} after ${delayMs}ms`);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Configuration Options
|
|
63
|
+
|
|
64
|
+
| Option | Type | Default | Description |
|
|
65
|
+
|--------|------|---------|-------------|
|
|
66
|
+
| `apiKey` | `string` | `SEMAPHORE_API_KEY` env var | Your Semaphore API key |
|
|
67
|
+
| `baseUrl` | `string` | `https://api.semaphore.co/api/v4` | API base URL |
|
|
68
|
+
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
69
|
+
| `onRetry` | `function` | `undefined` | Callback when rate-limited request is retried |
|
|
70
|
+
|
|
71
|
+
### Environment Variable
|
|
72
|
+
|
|
73
|
+
You can set your API key via environment variable instead of passing it to the constructor:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export SEMAPHORE_API_KEY=your-api-key
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// API key will be read from SEMAPHORE_API_KEY
|
|
81
|
+
const client = new SemaphoreClient();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
### Messages (`client.messages`)
|
|
87
|
+
|
|
88
|
+
#### `send(params)`
|
|
89
|
+
|
|
90
|
+
Send an SMS to one or more recipients.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Send to a single recipient
|
|
94
|
+
const response = await client.messages.send({
|
|
95
|
+
number: '639171234567',
|
|
96
|
+
message: 'Hello!',
|
|
97
|
+
sendername: 'SEMAPHORE',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
console.log(response.message_id); // 12345
|
|
101
|
+
console.log(response.status); // 'Queued'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Bulk send** - send to multiple recipients (up to 1000):
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const responses = await client.messages.send({
|
|
108
|
+
number: ['639171234567', '639181234567', '639191234567'],
|
|
109
|
+
message: 'Hello everyone!',
|
|
110
|
+
sendername: 'SEMAPHORE',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Returns array of responses
|
|
114
|
+
for (const response of responses) {
|
|
115
|
+
console.log(`Sent to ${response.recipient}: ${response.message_id}`);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Request Parameters:**
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Required | Description |
|
|
122
|
+
|-----------|------|----------|-------------|
|
|
123
|
+
| `number` | `string \| string[]` | Yes | Recipient number(s). Max 1000 for bulk |
|
|
124
|
+
| `message` | `string` | Yes | Message content |
|
|
125
|
+
| `sendername` | `string` | Yes | Approved sender name |
|
|
126
|
+
|
|
127
|
+
**Response:**
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
interface SendMessageResponse {
|
|
131
|
+
message_id: number;
|
|
132
|
+
user_id: number;
|
|
133
|
+
user: string;
|
|
134
|
+
account_id: number;
|
|
135
|
+
account: string;
|
|
136
|
+
recipient: string;
|
|
137
|
+
message: string;
|
|
138
|
+
sender_name: string;
|
|
139
|
+
network: string;
|
|
140
|
+
status: string;
|
|
141
|
+
type: string;
|
|
142
|
+
source: string;
|
|
143
|
+
created_at: string;
|
|
144
|
+
updated_at: string;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `list(params?)`
|
|
149
|
+
|
|
150
|
+
List sent messages with optional filtering.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// List all messages
|
|
154
|
+
const messages = await client.messages.list();
|
|
155
|
+
|
|
156
|
+
// List with filters
|
|
157
|
+
const filtered = await client.messages.list({
|
|
158
|
+
page: 1,
|
|
159
|
+
limit: 50,
|
|
160
|
+
status: 'Sent',
|
|
161
|
+
startDate: '2024-01-01',
|
|
162
|
+
endDate: '2024-01-31',
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Filter Parameters:**
|
|
167
|
+
|
|
168
|
+
| Parameter | Type | Description |
|
|
169
|
+
|-----------|------|-------------|
|
|
170
|
+
| `page` | `number` | Page number for pagination |
|
|
171
|
+
| `limit` | `number` | Number of results per page |
|
|
172
|
+
| `startDate` | `string` | Filter by start date (YYYY-MM-DD) |
|
|
173
|
+
| `endDate` | `string` | Filter by end date (YYYY-MM-DD) |
|
|
174
|
+
| `network` | `string` | Filter by network |
|
|
175
|
+
| `status` | `string` | Filter by status |
|
|
176
|
+
| `sendername` | `string` | Filter by sender name |
|
|
177
|
+
|
|
178
|
+
#### `get(id)`
|
|
179
|
+
|
|
180
|
+
Retrieve a specific message by ID.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const message = await client.messages.get(12345);
|
|
184
|
+
|
|
185
|
+
console.log(message.status); // 'Sent'
|
|
186
|
+
console.log(message.recipient); // '639171234567'
|
|
187
|
+
console.log(message.message); // 'Hello!'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### OTP (`client.otp`)
|
|
193
|
+
|
|
194
|
+
Send OTP messages via priority route with no rate limit. Each OTP message costs 2 credits per 160 characters.
|
|
195
|
+
|
|
196
|
+
#### `send(params)`
|
|
197
|
+
|
|
198
|
+
Send an OTP message. The API generates the OTP code and replaces the `{otp}` placeholder in your message.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const response = await client.otp.send({
|
|
202
|
+
number: '639171234567',
|
|
203
|
+
message: 'Your verification code is {otp}',
|
|
204
|
+
sendername: 'MyApp',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
console.log('OTP code:', response.code); // '123456'
|
|
208
|
+
console.log('Message ID:', response.message_id);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**With custom code length:**
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const response = await client.otp.send({
|
|
215
|
+
number: '639171234567',
|
|
216
|
+
message: 'Your code: {otp}',
|
|
217
|
+
sendername: 'MyApp',
|
|
218
|
+
code_length: 4, // Generate 4-digit code instead of default 6
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Request Parameters:**
|
|
223
|
+
|
|
224
|
+
| Parameter | Type | Required | Description |
|
|
225
|
+
|-----------|------|----------|-------------|
|
|
226
|
+
| `number` | `string` | Yes | Recipient phone number |
|
|
227
|
+
| `message` | `string` | Yes | Message template with `{otp}` placeholder |
|
|
228
|
+
| `sendername` | `string` | Yes | Approved sender name |
|
|
229
|
+
| `code_length` | `number` | No | OTP code length (default: 6) |
|
|
230
|
+
|
|
231
|
+
**Response:**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface SendOtpResponse {
|
|
235
|
+
message_id: number;
|
|
236
|
+
code: string; // The generated OTP code
|
|
237
|
+
user_id: number;
|
|
238
|
+
user: string;
|
|
239
|
+
account_id: number;
|
|
240
|
+
account: string;
|
|
241
|
+
recipient: string;
|
|
242
|
+
message: string;
|
|
243
|
+
sender_name: string;
|
|
244
|
+
network: string;
|
|
245
|
+
status: string;
|
|
246
|
+
type: string;
|
|
247
|
+
source: string;
|
|
248
|
+
created_at: string;
|
|
249
|
+
updated_at: string;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### Account (`client.account`)
|
|
256
|
+
|
|
257
|
+
#### `info()`
|
|
258
|
+
|
|
259
|
+
Get account information including credit balance.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const info = await client.account.info();
|
|
263
|
+
|
|
264
|
+
console.log('Account:', info.account_name);
|
|
265
|
+
console.log('Status:', info.status);
|
|
266
|
+
console.log('Balance:', info.credit_balance);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Response:**
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
interface AccountInfo {
|
|
273
|
+
account_id: number;
|
|
274
|
+
account_name: string;
|
|
275
|
+
status: string;
|
|
276
|
+
credit_balance: string;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `transactions(params?)`
|
|
281
|
+
|
|
282
|
+
Get transaction history with optional filtering.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Get all transactions
|
|
286
|
+
const transactions = await client.account.transactions();
|
|
287
|
+
|
|
288
|
+
// Get with pagination and date range
|
|
289
|
+
const filtered = await client.account.transactions({
|
|
290
|
+
page: 1,
|
|
291
|
+
limit: 50,
|
|
292
|
+
startDate: '2024-01-01',
|
|
293
|
+
endDate: '2024-01-31',
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
for (const tx of filtered) {
|
|
297
|
+
console.log(`${tx.type}: ${tx.amount} credits - ${tx.description}`);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Filter Parameters:**
|
|
302
|
+
|
|
303
|
+
| Parameter | Type | Description |
|
|
304
|
+
|-----------|------|-------------|
|
|
305
|
+
| `page` | `number` | Page number for pagination |
|
|
306
|
+
| `limit` | `number` | Number of results per page |
|
|
307
|
+
| `startDate` | `string` | Filter by start date (YYYY-MM-DD) |
|
|
308
|
+
| `endDate` | `string` | Filter by end date (YYYY-MM-DD) |
|
|
309
|
+
|
|
310
|
+
**Response:**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
interface Transaction {
|
|
314
|
+
id: number;
|
|
315
|
+
account_id: number;
|
|
316
|
+
user_id: number;
|
|
317
|
+
type: string;
|
|
318
|
+
amount: string;
|
|
319
|
+
balance_before: string;
|
|
320
|
+
balance_after: string;
|
|
321
|
+
description: string;
|
|
322
|
+
created_at: string;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `senderNames()`
|
|
327
|
+
|
|
328
|
+
Get list of approved sender names.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
const senderNames = await client.account.senderNames();
|
|
332
|
+
|
|
333
|
+
for (const sender of senderNames) {
|
|
334
|
+
console.log(`${sender.name}: ${sender.status}`);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Response:**
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
interface SenderName {
|
|
342
|
+
name: string;
|
|
343
|
+
status: string;
|
|
344
|
+
created_at: string;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Error Handling
|
|
349
|
+
|
|
350
|
+
The library throws typed errors for different failure scenarios:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import {
|
|
354
|
+
SemaphoreClient,
|
|
355
|
+
SemaphoreError,
|
|
356
|
+
AuthenticationError,
|
|
357
|
+
RateLimitError,
|
|
358
|
+
ValidationError,
|
|
359
|
+
NetworkError,
|
|
360
|
+
TimeoutError,
|
|
361
|
+
} from 'semaphoreco';
|
|
362
|
+
|
|
363
|
+
const client = new SemaphoreClient({ apiKey: 'your-api-key' });
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
await client.messages.send({
|
|
367
|
+
number: '639171234567',
|
|
368
|
+
message: 'Hello!',
|
|
369
|
+
sendername: 'SEMAPHORE',
|
|
370
|
+
});
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error instanceof RateLimitError) {
|
|
373
|
+
// Rate limited (429) - retry after delay
|
|
374
|
+
console.log('Rate limited, retry after:', error.retryAfter, 'seconds');
|
|
375
|
+
} else if (error instanceof AuthenticationError) {
|
|
376
|
+
// Invalid API key (401)
|
|
377
|
+
console.log('Invalid API key');
|
|
378
|
+
} else if (error instanceof ValidationError) {
|
|
379
|
+
// Invalid request parameters (400)
|
|
380
|
+
console.log('Validation error:', error.message);
|
|
381
|
+
console.log('Field:', error.field);
|
|
382
|
+
} else if (error instanceof TimeoutError) {
|
|
383
|
+
// Request timed out
|
|
384
|
+
console.log('Request timed out after:', error.timeoutMs, 'ms');
|
|
385
|
+
} else if (error instanceof NetworkError) {
|
|
386
|
+
// Network connection failed
|
|
387
|
+
console.log('Network error:', error.message);
|
|
388
|
+
console.log('Cause:', error.cause);
|
|
389
|
+
} else if (error instanceof SemaphoreError) {
|
|
390
|
+
// Other API errors (5xx, etc.)
|
|
391
|
+
console.log('API error:', error.message);
|
|
392
|
+
console.log('Status:', error.statusCode);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Error Types
|
|
398
|
+
|
|
399
|
+
| Error | Status Code | Description |
|
|
400
|
+
|-------|-------------|-------------|
|
|
401
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
402
|
+
| `RateLimitError` | 429 | Rate limit exceeded (120 req/min for standard SMS) |
|
|
403
|
+
| `ValidationError` | 400 | Invalid request parameters |
|
|
404
|
+
| `NetworkError` | - | Connection failure, DNS error |
|
|
405
|
+
| `TimeoutError` | - | Request exceeded timeout |
|
|
406
|
+
| `SemaphoreError` | 5xx | Server errors and other failures |
|
|
407
|
+
|
|
408
|
+
### Automatic Retry
|
|
409
|
+
|
|
410
|
+
The client automatically retries rate-limited requests (429 responses) with exponential backoff. You can track retries with the `onRetry` callback:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
const client = new SemaphoreClient({
|
|
414
|
+
apiKey: 'your-api-key',
|
|
415
|
+
onRetry: (attempt, delayMs) => {
|
|
416
|
+
console.log(`Rate limited. Retry attempt ${attempt} in ${delayMs}ms`);
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## SMS Credits
|
|
422
|
+
|
|
423
|
+
- **Standard SMS**: 1 credit per 160 ASCII characters
|
|
424
|
+
- **OTP SMS**: 2 credits per 160 ASCII characters (priority route)
|
|
425
|
+
- **Long messages**: Split into 153-character segments
|
|
426
|
+
- **Unicode**: Limited to 70 characters per segment
|
|
427
|
+
|
|
428
|
+
## Requirements
|
|
429
|
+
|
|
430
|
+
- Node.js 18.0.0 or later (uses native `fetch`)
|
|
431
|
+
- Also works with Deno, Bun, and other modern JavaScript runtimes
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
MIT
|