@neofaceid/web-sdk 1.1.1 → 1.3.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.
@@ -1108,6 +1108,7 @@ const useCamera = () => {
1108
1108
  };
1109
1109
  var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
1110
1110
  ErrorType2["NETWORK"] = "NetworkError";
1111
+ ErrorType2["NETWORK_ERROR"] = "NetworkError";
1111
1112
  ErrorType2["INVALID_TOKEN"] = "InvalidTokenError";
1112
1113
  ErrorType2["RECOGNITION_FAILED"] = "RecognitionFailedError";
1113
1114
  ErrorType2["LOGIN_FAILED"] = "LoginFailedError";
@@ -1115,12 +1116,18 @@ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
1115
1116
  ErrorType2["API_ERROR"] = "ApiError";
1116
1117
  ErrorType2["PERSON_NOT_FOUND"] = "PersonNotFoundError";
1117
1118
  ErrorType2["INITIALIZATION_ERROR"] = "InitializationError";
1118
- ErrorType2["NETWORK_ERROR"] = "NetworkError";
1119
1119
  ErrorType2["CAMERA_ERROR"] = "CameraError";
1120
1120
  ErrorType2["CAPTURE_ERROR"] = "CaptureError";
1121
+ ErrorType2["NOT_FOUND"] = "NotFoundError";
1122
+ ErrorType2["UNKNOWN"] = "UnknownError";
1121
1123
  return ErrorType2;
1122
1124
  })(ErrorType || {});
1123
1125
  class NeoFaceError extends Error {
1126
+ /**
1127
+ * Constrói um erro do SDK com mensagem e tipo categórico.
1128
+ * @param message Mensagem descritiva do erro
1129
+ * @param type Tipo categórico do erro
1130
+ */
1124
1131
  constructor(message, type) {
1125
1132
  super(message);
1126
1133
  __publicField(this, "type");
@@ -1197,6 +1204,92 @@ const validateToken = async (applicationToken) => {
1197
1204
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1198
1205
  }
1199
1206
  };
1207
+ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
1208
+ ensureSecureContext();
1209
+ const controller = createTimeoutController();
1210
+ try {
1211
+ const response = await fetch(`${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/`, {
1212
+ method: "GET",
1213
+ headers: {
1214
+ "X-App-Token": applicationToken
1215
+ },
1216
+ signal: controller.signal
1217
+ });
1218
+ if (!response.ok) {
1219
+ if (response.status === 401 || response.status === 403) {
1220
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1221
+ }
1222
+ if (response.status === 404) {
1223
+ throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1224
+ }
1225
+ throw new NeoFaceError(`Falha ao obter detalhes (${response.status})`, ErrorType.NETWORK);
1226
+ }
1227
+ const data = await response.json();
1228
+ return data;
1229
+ } catch (error) {
1230
+ if (error instanceof Error) {
1231
+ if (error.name === "AbortError") {
1232
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1233
+ }
1234
+ if (error instanceof NeoFaceError) {
1235
+ throw error;
1236
+ }
1237
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1238
+ }
1239
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1240
+ }
1241
+ };
1242
+ const completeOnboarding = async (applicationToken, onboardingToken, faceImage, documentImage) => {
1243
+ ensureSecureContext();
1244
+ const controller = createTimeoutController();
1245
+ const compressedFace = await compressImage(faceImage);
1246
+ const compressedDocument = await compressImage(documentImage);
1247
+ try {
1248
+ const formData = new FormData();
1249
+ formData.append("face_image", compressedFace, "face.jpg");
1250
+ formData.append("document_image", compressedDocument, "document.jpg");
1251
+ const response = await fetch(
1252
+ `${API_BASE_URL}/api/v1/onboardings/${encodeURIComponent(onboardingToken)}/complete`,
1253
+ {
1254
+ method: "POST",
1255
+ headers: {
1256
+ "X-App-Token": applicationToken
1257
+ },
1258
+ body: formData,
1259
+ signal: controller.signal
1260
+ }
1261
+ );
1262
+ const data = await response.json();
1263
+ if (!response.ok) {
1264
+ if (response.status === 401 || response.status === 403) {
1265
+ throw new NeoFaceError("Token de aplicação inválido ou expirado", ErrorType.INVALID_TOKEN);
1266
+ }
1267
+ if (response.status === 404) {
1268
+ throw new NeoFaceError("Link de onboarding não encontrado", ErrorType.NOT_FOUND);
1269
+ }
1270
+ throw new NeoFaceError((data == null ? void 0 : data.error) || (data == null ? void 0 : data.message) || "Falha ao concluir onboarding", ErrorType.NETWORK);
1271
+ }
1272
+ return {
1273
+ success: !!data.success,
1274
+ message: data.message || "Onboarding concluído",
1275
+ person_id: data.person_id,
1276
+ identity_data_id: data.identity_data_id,
1277
+ confidence_score: data.confidence_score,
1278
+ processing_time: data.processing_time
1279
+ };
1280
+ } catch (error) {
1281
+ if (error instanceof Error) {
1282
+ if (error.name === "AbortError") {
1283
+ throw new NeoFaceError("Tempo de requisição excedido", ErrorType.NETWORK);
1284
+ }
1285
+ if (error instanceof NeoFaceError) {
1286
+ throw error;
1287
+ }
1288
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1289
+ }
1290
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
1291
+ }
1292
+ };
1200
1293
  const recognize = async (image, applicationToken) => {
1201
1294
  ensureSecureContext();
1202
1295
  const controller = createTimeoutController();
@@ -1485,10 +1578,10 @@ const registerPersonWithoutFace = async (personData, applicationToken) => {
1485
1578
  const registerPersonWithBiometric = async (personData, facePhotos, applicationToken) => {
1486
1579
  ensureSecureContext();
1487
1580
  if (!facePhotos || facePhotos.length === 0) {
1488
- throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION);
1581
+ throw new NeoFaceError("At least one face photo is required for biometric registration", ErrorType.VALIDATION_ERROR);
1489
1582
  }
1490
1583
  if (facePhotos.length > 5) {
1491
- throw new NeoFaceError("Maximum of 5 face photos allowed", ErrorType.VALIDATION);
1584
+ throw new NeoFaceError("Maximum of 5 face photos allowed", ErrorType.VALIDATION_ERROR);
1492
1585
  }
1493
1586
  const controller = createTimeoutController();
1494
1587
  try {
@@ -1530,13 +1623,13 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1530
1623
  try {
1531
1624
  const errorData = await response.json();
1532
1625
  if (errorData.error === "fraud_detected") {
1533
- throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION);
1626
+ throw new NeoFaceError("Fraud detected in biometric images. Registration blocked.", ErrorType.VALIDATION_ERROR);
1534
1627
  }
1535
1628
  if (errorData.error === "user_exists") {
1536
- throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION);
1629
+ throw new NeoFaceError("User with this email already exists", ErrorType.VALIDATION_ERROR);
1537
1630
  }
1538
1631
  if (errorData.error === "person_exists") {
1539
- throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION);
1632
+ throw new NeoFaceError("Person with this CPF already exists", ErrorType.VALIDATION_ERROR);
1540
1633
  }
1541
1634
  throw new NeoFaceError(errorData.message || `Server returned status ${response.status}`, ErrorType.NETWORK);
1542
1635
  } catch (parseError) {
@@ -1566,6 +1659,121 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
1566
1659
  throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1567
1660
  }
