@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/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**