@neofaceid/web-sdk 1.6.1 → 1.9.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.
@@ -1,29 +1,40 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => {
4
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- return value;
6
- };
7
- var _a;
8
- import require$$0$1, { useRef, useState, useCallback, useReducer, useEffect } from "react";
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import React, { useRef, useState, useCallback, useReducer, useEffect } from "react";
9
5
  import require$$0 from "react-dom";
10
6
  import * as faceapi from "face-api.js";
11
- var createRoot;
12
- var m = require$$0;
13
- if (process.env.NODE_ENV === "production") {
14
- createRoot = m.createRoot;
15
- m.hydrateRoot;
16
- } else {
17
- var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
18
- createRoot = function(c, o) {
19
- i.usingClientEntryPoint = true;
20
- try {
21
- return m.createRoot(c, o);
22
- } finally {
23
- i.usingClientEntryPoint = false;
24
- }
25
- };
7
+ var client = {};
8
+ var hasRequiredClient;
9
+ function requireClient() {
10
+ if (hasRequiredClient) return client;
11
+ hasRequiredClient = 1;
12
+ var m = require$$0;
13
+ if (process.env.NODE_ENV === "production") {
14
+ client.createRoot = m.createRoot;
15
+ client.hydrateRoot = m.hydrateRoot;
16
+ } else {
17
+ var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
18
+ client.createRoot = function(c, o) {
19
+ i.usingClientEntryPoint = true;
20
+ try {
21
+ return m.createRoot(c, o);
22
+ } finally {
23
+ i.usingClientEntryPoint = false;
24
+ }
25
+ };
26
+ client.hydrateRoot = function(c, h, o) {
27
+ i.usingClientEntryPoint = true;
28
+ try {
29
+ return m.hydrateRoot(c, h, o);
30
+ } finally {
31
+ i.usingClientEntryPoint = false;
32
+ }
33
+ };
34
+ }
35
+ return client;
26
36
  }
37
+ var clientExports = requireClient();
27
38
  var jsxRuntime = { exports: {} };
28
39
  var reactJsxRuntime_production_min = {};
29
40
  /**
@@ -37,20 +48,16 @@ var reactJsxRuntime_production_min = {};
37
48
  */
38
49
  var hasRequiredReactJsxRuntime_production_min;
39
50
  function requireReactJsxRuntime_production_min() {
40
- if (hasRequiredReactJsxRuntime_production_min)
41
- return reactJsxRuntime_production_min;
51
+ if (hasRequiredReactJsxRuntime_production_min) return reactJsxRuntime_production_min;
42
52
  hasRequiredReactJsxRuntime_production_min = 1;
43
- var f = require$$0$1, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m2 = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
53
+ var f = React, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true };
44
54
  function q(c, a, g) {
45
55
  var b, d = {}, e = null, h = null;
46
56
  void 0 !== g && (e = "" + g);
47
57
  void 0 !== a.key && (e = "" + a.key);
48
58
  void 0 !== a.ref && (h = a.ref);
49
- for (b in a)
50
- m2.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
51
- if (c && c.defaultProps)
52
- for (b in a = c.defaultProps, a)
53
- void 0 === d[b] && (d[b] = a[b]);
59
+ for (b in a) m.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]);
60
+ if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
54
61
  return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current };
55
62
  }
56
63
  reactJsxRuntime_production_min.Fragment = l;
@@ -70,12 +77,11 @@ var reactJsxRuntime_development = {};
70
77
  */
71
78
  var hasRequiredReactJsxRuntime_development;
