plexisms 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.js +138 -0
- package/dist/exceptions.d.ts +14 -0
- package/dist/exceptions.js +33 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +2 -0
- package/eslint.config.js +12 -0
- package/jest.config.js +4 -0
- package/package.json +47 -0
- package/src/client.ts +159 -0
- package/src/exceptions.ts +32 -0
- package/src/index.ts +3 -0
- package/src/types.ts +63 -0
- package/tests/client.test.ts +5 -0
- package/tsconfig.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# PlexiSMS Node.js SDK
|
|
2
|
+
|
|
3
|
+
The official Node.js library for the [PlexiSMS](https://plexisms.com) API. Send SMS, manage OTPs, and check detailed analytics with our reliable Tier-1 network.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install plexisms
|
|
9
|
+
# or
|
|
10
|
+
yarn add plexisms
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### 1. Initialize the Client
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Client } from 'plexisms';
|
|
19
|
+
|
|
20
|
+
// Option 1: Pass API Key directly
|
|
21
|
+
const client = new Client('your_api_key_here');
|
|
22
|
+
|
|
23
|
+
// Option 2: Use environment variable 'PLEXISMS_API_KEY'
|
|
24
|
+
// const client = new Client();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Send an SMS
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
try {
|
|
31
|
+
const result = await client.messages.create({
|
|
32
|
+
to: '+243970000000', // Replace with your phone number
|
|
33
|
+
body: 'Hello from Node.js! 🚀',
|
|
34
|
+
senderId: 'MyApp' // Replace with your sender ID
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('SMS Sent! ID:', result.message_id);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error:', error.message);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Send Bulk SMS
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const result = await client.messages.createBulk({
|
|
47
|
+
phoneNumbers: ['+243970000000', '+243810000000'],
|
|
48
|
+
body: 'Bulk message test',
|
|
49
|
+
senderId: 'MyApp'
|
|
50
|
+
});
|
|
51
|
+
console.log(`Queued ${result.queued} messages`);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. OTP Verification
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Step 1: Send OTP
|
|
58
|
+
const otpRes = await client.otp.send('+243970000000', 'MyService');
|
|
59
|
+
const verificationId = otpRes.verification_id;
|
|
60
|
+
|
|
61
|
+
// Step 2: Verify Code
|
|
62
|
+
const verifyRes = await client.otp.verify(verificationId, '123456');
|
|
63
|
+
|
|
64
|
+
if (verifyRes.verified) {
|
|
65
|
+
console.log('User verified successfully!');
|
|
66
|
+
} else {
|
|
67
|
+
console.log('Invalid code');
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Error Handling
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { AuthenticationError, BalanceError } from 'plexisms';
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await client.messages.create({...});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof AuthenticationError) {
|
|
80
|
+
console.error('Check your API Key');
|
|
81
|
+
} else if (error instanceof BalanceError) {
|
|
82
|
+
console.error('Top up your account');
|
|
83
|
+
} else {
|
|
84
|
+
console.error('Unknown error:', error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { SendSMSOptions, SMSResponse, SendBulkSMSOptions, BulkSMSResponse, OTPRequestResponse, OTPVerifyResponse, BalanceResponse, MessageStatusResponse } from './types';
|
|
3
|
+
export declare class Client {
|
|
4
|
+
private client;
|
|
5
|
+
private readonly DEFAULT_BASE_URL;
|
|
6
|
+
messages: Messages;
|
|
7
|
+
otp: OTP;
|
|
8
|
+
account: Account;
|
|
9
|
+
/**
|
|
10
|
+
* @param apiKey Your PlexiSMS API Key.
|
|
11
|
+
* @param baseUrl Optional override for API URL.
|
|
12
|
+
*/
|
|
13
|
+
constructor(apiKey?: string, baseUrl?: string);
|
|
14
|
+
}
|
|
15
|
+
declare class Resource {
|
|
16
|
+
protected client: AxiosInstance;
|
|
17
|
+
constructor(client: AxiosInstance);
|
|
18
|
+
protected request<T>(method: 'get' | 'post', endpoint: string, data?: Record<string, unknown>, params?: Record<string, unknown>): Promise<T>;
|
|
19
|
+
protected handleError(error: unknown): void;
|
|
20
|
+
}
|
|
21
|
+
export declare class Messages extends Resource {
|
|
22
|
+
/**
|
|
23
|
+
* Send a single SMS
|
|
24
|
+
*/
|
|
25
|
+
create(options: SendSMSOptions): Promise<SMSResponse>;
|
|
26
|
+
/**
|
|
27
|
+
* Send bulk SMS
|
|
28
|
+
*/
|
|
29
|
+
createBulk(options: SendBulkSMSOptions): Promise<BulkSMSResponse>;
|
|
30
|
+
/**
|
|
31
|
+
* Get SMS status
|
|
32
|
+
*/
|
|
33
|
+
get(messageId: string | number): Promise<MessageStatusResponse>;
|
|
34
|
+
}
|
|
35
|
+
export declare class OTP extends Resource {
|
|
36
|
+
/**
|
|
37
|
+
* Send an OTP code
|
|
38
|
+
*/
|
|
39
|
+
send(to: string, brand?: string): Promise<OTPRequestResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Verify an OTP code
|
|
42
|
+
*/
|
|
43
|
+
verify(verificationId: string, code: string): Promise<OTPVerifyResponse>;
|
|
44
|
+
}
|
|
45
|
+
export declare class Account extends Resource {
|
|
46
|
+
/**
|
|
47
|
+
* Check account balance
|
|
48
|
+
*/
|
|
49
|
+
balance(): Promise<BalanceResponse>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Account = exports.OTP = exports.Messages = exports.Client = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const exceptions_1 = require("./exceptions");
|
|
9
|
+
class Client {
|
|
10
|
+
/**
|
|
11
|
+
* @param apiKey Your PlexiSMS API Key.
|
|
12
|
+
* @param baseUrl Optional override for API URL.
|
|
13
|
+
*/
|
|
14
|
+
constructor(apiKey, baseUrl) {
|
|
15
|
+
this.DEFAULT_BASE_URL = "https://server.plexisms.com";
|
|
16
|
+
const token = apiKey || process.env.PLEXISMS_API_KEY;
|
|
17
|
+
if (!token) {
|
|
18
|
+
throw new exceptions_1.AuthenticationError("API Key is required. Pass it to the constructor or set PLEXISMS_API_KEY environment variable.");
|
|
19
|
+
}
|
|
20
|
+
this.client = axios_1.default.create({
|
|
21
|
+
baseURL: (baseUrl || process.env.PLEXISMS_BASE_URL || this.DEFAULT_BASE_URL).replace(/\/$/, ""),
|
|
22
|
+
headers: {
|
|
23
|
+
'Authorization': `Token ${token}`,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'User-Agent': 'plexisms-node/1.0.0'
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
this.messages = new Messages(this.client);
|
|
29
|
+
this.otp = new OTP(this.client);
|
|
30
|
+
this.account = new Account(this.client);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.Client = Client;
|
|
34
|
+
class Resource {
|
|
35
|
+
constructor(client) {
|
|
36
|
+
this.client = client;
|
|
37
|
+
}
|
|
38
|
+
async request(method, endpoint, data, params) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.client.request({
|
|
41
|
+
method,
|
|
42
|
+
url: endpoint,
|
|
43
|
+
data,
|
|
44
|
+
params
|
|
45
|
+
});
|
|
46
|
+
return response.data;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.handleError(error);
|
|
50
|
+
throw error; // Should be unreachable given handleError throws
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
handleError(error) {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
56
|
+
const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
57
|
+
const data = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
58
|
+
const message = ((data === null || data === void 0 ? void 0 : data.error) || (data === null || data === void 0 ? void 0 : data.detail) || error.message);
|
|
59
|
+
if (status === 401 || status === 403) {
|
|
60
|
+
throw new exceptions_1.AuthenticationError(`Unauthorized: ${message}`);
|
|
61
|
+
}
|
|
62
|
+
else if (status === 402) {
|
|
63
|
+
throw new exceptions_1.BalanceError(`Insufficient funds: ${message}`);
|
|
64
|
+
}
|
|
65
|
+
else if (status && status >= 500) {
|
|
66
|
+
throw new exceptions_1.APIError(`Server Error (${status}): ${message}`, status, data);
|
|
67
|
+
}
|
|
68
|
+
else if (status) {
|
|
69
|
+
throw new exceptions_1.APIError(`API Error (${status}): ${message}`, status, data);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new exceptions_1.PlexismsError(`Network Error: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw new exceptions_1.PlexismsError(`Unexpected Error: ${error}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
class Messages extends Resource {
|
|
79
|
+
/**
|
|
80
|
+
* Send a single SMS
|
|
81
|
+
*/
|
|
82
|
+
async create(options) {
|
|
83
|
+
return this.request('post', '/api/sms/send/', {
|
|
84
|
+
phone_number: options.to,
|
|
85
|
+
message: options.body,
|
|
86
|
+
sender_id: options.senderId,
|
|
87
|
+
sms_type: options.smsType || 'transactional'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Send bulk SMS
|
|
92
|
+
*/
|
|
93
|
+
async createBulk(options) {
|
|
94
|
+
return this.request('post', '/api/sms/send-bulk/', {
|
|
95
|
+
phone_numbers: options.phoneNumbers,
|
|
96
|
+
message: options.body,
|
|
97
|
+
sender_id: options.senderId,
|
|
98
|
+
sms_type: options.smsType || 'transactional'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get SMS status
|
|
103
|
+
*/
|
|
104
|
+
async get(messageId) {
|
|
105
|
+
return this.request('get', `/api/sms/${messageId}/status/`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.Messages = Messages;
|
|
109
|
+
class OTP extends Resource {
|
|
110
|
+
/**
|
|
111
|
+
* Send an OTP code
|
|
112
|
+
*/
|
|
113
|
+
async send(to, brand = "PlexiSMS") {
|
|
114
|
+
return this.request('post', '/api/sms/send-otp/', {
|
|
115
|
+
phone_number: to,
|
|
116
|
+
brand
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Verify an OTP code
|
|
121
|
+
*/
|
|
122
|
+
async verify(verificationId, code) {
|
|
123
|
+
return this.request('post', '/api/sms/verify-otp/', {
|
|
124
|
+
verification_id: verificationId,
|
|
125
|
+
otp_code: code
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.OTP = OTP;
|
|
130
|
+
class Account extends Resource {
|
|
131
|
+
/**
|
|
132
|
+
* Check account balance
|
|
133
|
+
*/
|
|
134
|
+
async balance() {
|
|
135
|
+
return this.request('get', '/api/sms/balance/');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.Account = Account;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class PlexismsError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class AuthenticationError extends PlexismsError {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class BalanceError extends PlexismsError {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class APIError extends PlexismsError {
|
|
11
|
+
statusCode?: number;
|
|
12
|
+
responseBody?: Record<string, unknown>;
|
|
13
|
+
constructor(message: string, statusCode?: number, responseBody?: Record<string, unknown>);
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APIError = exports.BalanceError = exports.AuthenticationError = exports.PlexismsError = void 0;
|
|
4
|
+
class PlexismsError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'PlexismsError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.PlexismsError = PlexismsError;
|
|
11
|
+
class AuthenticationError extends PlexismsError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'AuthenticationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.AuthenticationError = AuthenticationError;
|
|
18
|
+
class BalanceError extends PlexismsError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'BalanceError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.BalanceError = BalanceError;
|
|
25
|
+
class APIError extends PlexismsError {
|
|
26
|
+
constructor(message, statusCode, responseBody) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'APIError';
|
|
29
|
+
this.statusCode = statusCode;
|
|
30
|
+
this.responseBody = responseBody;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.APIError = APIError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./client"), exports);
|
|
18
|
+
__exportStar(require("./exceptions"), exports);
|
|
19
|
+
__exportStar(require("./types"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface SMSResponse {
|
|
2
|
+
id: number;
|
|
3
|
+
message_id: string;
|
|
4
|
+
status: 'pending' | 'sent' | 'delivered' | 'failed';
|
|
5
|
+
phone_number: string;
|
|
6
|
+
parts: number;
|
|
7
|
+
cost_usd: string;
|
|
8
|
+
balance: {
|
|
9
|
+
remaining_usd: string;
|
|
10
|
+
remaining_sms: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface BulkSMSResponse {
|
|
14
|
+
total: number;
|
|
15
|
+
queued: number;
|
|
16
|
+
task_id: string;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
export interface OTPRequestResponse {
|
|
20
|
+
verification_id: string;
|
|
21
|
+
status: string;
|
|
22
|
+
phone_number: string;
|
|
23
|
+
expires_in: number;
|
|
24
|
+
}
|
|
25
|
+
export interface OTPVerifyResponse {
|
|
26
|
+
verified: boolean;
|
|
27
|
+
phone_number?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface BalanceResponse {
|
|
31
|
+
amount: string;
|
|
32
|
+
currency: string;
|
|
33
|
+
provider: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SendSMSOptions {
|
|
36
|
+
to: string;
|
|
37
|
+
body: string;
|
|
38
|
+
senderId?: string;
|
|
39
|
+
smsType?: 'transactional' | 'promotional';
|
|
40
|
+
}
|
|
41
|
+
export interface SendBulkSMSOptions {
|
|
42
|
+
phoneNumbers: string[];
|
|
43
|
+
body: string;
|
|
44
|
+
senderId?: string;
|
|
45
|
+
smsType?: 'transactional' | 'promotional';
|
|
46
|
+
}
|
|
47
|
+
export interface MessageStatusResponse {
|
|
48
|
+
id: number;
|
|
49
|
+
message_id: string;
|
|
50
|
+
status: 'pending' | 'sent' | 'delivered' | 'failed';
|
|
51
|
+
phone_number: string;
|
|
52
|
+
parts: number;
|
|
53
|
+
cost_usd: string;
|
|
54
|
+
created_at: string;
|
|
55
|
+
updated_at: string;
|
|
56
|
+
}
|
package/dist/types.js
ADDED
package/eslint.config.js
ADDED
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "plexisms",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for PlexiSMS API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"lint": "eslint 'src/**/*.ts'",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"test": "jest"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/plexisms/sdk-plexisms-js.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"sms",
|
|
19
|
+
"plexisms",
|
|
20
|
+
"sms rdc",
|
|
21
|
+
"sms congo",
|
|
22
|
+
"sms kinshasa",
|
|
23
|
+
"otp",
|
|
24
|
+
"api",
|
|
25
|
+
"sdk"
|
|
26
|
+
],
|
|
27
|
+
"author": "PlexiSMS Team <dev@plexisms.com>",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/plexisms/sdk-plexisms-js/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://plexisms.com/developers",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
35
|
+
"@types/node": "^18.0.0",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
38
|
+
"eslint": "^9.39.2",
|
|
39
|
+
"jest": "^30.2.0",
|
|
40
|
+
"ts-jest": "^29.4.6",
|
|
41
|
+
"typescript": "^5.0.0",
|
|
42
|
+
"typescript-eslint": "^8.54.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"axios": "^1.6.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import {
|
|
3
|
+
AuthenticationError,
|
|
4
|
+
BalanceError,
|
|
5
|
+
APIError,
|
|
6
|
+
PlexismsError
|
|
7
|
+
} from './exceptions';
|
|
8
|
+
import {
|
|
9
|
+
SendSMSOptions,
|
|
10
|
+
SMSResponse,
|
|
11
|
+
SendBulkSMSOptions,
|
|
12
|
+
BulkSMSResponse,
|
|
13
|
+
OTPRequestResponse,
|
|
14
|
+
OTPVerifyResponse,
|
|
15
|
+
BalanceResponse,
|
|
16
|
+
MessageStatusResponse
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export class Client {
|
|
20
|
+
private client: AxiosInstance;
|
|
21
|
+
private readonly DEFAULT_BASE_URL = "https://server.plexisms.com";
|
|
22
|
+
|
|
23
|
+
public messages: Messages;
|
|
24
|
+
public otp: OTP;
|
|
25
|
+
public account: Account;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param apiKey Your PlexiSMS API Key.
|
|
29
|
+
* @param baseUrl Optional override for API URL.
|
|
30
|
+
*/
|
|
31
|
+
constructor(apiKey?: string, baseUrl?: string) {
|
|
32
|
+
const token = apiKey || process.env.PLEXISMS_API_KEY;
|
|
33
|
+
|
|
34
|
+
if (!token) {
|
|
35
|
+
throw new AuthenticationError("API Key is required. Pass it to the constructor or set PLEXISMS_API_KEY environment variable.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.client = axios.create({
|
|
39
|
+
baseURL: (baseUrl || process.env.PLEXISMS_BASE_URL || this.DEFAULT_BASE_URL).replace(/\/$/, ""),
|
|
40
|
+
headers: {
|
|
41
|
+
'Authorization': `Token ${token}`,
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'User-Agent': 'plexisms-node/1.0.0'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.messages = new Messages(this.client);
|
|
48
|
+
this.otp = new OTP(this.client);
|
|
49
|
+
this.account = new Account(this.client);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class Resource {
|
|
54
|
+
protected client: AxiosInstance;
|
|
55
|
+
|
|
56
|
+
constructor(client: AxiosInstance) {
|
|
57
|
+
this.client = client;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected async request<T>(method: 'get' | 'post', endpoint: string, data?: Record<string, unknown>, params?: Record<string, unknown>): Promise<T> {
|
|
61
|
+
try {
|
|
62
|
+
const response = await this.client.request<T>({
|
|
63
|
+
method,
|
|
64
|
+
url: endpoint,
|
|
65
|
+
data,
|
|
66
|
+
params
|
|
67
|
+
});
|
|
68
|
+
return response.data;
|
|
69
|
+
} catch (error: unknown) {
|
|
70
|
+
this.handleError(error);
|
|
71
|
+
throw error; // Should be unreachable given handleError throws
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected handleError(error: unknown): void {
|
|
76
|
+
if (axios.isAxiosError(error)) {
|
|
77
|
+
const status = error.response?.status;
|
|
78
|
+
const data = error.response?.data as Record<string, unknown>;
|
|
79
|
+
const message = (data?.error || data?.detail || error.message) as string;
|
|
80
|
+
|
|
81
|
+
if (status === 401 || status === 403) {
|
|
82
|
+
throw new AuthenticationError(`Unauthorized: ${message}`);
|
|
83
|
+
} else if (status === 402) {
|
|
84
|
+
throw new BalanceError(`Insufficient funds: ${message}`);
|
|
85
|
+
} else if (status && status >= 500) {
|
|
86
|
+
throw new APIError(`Server Error (${status}): ${message}`, status, data);
|
|
87
|
+
} else if (status) {
|
|
88
|
+
throw new APIError(`API Error (${status}): ${message}`, status, data);
|
|
89
|
+
} else {
|
|
90
|
+
throw new PlexismsError(`Network Error: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw new PlexismsError(`Unexpected Error: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class Messages extends Resource {
|
|
98
|
+
/**
|
|
99
|
+
* Send a single SMS
|
|
100
|
+
*/
|
|
101
|
+
async create(options: SendSMSOptions): Promise<SMSResponse> {
|
|
102
|
+
return this.request<SMSResponse>('post', '/api/sms/send/', {
|
|
103
|
+
phone_number: options.to,
|
|
104
|
+
message: options.body,
|
|
105
|
+
sender_id: options.senderId,
|
|
106
|
+
sms_type: options.smsType || 'transactional'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Send bulk SMS
|
|
112
|
+
*/
|
|
113
|
+
async createBulk(options: SendBulkSMSOptions): Promise<BulkSMSResponse> {
|
|
114
|
+
return this.request<BulkSMSResponse>('post', '/api/sms/send-bulk/', {
|
|
115
|
+
phone_numbers: options.phoneNumbers,
|
|
116
|
+
message: options.body,
|
|
117
|
+
sender_id: options.senderId,
|
|
118
|
+
sms_type: options.smsType || 'transactional'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get SMS status
|
|
124
|
+
*/
|
|
125
|
+
async get(messageId: string | number): Promise<MessageStatusResponse> {
|
|
126
|
+
return this.request('get', `/api/sms/${messageId}/status/`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class OTP extends Resource {
|
|
131
|
+
/**
|
|
132
|
+
* Send an OTP code
|
|
133
|
+
*/
|
|
134
|
+
async send(to: string, brand: string = "PlexiSMS"): Promise<OTPRequestResponse> {
|
|
135
|
+
return this.request<OTPRequestResponse>('post', '/api/sms/send-otp/', {
|
|
136
|
+
phone_number: to,
|
|
137
|
+
brand
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Verify an OTP code
|
|
143
|
+
*/
|
|
144
|
+
async verify(verificationId: string, code: string): Promise<OTPVerifyResponse> {
|
|
145
|
+
return this.request<OTPVerifyResponse>('post', '/api/sms/verify-otp/', {
|
|
146
|
+
verification_id: verificationId,
|
|
147
|
+
otp_code: code
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class Account extends Resource {
|
|
153
|
+
/**
|
|
154
|
+
* Check account balance
|
|
155
|
+
*/
|
|
156
|
+
async balance(): Promise<BalanceResponse> {
|
|
157
|
+
return this.request<BalanceResponse>('get', '/api/sms/balance/');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class PlexismsError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'PlexismsError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class AuthenticationError extends PlexismsError {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'AuthenticationError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class BalanceError extends PlexismsError {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'BalanceError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class APIError extends PlexismsError {
|
|
23
|
+
statusCode?: number;
|
|
24
|
+
responseBody?: Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
constructor(message: string, statusCode?: number, responseBody?: Record<string, unknown>) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'APIError';
|
|
29
|
+
this.statusCode = statusCode;
|
|
30
|
+
this.responseBody = responseBody;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface SMSResponse {
|
|
2
|
+
id: number;
|
|
3
|
+
message_id: string;
|
|
4
|
+
status: 'pending' | 'sent' | 'delivered' | 'failed';
|
|
5
|
+
phone_number: string;
|
|
6
|
+
parts: number;
|
|
7
|
+
cost_usd: string;
|
|
8
|
+
balance: {
|
|
9
|
+
remaining_usd: string;
|
|
10
|
+
remaining_sms: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BulkSMSResponse {
|
|
15
|
+
total: number;
|
|
16
|
+
queued: number;
|
|
17
|
+
task_id: string;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OTPRequestResponse {
|
|
22
|
+
verification_id: string;
|
|
23
|
+
status: string;
|
|
24
|
+
phone_number: string;
|
|
25
|
+
expires_in: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OTPVerifyResponse {
|
|
29
|
+
verified: boolean;
|
|
30
|
+
phone_number?: string;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BalanceResponse {
|
|
35
|
+
amount: string;
|
|
36
|
+
currency: string;
|
|
37
|
+
provider: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SendSMSOptions {
|
|
41
|
+
to: string;
|
|
42
|
+
body: string;
|
|
43
|
+
senderId?: string;
|
|
44
|
+
smsType?: 'transactional' | 'promotional';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SendBulkSMSOptions {
|
|
48
|
+
phoneNumbers: string[];
|
|
49
|
+
body: string;
|
|
50
|
+
senderId?: string;
|
|
51
|
+
smsType?: 'transactional' | 'promotional';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MessageStatusResponse {
|
|
55
|
+
id: number;
|
|
56
|
+
message_id: string;
|
|
57
|
+
status: 'pending' | 'sent' | 'delivered' | 'failed';
|
|
58
|
+
phone_number: string;
|
|
59
|
+
parts: number;
|
|
60
|
+
cost_usd: string;
|
|
61
|
+
created_at: string;
|
|
62
|
+
updated_at: string;
|
|
63
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2018",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": [
|
|
6
|
+
"es2018",
|
|
7
|
+
"dom"
|
|
8
|
+
],
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"outDir": "./dist",
|
|
15
|
+
"rootDir": "./src"
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"**/*.spec.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|