smartcomply-web-sdk 1.0.3

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.
Files changed (133) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +482 -0
  3. package/dist/camera/ActionDetector.d.ts +26 -0
  4. package/dist/camera/ActionDetector.d.ts.map +1 -0
  5. package/dist/camera/ActionDetector.js +114 -0
  6. package/dist/camera/ActionDetector.js.map +1 -0
  7. package/dist/camera/CameraManager.d.ts +7 -0
  8. package/dist/camera/CameraManager.d.ts.map +1 -0
  9. package/dist/camera/CameraManager.js +31 -0
  10. package/dist/camera/CameraManager.js.map +1 -0
  11. package/dist/camera/FaceDetector.d.ts +12 -0
  12. package/dist/camera/FaceDetector.d.ts.map +1 -0
  13. package/dist/camera/FaceDetector.js +55 -0
  14. package/dist/camera/FaceDetector.js.map +1 -0
  15. package/dist/camera/Permission.d.ts +42 -0
  16. package/dist/camera/Permission.d.ts.map +1 -0
  17. package/dist/camera/Permission.js +107 -0
  18. package/dist/camera/Permission.js.map +1 -0
  19. package/dist/camera/VideoRecorder.d.ts +12 -0
  20. package/dist/camera/VideoRecorder.d.ts.map +1 -0
  21. package/dist/camera/VideoRecorder.js +51 -0
  22. package/dist/camera/VideoRecorder.js.map +1 -0
  23. package/dist/client/Config.d.ts +9 -0
  24. package/dist/client/Config.d.ts.map +1 -0
  25. package/dist/client/Config.js +8 -0
  26. package/dist/client/Config.js.map +1 -0
  27. package/dist/client/HttpClient.d.ts +44 -0
  28. package/dist/client/HttpClient.d.ts.map +1 -0
  29. package/dist/client/HttpClient.js +149 -0
  30. package/dist/client/HttpClient.js.map +1 -0
  31. package/dist/client/Smartcomply.d.ts +50 -0
  32. package/dist/client/Smartcomply.d.ts.map +1 -0
  33. package/dist/client/Smartcomply.js +77 -0
  34. package/dist/client/Smartcomply.js.map +1 -0
  35. package/dist/components/CameraView.d.ts +59 -0
  36. package/dist/components/CameraView.d.ts.map +1 -0
  37. package/dist/components/CameraView.js +242 -0
  38. package/dist/components/CameraView.js.map +1 -0
  39. package/dist/components/DocumentUpload.d.ts +21 -0
  40. package/dist/components/DocumentUpload.d.ts.map +1 -0
  41. package/dist/components/DocumentUpload.js +220 -0
  42. package/dist/components/DocumentUpload.js.map +1 -0
  43. package/dist/errors/AuthError.d.ts +8 -0
  44. package/dist/errors/AuthError.d.ts.map +1 -0
  45. package/dist/errors/AuthError.js +15 -0
  46. package/dist/errors/AuthError.js.map +1 -0
  47. package/dist/errors/NetworkError.d.ts +8 -0
  48. package/dist/errors/NetworkError.d.ts.map +1 -0
  49. package/dist/errors/NetworkError.js +15 -0
  50. package/dist/errors/NetworkError.js.map +1 -0
  51. package/dist/errors/SDKError.d.ts +7 -0
  52. package/dist/errors/SDKError.d.ts.map +1 -0
  53. package/dist/errors/SDKError.js +14 -0
  54. package/dist/errors/SDKError.js.map +1 -0
  55. package/dist/flow/DocumentCapture.d.ts +54 -0
  56. package/dist/flow/DocumentCapture.d.ts.map +1 -0
  57. package/dist/flow/DocumentCapture.js +801 -0
  58. package/dist/flow/DocumentCapture.js.map +1 -0
  59. package/dist/flow/SmartComplyFlow.d.ts +104 -0
  60. package/dist/flow/SmartComplyFlow.d.ts.map +1 -0
  61. package/dist/flow/SmartComplyFlow.js +1726 -0
  62. package/dist/flow/SmartComplyFlow.js.map +1 -0
  63. package/dist/flow/theme.d.ts +30 -0
  64. package/dist/flow/theme.d.ts.map +1 -0
  65. package/dist/flow/theme.js +103 -0
  66. package/dist/flow/theme.js.map +1 -0
  67. package/dist/index.d.ts +20 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +29 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/modules/liveness/LivenessUI.d.ts +46 -0
  72. package/dist/modules/liveness/LivenessUI.d.ts.map +1 -0
  73. package/dist/modules/liveness/LivenessUI.js +609 -0
  74. package/dist/modules/liveness/LivenessUI.js.map +1 -0
  75. package/dist/modules/liveness/index.d.ts +5 -0
  76. package/dist/modules/liveness/index.d.ts.map +1 -0
  77. package/dist/modules/liveness/index.js +10 -0
  78. package/dist/modules/liveness/index.js.map +1 -0
  79. package/dist/modules/liveness/liveness.d.ts +85 -0
  80. package/dist/modules/liveness/liveness.d.ts.map +1 -0
  81. package/dist/modules/liveness/liveness.js +425 -0
  82. package/dist/modules/liveness/liveness.js.map +1 -0
  83. package/dist/modules/liveness/uploader.d.ts +34 -0
  84. package/dist/modules/liveness/uploader.d.ts.map +1 -0
  85. package/dist/modules/liveness/uploader.js +106 -0
  86. package/dist/modules/liveness/uploader.js.map +1 -0
  87. package/dist/modules/onboarding/index.d.ts +2 -0
  88. package/dist/modules/onboarding/index.d.ts.map +1 -0
  89. package/dist/modules/onboarding/index.js +6 -0
  90. package/dist/modules/onboarding/index.js.map +1 -0
  91. package/dist/modules/onboarding/onboarding.d.ts +25 -0
  92. package/dist/modules/onboarding/onboarding.d.ts.map +1 -0
  93. package/dist/modules/onboarding/onboarding.js +33 -0
  94. package/dist/modules/onboarding/onboarding.js.map +1 -0
  95. package/dist/smartcomply.browser.js +8147 -0
  96. package/dist/smartcomply.browser.js.map +7 -0
  97. package/dist/types/common.d.ts +27 -0
  98. package/dist/types/common.d.ts.map +1 -0
  99. package/dist/types/common.js +3 -0
  100. package/dist/types/common.js.map +1 -0
  101. package/dist/types/liveness.d.ts +142 -0
  102. package/dist/types/liveness.d.ts.map +1 -0
  103. package/dist/types/liveness.js +3 -0
  104. package/dist/types/liveness.js.map +1 -0
  105. package/dist/types/onboarding.d.ts +53 -0
  106. package/dist/types/onboarding.d.ts.map +1 -0
  107. package/dist/types/onboarding.js +3 -0
  108. package/dist/types/onboarding.js.map +1 -0
  109. package/dist/types/quality.d.ts +23 -0
  110. package/dist/types/quality.d.ts.map +1 -0
  111. package/dist/types/quality.js +6 -0
  112. package/dist/types/quality.js.map +1 -0
  113. package/dist/utils/ImageQuality.d.ts +48 -0
  114. package/dist/utils/ImageQuality.d.ts.map +1 -0
  115. package/dist/utils/ImageQuality.js +186 -0
  116. package/dist/utils/ImageQuality.js.map +1 -0
  117. package/dist/utils/constants.d.ts +59 -0
  118. package/dist/utils/constants.d.ts.map +1 -0
  119. package/dist/utils/constants.js +70 -0
  120. package/dist/utils/constants.js.map +1 -0
  121. package/dist/utils/device.d.ts +14 -0
  122. package/dist/utils/device.d.ts.map +1 -0
  123. package/dist/utils/device.js +55 -0
  124. package/dist/utils/device.js.map +1 -0
  125. package/dist/utils/logger.d.ts +28 -0
  126. package/dist/utils/logger.d.ts.map +1 -0
  127. package/dist/utils/logger.js +67 -0
  128. package/dist/utils/logger.js.map +1 -0
  129. package/dist/utils/validator.d.ts +35 -0
  130. package/dist/utils/validator.d.ts.map +1 -0
  131. package/dist/utils/validator.js +116 -0
  132. package/dist/utils/validator.js.map +1 -0
  133. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 SmartComply
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,482 @@
1
+ # SmartComply Web SDK
2
+
3
+ Drop-in identity verification (KYC) widget for web applications. Embed one line of code and let your users verify their identity through facial liveness detection, government ID checks (BVN/NIN), and international document verification (passport, national ID, driver's license).
4
+
5
+ ## Overview
6
+
7
+ SmartComply SDK is a **KYC-as-a-service** widget that businesses embed in their web apps to verify end-user identities. It handles the full verification flow — from collecting ID details to live face matching — and delivers results asynchronously via webhook.
8
+
9
+ ### Target Audience
10
+
11
+ - **Fintechs, banks, and lending platforms** that need to onboard customers securely
12
+ - **Any business** required to perform Know Your Customer (KYC) checks before granting account access
13
+
14
+ ### What It Does
15
+
16
+ The SDK verifies that a person is **real**, **alive**, and **who they claim to be**:
17
+
18
+ 1. **Identity verification** — Validates the user's BVN/NIN against government databases, or extracts data from uploaded documents via OCR
19
+ 2. **Liveness detection** — Camera-based challenge-response (blink, turn head, open mouth) to prove the person is physically present — not a photo or video replay
20
+ 3. **Face matching** — Compares the user's live selfie against:
21
+ - The government-returned photo (BVN/NIN flow), or
22
+ - The face on their uploaded document (passport/license flow)
23
+
24
+ ### Two Verification Flows
25
+
26
+ | Flow | How it works | ID Types |
27
+ |------|-------------|----------|
28
+ | **Data verification** | User enters an ID number → backend validates against government database → face match against government photo | BVN, NIN |
29
+ | **Document verification** | User uploads/captures a document photo → OCR extracts data + expiry check → face match against document photo | Passport, National ID, Driver's License |
30
+
31
+ ### End-to-End Flow
32
+
33
+ ```
34
+ ┌─────────────────────────────────────────────────────────────────────┐
35
+ │ YOUR APP │
36
+ │ ┌───────────────────────────────────────────────────────────────┐ │
37
+ │ │ SmartComply Widget (modal) │ │
38
+ │ │ │ │
39
+ │ │ Welcome → Country → ID Type → Enter ID / Upload Doc │ │
40
+ │ │ → Face Liveness (camera) → Done │ │
41
+ │ └───────────────────────────────────────────────────────────────┘ │
42
+ │ │ │
43
+ │ submit │ │
44
+ │ ▼ │
45
+ │ Adhere Backend │
46
+ │ │ │
47
+ │ ┌─────────┼──────────┐ │
48
+ │ ▼ ▼ ▼ │
49
+ │ Face Match OCR Gov DB Check │
50
+ │ (DeepFace) (document) (BVN/NIN) │
51
+ │ └─────────┼──────────┘ │
52
+ │ ▼ │
53
+ │ Webhook POST ──────────► YOUR SERVER │
54
+ │ (signed with HMAC) (result handler) │
55
+ └─────────────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ npm install smartcomply-web-sdk
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Option 1: Drop-in Widget (Recommended)
67
+
68
+ The fastest way to add KYC. Opens a complete, branded verification modal — handles everything automatically.
69
+
70
+ ```html
71
+ <button id="verify-btn">Verify Identity</button>
72
+
73
+ <script type="module">
74
+ import { SmartComplyFlow } from "smartcomply-web-sdk";
75
+
76
+ document.getElementById("verify-btn").onclick = () => {
77
+ SmartComplyFlow.open({
78
+ apiKey: "pk_live_your_api_key",
79
+ clientId: "your-sdk-config-uuid",
80
+ environment: "production",
81
+
82
+ onComplete(result) {
83
+ // Verification submitted! Final result arrives via webhook.
84
+ console.log("Entry ID:", result.entryId);
85
+ console.log("Status:", result.status); // "processing"
86
+ // Update your UI — tell the user to wait for confirmation
87
+ },
88
+
89
+ onError(err) {
90
+ console.error("Verification failed:", err.message);
91
+ },
92
+
93
+ onClose() {
94
+ // User closed the modal without completing
95
+ },
96
+ });
97
+ };
98
+ </script>
99
+ ```
100
+
101
+ That's it. The SDK will:
102
+
103
+ 1. Create a secure session with the Adhere backend
104
+ 2. Show a branded welcome screen (your brand name + theme from dashboard)
105
+ 3. Let the user select their country and ID type
106
+ 4. Collect their BVN/NIN number **or** capture their document photo
107
+ 5. Run face liveness detection (camera + challenge actions)
108
+ 6. Record a video and submit everything to the backend
109
+ 7. Show a result screen and call your `onComplete` callback
110
+
111
+ ### What the User Sees
112
+
113
+ | Step | Screen | Description |
114
+ |------|--------|-------------|
115
+ | 1 | Welcome | Your brand name, description, and what to expect |
116
+ | 2 | Country | Select country (auto-skipped if only one country configured) |
117
+ | 3 | ID Type | Choose from the channels you configured (e.g., BVN, NIN, Passport) |
118
+ | 4a | ID Input | Enter BVN/NIN number → instant backend verification against government DB |
119
+ | 4b | Document | Capture a photo of passport/ID card (camera or file upload) |
120
+ | 5 | Liveness | Camera opens — user completes challenge actions (blink, turn head, open mouth) |
121
+ | 6 | Done | "Verification Submitted" — user clicks Done, result arrives via webhook |
122
+
123
+ ### `SmartComplyFlow.open()` Options
124
+
125
+ ```typescript
126
+ SmartComplyFlow.open({
127
+ // Required
128
+ apiKey: string, // Your API key from the Adhere dashboard
129
+ clientId: string, // UUID from your SDK Config (created in dashboard)
130
+
131
+ // Optional
132
+ environment: "production" | "sandbox", // Default: "sandbox"
133
+ timeout: number, // Request timeout in ms (default: 30000)
134
+
135
+ // Callbacks
136
+ onComplete: (result) => void, // Verification submitted successfully
137
+ onError: (error) => void, // Unrecoverable error
138
+ onClose: () => void, // User closed the modal
139
+ });
140
+ ```
141
+
142
+ ### `onComplete` Result
143
+
144
+ ```typescript
145
+ {
146
+ entryId: 42, // Use this to track the verification
147
+ status: "processing", // Always "processing" — final result via webhook
148
+ submittedAt: "2026-04-13T...", // ISO timestamp
149
+ verificationResult: { // Only for data verification (BVN/NIN)
150
+ status: "success",
151
+ code: "VERIFICATION_COMPLETE",
152
+ data: { first_name: "Amara", last_name: "Okafor", identity_check_id: 123, ... }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Option 2: Headless (Custom UI)
160
+
161
+ For full control over the user interface, use the SDK's API methods directly.
162
+
163
+ ### Initialize
164
+
165
+ ```typescript
166
+ import { SmartComply } from "smartcomply-web-sdk";
167
+
168
+ const sdk = new SmartComply({
169
+ apiKey: "pk_live_your_api_key",
170
+ clientId: "your-sdk-config-uuid",
171
+ environment: "production",
172
+ });
173
+ ```
174
+
175
+ ### Create Session
176
+
177
+ Every verification flow starts with a session. Sessions last 30 minutes and are single-use (revoked after liveness submission).
178
+
179
+ ```typescript
180
+ const session = await sdk.createSession();
181
+ // session.token — used internally for all subsequent API calls
182
+ // session.expires_at — ISO timestamp
183
+ ```
184
+
185
+ ### Load Configuration
186
+
187
+ Fetch your SDK config (brand name, theme, available ID types per country):
188
+
189
+ ```typescript
190
+ const config = await sdk.initializeConfig();
191
+
192
+ console.log(config.brand_name); // "Your Company Name"
193
+ console.log(config.verification_type); // ["data_verification"]
194
+
195
+ // Channels are grouped by country with field definitions:
196
+ console.log(config.channels);
197
+ // {
198
+ // "nigeria": [
199
+ // { id: 8, name: "National Identity Number (NIN)", fields: [{ type: "input", label: "Identification Number" }] },
200
+ // { id: 2, name: "Bank Verification Number Advanced (BVN)", fields: [{ type: "input", label: "Bank Verification Number" }] }
201
+ // ]
202
+ // }
203
+ ```
204
+
205
+ Use `config.channels` to build your own country/ID type selector. Each channel's `fields` array tells you what inputs to render.
206
+
207
+ ### Verify Identity (Data Verification)
208
+
209
+ For BVN/NIN — validates against the government database:
210
+
211
+ ```typescript
212
+ const result = await sdk.onboarding.verify({
213
+ identity_type_id: 8, // Channel ID from config.channels
214
+ fields: { identification_number: "12345678901" }, // Field keys from channel's fields definition
215
+ });
216
+
217
+ if (result.status === "success") {
218
+ console.log(result.data.identity_check_id); // Save this — needed for face matching in liveness
219
+ console.log(result.data.first_name); // "Amara"
220
+ }
221
+ ```
222
+
223
+ ### Liveness Check (with built-in camera UI)
224
+
225
+ Mount the SDK's liveness UI into any container element:
226
+
227
+ ```typescript
228
+ const container = document.getElementById("liveness-container");
229
+
230
+ const result = await sdk.liveness.startCheck(container, {
231
+ identifier: "12345678901", // The ID number entered by the user
232
+ identifier_type: "NIN",
233
+ country: "NG",
234
+ id_file: documentBlob, // Optional: document photo (for document flow)
235
+ identity_check: identityCheckId, // Optional: from verify response (for data flow)
236
+ }, ["BLINK", "TURN_LEFT", "OPEN_MOUTH"]);
237
+
238
+ console.log(result.status); // "processing"
239
+ ```
240
+
241
+ The SDK handles camera access, face detection, action prompts, video recording, and submission.
242
+
243
+ ### Liveness Check (fully manual)
244
+
245
+ If you want to handle camera and recording yourself:
246
+
247
+ ```typescript
248
+ // 1. Create entry
249
+ const entry = await sdk.liveness.create({
250
+ identifier: "A12345678",
251
+ identifier_type: "Passport",
252
+ country: "US",
253
+ challenge_actions: ["BLINK", "TURN_LEFT", "OPEN_MOUTH"],
254
+ autoshot_file: selfieBlob, // Captured selfie (JPEG/PNG, max 5MB)
255
+ id_file: passportPhotoBlob, // Optional: document photo
256
+ identity_check: identityCheckId, // Optional: from verify response
257
+ });
258
+
259
+ // 2. Run your own camera/detection/recording UI
260
+ // ...
261
+
262
+ // 3. Submit the recorded video + snapshot
263
+ const result = await sdk.liveness.submit(entry.id, videoBlob, snapshotBlob);
264
+ // result.status === "processing"
265
+ ```
266
+
267
+ **After submission, the session is revoked.** Each `clientId` can only complete one full verification.
268
+
269
+ ---
270
+
271
+ ## Receiving Results (Webhook)
272
+
273
+ Verification is processed asynchronously. After the user submits, the backend runs face matching using AI (DeepFace with RetinaFace detection) and delivers results via webhook to the URL configured in your SDK Config.
274
+
275
+ ### Webhook Payload — Data Verification (BVN/NIN)
276
+
277
+ ```json
278
+ POST https://your-server.com/webhook
279
+ Content-Type: application/json
280
+ X-Adhere-Signature: <hmac-sha256-hex>
281
+
282
+ {
283
+ "event": "liveness.completed",
284
+ "data": {
285
+ "entry_id": 42,
286
+ "status": "passed",
287
+ "is_verified": true,
288
+ "match_score": 0.15,
289
+ "confidence_percentage": 62.5,
290
+ "identity_face_match": {
291
+ "verified": true,
292
+ "distance": 0.12,
293
+ "threshold": 0.4,
294
+ "confidence_percentage": 70.0
295
+ },
296
+ "metadata": { "ip_address": "...", "user_agent": "..." }
297
+ }
298
+ }
299
+ ```
300
+
301
+ ### Webhook Payload — Document Verification (Passport/License)
302
+
303
+ ```json
304
+ {
305
+ "event": "liveness.completed",
306
+ "data": {
307
+ "entry_id": 43,
308
+ "status": "passed",
309
+ "is_verified": true,
310
+ "match_score": 0.18,
311
+ "confidence_percentage": 55.0,
312
+ "document_verification": {
313
+ "status": "verified",
314
+ "document_type": "passport",
315
+ "is_expired": false,
316
+ "face_match_verified": true,
317
+ "face_match_score": 0.22,
318
+ "extracted_name": "AMARA OKAFOR",
319
+ "extracted_expiry_date": "2030-06-15"
320
+ },
321
+ "metadata": { "ip_address": "...", "user_agent": "..." }
322
+ }
323
+ }
324
+ ```
325
+
326
+ ### Understanding the Scores
327
+
328
+ - **`match_score`** (distance) — Raw distance between face embeddings. Lower = more similar. Threshold is 0.4 (Facenet default).
329
+ - **`confidence_percentage`** — Human-readable score: how close the match is to perfect. 100% = identical, 0% = at the threshold boundary.
330
+ - **`identity_face_match`** — Only present in data verification (BVN/NIN). Confirms the person doing liveness matches the face on the government database.
331
+ - **`document_verification`** — Only present in document verification. Includes OCR results and document-to-selfie face match.
332
+
333
+ ### Possible Statuses
334
+
335
+ | Status | Meaning |
336
+ |--------|---------|
337
+ | `passed` | All checks passed — person is verified |
338
+ | `failed` | Face mismatch, expired document, or processing error |
339
+ | `processing` | Still being processed (rare — usually completes in seconds) |
340
+
341
+ ### Verify Signature
342
+
343
+ **Always verify the webhook signature** to prevent spoofing:
344
+
345
+ ```javascript
346
+ const crypto = require("crypto");
347
+
348
+ app.post("/webhook/smartcomply", express.json(), (req, res) => {
349
+ const signature = req.headers["x-adhere-signature"];
350
+ const secret = process.env.WEBHOOK_SECRET.replace(/-/g, "");
351
+ const expected = crypto
352
+ .createHmac("sha256", secret)
353
+ .update(JSON.stringify(req.body))
354
+ .digest("hex");
355
+
356
+ const isValid = crypto.timingSafeEqual(
357
+ Buffer.from(signature, "hex"),
358
+ Buffer.from(expected, "hex")
359
+ );
360
+
361
+ if (!isValid) return res.status(401).send("Bad signature");
362
+
363
+ // Handle the event
364
+ const { event, data } = req.body;
365
+
366
+ if (event === "liveness.completed" && data.is_verified) {
367
+ // ✓ User is verified — update your database
368
+ markUserAsVerified(data.entry_id);
369
+ }
370
+
371
+ res.json({ received: true });
372
+ });
373
+ ```
374
+
375
+ ---
376
+
377
+ ## Challenge Actions
378
+
379
+ | Action | What the user does |
380
+ |--------|-------------------|
381
+ | `BLINK` | Blink both eyes |
382
+ | `TURN_LEFT` | Turn head to the left |
383
+ | `TURN_RIGHT` | Turn head to the right |
384
+ | `TURN_HEAD` | Turn head in any direction |
385
+ | `OPEN_MOUTH` | Open mouth wide |
386
+
387
+ Actions must be **UPPERCASE**. We recommend using 3 actions: `["BLINK", "TURN_LEFT", "OPEN_MOUTH"]`.
388
+
389
+ ---
390
+
391
+ ## Error Handling
392
+
393
+ ```typescript
394
+ import { SDKError, AuthError, NetworkError } from "smartcomply-web-sdk";
395
+
396
+ try {
397
+ await sdk.createSession();
398
+ } catch (err) {
399
+ if (err instanceof AuthError) {
400
+ // 401: Invalid API key, expired session, or disabled branch
401
+ } else if (err instanceof NetworkError) {
402
+ // No internet, timeout, DNS failure
403
+ } else if (err instanceof SDKError) {
404
+ // Backend error: validation, insufficient balance, etc.
405
+ console.log(err.statusCode); // HTTP status
406
+ console.log(err.errorCode); // Machine-readable code
407
+ console.log(err.errorData); // Additional error details
408
+ }
409
+ }
410
+ ```
411
+
412
+ | Error Code | HTTP | Meaning |
413
+ |------------|------|---------|
414
+ | `INVALID_API_KEY` | 401 | Bad API key or expired session |
415
+ | `SDK_CONFIG_NOT_FOUND` | 404 | Invalid `clientId` |
416
+ | `VALIDATION_ERROR` | 400 | Missing or invalid fields |
417
+ | `INSUFFICIENT_BALANCE` | 402 | Top up your wallet |
418
+
419
+ ---
420
+
421
+ ## Theming
422
+
423
+ The widget automatically uses the theme from your SDK Config (set in the Adhere dashboard):
424
+
425
+ - `default` — Clean blue on white
426
+ - `midnight_blue` — Dark mode with blue accents
427
+ - `sunset_gold` — Warm gold on cream
428
+ - `forest_emerald` — Green on mint
429
+
430
+ No client-side theme configuration required.
431
+
432
+ ---
433
+
434
+ ## Browser Support
435
+
436
+ Requires camera access (`getUserMedia`), video recording (`MediaRecorder`), and WebAssembly.
437
+
438
+ | Browser | Version |
439
+ |---------|---------|
440
+ | Chrome | 80+ |
441
+ | Firefox | 75+ |
442
+ | Safari | 14+ |
443
+ | Edge | 80+ |
444
+
445
+ ---
446
+
447
+ ## Prerequisites
448
+
449
+ 1. **Adhere account** — Sign up at the dashboard
450
+ 2. **API Key** — Format: `pk_live_...` (from dashboard → API Keys)
451
+ 3. **SDK Config** — Create in dashboard with your brand name, theme, verification types, channels, and webhook URL
452
+ 4. **Client ID** — The UUID shown on your SDK Config
453
+ 5. **Funded wallet** — Each verification deducts from your balance
454
+ 6. **Webhook endpoint** — A URL on your server to receive verification results
455
+
456
+ ---
457
+
458
+ ## TypeScript Support
459
+
460
+ Full TypeScript definitions are included. Key types:
461
+
462
+ ```typescript
463
+ import type {
464
+ FlowOptions,
465
+ FlowResult,
466
+ SDKConfig,
467
+ SDKInitConfig,
468
+ SessionResponse,
469
+ VerifyIdentityResponse,
470
+ LivenessCreateResponse,
471
+ LivenessSubmitResponse,
472
+ ChallengeAction,
473
+ LivenessWebhookPayload,
474
+ ApiResponse,
475
+ } from "smartcomply-web-sdk";
476
+ ```
477
+
478
+ ---
479
+
480
+ ## License
481
+
482
+ ISC
@@ -0,0 +1,26 @@
1
+ import { FaceDetectionResult } from "./FaceDetector";
2
+ export interface ActionState {
3
+ action: string;
4
+ detected: boolean;
5
+ active: boolean;
6
+ confidence: number;
7
+ }
8
+ /**
9
+ * Sequential action detector aligned with backend's 5 UPPERCASE actions:
10
+ * BLINK, TURN_LEFT, TURN_RIGHT, TURN_HEAD, OPEN_MOUTH
11
+ */
12
+ export declare class ActionDetector {
13
+ private requiredActions;
14
+ private completedActions;
15
+ private holdCounters;
16
+ constructor(requiredActions: string[]);
17
+ check(result: FaceDetectionResult): ActionState[];
18
+ getCurrentAction(): string | null;
19
+ private buildStates;
20
+ private getConfidence;
21
+ private getHeadYaw;
22
+ allCompleted(): boolean;
23
+ completedCount(): number;
24
+ reset(): void;
25
+ }
26
+ //# sourceMappingURL=ActionDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionDetector.d.ts","sourceRoot":"","sources":["../../src/camera/ActionDetector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD;;;GAGG;AACH,qBAAa,cAAc;IAIb,OAAO,CAAC,eAAe;IAHnC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,YAAY,CAA6B;gBAE7B,eAAe,EAAE,MAAM,EAAE;IAE7C,KAAK,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,EAAE;IAgCjD,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAOjC,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,UAAU;IAKlB,YAAY,IAAI,OAAO;IAIvB,cAAc,IAAI,MAAM;IAIxB,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActionDetector = void 0;
4
+ /**
5
+ * Per-action detection tuning.
6
+ * - threshold : minimum confidence score required to start counting.
7
+ * - holdFrames: consecutive frames above threshold before action is confirmed.
8
+ *
9
+ * BLINK is a fast reflex (~100-200ms) so we use a low threshold and only 2
10
+ * frames. Head-turn and mouth-open are sustained movements so they need a
11
+ * slightly longer hold to avoid false positives.
12
+ */
13
+ const ACTION_CONFIGS = {
14
+ BLINK: { threshold: 0.30, holdFrames: 2 },
15
+ TURN_LEFT: { threshold: 0.45, holdFrames: 6 },
16
+ TURN_RIGHT: { threshold: 0.45, holdFrames: 6 },
17
+ TURN_HEAD: { threshold: 0.45, holdFrames: 6 },
18
+ OPEN_MOUTH: { threshold: 0.40, holdFrames: 3 },
19
+ };
20
+ const DEFAULT_CONFIG = { threshold: 0.55, holdFrames: 8 };
21
+ /**
22
+ * Sequential action detector aligned with backend's 5 UPPERCASE actions:
23
+ * BLINK, TURN_LEFT, TURN_RIGHT, TURN_HEAD, OPEN_MOUTH
24
+ */
25
+ class ActionDetector {
26
+ constructor(requiredActions) {
27
+ this.requiredActions = requiredActions;
28
+ this.completedActions = new Set();
29
+ this.holdCounters = new Map();
30
+ }
31
+ check(result) {
32
+ const currentAction = this.getCurrentAction();
33
+ if (!result.faceDetected) {
34
+ return this.buildStates(currentAction, 0);
35
+ }
36
+ const bs = result.blendshapes;
37
+ // Only check the current active action (sequential flow)
38
+ if (currentAction && !this.completedActions.has(currentAction)) {
39
+ const conf = this.getConfidence(currentAction, bs, result.transformMatrix);
40
+ const cfg = ACTION_CONFIGS[currentAction] ?? DEFAULT_CONFIG;
41
+ if (conf >= cfg.threshold) {
42
+ const count = (this.holdCounters.get(currentAction) || 0) + 1;
43
+ this.holdCounters.set(currentAction, count);
44
+ if (count >= cfg.holdFrames) {
45
+ this.completedActions.add(currentAction);
46
+ this.holdCounters.delete(currentAction);
47
+ }
48
+ }
49
+ else {
50
+ this.holdCounters.set(currentAction, 0);
51
+ }
52
+ return this.buildStates(currentAction, conf);
53
+ }
54
+ return this.buildStates(currentAction, 0);
55
+ }
56
+ getCurrentAction() {
57
+ for (const action of this.requiredActions) {
58
+ if (!this.completedActions.has(action))
59
+ return action;
60
+ }
61
+ return null;
62
+ }
63
+ buildStates(currentAction, currentConf) {
64
+ return this.requiredActions.map((action) => ({
65
+ action,
66
+ detected: this.completedActions.has(action),
67
+ active: action === currentAction,
68
+ confidence: action === currentAction ? currentConf : 0,
69
+ }));
70
+ }
71
+ getConfidence(action, bs, matrix) {
72
+ switch (action) {
73
+ case "BLINK": {
74
+ const left = bs.get("eyeBlinkLeft") || 0;
75
+ const right = bs.get("eyeBlinkRight") || 0;
76
+ return (left + right) / 2;
77
+ }
78
+ case "TURN_LEFT": {
79
+ const yaw = this.getHeadYaw(matrix);
80
+ return yaw < -12 ? Math.min(1, Math.abs(yaw) / 30) : 0;
81
+ }
82
+ case "TURN_RIGHT": {
83
+ const yaw = this.getHeadYaw(matrix);
84
+ return yaw > 12 ? Math.min(1, yaw / 30) : 0;
85
+ }
86
+ case "TURN_HEAD": {
87
+ // Any significant yaw (left OR right) counts
88
+ const yaw = Math.abs(this.getHeadYaw(matrix));
89
+ return yaw > 15 ? Math.min(1, yaw / 30) : 0;
90
+ }
91
+ case "OPEN_MOUTH":
92
+ return bs.get("jawOpen") || 0;
93
+ default:
94
+ return 0;
95
+ }
96
+ }
97
+ getHeadYaw(matrix) {
98
+ if (!matrix || matrix.length < 16)
99
+ return 0;
100
+ return Math.atan2(matrix[8], matrix[0]) * (180 / Math.PI);
101
+ }
102
+ allCompleted() {
103
+ return this.requiredActions.every((a) => this.completedActions.has(a));
104
+ }
105
+ completedCount() {
106
+ return this.completedActions.size;
107
+ }
108
+ reset() {
109
+ this.completedActions.clear();
110
+ this.holdCounters.clear();
111
+ }
112
+ }
113
+ exports.ActionDetector = ActionDetector;
114
+ //# sourceMappingURL=ActionDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionDetector.js","sourceRoot":"","sources":["../../src/camera/ActionDetector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare class CameraManager {
2
+ private stream;
3
+ open(constraints?: MediaStreamConstraints): Promise<MediaStream>;
4
+ getStream(): MediaStream | null;
5
+ stop(): void;
6
+ }
7
+ //# sourceMappingURL=CameraManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAEpC,IAAI,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAiBtE,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B,IAAI,IAAI,IAAI;CAMb"}