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