@kernaq/verify-react-native 1.0.4 → 2.0.1
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/dist/index.d.ts +95 -50
- package/dist/index.esm.js +1121 -571
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1119 -567
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/KernaqVerify.tsx +144 -175
- package/src/index.ts +9 -4
- package/src/locale.ts +32 -29
- package/src/quality.ts +180 -0
- package/src/steps/DocumentStep.tsx +214 -77
- package/src/steps/IntroStep.tsx +58 -37
- package/src/steps/LivenessStep.tsx +218 -125
- package/src/steps/ProcessingStep.tsx +10 -34
- package/src/steps/ResultStep.tsx +99 -35
- package/src/steps/SelfieStep.tsx +165 -96
- package/src/theme.ts +22 -14
- package/src/types.ts +27 -51
package/src/KernaqVerify.tsx
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <KernaqVerify /> — drop-in identity verification modal for React Native.
|
|
2
|
+
* <KernaqVerify /> — drop-in identity verification modal for React Native (v2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Matches @kernaq/verify web v2.0.0:
|
|
5
|
+
* - No header / logo
|
|
6
|
+
* - No progress bar
|
|
7
|
+
* - No cancel/close button — user must complete the flow
|
|
8
|
+
* - Front + back document capture
|
|
9
|
+
* - Auto-capture selfie with guidance pill
|
|
10
|
+
* - Randomised liveness tasks with in-viewport overlay
|
|
11
|
+
* - Configurable steps prop
|
|
12
|
+
* - "Secured by Kernaq" footer only
|
|
6
13
|
*
|
|
14
|
+
* Usage:
|
|
7
15
|
* <KernaqVerify
|
|
8
|
-
* visible={
|
|
9
|
-
*
|
|
16
|
+
* visible={show}
|
|
17
|
+
* documentType="national_id"
|
|
10
18
|
* country="KEN"
|
|
11
19
|
* reference={userId}
|
|
12
|
-
*
|
|
13
|
-
*
|
|
20
|
+
* steps={['document', 'selfie', 'liveness']}
|
|
21
|
+
* onComplete={(r) => console.log(r.verdict)}
|
|
14
22
|
* />
|
|
15
23
|
*/
|
|
16
24
|
import React, { useState, useCallback, useMemo } from 'react'
|
|
@@ -18,8 +26,6 @@ import {
|
|
|
18
26
|
Modal,
|
|
19
27
|
View,
|
|
20
28
|
Text,
|
|
21
|
-
TouchableOpacity,
|
|
22
|
-
ScrollView,
|
|
23
29
|
StyleSheet,
|
|
24
30
|
SafeAreaView,
|
|
25
31
|
Platform,
|
|
@@ -27,61 +33,81 @@ import {
|
|
|
27
33
|
import { DEFAULT_LOCALE } from './locale'
|
|
28
34
|
import { resolveTheme } from './theme'
|
|
29
35
|
import { usePermissions } from './hooks/usePermissions'
|
|
30
|
-
import { IntroStep }
|
|
31
|
-
import { DocumentStep }
|
|
32
|
-
import { SelfieStep }
|
|
33
|
-
import { LivenessStep }
|
|
36
|
+
import { IntroStep } from './steps/IntroStep'
|
|
37
|
+
import { DocumentStep } from './steps/DocumentStep'
|
|
38
|
+
import { SelfieStep } from './steps/SelfieStep'
|
|
39
|
+
import { LivenessStep } from './steps/LivenessStep'
|
|
34
40
|
import { ProcessingStep } from './steps/ProcessingStep'
|
|
35
|
-
import { ResultStep }
|
|
36
|
-
import type {
|
|
41
|
+
import { ResultStep } from './steps/ResultStep'
|
|
42
|
+
import type {
|
|
43
|
+
VerifyConfig, VerifyStep, VerifyResult, VerifyLocale, VerifyStepName,
|
|
44
|
+
} from './types'
|
|
37
45
|
|
|
38
46
|
interface Props extends VerifyConfig {
|
|
39
47
|
visible: boolean
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
type CaptureState = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const STEP_INDEX: Record<VerifyStep, number> = {
|
|
49
|
-
intro: -1,
|
|
50
|
-
document: 0,
|
|
51
|
-
selfie: 1,
|
|
52
|
-
liveness: 2,
|
|
53
|
-
processing: 3,
|
|
54
|
-
result: 3,
|
|
51
|
+
docFrontUri?: string
|
|
52
|
+
docBackUri?: string
|
|
53
|
+
selfieUri?: string
|
|
54
|
+
videoUri?: string
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export function KernaqVerify({
|
|
58
58
|
visible,
|
|
59
59
|
apiKey,
|
|
60
60
|
backendUrl,
|
|
61
|
-
country
|
|
61
|
+
country = 'KEN',
|
|
62
62
|
reference,
|
|
63
|
-
sandbox
|
|
63
|
+
sandbox = false,
|
|
64
64
|
livenessDuration = 3000,
|
|
65
|
-
|
|
65
|
+
livenessTaskCount = 2,
|
|
66
|
+
documentType = 'national_id',
|
|
67
|
+
steps: stepsProp,
|
|
66
68
|
theme: themeProp,
|
|
67
69
|
locale: localeProp,
|
|
68
|
-
onCancel,
|
|
69
70
|
onComplete,
|
|
70
71
|
onError,
|
|
71
72
|
}: Props) {
|
|
72
|
-
const [step,
|
|
73
|
+
const [step, setStep] = useState<VerifyStep>('intro')
|
|
73
74
|
const [capture, setCapture] = useState<CaptureState>({})
|
|
74
|
-
const [result,
|
|
75
|
-
const [error,
|
|
75
|
+
const [result, setResult] = useState<VerifyResult | null>(null)
|
|
76
|
+
const [error, setError] = useState<string | null>(null)
|
|
76
77
|
|
|
77
78
|
const theme = useMemo(() => resolveTheme(themeProp), [themeProp])
|
|
78
|
-
const locale: VerifyLocale = useMemo(
|
|
79
|
-
() => ({ ...DEFAULT_LOCALE, ...localeProp }),
|
|
80
|
-
[localeProp]
|
|
81
|
-
)
|
|
79
|
+
const locale: VerifyLocale = useMemo(() => ({ ...DEFAULT_LOCALE, ...localeProp }), [localeProp])
|
|
82
80
|
const { requestPermissions } = usePermissions()
|
|
83
81
|
const s = styles(theme)
|
|
84
82
|
|
|
83
|
+
// Build the ordered flow from the steps prop
|
|
84
|
+
const flow = useMemo((): VerifyStep[] => {
|
|
85
|
+
const wanted = stepsProp ?? ['document', 'selfie', 'liveness'] as VerifyStepName[]
|
|
86
|
+
const out: VerifyStep[] = []
|
|
87
|
+
if (wanted.includes('document')) { out.push('doc-front'); out.push('doc-back') }
|
|
88
|
+
if (wanted.includes('selfie')) out.push('selfie')
|
|
89
|
+
if (wanted.includes('liveness')) out.push('liveness')
|
|
90
|
+
out.push('processing')
|
|
91
|
+
return out
|
|
92
|
+
}, [stepsProp])
|
|
93
|
+
|
|
94
|
+
const next = useCallback((current: VerifyStep) => {
|
|
95
|
+
const idx = flow.indexOf(current)
|
|
96
|
+
const nextStep = flow[idx + 1] ?? 'processing'
|
|
97
|
+
goto(nextStep)
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
}, [flow])
|
|
100
|
+
|
|
101
|
+
const goto = useCallback(async (s: VerifyStep) => {
|
|
102
|
+
if (s === 'doc-front' || s === 'doc-back' || s === 'selfie' || s === 'liveness') {
|
|
103
|
+
const granted = await requestPermissions()
|
|
104
|
+
if (!granted) { setError(locale.error_camera_denied); return }
|
|
105
|
+
}
|
|
106
|
+
setStep(s)
|
|
107
|
+
if (s === 'processing') submit()
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
|
+
}, [capture, locale])
|
|
110
|
+
|
|
85
111
|
const reset = useCallback(() => {
|
|
86
112
|
setStep('intro')
|
|
87
113
|
setCapture({})
|
|
@@ -89,68 +115,38 @@ export function KernaqVerify({
|
|
|
89
115
|
setError(null)
|
|
90
116
|
}, [])
|
|
91
117
|
|
|
92
|
-
const goto = useCallback(async (next: VerifyStep) => {
|
|
93
|
-
if (next === 'document' || next === 'selfie' || next === 'liveness') {
|
|
94
|
-
const granted = await requestPermissions()
|
|
95
|
-
if (!granted) {
|
|
96
|
-
setError(locale.error_camera_denied)
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
setStep(next)
|
|
101
|
-
if (next === 'processing') {
|
|
102
|
-
submit()
|
|
103
|
-
}
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
-
}, [capture, locale])
|
|
106
|
-
|
|
107
118
|
const submit = useCallback(async () => {
|
|
108
|
-
const {
|
|
109
|
-
if (!
|
|
110
|
-
|
|
111
|
-
setStep('result')
|
|
112
|
-
return
|
|
119
|
+
const { docFrontUri, docBackUri, selfieUri, videoUri } = capture
|
|
120
|
+
if (!docFrontUri && !selfieUri) {
|
|
121
|
+
// At least one capture needed depending on steps
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
const base = (backendUrl ?? 'https://api.identity.kernaq.com/v1').replace(/\/$/, '')
|
|
116
125
|
const form = new FormData()
|
|
117
126
|
|
|
118
|
-
|
|
119
|
-
uri:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
uri: selfieUri,
|
|
126
|
-
|
|
127
|
-
type: 'image/jpeg',
|
|
128
|
-
} as unknown as Blob)
|
|
129
|
-
|
|
127
|
+
if (docFrontUri) {
|
|
128
|
+
form.append('document', { uri: docFrontUri, name: 'doc_front.jpg', type: 'image/jpeg' } as any)
|
|
129
|
+
}
|
|
130
|
+
if (docBackUri) {
|
|
131
|
+
form.append('document_back', { uri: docBackUri, name: 'doc_back.jpg', type: 'image/jpeg' } as any)
|
|
132
|
+
}
|
|
133
|
+
if (selfieUri) {
|
|
134
|
+
form.append('selfie', { uri: selfieUri, name: 'selfie.jpg', type: 'image/jpeg' } as any)
|
|
135
|
+
}
|
|
130
136
|
if (videoUri) {
|
|
131
|
-
form.append('video', {
|
|
132
|
-
uri: videoUri,
|
|
133
|
-
name: 'liveness.mp4',
|
|
134
|
-
type: 'video/mp4',
|
|
135
|
-
} as unknown as Blob)
|
|
137
|
+
form.append('video', { uri: videoUri, name: 'liveness.mp4', type: 'video/mp4' } as any)
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
form.append('document_type',
|
|
139
|
-
form.append('country',
|
|
140
|
-
form.append('reference',
|
|
140
|
+
form.append('document_type', documentType)
|
|
141
|
+
form.append('country', country)
|
|
142
|
+
form.append('reference', reference ?? `kq_${Date.now()}`)
|
|
141
143
|
if (sandbox) form.append('sandbox', 'true')
|
|
142
144
|
|
|
143
|
-
const headers: Record<string, string> = {
|
|
144
|
-
'Content-Type': 'multipart/form-data',
|
|
145
|
-
}
|
|
145
|
+
const headers: Record<string, string> = { 'Content-Type': 'multipart/form-data' }
|
|
146
146
|
if (apiKey) headers['X-API-Key'] = apiKey
|
|
147
147
|
|
|
148
148
|
try {
|
|
149
|
-
const res
|
|
150
|
-
method: 'POST',
|
|
151
|
-
headers,
|
|
152
|
-
body: form,
|
|
153
|
-
})
|
|
149
|
+
const res = await fetch(`${base}/verify`, { method: 'POST', headers, body: form })
|
|
154
150
|
const body = (await res.json()) as Record<string, unknown>
|
|
155
151
|
if (!res.ok) throw new Error((body['message'] as string) ?? `HTTP ${res.status}`)
|
|
156
152
|
|
|
@@ -169,117 +165,110 @@ export function KernaqVerify({
|
|
|
169
165
|
} catch (err) {
|
|
170
166
|
const msg = (err as Error).message
|
|
171
167
|
setError(`${locale.error_network} ${msg}`)
|
|
168
|
+
setResult(null)
|
|
172
169
|
setStep('result')
|
|
173
170
|
onError?.({ code: 'network_error', message: msg })
|
|
174
171
|
}
|
|
175
172
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
176
173
|
}, [capture])
|
|
177
174
|
|
|
178
|
-
const handleCancel = () => {
|
|
179
|
-
reset()
|
|
180
|
-
onCancel?.()
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const progressSteps: VerifyStep[] = ['document', 'selfie', 'liveness', 'processing']
|
|
184
|
-
const currentIdx = STEP_INDEX[step]
|
|
185
|
-
|
|
186
175
|
return (
|
|
187
176
|
<Modal
|
|
188
177
|
visible={visible}
|
|
189
178
|
animationType="slide"
|
|
190
179
|
presentationStyle="pageSheet"
|
|
191
|
-
onRequestClose={
|
|
180
|
+
onRequestClose={() => {/* intentionally empty — user cannot dismiss */}}
|
|
192
181
|
>
|
|
193
|
-
<SafeAreaView style={[s.
|
|
194
|
-
{/* Header */}
|
|
195
|
-
<View style={s.header}>
|
|
196
|
-
<Text style={s.logo}>Kernaq</Text>
|
|
197
|
-
{step !== 'processing' && (
|
|
198
|
-
<TouchableOpacity onPress={handleCancel} style={s.closeBtn} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
|
199
|
-
<Text style={s.closeText}>✕</Text>
|
|
200
|
-
</TouchableOpacity>
|
|
201
|
-
)}
|
|
202
|
-
</View>
|
|
182
|
+
<SafeAreaView style={[s.safe, { backgroundColor: theme.bg }]}>
|
|
203
183
|
|
|
204
|
-
{/*
|
|
205
|
-
{
|
|
206
|
-
<View style={s.progress}>
|
|
207
|
-
{progressSteps.map((_, i) => (
|
|
208
|
-
<View
|
|
209
|
-
key={i}
|
|
210
|
-
style={[
|
|
211
|
-
s.progressDot,
|
|
212
|
-
i < currentIdx && { backgroundColor: theme.accent, opacity: 0.4 },
|
|
213
|
-
i === currentIdx && { backgroundColor: theme.accent },
|
|
214
|
-
]}
|
|
215
|
-
/>
|
|
216
|
-
))}
|
|
217
|
-
</View>
|
|
218
|
-
)}
|
|
219
|
-
|
|
220
|
-
{/* Body */}
|
|
221
|
-
<ScrollView
|
|
222
|
-
style={s.scroll}
|
|
223
|
-
contentContainerStyle={s.scrollContent}
|
|
224
|
-
keyboardShouldPersistTaps="handled"
|
|
225
|
-
bounces={false}
|
|
226
|
-
>
|
|
184
|
+
{/* ── Content ── */}
|
|
185
|
+
<View style={s.body}>
|
|
227
186
|
{step === 'intro' && (
|
|
228
187
|
<IntroStep
|
|
229
188
|
theme={theme}
|
|
230
189
|
locale={locale}
|
|
231
|
-
onStart={() =>
|
|
190
|
+
onStart={() => {
|
|
191
|
+
const first = flow[0]
|
|
192
|
+
if (first) goto(first)
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{step === 'doc-front' && (
|
|
198
|
+
<DocumentStep
|
|
199
|
+
theme={theme}
|
|
200
|
+
locale={locale}
|
|
201
|
+
side="front"
|
|
202
|
+
onCapture={(uri) => {
|
|
203
|
+
setCapture(c => ({ ...c, docFrontUri: uri }))
|
|
204
|
+
next('doc-front')
|
|
205
|
+
}}
|
|
232
206
|
/>
|
|
233
207
|
)}
|
|
234
|
-
|
|
208
|
+
|
|
209
|
+
{step === 'doc-back' && (
|
|
235
210
|
<DocumentStep
|
|
236
211
|
theme={theme}
|
|
237
212
|
locale={locale}
|
|
213
|
+
side="back"
|
|
214
|
+
frontUri={capture.docFrontUri}
|
|
238
215
|
onCapture={(uri) => {
|
|
239
|
-
setCapture(
|
|
240
|
-
|
|
216
|
+
setCapture(c => ({ ...c, docBackUri: uri }))
|
|
217
|
+
next('doc-back')
|
|
218
|
+
}}
|
|
219
|
+
onRetakeFront={() => {
|
|
220
|
+
setCapture(c => ({ ...c, docFrontUri: undefined }))
|
|
221
|
+
goto('doc-front')
|
|
241
222
|
}}
|
|
242
223
|
/>
|
|
243
224
|
)}
|
|
225
|
+
|
|
244
226
|
{step === 'selfie' && (
|
|
245
227
|
<SelfieStep
|
|
246
228
|
theme={theme}
|
|
247
229
|
locale={locale}
|
|
248
230
|
onCapture={(uri) => {
|
|
249
|
-
setCapture(
|
|
250
|
-
|
|
231
|
+
setCapture(c => ({ ...c, selfieUri: uri }))
|
|
232
|
+
next('selfie')
|
|
251
233
|
}}
|
|
252
|
-
onBack={() => setStep('document')}
|
|
253
234
|
/>
|
|
254
235
|
)}
|
|
236
|
+
|
|
255
237
|
{step === 'liveness' && (
|
|
256
238
|
<LivenessStep
|
|
257
239
|
theme={theme}
|
|
258
240
|
locale={locale}
|
|
259
241
|
duration={livenessDuration}
|
|
242
|
+
taskCount={livenessTaskCount}
|
|
260
243
|
onCapture={(uri) => {
|
|
261
|
-
setCapture(
|
|
262
|
-
|
|
244
|
+
setCapture(c => ({ ...c, videoUri: uri }))
|
|
245
|
+
next('liveness')
|
|
263
246
|
}}
|
|
264
|
-
onBack={() => setStep('selfie')}
|
|
265
247
|
/>
|
|
266
248
|
)}
|
|
249
|
+
|
|
267
250
|
{step === 'processing' && (
|
|
268
251
|
<ProcessingStep theme={theme} locale={locale} />
|
|
269
252
|
)}
|
|
253
|
+
|
|
270
254
|
{step === 'result' && (
|
|
271
255
|
<ResultStep
|
|
272
256
|
theme={theme}
|
|
273
257
|
locale={locale}
|
|
274
258
|
result={result}
|
|
275
259
|
error={error}
|
|
276
|
-
onRetry={() => {
|
|
277
|
-
reset()
|
|
278
|
-
setStep('intro')
|
|
279
|
-
}}
|
|
260
|
+
onRetry={() => { reset(); goto('intro') }}
|
|
280
261
|
/>
|
|
281
262
|
)}
|
|
282
|
-
</
|
|
263
|
+
</View>
|
|
264
|
+
|
|
265
|
+
{/* ── Footer — Secured by Kernaq ── */}
|
|
266
|
+
{step !== 'processing' && (
|
|
267
|
+
<View style={s.footer}>
|
|
268
|
+
<Text style={s.footerText}>Secured by </Text>
|
|
269
|
+
<Text style={[s.footerText, { fontWeight: '600', color: theme.text }]}>Kernaq</Text>
|
|
270
|
+
</View>
|
|
271
|
+
)}
|
|
283
272
|
</SafeAreaView>
|
|
284
273
|
</Modal>
|
|
285
274
|
)
|
|
@@ -287,43 +276,23 @@ export function KernaqVerify({
|
|
|
287
276
|
|
|
288
277
|
const styles = (t: ReturnType<typeof resolveTheme>) =>
|
|
289
278
|
StyleSheet.create({
|
|
290
|
-
|
|
279
|
+
safe: {
|
|
291
280
|
flex: 1,
|
|
292
281
|
},
|
|
293
|
-
|
|
282
|
+
body: {
|
|
283
|
+
flex: 1,
|
|
284
|
+
},
|
|
285
|
+
footer: {
|
|
294
286
|
flexDirection: 'row',
|
|
295
287
|
alignItems: 'center',
|
|
296
|
-
justifyContent: '
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
logo: {
|
|
302
|
-
fontSize: 14,
|
|
303
|
-
fontWeight: '700',
|
|
304
|
-
color: t.accent,
|
|
305
|
-
letterSpacing: 1.2,
|
|
306
|
-
textTransform: 'uppercase',
|
|
288
|
+
justifyContent: 'center',
|
|
289
|
+
paddingVertical: 10,
|
|
290
|
+
paddingBottom: Platform.OS === 'ios' ? 4 : 10,
|
|
291
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
292
|
+
borderTopColor: t.border,
|
|
307
293
|
},
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
},
|
|
311
|
-
closeText: {
|
|
312
|
-
fontSize: 18,
|
|
294
|
+
footerText: {
|
|
295
|
+
fontSize: 11,
|
|
313
296
|
color: t.subtext,
|
|
314
297
|
},
|
|
315
|
-
progress: {
|
|
316
|
-
flexDirection: 'row',
|
|
317
|
-
gap: 6,
|
|
318
|
-
paddingHorizontal: 20,
|
|
319
|
-
paddingBottom: 8,
|
|
320
|
-
},
|
|
321
|
-
progressDot: {
|
|
322
|
-
flex: 1,
|
|
323
|
-
height: 3,
|
|
324
|
-
borderRadius: 999,
|
|
325
|
-
backgroundColor: t.border,
|
|
326
|
-
},
|
|
327
|
-
scroll: { flex: 1 },
|
|
328
|
-
scrollContent: { flexGrow: 1 },
|
|
329
298
|
})
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @kernaq/verify-react-native
|
|
2
|
+
* @kernaq/verify-react-native v2
|
|
3
3
|
*
|
|
4
4
|
* Drop-in KYC verification UI for React Native.
|
|
5
|
-
*
|
|
5
|
+
* Matches @kernaq/verify web v2.0.0 — same flow, same API surface.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* import { KernaqVerify } from '@kernaq/verify-react-native'
|
|
9
9
|
*
|
|
10
10
|
* <KernaqVerify
|
|
11
11
|
* visible={showVerify}
|
|
12
|
-
*
|
|
12
|
+
* documentType="national_id"
|
|
13
13
|
* country="KEN"
|
|
14
|
+
* reference={userId}
|
|
15
|
+
* steps={['document', 'selfie', 'liveness']}
|
|
14
16
|
* onComplete={(result) => console.log(result.verdict)}
|
|
15
|
-
* onCancel={() => setShowVerify(false)}
|
|
16
17
|
* />
|
|
17
18
|
*
|
|
18
19
|
* Peer dependencies:
|
|
@@ -21,12 +22,16 @@
|
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
export { KernaqVerify } from './KernaqVerify'
|
|
25
|
+
export { analyseImageUri, analyseSelfieFrame } from './quality'
|
|
26
|
+
export type { QualityReport, QualityConfig, QualityFailure } from './quality'
|
|
24
27
|
export type {
|
|
25
28
|
VerifyConfig,
|
|
26
29
|
VerifyResult,
|
|
27
30
|
VerifyStep,
|
|
31
|
+
VerifyStepName,
|
|
28
32
|
VerifyVerdict,
|
|
29
33
|
VerifyTheme,
|
|
30
34
|
VerifyLocale,
|
|
31
35
|
DocumentType,
|
|
36
|
+
LivenessTask,
|
|
32
37
|
} from './types'
|
package/src/locale.ts
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
import type { VerifyLocale } from './types'
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_LOCALE: VerifyLocale = {
|
|
4
|
-
intro_title:
|
|
5
|
-
intro_body:
|
|
6
|
-
intro_cta:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
4
|
+
intro_title: 'Verify your identity',
|
|
5
|
+
intro_body: 'This takes about 90 seconds. You\'ll need your ID document and a clear view of your face.',
|
|
6
|
+
intro_cta: 'Get started',
|
|
7
|
+
|
|
8
|
+
doc_front_title: 'Document — front',
|
|
9
|
+
doc_front_instruction: 'Hold the front of your document flat inside the frame. Ensure all four corners are visible and there is no glare.',
|
|
10
|
+
doc_back_title: 'Document — back',
|
|
11
|
+
doc_back_instruction: 'Flip your document over and hold the back inside the frame.',
|
|
12
|
+
doc_capture_btn: 'Capture',
|
|
13
|
+
doc_retake_btn: 'Retake',
|
|
14
|
+
|
|
15
|
+
selfie_title: 'Selfie',
|
|
16
|
+
selfie_instruction: 'Centre your face inside the oval. Look directly at the camera.',
|
|
17
|
+
|
|
18
|
+
liveness_title: 'Liveness check',
|
|
19
|
+
liveness_ready: 'Follow the instructions as they appear.',
|
|
20
|
+
liveness_task_prefix: 'Please',
|
|
21
|
+
liveness_complete: 'Hold still…',
|
|
22
|
+
|
|
23
|
+
processing_title: 'Verifying…',
|
|
24
|
+
|
|
25
|
+
result_pass_title: 'Identity verified',
|
|
26
|
+
result_pass_body: 'Your verification was successful.',
|
|
27
|
+
result_fail_title: 'Verification failed',
|
|
28
|
+
result_fail_body: 'We could not verify your identity. Please try again with better lighting.',
|
|
29
|
+
result_review_title: 'Under review',
|
|
30
|
+
result_review_body: 'Your submission is being manually reviewed. This usually takes a few minutes.',
|
|
31
|
+
|
|
32
|
+
error_camera_denied: 'Camera access denied. Allow camera permissions and try again.',
|
|
33
|
+
error_blur: 'Image is too blurry — hold steady and ensure good lighting.',
|
|
34
|
+
error_quality: 'Image quality too low. Try better lighting or a steadier hand.',
|
|
35
|
+
error_network: 'Network error. Check your connection and try again.',
|
|
33
36
|
}
|