@neofaceid/web-sdk 1.26.1 → 1.27.0

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
@@ -258,6 +258,19 @@ export declare interface CaptureFaceFramesOptions {
258
258
  livenessCheck: boolean;
259
259
  }
260
260
 
261
+ export declare type CapturePurpose = 'login' | 'onboarding' | 'authorization' | 'identification' | 'liveness';
262
+
263
+ export declare interface CaptureSession {
264
+ session_id: string;
265
+ expires_at: string;
266
+ }
267
+
268
+ export declare interface ChallengeGesture {
269
+ index: number;
270
+ gesture_key: GestureKey | string;
271
+ instruction_i18n?: Record<string, string>;
272
+ }
273
+
261
274
  /**
262
275
  * Check if a user exists by email or CPF
263
276
  * @param params Object with email or cpf to check (only one at a time)
@@ -557,6 +570,14 @@ export declare class ForgotPasswordModal {
557
570
  private addStyles;
558
571
  }
559
572
 
573
+ export declare type GestureKey = 'turn_left' | 'turn_right' | 'turn_up' | 'turn_down' | 'blink' | 'smile' | 'open_mouth';
574
+
575
+ export declare interface GestureResponse {
576
+ gesture_key: string;
577
+ /** Frames capturados como data URL ou base64. SDK só empacota — servidor valida. */
578
+ images: string[];
579
+ }
580
+
560
581
  /**
561
582
  * Retorna o token de aplicação configurado, se houver.
562
583
  */
@@ -568,6 +589,8 @@ export declare function getApplicationToken(): string | null;
568
589
  */
569
590
  export declare function getBaseUrl(): string;
570
591
 
592
+ export declare function getCachedCaptureSession(): CaptureSession | null;
593
+
571
594
  /**
572
595
  * Retorna toda a configuração atual (para debug).
573
596
  */
@@ -656,6 +679,19 @@ export declare function isAdvancedDetectionAvailable(): boolean;
656
679
  */
657
680
  export declare function isInitialized(): boolean;
658
681
 
682
+ export declare interface LivenessChallenge {
683
+ challenge_id: string;
684
+ gestures: ChallengeGesture[];
685
+ expires_at?: string;
686
+ }
687
+
688
+ export declare interface LivenessChallengeResult {
689
+ success: boolean;
690
+ liveness_passed?: boolean;
691
+ score?: number;
692
+ raw: unknown;
693
+ }
694
+
659
695
  /**
660
696
  * Options for login recognition
661
697
  */
@@ -942,6 +978,19 @@ export declare interface OnboardingPersonData {
942
978
  email?: string;
943
979
  }
944
980
 
981
+ /**
982
+ * Abre uma sessão de captura no servidor. Server-decides: o `session_id`
983
+ * vem do backend, nunca é inventado no cliente.
984
+ *
985
+ * @throws NeoFaceError em 4xx/5xx ou payload inválido.
986
+ */
987
+ export declare function openCaptureSession({ applicationToken, purpose, }: OpenCaptureSessionParams): Promise<CaptureSession>;
988
+
989
+ export declare interface OpenCaptureSessionParams {
990
+ applicationToken: string;
991
+ purpose: CapturePurpose;
992
+ }
993
+
945
994
  export declare type OverlayStatus = 'preparing' | 'detecting' | 'waiting-for-face' | 'capturing' | 'verifying' | 'success' | 'error';
946
995
 
947
996
  /**
@@ -1174,12 +1223,36 @@ export declare const registerPersonWithoutFace: (personData: {
1174
1223
  */
1175
1224
  export declare const RELEASE_DATE = "2026-09-01";
1176
1225
 
1226
+ declare interface RequestChallengeWithSessionParams {
1227
+ applicationToken: string;
1228
+ purpose: CapturePurpose;
1229
+ sessionId?: string;
1230
+ }
1231
+
1177
1232
  /**
1178
1233
  * Exibe o modal de consentimento e resolve `true` se o titular aceitou,
1179
1234
  * `false` se recusou. Deve ser chamado antes de qualquer `getUserMedia()`.
1180
1235
  */
1181
1236
  export declare function requestConsent(info?: Partial<ConsentInfo>): Promise<boolean>;
1182
1237
 
