omnipay-reactnative-sdk 1.2.2-beta.0 → 1.2.2-beta.2

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 (69) hide show
  1. package/README.md +45 -136
  2. package/android/build.gradle +13 -0
  3. package/android/src/main/AndroidManifest.xml +5 -0
  4. package/android/src/main/java/com/omniretail/omnipay/LivenessCameraViewManager.java +116 -0
  5. package/android/src/main/java/com/omniretail/omnipay/LivenessDetectionModule.java +588 -0
  6. package/android/src/main/java/com/omniretail/omnipay/OmnipayActivityPackage.java +4 -1
  7. package/ios/LivenessCameraView.h +22 -0
  8. package/ios/LivenessCameraView.m +135 -0
  9. package/ios/LivenessCameraViewManager.h +12 -0
  10. package/ios/LivenessCameraViewManager.m +24 -0
  11. package/ios/LivenessDetectionModule.h +46 -0
  12. package/ios/LivenessDetectionModule.m +603 -0
  13. package/lib/commonjs/components/OmnipayProvider.js +6 -56
  14. package/lib/commonjs/components/OmnipayProvider.js.map +1 -1
  15. package/lib/commonjs/components/biometrics/FaceVerification.js +439 -0
  16. package/lib/commonjs/components/biometrics/FaceVerification.js.map +1 -0
  17. package/lib/commonjs/components/biometrics/LivenessCameraView.js +43 -0
  18. package/lib/commonjs/components/biometrics/LivenessCameraView.js.map +1 -0
  19. package/lib/commonjs/components/biometrics/LivenessDetection.js +252 -0
  20. package/lib/commonjs/components/biometrics/LivenessDetection.js.map +1 -0
  21. package/lib/commonjs/index.js +28 -0
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/module/components/OmnipayProvider.js +6 -56
  24. package/lib/module/components/OmnipayProvider.js.map +1 -1
  25. package/lib/module/components/biometrics/FaceVerification.js +429 -0
  26. package/lib/module/components/biometrics/FaceVerification.js.map +1 -0
  27. package/lib/module/components/biometrics/LivenessCameraView.js +38 -0
  28. package/lib/module/components/biometrics/LivenessCameraView.js.map +1 -0
  29. package/lib/module/components/biometrics/LivenessDetection.js +244 -0
  30. package/lib/module/components/biometrics/LivenessDetection.js.map +1 -0
  31. package/lib/module/index.js +5 -0
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/typescript/components/OmnipayProvider.d.ts.map +1 -1
  34. package/lib/typescript/components/biometrics/FaceVerification.d.ts +12 -0
  35. package/lib/typescript/components/biometrics/FaceVerification.d.ts.map +1 -0
  36. package/lib/typescript/components/biometrics/LivenessCameraView.d.ts +22 -0
  37. package/lib/typescript/components/biometrics/LivenessCameraView.d.ts.map +1 -0
  38. package/lib/typescript/components/biometrics/LivenessDetection.d.ts +73 -0
  39. package/lib/typescript/components/biometrics/LivenessDetection.d.ts.map +1 -0
  40. package/lib/typescript/index.d.ts +3 -0
  41. package/lib/typescript/index.d.ts.map +1 -1
  42. package/omnipay-reactnative-sdk.podspec +47 -0
  43. package/package.json +6 -11
  44. package/src/components/OmnipayProvider.tsx +8 -65
  45. package/src/components/biometrics/FaceVerification.tsx +484 -0
  46. package/src/components/biometrics/LivenessCameraView.tsx +61 -0
  47. package/src/components/biometrics/LivenessDetection.ts +305 -0
  48. package/src/index.tsx +18 -0
  49. package/lib/commonjs/components/FaceVerification.js +0 -755
  50. package/lib/commonjs/components/FaceVerification.js.map +0 -1
  51. package/lib/commonjs/types/faceVerification.js +0 -2
  52. package/lib/commonjs/types/faceVerification.js.map +0 -1
  53. package/lib/commonjs/types/index.js +0 -17
  54. package/lib/commonjs/types/index.js.map +0 -1
  55. package/lib/module/components/FaceVerification.js +0 -746
  56. package/lib/module/components/FaceVerification.js.map +0 -1
  57. package/lib/module/types/faceVerification.js +0 -2
  58. package/lib/module/types/faceVerification.js.map +0 -1
  59. package/lib/module/types/index.js +0 -2
  60. package/lib/module/types/index.js.map +0 -1
  61. package/lib/typescript/components/FaceVerification.d.ts +0 -10
  62. package/lib/typescript/components/FaceVerification.d.ts.map +0 -1
  63. package/lib/typescript/types/faceVerification.d.ts +0 -18
  64. package/lib/typescript/types/faceVerification.d.ts.map +0 -1
  65. package/lib/typescript/types/index.d.ts +0 -2
  66. package/lib/typescript/types/index.d.ts.map +0 -1
  67. package/src/components/FaceVerification.tsx +0 -884
  68. package/src/types/faceVerification.ts +0 -27
  69. package/src/types/index.ts +0 -1
