unismsgateway 1.3.1 → 1.3.3

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 CHANGED
@@ -34,23 +34,27 @@ import { init, getSmsPlatform, reset, smsPlatform } from 'unismsgateway';
34
34
 
35
35
  ### `IgatewaySettings`
36
36
 
37
- | Field | Type | Description |
38
- |-------|------|-------------|
39
- | `platformId` | `'route' \| 'hubtel' \| 'nest'` | Which gateway to use. |
40
- | `param` | `IgatewayParam` | Provider-specific options (see below). |
37
+
38
+ | Field | Type | Description |
39
+ | ------------ | --------------- | -------------------------------------- |
40
+ | `platformId` | `'route' | 'hubtel' |
41
+ | `param` | `IgatewayParam` | Provider-specific options (see below). |
42
+
41
43
 
42
44
  ### `IgatewayParam` (all fields optional except what your `platformId` requires)
43
45
 
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`. |
46
+
47
+ | Field | Type | Used by | Description |
48
+ | -------------- | -------- | --------------- | ---------------------------------------------------------------------- |
49
+ | `username` | `string` | `route` | Route Mobile account username. **Required** for `route`. |
50
+ | `password` | `string` | `route` | Route Mobile account password. **Required** for `route`. |
51
+ | `host` | `string` | `route`, `nest` | API host. See per-gateway defaults below. |
52
+ | `port` | `number` | `route` | TCP port for Route Mobile. Default: `8080`. |
53
+ | `protocol` | `'http' | 'https'` | `route`, `nest` |
54
+ | `clientId` | `string` | `hubtel` | Hubtel client ID. **Required** for `hubtel`. |
55
+ | `clientSecret` | `string` | `hubtel` | Hubtel client secret. **Required** for `hubtel`. |
56
+ | `apiKey` | `string` | `nest` | SMSOnlineGH API key (`Authorization: key …`). **Required** for `nest`. |
57
+
54
58
 
55
59
  Validation runs in `smsPlatform` when the instance is constructed: missing required fields for the chosen `platformId` throw `Error` with a clear message.
56
60
 
@@ -58,47 +62,72 @@ Validation runs in `smsPlatform` when the instance is constructed: missing requi
58
62
 
59
63
  ## Environment variables
60
64
 
61
- **This library does not read `process.env` or any configuration files.** You pass all credentials and endpoints explicitly in `init({ platformId, param })`.
65
+ There are **two separate contexts**. Use the section that matches what you are doing.
66
+
67
+
68
+ | Context | Who reads env? | Purpose |
69
+ | --------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
70
+ | **Library usage** (`init()` in your app) | **Your code** — this package does **not** read `process.env`. | You choose variable names and map them into `platformId` and `param` yourself. |
71
+ | **Live integration test** (`npm test` / `scripts/test-live.ts`) | **The test script** via `dotenv` and `process.env`. | Fixed names in `.env` (see `.env.example`). |
72
+
73
+
74
+ ---
75
+
76
+ ### Library usage: required and optional param fields
77
+
78
+ Nothing is read from the environment unless **you** wire it. Required fields are determined only by `platformId`:
79
+
80
+
81
+ | `platformId` | Required in `param` | Optional in `param` (defaults in this library) |
82
+ | ------------ | -------------------------- | --------------------------------------------------------------------------------------------- |
83
+ | `nest` | `apiKey` | `host` (default `api.smsonlinegh.com`), `protocol` (default `https`) |
84
+ | `hubtel` | `clientId`, `clientSecret` | — |
85
+ | `route` | `username`, `password` | `host` (default `rslr.connectbind.com`), `protocol` (default `http`), `port` (default `8080`) |
86
+
87
+
88
+ **Suggested env names for your app** (optional; you can rename them). Credential keys (`NEST_`*, `HUBTEL_`*, `ROUTE_*`) match [live test](#live-integration-test-environment-variables) and `.env.example`. Platform selection differs: the test script requires `GATEWAY_PLATFORM` (or `TEST_ALL`); in your app you choose any name (the example below uses `SMS_PLATFORM_ID`):
62
89
 
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
90
 
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) |
91
+ | Env name (suggestion) | Maps to `param` | Required when `platformId` is |
92
+ | ---------------------- | -------------------------- | ----------------------------- |
93
+ | `NEST_API_KEY` | `apiKey` | `nest` |
94
+ | `NEST_HOST` | `host` | optional for `nest` |
95
+ | `NEST_PROTOCOL` | `protocol` | optional for `nest` |
96
+ | `HUBTEL_CLIENT_ID` | `clientId` | `hubtel` |
97
+ | `HUBTEL_CLIENT_SECRET` | `clientSecret` | `hubtel` |
98
+ | `ROUTE_USERNAME` | `username` | `route` |
99
+ | `ROUTE_PASSWORD` | `password` | `route` |
100
+ | `ROUTE_HOST` | `host` | optional for `route` |
101
+ | `ROUTE_PORT` | `port` (use `Number(...)`) | optional for `route` |
102
+ | `ROUTE_PROTOCOL` | `protocol` | optional for `route` |
78
103
 
