@neofaceid/web-sdk 1.11.1 → 1.13.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.
|
@@ -2808,12 +2808,14 @@ const modalReducer = (state, action) => {
|
|
|
2808
2808
|
};
|
|
2809
2809
|
}
|
|
2810
2810
|
return state;
|
|
2811
|
+
case "CAMERA_LOADING":
|
|
2812
|
+
return { status: "camera-loading" };
|
|
2811
2813
|
case "START_PROCESSING":
|
|
2812
2814
|
return { status: "processing" };
|
|
2813
2815
|
case "SUCCESS":
|
|
2814
2816
|
return { status: "success" };
|
|
2815
2817
|
case "ERROR":
|
|
2816
|
-
return { status: "error", message: action.message };
|
|
2818
|
+
return { status: "error", message: action.message, retryable: action.retryable ?? true };
|
|
2817
2819
|
case "RESET":
|
|
2818
2820
|
return initialState;
|
|
2819
2821
|
default:
|
|
@@ -2859,9 +2861,7 @@ function BiometricRegistrationModal({
|
|
|
2859
2861
|
onClose,
|
|
2860
2862
|
onSuccess,
|
|
2861
2863
|
onPhotosCaptured,
|
|
2862
|
-
onError
|
|
2863
|
-
useRealApi = true
|
|
2864
|
-
// Sempre usa API real em produção
|
|
2864
|
+
onError
|
|
2865
2865
|
}) {
|
|
2866
2866
|
const videoRef = useRef(null);
|
|
2867
2867
|
const canvasRef = useRef(null);
|
|
@@ -2870,10 +2870,12 @@ function BiometricRegistrationModal({
|
|
|
2870
2870
|
const progressIntervalRef = useRef(null);
|
|
2871
2871
|
const isCapturingRef = useRef(false);
|
|
2872
2872
|
const currentStepRef = useRef("center");
|
|
2873
|
+
const watchdogTimerRef = useRef(null);
|
|
2874
|
+
const detectionStartedRef = useRef(false);
|
|
2873
2875
|
const captureStateRef = useRef(null);
|
|
2874
2876
|
const [state, dispatch] = useReducer(modalReducer, initialState);
|
|
2875
2877
|
const [faceDetected, setFaceDetected] = useState(false);
|
|
2876
|
-
const [
|
|
2878
|
+
const [_faceDistance, setFaceDistance] = useState("ok");
|
|
2877
2879
|
useEffect(() => {
|
|
2878
2880
|
if (state.status === "liveness") {
|
|
2879
2881
|
const newStep = state.step;
|
|
@@ -2989,20 +2991,66 @@ function BiometricRegistrationModal({
|
|
|
2989
2991
|
const loadModels = async () => {
|
|
2990
2992
|
try {
|
|
2991
2993
|
const MODEL_URL = "https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model";
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2994
|
+
const timeout = new Promise(
|
|
2995
|
+
(_, reject) => setTimeout(() => reject(new Error("Timeout ao carregar modelos")), 1e4)
|
|
2996
|
+
);
|
|
2997
|
+
await Promise.race([
|
|
2998
|
+
Promise.all([
|
|
2999
|
+
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
|
|
3000
|
+
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
|
|
3001
|
+
]),
|
|
3002
|
+
timeout
|
|
2995
3003
|
]);
|
|
2996
3004
|
} catch (error) {
|
|
2997
|
-
dispatch({
|
|
3005
|
+
dispatch({
|
|
3006
|
+
type: "ERROR",
|
|
3007
|
+
message: "Não foi possível carregar os modelos de IA. Verifique sua conexão e tente novamente.",
|
|
3008
|
+
retryable: true
|
|
3009
|
+
});
|
|
2998
3010
|
}
|
|
2999
3011
|
};
|
|
3000
3012
|
loadModels();
|
|
3001
3013
|
}, []);
|
|
3014
|
+
useEffect(() => {
|
|
3015
|
+
return () => {
|
|
3016
|
+
if (streamRef.current) {
|
|
3017
|
+
streamRef.current.getTracks().forEach((track) => {
|
|
3018
|
+
track.stop();
|
|
3019
|
+
});
|
|
3020
|
+
streamRef.current = null;
|
|
3021
|
+
}
|
|
3022
|
+
if (videoRef.current) {
|
|
3023
|
+
videoRef.current.srcObject = null;
|
|
3024
|
+
}
|
|
3025
|
+
if (watchdogTimerRef.current) {
|
|
3026
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3027
|
+
watchdogTimerRef.current = null;
|
|
3028
|
+
}
|
|
3029
|
+
if (progressIntervalRef.current) {
|
|
3030
|
+
clearInterval(progressIntervalRef.current);
|
|
3031
|
+
progressIntervalRef.current = null;
|
|
3032
|
+
}
|
|
3033
|
+
};
|
|
3034
|
+
}, []);
|
|
3002
3035
|
useEffect(() => {
|
|
3003
3036
|
let mounted = true;
|
|
3004
3037
|
const initCamera = async () => {
|
|
3005
3038
|
if (state.status !== "liveness" || streamRef.current) return;
|
|
3039
|
+
detectionStartedRef.current = false;
|
|
3040
|
+
if (watchdogTimerRef.current) {
|
|
3041
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3042
|
+
watchdogTimerRef.current = null;
|
|
3043
|
+
}
|
|
3044
|
+
watchdogTimerRef.current = window.setTimeout(() => {
|
|
3045
|
+
if (!mounted) return;
|
|
3046
|
+
if (detectionStartedRef.current === false) {
|
|
3047
|
+
dispatch({
|
|
3048
|
+
type: "ERROR",
|
|
3049
|
+
message: "A câmera não respondeu a tempo. Verifique se ela está disponível e tente novamente.",
|
|
3050
|
+
retryable: true
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
}, 2e4);
|
|
3006
3054
|
try {
|
|
3007
3055
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
3008
3056
|
video: {
|
|
@@ -3032,13 +3080,30 @@ function BiometricRegistrationModal({
|
|
|
3032
3080
|
await video.play();
|
|
3033
3081
|
} catch (error) {
|
|
3034
3082
|
if (!mounted) return;
|
|
3035
|
-
|
|
3036
|
-
|
|
3083
|
+
if (watchdogTimerRef.current) {
|
|
3084
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3085
|
+
watchdogTimerRef.current = null;
|
|
3086
|
+
}
|
|
3087
|
+
let errorMessage = error instanceof Error ? error.message : "Erro ao acessar câmera";
|
|
3088
|
+
if (error instanceof DOMException) {
|
|
3089
|
+
if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
|
|
3090
|
+
errorMessage = "Permissão de câmera negada. Permita o acesso à câmera nas configurações do navegador e tente novamente.";
|
|
3091
|
+
} else if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
|
|
3092
|
+
errorMessage = "Nenhuma câmera encontrada. Conecte uma câmera e tente novamente.";
|
|
3093
|
+
} else if (error.name === "NotReadableError") {
|
|
3094
|
+
errorMessage = "A câmera está sendo usada por outro programa. Feche-o e tente novamente.";
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
dispatch({ type: "ERROR", message: errorMessage, retryable: true });
|
|
3037
3098
|
}
|
|
3038
3099
|
};
|
|
3039
3100
|
initCamera();
|
|
3040
3101
|
return () => {
|
|
3041
3102
|
mounted = false;
|
|
3103
|
+
if (watchdogTimerRef.current) {
|
|
3104
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3105
|
+
watchdogTimerRef.current = null;
|
|
3106
|
+
}
|
|
3042
3107
|
if (streamRef.current) {
|
|
3043
3108
|
streamRef.current.getTracks().forEach((track) => track.stop());
|
|
3044
3109
|
streamRef.current = null;
|
|
@@ -3118,21 +3183,59 @@ function BiometricRegistrationModal({
|
|
|
3118
3183
|
requestAnimationFrame(detectFace2);
|
|
3119
3184
|
}
|
|
3120
3185
|
};
|
|
3121
|
-
const
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
clearTimeout(startTimer);
|
|
3130
|
-
if (progressIntervalRef.current) {
|
|
3131
|
-
clearInterval(progressIntervalRef.current);
|
|
3132
|
-
progressIntervalRef.current = null;
|
|
3133
|
-
}
|
|
3134
|
-
holdStartTimeRef.current = null;
|
|
3186
|
+
const startDetection = () => {
|
|
3187
|
+
if (!isRunning || !videoRef.current) return;
|
|
3188
|
+
detectionStartedRef.current = true;
|
|
3189
|
+
if (watchdogTimerRef.current) {
|
|
3190
|
+
clearTimeout(watchdogTimerRef.current);
|
|
3191
|
+
watchdogTimerRef.current = null;
|
|
3192
|
+
}
|
|
3193
|
+
detectFace2();
|
|
3135
3194
|
};
|
|
3195
|
+
const video = videoRef.current;
|
|
3196
|
+
if (video && video.readyState >= 2) {
|
|
3197
|
+
const startTimer = setTimeout(startDetection, 200);
|
|
3198
|
+
return () => {
|
|
3199
|
+
isRunning = false;
|
|
3200
|
+
clearTimeout(startTimer);
|
|
3201
|
+
if (progressIntervalRef.current) {
|
|
3202
|
+
clearInterval(progressIntervalRef.current);
|
|
3203
|
+
progressIntervalRef.current = null;
|
|
3204
|
+
}
|
|
3205
|
+
holdStartTimeRef.current = null;
|
|
3206
|
+
};
|
|
3207
|
+
} else {
|
|
3208
|
+
const onCanPlay = () => {
|
|
3209
|
+
if (isRunning) startDetection();
|
|
3210
|
+
};
|
|
3211
|
+
let pollCount = 0;
|
|
3212
|
+
const MAX_POLLS = 50;
|
|
3213
|
+
const pollTimer = window.setInterval(() => {
|
|
3214
|
+
pollCount++;
|
|
3215
|
+
if (!isRunning) {
|
|
3216
|
+
clearInterval(pollTimer);
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
if (videoRef.current && videoRef.current.readyState >= 2) {
|
|
3220
|
+
clearInterval(pollTimer);
|
|
3221
|
+
videoRef.current.removeEventListener("canplay", onCanPlay);
|
|
3222
|
+
startDetection();
|
|
3223
|
+
} else if (pollCount >= MAX_POLLS) {
|
|
3224
|
+
clearInterval(pollTimer);
|
|
3225
|
+
}
|
|
3226
|
+
}, 300);
|
|
3227
|
+
video == null ? void 0 : video.addEventListener("canplay", onCanPlay, { once: true });
|
|
3228
|
+
return () => {
|
|
3229
|
+
isRunning = false;
|
|
3230
|
+
clearInterval(pollTimer);
|
|
3231
|
+
video == null ? void 0 : video.removeEventListener("canplay", onCanPlay);
|
|
3232
|
+
if (progressIntervalRef.current) {
|
|
3233
|
+
clearInterval(progressIntervalRef.current);
|
|
3234
|
+
progressIntervalRef.current = null;
|
|
3235
|
+
}
|
|
3236
|
+
holdStartTimeRef.current = null;
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3136
3239
|
}, [state.status]);
|
|
3137
3240
|
const capturePhoto = async () => {
|
|
3138
3241
|
if (!videoRef.current || !canvasRef.current || isCapturingRef.current) {
|
|
@@ -3287,14 +3390,37 @@ function BiometricRegistrationModal({
|
|
|
3287
3390
|
}
|
|
3288
3391
|
)
|
|
3289
3392
|
] }) }),
|
|
3290
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível
|
|
3393
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Não foi possível processar" }),
|
|
3291
3394
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: state.message }),
|
|
3292
3395
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "button-group", children: [
|
|
3293
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
|
|
3396
|
+
state.retryable && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "secondary-button", onClick: () => dispatch({ type: "RESET" }), children: "Tentar Novamente" }),
|
|
3294
3397
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ghost-button", onClick: onClose, children: "Cancelar" })
|
|
3295
3398
|
] })
|
|
3296
3399
|
] });
|
|
3297
3400
|
};
|
|
3401
|
+
const renderCameraLoading = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "processing-container", children: [
|
|
3402
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "spinner-wrapper", children: [
|
|
3403
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner" }),
|
|
3404
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "spinner-ring" })
|
|
3405
|
+
] }),
|
|
3406
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h3", { children: "Inicializando câmera" }),
|
|
3407
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Aguarde enquanto preparamos a câmera..." }),
|
|
3408
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
3409
|
+
"div",
|
|
3410
|
+
{
|
|
3411
|
+
className: "branding",
|
|
3412
|
+
style: {
|
|
3413
|
+
marginTop: "40px",
|
|
3414
|
+
paddingTop: "24px",
|
|
3415
|
+
borderTop: "1px solid rgba(255, 255, 255, 0.1)"
|
|
3416
|
+
},
|
|
3417
|
+
children: [
|
|
3418
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "NeoFaceId by" }),
|
|
3419
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "brand-name", children: "OCTA" })
|
|
3420
|
+
]
|
|
3421
|
+
}
|
|
3422
|
+
)
|
|
3423
|
+
] });
|
|
3298
3424
|
const renderSuccess = () => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "result-container success", children: [
|
|
3299
3425
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "result-icon success-icon", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", children: [
|
|
3300
3426
|
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "30", cy: "30", r: "28", stroke: "currentColor", strokeWidth: "3" }),
|
|
@@ -3876,6 +4002,7 @@ function BiometricRegistrationModal({
|
|
|
3876
4002
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "modal-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "modal-content", children: [
|
|
3877
4003
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "close-button", onClick: onClose, children: "×" }),
|
|
3878
4004
|
state.status === "liveness" && renderLivenessDetection(),
|
|
4005
|
+
state.status === "camera-loading" && renderCameraLoading(),
|
|
3879
4006
|
state.status === "processing" && renderProcessing(),
|
|
3880
4007
|
state.status === "error" && renderError(),
|
|
3881
4008
|
state.status === "success" && renderSuccess()
|
|
@@ -5844,8 +5971,8 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
|
|
|
5844
5971
|
initializeBiometricDetection,
|
|
5845
5972
|
isAdvancedDetectionAvailable
|
|
5846
5973
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
5847
|
-
const VERSION = "1.
|
|
5848
|
-
const RELEASE_DATE = "2026-02-
|
|
5974
|
+
const VERSION = "1.13.0";
|
|
5975
|
+
const RELEASE_DATE = "2026-02-20";
|
|
5849
5976
|
class OnboardingCaptureModal {
|
|
5850
5977
|
constructor(options) {
|
|
5851
5978
|
__publicField(this, "overlay", null);
|
|
@@ -6071,11 +6198,9 @@ class NeoFaceID {
|
|
|
6071
6198
|
*/
|
|
6072
6199
|
constructor(config) {
|
|
6073
6200
|
__publicField(this, "appToken");
|
|
6074
|
-
__publicField(this, "baseUrl");
|
|
6075
6201
|
__publicField(this, "signature");
|
|
6076
6202
|
__publicField(this, "sessionData");
|
|
6077
6203
|
this.appToken = config.appToken;
|
|
6078
|
-
this.baseUrl = config.baseUrl || getBaseUrl();
|
|
6079
6204
|
this.signature = config.signature;
|
|
6080
6205
|
this.sessionData = config.sessionData;
|
|
6081
6206
|
if (this.signature && !this.validateSignatureFormat(this.signature)) {
|
|
@@ -6164,7 +6289,7 @@ class NeoFaceID {
|
|
|
6164
6289
|
* ```
|
|
6165
6290
|
*/
|
|
6166
6291
|
async loginRecognition(options) {
|
|
6167
|
-
const { biometricData,
|
|
6292
|
+
const { biometricData, purpose, confidenceThreshold = 0.8 } = options;
|
|
6168
6293
|
const imageBlobs = Array.isArray(biometricData) ? biometricData : [biometricData];
|
|
6169
6294
|
if (imageBlobs.length === 0) {
|
|
6170
6295
|
throw new NeoFaceError(
|
|
@@ -7507,7 +7632,12 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7507
7632
|
const container = document.createElement("div");
|
|
7508
7633
|
container.id = "neoface-biometric-registration-container";
|
|
7509
7634
|
document.body.appendChild(container);
|
|
7635
|
+
let rootRef = null;
|
|
7510
7636
|
const cleanup = () => {
|
|
7637
|
+
if (rootRef) {
|
|
7638
|
+
rootRef.unmount();
|
|
7639
|
+
rootRef = null;
|
|
7640
|
+
}
|
|
7511
7641
|
if (document.body.contains(container)) {
|
|
7512
7642
|
document.body.removeChild(container);
|
|
7513
7643
|
}
|
|
@@ -7517,7 +7647,6 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7517
7647
|
personData,
|
|
7518
7648
|
applicationToken,
|
|
7519
7649
|
useRealApi: (options == null ? void 0 : options.useRealApi) ?? true,
|
|
7520
|
-
// Sempre usa API real em produção
|
|
7521
7650
|
onClose: cleanup,
|
|
7522
7651
|
onSuccess: (result) => {
|
|
7523
7652
|
cleanup();
|
|
@@ -7528,8 +7657,8 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
|
|
|
7528
7657
|
callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
|
|
7529
7658
|
}
|
|
7530
7659
|
});
|
|
7531
|
-
|
|
7532
|
-
|
|
7660
|
+
rootRef = clientExports.createRoot(container);
|
|
7661
|
+
rootRef.render(modalElement);
|
|
7533
7662
|
}).catch((error) => {
|
|
7534
7663
|
cleanup();
|
|
7535
7664
|
callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
|
|
@@ -7539,16 +7668,29 @@ function startLivenessCapture(applicationToken, callbacks) {
|
|
|
7539
7668
|
const container = document.createElement("div");
|
|
7540
7669
|
container.id = "neoface-liveness-capture-container";
|
|
7541
7670
|
document.body.appendChild(container);
|
|
7671
|
+
let completed = false;
|
|
7672
|
+
let rootRef = null;
|
|
7542
7673
|
const cleanup = () => {
|
|
7674
|
+
if (rootRef) {
|
|
7675
|
+
rootRef.unmount();
|
|
7676
|
+
rootRef = null;
|
|
7677
|
+
}
|
|
7543
7678
|
if (document.body.contains(container)) {
|
|
7544
7679
|
document.body.removeChild(container);
|
|
7545
7680
|
}
|
|
7546
7681
|
};
|
|
7682
|
+
const handleClose = () => {
|
|
7683
|
+
cleanup();
|
|
7684
|
+
if (!completed && callbacks.onCancel) {
|
|
7685
|
+
callbacks.onCancel();
|
|
7686
|
+
}
|
|
7687
|
+
};
|
|
7547
7688
|
validateToken(applicationToken).then(() => {
|
|
7548
7689
|
const modalElement = React.createElement(BiometricRegistrationModal, {
|
|
7549
7690
|
applicationToken,
|
|
7550
|
-
onClose:
|
|
7691
|
+
onClose: handleClose,
|
|
7551
7692
|
onPhotosCaptured: (photos) => {
|
|
7693
|
+
completed = true;
|
|
7552
7694
|
callbacks.onSuccess(photos);
|
|
7553
7695
|
},
|
|
7554
7696
|
onError: (error) => {
|
|
@@ -7556,8 +7698,8 @@ function startLivenessCapture(applicationToken, callbacks) {
|
|
|
7556
7698
|
callbacks.onError("LIVENESS_CAPTURE_ERROR", error);
|
|
7557
7699
|
}
|
|
7558
7700
|
});
|
|
7559
|
-
|
|
7560
|
-
|
|
7701
|
+
rootRef = clientExports.createRoot(container);
|
|
7702
|
+
rootRef.render(modalElement);
|
|
7561
7703
|
}).catch((error) => {
|
|
7562
7704
|
cleanup();
|
|
7563
7705
|
callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
|