@@ -0,0 +1,484 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ Modal,
6
+ Dimensions,
7
+ Platform,
8
+ StyleSheet,
9
+ TouchableOpacity,
10
+ ActivityIndicator,
11
+ } from 'react-native';
12
+ import {
13
+ LivenessDetection,
14
+ type LivenessDetectionConfig,
15
+ type LivenessResult,
16
+ } from './LivenessDetection';
17
+ import LivenessCameraView from './LivenessCameraView';
18
+
19
+ type FaceVerificationProps = {
20
+ onClose: () => void;
21
+ onSuccess: (result: LivenessResult) => void;
22
+ primaryColor: string;
23
+ challenges?: ('smile' | 'blink' | 'turn_left' | 'turn_right')[];
24
+ timeout?: number;
25
+ };
26
+
27
+ type DetectionState = 'idle' | 'starting' | 'active' | 'completed' | 'error';
28
+
29
+ const FaceVerification: React.FC<FaceVerificationProps> = ({
30
+ onClose,
31
+ onSuccess,
32
+ primaryColor,
33
+ challenges = ['smile', 'blink', 'turn_left'],
34
+ timeout = 300000, // 5 minutes default
35
+ }) => {
36
+ const [detectionState, setDetectionState] = useState<DetectionState>('idle');
37
+ const [currentChallenge, setCurrentChallenge] = useState<string>('');
38
+ const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
39
+ const [error, setError] = useState<string>('');
40
+ const [isLoading, setIsLoading] = useState(false);
41
+
42
+ // Challenge instruction messages
43
+ const getChallengeInstruction = (challenge: string): string => {
44
+ switch (challenge) {
45
+ case 'smile':
46
+ return 'Please smile naturally';
47
+ case 'blink':
48
+ return 'Please blink your eyes';
49
+ case 'turn_left':
50
+ return 'Please turn your head to the left';
51
+ case 'turn_right':
52
+ return 'Please turn your head to the right';
53
+ default:
54
+ return 'Please follow the instruction';
55
+ }
56
+ };
57
+
58
+ const startLivenessDetection = useCallback(async () => {
59
+ try {
60
+ setDetectionState('starting');
61
+ setIsLoading(true);
62
+ setError('');
63
+
64
+ // Check and request camera permission first
65
+ const hasPermission = await LivenessDetection.checkCameraPermission();
66
+ if (!hasPermission) {
67
+ const granted = await LivenessDetection.requestCameraPermission();
68
+ if (!granted) {
69
+ throw new Error(
70
+ 'Camera permission is required for face verification'
71
+ );
72
+ }
73
+ }
74
+
75
+ const config: LivenessDetectionConfig = {
76
+ challenges,
77
+ timeout,
78
+ };
79
+
80
+ await LivenessDetection.startLivenessDetection(config, {
81
+ onChallengeStart: (challenge) => {
82
+ setCurrentChallenge(challenge);
83
+ setDetectionState('active');
84
+ setIsLoading(false);
85
+ },
86
+ onChallengeSuccess: (challenge) => {
87
+ setCompletedChallenges((prev) => [...prev, challenge]);
88
+ },
89
+ onChallengeFailure: (challenge, reason) => {
90
+ console.warn(`Challenge ${challenge} failed: ${reason}`);
91
+ // Continue with next challenge - the native module handles this
92
+ },
93
+ onAllChallengesComplete: (result) => {
94
+ setDetectionState('completed');
95
+ setCurrentChallenge('');
96
+ onSuccess(result);
97
+ },
98
+ });
99
+ } catch (err) {
100
+ console.error('Liveness detection error:', err);
101
+ setError(err instanceof Error ? err.message : 'Unknown error occurred');
102
+ setDetectionState('error');
103
+ setIsLoading(false);
104
+ }
105
+ }, [challenges, timeout, onSuccess]);
106
+
107
+ const handleClose = useCallback(() => {
108
+ if (detectionState === 'active' || detectionState === 'starting') {
109
+ LivenessDetection.stopDetection();
110
+ }
111
+ onClose();
112
+ }, [detectionState, onClose]);
113
+
114
+ const handleRetry = useCallback(() => {
115
+ setDetectionState('idle');
116
+ setError('');
117
+ setCompletedChallenges([]);
118
+ setCurrentChallenge('');
119
+ }, []);
120
+
121
+ // Auto-start detection when component mounts
122
+ useEffect(() => {
123
+ if (detectionState === 'idle') {
124
+ // Small delay to ensure UI is ready
125
+ const timer = setTimeout(() => {
126
+ startLivenessDetection();
127
+ }, 500);
128
+ return () => clearTimeout(timer);
129
+ }
130
+ return undefined;
131
+ }, [detectionState, startLivenessDetection]);
132
+
133
+ // Cleanup on unmount
134
+ useEffect(() => {
135
+ return () => {
136
+ LivenessDetection.cleanup();
137
+ };
138
+ }, []);
139
+
140
+ const renderContent = () => {
141
+ if (detectionState === 'error') {
142
+ return (
143
+ <View style={styles.errorContainer}>
144
+ <Text style={styles.errorTitle}>Detection Failed</Text>
145
+ <Text style={styles.errorSubtitle}>{error}</Text>
146
+ <TouchableOpacity
147
+ style={[styles.button, { backgroundColor: primaryColor }]}
148
+ onPress={handleRetry}
149
+ >
150
+ <Text style={styles.buttonText}>Try Again</Text>
151
+ </TouchableOpacity>
152
+ </View>
153
+ );
154
+ }
155
+
156
+ if (detectionState === 'completed') {
157
+ return (
158
+ <View style={styles.successContainer}>
159
+ <Text style={styles.successTitle}>Verification Complete!</Text>
160
+ <Text style={styles.successSubtitle}>
161
+ All challenges completed successfully
162
+ </Text>
163
+ </View>
164
+ );
165
+ }
166
+
167
+ return (
168
+ <View style={styles.contentContainer}>
169
+ {/* Camera View */}
170
+ <View style={styles.cameraContainer}>
171
+ <LivenessCameraView
172
+ style={styles.camera}
173
+ scaleType="fillCenter"
174
+ onCameraReady={() => console.log('Camera ready')}
175
+ onCameraError={(error) => {
176
+ console.error('Camera error:', error);
177
+ setError('Camera initialization failed');
178
+ setDetectionState('error');
179
+ }}
180
+ />
181
+
182
+ {/* Overlay with instructions */}
183
+ <View style={styles.overlay}>
184
+ {isLoading ? (
185
+ <View style={styles.loadingContainer}>
186
+ <ActivityIndicator size="large" color={primaryColor} />
187
+ <Text style={styles.loadingText}>Initializing camera...</Text>
188
+ </View>
189
+ ) : (
190
+ <>
191
+ {/* Current challenge instruction */}
192
+ {currentChallenge && (
193
+ <View style={styles.instructionContainer}>
194
+ <Text style={styles.instructionText}>
195
+ {getChallengeInstruction(currentChallenge)}
196
+ </Text>
197
+ </View>
198
+ )}
199
+
200
+ {/* Progress indicator */}
201
+ <View style={styles.progressContainer}>
202
+ <Text style={styles.progressText}>
203
+ {completedChallenges.length} / {challenges.length} completed
204
+ </Text>
205
+ <View style={styles.progressBar}>
206
+ <View
207
+ style={[
208
+ styles.progressFill,
209
+ {
210
+ width: `${
211
+ (completedChallenges.length / challenges.length) *
212
+ 100
213
+ }%`,
214
+ backgroundColor: primaryColor,
215
+ },
216
+ ]}
217
+ />
218
+ </View>
219
+ </View>
220
+ </>
221
+ )}
222
+ </View>
223
+ </View>
224
+ </View>
225
+ );
226
+ };
227
+
228
+ return (
229
+ <Modal
230
+ visible={true}
231
+ transparent={true}
232
+ onRequestClose={handleClose}
233
+ style={styles.modal}
234
+ animationType="slide"
235
+ >
236
+ <View style={styles.backdrop}>
237
+ <View style={styles.container}>
238
+ {/* Header */}
239
+ <View style={styles.header}>
240
+ <Text style={styles.title}>Face Verification</Text>
241
+ <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
242
+ <Text style={styles.closeText}>✕</Text>
243
+ </TouchableOpacity>
244
+ </View>
245
+
246
+ {renderContent()}
247
+ </View>
248
+ </View>
249
+ </Modal>
250
+ );
251
+ };
252
+
253
+ export default FaceVerification;
254
+
255
+ const styles = StyleSheet.create({
256
+ hide: {
257
+ display: 'none',
258
+ },
259
+ full: {
260
+ flex: 1,
261
+ width: '100%',
262
+ height: '100%',
263
+ },
264
+ webview: {
265
+ flex: 1,
266
+ width: '100%',
267
+ height: Dimensions.get('window').height - 40,
268
+ backgroundColor: 'white',
269
+ borderTopRightRadius: 20,
270
+ borderTopLeftRadius: 20,
271
+ paddingTop: 150,
272
+ },
273
+ webviewLoader: {
274
+ zIndex: 3,
275
+ backgroundColor: 'white',
276
+ alignItems: 'center',
277
+ justifyContent: 'center',
278
+ flex: 1,
279
+ width: '100%',
280
+ height: '100%',
281
+ position: 'absolute',
282
+ top: 0,
283
+ left: 0,
284
+ borderTopRightRadius: 20,
285
+ borderTopLeftRadius: 20,
286
+ },
287
+ backdrop: {
288
+ backgroundColor: 'rgba(0,0,0,0.48)',
289
+ flex: 1,
290
+ justifyContent: 'flex-end',
291
+ position: 'relative',
292
+ height: '100%',
293
+ },
294
+ container: {
295
+ backgroundColor: 'white',
296
+ borderTopRightRadius: 20,
297
+ borderTopLeftRadius: 20,
298
+ maxHeight: Dimensions.get('window').height - 40,
299
+ flex: 1,
300
+ position: 'relative',
301
+ ...(Platform.OS === 'android' && { overflow: 'hidden' }),
302
+ },
303
+ modal: {
304
+ flex: 1,
305
+ backgroundColor: 'rgba(0,0,0,0.48)',
306
+ height: '100%',
307
+ width: '100%',
308
+ },
309
+ header: {
310
+ flexDirection: 'row',
311
+ justifyContent: 'space-between',
312
+ alignItems: 'center',
313
+ padding: 16,
314
+ borderBottomWidth: 1,
315
+ borderBottomColor: '#f0f0f0',
316
+ },
317
+ title: {
318
+ fontSize: 18,
319
+ fontWeight: '600',
320
+ color: '#333',
321
+ },
322
+ closeButton: {
323
+ width: 30,
324
+ height: 30,
325
+ borderRadius: 15,
326
+ backgroundColor: '#f0f0f0',
327
+ alignItems: 'center',
328
+ justifyContent: 'center',
329
+ },
330
+ closeText: {
331
+ fontSize: 16,
332
+ color: '#666',
333
+ },
334
+ contentContainer: {
335
+ flex: 1,
336
+ },
337
+ cameraContainer: {
338
+ flex: 1,
339
+ position: 'relative',
340
+ },
341
+ camera: {
342
+ flex: 1,
343
+ width: '100%',
344
+ height: '100%',
345
+ },
346
+ overlay: {
347
+ position: 'absolute',
348
+ top: 0,
349
+ left: 0,
350
+ right: 0,
351
+ bottom: 0,
352
+ justifyContent: 'space-between',
353
+ alignItems: 'center',
354
+ paddingVertical: 40,
355
+ paddingHorizontal: 20,
356
+ },
357
+ loadingContainer: {
358
+ flex: 1,
359
+ justifyContent: 'center',
360
+ alignItems: 'center',
361
+ },
362
+ loadingText: {
363
+ color: 'white',
364
+ fontSize: 16,
365
+ marginTop: 12,
366
+ textAlign: 'center',
367
+ },
368
+ instructionContainer: {
369
+ backgroundColor: 'rgba(0,0,0,0.7)',
370
+ paddingHorizontal: 20,
371
+ paddingVertical: 12,
372
+ borderRadius: 8,
373
+ marginTop: 20,
374
+ },
375
+ instructionText: {
376
+ color: 'white',
377
+ fontSize: 16,
378
+ fontWeight: '500',
379
+ textAlign: 'center',
380
+ },
381
+ progressContainer: {
382
+ backgroundColor: 'rgba(0,0,0,0.7)',
383
+ paddingHorizontal: 20,
384
+ paddingVertical: 12,
385
+ borderRadius: 8,
386
+ minWidth: 200,
387
+ },
388
+ progressText: {
389
+ color: 'white',
390
+ fontSize: 14,
391
+ textAlign: 'center',
392
+ marginBottom: 8,
393
+ },
394
+ progressBar: {
395
+ height: 4,
396
+ backgroundColor: 'rgba(255,255,255,0.3)',
397
+ borderRadius: 2,
398
+ overflow: 'hidden',
399
+ },
400
+ progressFill: {
401
+ height: '100%',
402
+ borderRadius: 2,
403
+ },
404
+ errorContainer: {
405
+ flex: 1,
406
+ justifyContent: 'center',
407
+ alignItems: 'center',
408
+ paddingHorizontal: 20,
409
+ },
410
+ errorTitle: {
411
+ fontSize: 20,
412
+ fontWeight: '600',
413
+ color: '#d32f2f',
414
+ marginBottom: 8,
415
+ textAlign: 'center',
416
+ },
417
+ errorSubtitle: {
418
+ textAlign: 'center',
419
+ fontSize: 14,
420
+ color: '#5e7079',
421
+ marginBottom: 20,
422
+ paddingHorizontal: 8,
423
+ },
424
+ successContainer: {
425
+ flex: 1,
426
+ justifyContent: 'center',
427
+ alignItems: 'center',
428
+ paddingHorizontal: 20,
429
+ },
430
+ successTitle: {
431
+ fontSize: 20,
432
+ fontWeight: '600',
433
+ color: '#2e7d32',
434
+ marginBottom: 8,
435
+ textAlign: 'center',
436
+ },
437
+ successSubtitle: {
438
+ textAlign: 'center',
439
+ fontSize: 14,
440
+ color: '#5e7079',
441
+ paddingHorizontal: 8,
442
+ },
443
+ close: {
444
+ position: 'absolute',
445
+ top: 10,
446
+ right: 10,
447
+ backgroundColor: 'white',
448
+ height: 24,
449
+ width: 24,
450
+ borderRadius: 1000,
451
+ alignItems: 'center',
452
+ justifyContent: 'center',
453
+ zIndex: 2,
454
+ },
455
+ closeIcon: {
456
+ height: 12,
457
+ width: 12,
458
+ },
459
+ testContent: {
460
+ paddingTop: 30,
461
+ paddingLeft: 16,
462
+ },
463
+ testTwoContent: {
464
+ paddingTop: 10,
465
+ paddingLeft: 16,
466
+ },
467
+ retryButton: {
468
+ minWidth: 160,
469
+ marginHorizontal: 'auto',
470
+ },
471
+ button: {
472
+ borderRadius: 6,
473
+ paddingHorizontal: 20,
474
+ paddingVertical: 14,
475
+ alignItems: 'center',
476
+ justifyContent: 'center',
477
+ minWidth: 120,
478
+ },
479
+ buttonText: {
480
+ color: 'white',
481
+ fontSize: 16,
482
+ fontWeight: '600',
483
+ },
484
+ });
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { requireNativeComponent, Platform, ViewStyle } from 'react-native';
3
+
4
+ // Props for the native camera view
5
+ interface LivenessCameraViewProps {
6
+ style?: ViewStyle;
7
+ scaleType?:
8
+ | 'fillCenter'
9
+ | 'fillStart'
10
+ | 'fillEnd'
11
+ | 'fitCenter'
12
+ | 'fitStart'
13
+ | 'fitEnd';
14
+ onCameraReady?: () => void;
15
+ onCameraError?: (error: { nativeEvent: { error: string } }) => void;
16
+ }
17
+
18
+ // Native component interface
19
+ interface NativeLivenessCameraViewProps extends LivenessCameraViewProps {
20
+ style?: ViewStyle;
21
+ }
22
+
23
+ // Import native component based on platform
24
+ const NativeLivenessCameraView = Platform.select({
25
+ android:
26
+ requireNativeComponent<NativeLivenessCameraViewProps>('LivenessCameraView'),
27
+ ios: requireNativeComponent<NativeLivenessCameraViewProps>(
28
+ 'LivenessCameraView'
29
+ ),
30
+ });
31
+
32
+ /**
33
+ * LivenessCameraView - Native camera component for liveness detection
34
+ *
35
+ * This component provides a camera preview that integrates with the
36
+ * liveness detection native module for real-time face analysis.
37
+ */
38
+ const LivenessCameraView: React.FC<LivenessCameraViewProps> = ({
39
+ style,
40
+ scaleType = 'fillCenter',
41
+ onCameraReady,
42
+ onCameraError,
43
+ }) => {
44
+ // Platform check
45
+ if (!NativeLivenessCameraView) {
46
+ console.warn('LivenessCameraView is not available on this platform');
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ <NativeLivenessCameraView
52
+ style={style}
53
+ scaleType={scaleType}
54
+ onCameraReady={onCameraReady}
55
+ onCameraError={onCameraError}
56
+ />
57
+ );
58
+ };
59
+
60
+ export default LivenessCameraView;
61
+ export type { LivenessCameraViewProps };