79
- Example wiring (conceptual): branch on `platformId` and build `param` so you do not mix unrelated fields.
104
+
105
+ You may also use names like `SMSONLINEGH_API_KEY` / `SMSONLINEGH_HOST` in your app only — there is **no** built-in support for alternate names in the test script; that script expects `NEST_`* and `ROUTE_`* as in `.env.example`.
106
+
107
+ Example wiring: branch on `platformId` and build `param` so you do not mix unrelated fields.
80
108
 
81
109
  ```javascript
82
110
  const unisms = require('unismsgateway');
83
111
 
112
+ // Pick any env name for the active gateway; the live test uses GATEWAY_PLATFORM instead.
84
113
  const platformId = process.env.SMS_PLATFORM_ID;
85
114
 
86
115
  const paramByPlatform = {
87
116
  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
117
+ username: process.env.ROUTE_USERNAME,
118
+ password: process.env.ROUTE_PASSWORD,
119
+ host: process.env.ROUTE_HOST,
120
+ port: process.env.ROUTE_PORT ? Number(process.env.ROUTE_PORT) : undefined,
121
+ protocol: process.env.ROUTE_PROTOCOL
93
122
  },
94
123
  hubtel: {
95
124
  clientId: process.env.HUBTEL_CLIENT_ID,
96
125
  clientSecret: process.env.HUBTEL_CLIENT_SECRET
97
126
  },
98
127
  nest: {
99
- apiKey: process.env.SMSONLINEGH_API_KEY,
100
- host: process.env.SMSONLINEGH_HOST,
101
- protocol: process.env.SMSONLINEGH_PROTOCOL
128
+ apiKey: process.env.NEST_API_KEY,
129
+ host: process.env.NEST_HOST,
130
+ protocol: process.env.NEST_PROTOCOL
102
131
  }
103
132
  };
104
133
 
@@ -110,19 +139,76 @@ const gateway = unisms.init({
110
139
 
111
140
  ---
112
141
 
113
- ## How initialization works
142
+ ### Live integration test environment variables
143
+
144
+ The script `scripts/test-live.ts` loads `.env` (copy from `.env.example`) and expects **these exact names**. It does not use `SMS_PLATFORM_ID` or other app-specific aliases.
145
+
146
+ #### How a run is selected
147
+
148
+
149
+ | Variable | Required? | Description |
150
+ | ------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | `GATEWAY_PLATFORM` | **Yes**, unless you use `TEST_ALL` | Must be exactly `nest`, `hubtel`, or `route`. |
152
+ | `TEST_ALL` | Optional | If set to `true`, the script runs `nest`, then `hubtel`, then `route` in order. You still need the **required** variables below for each platform you run; missing keys for a step cause that step to fail. |
153
+
154
+
155
+ #### Per gateway — credentials and overrides
156
+
157
+ `**nest` (SMSOnlineGH)**
158
+
159
+
160
+ | Variable | Required? | Purpose |
161
+ | --------------- | --------- | --------------------------------------------- |
162
+ | `NEST_API_KEY` | **Yes** | Maps to `param.apiKey`. |
163
+ | `NEST_HOST` | No | Overrides default host `api.smsonlinegh.com`. |
164
+ | `NEST_PROTOCOL` | No | Overrides default `https`. |
165
+
166
+
167
+ `**hubtel`**
168
+
114
169
 
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.
170
+ | Variable | Required? | Purpose |
171
+ | ---------------------- | --------- | ----------------------------- |
172
+ | `HUBTEL_CLIENT_ID` | **Yes** | Maps to `param.clientId`. |
173
+ | `HUBTEL_CLIENT_SECRET` | **Yes** | Maps to `param.clientSecret`. |
120
174
 
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
175
 
125
- 3. **`getSmsPlatform(): smsPlatform | null`**: Returns the current singleton, or `null` if `reset()` was called and no new `init()` has run.
176
+ `**route` (Route Mobile)**
177
+
178
+
179
+ | Variable | Required? | Purpose |
180
+ | ---------------- | --------- | --------------------------------------------------- |
181
+ | `ROUTE_USERNAME` | **Yes** | Maps to `param.username`. |
182
+ | `ROUTE_PASSWORD` | **Yes** | Maps to `param.password`. |
183
+ | `ROUTE_HOST` | No | Overrides default `rslr.connectbind.com`. |
184
+ | `ROUTE_PORT` | No | Overrides default `8080` (set as a numeric string). |
185
+ | `ROUTE_PROTOCOL` | No | Overrides default `http`. |
186
+
187
+
188
+ #### Live send (optional; all gateways)
189
+
190
+
191
+ | Variable | Required? | Purpose |
192
+ | -------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
193
+ | `TEST_SEND` | No (default: do not send) | Set to `true` to call `quickSend()` and send a real SMS. If unset or not `true`, only init and balance checks run. |
194
+ | `TEST_FROM` | **Yes** when `TEST_SEND=true` | `QuickSendParams.From`. |
195
+ | `TEST_TO` | **Yes** when `TEST_SEND=true` | `QuickSendParams.To`. |
196
+ | `TEST_CONTENT` | No | Message body; if omitted, the script uses a built-in default string. |
197
+
198
+
199
+ ---
200
+
201
+ ## How initialization works
202
+
203
+ 1. `**init(settings: IgatewaySettings): smsPlatform`** (in `src/lib/lib.ts`):
204
+ - Validates and constructs a new `smsPlatform` with your `settings`.
205
+ - Stores it as the **module singleton** (`smsPlatformInstance`).
206
+ - Calls `smsPlatform.init()` on that instance (returns the same facade for chaining).
207
+ - Returns the `smsPlatform` instance.
208
+ 2. `**smsPlatform` constructor** (in `src/lib/platform.ts`):
209
+ - Runs `validateSettings()` (platform id + required `param` fields for that id).
210
+ - Calls `createGateway()` to instantiate the underlying provider (`routeSms`, `HubtelSms`, or `NestSmsGateway`).
211
+ 3. `**getSmsPlatform(): smsPlatform | null`**: Returns the current singleton, or `null` if `reset()` was called and no new `init()` has run.
126
212
 
127
213
  There is **no async bootstrap**; after `init()` returns, `quickSend` is ready.
128
214
 
@@ -130,8 +216,8 @@ There is **no async bootstrap**; after `init()` returns, `quickSend` is ready.
130
216
 
131
217
  ## Re-initializing and reset
132
218
 
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).
219
+ - **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.
220
+ - **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).
135
221
 
136
222
  ```javascript
