@finclusionaibuild/liveness-sdk 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 +843 -0
- package/dist/index.cjs.js +85288 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +174 -0
- package/dist/index.esm.js +85256 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/styles.css +7224 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
# @finclusion/liveness-sdk
|
|
2
|
+
|
|
3
|
+
A comprehensive Face Liveness Detection SDK for React applications. This SDK provides biometric face verification capabilities with a mobile-responsive UI.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Face Liveness Detection** - Real-time face verification using AWS Amplify
|
|
8
|
+
- ✅ **Mobile Responsive** - Optimized for mobile, tablet, and desktop devices
|
|
9
|
+
- ✅ **Multiple Components** - `LivenessDetector`, `ExternalLiveness`, `PaymentLiveness`
|
|
10
|
+
- ✅ **TypeScript Support** - Full TypeScript definitions included
|
|
11
|
+
- ✅ **Error Handling** - Comprehensive error handling and retry mechanisms
|
|
12
|
+
- ✅ **Customizable** - Custom logos, instruction text, and styling
|
|
13
|
+
- ✅ **React Native Ready** - Works with React Native via WebView
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @finclusion/liveness-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Peer Dependencies
|
|
22
|
+
|
|
23
|
+
The SDK requires these peer dependencies to be installed in your project:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install react@^18.0.0 react-dom@^18.0.0 @tanstack/react-query@^5.39.0
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Prerequisites
|
|
30
|
+
|
|
31
|
+
1. **React Query Setup**: You must wrap your application (or the component using the SDK) with `QueryClientProvider`:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
35
|
+
|
|
36
|
+
const queryClient = new QueryClient();
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<QueryClientProvider client={queryClient}>
|
|
41
|
+
{/* Your app */}
|
|
42
|
+
</QueryClientProvider>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **CSS Import**: You must import the SDK's stylesheet:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import "@finclusion/liveness-sdk/styles.css";
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start (Web)
|
|
54
|
+
|
|
55
|
+
### Basic Example
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import React from "react";
|
|
59
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
60
|
+
import { LivenessDetector } from "@finclusion/liveness-sdk";
|
|
61
|
+
import "@finclusion/liveness-sdk/styles.css";
|
|
62
|
+
|
|
63
|
+
const queryClient = new QueryClient();
|
|
64
|
+
|
|
65
|
+
function App() {
|
|
66
|
+
const config = {
|
|
67
|
+
apiBaseUrl: "https://api.example.com",
|
|
68
|
+
getAuthToken: () => localStorage.getItem("authToken"),
|
|
69
|
+
endpoints: {
|
|
70
|
+
createSession: "/api/liveliness",
|
|
71
|
+
verify: "/api/liveliness/{sessionId}",
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleComplete = () => {
|
|
76
|
+
console.log("Liveness check completed!");
|
|
77
|
+
// Navigate to next step
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<QueryClientProvider client={queryClient}>
|
|
82
|
+
<LivenessDetector
|
|
83
|
+
config={config}
|
|
84
|
+
token={localStorage.getItem("authToken")}
|
|
85
|
+
gotoNext={handleComplete}
|
|
86
|
+
/>
|
|
87
|
+
</QueryClientProvider>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Advanced Example with Customization
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import React, { useState } from "react";
|
|
96
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
97
|
+
import {
|
|
98
|
+
LivenessDetector,
|
|
99
|
+
type LivenessConfig,
|
|
100
|
+
} from "@finclusion/liveness-sdk";
|
|
101
|
+
import "@finclusion/liveness-sdk/styles.css";
|
|
102
|
+
|
|
103
|
+
const queryClient = new QueryClient();
|
|
104
|
+
|
|
105
|
+
function AdvancedLivenessExample() {
|
|
106
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
107
|
+
const [error, setError] = useState<string | null>(null);
|
|
108
|
+
|
|
109
|
+
const config: LivenessConfig = {
|
|
110
|
+
apiBaseUrl: process.env.REACT_APP_API_BASE_URL || "https://api.example.com",
|
|
111
|
+
getAuthToken: () => {
|
|
112
|
+
// Get token from your auth system
|
|
113
|
+
return localStorage.getItem("authToken");
|
|
114
|
+
},
|
|
115
|
+
endpoints: {
|
|
116
|
+
createSession: "/api/v1/liveliness/create",
|
|
117
|
+
verify: "/api/v1/liveliness/{sessionId}/verify",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleComplete = async () => {
|
|
122
|
+
setIsLoading(true);
|
|
123
|
+
try {
|
|
124
|
+
// Handle successful liveness check
|
|
125
|
+
console.log("Liveness verification successful!");
|
|
126
|
+
// Proceed to next step in your flow
|
|
127
|
+
window.location.href = "/next-step";
|
|
128
|
+
} catch (err) {
|
|
129
|
+
setError("Failed to process liveness result");
|
|
130
|
+
console.error(err);
|
|
131
|
+
} finally {
|
|
132
|
+
setIsLoading(false);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<QueryClientProvider client={queryClient}>
|
|
138
|
+
<div className="container mx-auto p-4">
|
|
139
|
+
{error && (
|
|
140
|
+
<div className="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
|
141
|
+
{error}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
<LivenessDetector
|
|
146
|
+
config={config}
|
|
147
|
+
token={localStorage.getItem("authToken")}
|
|
148
|
+
gotoNext={handleComplete}
|
|
149
|
+
logoUrl="/custom-logo.svg" // Optional: Custom logo
|
|
150
|
+
instructionText="Please position your face in the center of the frame and follow the instructions."
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</QueryClientProvider>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Error Handling Example
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import React, { useState } from "react";
|
|
162
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
163
|
+
import { LivenessDetector, type LivenessError } from "@finclusion/liveness-sdk";
|
|
164
|
+
import "@finclusion/liveness-sdk/styles.css";
|
|
165
|
+
|
|
166
|
+
const queryClient = new QueryClient();
|
|
167
|
+
|
|
168
|
+
function ErrorHandlingExample() {
|
|
169
|
+
const [error, setError] = useState<LivenessError | null>(null);
|
|
170
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
171
|
+
|
|
172
|
+
const config = {
|
|
173
|
+
apiBaseUrl: "https://api.example.com",
|
|
174
|
+
getAuthToken: () => localStorage.getItem("authToken"),
|
|
175
|
+
endpoints: {
|
|
176
|
+
createSession: "/api/liveliness",
|
|
177
|
+
verify: "/api/liveliness/{sessionId}",
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleComplete = () => {
|
|
182
|
+
console.log("Success!");
|
|
183
|
+
setError(null);
|
|
184
|
+
setRetryCount(0);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleError = (err: LivenessError) => {
|
|
188
|
+
setError(err);
|
|
189
|
+
console.error("Liveness error:", err);
|
|
190
|
+
|
|
191
|
+
// Handle specific error types
|
|
192
|
+
if (err.response?.status === 403) {
|
|
193
|
+
// Handle unauthorized
|
|
194
|
+
alert("Session expired. Please login again.");
|
|
195
|
+
} else if (err.response?.status === 400) {
|
|
196
|
+
// Handle bad request
|
|
197
|
+
if (retryCount < 3) {
|
|
198
|
+
setRetryCount((prev) => prev + 1);
|
|
199
|
+
// Component will retry automatically
|
|
200
|
+
} else {
|
|
201
|
+
alert("Maximum retry attempts reached. Please contact support.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<QueryClientProvider client={queryClient}>
|
|
208
|
+
<div>
|
|
209
|
+
{error && (
|
|
210
|
+
<div className="mb-4 p-4 bg-yellow-100 border border-yellow-400 rounded">
|
|
211
|
+
<p className="font-semibold">Error occurred:</p>
|
|
212
|
+
<p>{error.message || "Unknown error"}</p>
|
|
213
|
+
<p className="text-sm mt-2">Retry count: {retryCount}/3</p>
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
<LivenessDetector
|
|
218
|
+
config={config}
|
|
219
|
+
token={localStorage.getItem("authToken")}
|
|
220
|
+
gotoNext={handleComplete}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
</QueryClientProvider>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Component API
|
|
229
|
+
|
|
230
|
+
### LivenessDetector
|
|
231
|
+
|
|
232
|
+
Main liveness detection component with full verification flow.
|
|
233
|
+
|
|
234
|
+
**Props:**
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface LivenessDetectorProps {
|
|
238
|
+
config: LivenessConfig; // Required: Configuration object
|
|
239
|
+
token?: string | null; // Optional: Authentication token
|
|
240
|
+
gotoNext: () => void; // Required: Callback on success
|
|
241
|
+
logoUrl?: string; // Optional: Custom logo URL (defaults to iDCERTIFY logo)
|
|
242
|
+
instructionText?: string; // Optional: Custom instruction text
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Example:**
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<LivenessDetector
|
|
250
|
+
config={config}
|
|
251
|
+
token={authToken}
|
|
252
|
+
gotoNext={() => console.log("Complete!")}
|
|
253
|
+
logoUrl="/my-logo.svg"
|
|
254
|
+
instructionText="Custom instructions here"
|
|
255
|
+
/>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### ExternalLiveness
|
|
259
|
+
|
|
260
|
+
Standalone liveness component for external pages or separate flows.
|
|
261
|
+
|
|
262
|
+
**Props:**
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
interface ExternalLivenessProps {
|
|
266
|
+
config: LivenessConfig;
|
|
267
|
+
token?: string | null;
|
|
268
|
+
gotoNext: () => void;
|
|
269
|
+
region?: string; // Optional: AWS region (default: 'us-east-1')
|
|
270
|
+
logoUrl?: string;
|
|
271
|
+
instructionText?: string;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Example:**
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
<ExternalLiveness
|
|
279
|
+
config={config}
|
|
280
|
+
token={authToken}
|
|
281
|
+
gotoNext={handleComplete}
|
|
282
|
+
region="us-west-2"
|
|
283
|
+
/>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### PaymentLiveness
|
|
287
|
+
|
|
288
|
+
Payment verification variant that compares face with user profile.
|
|
289
|
+
|
|
290
|
+
**Props:**
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface PaymentLivenessProps {
|
|
294
|
+
config: LivenessConfig;
|
|
295
|
+
token?: string | null;
|
|
296
|
+
gotoNext: () => void;
|
|
297
|
+
handleClose?: () => void; // Optional: Close handler
|
|
298
|
+
logoUrl?: string;
|
|
299
|
+
instructionText?: string;
|
|
300
|
+
onSendOtp?: () => Promise<void>; // Optional: OTP sending callback
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Example:**
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<PaymentLiveness
|
|
308
|
+
config={config}
|
|
309
|
+
token={authToken}
|
|
310
|
+
gotoNext={handleComplete}
|
|
311
|
+
handleClose={() => setShowModal(false)}
|
|
312
|
+
onSendOtp={async () => {
|
|
313
|
+
await sendOtpToUser();
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Configuration
|
|
319
|
+
|
|
320
|
+
### LivenessConfig
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
interface LivenessConfig {
|
|
324
|
+
apiBaseUrl: string; // Base URL for your API
|
|
325
|
+
getAuthToken: () => string | null; // Function to get auth token
|
|
326
|
+
includeLivenessGuide?: boolean; // Optional: Show guide modal
|
|
327
|
+
endpoints?: {
|
|
328
|
+
createSession?: string; // Optional: Custom endpoint (default: '/api/liveliness')
|
|
329
|
+
verify?: string; // Optional: Custom verify endpoint
|
|
330
|
+
compareWithUser?: string; // Optional: Custom compare endpoint (PaymentLiveness)
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Example:**
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
const config: LivenessConfig = {
|
|
339
|
+
apiBaseUrl: "https://api.example.com",
|
|
340
|
+
getAuthToken: () => {
|
|
341
|
+
// Your token retrieval logic
|
|
342
|
+
return localStorage.getItem("token");
|
|
343
|
+
},
|
|
344
|
+
includeLivenessGuide: true, // Show pre-liveness guide modal
|
|
345
|
+
endpoints: {
|
|
346
|
+
createSession: "/custom/liveliness/create",
|
|
347
|
+
verify: "/custom/liveliness/{sessionId}/verify",
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## React Native Usage (WebView)
|
|
353
|
+
|
|
354
|
+
To use this SDK in React Native applications, you'll need to embed it in a WebView component. Here's a complete example:
|
|
355
|
+
|
|
356
|
+
### Installation
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
npm install react-native-webview
|
|
360
|
+
# For iOS, you may also need:
|
|
361
|
+
cd ios && pod install
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Complete React Native Example
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import React, { useRef, useState } from "react";
|
|
368
|
+
import { View, StyleSheet, Alert, Platform } from "react-native";
|
|
369
|
+
import { WebView } from "react-native-webview";
|
|
370
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
371
|
+
|
|
372
|
+
const queryClient = new QueryClient();
|
|
373
|
+
|
|
374
|
+
// HTML template with SDK embedded
|
|
375
|
+
const getHTML = (token: string, apiBaseUrl: string) => `
|
|
376
|
+
<!DOCTYPE html>
|
|
377
|
+
<html lang="en">
|
|
378
|
+
<head>
|
|
379
|
+
<meta charset="UTF-8">
|
|
380
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
381
|
+
<title>Liveness Check</title>
|
|
382
|
+
<style>
|
|
383
|
+
body {
|
|
384
|
+
margin: 0;
|
|
385
|
+
padding: 0;
|
|
386
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
}
|
|
389
|
+
#root {
|
|
390
|
+
width: 100vw;
|
|
391
|
+
height: 100vh;
|
|
392
|
+
}
|
|
393
|
+
</style>
|
|
394
|
+
</head>
|
|
395
|
+
<body>
|
|
396
|
+
<div id="root"></div>
|
|
397
|
+
|
|
398
|
+
<!-- React and ReactDOM -->
|
|
399
|
+
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
|
400
|
+
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
401
|
+
|
|
402
|
+
<!-- React Query -->
|
|
403
|
+
<script src="https://unpkg.com/@tanstack/react-query@5/build/umd/index.production.min.js"></script>
|
|
404
|
+
|
|
405
|
+
<!-- SDK CSS -->
|
|
406
|
+
<link rel="stylesheet" href="https://unpkg.com/@finclusion/liveness-sdk@1.0.0/dist/styles.css">
|
|
407
|
+
|
|
408
|
+
<!-- SDK Bundle -->
|
|
409
|
+
<script src="https://unpkg.com/@finclusion/liveness-sdk@1.0.0/dist/index.esm.js" type="module"></script>
|
|
410
|
+
|
|
411
|
+
<script type="module">
|
|
412
|
+
const { QueryClient, QueryClientProvider } = TanStackReactQuery;
|
|
413
|
+
const { LivenessDetector } = window.FinclusionLivenessSDK;
|
|
414
|
+
const { createRoot } = ReactDOM;
|
|
415
|
+
|
|
416
|
+
const queryClient = new QueryClient({
|
|
417
|
+
defaultOptions: {
|
|
418
|
+
queries: {
|
|
419
|
+
retry: 2,
|
|
420
|
+
refetchOnWindowFocus: false,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const config = {
|
|
426
|
+
apiBaseUrl: '${apiBaseUrl}',
|
|
427
|
+
getAuthToken: () => '${token}',
|
|
428
|
+
endpoints: {
|
|
429
|
+
createSession: '/api/liveliness',
|
|
430
|
+
verify: '/api/liveliness/{sessionId}',
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const handleComplete = () => {
|
|
435
|
+
// Send message to React Native
|
|
436
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
437
|
+
type: 'LIVENESS_COMPLETE',
|
|
438
|
+
success: true,
|
|
439
|
+
}));
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const App = () => {
|
|
443
|
+
return React.createElement(QueryClientProvider, { client: queryClient },
|
|
444
|
+
React.createElement(LivenessDetector, {
|
|
445
|
+
config: config,
|
|
446
|
+
token: '${token}',
|
|
447
|
+
gotoNext: handleComplete,
|
|
448
|
+
})
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const root = createRoot(document.getElementById('root'));
|
|
453
|
+
root.render(React.createElement(App));
|
|
454
|
+
</script>
|
|
455
|
+
</body>
|
|
456
|
+
</html>
|
|
457
|
+
`;
|
|
458
|
+
|
|
459
|
+
function LivenessScreen() {
|
|
460
|
+
const webViewRef = useRef<WebView>(null);
|
|
461
|
+
const [loading, setLoading] = useState(true);
|
|
462
|
+
|
|
463
|
+
const authToken = "your-auth-token-here"; // Get from your auth system
|
|
464
|
+
const apiBaseUrl = "https://api.example.com";
|
|
465
|
+
|
|
466
|
+
const handleMessage = (event: any) => {
|
|
467
|
+
try {
|
|
468
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
469
|
+
|
|
470
|
+
switch (data.type) {
|
|
471
|
+
case "LIVENESS_COMPLETE":
|
|
472
|
+
Alert.alert("Success", "Liveness check completed!", [
|
|
473
|
+
{
|
|
474
|
+
text: "OK",
|
|
475
|
+
onPress: () => {
|
|
476
|
+
// Navigate to next screen
|
|
477
|
+
navigation.navigate("NextScreen");
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
]);
|
|
481
|
+
break;
|
|
482
|
+
case "LIVENESS_ERROR":
|
|
483
|
+
Alert.alert("Error", data.message || "Liveness check failed");
|
|
484
|
+
break;
|
|
485
|
+
default:
|
|
486
|
+
console.log("WebView message:", data);
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error("Error parsing WebView message:", error);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<View style={styles.container}>
|
|
495
|
+
<WebView
|
|
496
|
+
ref={webViewRef}
|
|
497
|
+
source={{ html: getHTML(authToken, apiBaseUrl) }}
|
|
498
|
+
onMessage={handleMessage}
|
|
499
|
+
onLoadEnd={() => setLoading(false)}
|
|
500
|
+
style={styles.webview}
|
|
501
|
+
javaScriptEnabled={true}
|
|
502
|
+
domStorageEnabled={true}
|
|
503
|
+
mediaPlaybackRequiresUserAction={false}
|
|
504
|
+
allowsInlineMediaPlayback={true}
|
|
505
|
+
// Camera permissions
|
|
506
|
+
permissions={["camera", "microphone"]}
|
|
507
|
+
// iOS specific
|
|
508
|
+
allowsBackForwardNavigationGestures={false}
|
|
509
|
+
// Android specific
|
|
510
|
+
mixedContentMode="always"
|
|
511
|
+
/>
|
|
512
|
+
</View>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const styles = StyleSheet.create({
|
|
517
|
+
container: {
|
|
518
|
+
flex: 1,
|
|
519
|
+
},
|
|
520
|
+
webview: {
|
|
521
|
+
flex: 1,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
export default LivenessScreen;
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### React Native with Local Bundle (Recommended)
|
|
529
|
+
|
|
530
|
+
For better performance and offline support, bundle the SDK locally:
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
import React, { useRef } from "react";
|
|
534
|
+
import { View, StyleSheet } from "react-native";
|
|
535
|
+
import { WebView } from "react-native-webview";
|
|
536
|
+
import { Asset } from "expo-asset"; // or react-native-fs for bare React Native
|
|
537
|
+
|
|
538
|
+
function LivenessScreen() {
|
|
539
|
+
const webViewRef = useRef<WebView>(null);
|
|
540
|
+
|
|
541
|
+
// Load SDK bundle from local assets
|
|
542
|
+
const [sdkHtml, setSdkHtml] = useState("");
|
|
543
|
+
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
// Load your bundled HTML file
|
|
546
|
+
// This should include React, React Query, and the SDK bundle
|
|
547
|
+
loadSDKHTML().then(setSdkHtml);
|
|
548
|
+
}, []);
|
|
549
|
+
|
|
550
|
+
return (
|
|
551
|
+
<View style={styles.container}>
|
|
552
|
+
<WebView
|
|
553
|
+
ref={webViewRef}
|
|
554
|
+
source={{ html: sdkHtml }}
|
|
555
|
+
onMessage={handleMessage}
|
|
556
|
+
style={styles.webview}
|
|
557
|
+
javaScriptEnabled={true}
|
|
558
|
+
domStorageEnabled={true}
|
|
559
|
+
mediaPlaybackRequiresUserAction={false}
|
|
560
|
+
allowsInlineMediaPlayback={true}
|
|
561
|
+
/>
|
|
562
|
+
</View>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### React Native Permissions Setup
|
|
568
|
+
|
|
569
|
+
#### iOS (Info.plist)
|
|
570
|
+
|
|
571
|
+
Add camera and microphone permissions:
|
|
572
|
+
|
|
573
|
+
```xml
|
|
574
|
+
<key>NSCameraUsageDescription</key>
|
|
575
|
+
<string>We need access to your camera for face verification</string>
|
|
576
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
577
|
+
<string>We need access to your microphone for face verification</string>
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Android (AndroidManifest.xml)
|
|
581
|
+
|
|
582
|
+
Add camera and internet permissions:
|
|
583
|
+
|
|
584
|
+
```xml
|
|
585
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
586
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
587
|
+
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
|
588
|
+
<uses-feature android:name="android.hardware.camera.front" android:required="true" />
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### React Native Error Handling
|
|
592
|
+
|
|
593
|
+
```tsx
|
|
594
|
+
const handleMessage = (event: any) => {
|
|
595
|
+
try {
|
|
596
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
597
|
+
|
|
598
|
+
if (data.type === "LIVENESS_ERROR") {
|
|
599
|
+
const error = data.error;
|
|
600
|
+
|
|
601
|
+
if (error.response?.status === 403) {
|
|
602
|
+
// Handle unauthorized - redirect to login
|
|
603
|
+
navigation.navigate("Login");
|
|
604
|
+
} else if (error.response?.status === 400) {
|
|
605
|
+
// Handle bad request
|
|
606
|
+
Alert.alert("Verification Failed", "Please try again");
|
|
607
|
+
} else {
|
|
608
|
+
// Generic error
|
|
609
|
+
Alert.alert("Error", error.message || "Something went wrong");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
} catch (error) {
|
|
613
|
+
console.error("Error handling message:", error);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// Handle WebView errors
|
|
618
|
+
const handleError = (syntheticEvent: any) => {
|
|
619
|
+
const { nativeEvent } = syntheticEvent;
|
|
620
|
+
console.error("WebView error: ", nativeEvent);
|
|
621
|
+
Alert.alert("Error", "Failed to load liveness check");
|
|
622
|
+
};
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
## TypeScript Types
|
|
626
|
+
|
|
627
|
+
The SDK exports comprehensive TypeScript types:
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
import type {
|
|
631
|
+
LivenessConfig,
|
|
632
|
+
LivenessSession,
|
|
633
|
+
LivenessVerificationResponse,
|
|
634
|
+
FaceMatchResponse,
|
|
635
|
+
LivenessError,
|
|
636
|
+
} from "@finclusion/liveness-sdk";
|
|
637
|
+
|
|
638
|
+
// Example usage
|
|
639
|
+
const config: LivenessConfig = {
|
|
640
|
+
apiBaseUrl: "https://api.example.com",
|
|
641
|
+
getAuthToken: () => localStorage.getItem("token"),
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const handleResponse = (response: LivenessVerificationResponse) => {
|
|
645
|
+
if (response.confidence > 60) {
|
|
646
|
+
console.log("Verification successful!", response.confidence);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
## API Endpoints
|
|
652
|
+
|
|
653
|
+
The SDK expects your backend to provide these endpoints:
|
|
654
|
+
|
|
655
|
+
### Create Session
|
|
656
|
+
|
|
657
|
+
**Endpoint:** `GET /api/liveliness` (or custom via `config.endpoints.createSession`)
|
|
658
|
+
|
|
659
|
+
**Response:**
|
|
660
|
+
|
|
661
|
+
```json
|
|
662
|
+
{
|
|
663
|
+
"sessionId": "string",
|
|
664
|
+
"awsAccessKeyId": "string",
|
|
665
|
+
"awsSecretAccessKey": "string",
|
|
666
|
+
"awsSessionToken": "string",
|
|
667
|
+
"awsRegion": "string"
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Verify Session
|
|
672
|
+
|
|
673
|
+
**Endpoint:** `GET /api/liveliness/{sessionId}` (or custom via `config.endpoints.verify`)
|
|
674
|
+
|
|
675
|
+
**Response:**
|
|
676
|
+
|
|
677
|
+
```json
|
|
678
|
+
{
|
|
679
|
+
"confidence": 85.5,
|
|
680
|
+
"image": "base64-encoded-image",
|
|
681
|
+
"success": true,
|
|
682
|
+
"message": "Verification successful"
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Compare with User (PaymentLiveness)
|
|
687
|
+
|
|
688
|
+
**Endpoint:** `GET /api/liveliness/{sessionId}/compare-with-user` (or custom via `config.endpoints.compareWithUser`)
|
|
689
|
+
|
|
690
|
+
**Response:**
|
|
691
|
+
|
|
692
|
+
```json
|
|
693
|
+
{
|
|
694
|
+
"isMatch": true,
|
|
695
|
+
"confidence": 92.3,
|
|
696
|
+
"details": "Face matches user profile"
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## Styling
|
|
701
|
+
|
|
702
|
+
The SDK includes all necessary styles bundled. Simply import:
|
|
703
|
+
|
|
704
|
+
```tsx
|
|
705
|
+
import "@finclusion/liveness-sdk/styles.css";
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
The stylesheet includes:
|
|
709
|
+
|
|
710
|
+
- Tailwind CSS utilities (responsive, mobile-optimized)
|
|
711
|
+
- AWS Amplify UI styles (processed and rebranded)
|
|
712
|
+
- Component-specific styling
|
|
713
|
+
|
|
714
|
+
**Note:** The SDK is mobile-responsive by default. No additional CSS configuration needed.
|
|
715
|
+
|
|
716
|
+
## Troubleshooting
|
|
717
|
+
|
|
718
|
+
### Camera Not Working
|
|
719
|
+
|
|
720
|
+
**Web:**
|
|
721
|
+
|
|
722
|
+
- Ensure HTTPS (required for camera access)
|
|
723
|
+
- Check browser permissions
|
|
724
|
+
- Verify `getUserMedia` API is available
|
|
725
|
+
|
|
726
|
+
**React Native:**
|
|
727
|
+
|
|
728
|
+
- Check camera permissions in `Info.plist` (iOS) and `AndroidManifest.xml` (Android)
|
|
729
|
+
- Ensure WebView has `allowsInlineMediaPlayback={true}` (iOS)
|
|
730
|
+
- Verify `mediaPlaybackRequiresUserAction={false}`
|
|
731
|
+
|
|
732
|
+
### Styles Not Applying
|
|
733
|
+
|
|
734
|
+
- Ensure you've imported the CSS: `import '@finclusion/liveness-sdk/styles.css';`
|
|
735
|
+
- Check that the CSS file is being loaded (inspect network tab)
|
|
736
|
+
- Verify no CSS conflicts with your app's styles
|
|
737
|
+
|
|
738
|
+
### React Query Errors
|
|
739
|
+
|
|
740
|
+
- Ensure `QueryClientProvider` wraps your component
|
|
741
|
+
- Verify React Query version matches peer dependency (`^5.39.0`)
|
|
742
|
+
- Check that `QueryClient` is created outside component render
|
|
743
|
+
|
|
744
|
+
### API Errors
|
|
745
|
+
|
|
746
|
+
- Verify `apiBaseUrl` is correct
|
|
747
|
+
- Check authentication token is valid
|
|
748
|
+
- Ensure endpoints match your backend API
|
|
749
|
+
- Check CORS settings if using cross-origin requests
|
|
750
|
+
|
|
751
|
+
### Build Errors
|
|
752
|
+
|
|
753
|
+
- Ensure all peer dependencies are installed
|
|
754
|
+
- Verify Node.js version (recommended: 18+)
|
|
755
|
+
- Check TypeScript version compatibility
|
|
756
|
+
|
|
757
|
+
## Examples
|
|
758
|
+
|
|
759
|
+
### Modal Integration
|
|
760
|
+
|
|
761
|
+
```tsx
|
|
762
|
+
import React, { useState } from "react";
|
|
763
|
+
import { LivenessDetector } from "@finclusion/liveness-sdk";
|
|
764
|
+
|
|
765
|
+
function LivenessModal() {
|
|
766
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
767
|
+
|
|
768
|
+
return (
|
|
769
|
+
<>
|
|
770
|
+
<button onClick={() => setIsOpen(true)}>Start Verification</button>
|
|
771
|
+
|
|
772
|
+
{isOpen && (
|
|
773
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
774
|
+
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-auto p-4">
|
|
775
|
+
<div className="flex justify-between items-center mb-4">
|
|
776
|
+
<h2 className="text-xl font-bold">Face Verification</h2>
|
|
777
|
+
<button onClick={() => setIsOpen(false)}>✕</button>
|
|
778
|
+
</div>
|
|
779
|
+
|
|
780
|
+
<LivenessDetector
|
|
781
|
+
config={config}
|
|
782
|
+
token={token}
|
|
783
|
+
gotoNext={() => {
|
|
784
|
+
setIsOpen(false);
|
|
785
|
+
// Handle success
|
|
786
|
+
}}
|
|
787
|
+
/>
|
|
788
|
+
</div>
|
|
789
|
+
</div>
|
|
790
|
+
)}
|
|
791
|
+
</>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### With Loading States
|
|
797
|
+
|
|
798
|
+
```tsx
|
|
799
|
+
import React, { useState } from "react";
|
|
800
|
+
import { LivenessDetector } from "@finclusion/liveness-sdk";
|
|
801
|
+
|
|
802
|
+
function LivenessWithLoading() {
|
|
803
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
804
|
+
|
|
805
|
+
return (
|
|
806
|
+
<div>
|
|
807
|
+
{isVerifying && (
|
|
808
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
809
|
+
<div className="bg-white p-6 rounded-lg">
|
|
810
|
+
<p>Processing verification...</p>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
)}
|
|
814
|
+
|
|
815
|
+
<LivenessDetector
|
|
816
|
+
config={config}
|
|
817
|
+
token={token}
|
|
818
|
+
gotoNext={async () => {
|
|
819
|
+
setIsVerifying(true);
|
|
820
|
+
try {
|
|
821
|
+
// Process result
|
|
822
|
+
await processVerification();
|
|
823
|
+
} finally {
|
|
824
|
+
setIsVerifying(false);
|
|
825
|
+
}
|
|
826
|
+
}}
|
|
827
|
+
/>
|
|
828
|
+
</div>
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
## License
|
|
834
|
+
|
|
835
|
+
MIT
|
|
836
|
+
|
|
837
|
+
## Support
|
|
838
|
+
|
|
839
|
+
For issues, questions, or contributions, please visit our [GitHub repository](https://github.com/finclusion/liveness-sdk).
|
|
840
|
+
|
|
841
|
+
## Version
|
|
842
|
+
|
|
843
|
+
Current version: **1.0.0**
|