@kreltix/liveness-sdk 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/README.md +425 -0
- package/dist/KreltixSDK.d.ts +9 -0
- package/dist/api/client.d.ts +8 -0
- package/dist/api/types.d.ts +71 -0
- package/dist/camera/CameraManager.d.ts +19 -0
- package/dist/camera/VideoRecorder.d.ts +34 -0
- package/dist/challenges/ChallengeDetector.d.ts +44 -0
- package/dist/challenges/detectors/BlinkDetector.d.ts +7 -0
- package/dist/challenges/detectors/HeadTurnDetector.d.ts +7 -0
- package/dist/challenges/detectors/SmileDetector.d.ts +7 -0
- package/dist/errors/KreltixError.d.ts +26 -0
- package/dist/index.cjs.js +1250 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.esm.js +1230 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/session/SessionManager.d.ts +11 -0
- package/dist/ui/ChallengeOverlay.d.ts +33 -0
- package/dist/ui/illustrations.d.ts +18 -0
- package/dist/utils/retry.d.ts +1 -0
- package/dist/utils/video.d.ts +4 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# @kreltix/liveness-sdk
|
|
2
|
+
|
|
3
|
+
Liveness detection SDK for web and React Native applications. Verifies that a real person is present by guiding them through randomized face challenges (head turns, blinks, smiles) with an interactive camera overlay.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @kreltix/liveness-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { KreltixSDK } from "@kreltix/liveness-sdk";
|
|
15
|
+
|
|
16
|
+
const sdk = KreltixSDK.initialize("pk_live_your_public_key");
|
|
17
|
+
|
|
18
|
+
const result = await sdk.startLivenessCheck();
|
|
19
|
+
|
|
20
|
+
if (result.isLive) {
|
|
21
|
+
console.log("Verified!", result.confidence);
|
|
22
|
+
} else {
|
|
23
|
+
console.log("Failed:", result.recommendation);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That's it. The SDK handles the entire flow: opens the camera, shows animated guides for each challenge, records video, submits to the server, and returns the result.
|
|
28
|
+
|
|
29
|
+
## Getting Your Keys
|
|
30
|
+
|
|
31
|
+
1. Log in to the [Kreltix Dashboard](https://kreltix.com)
|
|
32
|
+
2. Go to **API Keys** > **Generate New Key**
|
|
33
|
+
3. Select **SDK Key Pair**
|
|
34
|
+
4. Copy both keys:
|
|
35
|
+
- `pk_live_...` (public key) — safe to use in frontend/mobile apps
|
|
36
|
+
- `sk_live_...` (secret key) — server-side only, never expose to clients
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Your App SDK Kreltix API
|
|
42
|
+
──────── ─── ───────────
|
|
43
|
+
|
|
44
|
+
startLivenessCheck()
|
|
45
|
+
→ Create session ← Server picks challenges
|
|
46
|
+
(e.g. turn_right → turn_left)
|
|
47
|
+
|
|
48
|
+
→ Open camera
|
|
49
|
+
→ Show animated guide Step 1: "Turn right"
|
|
50
|
+
→ Record 3-7s video
|
|
51
|
+
Step 2: "Turn left"
|
|
52
|
+
→ Show animated guide
|
|
53
|
+
→ Record 3-7s video
|
|
54
|
+
|
|
55
|
+
→ Upload & verify ← Server analyzes frames:
|
|
56
|
+
Show processing animation challenge detection,
|
|
57
|
+
anti-spoof checks,
|
|
58
|
+
3D depth estimation
|
|
59
|
+
|
|
60
|
+
→ Show result animation ← { isLive, confidence }
|
|
61
|
+
← result returned
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API Reference
|
|
65
|
+
|
|
66
|
+
### `KreltixSDK.initialize(publicKey, options?)`
|
|
67
|
+
|
|
68
|
+
Creates a new SDK instance.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const sdk = KreltixSDK.initialize("pk_live_...", {
|
|
72
|
+
baseUrl: "https://api.kreltix.com", // default
|
|
73
|
+
timeout: 10000, // ms, default 10s
|
|
74
|
+
maxRetries: 2, // default 2
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `publicKey` | `string` | Your `pk_live_` public key (required) |
|
|
81
|
+
| `options.baseUrl` | `string` | API base URL. Override for self-hosted deployments |
|
|
82
|
+
| `options.timeout` | `number` | Request timeout in ms (default: 10000) |
|
|
83
|
+
| `options.maxRetries` | `number` | Retry count for failed requests (default: 2) |
|
|
84
|
+
|
|
85
|
+
### `sdk.startLivenessCheck(options?)`
|
|
86
|
+
|
|
87
|
+
Runs the full liveness verification flow. Returns a promise that resolves with the result.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const result = await sdk.startLivenessCheck({
|
|
91
|
+
// Mount inside a specific element (default: fullscreen overlay)
|
|
92
|
+
container: document.getElementById("my-container"),
|
|
93
|
+
|
|
94
|
+
// Show built-in UI overlay (default: true)
|
|
95
|
+
showOverlay: true,
|
|
96
|
+
|
|
97
|
+
// Attach metadata to the session (passed through to webhooks)
|
|
98
|
+
metadata: { userId: "user_123", flow: "onboarding" },
|
|
99
|
+
|
|
100
|
+
// Callbacks
|
|
101
|
+
onSessionCreated: (session) => {
|
|
102
|
+
console.log("Challenges:", session.challenges);
|
|
103
|
+
},
|
|
104
|
+
onChallengeStarted: (challenge) => {
|
|
105
|
+
console.log("Now doing:", challenge);
|
|
106
|
+
},
|
|
107
|
+
onProgress: (event) => {
|
|
108
|
+
console.log(event.stage, event.message);
|
|
109
|
+
},
|
|
110
|
+
onError: (error) => {
|
|
111
|
+
console.error("SDK error:", error.message);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Options
|
|
117
|
+
|
|
118
|
+
| Option | Type | Default | Description |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| `container` | `HTMLElement` | `document.body` | Element to mount the camera UI into |
|
|
121
|
+
| `showOverlay` | `boolean` | `true` | Show the built-in challenge overlay UI |
|
|
122
|
+
| `metadata` | `object` | — | Custom metadata attached to the session |
|
|
123
|
+
| `cameraAdapter` | `CameraAdapter` | — | Custom camera adapter (for React Native) |
|
|
124
|
+
| `onSessionCreated` | `function` | — | Called when the server creates the session |
|
|
125
|
+
| `onChallengeStarted` | `function` | — | Called when each challenge step begins |
|
|
126
|
+
| `onProgress` | `function` | — | Called with progress updates throughout the flow |
|
|
127
|
+
| `onError` | `function` | — | Called on any error |
|
|
128
|
+
|
|
129
|
+
#### Result: `LivenessResult`
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
{
|
|
133
|
+
sessionId: string; // Unique session identifier
|
|
134
|
+
isLive: boolean; // true if all checks passed
|
|
135
|
+
confidence: number; // 0-100 confidence score
|
|
136
|
+
allChallengesPassed: boolean;
|
|
137
|
+
challengeResults: [ // Per-step results
|
|
138
|
+
{ challenge: "turn_right", completed: true, indicators: {...} },
|
|
139
|
+
{ challenge: "turn_left", completed: true, indicators: {...} },
|
|
140
|
+
];
|
|
141
|
+
recommendation: string; // Human-readable result message
|
|
142
|
+
error: string | null; // Error message if something failed
|
|
143
|
+
indicators: object | null; // Detailed analysis indicators
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Progress Stages
|
|
148
|
+
|
|
149
|
+
The `onProgress` callback receives events with these stages:
|
|
150
|
+
|
|
151
|
+
| Stage | When |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `session_created` | Server session created, challenges assigned |
|
|
154
|
+
| `camera_ready` | Camera stream active |
|
|
155
|
+
| `recording` | Recording a challenge step |
|
|
156
|
+
| `uploading` | Submitting videos to server |
|
|
157
|
+
| `complete` | Result received |
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
### Basic — Fullscreen Overlay
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { KreltixSDK } from "@kreltix/liveness-sdk";
|
|
165
|
+
|
|
166
|
+
const sdk = KreltixSDK.initialize("pk_live_...");
|
|
167
|
+
|
|
168
|
+
document.getElementById("verify-btn").addEventListener("click", async () => {
|
|
169
|
+
try {
|
|
170
|
+
const result = await sdk.startLivenessCheck();
|
|
171
|
+
|
|
172
|
+
if (result.isLive) {
|
|
173
|
+
showSuccess(`Verified with ${result.confidence}% confidence`);
|
|
174
|
+
} else {
|
|
175
|
+
showError(result.recommendation);
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err.code === "CAMERA_ERROR") {
|
|
179
|
+
showError("Please allow camera access to continue.");
|
|
180
|
+
} else {
|
|
181
|
+
showError("Verification failed. Please try again.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Embedded in a Container
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<div id="liveness-container" style="width: 400px; height: 600px;"></div>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const result = await sdk.startLivenessCheck({
|
|
195
|
+
container: document.getElementById("liveness-container"),
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### With Metadata for Webhooks
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const result = await sdk.startLivenessCheck({
|
|
203
|
+
metadata: {
|
|
204
|
+
userId: "user_abc123",
|
|
205
|
+
flow: "kyc_onboarding",
|
|
206
|
+
tier: "premium",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// The metadata is included in webhook payloads sent to your server
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Full Progress Tracking
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const result = await sdk.startLivenessCheck({
|
|
217
|
+
onSessionCreated: (session) => {
|
|
218
|
+
console.log("Session:", session.session_id);
|
|
219
|
+
console.log("Challenges:", session.challenges.join(" → "));
|
|
220
|
+
// e.g. ["turn_right", "turn_left"]
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
onChallengeStarted: (challenge) => {
|
|
224
|
+
analytics.track("liveness_challenge_started", { challenge });
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
onProgress: ({ stage, message }) => {
|
|
228
|
+
updateStatusBar(stage, message);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
onError: (error) => {
|
|
232
|
+
errorReporting.capture(error);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### React Component
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { useState } from "react";
|
|
241
|
+
import { KreltixSDK, LivenessResult, KreltixError } from "@kreltix/liveness-sdk";
|
|
242
|
+
|
|
243
|
+
const sdk = KreltixSDK.initialize("pk_live_...");
|
|
244
|
+
|
|
245
|
+
function LivenessButton() {
|
|
246
|
+
const [status, setStatus] = useState<"idle" | "running" | "passed" | "failed">("idle");
|
|
247
|
+
|
|
248
|
+
const handleVerify = async () => {
|
|
249
|
+
setStatus("running");
|
|
250
|
+
try {
|
|
251
|
+
const result = await sdk.startLivenessCheck({
|
|
252
|
+
metadata: { component: "onboarding" },
|
|
253
|
+
});
|
|
254
|
+
setStatus(result.isLive ? "passed" : "failed");
|
|
255
|
+
} catch (err) {
|
|
256
|
+
setStatus("failed");
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<button onClick={handleVerify} disabled={status === "running"}>
|
|
262
|
+
{status === "running" ? "Verifying..." : "Verify Identity"}
|
|
263
|
+
</button>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### React Native
|
|
269
|
+
|
|
270
|
+
The SDK supports React Native via a custom camera adapter. You provide the camera implementation, the SDK handles the rest.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { KreltixSDK, CameraAdapter } from "@kreltix/liveness-sdk";
|
|
274
|
+
import { Camera } from "expo-camera";
|
|
275
|
+
|
|
276
|
+
class ExpoCameraAdapter implements CameraAdapter {
|
|
277
|
+
private camera: Camera | null = null;
|
|
278
|
+
private videoElement: HTMLVideoElement;
|
|
279
|
+
|
|
280
|
+
constructor() {
|
|
281
|
+
this.videoElement = document.createElement("video");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async start(facingMode: "user" | "environment") {
|
|
285
|
+
const { status } = await Camera.requestCameraPermissionsAsync();
|
|
286
|
+
if (status !== "granted") throw new Error("Camera permission denied");
|
|
287
|
+
|
|
288
|
+
// Return the camera stream
|
|
289
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
290
|
+
video: { facingMode },
|
|
291
|
+
});
|
|
292
|
+
return stream;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
stop() {
|
|
296
|
+
// Clean up camera resources
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
getVideoElement() {
|
|
300
|
+
return this.videoElement;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const sdk = KreltixSDK.initialize("pk_live_...");
|
|
305
|
+
|
|
306
|
+
const result = await sdk.startLivenessCheck({
|
|
307
|
+
cameraAdapter: new ExpoCameraAdapter(),
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Error Handling
|
|
312
|
+
|
|
313
|
+
All errors extend `KreltixError` with a `code` property and `isRetryable` flag.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import {
|
|
317
|
+
KreltixError,
|
|
318
|
+
KreltixCameraError,
|
|
319
|
+
KreltixAuthError,
|
|
320
|
+
KreltixInsufficientFundsError,
|
|
321
|
+
} from "@kreltix/liveness-sdk";
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const result = await sdk.startLivenessCheck();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (err instanceof KreltixCameraError) {
|
|
327
|
+
// User denied camera permission or no camera available
|
|
328
|
+
showMessage("Camera access is required for verification.");
|
|
329
|
+
} else if (err instanceof KreltixAuthError) {
|
|
330
|
+
// Invalid or expired API key
|
|
331
|
+
showMessage("Configuration error. Contact support.");
|
|
332
|
+
} else if (err instanceof KreltixInsufficientFundsError) {
|
|
333
|
+
// Account balance too low
|
|
334
|
+
showMessage("Service temporarily unavailable.");
|
|
335
|
+
} else if (err instanceof KreltixError && err.isRetryable) {
|
|
336
|
+
// Network/server error — safe to retry
|
|
337
|
+
showMessage("Connection issue. Please try again.");
|
|
338
|
+
} else {
|
|
339
|
+
showMessage("Verification failed. Please try again.");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Error Types
|
|
345
|
+
|
|
346
|
+
| Error | Code | Retryable | Cause |
|
|
347
|
+
|---|---|---|---|
|
|
348
|
+
| `KreltixCameraError` | `CAMERA_ERROR` | No | Camera permission denied or not available |
|
|
349
|
+
| `KreltixAuthError` | `AUTH_ERROR` | No | Invalid or expired public key |
|
|
350
|
+
| `KreltixInsufficientFundsError` | `INSUFFICIENT_FUNDS` | No | Account wallet balance too low |
|
|
351
|
+
| `KreltixSessionExpiredError` | `SESSION_EXPIRED` | No | Verification session timed out (5 min) |
|
|
352
|
+
| `KreltixValidationError` | `VALIDATION_ERROR` | No | Invalid request (e.g. session already used) |
|
|
353
|
+
| `KreltixNetworkError` | `NETWORK_ERROR` | Yes | Network failure or request timeout |
|
|
354
|
+
| `KreltixServerError` | `SERVER_ERROR` | Yes | Server-side error (5xx) |
|
|
355
|
+
|
|
356
|
+
## Security Model
|
|
357
|
+
|
|
358
|
+
### Public Key (`pk_live_`)
|
|
359
|
+
|
|
360
|
+
- Safe to embed in client-side apps (web, mobile)
|
|
361
|
+
- Can only create liveness sessions and submit verifications
|
|
362
|
+
- Cannot access account data, billing, verification history, or any other API
|
|
363
|
+
- Rate limited (30 requests/minute)
|
|
364
|
+
|
|
365
|
+
### Secret Key (`sk_live_`)
|
|
366
|
+
|
|
367
|
+
- Server-side only — never expose in client code
|
|
368
|
+
- Full API access: read session results, verification history, account management
|
|
369
|
+
- Use for webhooks, backend integrations, and admin operations
|
|
370
|
+
|
|
371
|
+
### How Challenges Work
|
|
372
|
+
|
|
373
|
+
1. The **server** picks a random multi-step challenge sequence (e.g., "turn right" then "turn left")
|
|
374
|
+
2. Challenges are **unpredictable** — the client cannot know them in advance
|
|
375
|
+
3. Each session is **single-use** — a session token works exactly once
|
|
376
|
+
4. Sessions **expire after 5 minutes**
|
|
377
|
+
5. The server performs the actual liveness analysis (anti-spoof, depth estimation, texture analysis) — on-device detection is only for UX guidance
|
|
378
|
+
|
|
379
|
+
## Browser Support
|
|
380
|
+
|
|
381
|
+
- Chrome 70+
|
|
382
|
+
- Firefox 65+
|
|
383
|
+
- Safari 14.1+
|
|
384
|
+
- Edge 79+
|
|
385
|
+
|
|
386
|
+
Requires `getUserMedia` and `MediaRecorder` APIs.
|
|
387
|
+
|
|
388
|
+
## TypeScript
|
|
389
|
+
|
|
390
|
+
The SDK is written in TypeScript and ships with full type definitions. All types are exported:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import type {
|
|
394
|
+
LivenessResult,
|
|
395
|
+
LivenessOptions,
|
|
396
|
+
ProgressEvent,
|
|
397
|
+
ChallengeStepResult,
|
|
398
|
+
CameraAdapter,
|
|
399
|
+
} from "@kreltix/liveness-sdk";
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Webhooks
|
|
403
|
+
|
|
404
|
+
When a liveness check completes, Kreltix sends a webhook to your server (if configured in the dashboard) with the full result:
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{
|
|
408
|
+
"event": "liveness_session.completed",
|
|
409
|
+
"session_id": "69c9ce0c...",
|
|
410
|
+
"status": "completed",
|
|
411
|
+
"challenges": ["turn_right", "turn_left"],
|
|
412
|
+
"is_live": true,
|
|
413
|
+
"confidence": 99.99,
|
|
414
|
+
"charge_amount": 90.0,
|
|
415
|
+
"metadata": { "userId": "user_123" },
|
|
416
|
+
"timestamp": "2026-03-30T06:12:44.000Z"
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Webhook payloads are signed with HMAC-SHA256. Verify the signature using your webhook secret from the dashboard.
|
|
421
|
+
|
|
422
|
+
## Support
|
|
423
|
+
|
|
424
|
+
- Dashboard: [https://kreltix.com](https://kreltix.com)
|
|
425
|
+
- API Docs: [https://api.kreltix.com/docs](https://api.kreltix.com/docs)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SDKOptions, LivenessOptions, LivenessResult } from "./api/types";
|
|
2
|
+
export declare class KreltixSDK {
|
|
3
|
+
private config;
|
|
4
|
+
private apiClient;
|
|
5
|
+
private sessionManager;
|
|
6
|
+
private constructor();
|
|
7
|
+
static initialize(publicKey: string, options?: SDKOptions): KreltixSDK;
|
|
8
|
+
startLivenessCheck(options?: LivenessOptions): Promise<LivenessResult>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SDKConfig, CreateSessionResponse, SubmitVerificationResponse } from "./types";
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: SDKConfig);
|
|
5
|
+
private request;
|
|
6
|
+
createSession(metadata?: Record<string, unknown>): Promise<CreateSessionResponse>;
|
|
7
|
+
submitVerification(sessionId: string, sessionToken: string, videos: string[]): Promise<SubmitVerificationResponse>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface SDKConfig {
|
|
2
|
+
publicKey: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SDKOptions {
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface LivenessOptions {
|
|
13
|
+
/** Container element to mount the camera UI into. If not provided, creates a fullscreen overlay. */
|
|
14
|
+
container?: HTMLElement;
|
|
15
|
+
/** Whether to show the built-in challenge overlay UI. Default: true */
|
|
16
|
+
showOverlay?: boolean;
|
|
17
|
+
/** Custom metadata to attach to the session */
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
/** Camera adapter for React Native or custom implementations */
|
|
20
|
+
cameraAdapter?: CameraAdapter;
|
|
21
|
+
/** Called when the session is created */
|
|
22
|
+
onSessionCreated?: (session: CreateSessionResponse) => void;
|
|
23
|
+
/** Called when the challenge starts (camera open, recording begins) */
|
|
24
|
+
onChallengeStarted?: (challenge: string) => void;
|
|
25
|
+
/** Called with progress updates */
|
|
26
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
27
|
+
/** Called on error */
|
|
28
|
+
onError?: (error: Error) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface ProgressEvent {
|
|
31
|
+
stage: "session_created" | "camera_ready" | "recording" | "uploading" | "processing" | "complete";
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
export interface CreateSessionResponse {
|
|
35
|
+
session_id: string;
|
|
36
|
+
session_token: string;
|
|
37
|
+
challenges: string[];
|
|
38
|
+
instructions: string[];
|
|
39
|
+
expires_at: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ChallengeStepResult {
|
|
42
|
+
challenge: string;
|
|
43
|
+
completed: boolean;
|
|
44
|
+
indicators: Record<string, unknown> | null;
|
|
45
|
+
}
|
|
46
|
+
export interface SubmitVerificationResponse {
|
|
47
|
+
session_id: string;
|
|
48
|
+
is_live: boolean;
|
|
49
|
+
confidence: number;
|
|
50
|
+
challenges_completed: ChallengeStepResult[];
|
|
51
|
+
all_challenges_passed: boolean;
|
|
52
|
+
indicators: Record<string, unknown> | null;
|
|
53
|
+
recommendation: string | null;
|
|
54
|
+
error: string | null;
|
|
55
|
+
charge_amount: number | null;
|
|
56
|
+
}
|
|
57
|
+
export interface LivenessResult {
|
|
58
|
+
sessionId: string;
|
|
59
|
+
isLive: boolean;
|
|
60
|
+
confidence: number;
|
|
61
|
+
challengeResults: ChallengeStepResult[];
|
|
62
|
+
allChallengesPassed: boolean;
|
|
63
|
+
recommendation: string | null;
|
|
64
|
+
error: string | null;
|
|
65
|
+
indicators: Record<string, unknown> | null;
|
|
66
|
+
}
|
|
67
|
+
export interface CameraAdapter {
|
|
68
|
+
start(facingMode: "user" | "environment"): Promise<MediaStream>;
|
|
69
|
+
stop(): void;
|
|
70
|
+
getVideoElement(): HTMLVideoElement;
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CameraAdapter } from "../api/types";
|
|
2
|
+
/**
|
|
3
|
+
* Default web camera adapter using getUserMedia.
|
|
4
|
+
*/
|
|
5
|
+
export declare class WebCameraAdapter implements CameraAdapter {
|
|
6
|
+
private stream;
|
|
7
|
+
private videoElement;
|
|
8
|
+
constructor();
|
|
9
|
+
start(facingMode?: "user" | "environment"): Promise<MediaStream>;
|
|
10
|
+
stop(): void;
|
|
11
|
+
getVideoElement(): HTMLVideoElement;
|
|
12
|
+
}
|
|
13
|
+
export declare class CameraManager {
|
|
14
|
+
private adapter;
|
|
15
|
+
constructor(adapter?: CameraAdapter);
|
|
16
|
+
start(): Promise<MediaStream>;
|
|
17
|
+
stop(): void;
|
|
18
|
+
getVideoElement(): HTMLVideoElement;
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface RecordingOptions {
|
|
2
|
+
/** Minimum recording duration in ms. Default: 3000 */
|
|
3
|
+
minDurationMs?: number;
|
|
4
|
+
/** Maximum recording duration in ms. Default: 7000 */
|
|
5
|
+
maxDurationMs?: number;
|
|
6
|
+
/** Video bitrate in bps. Default: 1000000 (1 Mbps) */
|
|
7
|
+
videoBitsPerSecond?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class VideoRecorder {
|
|
10
|
+
private mediaRecorder;
|
|
11
|
+
private chunks;
|
|
12
|
+
private resolveRecording;
|
|
13
|
+
/**
|
|
14
|
+
* Get the best supported MIME type for video recording.
|
|
15
|
+
*/
|
|
16
|
+
private getMimeType;
|
|
17
|
+
/**
|
|
18
|
+
* Start recording from a media stream.
|
|
19
|
+
*/
|
|
20
|
+
start(stream: MediaStream, options?: RecordingOptions): void;
|
|
21
|
+
/**
|
|
22
|
+
* Stop recording and return the video as a base64 string.
|
|
23
|
+
*/
|
|
24
|
+
stop(): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Record for a duration, then stop and return base64.
|
|
27
|
+
* Can be stopped early via the returned controller.
|
|
28
|
+
*/
|
|
29
|
+
record(stream: MediaStream, options?: RecordingOptions): {
|
|
30
|
+
promise: Promise<string>;
|
|
31
|
+
stopEarly: () => void;
|
|
32
|
+
};
|
|
33
|
+
private _pendingStopEarly;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface FaceLandmarks {
|
|
2
|
+
/** Left eye points [[x,y], ...] */
|
|
3
|
+
leftEye: [number, number][];
|
|
4
|
+
/** Right eye points [[x,y], ...] */
|
|
5
|
+
rightEye: [number, number][];
|
|
6
|
+
/** Nose tip [x, y] */
|
|
7
|
+
noseTip: [number, number];
|
|
8
|
+
/** Face bounding box { x, y, width, height } */
|
|
9
|
+
faceBounds: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
/** Upper lip points */
|
|
16
|
+
upperLip: [number, number][];
|
|
17
|
+
/** Lower lip points */
|
|
18
|
+
lowerLip: [number, number][];
|
|
19
|
+
}
|
|
20
|
+
export interface DetectionResult {
|
|
21
|
+
/** Whether the challenge appears completed */
|
|
22
|
+
completed: boolean;
|
|
23
|
+
/** 0-1 progress toward completion */
|
|
24
|
+
progress: number;
|
|
25
|
+
/** Human-readable feedback for the user */
|
|
26
|
+
feedback: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Lightweight on-device challenge detection for UX guidance.
|
|
30
|
+
* This is NOT a security layer -- the server is the sole authority on liveness.
|
|
31
|
+
*/
|
|
32
|
+
export declare class ChallengeDetector {
|
|
33
|
+
private blinkDetector;
|
|
34
|
+
private headTurnDetector;
|
|
35
|
+
private smileDetector;
|
|
36
|
+
/**
|
|
37
|
+
* Process a frame's face landmarks and return detection result for the given challenge.
|
|
38
|
+
*/
|
|
39
|
+
detect(challenge: string, landmarks: FaceLandmarks): DetectionResult;
|
|
40
|
+
/**
|
|
41
|
+
* Reset all detectors for a new session.
|
|
42
|
+
*/
|
|
43
|
+
reset(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class KreltixError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
isRetryable: boolean;
|
|
4
|
+
constructor(message: string, code: string, isRetryable?: boolean);
|
|
5
|
+
}
|
|
6
|
+
export declare class KreltixNetworkError extends KreltixError {
|
|
7
|
+
constructor(message?: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class KreltixAuthError extends KreltixError {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class KreltixSessionExpiredError extends KreltixError {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class KreltixCameraError extends KreltixError {
|
|
16
|
+
constructor(message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class KreltixServerError extends KreltixError {
|
|
19
|
+
constructor(message?: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class KreltixValidationError extends KreltixError {
|
|
22
|
+
constructor(message?: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class KreltixInsufficientFundsError extends KreltixError {
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|