137
223
  const unisms = require('unismsgateway');
@@ -151,11 +237,15 @@ const c = unisms.init({ platformId: 'nest', param: { apiKey: 'key-2' } });
151
237
 
152
238
  ## Supported gateways
153
239
 
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`) |
240
+
241
+ | `platformId` | Provider | Package / implementation |
242
+ | ------------ | ------------------ | --------------------------------------- |
243
+ | `route` | Route Mobile | `routemobilesms` |
244
+ | `hubtel` | Hubtel SMS (Ghana) | `hubtel-sms-extended` |
245
+ | `nest` | SMSOnlineGH | Built-in REST client (`NestSmsGateway`) |
246
+
247
+
248
+ **Configuration vs env:** Required and optional `param` fields are summarized in [Library usage: required and optional param fields](#library-usage-required-and-optional-param-fields). The live test runner’s `.env` names are listed in [Live integration test environment variables](#live-integration-test-environment-variables).
159
249
 
160
250
  ### `route` (Route Mobile)
161
251
 
@@ -163,11 +253,13 @@ const c = unisms.init({ platformId: 'nest', param: { apiKey: 'key-2' } });
163
253
 
164
254
  **Optional `param` (defaults in this library):**
165
255
 
166
- | Field | Default if omitted |
167
- |-------|-------------------|
168
- | `host` | `rslr.connectbind.com` |
169
- | `protocol` | `'http'` |
170
- | `port` | `8080` |
256
+
257
+ | Field | Default if omitted |
258
+ | ---------- | ---------------------- |
259
+ | `host` | `rslr.connectbind.com` |
260
+ | `protocol` | `'http'` |
261
+ | `port` | `8080` |
262
+
171
263
 
172
264
  These are passed into `routeSms` from `routemobilesms`.
173
265
 
@@ -206,12 +298,14 @@ const gateway = unisms.init({
206
298
 
207
299
  **Optional `param`:**
208
300
 
209
- | Field | Default if omitted |
210
- |-------|-------------------|
211
- | `host` | `api.smsonlinegh.com` |
212
- | `protocol` | `'https'` |
213
301
 
214
- Requests use `POST` to path **`/v5/<endpoint>`** (e.g. send: `message/sms/send`, balance: `account/balance`). Authorization header: `Authorization: key <apiKey>`.
302
+ | Field | Default if omitted |
303
+ | ---------- | --------------------- |
304
+ | `host` | `api.smsonlinegh.com` |
305
+ | `protocol` | `'https'` |
306
+
307
+
308
+ Requests use `POST` to path `**/v5/<endpoint>`** (e.g. send: `message/sms/send`, balance: `account/balance`). Authorization header: `Authorization: key <apiKey>`.
215
309
 
216
310
  ```javascript
