@neofaceid/web-sdk 1.36.0 → 1.36.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.d.ts CHANGED
@@ -88,7 +88,12 @@ export declare interface AuthorizeOptions {
88
88
  amount?: number;
89
89
  onChallengeStart?: (gesture: ChallengeGesture, index: number, total: number) => void;
90
90
  onChallengeComplete?: (gesture: ChallengeGesture, index: number, total: number) => void;
91
- onSuccess?: (result: LivenessChallengeResult) => void;
91
+ /**
92
+ * NEO-427: agora recebe o resultado da COLETA (sessionId + challenge + gestures
93
+ * capturadas). O SDK não decide aprovação — envie os frames via seu próprio
94
+ * pipeline de recognition (ex.: `POST /auth/recognition/face/`).
95
+ */
96
+ onSuccess?: (result: RunLivenessChallengeCollectResult) => void;
92
97
  onError?: (error: NeoFaceError) => void;
93
98
  onCancel?: () => void;
94
99
  /**
@@ -329,6 +334,11 @@ export declare interface CaptureSession {
329
334
  export declare interface ChallengeGesture {
330
335
  index: number;
331
336
  gesture_key: GestureKey | string;
337
+ /**
338
+ * Instrução pronta pelo backend (v1.4.x, respeita `Accept-Language`).
339
+ * Nos payloads legados vinha via `instruction_i18n`.
340
+ */
341
+ instruction?: string;
332
342
  instruction_i18n?: Record<string, string>;
333
343
  }
334
344
 
@@ -637,7 +647,12 @@ export declare class ForgotPasswordModal {
637
647
  private addStyles;
638
648
  }
639
649
 
640
- export declare type GestureKey = 'turn_left' | 'turn_right' | 'turn_up' | 'turn_down' | 'blink' | 'smile' | 'open_mouth';
650
+ /**
651
+ * Chaves de gesto reconhecidas pelo backend real (case-insensitive; SDK
652
+ * normaliza para maiúsculo). Novos gestos podem aparecer sem impacto no SDK
653
+ * — o campo é `string` na runtime.
654
+ */
655
+ export declare type GestureKey = 'LOOK_LEFT' | 'LOOK_RIGHT' | 'LOOK_UP' | 'LOOK_DOWN' | 'BLINK' | 'SMILE' | 'OPEN_MOUTH' | 'turn_left' | 'turn_right' | 'look_up' | 'look_down' | 'blink' | 'smile' | 'open_mouth';
641
656
 