1238
+ /**
1239
+ * Pede ao servidor a sequência de gestos do desafio de liveness.
1240
+ * O SDK apenas exibe e coleta — validação de gesto/yaw/pitch é responsabilidade
1241
+ * exclusiva do servidor (regra "client collects, server decides").
1242
+ */
1243
+ export declare function requestLivenessChallenge({ sessionId, applicationToken, }: RequestLivenessChallengeParams): Promise<LivenessChallenge>;
1244
+
1245
+ export declare interface RequestLivenessChallengeParams {
1246
+ sessionId: string;
1247
+ applicationToken: string;
1248
+ }
1249
+
1250
+ /**
1251
+ * Garante uma sessão válida e retorna o desafio.
1252
+ * Em 410 (sessão expirada), reabre 1x e reenvia. Segunda falha propaga.
1253
+ */
1254
+ export declare function requestLivenessChallengeWithSession({ applicationToken, purpose, sessionId, }: RequestChallengeWithSessionParams): Promise<LivenessChallenge>;
1255
+
1183
1256
  /**
1184
1257
  * Request a password reset for a user account
1185
1258
  * @param email User email address
@@ -1189,6 +1262,33 @@ export declare function requestPasswordReset(email: string, applicationToken: st
1189
1262
  message: string;
1190
1263
  }>;
1191
1264
 
1265
+ export declare function resetCaptureSession(): void;
1266
+
1267
+ /**
1268
+ * Orquestra o fluxo completo:
1269
+ * 1. Abre `capture/session`
1270
+ * 2. Pede `liveness/challenge` (sequência sorteada pelo servidor)
1271
+ * 3. Delega a coleta de frames ao consumidor gesto a gesto
1272
+ * 4. Submete a resposta para o servidor validar
1273
+ *
1274
+ * Retry: em 410 no submit, reabre a sessão + refaz o desafio + reenvia UMA vez.
1275
+ * Segunda falha propaga.
1276
+ */
1277
+ export declare function runLivenessChallenge({ applicationToken, purpose, collectFramesForGesture, retryOnSessionExpired, }: RunLivenessChallengeParams): Promise<LivenessChallengeResult>;
1278
+
1279
+ export declare interface RunLivenessChallengeParams {
1280
+ applicationToken: string;
1281
+ purpose: CapturePurpose;
1282
+ /**
1283
+ * Callback fornecido pelo consumidor: para cada gesto na sequência sorteada
1284
+ * pelo servidor, retorna os frames coletados. SDK NÃO decide se o gesto foi
1285
+ * cumprido — só coleta e envia; validação é 100% servidor.
1286
+ */
1287
+ collectFramesForGesture: (gesture: ChallengeGesture, index: number, total: number) => Promise<string[]>;
1288
+ /** Se true, quando o servidor devolver 410 no submit, reabre sessão + refaz o desafio 1x. */
1289
+ retryOnSessionExpired?: boolean;
1290
+ }
1291
+
1192
1292
  /**
1193
1293
  * Configuração interna do SDK
1194
1294
  */
@@ -1327,6 +1427,20 @@ export declare interface StartOnboardingOptions {
1327
1427
  onCancel?: () => void;
1328
1428
  }
1329
1429
 
1430
+ /**
1431
+ * Envia os frames coletados para o servidor validar o desafio de liveness.
1432
+ * Endpoint: `POST /api/v1/auth/liveness/challenge/{challenge_id}/submit/`
1433
+ * O SDK apenas empacota e envia — nenhuma decisão de aprovar/reprovar aqui.
1434
+ */
1435
+ export declare function submitLivenessChallenge({ challengeId, sessionId, applicationToken, gestures, }: SubmitLivenessChallengeParams): Promise<LivenessChallengeResult>;
1436
+
1437
+ export declare interface SubmitLivenessChallengeParams {
1438
+ challengeId: string;
1439
+ sessionId: string;
1440
+ applicationToken: string;
1441
+ gestures: GestureResponse[];
1442
+ }
1443
+
1330
1444
  /**
1331
1445
  * Result of user existence check
1332
1446
  */
@@ -1363,6 +1477,21 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
1363
1477
  * MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
1364
1478
  * PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
1365
1479
  */