217
311
  const gateway = unisms.init({
@@ -242,18 +336,20 @@ console.log(balance.balance, balance.model);
242
336
 
243
337
  ### `QuickSendParams`
244
338
 
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`). |
339
+
340
+ | Field | Type | Required | Description |
341
+ | --------- | -------- | -------- | ---------------------------------------------------------------------- |
342
+ | `From` | `string` | yes | Sender ID or label. |
343
+ | `To` | `string | number` | yes |
344
+ | `Content` | `string` | yes | Message body. |
345
+ | `Type` | `number` | no | Message type; **nest** maps this to request body `type` (default `0`). |
346
+
251
347
 
252
348
  ### `quickSend(params, callback?)`
253
349
 
254
350
  Returns `Promise<SendResult>`. Optional `callback` is invoked with the same result when the promise completes.
255
351
 
256
- **`SendResult`:**
352
+ `**SendResult`:**
257
353
 
258
354
  ```typescript
259
355
  {
@@ -314,7 +410,7 @@ There is no unit test suite in this package. For **manual integration checks** a
314
410
  **Setup**
315
411
 
316
412
  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
413
+ 2. Copy `.env.example` to `.env` and set variables for your chosen platform see [Live integration test environment variables](#live-integration-test-environment-variables) for required vs optional names per gateway.
318
414
  3. Run:
319
415
 
320
416
  ```bash
@@ -323,59 +419,39 @@ npm test
323
419
  npm run test:live
324
420
  ```
325
421
 
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
422
  **What runs**
334
423
 
335
424
  1. **Init** — Builds `param` from your `.env`, calls `init()`, and checks configuration validation.
336
425
  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`.
426
+ 3. **Send** — **Opt-in.** By default no SMS is sent. Set `TEST_SEND=true` and the send-related variables listed in [Live integration test environment variables](#live-integration-test-environment-variables).
338
427
 
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.
428
+ Full variable reference (selection, per-gateway credentials, live send): [Live integration test environment variables](#live-integration-test-environment-variables). 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
429
 
358
430
  ---
359
431
 
360
432
  ## API reference
361
433
 
362
- | Export | Description |
363
- |--------|-------------|
364
- | `init(settings)` | Create and register the singleton `smsPlatform`, return it. |
434
+
435
+ | Export | Description |
436
+ | ------------------ | -------------------------------------------------------------------- |
437
+ | `init(settings)` | Create and register the singleton `smsPlatform`, return it. |
365
438
  | `getSmsPlatform()` | Current `smsPlatform` or `null` after `reset()` and before `init()`. |
366
- | `reset()` | Clear the singleton. |
367
- | `smsPlatform` | Class type for typing/advanced use. |
439
+ | `reset()` | Clear the singleton. |
440
+ | `smsPlatform` | Class type for typing/advanced use. |
441
+
442
+
443
+ `**smsPlatform` instance methods**
444
+
368
445
 
369
- **`smsPlatform` instance methods**
446
+ | Method | Returns | Description |
447
+ | ------------------------------ | --------------------- | ---------------------------------------------- |
448
+ | `init()` | `ISmsGateway` | Returns `this` (facade). |
449
+ | `quickSend(params, callback?)` | `Promise<SendResult>` | Delegates to the active gateway. |
450
+ | `getGateway()` | `ISmsGateway` | Underlying adapter (for nest: `getBalance()`). |
370
451
 
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
452
 
377
453
  ---
378
454
 
379
455
  ## License
380
456
 
381
- [MIT](https://choosealicense.com/licenses/mit/)
457
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -2,12 +2,15 @@ import { ISmsGatewayDelegate, QuickSendParams, SendResult } from './types';
2
2
  export interface HubtelSmsGatewayConfig {
3
3
  clientId: string;
4
4
  clientSecret: string;
5
+ debug?: boolean;
5
6
  }
6
7
  /**
7
8
  * Wraps hubtel-sms-extended and maps API responses to {@link SendResult}.
8
9
  */
9
10
  export declare class HubtelSmsGateway implements ISmsGatewayDelegate {
10
11
  private _client;
12
+ private _debug;
11
13
  constructor(config: HubtelSmsGatewayConfig);
14
+ private log;
12
15
  quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
13
16
  }
@@ -16,41 +16,55 @@ const hubtel_sms_extended_1 = require("hubtel-sms-extended");
16
16
  */
17
17
  class HubtelSmsGateway {
18
18
  constructor(config) {
19
+ this._debug = config.debug || false;
19
20
  this._client = new hubtel_sms_extended_1.HubtelSms({
20
21
  clientId: config.clientId,
21
22
  clientSecret: config.clientSecret
22
23
  });
23
24
  }
25
+ log(...args) {
26
+ if (this._debug) {
27
+ console.log('[unismsgateway:hubtel]', ...args);
28
+ }
29
+ }
24
30
  quickSend(params, callback) {
31
+ var _a;
25
32
  return __awaiter(this, void 0, void 0, function* () {
33
+ this.log('quickSend params:', JSON.stringify(params));
34
+ let result;
26
35
  try {
27
36
  const raw = yield this._client.quickSend({
28
37
  From: params.From,
29
38
  To: String(params.To),
30
39
  Content: params.Content
31
40
  });
32
- const ok = Number(raw.Status) === 0;
33
- const result = {
41
+ this.log('quickSend raw response:', JSON.stringify(raw));
42
+ const ok = Number(raw === null || raw === void 0 ? void 0 : raw.Status) === 0;
43
+ result = {
34
44
  success: ok,
35
- messageId: String(raw.MessageId),
45
+ messageId: (raw === null || raw === void 0 ? void 0 : raw.MessageId) != null ? String(raw.MessageId) : undefined,
36
46
  data: raw,
37
- error: ok ? undefined : `Hubtel Status=${raw.Status}`
47
+ error: ok
48
+ ? undefined
49
+ : `Hubtel API Error: Status=${raw === null || raw === void 0 ? void 0 : raw.Status}, NetworkId=${(_a = raw === null || raw === void 0 ? void 0 : raw.NetworkId) !== null && _a !== void 0 ? _a : 'n/a'}`
38
50
  };
39
- if (callback) {
40
- callback(result);
41
- }
42
- return result;
43
51
  }
44
52
  catch (error) {
45
- const result = {
53
+ const errorMessage = error instanceof Error ? error.message : String(error);
54
+ this.log('quickSend error:', errorMessage);
55
+ result = {
46
56
  success: false,
47
- error: error instanceof Error ? error.message : String(error)
57
+ error: errorMessage,
58
+ data: error instanceof Error && error.response
59
+ ? error.response
60
+ : null
48
61
  };
49
- if (callback) {
50
- callback(result);
51
- }
52
- return result;
53
62
  }
63
+ this.log('quickSend result:', JSON.stringify(result));
64
+ if (callback) {
65
+ callback(result);
66
+ }
67
+ return result;
54
68
  });
55
69
  }
56
70
  }
@@ -3,6 +3,7 @@ export declare class NestSmsGateway implements ISmsGateway {
3
3
  private config;
4
4
  constructor(config: NestSmsConfig);
5
5
  init(): ISmsGateway;
6
+ private log;
6
7
  private makeRequest;
7
8
  quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
8
9
  getBalance(): Promise<{
@@ -38,59 +38,80 @@ class NestSmsGateway {
38
38
  this.config = {
39
39
  host: config.host || DEFAULT_HOST,
40
40
  protocol: config.protocol || DEFAULT_PROTOCOL,
41
- apiKey: config.apiKey
41
+ apiKey: config.apiKey,
42
+ debug: config.debug || false
42
43
  };
43
44
  }
44
45
  init() {
45
46
  return this;
46
47
  }
48
+ log(...args) {
49
+ if (this.config.debug) {
50
+ console.log('[unismsgateway:nest]', ...args);
51
+ }
52
+ }
47
53
  makeRequest(endpoint, data) {
48
54
  return __awaiter(this, void 0, void 0, function* () {
49
55
  return new Promise((resolve, reject) => {
50
- var _a;
51
56
  const postData = data ? JSON.stringify(data) : '';
52
57
  const protocol = this.config.protocol || DEFAULT_PROTOCOL;
53
58
  const httpModule = protocol === 'https' ? https : http;
54
59
  const defaultPort = protocol === 'https' ? 443 : 80;
60
+ const host = this.config.host || DEFAULT_HOST;
61
+ const hostname = host.includes(':') ? host.split(':')[0] : host;
62
+ const port = host.includes(':')
63
+ ? parseInt(host.split(':')[1], 10)
64
+ : defaultPort;
55
65
  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,
66
+ hostname,
67
+ port,
60
68
  path: `/v5/${endpoint}`,
61
69
  method: 'POST',
62
70
  headers: {
63
- 'Host': this.config.host || DEFAULT_HOST,
71
+ 'Host': hostname,
64
72
  'Content-Type': 'application/json',
65
73
  'Accept': 'application/json',
66
74
  'Authorization': `key ${this.config.apiKey}`,
67
75
  'Content-Length': Buffer.byteLength(postData)
68
76
  }
69
77
  };
78
+ this.log(`POST /v5/${endpoint}`, data ? JSON.stringify(data) : '(no body)');
70
79
  const req = httpModule.request(options, (res) => {
71
80
  let responseBody = '';
72
81
  res.on('data', (chunk) => {
73
82
  responseBody += chunk;
74
83
  });
75
84
  res.on('end', () => {
85
+ var _a;
86
+ const statusCode = (_a = res.statusCode) !== null && _a !== void 0 ? _a : 0;
87
+ this.log(`HTTP ${statusCode} response:`, responseBody);
76
88
  try {
77
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
78
- const parsed = JSON.parse(responseBody);
79
- resolve(parsed);
89
+ const parsed = JSON.parse(responseBody);
90
+ if (statusCode >= 200 && statusCode < 300) {
91
+ resolve({ statusCode, body: parsed });
80
92
  }
81
93
  else {
82
- reject(new Error(`HTTP ${res.statusCode}: ${responseBody}`));
94
+ const err = new Error(`HTTP ${statusCode}: ${responseBody}`);
95
+ err.statusCode = statusCode;
96
+ err.rawBody = responseBody;
97
+ reject(err);
83
98
  }
84
99
  }
85
- catch (error) {
86
- reject(new Error(`Failed to parse response: ${responseBody}`));
100
+ catch (_b) {
101
+ const err = new Error(`Failed to parse gateway response (HTTP ${statusCode}): ${responseBody}`);
102
+ err.statusCode = statusCode;
103
+ err.rawBody = responseBody;
104
+ reject(err);
87
105
  }
88
106
  });
89
107
  });
90
108
  req.on('error', (error) => {
109
+ this.log('Network error:', error.message);
91
110
  reject(error);
92
111
  });
93
- req.write(postData);
112
+ if (postData) {
113
+ req.write(postData);
114
+ }
94
115
  req.end();
95
116
  });
96
117
  });
@@ -98,57 +119,73 @@ class NestSmsGateway {
98
119
  quickSend(params, callback) {
99
120
  var _a, _b, _c, _d, _e;
100
121
  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
122
  const requestBody = {
104
123
  text: params.Content,
105
- type: params.Type || 0,
124
+ type: (_a = params.Type) !== null && _a !== void 0 ? _a : 0,
106
125
  sender: params.From,
107
126
  destinations: [String(params.To)]
108
127
  };
128
+ this.log('quickSend params:', JSON.stringify(params));
129
+ let result;
109
130
  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]
131
+ const { statusCode, body: response } = yield this.makeRequest('message/sms/send', requestBody);
132
+ const handshakeId = (_b = response.handshake) === null || _b === void 0 ? void 0 : _b.id;
133
+ const handshakeLabel = (_c = response.handshake) === null || _c === void 0 ? void 0 : _c.label;
134
+ const handshakeOk = Number(handshakeId) === 0;
135
+ const responseData = (_d = response.data) !== null && _d !== void 0 ? _d : null;
136
+ const batchId = responseData && typeof responseData === 'object'
137
+ ? responseData.batch
138
+ : undefined;
139
+ const firstDest = responseData && typeof responseData === 'object'
140
+ ? (_e = responseData.destinations) === null || _e === void 0 ? void 0 : _e[0]
116
141
  : undefined;
117
- const result = {
142
+ let errorMsg;
143
+ if (!handshakeOk) {
144
+ if (handshakeLabel) {
145
+ errorMsg = `API Error [code ${handshakeId}]: ${handshakeLabel}`;
146
+ }
147
+ else if (handshakeId !== undefined && handshakeId !== null) {
148
+ errorMsg = `API Error: handshake code=${handshakeId}`;
149
+ }
150
+ else {
151
+ errorMsg = `Unexpected API response: ${JSON.stringify(response)}`;
152
+ }
153
+ }
154
+ result = {
118
155
  success: handshakeOk,
119
- data,
120
156
  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)}`)
157
+ // On failure, expose the full raw response so callers can inspect it.
158
+ data: handshakeOk ? responseData : response,
159
+ error: errorMsg,
160
+ statusCode
125
161
  };
126
- if (callback) {
127
- callback(result);
128
- }
129
- return result;
130
162
  }
131
163
  catch (error) {
164
+ const httpErr = error;
132
165
  const errorMessage = error instanceof Error ? error.message : String(error);
133
- const result = {
166
+ result = {
134
167
  success: false,
135
- error: errorMessage
168
+ error: errorMessage,
169
+ statusCode: httpErr.statusCode,
170
+ // Preserve whatever raw body we got for inspection.
171
+ data: httpErr.rawBody !== undefined ? httpErr.rawBody : null
136
172
  };
137
- if (callback) {
138
- callback(result);
139
- }
140
- return result;
141
173
  }
174
+ this.log('quickSend result:', JSON.stringify(result));
175
+ if (callback) {
176
+ callback(result);
177
+ }
178
+ return result;
142
179
  });
