@neofaceid/web-sdk 1.4.0 → 1.6.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.
|
@@ -4,6 +4,7 @@ var __publicField = (obj, key, value) => {
|
|
|
4
4
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
|
+
var _a;
|
|
7
8
|
import require$$0$1, { useRef, useState, useCallback, useReducer, useEffect } from "react";
|
|
8
9
|
import require$$0 from "react-dom";
|
|
9
10
|
import * as faceapi from "face-api.js";
|
|
@@ -1006,7 +1007,7 @@ const useCamera = () => {
|
|
|
1006
1007
|
};
|
|
1007
1008
|
const startCamera = useCallback(
|
|
1008
1009
|
async (constraints) => {
|
|
1009
|
-
var
|
|
1010
|
+
var _a2;
|
|
1010
1011
|
console.log("🎬 [useCamera] startCamera chamado", {
|
|
1011
1012
|
hasExistingStream: !!streamRef.current,
|
|
1012
1013
|
isInitializing: isInitializingRef.current
|
|
@@ -1048,7 +1049,7 @@ const useCamera = () => {
|
|
|
1048
1049
|
console.log("📋 [useCamera] Detalhes do erro:", {
|
|
1049
1050
|
name: error.name,
|
|
1050
1051
|
message: error.message,
|
|
1051
|
-
stack: (
|
|
1052
|
+
stack: (_a2 = error.stack) == null ? void 0 : _a2.substring(0, 200)
|
|
1052
1053
|
});
|
|
1053
1054
|
if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
|
|
1054
1055
|
throw new CameraPermissionDenied(
|
|
@@ -1117,6 +1118,7 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
|
1117
1118
|
ErrorType2["PERSON_NOT_FOUND"] = "PersonNotFoundError";
|
|
1118
1119
|
ErrorType2["INITIALIZATION_ERROR"] = "InitializationError";
|
|
1119
1120
|
ErrorType2["CAMERA_ERROR"] = "CameraError";
|
|
1121
|
+
ErrorType2["NO_CAMERA"] = "NoCameraError";
|
|
1120
1122
|
ErrorType2["CAPTURE_ERROR"] = "CaptureError";
|
|
1121
1123
|
ErrorType2["NOT_FOUND"] = "NotFoundError";
|
|
1122
1124
|
ErrorType2["UNKNOWN"] = "UnknownError";
|
|
@@ -1135,10 +1137,11 @@ class NeoFaceError extends Error {
|
|
|
1135
1137
|
this.type = type;
|
|
1136
1138
|
}
|
|
1137
1139
|
}
|
|
1138
|
-
const API_BASE_URL = "
|
|
1140
|
+
const API_BASE_URL = ((_a = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a.VITE_API_BASE_URL) || "http://localhost:8000";
|
|
1139
1141
|
const REQUEST_TIMEOUT = 1e4;
|
|
1140
1142
|
const ensureSecureContext = () => {
|
|
1141
|
-
|
|
1143
|
+
const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "";
|
|
1144
|
+
if (!window.isSecureContext && !isLocalhost) {
|
|
1142
1145
|
throw new NeoFaceError(
|
|
1143
1146
|
"Secure context (HTTPS) is required for this operation",
|
|
1144
1147
|
ErrorType.NETWORK
|
|
@@ -1184,7 +1187,7 @@ const validateToken = async (applicationToken) => {
|
|
|
1184
1187
|
ensureSecureContext();
|
|
1185
1188
|
const controller = createTimeoutController();
|
|
1186
1189
|
try {
|
|
1187
|
-
const response = await fetch(`${API_BASE_URL}/api/v1/
|
|
1190
|
+
const response = await fetch(`${API_BASE_URL}/api/v1/auth/application/validate_token/`, {
|
|
1188
1191
|
method: "POST",
|
|
1189
1192
|
headers: {
|
|
1190
1193
|
"Content-Type": "application/json",
|
|
@@ -1444,7 +1447,7 @@ const recognize = async (image, applicationToken) => {
|
|
|
1444
1447
|
}
|
|
1445
1448
|
};
|
|
1446
1449
|
const loginWithBiometric$1 = async (image, applicationToken) => {
|
|
1447
|
-
var
|
|
1450
|
+
var _a2, _b, _c, _d, _e;
|
|
1448
1451
|
ensureSecureContext();
|
|
1449
1452
|
const controller = createTimeoutController();
|
|
1450
1453
|
const compressedImage = await compressImage(image);
|
|
@@ -1474,7 +1477,7 @@ const loginWithBiometric$1 = async (image, applicationToken) => {
|
|
|
1474
1477
|
accessToken: data.accessToken,
|
|
1475
1478
|
alias: data.alias,
|
|
1476
1479
|
user: {
|
|
1477
|
-
id: ((
|
|
1480
|
+
id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
|
|
1478
1481
|
email: ((_b = data.user) == null ? void 0 : _b.email) || "",
|
|
1479
1482
|
role: ((_c = data.user) == null ? void 0 : _c.role) || "",
|
|
1480
1483
|
validated: ((_d = data.user) == null ? void 0 : _d.validated) || false,
|
|
@@ -1526,7 +1529,7 @@ const recognizeBiometric = async (image, applicationToken, livenessCheck = true,
|
|
|
1526
1529
|
const data = await response.json();
|
|
1527
1530
|
return {
|
|
1528
1531
|
success: data.success,
|
|
1529
|
-
personId
|
|
1532
|
+
// personId is intentionally NOT returned for security reasons
|
|
1530
1533
|
confidenceScore: data.confidence_score,
|
|
1531
1534
|
accessToken: data.access_token,
|
|
1532
1535
|
payload: data.payload
|
|
@@ -1590,7 +1593,8 @@ const simpleIdentification = async (documentType, documentNumber, applicationTok
|
|
|
1590
1593
|
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
|
|
1591
1594
|
}
|
|
1592
1595
|
};
|
|
1593
|
-
const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8) => {
|
|
1596
|
+
const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8, signature, sessionData) => {
|
|
1597
|
+
var _a2;
|
|
1594
1598
|
ensureSecureContext();
|
|
1595
1599
|
const controller = createTimeoutController();
|
|
1596
1600
|
const compressedImage = await compressImage(image);
|
|
@@ -1604,18 +1608,22 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
|
|
|
1604
1608
|
reader.readAsDataURL(compressedImage);
|
|
1605
1609
|
});
|
|
1606
1610
|
try {
|
|
1611
|
+
const requestBody = {
|
|
1612
|
+
biometric_data: base64Image,
|
|
1613
|
+
type_of_identification: "FACE",
|
|
1614
|
+
purpose,
|
|
1615
|
+
confidence_threshold: confidenceThreshold
|
|
1616
|
+
};
|
|
1617
|
+
if (signature) {
|
|
1618
|
+
requestBody.signature = signature;
|
|
1619
|
+
}
|
|
1607
1620
|
const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/purpose/`, {
|
|
1608
1621
|
method: "POST",
|
|
1609
1622
|
headers: {
|
|
1610
1623
|
"Content-Type": "application/json",
|
|
1611
1624
|
"X-App-Token": applicationToken
|
|
1612
1625
|
},
|
|
1613
|
-
body: JSON.stringify(
|
|
1614
|
-
biometric_data: base64Image,
|
|
1615
|
-
type_of_identification: "FACE",
|
|
1616
|
-
purpose,
|
|
1617
|
-
confidence_threshold: confidenceThreshold
|
|
1618
|
-
}),
|
|
1626
|
+
body: JSON.stringify(requestBody),
|
|
1619
1627
|
signal: controller.signal
|
|
1620
1628
|
});
|
|
1621
1629
|
if (!response.ok) {
|
|
@@ -1627,10 +1635,16 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
|
|
|
1627
1635
|
const data = await response.json();
|
|
1628
1636
|
return {
|
|
1629
1637
|
success: data.success,
|
|
1630
|
-
|
|
1638
|
+
personName: data.person_name || ((_a2 = data.person) == null ? void 0 : _a2.name),
|
|
1639
|
+
email: data.email || (sessionData == null ? void 0 : sessionData.email),
|
|
1640
|
+
cpf: data.cpf || (sessionData == null ? void 0 : sessionData.cpf),
|
|
1631
1641
|
confidenceScore: data.confidence_score,
|
|
1632
1642
|
accessToken: data.access_token,
|
|
1633
|
-
payload: data.payload
|
|
1643
|
+
payload: data.payload,
|
|
1644
|
+
// Return signature and sessionId for external integrations
|
|
1645
|
+
signature,
|
|
1646
|
+
sessionId: sessionData == null ? void 0 : sessionData.sessionId
|
|
1647
|
+
// Note: personId is intentionally NOT returned for security reasons
|
|
1634
1648
|
};
|
|
1635
1649
|
} catch (error) {
|
|
1636
1650
|
if (error instanceof Error) {
|
|
@@ -1687,6 +1701,7 @@ const registerPersonWithoutFace = async (personData, applicationToken) => {
|
|
|
1687
1701
|
}
|
|
1688
1702
|
};
|
|
1689
1703
|
const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
|
|
1704
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h;
|
|
1690
1705
|
ensureSecureContext();
|
|
1691
1706
|
if (!facePhotos || facePhotos.length === 0) {
|
|
1692
1707
|
throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION_ERROR);
|
|
@@ -1733,29 +1748,56 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
|
|
|
1733
1748
|
}
|
|
1734
1749
|
try {
|
|
1735
1750
|
const errorData = await response.json();
|
|
1751
|
+
if (errorData.errors) {
|
|
1752
|
+
const errorMessages = Object.entries(errorData.errors).map(([field, messages]) => {
|
|
1753
|
+
const fieldName = field === "password" ? "senha" : field === "password_confirm" ? "confirmação de senha" : field;
|
|
1754
|
+
const messageList = Array.isArray(messages) ? messages.join(", ") : String(messages);
|
|
1755
|
+
return `${fieldName}: ${messageList}`;
|
|
1756
|
+
}).join("; ");
|
|
1757
|
+
throw new NeoFaceError(`Erro de validação: ${errorMessages}`, ErrorType.VALIDATION_ERROR);
|
|
1758
|
+
}
|
|
1736
1759
|
if (errorData.error === "fraud_detected") {
|
|
1737
1760
|
throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION_ERROR);
|
|
1738
1761
|
}
|
|
1739
|
-
if (errorData.error === "user_exists") {
|
|
1762
|
+
if (errorData.error === "user_exists" || errorData.error_code === "ALREADY_EXISTS") {
|
|
1740
1763
|
throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION_ERROR);
|
|
1741
1764
|
}
|
|
1742
1765
|
if (errorData.error === "person_exists") {
|
|
1743
1766
|
throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION_ERROR);
|
|
1744
1767
|
}
|
|
1745
|
-
|
|
1768
|
+
const errorMessage = errorData.message || errorData.error || `Server returned status ${response.status}`;
|
|
1769
|
+
throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
|
|
1746
1770
|
} catch (parseError) {
|
|
1771
|
+
if (parseError instanceof NeoFaceError) {
|
|
1772
|
+
throw parseError;
|
|
1773
|
+
}
|
|
1747
1774
|
throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
|
|
1748
1775
|
}
|
|
1749
1776
|
}
|
|
1750
1777
|
const data = await response.json();
|
|
1778
|
+
if (response.status === 202 && ((_a2 = data.data) == null ? void 0 : _a2.task_id)) {
|
|
1779
|
+
return {
|
|
1780
|
+
success: data.success,
|
|
1781
|
+
message: data.message || "Registration is being processed. You will receive an email when completed.",
|
|
1782
|
+
task_id: data.data.task_id,
|
|
1783
|
+
status: data.data.status || "processing",
|
|
1784
|
+
person_id: void 0,
|
|
1785
|
+
user_id: void 0,
|
|
1786
|
+
person_validated: void 0,
|
|
1787
|
+
face_processing: void 0,
|
|
1788
|
+
onboarding_link: void 0
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1751
1791
|
return {
|
|
1752
1792
|
success: data.success,
|
|
1753
1793
|
message: data.message,
|
|
1754
|
-
person_id: data.person_id,
|
|
1755
|
-
user_id: data.user_id,
|
|
1756
|
-
person_validated: data.person_validated,
|
|
1757
|
-
face_processing: data.face_processing,
|
|
1758
|
-
onboarding_link: data.onboarding_link
|
|
1794
|
+
person_id: ((_b = data.data) == null ? void 0 : _b.person_id) || data.person_id,
|
|
1795
|
+
user_id: ((_c = data.data) == null ? void 0 : _c.user_id) || data.user_id,
|
|
1796
|
+
person_validated: ((_d = data.data) == null ? void 0 : _d.person_validated) || data.person_validated,
|
|
1797
|
+
face_processing: ((_e = data.data) == null ? void 0 : _e.face_processing) || data.face_processing,
|
|
1798
|
+
onboarding_link: ((_f = data.data) == null ? void 0 : _f.onboarding_link) || data.onboarding_link,
|
|
1799
|
+
task_id: (_g = data.data) == null ? void 0 : _g.task_id,
|
|
1800
|
+
status: (_h = data.data) == null ? void 0 : _h.status
|
|
1759
1801
|
};
|
|
1760
1802
|
} catch (error) {
|
|
1761
1803
|
if (error instanceof Error) {
|
|
@@ -1817,7 +1859,7 @@ const loginWithEmail = async (email, password, applicationToken) => {
|
|
|
1817
1859
|
}
|
|
1818
1860
|
};
|
|
1819
1861
|
const identifyPerson = async (image, applicationToken) => {
|
|
1820
|
-
var
|
|
1862
|
+
var _a2, _b;
|
|
1821
1863
|
ensureSecureContext();
|
|
1822
1864
|
const controller = createTimeoutController();
|
|
1823
1865
|
const compressedImage = await compressImage(image);
|
|
@@ -1853,7 +1895,7 @@ const identifyPerson = async (image, applicationToken) => {
|
|
|
1853
1895
|
if (!data.success) {
|
|
1854
1896
|
throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
|
|
1855
1897
|
}
|
|
1856
|
-
const personName = ((
|
|
1898
|
+
const personName = ((_a2 = data.person) == null ? void 0 : _a2.name) || "";
|
|
1857
1899
|
const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
|
|
1858
1900
|
if (!personName) {
|
|
1859
1901
|
throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
|
|
@@ -1885,6 +1927,79 @@ const identifyPerson = async (image, applicationToken) => {
|
|
|
1885
1927
|
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
|
|
1886
1928
|
}
|
|
1887
1929
|
};
|
|
1930
|
+
const registerBiometric = async (personId, faceImage, applicationToken) => {
|
|
1931
|
+
ensureSecureContext();
|
|
1932
|
+
if (!personId) {
|
|
1933
|
+
throw new NeoFaceError("Person ID is required", ErrorType.VALIDATION_ERROR);
|
|
1934
|
+
}
|
|
1935
|
+
if (!faceImage) {
|
|
1936
|
+
throw new NeoFaceError("Face image is required", ErrorType.VALIDATION_ERROR);
|
|
1937
|
+
}
|
|
1938
|
+
const controller = createTimeoutController();
|
|
1939
|
+
const compressedImage = await compressImage(faceImage);
|
|
1940
|
+
const base64Image = await new Promise((resolve, reject) => {
|
|
1941
|
+
const reader = new FileReader();
|
|
1942
|
+
reader.onload = () => {
|
|
1943
|
+
const result = reader.result;
|
|
1944
|
+
resolve(result.split(",")[1]);
|
|
1945
|
+
};
|
|
1946
|
+
reader.onerror = reject;
|
|
1947
|
+
reader.readAsDataURL(compressedImage);
|
|
1948
|
+
});
|
|
1949
|
+
try {
|
|
1950
|
+
const response = await fetch(`${API_BASE_URL}/api/v1/identity-data/`, {
|
|
1951
|
+
method: "POST",
|
|
1952
|
+
headers: {
|
|
1953
|
+
"Content-Type": "application/json",
|
|
1954
|
+
"X-App-Token": applicationToken
|
|
1955
|
+
},
|
|
1956
|
+
body: JSON.stringify({
|
|
1957
|
+
person: personId,
|
|
1958
|
+
type_of_identification: "FACE",
|
|
1959
|
+
biometric_data: base64Image
|
|
1960
|
+
}),
|
|
1961
|
+
signal: controller.signal
|
|
1962
|
+
});
|
|
1963
|
+
if (!response.ok) {
|
|
1964
|
+
if (response.status === 401 || response.status === 403) {
|
|
1965
|
+
throw new NeoFaceError("Invalid or expired application token, or insufficient permissions", ErrorType.INVALID_TOKEN);
|
|
1966
|
+
}
|
|
1967
|
+
if (response.status === 400) {
|
|
1968
|
+
try {
|
|
1969
|
+
const errorData = await response.json();
|
|
1970
|
+
const errorMessage = errorData.message || errorData.error || "Invalid biometric data";
|
|
1971
|
+
throw new NeoFaceError(errorMessage, ErrorType.VALIDATION_ERROR);
|
|
1972
|
+
} catch (parseError) {
|
|
1973
|
+
if (parseError instanceof NeoFaceError) {
|
|
1974
|
+
throw parseError;
|
|
1975
|
+
}
|
|
1976
|
+
throw new NeoFaceError("Invalid biometric data", ErrorType.VALIDATION_ERROR);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
|
|
1980
|
+
}
|
|
1981
|
+
const data = await response.json();
|
|
1982
|
+
return {
|
|
1983
|
+
success: true,
|
|
1984
|
+
message: "Biometric data registered successfully",
|
|
1985
|
+
identity_data_id: data.id,
|
|
1986
|
+
type_of_identification: data.type_of_identification,
|
|
1987
|
+
have_biometric_data: data.have_biometric_data,
|
|
1988
|
+
created_at: data.created_at
|
|
1989
|
+
};
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
if (error instanceof Error) {
|
|
1992
|
+
if (error.name === "AbortError") {
|
|
1993
|
+
throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
|
|
1994
|
+
}
|
|
1995
|
+
if (error instanceof NeoFaceError) {
|
|
1996
|
+
throw error;
|
|
1997
|
+
}
|
|
1998
|
+
throw new NeoFaceError(error.message, ErrorType.NETWORK);
|
|
1999
|
+
}
|
|
2000
|
+
throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
1888
2003
|
const modalReducer$1 = (state, action) => {
|
|
1889
2004
|
switch (action.type) {
|
|
1890
2005
|
case "PERMISSION_GRANTED":
|
|
@@ -2129,8 +2244,8 @@ function FaceCaptureModal({ accessToken, onClose, onSuccess }) {
|
|
|
2129
2244
|
canvasRef.current.height = videoRef.current.videoHeight;
|
|
2130
2245
|
context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
2131
2246
|
const blob = await new Promise((resolve, reject) => {
|
|
2132
|
-
var
|
|
2133
|
-
(
|
|
2247
|
+
var _a2;
|
|
2248
|
+
(_a2 = canvasRef.current) == null ? void 0 : _a2.toBlob(
|
|
2134
2249
|
(b) => {
|
|
2135
2250
|
if (b)
|
|
2136
2251
|
resolve(b);
|
|
@@ -2267,8 +2382,8 @@ function BiometricRegistrationModal({
|
|
|
2267
2382
|
onClose,
|
|
2268
2383
|
onSuccess,
|
|
2269
2384
|
onError,
|
|
2270
|
-
useRealApi =
|
|
2271
|
-
//
|
|
2385
|
+
useRealApi = true
|
|
2386
|
+
// Sempre usa API real em produção
|
|
2272
2387
|
}) {
|
|
2273
2388
|
const videoRef = useRef(null);
|
|
2274
2389
|
const canvasRef = useRef(null);
|
|
@@ -2530,8 +2645,8 @@ function BiometricRegistrationModal({
|
|
|
2530
2645
|
}
|
|
2531
2646
|
};
|
|
2532
2647
|
const startTimer = setTimeout(() => {
|
|
2533
|
-
var
|
|
2534
|
-
if (isRunning && ((
|
|
2648
|
+
var _a2;
|
|
2649
|
+
if (isRunning && ((_a2 = videoRef.current) == null ? void 0 : _a2.readyState) === 4) {
|
|
2535
2650
|
detectFace2();
|
|
2536
2651
|
}
|
|
2537
2652
|
}, 1e3);
|
|
@@ -2592,60 +2707,19 @@ function BiometricRegistrationModal({
|
|
|
2592
2707
|
};
|
|
2593
2708
|
const processRegistration = async (photos) => {
|
|
2594
2709
|
try {
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
onboarding_link: result2.onboarding_link
|
|
2606
|
-
};
|
|
2607
|
-
dispatch({ type: "SUCCESS" });
|
|
2608
|
-
onSuccess(adaptedResult);
|
|
2609
|
-
return;
|
|
2610
|
-
}
|
|
2611
|
-
console.log("🎭 Usando modo SIMULADO para testes...");
|
|
2612
|
-
const facePhotosBase64 = await Promise.all(
|
|
2613
|
-
photos.map(async (photo) => {
|
|
2614
|
-
return new Promise((resolve, reject) => {
|
|
2615
|
-
const reader = new FileReader();
|
|
2616
|
-
reader.onload = () => {
|
|
2617
|
-
const result2 = reader.result;
|
|
2618
|
-
resolve(result2);
|
|
2619
|
-
};
|
|
2620
|
-
reader.onerror = reject;
|
|
2621
|
-
reader.readAsDataURL(photo);
|
|
2622
|
-
});
|
|
2623
|
-
})
|
|
2624
|
-
);
|
|
2625
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2626
|
-
const result = {
|
|
2627
|
-
success: true,
|
|
2628
|
-
message: "Registro biométrico realizado com sucesso",
|
|
2629
|
-
person_id: "test-person-123",
|
|
2630
|
-
user_id: "test-user-456",
|
|
2631
|
-
person_validated: true,
|
|
2632
|
-
face_processing: {
|
|
2633
|
-
total_photos_captured: photos.length,
|
|
2634
|
-
photo_sizes: photos.map((p, i) => ({
|
|
2635
|
-
photo_index: i + 1,
|
|
2636
|
-
size_kb: Math.round(p.size / 1024)
|
|
2637
|
-
})),
|
|
2638
|
-
// Mostra apenas preview dos primeiros 50 caracteres de cada foto em base64
|
|
2639
|
-
photos_preview: facePhotosBase64.map((b64, i) => ({
|
|
2640
|
-
photo_index: i + 1,
|
|
2641
|
-
format: "data:image/jpeg;base64,...",
|
|
2642
|
-
preview: b64.substring(0, 50) + "...",
|
|
2643
|
-
full_length: b64.length
|
|
2644
|
-
}))
|
|
2645
|
-
}
|
|
2710
|
+
console.log("🚀 Fazendo requisição ao backend...");
|
|
2711
|
+
const result = await registerPersonWithBiometric(personData, photos, applicationToken);
|
|
2712
|
+
const adaptedResult = {
|
|
2713
|
+
success: result.success,
|
|
2714
|
+
message: result.message,
|
|
2715
|
+
person_id: result.person_id,
|
|
2716
|
+
user_id: result.user_id,
|
|
2717
|
+
person_validated: result.person_validated,
|
|
2718
|
+
face_processing: result.face_processing,
|
|
2719
|
+
onboarding_link: result.onboarding_link
|
|
2646
2720
|
};
|
|
2647
2721
|
dispatch({ type: "SUCCESS" });
|
|
2648
|
-
onSuccess(
|
|
2722
|
+
onSuccess(adaptedResult);
|
|
2649
2723
|
} catch (error) {
|
|
2650
2724
|
const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
|
|
2651
2725
|
dispatch({ type: "ERROR", message: errorMessage });
|
|
@@ -3529,21 +3603,21 @@ let BiometricCaptureModal$1 = class BiometricCaptureModal2 {
|
|
|
3529
3603
|
const cancelBtn = this.modal.querySelector(".neofaceid-cancel-btn");
|
|
3530
3604
|
const captureBtn = this.modal.querySelector(".neofaceid-capture-btn");
|
|
3531
3605
|
closeBtn == null ? void 0 : closeBtn.addEventListener("click", () => {
|
|
3532
|
-
var
|
|
3606
|
+
var _a2, _b;
|
|
3533
3607
|
this.close();
|
|
3534
|
-
(_b = (
|
|
3608
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
3535
3609
|
});
|
|
3536
3610
|
cancelBtn == null ? void 0 : cancelBtn.addEventListener("click", () => {
|
|
3537
|
-
var
|
|
3611
|
+
var _a2, _b;
|
|
3538
3612
|
this.close();
|
|
3539
|
-
(_b = (
|
|
3613
|
+
(_b = (_a2 = this.options).onCancel) == null ? void 0 : _b.call(_a2);
|
|
3540
3614
|
});
|
|
3541
3615
|
captureBtn == null ? void 0 : captureBtn.addEventListener("click", () => {
|
|
3542
3616
|
this.captureImage();
|
|
3543
3617
|
});
|
|
3544
3618
|
this.modal.addEventListener("click", (e) => {
|
|
3545
|
-
var
|
|
3546
|
-
if (e.target === ((
|
|
3619
|
+
var _a2, _b, _c;
|
|
3620
|
+
if (e.target === ((_a2 = this.modal) == null ? void 0 : _a2.querySelector(".neofaceid-modal-overlay"))) {
|
|
3547
3621
|
this.close();
|
|
3548
3622
|
(_c = (_b = this.options).onCancel) == null ? void 0 : _c.call(_b);
|
|
3549
3623
|
}
|
|
@@ -4000,7 +4074,7 @@ function BiometricStatusOverlayComponent({ status, onClose }) {
|
|
|
4000
4074
|
alt: "NeoFaceId",
|
|
4001
4075
|
className: `neofaceid-logo ${status === "verifying" ? "pulsing" : ""}`,
|
|
4002
4076
|
onError: (e) => {
|
|
4003
|
-
var
|
|
4077
|
+
var _a2;
|
|
4004
4078
|
const img = e.target;
|
|
4005
4079
|
img.style.display = "none";
|
|
4006
4080
|
const fallback = document.createElement("div");
|
|
@@ -4011,7 +4085,7 @@ function BiometricStatusOverlayComponent({ status, onClose }) {
|
|
|
4011
4085
|
</svg>
|
|
4012
4086
|
`;
|
|
4013
4087
|
fallback.style.cssText = "width: 80px; height: 80px; display: flex; align-items: center; justify-content: center;";
|
|
4014
|
-
(
|
|
4088
|
+
(_a2 = img.parentElement) == null ? void 0 : _a2.appendChild(fallback);
|
|
4015
4089
|
}
|
|
4016
4090
|
}
|
|
4017
4091
|
) }),
|
|
@@ -4680,6 +4754,116 @@ class EmailPasswordModal {
|
|
|
4680
4754
|
}
|
|
4681
4755
|
const MAX_ATTEMPTS = 3;
|
|
4682
4756
|
const RETRY_DELAY = 500;
|
|
4757
|
+
async function captureFaceSilently() {
|
|
4758
|
+
return new Promise((resolve, reject) => {
|
|
4759
|
+
const video = document.createElement("video");
|
|
4760
|
+
video.style.position = "fixed";
|
|
4761
|
+
video.style.top = "-9999px";
|
|
4762
|
+
video.style.left = "-9999px";
|
|
4763
|
+
video.style.width = "1px";
|
|
4764
|
+
video.style.height = "1px";
|
|
4765
|
+
video.style.opacity = "0";
|
|
4766
|
+
video.setAttribute("autoplay", "");
|
|
4767
|
+
video.setAttribute("muted", "");
|
|
4768
|
+
video.setAttribute("playsinline", "");
|
|
4769
|
+
const canvas = document.createElement("canvas");
|
|
4770
|
+
canvas.style.display = "none";
|
|
4771
|
+
let stream = null;
|
|
4772
|
+
let faceDetectionAttempts = 0;
|
|
4773
|
+
const MAX_FACE_DETECTION_ATTEMPTS = 10;
|
|
4774
|
+
const cleanup = () => {
|
|
4775
|
+
if (stream) {
|
|
4776
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
4777
|
+
}
|
|
4778
|
+
if (video.parentElement) {
|
|
4779
|
+
video.parentElement.removeChild(video);
|
|
4780
|
+
}
|
|
4781
|
+
if (canvas.parentElement) {
|
|
4782
|
+
canvas.parentElement.removeChild(canvas);
|
|
4783
|
+
}
|
|
4784
|
+
};
|
|
4785
|
+
document.body.appendChild(video);
|
|
4786
|
+
document.body.appendChild(canvas);
|
|
4787
|
+
navigator.mediaDevices.getUserMedia({
|
|
4788
|
+
video: {
|
|
4789
|
+
width: { ideal: 640 },
|
|
4790
|
+
height: { ideal: 480 },
|
|
4791
|
+
facingMode: "user"
|
|
4792
|
+
},
|
|
4793
|
+
audio: false
|
|
4794
|
+
}).then((mediaStream) => {
|
|
4795
|
+
stream = mediaStream;
|
|
4796
|
+
video.srcObject = stream;
|
|
4797
|
+
video.play();
|
|
4798
|
+
const tryCapture = () => {
|
|
4799
|
+
if (!video.videoWidth || !video.videoHeight) {
|
|
4800
|
+
if (faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
4801
|
+
faceDetectionAttempts++;
|
|
4802
|
+
setTimeout(tryCapture, 100);
|
|
4803
|
+
return;
|
|
4804
|
+
}
|
|
4805
|
+
cleanup();
|
|
4806
|
+
reject(new NeoFaceError("Câmera não está pronta", ErrorType.CAMERA_ERROR));
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
canvas.width = video.videoWidth;
|
|
4810
|
+
canvas.height = video.videoHeight;
|
|
4811
|
+
const ctx = canvas.getContext("2d");
|
|
4812
|
+
if (!ctx) {
|
|
4813
|
+
cleanup();
|
|
4814
|
+
reject(new Error("Failed to get canvas context"));
|
|
4815
|
+
return;
|
|
4816
|
+
}
|
|
4817
|
+
const detectFace2 = async () => {
|
|
4818
|
+
try {
|
|
4819
|
+
const detection = await faceapi.detectSingleFace(
|
|
4820
|
+
video,
|
|
4821
|
+
new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.4 })
|
|
4822
|
+
);
|
|
4823
|
+
return !!detection;
|
|
4824
|
+
} catch {
|
|
4825
|
+
return false;
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
detectFace2().then((hasFace) => {
|
|
4829
|
+
if (!hasFace && faceDetectionAttempts < MAX_FACE_DETECTION_ATTEMPTS) {
|
|
4830
|
+
faceDetectionAttempts++;
|
|
4831
|
+
setTimeout(tryCapture, 200);
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
4835
|
+
canvas.toBlob(
|
|
4836
|
+
(blob) => {
|
|
4837
|
+
cleanup();
|
|
4838
|
+
if (blob) {
|
|
4839
|
+
resolve(blob);
|
|
4840
|
+
} else {
|
|
4841
|
+
reject(new Error("Failed to capture image"));
|
|
4842
|
+
}
|
|
4843
|
+
},
|
|
4844
|
+
"image/jpeg",
|
|
4845
|
+
0.85
|
|
4846
|
+
);
|
|
4847
|
+
});
|
|
4848
|
+
};
|
|
4849
|
+
video.onloadedmetadata = () => {
|
|
4850
|
+
setTimeout(tryCapture, 800);
|
|
4851
|
+
};
|
|
4852
|
+
video.onerror = () => {
|
|
4853
|
+
cleanup();
|
|
4854
|
+
reject(new NeoFaceError("Erro ao acessar câmera", ErrorType.CAMERA_ERROR));
|
|
4855
|
+
};
|
|
4856
|
+
}).catch((error) => {
|
|
4857
|
+
cleanup();
|
|
4858
|
+
reject(
|
|
4859
|
+
new NeoFaceError(
|
|
4860
|
+
"Não foi possível acessar a câmera: " + error.message,
|
|
4861
|
+
ErrorType.CAMERA_ERROR
|
|
4862
|
+
)
|
|
4863
|
+
);
|
|
4864
|
+
});
|
|
4865
|
+
});
|
|
4866
|
+
}
|
|
4683
4867
|
async function detectFaceContinuously(video, minDetectionTime = 3e3, detectionInterval = 200) {
|
|
4684
4868
|
return new Promise((resolve) => {
|
|
4685
4869
|
let faceDetectedCount = 0;
|
|
@@ -4902,6 +5086,11 @@ async function executeBiometricLoginFlow(options) {
|
|
|
4902
5086
|
tryLogin();
|
|
4903
5087
|
}, 300);
|
|
4904
5088
|
}
|
|
5089
|
+
const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
5090
|
+
__proto__: null,
|
|
5091
|
+
captureFaceSilently,
|
|
5092
|
+
executeBiometricLoginFlow
|
|
5093
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
4905
5094
|
async function startFaceLogin(options) {
|
|
4906
5095
|
try {
|
|
4907
5096
|
const isValidToken = await validateToken(options.applicationToken);
|
|
@@ -5229,8 +5418,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
5229
5418
|
initializeBiometricDetection,
|
|
5230
5419
|
isAdvancedDetectionAvailable
|
|
5231
5420
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
5232
|
-
const VERSION = "1.
|
|
5233
|
-
const RELEASE_DATE = "2025-
|
|
5421
|
+
const VERSION = "1.6.0";
|
|
5422
|
+
const RELEASE_DATE = "2025-12-01";
|
|
5234
5423
|
class OnboardingCaptureModal {
|
|
5235
5424
|
constructor(options) {
|
|
5236
5425
|
__publicField(this, "overlay", null);
|
|
@@ -5454,6 +5643,284 @@ const startOnboarding = async (options) => {
|
|
|
5454
5643
|
onError(new NeoFaceError(message, ErrorType.UNKNOWN));
|
|
5455
5644
|
}
|
|
5456
5645
|
};
|
|
5646
|
+
class NeoFaceID {
|
|
5647
|
+
/**
|
|
5648
|
+
* Creates a new NeoFaceID instance
|
|
5649
|
+
* @param config Configuration object
|
|
5650
|
+
* @throws NeoFaceError if signature format is invalid
|
|
5651
|
+
*/
|
|
5652
|
+
constructor(config) {
|
|
5653
|
+
__publicField(this, "appToken");
|
|
5654
|
+
__publicField(this, "baseUrl");
|
|
5655
|
+
__publicField(this, "signature");
|
|
5656
|
+
__publicField(this, "sessionData");
|
|
5657
|
+
var _a2;
|
|
5658
|
+
this.appToken = config.appToken;
|
|
5659
|
+
this.baseUrl = config.baseUrl || (((_a2 = { "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false }) == null ? void 0 : _a2.VITE_API_BASE_URL) || "http://localhost:8000");
|
|
5660
|
+
this.signature = config.signature;
|
|
5661
|
+
this.sessionData = config.sessionData;
|
|
5662
|
+
if (this.signature && !this.validateSignatureFormat(this.signature)) {
|
|
5663
|
+
throw new NeoFaceError(
|
|
5664
|
+
"Invalid signature format. Expected HMAC-SHA256 hex string (minimum 64 characters)",
|
|
5665
|
+
ErrorType.VALIDATION_ERROR
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
if (this.sessionData) {
|
|
5669
|
+
if (!this.sessionData.email || !this.sessionData.cpf || !this.sessionData.sessionId) {
|
|
5670
|
+
throw new NeoFaceError(
|
|
5671
|
+
"Session data must include email, cpf, and sessionId",
|
|
5672
|
+
ErrorType.VALIDATION_ERROR
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Validates the format of a signature
|
|
5679
|
+
* HMAC-SHA256 produces a 64-character hexadecimal string
|
|
5680
|
+
* @param signature The signature to validate
|
|
5681
|
+
* @returns true if valid, false otherwise
|
|
5682
|
+
*/
|
|
5683
|
+
validateSignatureFormat(signature) {
|
|
5684
|
+
return /^[a-f0-9]{64,}$/i.test(signature);
|
|
5685
|
+
}
|
|
5686
|
+
/**
|
|
5687
|
+
* Captures multiple face frames for biometric recognition
|
|
5688
|
+
* @param options Capture options
|
|
5689
|
+
* @returns Promise that resolves to an array of image blobs
|
|
5690
|
+
* @throws NeoFaceError if capture fails
|
|
5691
|
+
*/
|
|
5692
|
+
async captureFaceFrames(options) {
|
|
5693
|
+
const { numFrames, livenessCheck } = options;
|
|
5694
|
+
if (numFrames < 1 || numFrames > 10) {
|
|
5695
|
+
throw new NeoFaceError(
|
|
5696
|
+
"Number of frames must be between 1 and 10",
|
|
5697
|
+
ErrorType.VALIDATION_ERROR
|
|
5698
|
+
);
|
|
5699
|
+
}
|
|
5700
|
+
const frames = [];
|
|
5701
|
+
try {
|
|
5702
|
+
const { captureFaceSilently: captureFaceSilently2 } = await Promise.resolve().then(() => biometricLoginFlow);
|
|
5703
|
+
for (let i = 0; i < numFrames; i++) {
|
|
5704
|
+
const frame = await captureFaceSilently2();
|
|
5705
|
+
frames.push(frame);
|
|
5706
|
+
if (livenessCheck && i < numFrames - 1) {
|
|
5707
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
return frames;
|
|
5711
|
+
} catch (error) {
|
|
5712
|
+
if (error instanceof NeoFaceError) {
|
|
5713
|
+
throw error;
|
|
5714
|
+
}
|
|
5715
|
+
throw new NeoFaceError(
|
|
5716
|
+
`Failed to capture face frames: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
5717
|
+
ErrorType.CAPTURE_ERROR
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Performs login recognition using biometric data
|
|
5723
|
+
*
|
|
5724
|
+
* This method is designed for external integrations that require
|
|
5725
|
+
* signature validation and session management.
|
|
5726
|
+
*
|
|
5727
|
+
* @param options Recognition options
|
|
5728
|
+
* @returns Promise that resolves to recognition result
|
|
5729
|
+
* @throws NeoFaceError if recognition fails
|
|
5730
|
+
*
|
|
5731
|
+
* @example
|
|
5732
|
+
* ```typescript
|
|
5733
|
+
* const result = await sdk.loginRecognition({
|
|
5734
|
+
* biometricData: await sdk.captureFaceFrames({
|
|
5735
|
+
* numFrames: 5,
|
|
5736
|
+
* livenessCheck: true
|
|
5737
|
+
* }),
|
|
5738
|
+
* typeOfIdentification: 'FACE',
|
|
5739
|
+
* purpose: 'LOGIN',
|
|
5740
|
+
* confidenceThreshold: 0.8
|
|
5741
|
+
* });
|
|
5742
|
+
*
|
|
5743
|
+
* // Result includes signature and sessionId for callback validation
|
|
5744
|
+
* console.log(result.signature, result.sessionId);
|
|
5745
|
+
* ```
|
|
5746
|
+
*/
|
|
5747
|
+
async loginRecognition(options) {
|
|
5748
|
+
const {
|
|
5749
|
+
biometricData,
|
|
5750
|
+
typeOfIdentification,
|
|
5751
|
+
purpose,
|
|
5752
|
+
confidenceThreshold = 0.8
|
|
5753
|
+
} = options;
|
|
5754
|
+
const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
|
|
5755
|
+
if (imageBlobs.length === 0) {
|
|
5756
|
+
throw new NeoFaceError(
|
|
5757
|
+
"At least one biometric data frame is required",
|
|
5758
|
+
ErrorType.VALIDATION_ERROR
|
|
5759
|
+
);
|
|
5760
|
+
}
|
|
5761
|
+
const primaryImage = imageBlobs[0];
|
|
5762
|
+
try {
|
|
5763
|
+
const result = await recognizeByPurpose(
|
|
5764
|
+
primaryImage,
|
|
5765
|
+
this.appToken,
|
|
5766
|
+
purpose,
|
|
5767
|
+
confidenceThreshold,
|
|
5768
|
+
this.signature,
|
|
5769
|
+
this.sessionData
|
|
5770
|
+
);
|
|
5771
|
+
if (!result.success) {
|
|
5772
|
+
throw new NeoFaceError(
|
|
5773
|
+
"Recognition failed",
|
|
5774
|
+
ErrorType.RECOGNITION_FAILED
|
|
5775
|
+
);
|
|
5776
|
+
}
|
|
5777
|
+
if (!result.personName || !result.email || !result.cpf) {
|
|
5778
|
+
throw new NeoFaceError(
|
|
5779
|
+
"Incomplete recognition result: missing personName, email, or cpf",
|
|
5780
|
+
ErrorType.RECOGNITION_FAILED
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5783
|
+
return {
|
|
5784
|
+
success: true,
|
|
5785
|
+
personName: result.personName,
|
|
5786
|
+
email: result.email,
|
|
5787
|
+
cpf: result.cpf,
|
|
5788
|
+
signature: result.signature,
|
|
5789
|
+
sessionId: result.sessionId,
|
|
5790
|
+
confidenceScore: result.confidenceScore,
|
|
5791
|
+
accessToken: result.accessToken
|
|
5792
|
+
};
|
|
5793
|
+
} catch (error) {
|
|
5794
|
+
if (error instanceof NeoFaceError) {
|
|
5795
|
+
throw error;
|
|
5796
|
+
}
|
|
5797
|
+
throw new NeoFaceError(
|
|
5798
|
+
`Login recognition failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
5799
|
+
ErrorType.RECOGNITION_FAILED
|
|
5800
|
+
);
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
const PHOTO_INSTRUCTIONS = [
|
|
5805
|
+
"Olhe diretamente para a câmera",
|
|
5806
|
+
"Vire levemente a cabeça para a esquerda",
|
|
5807
|
+
"Vire levemente a cabeça para a direita",
|
|
5808
|
+
"Mostre o polegar para cima (👍) e olhe para a câmera"
|
|
5809
|
+
];
|
|
5810
|
+
const TOTAL_PHOTOS = 4;
|
|
5811
|
+
const authorizeOperation = async (applicationToken, cpf, options) => {
|
|
5812
|
+
if (!applicationToken) {
|
|
5813
|
+
throw new NeoFaceError("Application token is required", ErrorType.VALIDATION_ERROR);
|
|
5814
|
+
}
|
|
5815
|
+
if (!cpf) {
|
|
5816
|
+
throw new NeoFaceError("CPF is required", ErrorType.VALIDATION_ERROR);
|
|
5817
|
+
}
|
|
5818
|
+
const cpfClean = cpf.replace(/\D/g, "");
|
|
5819
|
+
if (cpfClean.length !== 11) {
|
|
5820
|
+
throw new NeoFaceError("Invalid CPF format", ErrorType.VALIDATION_ERROR);
|
|
5821
|
+
}
|
|
5822
|
+
const capturedPhotos = [];
|
|
5823
|
+
const captureDelay = (options == null ? void 0 : options.captureDelay) || 2e3;
|
|
5824
|
+
try {
|
|
5825
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
5826
|
+
throw new NeoFaceError("Camera not available", ErrorType.NO_CAMERA);
|
|
5827
|
+
}
|
|
5828
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
5829
|
+
video: {
|
|
5830
|
+
facingMode: "user",
|
|
5831
|
+
width: { ideal: 1280 },
|
|
5832
|
+
height: { ideal: 720 }
|
|
5833
|
+
}
|
|
5834
|
+
});
|
|
5835
|
+
const video = document.createElement("video");
|
|
5836
|
+
video.srcObject = stream;
|
|
5837
|
+
video.autoplay = true;
|
|
5838
|
+
video.playsInline = true;
|
|
5839
|
+
await new Promise((resolve) => {
|
|
5840
|
+
video.onloadedmetadata = () => {
|
|
5841
|
+
video.play();
|
|
5842
|
+
resolve();
|
|
5843
|
+
};
|
|
5844
|
+
});
|
|
5845
|
+
const canvas = document.createElement("canvas");
|
|
5846
|
+
canvas.width = video.videoWidth;
|
|
5847
|
+
canvas.height = video.videoHeight;
|
|
5848
|
+
const ctx = canvas.getContext("2d");
|
|
5849
|
+
if (!ctx) {
|
|
5850
|
+
throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
|
|
5851
|
+
}
|
|
5852
|
+
for (let i = 0; i < TOTAL_PHOTOS; i++) {
|
|
5853
|
+
const instruction = PHOTO_INSTRUCTIONS[i];
|
|
5854
|
+
if (options == null ? void 0 : options.onProgress) {
|
|
5855
|
+
options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
|
|
5856
|
+
}
|
|
5857
|
+
if (i > 0) {
|
|
5858
|
+
await new Promise((resolve) => setTimeout(resolve, captureDelay));
|
|
5859
|
+
}
|
|
5860
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
5861
|
+
const blob = await new Promise((resolve, reject) => {
|
|
5862
|
+
canvas.toBlob(
|
|
5863
|
+
(blob2) => {
|
|
5864
|
+
if (blob2) {
|
|
5865
|
+
resolve(blob2);
|
|
5866
|
+
} else {
|
|
5867
|
+
reject(new Error("Failed to capture photo"));
|
|
5868
|
+
}
|
|
5869
|
+
},
|
|
5870
|
+
"image/jpeg",
|
|
5871
|
+
0.9
|
|
5872
|
+
);
|
|
5873
|
+
});
|
|
5874
|
+
capturedPhotos.push(blob);
|
|
5875
|
+
if (options == null ? void 0 : options.onPhotoTaken) {
|
|
5876
|
+
options.onPhotoTaken(i + 1, blob);
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
5880
|
+
const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
|
|
5881
|
+
const sessionData = {
|
|
5882
|
+
cpf: cpfClean,
|
|
5883
|
+
email: "",
|
|
5884
|
+
// Not required for authorization
|
|
5885
|
+
sessionId: `auth_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
5886
|
+
};
|
|
5887
|
+
const recognitionResult = await recognizeByPurpose(
|
|
5888
|
+
lastPhoto,
|
|
5889
|
+
applicationToken,
|
|
5890
|
+
"AUTHORIZATION",
|
|
5891
|
+
0.8,
|
|
5892
|
+
// Confidence threshold
|
|
5893
|
+
void 0,
|
|
5894
|
+
// No signature for now
|
|
5895
|
+
sessionData
|
|
5896
|
+
);
|
|
5897
|
+
if (!recognitionResult.success) {
|
|
5898
|
+
throw new NeoFaceError(
|
|
5899
|
+
"Authorization failed: Person not recognized",
|
|
5900
|
+
ErrorType.RECOGNITION_FAILED
|
|
5901
|
+
);
|
|
5902
|
+
}
|
|
5903
|
+
return {
|
|
5904
|
+
success: true,
|
|
5905
|
+
authorizationHash: sessionData.sessionId,
|
|
5906
|
+
// TEMPORARY: Using sessionId as hash
|
|
5907
|
+
name: recognitionResult.personName,
|
|
5908
|
+
birthDate: void 0,
|
|
5909
|
+
// Should come from backend
|
|
5910
|
+
cpf: recognitionResult.cpf || cpfClean,
|
|
5911
|
+
confidenceScore: recognitionResult.confidenceScore,
|
|
5912
|
+
message: "Authorization successful"
|
|
5913
|
+
};
|
|
5914
|
+
} catch (error) {
|
|
5915
|
+
if (error instanceof NeoFaceError) {
|
|
5916
|
+
throw error;
|
|
5917
|
+
}
|
|
5918
|
+
if (error instanceof Error) {
|
|
5919
|
+
throw new NeoFaceError(error.message, ErrorType.CAPTURE_ERROR);
|
|
5920
|
+
}
|
|
5921
|
+
throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
|
|
5922
|
+
}
|
|
5923
|
+
};
|
|
5457
5924
|
function start(applicationToken, callbacks) {
|
|
5458
5925
|
const container = document.createElement("div");
|
|
5459
5926
|
container.id = "neoface-modal-container";
|
|
@@ -5496,8 +5963,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
5496
5963
|
const modalElement = require$$0$1.createElement(BiometricRegistrationModal, {
|
|
5497
5964
|
personData,
|
|
5498
5965
|
applicationToken,
|
|
5499
|
-
useRealApi: (options == null ? void 0 : options.useRealApi)
|
|
5500
|
-
//
|
|
5966
|
+
useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
|
|
5967
|
+
// Sempre usa API real em produção
|
|
5501
5968
|
onClose: cleanup,
|
|
5502
5969
|
onSuccess: (result) => {
|
|
5503
5970
|
cleanup();
|
|
@@ -5524,8 +5991,10 @@ export {
|
|
|
5524
5991
|
FaceCaptureModal,
|
|
5525
5992
|
FallbackPrompt,
|
|
5526
5993
|
NeoFaceError,
|
|
5994
|
+
NeoFaceID,
|
|
5527
5995
|
RELEASE_DATE,
|
|
5528
5996
|
VERSION,
|
|
5997
|
+
authorizeOperation,
|
|
5529
5998
|
biometricLogin,
|
|
5530
5999
|
biometricLoginWithFallback,
|
|
5531
6000
|
completeOnboarding,
|
|
@@ -5538,6 +6007,7 @@ export {
|
|
|
5538
6007
|
recognize,
|
|
5539
6008
|
recognizeBiometric,
|
|
5540
6009
|
recognizeByPurpose,
|
|
6010
|
+
registerBiometric,
|
|
5541
6011
|
registerPersonWithBiometric,
|
|
5542
6012
|
registerPersonWithoutFace,
|
|
5543
6013
|
simpleIdentification,
|