@neofaceid/web-sdk 1.0.18 → 1.1.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.
@@ -988,6 +988,7 @@ class CameraStream {
988
988
  const useCamera = () => {
989
989
  const streamRef = useRef(null);
990
990
  const [isActive, setIsActive] = useState(false);
991
+ const isInitializingRef = useRef(false);
991
992
  const getOptimalConstraints = () => {
992
993
  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
993
994
  navigator.userAgent
@@ -1005,45 +1006,103 @@ const useCamera = () => {
1005
1006
  };
1006
1007
  const startCamera = useCallback(
1007
1008
  async (constraints) => {
1008
- if (isActive) {
1009
- throw new Error("Camera is already active");
1009
+ var _a;
1010
+ console.log("🎬 [useCamera] startCamera chamado", {
1011
+ hasExistingStream: !!streamRef.current,
1012
+ isInitializing: isInitializingRef.current
1013
+ });
1014
+ const existingStream = streamRef.current;
1015
+ if (existingStream) {
1016
+ console.log("⚠️ [useCamera] Câmera já está ativa, retornando stream existente");
1017
+ return existingStream.getStream();
1018
+ }
1019
+ if (isInitializingRef.current) {
1020
+ console.log("⏳ [useCamera] Inicialização já em andamento, aguardando...");
1021
+ await new Promise((resolve) => setTimeout(resolve, 100));
1022
+ const currentStream = streamRef.current;
1023
+ if (currentStream) {
1024
+ console.log("✅ [useCamera] Stream criado por outra chamada, usando-o");
1025
+ return currentStream.getStream();
1026
+ }
1010
1027
  }
1028
+ isInitializingRef.current = true;
1011
1029
  try {
1012
1030
  const mediaConstraints = constraints || getOptimalConstraints();
1031
+ console.log("📹 [useCamera] Solicitando acesso à câmera...", mediaConstraints);
1013
1032
  const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
1033
+ console.log("✅ [useCamera] Stream obtido com sucesso:", {
1034
+ tracks: stream.getTracks().length,
1035
+ videoTracks: stream.getVideoTracks().length,
1036
+ active: stream.active
1037
+ });
1014
1038
  const cameraStream = new CameraStream(stream);
1015
1039
  streamRef.current = cameraStream;
1016
1040
  setIsActive(true);
1041
+ isInitializingRef.current = false;
1042
+ console.log("✅ [useCamera] Câmera iniciada com sucesso");
1017
1043
  return stream;
1018
1044
  } catch (error) {
1019
- if (error instanceof Error && error.name === "NotAllowedError") {
1020
- throw new CameraPermissionDenied("Camera access was denied by the user");
1045
+ isInitializingRef.current = false;
1046
+ console.error(" [useCamera] Erro ao acessar câmera:", error);
1047
+ if (error instanceof Error) {
1048
+ console.log("📋 [useCamera] Detalhes do erro:", {
1049
+ name: error.name,
1050
+ message: error.message,
1051
+ stack: (_a = error.stack) == null ? void 0 : _a.substring(0, 200)
1052
+ });
1053
+ if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
1054
+ throw new CameraPermissionDenied(
1055
+ "Permissão para acessar a câmera foi negada pelo usuário"
1056
+ );
1057
+ }
1058
+ if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
1059
+ throw new Error("Nenhuma câmera foi encontrada no dispositivo");
1060
+ }
1061
+ if (error.name === "NotReadableError" || error.name === "TrackStartError") {
1062
+ throw new Error("A câmera já está sendo usada por outro aplicativo");
1063
+ }
1064
+ if (error.name === "OverconstrainedError" || error.name === "ConstraintNotSatisfiedError") {
1065
+ throw new Error("As configurações solicitadas não são suportadas pela câmera");
1066
+ }
1021
1067
  }
1022
- throw error;
1068
+ throw new Error(
1069
+ `Erro ao inicializar câmera: ${error instanceof Error ? error.message : String(error)}`
1070
+ );
1023
1071
  }
1024
1072
  },
1025
- [isActive]
1073
+ []
1074
+ // 🔥 Array vazio - função é criada apenas uma vez
1026
1075
  );
1027
1076
  const stopCamera = useCallback((stream) => {
1077
+ console.log("🛑 [useCamera] Parando câmera...");
1028
1078
  const cameraStream = streamRef.current;
1029
1079
  if (cameraStream && cameraStream.getStream() === stream) {
1030
1080
  cameraStream.stop();
1081
+ console.log("✅ [useCamera] Todas as tracks paradas");
1031
1082
  streamRef.current = null;
1032
1083
  setIsActive(false);
1084
+ isInitializingRef.current = false;
1085
+ console.log("✅ [useCamera] Câmera desligada com sucesso");
1033
1086
  } else {
1034
- console.warn("Attempted to stop a camera stream that was not started by this hook");
1087
+ console.warn(
1088
+ "⚠️ [useCamera] Tentativa de parar um stream que não foi iniciado por este hook"
1089
+ );
1035
1090
  }
1036
1091
  }, []);
1037
- useCallback(() => {
1092
+ const cleanup = useCallback(() => {
1093
+ console.log("🧹 [useCamera] Cleanup chamado");
1038
1094
  if (streamRef.current) {
1039
1095
  streamRef.current.stop();
1040
1096
  streamRef.current = null;
1041
1097
  setIsActive(false);
1098
+ isInitializingRef.current = false;
1099
+ console.log("✅ [useCamera] Cleanup concluído");
1042
1100
  }
1043
1101
  }, []);
1044
1102
  return {
1045
1103
  startCamera,
1046
1104
  stopCamera,
1105
+ cleanup,
1047
1106
  isActive
1048
1107
  };
1049
1108
  };
@@ -1051,6 +1110,14 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
1051
1110
  ErrorType2["NETWORK"] = "NetworkError";
1052
1111
  ErrorType2["INVALID_TOKEN"] = "InvalidTokenError";
1053
1112
  ErrorType2["RECOGNITION_FAILED"] = "RecognitionFailedError";
1113
+ ErrorType2["LOGIN_FAILED"] = "LoginFailedError";
1114
+ ErrorType2["VALIDATION_ERROR"] = "ValidationError";
1115
+ ErrorType2["API_ERROR"] = "ApiError";
1116
+ ErrorType2["PERSON_NOT_FOUND"] = "PersonNotFoundError";
1117
+ ErrorType2["INITIALIZATION_ERROR"] = "InitializationError";
1118
+ ErrorType2["NETWORK_ERROR"] = "NetworkError";
1119
+ ErrorType2["CAMERA_ERROR"] = "CameraError";
1120
+ ErrorType2["CAPTURE_ERROR"] = "CaptureError";
1054
1121
  return ErrorType2;
1055
1122
  })(ErrorType || {});
1056
1123
  class NeoFaceError extends Error {
@@ -1061,11 +1128,14 @@ class NeoFaceError extends Error {
1061
1128
  this.type = type;
1062
1129
  }
1063
1130
  }
1064
- const API_BASE_URL = "https://core.neofaceid.com/v1";
1131
+ const API_BASE_URL = "https://core.neofaceid.com.br";
1065
1132
  const REQUEST_TIMEOUT = 1e4;
1066
1133
  const ensureSecureContext = () => {
1067
1134
  if (!window.isSecureContext) {
1068
- throw new NeoFaceError("Secure context (HTTPS) is required for this operation", ErrorType.NETWORK);
1135
+ throw new NeoFaceError(
1136
+ "Secure context (HTTPS) is required for this operation",
1137
+ ErrorType.NETWORK
1138
+ );
1069
1139
  }
1070
1140
  };
1071
1141
  const createTimeoutController = () => {
@@ -1107,43 +1177,81 @@ const validateToken = async (applicationToken) => {
1107
1177
  ensureSecureContext();
1108
1178
  const controller = createTimeoutController();
1109
1179
  try {
1110
- const response = await fetch(
1111
- `${API_BASE_URL}/auth/consumer`,
1112
- {
1113
- method: "POST",
1114
- headers: {
1115
- "Content-Type": "application/json"
1116
- },
1117
- body: JSON.stringify({ token: applicationToken }),
1118
- signal: controller.signal
1180
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/auth/validate-token/`, {
1181
+ method: "POST",
1182
+ headers: {
1183
+ "Content-Type": "application/json",
1184
+ "X-App-Token": applicationToken
1185
+ },
1186
+ body: JSON.stringify({}),
1187
+ signal: controller.signal
1188
+ });
1189
+ return response.ok;
1190
+ } catch (error) {
1191
+ if (error instanceof Error) {
1192
+ if (error.name === "AbortError") {
1193
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK_ERROR);
1119
1194
  }
1120
- );
1195
+ throw new NeoFaceError(error.message, ErrorType.NETWORK_ERROR);
1196
+ }
1197
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1198
+ }
1199
+ };
1200
+ const recognize = async (image, applicationToken) => {
1201
+ ensureSecureContext();
1202
+ const controller = createTimeoutController();
1203
+ const compressedImage = await compressImage(image);
1204
+ try {
1205
+ const formData = new FormData();
1206
+ formData.append("face_frames", compressedImage);
1207
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/face/`, {
1208
+ method: "POST",
1209
+ headers: {
1210
+ "X-App-Token": applicationToken
1211
+ },
1212
+ body: formData,
1213
+ signal: controller.signal
1214
+ });
1121
1215
  if (!response.ok) {
1122
1216
  if (response.status === 401 || response.status === 403) {
1123
1217
  throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1124
1218
  }
1125
1219
  throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1126
1220
  }
1221
+ const data = await response.json();
1222
+ if (!data.success) {
1223
+ throw new NeoFaceError(data.message || "Recognition failed", ErrorType.RECOGNITION_FAILED);
1224
+ }
1225
+ return {
1226
+ accessToken: data.accessToken,
1227
+ payload: data.payload
1228
+ };
1127
1229
  } catch (error) {
1128
1230
  if (error instanceof Error) {
1129
1231
  if (error.name === "AbortError") {
1130
1232
  throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1131
1233
  }
1132
- throw error;
1234
+ if (error instanceof NeoFaceError) {
1235
+ throw error;
1236
+ }
1237
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1133
1238
  }
1134
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1239
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1135
1240
  }
1136
1241
  };
1137
- const recognize = async (image, applicationToken) => {
1242
+ const loginWithBiometric = async (image, applicationToken) => {
1243
+ var _a, _b, _c, _d, _e;
1138
1244
  ensureSecureContext();
1139
1245
  const controller = createTimeoutController();
1140
1246
  const compressedImage = await compressImage(image);
1141
1247
  try {
1142
1248
  const formData = new FormData();
1143
- formData.append("image", compressedImage);
1144
- formData.append("token", applicationToken);
1145
- const response = await fetch(`${API_BASE_URL}/auth/person/register/liverecognition`, {
1249
+ formData.append("face_frames", compressedImage);
1250
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/auth/login/`, {
1146
1251
  method: "POST",
1252
+ headers: {
1253
+ "X-App-Token": applicationToken
1254
+ },
1147
1255
  body: formData,
1148
1256
  signal: controller.signal
1149
1257
  });
@@ -1155,12 +1263,212 @@ const recognize = async (image, applicationToken) => {
1155
1263
  }
1156
1264
  const data = await response.json();
1157
1265
  if (!data.success) {
1158
- throw new NeoFaceError(data.message || "Recognition failed", ErrorType.RECOGNITION_FAILED);
1266
+ throw new NeoFaceError(data.message || "Login failed", ErrorType.LOGIN_FAILED);
1159
1267
  }
1160
1268
  return {
1269
+ success: true,
1161
1270
  accessToken: data.accessToken,
1271
+ alias: data.alias,
1272
+ user: {
1273
+ id: ((_a = data.user) == null ? void 0 : _a.id) || "",
1274
+ email: ((_b = data.user) == null ? void 0 : _b.email) || "",
1275
+ role: ((_c = data.user) == null ? void 0 : _c.role) || "",
1276
+ validated: ((_d = data.user) == null ? void 0 : _d.validated) || false,
1277
+ active: ((_e = data.user) == null ? void 0 : _e.active) || false
1278
+ },
1279
+ person: data.person ? {
1280
+ name: data.person.name || "",
1281
+ birth_date: data.person.birth_date || ""
1282
+ } : void 0,
1283
+ confidence_score: data.confidence_score,
1284
+ recognition_id: data.recognition_id
1285
+ };
1286
+ } catch (error) {
1287
+ if (error instanceof Error) {
1288
+ if (error.name === "AbortError") {
1289
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1290
+ }
1291
+ if (error instanceof NeoFaceError) {
1292
+ throw error;
1293
+ }
1294
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1295
+ }
1296
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1297
+ }
1298
+ };
1299
+ const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
1300
+ ensureSecureContext();
1301
+ const controller = createTimeoutController();
1302
+ const compressedImage = await compressImage(image);
1303
+ try {
1304
+ const formData = new FormData();
1305
+ formData.append("face_frames", compressedImage);
1306
+ formData.append("liveness_check", livenessCheck.toString());
1307
+ formData.append("confidence_threshold", confidenceThreshold.toString());
1308
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/biometric/`, {
1309
+ method: "POST",
1310
+ headers: {
1311
+ "X-App-Token": applicationToken
1312
+ },
1313
+ body: formData,
1314
+ signal: controller.signal
1315
+ });
1316
+ if (!response.ok) {
1317
+ if (response.status === 401 || response.status === 403) {
1318
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1319
+ }
1320
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1321
+ }
1322
+ const data = await response.json();
1323
+ return {
1324
+ success: data.success,
1325
+ personId: data.person_id,
1326
+ confidenceScore: data.confidence_score,
1327
+ accessToken: data.access_token,
1328
+ payload: data.payload
1329
+ };
1330
+ } catch (error) {
1331
+ if (error instanceof Error) {
1332
+ if (error.name === "AbortError") {
1333
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1334
+ }
1335
+ if (error instanceof NeoFaceError) {
1336
+ throw error;
1337
+ }
1338
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1339
+ }
1340
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1341
+ }
1342
+ };
1343
+ const simpleIdentification = async (documentType, documentNumber, applicationToken, purpose = "LOGIN") => {
1344
+ ensureSecureContext();
1345
+ const controller = createTimeoutController();
1346
+ try {
1347
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/simple/`, {
1348
+ method: "POST",
1349
+ headers: {
1350
+ "Content-Type": "application/json",
1351
+ "X-App-Token": applicationToken
1352
+ },
1353
+ body: JSON.stringify({
1354
+ document_type: documentType,
1355
+ document_number: documentNumber,
1356
+ purpose
1357
+ }),
1358
+ signal: controller.signal
1359
+ });
1360
+ if (!response.ok) {
1361
+ if (response.status === 401 || response.status === 403) {
1362
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1363
+ }
1364
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1365
+ }
1366
+ const data = await response.json();
1367
+ if (!data.success) {
1368
+ throw new NeoFaceError(data.message || "Identification failed", ErrorType.RECOGNITION_FAILED);
1369
+ }
1370
+ return {
1371
+ success: data.success,
1372
+ personId: data.person_id,
1373
+ accessToken: data.access_token,
1374
+ payload: data.payload
1375
+ };
1376
+ } catch (error) {
1377
+ if (error instanceof Error) {
1378
+ if (error.name === "AbortError") {
1379
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1380
+ }
1381
+ if (error instanceof NeoFaceError) {
1382
+ throw error;
1383
+ }
1384
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1385
+ }
1386
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1387
+ }
1388
+ };
1389
+ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8) => {
1390
+ ensureSecureContext();
1391
+ const controller = createTimeoutController();
1392
+ const compressedImage = await compressImage(image);
1393
+ const base64Image = await new Promise((resolve, reject) => {
1394
+ const reader = new FileReader();
1395
+ reader.onload = () => {
1396
+ const result = reader.result;
1397
+ resolve(result.split(",")[1]);
1398
+ };
1399
+ reader.onerror = reject;
1400
+ reader.readAsDataURL(compressedImage);
1401
+ });
1402
+ try {
1403
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/purpose/`, {
1404
+ method: "POST",
1405
+ headers: {
1406
+ "Content-Type": "application/json",
1407
+ "X-App-Token": applicationToken
1408
+ },
1409
+ body: JSON.stringify({
1410
+ biometric_data: base64Image,
1411
+ type_of_identification: "FACE",
1412
+ purpose,
1413
+ confidence_threshold: confidenceThreshold
1414
+ }),
1415
+ signal: controller.signal
1416
+ });
1417
+ if (!response.ok) {
1418
+ if (response.status === 401 || response.status === 403) {
1419
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1420
+ }
1421
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1422
+ }
1423
+ const data = await response.json();
1424
+ return {
1425
+ success: data.success,
1426
+ personId: data.person_id,
1427
+ confidenceScore: data.confidence_score,
1428
+ accessToken: data.access_token,
1162
1429
  payload: data.payload
1163
1430
  };
1431
+ } catch (error) {
1432
+ if (error instanceof Error) {
1433
+ if (error.name === "AbortError") {
1434
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1435
+ }
1436
+ if (error instanceof NeoFaceError) {
1437
+ throw error;
1438
+ }
1439
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1440
+ }
1441
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1442
+ }
1443
+ };
1444
+ const registerPersonWithoutFace = async (personData, applicationToken) => {
1445
+ ensureSecureContext();
1446
+ const controller = createTimeoutController();
1447
+ try {
1448
+ const response = await fetch(`${API_BASE_URL}/api/v1/signup/donor/`, {
1449
+ method: "POST",
1450
+ headers: {
1451
+ "Content-Type": "application/json",
1452
+ "X-App-Token": applicationToken
1453
+ },
1454
+ body: JSON.stringify({
1455
+ name: personData.name,
1456
+ birth_date: personData.birth_date,
1457
+ cpf: personData.cpf,
1458
+ email: personData.email,
1459
+ password: personData.password,
1460
+ password_confirm: personData.password
1461
+ }),
1462
+ signal: controller.signal
1463
+ });
1464
+ if (!response.ok) {
1465
+ if (response.status === 401 || response.status === 403) {
1466
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1467
+ }
1468
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1469
+ }
1470
+ const data = await response.json();
1471
+ return data;
1164
1472
  } catch (error) {
1165
1473
  if (error instanceof Error) {
1166
1474
  if (error.name === "AbortError") {
@@ -1174,7 +1482,91 @@ const recognize = async (image, applicationToken) => {
1174
1482
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
1175
1483
  }
1176
1484
  };
1177
- const modalReducer = (state, action) => {
1485
+ const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
1486
+ ensureSecureContext();
1487
+ if (!facePhotos || facePhotos.length === 0) {
1488
+ throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION);
1489
+ }
1490
+ if (facePhotos.length > 5) {
1491
+ throw new NeoFaceError("Maximum of 5 face photos allowed", ErrorType.VALIDATION);
1492
+ }
1493
+ const controller = createTimeoutController();
1494
+ try {
1495
+ const facePhotosBase64 = await Promise.all(
1496
+ facePhotos.map(async (photo) => {
1497
+ const compressedImage = await compressImage(photo);
1498
+ return new Promise((resolve, reject) => {
1499
+ const reader = new FileReader();
1500
+ reader.onload = () => {
1501
+ const result = reader.result;
1502
+ resolve(result);
1503
+ };
1504
+ reader.onerror = reject;
1505
+ reader.readAsDataURL(compressedImage);
1506
+ });
1507
+ })
1508
+ );
1509
+ const response = await fetch(`${API_BASE_URL}/api/v1/signup/donor/`, {
1510
+ method: "POST",
1511
+ headers: {
1512
+ "Content-Type": "application/json",
1513
+ "X-App-Token": applicationToken
1514
+ },
1515
+ body: JSON.stringify({
1516
+ name: personData.name,
1517
+ birth_date: personData.birth_date,
1518
+ cpf: personData.cpf,
1519
+ email: personData.email,
1520
+ password: personData.password,
1521
+ password_confirm: personData.password,
1522
+ face_photos: facePhotosBase64
1523
+ }),
1524
+ signal: controller.signal
1525
+ });
1526
+ if (!response.ok) {
1527
+ if (response.status === 401 || response.status === 403) {
1528
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1529
+ }
1530
+ try {
1531
+ const errorData = await response.json();
1532
+ if (errorData.error === "fraud_detected") {
1533
+ throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION);
1534
+ }
1535
+ if (errorData.error === "user_exists") {
1536
+ throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION);
1537
+ }
1538
+ if (errorData.error === "person_exists") {
1539
+ throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION);
1540
+ }
1541
+ throw new NeoFaceError(errorData.message || `Server returned status ${response.status}`, ErrorType.NETWORK);
1542
+ } catch (parseError) {
1543
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1544
+ }
1545
+ }
1546
+ const data = await response.json();
1547
+ return {
1548
+ success: data.success,
1549
+ message: data.message,
1550
+ person_id: data.person_id,
1551
+ user_id: data.user_id,
1552
+ person_validated: data.person_validated,
1553
+ face_processing: data.face_processing,
1554
+ onboarding_link: data.onboarding_link
1555
+ };
1556
+ } catch (error) {
1557
+ if (error instanceof Error) {
1558
+ if (error.name === "AbortError") {
1559
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1560
+ }
1561
+ if (error instanceof NeoFaceError) {
1562
+ throw error;
1563
+ }
1564
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1565
+ }
1566
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1567
+ }
1568
+ };
1569
+ const modalReducer$1 = (state, action) => {
1178
1570
  switch (action.type) {
1179
1571
  case "PERMISSION_GRANTED":
1180
1572
  return { status: "ready" };
@@ -1315,7 +1707,7 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
1315
1707
  const streamRef = useRef(null);
1316
1708
  const faceDetectionTimeoutRef = useRef(null);
1317
1709
  const livenessCheckRef = useRef(null);
1318
- const [state, dispatch] = useReducer(modalReducer, { status: "requestingPermission" });
1710
+ const [state, dispatch] = useReducer(modalReducer$1, { status: "requestingPermission" });
1319
1711
  const [faceDetected, setFaceDetected] = useState(false);
1320
1712
  const [livenessAttempts, setLivenessAttempts] = useState(0);
1321
1713
  const { startCamera, stopCamera } = useCamera();
@@ -1366,8 +1758,8 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
1366
1758
  const loadModels = async () => {
1367
1759
  try {
1368
1760
  await Promise.all([
1369
- faceapi.nets.tinyFaceDetector.loadFromUri("/models"),
1370
- faceapi.nets.faceLandmark68Net.loadFromUri("/models")
1761
+ faceapi.nets.tinyFaceDetector.loadFromUri("./models"),
1762
+ faceapi.nets.faceLandmark68Net.loadFromUri("./models")
1371
1763
  ]);
1372
1764
  } catch (error) {
1373
1765
  console.error("Failed to load face-api.js models:", error);
@@ -1473,28 +1865,1903 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
1473
1865
  ] })
1474
1866
  ] });
1475
1867
  }
1476
- const VERSION = "1.0.18";
1477
- const RELEASE_DATE = "2025-06-02";
1478
- function start(applicationToken, callbacks) {
1479
- const container = document.createElement("div");
1480
- container.id = "neoface-modal-container";
1481
- document.body.appendChild(container);
1482
- const cleanup = () => {
1483
- if (document.body.contains(container)) {
1484
- document.body.removeChild(container);
1485
- }
1868
+ const modalReducer = (state, action) => {
1869
+ switch (action.type) {
1870
+ case "START_LIVENESS":
1871
+ return {
1872
+ status: "liveness",
1873
+ step: action.stepSequence[0],
1874
+ capturedPhotos: [],
1875
+ stepSequence: action.stepSequence,
1876
+ currentStepIndex: 0,
1877
+ stepProgress: 0
1878
+ };
1879
+ case "NEXT_STEP":
1880
+ if (state.status === "liveness") {
1881
+ const newPhotos = action.photo ? [...state.capturedPhotos, action.photo] : state.capturedPhotos;
1882
+ return {
1883
+ status: "liveness",
1884
+ step: action.step,
1885
+ capturedPhotos: newPhotos,
1886
+ stepSequence: state.stepSequence,
1887
+ currentStepIndex: state.currentStepIndex + 1,
1888
+ stepProgress: 0
1889
+ };
1890
+ }
1891
+ return state;
1892
+ case "UPDATE_PROGRESS":
1893
+ if (state.status === "liveness") {
1894
+ return {
1895
+ ...state,
1896
+ stepProgress: action.progress
1897
+ };
1898
+ }
1899
+ return state;
1900
+ case "START_PROCESSING":
1901
+ return { status: "processing" };
1902
+ case "SUCCESS":
1903
+ return { status: "success" };
1904
+ case "ERROR":
1905
+ return { status: "error", message: action.message };
1906
+ case "RESET":
1907
+ return initialState;
1908
+ default:
1909
+ return state;
1910
+ }
1911
+ };
1912
+ const livenessInstructions = {
1913
+ center: "Posicione seu rosto no centro",
1914
+ up: "Incline a cabeça para cima",
1915
+ down: "Incline a cabeça para baixo",
1916
+ left: "Vire o rosto para a esquerda",
1917
+ right: "Vire o rosto para a direita",
1918
+ complete: "Processando..."
1919
+ };
1920
+ const generateRandomStepSequence = () => {
1921
+ return ["center", "up", "down", "left", "right", "complete"];
1922
+ };
1923
+ const initialState = (() => {
1924
+ const sequence = generateRandomStepSequence();
1925
+ return {
1926
+ status: "liveness",
1927
+ step: sequence[0],
1928
+ capturedPhotos: [],
1929
+ stepSequence: sequence,
1930
+ currentStepIndex: 0,
1931
+ stepProgress: 0
1486
1932
  };
1487
- validateToken(applicationToken).then(() => {
1488
- const modalElement = require$$0$1.createElement(FaceCaptureModal, {
1489
- accessToken: applicationToken,
1490
- onClose: cleanup,
1491
- onSuccess: (result) => {
1492
- cleanup();
1493
- callbacks.onSuccess({
1494
- name: result.data.faceId,
1495
- email: result.data.faceId,
1496
- documentId: result.data.faceId
1497
- });
1933
+ })();
1934
+ const CALIBRATION = {
1935
+ PITCH_DEAD_ZONE: 8,
1936
+ YAW_DEAD_ZONE: 10,
1937
+ PITCH_OFFSET: 26,
1938
+ PITCH_UP_THRESHOLD: 8,
1939
+ PITCH_DOWN_THRESHOLD: -8,
1940
+ YAW_LEFT_THRESHOLD: 15,
1941
+ YAW_RIGHT_THRESHOLD: -15,
1942
+ CAPTURE_HOLD_TIME: 1500,
1943
+ DETECTION_INTERVAL: 200
1944
+ };
1945
+ function BiometricRegistrationModal({
1946
+ personData,
1947
+ applicationToken,
1948
+ onClose,
1949
+ onSuccess,
1950
+ onError,
1951
+ useRealApi = false
1952
+ // 🚀 Por padrão, modo simulado
1953
+ }) {
1954
+ const videoRef = useRef(null);
1955
+ const canvasRef = useRef(null);
1956
+ const streamRef = useRef(null);
1957
+ const holdStartTimeRef = useRef(null);
1958
+ const progressIntervalRef = useRef(null);
1959
+ const isCapturingRef = useRef(false);
1960
+ const currentStepRef = useRef("center");
1961
+ const captureStateRef = useRef(null);
1962
+ const [state, dispatch] = useReducer(modalReducer, initialState);
1963
+ const [faceDetected, setFaceDetected] = useState(false);
1964
+ const [faceDistance, setFaceDistance] = useState("ok");
1965
+ useEffect(() => {
1966
+ if (state.status === "liveness") {
1967
+ const newStep = state.step;
1968
+ if (currentStepRef.current !== newStep) {
1969
+ currentStepRef.current = newStep;
1970
+ if (!isCapturingRef.current) {
1971
+ if (holdStartTimeRef.current) {
1972
+ holdStartTimeRef.current = null;
1973
+ }
1974
+ if (progressIntervalRef.current) {
1975
+ clearInterval(progressIntervalRef.current);
1976
+ progressIntervalRef.current = null;
1977
+ }
1978
+ dispatch({ type: "UPDATE_PROGRESS", progress: 0 });
1979
+ }
1980
+ }
1981
+ captureStateRef.current = {
1982
+ stepSequence: state.stepSequence,
1983
+ currentStepIndex: state.currentStepIndex,
1984
+ capturedPhotos: state.capturedPhotos
1985
+ };
1986
+ }
1987
+ }, [
1988
+ state.status,
1989
+ state.status === "liveness" && state.step,
1990
+ state.status === "liveness" && state.currentStepIndex
1991
+ ]);
1992
+ const calculateHeadAngles = (landmarks) => {
1993
+ try {
1994
+ const points = landmarks.positions || landmarks._positions;
1995
+ if (!Array.isArray(points) || points.length < 68) {
1996
+ return null;
1997
+ }
1998
+ const getPoint = (index) => {
1999
+ const point = points[index];
2000
+ return {
2001
+ x: point._x !== void 0 ? point._x : point.x,
2002
+ y: point._y !== void 0 ? point._y : point.y
2003
+ };
2004
+ };
2005
+ const noseTip = getPoint(30);
2006
+ const chin = getPoint(8);
2007
+ const leftEye = getPoint(36);
2008
+ const rightEye = getPoint(45);
2009
+ const eyeCenterX = (leftEye.x + rightEye.x) / 2;
2010
+ const noseOffsetX = noseTip.x - eyeCenterX;
2011
+ const eyeDistance = Math.abs(rightEye.x - leftEye.x);
2012
+ const yaw = noseOffsetX / eyeDistance * 90;
2013
+ const eyeCenterY = (leftEye.y + rightEye.y) / 2;
2014
+ const noseOffsetY = noseTip.y - eyeCenterY;
2015
+ const faceHeight = Math.abs(chin.y - eyeCenterY);
2016
+ const pitchRaw = -(noseOffsetY / faceHeight) * 90;
2017
+ const pitch = pitchRaw + CALIBRATION.PITCH_OFFSET;
2018
+ return {
2019
+ pitch: Math.round(pitch),
2020
+ yaw: Math.round(yaw)
2021
+ };
2022
+ } catch (error) {
2023
+ return null;
2024
+ }
2025
+ };
2026
+ const checkPositionMatch = (pitch, yaw, step) => {
2027
+ switch (step) {
2028
+ case "center":
2029
+ return Math.abs(pitch) < CALIBRATION.PITCH_DEAD_ZONE && Math.abs(yaw) < CALIBRATION.YAW_DEAD_ZONE;
2030
+ case "up":
2031
+ return pitch > CALIBRATION.PITCH_UP_THRESHOLD;
2032
+ case "down":
2033
+ return pitch < CALIBRATION.PITCH_DOWN_THRESHOLD;
2034
+ case "left":
2035
+ return yaw > CALIBRATION.YAW_LEFT_THRESHOLD;
2036
+ case "right":
2037
+ return yaw < CALIBRATION.YAW_RIGHT_THRESHOLD;
2038
+ default:
2039
+ return false;
2040
+ }
2041
+ };
2042
+ const manageHoldAndCapture = (positionCorrect) => {
2043
+ if (isCapturingRef.current || currentStepRef.current === "complete") {
2044
+ return;
2045
+ }
2046
+ const now = Date.now();
2047
+ if (positionCorrect) {
2048
+ if (!holdStartTimeRef.current) {
2049
+ holdStartTimeRef.current = now;
2050
+ if (progressIntervalRef.current) {
2051
+ clearInterval(progressIntervalRef.current);
2052
+ }
2053
+ progressIntervalRef.current = window.setInterval(() => {
2054
+ if (!holdStartTimeRef.current)
2055
+ return;
2056
+ const elapsed = Date.now() - holdStartTimeRef.current;
2057
+ const progress = Math.min(elapsed / CALIBRATION.CAPTURE_HOLD_TIME * 100, 100);
2058
+ dispatch({ type: "UPDATE_PROGRESS", progress });
2059
+ if (progress >= 100) {
2060
+ clearInterval(progressIntervalRef.current);
2061
+ progressIntervalRef.current = null;
2062
+ capturePhoto();
2063
+ }
2064
+ }, 50);
2065
+ }
2066
+ } else {
2067
+ if (holdStartTimeRef.current) {
2068
+ holdStartTimeRef.current = null;
2069
+ if (progressIntervalRef.current) {
2070
+ clearInterval(progressIntervalRef.current);
2071
+ progressIntervalRef.current = null;
2072
+ }
2073
+ dispatch({ type: "UPDATE_PROGRESS", progress: 0 });
2074
+ }
2075
+ }
2076
+ };
2077
+ useEffect(() => {
2078
+ const loadModels = async () => {
2079
+ try {
2080
+ const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
2081
+ await Promise.all([
2082
+ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
2083
+ faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
2084
+ ]);
2085
+ } catch (error) {
2086
+ dispatch({ type: "ERROR", message: "Erro ao carregar modelos de IA" });
2087
+ }
2088
+ };
2089
+ loadModels();
2090
+ }, []);
2091
+ useEffect(() => {
2092
+ let mounted = true;
2093
+ const initCamera = async () => {
2094
+ if (state.status !== "liveness" || streamRef.current)
2095
+ return;
2096
+ try {
2097
+ const stream = await navigator.mediaDevices.getUserMedia({
2098
+ video: {
2099
+ width: { ideal: 640 },
2100
+ height: { ideal: 480 },
2101
+ facingMode: "user"
2102
+ }
2103
+ });
2104
+ if (!mounted) {
2105
+ stream.getTracks().forEach((track) => track.stop());
2106
+ return;
2107
+ }
2108
+ const waitForVideo = async () => {
2109
+ let attempts = 0;
2110
+ while (!videoRef.current && attempts < 50) {
2111
+ await new Promise((resolve) => setTimeout(resolve, 100));
2112
+ attempts++;
2113
+ }
2114
+ return videoRef.current;
2115
+ };
2116
+ const video = await waitForVideo();
2117
+ if (!video) {
2118
+ throw new Error("Elemento de vídeo não encontrado");
2119
+ }
2120
+ video.srcObject = stream;
2121
+ streamRef.current = stream;
2122
+ await video.play();
2123
+ } catch (error) {
2124
+ if (!mounted)
2125
+ return;
2126
+ const errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
2127
+ dispatch({ type: "ERROR", message: errorMessage });
2128
+ }
2129
+ };
2130
+ initCamera();
2131
+ return () => {
2132
+ mounted = false;
2133
+ if (streamRef.current) {
2134
+ streamRef.current.getTracks().forEach((track) => track.stop());
2135
+ streamRef.current = null;
2136
+ }
2137
+ if (progressIntervalRef.current) {
2138
+ clearInterval(progressIntervalRef.current);
2139
+ progressIntervalRef.current = null;
2140
+ }
2141
+ holdStartTimeRef.current = null;
2142
+ };
2143
+ }, [state.status]);
2144
+ useEffect(() => {
2145
+ if (state.status !== "liveness" || !videoRef.current) {
2146
+ return;
2147
+ }
2148
+ let isRunning = true;
2149
+ let lastDetectionTime = 0;
2150
+ const detectFace2 = async () => {
2151
+ if (!isRunning || state.status !== "liveness") {
2152
+ return;
2153
+ }
2154
+ if (isCapturingRef.current || currentStepRef.current === "complete") {
2155
+ requestAnimationFrame(detectFace2);
2156
+ return;
2157
+ }
2158
+ const now = Date.now();
2159
+ if (now - lastDetectionTime < CALIBRATION.DETECTION_INTERVAL) {
2160
+ requestAnimationFrame(detectFace2);
2161
+ return;
2162
+ }
2163
+ lastDetectionTime = now;
2164
+ try {
2165
+ const detection = await faceapi.detectSingleFace(
2166
+ videoRef.current,
2167
+ new faceapi.TinyFaceDetectorOptions({
2168
+ inputSize: 160,
2169
+ scoreThreshold: 0.4
2170
+ })
2171
+ ).withFaceLandmarks();
2172
+ if (!isRunning)
2173
+ return;
2174
+ const hasFace = !!detection;
2175
+ setFaceDetected(hasFace);
2176
+ if (hasFace && detection.detection) {
2177
+ const box = detection.detection.box;
2178
+ const faceWidth = box.width;
2179
+ if (faceWidth > 250) {
2180
+ setFaceDistance("too-close");
2181
+ } else if (faceWidth < 120) {
2182
+ setFaceDistance("too-far");
2183
+ } else {
2184
+ setFaceDistance("ok");
2185
+ }
2186
+ }
2187
+ if (hasFace && detection.landmarks) {
2188
+ const angles = calculateHeadAngles(detection.landmarks);
2189
+ if (angles) {
2190
+ const positionCorrect = checkPositionMatch(
2191
+ angles.pitch,
2192
+ angles.yaw,
2193
+ currentStepRef.current
2194
+ );
2195
+ manageHoldAndCapture(positionCorrect);
2196
+ }
2197
+ } else {
2198
+ if (holdStartTimeRef.current) {
2199
+ holdStartTimeRef.current = null;
2200
+ if (progressIntervalRef.current) {
2201
+ clearInterval(progressIntervalRef.current);
2202
+ progressIntervalRef.current = null;
2203
+ }
2204
+ dispatch({ type: "UPDATE_PROGRESS", progress: 0 });
2205
+ }
2206
+ }
2207
+ } catch (error) {
2208
+ }
2209
+ if (isRunning && state.status === "liveness") {
2210
+ requestAnimationFrame(detectFace2);
2211
+ }
2212
+ };
2213
+ const startTimer = setTimeout(() => {
2214
+ var _a;
2215
+ if (isRunning && ((_a = videoRef.current) == null ? void 0 : _a.readyState) === 4) {
2216
+ detectFace2();
2217
+ }
2218
+ }, 1e3);
2219
+ return () => {
2220
+ isRunning = false;
2221
+ clearTimeout(startTimer);
2222
+ if (progressIntervalRef.current) {
2223
+ clearInterval(progressIntervalRef.current);
2224
+ progressIntervalRef.current = null;
2225
+ }
2226
+ holdStartTimeRef.current = null;
2227
+ };
2228
+ }, [state.status]);
2229
+ const capturePhoto = async () => {
2230
+ if (!videoRef.current || !canvasRef.current || isCapturingRef.current) {
2231
+ return;
2232
+ }
2233
+ isCapturingRef.current = true;
2234
+ holdStartTimeRef.current = null;
2235
+ const capturedState = captureStateRef.current;
2236
+ if (!capturedState) {
2237
+ isCapturingRef.current = false;
2238
+ return;
2239
+ }
2240
+ const canvas = canvasRef.current;
2241
+ const video = videoRef.current;
2242
+ const ctx = canvas.getContext("2d");
2243
+ if (!ctx) {
2244
+ isCapturingRef.current = false;
2245
+ return;
2246
+ }
2247
+ canvas.width = video.videoWidth;
2248
+ canvas.height = video.videoHeight;
2249
+ ctx.scale(-1, 1);
2250
+ ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
2251
+ canvas.toBlob(
2252
+ (blob) => {
2253
+ if (!blob) {
2254
+ isCapturingRef.current = false;
2255
+ return;
2256
+ }
2257
+ const nextStepIndex = capturedState.currentStepIndex + 1;
2258
+ const nextStep = capturedState.stepSequence[nextStepIndex];
2259
+ if (nextStep === "complete") {
2260
+ const allPhotos = [...capturedState.capturedPhotos, blob];
2261
+ dispatch({ type: "START_PROCESSING" });
2262
+ processRegistration(allPhotos);
2263
+ } else {
2264
+ setTimeout(() => {
2265
+ isCapturingRef.current = false;
2266
+ dispatch({ type: "NEXT_STEP", step: nextStep, photo: blob });
2267
+ }, 100);
2268
+ }
2269
+ },
2270
+ "image/jpeg",
2271
+ 0.85
2272
+ );
2273
+ };
2274
+ const processRegistration = async (photos) => {
2275
+ try {
2276
+ if (useRealApi) {
2277
+ console.log("🚀 Fazendo requisição REAL ao backend de produção...");
2278
+ const result2 = await registerPersonWithBiometric(personData, photos, applicationToken);
2279
+ const adaptedResult = {
2280
+ success: result2.success,
2281
+ message: result2.message,
2282
+ person_id: result2.person_id,
2283
+ user_id: result2.user_id,
2284
+ person_validated: result2.person_validated,
2285
+ face_processing: result2.face_processing,
2286
+ onboarding_link: result2.onboarding_link
2287
+ };
2288
+ dispatch({ type: "SUCCESS" });
2289
+ onSuccess(adaptedResult);
2290
+ return;
2291
+ }
2292
+ console.log("🎭 Usando modo SIMULADO para testes...");
2293
+ const facePhotosBase64 = await Promise.all(
2294
+ photos.map(async (photo) => {
2295
+ return new Promise((resolve, reject) => {
2296
+ const reader = new FileReader();
2297
+ reader.onload = () => {
2298
+ const result2 = reader.result;
2299
+ resolve(result2);
2300
+ };
2301
+ reader.onerror = reject;
2302
+ reader.readAsDataURL(photo);
2303
+ });
2304
+ })
2305
+ );
2306
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2307
+ const result = {
2308
+ success: true,
2309
+ message: "Registro biométrico realizado com sucesso",
2310
+ person_id: "test-person-123",
2311
+ user_id: "test-user-456",
2312
+ person_validated: true,
2313
+ face_processing: {
2314
+ total_photos_captured: photos.length,
2315
+ photo_sizes: photos.map((p, i) => ({
2316
+ photo_index: i + 1,
2317
+ size_kb: Math.round(p.size / 1024)
2318
+ })),
2319
+ // Mostra apenas preview dos primeiros 50 caracteres de cada foto em base64
2320
+ photos_preview: facePhotosBase64.map((b64, i) => ({
2321
+ photo_index: i + 1,
2322
+ format: "data:image/jpeg;base64,...",
2323
+ preview: b64.substring(0, 50) + "...",
2324
+ full_length: b64.length
2325
+ }))
2326
+ }
2327
+ };
2328
+ dispatch({ type: "SUCCESS" });
2329
+ onSuccess(result);
2330
+ } catch (error) {
2331
+ const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
2332
+ dispatch({ type: "ERROR", message: errorMessage });
2333
+ onError(errorMessage);
2334
+ }
2335
+ };
2336
+ const renderLivenessDetection = () => {
2337
+ if (state.status !== "liveness")
2338
+ return null;
2339
+ const progress = `${state.currentStepIndex + 1}/${state.stepSequence.length - 1}`;
2340
+ const stepProgress = state.stepProgress;
2341
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "liveness-container", children: [
2342
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-indicator", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "progress-text", children: progress }) }),
2343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "video-wrapper", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2344
+ "div",
2345
+ {
2346
+ className: `video-circle ${faceDetected ? "face-detected" : ""} ${stepProgress > 0 ? "capturing" : ""}`,
2347
+ children: [
2348
+ /* @__PURE__ */ jsxRuntimeExports.jsx("video", { ref: videoRef, className: "video-feed", autoPlay: true, muted: true, playsInline: true }),
2349
+ faceDetected && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "face-badge", children: [
2350
+ /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2351
+ "path",
2352
+ {
2353
+ d: "M5 10 L8 13 L15 6",
2354
+ stroke: "currentColor",
2355
+ strokeWidth: "2.5",
2356
+ strokeLinecap: "round",
2357
+ strokeLinejoin: "round"
2358
+ }
2359
+ ) }),
2360
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Rosto detectado" })
2361
+ ] })
2362
+ ]
2363
+ }
2364
+ ) }),
2365
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "instruction-box", children: [
2366
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "instruction-text", children: livenessInstructions[state.step] }),
2367
+ stepProgress > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-bar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "progress-fill", style: { width: `${stepProgress}%` } }) })
2368
+ ] }),
2369
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "branding-small", children: [
2370
+ /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: "/public/logo-icone.png", alt: "NeoFaceId", className: "brand-logo-small" }),
2371
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
2372
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
2373
+ ] })
2374
+ ] });
2375
+ };
2376
+ const renderProcessing = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "processing-container", children: [
2377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "spinner-wrapper", children: [
2378
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner" }),
2379
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner-ring" })
2380
+ ] }),
2381
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Processando biometria" }),
2382
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Analisando suas capturas faciais..." }),
2383
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
2384
+ "div",
2385
+ {
2386
+ className: "branding",
2387
+ style: {
2388
+ marginTop: "40px",
2389
+ paddingTop: "24px",
2390
+ borderTop: "1px solid rgba(255, 255, 255, 0.1)"
2391
+ },
2392
+ children: [
2393
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
2394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
2395
+ ]
2396
+ }
2397
+ )
2398
+ ] });
2399
+ const renderError = () => {
2400
+ if (state.status !== "error")
2401
+ return null;
2402
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container error", children: [
2403
+ /* @__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: [
2404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
2405
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2406
+ "path",
2407
+ {
2408
+ d: "M20 20 L40 40 M40 20 L20 40",
2409
+ stroke: "currentColor",
2410
+ strokeWidth: "3",
2411
+ strokeLinecap: "round"
2412
+ }
2413
+ )
2414
+ ] }) }),
2415
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível concluir" }),
2416
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: state.message }),
2417
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group", children: [
2418
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
2419
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ghost-button", onClick: onClose, children: "Cancelar" })
2420
+ ] })
2421
+ ] });
2422
+ };
2423
+ const renderSuccess = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container success", children: [
2424
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "result-icon success-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", children: [
2425
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
2426
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
2427
+ "path",
2428
+ {
2429
+ d: "M15 30 L25 40 L45 20",
2430
+ stroke: "currentColor",
2431
+ strokeWidth: "3",
2432
+ strokeLinecap: "round",
2433
+ strokeLinejoin: "round"
2434
+ }
2435
+ )
2436
+ ] }) }),
2437
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Verificação concluída!" }),
2438
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Sua biometria foi registrada com sucesso" }),
2439
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary-button", onClick: onClose, children: "Continuar" })
2440
+ ] });
2441
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2442
+ /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
2443
+ @keyframes fadeIn {
2444
+ from { opacity: 0; transform: scale(0.95); }
2445
+ to { opacity: 1; transform: scale(1); }
2446
+ }
2447
+
2448
+ @keyframes slideUp {
2449
+ from { opacity: 0; transform: translateY(20px); }
2450
+ to { opacity: 1; transform: translateY(0); }
2451
+ }
2452
+
2453
+ @keyframes slideDown {
2454
+ from { opacity: 0; transform: translateY(-10px); }
2455
+ to { opacity: 1; transform: translateY(0); }
2456
+ }
2457
+
2458
+ @keyframes spin {
2459
+ from { transform: rotate(0deg); }
2460
+ to { transform: rotate(360deg); }
2461
+ }
2462
+
2463
+ @keyframes pulseRing {
2464
+ 0%, 100% {
2465
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.8), 0 0 40px rgba(16, 185, 129, 0.4);
2466
+ }
2467
+ 50% {
2468
+ box-shadow: 0 0 0 6px rgba(16, 185, 129, 0.9), 0 0 50px rgba(16, 185, 129, 0.5);
2469
+ }
2470
+ }
2471
+
2472
+ @keyframes checkmark {
2473
+ 0% {
2474
+ stroke-dashoffset: 100;
2475
+ }
2476
+ 100% {
2477
+ stroke-dashoffset: 0;
2478
+ }
2479
+ }
2480
+
2481
+ @keyframes scaleIn {
2482
+ 0% {
2483
+ transform: scale(0);
2484
+ opacity: 0;
2485
+ }
2486
+ 50% {
2487
+ transform: scale(1.1);
2488
+ }
2489
+ 100% {
2490
+ transform: scale(1);
2491
+ opacity: 1;
2492
+ }
2493
+ }
2494
+
2495
+ @keyframes scanRing {
2496
+ 0%, 100% { transform: scale(1); opacity: 0.3; }
2497
+ 50% { transform: scale(1.1); opacity: 0.6; }
2498
+ }
2499
+
2500
+ @keyframes cornerBlink {
2501
+ 0%, 100% { opacity: 0.3; }
2502
+ 50% { opacity: 1; }
2503
+ }
2504
+
2505
+ .modal-overlay {
2506
+ position: fixed;
2507
+ inset: 0;
2508
+ background: rgba(0, 0, 0, 0.92);
2509
+ backdrop-filter: blur(8px);
2510
+ display: flex;
2511
+ align-items: center;
2512
+ justify-content: center;
2513
+ z-index: 9999;
2514
+ padding: 20px;
2515
+ }
2516
+
2517
+ .modal-content {
2518
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
2519
+ border-radius: 24px;
2520
+ width: 100%;
2521
+ max-width: 420px;
2522
+ max-height: 90vh;
2523
+ overflow: hidden;
2524
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
2525
+ animation: fadeIn 0.3s ease-out;
2526
+ position: relative;
2527
+ }
2528
+
2529
+ .close-button {
2530
+ position: absolute;
2531
+ top: 20px;
2532
+ right: 20px;
2533
+ width: 32px;
2534
+ height: 32px;
2535
+ background: transparent;
2536
+ border: none;
2537
+ color: rgba(255, 255, 255, 0.6);
2538
+ font-size: 28px;
2539
+ font-weight: 300;
2540
+ cursor: pointer;
2541
+ display: flex;
2542
+ align-items: center;
2543
+ justify-content: center;
2544
+ transition: all 0.2s;
2545
+ z-index: 10;
2546
+ line-height: 1;
2547
+ }
2548
+
2549
+ .close-button:hover {
2550
+ color: rgba(255, 255, 255, 0.9);
2551
+ transform: rotate(90deg);
2552
+ }
2553
+
2554
+ /* Instructions Screen */
2555
+ .instructions-container {
2556
+ padding: 60px 40px 40px;
2557
+ color: white;
2558
+ text-align: center;
2559
+ animation: slideUp 0.4s ease-out;
2560
+ }
2561
+
2562
+ .instructions-header {
2563
+ margin-bottom: 40px;
2564
+ }
2565
+
2566
+ .icon-wrapper {
2567
+ display: inline-block;
2568
+ color: #7c3aed;
2569
+ margin-bottom: 24px;
2570
+ }
2571
+
2572
+ .icon-wrapper .scan-ring {
2573
+ animation: scanRing 2s ease-in-out infinite;
2574
+ }
2575
+
2576
+ .icon-wrapper .corner {
2577
+ animation: cornerBlink 2s ease-in-out infinite;
2578
+ }
2579
+
2580
+ .icon-wrapper .corner.tl { animation-delay: 0s; }
2581
+ .icon-wrapper .corner.tr { animation-delay: 0.5s; }
2582
+ .icon-wrapper .corner.bl { animation-delay: 1s; }
2583
+ .icon-wrapper .corner.br { animation-delay: 1.5s; }
2584
+
2585
+ .instructions-header h2 {
2586
+ font-size: 28px;
2587
+ font-weight: 700;
2588
+ margin: 0 0 12px 0;
2589
+ background: linear-gradient(135deg, #7c3aed 0%, #a78bfa 100%);
2590
+ -webkit-background-clip: text;
2591
+ -webkit-text-fill-color: transparent;
2592
+ background-clip: text;
2593
+ }
2594
+
2595
+ .subtitle {
2596
+ font-size: 15px;
2597
+ color: rgba(255, 255, 255, 0.7);
2598
+ margin: 0;
2599
+ line-height: 1.5;
2600
+ }
2601
+
2602
+ .requirements-list {
2603
+ text-align: left;
2604
+ margin: 0 auto 40px;
2605
+ max-width: 360px;
2606
+ }
2607
+
2608
+ .requirement-item {
2609
+ display: flex;
2610
+ align-items: center;
2611
+ gap: 16px;
2612
+ padding: 14px 0;
2613
+ font-size: 15px;
2614
+ color: rgba(255, 255, 255, 0.9);
2615
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
2616
+ }
2617
+
2618
+ .requirement-item:last-child {
2619
+ border-bottom: none;
2620
+ }
2621
+
2622
+ .req-icon {
2623
+ width: 32px;
2624
+ height: 32px;
2625
+ border-radius: 50%;
2626
+ display: flex;
2627
+ align-items: center;
2628
+ justify-content: center;
2629
+ flex-shrink: 0;
2630
+ }
2631
+
2632
+ .req-icon.success {
2633
+ background: rgba(16, 185, 129, 0.15);
2634
+ color: #10b981;
2635
+ border: 1.5px solid rgba(16, 185, 129, 0.3);
2636
+ }
2637
+
2638
+ .primary-button {
2639
+ width: 100%;
2640
+ padding: 16px 32px;
2641
+ border-radius: 12px;
2642
+ border: none;
2643
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
2644
+ color: white;
2645
+ font-size: 16px;
2646
+ font-weight: 600;
2647
+ cursor: pointer;
2648
+ transition: all 0.3s;
2649
+ box-shadow: 0 4px 16px rgba(124, 58, 237, 0.3);
2650
+ }
2651
+
2652
+ .primary-button:hover {
2653
+ transform: translateY(-2px);
2654
+ box-shadow: 0 8px 24px rgba(124, 58, 237, 0.4);
2655
+ }
2656
+
2657
+ .primary-button:active {
2658
+ transform: translateY(0);
2659
+ }
2660
+
2661
+ .branding {
2662
+ display: flex;
2663
+ align-items: center;
2664
+ justify-content: center;
2665
+ gap: 8px;
2666
+ margin-top: 32px;
2667
+ padding-top: 24px;
2668
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
2669
+ font-size: 13px;
2670
+ color: rgba(255, 255, 255, 0.5);
2671
+ }
2672
+
2673
+ .brand-logo {
2674
+ height: 20px;
2675
+ width: auto;
2676
+ }
2677
+
2678
+ .brand-name {
2679
+ font-weight: 600;
2680
+ font-family: 'SF Pro Display', -apple-system, system-ui, sans-serif;
2681
+ color: #ffffff;
2682
+ letter-spacing: 0.3px;
2683
+ }
2684
+
2685
+ /* Liveness Detection */
2686
+ .liveness-container {
2687
+ height: 100%;
2688
+ min-height: 520px;
2689
+ display: flex;
2690
+ flex-direction: column;
2691
+ align-items: center;
2692
+ justify-content: center;
2693
+ padding: 70px 20px 80px;
2694
+ position: relative;
2695
+ animation: fadeIn 0.3s ease-out;
2696
+ }
2697
+
2698
+ .progress-indicator {
2699
+ position: absolute;
2700
+ top: 20px;
2701
+ left: 50%;
2702
+ transform: translateX(-50%);
2703
+ background: rgba(255, 255, 255, 0.08);
2704
+ backdrop-filter: blur(12px);
2705
+ border-radius: 20px;
2706
+ padding: 8px 20px;
2707
+ border: 1px solid rgba(255, 255, 255, 0.12);
2708
+ z-index: 5;
2709
+ }
2710
+
2711
+ .progress-text {
2712
+ font-size: 14px;
2713
+ font-weight: 600;
2714
+ color: white;
2715
+ }
2716
+
2717
+ .video-wrapper {
2718
+ position: relative;
2719
+ margin: 20px 0;
2720
+ }
2721
+
2722
+ .video-circle {
2723
+ width: 320px;
2724
+ height: 400px;
2725
+ border-radius: 50%;
2726
+ overflow: hidden;
2727
+ position: relative;
2728
+ transition: all 0.3s ease;
2729
+ box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.3);
2730
+ }
2731
+
2732
+ .video-circle.face-detected {
2733
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.5), 0 0 40px rgba(16, 185, 129, 0.3);
2734
+ }
2735
+
2736
+ .video-circle.capturing {
2737
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.9), 0 0 40px rgba(16, 185, 129, 0.5);
2738
+ transition: box-shadow 0.3s ease;
2739
+ }
2740
+
2741
+ .video-feed {
2742
+ width: 100%;
2743
+ height: 100%;
2744
+ object-fit: cover;
2745
+ transform: scaleX(-1);
2746
+ }
2747
+
2748
+ .face-badge {
2749
+ position: absolute;
2750
+ top: 20px;
2751
+ left: 50%;
2752
+ transform: translateX(-50%);
2753
+ background: rgba(16, 185, 129, 0.95);
2754
+ backdrop-filter: blur(10px);
2755
+ border-radius: 20px;
2756
+ padding: 8px 16px;
2757
+ display: flex;
2758
+ align-items: center;
2759
+ gap: 8px;
2760
+ color: white;
2761
+ font-size: 13px;
2762
+ font-weight: 600;
2763
+ border: 1px solid rgba(255, 255, 255, 0.3);
2764
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
2765
+ animation: slideDown 0.3s ease-out;
2766
+ }
2767
+
2768
+ .face-badge.warning {
2769
+ background: rgba(251, 191, 36, 0.95);
2770
+ border: 1px solid rgba(255, 255, 255, 0.3);
2771
+ box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3);
2772
+ color: #78350f;
2773
+ }
2774
+
2775
+ .face-badge.error {
2776
+ background: rgba(239, 68, 68, 0.95);
2777
+ border: 1px solid rgba(255, 255, 255, 0.3);
2778
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
2779
+ color: white;
2780
+ }
2781
+
2782
+ .instruction-box {
2783
+ background: rgba(255, 255, 255, 0.08);
2784
+ backdrop-filter: blur(12px);
2785
+ border-radius: 16px;
2786
+ padding: 16px 24px;
2787
+ margin-top: 20px;
2788
+ border: 1px solid rgba(255, 255, 255, 0.12);
2789
+ max-width: 320px;
2790
+ animation: slideUp 0.3s ease-out;
2791
+ }
2792
+
2793
+ .instruction-text {
2794
+ font-size: 18px;
2795
+ font-weight: 600;
2796
+ color: white;
2797
+ margin: 0 0 12px 0;
2798
+ text-align: center;
2799
+ }
2800
+
2801
+ .progress-bar {
2802
+ height: 4px;
2803
+ background: rgba(255, 255, 255, 0.2);
2804
+ border-radius: 2px;
2805
+ overflow: hidden;
2806
+ }
2807
+
2808
+ .progress-fill {
2809
+ height: 100%;
2810
+ background: linear-gradient(90deg, #7c3aed 0%, #10b981 100%);
2811
+ border-radius: 2px;
2812
+ transition: width 0.05s linear;
2813
+ }
2814
+
2815
+ .branding-small {
2816
+ position: absolute;
2817
+ bottom: 24px;
2818
+ left: 50%;
2819
+ transform: translateX(-50%);
2820
+ display: flex;
2821
+ align-items: center;
2822
+ justify-content: center;
2823
+ gap: 5px;
2824
+ font-size: 12px;
2825
+ color: rgba(255, 255, 255, 0.5);
2826
+ }
2827
+
2828
+ .brand-logo-small {
2829
+ height: 18px;
2830
+ width: 18px;
2831
+ }
2832
+
2833
+ /* Processing */
2834
+ .processing-container {
2835
+ padding: 80px 40px;
2836
+ text-align: center;
2837
+ color: white;
2838
+ animation: fadeIn 0.3s ease-out;
2839
+ }
2840
+
2841
+ .spinner-wrapper {
2842
+ position: relative;
2843
+ width: 80px;
2844
+ height: 80px;
2845
+ margin: 0 auto 32px;
2846
+ }
2847
+
2848
+ .spinner {
2849
+ position: absolute;
2850
+ inset: 0;
2851
+ border: 4px solid rgba(124, 58, 237, 0.2);
2852
+ border-top-color: #7c3aed;
2853
+ border-radius: 50%;
2854
+ animation: spin 1s linear infinite;
2855
+ }
2856
+
2857
+ .spinner-ring {
2858
+ position: absolute;
2859
+ inset: -10px;
2860
+ border: 2px solid rgba(124, 58, 237, 0.1);
2861
+ border-radius: 50%;
2862
+ animation: pulseSpinner 2s ease-in-out infinite;
2863
+ }
2864
+
2865
+ @keyframes pulseSpinner {
2866
+ 0%, 100% { transform: scale(1); opacity: 1; }
2867
+ 50% { transform: scale(1.1); opacity: 0.5; }
2868
+ }
2869
+
2870
+ .processing-container h3 {
2871
+ font-size: 24px;
2872
+ font-weight: 700;
2873
+ margin: 0 0 12px 0;
2874
+ }
2875
+
2876
+ .processing-container p {
2877
+ font-size: 15px;
2878
+ color: rgba(255, 255, 255, 0.7);
2879
+ margin: 0;
2880
+ }
2881
+
2882
+ /* Result Screens */
2883
+ .result-container {
2884
+ padding: 80px 40px 40px;
2885
+ text-align: center;
2886
+ color: white;
2887
+ animation: slideUp 0.4s ease-out;
2888
+ }
2889
+
2890
+ .result-icon {
2891
+ display: inline-flex;
2892
+ margin-bottom: 24px;
2893
+ }
2894
+
2895
+ .success-icon {
2896
+ color: #10b981;
2897
+ animation: scaleIn 0.6s ease-out;
2898
+ }
2899
+
2900
+ .success-icon svg circle {
2901
+ animation: scaleIn 0.5s ease-out;
2902
+ }
2903
+
2904
+ .success-icon svg path {
2905
+ stroke-dasharray: 100;
2906
+ stroke-dashoffset: 100;
2907
+ animation: checkmark 0.6s ease-out 0.3s forwards;
2908
+ }
2909
+
2910
+ .error-icon {
2911
+ color: #ef4444;
2912
+ animation: fadeIn 0.5s ease-out;
2913
+ }
2914
+
2915
+ .result-container h3 {
2916
+ font-size: 26px;
2917
+ font-weight: 700;
2918
+ margin: 0 0 12px 0;
2919
+ }
2920
+
2921
+ .result-container p {
2922
+ font-size: 15px;
2923
+ color: rgba(255, 255, 255, 0.7);
2924
+ margin: 0 0 32px 0;
2925
+ line-height: 1.5;
2926
+ }
2927
+
2928
+ .button-group {
2929
+ display: flex;
2930
+ gap: 12px;
2931
+ margin-top: 32px;
2932
+ }
2933
+
2934
+ .secondary-button {
2935
+ flex: 1;
2936
+ padding: 14px 24px;
2937
+ border-radius: 12px;
2938
+ border: 1px solid rgba(255, 255, 255, 0.2);
2939
+ background: rgba(255, 255, 255, 0.1);
2940
+ color: white;
2941
+ font-size: 15px;
2942
+ font-weight: 600;
2943
+ cursor: pointer;
2944
+ transition: all 0.2s;
2945
+ }
2946
+
2947
+ .secondary-button:hover {
2948
+ background: rgba(255, 255, 255, 0.15);
2949
+ border-color: rgba(255, 255, 255, 0.3);
2950
+ }
2951
+
2952
+ .ghost-button {
2953
+ flex: 1;
2954
+ padding: 14px 24px;
2955
+ border-radius: 12px;
2956
+ border: none;
2957
+ background: transparent;
2958
+ color: rgba(255, 255, 255, 0.7);
2959
+ font-size: 15px;
2960
+ font-weight: 600;
2961
+ cursor: pointer;
2962
+ transition: all 0.2s;
2963
+ }
2964
+
2965
+ .ghost-button:hover {
2966
+ color: white;
2967
+ background: rgba(255, 255, 255, 0.05);
2968
+ }
2969
+
2970
+ /* Canvas */
2971
+ canvas {
2972
+ display: none;
2973
+ }
2974
+
2975
+ /* Responsive */
2976
+ @media (max-width: 640px) {
2977
+ .modal-content {
2978
+ max-width: 100%;
2979
+ border-radius: 0;
2980
+ max-height: 100vh;
2981
+ }
2982
+
2983
+ .instructions-container {
2984
+ padding: 50px 24px 24px;
2985
+ }
2986
+
2987
+ .instructions-header h2 {
2988
+ font-size: 24px;
2989
+ }
2990
+
2991
+ .video-circle {
2992
+ width: 280px;
2993
+ height: 350px;
2994
+ }
2995
+
2996
+ .liveness-container {
2997
+ min-height: 100vh;
2998
+ }
2999
+ }
3000
+ ` }),
3001
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "modal-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "modal-content", children: [
3002
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "close-button", onClick: onClose, children: "×" }),
3003
+ state.status === "liveness" && renderLivenessDetection(),
3004
+ state.status === "processing" && renderProcessing(),
3005
+ state.status === "error" && renderError(),
3006
+ state.status === "success" && renderSuccess()
3007
+ ] }) }),
3008
+ /* @__PURE__ */ jsxRuntimeExports.jsx("canvas", { ref: canvasRef })
3009
+ ] });
3010
+ }
3011
+ class BiometricCaptureModal {
3012
+ constructor(options) {
3013
+ __publicField(this, "modal", null);
3014
+ __publicField(this, "video", null);
3015
+ __publicField(this, "canvas", null);
3016
+ __publicField(this, "stream", null);
3017
+ __publicField(this, "options");
3018
+ __publicField(this, "countdownInterval", null);
3019
+ __publicField(this, "isCapturing", false);
3020
+ this.options = options;
3021
+ }
3022
+ /**
3023
+ * Abre o modal de captura biométrica
3024
+ */
3025
+ async open() {
3026
+ try {
3027
+ await this.createModal();
3028
+ await this.initializeCamera();
3029
+ this.startCountdown();
3030
+ } catch (error) {
3031
+ this.options.onError(
3032
+ new NeoFaceError(
3033
+ "Erro ao inicializar câmera: " + error.message,
3034
+ ErrorType.CAMERA_ERROR
3035
+ )
3036
+ );
3037
+ }
3038
+ }
3039
+ /**
3040
+ * Fecha o modal e limpa recursos
3041
+ */
3042
+ close() {
3043
+ this.cleanup();
3044
+ if (this.modal) {
3045
+ document.body.removeChild(this.modal);
3046
+ this.modal = null;
3047
+ }
3048
+ }
3049
+ /**
3050
+ * Cria a estrutura HTML do modal
3051
+ */
3052
+ async createModal() {
3053
+ this.modal = document.createElement("div");
3054
+ this.modal.className = "neofaceid-biometric-modal";
3055
+ const title = this.options.title || this.getDefaultTitle();
3056
+ const subtitle = this.options.subtitle || this.getDefaultSubtitle();
3057
+ this.modal.innerHTML = `
3058
+ <div class="neofaceid-modal-overlay">
3059
+ <div class="neofaceid-modal-content">
3060
+ <div class="neofaceid-modal-header">
3061
+ <h2>${title}</h2>
3062
+ <button class="neofaceid-close-btn" type="button">&times;</button>
3063
+ </div>
3064
+ <div class="neofaceid-modal-body">
3065
+ <p class="neofaceid-subtitle">${subtitle}</p>
3066
+ <div class="neofaceid-camera-container">
3067
+ <video class="neofaceid-video" autoplay muted playsinline></video>
3068
+ <canvas class="neofaceid-canvas" style="display: none;"></canvas>
3069
+ <div class="neofaceid-overlay">
3070
+ <div class="neofaceid-frame"></div>
3071
+ <div class="neofaceid-countdown">
3072
+ <span class="neofaceid-countdown-number">${this.options.countdown || 3}</span>
3073
+ </div>
3074
+ </div>
3075
+ </div>
3076
+ <div class="neofaceid-status">
3077
+ <p class="neofaceid-status-text">Posicione-se na frente da câmera</p>
3078
+ </div>
3079
+ </div>
3080
+ <div class="neofaceid-modal-footer">
3081
+ <button class="neofaceid-cancel-btn" type="button">Cancelar</button>
3082
+ <button class="neofaceid-capture-btn" type="button" disabled>Capturar</button>
3083
+ </div>
3084
+ </div>
3085
+ </div>
3086
+ `;
3087
+ this.addStyles();
3088
+ this.addEventListeners();
3089
+ document.body.appendChild(this.modal);
3090
+ }
3091
+ /**
3092
+ * Inicializa a câmera
3093
+ */
3094
+ async initializeCamera() {
3095
+ if (!this.modal)
3096
+ throw new Error("Modal não inicializado");
3097
+ this.video = this.modal.querySelector(".neofaceid-video");
3098
+ this.canvas = this.modal.querySelector(".neofaceid-canvas");
3099
+ if (!this.video || !this.canvas) {
3100
+ throw new Error("Elementos de vídeo ou canvas não encontrados");
3101
+ }
3102
+ try {
3103
+ this.stream = await navigator.mediaDevices.getUserMedia({
3104
+ video: {
3105
+ width: { ideal: 640 },
3106
+ height: { ideal: 480 },
3107
+ facingMode: "user"
3108
+ },
3109
+ audio: false
3110
+ });
3111
+ this.video.srcObject = this.stream;
3112
+ await this.video.play();
3113
+ this.canvas.width = this.video.videoWidth;
3114
+ this.canvas.height = this.video.videoHeight;
3115
+ } catch (error) {
3116
+ throw new Error("Não foi possível acessar a câmera: " + error.message);
3117
+ }
3118
+ }
3119
+ /**
3120
+ * Inicia a contagem regressiva
3121
+ */
3122
+ startCountdown() {
3123
+ if (!this.modal)
3124
+ return;
3125
+ const countdownElement = this.modal.querySelector(".neofaceid-countdown-number");
3126
+ const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3127
+ const statusText = this.modal.querySelector(".neofaceid-status-text");
3128
+ let count = this.options.countdown || 3;
3129
+ if (countdownElement) {
3130
+ countdownElement.textContent = count.toString();
3131
+ }
3132
+ this.countdownInterval = window.setInterval(() => {
3133
+ count--;
3134
+ if (countdownElement) {
3135
+ countdownElement.textContent = count.toString();
3136
+ }
3137
+ if (count <= 0) {
3138
+ if (this.countdownInterval) {
3139
+ clearInterval(this.countdownInterval);
3140
+ this.countdownInterval = null;
3141
+ }
3142
+ if (countdownElement) {
3143
+ countdownElement.style.display = "none";
3144
+ }
3145
+ if (captureBtn) {
3146
+ captureBtn.disabled = false;
3147
+ captureBtn.textContent = "Capturar Agora";
3148
+ }
3149
+ if (statusText) {
3150
+ statusText.textContent = "Pronto para capturar!";
3151
+ }
3152
+ if (this.options.mode === "auto") {
3153
+ setTimeout(() => this.captureImage(), 500);
3154
+ }
3155
+ }
3156
+ }, 1e3);
3157
+ }
3158
+ /**
3159
+ * Captura a imagem da câmera
3160
+ */
3161
+ async captureImage() {
3162
+ if (this.isCapturing || !this.video || !this.canvas)
3163
+ return;
3164
+ this.isCapturing = true;
3165
+ try {
3166
+ const context = this.canvas.getContext("2d");
3167
+ if (!context)
3168
+ throw new Error("Não foi possível obter contexto do canvas");
3169
+ context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
3170
+ const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
3171
+ const base64Data = imageData.split(",")[1];
3172
+ let detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
3173
+ this.close();
3174
+ this.options.onSuccess(base64Data, detectedType);
3175
+ } catch (error) {
3176
+ this.options.onError(
3177
+ new NeoFaceError(
3178
+ "Erro ao capturar imagem: " + error.message,
3179
+ ErrorType.CAPTURE_ERROR
3180
+ )
3181
+ );
3182
+ } finally {
3183
+ this.isCapturing = false;
3184
+ }
3185
+ }
3186
+ /**
3187
+ * Detecta o tipo biométrico na imagem (face ou mão)
3188
+ */
3189
+ async detectBiometricType(imageData) {
3190
+ try {
3191
+ const { detectBiometricType: detectBiometricType2 } = await Promise.resolve().then(() => biometricDetection);
3192
+ const result = await detectBiometricType2(imageData);
3193
+ if (result.type !== "unknown" && result.confidence > 0.6) {
3194
+ return result.type;
3195
+ }
3196
+ return "face";
3197
+ } catch (error) {
3198
+ console.warn("Erro na detecção automática, usando face como padrão:", error);
3199
+ return "face";
3200
+ }
3201
+ }
3202
+ /**
3203
+ * Adiciona event listeners aos elementos do modal
3204
+ */
3205
+ addEventListeners() {
3206
+ if (!this.modal)
3207
+ return;
3208
+ const closeBtn = this.modal.querySelector(".neofaceid-close-btn");
3209
+ const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
3210
+ const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
3211
+ closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
3212
+ var _a, _b;
3213
+ this.close();
3214
+ (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3215
+ });
3216
+ cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
3217
+ var _a, _b;
3218
+ this.close();
3219
+ (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a);
3220
+ });
3221
+ captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
3222
+ this.captureImage();
3223
+ });
3224
+ this.modal.addEventListener("click", (e) => {
3225
+ var _a, _b, _c;
3226
+ if (e.target === ((_a = this.modal) == null ? void 0 : _a.querySelector(".neofaceid-modal-overlay"))) {
3227
+ this.close();
3228
+ (_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
3229
+ }
3230
+ });
3231
+ }
3232
+ /**
3233
+ * Limpa recursos (câmera, intervalos, etc.)
3234
+ */
3235
+ cleanup() {
3236
+ if (this.stream) {
3237
+ this.stream.getTracks().forEach((track) => track.stop());
3238
+ this.stream = null;
3239
+ }
3240
+ if (this.countdownInterval) {
3241
+ clearInterval(this.countdownInterval);
3242
+ this.countdownInterval = null;
3243
+ }
3244
+ this.video = null;
3245
+ this.canvas = null;
3246
+ this.isCapturing = false;
3247
+ }
3248
+ /**
3249
+ * Retorna o título padrão baseado no modo
3250
+ */
3251
+ getDefaultTitle() {
3252
+ switch (this.options.mode) {
3253
+ case "face":
3254
+ return "Autenticação Facial";
3255
+ case "hand":
3256
+ return "Autenticação por Mão";
3257
+ case "auto":
3258
+ return "Autenticação Biométrica";
3259
+ default:
3260
+ return "Captura Biométrica";
3261
+ }
3262
+ }
3263
+ /**
3264
+ * Retorna o subtítulo padrão baseado no modo
3265
+ */
3266
+ getDefaultSubtitle() {
3267
+ switch (this.options.mode) {
3268
+ case "face":
3269
+ return "Posicione seu rosto dentro do quadro e aguarde a captura automática.";
3270
+ case "hand":
3271
+ return "Posicione sua mão dentro do quadro e aguarde a captura automática.";
3272
+ case "auto":
3273
+ return "Posicione seu rosto ou mão dentro do quadro para autenticação automática.";
3274
+ default:
3275
+ return "Posicione-se dentro do quadro para captura.";
3276
+ }
3277
+ }
3278
+ /**
3279
+ * Adiciona estilos CSS ao modal
3280
+ */
3281
+ addStyles() {
3282
+ const styleId = "neofaceid-biometric-modal-styles";
3283
+ const existingStyles = document.getElementById(styleId);
3284
+ if (existingStyles) {
3285
+ existingStyles.remove();
3286
+ }
3287
+ const style = document.createElement("style");
3288
+ style.id = styleId;
3289
+ style.textContent = `
3290
+ .neofaceid-biometric-modal {
3291
+ position: fixed;
3292
+ top: 0;
3293
+ left: 0;
3294
+ width: 100%;
3295
+ height: 100%;
3296
+ z-index: 10000;
3297
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3298
+ }
3299
+
3300
+ .neofaceid-modal-overlay {
3301
+ position: absolute;
3302
+ top: 0;
3303
+ left: 0;
3304
+ width: 100%;
3305
+ height: 100%;
3306
+ background: rgba(0, 0, 0, 0.8);
3307
+ display: flex;
3308
+ align-items: center;
3309
+ justify-content: center;
3310
+ padding: 20px;
3311
+ box-sizing: border-box;
3312
+ }
3313
+
3314
+ .neofaceid-modal-content {
3315
+ background: white;
3316
+ border-radius: 12px;
3317
+ max-width: 600px;
3318
+ width: 100%;
3319
+ max-height: 90vh;
3320
+ overflow: hidden;
3321
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
3322
+ }
3323
+
3324
+ .neofaceid-modal-header {
3325
+ padding: 20px;
3326
+ border-bottom: 1px solid #e0e0e0;
3327
+ display: flex;
3328
+ justify-content: space-between;
3329
+ align-items: center;
3330
+ }
3331
+
3332
+ .neofaceid-modal-header h2 {
3333
+ margin: 0;
3334
+ font-size: 24px;
3335
+ font-weight: 600;
3336
+ color: #333;
3337
+ }
3338
+
3339
+ .neofaceid-close-btn {
3340
+ background: none;
3341
+ border: none;
3342
+ font-size: 28px;
3343
+ cursor: pointer;
3344
+ color: #666;
3345
+ padding: 0;
3346
+ width: 32px;
3347
+ height: 32px;
3348
+ display: flex;
3349
+ align-items: center;
3350
+ justify-content: center;
3351
+ border-radius: 50%;
3352
+ transition: background-color 0.2s;
3353
+ }
3354
+
3355
+ .neofaceid-close-btn:hover {
3356
+ background-color: #f0f0f0;
3357
+ }
3358
+
3359
+ .neofaceid-modal-body {
3360
+ padding: 20px;
3361
+ }
3362
+
3363
+ .neofaceid-subtitle {
3364
+ margin: 0 0 20px 0;
3365
+ color: #666;
3366
+ font-size: 16px;
3367
+ line-height: 1.4;
3368
+ }
3369
+
3370
+ .neofaceid-camera-container {
3371
+ position: relative;
3372
+ background: #000;
3373
+ border-radius: 8px;
3374
+ overflow: hidden;
3375
+ aspect-ratio: 4/3;
3376
+ margin-bottom: 20px;
3377
+ }
3378
+
3379
+ .neofaceid-video {
3380
+ width: 100%;
3381
+ height: 100%;
3382
+ object-fit: cover;
3383
+ }
3384
+
3385
+ .neofaceid-canvas {
3386
+ position: absolute;
3387
+ top: 0;
3388
+ left: 0;
3389
+ }
3390
+
3391
+ .neofaceid-overlay {
3392
+ position: absolute;
3393
+ top: 0;
3394
+ left: 0;
3395
+ width: 100%;
3396
+ height: 100%;
3397
+ display: flex;
3398
+ align-items: center;
3399
+ justify-content: center;
3400
+ }
3401
+
3402
+ .neofaceid-frame {
3403
+ width: 200px;
3404
+ height: 200px;
3405
+ border: 3px solid #4CAF50;
3406
+ border-radius: 50%;
3407
+ box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
3408
+ animation: pulse 2s infinite;
3409
+ }
3410
+
3411
+ .neofaceid-countdown {
3412
+ position: absolute;
3413
+ top: 20px;
3414
+ right: 20px;
3415
+ background: rgba(0, 0, 0, 0.7);
3416
+ color: white;
3417
+ padding: 10px;
3418
+ border-radius: 50%;
3419
+ width: 50px;
3420
+ height: 50px;
3421
+ display: flex;
3422
+ align-items: center;
3423
+ justify-content: center;
3424
+ }
3425
+
3426
+ .neofaceid-countdown-number {
3427
+ font-size: 24px;
3428
+ font-weight: bold;
3429
+ }
3430
+
3431
+ .neofaceid-status {
3432
+ text-align: center;
3433
+ margin-bottom: 20px;
3434
+ }
3435
+
3436
+ .neofaceid-status-text {
3437
+ margin: 0;
3438
+ color: #666;
3439
+ font-size: 16px;
3440
+ }
3441
+
3442
+ .neofaceid-modal-footer {
3443
+ padding: 20px;
3444
+ border-top: 1px solid #e0e0e0;
3445
+ display: flex;
3446
+ gap: 12px;
3447
+ justify-content: flex-end;
3448
+ }
3449
+
3450
+ .neofaceid-cancel-btn,
3451
+ .neofaceid-capture-btn {
3452
+ padding: 12px 24px;
3453
+ border: none;
3454
+ border-radius: 6px;
3455
+ font-size: 16px;
3456
+ font-weight: 500;
3457
+ cursor: pointer;
3458
+ transition: all 0.2s;
3459
+ }
3460
+
3461
+ .neofaceid-cancel-btn {
3462
+ background: #f5f5f5;
3463
+ color: #666;
3464
+ }
3465
+
3466
+ .neofaceid-cancel-btn:hover {
3467
+ background: #e0e0e0;
3468
+ }
3469
+
3470
+ .neofaceid-capture-btn {
3471
+ background: #4CAF50;
3472
+ color: white;
3473
+ }
3474
+
3475
+ .neofaceid-capture-btn:hover:not(:disabled) {
3476
+ background: #45a049;
3477
+ }
3478
+
3479
+ .neofaceid-capture-btn:disabled {
3480
+ background: #ccc;
3481
+ cursor: not-allowed;
3482
+ }
3483
+
3484
+ @keyframes pulse {
3485
+ 0% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
3486
+ 50% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0.1); }
3487
+ 100% { box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); }
3488
+ }
3489
+
3490
+ @media (max-width: 640px) {
3491
+ .neofaceid-modal-overlay {
3492
+ padding: 10px;
3493
+ }
3494
+
3495
+ .neofaceid-modal-header,
3496
+ .neofaceid-modal-body,
3497
+ .neofaceid-modal-footer {
3498
+ padding: 15px;
3499
+ }
3500
+
3501
+ .neofaceid-frame {
3502
+ width: 150px;
3503
+ height: 150px;
3504
+ }
3505
+ }
3506
+ `;
3507
+ document.head.appendChild(style);
3508
+ }
3509
+ }
3510
+ async function startFaceLogin(options) {
3511
+ try {
3512
+ const isValidToken = await validateToken(options.applicationToken);
3513
+ if (!isValidToken) {
3514
+ options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3515
+ return;
3516
+ }
3517
+ const captureOptions = {
3518
+ mode: "face",
3519
+ onSuccess: async (imageData, _detectedType) => {
3520
+ try {
3521
+ const imageBlob = await fetch(imageData).then((res) => res.blob());
3522
+ const result = await loginWithBiometric(imageBlob, options.applicationToken);
3523
+ options.onSuccess(result);
3524
+ } catch (error) {
3525
+ options.onError(error);
3526
+ }
3527
+ },
3528
+ onError: options.onError,
3529
+ onCancel: options.onCancel,
3530
+ countdown: options.countdown,
3531
+ title: options.title || "Login Facial",
3532
+ subtitle: options.subtitle || "Posicione seu rosto dentro do quadro para fazer login."
3533
+ };
3534
+ const modal = new BiometricCaptureModal(captureOptions);
3535
+ await modal.open();
3536
+ } catch (error) {
3537
+ options.onError(
3538
+ new NeoFaceError(
3539
+ "Erro ao inicializar login facial: " + error.message,
3540
+ ErrorType.INITIALIZATION_ERROR
3541
+ )
3542
+ );
3543
+ }
3544
+ }
3545
+ async function detectBiometricType(imageData) {
3546
+ try {
3547
+ const img = await loadImageFromBase64(imageData);
3548
+ const faceResult = await detectFace(img);
3549
+ if (faceResult.confidence > 0.7) {
3550
+ return {
3551
+ type: "face",
3552
+ confidence: faceResult.confidence,
3553
+ details: faceResult.details
3554
+ };
3555
+ }
3556
+ const handResult = await detectHand(img);
3557
+ if (handResult.confidence > 0.6) {
3558
+ return {
3559
+ type: "hand",
3560
+ confidence: handResult.confidence,
3561
+ details: handResult.details
3562
+ };
3563
+ }
3564
+ return {
3565
+ type: "unknown",
3566
+ confidence: Math.max(faceResult.confidence, handResult.confidence)
3567
+ };
3568
+ } catch (error) {
3569
+ console.warn("Erro na detecção biométrica:", error);
3570
+ return {
3571
+ type: "face",
3572
+ confidence: 0.5
3573
+ };
3574
+ }
3575
+ }
3576
+ async function detectFace(img) {
3577
+ try {
3578
+ if (typeof window !== "undefined" && window.faceapi) {
3579
+ const faceapi2 = window.faceapi;
3580
+ await loadFaceApiModels();
3581
+ const detections = await faceapi2.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors();
3582
+ if (detections && detections.length > 0) {
3583
+ const bestDetection = detections.reduce(
3584
+ (best, current) => current.detection.score > best.detection.score ? current : best
3585
+ );
3586
+ return {
3587
+ confidence: bestDetection.detection.score,
3588
+ details: {
3589
+ faces: detections.length,
3590
+ landmarks: bestDetection.landmarks,
3591
+ box: bestDetection.detection.box
3592
+ }
3593
+ };
3594
+ }
3595
+ }
3596
+ return { confidence: 0 };
3597
+ } catch (error) {
3598
+ console.warn("Erro na detecção facial:", error);
3599
+ return { confidence: 0 };
3600
+ }
3601
+ }
3602
+ async function detectHand(img) {
3603
+ try {
3604
+ const canvas = document.createElement("canvas");
3605
+ const ctx = canvas.getContext("2d");
3606
+ if (!ctx) {
3607
+ return { confidence: 0 };
3608
+ }
3609
+ canvas.width = img.width;
3610
+ canvas.height = img.height;
3611
+ ctx.drawImage(img, 0, 0);
3612
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
3613
+ const handFeatures = analyzeHandFeatures(imageData);
3614
+ return {
3615
+ confidence: handFeatures.confidence,
3616
+ details: handFeatures
3617
+ };
3618
+ } catch (error) {
3619
+ console.warn("Erro na detecção de mão:", error);
3620
+ return { confidence: 0 };
3621
+ }
3622
+ }
3623
+ function analyzeHandFeatures(imageData) {
3624
+ const { data, width, height } = imageData;
3625
+ let skinPixels = 0;
3626
+ let totalPixels = 0;
3627
+ for (let i = 0; i < data.length; i += 4) {
3628
+ const r = data[i];
3629
+ const g = data[i + 1];
3630
+ const b = data[i + 2];
3631
+ if (isSkinColor(r, g, b)) {
3632
+ skinPixels++;
3633
+ }
3634
+ totalPixels++;
3635
+ }
3636
+ const skinRatio = skinPixels / totalPixels;
3637
+ const aspectRatio = width / height;
3638
+ const isHandAspectRatio = aspectRatio > 0.6 && aspectRatio < 1.8;
3639
+ let confidence = 0;
3640
+ if (skinRatio > 0.15 && skinRatio < 0.7) {
3641
+ confidence += 0.4 * (skinRatio / 0.7);
3642
+ }
3643
+ if (isHandAspectRatio) {
3644
+ confidence += 0.3;
3645
+ }
3646
+ const imageSize = width * height;
3647
+ if (imageSize > 1e4 && imageSize < 5e5) {
3648
+ confidence += 0.3;
3649
+ }
3650
+ return {
3651
+ confidence: Math.min(confidence, 0.8),
3652
+ // Máximo 80% para detecção básica
3653
+ skinRatio,
3654
+ aspectRatio,
3655
+ imageSize,
3656
+ skinPixels,
3657
+ totalPixels
3658
+ };
3659
+ }
3660
+ function isSkinColor(r, g, b) {
3661
+ const y = 0.299 * r + 0.587 * g + 0.114 * b;
3662
+ const cb = -0.169 * r - 0.331 * g + 0.5 * b + 128;
3663
+ const cr = 0.5 * r - 0.419 * g - 0.081 * b + 128;
3664
+ return y > 80 && y < 255 && cb > 85 && cb < 135 && cr > 135 && cr < 180;
3665
+ }
3666
+ function loadImageFromBase64(base64Data) {
3667
+ return new Promise((resolve, reject) => {
3668
+ const img = new Image();
3669
+ img.onload = () => resolve(img);
3670
+ img.onerror = () => reject(new Error("Erro ao carregar imagem"));
3671
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:image/jpeg;base64,${base64Data}`;
3672
+ img.src = dataUrl;
3673
+ });
3674
+ }
3675
+ async function loadFaceApiModels() {
3676
+ if (typeof window === "undefined" || !window.faceapi) {
3677
+ return;
3678
+ }
3679
+ const faceapi2 = window.faceapi;
3680
+ if (faceapi2.nets.tinyFaceDetector.isLoaded) {
3681
+ return;
3682
+ }
3683
+ try {
3684
+ await Promise.all([
3685
+ faceapi2.nets.tinyFaceDetector.loadFromUri("./models"),
3686
+ faceapi2.nets.faceLandmark68Net.loadFromUri("./models"),
3687
+ faceapi2.nets.faceRecognitionNet.loadFromUri("./models")
3688
+ ]);
3689
+ } catch (error) {
3690
+ console.warn("Não foi possível carregar modelos do face-api.js:", error);
3691
+ }
3692
+ }
3693
+ async function initializeBiometricDetection() {
3694
+ try {
3695
+ if (typeof window !== "undefined") {
3696
+ await loadFaceApiModels();
3697
+ }
3698
+ } catch (error) {
3699
+ console.warn("Inicialização da detecção biométrica com limitações:", error);
3700
+ }
3701
+ }
3702
+ function isAdvancedDetectionAvailable() {
3703
+ return typeof window !== "undefined" && window.faceapi && window.faceapi.nets.tinyFaceDetector.isLoaded;
3704
+ }
3705
+ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
3706
+ __proto__: null,
3707
+ detectBiometricType,
3708
+ initializeBiometricDetection,
3709
+ isAdvancedDetectionAvailable
3710
+ }, Symbol.toStringTag, { value: "Module" }));
3711
+ const VERSION = "1.1.0";
3712
+ const RELEASE_DATE = "2025-11-01";
3713
+ function start(applicationToken, callbacks) {
3714
+ const container = document.createElement("div");
3715
+ container.id = "neoface-modal-container";
3716
+ document.body.appendChild(container);
3717
+ const cleanup = () => {
3718
+ if (document.body.contains(container)) {
3719
+ document.body.removeChild(container);
3720
+ }
3721
+ };
3722
+ validateToken(applicationToken).then(() => {
3723
+ const modalElement = require$$0$1.createElement(FaceCaptureModal, {
3724
+ accessToken: applicationToken,
3725
+ onClose: cleanup,
3726
+ onSuccess: (result) => {
3727
+ cleanup();
3728
+ callbacks.onSuccess({
3729
+ name: result.data.faceId,
3730
+ email: result.data.faceId,
3731
+ documentId: result.data.faceId
3732
+ });
3733
+ }
3734
+ });
3735
+ const root = createRoot(container);
3736
+ root.render(modalElement);
3737
+ }).catch((error) => {
3738
+ cleanup();
3739
+ callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
3740
+ });
3741
+ }
3742
+ function startBiometricRegistration(personData, applicationToken, callbacks, options) {
3743
+ const container = document.createElement("div");
3744
+ container.id = "neoface-biometric-registration-container";
3745
+ document.body.appendChild(container);
3746
+ const cleanup = () => {
3747
+ if (document.body.contains(container)) {
3748
+ document.body.removeChild(container);
3749
+ }
3750
+ };
3751
+ validateToken(applicationToken).then(() => {
3752
+ const modalElement = require$$0$1.createElement(BiometricRegistrationModal, {
3753
+ personData,
3754
+ applicationToken,
3755
+ useRealApi: (options == null ? void 0 : options.useRealApi) || false,
3756
+ // 🚀 Passa a flag para o modal
3757
+ onClose: cleanup,
3758
+ onSuccess: (result) => {
3759
+ cleanup();
3760
+ callbacks.onSuccess(result);
3761
+ },
3762
+ onError: (error) => {
3763
+ cleanup();
3764
+ callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
1498
3765
  }
1499
3766
  });
1500
3767
  const root = createRoot(container);
@@ -1505,13 +3772,26 @@ function start(applicationToken, callbacks) {
1505
3772
  });
1506
3773
  }
1507
3774
  export {
3775
+ BiometricCaptureModal,
3776
+ BiometricRegistrationModal,
1508
3777
  ErrorType,
1509
3778
  FaceCaptureModal,
1510
3779
  NeoFaceError,
1511
3780
  RELEASE_DATE,
1512
3781
  VERSION,
3782
+ detectBiometricType,
3783
+ initializeBiometricDetection,
3784
+ isAdvancedDetectionAvailable,
3785
+ loginWithBiometric,
1513
3786
  recognize,
3787
+ recognizeBiometric,
3788
+ recognizeByPurpose,
3789
+ registerPersonWithBiometric,
3790
+ registerPersonWithoutFace,
3791
+ simpleIdentification,
1514
3792
  start,
3793
+ startBiometricRegistration,
3794
+ startFaceLogin,
1515
3795
  validateToken
1516
3796
  };
1517
3797
  //# sourceMappingURL=neoface-id-sdk.es.js.map