@neofaceid/web-sdk 2.0.1 → 2.1.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 CHANGED
@@ -729,7 +729,9 @@ export declare enum ErrorType {
729
729
  UNKNOWN = "UnknownError",
730
730
  CONSENT_DENIED = "ConsentDeniedError",
731
731
  /** NEO-224 · piscada real (EAR) não detectada dentro da janela de liveness. */
732
- LIVENESS_BLINK_MISSING = "LivenessBlinkMissingError"
732
+ LIVENESS_BLINK_MISSING = "LivenessBlinkMissingError",
733
+ /** NEO-230 · polegar para cima não detectado na janela de authorizeOperation. */
734
+ GESTURE_NOT_DETECTED = "GestureNotDetectedError"
733
735
  }
734
736
 
735
737
  export declare function FaceCaptureModal({ accessToken, onClose, onSuccess, onCannotGesture, autoLighting, }: FaceCaptureModalProps): JSX_2.Element;
@@ -1391,7 +1393,7 @@ export declare const registerPersonWithoutFace: (personData: {
1391
1393
  /**
1392
1394
  * Data de lançamento da versão atual
1393
1395
  */
1394
- export declare const RELEASE_DATE = "2026-09-20";
1396
+ export declare const RELEASE_DATE = "2026-09-23";
1395
1397
 
1396
1398
  declare interface RequestChallengeWithSessionParams {
1397
1399
  applicationToken: string;
@@ -1864,7 +1866,7 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
1864
1866
  * MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
1865
1867
  * PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
1866
1868
  */
1867
- export declare const VERSION = "2.0.1";
1869
+ export declare const VERSION = "2.1.1";
1868
1870
 
1869
1871
  /**
1870
1872
  * Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
@@ -162,6 +162,7 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
162
162
  ErrorType2["UNKNOWN"] = "UnknownError";
163
163
  ErrorType2["CONSENT_DENIED"] = "ConsentDeniedError";
164
164
  ErrorType2["LIVENESS_BLINK_MISSING"] = "LivenessBlinkMissingError";
165
+ ErrorType2["GESTURE_NOT_DETECTED"] = "GestureNotDetectedError";
165
166
  return ErrorType2;
166
167
  })(ErrorType || {});
167
168
  class NeoFaceError extends Error {
@@ -201,6 +202,8 @@ class NeoFaceError extends Error {
201
202
  return "É necessário aceitar o uso do seu rosto para continuar.";
202
203
  case "LivenessBlinkMissingError":
203
204
  return "Não detectamos uma piscada. Pisque os olhos naturalmente e tente novamente.";
205
+ case "GestureNotDetectedError":
206
+ return "Não detectamos o polegar para cima. Mostre o gesto claramente para a câmera e tente novamente.";
204
207
  case "ApiError":
205
208
  if (this.message.includes("400")) return "Requisição inválida. Tente novamente.";
206
209
  if (this.message.includes("500"))
@@ -6095,7 +6098,7 @@ const recordFaceVideo = async (options = {}) => {
6095
6098
  const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
6096
6099
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
6097
6100
  const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
6098
- const wait = (ms = intervalMs) => new Promise((resolve) => {
6101
+ const wait2 = (ms = intervalMs) => new Promise((resolve) => {
6099
6102
  setTimeout(resolve, ms);
6100
6103
  });
6101
6104
  const retryAfterMs = (response) => {
@@ -6131,12 +6134,12 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
6131
6134
  }
6132
6135
  if (response.status >= 500) {
6133
6136
  lastTransient = `HTTP ${response.status}`;
6134
- await wait();
6137
+ await wait2();
6135
6138
  continue;
6136
6139
  }
6137
6140
  if (response.status === 429) {
6138
6141
  lastTransient = "HTTP 429";
6139
- await wait(retryAfterMs(response));
6142
+ await wait2(retryAfterMs(response));
6140
6143
  continue;
6141
6144
  }
6142
6145
  if (response.status === 401 || response.status === 403) {
@@ -6165,13 +6168,13 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
6165
6168
  const reason = ((_h = (_g = data.data) == null ? void 0 : _g.result) == null ? void 0 : _h.message) || ((_i = data.data) == null ? void 0 : _i.error) || ((_j = data.data) == null ? void 0 : _j.message) || data.message || "Task failed";
6166
6169
  throw new NeoFaceError(reason, ErrorType.RECOGNITION_FAILED);
6167
6170
  }
6168
- await wait();
6171
+ await wait2();
6169
6172
  } catch (error) {
6170
6173
  if (error instanceof NeoFaceError) {
6171
6174
  throw error;
6172
6175
  }
6173
6176
  lastTransient = error instanceof Error ? error.message : "erro desconhecido";
6174
- await wait();
6177
+ await wait2();
6175
6178
  }
6176
6179
  }
6177
6180
  throw new NeoFaceError(
@@ -6633,9 +6636,9 @@ function ScreenFlashOverlay({
6633
6636
  ] })
6634
6637
  ] });
6635
6638
  }
6636
- const POLL_INTERVAL_MS = 50;
6639
+ const POLL_INTERVAL_MS$1 = 50;
6637
6640
  const MIN_HORIZONTAL_DISTANCE = 1e-6;
6638
- function distance(a, b) {
6641
+ function distance$1(a, b) {
6639
6642
  return Math.hypot(a.x - b.x, a.y - b.y);
6640
6643
  }
6641
6644
  function calculateEAR(eyePoints) {
@@ -6643,9 +6646,9 @@ function calculateEAR(eyePoints) {
6643
6646
  throw new Error(`calculateEAR espera exatamente 6 pontos do olho, recebeu ${eyePoints.length}`);
6644
6647
  }
6645
6648
  const [p1, p2, p3, p4, p5, p6] = eyePoints;
6646
- const verticalA = distance(p2, p6);
6647
- const verticalB = distance(p3, p5);
6648
- const horizontal = distance(p1, p4);
6649
+ const verticalA = distance$1(p2, p6);
6650
+ const verticalB = distance$1(p3, p5);
6651
+ const horizontal = distance$1(p1, p4);
6649
6652
  if (horizontal < MIN_HORIZONTAL_DISTANCE) return 0;
6650
6653
  return (verticalA + verticalB) / (2 * horizontal);
6651
6654
  }
@@ -6682,9 +6685,9 @@ function detectBlinkInWindow(faceapi2, videoEl, windowMs = 3e3, threshold = 0.2,
6682
6685
  return;
6683
6686
  }
6684
6687
  }
6685
- setTimeout(checkFrame, POLL_INTERVAL_MS);
6688
+ setTimeout(checkFrame, POLL_INTERVAL_MS$1);
6686
6689
  }).catch(() => {
6687
- setTimeout(checkFrame, POLL_INTERVAL_MS);
6690
+ setTimeout(checkFrame, POLL_INTERVAL_MS$1);
6688
6691
  });
6689
6692
  };
6690
6693
  checkFrame();
@@ -8916,8 +8919,8 @@ function BiometricRegistrationModal({
8916
8919
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
8917
8920
  ] });
8918
8921
  }
8919
- const VERSION = "2.0.1";
8920
- const RELEASE_DATE = "2026-09-20";
8922
+ const VERSION = "2.1.1";
8923
+ const RELEASE_DATE = "2026-09-23";
8921
8924
  const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
8922
8925
  const CONSENT_TRAIL_MAX_LIMIT = 100;
8923
8926
  async function hashString(inputStr) {
@@ -13325,6 +13328,73 @@ async function registerDocumentByImage(personId, jwtToken, options = {}) {
13325
13328
  if (captured.backImage) images.push(captured.backImage);
13326
13329
  return registerDocumentByImage$1(personId, jwtToken, images);
13327
13330
  }
13331
+ const WRIST = 0;
13332
+ const THUMB_MCP = 2;
13333
+ const THUMB_TIP = 4;
13334
+ const OTHER_FINGERS = [
13335
+ { tip: 8, pip: 6 },
13336
+ // indicador
13337
+ { tip: 12, pip: 10 },
13338
+ // médio
13339
+ { tip: 16, pip: 14 },
13340
+ // anelar
13341
+ { tip: 20, pip: 18 }
13342
+ // mindinho
13343
+ ];
13344
+ const distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
13345
+ function isThumbsUp(keypoints) {
13346
+ if (keypoints.length < 21) return false;
13347
+ const wrist = keypoints[WRIST];
13348
+ const thumbMcp = keypoints[THUMB_MCP];
13349
+ const thumbTip = keypoints[THUMB_TIP];
13350
+ const thumbExtended = distance(thumbTip, wrist) > distance(thumbMcp, wrist) * 1.3;
13351
+ if (!thumbExtended) return false;
13352
+ return OTHER_FINGERS.every(({ tip, pip }) => {
13353
+ const tipPoint = keypoints[tip];
13354
+ const pipPoint = keypoints[pip];
13355
+ return distance(tipPoint, wrist) < distance(pipPoint, wrist);
13356
+ });
13357
+ }
13358
+ const POLL_INTERVAL_MS = 150;
13359
+ let detectorPromise = null;
13360
+ const MEDIAPIPE_HANDS_VERSION = "0.4.1675469240";
13361
+ async function createDetector() {
13362
+ const handPoseDetection = await import("@tensorflow-models/hand-pose-detection");
13363
+ return handPoseDetection.createDetector(handPoseDetection.SupportedModels.MediaPipeHands, {
13364
+ runtime: "mediapipe",
13365
+ modelType: "lite",
13366
+ maxHands: 1,
13367
+ solutionPath: `https://cdn.jsdelivr.net/npm/@mediapipe/hands@${MEDIAPIPE_HANDS_VERSION}`
13368
+ });
13369
+ }
13370
+ function getDetector() {
13371
+ if (!detectorPromise) {
13372
+ detectorPromise = createDetector();
13373
+ }
13374
+ return detectorPromise;
13375
+ }
13376
+ const wait = (ms) => new Promise((resolve) => {
13377
+ setTimeout(resolve, ms);
13378
+ });
13379
+ async function preloadGestureDetector(videoEl) {
13380
+ const detector = await getDetector();
13381
+ try {
13382
+ await detector.estimateHands(videoEl, {});
13383
+ } catch {
13384
+ }
13385
+ }
13386
+ async function detectThumbsUp(videoEl, timeoutMs) {
13387
+ const detector = await getDetector();
13388
+ const deadline = Date.now() + timeoutMs;
13389
+ while (Date.now() < deadline) {
13390
+ const hands = await detector.estimateHands(videoEl, { flipHorizontal: true });
13391
+ if (hands.length > 0 && isThumbsUp(hands[0].keypoints)) {
13392
+ return true;
13393
+ }
13394
+ await wait(POLL_INTERVAL_MS);
13395
+ }
13396
+ return false;
13397
+ }
13328
13398
  const PHOTO_INSTRUCTIONS = [
13329
13399
  "Olhe diretamente para a câmera",
13330
13400
  "Vire levemente a cabeça para a esquerda",
@@ -13332,6 +13402,7 @@ const PHOTO_INSTRUCTIONS = [
13332
13402
  "Mostre o polegar para cima (👍) e olhe para a câmera"
13333
13403
  ];
13334
13404
  const TOTAL_PHOTOS = 4;
13405
+ const GESTURE_TIMEOUT_MS = 5e3;
13335
13406
  const authorizeOperation = async (applicationToken, cpf, options) => {
13336
13407
  if (!applicationToken) {
13337
13408
  throw new NeoFaceError("Application token is required", ErrorType.VALIDATION_ERROR);
@@ -13373,6 +13444,8 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
13373
13444
  resolve();
13374
13445
  };
13375
13446
  });
13447
+ preloadGestureDetector(video).catch(() => {
13448
+ });
13376
13449
  const guideModal = showCameraOvalGuideModal({
13377
13450
  stream,
13378
13451
  faceState: "centered",
@@ -13393,7 +13466,19 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
13393
13466
  if (options == null ? void 0 : options.onProgress) {
13394
13467
  options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
13395
13468
  }
13396
- if (i > 0) {
13469
+ const isGesturePhoto = i === TOTAL_PHOTOS - 1;
13470
+ if (isGesturePhoto) {
13471
+ const detected = await detectThumbsUp(video, GESTURE_TIMEOUT_MS);
13472
+ if (!detected) {
13473
+ guideModal.close();
13474
+ stream.getTracks().forEach((track) => track.stop());
13475
+ capturedPhotos.length = 0;
13476
+ throw new NeoFaceError(
13477
+ "Polegar para cima não detectado dentro do tempo limite.",
13478
+ ErrorType.GESTURE_NOT_DETECTED
13479
+ );
13480
+ }
13481
+ } else if (i > 0) {
13397
13482
  await new Promise((resolve) => setTimeout(resolve, captureDelay));
13398
13483
  }
13399
13484
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);