1568
1661
  };
1662
+ const loginWithEmail = async (email, password, applicationToken) => {
1663
+ ensureSecureContext();
1664
+ const controller = createTimeoutController();
1665
+ try {
1666
+ const response = await fetch(`${API_BASE_URL}/api/v1/auth/login/`, {
1667
+ method: "POST",
1668
+ headers: {
1669
+ "Content-Type": "application/json",
1670
+ "X-App-Token": applicationToken
1671
+ },
1672
+ body: JSON.stringify({ email, password }),
1673
+ signal: controller.signal
1674
+ });
1675
+ if (!response.ok) {
1676
+ if (response.status === 401 || response.status === 403) {
1677
+ throw new NeoFaceError("Invalid credentials or application token", ErrorType.INVALID_TOKEN);
1678
+ }
1679
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1680
+ }
1681
+ const data = await response.json();
1682
+ return {
1683
+ success: true,
1684
+ // Compatível com diferentes formatos de resposta do backend
1685
+ accessToken: data.access_token || data.access,
1686
+ alias: data.user && data.user.alias || data.alias,
1687
+ user: {
1688
+ id: data.user && data.user.id || "",
1689
+ email: data.user && data.user.email || "",
1690
+ role: data.user && data.user.role || "",
1691
+ validated: data.user && data.user.validated || false,
1692
+ active: data.user && data.user.active || false
1693
+ }
1694
+ };
1695
+ } catch (error) {
1696
+ if (error instanceof Error) {
1697
+ if (error.name === "AbortError") {
1698
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1699
+ }
1700
+ if (error instanceof NeoFaceError) {
1701
+ throw error;
1702
+ }
1703
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1704
+ }
1705
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1706
+ }
1707
+ };
1708
+ const identifyPerson = async (image, applicationToken) => {
1709
+ var _a, _b;
1710
+ ensureSecureContext();
1711
+ const controller = createTimeoutController();
1712
+ const compressedImage = await compressImage(image);
1713
+ const base64Image = await new Promise((resolve, reject) => {
1714
+ const reader = new FileReader();
1715
+ reader.onload = () => {
1716
+ const result = reader.result;
1717
+ resolve(result.split(",")[1]);
1718
+ };
1719
+ reader.onerror = reject;
1720
+ reader.readAsDataURL(compressedImage);
1721
+ });
1722
+ try {
1723
+ const response = await fetch(`${API_BASE_URL}/api/v1/external/recognition/face/`, {
1724
+ method: "POST",
1725
+ headers: {
1726
+ "Content-Type": "application/json",
1727
+ "X-App-Token": applicationToken
1728
+ },
1729
+ body: JSON.stringify({
1730
+ image: base64Image,
1731
+ purpose: "SIMPLE_IDENTIFICATION_F"
1732
+ }),
1733
+ signal: controller.signal
1734
+ });
1735
+ if (!response.ok) {
1736
+ if (response.status === 401 || response.status === 403) {
1737
+ throw new NeoFaceError("Invalid or expired application token", ErrorType.INVALID_TOKEN);
1738
+ }
1739
+ throw new NeoFaceError(`Server returned status ${response.status}`, ErrorType.NETWORK);
1740
+ }
1741
+ const data = await response.json();
1742
+ if (!data.success) {
1743
+ throw new NeoFaceError(data.message || "Person identification failed", ErrorType.RECOGNITION_FAILED);
1744
+ }
1745
+ const personName = ((_a = data.person) == null ? void 0 : _a.name) || "";
1746
+ const birthDateStr = (_b = data.person) == null ? void 0 : _b.birth_date;
1747
+ if (!personName) {
1748
+ throw new NeoFaceError("Person name not found in response", ErrorType.PERSON_NOT_FOUND);
1749
+ }
1750
+ if (!birthDateStr) {
1751
+ throw new NeoFaceError("Birth date not found in response", ErrorType.PERSON_NOT_FOUND);
1752
+ }
1753
+ const birthDate = new Date(birthDateStr);
1754
+ const today = /* @__PURE__ */ new Date();
1755
+ let age = today.getFullYear() - birthDate.getFullYear();
1756
+ const monthDiff = today.getMonth() - birthDate.getMonth();
1757
+ if (monthDiff < 0 || monthDiff === 0 && today.getDate() < birthDate.getDate()) {
1758
+ age--;
1759
+ }
1760
+ return {
1761
+ name: personName,
1762
+ age
1763
+ };
1764
+ } catch (error) {
1765
+ if (error instanceof Error) {
1766
+ if (error.name === "AbortError") {
1767
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
1768
+ }
1769
+ if (error instanceof NeoFaceError) {
1770
+ throw error;
1771
+ }
1772
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
1773
+ }
1774
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
1775
+ }
1776
+ };
1569
1777
  const modalReducer$1 = (state, action) => {
1570
1778
  switch (action.type) {
1571
1779
  case "PERMISSION_GRANTED":
@@ -3169,9 +3377,10 @@ class BiometricCaptureModal {
3169
3377
  context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
3170
3378
  const imageData = this.canvas.toDataURL("image/jpeg", 0.8);
3171
3379
  const base64Data = imageData.split(",")[1];
3380
+ const dataUrl = `data:image/jpeg;base64,${base64Data}`;
3172
3381
  let detectedType = this.options.mode === "auto" ? await this.detectBiometricType(base64Data) : this.options.mode;
3173
3382
  this.close();
3174
- this.options.onSuccess(base64Data, detectedType);
3383
+ this.options.onSuccess(dataUrl, detectedType);
3175
3384
  } catch (error) {
3176
3385
  this.options.onError(
3177
3386
  new NeoFaceError(
@@ -3507,6 +3716,70 @@ class BiometricCaptureModal {
3507
3716
  document.head.appendChild(style);
3508
3717
  }
3509
3718
  }
3719
+ class EmailPasswordModal {
3720
+ constructor(options) {
3721
+ __publicField(this, "options");
3722
+ __publicField(this, "modalElement", null);
3723
+ this.options = options;
3724
+ }
3725
+ /**
3726
+ * Abre o modal de login com email e senha.
3727
+ */
3728
+ async open() {
3729
+ this.createModal();
3730
+ document.body.appendChild(this.modalElement);
3731
+ }
3732
+ /**
3733
+ * Fecha o modal.
3734
+ */
3735
+ close() {
3736
+ if (this.modalElement) {
3737
+ document.body.removeChild(this.modalElement);
3738
+ this.modalElement = null;
3739
+ }
3740
+ if (this.options.onCancel) {
3741
+ this.options.onCancel();
3742
+ }
3743
+ }
3744
+ createModal() {
3745
+ this.modalElement = document.createElement("div");
3746
+ this.modalElement.className = "neoface-modal";
3747
+ this.modalElement.innerHTML = `
3748
+ <div class="modal-content">
3749
+ <h2>${this.options.title || "Login com Email e Senha"}</h2>
3750
+ <p>${this.options.subtitle || "Informe seus dados para login."}</p>
3751
+ <form id="email-password-form">
3752
+ <input type="email" id="email" placeholder="Email" required />
3753
+ <input type="password" id="password" placeholder="Senha" required />
3754
+ <button type="submit">Entrar</button>
3755
+ </form>
3756
+ <button class="cancel-button">Cancelar</button>
3757
+ </div>
3758
+ `;
3759
+ const form = this.modalElement.querySelector("#email-password-form");
3760
+ form.addEventListener("submit", this.handleSubmit.bind(this));
3761
+ const cancelButton = this.modalElement.querySelector(".cancel-button");
3762
+ cancelButton.addEventListener("click", this.close.bind(this));
3763
+ }
3764
+ async handleSubmit(event) {
3765
+ event.preventDefault();
3766
+ const emailInput = this.modalElement.querySelector("#email");
3767
+ const passwordInput = this.modalElement.querySelector("#password");
3768
+ const email = emailInput.value.trim();
3769
+ const password = passwordInput.value.trim();
3770
+ if (!email || !password) {
3771
+ this.options.onError(new NeoFaceError("Email e senha são obrigatórios", ErrorType.VALIDATION_ERROR));
3772
+ return;
3773
+ }
3774
+ try {
3775
+ const result = await loginWithEmail(email, password, this.options.applicationToken);
3776
+ this.options.onSuccess(result);
3777
+ this.close();
3778
+ } catch (error) {
3779
+ this.options.onError(error);
3780
+ }
3781
+ }
3782
+ }
3510
3783
  async function startFaceLogin(options) {
3511
3784
  try {
3512
3785
  const isValidToken = await validateToken(options.applicationToken);
@@ -3542,6 +3815,134 @@ async function startFaceLogin(options) {
3542
3815
  );
3543
3816
  }
3544
3817
  }
3818
+ async function startHandLogin(options) {
3819
+ try {
3820
+ const isValidToken = await validateToken(options.applicationToken);
3821
+ if (!isValidToken) {
3822
+ options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3823
+ return;
3824
+ }
3825
+ const captureOptions = {
3826
+ mode: "hand",
3827
+ onSuccess: async (imageData, _detectedType) => {
3828
+ try {
3829
+ const imageBlob = await fetch(imageData).then((res) => res.blob());
3830
+ const result = await loginWithBiometric(imageBlob, options.applicationToken);
3831
+ options.onSuccess(result);
3832
+ } catch (error) {
3833
+ options.onError(error);
3834
+ }
3835
+ },
3836
+ onError: options.onError,
3837
+ onCancel: options.onCancel,
3838
+ countdown: options.countdown,
3839
+ title: options.title || "Login por Mão",
3840
+ subtitle: options.subtitle || "Posicione sua mão dentro do quadro para fazer login."
3841
+ };
3842
+ const modal = new BiometricCaptureModal(captureOptions);
3843
+ await modal.open();
3844
+ } catch (error) {
3845
+ options.onError(
3846
+ new NeoFaceError(
3847
+ "Erro ao inicializar login por mão: " + error.message,
3848
+ ErrorType.INITIALIZATION_ERROR
3849
+ )
3850
+ );
3851
+ }
3852
+ }
3853
+ async function startAutoLogin(options) {
3854
+ try {
3855
+ const isValidToken = await validateToken(options.applicationToken);
3856
+ if (!isValidToken) {
3857
+ options.onError(new NeoFaceError("Token de aplicação inválido", ErrorType.INVALID_TOKEN));
3858
+ return;
3859
+ }
3860
+ const captureOptions = {
3861
+ mode: "auto",
3862
+ onSuccess: async (imageData, _detectedType) => {
3863
+ try {
3864
+ const imageBlob = await fetch(imageData).then((res) => res.blob());
3865
+ const result = await loginWithBiometric(imageBlob, options.applicationToken);
3866
+ options.onSuccess(result);
3867
+ } catch (error) {
3868
+ options.onError(error);
3869
+ }
3870
+ },
3871
+ onError: options.onError,
3872
+ onCancel: options.onCancel,
3873
+ countdown: options.countdown,
3874
+ title: options.title || "Login Biométrico",
3875
+ subtitle: options.subtitle || "Posicione seu rosto ou mão dentro do quadro para login automático."
3876
+ };
3877
+ const modal = new BiometricCaptureModal(captureOptions);
3878
+ await modal.open();
3879
+ } catch (error) {
3880
+ options.onError(
3881
+ new NeoFaceError(
3882
+ "Erro ao inicializar login automático: " + error.message,
3883
+ ErrorType.INITIALIZATION_ERROR
3884
+ )
3885
+ );
3886
+ }
3887
+ }
3888
+ function biometricLogin(applicationToken, mode = "auto") {
3889
+ return new Promise((resolve, reject) => {
3890
+ const options = {
3891
+ applicationToken,
3892
+ onSuccess: resolve,
3893
+ onError: reject
3894
+ };
3895
+ switch (mode) {
3896
+ case "face":
3897
+ startFaceLogin(options);
3898
+ break;
3899
+ case "hand":
3900
+ startHandLogin(options);
3901
+ break;
3902
+ case "auto":
3903
+ startAutoLogin(options);
3904
+ break;
3905
+ default:
3906
+ reject(new NeoFaceError("Modo de login inválido", ErrorType.VALIDATION_ERROR));
3907
+ }
3908
+ });
3909
+ }
3910
+ function biometricLoginWithFallback(applicationToken, mode = "auto") {
3911
+ return new Promise((resolve, reject) => {
3912
+ const options = {
3913
+ applicationToken,
3914
+ onSuccess: resolve,
3915
+ onError: (error) => {
3916
+ if (error.type === ErrorType.LOGIN_FAILED) {
3917
+ const fallbackOptions = {
3918
+ applicationToken,
3919
+ onSuccess: resolve,
3920
+ onError: reject,
3921
+ title: "Login Alternativo",
3922
+ subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
3923
+ };
3924
+ const modal = new EmailPasswordModal(fallbackOptions);
3925
+ modal.open();
3926
+ } else {
3927
+ reject(error);
3928
+ }
3929
+ }
3930
+ };
3931
+ switch (mode) {
3932
+ case "face":
3933
+ startFaceLogin(options);
3934
+ break;
3935
+ case "hand":
3936
+ startHandLogin(options);
3937
+ break;
3938
+ case "auto":
3939
+ startAutoLogin(options);
3940
+ break;
3941
+ default:
3942
+ reject(new NeoFaceError("Modo de login inválido", ErrorType.VALIDATION_ERROR));
3943
+ }
3944
+ });
3945
+ }
3545
3946
  async function detectBiometricType(imageData) {
3546
3947
  try {
3547
3948
  const img = await loadImageFromBase64(imageData);
@@ -3578,7 +3979,8 @@ async function detectFace(img) {
3578
3979
  if (typeof window !== "undefined" && window.faceapi) {
3579
3980
  const faceapi2 = window.faceapi;
3580
3981
  await loadFaceApiModels();
3581
- const detections = await faceapi2.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors();
3982
+ const tinyOptions = new faceapi2.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });
3983
+ const detections = await faceapi2.detectAllFaces(img, tinyOptions).withFaceLandmarks();
3582
3984
  if (detections && detections.length > 0) {
3583
3985
  const bestDetection = detections.reduce(
3584
3986
  (best, current) => current.detection.score > best.detection.score ? current : best
@@ -3683,8 +4085,7 @@ async function loadFaceApiModels() {
3683
4085
  try {
3684
4086
  await Promise.all([
3685
4087
  faceapi2.nets.tinyFaceDetector.loadFromUri("./models"),
3686
- faceapi2.nets.faceLandmark68Net.loadFromUri("./models"),
3687
- faceapi2.nets.faceRecognitionNet.loadFromUri("./models")
4088
+ faceapi2.nets.faceLandmark68Net.loadFromUri("./models")
3688
4089
  ]);
3689
4090
  } catch (error) {
3690
4091
  console.warn("Não foi possível carregar modelos do face-api.js:", error);
@@ -3708,8 +4109,231 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
3708
4109
  initializeBiometricDetection,
3709
4110
  isAdvancedDetectionAvailable
3710
4111
  }, Symbol.toStringTag, { value: "Module" }));
3711
- const VERSION = "1.1.1";
3712
- const RELEASE_DATE = "2025-11-02";
4112
+ const VERSION = "1.3.0";
4113
+ const RELEASE_DATE = "2025-11-17";
4114
+ class OnboardingCaptureModal {
4115
+ constructor(options) {
4116
+ __publicField(this, "overlay", null);
4117
+ __publicField(this, "video", null);
4118
+ __publicField(this, "canvas", null);
4119
+ __publicField(this, "stream", null);
4120
+ __publicField(this, "step", "face");
4121
+ __publicField(this, "countdownSeconds");
4122
+ __publicField(this, "title");
4123
+ __publicField(this, "subtitle");
4124
+ __publicField(this, "faceBlob", null);
4125
+ __publicField(this, "documentBlob", null);
4126
+ this.countdownSeconds = (options == null ? void 0 : options.countdown) ?? 3;
4127
+ this.title = options == null ? void 0 : options.title;
4128
+ this.subtitle = options == null ? void 0 : options.subtitle;
4129
+ }
4130
+ /**
4131
+ * Abre o modal, inicia câmera e gerencia o fluxo de captura dos dois passos.
4132
+ * Retorna uma promessa resolvida com os Blobs de face e documento.
4133
+ */
4134
+ async open() {
4135
+ await this.createDom();
4136
+ await this.startCamera();
4137
+ await this.runFaceCaptureCountdown();
4138
+ this.faceBlob = await this.captureFrame();
4139
+ this.step = "document";
4140
+ this.updateTexts("Captura do Documento", 'Posicione seu documento visível e clique em "Capturar"');
4141
+ await this.waitForUserCaptureClick();
4142
+ this.documentBlob = await this.captureFrame();
4143
+ await this.close();
4144
+ if (!this.faceBlob || !this.documentBlob) {
4145
+ throw new Error("Falha na captura das imagens de onboarding");
4146
+ }
4147
+ return { face: this.faceBlob, document: this.documentBlob };
4148
+ }
4149
+ /**
4150
+ * Cria estrutura de DOM do modal e elementos de UI.
4151
+ */
4152
+ async createDom() {
4153
+ const overlay = document.createElement("div");
4154
+ overlay.className = "neofaceid-modal-overlay";
4155
+ overlay.innerHTML = `
4156
+ <div class="neofaceid-modal">
4157
+ <div class="neofaceid-modal-header">
4158
+ <h2 class="neofaceid-title">${this.title ?? "Onboarding NeoFaceID"}</h2>
4159
+ <button class="neofaceid-close-btn" aria-label="Fechar">×</button>
4160
+ </div>
4161
+ <div class="neofaceid-modal-body">
4162
+ <p class="neofaceid-subtitle">${this.subtitle ?? "Vamos capturar sua face e documento"}</p>
4163
+ <div class="neofaceid-camera-container">
4164
+ <video class="neofaceid-video" autoplay playsinline></video>
4165
+ <canvas class="neofaceid-canvas"></canvas>
4166
+ <div class="neofaceid-overlay">
4167
+ <div class="neofaceid-frame"></div>
4168
+ <div class="neofaceid-countdown" style="display:none"><span class="neofaceid-countdown-number">${this.countdownSeconds}</span></div>
4169
+ </div>
4170
+ </div>
4171
+ <div class="neofaceid-status"><p class="neofaceid-status-text">Posicione seu rosto no quadro</p></div>
4172
+ </div>
4173
+ <div class="neofaceid-modal-footer">
4174
+ <button class="neofaceid-cancel-btn">Cancelar</button>
4175
+ <button class="neofaceid-capture-btn" disabled>Capturar</button>
4176
+ </div>
4177
+ </div>
4178
+ `;
4179
+ const style = document.createElement("style");
4180
+ style.textContent = `
4181
+ .neofaceid-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; z-index: 9999; }
4182
+ .neofaceid-modal { background:#fff; border-radius:12px; width: 92%; max-width: 640px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow:hidden; }
4183
+ .neofaceid-modal-header { padding: 16px 20px; border-bottom: 1px solid #eee; display:flex; align-items:center; justify-content:space-between; }
4184
+ .neofaceid-title { margin:0; font-size: 20px; color:#333; }
4185
+ .neofaceid-close-btn { background:none; border:none; font-size:24px; cursor:pointer; color:#666; }
4186
+ .neofaceid-modal-body { padding: 16px 20px; }
4187
+ .neofaceid-subtitle { margin: 0 0 12px 0; color:#666; }
4188
+ .neofaceid-camera-container { position: relative; background:#000; border-radius:8px; overflow:hidden; aspect-ratio: 4/3; margin-bottom: 12px; }
4189
+ .neofaceid-video { width:100%; height:100%; object-fit: cover; }
4190
+ .neofaceid-canvas { position:absolute; top:0; left:0; }
4191
+ .neofaceid-overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; }
4192
+ .neofaceid-frame { width: 200px; height: 200px; border: 3px solid #4CAF50; border-radius: 50%; box-shadow: 0 0 0 2px rgba(76,175,80,0.3); }
4193
+ .neofaceid-countdown { position:absolute; top:16px; right:16px; background: rgba(0,0,0,0.7); color:#fff; width: 48px; height:48px; display:flex; align-items:center; justify-content:center; border-radius:50%; }
4194
+ .neofaceid-modal-footer { padding: 16px 20px; border-top: 1px solid #eee; display:flex; gap: 8px; justify-content:flex-end; }
4195
+ .neofaceid-cancel-btn { background:#f5f5f5; color:#666; border:none; border-radius:6px; padding:10px 18px; cursor:pointer; }
4196
+ .neofaceid-capture-btn { background:#4CAF50; color:#fff; border:none; border-radius:6px; padding:10px 18px; cursor:pointer; }
4197
+ .neofaceid-capture-btn:disabled { background:#ccc; cursor:not-allowed; }
4198
+ `;
4199
+ document.head.appendChild(style);
4200
+ document.body.appendChild(overlay);
4201
+ this.overlay = overlay;
4202
+ this.video = this.overlay.querySelector(".neofaceid-video");
4203
+ this.canvas = this.overlay.querySelector(".neofaceid-canvas");
4204
+ const closeBtn = this.overlay.querySelector(".neofaceid-close-btn");
4205
+ const cancelBtn = this.overlay.querySelector(".neofaceid-cancel-btn");
4206
+ closeBtn.onclick = () => this.close();
4207
+ cancelBtn.onclick = () => this.close();
4208
+ const captureBtn = this.overlay.querySelector(".neofaceid-capture-btn");
4209
+ captureBtn.disabled = true;
4210
+ }
4211
+ /**
4212
+ * Inicia a câmera do usuário com resolução adequada.
4213
+ */
4214
+ async startCamera() {
4215
+ const constraints = {
4216
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } },
4217
+ audio: false
4218
+ };
4219
+ this.stream = await navigator.mediaDevices.getUserMedia(constraints);
4220
+ if (!this.video)
4221
+ throw new Error("Elemento de vídeo não encontrado");
4222
+ this.video.srcObject = this.stream;
4223
+ await this.video.play();
4224
+ }
4225
+ /**
4226
+ * Executa a contagem regressiva e atualiza textos para captura de rosto.
4227
+ */
4228
+ async runFaceCaptureCountdown() {
4229
+ if (!this.overlay)
4230
+ return;
4231
+ const countdownEl = this.overlay.querySelector(".neofaceid-countdown");
4232
+ const countdownNumberEl = this.overlay.querySelector(".neofaceid-countdown-number");
4233
+ const statusText = this.overlay.querySelector(".neofaceid-status-text");
4234
+ countdownEl.style.display = "flex";
4235
+ statusText.textContent = "Prepare-se! Capturando seu rosto...";
4236
+ let remaining = this.countdownSeconds;
4237
+ countdownNumberEl.textContent = String(remaining);
4238
+ await new Promise((resolve) => {
4239
+ const interval = setInterval(() => {
4240
+ remaining -= 1;
4241
+ countdownNumberEl.textContent = String(remaining);
4242
+ if (remaining <= 0) {
4243
+ clearInterval(interval);
4244
+ countdownEl.style.display = "none";
4245
+ resolve();
4246
+ }
4247
+ }, 1e3);
4248
+ });
4249
+ }
4250
+ /**
4251
+ * Aguarda o clique do usuário no botão "Capturar" (para documento).
4252
+ */
4253
+ async waitForUserCaptureClick() {
4254
+ if (!this.overlay)
4255
+ return;
4256
+ const captureBtn = this.overlay.querySelector(".neofaceid-capture-btn");
4257
+ const statusText = this.overlay.querySelector(".neofaceid-status-text");
4258
+ captureBtn.disabled = false;
4259
+ statusText.textContent = 'Clique em "Capturar" quando o documento estiver visível';
4260
+ await new Promise((resolve) => {
4261
+ const handler = () => {
4262
+ captureBtn.removeEventListener("click", handler);
4263
+ resolve();
4264
+ };
4265
+ captureBtn.addEventListener("click", handler);
4266
+ });
4267
+ captureBtn.disabled = true;
4268
+ }
4269
+ /**
4270
+ * Captura o frame atual do vídeo e retorna como Blob JPEG.
4271
+ */
4272
+ async captureFrame() {
4273
+ if (!this.video || !this.canvas)
4274
+ throw new Error("Elementos de captura não encontrados");
4275
+ const ctx = this.canvas.getContext("2d");
4276
+ if (!ctx)
4277
+ throw new Error("Falha ao obter contexto do canvas");
4278
+ this.canvas.width = this.video.videoWidth;
4279
+ this.canvas.height = this.video.videoHeight;
4280
+ ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
4281
+ const blob = await new Promise((resolve, reject) => {
4282
+ this.canvas.toBlob((b) => b ? resolve(b) : reject(new Error("Falha ao gerar imagem")), "image/jpeg", 0.92);
4283
+ });
4284
+ return blob;
4285
+ }
4286
+ /**
4287
+ * Atualiza título e subtítulo do modal conforme o passo.
4288
+ */
4289
+ updateTexts(title, subtitle) {
4290
+ if (!this.overlay)
4291
+ return;
4292
+ const titleEl = this.overlay.querySelector(".neofaceid-title");
4293
+ const subtitleEl = this.overlay.querySelector(".neofaceid-subtitle");
4294
+ const frameEl = this.overlay.querySelector(".neofaceid-frame");
4295
+ titleEl.textContent = title;
4296
+ subtitleEl.textContent = subtitle;
4297
+ if (this.step === "document") {
4298
+ frameEl.style.borderRadius = "8px";
4299
+ frameEl.style.width = "280px";
4300
+ frameEl.style.height = "180px";
4301
+ }
4302
+ }
4303
+ /**
4304
+ * Fecha o modal e libera a câmera.
4305
+ */
4306
+ async close() {
4307
+ if (this.stream) {
4308
+ this.stream.getTracks().forEach((t) => t.stop());
4309
+ this.stream = null;
4310
+ }
4311
+ if (this.overlay) {
4312
+ this.overlay.remove();
4313
+ this.overlay = null;
4314
+ }
4315
+ }
4316
+ }
4317
+ const startOnboarding = async (options) => {
4318
+ const { applicationToken, onboardingToken, countdown, title, subtitle, onSuccess, onError } = options;
4319
+ try {
4320
+ const details = await getOnboardingDetails(applicationToken, onboardingToken);
4321
+ if (!(details == null ? void 0 : details.is_valid)) {
4322
+ throw new NeoFaceError("Link de onboarding inválido ou expirado", ErrorType.VALIDATION_ERROR);
4323
+ }
4324
+ const modal = new OnboardingCaptureModal({ countdown, title, subtitle });
4325
+ const { face, document: document2 } = await modal.open();
4326
+ const result = await completeOnboarding(applicationToken, onboardingToken, face, document2);
4327
+ onSuccess({ ...result, details });
4328
+ } catch (err) {
4329
+ if (err instanceof NeoFaceError) {
4330
+ onError(err);
4331
+ return;
4332
+ }
4333
+ const message = (err == null ? void 0 : err.message) || "Erro desconhecido no onboarding";
4334
+ onError(new NeoFaceError(message, ErrorType.UNKNOWN));
4335
+ }
4336
+ };
3713
4337
  function start(applicationToken, callbacks) {
3714
4338
  const container = document.createElement("div");
3715
4339
  container.id = "neoface-modal-container";
@@ -3779,7 +4403,10 @@ export {
3779
4403
  NeoFaceError,
3780
4404
  RELEASE_DATE,
3781
4405
  VERSION,
4406
+ biometricLogin,
4407
+ biometricLoginWithFallback,
3782
4408
  detectBiometricType,
4409
+ identifyPerson,
3783
4410
  initializeBiometricDetection,
3784
4411
  isAdvancedDetectionAvailable,
3785
4412
  loginWithBiometric,
@@ -3790,8 +4417,11 @@ export {
3790
4417
  registerPersonWithoutFace,
3791
4418
  simpleIdentification,
3792
4419
  start,
4420
+ startAutoLogin,
3793
4421
  startBiometricRegistration,
3794
4422
  startFaceLogin,
4423
+ startHandLogin,
4424
+ startOnboarding,
3795
4425
  validateToken
3796
4426
  };
3797
4427
  //# sourceMappingURL=neoface-id-sdk.es.js.map