unismsgateway 1.3.0 → 1.3.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 +328 -131
- package/dist/scripts/test-live.d.ts +11 -0
- package/dist/scripts/test-live.js +188 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +13 -0
- package/dist/src/lib/hubtel-gateway.d.ts +13 -0
- package/dist/src/lib/hubtel-gateway.js +57 -0
- package/dist/src/lib/lib.d.ts +5 -0
- package/dist/src/lib/lib.js +20 -0
- package/dist/src/lib/nest-gateway.d.ts +12 -0
- package/dist/src/lib/nest-gateway.js +156 -0
- package/dist/src/lib/platform.d.ts +13 -0
- package/dist/src/lib/platform.js +85 -0
- package/dist/src/lib/route-gateway.d.ts +12 -0
- package/dist/src/lib/route-gateway.js +70 -0
- package/dist/src/lib/types.d.ts +47 -0
- package/dist/src/lib/types.js +2 -0
- package/package.json +6 -2
- /package/dist/lib/{types.d.ts → j;[ ]p;;;j]/ts} +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
# Unified
|
|
1
|
+
# Unified SMS Gateway
|
|
2
2
|
|
|
3
|
-
Most
|
|
4
|
-
However, each sms api specification is different from the other, hence the need to create separate implementation
|
|
5
|
-
for each sms gateway.
|
|
6
|
-
|
|
7
|
-
Unified sms gateway is library that brings most common sms gateways under a Unified api.
|
|
8
|
-
which means you only does one implementation in it works for all supported sms gateway.
|
|
9
|
-
you just have select or switch your sms platform and your code still works fine like nothing has changed
|
|
3
|
+
Most projects rely on more than one SMS provider so they can switch if a gateway is unavailable. Each provider’s API differs, so separate integrations are usually required.
|
|
10
4
|
|
|
5
|
+
**unismsgateway** exposes a single API for multiple SMS gateways. You implement once, then select or switch the platform; your send flow stays the same.
|
|
11
6
|
|
|
12
7
|
## Installation
|
|
13
8
|
|
|
@@ -15,170 +10,372 @@ you just have select or switch your sms platform and your code still works fine
|
|
|
15
10
|
npm install unismsgateway
|
|
16
11
|
```
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
| Platform ID | Provider | Required Params |
|
|
21
|
-
|-------------|----------|-----------------|
|
|
22
|
-
| `route` | routeMobile | username, password, host |
|
|
23
|
-
| `hubtel` | Hubtel SMS (Ghana) | clientId, clientSecret |
|
|
24
|
-
| `nest` | SMSOnlineGH / smsonlinegh | apiKey |
|
|
13
|
+
**Requirements:** Node.js `>= 12.0.0` (see `package.json` `engines`).
|
|
25
14
|
|
|
26
|
-
##
|
|
15
|
+
## Module import
|
|
27
16
|
|
|
28
|
-
|
|
17
|
+
**CommonJS**
|
|
29
18
|
|
|
30
19
|
```javascript
|
|
31
|
-
const unisms = require('unismsgateway')
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
protocol: 'http',
|
|
41
|
-
port: 8080
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// For Hubtel
|
|
46
|
-
const hubtelGateway = unisms.init({
|
|
47
|
-
platformId: 'hubtel',
|
|
48
|
-
param: {
|
|
49
|
-
clientId: 'your-client-id',
|
|
50
|
-
clientSecret: 'your-client-secret'
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// For SMSOnlineGH (nest)
|
|
55
|
-
const nestGateway = unisms.init({
|
|
56
|
-
platformId: 'nest',
|
|
57
|
-
param: {
|
|
58
|
-
apiKey: 'your-api-key',
|
|
59
|
-
// Optional: host, protocol (defaults to api.smsonlinegh.com, https)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
20
|
+
const unisms = require('unismsgateway');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**ESM / TypeScript**
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import * as unisms from 'unismsgateway';
|
|
27
|
+
// or named:
|
|
28
|
+
import { init, getSmsPlatform, reset, smsPlatform } from 'unismsgateway';
|
|
62
29
|
```
|
|
63
30
|
|
|
64
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Configuration overview
|
|
34
|
+
|
|
35
|
+
### `IgatewaySettings`
|
|
36
|
+
|
|
37
|
+
| Field | Type | Description |
|
|
38
|
+
|-------|------|-------------|
|
|
39
|
+
| `platformId` | `'route' \| 'hubtel' \| 'nest'` | Which gateway to use. |
|
|
40
|
+
| `param` | `IgatewayParam` | Provider-specific options (see below). |
|
|
41
|
+
|
|
42
|
+
### `IgatewayParam` (all fields optional except what your `platformId` requires)
|
|
43
|
+
|
|
44
|
+
| Field | Type | Used by | Description |
|
|
45
|
+
|-------|------|---------|-------------|
|
|
46
|
+
| `username` | `string` | `route` | Route Mobile account username. **Required** for `route`. |
|
|
47
|
+
| `password` | `string` | `route` | Route Mobile account password. **Required** for `route`. |
|
|
48
|
+
| `host` | `string` | `route`, `nest` | API host. See per-gateway defaults below. |
|
|
49
|
+
| `port` | `number` | `route` | TCP port for Route Mobile. Default: `8080`. |
|
|
50
|
+
| `protocol` | `'http' \| 'https'` | `route`, `nest` | URL scheme. See defaults per gateway. |
|
|
51
|
+
| `clientId` | `string` | `hubtel` | Hubtel client ID. **Required** for `hubtel`. |
|
|
52
|
+
| `clientSecret` | `string` | `hubtel` | Hubtel client secret. **Required** for `hubtel`. |
|
|
53
|
+
| `apiKey` | `string` | `nest` | SMSOnlineGH API key (`Authorization: key …`). **Required** for `nest`. |
|
|
54
|
+
|
|
55
|
+
Validation runs in `smsPlatform` when the instance is constructed: missing required fields for the chosen `platformId` throw `Error` with a clear message.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Environment variables
|
|
60
|
+
|
|
61
|
+
**This library does not read `process.env` or any configuration files.** You pass all credentials and endpoints explicitly in `init({ platformId, param })`.
|
|
62
|
+
|
|
63
|
+
In your own application it is common to map environment variables into `param`. Suggested names (you define these in `.env` or your host’s secret store):
|
|
64
|
+
|
|
65
|
+
| Suggested env name | Maps to `param` | Gateways |
|
|
66
|
+
|--------------------|-----------------|----------|
|
|
67
|
+
| `SMS_PLATFORM_ID` | `platformId` | all |
|
|
68
|
+
| `ROUTE_SMS_USERNAME` | `username` | `route` |
|
|
69
|
+
| `ROUTE_SMS_PASSWORD` | `password` | `route` |
|
|
70
|
+
| `ROUTE_SMS_HOST` | `host` | `route` (optional; has default) |
|
|
71
|
+
| `ROUTE_SMS_PORT` | `port` | `route` (optional) |
|
|
72
|
+
| `ROUTE_SMS_PROTOCOL` | `protocol` | `route` (optional) |
|
|
73
|
+
| `HUBTEL_CLIENT_ID` | `clientId` | `hubtel` |
|
|
74
|
+
| `HUBTEL_CLIENT_SECRET` | `clientSecret` | `hubtel` |
|
|
75
|
+
| `SMSONLINEGH_API_KEY` or `NEST_API_KEY` | `apiKey` | `nest` |
|
|
76
|
+
| `SMSONLINEGH_HOST` or `NEST_HOST` | `host` | `nest` (optional) |
|
|
77
|
+
| `SMSONLINEGH_PROTOCOL` or `NEST_PROTOCOL` | `protocol` | `nest` (optional) |
|
|
78
|
+
|
|
79
|
+
Example wiring (conceptual): branch on `platformId` and build `param` so you do not mix unrelated fields.
|
|
65
80
|
|
|
66
81
|
```javascript
|
|
67
|
-
const unisms = require('unismsgateway')
|
|
82
|
+
const unisms = require('unismsgateway');
|
|
83
|
+
|
|
84
|
+
const platformId = process.env.SMS_PLATFORM_ID;
|
|
85
|
+
|
|
86
|
+
const paramByPlatform = {
|
|
87
|
+
route: {
|
|
88
|
+
username: process.env.ROUTE_SMS_USERNAME,
|
|
89
|
+
password: process.env.ROUTE_SMS_PASSWORD,
|
|
90
|
+
host: process.env.ROUTE_SMS_HOST,
|
|
91
|
+
port: process.env.ROUTE_SMS_PORT ? Number(process.env.ROUTE_SMS_PORT) : undefined,
|
|
92
|
+
protocol: process.env.ROUTE_SMS_PROTOCOL
|
|
93
|
+
},
|
|
94
|
+
hubtel: {
|
|
95
|
+
clientId: process.env.HUBTEL_CLIENT_ID,
|
|
96
|
+
clientSecret: process.env.HUBTEL_CLIENT_SECRET
|
|
97
|
+
},
|
|
98
|
+
nest: {
|
|
99
|
+
apiKey: process.env.SMSONLINEGH_API_KEY,
|
|
100
|
+
host: process.env.SMSONLINEGH_HOST,
|
|
101
|
+
protocol: process.env.SMSONLINEGH_PROTOCOL
|
|
102
|
+
}
|
|
103
|
+
};
|
|
68
104
|
|
|
69
|
-
// Initialize gateway
|
|
70
105
|
const gateway = unisms.init({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
})
|
|
106
|
+
platformId,
|
|
107
|
+
param: paramByPlatform[platformId]
|
|
108
|
+
});
|
|
109
|
+
```
|
|
76
110
|
|
|
77
|
-
|
|
78
|
-
async function sendSms() {
|
|
79
|
-
try {
|
|
80
|
-
const result = await gateway.quickSend({
|
|
81
|
-
From: 'SenderName',
|
|
82
|
-
To: '233XXXXXXXXX', // recipient number
|
|
83
|
-
Content: 'Hello from unismsgateway!',
|
|
84
|
-
Type: 0 // optional, defaults to 0
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
if (result.success) {
|
|
88
|
-
console.log('Message sent successfully:', result.messageId)
|
|
89
|
-
} else {
|
|
90
|
-
console.error('Failed to send:', result.error)
|
|
91
|
-
}
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.error('Error:', err)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
111
|
+
---
|
|
96
112
|
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
## How initialization works
|
|
114
|
+
|
|
115
|
+
1. **`init(settings: IgatewaySettings): smsPlatform`** (in `src/lib/lib.ts`):
|
|
116
|
+
- Validates and constructs a new `smsPlatform` with your `settings`.
|
|
117
|
+
- Stores it as the **module singleton** (`smsPlatformInstance`).
|
|
118
|
+
- Calls `smsPlatform.init()` on that instance (returns the same facade for chaining).
|
|
119
|
+
- Returns the `smsPlatform` instance.
|
|
120
|
+
|
|
121
|
+
2. **`smsPlatform` constructor** (in `src/lib/platform.ts`):
|
|
122
|
+
- Runs `validateSettings()` (platform id + required `param` fields for that id).
|
|
123
|
+
- Calls `createGateway()` to instantiate the underlying provider (`routeSms`, `HubtelSms`, or `NestSmsGateway`).
|
|
124
|
+
|
|
125
|
+
3. **`getSmsPlatform(): smsPlatform | null`**: Returns the current singleton, or `null` if `reset()` was called and no new `init()` has run.
|
|
99
126
|
|
|
100
|
-
|
|
127
|
+
There is **no async bootstrap**; after `init()` returns, `quickSend` is ready.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Re-initializing and reset
|
|
132
|
+
|
|
133
|
+
- **Switch platform or credentials:** Call **`init(newSettings)`** again. Each call **replaces** the stored singleton with a new `smsPlatform`. You do not have to call `reset()` first.
|
|
134
|
+
- **Clear the singleton:** **`reset()`** sets the internal reference to `null`. `getSmsPlatform()` then returns `null` until the next `init()`. Use this when you want to guarantee nothing holds a gateway instance (e.g. tests or explicit teardown).
|
|
101
135
|
|
|
102
136
|
```javascript
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
const unisms = require('unismsgateway');
|
|
138
|
+
|
|
139
|
+
const a = unisms.init({ platformId: 'nest', param: { apiKey: 'key-1' } });
|
|
140
|
+
// Later: new config
|
|
141
|
+
const b = unisms.init({ platformId: 'hubtel', param: { clientId: 'x', clientSecret: 'y' } });
|
|
142
|
+
// b replaces a; unisms.getSmsPlatform() === b
|
|
143
|
+
|
|
144
|
+
unisms.reset();
|
|
145
|
+
// unisms.getSmsPlatform() === null
|
|
146
|
+
|
|
147
|
+
const c = unisms.init({ platformId: 'nest', param: { apiKey: 'key-2' } });
|
|
110
148
|
```
|
|
111
149
|
|
|
112
|
-
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Supported gateways
|
|
153
|
+
|
|
154
|
+
| `platformId` | Provider | Package / implementation |
|
|
155
|
+
|----------------|----------|---------------------------|
|
|
156
|
+
| `route` | Route Mobile | `routemobilesms` |
|
|
157
|
+
| `hubtel` | Hubtel SMS (Ghana) | `hubtel-sms-extended` |
|
|
158
|
+
| `nest` | SMSOnlineGH | Built-in REST client (`NestSmsGateway`) |
|
|
159
|
+
|
|
160
|
+
### `route` (Route Mobile)
|
|
161
|
+
|
|
162
|
+
**Required `param`:** `username`, `password`.
|
|
163
|
+
|
|
164
|
+
**Optional `param` (defaults in this library):**
|
|
165
|
+
|
|
166
|
+
| Field | Default if omitted |
|
|
167
|
+
|-------|-------------------|
|
|
168
|
+
| `host` | `rslr.connectbind.com` |
|
|
169
|
+
| `protocol` | `'http'` |
|
|
170
|
+
| `port` | `8080` |
|
|
171
|
+
|
|
172
|
+
These are passed into `routeSms` from `routemobilesms`.
|
|
113
173
|
|
|
114
174
|
```javascript
|
|
115
175
|
const gateway = unisms.init({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
176
|
+
platformId: 'route',
|
|
177
|
+
param: {
|
|
178
|
+
username: 'your-username',
|
|
179
|
+
password: 'your-password',
|
|
180
|
+
host: 'rslr.connectbind.com',
|
|
181
|
+
protocol: 'http',
|
|
182
|
+
port: 8080
|
|
183
|
+
}
|
|
184
|
+
});
|
|
126
185
|
```
|
|
127
186
|
|
|
128
|
-
###
|
|
187
|
+
### `hubtel` (Hubtel)
|
|
188
|
+
|
|
189
|
+
**Required `param`:** `clientId`, `clientSecret`.
|
|
190
|
+
|
|
191
|
+
No `host` / `protocol` in `IgatewayParam` for Hubtel in this library; configuration follows `hubtel-sms-extended`.
|
|
129
192
|
|
|
130
193
|
```javascript
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
clientId: 'new-client-id',
|
|
139
|
-
clientSecret: 'new-secret'
|
|
140
|
-
}
|
|
141
|
-
})
|
|
194
|
+
const gateway = unisms.init({
|
|
195
|
+
platformId: 'hubtel',
|
|
196
|
+
param: {
|
|
197
|
+
clientId: 'your-client-id',
|
|
198
|
+
clientSecret: 'your-client-secret'
|
|
199
|
+
}
|
|
200
|
+
});
|
|
142
201
|
```
|
|
143
202
|
|
|
144
|
-
|
|
203
|
+
### `nest` (SMSOnlineGH)
|
|
145
204
|
|
|
146
|
-
|
|
205
|
+
**Required `param`:** `apiKey`.
|
|
147
206
|
|
|
148
|
-
|
|
207
|
+
**Optional `param`:**
|
|
149
208
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
| Field | Default if omitted |
|
|
210
|
+
|-------|-------------------|
|
|
211
|
+
| `host` | `api.smsonlinegh.com` |
|
|
212
|
+
| `protocol` | `'https'` |
|
|
153
213
|
|
|
154
|
-
|
|
214
|
+
Requests use `POST` to path **`/v5/<endpoint>`** (e.g. send: `message/sms/send`, balance: `account/balance`). Authorization header: `Authorization: key <apiKey>`.
|
|
155
215
|
|
|
156
|
-
|
|
216
|
+
```javascript
|
|
217
|
+
const gateway = unisms.init({
|
|
218
|
+
platformId: 'nest',
|
|
219
|
+
param: {
|
|
220
|
+
apiKey: 'your-api-key'
|
|
221
|
+
// optional: host, protocol
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Balance (nest only):** The underlying `NestSmsGateway` implements `getBalance()`. Access it via the facade’s `getGateway()`:
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
const gateway = unisms.init({
|
|
230
|
+
platformId: 'nest',
|
|
231
|
+
param: { apiKey: 'your-api-key' }
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const nest = gateway.getGateway();
|
|
235
|
+
const balance = await nest.getBalance();
|
|
236
|
+
console.log(balance.balance, balance.model);
|
|
237
|
+
```
|
|
157
238
|
|
|
158
|
-
|
|
239
|
+
---
|
|
159
240
|
|
|
160
|
-
|
|
241
|
+
## Sending messages
|
|
161
242
|
|
|
162
|
-
### `
|
|
243
|
+
### `QuickSendParams`
|
|
163
244
|
|
|
164
|
-
|
|
245
|
+
| Field | Type | Required | Description |
|
|
246
|
+
|-------|------|----------|-------------|
|
|
247
|
+
| `From` | `string` | yes | Sender ID or label. |
|
|
248
|
+
| `To` | `string \| number` | yes | Recipient number (format as required by the provider). |
|
|
249
|
+
| `Content` | `string` | yes | Message body. |
|
|
250
|
+
| `Type` | `number` | no | Message type; **nest** maps this to request body `type` (default `0`). |
|
|
165
251
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
252
|
+
### `quickSend(params, callback?)`
|
|
253
|
+
|
|
254
|
+
Returns `Promise<SendResult>`. Optional `callback` is invoked with the same result when the promise completes.
|
|
255
|
+
|
|
256
|
+
**`SendResult`:**
|
|
171
257
|
|
|
172
|
-
**Returns:**
|
|
173
258
|
```typescript
|
|
174
259
|
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
260
|
+
success: boolean;
|
|
261
|
+
messageId?: string;
|
|
262
|
+
data?: any;
|
|
263
|
+
error?: string;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Example**
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
const unisms = require('unismsgateway');
|
|
271
|
+
|
|
272
|
+
const gateway = unisms.init({
|
|
273
|
+
platformId: 'nest',
|
|
274
|
+
param: { apiKey: 'your-api-key' }
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
async function sendSms() {
|
|
278
|
+
try {
|
|
279
|
+
const result = await gateway.quickSend({
|
|
280
|
+
From: 'SenderName',
|
|
281
|
+
To: '233XXXXXXXXX',
|
|
282
|
+
Content: 'Hello from unismsgateway!',
|
|
283
|
+
Type: 0
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (result.success) {
|
|
287
|
+
console.log('Sent:', result.messageId);
|
|
288
|
+
} else {
|
|
289
|
+
console.error('Failed:', result.error);
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error(err);
|
|
293
|
+
}
|
|
179
294
|
}
|
|
180
295
|
```
|
|
181
296
|
|
|
297
|
+
**With callback**
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
gateway.quickSend(
|
|
301
|
+
{ From: 'SenderName', To: '233XXXXXXXXX', Content: 'Test' },
|
|
302
|
+
(response) => {
|
|
303
|
+
console.log(response);
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Testing (live integration)
|
|
311
|
+
|
|
312
|
+
There is no unit test suite in this package. For **manual integration checks** against real gateways, use the script in `scripts/test-live.ts`.
|
|
313
|
+
|
|
314
|
+
**Setup**
|
|
315
|
+
|
|
316
|
+
1. Clone the repo and install dependencies: `npm install`
|
|
317
|
+
2. Copy `.env.example` to `.env` and fill in credentials for the platform you want to exercise
|
|
318
|
+
3. Run:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npm test
|
|
322
|
+
# same as:
|
|
323
|
+
npm run test:live
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Selecting a platform**
|
|
327
|
+
|
|
328
|
+
| Env variable | Description |
|
|
329
|
+
|--------------|-------------|
|
|
330
|
+
| `GATEWAY_PLATFORM` | One of `nest`, `hubtel`, or `route`. Required unless you use `TEST_ALL`. |
|
|
331
|
+
| `TEST_ALL` | If set to `true`, runs tests for `nest`, `hubtel`, and `route` in sequence (each needs its env vars set). |
|
|
332
|
+
|
|
333
|
+
**What runs**
|
|
334
|
+
|
|
335
|
+
1. **Init** — Builds `param` from your `.env`, calls `init()`, and checks configuration validation.
|
|
336
|
+
2. **Balance** — For `nest` and `hubtel` only, calls `getBalance()` when the adapter supports it. `route` skips this step.
|
|
337
|
+
3. **Send** — **Opt-in.** By default no SMS is sent. Set `TEST_SEND=true` to call `quickSend()` with `TEST_FROM`, `TEST_TO`, and optional `TEST_CONTENT`.
|
|
338
|
+
|
|
339
|
+
**Environment variables (live script)**
|
|
340
|
+
|
|
341
|
+
Variables below match `.env.example` and `scripts/test-live.ts`.
|
|
342
|
+
|
|
343
|
+
| Variable | Required when | Purpose |
|
|
344
|
+
|----------|----------------|---------|
|
|
345
|
+
| `GATEWAY_PLATFORM` | Unless `TEST_ALL=true` | `nest` \| `hubtel` \| `route` |
|
|
346
|
+
| `TEST_ALL` | Optional | `true` to test all three platforms |
|
|
347
|
+
| `NEST_API_KEY` | `nest` | SMSOnlineGH API key |
|
|
348
|
+
| `NEST_HOST`, `NEST_PROTOCOL` | `nest` | Optional overrides |
|
|
349
|
+
| `HUBTEL_CLIENT_ID`, `HUBTEL_CLIENT_SECRET` | `hubtel` | Hubtel credentials |
|
|
350
|
+
| `ROUTE_USERNAME`, `ROUTE_PASSWORD` | `route` | Route Mobile credentials |
|
|
351
|
+
| `ROUTE_HOST`, `ROUTE_PORT`, `ROUTE_PROTOCOL` | `route` | Optional overrides |
|
|
352
|
+
| `TEST_SEND` | To send SMS | Set to `true` to enable live send |
|
|
353
|
+
| `TEST_FROM`, `TEST_TO` | When `TEST_SEND=true` | Sender and recipient |
|
|
354
|
+
| `TEST_CONTENT` | When `TEST_SEND=true` | Message body (script has a default if omitted) |
|
|
355
|
+
|
|
356
|
+
The script loads `.env` via `dotenv` (dev dependency). Exit code is `0` when all checks pass, non-zero if a step fails or no platform is selected.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## API reference
|
|
361
|
+
|
|
362
|
+
| Export | Description |
|
|
363
|
+
|--------|-------------|
|
|
364
|
+
| `init(settings)` | Create and register the singleton `smsPlatform`, return it. |
|
|
365
|
+
| `getSmsPlatform()` | Current `smsPlatform` or `null` after `reset()` and before `init()`. |
|
|
366
|
+
| `reset()` | Clear the singleton. |
|
|
367
|
+
| `smsPlatform` | Class type for typing/advanced use. |
|
|
368
|
+
|
|
369
|
+
**`smsPlatform` instance methods**
|
|
370
|
+
|
|
371
|
+
| Method | Returns | Description |
|
|
372
|
+
|--------|---------|-------------|
|
|
373
|
+
| `init()` | `ISmsGateway` | Returns `this` (facade). |
|
|
374
|
+
| `quickSend(params, callback?)` | `Promise<SendResult>` | Delegates to the active gateway. |
|
|
375
|
+
| `getGateway()` | `ISmsGateway` | Underlying adapter (for nest: `getBalance()`). |
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
182
379
|
## License
|
|
183
380
|
|
|
184
|
-
[MIT](https://choosealicense.com/licenses/mit/)
|
|
381
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live integration test script for unismsgateway.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* 1. Copy .env.example to .env and fill in your credentials.
|
|
6
|
+
* 2. npm run test:live
|
|
7
|
+
*
|
|
8
|
+
* Set TEST_SEND=true in .env (or inline) to actually send an SMS.
|
|
9
|
+
* Without it, only init and balance checks run.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live integration test script for unismsgateway.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* 1. Copy .env.example to .env and fill in your credentials.
|
|
7
|
+
* 2. npm run test:live
|
|
8
|
+
*
|
|
9
|
+
* Set TEST_SEND=true in .env (or inline) to actually send an SMS.
|
|
10
|
+
* Without it, only init and balance checks run.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
+
if (mod && mod.__esModule) return mod;
|
|
26
|
+
var result = {};
|
|
27
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
+
__setModuleDefault(result, mod);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
32
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
33
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
34
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
35
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
36
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
37
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const dotenv = __importStar(require("dotenv"));
|
|
42
|
+
dotenv.config();
|
|
43
|
+
const lib_1 = require("../src/lib/lib");
|
|
44
|
+
// ─── Colour helpers ──────────────────────────────────────────────────────────
|
|
45
|
+
const GREEN = '\x1b[32m';
|
|
46
|
+
const RED = '\x1b[31m';
|
|
47
|
+
const YELLOW = '\x1b[33m';
|
|
48
|
+
const CYAN = '\x1b[36m';
|
|
49
|
+
const BOLD = '\x1b[1m';
|
|
50
|
+
const RESET = '\x1b[0m';
|
|
51
|
+
const pass = (msg) => console.log(` ${GREEN}✔${RESET} ${msg}`);
|
|
52
|
+
const fail = (msg) => console.log(` ${RED}✖${RESET} ${msg}`);
|
|
53
|
+
const info = (msg) => console.log(` ${CYAN}ℹ${RESET} ${msg}`);
|
|
54
|
+
const warn = (msg) => console.log(` ${YELLOW}⚠${RESET} ${msg}`);
|
|
55
|
+
const title = (msg) => console.log(`\n${BOLD}${msg}${RESET}`);
|
|
56
|
+
// ─── Env helpers ─────────────────────────────────────────────────────────────
|
|
57
|
+
function env(key) {
|
|
58
|
+
return process.env[key] || undefined;
|
|
59
|
+
}
|
|
60
|
+
function requireEnv(key) {
|
|
61
|
+
const val = process.env[key];
|
|
62
|
+
if (!val)
|
|
63
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
64
|
+
return val;
|
|
65
|
+
}
|
|
66
|
+
// ─── Test counters ───────────────────────────────────────────────────────────
|
|
67
|
+
let passed = 0;
|
|
68
|
+
let failed = 0;
|
|
69
|
+
function runTest(label, fn) {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
try {
|
|
72
|
+
yield fn();
|
|
73
|
+
passed++;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
failed++;
|
|
77
|
+
fail(`${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// ─── Per-platform tests ───────────────────────────────────────────────────────
|
|
82
|
+
function testPlatform(platformId) {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
title(`Platform: ${platformId.toUpperCase()}`);
|
|
85
|
+
let platform;
|
|
86
|
+
// 1. Initialisation
|
|
87
|
+
yield runTest('Init / config validation', () => __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
const param = {};
|
|
89
|
+
switch (platformId) {
|
|
90
|
+
case 'nest':
|
|
91
|
+
param.apiKey = requireEnv('NEST_API_KEY');
|
|
92
|
+
if (env('NEST_HOST'))
|
|
93
|
+
param.host = env('NEST_HOST');
|
|
94
|
+
if (env('NEST_PROTOCOL'))
|
|
95
|
+
param.protocol = env('NEST_PROTOCOL');
|
|
96
|
+
break;
|
|
97
|
+
case 'hubtel':
|
|
98
|
+
param.clientId = requireEnv('HUBTEL_CLIENT_ID');
|
|
99
|
+
param.clientSecret = requireEnv('HUBTEL_CLIENT_SECRET');
|
|
100
|
+
break;
|
|
101
|
+
case 'route':
|
|
102
|
+
param.username = requireEnv('ROUTE_USERNAME');
|
|
103
|
+
param.password = requireEnv('ROUTE_PASSWORD');
|
|
104
|
+
if (env('ROUTE_HOST'))
|
|
105
|
+
param.host = env('ROUTE_HOST');
|
|
106
|
+
if (env('ROUTE_PORT'))
|
|
107
|
+
param.port = Number(env('ROUTE_PORT'));
|
|
108
|
+
if (env('ROUTE_PROTOCOL'))
|
|
109
|
+
param.protocol = env('ROUTE_PROTOCOL');
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
platform = (0, lib_1.init)({ platformId, param: param });
|
|
113
|
+
pass(`Initialized ${platformId} platform`);
|
|
114
|
+
}));
|
|
115
|
+
// 2. Balance check (only nest and hubtel expose getBalance)
|
|
116
|
+
if (platformId === 'nest' || platformId === 'hubtel') {
|
|
117
|
+
yield runTest('getBalance()', () => __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
const gateway = platform.getGateway();
|
|
119
|
+
if (!gateway.getBalance) {
|
|
120
|
+
warn('getBalance not implemented — skipped');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const balance = yield gateway.getBalance();
|
|
124
|
+
pass(`Balance retrieved: ${JSON.stringify(balance)}`);
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
warn(`getBalance not supported by '${platformId}' — skipped`);
|
|
129
|
+
}
|
|
130
|
+
// 3. Send SMS (opt-in via TEST_SEND=true)
|
|
131
|
+
const shouldSend = env('TEST_SEND') === 'true';
|
|
132
|
+
if (!shouldSend) {
|
|
133
|
+
warn('TEST_SEND is not set to true — skipping live send');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
yield runTest('quickSend()', () => __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
const sendParams = {
|
|
138
|
+
From: requireEnv('TEST_FROM'),
|
|
139
|
+
To: requireEnv('TEST_TO'),
|
|
140
|
+
Content: env('TEST_CONTENT') || 'unismsgateway live test — please ignore.'
|
|
141
|
+
};
|
|
142
|
+
info(`Sending from "${sendParams.From}" to "${sendParams.To}"...`);
|
|
143
|
+
const result = yield platform.quickSend(sendParams);
|
|
144
|
+
if (!result.success) {
|
|
145
|
+
throw new Error(result.error || `Send failed: ${JSON.stringify(result)}`);
|
|
146
|
+
}
|
|
147
|
+
pass(`Message sent. Result: ${JSON.stringify(result)}`);
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
152
|
+
function main() {
|
|
153
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
154
|
+
console.log(`\n${BOLD}═══════════════════════════════════════${RESET}`);
|
|
155
|
+
console.log(`${BOLD} unismsgateway — live test runner ${RESET}`);
|
|
156
|
+
console.log(`${BOLD}═══════════════════════════════════════${RESET}`);
|
|
157
|
+
const platformArg = env('GATEWAY_PLATFORM');
|
|
158
|
+
const platforms = platformArg
|
|
159
|
+
? [platformArg]
|
|
160
|
+
: (env('TEST_ALL') === 'true' ? ['nest', 'hubtel', 'route'] : []);
|
|
161
|
+
if (platforms.length === 0) {
|
|
162
|
+
console.log(`\n${YELLOW}No platform selected.${RESET}`);
|
|
163
|
+
console.log('Set GATEWAY_PLATFORM=nest|hubtel|route in your .env file,');
|
|
164
|
+
console.log('or set TEST_ALL=true to test all configured platforms.\n');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
for (const p of platforms) {
|
|
168
|
+
try {
|
|
169
|
+
yield testPlatform(p);
|
|
170
|
+
}
|
|
171
|
+
catch (_a) {
|
|
172
|
+
// individual test errors already captured above
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ─── Summary ────────────────────────────────────────────────────────────
|
|
176
|
+
title('Summary');
|
|
177
|
+
if (passed > 0)
|
|
178
|
+
pass(`${passed} check(s) passed`);
|
|
179
|
+
if (failed > 0)
|
|
180
|
+
fail(`${failed} check(s) failed`);
|
|
181
|
+
console.log();
|
|
182
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
main().catch((err) => {
|
|
186
|
+
console.error(`\n${RED}Unexpected error:${RESET}`, err);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/lib';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./lib/lib"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ISmsGatewayDelegate, QuickSendParams, SendResult } from './types';
|
|
2
|
+
export interface HubtelSmsGatewayConfig {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Wraps hubtel-sms-extended and maps API responses to {@link SendResult}.
|
|
8
|
+
*/
|
|
9
|
+
export declare class HubtelSmsGateway implements ISmsGatewayDelegate {
|
|
10
|
+
private _client;
|
|
11
|
+
constructor(config: HubtelSmsGatewayConfig);
|
|
12
|
+
quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.HubtelSmsGateway = void 0;
|
|
13
|
+
const hubtel_sms_extended_1 = require("hubtel-sms-extended");
|
|
14
|
+
/**
|
|
15
|
+
* Wraps hubtel-sms-extended and maps API responses to {@link SendResult}.
|
|
16
|
+
*/
|
|
17
|
+
class HubtelSmsGateway {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this._client = new hubtel_sms_extended_1.HubtelSms({
|
|
20
|
+
clientId: config.clientId,
|
|
21
|
+
clientSecret: config.clientSecret
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
quickSend(params, callback) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
try {
|
|
27
|
+
const raw = yield this._client.quickSend({
|
|
28
|
+
From: params.From,
|
|
29
|
+
To: String(params.To),
|
|
30
|
+
Content: params.Content
|
|
31
|
+
});
|
|
32
|
+
const ok = Number(raw.Status) === 0;
|
|
33
|
+
const result = {
|
|
34
|
+
success: ok,
|
|
35
|
+
messageId: String(raw.MessageId),
|
|
36
|
+
data: raw,
|
|
37
|
+
error: ok ? undefined : `Hubtel Status=${raw.Status}`
|
|
38
|
+
};
|
|
39
|
+
if (callback) {
|
|
40
|
+
callback(result);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const result = {
|
|
46
|
+
success: false,
|
|
47
|
+
error: error instanceof Error ? error.message : String(error)
|
|
48
|
+
};
|
|
49
|
+
if (callback) {
|
|
50
|
+
callback(result);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.HubtelSmsGateway = HubtelSmsGateway;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { smsPlatform, IgatewaySettings, IgatewayParam, PlatformId, QuickSendParams, SendResult, ISmsGateway } from './platform';
|
|
2
|
+
export declare function init(settings: IgatewaySettings): smsPlatform;
|
|
3
|
+
export declare function getSmsPlatform(): smsPlatform | null;
|
|
4
|
+
export declare function reset(): void;
|
|
5
|
+
export { smsPlatform, IgatewaySettings, IgatewayParam, PlatformId, QuickSendParams, SendResult, ISmsGateway };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.smsPlatform = exports.reset = exports.getSmsPlatform = exports.init = void 0;
|
|
4
|
+
const platform_1 = require("./platform");
|
|
5
|
+
Object.defineProperty(exports, "smsPlatform", { enumerable: true, get: function () { return platform_1.smsPlatform; } });
|
|
6
|
+
let smsPlatformInstance = null;
|
|
7
|
+
function init(settings) {
|
|
8
|
+
smsPlatformInstance = new platform_1.smsPlatform(settings);
|
|
9
|
+
smsPlatformInstance.init();
|
|
10
|
+
return smsPlatformInstance;
|
|
11
|
+
}
|
|
12
|
+
exports.init = init;
|
|
13
|
+
function getSmsPlatform() {
|
|
14
|
+
return smsPlatformInstance;
|
|
15
|
+
}
|
|
16
|
+
exports.getSmsPlatform = getSmsPlatform;
|
|
17
|
+
function reset() {
|
|
18
|
+
smsPlatformInstance = null;
|
|
19
|
+
}
|
|
20
|
+
exports.reset = reset;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ISmsGateway, QuickSendParams, SendResult, NestSmsConfig } from './types';
|
|
2
|
+
export declare class NestSmsGateway implements ISmsGateway {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: NestSmsConfig);
|
|
5
|
+
init(): ISmsGateway;
|
|
6
|
+
private makeRequest;
|
|
7
|
+
quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
|
|
8
|
+
getBalance(): Promise<{
|
|
9
|
+
balance: number;
|
|
10
|
+
model: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.NestSmsGateway = void 0;
|
|
32
|
+
const https = __importStar(require("https"));
|
|
33
|
+
const http = __importStar(require("http"));
|
|
34
|
+
const DEFAULT_HOST = 'api.smsonlinegh.com';
|
|
35
|
+
const DEFAULT_PROTOCOL = 'https';
|
|
36
|
+
class NestSmsGateway {
|
|
37
|
+
constructor(config) {
|
|
38
|
+
this.config = {
|
|
39
|
+
host: config.host || DEFAULT_HOST,
|
|
40
|
+
protocol: config.protocol || DEFAULT_PROTOCOL,
|
|
41
|
+
apiKey: config.apiKey
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
init() {
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
makeRequest(endpoint, data) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
var _a;
|
|
51
|
+
const postData = data ? JSON.stringify(data) : '';
|
|
52
|
+
const protocol = this.config.protocol || DEFAULT_PROTOCOL;
|
|
53
|
+
const httpModule = protocol === 'https' ? https : http;
|
|
54
|
+
const defaultPort = protocol === 'https' ? 443 : 80;
|
|
55
|
+
const options = {
|
|
56
|
+
hostname: this.config.host || DEFAULT_HOST,
|
|
57
|
+
port: ((_a = this.config.host) === null || _a === void 0 ? void 0 : _a.includes(':'))
|
|
58
|
+
? parseInt(this.config.host.split(':')[1])
|
|
59
|
+
: defaultPort,
|
|
60
|
+
path: `/v5/${endpoint}`,
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Host': this.config.host || DEFAULT_HOST,
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
'Accept': 'application/json',
|
|
66
|
+
'Authorization': `key ${this.config.apiKey}`,
|
|
67
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const req = httpModule.request(options, (res) => {
|
|
71
|
+
let responseBody = '';
|
|
72
|
+
res.on('data', (chunk) => {
|
|
73
|
+
responseBody += chunk;
|
|
74
|
+
});
|
|
75
|
+
res.on('end', () => {
|
|
76
|
+
try {
|
|
77
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
78
|
+
const parsed = JSON.parse(responseBody);
|
|
79
|
+
resolve(parsed);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
reject(new Error(`HTTP ${res.statusCode}: ${responseBody}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
reject(new Error(`Failed to parse response: ${responseBody}`));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
req.on('error', (error) => {
|
|
91
|
+
reject(error);
|
|
92
|
+
});
|
|
93
|
+
req.write(postData);
|
|
94
|
+
req.end();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
quickSend(params, callback) {
|
|
99
|
+
var _a, _b, _c, _d, _e;
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
const endpoint = 'message/sms/send';
|
|
102
|
+
// SMSOnlineGH v5 expects: text, sender, destinations[] (see API docs — not from/to/content).
|
|
103
|
+
const requestBody = {
|
|
104
|
+
text: params.Content,
|
|
105
|
+
type: params.Type || 0,
|
|
106
|
+
sender: params.From,
|
|
107
|
+
destinations: [String(params.To)]
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
const response = yield this.makeRequest(endpoint, requestBody);
|
|
111
|
+
const handshakeOk = Number((_a = response.handshake) === null || _a === void 0 ? void 0 : _a.id) === 0;
|
|
112
|
+
const data = (_b = response.data) !== null && _b !== void 0 ? _b : null;
|
|
113
|
+
const batchId = data && typeof data === 'object' ? data.batch : undefined;
|
|
114
|
+
const firstDest = data && typeof data === 'object'
|
|
115
|
+
? (_c = data.destinations) === null || _c === void 0 ? void 0 : _c[0]
|
|
116
|
+
: undefined;
|
|
117
|
+
const result = {
|
|
118
|
+
success: handshakeOk,
|
|
119
|
+
data,
|
|
120
|
+
messageId: batchId || (firstDest === null || firstDest === void 0 ? void 0 : firstDest.id),
|
|
121
|
+
error: handshakeOk
|
|
122
|
+
? undefined
|
|
123
|
+
: (((_d = response.handshake) === null || _d === void 0 ? void 0 : _d.label)
|
|
124
|
+
|| `handshake id ${String((_e = response.handshake) === null || _e === void 0 ? void 0 : _e.id)}`)
|
|
125
|
+
};
|
|
126
|
+
if (callback) {
|
|
127
|
+
callback(result);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
133
|
+
const result = {
|
|
134
|
+
success: false,
|
|
135
|
+
error: errorMessage
|
|
136
|
+
};
|
|
137
|
+
if (callback) {
|
|
138
|
+
callback(result);
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
getBalance() {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
const endpoint = 'account/balance';
|
|
148
|
+
const response = yield this.makeRequest(endpoint);
|
|
149
|
+
return {
|
|
150
|
+
balance: ((_a = response.data) === null || _a === void 0 ? void 0 : _a.balance) || 0,
|
|
151
|
+
model: ((_b = response.data) === null || _b === void 0 ? void 0 : _b.model) || 'quantity'
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.NestSmsGateway = NestSmsGateway;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IgatewaySettings, IgatewayParam, ISmsGateway, ISmsGatewayDelegate, QuickSendParams, SendResult } from './types';
|
|
2
|
+
export * from './types';
|
|
3
|
+
export declare class smsPlatform implements ISmsGateway {
|
|
4
|
+
private _settings;
|
|
5
|
+
private _gateway;
|
|
6
|
+
constructor(settings: IgatewaySettings);
|
|
7
|
+
private validateSettings;
|
|
8
|
+
private createGateway;
|
|
9
|
+
init(): ISmsGateway;
|
|
10
|
+
quickSend(param: QuickSendParams, callback?: Function): Promise<SendResult>;
|
|
11
|
+
getGateway(): ISmsGatewayDelegate;
|
|
12
|
+
}
|
|
13
|
+
export { IgatewaySettings, IgatewayParam };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.smsPlatform = void 0;
|
|
14
|
+
const nest_gateway_1 = require("./nest-gateway");
|
|
15
|
+
const hubtel_gateway_1 = require("./hubtel-gateway");
|
|
16
|
+
const route_gateway_1 = require("./route-gateway");
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
const GATEWAY_CONFIGS = {
|
|
19
|
+
route: { requiresUsernamePassword: true },
|
|
20
|
+
hubtel: { requiresClientCredentials: true },
|
|
21
|
+
nest: { requiresApiKey: true }
|
|
22
|
+
};
|
|
23
|
+
class smsPlatform {
|
|
24
|
+
constructor(settings) {
|
|
25
|
+
this.validateSettings(settings);
|
|
26
|
+
this._settings = settings;
|
|
27
|
+
this._gateway = this.createGateway();
|
|
28
|
+
}
|
|
29
|
+
validateSettings(settings) {
|
|
30
|
+
const validPlatforms = ['route', 'hubtel', 'nest'];
|
|
31
|
+
if (!validPlatforms.includes(settings.platformId)) {
|
|
32
|
+
throw new Error(`Invalid platform ID. Supported platforms: ${validPlatforms.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
const config = GATEWAY_CONFIGS[settings.platformId];
|
|
35
|
+
const param = settings.param;
|
|
36
|
+
if (config.requiresApiKey && !param.apiKey) {
|
|
37
|
+
throw new Error(`Platform '${settings.platformId}' requires 'apiKey' in param`);
|
|
38
|
+
}
|
|
39
|
+
if (config.requiresClientCredentials && (!param.clientId || !param.clientSecret)) {
|
|
40
|
+
throw new Error(`Platform '${settings.platformId}' requires 'clientId' and 'clientSecret' in param`);
|
|
41
|
+
}
|
|
42
|
+
if (config.requiresUsernamePassword && (!param.username || !param.password)) {
|
|
43
|
+
throw new Error(`Platform '${settings.platformId}' requires 'username' and 'password' in param`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
createGateway() {
|
|
47
|
+
const { platformId, param } = this._settings;
|
|
48
|
+
switch (platformId) {
|
|
49
|
+
case 'route':
|
|
50
|
+
return new route_gateway_1.RouteSmsGateway({
|
|
51
|
+
host: param.host || 'rslr.connectbind.com',
|
|
52
|
+
username: param.username,
|
|
53
|
+
password: param.password,
|
|
54
|
+
protocol: param.protocol || 'http',
|
|
55
|
+
port: param.port || 8080
|
|
56
|
+
});
|
|
57
|
+
case 'hubtel':
|
|
58
|
+
return new hubtel_gateway_1.HubtelSmsGateway({
|
|
59
|
+
clientId: param.clientId,
|
|
60
|
+
clientSecret: param.clientSecret
|
|
61
|
+
});
|
|
62
|
+
case 'nest':
|
|
63
|
+
return new nest_gateway_1.NestSmsGateway({
|
|
64
|
+
apiKey: param.apiKey,
|
|
65
|
+
host: param.host,
|
|
66
|
+
protocol: param.protocol
|
|
67
|
+
});
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Unsupported platform: ${platformId}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
init() {
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
quickSend(param, callback) {
|
|
76
|
+
if (!this._gateway) {
|
|
77
|
+
throw new Error('Gateway not initialized. Call init() first.');
|
|
78
|
+
}
|
|
79
|
+
return this._gateway.quickSend(param, callback);
|
|
80
|
+
}
|
|
81
|
+
getGateway() {
|
|
82
|
+
return this._gateway;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.smsPlatform = smsPlatform;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ISmsGatewayDelegate, QuickSendParams, SendResult } from './types';
|
|
2
|
+
export interface RouteSmsGatewayConfig {
|
|
3
|
+
host: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
protocol: 'http' | 'https';
|
|
7
|
+
port: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class RouteSmsGateway implements ISmsGatewayDelegate {
|
|
10
|
+
constructor(config: RouteSmsGatewayConfig);
|
|
11
|
+
quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RouteSmsGateway = void 0;
|
|
13
|
+
const routemobilesms_1 = require("routemobilesms");
|
|
14
|
+
/**
|
|
15
|
+
* Adapts routemobilesms static `sendAsync` API to {@link ISmsGatewayDelegate}.
|
|
16
|
+
*/
|
|
17
|
+
function toRouteDestination(to) {
|
|
18
|
+
if (typeof to === 'number') {
|
|
19
|
+
return to;
|
|
20
|
+
}
|
|
21
|
+
const digits = String(to).replace(/\D/g, '');
|
|
22
|
+
const n = Number(digits);
|
|
23
|
+
return Number.isNaN(n) ? 0 : n;
|
|
24
|
+
}
|
|
25
|
+
class RouteSmsGateway {
|
|
26
|
+
constructor(config) {
|
|
27
|
+
new routemobilesms_1.routeSms({
|
|
28
|
+
host: config.host,
|
|
29
|
+
username: config.username,
|
|
30
|
+
password: config.password,
|
|
31
|
+
protocol: config.protocol,
|
|
32
|
+
port: config.port
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
quickSend(params, callback) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const sendParams = {
|
|
38
|
+
From: params.From,
|
|
39
|
+
To: toRouteDestination(params.To),
|
|
40
|
+
Content: params.Content
|
|
41
|
+
};
|
|
42
|
+
if (params.Type !== undefined) {
|
|
43
|
+
sendParams.config = { type: params.Type, dlr: 0 };
|
|
44
|
+
}
|
|
45
|
+
const raw = yield routemobilesms_1.routeSms.sendAsync(sendParams);
|
|
46
|
+
let result;
|
|
47
|
+
if (raw === undefined || raw === null) {
|
|
48
|
+
result = { success: false, error: 'No response from route SMS gateway' };
|
|
49
|
+
}
|
|
50
|
+
else if (Array.isArray(raw) && raw.length > 0) {
|
|
51
|
+
const first = raw[0];
|
|
52
|
+
const ok = first.status === 'successful';
|
|
53
|
+
result = {
|
|
54
|
+
success: ok,
|
|
55
|
+
messageId: first.id,
|
|
56
|
+
data: raw,
|
|
57
|
+
error: ok ? undefined : (first.message || first.code || 'Send failed')
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
result = { success: false, error: 'Unexpected response from route SMS gateway', data: raw };
|
|
62
|
+
}
|
|
63
|
+
if (callback) {
|
|
64
|
+
callback(result);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.RouteSmsGateway = RouteSmsGateway;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare type PlatformId = 'route' | 'hubtel' | 'nest';
|
|
2
|
+
export interface QuickSendParams {
|
|
3
|
+
From: string;
|
|
4
|
+
To: string | number;
|
|
5
|
+
Content: string;
|
|
6
|
+
Type?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SendResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
messageId?: string;
|
|
11
|
+
data?: any;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IgatewayParam {
|
|
15
|
+
host?: string;
|
|
16
|
+
port?: number;
|
|
17
|
+
username?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
clientId?: string;
|
|
20
|
+
clientSecret?: string;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
protocol?: 'http' | 'https';
|
|
23
|
+
}
|
|
24
|
+
export interface IgatewaySettings {
|
|
25
|
+
platformId: PlatformId;
|
|
26
|
+
param: IgatewayParam;
|
|
27
|
+
}
|
|
28
|
+
/** Send surface used by the facade; third-party SDKs may omit `init()`. */
|
|
29
|
+
export interface ISmsGatewayDelegate {
|
|
30
|
+
quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
|
|
31
|
+
getBalance?(): Promise<any>;
|
|
32
|
+
}
|
|
33
|
+
export interface ISmsGateway extends ISmsGatewayDelegate {
|
|
34
|
+
init(): ISmsGateway;
|
|
35
|
+
}
|
|
36
|
+
export interface NestSmsConfig {
|
|
37
|
+
apiKey: string;
|
|
38
|
+
host?: string;
|
|
39
|
+
protocol?: 'http' | 'https';
|
|
40
|
+
}
|
|
41
|
+
export interface NestSendResponse {
|
|
42
|
+
handshake: {
|
|
43
|
+
id: number;
|
|
44
|
+
label: string;
|
|
45
|
+
};
|
|
46
|
+
data?: any;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unismsgateway",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A unified SMS gateway library that brings access to multiple SMS gateways under a single API",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "
|
|
8
|
+
"test": "npm run test:live",
|
|
9
|
+
"test:live": "ts-node scripts/test-live.ts",
|
|
9
10
|
"build": "tsc",
|
|
10
11
|
"prepublish": "npm run build"
|
|
11
12
|
},
|
|
@@ -38,7 +39,10 @@
|
|
|
38
39
|
"node": ">=12.0.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
42
|
+
"@types/dotenv": "^6.1.1",
|
|
41
43
|
"@types/node": "^17.0.4",
|
|
44
|
+
"dotenv": "^17.4.0",
|
|
45
|
+
"ts-node": "^10.9.2",
|
|
42
46
|
"typescript": "^4.5.4"
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {
|
|
File without changes
|