@simplr-ai/node 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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/index.cjs +236 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +180 -0
- package/dist/index.d.ts +180 -0
- package/dist/index.js +211 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Simplr
|
|
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,96 @@
|
|
|
1
|
+
# @simplr-ai/node
|
|
2
|
+
|
|
3
|
+
Simplr's **server-side** SDK for Node.js — run fraud/identity checks, score orders, ingest edge logs, and verify webhook signatures, all with your secret key.
|
|
4
|
+
|
|
5
|
+
> This is the backend SDK. For client-side device signals, RUM, and feature-flag evaluation use [`@simplr-ai/fraud-sdk`](https://www.npmjs.com/package/@simplr-ai/fraud-sdk) (browser) or `simplify_fraud` (Flutter).
|
|
6
|
+
|
|
7
|
+
Docs: https://docs.simplr.so/docs/sdks/node
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @simplr-ai/node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Node 18+ (uses the built-in `fetch` and `crypto`).
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Simplr } from "@simplr-ai/node";
|
|
21
|
+
|
|
22
|
+
const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! }); // sk_live_… / sk_test_…
|
|
23
|
+
|
|
24
|
+
const result = await simplr.check({ email: "user@example.com", event_type: "signup" });
|
|
25
|
+
if (result.risk_level === "high" || result.risk_level === "critical") {
|
|
26
|
+
// require extra verification
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`baseUrl` defaults to `https://api.simplr.sh`; override it for local/dev:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
new Simplr({ apiKey: "sk_test_…", baseUrl: "http://localhost:7002" });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Checks
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
await simplr.check({ phone: "+14155550100", event_type: "login" });
|
|
40
|
+
await simplr.checkBulk([{ email: "a@x.com" }, { phone: "+1..." }]); // up to 100
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Orders
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
await simplr.orders.submit({ order_id: "o_1", external_id: "cust_1", amount: 249, currency: "USD" });
|
|
47
|
+
await simplr.orders.submitBulk([/* up to 100 orders */]);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Phone intelligence
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
await simplr.phone.report({ phone: "+14155550100", outcome: "sim_swap_fraud", confidence: 0.9 });
|
|
54
|
+
await simplr.phone.intelligence("+14155550100");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Edge devices & logs
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
await simplr.edge.registerDevice({ device_id: "POS-0042", name: "Front till" });
|
|
61
|
+
await simplr.edge.heartbeat("POS-0042", { cpu: 0.34, battery: 0.78 });
|
|
62
|
+
await simplr.edge.ingestLogs("POS-0042", [{ category: "transaction", level: "info", message: "sale ok" }]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Webhooks
|
|
66
|
+
|
|
67
|
+
Verify the `X-Simplr-Signature` header against the **raw** request body (don't re-serialize parsed JSON):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import express from "express";
|
|
71
|
+
import { Simplr } from "@simplr-ai/node";
|
|
72
|
+
|
|
73
|
+
const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });
|
|
74
|
+
const app = express();
|
|
75
|
+
|
|
76
|
+
app.post("/hooks/simplify", express.raw({ type: "application/json" }), (req, res) => {
|
|
77
|
+
const sig = req.header("X-Simplr-Signature")!;
|
|
78
|
+
try {
|
|
79
|
+
const event = simplr.webhooks.constructEvent(req.body, sig, process.env.SIMPLR_WEBHOOK_SECRET!);
|
|
80
|
+
// event.event, event.data
|
|
81
|
+
res.sendStatus(200);
|
|
82
|
+
} catch {
|
|
83
|
+
res.sendStatus(400); // invalid signature
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`verify(payload, header, secret, { toleranceSec })` returns a boolean; `constructEvent(...)` returns the parsed event or throws `WebhookVerificationError`.
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Non-2xx responses throw `SimplrError` with `.status` and `.body`.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Simplr: () => Simplr,
|
|
24
|
+
SimplrError: () => SimplrError,
|
|
25
|
+
WebhookVerificationError: () => WebhookVerificationError,
|
|
26
|
+
constructWebhookEvent: () => constructEvent,
|
|
27
|
+
default: () => src_default,
|
|
28
|
+
verifyWebhook: () => verify
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var SimplrError = class extends Error {
|
|
34
|
+
status;
|
|
35
|
+
body;
|
|
36
|
+
constructor(message, status, body) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "SimplrError";
|
|
39
|
+
this.status = status;
|
|
40
|
+
this.body = body;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var WebhookVerificationError = class extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "WebhookVerificationError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/http.ts
|
|
51
|
+
async function apiRequest(cfg, method, path, body) {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
54
|
+
try {
|
|
55
|
+
const res = await cfg.fetchImpl(`${cfg.baseUrl}${path}`, {
|
|
56
|
+
method,
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
"X-API-Key": cfg.apiKey
|
|
60
|
+
},
|
|
61
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
62
|
+
signal: controller.signal
|
|
63
|
+
});
|
|
64
|
+
const text = await res.text();
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
68
|
+
} catch {
|
|
69
|
+
parsed = text;
|
|
70
|
+
}
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const message = parsed && (parsed.message || parsed.error) || `Simplr API error ${res.status}`;
|
|
73
|
+
throw new SimplrError(message, res.status, parsed);
|
|
74
|
+
}
|
|
75
|
+
return parsed && typeof parsed === "object" && "content" in parsed ? parsed.content : parsed;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err instanceof SimplrError) throw err;
|
|
78
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
79
|
+
throw new SimplrError(`Request to ${path} timed out after ${cfg.timeoutMs}ms`, 0, null);
|
|
80
|
+
}
|
|
81
|
+
throw new SimplrError(err instanceof Error ? err.message : "Network error", 0, null);
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/resources.ts
|
|
88
|
+
var OrdersResource = class {
|
|
89
|
+
constructor(cfg) {
|
|
90
|
+
this.cfg = cfg;
|
|
91
|
+
}
|
|
92
|
+
cfg;
|
|
93
|
+
/** Submit a single order for fraud scoring. */
|
|
94
|
+
submit(order) {
|
|
95
|
+
return apiRequest(this.cfg, "POST", "/v1/orders", order);
|
|
96
|
+
}
|
|
97
|
+
/** Submit up to 100 orders at once. */
|
|
98
|
+
submitBulk(orders) {
|
|
99
|
+
return apiRequest(this.cfg, "POST", "/v1/orders/bulk", { orders });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var PhoneResource = class {
|
|
103
|
+
constructor(cfg) {
|
|
104
|
+
this.cfg = cfg;
|
|
105
|
+
}
|
|
106
|
+
cfg;
|
|
107
|
+
/** Report the real-world outcome for a phone number to improve scoring. */
|
|
108
|
+
report(input) {
|
|
109
|
+
return apiRequest(this.cfg, "POST", "/v1/check/phone/report", input);
|
|
110
|
+
}
|
|
111
|
+
/** Fetch stored risk intelligence for a phone number. */
|
|
112
|
+
intelligence(phone) {
|
|
113
|
+
return apiRequest(this.cfg, "GET", `/v1/check/phone/intelligence/${encodeURIComponent(phone)}`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var EdgeResource = class {
|
|
117
|
+
constructor(cfg) {
|
|
118
|
+
this.cfg = cfg;
|
|
119
|
+
}
|
|
120
|
+
cfg;
|
|
121
|
+
/** Register an edge device. */
|
|
122
|
+
registerDevice(input) {
|
|
123
|
+
return apiRequest(this.cfg, "POST", "/v1/edge/devices/register", input);
|
|
124
|
+
}
|
|
125
|
+
/** Report a device heartbeat with health metrics. */
|
|
126
|
+
heartbeat(deviceId, metrics) {
|
|
127
|
+
return apiRequest(
|
|
128
|
+
this.cfg,
|
|
129
|
+
"POST",
|
|
130
|
+
`/v1/edge/devices/${encodeURIComponent(deviceId)}/heartbeat`,
|
|
131
|
+
metrics
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
/** Batch-ingest structured logs for a device. */
|
|
135
|
+
ingestLogs(deviceId, logs) {
|
|
136
|
+
return apiRequest(this.cfg, "POST", "/v1/edge/logs", { device_id: deviceId, logs });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/webhooks.ts
|
|
141
|
+
var webhooks_exports = {};
|
|
142
|
+
__export(webhooks_exports, {
|
|
143
|
+
constructEvent: () => constructEvent,
|
|
144
|
+
verify: () => verify,
|
|
145
|
+
webhooks: () => webhooks
|
|
146
|
+
});
|
|
147
|
+
var import_node_crypto = require("crypto");
|
|
148
|
+
function parseHeader(header) {
|
|
149
|
+
const parts = header.split(",").map((p) => p.trim());
|
|
150
|
+
let t = "";
|
|
151
|
+
let v1 = "";
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
const [k, v] = part.split("=");
|
|
154
|
+
if (k === "t") t = v;
|
|
155
|
+
if (k === "v1") v1 = v;
|
|
156
|
+
}
|
|
157
|
+
return t && v1 ? { t, v1 } : null;
|
|
158
|
+
}
|
|
159
|
+
function expectedSignature(timestamp, payload, secret) {
|
|
160
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(`${timestamp}.${payload}`).digest("hex");
|
|
161
|
+
}
|
|
162
|
+
function safeEqualHex(a, b) {
|
|
163
|
+
if (a.length !== b.length) return false;
|
|
164
|
+
try {
|
|
165
|
+
return (0, import_node_crypto.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function verify(payload, header, secret, options = {}) {
|
|
171
|
+
const tolerance = options.toleranceSec ?? 300;
|
|
172
|
+
const parsed = parseHeader(header || "");
|
|
173
|
+
if (!parsed) return false;
|
|
174
|
+
const body = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
175
|
+
const expected = expectedSignature(parsed.t, body, secret);
|
|
176
|
+
if (!safeEqualHex(parsed.v1, expected)) return false;
|
|
177
|
+
if (tolerance > 0) {
|
|
178
|
+
const ts = Number(parsed.t);
|
|
179
|
+
if (!Number.isFinite(ts)) return false;
|
|
180
|
+
const ageSec = Math.abs(Date.now() / 1e3 - ts);
|
|
181
|
+
if (ageSec > tolerance) return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
function constructEvent(payload, header, secret, options = {}) {
|
|
186
|
+
if (!verify(payload, header, secret, options)) {
|
|
187
|
+
throw new WebhookVerificationError("Webhook signature verification failed");
|
|
188
|
+
}
|
|
189
|
+
const body = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
190
|
+
return JSON.parse(body);
|
|
191
|
+
}
|
|
192
|
+
var webhooks = { verify, constructEvent };
|
|
193
|
+
|
|
194
|
+
// src/index.ts
|
|
195
|
+
var DEFAULT_BASE_URL = "https://api.simplr.sh";
|
|
196
|
+
var Simplr = class {
|
|
197
|
+
cfg;
|
|
198
|
+
orders;
|
|
199
|
+
phone;
|
|
200
|
+
edge;
|
|
201
|
+
/** Webhook signature helpers (no network). */
|
|
202
|
+
webhooks = webhooks_exports;
|
|
203
|
+
constructor(options) {
|
|
204
|
+
if (!options?.apiKey) throw new Error("Simplr: `apiKey` is required");
|
|
205
|
+
this.cfg = {
|
|
206
|
+
apiKey: options.apiKey,
|
|
207
|
+
baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
208
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
209
|
+
fetchImpl: options.fetch ?? globalThis.fetch
|
|
210
|
+
};
|
|
211
|
+
if (typeof this.cfg.fetchImpl !== "function") {
|
|
212
|
+
throw new Error("Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options");
|
|
213
|
+
}
|
|
214
|
+
this.orders = new OrdersResource(this.cfg);
|
|
215
|
+
this.phone = new PhoneResource(this.cfg);
|
|
216
|
+
this.edge = new EdgeResource(this.cfg);
|
|
217
|
+
}
|
|
218
|
+
/** Run an identity/fraud check. Provide any of email, phone, device, behavior. */
|
|
219
|
+
check(input) {
|
|
220
|
+
return apiRequest(this.cfg, "POST", "/v1/check", input);
|
|
221
|
+
}
|
|
222
|
+
/** Run up to 100 checks at once. */
|
|
223
|
+
checkBulk(items) {
|
|
224
|
+
return apiRequest(this.cfg, "POST", "/v1/check/bulk", { items });
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
var src_default = Simplr;
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
Simplr,
|
|
231
|
+
SimplrError,
|
|
232
|
+
WebhookVerificationError,
|
|
233
|
+
constructWebhookEvent,
|
|
234
|
+
verifyWebhook
|
|
235
|
+
});
|
|
236
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/resources.ts","../src/webhooks.ts"],"sourcesContent":["import { apiRequest, type HttpConfig } from \"./http.js\";\nimport { EdgeResource, OrdersResource, PhoneResource } from \"./resources.js\";\nimport * as webhooks from \"./webhooks.js\";\nimport type { BulkResult, CheckInput, CheckResult, SimplrOptions } from \"./types.js\";\n\nexport { SimplrError, WebhookVerificationError } from \"./errors.js\";\nexport * from \"./types.js\";\nexport { verify as verifyWebhook, constructEvent as constructWebhookEvent } from \"./webhooks.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/**\n * Simplr server-side client.\n *\n * ```ts\n * import { Simplr } from \"@simplr-ai/node\";\n * const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });\n * const result = await simplr.check({ email: \"user@example.com\", event_type: \"signup\" });\n * ```\n */\nexport class Simplr {\n private readonly cfg: HttpConfig;\n\n readonly orders: OrdersResource;\n readonly phone: PhoneResource;\n readonly edge: EdgeResource;\n /** Webhook signature helpers (no network). */\n readonly webhooks = webhooks;\n\n constructor(options: SimplrOptions) {\n if (!options?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n this.cfg = {\n apiKey: options.apiKey,\n baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\"),\n timeoutMs: options.timeoutMs ?? 15000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n };\n if (typeof this.cfg.fetchImpl !== \"function\") {\n throw new Error(\"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\");\n }\n this.orders = new OrdersResource(this.cfg);\n this.phone = new PhoneResource(this.cfg);\n this.edge = new EdgeResource(this.cfg);\n }\n\n /** Run an identity/fraud check. Provide any of email, phone, device, behavior. */\n check(input: CheckInput): Promise<CheckResult> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check\", input);\n }\n\n /** Run up to 100 checks at once. */\n checkBulk(items: CheckInput[]): Promise<BulkResult<CheckResult>> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check/bulk\", { items });\n }\n}\n\nexport default Simplr;\n","/** Thrown when the Simplr API returns a non-2xx response. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/** Thrown when a webhook signature fails verification. */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n","import { SimplrError } from \"./errors.js\";\n\nexport interface HttpConfig {\n apiKey: string;\n baseUrl: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n}\n\n/**\n * Internal request helper. Sends X-API-Key, applies a timeout, and unwraps the\n * API's `{ success, message, content }` envelope (returning `content`).\n */\nexport async function apiRequest<T>(\n cfg: HttpConfig,\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\",\n path: string,\n body?: unknown,\n): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);\n try {\n const res = await cfg.fetchImpl(`${cfg.baseUrl}${path}`, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": cfg.apiKey,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n\n // Unwrap the standard envelope when present.\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new SimplrError(`Request to ${path} timed out after ${cfg.timeoutMs}ms`, 0, null);\n }\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n}\n","import { apiRequest, type HttpConfig } from \"./http.js\";\nimport type {\n BulkResult,\n EdgeLogEntry,\n OrderInput,\n OrderResult,\n PhoneReportInput,\n} from \"./types.js\";\n\n/** Order fraud scoring. */\nexport class OrdersResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Submit a single order for fraud scoring. */\n submit(order: OrderInput): Promise<OrderResult> {\n return apiRequest(this.cfg, \"POST\", \"/v1/orders\", order);\n }\n\n /** Submit up to 100 orders at once. */\n submitBulk(orders: OrderInput[]): Promise<BulkResult<OrderResult>> {\n return apiRequest(this.cfg, \"POST\", \"/v1/orders/bulk\", { orders });\n }\n}\n\n/** Phone intelligence + outcome reporting. */\nexport class PhoneResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Report the real-world outcome for a phone number to improve scoring. */\n report(input: PhoneReportInput): Promise<{ success: boolean }> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check/phone/report\", input);\n }\n\n /** Fetch stored risk intelligence for a phone number. */\n intelligence(phone: string): Promise<Record<string, unknown>> {\n return apiRequest(this.cfg, \"GET\", `/v1/check/phone/intelligence/${encodeURIComponent(phone)}`);\n }\n}\n\n/** Edge device registration + log ingestion. */\nexport class EdgeResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Register an edge device. */\n registerDevice(input: { device_id: string; name?: string; firmware?: string; [k: string]: unknown }) {\n return apiRequest(this.cfg, \"POST\", \"/v1/edge/devices/register\", input);\n }\n\n /** Report a device heartbeat with health metrics. */\n heartbeat(deviceId: string, metrics: Record<string, unknown>) {\n return apiRequest(\n this.cfg,\n \"POST\",\n `/v1/edge/devices/${encodeURIComponent(deviceId)}/heartbeat`,\n metrics,\n );\n }\n\n /** Batch-ingest structured logs for a device. */\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]) {\n return apiRequest(this.cfg, \"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs });\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { WebhookVerificationError } from \"./errors.js\";\n\nexport interface VerifyOptions {\n /** Reject signatures whose timestamp is older than this many seconds (default 300). 0 disables. */\n toleranceSec?: number;\n}\n\nfunction parseHeader(header: string): { t: string; v1: string } | null {\n // Format: \"t=<unix-seconds>,v1=<hex-hmac>\"\n const parts = header.split(\",\").map((p) => p.trim());\n let t = \"\";\n let v1 = \"\";\n for (const part of parts) {\n const [k, v] = part.split(\"=\");\n if (k === \"t\") t = v;\n if (k === \"v1\") v1 = v;\n }\n return t && v1 ? { t, v1 } : null;\n}\n\nfunction expectedSignature(timestamp: string, payload: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${timestamp}.${payload}`).digest(\"hex\");\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n try {\n return timingSafeEqual(Buffer.from(a, \"hex\"), Buffer.from(b, \"hex\"));\n } catch {\n return false;\n }\n}\n\n/**\n * Verify a Simplr webhook signature.\n *\n * @param payload The RAW request body string (do not re-serialize parsed JSON).\n * @param header The `X-Simplr-Signature` header value (`t=…,v1=…`).\n * @param secret The webhook's signing secret.\n * @returns true if the signature is valid and within the tolerance window.\n */\nexport function verify(\n payload: string | Buffer,\n header: string,\n secret: string,\n options: VerifyOptions = {},\n): boolean {\n const tolerance = options.toleranceSec ?? 300;\n const parsed = parseHeader(header || \"\");\n if (!parsed) return false;\n\n const body = typeof payload === \"string\" ? payload : payload.toString(\"utf8\");\n const expected = expectedSignature(parsed.t, body, secret);\n if (!safeEqualHex(parsed.v1, expected)) return false;\n\n if (tolerance > 0) {\n const ts = Number(parsed.t);\n if (!Number.isFinite(ts)) return false;\n const ageSec = Math.abs(Date.now() / 1000 - ts);\n if (ageSec > tolerance) return false;\n }\n return true;\n}\n\n/**\n * Verify the signature and return the parsed event object.\n * Throws {@link WebhookVerificationError} if verification fails.\n */\nexport function constructEvent<T = { event: string; data: unknown }>(\n payload: string | Buffer,\n header: string,\n secret: string,\n options: VerifyOptions = {},\n): T {\n if (!verify(payload, header, secret, options)) {\n throw new WebhookVerificationError(\"Webhook signature verification failed\");\n }\n const body = typeof payload === \"string\" ? payload : payload.toString(\"utf8\");\n return JSON.parse(body) as T;\n}\n\nexport const webhooks = { verify, constructEvent };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAClD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;;ACNA,eAAsB,WACpB,KACA,QACA,MACA,MACY;AACZ,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,IAAI,SAAS;AAChE,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,UAAU,GAAG,IAAI,OAAO,GAAG,IAAI,IAAI;AAAA,MACvD;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa,IAAI;AAAA,MACnB;AAAA,MACA,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,MAClD,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI;AACJ,QAAI;AACF,eAAS,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,IACrC,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UACH,WAAW,OAAO,WAAW,OAAO,UAAW,oBAAoB,IAAI,MAAM;AAChF,YAAM,IAAI,YAAY,SAAS,IAAI,QAAQ,MAAM;AAAA,IACnD;AAGA,WAAQ,UAAU,OAAO,WAAW,YAAY,aAAa,SACzD,OAAO,UACP;AAAA,EACN,SAAS,KAAK;AACZ,QAAI,eAAe,YAAa,OAAM;AACtC,QAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,YAAM,IAAI,YAAY,cAAc,IAAI,oBAAoB,IAAI,SAAS,MAAM,GAAG,IAAI;AAAA,IACxF;AACA,UAAM,IAAI,YAAY,eAAe,QAAQ,IAAI,UAAU,iBAAiB,GAAG,IAAI;AAAA,EACrF,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACF;;;ACjDO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAA6B,KAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA;AAAA,EAG7B,OAAO,OAAyC;AAC9C,WAAO,WAAW,KAAK,KAAK,QAAQ,cAAc,KAAK;AAAA,EACzD;AAAA;AAAA,EAGA,WAAW,QAAwD;AACjE,WAAO,WAAW,KAAK,KAAK,QAAQ,mBAAmB,EAAE,OAAO,CAAC;AAAA,EACnE;AACF;AAGO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAA6B,KAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA;AAAA,EAG7B,OAAO,OAAwD;AAC7D,WAAO,WAAW,KAAK,KAAK,QAAQ,0BAA0B,KAAK;AAAA,EACrE;AAAA;AAAA,EAGA,aAAa,OAAiD;AAC5D,WAAO,WAAW,KAAK,KAAK,OAAO,gCAAgC,mBAAmB,KAAK,CAAC,EAAE;AAAA,EAChG;AACF;AAGO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAA6B,KAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA;AAAA,EAG7B,eAAe,OAAsF;AACnG,WAAO,WAAW,KAAK,KAAK,QAAQ,6BAA6B,KAAK;AAAA,EACxE;AAAA;AAAA,EAGA,UAAU,UAAkB,SAAkC;AAC5D,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,oBAAoB,mBAAmB,QAAQ,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,WAAW,UAAkB,MAAsB;AACjD,WAAO,WAAW,KAAK,KAAK,QAAQ,iBAAiB,EAAE,WAAW,UAAU,KAAK,CAAC;AAAA,EACpF;AACF;;;AC9DA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA4C;AAQ5C,SAAS,YAAY,QAAkD;AAErE,QAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACnD,MAAI,IAAI;AACR,MAAI,KAAK;AACT,aAAW,QAAQ,OAAO;AACxB,UAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,GAAG;AAC7B,QAAI,MAAM,IAAK,KAAI;AACnB,QAAI,MAAM,KAAM,MAAK;AAAA,EACvB;AACA,SAAO,KAAK,KAAK,EAAE,GAAG,GAAG,IAAI;AAC/B;AAEA,SAAS,kBAAkB,WAAmB,SAAiB,QAAwB;AACrF,aAAO,+BAAW,UAAU,MAAM,EAAE,OAAO,GAAG,SAAS,IAAI,OAAO,EAAE,EAAE,OAAO,KAAK;AACpF;AAEA,SAAS,aAAa,GAAW,GAAoB;AACnD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI;AACF,eAAO,oCAAgB,OAAO,KAAK,GAAG,KAAK,GAAG,OAAO,KAAK,GAAG,KAAK,CAAC;AAAA,EACrE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUO,SAAS,OACd,SACA,QACA,QACA,UAAyB,CAAC,GACjB;AACT,QAAM,YAAY,QAAQ,gBAAgB;AAC1C,QAAM,SAAS,YAAY,UAAU,EAAE;AACvC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,OAAO,OAAO,YAAY,WAAW,UAAU,QAAQ,SAAS,MAAM;AAC5E,QAAM,WAAW,kBAAkB,OAAO,GAAG,MAAM,MAAM;AACzD,MAAI,CAAC,aAAa,OAAO,IAAI,QAAQ,EAAG,QAAO;AAE/C,MAAI,YAAY,GAAG;AACjB,UAAM,KAAK,OAAO,OAAO,CAAC;AAC1B,QAAI,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACjC,UAAM,SAAS,KAAK,IAAI,KAAK,IAAI,IAAI,MAAO,EAAE;AAC9C,QAAI,SAAS,UAAW,QAAO;AAAA,EACjC;AACA,SAAO;AACT;AAMO,SAAS,eACd,SACA,QACA,QACA,UAAyB,CAAC,GACvB;AACH,MAAI,CAAC,OAAO,SAAS,QAAQ,QAAQ,OAAO,GAAG;AAC7C,UAAM,IAAI,yBAAyB,uCAAuC;AAAA,EAC5E;AACA,QAAM,OAAO,OAAO,YAAY,WAAW,UAAU,QAAQ,SAAS,MAAM;AAC5E,SAAO,KAAK,MAAM,IAAI;AACxB;AAEO,IAAM,WAAW,EAAE,QAAQ,eAAe;;;AJzEjD,IAAM,mBAAmB;AAWlB,IAAM,SAAN,MAAa;AAAA,EACD;AAAA,EAER;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,WAAW;AAAA,EAEpB,YAAY,SAAwB;AAClC,QAAI,CAAC,SAAS,OAAQ,OAAM,IAAI,MAAM,8BAA8B;AACpE,SAAK,MAAM;AAAA,MACT,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AAAA,MACjE,WAAW,QAAQ,aAAa;AAAA,MAChC,WAAW,QAAQ,SAAS,WAAW;AAAA,IACzC;AACA,QAAI,OAAO,KAAK,IAAI,cAAc,YAAY;AAC5C,YAAM,IAAI,MAAM,kFAA6E;AAAA,IAC/F;AACA,SAAK,SAAS,IAAI,eAAe,KAAK,GAAG;AACzC,SAAK,QAAQ,IAAI,cAAc,KAAK,GAAG;AACvC,SAAK,OAAO,IAAI,aAAa,KAAK,GAAG;AAAA,EACvC;AAAA;AAAA,EAGA,MAAM,OAAyC;AAC7C,WAAO,WAAW,KAAK,KAAK,QAAQ,aAAa,KAAK;AAAA,EACxD;AAAA;AAAA,EAGA,UAAU,OAAuD;AAC/D,WAAO,WAAW,KAAK,KAAK,QAAQ,kBAAkB,EAAE,MAAM,CAAC;AAAA,EACjE;AACF;AAEA,IAAO,cAAQ;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
interface HttpConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
fetchImpl: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type RiskLevel = "low" | "medium" | "high" | "critical";
|
|
9
|
+
interface SimplrOptions {
|
|
10
|
+
/** Secret API key (sk_live_… / sk_test_…). Keep this server-side only. */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** API base URL. Defaults to https://api.simplr.sh. */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** Per-request timeout in ms (default 15000). */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
/** Override the fetch implementation (defaults to global fetch). */
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
}
|
|
19
|
+
interface CheckInput {
|
|
20
|
+
email?: string;
|
|
21
|
+
phone?: string;
|
|
22
|
+
device?: Record<string, unknown>;
|
|
23
|
+
behavior?: Record<string, unknown>;
|
|
24
|
+
event_type?: string;
|
|
25
|
+
event_id?: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
interface CheckResult {
|
|
29
|
+
type?: string;
|
|
30
|
+
risk_score: number;
|
|
31
|
+
risk_level: RiskLevel;
|
|
32
|
+
signals?: Record<string, unknown>;
|
|
33
|
+
email?: string;
|
|
34
|
+
phone_number?: string;
|
|
35
|
+
fingerprint_hash?: string;
|
|
36
|
+
checked_at?: string;
|
|
37
|
+
is_sandbox?: boolean;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface BulkResult<T = unknown> {
|
|
41
|
+
total: number;
|
|
42
|
+
successful: number;
|
|
43
|
+
failed: number;
|
|
44
|
+
results: T[];
|
|
45
|
+
}
|
|
46
|
+
interface OrderInput {
|
|
47
|
+
order_id: string;
|
|
48
|
+
external_id?: string;
|
|
49
|
+
amount: number;
|
|
50
|
+
currency: string;
|
|
51
|
+
fingerprint_hash?: string;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
interface OrderResult {
|
|
55
|
+
order_id: string;
|
|
56
|
+
risk_score: number;
|
|
57
|
+
risk_level: RiskLevel;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
type PhoneOutcome = "fraud" | "sim_swap_fraud" | "account_takeover" | "spam" | "bot" | "legitimate";
|
|
61
|
+
interface PhoneReportInput {
|
|
62
|
+
phone: string;
|
|
63
|
+
outcome: PhoneOutcome;
|
|
64
|
+
check_id?: string;
|
|
65
|
+
confidence?: number;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
interface EdgeLogEntry {
|
|
69
|
+
category: "transaction" | "health" | "security" | "audit" | "network" | string;
|
|
70
|
+
level?: "debug" | "info" | "warn" | "error" | string;
|
|
71
|
+
message: string;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Order fraud scoring. */
|
|
76
|
+
declare class OrdersResource {
|
|
77
|
+
private readonly cfg;
|
|
78
|
+
constructor(cfg: HttpConfig);
|
|
79
|
+
/** Submit a single order for fraud scoring. */
|
|
80
|
+
submit(order: OrderInput): Promise<OrderResult>;
|
|
81
|
+
/** Submit up to 100 orders at once. */
|
|
82
|
+
submitBulk(orders: OrderInput[]): Promise<BulkResult<OrderResult>>;
|
|
83
|
+
}
|
|
84
|
+
/** Phone intelligence + outcome reporting. */
|
|
85
|
+
declare class PhoneResource {
|
|
86
|
+
private readonly cfg;
|
|
87
|
+
constructor(cfg: HttpConfig);
|
|
88
|
+
/** Report the real-world outcome for a phone number to improve scoring. */
|
|
89
|
+
report(input: PhoneReportInput): Promise<{
|
|
90
|
+
success: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
/** Fetch stored risk intelligence for a phone number. */
|
|
93
|
+
intelligence(phone: string): Promise<Record<string, unknown>>;
|
|
94
|
+
}
|
|
95
|
+
/** Edge device registration + log ingestion. */
|
|
96
|
+
declare class EdgeResource {
|
|
97
|
+
private readonly cfg;
|
|
98
|
+
constructor(cfg: HttpConfig);
|
|
99
|
+
/** Register an edge device. */
|
|
100
|
+
registerDevice(input: {
|
|
101
|
+
device_id: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
firmware?: string;
|
|
104
|
+
[k: string]: unknown;
|
|
105
|
+
}): Promise<unknown>;
|
|
106
|
+
/** Report a device heartbeat with health metrics. */
|
|
107
|
+
heartbeat(deviceId: string, metrics: Record<string, unknown>): Promise<unknown>;
|
|
108
|
+
/** Batch-ingest structured logs for a device. */
|
|
109
|
+
ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface VerifyOptions {
|
|
113
|
+
/** Reject signatures whose timestamp is older than this many seconds (default 300). 0 disables. */
|
|
114
|
+
toleranceSec?: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Verify a Simplr webhook signature.
|
|
118
|
+
*
|
|
119
|
+
* @param payload The RAW request body string (do not re-serialize parsed JSON).
|
|
120
|
+
* @param header The `X-Simplr-Signature` header value (`t=…,v1=…`).
|
|
121
|
+
* @param secret The webhook's signing secret.
|
|
122
|
+
* @returns true if the signature is valid and within the tolerance window.
|
|
123
|
+
*/
|
|
124
|
+
declare function verify(payload: string | Buffer, header: string, secret: string, options?: VerifyOptions): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Verify the signature and return the parsed event object.
|
|
127
|
+
* Throws {@link WebhookVerificationError} if verification fails.
|
|
128
|
+
*/
|
|
129
|
+
declare function constructEvent<T = {
|
|
130
|
+
event: string;
|
|
131
|
+
data: unknown;
|
|
132
|
+
}>(payload: string | Buffer, header: string, secret: string, options?: VerifyOptions): T;
|
|
133
|
+
declare const webhooks: {
|
|
134
|
+
verify: typeof verify;
|
|
135
|
+
constructEvent: typeof constructEvent;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type webhooks$1_VerifyOptions = VerifyOptions;
|
|
139
|
+
declare const webhooks$1_constructEvent: typeof constructEvent;
|
|
140
|
+
declare const webhooks$1_verify: typeof verify;
|
|
141
|
+
declare const webhooks$1_webhooks: typeof webhooks;
|
|
142
|
+
declare namespace webhooks$1 {
|
|
143
|
+
export { type webhooks$1_VerifyOptions as VerifyOptions, webhooks$1_constructEvent as constructEvent, webhooks$1_verify as verify, webhooks$1_webhooks as webhooks };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Thrown when the Simplr API returns a non-2xx response. */
|
|
147
|
+
declare class SimplrError extends Error {
|
|
148
|
+
readonly status: number;
|
|
149
|
+
readonly body: unknown;
|
|
150
|
+
constructor(message: string, status: number, body: unknown);
|
|
151
|
+
}
|
|
152
|
+
/** Thrown when a webhook signature fails verification. */
|
|
153
|
+
declare class WebhookVerificationError extends Error {
|
|
154
|
+
constructor(message: string);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Simplr server-side client.
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* import { Simplr } from "@simplr-ai/node";
|
|
162
|
+
* const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });
|
|
163
|
+
* const result = await simplr.check({ email: "user@example.com", event_type: "signup" });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare class Simplr {
|
|
167
|
+
private readonly cfg;
|
|
168
|
+
readonly orders: OrdersResource;
|
|
169
|
+
readonly phone: PhoneResource;
|
|
170
|
+
readonly edge: EdgeResource;
|
|
171
|
+
/** Webhook signature helpers (no network). */
|
|
172
|
+
readonly webhooks: typeof webhooks$1;
|
|
173
|
+
constructor(options: SimplrOptions);
|
|
174
|
+
/** Run an identity/fraud check. Provide any of email, phone, device, behavior. */
|
|
175
|
+
check(input: CheckInput): Promise<CheckResult>;
|
|
176
|
+
/** Run up to 100 checks at once. */
|
|
177
|
+
checkBulk(items: CheckInput[]): Promise<BulkResult<CheckResult>>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { type BulkResult, type CheckInput, type CheckResult, type EdgeLogEntry, type OrderInput, type OrderResult, type PhoneOutcome, type PhoneReportInput, type RiskLevel, Simplr, SimplrError, type SimplrOptions, WebhookVerificationError, constructEvent as constructWebhookEvent, Simplr as default, verify as verifyWebhook };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
interface HttpConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
fetchImpl: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type RiskLevel = "low" | "medium" | "high" | "critical";
|
|
9
|
+
interface SimplrOptions {
|
|
10
|
+
/** Secret API key (sk_live_… / sk_test_…). Keep this server-side only. */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** API base URL. Defaults to https://api.simplr.sh. */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** Per-request timeout in ms (default 15000). */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
/** Override the fetch implementation (defaults to global fetch). */
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
}
|
|
19
|
+
interface CheckInput {
|
|
20
|
+
email?: string;
|
|
21
|
+
phone?: string;
|
|
22
|
+
device?: Record<string, unknown>;
|
|
23
|
+
behavior?: Record<string, unknown>;
|
|
24
|
+
event_type?: string;
|
|
25
|
+
event_id?: string;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
interface CheckResult {
|
|
29
|
+
type?: string;
|
|
30
|
+
risk_score: number;
|
|
31
|
+
risk_level: RiskLevel;
|
|
32
|
+
signals?: Record<string, unknown>;
|
|
33
|
+
email?: string;
|
|
34
|
+
phone_number?: string;
|
|
35
|
+
fingerprint_hash?: string;
|
|
36
|
+
checked_at?: string;
|
|
37
|
+
is_sandbox?: boolean;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface BulkResult<T = unknown> {
|
|
41
|
+
total: number;
|
|
42
|
+
successful: number;
|
|
43
|
+
failed: number;
|
|
44
|
+
results: T[];
|
|
45
|
+
}
|
|
46
|
+
interface OrderInput {
|
|
47
|
+
order_id: string;
|
|
48
|
+
external_id?: string;
|
|
49
|
+
amount: number;
|
|
50
|
+
currency: string;
|
|
51
|
+
fingerprint_hash?: string;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
interface OrderResult {
|
|
55
|
+
order_id: string;
|
|
56
|
+
risk_score: number;
|
|
57
|
+
risk_level: RiskLevel;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
type PhoneOutcome = "fraud" | "sim_swap_fraud" | "account_takeover" | "spam" | "bot" | "legitimate";
|
|
61
|
+
interface PhoneReportInput {
|
|
62
|
+
phone: string;
|
|
63
|
+
outcome: PhoneOutcome;
|
|
64
|
+
check_id?: string;
|
|
65
|
+
confidence?: number;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
interface EdgeLogEntry {
|
|
69
|
+
category: "transaction" | "health" | "security" | "audit" | "network" | string;
|
|
70
|
+
level?: "debug" | "info" | "warn" | "error" | string;
|
|
71
|
+
message: string;
|
|
72
|
+
[key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Order fraud scoring. */
|
|
76
|
+
declare class OrdersResource {
|
|
77
|
+
private readonly cfg;
|
|
78
|
+
constructor(cfg: HttpConfig);
|
|
79
|
+
/** Submit a single order for fraud scoring. */
|
|
80
|
+
submit(order: OrderInput): Promise<OrderResult>;
|
|
81
|
+
/** Submit up to 100 orders at once. */
|
|
82
|
+
submitBulk(orders: OrderInput[]): Promise<BulkResult<OrderResult>>;
|
|
83
|
+
}
|
|
84
|
+
/** Phone intelligence + outcome reporting. */
|
|
85
|
+
declare class PhoneResource {
|
|
86
|
+
private readonly cfg;
|
|
87
|
+
constructor(cfg: HttpConfig);
|
|
88
|
+
/** Report the real-world outcome for a phone number to improve scoring. */
|
|
89
|
+
report(input: PhoneReportInput): Promise<{
|
|
90
|
+
success: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
/** Fetch stored risk intelligence for a phone number. */
|
|
93
|
+
intelligence(phone: string): Promise<Record<string, unknown>>;
|
|
94
|
+
}
|
|
95
|
+
/** Edge device registration + log ingestion. */
|
|
96
|
+
declare class EdgeResource {
|
|
97
|
+
private readonly cfg;
|
|
98
|
+
constructor(cfg: HttpConfig);
|
|
99
|
+
/** Register an edge device. */
|
|
100
|
+
registerDevice(input: {
|
|
101
|
+
device_id: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
firmware?: string;
|
|
104
|
+
[k: string]: unknown;
|
|
105
|
+
}): Promise<unknown>;
|
|
106
|
+
/** Report a device heartbeat with health metrics. */
|
|
107
|
+
heartbeat(deviceId: string, metrics: Record<string, unknown>): Promise<unknown>;
|
|
108
|
+
/** Batch-ingest structured logs for a device. */
|
|
109
|
+
ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface VerifyOptions {
|
|
113
|
+
/** Reject signatures whose timestamp is older than this many seconds (default 300). 0 disables. */
|
|
114
|
+
toleranceSec?: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Verify a Simplr webhook signature.
|
|
118
|
+
*
|
|
119
|
+
* @param payload The RAW request body string (do not re-serialize parsed JSON).
|
|
120
|
+
* @param header The `X-Simplr-Signature` header value (`t=…,v1=…`).
|
|
121
|
+
* @param secret The webhook's signing secret.
|
|
122
|
+
* @returns true if the signature is valid and within the tolerance window.
|
|
123
|
+
*/
|
|
124
|
+
declare function verify(payload: string | Buffer, header: string, secret: string, options?: VerifyOptions): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Verify the signature and return the parsed event object.
|
|
127
|
+
* Throws {@link WebhookVerificationError} if verification fails.
|
|
128
|
+
*/
|
|
129
|
+
declare function constructEvent<T = {
|
|
130
|
+
event: string;
|
|
131
|
+
data: unknown;
|
|
132
|
+
}>(payload: string | Buffer, header: string, secret: string, options?: VerifyOptions): T;
|
|
133
|
+
declare const webhooks: {
|
|
134
|
+
verify: typeof verify;
|
|
135
|
+
constructEvent: typeof constructEvent;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type webhooks$1_VerifyOptions = VerifyOptions;
|
|
139
|
+
declare const webhooks$1_constructEvent: typeof constructEvent;
|
|
140
|
+
declare const webhooks$1_verify: typeof verify;
|
|
141
|
+
declare const webhooks$1_webhooks: typeof webhooks;
|
|
142
|
+
declare namespace webhooks$1 {
|
|
143
|
+
export { type webhooks$1_VerifyOptions as VerifyOptions, webhooks$1_constructEvent as constructEvent, webhooks$1_verify as verify, webhooks$1_webhooks as webhooks };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Thrown when the Simplr API returns a non-2xx response. */
|
|
147
|
+
declare class SimplrError extends Error {
|
|
148
|
+
readonly status: number;
|
|
149
|
+
readonly body: unknown;
|
|
150
|
+
constructor(message: string, status: number, body: unknown);
|
|
151
|
+
}
|
|
152
|
+
/** Thrown when a webhook signature fails verification. */
|
|
153
|
+
declare class WebhookVerificationError extends Error {
|
|
154
|
+
constructor(message: string);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Simplr server-side client.
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* import { Simplr } from "@simplr-ai/node";
|
|
162
|
+
* const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });
|
|
163
|
+
* const result = await simplr.check({ email: "user@example.com", event_type: "signup" });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare class Simplr {
|
|
167
|
+
private readonly cfg;
|
|
168
|
+
readonly orders: OrdersResource;
|
|
169
|
+
readonly phone: PhoneResource;
|
|
170
|
+
readonly edge: EdgeResource;
|
|
171
|
+
/** Webhook signature helpers (no network). */
|
|
172
|
+
readonly webhooks: typeof webhooks$1;
|
|
173
|
+
constructor(options: SimplrOptions);
|
|
174
|
+
/** Run an identity/fraud check. Provide any of email, phone, device, behavior. */
|
|
175
|
+
check(input: CheckInput): Promise<CheckResult>;
|
|
176
|
+
/** Run up to 100 checks at once. */
|
|
177
|
+
checkBulk(items: CheckInput[]): Promise<BulkResult<CheckResult>>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { type BulkResult, type CheckInput, type CheckResult, type EdgeLogEntry, type OrderInput, type OrderResult, type PhoneOutcome, type PhoneReportInput, type RiskLevel, Simplr, SimplrError, type SimplrOptions, WebhookVerificationError, constructEvent as constructWebhookEvent, Simplr as default, verify as verifyWebhook };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var SimplrError = class extends Error {
|
|
9
|
+
status;
|
|
10
|
+
body;
|
|
11
|
+
constructor(message, status, body) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "SimplrError";
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.body = body;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var WebhookVerificationError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "WebhookVerificationError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/http.ts
|
|
26
|
+
async function apiRequest(cfg, method, path, body) {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
29
|
+
try {
|
|
30
|
+
const res = await cfg.fetchImpl(`${cfg.baseUrl}${path}`, {
|
|
31
|
+
method,
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"X-API-Key": cfg.apiKey
|
|
35
|
+
},
|
|
36
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
37
|
+
signal: controller.signal
|
|
38
|
+
});
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
43
|
+
} catch {
|
|
44
|
+
parsed = text;
|
|
45
|
+
}
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const message = parsed && (parsed.message || parsed.error) || `Simplr API error ${res.status}`;
|
|
48
|
+
throw new SimplrError(message, res.status, parsed);
|
|
49
|
+
}
|
|
50
|
+
return parsed && typeof parsed === "object" && "content" in parsed ? parsed.content : parsed;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof SimplrError) throw err;
|
|
53
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
54
|
+
throw new SimplrError(`Request to ${path} timed out after ${cfg.timeoutMs}ms`, 0, null);
|
|
55
|
+
}
|
|
56
|
+
throw new SimplrError(err instanceof Error ? err.message : "Network error", 0, null);
|
|
57
|
+
} finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/resources.ts
|
|
63
|
+
var OrdersResource = class {
|
|
64
|
+
constructor(cfg) {
|
|
65
|
+
this.cfg = cfg;
|
|
66
|
+
}
|
|
67
|
+
cfg;
|
|
68
|
+
/** Submit a single order for fraud scoring. */
|
|
69
|
+
submit(order) {
|
|
70
|
+
return apiRequest(this.cfg, "POST", "/v1/orders", order);
|
|
71
|
+
}
|
|
72
|
+
/** Submit up to 100 orders at once. */
|
|
73
|
+
submitBulk(orders) {
|
|
74
|
+
return apiRequest(this.cfg, "POST", "/v1/orders/bulk", { orders });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var PhoneResource = class {
|
|
78
|
+
constructor(cfg) {
|
|
79
|
+
this.cfg = cfg;
|
|
80
|
+
}
|
|
81
|
+
cfg;
|
|
82
|
+
/** Report the real-world outcome for a phone number to improve scoring. */
|
|
83
|
+
report(input) {
|
|
84
|
+
return apiRequest(this.cfg, "POST", "/v1/check/phone/report", input);
|
|
85
|
+
}
|
|
86
|
+
/** Fetch stored risk intelligence for a phone number. */
|
|
87
|
+
intelligence(phone) {
|
|
88
|
+
return apiRequest(this.cfg, "GET", `/v1/check/phone/intelligence/${encodeURIComponent(phone)}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var EdgeResource = class {
|
|
92
|
+
constructor(cfg) {
|
|
93
|
+
this.cfg = cfg;
|
|
94
|
+
}
|
|
95
|
+
cfg;
|
|
96
|
+
/** Register an edge device. */
|
|
97
|
+
registerDevice(input) {
|
|
98
|
+
return apiRequest(this.cfg, "POST", "/v1/edge/devices/register", input);
|
|
99
|
+
}
|
|
100
|
+
/** Report a device heartbeat with health metrics. */
|
|
101
|
+
heartbeat(deviceId, metrics) {
|
|
102
|
+
return apiRequest(
|
|
103
|
+
this.cfg,
|
|
104
|
+
"POST",
|
|
105
|
+
`/v1/edge/devices/${encodeURIComponent(deviceId)}/heartbeat`,
|
|
106
|
+
metrics
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
/** Batch-ingest structured logs for a device. */
|
|
110
|
+
ingestLogs(deviceId, logs) {
|
|
111
|
+
return apiRequest(this.cfg, "POST", "/v1/edge/logs", { device_id: deviceId, logs });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/webhooks.ts
|
|
116
|
+
var webhooks_exports = {};
|
|
117
|
+
__export(webhooks_exports, {
|
|
118
|
+
constructEvent: () => constructEvent,
|
|
119
|
+
verify: () => verify,
|
|
120
|
+
webhooks: () => webhooks
|
|
121
|
+
});
|
|
122
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
123
|
+
function parseHeader(header) {
|
|
124
|
+
const parts = header.split(",").map((p) => p.trim());
|
|
125
|
+
let t = "";
|
|
126
|
+
let v1 = "";
|
|
127
|
+
for (const part of parts) {
|
|
128
|
+
const [k, v] = part.split("=");
|
|
129
|
+
if (k === "t") t = v;
|
|
130
|
+
if (k === "v1") v1 = v;
|
|
131
|
+
}
|
|
132
|
+
return t && v1 ? { t, v1 } : null;
|
|
133
|
+
}
|
|
134
|
+
function expectedSignature(timestamp, payload, secret) {
|
|
135
|
+
return createHmac("sha256", secret).update(`${timestamp}.${payload}`).digest("hex");
|
|
136
|
+
}
|
|
137
|
+
function safeEqualHex(a, b) {
|
|
138
|
+
if (a.length !== b.length) return false;
|
|
139
|
+
try {
|
|
140
|
+
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function verify(payload, header, secret, options = {}) {
|
|
146
|
+
const tolerance = options.toleranceSec ?? 300;
|
|
147
|
+
const parsed = parseHeader(header || "");
|
|
148
|
+
if (!parsed) return false;
|
|
149
|
+
const body = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
150
|
+
const expected = expectedSignature(parsed.t, body, secret);
|
|
151
|
+
if (!safeEqualHex(parsed.v1, expected)) return false;
|
|
152
|
+
if (tolerance > 0) {
|
|
153
|
+
const ts = Number(parsed.t);
|
|
154
|
+
if (!Number.isFinite(ts)) return false;
|
|
155
|
+
const ageSec = Math.abs(Date.now() / 1e3 - ts);
|
|
156
|
+
if (ageSec > tolerance) return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function constructEvent(payload, header, secret, options = {}) {
|
|
161
|
+
if (!verify(payload, header, secret, options)) {
|
|
162
|
+
throw new WebhookVerificationError("Webhook signature verification failed");
|
|
163
|
+
}
|
|
164
|
+
const body = typeof payload === "string" ? payload : payload.toString("utf8");
|
|
165
|
+
return JSON.parse(body);
|
|
166
|
+
}
|
|
167
|
+
var webhooks = { verify, constructEvent };
|
|
168
|
+
|
|
169
|
+
// src/index.ts
|
|
170
|
+
var DEFAULT_BASE_URL = "https://api.simplr.sh";
|
|
171
|
+
var Simplr = class {
|
|
172
|
+
cfg;
|
|
173
|
+
orders;
|
|
174
|
+
phone;
|
|
175
|
+
edge;
|
|
176
|
+
/** Webhook signature helpers (no network). */
|
|
177
|
+
webhooks = webhooks_exports;
|
|
178
|
+
constructor(options) {
|
|
179
|
+
if (!options?.apiKey) throw new Error("Simplr: `apiKey` is required");
|
|
180
|
+
this.cfg = {
|
|
181
|
+
apiKey: options.apiKey,
|
|
182
|
+
baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
183
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
184
|
+
fetchImpl: options.fetch ?? globalThis.fetch
|
|
185
|
+
};
|
|
186
|
+
if (typeof this.cfg.fetchImpl !== "function") {
|
|
187
|
+
throw new Error("Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options");
|
|
188
|
+
}
|
|
189
|
+
this.orders = new OrdersResource(this.cfg);
|
|
190
|
+
this.phone = new PhoneResource(this.cfg);
|
|
191
|
+
this.edge = new EdgeResource(this.cfg);
|
|
192
|
+
}
|
|
193
|
+
/** Run an identity/fraud check. Provide any of email, phone, device, behavior. */
|
|
194
|
+
check(input) {
|
|
195
|
+
return apiRequest(this.cfg, "POST", "/v1/check", input);
|
|
196
|
+
}
|
|
197
|
+
/** Run up to 100 checks at once. */
|
|
198
|
+
checkBulk(items) {
|
|
199
|
+
return apiRequest(this.cfg, "POST", "/v1/check/bulk", { items });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var src_default = Simplr;
|
|
203
|
+
export {
|
|
204
|
+
Simplr,
|
|
205
|
+
SimplrError,
|
|
206
|
+
WebhookVerificationError,
|
|
207
|
+
constructEvent as constructWebhookEvent,
|
|
208
|
+
src_default as default,
|
|
209
|
+
verify as verifyWebhook
|
|
210
|
+
};
|
|
211
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/resources.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["/** Thrown when the Simplr API returns a non-2xx response. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/** Thrown when a webhook signature fails verification. */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n","import { SimplrError } from \"./errors.js\";\n\nexport interface HttpConfig {\n apiKey: string;\n baseUrl: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n}\n\n/**\n * Internal request helper. Sends X-API-Key, applies a timeout, and unwraps the\n * API's `{ success, message, content }` envelope (returning `content`).\n */\nexport async function apiRequest<T>(\n cfg: HttpConfig,\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\",\n path: string,\n body?: unknown,\n): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);\n try {\n const res = await cfg.fetchImpl(`${cfg.baseUrl}${path}`, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": cfg.apiKey,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n\n // Unwrap the standard envelope when present.\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new SimplrError(`Request to ${path} timed out after ${cfg.timeoutMs}ms`, 0, null);\n }\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n}\n","import { apiRequest, type HttpConfig } from \"./http.js\";\nimport type {\n BulkResult,\n EdgeLogEntry,\n OrderInput,\n OrderResult,\n PhoneReportInput,\n} from \"./types.js\";\n\n/** Order fraud scoring. */\nexport class OrdersResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Submit a single order for fraud scoring. */\n submit(order: OrderInput): Promise<OrderResult> {\n return apiRequest(this.cfg, \"POST\", \"/v1/orders\", order);\n }\n\n /** Submit up to 100 orders at once. */\n submitBulk(orders: OrderInput[]): Promise<BulkResult<OrderResult>> {\n return apiRequest(this.cfg, \"POST\", \"/v1/orders/bulk\", { orders });\n }\n}\n\n/** Phone intelligence + outcome reporting. */\nexport class PhoneResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Report the real-world outcome for a phone number to improve scoring. */\n report(input: PhoneReportInput): Promise<{ success: boolean }> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check/phone/report\", input);\n }\n\n /** Fetch stored risk intelligence for a phone number. */\n intelligence(phone: string): Promise<Record<string, unknown>> {\n return apiRequest(this.cfg, \"GET\", `/v1/check/phone/intelligence/${encodeURIComponent(phone)}`);\n }\n}\n\n/** Edge device registration + log ingestion. */\nexport class EdgeResource {\n constructor(private readonly cfg: HttpConfig) {}\n\n /** Register an edge device. */\n registerDevice(input: { device_id: string; name?: string; firmware?: string; [k: string]: unknown }) {\n return apiRequest(this.cfg, \"POST\", \"/v1/edge/devices/register\", input);\n }\n\n /** Report a device heartbeat with health metrics. */\n heartbeat(deviceId: string, metrics: Record<string, unknown>) {\n return apiRequest(\n this.cfg,\n \"POST\",\n `/v1/edge/devices/${encodeURIComponent(deviceId)}/heartbeat`,\n metrics,\n );\n }\n\n /** Batch-ingest structured logs for a device. */\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]) {\n return apiRequest(this.cfg, \"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs });\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { WebhookVerificationError } from \"./errors.js\";\n\nexport interface VerifyOptions {\n /** Reject signatures whose timestamp is older than this many seconds (default 300). 0 disables. */\n toleranceSec?: number;\n}\n\nfunction parseHeader(header: string): { t: string; v1: string } | null {\n // Format: \"t=<unix-seconds>,v1=<hex-hmac>\"\n const parts = header.split(\",\").map((p) => p.trim());\n let t = \"\";\n let v1 = \"\";\n for (const part of parts) {\n const [k, v] = part.split(\"=\");\n if (k === \"t\") t = v;\n if (k === \"v1\") v1 = v;\n }\n return t && v1 ? { t, v1 } : null;\n}\n\nfunction expectedSignature(timestamp: string, payload: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(`${timestamp}.${payload}`).digest(\"hex\");\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n try {\n return timingSafeEqual(Buffer.from(a, \"hex\"), Buffer.from(b, \"hex\"));\n } catch {\n return false;\n }\n}\n\n/**\n * Verify a Simplr webhook signature.\n *\n * @param payload The RAW request body string (do not re-serialize parsed JSON).\n * @param header The `X-Simplr-Signature` header value (`t=…,v1=…`).\n * @param secret The webhook's signing secret.\n * @returns true if the signature is valid and within the tolerance window.\n */\nexport function verify(\n payload: string | Buffer,\n header: string,\n secret: string,\n options: VerifyOptions = {},\n): boolean {\n const tolerance = options.toleranceSec ?? 300;\n const parsed = parseHeader(header || \"\");\n if (!parsed) return false;\n\n const body = typeof payload === \"string\" ? payload : payload.toString(\"utf8\");\n const expected = expectedSignature(parsed.t, body, secret);\n if (!safeEqualHex(parsed.v1, expected)) return false;\n\n if (tolerance > 0) {\n const ts = Number(parsed.t);\n if (!Number.isFinite(ts)) return false;\n const ageSec = Math.abs(Date.now() / 1000 - ts);\n if (ageSec > tolerance) return false;\n }\n return true;\n}\n\n/**\n * Verify the signature and return the parsed event object.\n * Throws {@link WebhookVerificationError} if verification fails.\n */\nexport function constructEvent<T = { event: string; data: unknown }>(\n payload: string | Buffer,\n header: string,\n secret: string,\n options: VerifyOptions = {},\n): T {\n if (!verify(payload, header, secret, options)) {\n throw new WebhookVerificationError(\"Webhook signature verification failed\");\n }\n const body = typeof payload === \"string\" ? payload : payload.toString(\"utf8\");\n return JSON.parse(body) as T;\n}\n\nexport const webhooks = { verify, constructEvent };\n","import { apiRequest, type HttpConfig } from \"./http.js\";\nimport { EdgeResource, OrdersResource, PhoneResource } from \"./resources.js\";\nimport * as webhooks from \"./webhooks.js\";\nimport type { BulkResult, CheckInput, CheckResult, SimplrOptions } from \"./types.js\";\n\nexport { SimplrError, WebhookVerificationError } from \"./errors.js\";\nexport * from \"./types.js\";\nexport { verify as verifyWebhook, constructEvent as constructWebhookEvent } from \"./webhooks.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/**\n * Simplr server-side client.\n *\n * ```ts\n * import { Simplr } from \"@simplr-ai/node\";\n * const simplr = new Simplr({ apiKey: process.env.SIMPLR_API_KEY! });\n * const result = await simplr.check({ email: \"user@example.com\", event_type: \"signup\" });\n * ```\n */\nexport class Simplr {\n private readonly cfg: HttpConfig;\n\n readonly orders: OrdersResource;\n readonly phone: PhoneResource;\n readonly edge: EdgeResource;\n /** Webhook signature helpers (no network). */\n readonly webhooks = webhooks;\n\n constructor(options: SimplrOptions) {\n if (!options?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n this.cfg = {\n apiKey: options.apiKey,\n baseUrl: (options.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\"),\n timeoutMs: options.timeoutMs ?? 15000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n };\n if (typeof this.cfg.fetchImpl !== \"function\") {\n throw new Error(\"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\");\n }\n this.orders = new OrdersResource(this.cfg);\n this.phone = new PhoneResource(this.cfg);\n this.edge = new EdgeResource(this.cfg);\n }\n\n /** Run an identity/fraud check. Provide any of email, phone, device, behavior. */\n check(input: CheckInput): Promise<CheckResult> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check\", input);\n }\n\n /** Run up to 100 checks at once. */\n checkBulk(items: CheckInput[]): Promise<BulkResult<CheckResult>> {\n return apiRequest(this.cfg, \"POST\", \"/v1/check/bulk\", { items });\n }\n}\n\nexport default Simplr;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@simplr-ai/node",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Simplr server-side SDK for Node.js — fraud/identity checks, order scoring, edge ingestion, and webhook signature verification.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": ["dist"],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "node --test"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.3.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": ["simplr", "simplify", "fraud", "identity", "feature-flags", "webhooks", "server", "node"]
|
|
38
|
+
}
|