143
180
  }
144
181
  getBalance() {
145
- var _a, _b;
182
+ var _a, _b, _c, _d;
146
183
  return __awaiter(this, void 0, void 0, function* () {
147
- const endpoint = 'account/balance';
148
- const response = yield this.makeRequest(endpoint);
184
+ this.log('getBalance called');
185
+ const { body: response } = yield this.makeRequest('account/balance');
149
186
  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'
187
+ balance: (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.balance) !== null && _b !== void 0 ? _b : 0,
188
+ model: (_d = (_c = response.data) === null || _c === void 0 ? void 0 : _c.model) !== null && _d !== void 0 ? _d : 'quantity'
152
189
  };
153
190
  });
154
191
  }
@@ -52,18 +52,21 @@ class smsPlatform {
52
52
  username: param.username,
53
53
  password: param.password,
54
54
  protocol: param.protocol || 'http',
55
- port: param.port || 8080
55
+ port: param.port || 8080,
56
+ debug: param.debug
56
57
  });
57
58
  case 'hubtel':
58
59
  return new hubtel_gateway_1.HubtelSmsGateway({
59
60
  clientId: param.clientId,
60
- clientSecret: param.clientSecret
61
+ clientSecret: param.clientSecret,
62
+ debug: param.debug
61
63
  });
