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