verifence 0.1.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/LICENSE +21 -0
- package/README.md +53 -0
- package/index.d.ts +60 -0
- package/index.js +109 -0
- package/package.json +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Verifence
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Verifence JavaScript SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js / TypeScript client for [Verifence](../../README.md) — the pre-send
|
|
4
|
+
firewall for SMS verification. Zero dependencies (uses built-in `fetch`, Node ≥ 18).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install verifence
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
> **Your API key is a secret.** Every call is server-to-server. Read it from an
|
|
11
|
+
> environment variable; never ship it in browser or mobile code.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { Verifence, QuotaExceededError } from 'verifence';
|
|
17
|
+
|
|
18
|
+
const vf = new Verifence(process.env.VERIFENCE_KEY);
|
|
19
|
+
|
|
20
|
+
// Before you hand a message to Twilio/Vonage/etc:
|
|
21
|
+
const verdict = await vf.check('+14155552671', req.ip, { userId: 'u_123' });
|
|
22
|
+
// ^ the END USER's IP, from your inbound request
|
|
23
|
+
|
|
24
|
+
if (!verdict.allow) {
|
|
25
|
+
return res.status(429).json({ error: 'too_many_verification_requests' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ... send the SMS with your provider ...
|
|
29
|
+
|
|
30
|
+
// After the user enters the correct code:
|
|
31
|
+
await vf.confirm(req.ip);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Handle a spent quota:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
try {
|
|
38
|
+
await vf.check('+14155552671', req.ip);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err instanceof QuotaExceededError) {
|
|
41
|
+
console.log('quota resets at', err.resetsAt);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
- `check(phone, clientIp, { userId?, requireMobile? })` → `Decision` (`{ allow, reason, detail, degraded, ... }`)
|
|
49
|
+
- `confirm(clientIp)` → `{ recorded }`
|
|
50
|
+
- `stats()` → account totals
|
|
51
|
+
|
|
52
|
+
A block is a resolved promise with `allow: false` — not a throw. Only bad input,
|
|
53
|
+
auth, and quota (429) reject. Self-hosting? `new Verifence(key, { baseUrl: 'https://verifence.internal' })`.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface CheckOptions {
|
|
2
|
+
/** Your id for the end user — the counter that survives account rotation. */
|
|
3
|
+
userId?: string;
|
|
4
|
+
/** Reject numbers classified as landline. */
|
|
5
|
+
requireMobile?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Decision {
|
|
9
|
+
allow: boolean;
|
|
10
|
+
reason: string | null;
|
|
11
|
+
detail: string;
|
|
12
|
+
degraded: boolean;
|
|
13
|
+
e164?: string;
|
|
14
|
+
country?: string;
|
|
15
|
+
lineType?: string;
|
|
16
|
+
network?: string;
|
|
17
|
+
counts?: Record<string, number>;
|
|
18
|
+
confirmationRate?: number;
|
|
19
|
+
allowlisted?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ConfirmResult {
|
|
23
|
+
recorded: boolean;
|
|
24
|
+
detail?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Stats {
|
|
28
|
+
allowed: number;
|
|
29
|
+
blocked: number;
|
|
30
|
+
total: number;
|
|
31
|
+
block_rate: number;
|
|
32
|
+
estimated_saving_usd: { low: number; high: number; note: string };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VerifenceOptions {
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class VerifenceError extends Error {
|
|
42
|
+
status: number;
|
|
43
|
+
code: string | null;
|
|
44
|
+
body: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class QuotaExceededError extends VerifenceError {
|
|
48
|
+
/** ISO-8601 timestamp of when the quota resets. */
|
|
49
|
+
resetsAt: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class Verifence {
|
|
53
|
+
constructor(apiKey: string, options?: VerifenceOptions);
|
|
54
|
+
/** @param clientIp The END USER's IP, forwarded from your inbound request. */
|
|
55
|
+
check(phone: string, clientIp: string, opts?: CheckOptions): Promise<Decision>;
|
|
56
|
+
confirm(clientIp: string): Promise<ConfirmResult>;
|
|
57
|
+
stats(): Promise<Stats>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default Verifence;
|
package/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifence JavaScript SDK.
|
|
3
|
+
*
|
|
4
|
+
* Call check() immediately before you hand a message to your SMS provider. A deny
|
|
5
|
+
* is a message you never pay for. Report a real code entry with confirm() so the
|
|
6
|
+
* network-reputation layer can learn.
|
|
7
|
+
*
|
|
8
|
+
* The API key is a SECRET — server-to-server only. Never ship it in browser or
|
|
9
|
+
* mobile code. Read it from an environment variable.
|
|
10
|
+
*
|
|
11
|
+
* import { Verifence } from '@verifence/verifence';
|
|
12
|
+
* const vf = new Verifence(process.env.VERIFENCE_KEY);
|
|
13
|
+
* const verdict = await vf.check('+14155552671', req.ip, { userId: 'u_123' });
|
|
14
|
+
* if (!verdict.allow) return; // do not send
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class VerifenceError extends Error {
|
|
18
|
+
constructor(message, { status = 0, code = null, body = null } = {}) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'VerifenceError';
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.body = body;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class QuotaExceededError extends VerifenceError {
|
|
28
|
+
constructor(message, opts) {
|
|
29
|
+
super(message, opts);
|
|
30
|
+
this.name = 'QuotaExceededError';
|
|
31
|
+
this.resetsAt = opts?.body?.resets_at ?? null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Verifence {
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} apiKey Your secret `vf_live_…` key.
|
|
38
|
+
* @param {{ baseUrl?: string, timeoutMs?: number, fetch?: typeof fetch }} [options]
|
|
39
|
+
*/
|
|
40
|
+
constructor(apiKey, options = {}) {
|
|
41
|
+
if (!apiKey) throw new VerifenceError('A Verifence API key is required.');
|
|
42
|
+
this.apiKey = apiKey;
|
|
43
|
+
this.baseUrl = options.baseUrl ?? 'https://api.verifence.dev';
|
|
44
|
+
this.timeoutMs = options.timeoutMs ?? 4000;
|
|
45
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Pre-send decision.
|
|
50
|
+
* @param {string} phone E.164, e.g. "+14155552671".
|
|
51
|
+
* @param {string} clientIp The END USER's IP, forwarded from your inbound request — not your server's.
|
|
52
|
+
* @param {{ userId?: string, requireMobile?: boolean }} [opts]
|
|
53
|
+
*/
|
|
54
|
+
async check(phone, clientIp, opts = {}) {
|
|
55
|
+
const body = { phone, client_ip: clientIp };
|
|
56
|
+
if (opts.userId != null) body.user_id = opts.userId;
|
|
57
|
+
if (opts.requireMobile) body.require_mobile = true;
|
|
58
|
+
return this.#request('POST', '/v1/check', body);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Report that a code sent to this end user was entered correctly.
|
|
63
|
+
* @param {string} clientIp The same end-user IP you sent to check().
|
|
64
|
+
*/
|
|
65
|
+
async confirm(clientIp) {
|
|
66
|
+
return this.#request('POST', '/v1/confirm', { client_ip: clientIp });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Account totals: allowed, blocked, block rate, estimated saving. */
|
|
70
|
+
async stats() {
|
|
71
|
+
return this.#request('GET', '/v1/stats', null);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async #request(method, path, body) {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
77
|
+
|
|
78
|
+
let res;
|
|
79
|
+
try {
|
|
80
|
+
res = await this._fetch(this.baseUrl + path, {
|
|
81
|
+
method,
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
84
|
+
Accept: 'application/json',
|
|
85
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
86
|
+
},
|
|
87
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
throw new VerifenceError(`Verifence request failed: ${err.message}`);
|
|
92
|
+
} finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = await res.json().catch(() => null);
|
|
97
|
+
if (!data) throw new VerifenceError(`Unexpected response (HTTP ${res.status})`, { status: res.status });
|
|
98
|
+
|
|
99
|
+
if (res.status >= 400) {
|
|
100
|
+
const message = data.detail ?? data.error ?? `HTTP ${res.status}`;
|
|
101
|
+
const opts = { status: res.status, code: data.error ?? null, body: data };
|
|
102
|
+
throw res.status === 429 ? new QuotaExceededError(message, opts) : new VerifenceError(message, opts);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default Verifence;
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "verifence",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official JavaScript/TypeScript SDK for Verifence — the pre-send firewall for SMS verification.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["index.js", "index.d.ts", "README.md"],
|
|
15
|
+
"engines": { "node": ">=18" },
|
|
16
|
+
"keywords": ["verifence", "sms", "verification", "fraud", "otp", "sms-pumping"],
|
|
17
|
+
"license": "MIT"
|
|
18
|
+
}
|