@myazahq/kyc-sdk-react-native 2.6.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +128 -4
  2. package/android/build.gradle +30 -16
  3. package/android/consumer-rules.pro +22 -0
  4. package/android/src/main/AndroidManifest.xml +17 -6
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +27 -72
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +14 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/MlKitModelReadiness.kt +154 -0
  8. package/app.plugin.js +57 -2
  9. package/ios/HybridMyazaTextRecognizer.swift +8 -0
  10. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +21 -0
  11. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +2 -0
  12. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +8 -0
  13. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +16 -0
  14. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +2 -0
  15. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +31 -0
  16. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +2 -0
  17. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +2 -0
  18. package/package.json +24 -14
  19. package/src/MyazaKYC.tsx +4 -11
  20. package/src/assets/fonts/Karla_400Regular.ttf +0 -0
  21. package/src/assets/fonts/Karla_500Medium.ttf +0 -0
  22. package/src/assets/fonts/Karla_600SemiBold.ttf +0 -0
  23. package/src/assets/fonts/Karla_700Bold.ttf +0 -0
  24. package/src/assets/fonts/OFL-Karla.txt +93 -0
  25. package/src/assets/fonts/OFL-SpaceGrotesk.txt +93 -0
  26. package/src/assets/fonts/README.md +12 -0
  27. package/src/assets/fonts/SpaceGrotesk_500Medium.ttf +0 -0
  28. package/src/assets/fonts/SpaceGrotesk_600SemiBold.ttf +0 -0
  29. package/src/assets/fonts/SpaceGrotesk_700Bold.ttf +0 -0
  30. package/src/capture/useAutoCapture.ts +8 -1
  31. package/src/components/DocumentCropper.tsx +30 -24
  32. package/src/components/DocumentReview.tsx +11 -1
  33. package/src/components/DocumentReviewSide.tsx +6 -2
  34. package/src/components/DocumentReviewZoom.tsx +5 -1
  35. package/src/components/GlassIconButton.tsx +2 -5
  36. package/src/components/Icon.tsx +17 -13
  37. package/src/components/KycFlow.tsx +10 -2
  38. package/src/components/KycSheet.tsx +0 -1
  39. package/src/components/MyazaButton.tsx +9 -0
  40. package/src/components/RequiredDocumentPill.tsx +114 -0
  41. package/src/components/StepHeader.tsx +23 -3
  42. package/src/components/StepView.tsx +3 -0
  43. package/src/components/documentReviewCopy.ts +39 -0
  44. package/src/components/fonts.ts +14 -17
  45. package/src/components/icons/glyphs.ts +35 -0
  46. package/src/components/icons/index.ts +3 -0
  47. package/src/components/icons/map.ts +165 -0
  48. package/src/components/icons/names.ts +86 -0
  49. package/src/components/stepHeaderMeta.tsx +21 -1
  50. package/src/config/documentCaptureMethods.ts +31 -0
  51. package/src/config/stepOrder.ts +20 -5
  52. package/src/config/supportingDocuments.ts +131 -0
  53. package/src/config/theme.ts +29 -3
  54. package/src/config/workflowMerge.ts +15 -0
  55. package/src/index.ts +5 -10
  56. package/src/lib/documentCaptureCheck.ts +136 -0
  57. package/src/lib/model-ready.ts +82 -0
  58. package/src/lib/prime-models.ts +50 -0
  59. package/src/lib/resubmit.ts +36 -7
  60. package/src/lib/selfie-sharpness.ts +211 -0
  61. package/src/lib/supportingDocumentsIntro.ts +35 -0
  62. package/src/liveness/avatarSource.ts +59 -0
  63. package/src/liveness/useModelReady.ts +9 -55
  64. package/src/mrz/extract.ts +71 -4
  65. package/src/mrz/textRecognizer.ts +36 -0
  66. package/src/mrz/useTextModelReady.ts +21 -0
  67. package/src/screens/BusinessDocumentSlot.tsx +37 -8
  68. package/src/screens/DocumentCaptureStep.tsx +177 -93
  69. package/src/screens/LivenessAvatar.tsx +18 -13
  70. package/src/screens/LivenessStep.tsx +37 -0
  71. package/src/screens/MrzScanView.tsx +33 -0
  72. package/src/screens/SupportingDocumentCard.tsx +134 -0
  73. package/src/screens/SupportingDocumentsStep.tsx +203 -0
  74. package/src/screens/consent/model.ts +22 -5
  75. package/src/screens/document/CaptureCheckNotice.tsx +77 -0
  76. package/src/screens/document/UploadPhase.tsx +177 -0
  77. package/src/screens/document/useDocumentCamera.ts +109 -0
  78. package/src/screens/liveness/CaptureRing.tsx +31 -17
  79. package/src/screens/liveness/LivenessOutcome.tsx +4 -1
  80. package/src/screens/liveness/SelfiePreview.tsx +19 -4
  81. package/src/screens/supportingDocumentParts.tsx +126 -0
  82. package/src/screens/useBusinessDocumentAttach.ts +20 -9
  83. package/src/services/api-types.ts +31 -2
  84. package/src/services/api.ts +19 -3
  85. package/src/services/deviceMetadata.ts +1 -1
  86. package/src/services/mediaCompress.ts +23 -5
  87. package/src/specs/MyazaTextRecognizer.nitro.ts +29 -0
  88. package/src/store/derive.ts +11 -0
  89. package/src/store/kycStore.ts +23 -2
  90. package/src/store/session.ts +18 -0
  91. package/src/store/state.ts +26 -0
  92. package/src/store/submit.ts +11 -0
  93. package/src/types/config.ts +25 -0
  94. package/src/types/workflow.ts +39 -0
  95. package/src/MyazaBiometricAuth.tsx +0 -119
  96. package/src/assets/liveness/Blink.gif +0 -0
  97. package/src/assets/liveness/Nod.gif +0 -0
  98. package/src/assets/liveness/Smile.gif +0 -0
  99. package/src/assets/liveness/Turn.gif +0 -0
  100. package/src/components/icon-map.ts +0 -176
  101. package/src/lib/biometric-auth.ts +0 -52
  102. package/src/screens/biometric/BiometricAuthFlow.tsx +0 -170
  103. package/src/services/api-biometric.ts +0 -37
  104. package/src/services/api-types-biometric.ts +0 -41
