@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 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 `document`, `selfie`, `video`, `document_type`, `country`, and `reference` fields. Forward it to `https://api.identity.kernaq.com/v1/verify` with your secret key.
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[taskIdx];
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 = taskIdx + 1;
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
- setTaskIdx(next);
1020
+ setTaskIdxSynced(next);
1015
1021
  setStarted(false);
1016
1022
  setProgress(0);
1017
- // Short pause then auto-start next task
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 } = capture;
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('document', {
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('document_back', {
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
- if (sandbox) form.append('sandbox', 'true');
1588
- const headers = {
1589
- 'Content-Type': 'multipart/form-data'
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}/verify`, {
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
- capture
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
- setCapture((c)=>({
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
- setCapture((c)=>({
1705
+ setCaptureSync((c)=>({
1665
1706
  ...c,
1666
1707
  docBackUri: uri
1667
1708
  }));
1668
1709
  next('doc-back');
1669
1710
  },
1670
1711
  onRetakeFront: ()=>{
1671
- setCapture((c)=>({
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
- setCapture((c)=>({
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
- setCapture((c)=>({
1734
+ setCaptureSync((c)=>({
1694
1735
  ...c,
1695
1736
  videoUri: uri
1696
1737
  }));