62
64
  case 'nest':
63
65
  return new nest_gateway_1.NestSmsGateway({
64
66
  apiKey: param.apiKey,
65
67
  host: param.host,
66
- protocol: param.protocol
68
+ protocol: param.protocol,
69
+ debug: param.debug
67
70
  });
68
71
  default:
69
72
  throw new Error(`Unsupported platform: ${platformId}`);
@@ -5,8 +5,18 @@ export interface RouteSmsGatewayConfig {
5
5
  password: string;
6
6
  protocol: 'http' | 'https';
7
7
  port: number;
8
+ debug?: boolean;
8
9
  }
10
+ /**
11
+ * Adapts routemobilesms to {@link ISmsGatewayDelegate}.
12
+ *
13
+ * NOTE: routemobilesms stores config in module-level state via the constructor,
14
+ * then exposes `routeSms.sendAsync` as a static-style call. The instance is
15
+ * intentionally discarded after construction.
16
+ */
9
17
  export declare class RouteSmsGateway implements ISmsGatewayDelegate {
18
+ private _debug;
10
19
  constructor(config: RouteSmsGatewayConfig);
20
+ private log;
11
21
  quickSend(params: QuickSendParams, callback?: Function): Promise<SendResult>;
12
22
  }
@@ -11,9 +11,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RouteSmsGateway = void 0;
13
13
  const routemobilesms_1 = require("routemobilesms");
