sms-verification-api 0.9.1 → 0.9.7
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/CHANGELOG.md +71 -0
- package/examples/{client.js → client.ts} +105 -119
- package/examples/{libphonenumber-example.js → libphonenumber-example.ts} +1 -1
- package/package.json +16 -14
- package/src/identity-verification-server.ts +385 -261
- package/src/{index.js → index.ts} +1 -1
- package/src/sns.ts +265 -0
- package/src/{verify-phone-server.js → verify-phone-server.ts} +206 -151
- package/src/verify-phone.ts +48 -22
- package/test/{api.test.js → api.test.ts} +20 -16
- package/test/{integration.test.js → integration.test.ts} +10 -10
- package/test/{server.test.js → server.test.ts} +3 -3
- package/test/{verify.test.js → verify.test.ts} +20 -23
- package/test/{voip.test.js → voip.test.ts} +13 -12
- package/tsconfig.json +24 -0
- package/{vitest.config.js → vitest.config.ts} +1 -1
- package/wrangler.toml +1 -4
- package/src/sns.js +0 -236
- /package/test/{metadata-test.js → metadata-test.ts} +0 -0
- /package/test/{setup.js → setup.ts} +0 -0
- /package/test/{utils.test.js → utils.test.ts} +0 -0
package/src/verify-phone.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parsePhoneNumber,
|
|
1
|
+
import { parsePhoneNumber, getNumberType } from 'libphonenumber-js';
|
|
2
2
|
|
|
3
3
|
interface VerifyPhoneOptions {
|
|
4
4
|
/**
|
|
@@ -71,13 +71,26 @@ interface VerifyPhoneOptions {
|
|
|
71
71
|
* @param {string} [options.messageTemplate] - Custom message template. Use {code} as placeholder for the code.
|
|
72
72
|
* @returns {Promise<Object>} Response object with success status, message, messageId, and code
|
|
73
73
|
*/
|
|
74
|
-
export
|
|
74
|
+
export interface VerifyPhoneResult {
|
|
75
|
+
success: boolean;
|
|
76
|
+
message?: string;
|
|
77
|
+
messageId?: string;
|
|
78
|
+
code?: string;
|
|
79
|
+
phoneNumber?: string;
|
|
80
|
+
expiresIn?: number;
|
|
81
|
+
error?: string;
|
|
82
|
+
details?: string;
|
|
83
|
+
isVoip?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default async function verifyPhone(options: VerifyPhoneOptions = {} as VerifyPhoneOptions): Promise<VerifyPhoneResult> {
|
|
87
|
+
const env = (typeof process !== 'undefined' ? process.env : undefined) as Record<string, string | undefined> | undefined;
|
|
75
88
|
var {
|
|
76
|
-
phoneNumber,
|
|
89
|
+
phoneNumber,
|
|
77
90
|
code,
|
|
78
|
-
accessKeyId =
|
|
79
|
-
secretAccessKey =
|
|
80
|
-
awsRegion =
|
|
91
|
+
accessKeyId = env?.AWS_ACCESS_KEY_ID,
|
|
92
|
+
secretAccessKey = env?.AWS_SECRET_ACCESS_KEY,
|
|
93
|
+
awsRegion = env?.AWS_REGION,
|
|
81
94
|
blockVoip = false,
|
|
82
95
|
voipDetectionMethod = 'api',
|
|
83
96
|
useLibPhoneNumber = false,
|
|
@@ -154,10 +167,11 @@ export default async function verifyPhone(options = {} as VerifyPhoneOptions) {
|
|
|
154
167
|
};
|
|
155
168
|
|
|
156
169
|
} catch (error) {
|
|
170
|
+
const err = error as Error;
|
|
157
171
|
return {
|
|
158
172
|
success: false,
|
|
159
|
-
error: error
|
|
160
|
-
details:
|
|
173
|
+
error: err?.message ?? String(error),
|
|
174
|
+
details: err?.stack || undefined
|
|
161
175
|
};
|
|
162
176
|
}
|
|
163
177
|
}
|
|
@@ -167,7 +181,7 @@ export default async function verifyPhone(options = {} as VerifyPhoneOptions) {
|
|
|
167
181
|
* @param {string} phone - The input phone number
|
|
168
182
|
* @returns {string} - The formatted E.164 phone number
|
|
169
183
|
*/
|
|
170
|
-
function formatPhoneNumberLibPhoneNumber(phone) {
|
|
184
|
+
function formatPhoneNumberLibPhoneNumber(phone: string): string {
|
|
171
185
|
try {
|
|
172
186
|
const phoneNumber = parsePhoneNumber(phone);
|
|
173
187
|
if (phoneNumber && phoneNumber.isValid()) {
|
|
@@ -186,7 +200,7 @@ function formatPhoneNumberLibPhoneNumber(phone) {
|
|
|
186
200
|
* @param {string} phone - The input phone number
|
|
187
201
|
* @returns {string} - The formatted E.164 phone number
|
|
188
202
|
*/
|
|
189
|
-
function formatPhoneNumber(phone) {
|
|
203
|
+
function formatPhoneNumber(phone: string): string {
|
|
190
204
|
const cleaned = phone.replace(/\D/g, '');
|
|
191
205
|
|
|
192
206
|
if (cleaned.length === 10) {
|
|
@@ -204,7 +218,7 @@ function formatPhoneNumber(phone) {
|
|
|
204
218
|
* @param {boolean} useLibPhoneNumber - Whether to use libphonenumber-js for validation
|
|
205
219
|
* @returns {boolean} - True if valid, false otherwise
|
|
206
220
|
*/
|
|
207
|
-
function isValidPhoneNumber(phone, useLibPhoneNumber = false) {
|
|
221
|
+
function isValidPhoneNumber(phone: string, useLibPhoneNumber = false): boolean {
|
|
208
222
|
if (useLibPhoneNumber) {
|
|
209
223
|
try {
|
|
210
224
|
const phoneNumber = parsePhoneNumber(phone);
|
|
@@ -224,25 +238,36 @@ function isValidPhoneNumber(phone, useLibPhoneNumber = false) {
|
|
|
224
238
|
* Works directly in the browser using Web Crypto API
|
|
225
239
|
*/
|
|
226
240
|
|
|
241
|
+
interface InternalSNSClientOptions {
|
|
242
|
+
accessKeyId?: string;
|
|
243
|
+
secretAccessKey?: string;
|
|
244
|
+
awsRegion?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
227
247
|
class SNSClient {
|
|
228
|
-
|
|
248
|
+
accessKeyId?: string;
|
|
249
|
+
secretAccessKey?: string;
|
|
250
|
+
region: string;
|
|
251
|
+
endpoint: string;
|
|
252
|
+
|
|
253
|
+
constructor(options: InternalSNSClientOptions = {}) {
|
|
229
254
|
this.accessKeyId = options.accessKeyId;
|
|
230
255
|
this.secretAccessKey = options.secretAccessKey;
|
|
231
256
|
this.region = options.awsRegion || 'us-east-1';
|
|
232
257
|
this.endpoint = `https://sns.${this.region}.amazonaws.com`;
|
|
233
258
|
}
|
|
234
259
|
|
|
235
|
-
stringToUint8Array(str) {
|
|
260
|
+
stringToUint8Array(str: string): Uint8Array {
|
|
236
261
|
return new TextEncoder().encode(str);
|
|
237
262
|
}
|
|
238
263
|
|
|
239
|
-
arrayBufferToHex(buffer) {
|
|
240
|
-
return Array.from(new Uint8Array(buffer))
|
|
264
|
+
arrayBufferToHex(buffer: ArrayBuffer | Uint8Array): string {
|
|
265
|
+
return Array.from(new Uint8Array(buffer as ArrayBuffer))
|
|
241
266
|
.map(b => b.toString(16).padStart(2, '0'))
|
|
242
267
|
.join('');
|
|
243
268
|
}
|
|
244
269
|
|
|
245
|
-
async sign(method, url, headers, payload) {
|
|
270
|
+
async sign(method: string, url: string, headers: Record<string, string>, payload: string): Promise<Record<string, string>> {
|
|
246
271
|
const now = new Date();
|
|
247
272
|
const amzDate = now.toISOString().replace(/[:\-]|\.\d{3}/g, '');
|
|
248
273
|
const dateStamp = amzDate.slice(0, 8);
|
|
@@ -334,7 +359,7 @@ class SNSClient {
|
|
|
334
359
|
return headers;
|
|
335
360
|
}
|
|
336
361
|
|
|
337
|
-
async makeRequest(action, params = {}) {
|
|
362
|
+
async makeRequest(action: string, params: Record<string, string> = {}): Promise<{ MessageId?: string; raw?: string }> {
|
|
338
363
|
const queryParams = new URLSearchParams({
|
|
339
364
|
Action: action,
|
|
340
365
|
Version: '2010-03-31',
|
|
@@ -363,11 +388,12 @@ class SNSClient {
|
|
|
363
388
|
|
|
364
389
|
return this.parseXMLResponse(text);
|
|
365
390
|
} catch (error) {
|
|
366
|
-
|
|
391
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
392
|
+
throw new Error(`SNS Request failed: ${message}`);
|
|
367
393
|
}
|
|
368
394
|
}
|
|
369
395
|
|
|
370
|
-
parseXMLResponse(xmlText) {
|
|
396
|
+
parseXMLResponse(xmlText: string): { MessageId?: string; raw?: string } {
|
|
371
397
|
const messageIdMatch = xmlText.match(/<MessageId>([^<]+)<\/MessageId>/);
|
|
372
398
|
const errorCodeMatch = xmlText.match(/<Code>([^<]+)<\/Code>/);
|
|
373
399
|
const errorMessageMatch = xmlText.match(/<Message>([^<]+)<\/Message>/);
|
|
@@ -391,7 +417,7 @@ class SNSClient {
|
|
|
391
417
|
* @param {string} phone - The phone number in E.164 format (e.g., +1234567890).
|
|
392
418
|
* @returns {Promise<boolean>} True if phone is Bandwidth-only VoIP
|
|
393
419
|
*/
|
|
394
|
-
async function isPhoneNumberVoip(phone) {
|
|
420
|
+
export async function isPhoneNumberVoip(phone: string): Promise<boolean> {
|
|
395
421
|
try {
|
|
396
422
|
const response = await fetch(`https://www.sent.dm/api/phone-lookup?phone=${encodeURIComponent(phone)}`, {
|
|
397
423
|
headers: {
|
|
@@ -404,7 +430,7 @@ async function isPhoneNumberVoip(phone) {
|
|
|
404
430
|
return false; // Default to allowing the number if lookup fails
|
|
405
431
|
}
|
|
406
432
|
|
|
407
|
-
const phoneData = await response.json();
|
|
433
|
+
const phoneData = await response.json() as any;
|
|
408
434
|
|
|
409
435
|
if (!phoneData || !phoneData.carrier) return false;
|
|
410
436
|
|
|
@@ -428,7 +454,7 @@ async function isPhoneNumberVoip(phone) {
|
|
|
428
454
|
* @param {string} metadataType - Metadata type: 'minimal' (75KB) or 'full' (140KB)
|
|
429
455
|
* @returns {Promise<boolean>} True if phone is likely VoIP
|
|
430
456
|
*/
|
|
431
|
-
async function isPhoneNumberVoipLibPhoneNumber(phone, metadataType = 'minimal') {
|
|
457
|
+
async function isPhoneNumberVoipLibPhoneNumber(phone: string, metadataType: 'minimal' | 'full' = 'minimal'): Promise<boolean> {
|
|
432
458
|
try {
|
|
433
459
|
// Parse the phone number using libphonenumber-js
|
|
434
460
|
const phoneNumber = parsePhoneNumber(phone);
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { createApp } from '../src/verify-phone-server
|
|
2
|
+
import { createApp } from '../src/verify-phone-server';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
// eslint-disable-next-line no-var
|
|
6
|
+
var env: Record<string, string>;
|
|
7
|
+
}
|
|
3
8
|
|
|
4
9
|
describe('SMS Verification API', () => {
|
|
5
|
-
let app
|
|
10
|
+
let app: ReturnType<typeof createApp>;
|
|
6
11
|
|
|
7
12
|
beforeEach(() => {
|
|
8
|
-
// Create a fresh app instance for each test
|
|
9
13
|
app = createApp(globalThis.env);
|
|
10
14
|
});
|
|
11
15
|
|
|
@@ -13,7 +17,7 @@ describe('SMS Verification API', () => {
|
|
|
13
17
|
it('should return health status', async () => {
|
|
14
18
|
const req = new Request('http://localhost/health');
|
|
15
19
|
const res = await app.request(req, globalThis.env);
|
|
16
|
-
const data = await res.json();
|
|
20
|
+
const data: any = await res.json();
|
|
17
21
|
|
|
18
22
|
expect(res.status).toBe(200);
|
|
19
23
|
expect(data).toMatchObject({
|
|
@@ -27,7 +31,7 @@ describe('SMS Verification API', () => {
|
|
|
27
31
|
it('should require API key authentication', async () => {
|
|
28
32
|
const req = new Request('http://localhost/api/send-verification?phoneNumber=%2B1234567890');
|
|
29
33
|
const res = await app.request(req, globalThis.env);
|
|
30
|
-
const data = await res.json();
|
|
34
|
+
const data: any = await res.json();
|
|
31
35
|
|
|
32
36
|
expect(res.status).toBe(401);
|
|
33
37
|
expect(data).toMatchObject({
|
|
@@ -42,7 +46,7 @@ describe('SMS Verification API', () => {
|
|
|
42
46
|
}
|
|
43
47
|
});
|
|
44
48
|
const res = await app.request(req, globalThis.env);
|
|
45
|
-
const data = await res.json();
|
|
49
|
+
const data: any = await res.json();
|
|
46
50
|
|
|
47
51
|
expect(res.status).toBe(401);
|
|
48
52
|
expect(data).toMatchObject({
|
|
@@ -57,7 +61,7 @@ describe('SMS Verification API', () => {
|
|
|
57
61
|
}
|
|
58
62
|
});
|
|
59
63
|
const res = await app.request(req, globalThis.env);
|
|
60
|
-
const data = await res.json();
|
|
64
|
+
const data: any = await res.json();
|
|
61
65
|
|
|
62
66
|
expect(res.status).toBe(200);
|
|
63
67
|
expect(data.success).toBe(true);
|
|
@@ -70,7 +74,7 @@ describe('SMS Verification API', () => {
|
|
|
70
74
|
}
|
|
71
75
|
});
|
|
72
76
|
const res = await app.request(req, globalThis.env);
|
|
73
|
-
const data = await res.json();
|
|
77
|
+
const data: any = await res.json();
|
|
74
78
|
|
|
75
79
|
expect(res.status).toBe(400);
|
|
76
80
|
expect(data).toMatchObject({
|
|
@@ -85,7 +89,7 @@ describe('SMS Verification API', () => {
|
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
91
|
const res = await app.request(req, globalThis.env);
|
|
88
|
-
const data = await res.json();
|
|
92
|
+
const data: any = await res.json();
|
|
89
93
|
|
|
90
94
|
expect(res.status).toBe(200);
|
|
91
95
|
expect(data.success).toBe(true);
|
|
@@ -98,7 +102,7 @@ describe('SMS Verification API', () => {
|
|
|
98
102
|
}
|
|
99
103
|
});
|
|
100
104
|
const res = await app.request(req, globalThis.env);
|
|
101
|
-
const data = await res.json();
|
|
105
|
+
const data: any = await res.json();
|
|
102
106
|
|
|
103
107
|
expect(res.status).toBe(200);
|
|
104
108
|
expect(data.success).toBe(true);
|
|
@@ -111,7 +115,7 @@ describe('SMS Verification API', () => {
|
|
|
111
115
|
}
|
|
112
116
|
});
|
|
113
117
|
const res = await app.request(req, globalThis.env);
|
|
114
|
-
const data = await res.json();
|
|
118
|
+
const data: any = await res.json();
|
|
115
119
|
|
|
116
120
|
expect(res.status).toBe(200);
|
|
117
121
|
expect(data.success).toBe(true);
|
|
@@ -122,7 +126,7 @@ describe('SMS Verification API', () => {
|
|
|
122
126
|
it('should require API key authentication', async () => {
|
|
123
127
|
const req = new Request('http://localhost/api/verify-code?phoneNumber=%2B1234567890&code=123456');
|
|
124
128
|
const res = await app.request(req, globalThis.env);
|
|
125
|
-
const data = await res.json();
|
|
129
|
+
const data: any = await res.json();
|
|
126
130
|
|
|
127
131
|
expect(res.status).toBe(401);
|
|
128
132
|
expect(data).toMatchObject({
|
|
@@ -137,7 +141,7 @@ describe('SMS Verification API', () => {
|
|
|
137
141
|
}
|
|
138
142
|
});
|
|
139
143
|
const res = await app.request(req, globalThis.env);
|
|
140
|
-
const data = await res.json();
|
|
144
|
+
const data: any = await res.json();
|
|
141
145
|
|
|
142
146
|
expect(res.status).toBe(401);
|
|
143
147
|
expect(data).toMatchObject({
|
|
@@ -152,7 +156,7 @@ describe('SMS Verification API', () => {
|
|
|
152
156
|
}
|
|
153
157
|
});
|
|
154
158
|
const res = await app.request(req, globalThis.env);
|
|
155
|
-
const data = await res.json();
|
|
159
|
+
const data: any = await res.json();
|
|
156
160
|
|
|
157
161
|
expect(res.status).toBe(400);
|
|
158
162
|
expect(data).toMatchObject({
|
|
@@ -167,7 +171,7 @@ describe('SMS Verification API', () => {
|
|
|
167
171
|
}
|
|
168
172
|
});
|
|
169
173
|
const res = await app.request(req, globalThis.env);
|
|
170
|
-
const data = await res.json();
|
|
174
|
+
const data: any = await res.json();
|
|
171
175
|
|
|
172
176
|
expect(res.status).toBe(400); // Will fail because no code was sent, but auth works
|
|
173
177
|
expect(data.error).toBeDefined();
|
|
@@ -178,7 +182,7 @@ describe('SMS Verification API', () => {
|
|
|
178
182
|
it('should serve OpenAPI documentation', async () => {
|
|
179
183
|
const req = new Request('http://localhost/openapi.json');
|
|
180
184
|
const res = await app.request(req, globalThis.env);
|
|
181
|
-
const data = await res.json();
|
|
185
|
+
const data: any = await res.json();
|
|
182
186
|
|
|
183
187
|
expect(res.status).toBe(200);
|
|
184
188
|
expect(data).toMatchObject({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { createApp } from '../src/verify-phone-server
|
|
2
|
+
import { createApp } from '../src/verify-phone-server';
|
|
3
3
|
|
|
4
4
|
describe('SMS Verification API Integration Tests', () => {
|
|
5
|
-
let app
|
|
5
|
+
let app: ReturnType<typeof createApp>;
|
|
6
6
|
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
app = createApp(globalThis.env);
|
|
@@ -19,7 +19,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
const res1 = await app.request(req1, globalThis.env);
|
|
22
|
-
const data1 = await res1.json();
|
|
22
|
+
const data1: any = await res1.json();
|
|
23
23
|
|
|
24
24
|
expect(res1.status).toBe(200);
|
|
25
25
|
expect(data1.success).toBe(true);
|
|
@@ -31,7 +31,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
const res2 = await app.request(req2, globalThis.env);
|
|
34
|
-
const data2 = await res2.json();
|
|
34
|
+
const data2: any = await res2.json();
|
|
35
35
|
|
|
36
36
|
expect(res2.status).toBe(200);
|
|
37
37
|
expect(data2.success).toBe(true);
|
|
@@ -42,7 +42,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
42
42
|
|
|
43
43
|
const req = new Request(`http://localhost/api/send-verification?phoneNumber=${encodeURIComponent(phoneNumber)}`);
|
|
44
44
|
const res = await app.request(req, globalThis.env);
|
|
45
|
-
const data = await res.json();
|
|
45
|
+
const data: any = await res.json();
|
|
46
46
|
|
|
47
47
|
expect(res.status).toBe(401);
|
|
48
48
|
expect(data.error).toBe('API key required');
|
|
@@ -57,7 +57,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
59
|
const res = await app.request(req, globalThis.env);
|
|
60
|
-
const data = await res.json();
|
|
60
|
+
const data: any = await res.json();
|
|
61
61
|
|
|
62
62
|
expect(res.status).toBe(401);
|
|
63
63
|
expect(data.error).toBe('Invalid API key');
|
|
@@ -81,7 +81,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
81
81
|
}
|
|
82
82
|
});
|
|
83
83
|
const res = await app.request(req, globalThis.env);
|
|
84
|
-
const data = await res.json();
|
|
84
|
+
const data: any = await res.json();
|
|
85
85
|
|
|
86
86
|
expect(res.status).toBe(200);
|
|
87
87
|
expect(data.success).toBe(true);
|
|
@@ -104,7 +104,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
const res = await app.request(req, globalThis.env);
|
|
107
|
-
const data = await res.json();
|
|
107
|
+
const data: any = await res.json();
|
|
108
108
|
|
|
109
109
|
expect(res.status).toBe(400);
|
|
110
110
|
expect(data.error).toBe('Invalid phone number format');
|
|
@@ -122,7 +122,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
const res = await app.request(req, globalThis.env);
|
|
125
|
-
const data = await res.json();
|
|
125
|
+
const data: any = await res.json();
|
|
126
126
|
|
|
127
127
|
expect(res.status).toBe(200);
|
|
128
128
|
expect(data).toMatchObject({
|
|
@@ -143,7 +143,7 @@ describe('SMS Verification API Integration Tests', () => {
|
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
const res = await app.request(req, globalThis.env);
|
|
146
|
-
const data = await res.json();
|
|
146
|
+
const data: any = await res.json();
|
|
147
147
|
|
|
148
148
|
expect(res.status).toBe(400); // Will fail because no code was sent, but endpoint works
|
|
149
149
|
expect(data.error).toBeDefined();
|
|
@@ -21,7 +21,7 @@ describe('SMS Verification API Server', () => {
|
|
|
21
21
|
describe('Health Check Endpoints', () => {
|
|
22
22
|
it('should return API info on root endpoint', async () => {
|
|
23
23
|
const res = await server.request('/');
|
|
24
|
-
const data = await res.json();
|
|
24
|
+
const data: any = await res.json();
|
|
25
25
|
|
|
26
26
|
expect(res.status).toBe(200);
|
|
27
27
|
expect(data.success).toBe(true);
|
|
@@ -31,7 +31,7 @@ describe('SMS Verification API Server', () => {
|
|
|
31
31
|
|
|
32
32
|
it('should return health status', async () => {
|
|
33
33
|
const res = await server.request('/health');
|
|
34
|
-
const data = await res.json();
|
|
34
|
+
const data: any = await res.json();
|
|
35
35
|
|
|
36
36
|
expect(res.status).toBe(200);
|
|
37
37
|
expect(data.success).toBe(true);
|
|
@@ -82,7 +82,7 @@ describe('SMS Verification API Server', () => {
|
|
|
82
82
|
describe('Error Handling', () => {
|
|
83
83
|
it('should return 404 for non-existent endpoints', async () => {
|
|
84
84
|
const res = await server.request('/nonexistent');
|
|
85
|
-
const data = await res.json();
|
|
85
|
+
const data: any = await res.json();
|
|
86
86
|
|
|
87
87
|
expect(res.status).toBe(404);
|
|
88
88
|
expect(data.success).toBe(false);
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import verifyPhone from '../src/verify-phone
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
console.
|
|
20
|
-
|
|
21
|
-
} else {
|
|
22
|
-
console.error('Failed to send SMS:', result.error);
|
|
23
|
-
}
|
|
1
|
+
import verifyPhone from '../src/verify-phone';
|
|
2
|
+
|
|
3
|
+
// Basic usage with custom code
|
|
4
|
+
const result = await verifyPhone({
|
|
5
|
+
phoneNumber: '+1234567890',
|
|
6
|
+
code: 'ABC123',
|
|
7
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
8
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
9
|
+
awsRegion: 'us-east-1',
|
|
10
|
+
blockVoip: true,
|
|
11
|
+
senderId: 'MyApp',
|
|
12
|
+
messageTemplate: 'MyApp: Your code is {code}. Valid for 10 minutes.'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (result.success) {
|
|
16
|
+
console.log('SMS sent successfully:', result.messageId);
|
|
17
|
+
console.log('Code sent:', result.code);
|
|
18
|
+
} else {
|
|
19
|
+
console.error('Failed to send SMS:', result.error);
|
|
20
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { createApp } from '../src/verify-phone-server
|
|
2
|
+
import { createApp } from '../src/verify-phone-server';
|
|
3
3
|
|
|
4
4
|
describe('VoIP Blocking Functionality', () => {
|
|
5
|
-
let app
|
|
5
|
+
let app: ReturnType<typeof createApp>;
|
|
6
6
|
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
app = createApp(globalThis.env);
|
|
@@ -16,7 +16,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
const res = await app.request(req, globalThis.env);
|
|
19
|
-
const data = await res.json();
|
|
19
|
+
const data: any = await res.json();
|
|
20
20
|
|
|
21
21
|
expect(res.status).toBe(200);
|
|
22
22
|
expect(data.success).toBe(true);
|
|
@@ -29,7 +29,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
const res = await app.request(req, globalThis.env);
|
|
32
|
-
const data = await res.json();
|
|
32
|
+
const data: any = await res.json();
|
|
33
33
|
|
|
34
34
|
expect(res.status).toBe(200);
|
|
35
35
|
expect(data.success).toBe(true);
|
|
@@ -42,7 +42,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
const res = await app.request(req, globalThis.env);
|
|
45
|
-
const data = await res.json();
|
|
45
|
+
const data: any = await res.json();
|
|
46
46
|
|
|
47
47
|
expect(res.status).toBe(200);
|
|
48
48
|
expect(data.success).toBe(true);
|
|
@@ -52,12 +52,13 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
52
52
|
describe('VoIP detection logic', () => {
|
|
53
53
|
it('should correctly identify VoIP numbers', async () => {
|
|
54
54
|
// Import the function from the module
|
|
55
|
-
const module = await import('../src/verify-phone-server
|
|
55
|
+
const module = await import('../src/verify-phone-server');
|
|
56
56
|
const { isPhoneNumberVoip } = module;
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
// Mock the grab function to return VoIP data
|
|
59
|
-
const
|
|
60
|
-
|
|
59
|
+
const g = globalThis as any;
|
|
60
|
+
const originalGrab = g.grab;
|
|
61
|
+
g.grab = {
|
|
61
62
|
instance: () => ({
|
|
62
63
|
"phone-lookup": async () => ({
|
|
63
64
|
carrier: {
|
|
@@ -76,7 +77,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
76
77
|
expect(isVoip).toBe(true);
|
|
77
78
|
|
|
78
79
|
// Mock the grab function to return mobile data
|
|
79
|
-
|
|
80
|
+
g.grab = {
|
|
80
81
|
instance: () => ({
|
|
81
82
|
"phone-lookup": async () => ({
|
|
82
83
|
carrier: {
|
|
@@ -95,7 +96,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
95
96
|
expect(isMobile).toBe(false);
|
|
96
97
|
|
|
97
98
|
// Mock the grab function to return null data
|
|
98
|
-
|
|
99
|
+
g.grab = {
|
|
99
100
|
instance: () => ({
|
|
100
101
|
"phone-lookup": async () => null
|
|
101
102
|
})
|
|
@@ -106,7 +107,7 @@ describe('VoIP Blocking Functionality', () => {
|
|
|
106
107
|
expect(isNull).toBe(false);
|
|
107
108
|
|
|
108
109
|
// Restore original grab function
|
|
109
|
-
|
|
110
|
+
g.grab = originalGrab;
|
|
110
111
|
});
|
|
111
112
|
});
|
|
112
113
|
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["@cloudflare/workers-types", "node"],
|
|
8
|
+
"strict": false,
|
|
9
|
+
"noImplicitAny": false,
|
|
10
|
+
"strictNullChecks": false,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"allowJs": false,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"verbatimModuleSyntax": false
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*.ts", "test/**/*.ts", "examples/**/*.ts", "vitest.config.ts"],
|
|
23
|
+
"exclude": ["node_modules", "docs", ".wrangler", "dist"]
|
|
24
|
+
}
|
package/wrangler.toml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name = "sms-verification-api"
|
|
2
|
-
main = "src/index.
|
|
2
|
+
main = "src/index.ts"
|
|
3
3
|
compatibility_date = "2024-01-01"
|
|
4
4
|
compatibility_flags = ["nodejs_compat"]
|
|
5
5
|
|
|
@@ -8,9 +8,6 @@ compatibility_flags = ["nodejs_compat"]
|
|
|
8
8
|
AWS_REGION = "us-east-1"
|
|
9
9
|
SMS_SENDER_ID = "Verify"
|
|
10
10
|
|
|
11
|
-
# Secrets (these should be set via wrangler secret put)
|
|
12
|
-
TRESTLE_API_KEY = "URx3hLxr6o9uSghxffe2S72oMXuM12Ry9zW80Src"
|
|
13
|
-
|
|
14
11
|
# Staging environment
|
|
15
12
|
[env.staging]
|
|
16
13
|
name = "sms-verification-api-staging"
|