642
657
  export declare interface GestureResponse {
643
658
  gesture_key: string;
@@ -769,9 +784,18 @@ export declare function isAdvancedDetectionAvailable(): boolean;
769
784
  export declare function isInitialized(): boolean;
770
785
 
771
786
  export declare interface LivenessChallenge {
772
- challenge_id: string;
787
+ /**
788
+ * ID do desafio. **Opcional no contrato v1.4.x** — o backend real rastreia
789
+ * o desafio pelo `session_id` e não devolve `challenge_id` no payload.
790
+ * Mantido opcional para consumidores que ainda dependem do valor.
791
+ */
792
+ challenge_id?: string;
773
793
  gestures: ChallengeGesture[];
794
+ /** Data ISO derivada de `expires_in` (segundos) ou legado `expires_at`. */
774
795
  expires_at?: string;
796
+ /** Segundos até expirar (contrato v1.4.x). */
797
+ expires_in?: number;
798
+ language?: string;
775
799
  }
776
800
 
777
801
  export declare interface LivenessChallengeResult {
@@ -1310,7 +1334,7 @@ export declare const registerPersonWithoutFace: (personData: {
1310
1334
  /**
1311
1335
  * Data de lançamento da versão atual
1312
1336
  */
1313
- export declare const RELEASE_DATE = "2026-09-02";
1337
+ export declare const RELEASE_DATE = "2026-09-03";
1314
1338
 
1315
1339
  declare interface RequestChallengeWithSessionParams {
1316
1340
  applicationToken: string;
@@ -1367,16 +1391,33 @@ declare interface ResolvedTheme {
1367
1391
  }
1368
1392
 
1369
1393
  /**
1370
- * Orquestra o fluxo completo:
1394
+ * Orquestra:
1371
1395
  * 1. Abre `capture/session`
1372
- * 2. Pede `liveness/challenge` (sequência sorteada pelo servidor)
1396
+ * 2. Pede `liveness/challenge` (backend sorteia)
1373
1397
  * 3. Delega a coleta de frames ao consumidor gesto a gesto
1374
- * 4. Submete a resposta para o servidor validar
1398
+ * 4. **Retorna a coleta** para o consumidor submeter no pipeline dele.
1375
1399
  *
1376
- * Retry: em 410 no submit, reabre a sessão + refaz o desafio + reenvia UMA vez.
1377
- * Segunda falha propaga.
1400
+ * Retry: em 410 ao abrir sessão/pedir desafio, reabre + refaz 1x. Segunda falha propaga.
1401
+ *
1402
+ * Nota: até 1.36.1 esta função chamava `submitLivenessChallenge` no fim, mas o
1403
+ * endpoint não existe no backend real. NEO-427 removeu essa chamada. Quando
1404
+ * NEO-428 identificar o endpoint real de submit, o consumidor pode usá-lo, ou
1405
+ * uma variante `runLivenessChallengeAndSubmit` pode ser reintroduzida.
1378
1406
  */
1379
- export declare function runLivenessChallenge({ applicationToken, purpose, collectFramesForGesture, retryOnSessionExpired, }: RunLivenessChallengeParams): Promise<LivenessChallengeResult>;
1407
+ export declare function runLivenessChallenge({ applicationToken, purpose, collectFramesForGesture, retryOnSessionExpired, }: RunLivenessChallengeParams): Promise<RunLivenessChallengeCollectResult>;
1408
+
1409
+ /**
1410
+ * Resultado da coleta orquestrada. NEO-427: NÃO submete os frames automaticamente
1411
+ * (o endpoint de submit `/challenge/{id}/submit/` não existe no backend v1.4.x —
1412
+ * devolve 404). O consumidor recebe as gestures coletadas e envia via seu próprio
1413
+ * pipeline (ex.: `POST /auth/recognition/face/` com `challenge_response` no body).
1414
+ */
1415
+ export declare interface RunLivenessChallengeCollectResult {
1416
+ sessionId: string;
1417
+ challenge: LivenessChallenge;
1418
+ /** Frames coletados por gesto, na ordem sorteada pelo servidor. */
1419
+ capturedGestures: GestureResponse[];
1420
+ }
1380
1421
 
1381
1422
  export declare interface RunLivenessChallengeParams {
1382
1423
  applicationToken: string;
@@ -1384,10 +1425,10 @@ export declare interface RunLivenessChallengeParams {
1384
1425
  /**
1385
1426
  * Callback fornecido pelo consumidor: para cada gesto na sequência sorteada
1386
1427
  * pelo servidor, retorna os frames coletados. SDK NÃO decide se o gesto foi
1387
- * cumprido — só coleta e envia; validação é 100% servidor.
1428
+ * cumprido — só coleta; validação é 100% servidor.
1388
1429
  */
1389
1430
  collectFramesForGesture: (gesture: ChallengeGesture, index: number, total: number) => Promise<string[]>;
1390
- /** Se true, quando o servidor devolver 410 no submit, reabre sessão + refaz o desafio 1x. */
1431
+ /** Se true, quando o servidor devolver 410 na abertura do desafio, reabre sessão + refaz 1x. */
1391
1432
  retryOnSessionExpired?: boolean;
1392
1433
  }
1393
1434
 
@@ -1583,14 +1624,18 @@ export declare interface StartOnboardingOptions {
1583
1624
  }
1584
1625
 
1585
1626
  /**
1586
- * Envia os frames coletados para o servidor validar o desafio de liveness.
1587
- * Endpoint: `POST /api/v1/auth/liveness/challenge/{challenge_id}/submit/`
1588
- * O SDK apenas empacota e envia — nenhuma decisão de aprovar/reprovar aqui.
1627
+ * @deprecated NEO-427: o endpoint `POST /liveness/challenge/{id}/submit/` NÃO
1628
+ * EXISTE no backend v1.4.x — devolve 404. `runLivenessChallenge` não chama mais
1629
+ * esta função. Mantida apenas para consumidores que ainda dependem do símbolo;
1630
+ * será removida quando o endpoint real de submit for identificado (NEO-428).
1631
+ *
1632
+ * Continua tentando o path histórico e propaga o erro do servidor.
1589
1633
  */
1590
1634
  export declare function submitLivenessChallenge({ challengeId, sessionId, applicationToken, gestures, }: SubmitLivenessChallengeParams): Promise<LivenessChallengeResult>;
1591
1635
 
1592
1636
  export declare interface SubmitLivenessChallengeParams {
1593
- challengeId: string;
1637
+ /** Opcional no v1.4.x — backend rastreia via session_id. */
1638
+ challengeId?: string;
1594
1639
  sessionId: string;
1595
1640
  applicationToken: string;
1596
1641
  gestures: GestureResponse[];
@@ -1650,7 +1695,7 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
1650
1695
  * MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
1651
1696
  * PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
1652
1697
  */
1653
- export declare const VERSION = "1.36.0";
1698
+ export declare const VERSION = "1.36.2";
1654
1699
 
1655
1700
  /**
1656
1701
  * Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
@@ -6560,10 +6560,8 @@ function FaceCaptureModal({
6560
6560
  const canvasRef = useRef(null);
6561
6561
  const streamRef = useRef(null);
6562
6562
  const faceDetectionTimeoutRef = useRef(null);
6563
- const livenessCheckRef = useRef(null);
6564
6563
  const [state, dispatch] = useReducer(modalReducer$1, { status: "requestingPermission" });
6565
6564
  const [faceDetected, setFaceDetected] = useState(false);
6566
- const [livenessAttempts, setLivenessAttempts] = useState(0);
6567
6565
  const { startCamera, stopCamera } = useCamera();
6568
6566
  const isFaceCentered = (detection) => {
6569
6567
  if (!videoRef.current) return false;
@@ -6575,14 +6573,6 @@ function FaceCaptureModal({
6575
6573
  const centerHeight = videoHeight * 0.4;
6576
6574
  return box.x + box.width / 2 > centerX - centerWidth / 2 && box.x + box.width / 2 < centerX + centerWidth / 2 && box.y + box.height / 2 > centerY - centerHeight / 2 && box.y + box.height / 2 < centerY + centerHeight / 2;
6577
6575
  };
6578
- const startLivenessCheck = () => {
6579
- livenessCheckRef.current = window.setTimeout(() => {
6580
- if (state.status === "ready") {
6581
- setLivenessAttempts((prev) => prev + 1);
6582
- dispatch({ type: "START_CAPTURE" });
6583
- }
6584
- }, 3e3);
6585
- };
6586
6576
  const startFaceDetection = () => {
6587
6577
  if (!videoRef.current) return;
6588
6578
  const detectFaces = async () => {
@@ -6595,9 +6585,6 @@ function FaceCaptureModal({
6595
6585
  const isOneFace = detections.length === 1;
6596
6586
  const isCentered = isOneFace && isFaceCentered(detections[0]);
6597
6587
  setFaceDetected(isOneFace && isCentered);
6598
- if (isOneFace && isCentered && livenessAttempts === 0) {
6599
- startLivenessCheck();
6600
- }
6601
6588
  } catch (error) {
6602
6589
  console.error("Face detection error:", error);
6603
6590
  }
@@ -6626,9 +6613,6 @@ function FaceCaptureModal({
6626
6613
  if (faceDetectionTimeoutRef.current) {
6627
6614
  window.clearTimeout(faceDetectionTimeoutRef.current);
6628
6615
  }
6629
- if (livenessCheckRef.current) {
6630
- window.clearInterval(livenessCheckRef.current);
6631
- }
6632
6616
  };
6633
6617
  }, []);
6634
6618
  useEffect(() => {
@@ -6695,7 +6679,6 @@ function FaceCaptureModal({
6695
6679
  };
6696
6680
  const handleRetry = () => {
6697
6681
  dispatch({ type: "RESET" });
6698
- setLivenessAttempts(0);
6699
6682
  };
6700
6683
  return /* @__PURE__ */ jsxs("div", { className: "neofaceid-root", style: styles.overlay, children: [
6701
6684
  /* @__PURE__ */ jsx(ScreenFlashOverlay, { active: flashOn }),
@@ -11194,8 +11177,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
11194
11177
  initializeBiometricDetection,
11195
11178
  isAdvancedDetectionAvailable
11196
11179
  }, Symbol.toStringTag, { value: "Module" }));
11197
- const VERSION = "1.36.0";
11198
- const RELEASE_DATE = "2026-09-02";
11180
+ const VERSION = "1.36.2";
11181
+ const RELEASE_DATE = "2026-09-03";
11199
11182
  let cachedSession = null;
11200
11183
  function getCachedCaptureSession() {
11201
11184
  return cachedSession;
@@ -11322,18 +11305,33 @@ async function requestLivenessChallenge({
11322
11305
  }
11323
11306
  const raw = await response.json().catch(() => null);
11324
11307
  const payload = (raw == null ? void 0 : raw.data) ?? raw;
11325
- const challengeId = payload == null ? void 0 : payload.challenge_id;
11326
- const gestures = payload == null ? void 0 : payload.gestures;
11327
- if (!challengeId || !Array.isArray(gestures) || gestures.length === 0) {
11308
+ const rawSteps = payload == null ? void 0 : payload.steps;
11309
+ const rawGestures = payload == null ? void 0 : payload.gestures;
11310
+ const source = Array.isArray(rawSteps) ? rawSteps : Array.isArray(rawGestures) ? rawGestures : void 0;
11311
+ if (!source || source.length === 0) {
11328
11312
  throw new NeoFaceError(
11329
- "Resposta inválida ao solicitar desafio (campos ausentes)",
11313
+ "Resposta inválida ao solicitar desafio (steps/gestures ausentes)",
11330
11314
  ErrorType.API_ERROR
11331
11315
  );
11332
11316
  }
11317
+ const gestures = source.map((s, i) => {
11318
+ const item = s;
11319
+ const gestureRaw = item.gesture ?? item.gesture_key;
11320
+ return {
11321
+ index: typeof item.order === "number" ? item.order : typeof item.index === "number" ? item.index : i + 1,
11322
+ gesture_key: gestureRaw ?? "",
11323
+ instruction: typeof item.instruction === "string" ? item.instruction : void 0,
11324
+ instruction_i18n: item.instruction_i18n && typeof item.instruction_i18n === "object" ? item.instruction_i18n : void 0
11325
+ };
11326
+ });
11327
+ const expiresIn = typeof (payload == null ? void 0 : payload.expires_in) === "number" ? payload.expires_in : void 0;
11328
+ const expiresAt = typeof (payload == null ? void 0 : payload.expires_at) === "string" && payload.expires_at.length > 0 ? payload.expires_at : expiresIn !== void 0 ? new Date(Date.now() + expiresIn * 1e3).toISOString() : void 0;
11333
11329
  return {
11334
- challenge_id: challengeId,
11330
+ challenge_id: typeof (payload == null ? void 0 : payload.challenge_id) === "string" ? payload.challenge_id : void 0,
11335
11331
  gestures,
11336
- expires_at: payload == null ? void 0 : payload.expires_at
11332
+ expires_at: expiresAt,
11333
+ expires_in: expiresIn,
11334
+ language: typeof (payload == null ? void 0 : payload.language) === "string" ? payload.language : void 0
11337
11335
  };
11338
11336
  }
11339
11337
  async function requestLivenessChallengeWithSession({
@@ -11359,22 +11357,26 @@ async function submitLivenessChallenge({
11359
11357
  applicationToken,
11360
11358
  gestures
11361
11359
  }) {
11360
+ console.warn(
11361
+ "[NeoFaceID SDK] submitLivenessChallenge está deprecated (NEO-427): o endpoint /liveness/challenge/{id}/submit/ não existe no backend v1.4.x. Coleta os frames via runLivenessChallenge e envie via seu próprio pipeline até que NEO-428 defina o endpoint real."
11362
+ );
11362
11363
  if (!applicationToken) {
11363
11364
  throw new NeoFaceError(
11364
11365
  "applicationToken é obrigatório para submeter desafio",
11365
11366
  ErrorType.INVALID_TOKEN
11366
11367
  );
11367
11368
  }
11368
- if (!challengeId || !sessionId) {
11369
+ if (!sessionId) {
11369
11370
  throw new NeoFaceError(
11370
- "challenge_id e session_id são obrigatórios para submeter desafio",
11371
+ "session_id é obrigatório para submeter desafio",
11371
11372
  ErrorType.VALIDATION_ERROR
11372
11373
  );
11373
11374
  }
11374
11375
  if (!Array.isArray(gestures) || gestures.length === 0) {
11375
11376
  throw new NeoFaceError("gestures[] vazio — nada para submeter", ErrorType.VALIDATION_ERROR);
11376
11377
  }
11377
- const url = `${getBaseUrl()}/api/v1/auth/liveness/challenge/${encodeURIComponent(challengeId)}/submit/`;
11378
+ const idSuffix = challengeId ? `${encodeURIComponent(challengeId)}/submit/` : "submit/";
11379
+ const url = `${getBaseUrl()}/api/v1/auth/liveness/challenge/${idSuffix}`;
11378
11380
  let response;
11379
11381
  try {
11380
11382
  response = await fetch(url, {
@@ -11425,18 +11427,13 @@ async function runLivenessChallenge({
11425
11427
  sessionId: session.session_id,
11426
11428
  applicationToken
11427
11429
  });
11428
- const gestures = [];
11430
+ const capturedGestures = [];
11429
11431
  for (let i = 0; i < challenge.gestures.length; i += 1) {
11430
11432
  const g = challenge.gestures[i];
11431
11433
  const images = await collectFramesForGesture(g, i, challenge.gestures.length);
11432
- gestures.push({ gesture_key: String(g.gesture_key), images });
11434
+ capturedGestures.push({ gesture_key: String(g.gesture_key), images });
11433
11435
  }
11434
- return submitLivenessChallenge({
11435
- challengeId: challenge.challenge_id,
11436
- sessionId: session.session_id,
11437
- applicationToken,
11438
- gestures
11439
- });
11436
+ return { sessionId: session.session_id, challenge, capturedGestures };
11440
11437
  };
11441
11438
  try {
11442
11439
  return await attempt();
@@ -12941,12 +12938,6 @@ function AuthorizeModalComponent({
12941
12938
  });
12942
12939
  if (stream) stream.getTracks().forEach((t) => t.stop());
12943
12940
  stream = null;
12944
- if (result.success === false || result.liveness_passed === false) {
12945
- setFailureCode("LIVENESS_LOW");
12946
- setFailureAt(/* @__PURE__ */ new Date());
12947
- setPhase("failure");
12948
- return;
12949
- }
12950
12941
  setPhase("success");
12951
12942
  if (onSuccess) onSuccess(result);
12952
12943
  } catch (err) {