72
79
  function requireReactJsxRuntime_development() {
73
- if (hasRequiredReactJsxRuntime_development)
74
- return reactJsxRuntime_development;
80
+ if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development;
75
81
  hasRequiredReactJsxRuntime_development = 1;
76
82
  if (process.env.NODE_ENV !== "production") {
77
83
  (function() {
78
- var React = require$$0$1;
84
+ var React$1 = React;
79
85
  var REACT_ELEMENT_TYPE = Symbol.for("react.element");
80
86
  var REACT_PORTAL_TYPE = Symbol.for("react.portal");
81
87
  var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
@@ -101,7 +107,7 @@ function requireReactJsxRuntime_development() {
101
107
  }
102
108
  return null;
103
109
  }
104
- var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
110
+ var ReactSharedInternals = React$1.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
105
111
  function error(format) {
106
112
  {
107
113
  {
@@ -559,10 +565,6 @@ function requireReactJsxRuntime_development() {
559
565
  };
560
566
  var specialPropKeyWarningShown;
561
567
  var specialPropRefWarningShown;
562
- var didWarnAboutStringRefs;
563
- {
564
- didWarnAboutStringRefs = {};
565
- }
566
568
  function hasValidRef(config) {
567
569
  {
568
570
  if (hasOwnProperty.call(config, "ref")) {
@@ -587,13 +589,7 @@ function requireReactJsxRuntime_development() {
587
589
  }
588
590
  function warnIfStringRefCannotBeAutoConverted(config, self) {
589
591
  {
590
- if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
591
- var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
592
- if (!didWarnAboutStringRefs[componentName]) {
593
- error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
594
- didWarnAboutStringRefs[componentName] = true;
595
- }
596
- }
592
+ if (typeof config.ref === "string" && ReactCurrentOwner.current && self) ;
597
593
  }
598
594
  }
599
595
  function defineKeyPropWarningGetter(props, displayName) {
@@ -747,11 +743,6 @@ function requireReactJsxRuntime_development() {
747
743
  }
748
744
  function getSourceInfoErrorAddendum(source) {
749
745
  {
750
- if (source !== void 0) {
751
- var fileName = source.fileName.replace(/^.*[\\\/]/, "");
752
- var lineNumber = source.lineNumber;
753
- return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
754
- }
755
746
  return "";
756
747
  }
757
748
  }
@@ -877,7 +868,7 @@ function requireReactJsxRuntime_development() {
877
868
  if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
878
869
  info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
879
870
  }
880
- var sourceInfo = getSourceInfoErrorAddendum(source);
871
+ var sourceInfo = getSourceInfoErrorAddendum();
881
872
  if (sourceInfo) {
882
873
  info += sourceInfo;
883
874
  } else {
@@ -960,12 +951,18 @@ function requireReactJsxRuntime_development() {
960
951
  }
961
952
  return reactJsxRuntime_development;
962
953
  }
963
- if (process.env.NODE_ENV === "production") {
964
- jsxRuntime.exports = requireReactJsxRuntime_production_min();
965
- } else {
966
- jsxRuntime.exports = requireReactJsxRuntime_development();
954
+ var hasRequiredJsxRuntime;
955
+ function requireJsxRuntime() {
956
+ if (hasRequiredJsxRuntime) return jsxRuntime.exports;
957
+ hasRequiredJsxRuntime = 1;
958
+ if (process.env.NODE_ENV === "production") {
959
+ jsxRuntime.exports = requireReactJsxRuntime_production_min();
960
+ } else {
961
+ jsxRuntime.exports = requireReactJsxRuntime_development();
962
+ }
963
+ return jsxRuntime.exports;
967
964
  }
968
- var jsxRuntimeExports = jsxRuntime.exports;
965
+ var jsxRuntimeExports = requireJsxRuntime();
969
966
  class CameraPermissionDenied extends Error {
970
967
  constructor(message) {
971
968
  super(message);
@@ -1007,7 +1004,7 @@ const useCamera = () => {
1007
1004
  };
1008
1005
  const startCamera = useCallback(
1009
1006
  async (constraints) => {
1010
- var _a2;
1007
+ var _a;
1011
1008
  console.log("🎬 [useCamera] startCamera chamado", {
1012
1009
  hasExistingStream: !!streamRef.current,
1013
1010
  isInitializing: isInitializingRef.current
@@ -1049,7 +1046,7 @@ const useCamera = () => {
1049
1046
  console.log("📋 [useCamera] Detalhes do erro:", {
1050
1047
  name: error.name,
1051
1048
  message: error.message,
1052
- stack: (_a2 = error.stack) == null ? void 0 : _a2.substring(0, 200)
1049
+ stack: (_a = error.stack) == null ? void 0 : _a.substring(0, 200)
1053
1050
  });
1054
1051
  if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
1055
1052
  throw new CameraPermissionDenied(
@@ -1137,7 +1134,11 @@ class NeoFaceError extends Error {
1137
1134
  this.type = type;
1138
1135
  }
1139
1136
  }
1140
- const API_BASE_URL = ((_a = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a.VITE_API_BASE_URL) || "http://localhost:8000";
1137
+ const __vite_import_meta_env__$1 = {};
1138
+ const API_BASE_URL = __vite_import_meta_env__$1 == null ? void 0 : __vite_import_meta_env__$1.VITE_API_BASE_URL;
1139
+ if (!API_BASE_URL) {
1140
+ console.error("[NeoFaceID SDK] VITE_API_BASE_URL não está configurado. Configure no arquivo .env");
1141
+ }
1141
1142
  const REQUEST_TIMEOUT = 1e4;
1142
1143
  const ensureSecureContext = () => {
1143
1144
  const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "";
@@ -1183,6 +1184,46 @@ const compressImage = async (imageBlob) => new Promise((resolve, reject) => {
1183
1184
  };
1184
1185
  img.src = URL.createObjectURL(imageBlob);
1185
1186
  });
1187
+ const compressDocumentImage = async (imageBlob) => new Promise((resolve, reject) => {
1188
+ const img = new Image();
1189
+ img.onload = () => {
1190
+ const MAX_SIZE = 1280;
1191
+ let width = img.width;
1192
+ let height = img.height;
1193
+ if (width > height && width > MAX_SIZE) {
1194
+ height = height * MAX_SIZE / width;
1195
+ width = MAX_SIZE;
1196
+ } else if (height > MAX_SIZE) {
1197
+ width = width * MAX_SIZE / height;
1198
+ height = MAX_SIZE;
1199
+ }
1200
+ const canvas = document.createElement("canvas");
1201
+ canvas.width = width;
1202
+ canvas.height = height;
1203
+ const ctx = canvas.getContext("2d");
1204
+ if (!ctx) {
1205
+ reject(new Error("Failed to get canvas context"));
1206
+ return;
1207
+ }
1208
+ ctx.drawImage(img, 0, 0, width, height);
1209
+ canvas.toBlob(
1210
+ (blob) => {
1211
+ if (blob) {
1212
+ resolve(blob);
1213
+ } else {
1214
+ reject(new Error("Failed to compress document image"));
1215
+ }
1216
+ },
1217
+ "image/jpeg",
1218
+ 0.75
1219
+ // 75% quality to keep file size manageable
1220
+ );
1221
+ };
1222
+ img.onerror = () => {
1223
+ reject(new Error("Failed to load document image"));
1224
+ };
1225
+ img.src = URL.createObjectURL(imageBlob);
1226
+ });
1186
1227
  const validateToken = async (applicationToken) => {
1187
1228
  ensureSecureContext();
1188
1229
  const controller = createTimeoutController();
@@ -1211,13 +1252,16 @@ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
1211
1252
  ensureSecureContext();
1212
1253
  const controller = createTimeoutController();
1213
1254
  try {
1214
- const response = await fetch(`${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`, {
1215
- method: "GET",
1216
- headers: {
1217
- "X-App-Token": applicationToken
1218
- },
1219
- signal: controller.signal
1220
- });
1255
+ const response = await fetch(
1256
+ `${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`,
1257
+ {
1258
+ method: "GET",
1259
+ headers: {
1260
+ "X-App-Token": applicationToken
1261
+ },
1262
+ signal: controller.signal
1263
+ }
1264
+ );
1221
1265
  if (!response.ok) {
1222
1266
  if (response.status === 401 || response.status === 403) {
1223
1267
  throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
@@ -1246,13 +1290,16 @@ const validateOnboardingToken = async (applicationToken, onboardingToken) => {
1246
1290
  ensureSecureContext();
1247
1291
  const controller = createTimeoutController();
1248
1292
  try {
1249
- const response = await fetch(`${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`, {
1250
- method: "GET",
1251
- headers: {
1252
- "X-App-Token": applicationToken
1253
- },
1254
- signal: controller.signal
1255
- });
1293
+ const response = await fetch(
1294
+ `${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`,
1295
+ {
1296
+ method: "GET",
1297
+ headers: {
1298
+ "X-App-Token": applicationToken
1299
+ },
1300
+ signal: controller.signal
1301
+ }
1302
+ );
1256
1303
  if (!response.ok) {
1257
1304
  if (response.status === 401 || response.status === 403) {
1258
1305
  throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
@@ -1305,7 +1352,10 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
1305
1352
  if (response.status === 404) {
1306
1353
  throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1307
1354
  }
1308
- throw new NeoFaceError((data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding", ErrorType.NETWORK);
1355
+ throw new NeoFaceError(
1356
+ (data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding",
1357
+ ErrorType.NETWORK
1358
+ );
1309
1359
  }
1310
1360
  return {
1311
1361
  success: !!data.success,
@@ -1331,13 +1381,14 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
1331
1381
  const completeOnboardingWithData = async (applicationToken, onboardingToken, personData, personImages, documentImage) => {
1332
1382
  ensureSecureContext();
1333
1383
  if (!personImages || personImages.length === 0) {
1334
- throw new NeoFaceError("Pelo menos uma imagem da pessoa é obrigatória", ErrorType.VALIDATION_ERROR);
1384
+ throw new NeoFaceError(
1385
+ "Pelo menos uma imagem da pessoa é obrigatória",
1386
+ ErrorType.VALIDATION_ERROR
1387
+ );
1335
1388
  }
1336
1389
  const controller = createTimeoutController();
1337
1390
  try {
1338
- const compressedPersonImages = await Promise.all(
1339
- personImages.map((img) => compressImage(img))
1340
- );
1391
+ const compressedPersonImages = await Promise.all(personImages.map((img) => compressImage(img)));
1341
1392
  const compressedDocument = await compressImage(documentImage);
1342
1393
  const formData = new FormData();
1343
1394
  compressedPersonImages.forEach((img, index) => {
@@ -1378,7 +1429,10 @@ const completeOnboardingWithData = async (applicationToken, onboardingToken, per
1378
1429
  if (response.status === 404) {
1379
1430
  throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1380
1431
  }
1381
- throw new NeoFaceError((data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding", ErrorType.NETWORK);
1432
+ throw new NeoFaceError(
1433
+ (data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding",
1434
+ ErrorType.NETWORK
1435
+ );
1382
1436
  }
1383
1437
  return {
1384
1438
  success: !!data.success,
@@ -1447,19 +1501,25 @@ const recognize = async (image, applicationToken) => {
1447
1501
  }
1448
1502
  };
1449
1503
  const loginWithBiometric$1 = async (image, applicationToken) => {
1450
- var _a2, _b, _c, _d, _e;
1504
+ var _a, _b, _c, _d, _e;
1451
1505
  ensureSecureContext();
1452
1506
  const controller = createTimeoutController();
1453
1507
  const compressedImage = await compressImage(image);
1454
1508
  try {
1455
- const formData = new FormData();
1456
- formData.append("face_frames", compressedImage);
1457
- const response = await fetch(`${API_BASE_URL}/api/v1/external/auth/login/`, {
1509
+ const arrayBuffer = await compressedImage.arrayBuffer();
1510
+ const base64Image = btoa(
1511
+ new Uint8Array(arrayBuffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "")
1512
+ );
1513
+ const response = await fetch(`${API_BASE_URL}/api/v1/login/donor/face/`, {
1458
1514
  method: "POST",
1459
1515
  headers: {
1516
+ "Content-Type": "application/json",
1460
1517
  "X-App-Token": applicationToken
1461
1518
  },
1462
- body: formData,
1519
+ body: JSON.stringify({
1520
+ image: base64Image,
1521
+ biometric_type: "FACE"
1522
+ }),
1463
1523
  signal: controller.signal
1464
1524
  });
1465
1525
  if (!response.ok) {
@@ -1477,7 +1537,7 @@ const loginWithBiometric$1 = async (image, applicationToken) => {
1477
1537
  accessToken: data.accessToken,
1478
1538
  alias: data.alias,
1479
1539
  user: {
1480
- id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
1540
+ id: ((_a = data.user) == null ? void 0 : _a.id) || "",
1481
1541
  email: ((_b = data.user) == null ? void 0 : _b.email) || "",
1482
1542
  role: ((_c = data.user) == null ? void 0 : _c.role) || "",
1483
1543
  validated: ((_d = data.user) == null ? void 0 : _d.validated) || false,
@@ -1594,7 +1654,7 @@ const simpleIdentification = async (documentType, documentNumber, applicationTok
1594
1654
  }
1595
1655
  };
1596
1656
  const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8, signature, sessionData) => {
1597
- var _a2;
1657
+ var _a;
1598
1658
  ensureSecureContext();
1599
1659
  const controller = createTimeoutController();
1600
1660
  const compressedImage = await compressImage(image);
@@ -1635,7 +1695,7 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
1635
1695
  const data = await response.json();
1636
1696
  return {
1637
1697
  success: data.success,
1638
- personName: data.person_name || ((_a2 = data.person) == null ? void 0 : _a2.name),
1698
+ personName: data.person_name || ((_a = data.person) == null ? void 0 : _a.name),
1639
1699
  email: data.email || (sessionData == null ? void 0 : sessionData.email),
1640
1700
  cpf: data.cpf || (sessionData == null ? void 0 : sessionData.cpf),
1641
1701
  confidenceScore: data.confidence_score,
@@ -1659,24 +1719,52 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
1659
1719
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1660
1720
  }
1661
1721
  };
1662
- const registerPersonWithoutFace = async (personData, applicationToken) => {
1722
+ const registerPersonWithoutFace = async (personData, applicationToken, options) => {
1663
1723
  ensureSecureContext();
1664
1724
  const controller = createTimeoutController();
1665
1725
  try {
1726
+ let documentFrontBase64;
1727
+ let documentBackBase64;
1728
+ if (options == null ? void 0 : options.documentFront) {
1729
+ const compressed = await compressDocumentImage(options.documentFront);
1730
+ documentFrontBase64 = await new Promise((resolve, reject) => {
1731
+ const reader = new FileReader();
1732
+ reader.onload = () => resolve(reader.result);
1733
+ reader.onerror = reject;
1734
+ reader.readAsDataURL(compressed);
1735
+ });
1736
+ }
1737
+ if (options == null ? void 0 : options.documentBack) {
1738
+ const compressed = await compressDocumentImage(options.documentBack);
1739
+ documentBackBase64 = await new Promise((resolve, reject) => {
1740
+ const reader = new FileReader();
1741
+ reader.onload = () => resolve(reader.result);
1742
+ reader.onerror = reject;
1743
+ reader.readAsDataURL(compressed);
1744
+ });
1745
+ }
1746
+ const body = {
1747
+ name: personData.name,
1748
+ birth_date: personData.birth_date,
1749
+ cpf: personData.cpf,
1750
+ email: personData.email,
1751
+ password: personData.password,
1752
+ password_confirm: personData.password
1753
+ };
1754
+ if (documentFrontBase64) {
1755
+ body.document_images = [{
1756
+ front: documentFrontBase64,
1757
+ back: documentBackBase64 || void 0,
1758
+ type: (options == null ? void 0 : options.documentType) || "RG"
1759
+ }];
1760
+ }
1666
1761
  const response = await fetch(`${API_BASE_URL}/api/v1/signup/donor/`, {
1667
1762
  method: "POST",
1668
1763
  headers: {
1669
1764
  "Content-Type": "application/json",
1670
1765
  "X-App-Token": applicationToken
1671
1766
  },
1672
- body: JSON.stringify({
1673
- name: personData.name,
1674
- birth_date: personData.birth_date,
1675
- cpf: personData.cpf,
1676
- email: personData.email,
1677
- password: personData.password,
1678
- password_confirm: personData.password
1679
- }),
1767
+ body: JSON.stringify(body),
1680
1768
  signal: controller.signal
1681
1769
  });
1682
1770
  if (!response.ok) {
@@ -1700,11 +1788,14 @@ const registerPersonWithoutFace = async (personData, applicationToken) => {
1700
1788
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1701
1789
  }
1702
1790
  };
1703
- const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
1704
- var _a2, _b, _c, _d, _e, _f, _g, _h;
1791
+ const registerPersonWithBiometric = async (personData, facePhotos, applicationToken, options) => {
1792
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1705
1793
  ensureSecureContext();
1706
1794
  if (!facePhotos || facePhotos.length === 0) {
1707
- throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION_ERROR);
1795
+ throw new NeoFaceError(
1796
+ "At least one face photo is required for biometric registration",
1797
+ ErrorType.VALIDATION_ERROR
1798
+ );
1708
1799
  }
1709
1800
  if (facePhotos.length > 5) {
1710
1801
  throw new NeoFaceError("Maximum of 5 face photos allowed", ErrorType.VALIDATION_ERROR);
@@ -1725,21 +1816,49 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1725
1816
  });
1726
1817
  })
1727
1818
  );
1819
+ let documentFrontBase64;
1820
+ let documentBackBase64;
1821
+ if (options == null ? void 0 : options.documentFront) {
1822
+ const compressed = await compressDocumentImage(options.documentFront);
1823
+ documentFrontBase64 = await new Promise((resolve, reject) => {
1824
+ const reader = new FileReader();
1825
+ reader.onload = () => resolve(reader.result);
1826
+ reader.onerror = reject;
1827
+ reader.readAsDataURL(compressed);
1828
+ });
1829
+ }
1830
+ if (options == null ? void 0 : options.documentBack) {
1831
+ const compressed = await compressDocumentImage(options.documentBack);
1832
+ documentBackBase64 = await new Promise((resolve, reject) => {
1833
+ const reader = new FileReader();
1834
+ reader.onload = () => resolve(reader.result);
1835
+ reader.onerror = reject;
1836
+ reader.readAsDataURL(compressed);
1837
+ });
1838
+ }
1839
+ const body = {
1840
+ name: personData.name,
1841
+ birth_date: personData.birth_date,
1842
+ cpf: personData.cpf,
1843
+ email: personData.email,
1844
+ password: personData.password,
1845
+ password_confirm: personData.password,
1846
+ face_photos: facePhotosBase64
1847
+ };
1848
+ if (documentFrontBase64) {
1849
+ body.document_images = [{
1850
+ front: documentFrontBase64,
1851
+ back: documentBackBase64 || void 0,
1852
+ type: (options == null ? void 0 : options.documentType) || "RG"
1853
+ }];
1854
+ }
1728
1855
  const response = await fetch(`${API_BASE_URL}/api/v1/signup/donor/`, {
1729
1856
  method: "POST",
1730
1857
  headers: {
1731
1858
  "Content-Type": "application/json",
1732
1859
  "X-App-Token": applicationToken
1733
1860
  },
1734
- body: JSON.stringify({
1735
- name: personData.name,
1736
- birth_date: personData.birth_date,
1737
- cpf: personData.cpf,
1738
- email: personData.email,
1739
- password: personData.password,
1740
- password_confirm: personData.password,
1741
- face_photos: facePhotosBase64
1742
- }),
1861
+ body: JSON.stringify(body),
1743
1862
  signal: controller.signal
1744
1863
  });
1745
1864
  if (!response.ok) {
@@ -1757,7 +1876,10 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1757
1876
  throw new NeoFaceError(`Erro de validação: ${errorMessages}`, ErrorType.VALIDATION_ERROR);
1758
1877
  }
1759
1878
  if (errorData.error === "fraud_detected") {
1760
- throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION_ERROR);
1879
+ throw new NeoFaceError(
1880
+ "Fraud detected in biometric images. Registration blocked.",
1881
+ ErrorType.VALIDATION_ERROR
1882
+ );
1761
1883
  }
1762
1884
  if (errorData.error === "user_exists" || errorData.error_code === "ALREADY_EXISTS") {
1763
1885
  throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION_ERROR);
@@ -1775,7 +1897,7 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1775
1897
  }
1776
1898
  }
1777
1899
  const data = await response.json();
1778
- if (response.status === 202 && ((_a2 = data.data) == null ? void 0 : _a2.task_id)) {
1900
+ if (response.status === 202 && ((_a = data.data) == null ? void 0 : _a.task_id)) {
1779
1901
  return {
1780
1902
  success: data.success,
1781
1903
  message: data.message || "Registration is being processed. You will receive an email when completed.",
@@ -1859,7 +1981,7 @@ const loginWithEmail = async (email, password, applicationToken) => {
1859
1981
  }
1860
1982
  };
1861
1983
  const identifyPerson = async (image, applicationToken) => {
1862
- var _a2, _b;
1984
+ var _a, _b;
1863
1985
  ensureSecureContext();
1864
1986
  const controller = createTimeoutController();
1865
1987
  const compressedImage = await compressImage(image);
@@ -1893,9 +2015,12 @@ const identifyPerson = async (image, applicationToken) => {
1893
2015
  }
1894
2016
  const data = await response.json();
1895
2017
  if (!data.success) {
1896
- throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
2018
+ throw new NeoFaceError(
2019
+ data.message || "Person identification failed",
2020
+ ErrorType.RECOGNITION_FAILED
2021
+ );
1897
2022
  }
1898
- const personName = ((_a2 = data.person) == null ? void 0 : _a2.name) || "";
2023
+ const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
1899
2024
  const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
1900
2025
  if (!personName) {
1901
2026
  throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
@@ -1962,7 +2087,10 @@ const registerBiometric = async (personId, faceImage, applicationToken) => {
1962
2087
  });
1963
2088
  if (!response.ok) {
1964
2089
  if (response.status === 401 || response.status === 403) {
1965
- throw new NeoFaceError("Invalid or expired application token, or insufficient permissions", ErrorType.INVALID_TOKEN);
2090
+ throw new NeoFaceError(
2091
+ "Invalid or expired application token, or insufficient permissions",
2092
+ ErrorType.INVALID_TOKEN
2093
+ );
1966
2094
  }
1967
2095
  if (response.status === 400) {
1968
2096
  try {
@@ -2000,6 +2128,272 @@ const registerBiometric = async (personId, faceImage, applicationToken) => {
2000
2128
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2001
2129
  }
2002
2130
  };
2131
+ const registerApplication = async (jwtToken, consumerId, applicationData) => {
2132
+ ensureSecureContext();
2133
+ const controller = createTimeoutController();
2134
+ try {
2135
+ const response = await fetch(`${API_BASE_URL}/api/v1/applications/`, {
2136
+ method: "POST",
2137
+ headers: {
2138
+ "Content-Type": "application/json",
2139
+ Authorization: `Bearer ${jwtToken}`
2140
+ },
2141
+ body: JSON.stringify({
2142
+ consumer: consumerId,
2143
+ application_name: applicationData.applicationName,
2144
+ domain: applicationData.domain,
2145
+ accept_only_email_with_same_domain: applicationData.acceptOnlyEmailWithSameDomain
2146
+ }),
2147
+ signal: controller.signal
2148
+ });
2149
+ if (!response.ok) {
2150
+ if (response.status === 401) {
2151
+ throw new NeoFaceError("Invalid or expired JWT token", ErrorType.INVALID_TOKEN);
2152
+ }
2153
+ if (response.status === 403) {
2154
+ throw new NeoFaceError(
2155
+ "Insufficient permissions to create application",
2156
+ ErrorType.INVALID_TOKEN
2157
+ );
2158
+ }
2159
+ if (response.status === 400) {
2160
+ try {
2161
+ const errorData = await response.json();
2162
+ const errorMessage = errorData.message || errorData.error || "Invalid application data";
2163
+ throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
2164
+ } catch (parseError) {
2165
+ if (parseError instanceof NeoFaceError) {
2166
+ throw parseError;
2167
+ }
2168
+ throw new NeoFaceError("Invalid application data", ErrorType.VALIDATION_ERROR);
2169
+ }
2170
+ }
2171
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
2172
+ }
2173
+ const data = await response.json();
2174
+ return {
2175
+ success: true,
2176
+ application: {
2177
+ id: data.id,
2178
+ application_name: data.application_name,
2179
+ domain: data.domain,
2180
+ accept_only_email_with_same_domain: data.accept_only_email_with_same_domain,
2181
+ app_token: data.app_token,
2182
+ is_active: data.is_active,
2183
+ created_at: data.created_at
2184
+ },
2185
+ message: "Application registered successfully"
2186
+ };
2187
+ } catch (error) {
2188
+ if (error instanceof Error) {
2189
+ if (error.name === "AbortError") {
2190
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
2191
+ }
2192
+ if (error instanceof NeoFaceError) {
2193
+ throw error;
2194
+ }
2195
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
2196
+ }
2197
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2198
+ }
2199
+ };
2200
+ const recordFaceVideo = async (options = {}) => {
2201
+ ensureSecureContext();
2202
+ const { durationMs = 3e3, mimeType = "video/webm", onProgress } = options;
2203
+ return new Promise(async (resolve, reject) => {
2204
+ let stream = null;
2205
+ let mediaRecorder = null;
2206
+ const chunks = [];
2207
+ try {
2208
+ stream = await navigator.mediaDevices.getUserMedia({
2209
+ video: {
2210
+ facingMode: "user",
2211
+ width: { ideal: 640 },
2212
+ height: { ideal: 480 }
2213
+ },
2214
+ audio: false
2215
+ });
2216
+ let selectedMimeType = mimeType;
2217
+ const mimeTypes = ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"];
2218
+ for (const type of mimeTypes) {
2219
+ if (MediaRecorder.isTypeSupported(type)) {
2220
+ selectedMimeType = type;
2221
+ break;
2222
+ }
2223
+ }
2224
+ mediaRecorder = new MediaRecorder(stream, {
2225
+ mimeType: selectedMimeType,
2226
+ videoBitsPerSecond: 1e6
2227
+ // 1 Mbps
2228
+ });
2229
+ mediaRecorder.ondataavailable = (event) => {
2230
+ if (event.data.size > 0) {
2231
+ chunks.push(event.data);
2232
+ }
2233
+ };
2234
+ mediaRecorder.onstop = () => {
2235
+ if (stream) {
2236
+ stream.getTracks().forEach((track) => track.stop());
2237
+ }
2238
+ const videoBlob = new Blob(chunks, { type: selectedMimeType });
2239
+ resolve(videoBlob);
2240
+ };
2241
+ mediaRecorder.onerror = (event) => {
2242
+ if (stream) {
2243
+ stream.getTracks().forEach((track) => track.stop());
2244
+ }
2245
+ reject(new NeoFaceError(`Recording error: ${event}`, ErrorType.CAPTURE_ERROR));
2246
+ };
2247
+ mediaRecorder.start(100);
2248
+ if (onProgress) {
2249
+ const progressInterval = 100;
2250
+ let elapsed = 0;
2251
+ const progressTimer = setInterval(() => {
2252
+ elapsed += progressInterval;
2253
+ const progress = Math.min(100, elapsed / durationMs * 100);
2254
+ onProgress(progress);
2255
+ if (elapsed >= durationMs) {
2256
+ clearInterval(progressTimer);
2257
+ }
2258
+ }, progressInterval);
2259
+ }
2260
+ setTimeout(() => {
2261
+ if (mediaRecorder && mediaRecorder.state === "recording") {
2262
+ mediaRecorder.stop();
2263
+ }
2264
+ }, durationMs);
2265
+ } catch (error) {
2266
+ if (stream) {
2267
+ stream.getTracks().forEach((track) => track.stop());
2268
+ }
2269
+ if (error instanceof Error) {
2270
+ if (error.name === "NotAllowedError") {
2271
+ reject(new NeoFaceError("Camera permission denied", ErrorType.CAMERA_ERROR));
2272
+ } else if (error.name === "NotFoundError") {
2273
+ reject(new NeoFaceError("No camera found", ErrorType.NO_CAMERA));
2274
+ } else {
2275
+ reject(new NeoFaceError(error.message, ErrorType.CAPTURE_ERROR));
2276
+ }
2277
+ } else {
2278
+ reject(new NeoFaceError("Failed to record video", ErrorType.CAPTURE_ERROR));
2279
+ }
2280
+ }
2281
+ });
2282
+ };
2283
+ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
2284
+ var _a, _b, _c, _d, _e, _f;
2285
+ const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
2286
+ let attempts = 0;
2287
+ while (attempts < maxAttempts) {
2288
+ attempts++;
2289
+ try {
2290
+ const response = await fetch(`${API_BASE_URL}/api/v1/tasks/status/?task_id=${taskId}`, {
2291
+ method: "GET",
2292
+ headers: {
2293
+ Accept: "application/json",
2294
+ "X-App-Token": applicationToken
2295
+ }
2296
+ });
2297
+ if (!response.ok) {
2298
+ throw new NeoFaceError(`Task status check failed: ${response.status}`, ErrorType.NETWORK);
2299
+ }
2300
+ const data = await response.json();
2301
+ const status = ((_a = data.data) == null ? void 0 : _a.status) || "unknown";
2302
+ if (onStatusChange) {
2303
+ const progress = (_c = (_b = data.data) == null ? void 0 : _b.progress) == null ? void 0 : _c.percentage;
2304
+ onStatusChange(status, progress);
2305
+ }
2306
+ if (status === "success") {
2307
+ return data.data.result;
2308
+ }
2309
+ if (status === "failure") {
2310
+ const errorMessage = ((_e = (_d = data.data) == null ? void 0 : _d.result) == null ? void 0 : _e.message) || ((_f = data.data) == null ? void 0 : _f.error) || "Task failed";
2311
+ throw new NeoFaceError(errorMessage, ErrorType.RECOGNITION_FAILED);
2312
+ }
2313
+ if (status === "revoked") {
2314
+ throw new NeoFaceError("Task was cancelled", ErrorType.RECOGNITION_FAILED);
2315
+ }
2316
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
2317
+ } catch (error) {
2318
+ if (error instanceof NeoFaceError) {
2319
+ throw error;
2320
+ }
2321
+ throw new NeoFaceError(
2322
+ `Polling error: ${error instanceof Error ? error.message : "Unknown error"}`,
2323
+ ErrorType.NETWORK
2324
+ );
2325
+ }
2326
+ }
2327
+ throw new NeoFaceError("Task status polling timed out", ErrorType.NETWORK);
2328
+ };
2329
+ const startProofOfLife = async (videoBase64, applicationToken) => {
2330
+ var _a;
2331
+ ensureSecureContext();
2332
+ const controller = createTimeoutController();
2333
+ try {
2334
+ const response = await fetch(`${API_BASE_URL}/api/v1/auth/recognition/face/external/`, {
2335
+ method: "POST",
2336
+ headers: {
2337
+ "Content-Type": "application/json",
2338
+ "X-App-Token": applicationToken
2339
+ },
2340
+ body: JSON.stringify({
2341
+ video: videoBase64,
2342
+ purpose: "PROOF_OF_LIFE"
2343
+ }),
2344
+ signal: controller.signal
2345
+ });
2346
+ if (!response.ok) {
2347
+ if (response.status === 401 || response.status === 403) {
2348
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
2349
+ }
2350
+ if (response.status === 400) {
2351
+ const errorData = await response.json();
2352
+ throw new NeoFaceError(
2353
+ errorData.message || "Invalid request data",
2354
+ ErrorType.VALIDATION_ERROR
2355
+ );
2356
+ }
2357
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
2358
+ }
2359
+ const data = await response.json();
2360
+ if (!data.success || !((_a = data.data) == null ? void 0 : _a.task_id)) {
2361
+ throw new NeoFaceError(
2362
+ data.message || "Failed to start proof of life",
2363
+ ErrorType.RECOGNITION_FAILED
2364
+ );
2365
+ }
2366
+ return {
2367
+ taskId: data.data.task_id,
2368
+ historyId: data.data.history_id,
2369
+ status: data.data.status,
2370
+ message: data.message || "Recognition started successfully"
2371
+ };
2372
+ } catch (error) {
2373
+ if (error instanceof Error) {
2374
+ if (error.name === "AbortError") {
2375
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
2376
+ }
2377
+ if (error instanceof NeoFaceError) {
2378
+ throw error;
2379
+ }
2380
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
2381
+ }
2382
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
2383
+ }
2384
+ };
2385
+ const videoToBase64 = async (videoBlob) => {
2386
+ return new Promise((resolve, reject) => {
2387
+ const reader = new FileReader();
2388
+ reader.onload = () => {
2389
+ const result = reader.result;
2390
+ const base64 = result.includes(",") ? result.split(",")[1] : result;
2391
+ resolve(base64);
2392
+ };
2393
+ reader.onerror = () => reject(new NeoFaceError("Failed to convert video to base64", ErrorType.CAPTURE_ERROR));
2394
+ reader.readAsDataURL(videoBlob);
2395
+ });
2396
+ };
2003
2397
  const modalReducer$1 = (state, action) => {
2004
2398
  switch (action.type) {
2005
2399
  case "PERMISSION_GRANTED":
@@ -2146,8 +2540,7 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2146
2540
  const [livenessAttempts, setLivenessAttempts] = useState(0);
2147
2541
  const { startCamera, stopCamera } = useCamera();
2148
2542
  const isFaceCentered = (detection) => {
2149
- if (!videoRef.current)
2150
- return false;
2543
+ if (!videoRef.current) return false;
2151
2544
  const { box } = detection;
2152
2545
  const { videoWidth, videoHeight } = videoRef.current;
2153
2546
  const centerX = videoWidth / 2;
@@ -2165,11 +2558,9 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2165
2558
  }, 3e3);
2166
2559
  };
2167
2560
  const startFaceDetection = () => {
2168
- if (!videoRef.current)
2169
- return;
2561
+ if (!videoRef.current) return;
2170
2562
  const detectFaces = async () => {
2171
- if (!videoRef.current || state.status !== "ready")
2172
- return;
2563
+ if (!videoRef.current || state.status !== "ready") return;
2173
2564
  try {
2174
2565
  const detections = await faceapi.detectAllFaces(
2175
2566
  videoRef.current,
@@ -2231,8 +2622,7 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2231
2622
  };
2232
2623
  }, [startCamera, stopCamera]);
2233
2624
  const handleCapture = async () => {
2234
- if (!canvasRef.current || !videoRef.current)
2235
- return;
2625
+ if (!canvasRef.current || !videoRef.current) return;
2236
2626
  try {
2237
2627
  dispatch({ type: "START_CAPTURE" });
2238
2628
  const context = canvasRef.current.getContext("2d");
@@ -2244,13 +2634,11 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
2244
2634
  canvasRef.current.height = videoRef.current.videoHeight;
2245
2635
  context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
2246
2636
  const blob = await new Promise((resolve, reject) => {
2247
- var _a2;
2248
- (_a2 = canvasRef.current) == null ? void 0 : _a2.toBlob(
2637
+ var _a;
2638
+ (_a = canvasRef.current) == null ? void 0 : _a.toBlob(
2249
2639
  (b) => {
2250
- if (b)
2251
- resolve(b);
2252
- else
2253
- reject(new Error("Failed to convert canvas to blob"));
2640
+ if (b) resolve(b);
2641
+ else reject(new Error("Failed to convert canvas to blob"));
2254
2642
  },
2255
2643
  "image/jpeg",
2256
2644
  0.95
@@ -2381,6 +2769,7 @@ function BiometricRegistrationModal({
2381
2769
  applicationToken,
2382
2770
  onClose,
2383
2771
  onSuccess,
2772
+ onPhotosCaptured,
2384
2773
  onError,
2385
2774
  useRealApi = true
2386
2775
  // Sempre usa API real em produção
@@ -2485,8 +2874,7 @@ function BiometricRegistrationModal({
2485
2874
  clearInterval(progressIntervalRef.current);
2486
2875
  }
2487
2876
  progressIntervalRef.current = window.setInterval(() => {
2488
- if (!holdStartTimeRef.current)
2489
- return;
2877
+ if (!holdStartTimeRef.current) return;
2490
2878
  const elapsed = Date.now() - holdStartTimeRef.current;
2491
2879
  const progress = Math.min(elapsed / CALIBRATION.CAPTURE_HOLD_TIME * 100, 100);
2492
2880
  dispatch({ type: "UPDATE_PROGRESS", progress });
@@ -2525,8 +2913,7 @@ function BiometricRegistrationModal({
2525
2913
  useEffect(() => {
2526
2914
  let mounted = true;
2527
2915
  const initCamera = async () => {
2528
- if (state.status !== "liveness" || streamRef.current)
2529
- return;
2916
+ if (state.status !== "liveness" || streamRef.current) return;
2530
2917
  try {
2531
2918
  const stream = await navigator.mediaDevices.getUserMedia({
2532
2919
  video: {
@@ -2555,8 +2942,7 @@ function BiometricRegistrationModal({
2555
2942
  streamRef.current = stream;
2556
2943
  await video.play();
2557
2944
  } catch (error) {
2558
- if (!mounted)
2559
- return;
2945
+ if (!mounted) return;
2560
2946
  const errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
2561
2947
  dispatch({ type: "ERROR", message: errorMessage });
2562
2948
  }
@@ -2603,8 +2989,7 @@ function BiometricRegistrationModal({
2603
2989
  scoreThreshold: 0.4
2604
2990
  })
2605
2991
  ).withFaceLandmarks();
2606
- if (!isRunning)
2607
- return;
2992
+ if (!isRunning) return;
2608
2993
  const hasFace = !!detection;
2609
2994
  setFaceDetected(hasFace);
2610
2995
  if (hasFace && detection.detection) {
@@ -2645,8 +3030,8 @@ function BiometricRegistrationModal({
2645
3030
  }
2646
3031
  };
2647
3032
  const startTimer = setTimeout(() => {
2648
- var _a2;
2649
- if (isRunning && ((_a2 = videoRef.current) == null ? void 0 : _a2.readyState) === 4) {
3033
+ var _a;
3034
+ if (isRunning && ((_a = videoRef.current) == null ? void 0 : _a.readyState) === 4) {
2650
3035
  detectFace2();
2651
3036
  }
2652
3037
  }, 1e3);
@@ -2707,6 +3092,14 @@ function BiometricRegistrationModal({
2707
3092
  };
2708
3093
  const processRegistration = async (photos) => {
2709
3094
  try {
3095
+ if (onPhotosCaptured) {
3096
+ dispatch({ type: "SUCCESS" });
3097
+ onPhotosCaptured(photos);
3098
+ return;
3099
+ }
3100
+ if (!personData || !applicationToken) {
3101
+ throw new Error("Dados de registro não fornecidos");
3102
+ }
2710
3103
  console.log("🚀 Fazendo requisição ao backend...");
2711
3104
  const result = await registerPersonWithBiometric(personData, photos, applicationToken);
2712
3105
  const adaptedResult = {
@@ -2719,7 +3112,9 @@ function BiometricRegistrationModal({
2719
3112
  onboarding_link: result.onboarding_link
2720
3113
  };
2721
3114
  dispatch({ type: "SUCCESS" });
2722
- onSuccess(adaptedResult);
3115
+ if (onSuccess) {
3116
+ onSuccess(adaptedResult);
3117
+ }
2723
3118
  } catch (error) {
2724
3119
  const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
2725
3120
  dispatch({ type: "ERROR", message: errorMessage });
@@ -2727,8 +3122,7 @@ function BiometricRegistrationModal({
2727
3122
  }
2728
3123
  };
2729
3124
  const renderLivenessDetection = () => {
2730
- if (state.status !== "liveness")
2731
- return null;
3125
+ if (state.status !== "liveness") return null;
2732
3126
  const progress = `${state.currentStepIndex + 1}/${state.stepSequence.length - 1}`;
2733
3127
  const stepProgress = state.stepProgress;
2734
3128
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "liveness-container", children: [
@@ -2760,7 +3154,7 @@ function BiometricRegistrationModal({
2760
3154
  stepProgress > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-bar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-fill", style: { width: `${stepProgress}%` } }) })
2761
3155
  ] }),
2762
3156
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "branding-small", children: [
2763
- /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: "/public/logo-icone.png", alt: "NeoFaceId", className: "brand-logo-small" }),
3157
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: "/logo-icone.png", alt: "NeoFaceId", className: "brand-logo-small" }),
2764
3158
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
2765
3159
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
2766
3160
  ] })
@@ -2790,8 +3184,7 @@ function BiometricRegistrationModal({
2790
3184
  )
2791
3185
  ] });
2792
3186
  const renderError = () => {
2793
- if (state.status !== "error")
2794
- return null;
3187
+ if (state.status !== "error") return null;
2795
3188
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container error", children: [
2796
3189
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "result-icon error-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", children: [
2797
3190
  /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
@@ -2827,8 +3220,8 @@ function BiometricRegistrationModal({
2827
3220
  }
2828
3221
  )
2829
3222
  ] }) }),
2830
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Verificação concluída!" }),
2831
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Sua biometria foi registrada com sucesso" }),
3223
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: onPhotosCaptured ? "Captura concluída!" : "Verificação concluída!" }),
3224
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: onPhotosCaptured ? "Suas capturas faciais foram realizadas" : "Sua biometria foi registrada com sucesso" }),
2832
3225
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary-button", onClick: onClose, children: "Continuar" })
2833
3226
  ] });
2834
3227
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -3485,8 +3878,7 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3485
3878
  * Inicializa a câmera
3486
3879
  */
3487
3880
  async initializeCamera() {
3488
- if (!this.modal)
3489
- throw new Error("Modal não inicializado");
3881
+ if (!this.modal) throw new Error("Modal não inicializado");
3490
3882
  this.video = this.modal.querySelector(".neofaceid-video");
3491
3883
  this.canvas = this.modal.querySelector(".neofaceid-canvas");
3492
3884
  if (!this.video || !this.canvas) {
@@ -3513,8 +3905,7 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3513
3905
  * Inicia a contagem regressiva
3514
3906
  */
3515
3907
  startCountdown() {
3516
- if (!this.modal)
3517
- return;
3908
+ if (!this.modal) return;
3518
3909
  const countdownElement = this.modal.querySelector(".neofaceid-countdown-number");
3519
3910
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3520
3911
  const statusText = this.modal.querySelector(".neofaceid-status-text");
@@ -3552,13 +3943,11 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3552
3943
  * Captura a imagem da câmera
3553
3944
  */
3554
3945
  async captureImage() {
3555
- if (this.isCapturing || !this.video || !this.canvas)
3556
- return;
3946
+ if (this.isCapturing || !this.video || !this.canvas) return;
3557
3947
  this.isCapturing = true;
3558
3948
  try {
3559
3949
  const context = this.canvas.getContext("2d");
3560
- if (!context)
3561
- throw new Error("Não foi possível obter contexto do canvas");
3950
+ if (!context) throw new Error("Não foi possível obter contexto do canvas");
3562
3951
  context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
3563
3952
  const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
3564
3953
  const base64Data = imageData.split(",")[1];
@@ -3597,27 +3986,26 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3597
3986
  * Adiciona event listeners aos elementos do modal
3598
3987
  */
3599
3988
  addEventListeners() {
3600
- if (!this.modal)
3601
- return;
3989
+ if (!this.modal) return;
3602
3990
  const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
3603
3991
  const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
3604
3992
  const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3605
3993
  closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
3606
- var _a2, _b;
3994
+ var _a, _b;
3607
3995
  this.close();
3608
- (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
3996
+ (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3609
3997
  });
3610
3998
  cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
3611
- var _a2, _b;
3999
+ var _a, _b;
3612
4000
  this.close();
3613
- (_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
4001
+ (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3614
4002
  });
3615
4003
  captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
3616
4004
  this.captureImage();
3617
4005
  });
3618
4006
  this.modal.addEventListener("click", (e) => {
3619
- var _a2, _b, _c;
3620
- if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
4007
+ var _a, _b, _c;
4008
+ if (e.target === ((_a = this.modal) == null ? void 0 : _a.querySelector(".neofaceid-modal-overlay"))) {
3621
4009
  this.close();
3622
4010
  (_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
3623
4011
  }
@@ -3901,266 +4289,190 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
3901
4289
  document.head.appendChild(style);
3902
4290
  }
3903
4291
  };
3904
- function BiometricStatusOverlayComponent({ status, onClose }) {
3905
- const [isVisible, setIsVisible] = useState(true);
3906
- useEffect(() => {
3907
- if (status === "success") {
3908
- const timer = setTimeout(() => {
3909
- setIsVisible(false);
3910
- setTimeout(() => {
3911
- onClose == null ? void 0 : onClose();
3912
- }, 300);
3913
- }, 800);
3914
- return () => clearTimeout(timer);
3915
- }
3916
- }, [status, onClose]);
3917
- if (!isVisible)
3918
- return null;
3919
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3920
- /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
3921
- @keyframes fadeIn {
3922
- from { opacity: 0; transform: scale(0.9); }
3923
- to { opacity: 1; transform: scale(1); }
3924
- }
4292
+ class BiometricStatusOverlay {
4293
+ constructor() {
4294
+ __publicField(this, "container", null);
4295
+ __publicField(this, "currentStatus", "preparing");
4296
+ }
4297
+ getStyles() {
4298
+ return `
4299
+ @keyframes neofaceid-fadeIn {
4300
+ from { opacity: 0; transform: scale(0.9); }
4301
+ to { opacity: 1; transform: scale(1); }
4302
+ }
3925
4303
 
3926
- @keyframes fadeOut {
3927
- from { opacity: 1; transform: scale(1); }
3928
- to { opacity: 0; transform: scale(0.9); }
3929
- }
4304
+ @keyframes neofaceid-fadeOut {
4305
+ from { opacity: 1; transform: scale(1); }
4306
+ to { opacity: 0; transform: scale(0.9); }
4307
+ }
3930
4308
 
3931
- @keyframes pulse {
3932
- 0%, 100% {
3933
- transform: scale(1);
3934
- opacity: 1;
3935
- }
3936
- 50% {
3937
- transform: scale(1.1);
3938
- opacity: 0.9;
3939
- }
4309
+ @keyframes neofaceid-pulse {
4310
+ 0%, 100% {
4311
+ transform: scale(1);
4312
+ opacity: 1;
3940
4313
  }
3941
-
3942
- @keyframes spin {
3943
- from { transform: rotate(0deg); }
3944
- to { transform: rotate(360deg); }
4314
+ 50% {
4315
+ transform: scale(1.1);
4316
+ opacity: 0.9;
3945
4317
  }
4318
+ }
3946
4319
 
3947
- @keyframes checkmark {
3948
- 0% {
3949
- stroke-dashoffset: 100;
3950
- opacity: 0;
3951
- }
3952
- 50% {
3953
- opacity: 1;
3954
- }
3955
- 100% {
3956
- stroke-dashoffset: 0;
3957
- opacity: 1;
3958
- }
4320
+ @keyframes neofaceid-checkmark {
4321
+ 0% {
4322
+ stroke-dashoffset: 100;
4323
+ opacity: 0;
3959
4324
  }
3960
-
3961
- @keyframes errorShake {
3962
- 0%, 100% { transform: translateX(0) scale(1); }
3963
- 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px) scale(1); }
3964
- 20%, 40%, 60%, 80% { transform: translateX(4px) scale(1); }
4325
+ 50% {
4326
+ opacity: 1;
3965
4327
  }
3966
-
3967
- @keyframes scaleIn {
3968
- 0% {
3969
- transform: scale(0);
3970
- opacity: 0;
3971
- }
3972
- 50% {
3973
- transform: scale(1.1);
3974
- }
3975
- 100% {
3976
- transform: scale(1);
3977
- opacity: 1;
3978
- }
4328
+ 100% {
4329
+ stroke-dashoffset: 0;
4330
+ opacity: 1;
3979
4331
  }
4332
+ }
3980
4333
 
3981
- .neofaceid-overlay-container {
3982
- position: fixed;
3983
- top: 0;
3984
- left: 0;
3985
- right: 0;
3986
- bottom: 0;
3987
- display: flex;
3988
- align-items: center;
3989
- justify-content: center;
3990
- z-index: 10000;
3991
- pointer-events: none;
3992
- animation: fadeIn 0.3s ease-out;
3993
- }
4334
+ @keyframes neofaceid-errorShake {
4335
+ 0%, 100% { transform: translateX(0) scale(1); }
4336
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px) scale(1); }
4337
+ 20%, 40%, 60%, 80% { transform: translateX(4px) scale(1); }
4338
+ }
3994
4339
 
3995
- .neofaceid-overlay-container.fade-out {
3996
- animation: fadeOut 0.3s ease-out;
4340
+ @keyframes neofaceid-scaleIn {
4341
+ 0% {
4342
+ transform: scale(0);
4343
+ opacity: 0;
3997
4344
  }
3998
-
3999
- .neofaceid-overlay-content {
4000
- display: flex;
4001
- flex-direction: column;
4002
- align-items: center;
4003
- justify-content: center;
4004
- gap: 24px;
4005
- pointer-events: auto;
4345
+ 50% {
4346
+ transform: scale(1.1);
4006
4347
  }
4007
-
4008
- .neofaceid-logo-wrapper {
4009
- position: relative;
4010
- width: 120px;
4011
- height: 120px;
4012
- display: flex;
4013
- align-items: center;
4014
- justify-content: center;
4348
+ 100% {
4349
+ transform: scale(1);
4350
+ opacity: 1;
4015
4351
  }
4352
+ }
4016
4353
 
4017
- .neofaceid-logo {
4018
- width: 80px;
4019
- height: 80px;
4020
- object-fit: contain;
4021
- filter: drop-shadow(0 4px 12px rgba(124, 58, 237, 0.3));
4022
- }
4354
+ .neofaceid-overlay-container {
4355
+ position: fixed;
4356
+ top: 0;
4357
+ left: 0;
4358
+ right: 0;
4359
+ bottom: 0;
4360
+ display: flex;
4361
+ align-items: center;
4362
+ justify-content: center;
4363
+ z-index: 10000;
4364
+ pointer-events: none;
4365
+ animation: neofaceid-fadeIn 0.3s ease-out;
4366
+ }
4023
4367
 
4024
- .neofaceid-logo.pulsing {
4025
- animation: pulse 2s ease-in-out infinite;
4026
- }
4368
+ .neofaceid-overlay-container.fade-out {
4369
+ animation: neofaceid-fadeOut 0.3s ease-out forwards;
4370
+ }
4027
4371
 
4028
- .neofaceid-status-icon {
4029
- position: absolute;
4030
- inset: 0;
4031
- display: flex;
4032
- align-items: center;
4033
- justify-content: center;
4034
- }
4372
+ .neofaceid-overlay-content {
4373
+ display: flex;
4374
+ flex-direction: column;
4375
+ align-items: center;
4376
+ justify-content: center;
4377
+ gap: 24px;
4378
+ pointer-events: auto;
4379
+ }
4035
4380
 
4036
- .neofaceid-success-icon {
4037
- width: 60px;
4038
- height: 60px;
4039
- color: #10b981;
4040
- animation: scaleIn 0.6s ease-out;
4041
- }
4381
+ .neofaceid-logo-wrapper {
4382
+ position: relative;
4383
+ width: 120px;
4384
+ height: 120px;
4385
+ display: flex;
4386
+ align-items: center;
4387
+ justify-content: center;
4388
+ }
4042
4389
 
4043
- .neofaceid-success-icon svg circle {
4044
- stroke-dasharray: 100;
4045
- stroke-dashoffset: 100;
4046
- animation: checkmark 0.6s ease-out 0.2s forwards;
4047
- }
4390
+ .neofaceid-logo {
4391
+ width: 80px;
4392
+ height: 80px;
4393
+ object-fit: contain;
4394
+ filter: drop-shadow(0 4px 12px rgba(124, 58, 237, 0.3));
4395
+ }
4048
4396
 
4049
- .neofaceid-error-icon {
4050
- width: 60px;
4051
- height: 60px;
4052
- color: #ef4444;
4053
- animation: errorShake 0.5s ease-out, scaleIn 0.4s ease-out;
4054
- }
4397
+ .neofaceid-logo.pulsing {
4398
+ animation: neofaceid-pulse 2s ease-in-out infinite;
4399
+ }
4055
4400
 
4056
- /* Responsive */
4057
- @media (max-width: 640px) {
4058
- .neofaceid-logo-wrapper {
4059
- width: 100px;
4060
- height: 100px;
4061
- }
4401
+ .neofaceid-status-icon {
4402
+ position: absolute;
4403
+ inset: 0;
4404
+ display: flex;
4405
+ align-items: center;
4406
+ justify-content: center;
4407
+ }
4062
4408
 
4063
- .neofaceid-logo {
4064
- width: 70px;
4065
- height: 70px;
4066
- }
4067
- }
4068
- ` }),
4069
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `neofaceid-overlay-container ${!isVisible ? "fade-out" : ""}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-overlay-content", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "neofaceid-logo-wrapper", children: [
4070
- (status === "preparing" || status === "verifying") && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4071
- "img",
4072
- {
4073
- src: "/public/logo-icone-no-backgorund.png",
4074
- alt: "NeoFaceId",
4075
- className: `neofaceid-logo ${status === "verifying" ? "pulsing" : ""}`,
4076
- onError: (e) => {
4077
- var _a2;
4078
- const img = e.target;
4079
- img.style.display = "none";
4080
- const fallback = document.createElement("div");
4081
- fallback.innerHTML = `
4082
- <svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
4083
- <circle cx="40" cy="40" r="35" fill="#7c3aed" opacity="0.2"/>
4084
- <path d="M40 20 L40 60 M20 40 L60 40" stroke="#7c3aed" stroke-width="4" stroke-linecap="round"/>
4085
- </svg>
4086
- `;
4087
- fallback.style.cssText = "width: 80px; height: 80px; display: flex; align-items: center; justify-content: center;";
4088
- (_a2 = img.parentElement) == null ? void 0 : _a2.appendChild(fallback);
4089
- }
4090
- }
4091
- ) }),
4092
- status === "success" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4093
- "svg",
4094
- {
4095
- className: "neofaceid-success-icon",
4096
- viewBox: "0 0 60 60",
4097
- fill: "none",
4098
- xmlns: "http://www.w3.org/2000/svg",
4099
- children: [
4100
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4101
- "circle",
4102
- {
4103
- cx: "30",
4104
- cy: "30",
4105
- r: "28",
4106
- stroke: "currentColor",
4107
- strokeWidth: "3",
4108
- fill: "none"
4109
- }
4110
- ),
4111
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4112
- "path",
4113
- {
4114
- d: "M15 30 L25 40 L45 20",
4115
- stroke: "currentColor",
4116
- strokeWidth: "3",
4117
- strokeLinecap: "round",
4118
- strokeLinejoin: "round",
4119
- fill: "none"
4120
- }
4121
- )
4122
- ]
4409
+ .neofaceid-success-icon {
4410
+ width: 60px;
4411
+ height: 60px;
4412
+ color: #10b981;
4413
+ animation: neofaceid-scaleIn 0.6s ease-out;
4414
+ }
4415
+
4416
+ .neofaceid-error-icon {
4417
+ width: 60px;
4418
+ height: 60px;
4419
+ color: #ef4444;
4420
+ animation: neofaceid-errorShake 0.5s ease-out, neofaceid-scaleIn 0.4s ease-out;
4421
+ }
4422
+
4423
+ @media (max-width: 640px) {
4424
+ .neofaceid-logo-wrapper {
4425
+ width: 100px;
4426
+ height: 100px;
4123
4427
  }
4124
- ) }),
4125
- status === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "neofaceid-status-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
4126
- "svg",
4127
- {
4128
- className: "neofaceid-error-icon",
4129
- viewBox: "0 0 60 60",
4130
- fill: "none",
4131
- xmlns: "http://www.w3.org/2000/svg",
4132
- children: [
4133
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4134
- "circle",
4135
- {
4136
- cx: "30",
4137
- cy: "30",
4138
- r: "28",
4139
- stroke: "currentColor",
4140
- strokeWidth: "3",
4141
- fill: "none"
4142
- }
4143
- ),
4144
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4145
- "path",
4146
- {
4147
- d: "M20 20 L40 40 M40 20 L20 40",
4148
- stroke: "currentColor",
4149
- strokeWidth: "3",
4150
- strokeLinecap: "round"
4151
- }
4152
- )
4153
- ]
4428
+
4429
+ .neofaceid-logo {
4430
+ width: 70px;
4431
+ height: 70px;
4154
4432
  }
4155
- ) })
4156
- ] }) }) })
4157
- ] });
4158
- }
4159
- class BiometricStatusOverlay {
4160
- constructor() {
4161
- __publicField(this, "container", null);
4162
- __publicField(this, "root", null);
4163
- __publicField(this, "currentStatus", "preparing");
4433
+ }
4434
+ `;
4435
+ }
4436
+ renderContent() {
4437
+ const isPulsing = this.currentStatus === "verifying";
4438
+ const showLogo = this.currentStatus === "preparing" || this.currentStatus === "verifying";
4439
+ const showSuccess = this.currentStatus === "success";
4440
+ const showError = this.currentStatus === "error";
4441
+ return `
4442
+ <style>${this.getStyles()}</style>
4443
+ <div class="neofaceid-overlay-container">
4444
+ <div class="neofaceid-overlay-content">
4445
+ <div class="neofaceid-logo-wrapper">
4446
+ ${showLogo ? `
4447
+ <img
4448
+ src="/logo-icone-no-background.png"
4449
+ alt="NeoFaceId"
4450
+ class="neofaceid-logo ${isPulsing ? "pulsing" : ""}"
4451
+ onerror="this.style.display='none'"
4452
+ />
4453
+ ` : ""}
4454
+
4455
+ ${showSuccess ? `
4456
+ <div class="neofaceid-status-icon">
4457
+ <svg class="neofaceid-success-icon" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
4458
+ <circle cx="30" cy="30" r="28" stroke="currentColor" stroke-width="3" fill="none"/>
4459
+ <path d="M15 30 L25 40 L45 20" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
4460
+ </svg>
4461
+ </div>
4462
+ ` : ""}
4463
+
4464
+ ${showError ? `
4465
+ <div class="neofaceid-status-icon">
4466
+ <svg class="neofaceid-error-icon" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
4467
+ <circle cx="30" cy="30" r="28" stroke="currentColor" stroke-width="3" fill="none"/>
4468
+ <path d="M20 20 L40 40 M40 20 L20 40" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
4469
+ </svg>
4470
+ </div>
4471
+ ` : ""}
4472
+ </div>
4473
+ </div>
4474
+ </div>
4475
+ `;
4164
4476
  }
4165
4477
  /**
4166
4478
  * Cria e exibe o overlay
@@ -4172,18 +4484,17 @@ class BiometricStatusOverlay {
4172
4484
  }
4173
4485
  this.container = document.createElement("div");
4174
4486
  this.container.id = "neofaceid-status-overlay";
4175
- document.body.appendChild(this.container);
4176
- this.root = createRoot(this.container);
4177
4487
  this.currentStatus = status;
4178
- this.render();
4488
+ this.container.innerHTML = this.renderContent();
4489
+ document.body.appendChild(this.container);
4179
4490
  }
4180
4491
  /**
4181
4492
  * Atualiza o status do overlay
4182
4493
  */
4183
4494
  updateStatus(status) {
4184
4495
  this.currentStatus = status;
4185
- if (this.root) {
4186
- this.render();
4496
+ if (this.container) {
4497
+ this.container.innerHTML = this.renderContent();
4187
4498
  }
4188
4499
  }
4189
4500
  /**
@@ -4191,42 +4502,18 @@ class BiometricStatusOverlay {
4191
4502
  */
4192
4503
  close() {
4193
4504
  if (this.container) {
4194
- if (this.root) {
4195
- this.root.render(
4196
- /* @__PURE__ */ jsxRuntimeExports.jsx(
4197
- BiometricStatusOverlayComponent,
4198
- {
4199
- status: this.currentStatus,
4200
- onClose: () => {
4201
- if (this.container && document.body.contains(this.container)) {
4202
- document.body.removeChild(this.container);
4203
- }
4204
- this.container = null;
4205
- this.root = null;
4206
- }
4207
- }
4208
- )
4209
- );
4505
+ const overlayElement = this.container.querySelector(".neofaceid-overlay-container");
4506
+ if (overlayElement) {
4507
+ overlayElement.classList.add("fade-out");
4210
4508
  }
4211
4509
  setTimeout(() => {
4212
4510
  if (this.container && document.body.contains(this.container)) {
4213
4511
  document.body.removeChild(this.container);
4214
4512
  }
4215
4513
  this.container = null;
4216
- this.root = null;
4217
4514
  }, 300);
4218
4515
  }
4219
4516
  }
4220
- /**
4221
- * Renderiza o componente
4222
- */
4223
- render() {
4224
- if (!this.root || !this.container)
4225
- return;
4226
- this.root.render(
4227
- /* @__PURE__ */ jsxRuntimeExports.jsx(BiometricStatusOverlayComponent, { status: this.currentStatus, onClose: () => this.close() })
4228
- );
4229
- }
4230
4517
  }
4231
4518
  function FallbackPromptComponent({
4232
4519
  error,
@@ -4439,7 +4726,7 @@ class FallbackPrompt {
4439
4726
  this.container = document.createElement("div");
4440
4727
  this.container.id = "neofaceid-fallback-prompt";
4441
4728
  document.body.appendChild(this.container);
4442
- this.root = createRoot(this.container);
4729
+ this.root = clientExports.createRoot(this.container);
4443
4730
  this.render(error, onRetry, onCredentials, onCancel);
4444
4731
  }
4445
4732
  /**
@@ -4459,8 +4746,7 @@ class FallbackPrompt {
4459
4746
  * Renderiza o componente
4460
4747
  */
4461
4748
  render(error, onRetry, onCredentials, onCancel) {
4462
- if (!this.root || !this.container)
4463
- return;
4749
+ if (!this.root || !this.container) return;
4464
4750
  this.root.render(
4465
4751
  /* @__PURE__ */ jsxRuntimeExports.jsx(
4466
4752
  FallbackPromptComponent,
@@ -4526,7 +4812,7 @@ class EmailPasswordModal {
4526
4812
 
4527
4813
  <!-- Branding -->
4528
4814
  <div class="neoface-email-branding">
4529
- <img src="/public/logo-icone-no-backgorund.png" alt="NeoFaceId" class="neoface-email-brand-logo" onerror="this.style.display='none'" />
4815
+ <img src="/logo-icone-no-background.png" alt="NeoFaceId" class="neoface-email-brand-logo" onerror="this.style.display='none'" />
4530
4816
  <span>NeoFaceId by</span>
4531
4817
  <span class="neoface-email-brand-name">OCTA</span>
4532
4818
  </div>
@@ -4752,8 +5038,30 @@ class EmailPasswordModal {
4752
5038
  }
4753
5039
  }
4754
5040
  }
4755
- const MAX_ATTEMPTS = 3;
5041
+ const MAX_ATTEMPTS = 2;
4756
5042
  const RETRY_DELAY = 500;
5043
+ const CAMERA_READY_DELAY = 200;
5044
+ const CAMERA_STABILITY_DELAY = 100;
5045
+ const FACE_DETECTION_TIME = 5e3;
5046
+ const DETECTION_INTERVAL = 100;
5047
+ const SUCCESS_DELAY = 200;
5048
+ let modelsLoaded = false;
5049
+ let modelsLoading = null;
5050
+ async function preloadFaceDetectionModels() {
5051
+ if (modelsLoaded) return;
5052
+ if (modelsLoading) return modelsLoading;
5053
+ modelsLoading = (async () => {
5054
+ try {
5055
+ const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
5056
+ await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL);
5057
+ modelsLoaded = true;
5058
+ console.log("✅ Face detection models preloaded");
5059
+ } catch (error) {
5060
+ console.warn("⚠️ Face-api.js models not loaded:", error);
5061
+ }
5062
+ })();
5063
+ return modelsLoading;
5064
+ }
4757
5065
  async function captureFaceSilently() {
4758
5066
  return new Promise((resolve, reject) => {
4759
5067
  const video = document.createElement("video");
@@ -4815,20 +5123,21 @@ async function captureFaceSilently() {
4815
5123
  return;
4816
5124
  }
4817
5125
  const detectFace2 = async () => {
5126
+ if (!modelsLoaded) return true;
4818
5127
  try {
4819
5128
  const detection = await faceapi.detectSingleFace(
4820
5129
  video,
4821
- new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
5130
+ new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
4822
5131
  );
4823
5132
  return !!detection;
4824
5133
  } catch {
4825
- return false;
5134
+ return true;
4826
5135
  }
4827
5136
  };
4828
5137
  detectFace2().then((hasFace) => {
4829
5138
  if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
4830
5139
  faceDetectionAttempts++;
4831
- setTimeout(tryCapture, 200);
5140
+ setTimeout(tryCapture, 100);
4832
5141
  return;
4833
5142
  }
4834
5143
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
@@ -4847,7 +5156,7 @@ async function captureFaceSilently() {
4847
5156
  });
4848
5157
  };
4849
5158
  video.onloadedmetadata = () => {
4850
- setTimeout(tryCapture, 800);
5159
+ setTimeout(tryCapture, CAMERA_READY_DELAY);
4851
5160
  };
4852
5161
  video.onerror = () => {
4853
5162
  cleanup();
@@ -4864,64 +5173,52 @@ async function captureFaceSilently() {
4864
5173
  });
4865
5174
  });
4866
5175
  }
4867
- async function detectFaceContinuously(video, minDetectionTime = 3e3, detectionInterval = 200) {
5176
+ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
4868
5177
  return new Promise((resolve) => {
4869
- let faceDetectedCount = 0;
4870
- let totalChecks = 0;
4871
- const requiredChecks = Math.ceil(minDetectionTime / detectionInterval);
4872
- const minSuccessRate = 0.6;
5178
+ let attempts = 0;
5179
+ const maxAttempts = Math.ceil(maxTime / interval);
4873
5180
  let timeoutId = null;
4874
5181
  let isResolved = false;
4875
5182
  const finish = (success) => {
4876
- if (isResolved)
4877
- return;
5183
+ if (isResolved) return;
4878
5184
  isResolved = true;
4879
- if (timeoutId) {
4880
- clearTimeout(timeoutId);
4881
- }
5185
+ if (timeoutId) clearTimeout(timeoutId);
4882
5186
  resolve(success);
4883
5187
  };
4884
5188
  const checkFace = async () => {
4885
- if (isResolved)
5189
+ if (isResolved) return;
5190
+ attempts++;
5191
+ if (!modelsLoaded) {
5192
+ finish(true);
4886
5193
  return;
5194
+ }
4887
5195
  try {
4888
5196
  const detection = await faceapi.detectSingleFace(
4889
5197
  video,
4890
- new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
5198
+ new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
4891
5199
  );
4892
- totalChecks++;
4893
5200
  if (detection) {
4894
- faceDetectedCount++;
5201
+ finish(true);
5202
+ return;
4895
5203
  }
4896
- if (totalChecks >= requiredChecks) {
4897
- const successRate = faceDetectedCount / totalChecks;
4898
- finish(successRate >= minSuccessRate);
5204
+ if (attempts >= maxAttempts) {
5205
+ finish(false);
4899
5206
  return;
4900
5207
  }
4901
- timeoutId = window.setTimeout(checkFace, detectionInterval);
5208
+ timeoutId = window.setTimeout(checkFace, interval);
4902
5209
  } catch (error) {
4903
- totalChecks++;
4904
- if (totalChecks >= requiredChecks) {
4905
- const successRate = faceDetectedCount / totalChecks;
4906
- finish(successRate >= minSuccessRate);
4907
- } else {
4908
- timeoutId = window.setTimeout(checkFace, detectionInterval);
4909
- }
5210
+ finish(true);
4910
5211
  }
4911
5212
  };
4912
- const safetyTimeout = window.setTimeout(() => {
5213
+ window.setTimeout(() => {
4913
5214
  if (!isResolved) {
4914
- const successRate = totalChecks > 0 ? faceDetectedCount / totalChecks : 0;
4915
- finish(successRate >= minSuccessRate);
4916
- }
4917
- }, minDetectionTime + 1e3);
4918
- timeoutId = window.setTimeout(() => {
4919
- checkFace();
4920
- clearTimeout(safetyTimeout);
4921
- }, detectionInterval);
5215
+ finish(true);
5216
+ }
5217
+ }, maxTime + 500);
5218
+ checkFace();
4922
5219
  });
4923
5220
  }
4924
- async function attemptLogin(applicationToken, overlay) {
5221
+ async function attemptLogin(applicationToken, overlay, fastMode = false) {
4925
5222
  overlay.updateStatus("verifying");
4926
5223
  const video = document.createElement("video");
4927
5224
  video.style.position = "fixed";
@@ -4953,8 +5250,11 @@ async function attemptLogin(applicationToken, overlay) {
4953
5250
  video.onloadedmetadata = () => resolve(void 0);
4954
5251
  }
4955
5252
  });
4956
- await new Promise((resolve) => setTimeout(resolve, 500));
4957
- const faceDetected = await detectFaceContinuously(video, 3e3);
5253
+ await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
5254
+ let faceDetected = true;
5255
+ if (!fastMode) {
5256
+ faceDetected = await detectFaceQuickly(video);
5257
+ }
4958
5258
  if (!faceDetected) {
4959
5259
  if (stream) {
4960
5260
  stream.getTracks().forEach((track) => track.stop());
@@ -5020,30 +5320,25 @@ async function attemptLogin(applicationToken, overlay) {
5020
5320
  }
5021
5321
  }
5022
5322
  async function executeBiometricLoginFlow(options) {
5023
- const { applicationToken, onSuccess, onError, onFallbackRequest, onCancel } = options;
5323
+ const { applicationToken, onSuccess, onError, onFallbackRequest, onCancel, fastMode = false } = options;
5024
5324
  const overlay = new BiometricStatusOverlay();
5025
5325
  const fallbackPrompt = new FallbackPrompt();
5026
5326
  let attempts = 0;
5027
5327
  let lastError;
5028
- try {
5029
- const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
5030
- await Promise.all([
5031
- faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
5032
- faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
5033
- ]);
5034
- } catch (error) {
5035
- console.warn("Face-api.js models not loaded, continuing without face detection");
5328
+ if (!modelsLoaded && !fastMode) {
5329
+ preloadFaceDetectionModels().catch(() => {
5330
+ });
5036
5331
  }
5037
5332
  overlay.show("preparing");
5038
5333
  const tryLogin = async () => {
5039
5334
  try {
5040
5335
  attempts++;
5041
- const result = await attemptLogin(applicationToken, overlay);
5336
+ const result = await attemptLogin(applicationToken, overlay, fastMode);
5042
5337
  overlay.updateStatus("success");
5043
5338
  setTimeout(() => {
5044
5339
  overlay.close();
5045
5340
  onSuccess(result);
5046
- }, 800);
5341
+ }, SUCCESS_DELAY);
5047
5342
  } catch (error) {
5048
5343
  lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
5049
5344
  error instanceof Error ? error.message : "Erro desconhecido",
@@ -5062,7 +5357,7 @@ async function executeBiometricLoginFlow(options) {
5062
5357
  () => {
5063
5358
  attempts = 0;
5064
5359
  overlay.show("preparing");
5065
- setTimeout(() => tryLogin(), 500);
5360
+ setTimeout(() => tryLogin(), RETRY_DELAY);
5066
5361
  },
5067
5362
  () => {
5068
5363
  if (onFallbackRequest) {
@@ -5078,18 +5373,19 @@ async function executeBiometricLoginFlow(options) {
5078
5373
  },
5079
5374
  onCancel
5080
5375
  );
5081
- }, 1e3);
5376
+ }, 500);
5082
5377
  }
5083
5378
  }
5084
5379
  };
5085
- setTimeout(() => {
5380
+ {
5086
5381
  tryLogin();
5087
- }, 300);
5382
+ }
5088
5383
  }
5089
5384
  const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
5090
5385
  __proto__: null,
5091
5386
  captureFaceSilently,
5092
- executeBiometricLoginFlow
5387
+ executeBiometricLoginFlow,
5388
+ preloadFaceDetectionModels
5093
5389
  }, Symbol.toStringTag, { value: "Module" }));
5094
5390
  async function startFaceLogin(options) {
5095
5391
  try {
@@ -5418,8 +5714,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
5418
5714
  initializeBiometricDetection,
5419
5715
  isAdvancedDetectionAvailable
5420
5716
  }, Symbol.toStringTag, { value: "Module" }));
5421
- const VERSION = "1.6.1";
5422
- const RELEASE_DATE = "2025-12-01";
5717
+ const VERSION = "1.9.0";
5718
+ const RELEASE_DATE = "2026-01-20";
5423
5719
  class OnboardingCaptureModal {
5424
5720
  constructor(options) {
5425
5721
  __publicField(this, "overlay", null);
@@ -5526,8 +5822,7 @@ class OnboardingCaptureModal {
5526
5822
  audio: false
5527
5823
  };
5528
5824
  this.stream = await navigator.mediaDevices.getUserMedia(constraints);
5529
- if (!this.video)
5530
- throw new Error("Elemento de vídeo não encontrado");
5825
+ if (!this.video) throw new Error("Elemento de vídeo não encontrado");
5531
5826
  this.video.srcObject = this.stream;
5532
5827
  await this.video.play();
5533
5828
  }
@@ -5535,8 +5830,7 @@ class OnboardingCaptureModal {
5535
5830
  * Executa a contagem regressiva e atualiza textos para captura de rosto.
5536
5831
  */
5537
5832
  async runFaceCaptureCountdown() {
5538
- if (!this.overlay)
5539
- return;
5833
+ if (!this.overlay) return;
5540
5834
  const countdownEl = this.overlay.querySelector(".neofaceid-countdown");
5541
5835
  const countdownNumberEl = this.overlay.querySelector(".neofaceid-countdown-number");
5542
5836
  const statusText = this.overlay.querySelector(".neofaceid-status-text");
@@ -5560,8 +5854,7 @@ class OnboardingCaptureModal {
5560
5854
  * Aguarda o clique do usuário no botão "Capturar" (para documento).
5561
5855
  */
5562
5856
  async waitForUserCaptureClick() {
5563
- if (!this.overlay)
5564
- return;
5857
+ if (!this.overlay) return;
5565
5858
  const captureBtn = this.overlay.querySelector(".neofaceid-capture-btn");
5566
5859
  const statusText = this.overlay.querySelector(".neofaceid-status-text");
5567
5860
  captureBtn.disabled = false;
@@ -5579,11 +5872,9 @@ class OnboardingCaptureModal {
5579
5872
  * Captura o frame atual do vídeo e retorna como Blob JPEG.
5580
5873
  */
5581
5874
  async captureFrame() {
5582
- if (!this.video || !this.canvas)
5583
- throw new Error("Elementos de captura não encontrados");
5875
+ if (!this.video || !this.canvas) throw new Error("Elementos de captura não encontrados");
5584
5876
  const ctx = this.canvas.getContext("2d");
5585
- if (!ctx)
5586
- throw new Error("Falha ao obter contexto do canvas");
5877
+ if (!ctx) throw new Error("Falha ao obter contexto do canvas");
5587
5878
  this.canvas.width = this.video.videoWidth;
5588
5879
  this.canvas.height = this.video.videoHeight;
5589
5880
  ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
@@ -5596,8 +5887,7 @@ class OnboardingCaptureModal {
5596
5887
  * Atualiza título e subtítulo do modal conforme o passo.
5597
5888
  */
5598
5889
  updateTexts(title, subtitle) {
5599
- if (!this.overlay)
5600
- return;
5890
+ if (!this.overlay) return;
5601
5891
  const titleEl = this.overlay.querySelector(".neofaceid-title");
5602
5892
  const subtitleEl = this.overlay.querySelector(".neofaceid-subtitle");
5603
5893
  const frameEl = this.overlay.querySelector(".neofaceid-frame");
@@ -5643,6 +5933,7 @@ const startOnboarding = async (options) => {
5643
5933
  onError(new NeoFaceError(message, ErrorType.UNKNOWN));
5644
5934
  }
5645
5935
  };
5936
+ const __vite_import_meta_env__ = {};
5646
5937
  class NeoFaceID {
5647
5938
  /**
5648
5939
  * Creates a new NeoFaceID instance
@@ -5654,11 +5945,16 @@ class NeoFaceID {
5654
5945
  __publicField(this, "baseUrl");
5655
5946
  __publicField(this, "signature");
5656
5947
  __publicField(this, "sessionData");
5657
- var _a2;
5658
5948
  this.appToken = config.appToken;
5659
- this.baseUrl = config.baseUrl || (((_a2 = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a2.VITE_API_BASE_URL) || "http://localhost:8000");
5949
+ this.baseUrl = config.baseUrl || (__vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_API_BASE_URL);
5660
5950
  this.signature = config.signature;
5661
5951
  this.sessionData = config.sessionData;
5952
+ if (!this.baseUrl) {
5953
+ throw new NeoFaceError(
5954
+ "baseUrl não configurado. Passe via config ou configure VITE_API_BASE_URL no .env",
5955
+ ErrorType.VALIDATION_ERROR
5956
+ );
5957
+ }
5662
5958
  if (this.signature && !this.validateSignatureFormat(this.signature)) {
5663
5959
  throw new NeoFaceError(
5664
5960
  "Invalid signature format. Expected HMAC-SHA256 hex string (minimum 64 characters)",
@@ -5720,14 +6016,14 @@ class NeoFaceID {
5720
6016
  }
5721
6017
  /**
5722
6018
  * Performs login recognition using biometric data
5723
- *
6019
+ *
5724
6020
  * This method is designed for external integrations that require
5725
6021
  * signature validation and session management.
5726
- *
6022
+ *
5727
6023
  * @param options Recognition options
5728
6024
  * @returns Promise that resolves to recognition result
5729
6025
  * @throws NeoFaceError if recognition fails
5730
- *
6026
+ *
5731
6027
  * @example
5732
6028
  * ```typescript
5733
6029
  * const result = await sdk.loginRecognition({
@@ -5739,18 +6035,13 @@ class NeoFaceID {
5739
6035
  * purpose: 'LOGIN',
5740
6036
  * confidenceThreshold: 0.8
5741
6037
  * });
5742
- *
6038
+ *
5743
6039
  * // Result includes signature and sessionId for callback validation
5744
6040
  * console.log(result.signature, result.sessionId);
5745
6041
  * ```
5746
6042
  */
5747
6043
  async loginRecognition(options) {
5748
- const {
5749
- biometricData,
5750
- typeOfIdentification,
5751
- purpose,
5752
- confidenceThreshold = 0.8
5753
- } = options;
6044
+ const { biometricData, typeOfIdentification, purpose, confidenceThreshold = 0.8 } = options;
5754
6045
  const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
5755
6046
  if (imageBlobs.length === 0) {
5756
6047
  throw new NeoFaceError(
@@ -5769,10 +6060,7 @@ class NeoFaceID {
5769
6060
  this.sessionData
5770
6061
  );
5771
6062
  if (!result.success) {
5772
- throw new NeoFaceError(
5773
- "Recognition failed",
5774
- ErrorType.RECOGNITION_FAILED
5775
- );
6063
+ throw new NeoFaceError("Recognition failed", ErrorType.RECOGNITION_FAILED);
5776
6064
  }
5777
6065
  if (!result.personName || !result.email || !result.cpf) {
5778
6066
  throw new NeoFaceError(
@@ -5800,20 +6088,169 @@ class NeoFaceID {
5800
6088
  );
5801
6089
  }
5802
6090
  }
5803
- }
5804
- const PHOTO_INSTRUCTIONS = [
5805
- "Olhe diretamente para a câmera",
5806
- "Vire levemente a cabeça para a esquerda",
5807
- "Vire levemente a cabeça para a direita",
5808
- "Mostre o polegar para cima (👍) e olhe para a câmera"
5809
- ];
5810
- const TOTAL_PHOTOS = 4;
5811
- const authorizeOperation = async (applicationToken, cpf, options) => {
5812
- if (!applicationToken) {
5813
- throw new NeoFaceError("Application token is required", ErrorType.VALIDATION_ERROR);
5814
- }
5815
- if (!cpf) {
5816
- throw new NeoFaceError("CPF is required", ErrorType.VALIDATION_ERROR);
6091
+ /**
6092
+ * Register a new application for a consumer
6093
+ *
6094
+ * This method allows authenticated users to register new applications
6095
+ * that will receive their own app_token for API access.
6096
+ *
6097
+ * @param jwtToken JWT authentication token from logged user
6098
+ * @param consumerId Consumer ID (UUID) who will own the application
6099
+ * @param applicationData Application registration data
6100
+ * @returns Promise with registration result including app_token
6101
+ * @throws NeoFaceError if registration fails
6102
+ *
6103
+ * @example
6104
+ * ```typescript
6105
+ * const sdk = new NeoFaceID({
6106
+ * appToken: 'your-application-token'
6107
+ * });
6108
+ *
6109
+ * const result = await sdk.registerApplication(
6110
+ * userJwtToken,
6111
+ * consumerUuid,
6112
+ * {
6113
+ * applicationName: 'My New App',
6114
+ * domain: 'example.com',
6115
+ * acceptOnlyEmailWithSameDomain: true
6116
+ * }
6117
+ * );
6118
+ *
6119
+ * // Use the generated app_token for the new application
6120
+ * console.log('New App Token:', result.application.app_token);
6121
+ * ```
6122
+ */
6123
+ async registerApplication(jwtToken, consumerId, applicationData) {
6124
+ try {
6125
+ const result = await registerApplication(jwtToken, consumerId, applicationData);
6126
+ return result;
6127
+ } catch (error) {
6128
+ if (error instanceof NeoFaceError) {
6129
+ throw error;
6130
+ }
6131
+ throw new NeoFaceError(
6132
+ `Application registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
6133
+ ErrorType.VALIDATION_ERROR
6134
+ );
6135
+ }
6136
+ }
6137
+ /**
6138
+ * Performs Proof of Life verification
6139
+ *
6140
+ * This method records a video from the camera, sends it to the backend
6141
+ * for liveness detection and face recognition, and returns the result
6142
+ * with personal data filtered by purpose.
6143
+ *
6144
+ * The process is asynchronous:
6145
+ * 1. Records video from the camera (default 3 seconds)
6146
+ * 2. Converts video to base64 and sends to backend
6147
+ * 3. Backend processes liveness detection and face recognition
6148
+ * 4. Polls for task completion
6149
+ * 5. Returns personal data of the identified person
6150
+ *
6151
+ * @param options Configuration options for the proof of life process
6152
+ * @returns Promise that resolves to ProofOfLifeResult
6153
+ * @throws NeoFaceError if verification fails
6154
+ *
6155
+ * @example
6156
+ * ```typescript
6157
+ * const sdk = new NeoFaceID({
6158
+ * appToken: 'your-application-token'
6159
+ * });
6160
+ *
6161
+ * const result = await sdk.proofOfLife({
6162
+ * videoDurationMs: 3000, // 3 seconds
6163
+ * onRecordingProgress: (progress) => {
6164
+ * console.log(`Recording: ${progress}%`);
6165
+ * },
6166
+ * onTaskStatusChange: (status, progress) => {
6167
+ * console.log(`Status: ${status}, Progress: ${progress}%`);
6168
+ * }
6169
+ * });
6170
+ *
6171
+ * if (result.success && result.isLive) {
6172
+ * console.log('Person verified:', result.personalData);
6173
+ * console.log('Liveness score:', result.livenessScore);
6174
+ * console.log('Face recognition score:', result.faceRecognitionScore);
6175
+ * }
6176
+ * ```
6177
+ */
6178
+ async proofOfLife(options = {}) {
6179
+ const {
6180
+ videoDurationMs = 3e3,
6181
+ maxPollingAttempts = 60,
6182
+ pollingIntervalMs = 1e3,
6183
+ onRecordingProgress,
6184
+ onTaskStatusChange
6185
+ } = options;
6186
+ try {
6187
+ const videoBlob = await recordFaceVideo({
6188
+ durationMs: videoDurationMs,
6189
+ onProgress: onRecordingProgress
6190
+ });
6191
+ const videoBase64 = await videoToBase64(videoBlob);
6192
+ const startResult = await startProofOfLife(videoBase64, this.appToken);
6193
+ if (onTaskStatusChange) {
6194
+ onTaskStatusChange("processing", 0);
6195
+ }
6196
+ const taskResult = await pollTaskStatus(startResult.taskId, this.appToken, {
6197
+ maxAttempts: maxPollingAttempts,
6198
+ intervalMs: pollingIntervalMs,
6199
+ onStatusChange: onTaskStatusChange
6200
+ });
6201
+ if (!taskResult || !taskResult.success) {
6202
+ return {
6203
+ success: false,
6204
+ isLive: (taskResult == null ? void 0 : taskResult.is_live) || false,
6205
+ livenessScore: (taskResult == null ? void 0 : taskResult.liveness_score) || 0,
6206
+ faceRecognitionScore: (taskResult == null ? void 0 : taskResult.face_recognition_score) || 0,
6207
+ combinedConfidence: (taskResult == null ? void 0 : taskResult.combined_confidence) || 0,
6208
+ personId: taskResult == null ? void 0 : taskResult.person_id,
6209
+ personalData: (taskResult == null ? void 0 : taskResult.personal_data) || [],
6210
+ processingTime: (taskResult == null ? void 0 : taskResult.processing_time) || 0,
6211
+ historyId: startResult.historyId,
6212
+ taskId: startResult.taskId,
6213
+ error: (taskResult == null ? void 0 : taskResult.error) || "Verification failed",
6214
+ message: (taskResult == null ? void 0 : taskResult.message) || "Proof of life verification failed"
6215
+ };
6216
+ }
6217
+ return {
6218
+ success: true,
6219
+ isLive: taskResult.is_live || false,
6220
+ livenessScore: taskResult.liveness_score || 0,
6221
+ faceRecognitionScore: taskResult.face_recognition_score || 0,
6222
+ combinedConfidence: taskResult.combined_confidence || 0,
6223
+ personId: taskResult.person_id,
6224
+ personalData: taskResult.personal_data || [],
6225
+ processingTime: taskResult.processing_time || 0,
6226
+ historyId: startResult.historyId,
6227
+ taskId: startResult.taskId,
6228
+ message: "Proof of life verification successful"
6229
+ };
6230
+ } catch (error) {
6231
+ if (error instanceof NeoFaceError) {
6232
+ throw error;
6233
+ }
6234
+ throw new NeoFaceError(
6235
+ `Proof of life failed: ${error instanceof Error ? error.message : "Unknown error"}`,
6236
+ ErrorType.RECOGNITION_FAILED
6237
+ );
6238
+ }
6239
+ }
6240
+ }
6241
+ const PHOTO_INSTRUCTIONS = [
6242
+ "Olhe diretamente para a câmera",
6243
+ "Vire levemente a cabeça para a esquerda",
6244
+ "Vire levemente a cabeça para a direita",
6245
+ "Mostre o polegar para cima (👍) e olhe para a câmera"
6246
+ ];
6247
+ const TOTAL_PHOTOS = 4;
6248
+ const authorizeOperation = async (applicationToken, cpf, options) => {
6249
+ if (!applicationToken) {
6250
+ throw new NeoFaceError("Application token is required", ErrorType.VALIDATION_ERROR);
6251
+ }
6252
+ if (!cpf) {
6253
+ throw new NeoFaceError("CPF is required", ErrorType.VALIDATION_ERROR);
5817
6254
  }
5818
6255
  const cpfClean = cpf.replace(/\D/g, "");
5819
6256
  if (cpfClean.length !== 11) {
@@ -5921,6 +6358,999 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
5921
6358
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
5922
6359
  }
5923
6360
  };
6361
+ const DOCUMENT_INFO = {
6362
+ RG: {
6363
+ name: "RG / Carteira de Identidade",
6364
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M15 8h2M15 12h2M7 16h10"/></svg>',
6365
+ hasBack: true,
6366
+ frontLabel: "Frente do RG",
6367
+ backLabel: "Verso do RG"
6368
+ },
6369
+ CNH: {
6370
+ name: "CNH / Carteira de Motorista",
6371
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="6" width="18" height="12" rx="2"/><circle cx="8" cy="12" r="2"/><path d="M13 10h5M13 14h3"/></svg>',
6372
+ hasBack: true,
6373
+ frontLabel: "Frente da CNH",
6374
+ backLabel: "Verso da CNH"
6375
+ },
6376
+ CPF: {
6377
+ name: "CPF",
6378
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>',
6379
+ hasBack: false,
6380
+ frontLabel: "Cartão CPF"
6381
+ }
6382
+ };
6383
+ class DocumentCaptureModal {
6384
+ constructor(options) {
6385
+ __publicField(this, "overlay", null);
6386
+ __publicField(this, "video", null);
6387
+ __publicField(this, "canvas", null);
6388
+ __publicField(this, "stream", null);
6389
+ __publicField(this, "resolvePromise", null);
6390
+ __publicField(this, "options");
6391
+ __publicField(this, "selectedDocument", null);
6392
+ __publicField(this, "currentStep", "select");
6393
+ __publicField(this, "frontBlob", null);
6394
+ __publicField(this, "backBlob", null);
6395
+ this.options = {
6396
+ title: (options == null ? void 0 : options.title) ?? "Validação de Identidade",
6397
+ subtitle: (options == null ? void 0 : options.subtitle) ?? "Selecione o tipo de documento",
6398
+ useBackCamera: (options == null ? void 0 : options.useBackCamera) ?? true,
6399
+ onCancel: options == null ? void 0 : options.onCancel,
6400
+ preSelectedDocument: options == null ? void 0 : options.preSelectedDocument
6401
+ };
6402
+ }
6403
+ async open() {
6404
+ return new Promise((resolve) => {
6405
+ this.resolvePromise = resolve;
6406
+ if (this.options.preSelectedDocument) {
6407
+ this.selectedDocument = this.options.preSelectedDocument;
6408
+ this.currentStep = "front";
6409
+ }
6410
+ this.render();
6411
+ });
6412
+ }
6413
+ render() {
6414
+ var _a;
6415
+ if (!this.overlay) {
6416
+ this.createOverlay();
6417
+ }
6418
+ const content = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-content");
6419
+ if (!content) return;
6420
+ switch (this.currentStep) {
6421
+ case "select":
6422
+ content.innerHTML = this.renderSelectStep();
6423
+ this.bindSelectEvents();
6424
+ break;
6425
+ case "front":
6426
+ case "back":
6427
+ content.innerHTML = this.renderCaptureStep();
6428
+ this.bindCaptureEvents();
6429
+ this.startCamera();
6430
+ break;
6431
+ case "preview-front":
6432
+ case "preview-back":
6433
+ content.innerHTML = this.renderPreviewStep();
6434
+ this.bindPreviewEvents();
6435
+ break;
6436
+ }
6437
+ }
6438
+ createOverlay() {
6439
+ const overlay = document.createElement("div");
6440
+ overlay.className = "neoface-doc-overlay";
6441
+ overlay.innerHTML = `
6442
+ <div class="neoface-doc-modal">
6443
+ <div class="neoface-doc-content"></div>
6444
+ </div>
6445
+ `;
6446
+ if (!document.getElementById("neoface-doc-styles-v2")) {
6447
+ const style = document.createElement("style");
6448
+ style.id = "neoface-doc-styles-v2";
6449
+ style.textContent = this.getStyles();
6450
+ document.head.appendChild(style);
6451
+ }
6452
+ document.body.appendChild(overlay);
6453
+ this.overlay = overlay;
6454
+ }
6455
+ renderSelectStep() {
6456
+ return `
6457
+ <div class="neoface-doc-header">
6458
+ <div class="neoface-doc-header-left">
6459
+ <h2 class="neoface-doc-title">${this.options.title}</h2>
6460
+ <p class="neoface-doc-subtitle">${this.options.subtitle}</p>
6461
+ </div>
6462
+ <button class="neoface-doc-close-btn" aria-label="Fechar">
6463
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6464
+ <path d="M18 6L6 18M6 6l12 12"/>
6465
+ </svg>
6466
+ </button>
6467
+ </div>
6468
+
6469
+ <div class="neoface-doc-body">
6470
+ <div class="neoface-doc-select-grid">
6471
+ ${Object.entries(DOCUMENT_INFO).map(([type, info]) => `
6472
+ <button class="neoface-doc-select-option" data-type="${type}">
6473
+ <span class="neoface-doc-select-icon">${info.icon}</span>
6474
+ <span class="neoface-doc-select-name">${info.name}</span>
6475
+ <svg class="neoface-doc-select-arrow" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
6476
+ </button>
6477
+ `).join("")}
6478
+ </div>
6479
+
6480
+ <div class="neoface-doc-tips">
6481
+ <div class="neoface-doc-tip">
6482
+ <span>Garanta boa iluminação e que todos os dados estejam legíveis</span>
6483
+ </div>
6484
+ </div>
6485
+ </div>
6486
+
6487
+ ${this.renderBranding()}
6488
+ `;
6489
+ }
6490
+ renderCaptureStep() {
6491
+ const docInfo = this.selectedDocument ? DOCUMENT_INFO[this.selectedDocument] : null;
6492
+ if (!docInfo) return "";
6493
+ const isBack = this.currentStep === "back";
6494
+ const label = isBack ? docInfo.backLabel : docInfo.frontLabel;
6495
+ const stepNumber = isBack ? 2 : 1;
6496
+ const totalSteps = docInfo.hasBack ? 2 : 1;
6497
+ return `
6498
+ <div class="neoface-doc-header">
6499
+ <div class="neoface-doc-header-left">
6500
+ <div class="neoface-doc-step-indicator">
6501
+ <span class="neoface-doc-step-badge">${stepNumber}/${totalSteps}</span>
6502
+ <span class="neoface-doc-step-label">${label}</span>
6503
+ </div>
6504
+ </div>
6505
+ <button class="neoface-doc-close-btn" aria-label="Fechar">
6506
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6507
+ <path d="M18 6L6 18M6 6l12 12"/>
6508
+ </svg>
6509
+ </button>
6510
+ </div>
6511
+
6512
+ <div class="neoface-doc-body">
6513
+ <div class="neoface-doc-camera-container">
6514
+ <video class="neoface-doc-video" autoplay playsinline></video>
6515
+ <canvas class="neoface-doc-canvas"></canvas>
6516
+
6517
+ <div class="neoface-doc-guide">
6518
+ <div class="neoface-doc-frame">
6519
+ <div class="neoface-doc-corner neoface-doc-corner-tl"></div>
6520
+ <div class="neoface-doc-corner neoface-doc-corner-tr"></div>
6521
+ <div class="neoface-doc-corner neoface-doc-corner-bl"></div>
6522
+ <div class="neoface-doc-corner neoface-doc-corner-br"></div>
6523
+ <div class="neoface-doc-frame-label">${label}</div>
6524
+ </div>
6525
+ </div>
6526
+
6527
+ <div class="neoface-doc-loading" style="display:none">
6528
+ <div class="neoface-doc-spinner"></div>
6529
+ </div>
6530
+ </div>
6531
+
6532
+ <div class="neoface-doc-instructions">
6533
+ <div class="neoface-doc-instruction">
6534
+ <svg class="neoface-doc-instruction-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
6535
+ <span>Posicione o documento dentro do quadro</span>
6536
+ </div>
6537
+ <div class="neoface-doc-instruction">
6538
+ <svg class="neoface-doc-instruction-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
6539
+ <span>Evite reflexos e sombras</span>
6540
+ </div>
6541
+ </div>
6542
+ </div>
6543
+
6544
+ <div class="neoface-doc-footer">
6545
+ <button class="neoface-doc-btn neoface-doc-btn-secondary neoface-doc-back-btn">
6546
+ ← Voltar
6547
+ </button>
6548
+ <input type="file" accept="image/*" class="neoface-doc-file-input" style="display:none" />
6549
+ <button class="neoface-doc-btn neoface-doc-btn-secondary neoface-doc-upload-btn">
6550
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6551
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
6552
+ <polyline points="17,8 12,3 7,8"/>
6553
+ <line x1="12" y1="3" x2="12" y2="15"/>
6554
+ </svg>
6555
+ Anexar
6556
+ </button>
6557
+ <button class="neoface-doc-btn neoface-doc-btn-primary neoface-doc-capture-btn">
6558
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6559
+ <circle cx="12" cy="12" r="10"/>
6560
+ <circle cx="12" cy="12" r="4"/>
6561
+ </svg>
6562
+ Capturar
6563
+ </button>
6564
+ </div>
6565
+
6566
+ ${this.renderBranding()}
6567
+ `;
6568
+ }
6569
+ renderPreviewStep() {
6570
+ const docInfo = this.selectedDocument ? DOCUMENT_INFO[this.selectedDocument] : null;
6571
+ if (!docInfo) return "";
6572
+ const isPreviewFront = this.currentStep === "preview-front";
6573
+ const label = isPreviewFront ? docInfo.frontLabel : docInfo.backLabel;
6574
+ const isLastStep = !docInfo.hasBack || this.currentStep === "preview-back";
6575
+ const stepNumber = isPreviewFront ? 1 : 2;
6576
+ const totalSteps = docInfo.hasBack ? 2 : 1;
6577
+ return `
6578
+ <div class="neoface-doc-header">
6579
+ <div class="neoface-doc-header-left">
6580
+ <div class="neoface-doc-step-indicator">
6581
+ <span class="neoface-doc-step-badge">${stepNumber}/${totalSteps}</span>
6582
+ <span class="neoface-doc-step-label">Confirmar ${label}</span>
6583
+ </div>
6584
+ </div>
6585
+ <button class="neoface-doc-close-btn" aria-label="Fechar">
6586
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6587
+ <path d="M18 6L6 18M6 6l12 12"/>
6588
+ </svg>
6589
+ </button>
6590
+ </div>
6591
+
6592
+ <div class="neoface-doc-body">
6593
+ <div class="neoface-doc-preview-single">
6594
+ <span class="neoface-doc-preview-label">${label}</span>
6595
+ <img class="neoface-doc-preview-img neoface-doc-preview-current" />
6596
+ </div>
6597
+
6598
+ <div class="neoface-doc-preview-hint">
6599
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
6600
+ <span>Verifique se todos os dados estão legíveis</span>
6601
+ </div>
6602
+ </div>
6603
+
6604
+ <div class="neoface-doc-footer">
6605
+ <button class="neoface-doc-btn neoface-doc-btn-secondary neoface-doc-retake-btn">
6606
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6607
+ <path d="M1 4v6h6M23 20v-6h-6"/>
6608
+ <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/>
6609
+ </svg>
6610
+ Refazer
6611
+ </button>
6612
+ <button class="neoface-doc-btn neoface-doc-btn-success neoface-doc-confirm-btn">
6613
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6614
+ <path d="M20 6L9 17l-5-5"/>
6615
+ </svg>
6616
+ ${isLastStep ? "Confirmar e Enviar" : "Próximo"}
6617
+ </button>
6618
+ </div>
6619
+
6620
+ ${this.renderBranding()}
6621
+ `;
6622
+ }
6623
+ renderBranding() {
6624
+ return `
6625
+ <div class="neoface-doc-branding">
6626
+ <img src="/logo-icone-no-background.png"
6627
+ alt="NeoFaceId" class="neoface-doc-brand-icon"
6628
+ onerror="this.style.display='none'" />
6629
+ <span class="neoface-doc-brand-name-text">NeoFaceId</span>
6630
+ <span class="neoface-doc-brand-text">by</span>
6631
+ <span class="neoface-doc-brand-name">OCTA</span>
6632
+ </div>
6633
+ `;
6634
+ }
6635
+ bindSelectEvents() {
6636
+ var _a;
6637
+ const options = (_a = this.overlay) == null ? void 0 : _a.querySelectorAll(".neoface-doc-select-option");
6638
+ options == null ? void 0 : options.forEach((option) => {
6639
+ option.addEventListener("click", () => {
6640
+ const type = option.getAttribute("data-type");
6641
+ this.selectedDocument = type;
6642
+ this.currentStep = "front";
6643
+ this.render();
6644
+ });
6645
+ });
6646
+ this.bindCloseEvent();
6647
+ }
6648
+ bindCaptureEvents() {
6649
+ var _a, _b, _c, _d;
6650
+ const captureBtn = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-capture-btn");
6651
+ const backBtn = (_b = this.overlay) == null ? void 0 : _b.querySelector(".neoface-doc-back-btn");
6652
+ const uploadBtn = (_c = this.overlay) == null ? void 0 : _c.querySelector(".neoface-doc-upload-btn");
6653
+ const fileInput = (_d = this.overlay) == null ? void 0 : _d.querySelector(".neoface-doc-file-input");
6654
+ captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => this.handleCapture());
6655
+ backBtn == null ? void 0 : backBtn.addEventListener("click", () => this.handleBack());
6656
+ uploadBtn == null ? void 0 : uploadBtn.addEventListener("click", () => fileInput == null ? void 0 : fileInput.click());
6657
+ fileInput == null ? void 0 : fileInput.addEventListener("change", (e) => this.handleFileUpload(e));
6658
+ this.bindCloseEvent();
6659
+ }
6660
+ async handleFileUpload(event) {
6661
+ var _a, _b;
6662
+ const input = event.target;
6663
+ const file = (_a = input.files) == null ? void 0 : _a[0];
6664
+ if (!file) return;
6665
+ const loadingEl = (_b = this.overlay) == null ? void 0 : _b.querySelector(".neoface-doc-loading");
6666
+ if (loadingEl) loadingEl.style.display = "flex";
6667
+ try {
6668
+ const compressedBlob = await this.compressUploadedImage(file);
6669
+ if (this.currentStep === "front") {
6670
+ this.frontBlob = compressedBlob;
6671
+ this.currentStep = "preview-front";
6672
+ } else if (this.currentStep === "back") {
6673
+ this.backBlob = compressedBlob;
6674
+ this.currentStep = "preview-back";
6675
+ }
6676
+ this.stopCamera();
6677
+ this.render();
6678
+ } catch (error) {
6679
+ console.error("[DocumentCapture] Error compressing uploaded image:", error);
6680
+ const blob = new Blob([file], { type: file.type });
6681
+ if (this.currentStep === "front") {
6682
+ this.frontBlob = blob;
6683
+ this.currentStep = "preview-front";
6684
+ } else if (this.currentStep === "back") {
6685
+ this.backBlob = blob;
6686
+ this.currentStep = "preview-back";
6687
+ }
6688
+ this.stopCamera();
6689
+ this.render();
6690
+ }
6691
+ }
6692
+ compressUploadedImage(file) {
6693
+ return new Promise((resolve, reject) => {
6694
+ const img = new Image();
6695
+ img.onload = () => {
6696
+ const MAX_SIZE = 1280;
6697
+ let width = img.width;
6698
+ let height = img.height;
6699
+ if (width > height && width > MAX_SIZE) {
6700
+ height = height * MAX_SIZE / width;
6701
+ width = MAX_SIZE;
6702
+ } else if (height > MAX_SIZE) {
6703
+ width = width * MAX_SIZE / height;
6704
+ height = MAX_SIZE;
6705
+ }
6706
+ const canvas = document.createElement("canvas");
6707
+ canvas.width = width;
6708
+ canvas.height = height;
6709
+ const ctx = canvas.getContext("2d");
6710
+ if (!ctx) {
6711
+ reject(new Error("Failed to get canvas context"));
6712
+ return;
6713
+ }
6714
+ ctx.drawImage(img, 0, 0, width, height);
6715
+ canvas.toBlob(
6716
+ (blob) => {
6717
+ if (blob) {
6718
+ console.log(`[DocumentCapture] Compressed image from ${(file.size / 1024).toFixed(0)}KB to ${(blob.size / 1024).toFixed(0)}KB`);
6719
+ resolve(blob);
6720
+ } else {
6721
+ reject(new Error("Failed to compress image"));
6722
+ }
6723
+ },
6724
+ "image/jpeg",
6725
+ 0.75
6726
+ // 75% quality - keeps file size manageable for server limits
6727
+ );
6728
+ };
6729
+ img.onerror = () => reject(new Error("Failed to load image"));
6730
+ img.src = URL.createObjectURL(file);
6731
+ });
6732
+ }
6733
+ bindPreviewEvents() {
6734
+ var _a, _b, _c;
6735
+ const confirmBtn = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-confirm-btn");
6736
+ const retakeBtn = (_b = this.overlay) == null ? void 0 : _b.querySelector(".neoface-doc-retake-btn");
6737
+ confirmBtn == null ? void 0 : confirmBtn.addEventListener("click", () => this.handleConfirm());
6738
+ retakeBtn == null ? void 0 : retakeBtn.addEventListener("click", () => this.handleRetake());
6739
+ const previewImg = (_c = this.overlay) == null ? void 0 : _c.querySelector(".neoface-doc-preview-current");
6740
+ if (previewImg) {
6741
+ const blob = this.currentStep === "preview-front" ? this.frontBlob : this.backBlob;
6742
+ if (blob) previewImg.src = URL.createObjectURL(blob);
6743
+ }
6744
+ this.bindCloseEvent();
6745
+ }
6746
+ bindCloseEvent() {
6747
+ var _a;
6748
+ const closeBtn = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-close-btn");
6749
+ closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => this.handleCancel());
6750
+ }
6751
+ async startCamera() {
6752
+ var _a, _b, _c;
6753
+ try {
6754
+ const constraints = {
6755
+ video: {
6756
+ facingMode: this.options.useBackCamera ? "environment" : "user",
6757
+ width: { ideal: 1920 },
6758
+ height: { ideal: 1080 }
6759
+ },
6760
+ audio: false
6761
+ };
6762
+ this.stream = await navigator.mediaDevices.getUserMedia(constraints);
6763
+ this.video = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-video");
6764
+ this.canvas = (_b = this.overlay) == null ? void 0 : _b.querySelector(".neoface-doc-canvas");
6765
+ if (this.video) {
6766
+ this.video.srcObject = this.stream;
6767
+ await this.video.play();
6768
+ }
6769
+ } catch (error) {
6770
+ console.error("[DocumentCapture] Camera error:", error);
6771
+ if (this.options.useBackCamera) {
6772
+ try {
6773
+ this.stream = await navigator.mediaDevices.getUserMedia({
6774
+ video: { facingMode: "user" },
6775
+ audio: false
6776
+ });
6777
+ if (this.video) {
6778
+ this.video.srcObject = this.stream;
6779
+ await this.video.play();
6780
+ }
6781
+ } catch (fallbackError) {
6782
+ (_c = this.resolvePromise) == null ? void 0 : _c.call(this, {
6783
+ success: false,
6784
+ frontImage: null,
6785
+ backImage: null,
6786
+ error: "Não foi possível acessar a câmera"
6787
+ });
6788
+ this.close();
6789
+ }
6790
+ }
6791
+ }
6792
+ }
6793
+ stopCamera() {
6794
+ if (this.stream) {
6795
+ this.stream.getTracks().forEach((track) => track.stop());
6796
+ this.stream = null;
6797
+ }
6798
+ }
6799
+ async handleCapture() {
6800
+ var _a;
6801
+ if (!this.video || !this.canvas) return;
6802
+ const loading = (_a = this.overlay) == null ? void 0 : _a.querySelector(".neoface-doc-loading");
6803
+ if (loading) loading.style.display = "flex";
6804
+ const ctx = this.canvas.getContext("2d");
6805
+ if (!ctx) return;
6806
+ const videoWidth = this.video.videoWidth;
6807
+ const videoHeight = this.video.videoHeight;
6808
+ const frameWidthPercent = 0.75;
6809
+ const frameHeightPercent = 0.65;
6810
+ const cropWidth = Math.floor(videoWidth * frameWidthPercent);
6811
+ const cropHeight = Math.floor(videoHeight * frameHeightPercent);
6812
+ const cropX = Math.floor((videoWidth - cropWidth) / 2);
6813
+ const cropY = Math.floor((videoHeight - cropHeight) / 2);
6814
+ this.canvas.width = cropWidth;
6815
+ this.canvas.height = cropHeight;
6816
+ ctx.drawImage(
6817
+ this.video,
6818
+ cropX,
6819
+ cropY,
6820
+ cropWidth,
6821
+ cropHeight,
6822
+ // Área de origem (crop)
6823
+ 0,
6824
+ 0,
6825
+ cropWidth,
6826
+ cropHeight
6827
+ // Área de destino (canvas inteiro)
6828
+ );
6829
+ try {
6830
+ const blob = await new Promise((resolve, reject) => {
6831
+ this.canvas.toBlob(
6832
+ (b) => b ? resolve(b) : reject(new Error("Capture failed")),
6833
+ "image/jpeg",
6834
+ 0.92
6835
+ );
6836
+ });
6837
+ if (this.currentStep === "front") {
6838
+ this.frontBlob = blob;
6839
+ this.stopCamera();
6840
+ this.currentStep = "preview-front";
6841
+ this.render();
6842
+ } else if (this.currentStep === "back") {
6843
+ this.backBlob = blob;
6844
+ this.stopCamera();
6845
+ this.currentStep = "preview-back";
6846
+ this.render();
6847
+ }
6848
+ } catch (error) {
6849
+ console.error("[DocumentCapture] Capture error:", error);
6850
+ } finally {
6851
+ if (loading) loading.style.display = "none";
6852
+ }
6853
+ }
6854
+ handleBack() {
6855
+ this.stopCamera();
6856
+ if (this.currentStep === "back") {
6857
+ this.currentStep = "front";
6858
+ this.frontBlob = null;
6859
+ } else if (this.currentStep === "front") {
6860
+ this.currentStep = "select";
6861
+ this.selectedDocument = null;
6862
+ }
6863
+ this.render();
6864
+ }
6865
+ handleRetake() {
6866
+ if (this.currentStep === "preview-front") {
6867
+ this.frontBlob = null;
6868
+ this.currentStep = "front";
6869
+ } else if (this.currentStep === "preview-back") {
6870
+ this.backBlob = null;
6871
+ this.currentStep = "back";
6872
+ }
6873
+ this.render();
6874
+ }
6875
+ handleConfirm() {
6876
+ var _a, _b;
6877
+ const docInfo = this.selectedDocument ? DOCUMENT_INFO[this.selectedDocument] : null;
6878
+ if (this.currentStep === "preview-front") {
6879
+ if (docInfo == null ? void 0 : docInfo.hasBack) {
6880
+ this.currentStep = "back";
6881
+ this.render();
6882
+ } else {
6883
+ (_a = this.resolvePromise) == null ? void 0 : _a.call(this, {
6884
+ success: true,
6885
+ documentType: this.selectedDocument || void 0,
6886
+ frontImage: this.frontBlob,
6887
+ backImage: null
6888
+ });
6889
+ this.close();
6890
+ }
6891
+ } else if (this.currentStep === "preview-back") {
6892
+ (_b = this.resolvePromise) == null ? void 0 : _b.call(this, {
6893
+ success: true,
6894
+ documentType: this.selectedDocument || void 0,
6895
+ frontImage: this.frontBlob,
6896
+ backImage: this.backBlob
6897
+ });
6898
+ this.close();
6899
+ }
6900
+ }
6901
+ handleCancel() {
6902
+ var _a, _b, _c;
6903
+ (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
6904
+ (_c = this.resolvePromise) == null ? void 0 : _c.call(this, {
6905
+ success: false,
6906
+ frontImage: null,
6907
+ backImage: null,
6908
+ error: "cancelled"
6909
+ });
6910
+ this.close();
6911
+ }
6912
+ close() {
6913
+ this.stopCamera();
6914
+ if (this.overlay) {
6915
+ this.overlay.remove();
6916
+ this.overlay = null;
6917
+ }
6918
+ }
6919
+ getStyles() {
6920
+ return `
6921
+ .neoface-doc-overlay {
6922
+ position: fixed;
6923
+ inset: 0;
6924
+ background: rgba(0, 0, 0, 0.85);
6925
+ backdrop-filter: blur(8px);
6926
+ display: flex;
6927
+ align-items: center;
6928
+ justify-content: center;
6929
+ z-index: 10000;
6930
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
6931
+ padding: 16px;
6932
+ }
6933
+
6934
+ .neoface-doc-modal {
6935
+ background: linear-gradient(145deg, #1a1a2e 0%, #16213e 100%);
6936
+ border-radius: 24px;
6937
+ width: 100%;
6938
+ max-width: 520px;
6939
+ box-shadow: 0 25px 80px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255,255,255,0.1);
6940
+ overflow: hidden;
6941
+ animation: neoface-doc-slideIn 0.3s ease;
6942
+ }
6943
+
6944
+ @keyframes neoface-doc-slideIn {
6945
+ from { transform: translateY(20px) scale(0.98); opacity: 0; }
6946
+ to { transform: translateY(0) scale(1); opacity: 1; }
6947
+ }
6948
+
6949
+ .neoface-doc-header {
6950
+ padding: 20px 24px;
6951
+ display: flex;
6952
+ align-items: flex-start;
6953
+ justify-content: space-between;
6954
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
6955
+ }
6956
+
6957
+ .neoface-doc-title {
6958
+ margin: 0 0 4px 0;
6959
+ font-size: 20px;
6960
+ font-weight: 600;
6961
+ color: #fff;
6962
+ }
6963
+
6964
+ .neoface-doc-subtitle {
6965
+ margin: 0;
6966
+ font-size: 14px;
6967
+ color: rgba(255, 255, 255, 0.6);
6968
+ }
6969
+
6970
+ .neoface-doc-step-indicator {
6971
+ display: flex;
6972
+ align-items: center;
6973
+ gap: 12px;
6974
+ }
6975
+
6976
+ .neoface-doc-step-badge {
6977
+ background: linear-gradient(135deg, #00c8ff, #0099cc);
6978
+ color: #fff;
6979
+ font-size: 12px;
6980
+ font-weight: 600;
6981
+ padding: 4px 10px;
6982
+ border-radius: 20px;
6983
+ }
6984
+
6985
+ .neoface-doc-step-label {
6986
+ font-size: 16px;
6987
+ font-weight: 500;
6988
+ color: #fff;
6989
+ }
6990
+
6991
+ .neoface-doc-close-btn {
6992
+ background: transparent;
6993
+ border: none;
6994
+ padding: 4px;
6995
+ cursor: pointer;
6996
+ color: rgba(255, 255, 255, 0.5);
6997
+ transition: all 0.2s;
6998
+ }
6999
+
7000
+ .neoface-doc-close-btn:hover {
7001
+ color: #fff;
7002
+ }
7003
+
7004
+ .neoface-doc-body {
7005
+ padding: 20px 24px;
7006
+ }
7007
+
7008
+ /* Select Step */
7009
+ .neoface-doc-select-grid {
7010
+ display: flex;
7011
+ flex-direction: column;
7012
+ gap: 12px;
7013
+ }
7014
+
7015
+ .neoface-doc-select-option {
7016
+ display: flex;
7017
+ align-items: center;
7018
+ gap: 16px;
7019
+ padding: 16px 20px;
7020
+ background: rgba(255, 255, 255, 0.05);
7021
+ border: 2px solid rgba(255, 255, 255, 0.1);
7022
+ border-radius: 14px;
7023
+ cursor: pointer;
7024
+ transition: all 0.2s;
7025
+ text-align: left;
7026
+ }
7027
+
7028
+ .neoface-doc-select-option:hover {
7029
+ background: rgba(0, 200, 255, 0.1);
7030
+ border-color: rgba(0, 200, 255, 0.4);
7031
+ transform: translateX(4px);
7032
+ }
7033
+
7034
+ .neoface-doc-select-icon {
7035
+ display: flex;
7036
+ align-items: center;
7037
+ justify-content: center;
7038
+ width: 44px;
7039
+ height: 44px;
7040
+ background: rgba(0, 200, 255, 0.15);
7041
+ border-radius: 12px;
7042
+ color: #00c8ff;
7043
+ }
7044
+
7045
+ .neoface-doc-select-icon svg {
7046
+ width: 24px;
7047
+ height: 24px;
7048
+ }
7049
+
7050
+ .neoface-doc-select-name {
7051
+ flex: 1;
7052
+ font-size: 15px;
7053
+ font-weight: 500;
7054
+ color: #fff;
7055
+ }
7056
+
7057
+ .neoface-doc-select-arrow {
7058
+ color: rgba(255, 255, 255, 0.4);
7059
+ transition: transform 0.2s;
7060
+ }
7061
+
7062
+ .neoface-doc-select-option:hover .neoface-doc-select-arrow {
7063
+ color: #00c8ff;
7064
+ transform: translateX(4px);
7065
+ }
7066
+
7067
+ .neoface-doc-tips {
7068
+ margin-top: 20px;
7069
+ }
7070
+
7071
+ .neoface-doc-tip {
7072
+ display: flex;
7073
+ align-items: center;
7074
+ justify-content: center;
7075
+ padding: 14px 16px;
7076
+ background: rgba(0, 200, 255, 0.08);
7077
+ border: 1px solid rgba(0, 200, 255, 0.15);
7078
+ border-radius: 10px;
7079
+ color: rgba(255, 255, 255, 0.8);
7080
+ font-size: 13px;
7081
+ line-height: 1;
7082
+ text-align: center;
7083
+ }
7084
+
7085
+ .neoface-doc-tip span {
7086
+ transform: translateY(-2px);
7087
+ }
7088
+
7089
+ .neoface-doc-tip-icon {
7090
+ color: #00c8ff;
7091
+ flex-shrink: 0;
7092
+ }
7093
+
7094
+ /* Camera */
7095
+ .neoface-doc-camera-container {
7096
+ position: relative;
7097
+ background: #000;
7098
+ border-radius: 16px;
7099
+ overflow: hidden;
7100
+ aspect-ratio: 4/3;
7101
+ }
7102
+
7103
+ .neoface-doc-video {
7104
+ width: 100%;
7105
+ height: 100%;
7106
+ object-fit: cover;
7107
+ }
7108
+
7109
+ .neoface-doc-canvas {
7110
+ display: none;
7111
+ }
7112
+
7113
+ .neoface-doc-guide {
7114
+ position: absolute;
7115
+ inset: 0;
7116
+ display: flex;
7117
+ align-items: center;
7118
+ justify-content: center;
7119
+ pointer-events: none;
7120
+ }
7121
+
7122
+ .neoface-doc-frame {
7123
+ width: 75%;
7124
+ height: 65%;
7125
+ position: relative;
7126
+ border: 2px dashed rgba(0, 200, 255, 0.5);
7127
+ border-radius: 12px;
7128
+ background: rgba(0, 200, 255, 0.03);
7129
+ }
7130
+
7131
+ .neoface-doc-corner {
7132
+ position: absolute;
7133
+ width: 28px;
7134
+ height: 28px;
7135
+ border-color: #00c8ff;
7136
+ border-style: solid;
7137
+ }
7138
+
7139
+ .neoface-doc-corner-tl { top: -2px; left: -2px; border-width: 4px 0 0 4px; border-radius: 10px 0 0 0; }
7140
+ .neoface-doc-corner-tr { top: -2px; right: -2px; border-width: 4px 4px 0 0; border-radius: 0 10px 0 0; }
7141
+ .neoface-doc-corner-bl { bottom: -2px; left: -2px; border-width: 0 0 4px 4px; border-radius: 0 0 0 10px; }
7142
+ .neoface-doc-corner-br { bottom: -2px; right: -2px; border-width: 0 4px 4px 0; border-radius: 0 0 10px 0; }
7143
+
7144
+ .neoface-doc-frame-label {
7145
+ position: absolute;
7146
+ bottom: -30px;
7147
+ left: 50%;
7148
+ transform: translateX(-50%);
7149
+ background: rgba(0, 0, 0, 0.7);
7150
+ color: #00c8ff;
7151
+ font-size: 12px;
7152
+ font-weight: 500;
7153
+ padding: 6px 14px;
7154
+ border-radius: 20px;
7155
+ white-space: nowrap;
7156
+ }
7157
+
7158
+ .neoface-doc-loading {
7159
+ position: absolute;
7160
+ inset: 0;
7161
+ background: rgba(0, 0, 0, 0.7);
7162
+ display: none;
7163
+ align-items: center;
7164
+ justify-content: center;
7165
+ }
7166
+
7167
+ .neoface-doc-spinner {
7168
+ width: 48px;
7169
+ height: 48px;
7170
+ border: 4px solid rgba(255, 255, 255, 0.2);
7171
+ border-top-color: #00c8ff;
7172
+ border-radius: 50%;
7173
+ animation: neoface-doc-spin 0.8s linear infinite;
7174
+ }
7175
+
7176
+ @keyframes neoface-doc-spin {
7177
+ to { transform: rotate(360deg); }
7178
+ }
7179
+
7180
+ .neoface-doc-instructions {
7181
+ display: flex;
7182
+ flex-direction: column;
7183
+ gap: 8px;
7184
+ margin-top: 16px;
7185
+ }
7186
+
7187
+ .neoface-doc-instruction {
7188
+ display: flex;
7189
+ align-items: center;
7190
+ gap: 10px;
7191
+ padding: 10px 14px;
7192
+ background: rgba(255, 255, 255, 0.04);
7193
+ border-radius: 10px;
7194
+ font-size: 13px;
7195
+ color: rgba(255, 255, 255, 0.75);
7196
+ }
7197
+
7198
+ .neoface-doc-instruction-icon {
7199
+ color: rgba(255, 255, 255, 0.5);
7200
+ flex-shrink: 0;
7201
+ }
7202
+
7203
+ /* Preview */
7204
+ .neoface-doc-preview-single {
7205
+ display: flex;
7206
+ flex-direction: column;
7207
+ align-items: center;
7208
+ gap: 12px;
7209
+ }
7210
+
7211
+ .neoface-doc-preview-label {
7212
+ font-size: 12px;
7213
+ font-weight: 500;
7214
+ color: rgba(255, 255, 255, 0.6);
7215
+ text-transform: uppercase;
7216
+ letter-spacing: 0.5px;
7217
+ text-align: center;
7218
+ }
7219
+
7220
+ .neoface-doc-preview-img {
7221
+ width: 75%;
7222
+ height: auto;
7223
+ border-radius: 12px;
7224
+ border: 2px solid rgba(0, 200, 255, 0.3);
7225
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
7226
+ }
7227
+
7228
+ .neoface-doc-preview-hint {
7229
+ display: flex;
7230
+ align-items: center;
7231
+ gap: 10px;
7232
+ padding: 12px 16px;
7233
+ background: rgba(0, 200, 255, 0.08);
7234
+ border: 1px solid rgba(0, 200, 255, 0.15);
7235
+ border-radius: 10px;
7236
+ font-size: 13px;
7237
+ color: rgba(255, 255, 255, 0.8);
7238
+ margin-top: 8px;
7239
+ }
7240
+
7241
+ .neoface-doc-preview-hint svg {
7242
+ color: #00c8ff;
7243
+ flex-shrink: 0;
7244
+ }
7245
+
7246
+ /* Footer */
7247
+ .neoface-doc-footer {
7248
+ padding: 16px 24px;
7249
+ display: flex;
7250
+ gap: 12px;
7251
+ justify-content: flex-end;
7252
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
7253
+ }
7254
+
7255
+ .neoface-doc-btn {
7256
+ display: flex;
7257
+ align-items: center;
7258
+ gap: 8px;
7259
+ padding: 12px 20px;
7260
+ border-radius: 12px;
7261
+ font-size: 14px;
7262
+ font-weight: 500;
7263
+ cursor: pointer;
7264
+ transition: all 0.2s;
7265
+ border: none;
7266
+ }
7267
+
7268
+ .neoface-doc-btn-primary {
7269
+ background: linear-gradient(135deg, #00c8ff 0%, #0099cc 100%);
7270
+ color: #fff;
7271
+ }
7272
+
7273
+ .neoface-doc-btn-primary:hover {
7274
+ transform: translateY(-2px);
7275
+ box-shadow: 0 6px 20px rgba(0, 200, 255, 0.4);
7276
+ }
7277
+
7278
+ .neoface-doc-btn-success {
7279
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
7280
+ color: #fff;
7281
+ }
7282
+
7283
+ .neoface-doc-btn-success:hover {
7284
+ transform: translateY(-2px);
7285
+ box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
7286
+ }
7287
+
7288
+ .neoface-doc-btn-secondary {
7289
+ background: rgba(255, 255, 255, 0.08);
7290
+ color: rgba(255, 255, 255, 0.8);
7291
+ }
7292
+
7293
+ .neoface-doc-btn-secondary:hover {
7294
+ background: rgba(255, 255, 255, 0.12);
7295
+ color: #fff;
7296
+ }
7297
+
7298
+ /* Branding */
7299
+ .neoface-doc-branding {
7300
+ display: flex;
7301
+ align-items: center;
7302
+ justify-content: center;
7303
+ gap: 8px;
7304
+ padding: 16px 24px;
7305
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
7306
+ }
7307
+
7308
+ .neoface-doc-brand-icon {
7309
+ height: 22px;
7310
+ width: auto;
7311
+ }
7312
+
7313
+ .neoface-doc-brand-name-text {
7314
+ font-size: 13px;
7315
+ font-weight: 600;
7316
+ color: rgba(255, 255, 255, 0.9);
7317
+ }
7318
+
7319
+ .neoface-doc-brand-text {
7320
+ font-size: 12px;
7321
+ color: rgba(255, 255, 255, 0.5);
7322
+ }
7323
+
7324
+ .neoface-doc-brand-name {
7325
+ font-size: 13px;
7326
+ font-weight: 700;
7327
+ color: #00c8ff;
7328
+ letter-spacing: 1px;
7329
+ }
7330
+
7331
+ /* Mobile */
7332
+ @media (max-width: 500px) {
7333
+ .neoface-doc-modal {
7334
+ border-radius: 20px;
7335
+ }
7336
+
7337
+ .neoface-doc-header,
7338
+ .neoface-doc-body,
7339
+ .neoface-doc-footer {
7340
+ padding: 16px 18px;
7341
+ }
7342
+
7343
+ .neoface-doc-preview-grid.has-back {
7344
+ grid-template-columns: 1fr;
7345
+ }
7346
+ }
7347
+ `;
7348
+ }
7349
+ }
7350
+ const startDocumentCapture = async (options) => {
7351
+ const modal = new DocumentCaptureModal(options);
7352
+ return modal.open();
7353
+ };
5924
7354
  function start(applicationToken, callbacks) {
5925
7355
  const container = document.createElement("div");
5926
7356
  container.id = "neoface-modal-container";
@@ -5931,7 +7361,7 @@ function start(applicationToken, callbacks) {
5931
7361
  }
5932
7362
  };
5933
7363
  validateToken(applicationToken).then(() => {
5934
- const modalElement = require$$0$1.createElement(FaceCaptureModal, {
7364
+ const modalElement = React.createElement(FaceCaptureModal, {
5935
7365
  accessToken: applicationToken,
5936
7366
  onClose: cleanup,
5937
7367
  onSuccess: (result) => {
@@ -5943,7 +7373,7 @@ function start(applicationToken, callbacks) {
5943
7373
  });
5944
7374
  }
5945
7375
  });
5946
- const root = createRoot(container);
7376
+ const root = clientExports.createRoot(container);
5947
7377
  root.render(modalElement);
5948
7378
  }).catch((error) => {
5949
7379
  cleanup();
@@ -5960,7 +7390,7 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
5960
7390
  }
5961
7391
  };
5962
7392
  validateToken(applicationToken).then(() => {
5963
- const modalElement = require$$0$1.createElement(BiometricRegistrationModal, {
7393
+ const modalElement = React.createElement(BiometricRegistrationModal, {
5964
7394
  personData,
5965
7395
  applicationToken,
5966
7396
  useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
@@ -5975,7 +7405,35 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
5975
7405
  callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
5976
7406
  }
5977
7407
  });
5978
- const root = createRoot(container);
7408
+ const root = clientExports.createRoot(container);
7409
+ root.render(modalElement);
7410
+ }).catch((error) => {
7411
+ cleanup();
7412
+ callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
7413
+ });
7414
+ }
7415
+ function startLivenessCapture(applicationToken, callbacks) {
7416
+ const container = document.createElement("div");
7417
+ container.id = "neoface-liveness-capture-container";
7418
+ document.body.appendChild(container);
7419
+ const cleanup = () => {
7420
+ if (document.body.contains(container)) {
7421
+ document.body.removeChild(container);
7422
+ }
7423
+ };
7424
+ validateToken(applicationToken).then(() => {
7425
+ const modalElement = React.createElement(BiometricRegistrationModal, {
7426
+ applicationToken,
7427
+ onClose: cleanup,
7428
+ onPhotosCaptured: (photos) => {
7429
+ callbacks.onSuccess(photos);
7430
+ },
7431
+ onError: (error) => {
7432
+ cleanup();
7433
+ callbacks.onError("LIVENESS_CAPTURE_ERROR", error);
7434
+ }
7435
+ });
7436
+ const root = clientExports.createRoot(container);
5979
7437
  root.render(modalElement);
5980
7438
  }).catch((error) => {
5981
7439
  cleanup();
@@ -5986,6 +7444,7 @@ export {
5986
7444
  BiometricCaptureModal$1 as BiometricCaptureModal,
5987
7445
  BiometricRegistrationModal,
5988
7446
  BiometricStatusOverlay,
7447
+ DocumentCaptureModal,
5989
7448
  EmailPasswordModal,
5990
7449
  ErrorType,
5991
7450
  FaceCaptureModal,
@@ -6004,6 +7463,8 @@ export {
6004
7463
  initializeBiometricDetection,
6005
7464
  isAdvancedDetectionAvailable,
6006
7465
  loginWithBiometric$1 as loginWithBiometric,
7466
+ loginWithEmail,
7467
+ preloadFaceDetectionModels,
6007
7468
  recognize,
6008
7469
  recognizeBiometric,
6009
7470
  recognizeByPurpose,
@@ -6014,8 +7475,10 @@ export {
6014
7475
  start,
6015
7476
  startAutoLogin,
6016
7477
  startBiometricRegistration,
7478
+ startDocumentCapture,
6017
7479
  startFaceLogin,
6018
7480
  startHandLogin,
7481
+ startLivenessCapture,
6019
7482
  startOnboarding,
6020
7483
  validateOnboardingToken,
6021
7484
  validateToken