1366
- export declare const VERSION = "1.26.1";
1480
+ export declare const VERSION = "1.27.0";
1481
+
1482
+ /**
1483
+ * Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
1484
+ * reabre a sessão automaticamente 1x e reexecuta. Segunda falha propaga.
1485
+ */
1486
+ export declare function withCaptureSession<T>(params: OpenCaptureSessionParams, fn: (sessionId: string) => Promise<T>): Promise<T>;
1487
+
1488
+ /**
1489
+ * Injeta `session_id` cacheado no payload. Se não houver sessão aberta,
1490
+ * retorna o payload original inalterado — a decisão de exigir sessão
1491
+ * fica no servidor.
1492
+ */
1493
+ export declare function withSessionId<T extends Record<string, unknown>>(payload: T): T & {
1494
+ session_id?: string;
1495
+ };
1367
1496
 
1368
1497
  export { }
@@ -10417,8 +10417,254 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10417
10417
  initializeBiometricDetection,
10418
10418
  isAdvancedDetectionAvailable
10419
10419
  }, Symbol.toStringTag, { value: "Module" }));
10420
- const VERSION = "1.26.1";
10420
+ const VERSION = "1.27.0";
10421
10421
  const RELEASE_DATE = "2026-09-01";
10422
+ let cachedSession = null;
10423
+ function getCachedCaptureSession() {
10424
+ return cachedSession;
10425
+ }
10426
+ function resetCaptureSession() {
10427
+ cachedSession = null;
10428
+ }
10429
+ async function openCaptureSession({
10430
+ applicationToken,
10431
+ purpose
10432
+ }) {
10433
+ if (!applicationToken) {
10434
+ throw new NeoFaceError(
10435
+ "applicationToken é obrigatório para abrir sessão de captura",
10436
+ ErrorType.INVALID_TOKEN
10437
+ );
10438
+ }
10439
+ const url = `${getBaseUrl()}/api/v1/auth/capture/session/`;
10440
+ let response;
10441
+ try {
10442
+ response = await fetch(url, {
10443
+ method: "POST",
10444
+ headers: {
10445
+ "Content-Type": "application/json",
10446
+ "X-App-Token": applicationToken
10447
+ },
10448
+ body: JSON.stringify({ purpose })
10449
+ });
10450
+ } catch (err) {
10451
+ const message = err instanceof Error ? err.message : "Erro de rede";
10452
+ throw new NeoFaceError(`Falha de rede ao abrir sessão: ${message}`, ErrorType.NETWORK);
10453
+ }
10454
+ if (response.status === 401 || response.status === 403) {
10455
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
10456
+ }
10457
+ if (!response.ok) {
10458
+ throw new NeoFaceError(
10459
+ `Falha ao abrir sessão de captura (${response.status})`,
10460
+ ErrorType.API_ERROR
10461
+ );
10462
+ }
10463
+ const raw = await response.json().catch(() => null);
10464
+ const payload = (raw == null ? void 0 : raw.data) ?? raw;
10465
+ const sessionId = payload == null ? void 0 : payload.session_id;
10466
+ const expiresAt = payload == null ? void 0 : payload.expires_at;
10467
+ if (!sessionId || !expiresAt) {
10468
+ throw new NeoFaceError(
10469
+ "Resposta inválida ao abrir sessão de captura (campos ausentes)",
10470
+ ErrorType.API_ERROR
10471
+ );
10472
+ }
10473
+ cachedSession = { session_id: sessionId, expires_at: expiresAt };
10474
+ return cachedSession;
10475
+ }
10476
+ async function withCaptureSession(params, fn) {
10477
+ const session = cachedSession ?? await openCaptureSession(params);
10478
+ try {
10479
+ return await fn(session.session_id);
10480
+ } catch (err) {
10481
+ if (err instanceof NeoFaceError && err.message.includes("410")) {
10482
+ resetCaptureSession();
10483
+ const fresh = await openCaptureSession(params);
10484
+ return fn(fresh.session_id);
10485
+ }
10486
+ throw err;
10487
+ }
10488
+ }
10489
+ function withSessionId(payload) {
10490
+ const session = cachedSession;
10491
+ if (!session) return payload;
10492
+ return { ...payload, session_id: session.session_id };
10493
+ }
10494
+ async function requestLivenessChallenge({
10495
+ sessionId,
10496
+ applicationToken
10497
+ }) {
10498
+ if (!applicationToken) {
10499
+ throw new NeoFaceError(
10500
+ "applicationToken é obrigatório para solicitar desafio de liveness",
10501
+ ErrorType.INVALID_TOKEN
10502
+ );
10503
+ }
10504
+ if (!sessionId) {
10505
+ throw new NeoFaceError(
10506
+ "session_id é obrigatório para solicitar desafio de liveness",
10507
+ ErrorType.VALIDATION_ERROR
10508
+ );
10509
+ }
10510
+ const url = `${getBaseUrl()}/api/v1/auth/liveness/challenge/`;
10511
+ let response;
10512
+ try {
10513
+ response = await fetch(url, {
10514
+ method: "POST",
10515
+ headers: {
10516
+ "Content-Type": "application/json",
10517
+ "X-App-Token": applicationToken
10518
+ },
10519
+ body: JSON.stringify({ session_id: sessionId })
10520
+ });
10521
+ } catch (err) {
10522
+ const message = err instanceof Error ? err.message : "Erro de rede";
10523
+ throw new NeoFaceError(`Falha de rede ao solicitar desafio: ${message}`, ErrorType.NETWORK);
10524
+ }
10525
+ if (response.status === 401 || response.status === 403) {
10526
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
10527
+ }
10528
+ if (response.status === 410) {
10529
+ throw new NeoFaceError(
10530
+ "Sessão de captura expirada (410) — reabrir sessão",
10531
+ ErrorType.API_ERROR
10532
+ );
10533
+ }
10534
+ if (!response.ok) {
10535
+ throw new NeoFaceError(
10536
+ `Falha ao solicitar desafio de liveness (${response.status})`,
10537
+ ErrorType.API_ERROR
10538
+ );
10539
+ }
10540
+ const raw = await response.json().catch(() => null);
10541
+ const payload = (raw == null ? void 0 : raw.data) ?? raw;
10542
+ const challengeId = payload == null ? void 0 : payload.challenge_id;
10543
+ const gestures = payload == null ? void 0 : payload.gestures;
10544
+ if (!challengeId || !Array.isArray(gestures) || gestures.length === 0) {
10545
+ throw new NeoFaceError(
10546
+ "Resposta inválida ao solicitar desafio (campos ausentes)",
10547
+ ErrorType.API_ERROR
10548
+ );
10549
+ }
10550
+ return {
10551
+ challenge_id: challengeId,
10552
+ gestures,
10553
+ expires_at: payload == null ? void 0 : payload.expires_at
10554
+ };
10555
+ }
10556
+ async function requestLivenessChallengeWithSession({
10557
+ applicationToken,
10558
+ purpose,
10559
+ sessionId
10560
+ }) {
10561
+ let currentSessionId = sessionId ?? (await openCaptureSession({ applicationToken, purpose })).session_id;
10562
+ try {
10563
+ return await requestLivenessChallenge({ sessionId: currentSessionId, applicationToken });
10564
+ } catch (err) {
10565
+ if (err instanceof NeoFaceError && err.message.includes("410")) {
10566
+ resetCaptureSession();
10567
+ currentSessionId = (await openCaptureSession({ applicationToken, purpose })).session_id;
10568
+ return requestLivenessChallenge({ sessionId: currentSessionId, applicationToken });
10569
+ }
10570
+ throw err;
10571
+ }
10572
+ }
10573
+ async function submitLivenessChallenge({
10574
+ challengeId,
10575
+ sessionId,
10576
+ applicationToken,
10577
+ gestures
10578
+ }) {
10579
+ if (!applicationToken) {
10580
+ throw new NeoFaceError(
10581
+ "applicationToken é obrigatório para submeter desafio",
10582
+ ErrorType.INVALID_TOKEN
10583
+ );
10584
+ }
10585
+ if (!challengeId || !sessionId) {
10586
+ throw new NeoFaceError(
10587
+ "challenge_id e session_id são obrigatórios para submeter desafio",
10588
+ ErrorType.VALIDATION_ERROR
10589
+ );
10590
+ }
10591
+ if (!Array.isArray(gestures) || gestures.length === 0) {
10592
+ throw new NeoFaceError("gestures[] vazio — nada para submeter", ErrorType.VALIDATION_ERROR);
10593
+ }
10594
+ const url = `${getBaseUrl()}/api/v1/auth/liveness/challenge/${encodeURIComponent(challengeId)}/submit/`;
10595
+ let response;
10596
+ try {
10597
+ response = await fetch(url, {
10598
+ method: "POST",
10599
+ headers: {
10600
+ "Content-Type": "application/json",
10601
+ "X-App-Token": applicationToken
10602
+ },
10603
+ body: JSON.stringify({ session_id: sessionId, gestures })
10604
+ });
10605
+ } catch (err) {
10606
+ const message = err instanceof Error ? err.message : "Erro de rede";
10607
+ throw new NeoFaceError(`Falha de rede ao submeter desafio: ${message}`, ErrorType.NETWORK);
10608
+ }
10609
+ if (response.status === 401 || response.status === 403) {
10610
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
10611
+ }
10612
+ if (response.status === 410) {
10613
+ throw new NeoFaceError(
10614
+ "Sessão ou desafio expirado (410) — reabrir sessão",
10615
+ ErrorType.API_ERROR
10616
+ );
10617
+ }
10618
+ if (!response.ok) {
10619
+ throw new NeoFaceError(
10620
+ `Falha ao submeter desafio de liveness (${response.status})`,
10621
+ ErrorType.API_ERROR
10622
+ );
10623
+ }
10624
+ const raw = await response.json().catch(() => null);
10625
+ const payload = (raw == null ? void 0 : raw.data) ?? raw ?? {};
10626
+ return {
10627
+ success: (payload == null ? void 0 : payload.success) ?? true,
10628
+ liveness_passed: payload == null ? void 0 : payload.liveness_passed,
10629
+ score: payload == null ? void 0 : payload.score,
10630
+ raw
10631
+ };
10632
+ }
10633
+ async function runLivenessChallenge({
10634
+ applicationToken,
10635
+ purpose,
10636
+ collectFramesForGesture,
10637
+ retryOnSessionExpired = true
10638
+ }) {
10639
+ const attempt = async () => {
10640
+ const session = await openCaptureSession({ applicationToken, purpose });
10641
+ const challenge = await requestLivenessChallenge({
10642
+ sessionId: session.session_id,
10643
+ applicationToken
10644
+ });
10645
+ const gestures = [];
10646
+ for (let i = 0; i < challenge.gestures.length; i += 1) {
10647
+ const g = challenge.gestures[i];
10648
+ const images = await collectFramesForGesture(g, i, challenge.gestures.length);
10649
+ gestures.push({ gesture_key: String(g.gesture_key), images });
10650
+ }
10651
+ return submitLivenessChallenge({
10652
+ challengeId: challenge.challenge_id,
10653
+ sessionId: session.session_id,
10654
+ applicationToken,
10655
+ gestures
10656
+ });
10657
+ };
10658
+ try {
10659
+ return await attempt();
10660
+ } catch (err) {
10661
+ if (retryOnSessionExpired && err instanceof NeoFaceError && err.message.includes("410")) {
10662
+ resetCaptureSession();
10663
+ return attempt();
10664
+ }
10665
+ throw err;
10666
+ }
10667
+ }
10422
10668
  class OnboardingCaptureModal {
10423
10669
  constructor(options) {
10424
10670
  __publicField(this, "overlay", null);
@@ -12275,6 +12521,7 @@ export {
12275
12521
  detectBiometricType,
12276
12522
  getApplicationToken,
12277
12523
  getBaseUrl,
12524
+ getCachedCaptureSession,
12278
12525
  getConfig,
12279
12526
  getEnvironment,
12280
12527
  identifyPerson,
@@ -12285,6 +12532,7 @@ export {
12285
12532
  isInitialized,
12286
12533
  loginWithBiometric,
12287
12534
  loginWithEmail,
12535
+ openCaptureSession,
12288
12536
  preloadFaceDetectionModels,
12289
12537
  recognize,
12290
12538
  recognizeBiometric,
@@ -12293,7 +12541,11 @@ export {
12293
12541
  registerPersonWithBiometric,
12294
12542
  registerPersonWithoutFace,
12295
12543
  requestConsent,
12544
+ requestLivenessChallenge,
12545
+ requestLivenessChallengeWithSession,
12296
12546
  requestPasswordReset,
12547
+ resetCaptureSession,
12548
+ runLivenessChallenge,
12297
12549
  simpleIdentification,
12298
12550
  start,
12299
12551
  startAutoLogin,
@@ -12303,6 +12555,9 @@ export {
12303
12555
  startHandLogin,
12304
12556
  startLivenessCapture,
12305
12557
  startOnboarding,
12558
+ submitLivenessChallenge,
12306
12559
  validateOnboardingToken,
12307
- validateToken
12560
+ validateToken,
12561
+ withCaptureSession,
12562
+ withSessionId
12308
12563
  };