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