@kernaq/verify-react-native 2.0.1 → 2.0.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.
- package/README.md +1 -1
- package/dist/index.esm.js +63 -22
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +63 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -9
- package/src/KernaqVerify.tsx +43 -16
- package/src/steps/LivenessStep.tsx +15 -8
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ Never expose a live API key in your app. Instead, forward requests through your
|
|
|
159
159
|
/>
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
Your backend endpoint receives a `multipart/form-data` POST with `
|
|
162
|
+
Your backend endpoint receives a `multipart/form-data` POST with `doc_front`, `doc_back`, `selfie`, `video`, `document_type`, `country`, and `reference` fields. Forward it to `https://api.identity.kernaq.com/v1/verify` with your secret key.
|
|
163
163
|
|
|
164
164
|
## Result object
|
|
165
165
|
|
package/dist/index.esm.js
CHANGED
|
@@ -954,6 +954,13 @@ function LivenessStep({ theme, locale, duration, taskCount = 2, onCapture }) {
|
|
|
954
954
|
;
|
|
955
955
|
const [complete, setComplete] = useState(false);
|
|
956
956
|
const [recording, setRecording] = useState(false);
|
|
957
|
+
// Keep a ref in sync with taskIdx so setTimeout/setInterval closures always
|
|
958
|
+
// read the latest value and don't stall on a stale snapshot.
|
|
959
|
+
const taskIdxRef = useRef(0);
|
|
960
|
+
const setTaskIdxSynced = (n)=>{
|
|
961
|
+
taskIdxRef.current = n;
|
|
962
|
+
setTaskIdx(n);
|
|
963
|
+
};
|
|
957
964
|
const progressTimer = useRef(null);
|
|
958
965
|
const taskTimer = useRef(null);
|
|
959
966
|
const pulseAnim = useRef(new Animated.Value(1)).current;
|
|
@@ -982,13 +989,12 @@ function LivenessStep({ theme, locale, duration, taskCount = 2, onCapture }) {
|
|
|
982
989
|
]);
|
|
983
990
|
const startTask = ()=>{
|
|
984
991
|
if (!recording) {
|
|
985
|
-
// Begin recording when first task starts
|
|
986
992
|
startRecording((video)=>{
|
|
987
993
|
onCapture(video.uri);
|
|
988
994
|
}, ()=>{});
|
|
989
995
|
setRecording(true);
|
|
990
996
|
}
|
|
991
|
-
const task = tasks[
|
|
997
|
+
const task = tasks[taskIdxRef.current];
|
|
992
998
|
if (!task) return;
|
|
993
999
|
setStarted(true);
|
|
994
1000
|
setProgress(0);
|
|
@@ -1005,16 +1011,16 @@ function LivenessStep({ theme, locale, duration, taskCount = 2, onCapture }) {
|
|
|
1005
1011
|
}, 80);
|
|
1006
1012
|
};
|
|
1007
1013
|
const nextTask = ()=>{
|
|
1008
|
-
const next =
|
|
1014
|
+
const next = taskIdxRef.current + 1;
|
|
1009
1015
|
setTaskDone((prev)=>prev + 1);
|
|
1010
1016
|
if (next >= tasks.length) {
|
|
1011
1017
|
finishLiveness();
|
|
1012
1018
|
return;
|
|
1013
1019
|
}
|
|
1014
|
-
|
|
1020
|
+
setTaskIdxSynced(next);
|
|
1015
1021
|
setStarted(false);
|
|
1016
1022
|
setProgress(0);
|
|
1017
|
-
//
|
|
1023
|
+
// Use the ref-based startTask via the timer so it reads the fresh taskIdx
|
|
1018
1024
|
taskTimer.current = setTimeout(()=>startTask(), 500);
|
|
1019
1025
|
};
|
|
1020
1026
|
const finishLiveness = ()=>{
|
|
@@ -1490,6 +1496,16 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1490
1496
|
const [capture, setCapture] = useState({});
|
|
1491
1497
|
const [result, setResult] = useState(null);
|
|
1492
1498
|
const [error, setError] = useState(null);
|
|
1499
|
+
// Mirror capture in a ref so submit() always reads the latest value even
|
|
1500
|
+
// when called from inside a stale closure (e.g. goto → submit chain).
|
|
1501
|
+
const captureRef = useRef({});
|
|
1502
|
+
const setCaptureSync = (updater)=>{
|
|
1503
|
+
setCapture((prev)=>{
|
|
1504
|
+
const next = updater(prev);
|
|
1505
|
+
captureRef.current = next;
|
|
1506
|
+
return next;
|
|
1507
|
+
});
|
|
1508
|
+
};
|
|
1493
1509
|
const theme = useMemo(()=>resolveTheme(themeProp), [
|
|
1494
1510
|
themeProp
|
|
1495
1511
|
]);
|
|
@@ -1538,30 +1554,55 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1538
1554
|
}
|
|
1539
1555
|
setStep(s);
|
|
1540
1556
|
if (s === 'processing') submit();
|
|
1557
|
+
// submit is stable (no deps that change); locale/requestPermissions are stable refs
|
|
1541
1558
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1542
1559
|
}, [
|
|
1543
|
-
capture,
|
|
1544
1560
|
locale
|
|
1545
1561
|
]);
|
|
1546
1562
|
const reset = useCallback(()=>{
|
|
1547
1563
|
setStep('intro');
|
|
1564
|
+
captureRef.current = {};
|
|
1548
1565
|
setCapture({});
|
|
1549
1566
|
setResult(null);
|
|
1550
1567
|
setError(null);
|
|
1551
1568
|
}, []);
|
|
1552
1569
|
const submit = useCallback(async ()=>{
|
|
1553
|
-
const { docFrontUri, docBackUri, selfieUri, videoUri } =
|
|
1570
|
+
const { docFrontUri, docBackUri, selfieUri, videoUri } = captureRef.current;
|
|
1571
|
+
const steps = stepsProp ?? [
|
|
1572
|
+
'document',
|
|
1573
|
+
'selfie',
|
|
1574
|
+
'liveness'
|
|
1575
|
+
];
|
|
1576
|
+
if (steps.includes('document') && !docFrontUri) {
|
|
1577
|
+
setError('Document capture is missing — please restart.');
|
|
1578
|
+
setStep('result');
|
|
1579
|
+
onError?.({
|
|
1580
|
+
code: 'capture_missing',
|
|
1581
|
+
message: 'Document capture missing'
|
|
1582
|
+
});
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
if (steps.includes('selfie') && !selfieUri) {
|
|
1586
|
+
setError('Selfie capture is missing — please restart.');
|
|
1587
|
+
setStep('result');
|
|
1588
|
+
onError?.({
|
|
1589
|
+
code: 'capture_missing',
|
|
1590
|
+
message: 'Selfie capture missing'
|
|
1591
|
+
});
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1554
1594
|
const base = (backendUrl ?? 'https://api.identity.kernaq.com/v1').replace(/\/$/, '');
|
|
1595
|
+
const endpoint = sandbox ? '/verify/sandbox' : '/verify';
|
|
1555
1596
|
const form = new FormData();
|
|
1556
1597
|
if (docFrontUri) {
|
|
1557
|
-
form.append('
|
|
1598
|
+
form.append('doc_front', {
|
|
1558
1599
|
uri: docFrontUri,
|
|
1559
1600
|
name: 'doc_front.jpg',
|
|
1560
1601
|
type: 'image/jpeg'
|
|
1561
1602
|
});
|
|
1562
1603
|
}
|
|
1563
1604
|
if (docBackUri) {
|
|
1564
|
-
form.append('
|
|
1605
|
+
form.append('doc_back', {
|
|
1565
1606
|
uri: docBackUri,
|
|
1566
1607
|
name: 'doc_back.jpg',
|
|
1567
1608
|
type: 'image/jpeg'
|
|
@@ -1584,13 +1625,14 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1584
1625
|
form.append('document_type', documentType);
|
|
1585
1626
|
form.append('country', country);
|
|
1586
1627
|
form.append('reference', reference ?? `kq_${Date.now()}`);
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1628
|
+
// sandbox is handled via URL routing above — no form field needed
|
|
1629
|
+
// Do NOT set Content-Type manually — RN fetch will set multipart/form-data with
|
|
1630
|
+
// the correct boundary automatically. Setting it manually omits the boundary and
|
|
1631
|
+
// causes the server to reject the request.
|
|
1632
|
+
const headers = {};
|
|
1591
1633
|
if (apiKey) headers['X-API-Key'] = apiKey;
|
|
1592
1634
|
try {
|
|
1593
|
-
const res = await fetch(`${base}
|
|
1635
|
+
const res = await fetch(`${base}${endpoint}`, {
|
|
1594
1636
|
method: 'POST',
|
|
1595
1637
|
headers,
|
|
1596
1638
|
body: form
|
|
@@ -1620,9 +1662,8 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1620
1662
|
});
|
|
1621
1663
|
}
|
|
1622
1664
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1623
|
-
}, [
|
|
1624
|
-
|
|
1625
|
-
]);
|
|
1665
|
+
}, []) // no deps — always reads from captureRef.current, never from stale closure
|
|
1666
|
+
;
|
|
1626
1667
|
return /*#__PURE__*/ React.createElement(Modal, {
|
|
1627
1668
|
visible: visible,
|
|
1628
1669
|
animationType: "slide",
|
|
@@ -1649,7 +1690,7 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1649
1690
|
locale: locale,
|
|
1650
1691
|
side: "front",
|
|
1651
1692
|
onCapture: (uri)=>{
|
|
1652
|
-
|
|
1693
|
+
setCaptureSync((c)=>({
|
|
1653
1694
|
...c,
|
|
1654
1695
|
docFrontUri: uri
|
|
1655
1696
|
}));
|
|
@@ -1661,14 +1702,14 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1661
1702
|
side: "back",
|
|
1662
1703
|
frontUri: capture.docFrontUri,
|
|
1663
1704
|
onCapture: (uri)=>{
|
|
1664
|
-
|
|
1705
|
+
setCaptureSync((c)=>({
|
|
1665
1706
|
...c,
|
|
1666
1707
|
docBackUri: uri
|
|
1667
1708
|
}));
|
|
1668
1709
|
next('doc-back');
|
|
1669
1710
|
},
|
|
1670
1711
|
onRetakeFront: ()=>{
|
|
1671
|
-
|
|
1712
|
+
setCaptureSync((c)=>({
|
|
1672
1713
|
...c,
|
|
1673
1714
|
docFrontUri: undefined
|
|
1674
1715
|
}));
|
|
@@ -1678,7 +1719,7 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1678
1719
|
theme: theme,
|
|
1679
1720
|
locale: locale,
|
|
1680
1721
|
onCapture: (uri)=>{
|
|
1681
|
-
|
|
1722
|
+
setCaptureSync((c)=>({
|
|
1682
1723
|
...c,
|
|
1683
1724
|
selfieUri: uri
|
|
1684
1725
|
}));
|
|
@@ -1690,7 +1731,7 @@ function KernaqVerify({ visible, apiKey, backendUrl, country = 'KEN', reference,
|
|
|
1690
1731
|
duration: livenessDuration,
|
|
1691
1732
|
taskCount: livenessTaskCount,
|
|
1692
1733
|
onCapture: (uri)=>{
|
|
1693
|
-
|
|
1734
|
+
setCaptureSync((c)=>({
|
|
1694
1735
|
...c,
|
|
1695
1736
|
videoUri: uri
|
|
1696
1737
|
}));
|