@@ -1,170 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { ActivityIndicator, Pressable, View } from 'react-native';
3
-
4
- import { spacing } from '../../config/theme';
5
- import { useKyc, useKycConfig, useKycStore, useTheme } from '../../components/runtime';
6
- import { KycSheet } from '../../components/KycSheet';
7
- import { ToastProvider } from '../../components/toast';
8
- import { MyazaText } from '../../components/Typography';
9
- import { MyazaButton } from '../../components/MyazaButton';
10
- import { Icon } from '../../components/Icon';
11
- import { LivenessStep } from '../LivenessStep';
12
- import { mapAuthError, outcomeOf, type BiometricAuthOutcome } from '../../lib/biometric-auth';
13
- import type { KYCError } from '../../types/verification';
14
- import type { BiometricAuthResponse } from '../../services/api-types-biometric';
15
-
16
- // ---------------------------------------------------------------------------
17
- // The re-auth flow inside its own runtime: intro → the REAL liveness step →
18
- // authenticating → result. The liveness step is the ordinary one — camera,
19
- // challenges, capture ring, selfie upload — mounted alone with the store
20
- // parked on 'liveness'. Its Continue advances the store to 'submitted' (the
21
- // scope's step order), which is the hand-over: the uploaded selfie's mediaId
22
- // is read off the store and sent to /biometric/authenticate. The submitted
23
- // screen is never rendered, so nothing ever calls /verify.
24
- // ---------------------------------------------------------------------------
25
-
26
- type View_ = 'intro' | 'capture' | 'authenticating' | 'result';
27
-
28
- export function BiometricAuthFlow({
29
- externalUserId,
30
- onAuthenticated,
31
- onFailed,
32
- onError,
33
- onClose,
34
- }: {
35
- externalUserId: string;
36
- onAuthenticated?: (result: BiometricAuthResponse) => void;
37
- onFailed?: (result: BiometricAuthResponse) => void;
38
- onError?: (error: KYCError) => void;
39
- onClose: () => void;
40
- }): React.ReactElement {
41
- const config = useKycConfig();
42
- const store = useKycStore();
43
- const currentStep = useKyc((s) => s.currentStep);
44
- const selfieId = useKyc((s) => s.mediaIds.selfie);
45
- const immersive = useKyc((s) => s.immersiveCapture);
46
- const [view, setView] = useState<View_>('intro');
47
- const [outcome, setOutcome] = useState<BiometricAuthOutcome | null>(null);
48
- const inFlight = useRef(false);
49
-
50
- const startCapture = () => {
51
- store.setState({ currentStep: 'liveness', mediaIds: {}, selfiePreviewUri: null });
52
- setOutcome(null);
53
- setView('capture');
54
- };
55
-
56
- // The hand-over: the step's Continue moved the store past 'liveness' with
57
- // the selfie uploaded.
58
- useEffect(() => {
59
- if (view !== 'capture' || currentStep === 'liveness' || !selfieId || inFlight.current) return;
60
- inFlight.current = true;
61
- setView('authenticating');
62
- const mode = config.livenessMode ?? 'gestures';
63
- store
64
- .getState()
65
- .api.authenticate({ externalUserId, selfie: selfieId, liveness: { mode, passed: true } })
66
- .then((result) => {
67
- const next = outcomeOf(result);
68
- setOutcome(next);
69
- if (next.kind === 'success') onAuthenticated?.(result);
70
- else onFailed?.(result);
71
- })
72
- .catch((err: unknown) => {
73
- const kyc = mapAuthError(err);
74
- setOutcome({ kind: 'error', message: kyc.message });
75
- onError?.(kyc);
76
- })
77
- .finally(() => {
78
- inFlight.current = false;
79
- setView('result');
80
- });
81
- }, [view, currentStep, selfieId, config.livenessMode, externalUserId, store, onAuthenticated, onFailed, onError]);
82
-
83
- const title =
84
- view === 'capture' ? 'Face check' : view === 'authenticating' ? "Verifying it's you" : "Verify it's you";
85
- const dismissBlocked = config.disableClose === true || view === 'authenticating';
86
-
87
- return (
88
- <ToastProvider>
89
- <KycSheet
90
- title={title}
91
- description={view === 'capture' ? 'Follow the prompts, then hold still for the photo.' : null}
92
- progress={null}
93
- stepCount={null}
94
- onBack={view === 'capture' ? () => setView('intro') : null}
95
- onClose={dismissBlocked ? () => undefined : onClose}
96
- immersive={immersive}
97
- >
98
- {view === 'intro' && <Intro onStart={startCapture} />}
99
- {view === 'capture' && <LivenessStep />}
100
- {view === 'authenticating' && <Authenticating />}
101
- {view === 'result' && outcome && <Result outcome={outcome} onRetry={startCapture} onClose={onClose} />}
102
- </KycSheet>
103
- </ToastProvider>
104
- );
105
- }
106
-
107
- function Intro({ onStart }: { onStart: () => void }): React.ReactElement {
108
- const { colors } = useTheme();
109
- return (
110
- <View style={{ alignItems: 'center', paddingVertical: spacing.lg }}>
111
- <View style={{ width: 80, height: 80, borderRadius: 40, backgroundColor: `${colors.primary}1A`, alignItems: 'center', justifyContent: 'center' }}>
112
- <Icon name="user" size={36} color={colors.primary} />
113
- </View>
114
- <View style={{ height: spacing.lg }} />
115
- <MyazaText variant="body" style={{ textAlign: 'center' }} color={colors.textSecondary}>
116
- We'll take a quick face check to confirm your identity. No documents needed.
117
- </MyazaText>
118
- <View style={{ height: spacing.xl }} />
119
- <MyazaButton label="Start" onPress={onStart} />
120
- </View>
121
- );
122
- }
123
-
124
- function Authenticating(): React.ReactElement {
125
- const { colors } = useTheme();
126
- return (
127
- <View style={{ alignItems: 'center', paddingVertical: spacing.xl * 2 }}>
128
- <ActivityIndicator size="large" color={colors.primary} />
129
- <View style={{ height: spacing.lg }} />
130
- <MyazaText variant="body" style={{ fontWeight: '600' }}>Verifying it's you…</MyazaText>
131
- <MyazaText variant="bodySmall" color={colors.textSecondary}>This only takes a moment.</MyazaText>
132
- </View>
133
- );
134
- }
135
-
136
- function Result({ outcome, onRetry, onClose }: { outcome: BiometricAuthOutcome; onRetry: () => void; onClose: () => void }): React.ReactElement {
137
- const { colors } = useTheme();
138
- const ok = outcome.kind === 'success';
139
- const tint = ok ? colors.success : colors.error;
140
- const heading = ok ? "You're verified" : outcome.kind === 'failed' ? "Couldn't verify you" : 'Something went wrong';
141
- const detail = ok
142
- ? "We confirmed it's really you."
143
- : outcome.kind === 'failed'
144
- ? "We couldn't confirm it's you. Make sure your face is clear and well lit, then try again."
145
- : outcome.message;
146
- return (
147
- <View style={{ alignItems: 'center', paddingVertical: spacing.lg }}>
148
- <View style={{ width: 80, height: 80, borderRadius: 40, backgroundColor: `${tint}1A`, alignItems: 'center', justifyContent: 'center' }}>
149
- {/* The web's result: a check, or the alert circle the flow's own
150
- verdict screen uses (never a bare X), under a heading-font title. */}
151
- <Icon name={ok ? 'check' : 'alert'} size={36} color={tint} />
152
- </View>
153
- <View style={{ height: spacing.lg }} />
154
- <MyazaText variant="heading2" style={{ textAlign: 'center' }}>{heading}</MyazaText>
155
- <View style={{ height: spacing.xs }} />
156
- <MyazaText variant="body" color={colors.textSecondary} style={{ textAlign: 'center' }}>{detail}</MyazaText>
157
- <View style={{ height: spacing.xl }} />
158
- {ok ? (
159
- <MyazaButton label="Done" onPress={onClose} />
160
- ) : (
161
- <>
162
- <MyazaButton label="Try again" onPress={onRetry} />
163
- <Pressable onPress={onClose} accessibilityRole="button" hitSlop={8} style={{ paddingVertical: spacing.md }}>
164
- <MyazaText variant="body" color={colors.textSecondary}>Close</MyazaText>
165
- </Pressable>
166
- </>
167
- )}
168
- </View>
169
- );
170
- }
@@ -1,37 +0,0 @@
1
- import type {
2
- BiometricAuthRequest,
3
- BiometricAuthResponse,
4
- BiometricStatusResponse,
5
- } from './api-types-biometric';
6
-
7
- // ---------------------------------------------------------------------------
8
- // The biometric re-authentication calls, split out of createKYCApi (200-line
9
- // rule). Given the client's own `request`, so they ride the same base URL,
10
- // bearer key, SDK-version header and error mapping as everything else.
11
- // ---------------------------------------------------------------------------
12
-
13
- export type JsonRequest = <T>(path: string, init?: RequestInit) => Promise<T>;
14
-
15
- export function biometricCalls(request: JsonRequest) {
16
- return {
17
- /**
18
- * Re-authenticate a verified user by matching a live selfie 1:1 against
19
- * their KYC enrollment reference. Publishable-safe. Uniform 404
20
- * `not_enrolled` — a missing entity, a business entity and no template
21
- * all answer the same, so nothing can be probed.
22
- */
23
- async authenticate(body: BiometricAuthRequest): Promise<BiometricAuthResponse> {
24
- return request<BiometricAuthResponse>('/biometric/authenticate', {
25
- method: 'POST',
26
- body: JSON.stringify(body),
27
- });
28
- },
29
-
30
- /** Whether a user is enrolled for face re-auth (whether to OFFER it). */
31
- async getBiometricStatus(externalUserId: string): Promise<BiometricStatusResponse> {
32
- return request<BiometricStatusResponse>(
33
- `/biometric/status/${encodeURIComponent(externalUserId)}`,
34
- );
35
- },
36
- };
37
- }
@@ -1,41 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Biometric re-authentication wire shapes — a mirror of the web SDK's
3
- // services/api.ts (keep the two in lockstep). Publishable-safe: the verdict,
4
- // a confidence, and a single-use proof token. No PII.
5
- // ---------------------------------------------------------------------------
6
-
7
- /** What the SDK asserts about the liveness run that produced the selfie. */
8
- export interface BiometricLivenessClaim {
9
- mode: 'gestures' | 'flash' | 'both';
10
- passed: boolean;
11
- }
12
-
13
- /** Request body for `POST /api/kyc/biometric/authenticate`. */
14
- export interface BiometricAuthRequest {
15
- /** The org's user reference (Entity.externalUserId) being re-authenticated. */
16
- externalUserId: string;
17
- /** A mediaId from `upload(file, 'selfie')` — the live selfie. */
18
- selfie: string;
19
- liveness?: BiometricLivenessClaim;
20
- }
21
-
22
- /**
23
- * Response from `POST /api/kyc/biometric/authenticate`. `token` is present
24
- * only on `authenticated` — redeem it from your backend with a secret key at
25
- * `/biometric/verify-proof`.
26
- */
27
- export interface BiometricAuthResponse {
28
- authenticated: boolean;
29
- status: 'authenticated' | 'no_match' | 'liveness_failed';
30
- confidence: number | null;
31
- live: boolean;
32
- attemptId: string;
33
- token?: string;
34
- }
35
-
36
- /** Response from `GET /api/kyc/biometric/status/:externalUserId`. */
37
- export interface BiometricStatusResponse {
38
- enrolled: boolean;
39
- enrolledAt?: string;
40
- lastAuthenticatedAt?: string | null;
41
- }