14
- /**
15
- * Adapts routemobilesms static `sendAsync` API to {@link ISmsGatewayDelegate}.
16
- */
17
14
  function toRouteDestination(to) {
18
15
  if (typeof to === 'number') {
19
16
  return to;
@@ -22,8 +19,18 @@ function toRouteDestination(to) {
22
19
  const n = Number(digits);
23
20
  return Number.isNaN(n) ? 0 : n;
24
21
  }
22
+ /**
23
+ * Adapts routemobilesms to {@link ISmsGatewayDelegate}.
24
+ *
25
+ * NOTE: routemobilesms stores config in module-level state via the constructor,
26
+ * then exposes `routeSms.sendAsync` as a static-style call. The instance is
27
+ * intentionally discarded after construction.
28
+ */
25
29
  class RouteSmsGateway {
26
30
  constructor(config) {
31
+ this._debug = config.debug || false;
32
+ // routemobilesms configures itself through its constructor and exposes
33
+ // sendAsync as a static method — the returned instance is not needed.
27
34
  new routemobilesms_1.routeSms({
28
35
  host: config.host,
29
36
  username: config.username,
@@ -32,8 +39,15 @@ class RouteSmsGateway {
32
39
  port: config.port
33
40
  });
34
41
  }
42
+ log(...args) {
43
+ if (this._debug) {
44
+ console.log('[unismsgateway:route]', ...args);
45
+ }
46
+ }
35
47
  quickSend(params, callback) {
48
+ var _a, _b;
36
49
  return __awaiter(this, void 0, void 0, function* () {
50
+ this.log('quickSend params:', JSON.stringify(params));
37
51
  const sendParams = {
38
52
  From: params.From,
39
53
  To: toRouteDestination(params.To),
@@ -42,24 +56,47 @@ class RouteSmsGateway {
42
56
  if (params.Type !== undefined) {
43
57
  sendParams.config = { type: params.Type, dlr: 0 };
44
58
  }
45
- const raw = yield routemobilesms_1.routeSms.sendAsync(sendParams);
46
59
  let result;
47
- if (raw === undefined || raw === null) {
48
- result = { success: false, error: 'No response from route SMS gateway' };
60
+ try {
61
+ const raw = yield routemobilesms_1.routeSms.sendAsync(sendParams);
62
+ this.log('quickSend raw response:', JSON.stringify(raw));
63
+ if (raw === undefined || raw === null) {
64
+ result = {
65
+ success: false,
66
+ error: 'No response received from Route SMS gateway',
67
+ data: null
68
+ };
69
+ }
70
+ else if (Array.isArray(raw) && raw.length > 0) {
71
+ const first = raw[0];
72
+ const ok = first.status === 'successful';
73
+ result = {
74
+ success: ok,
75
+ messageId: first.id,
76
+ data: raw,
77
+ error: ok
78
+ ? undefined
79
+ : `Route SMS Error [${(_a = first.code) !== null && _a !== void 0 ? _a : 'unknown'}]: ${(_b = first.message) !== null && _b !== void 0 ? _b : 'Send failed'}`
80
+ };
81
+ }
82
+ else {
83
+ result = {
84
+ success: false,
85
+ error: 'Unexpected response format from Route SMS gateway',
86
+ data: raw
87
+ };
88
+ }
49
89
  }
50
- else if (Array.isArray(raw) && raw.length > 0) {
51
- const first = raw[0];
52
- const ok = first.status === 'successful';
90
+ catch (error) {
91
+ const errorMessage = error instanceof Error ? error.message : String(error);
92
+ this.log('quickSend error:', errorMessage);
53
93
  result = {
54
- success: ok,
55
- messageId: first.id,
56
- data: raw,
57
- error: ok ? undefined : (first.message || first.code || 'Send failed')
94
+ success: false,
95
+ error: errorMessage,
96
+ data: null
58
97
  };
59
98
  }
60
- else {
61
- result = { success: false, error: 'Unexpected response from route SMS gateway', data: raw };
62
- }
99
+ this.log('quickSend result:', JSON.stringify(result));
63
100
  if (callback) {
64
101
  callback(result);
65
102
  }
@@ -10,6 +10,8 @@ export interface SendResult {
10
10
  messageId?: string;
11
11
  data?: any;
12
12
  error?: string;
13
+ /** HTTP status code returned by the gateway (when available). */
14
+ statusCode?: number;
13
15
  }
14
16
  export interface IgatewayParam {
15
17
  host?: string;
@@ -20,6 +22,8 @@ export interface IgatewayParam {
20
22
  clientSecret?: string;
21
23
  apiKey?: string;
22
24
  protocol?: 'http' | 'https';
25
+ /** Set to true to print request/response details to console for debugging. */
26
+ debug?: boolean;
23
27
  }
24
28
  export interface IgatewaySettings {
25
29
  platformId: PlatformId;
@@ -37,6 +41,7 @@ export interface NestSmsConfig {
37
41
  apiKey: string;
38
42
  host?: string;
39
43
  protocol?: 'http' | 'https';
44
+ debug?: boolean;
40
45
  }
41
46
  export interface NestSendResponse {
42
47
  handshake: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unismsgateway",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",