@neofaceid/web-sdk 1.40.3 → 1.42.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.
@@ -148,7 +148,6 @@ const useCamera = () => {
148
148
  };
149
149
  var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
150
150
  ErrorType2["NETWORK"] = "NetworkError";
151
- ErrorType2["NETWORK_ERROR"] = "NetworkError";
152
151
  ErrorType2["INVALID_TOKEN"] = "InvalidTokenError";
153
152
  ErrorType2["RECOGNITION_FAILED"] = "RecognitionFailedError";
154
153
  ErrorType2["LOGIN_FAILED"] = "LoginFailedError";
@@ -4312,9 +4311,16 @@ function refine(fn, _params = {}) {
4312
4311
  function superRefine(fn) {
4313
4312
  return /* @__PURE__ */ _superRefine(fn);
4314
4313
  }
4314
+ const AuthMethodSchema = _enum(["face", "password"]);
4315
4315
  object({
4316
4316
  success: boolean(),
4317
4317
  accessToken: string().optional(),
4318
+ /**
4319
+ * Só chega quando `application.can_issue_user_tokens` está ligado no core.
4320
+ * Sessões longas (o console) dependem dele; fluxos curtos podem ignorá-lo.
4321
+ */
4322
+ refreshToken: string().optional(),
4323
+ method: AuthMethodSchema.optional(),
4318
4324
  alias: string().optional(),
4319
4325
  user: object({
4320
4326
  id: string(),
@@ -4548,6 +4554,10 @@ const ENVIRONMENT_URLS = {
4548
4554
  sandbox: "https://sandbox-core.neofaceid.com",
4549
4555
  production: "https://core.neofaceid.com.br"
4550
4556
  };
4557
+ const DEFAULT_AUTO_FALLBACK = {
4558
+ toPhone: false,
4559
+ toPassword: true
4560
+ };
4551
4561
  const DEFAULT_LOCALE = "pt-BR";
4552
4562
  const DEFAULT_RADIUS = 16;
4553
4563
  const DEFAULT_THEME = "light";
@@ -4564,13 +4574,31 @@ function makeDefaultConfig() {
4564
4574
  theme: DEFAULT_THEME,
4565
4575
  locale: DEFAULT_LOCALE,
4566
4576
  resolvedTheme: null,
4567
- consent: { ...DEFAULT_CONSENT }
4577
+ consent: { ...DEFAULT_CONSENT },
4578
+ autoFallback: { ...DEFAULT_AUTO_FALLBACK }
4568
4579
  };
4569
4580
  }
4570
4581
  let globalConfig = makeDefaultConfig();
4571
4582
  let consentWarningShown = false;
4572
4583
  function init(options = {}) {
4573
- const { environment, baseUrl, applicationToken, appName, accent, radius, theme, locale, consent } = options;
4584
+ const {
4585
+ environment,
4586
+ baseUrl,
4587
+ applicationToken,
4588
+ appName,
4589
+ accent,
4590
+ radius,
4591
+ theme,
4592
+ locale,
4593
+ consent,
4594
+ autoFallback
4595
+ } = options;
4596
+ if (autoFallback) {
4597
+ globalConfig.autoFallback = {
4598
+ toPhone: autoFallback.toPhone ?? DEFAULT_AUTO_FALLBACK.toPhone,
4599
+ toPassword: autoFallback.toPassword ?? DEFAULT_AUTO_FALLBACK.toPassword
4600
+ };
4601
+ }
4574
4602
  if (consent) {
4575
4603
  globalConfig.consent = consent;
4576
4604
  } else {
@@ -4668,6 +4696,9 @@ function getAppName() {
4668
4696
  function getAccent() {
4669
4697
  return globalConfig.accent;
4670
4698
  }
4699
+ function getAutoFallback() {
4700
+ return { ...globalConfig.autoFallback };
4701
+ }
4671
4702
  function getRadius() {
4672
4703
  return globalConfig.radius;
4673
4704
  }
@@ -4804,11 +4835,14 @@ const validateToken = async (applicationToken) => {
4804
4835
  } catch (error) {
4805
4836
  if (error instanceof Error) {
4806
4837
  if (error.name === "AbortError") {
4807
- throw new NeoFaceError("Request timeout", ErrorType.NETWORK_ERROR);
4838
+ throw new NeoFaceError("Request timeout", ErrorType.NETWORK);
4839
+ }
4840
+ if (error instanceof NeoFaceError) {
4841
+ throw error;
4808
4842
  }
4809
- throw new NeoFaceError(error.message, ErrorType.NETWORK_ERROR);
4843
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
4810
4844
  }
4811
- throw new NeoFaceError("Unknown error", ErrorType.NETWORK_ERROR);
4845
+ throw new NeoFaceError("Unknown error", ErrorType.NETWORK);
4812
4846
  }
4813
4847
  };
4814
4848
  const getOnboardingDetails = async (applicationToken, onboardingToken) => {
@@ -4846,7 +4880,7 @@ const getOnboardingDetails = async (applicationToken, onboardingToken) => {
4846
4880
  }
4847
4881
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
4848
4882
  }
4849
- throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
4883
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
4850
4884
  }
4851
4885
  };
4852
4886
  const validateOnboardingToken = async (applicationToken, onboardingToken) => {
@@ -4884,7 +4918,7 @@ const validateOnboardingToken = async (applicationToken, onboardingToken) => {
4884
4918
  }
4885
4919
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
4886
4920
  }
4887
- throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
4921
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
4888
4922
  }
4889
4923
  };
4890
4924
  const completeOnboarding = async (applicationToken, onboardingToken, faceImage, documentImage) => {
@@ -4951,7 +4985,7 @@ const completeOnboarding = async (applicationToken, onboardingToken, faceImage,
4951
4985
  }
4952
4986
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
4953
4987
  }
4954
- throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
4988
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
4955
4989
  }
4956
4990
  };
4957
4991
  const completeOnboardingWithData = async (applicationToken, onboardingToken, personData, personImages, documentImage) => {
@@ -5037,7 +5071,7 @@ const completeOnboardingWithData = async (applicationToken, onboardingToken, per
5037
5071
  }
5038
5072
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5039
5073
  }
5040
- throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK_ERROR);
5074
+ throw new NeoFaceError("Erro desconhecido", ErrorType.NETWORK);
5041
5075
  }
5042
5076
  };
5043
5077
  const recognize = async (image, applicationToken) => {
@@ -5089,7 +5123,7 @@ const recognize = async (image, applicationToken) => {
5089
5123
  }
5090
5124
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5091
5125
  }
5092
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5126
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5093
5127
  }
5094
5128
  };
5095
5129
  const loginWithBiometric = async (image, applicationToken) => {
@@ -5130,6 +5164,9 @@ const loginWithBiometric = async (image, applicationToken) => {
5130
5164
  return {
5131
5165
  success: true,
5132
5166
  accessToken: data.accessToken || data.access_token || "",
5167
+ // Só vem quando `can_issue_user_tokens` está ligado na aplicação.
5168
+ refreshToken: data.refreshToken || data.refresh_token || void 0,
5169
+ method: "face",
5133
5170
  alias: data.alias,
5134
5171
  user: {
5135
5172
  id: ((_a2 = data.user) == null ? void 0 : _a2.id) || "",
@@ -5156,7 +5193,7 @@ const loginWithBiometric = async (image, applicationToken) => {
5156
5193
  }
5157
5194
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5158
5195
  }
5159
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5196
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5160
5197
  }
5161
5198
  };
5162
5199
  const recognizeBiometric = async (image, applicationToken, livenessCheck = true, confidenceThreshold = 0.8) => {
@@ -5210,7 +5247,7 @@ const recognizeBiometric = async (image, applicationToken, livenessCheck = true,
5210
5247
  }
5211
5248
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5212
5249
  }
5213
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5250
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5214
5251
  }
5215
5252
  };
5216
5253
  const simpleIdentification = async (documentType, documentNumber, applicationToken, purpose = "LOGIN") => {
@@ -5256,7 +5293,7 @@ const simpleIdentification = async (documentType, documentNumber, applicationTok
5256
5293
  }
5257
5294
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5258
5295
  }
5259
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5296
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5260
5297
  }
5261
5298
  };
5262
5299
  const recognizeByPurpose = async (image, applicationToken, purpose, confidenceThreshold = 0.8, signature, sessionData) => {
@@ -5322,7 +5359,7 @@ const recognizeByPurpose = async (image, applicationToken, purpose, confidenceTh
5322
5359
  }
5323
5360
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5324
5361
  }
5325
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5362
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5326
5363
  }
5327
5364
  };
5328
5365
  const registerPersonWithoutFace = async (personData, applicationToken, options) => {
@@ -5590,7 +5627,7 @@ const registerPersonWithBiometric = async (personData, facePhotos, applicationTo
5590
5627
  }
5591
5628
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5592
5629
  }
5593
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5630
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5594
5631
  }
5595
5632
  };
5596
5633
  const loginWithEmail = async (email2, password, applicationToken) => {
@@ -5616,6 +5653,8 @@ const loginWithEmail = async (email2, password, applicationToken) => {
5616
5653
  success: true,
5617
5654
  // Compatível com diferentes formatos de resposta do backend
5618
5655
  accessToken: data.access_token || data.access,
5656
+ refreshToken: data.refresh_token || data.refresh || void 0,
5657
+ method: "password",
5619
5658
  alias: data.user && data.user.alias || data.alias,
5620
5659
  user: {
5621
5660
  id: data.user && data.user.id || "",
@@ -5635,7 +5674,99 @@ const loginWithEmail = async (email2, password, applicationToken) => {
5635
5674
  }
5636
5675
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5637
5676
  }
5638
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5677
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5678
+ }
5679
+ };
5680
+ const requestDeviceEnrollmentToken = async (userAccessToken, applicationToken) => {
5681
+ var _a2, _b, _c;
5682
+ if (!userAccessToken) {
5683
+ throw new NeoFaceError(
5684
+ "Inscrição de aparelho exige um usuário autenticado (access token ausente).",
5685
+ ErrorType.INVALID_TOKEN
5686
+ );
5687
+ }
5688
+ const controller = createTimeoutController();
5689
+ try {
5690
+ const response = await fetch(`${getApiBaseUrl()}/api/v1/devices/enrollment-token/`, {
5691
+ method: "POST",
5692
+ headers: {
5693
+ "Content-Type": "application/json",
5694
+ "X-App-Token": applicationToken,
5695
+ Authorization: `Bearer ${userAccessToken}`
5696
+ },
5697
+ signal: controller.signal
5698
+ });
5699
+ if (!response.ok) {
5700
+ if (response.status === 401 || response.status === 403) {
5701
+ throw new NeoFaceError(
5702
+ "Não foi possível inscrever o aparelho: sessão expirada ou conta sem permissão. Faça login novamente.",
5703
+ ErrorType.INVALID_TOKEN
5704
+ );
5705
+ }
5706
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
5707
+ }
5708
+ const data = await response.json();
5709
+ const token = data.token ?? ((_a2 = data.data) == null ? void 0 : _a2.token);
5710
+ if (!token) {
5711
+ throw new NeoFaceError("Resposta sem token de inscrição", ErrorType.API_ERROR);
5712
+ }
5713
+ return {
5714
+ token,
5715
+ expiresIn: data.expires_in ?? ((_b = data.data) == null ? void 0 : _b.expires_in) ?? 300,
5716
+ purpose: data.purpose ?? ((_c = data.data) == null ? void 0 : _c.purpose) ?? "device-enrollment"
5717
+ };
5718
+ } catch (error) {
5719
+ if (error instanceof NeoFaceError) throw error;
5720
+ if (error instanceof Error) {
5721
+ if (error.name === "AbortError") {
5722
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
5723
+ }
5724
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
5725
+ }
5726
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5727
+ }
5728
+ };
5729
+ const refreshSession = async (refreshToken, applicationToken) => {
5730
+ const controller = createTimeoutController();
5731
+ try {
5732
+ const response = await fetch(`${getApiBaseUrl()}/api/v1/token/refresh/`, {
5733
+ method: "POST",
5734
+ headers: {
5735
+ "Content-Type": "application/json",
5736
+ "X-App-Token": applicationToken
5737
+ },
5738
+ body: JSON.stringify({ refresh: refreshToken }),
5739
+ signal: controller.signal
5740
+ });
5741
+ if (!response.ok) {
5742
+ if (response.status === 401 || response.status === 403) {
5743
+ throw new NeoFaceError(
5744
+ "Sessão expirada — faça login novamente",
5745
+ ErrorType.INVALID_TOKEN
5746
+ );
5747
+ }
5748
+ throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
5749
+ }
5750
+ const data = await response.json();
5751
+ const access = data.access ?? data.access_token;
5752
+ if (!access) {
5753
+ throw new NeoFaceError("Resposta de refresh sem access token", ErrorType.API_ERROR);
5754
+ }
5755
+ return {
5756
+ accessToken: access,
5757
+ // Com rotação ligada o core devolve refresh novo. Se algum ambiente
5758
+ // estiver sem rotação, o refresh atual segue valendo.
5759
+ refreshToken: data.refresh ?? data.refresh_token ?? refreshToken
5760
+ };
5761
+ } catch (error) {
5762
+ if (error instanceof NeoFaceError) throw error;
5763
+ if (error instanceof Error) {
5764
+ if (error.name === "AbortError") {
5765
+ throw new NeoFaceError("Request timed out", ErrorType.NETWORK);
5766
+ }
5767
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
5768
+ }
5769
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5639
5770
  }
5640
5771
  };
5641
5772
  const identifyPerson = async (image, applicationToken) => {
@@ -5703,7 +5834,7 @@ const identifyPerson = async (image, applicationToken) => {
5703
5834
  }
5704
5835
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5705
5836
  }
5706
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5837
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5707
5838
  }
5708
5839
  };
5709
5840
  const registerBiometric = async (personId, faceImage, applicationToken) => {
@@ -5779,7 +5910,7 @@ const registerBiometric = async (personId, faceImage, applicationToken) => {
5779
5910
  }
5780
5911
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5781
5912
  }
5782
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5913
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5783
5914
  }
5784
5915
  };
5785
5916
  const registerApplication = async (jwtToken, consumerId, applicationData) => {
@@ -5848,7 +5979,7 @@ const registerApplication = async (jwtToken, consumerId, applicationData) => {
5848
5979
  }
5849
5980
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
5850
5981
  }
5851
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
5982
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
5852
5983
  }
5853
5984
  };
5854
5985
  const recordFaceVideo = async (options = {}) => {
@@ -5950,7 +6081,7 @@ const recordFaceVideo = async (options = {}) => {
5950
6081
  });
5951
6082
  };
5952
6083
  const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
5953
- var _a2, _b, _c, _d, _e, _f;
6084
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
5954
6085
  const { maxAttempts = 60, intervalMs = 1e3, onStatusChange } = options;
5955
6086
  let attempts = 0;
5956
6087
  while (attempts < maxAttempts) {
@@ -5982,6 +6113,10 @@ const pollTaskStatus = async (taskId, applicationToken, options = {}) => {
5982
6113
  if (status === "revoked") {
5983
6114
  throw new NeoFaceError("Task was cancelled", ErrorType.RECOGNITION_FAILED);
5984
6115
  }
6116
+ if (data.success === false) {
6117
+ const reason = ((_h = (_g = data.data) == null ? void 0 : _g.result) == null ? void 0 : _h.message) || ((_i = data.data) == null ? void 0 : _i.error) || ((_j = data.data) == null ? void 0 : _j.message) || data.message || "Task failed";
6118
+ throw new NeoFaceError(reason, ErrorType.RECOGNITION_FAILED);
6119
+ }
5985
6120
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
5986
6121
  } catch (error) {
5987
6122
  if (error instanceof NeoFaceError) {
@@ -6048,7 +6183,7 @@ const startProofOfLife = async (videoBase64, applicationToken) => {
6048
6183
  }
6049
6184
  throw new NeoFaceError(error.message, ErrorType.NETWORK);
6050
6185
  }
6051
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
6186
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
6052
6187
  }
6053
6188
  };
6054
6189
  const videoToBase64 = async (videoBlob) => new Promise((resolve, reject) => {
@@ -6101,10 +6236,7 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
6101
6236
  });
6102
6237
  const data = await response.json();
6103
6238
  if (!response.ok) {
6104
- throw new NeoFaceError(
6105
- (data == null ? void 0 : data.message) || `API Error ${response.status}`,
6106
- ErrorType.NETWORK_ERROR
6107
- );
6239
+ throw new NeoFaceError((data == null ? void 0 : data.message) || `API Error ${response.status}`, ErrorType.NETWORK);
6108
6240
  }
6109
6241
  return {
6110
6242
  success: true,
@@ -6116,11 +6248,11 @@ const registerDocumentByImage = async (personId, jwtToken, images) => {
6116
6248
  if (error instanceof NeoFaceError) throw error;
6117
6249
  if (error instanceof Error) {
6118
6250
  if (error.name === "AbortError") {
6119
- throw new NeoFaceError("Request timeout", ErrorType.NETWORK_ERROR);
6251
+ throw new NeoFaceError("Request timeout", ErrorType.NETWORK);
6120
6252
  }
6121
- throw new NeoFaceError(error.message, ErrorType.NETWORK_ERROR);
6253
+ throw new NeoFaceError(error.message, ErrorType.NETWORK);
6122
6254
  }
6123
- throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK_ERROR);
6255
+ throw new NeoFaceError("Unknown error occurred", ErrorType.NETWORK);
6124
6256
  }
6125
6257
  };
6126
6258
  const getTaskStatus = async (taskId, applicationToken) => {
@@ -6141,10 +6273,7 @@ const getTaskStatus = async (taskId, applicationToken) => {
6141
6273
  });
6142
6274
  const data = await response.json();
6143
6275
  if (!response.ok) {
6144
- throw new NeoFaceError(
6145
- (data == null ? void 0 : data.message) || `API Error ${response.status}`,
6146
- ErrorType.NETWORK_ERROR
6147
- );
6276
+ throw new NeoFaceError((data == null ? void 0 : data.message) || `API Error ${response.status}`, ErrorType.NETWORK);
6148
6277
  }
6149
6278
  return {
6150
6279
  success: data.success,
@@ -6154,7 +6283,7 @@ const getTaskStatus = async (taskId, applicationToken) => {
6154
6283
  };
6155
6284
  } catch (error) {
6156
6285
  if (error instanceof NeoFaceError) throw error;
6157
- throw new NeoFaceError("Failed to fetch task status", ErrorType.NETWORK_ERROR);
6286
+ throw new NeoFaceError("Failed to fetch task status", ErrorType.NETWORK);
6158
6287
  }
6159
6288
  };
6160
6289
  const identifyPersonAsync = async (image, applicationToken, options = {}) => {
@@ -6190,7 +6319,7 @@ const identifyPersonAsync = async (image, applicationToken, options = {}) => {
6190
6319
  }
6191
6320
  await new Promise((resolve) => setTimeout(resolve, interval));
6192
6321
  }
6193
- throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK_ERROR);
6322
+ throw new NeoFaceError("Timed out waiting for identification", ErrorType.NETWORK);
6194
6323
  };
6195
6324
  async function requestPasswordReset(email2, applicationToken) {
6196
6325
  const baseUrl = getBaseUrl();
@@ -6246,11 +6375,13 @@ const api = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty(
6246
6375
  recognizeBiometric,
6247
6376
  recognizeByPurpose,
6248
6377
  recordFaceVideo,
6378
+ refreshSession,
6249
6379
  registerApplication,
6250
6380
  registerBiometric,
6251
6381
  registerDocumentByImage,
6252
6382
  registerPersonWithBiometric,
6253
6383
  registerPersonWithoutFace,
6384
+ requestDeviceEnrollmentToken,
6254
6385
  requestPasswordReset,
6255
6386
  simpleIdentification,
6256
6387
  startProofOfLife,
@@ -8736,7 +8867,7 @@ function BiometricRegistrationModal({
8736
8867
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
8737
8868
  ] });
8738
8869
  }
8739
- const VERSION = "1.40.3";
8870
+ const VERSION = "1.42.0";
8740
8871
  const RELEASE_DATE = "2026-09-10";
8741
8872
  const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
8742
8873
  const CONSENT_TRAIL_MAX_LIMIT = 100;
@@ -9103,85 +9234,968 @@ function ConsentModalComponent({
9103
9234
  children: copy.primary
9104
9235
  }
9105
9236
  ),
9106
- /* @__PURE__ */ jsx(
9107
- "button",
9237
+ /* @__PURE__ */ jsx(
9238
+ "button",
9239
+ {
9240
+ type: "button",
9241
+ className: "neofaceid-consent-button neofaceid-consent-button-ghost",
9242
+ onClick: onDecline,
9243
+ children: "Agora não"
9244
+ }
9245
+ )
9246
+ ] }),
9247
+ /* @__PURE__ */ jsx("div", { className: "neofaceid-consent-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9248
+ ] }) })
9249
+ ] });
9250
+ }
9251
+ class ConsentModal {
9252
+ constructor() {
9253
+ __publicField(this, "container", null);
9254
+ __publicField(this, "root", null);
9255
+ }
9256
+ show(props) {
9257
+ if (this.container) return;
9258
+ injectThemeStyles();
9259
+ this.container = document.createElement("div");
9260
+ this.container.id = "neofaceid-consent-modal-container";
9261
+ document.body.appendChild(this.container);
9262
+ this.root = createRoot(this.container);
9263
+ this.root.render(
9264
+ /* @__PURE__ */ jsx(
9265
+ ConsentModalComponent,
9266
+ {
9267
+ ...props,
9268
+ onAccept: () => {
9269
+ this.close();
9270
+ props.onAccept();
9271
+ },
9272
+ onDecline: () => {
9273
+ this.close();
9274
+ props.onDecline();
9275
+ }
9276
+ }
9277
+ )
9278
+ );
9279
+ }
9280
+ close() {
9281
+ if (this.container && document.body.contains(this.container)) {
9282
+ if (this.root) this.root.unmount();
9283
+ document.body.removeChild(this.container);
9284
+ this.container = null;
9285
+ this.root = null;
9286
+ }
9287
+ }
9288
+ }
9289
+ const DEFAULT_APP_NAME = "sua aplicação";
9290
+ function requestConsent(info = {}) {
9291
+ const globalAppName = getAppName();
9292
+ const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
9293
+ if (!info.appName && !globalAppName) {
9294
+ console.warn(
9295
+ '[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via NeoFaceSDK.init({ appName }) ou opts do caller.'
9296
+ );
9297
+ }
9298
+ const flow = info.flow ?? "verification";
9299
+ return new Promise((resolve) => {
9300
+ const modal = new ConsentModal();
9301
+ modal.show({
9302
+ appName,
9303
+ flow,
9304
+ privacyPolicyUrl: info.privacyPolicyUrl ?? DEFAULT_PRIVACY_URL,
9305
+ retentionDays: info.retentionDays ?? DEFAULT_RETENTION_DAYS,
9306
+ onAccept: () => resolve(true),
9307
+ onDecline: () => resolve(false)
9308
+ });
9309
+ });
9310
+ }
9311
+ const DEFAULT_CONSENT_INFO = {
9312
+ flow: "verification",
9313
+ privacyPolicyUrl: DEFAULT_PRIVACY_URL,
9314
+ retentionDays: DEFAULT_RETENTION_DAYS
9315
+ };
9316
+ function Sheet({
9317
+ open,
9318
+ onDismiss,
9319
+ dismissOnOverlay = true,
9320
+ ariaLabel,
9321
+ ariaLabelledBy,
9322
+ children,
9323
+ className,
9324
+ style
9325
+ }) {
9326
+ useEffect(() => {
9327
+ injectThemeStyles();
9328
+ }, []);
9329
+ if (!open) return null;
9330
+ const handleOverlay = () => {
9331
+ if (dismissOnOverlay && onDismiss) onDismiss();
9332
+ };
9333
+ return /* @__PURE__ */ jsx(
9334
+ "div",
9335
+ {
9336
+ className: "neofaceid-root",
9337
+ role: "presentation",
9338
+ style: {
9339
+ position: "fixed",
9340
+ inset: 0,
9341
+ zIndex: 10001,
9342
+ display: "flex",
9343
+ alignItems: "center",
9344
+ justifyContent: "center",
9345
+ padding: 20,
9346
+ background: `var(${CSS_VAR.overlay})`,
9347
+ backdropFilter: "blur(3px)",
9348
+ WebkitBackdropFilter: "blur(3px)"
9349
+ },
9350
+ onClick: handleOverlay,
9351
+ children: /* @__PURE__ */ jsx(
9352
+ "div",
9353
+ {
9354
+ role: "dialog",
9355
+ "aria-modal": "true",
9356
+ "aria-label": ariaLabel,
9357
+ "aria-labelledby": ariaLabelledBy,
9358
+ className,
9359
+ onClick: (e) => e.stopPropagation(),
9360
+ style: {
9361
+ background: `var(${CSS_VAR.surface})`,
9362
+ color: `var(${CSS_VAR.text})`,
9363
+ borderRadius: `var(${CSS_VAR.radius}, 16px)`,
9364
+ padding: 28,
9365
+ maxWidth: 440,
9366
+ width: "100%",
9367
+ boxShadow: "0 1px 3px rgba(10,19,32,.08)",
9368
+ boxSizing: "border-box",
9369
+ ...style
9370
+ },
9371
+ children
9372
+ }
9373
+ )
9374
+ }
9375
+ );
9376
+ }
9377
+ const NON_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["connected", "heartbeat", "processing"]);
9378
+ const DEFAULT_TIMEOUT_MS = 31e4;
9379
+ const DEFAULT_MAX_RECONNECTS = 5;
9380
+ const RECONNECT_BASE_DELAY_MS = 500;
9381
+ const RECONNECT_MAX_DELAY_MS = 8e3;
9382
+ function delay(ms, signal) {
9383
+ return new Promise((resolve) => {
9384
+ const id = setTimeout(resolve, ms);
9385
+ signal == null ? void 0 : signal.addEventListener(
9386
+ "abort",
9387
+ () => {
9388
+ clearTimeout(id);
9389
+ resolve();
9390
+ },
9391
+ { once: true }
9392
+ );
9393
+ });
9394
+ }
9395
+ async function consumeSseStream(options) {
9396
+ const {
9397
+ requestTicket,
9398
+ buildUrl,
9399
+ classify,
9400
+ timeoutMs = DEFAULT_TIMEOUT_MS,
9401
+ maxReconnects = DEFAULT_MAX_RECONNECTS,
9402
+ signal,
9403
+ eventSourceFactory,
9404
+ onProgress
9405
+ } = options;
9406
+ const makeEventSource = eventSourceFactory ?? ((url) => {
9407
+ const Ctor = globalThis.EventSource;
9408
+ if (!Ctor) {
9409
+ throw new NeoFaceError(
9410
+ "EventSource indisponível neste ambiente",
9411
+ ErrorType.INITIALIZATION_ERROR
9412
+ );
9413
+ }
9414
+ return new Ctor(url);
9415
+ });
9416
+ const startedAt = Date.now();
9417
+ let reconnects = 0;
9418
+ for (; ; ) {
9419
+ if (signal == null ? void 0 : signal.aborted) {
9420
+ throw new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN);
9421
+ }
9422
+ const remaining = timeoutMs - (Date.now() - startedAt);
9423
+ if (remaining <= 0) {
9424
+ throw new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK);
9425
+ }
9426
+ const ticket = await requestTicket();
9427
+ const outcome = await new Promise((resolve, reject) => {
9428
+ let source;
9429
+ try {
9430
+ source = makeEventSource(buildUrl(ticket));
9431
+ } catch (err) {
9432
+ reject(err);
9433
+ return;
9434
+ }
9435
+ let settled = false;
9436
+ let timerId;
9437
+ let onAbort;
9438
+ const cleanup = () => {
9439
+ if (timerId !== void 0) clearTimeout(timerId);
9440
+ if (onAbort) signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
9441
+ source.close();
9442
+ };
9443
+ timerId = setTimeout(() => {
9444
+ if (settled) return;
9445
+ settled = true;
9446
+ cleanup();
9447
+ reject(new NeoFaceError("Tempo esgotado aguardando resposta", ErrorType.NETWORK));
9448
+ }, remaining);
9449
+ onAbort = () => {
9450
+ if (settled) return;
9451
+ settled = true;
9452
+ cleanup();
9453
+ reject(new NeoFaceError("Fluxo cancelado", ErrorType.UNKNOWN));
9454
+ };
9455
+ signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
9456
+ source.onmessage = (event) => {
9457
+ if (settled) return;
9458
+ let frame;
9459
+ try {
9460
+ frame = JSON.parse(event.data);
9461
+ } catch {
9462
+ return;
9463
+ }
9464
+ if (frame.status && NON_TERMINAL_STATUSES.has(frame.status)) {
9465
+ if (onProgress) onProgress(frame);
9466
+ return;
9467
+ }
9468
+ const verdict = classify(frame);
9469
+ if (verdict.kind === "pending") {
9470
+ if (onProgress) onProgress(frame);
9471
+ return;
9472
+ }
9473
+ settled = true;
9474
+ cleanup();
9475
+ resolve({ type: "settled", verdict });
9476
+ };
9477
+ source.onerror = () => {
9478
+ if (settled) return;
9479
+ settled = true;
9480
+ cleanup();
9481
+ resolve({ type: "transport" });
9482
+ };
9483
+ });
9484
+ if (outcome.type === "settled") {
9485
+ const { verdict } = outcome;
9486
+ if (verdict.kind === "resolve") return verdict.value;
9487
+ throw verdict.error;
9488
+ }
9489
+ reconnects += 1;
9490
+ if (reconnects > maxReconnects) {
9491
+ throw new NeoFaceError(
9492
+ `Conexão perdida após ${maxReconnects} tentativas de reconexão`,
9493
+ ErrorType.NETWORK
9494
+ );
9495
+ }
9496
+ const backoff = Math.min(
9497
+ RECONNECT_BASE_DELAY_MS * 2 ** (reconnects - 1),
9498
+ RECONNECT_MAX_DELAY_MS
9499
+ );
9500
+ await delay(backoff, signal);
9501
+ }
9502
+ }
9503
+ const BASE_STYLE = {
9504
+ minHeight: 44,
9505
+ minWidth: 44,
9506
+ padding: "12px 20px",
9507
+ borderRadius: 16,
9508
+ border: "none",
9509
+ fontSize: 15,
9510
+ fontWeight: 600,
9511
+ fontFamily: "inherit",
9512
+ cursor: "pointer",
9513
+ transition: "background 120ms ease-out",
9514
+ display: "inline-flex",
9515
+ alignItems: "center",
9516
+ justifyContent: "center",
9517
+ gap: 8
9518
+ };
9519
+ const Button = forwardRef(
9520
+ ({ variant = "primary", style, type = "button", ...rest }, ref) => {
9521
+ const variantStyle = variant === "primary" ? {
9522
+ background: `var(${CSS_VAR.action})`,
9523
+ color: `var(${CSS_VAR.actionText})`
9524
+ } : variant === "secondary" ? {
9525
+ background: `var(${CSS_VAR.surfaceMuted})`,
9526
+ color: `var(${CSS_VAR.text})`
9527
+ } : {
9528
+ background: "transparent",
9529
+ color: `var(${CSS_VAR.textMuted})`
9530
+ };
9531
+ return (
9532
+ // eslint-disable-next-line react/button-has-type
9533
+ /* @__PURE__ */ jsx(
9534
+ "button",
9535
+ {
9536
+ ref,
9537
+ type,
9538
+ ...rest,
9539
+ style: { ...BASE_STYLE, ...variantStyle, ...style }
9540
+ }
9541
+ )
9542
+ );
9543
+ }
9544
+ );
9545
+ Button.displayName = "Button";
9546
+ function StatusPill({ tone, children, icon, className }) {
9547
+ const palette = SEMANTIC[tone];
9548
+ return /* @__PURE__ */ jsxs(
9549
+ "span",
9550
+ {
9551
+ className,
9552
+ style: {
9553
+ display: "inline-flex",
9554
+ alignItems: "center",
9555
+ gap: 6,
9556
+ padding: "4px 10px",
9557
+ borderRadius: 9999,
9558
+ background: palette.bg,
9559
+ color: palette.fg,
9560
+ fontSize: 13,
9561
+ fontWeight: 600,
9562
+ fontFamily: "inherit",
9563
+ lineHeight: 1.3
9564
+ },
9565
+ children: [
9566
+ icon,
9567
+ children
9568
+ ]
9569
+ }
9570
+ );
9571
+ }
9572
+ const STYLE_ID$2 = "neofaceid-awaiting-push-styles";
9573
+ const CSS$1 = `
9574
+ .neofaceid-root .nfid-push {
9575
+ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
9576
+ padding: 8px 8px 4px;
9577
+ }
9578
+ .neofaceid-root .nfid-push h2 {
9579
+ margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
9580
+ letter-spacing: -0.015em;
9581
+ }
9582
+ .neofaceid-root .nfid-push p {
9583
+ margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
9584
+ }
9585
+ .neofaceid-root .nfid-push-phone-icon {
9586
+ width: 56px; height: 56px; color: var(--nfid-action);
9587
+ }
9588
+ .neofaceid-root .nfid-push-code {
9589
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
9590
+ margin-top: 14px; padding: 16px 28px;
9591
+ background: var(--nfid-surface-muted); border-radius: 16px; width: 100%;
9592
+ }
9593
+ .neofaceid-root .nfid-push-code-label {
9594
+ font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
9595
+ color: var(--nfid-text-subtle);
9596
+ }
9597
+ .neofaceid-root .nfid-push-code-value {
9598
+ font-size: 52px; font-weight: 800; line-height: 1; letter-spacing: 0.08em;
9599
+ color: var(--nfid-text); font-variant-numeric: tabular-nums;
9600
+ }
9601
+ .neofaceid-root .nfid-push-countdown {
9602
+ font-size: 13px; color: var(--nfid-text-muted); font-variant-numeric: tabular-nums;
9603
+ margin-top: 12px;
9604
+ }
9605
+ .neofaceid-root .nfid-push-actions {
9606
+ display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 16px;
9607
+ }
9608
+ .neofaceid-root .nfid-push-footer {
9609
+ display: flex; justify-content: center; margin-top: 16px; padding-top: 12px;
9610
+ border-top: 1px solid var(--nfid-line);
9611
+ width: 100%;
9612
+ }
9613
+ .neofaceid-root .nfid-push-pulse {
9614
+ animation: nfidPushPulse 1.8s ease-in-out infinite;
9615
+ }
9616
+ @keyframes nfidPushPulse {
9617
+ 0%, 100% { opacity: 1; }
9618
+ 50% { opacity: 0.45; }
9619
+ }
9620
+ @media (prefers-reduced-motion: reduce) {
9621
+ .neofaceid-root .nfid-push-pulse { animation: none; }
9622
+ }
9623
+ `;
9624
+ function formatRemaining(seconds) {
9625
+ const safe = Math.max(0, seconds);
9626
+ const mm = Math.floor(safe / 60);
9627
+ const ss = safe % 60;
9628
+ return `${mm}:${String(ss).padStart(2, "0")}`;
9629
+ }
9630
+ function AwaitingPushView({
9631
+ appName,
9632
+ numericCode,
9633
+ remainingSeconds,
9634
+ onCancel
9635
+ }) {
9636
+ useEffect(() => {
9637
+ injectThemeStyles();
9638
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9639
+ }, []);
9640
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "status", "aria-live": "polite", children: [
9641
+ /* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando confirmação" }),
9642
+ /* @__PURE__ */ jsxs(
9643
+ "svg",
9644
+ {
9645
+ className: "nfid-push-phone-icon nfid-push-pulse",
9646
+ viewBox: "0 0 24 24",
9647
+ fill: "none",
9648
+ stroke: "currentColor",
9649
+ strokeWidth: "1.75",
9650
+ strokeLinecap: "round",
9651
+ strokeLinejoin: "round",
9652
+ "aria-hidden": "true",
9653
+ children: [
9654
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "12", height: "20", rx: "3" }),
9655
+ /* @__PURE__ */ jsx("path", { d: "M11 18h2" })
9656
+ ]
9657
+ }
9658
+ ),
9659
+ /* @__PURE__ */ jsx("h2", { children: "Confirme no seu celular" }),
9660
+ /* @__PURE__ */ jsxs("p", { children: [
9661
+ "Enviamos um pedido de confirmação para o seu aparelho. Abra o NeoFaceID Push e confirme com o seu rosto para continuar no ",
9662
+ appName,
9663
+ "."
9664
+ ] }),
9665
+ numericCode && /* @__PURE__ */ jsxs("div", { className: "nfid-push-code", children: [
9666
+ /* @__PURE__ */ jsx("span", { className: "nfid-push-code-label", children: "Digite este número no celular" }),
9667
+ /* @__PURE__ */ jsx(
9668
+ "span",
9669
+ {
9670
+ className: "nfid-push-code-value",
9671
+ "aria-label": `Código de confirmação: ${numericCode.split("").join(" ")}`,
9672
+ children: numericCode
9673
+ }
9674
+ )
9675
+ ] }),
9676
+ /* @__PURE__ */ jsxs("p", { className: "nfid-push-countdown", children: [
9677
+ "Expira em ",
9678
+ formatRemaining(remainingSeconds)
9679
+ ] }),
9680
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
9681
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9682
+ ] });
9683
+ }
9684
+ function PushExpiredView({ onFallback, onCancel }) {
9685
+ useEffect(() => {
9686
+ injectThemeStyles();
9687
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9688
+ }, []);
9689
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9690
+ /* @__PURE__ */ jsxs(
9691
+ "svg",
9692
+ {
9693
+ className: "nfid-push-phone-icon",
9694
+ viewBox: "0 0 24 24",
9695
+ fill: "none",
9696
+ stroke: "currentColor",
9697
+ strokeWidth: "1.75",
9698
+ strokeLinecap: "round",
9699
+ strokeLinejoin: "round",
9700
+ "aria-hidden": "true",
9701
+ style: { color: "var(--nfid-text-muted)" },
9702
+ children: [
9703
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
9704
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
9705
+ ]
9706
+ }
9707
+ ),
9708
+ /* @__PURE__ */ jsx("h2", { children: "O tempo esgotou" }),
9709
+ /* @__PURE__ */ jsx("p", { children: "Não recebemos a confirmação do seu celular a tempo." }),
9710
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: onFallback ? /* @__PURE__ */ jsxs(Fragment, { children: [
9711
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Entrar de outro jeito" }),
9712
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
9713
+ ] }) : /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
9714
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9715
+ ] });
9716
+ }
9717
+ function PushDeniedView({ appName, onCancel }) {
9718
+ useEffect(() => {
9719
+ injectThemeStyles();
9720
+ injectScopedStyles(STYLE_ID$2, CSS$1);
9721
+ }, []);
9722
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9723
+ /* @__PURE__ */ jsxs(
9724
+ "svg",
9725
+ {
9726
+ className: "nfid-push-phone-icon",
9727
+ viewBox: "0 0 24 24",
9728
+ fill: "none",
9729
+ stroke: "currentColor",
9730
+ strokeWidth: "1.75",
9731
+ strokeLinecap: "round",
9732
+ strokeLinejoin: "round",
9733
+ "aria-hidden": "true",
9734
+ style: { color: "#D64545" },
9735
+ children: [
9736
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
9737
+ /* @__PURE__ */ jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
9738
+ ]
9739
+ }
9740
+ ),
9741
+ /* @__PURE__ */ jsx("h2", { children: "Confirmação recusada" }),
9742
+ /* @__PURE__ */ jsxs("p", { children: [
9743
+ "O pedido foi recusado no celular. Se não foi você, procure o suporte do ",
9744
+ appName,
9745
+ "."
9746
+ ] }),
9747
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
9748
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9749
+ ] });
9750
+ }
9751
+ const CONTAINER_ID = "neofaceid-push-approval-container";
9752
+ function classifyAuthorizationFrame(frame) {
9753
+ var _a2, _b;
9754
+ switch (frame.status) {
9755
+ case "approved":
9756
+ case "success":
9757
+ return { kind: "resolve", value: { status: "approved", data: frame.data ?? {} } };
9758
+ case "denied":
9759
+ return {
9760
+ kind: "resolve",
9761
+ value: { status: "denied", reason: ((_a2 = frame.data) == null ? void 0 : _a2.reason) ?? frame.message }
9762
+ };
9763
+ case "expired":
9764
+ return { kind: "resolve", value: { status: "expired" } };
9765
+ case "failed":
9766
+ case "error":
9767
+ return {
9768
+ kind: "reject",
9769
+ error: new NeoFaceError(
9770
+ frame.message ?? ((_b = frame.data) == null ? void 0 : _b.message) ?? "Falha na confirmação",
9771
+ ErrorType.API_ERROR
9772
+ )
9773
+ };
9774
+ default:
9775
+ return { kind: "pending" };
9776
+ }
9777
+ }
9778
+ function PushApprovalModal({
9779
+ requestId,
9780
+ applicationToken,
9781
+ expiresIn,
9782
+ numericCode,
9783
+ appName,
9784
+ onFallback,
9785
+ onSettle
9786
+ }) {
9787
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
9788
+ const [remaining, setRemaining] = useState(expiresIn);
9789
+ const [phase, setPhase] = useState({ kind: "waiting" });
9790
+ useEffect(() => {
9791
+ if (phase.kind !== "waiting") return void 0;
9792
+ const id = setInterval(() => {
9793
+ setRemaining((r) => r > 0 ? r - 1 : 0);
9794
+ }, 1e3);
9795
+ return () => clearInterval(id);
9796
+ }, [phase.kind]);
9797
+ useEffect(() => {
9798
+ const controller = new AbortController();
9799
+ let done = false;
9800
+ const base = getBaseUrl();
9801
+ const ticketUrl = `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/ticket/`;
9802
+ consumeSseStream({
9803
+ signal: controller.signal,
9804
+ timeoutMs: (expiresIn + 30) * 1e3,
9805
+ requestTicket: async () => {
9806
+ var _a2;
9807
+ const res = await fetch(ticketUrl, {
9808
+ method: "POST",
9809
+ headers: { "Content-Type": "application/json", "X-App-Token": applicationToken }
9810
+ });
9811
+ if (!res.ok) {
9812
+ throw new NeoFaceError(
9813
+ `Falha ao abrir canal de confirmação: ${res.status}`,
9814
+ res.status === 401 || res.status === 403 ? ErrorType.INVALID_TOKEN : ErrorType.NETWORK
9815
+ );
9816
+ }
9817
+ const body = await res.json();
9818
+ const ticket = ((_a2 = body == null ? void 0 : body.data) == null ? void 0 : _a2.ticket) ?? (body == null ? void 0 : body.ticket);
9819
+ if (!ticket) {
9820
+ throw new NeoFaceError("Canal de confirmação sem ticket", ErrorType.API_ERROR);
9821
+ }
9822
+ return ticket;
9823
+ },
9824
+ buildUrl: (ticket) => `${base}/api/v1/authorization/events/${encodeURIComponent(requestId)}/?ticket=${encodeURIComponent(ticket)}`,
9825
+ classify: classifyAuthorizationFrame
9826
+ }).then((outcome) => {
9827
+ if (done) return;
9828
+ done = true;
9829
+ if (outcome.status === "denied") {
9830
+ setPhase({ kind: "denied" });
9831
+ return;
9832
+ }
9833
+ if (outcome.status === "expired") {
9834
+ setPhase({ kind: "expired" });
9835
+ return;
9836
+ }
9837
+ onSettle(outcome);
9838
+ }).catch(() => {
9839
+ if (done || controller.signal.aborted) return;
9840
+ done = true;
9841
+ setPhase({ kind: "expired" });
9842
+ });
9843
+ return () => {
9844
+ done = true;
9845
+ controller.abort();
9846
+ };
9847
+ }, [requestId]);
9848
+ if (phase.kind === "denied") {
9849
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Confirmação recusada", children: /* @__PURE__ */ jsx(PushDeniedView, { appName: headerAppName, onCancel: () => onSettle({ status: "denied" }) }) });
9850
+ }
9851
+ if (phase.kind === "expired") {
9852
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Tempo esgotado", children: /* @__PURE__ */ jsx(
9853
+ PushExpiredView,
9854
+ {
9855
+ onFallback: onFallback ? () => {
9856
+ onFallback();
9857
+ onSettle({ status: "expired" });
9858
+ } : void 0,
9859
+ onCancel: () => onSettle({ status: "expired" })
9860
+ }
9861
+ ) });
9862
+ }
9863
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Aguardando confirmação no celular", children: /* @__PURE__ */ jsx(
9864
+ AwaitingPushView,
9865
+ {
9866
+ appName: headerAppName,
9867
+ numericCode,
9868
+ remainingSeconds: remaining,
9869
+ onCancel: () => onSettle({ status: "cancelled" })
9870
+ }
9871
+ ) });
9872
+ }
9873
+ function awaitPushApproval(options) {
9874
+ return new Promise((resolve) => {
9875
+ const container = document.createElement("div");
9876
+ container.id = CONTAINER_ID;
9877
+ document.body.appendChild(container);
9878
+ const root = createRoot(container);
9879
+ const settle = (outcome) => {
9880
+ root.unmount();
9881
+ if (document.body.contains(container)) document.body.removeChild(container);
9882
+ resolve(outcome);
9883
+ };
9884
+ root.render(/* @__PURE__ */ jsx(PushApprovalModal, { ...options, onSettle: settle }));
9885
+ });
9886
+ }
9887
+ function evaluateFacePosition(box, videoWidth, videoHeight) {
9888
+ if (!box || !videoWidth || !videoHeight) {
9889
+ return {
9890
+ faceState: "searching",
9891
+ instruction: "Centralize o rosto"
9892
+ };
9893
+ }
9894
+ const faceCenterX = box.x + box.width / 2;
9895
+ const faceCenterY = box.y + box.height / 2;
9896
+ const frameCenterX = videoWidth / 2;
9897
+ const frameCenterY = videoHeight / 2;
9898
+ const offsetX = Math.abs(faceCenterX - frameCenterX) / videoWidth;
9899
+ const offsetY = Math.abs(faceCenterY - frameCenterY) / videoHeight;
9900
+ const faceArea = box.width * box.height;
9901
+ const frameArea = videoWidth * videoHeight;
9902
+ const faceAreaRatio = faceArea / frameArea;
9903
+ if (faceAreaRatio < 0.08) {
9904
+ return {
9905
+ faceState: "too-far",
9906
+ instruction: "Aproxime-se"
9907
+ };
9908
+ }
9909
+ if (faceAreaRatio > 0.45) {
9910
+ return {
9911
+ faceState: "too-close",
9912
+ instruction: "Afaste-se"
9913
+ };
9914
+ }
9915
+ if (offsetX > 0.18 || offsetY > 0.18) {
9916
+ return {
9917
+ faceState: "off-center",
9918
+ instruction: "Centralize o rosto"
9919
+ };
9920
+ }
9921
+ return {
9922
+ faceState: "centered",
9923
+ instruction: "Perfeito, mantenha"
9924
+ };
9925
+ }
9926
+ function showCameraOvalGuideModal(initialOptions = {}) {
9927
+ const overlayDiv = document.createElement("div");
9928
+ overlayDiv.className = "neofaceid-camera-oval-guide-overlay-root";
9929
+ Object.assign(overlayDiv.style, {
9930
+ position: "fixed",
9931
+ top: "0",
9932
+ left: "0",
9933
+ width: "100vw",
9934
+ height: "100vh",
9935
+ backgroundColor: "rgba(0, 0, 0, 0.85)",
9936
+ backdropFilter: "blur(8px)",
9937
+ zIndex: "99999",
9938
+ display: "flex",
9939
+ flexDirection: "column",
9940
+ alignItems: "center",
9941
+ justifyContent: "center"
9942
+ });
9943
+ document.body.appendChild(overlayDiv);
9944
+ let root = createRoot(overlayDiv);
9945
+ let currentStream = initialOptions.stream || null;
9946
+ let currentFaceState = initialOptions.faceState || "searching";
9947
+ let currentInstruction = initialOptions.instruction || "Centralize o rosto";
9948
+ const renderModal = (stream, faceState, instruction) => {
9949
+ if (!root) return;
9950
+ root.render(
9951
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: [
9952
+ initialOptions.onCancel && /* @__PURE__ */ jsx(
9953
+ "button",
9954
+ {
9955
+ onClick: () => {
9956
+ var _a2;
9957
+ handle.close();
9958
+ (_a2 = initialOptions.onCancel) == null ? void 0 : _a2.call(initialOptions);
9959
+ },
9960
+ style: {
9961
+ position: "absolute",
9962
+ top: "20px",
9963
+ right: "20px",
9964
+ background: "rgba(255, 255, 255, 0.2)",
9965
+ border: "none",
9966
+ color: "#fff",
9967
+ fontSize: "24px",
9968
+ width: "40px",
9969
+ height: "40px",
9970
+ borderRadius: "50%",
9971
+ cursor: "pointer",
9972
+ zIndex: 10,
9973
+ display: "flex",
9974
+ alignItems: "center",
9975
+ justifyContent: "center"
9976
+ },
9977
+ "aria-label": "Cancelar",
9978
+ children: "✕"
9979
+ }
9980
+ ),
9981
+ /* @__PURE__ */ jsx(
9982
+ CameraOvalGuide,
9983
+ {
9984
+ stream,
9985
+ faceState,
9986
+ instruction
9987
+ }
9988
+ )
9989
+ ] })
9990
+ );
9991
+ };
9992
+ renderModal(currentStream, currentFaceState, currentInstruction);
9993
+ const handle = {
9994
+ update: (stream, faceState, instruction) => {
9995
+ currentStream = stream;
9996
+ currentFaceState = faceState;
9997
+ currentInstruction = instruction;
9998
+ renderModal(stream, faceState, instruction);
9999
+ },
10000
+ close: () => {
10001
+ if (root) {
10002
+ root.unmount();
10003
+ root = null;
10004
+ }
10005
+ if (overlayDiv.parentElement) {
10006
+ overlayDiv.parentElement.removeChild(overlayDiv);
10007
+ }
10008
+ }
10009
+ };
10010
+ return handle;
10011
+ }
10012
+ function getHaloStyles(state) {
10013
+ switch (state) {
10014
+ case "centered":
10015
+ return {
10016
+ borderColor: "#0E9F6E",
10017
+ boxShadow: "0 0 0 4px rgba(14, 159, 110, 0.8), 0 0 45px rgba(14, 159, 110, 0.45)",
10018
+ svgStroke: "#0E9F6E",
10019
+ badgeBg: "rgba(14, 159, 110, 0.95)",
10020
+ badgeColor: "#FFFFFF"
10021
+ };
10022
+ case "too-far":
10023
+ case "too-close":
10024
+ case "off-center":
10025
+ return {
10026
+ borderColor: "#F59E0B",
10027
+ boxShadow: "0 0 0 4px rgba(245, 158, 11, 0.7), 0 0 35px rgba(245, 158, 11, 0.35)",
10028
+ svgStroke: "#F59E0B",
10029
+ badgeBg: "rgba(245, 158, 11, 0.95)",
10030
+ badgeColor: "#1F2937"
10031
+ };
10032
+ case "searching":
10033
+ default:
10034
+ return {
10035
+ borderColor: "rgba(156, 163, 175, 0.5)",
10036
+ boxShadow: "0 0 0 4px rgba(156, 163, 175, 0.3), 0 0 20px rgba(156, 163, 175, 0.15)",
10037
+ svgStroke: "#9CA3AF",
10038
+ badgeBg: "rgba(55, 65, 81, 0.9)",
10039
+ badgeColor: "#FFFFFF"
10040
+ };
10041
+ }
10042
+ }
10043
+ function CameraOvalGuide({
10044
+ stream,
10045
+ faceState,
10046
+ instruction,
10047
+ className = "",
10048
+ style = {}
10049
+ }) {
10050
+ const videoRef = useRef(null);
10051
+ useEffect(() => {
10052
+ const videoEl = videoRef.current;
10053
+ if (!videoEl) return;
10054
+ if (stream) {
10055
+ videoEl.srcObject = stream;
10056
+ videoEl.play().catch(() => {
10057
+ });
10058
+ } else {
10059
+ videoEl.srcObject = null;
10060
+ }
10061
+ return () => {
10062
+ if (stream) {
10063
+ stream.getTracks().forEach((track) => track.stop());
10064
+ }
10065
+ if (videoEl) {
10066
+ videoEl.srcObject = null;
10067
+ }
10068
+ };
10069
+ }, [stream]);
10070
+ const halo = getHaloStyles(faceState);
10071
+ return /* @__PURE__ */ jsxs(
10072
+ "div",
10073
+ {
10074
+ className: `neofaceid-camera-oval-guide-container ${className}`,
10075
+ style: {
10076
+ display: "flex",
10077
+ flexDirection: "column",
10078
+ alignItems: "center",
10079
+ justifyContent: "center",
10080
+ position: "relative",
10081
+ width: "100%",
10082
+ maxHeight: "100%",
10083
+ padding: "16px",
10084
+ boxSizing: "border-box",
10085
+ ...style
10086
+ },
10087
+ children: [
10088
+ /* @__PURE__ */ jsx("style", { children: `
10089
+ .neofaceid-oval-frame {
10090
+ width: 320px;
10091
+ height: 400px;
10092
+ border-radius: 50%;
10093
+ overflow: hidden;
10094
+ position: relative;
10095
+ background: #000000;
10096
+ transition: box-shadow 0.3s ease, border-color 0.3s ease;
10097
+ }
10098
+
10099
+ .neofaceid-oval-video {
10100
+ width: 100%;
10101
+ height: 100%;
10102
+ object-fit: cover;
10103
+ transform: scaleX(-1);
10104
+ display: block;
10105
+ }
10106
+
10107
+ .neofaceid-oval-svg-overlay {
10108
+ position: absolute;
10109
+ inset: 0;
10110
+ width: 100%;
10111
+ height: 100%;
10112
+ pointer-events: none;
10113
+ z-index: 2;
10114
+ }
10115
+
10116
+ .neofaceid-oval-instruction-badge {
10117
+ margin-top: 20px;
10118
+ padding: 10px 20px;
10119
+ border-radius: 20px;
10120
+ font-size: 15px;
10121
+ font-weight: 600;
10122
+ text-align: center;
10123
+ max-width: 320px;
10124
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
10125
+ backdrop-filter: blur(8px);
10126
+ transition: background-color 0.3s ease, color 0.3s ease;
10127
+ z-index: 5;
10128
+ }
10129
+
10130
+ @media (max-width: 640px) {
10131
+ .neofaceid-oval-frame {
10132
+ width: 280px;
10133
+ height: 350px;
10134
+ }
10135
+ }
10136
+ ` }),
10137
+ /* @__PURE__ */ jsxs(
10138
+ "div",
10139
+ {
10140
+ className: "neofaceid-oval-frame",
10141
+ style: {
10142
+ boxShadow: halo.boxShadow,
10143
+ borderColor: halo.borderColor
10144
+ },
10145
+ children: [
10146
+ /* @__PURE__ */ jsx(
10147
+ "video",
10148
+ {
10149
+ ref: videoRef,
10150
+ className: "neofaceid-oval-video",
10151
+ autoPlay: true,
10152
+ playsInline: true,
10153
+ muted: true,
10154
+ "data-testid": "camera-oval-video"
10155
+ }
10156
+ ),
10157
+ /* @__PURE__ */ jsx(
10158
+ "svg",
10159
+ {
10160
+ className: "neofaceid-oval-svg-overlay",
10161
+ viewBox: "0 0 320 400",
10162
+ fill: "none",
10163
+ xmlns: "http://www.w3.org/2000/svg",
10164
+ children: /* @__PURE__ */ jsx(
10165
+ "ellipse",
10166
+ {
10167
+ cx: "160",
10168
+ cy: "200",
10169
+ rx: "140",
10170
+ ry: "180",
10171
+ stroke: halo.svgStroke,
10172
+ strokeWidth: "3",
10173
+ strokeDasharray: faceState === "searching" ? "8 8" : "none",
10174
+ style: { transition: "stroke 0.3s ease" }
10175
+ }
10176
+ )
10177
+ }
10178
+ )
10179
+ ]
10180
+ }
10181
+ ),
10182
+ instruction && /* @__PURE__ */ jsx(
10183
+ "div",
9108
10184
  {
9109
- type: "button",
9110
- className: "neofaceid-consent-button neofaceid-consent-button-ghost",
9111
- onClick: onDecline,
9112
- children: "Agora não"
10185
+ className: "neofaceid-oval-instruction-badge",
10186
+ "aria-live": "assertive",
10187
+ style: {
10188
+ backgroundColor: halo.badgeBg,
10189
+ color: halo.badgeColor
10190
+ },
10191
+ "data-testid": "camera-oval-instruction",
10192
+ children: instruction
9113
10193
  }
9114
10194
  )
9115
- ] }),
9116
- /* @__PURE__ */ jsx("div", { className: "neofaceid-consent-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9117
- ] }) })
9118
- ] });
9119
- }
9120
- class ConsentModal {
9121
- constructor() {
9122
- __publicField(this, "container", null);
9123
- __publicField(this, "root", null);
9124
- }
9125
- show(props) {
9126
- if (this.container) return;
9127
- injectThemeStyles();
9128
- this.container = document.createElement("div");
9129
- this.container.id = "neofaceid-consent-modal-container";
9130
- document.body.appendChild(this.container);
9131
- this.root = createRoot(this.container);
9132
- this.root.render(
9133
- /* @__PURE__ */ jsx(
9134
- ConsentModalComponent,
9135
- {
9136
- ...props,
9137
- onAccept: () => {
9138
- this.close();
9139
- props.onAccept();
9140
- },
9141
- onDecline: () => {
9142
- this.close();
9143
- props.onDecline();
9144
- }
9145
- }
9146
- )
9147
- );
9148
- }
9149
- close() {
9150
- if (this.container && document.body.contains(this.container)) {
9151
- if (this.root) this.root.unmount();
9152
- document.body.removeChild(this.container);
9153
- this.container = null;
9154
- this.root = null;
10195
+ ]
9155
10196
  }
9156
- }
9157
- }
9158
- const DEFAULT_APP_NAME = "sua aplicação";
9159
- function requestConsent(info = {}) {
9160
- const globalAppName = getAppName();
9161
- const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
9162
- if (!info.appName && !globalAppName) {
9163
- console.warn(
9164
- '[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via NeoFaceSDK.init({ appName }) ou opts do caller.'
9165
- );
9166
- }
9167
- const flow = info.flow ?? "verification";
9168
- return new Promise((resolve) => {
9169
- const modal = new ConsentModal();
9170
- modal.show({
9171
- appName,
9172
- flow,
9173
- privacyPolicyUrl: info.privacyPolicyUrl ?? DEFAULT_PRIVACY_URL,
9174
- retentionDays: info.retentionDays ?? DEFAULT_RETENTION_DAYS,
9175
- onAccept: () => resolve(true),
9176
- onDecline: () => resolve(false)
9177
- });
9178
- });
10197
+ );
9179
10198
  }
9180
- const DEFAULT_CONSENT_INFO = {
9181
- flow: "verification",
9182
- privacyPolicyUrl: DEFAULT_PRIVACY_URL,
9183
- retentionDays: DEFAULT_RETENTION_DAYS
9184
- };
9185
10199
  const DARK_THRESHOLD = 0.235;
9186
10200
  const IMPROVEMENT_TARGET = 0.12;
9187
10201
  const SAMPLE_INTERVAL_MS = 600;
@@ -10606,7 +11620,6 @@ const CAMERA_READY_DELAY = 200;
10606
11620
  const CAMERA_STABILITY_DELAY = 100;
10607
11621
  const FACE_DETECTION_TIME = 5e3;
10608
11622
  const DETECTION_INTERVAL = 100;
10609
- const SUCCESS_DELAY = 200;
10610
11623
  let modelsLoaded = false;
10611
11624
  let modelsLoading = null;
10612
11625
  async function preloadFaceDetectionModels() {
@@ -10763,14 +11776,13 @@ async function captureFaceSilently() {
10763
11776
  });
10764
11777
  });
10765
11778
  }
10766
- async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL, overlay) {
11779
+ async function detectFaceWithPosition(video, stream, guideModal, maxTime = FACE_DETECTION_TIME, interval = DETECTION_INTERVAL) {
10767
11780
  return new Promise((resolve) => {
10768
11781
  let attempts = 0;
10769
11782
  const maxAttempts = Math.ceil(maxTime / interval);
10770
11783
  let timeoutId = null;
10771
11784
  let isResolved = false;
10772
- let overlayShowingWaiting = false;
10773
- const ATTEMPTS_BEFORE_WAITING = Math.ceil(3e3 / interval);
11785
+ let centeredDurationMs = 0;
10774
11786
  const finish = (success) => {
10775
11787
  if (isResolved) return;
10776
11788
  isResolved = true;
@@ -10781,11 +11793,7 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
10781
11793
  if (isResolved) return;
10782
11794
  attempts++;
10783
11795
  if (!modelsLoaded) {
10784
- if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
10785
- overlay.updateStatus("waiting-for-face");
10786
- overlayShowingWaiting = true;
10787
- console.log("⏳ Models not loaded - waiting for face...");
10788
- }
11796
+ guideModal.update(stream, "searching", "Carregando modelos de detecção...");
10789
11797
  if (attempts >= maxAttempts) {
10790
11798
  finish(false);
10791
11799
  return;
@@ -10798,33 +11806,29 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
10798
11806
  video,
10799
11807
  new faceapi.TinyFaceDetectorOptions({ inputSize: 160, scoreThreshold: 0.3 })
10800
11808
  );
10801
- if (detection) {
10802
- if (overlay && overlayShowingWaiting) {
10803
- overlay.updateStatus("detecting");
10804
- overlayShowingWaiting = false;
10805
- console.log("👤 Face appeared");
11809
+ const { faceState, instruction } = evaluateFacePosition(
11810
+ detection ? detection.box : null,
11811
+ video.videoWidth,
11812
+ video.videoHeight
11813
+ );
11814
+ guideModal.update(stream, faceState, instruction);
11815
+ if (faceState === "centered") {
11816
+ centeredDurationMs += interval;
11817
+ if (centeredDurationMs >= 1e3) {
11818
+ console.log("✅ Rosto mantido centralizado por 1s consecutivo");
11819
+ finish(true);
11820
+ return;
10806
11821
  }
10807
- console.log("✅ Face detected!");
10808
- finish(true);
10809
- return;
10810
- }
10811
- if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
10812
- overlay.updateStatus("waiting-for-face");
10813
- overlayShowingWaiting = true;
10814
- console.log("⏳ Waiting for face...");
11822
+ } else {
11823
+ centeredDurationMs = 0;
10815
11824
  }
10816
11825
  if (attempts >= maxAttempts) {
10817
- console.warn("⏱️ Face detection timeout - no face found");
10818
11826
  finish(false);
10819
11827
  return;
10820
11828
  }
10821
11829
  timeoutId = window.setTimeout(checkFace, interval);
10822
11830
  } catch (error) {
10823
- console.error("❌ Face detection error:", error);
10824
- if (overlay && !overlayShowingWaiting && attempts >= ATTEMPTS_BEFORE_WAITING) {
10825
- overlay.updateStatus("waiting-for-face");
10826
- overlayShowingWaiting = true;
10827
- }
11831
+ console.error("❌ Erro na detecção facial:", error);
10828
11832
  if (attempts >= maxAttempts) {
10829
11833
  finish(false);
10830
11834
  return;
@@ -10834,19 +11838,19 @@ async function detectFaceQuickly(video, maxTime = FACE_DETECTION_TIME, interval
10834
11838
  };
10835
11839
  window.setTimeout(() => {
10836
11840
  if (!isResolved) {
10837
- console.warn("⏱️ Face detection safety timeout reached");
10838
11841
  finish(false);
10839
11842
  }
10840
11843
  }, maxTime + 500);
10841
11844
  checkFace();
10842
11845
  });
10843
11846
  }
10844
- async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry = false) {
10845
- if (!isRetry) {
10846
- overlay.updateStatus("preparing");
10847
- } else {
10848
- overlay.updateStatus("detecting");
10849
- }
11847
+ async function attemptLogin(applicationToken, fastMode = false, isRetry = false, onCancel) {
11848
+ const guideModal = showCameraOvalGuideModal({
11849
+ stream: null,
11850
+ faceState: "searching",
11851
+ instruction: "Iniciando câmera...",
11852
+ onCancel
11853
+ });
10850
11854
  const video = document.createElement("video");
10851
11855
  video.style.position = "fixed";
10852
11856
  video.style.top = "-9999px";
@@ -10877,6 +11881,7 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
10877
11881
  });
10878
11882
  }
10879
11883
  video.srcObject = stream;
11884
+ guideModal.update(stream, "searching", "Centralize o rosto");
10880
11885
  await new Promise((resolve) => {
10881
11886
  if (video.readyState >= 1) {
10882
11887
  resolve();
@@ -10905,18 +11910,19 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
10905
11910
  checkSize();
10906
11911
  }
10907
11912
  });
10908
- overlay.updateStatus("detecting");
10909
11913
  await new Promise((resolve) => setTimeout(resolve, CAMERA_STABILITY_DELAY));
10910
11914
  let faceDetected = true;
10911
11915
  if (!fastMode) {
10912
- faceDetected = await detectFaceQuickly(
11916
+ faceDetected = await detectFaceWithPosition(
10913
11917
  video,
11918
+ stream,
11919
+ guideModal,
10914
11920
  FACE_DETECTION_TIME,
10915
- DETECTION_INTERVAL,
10916
- overlay
11921
+ DETECTION_INTERVAL
10917
11922
  );
10918
11923
  }
10919
11924
  if (!faceDetected) {
11925
+ guideModal.close();
10920
11926
  if (stream) {
10921
11927
  stream.getTracks().forEach((track) => track.stop());
10922
11928
  }
@@ -10924,16 +11930,17 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
10924
11930
  video.parentElement.removeChild(video);
10925
11931
  }
10926
11932
  throw new NeoFaceError(
10927
- "Nenhum rosto detectado. Por favor, posicione seu rosto na frente da câmera e tente novamente.",
11933
+ "Nenhum rosto centralizado detectado. Por favor, tente novamente.",
10928
11934
  ErrorType.VALIDATION_ERROR
10929
11935
  );
10930
11936
  }
10931
- overlay.updateStatus("capturing");
11937
+ guideModal.update(stream, "centered", "Capturando imagem...");
10932
11938
  const canvas = document.createElement("canvas");
10933
11939
  canvas.width = video.videoWidth;
10934
11940
  canvas.height = video.videoHeight;
10935
11941
  const ctx = canvas.getContext("2d");
10936
11942
  if (!ctx) {
11943
+ guideModal.close();
10937
11944
  if (stream) {
10938
11945
  stream.getTracks().forEach((track) => track.stop());
10939
11946
  }
@@ -10943,12 +11950,6 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
10943
11950
  throw new NeoFaceError("Erro ao capturar imagem", ErrorType.CAPTURE_ERROR);
10944
11951
  }
10945
11952
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
10946
- if (stream) {
10947
- stream.getTracks().forEach((track) => track.stop());
10948
- }
10949
- if (video.parentElement) {
10950
- video.parentElement.removeChild(video);
10951
- }
10952
11953
  const imageBlob = await new Promise((resolve, reject) => {
10953
11954
  canvas.toBlob(
10954
11955
  (blob) => {
@@ -10963,13 +11964,21 @@ async function attemptLogin(applicationToken, overlay, fastMode = false, isRetry
10963
11964
  0.85
10964
11965
  );
10965
11966
  });
10966
- overlay.updateStatus("verifying");
11967
+ guideModal.update(stream, "centered", "Verificando com o servidor...");
10967
11968
  const result = await loginWithBiometric(imageBlob, applicationToken);
11969
+ guideModal.close();
11970
+ if (stream) {
11971
+ stream.getTracks().forEach((track) => track.stop());
11972
+ }
11973
+ if (video.parentElement) {
11974
+ video.parentElement.removeChild(video);
11975
+ }
10968
11976
  if (!result.success) {
10969
11977
  throw new NeoFaceError("Login falhou", ErrorType.LOGIN_FAILED);
10970
11978
  }
10971
11979
  return result;
10972
11980
  } catch (error) {
11981
+ guideModal.close();
10973
11982
  if (stream) {
10974
11983
  stream.getTracks().forEach((track) => track.stop());
10975
11984
  }
@@ -10994,7 +12003,6 @@ async function executeBiometricLoginFlow(options) {
10994
12003
  onCancel,
10995
12004
  fastMode = false
10996
12005
  } = options;
10997
- const overlay = new BiometricStatusOverlay();
10998
12006
  const fallbackPrompt = new FallbackPrompt();
10999
12007
  let attempts = 0;
11000
12008
  let lastError;
@@ -11002,34 +12010,26 @@ async function executeBiometricLoginFlow(options) {
11002
12010
  preloadFaceDetectionModels().catch(() => {
11003
12011
  });
11004
12012
  }
11005
- overlay.show("preparing");
11006
12013
  const tryLogin = async () => {
11007
12014
  try {
11008
12015
  attempts++;
11009
- const result = await attemptLogin(applicationToken, overlay, fastMode, attempts > 1);
11010
- overlay.updateStatus("success");
11011
- setTimeout(() => {
11012
- overlay.close();
11013
- onSuccess(result);
11014
- }, SUCCESS_DELAY);
12016
+ const result = await attemptLogin(applicationToken, fastMode, attempts > 1, onCancel);
12017
+ onSuccess(result);
11015
12018
  } catch (error) {
11016
12019
  lastError = error instanceof NeoFaceError ? error : new NeoFaceError(
11017
12020
  error instanceof Error ? error.message : "Erro desconhecido",
11018
12021
  ErrorType.UNKNOWN
11019
12022
  );
11020
- overlay.updateStatus("error");
11021
12023
  if (attempts < MAX_ATTEMPTS$1) {
11022
12024
  setTimeout(() => {
11023
12025
  tryLogin();
11024
12026
  }, RETRY_DELAY);
11025
12027
  } else {
11026
12028
  setTimeout(() => {
11027
- overlay.close();
11028
12029
  fallbackPrompt.show(
11029
12030
  lastError,
11030
12031
  () => {
11031
12032
  attempts = 0;
11032
- overlay.show("preparing");
11033
12033
  setTimeout(() => tryLogin(), RETRY_DELAY);
11034
12034
  },
11035
12035
  () => {
@@ -11051,9 +12051,7 @@ async function executeBiometricLoginFlow(options) {
11051
12051
  }
11052
12052
  }
11053
12053
  };
11054
- {
11055
- tryLogin();
11056
- }
12054
+ tryLogin();
11057
12055
  }
11058
12056
  const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11059
12057
  __proto__: null,
@@ -11062,6 +12060,25 @@ const biometricLoginFlow = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
11062
12060
  preloadFaceDetectionModels
11063
12061
  }, Symbol.toStringTag, { value: "Module" }));
11064
12062
  const CONSENT_DENIED_MESSAGE$1 = "Consentimento recusado pelo usuário.";
12063
+ function openPasswordFallback(options) {
12064
+ if (!getAutoFallback().toPassword) {
12065
+ options.onError(
12066
+ new NeoFaceError(
12067
+ "Biometria não reconhecida e a queda automática para senha está desligada.",
12068
+ ErrorType.LOGIN_FAILED
12069
+ )
12070
+ );
12071
+ return;
12072
+ }
12073
+ const fallbackOptions = {
12074
+ applicationToken: options.applicationToken,
12075
+ onSuccess: options.onSuccess,
12076
+ onError: options.onError,
12077
+ title: "Login Alternativo",
12078
+ subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
12079
+ };
12080
+ new EmailPasswordModal(fallbackOptions).open();
12081
+ }
11065
12082
  async function startFaceLogin(options) {
11066
12083
  const consentAccepted = await requestConsent({
11067
12084
  appName: options.appName,
@@ -11076,17 +12093,7 @@ async function startFaceLogin(options) {
11076
12093
  applicationToken: options.applicationToken,
11077
12094
  onSuccess: options.onSuccess,
11078
12095
  onError: options.onError,
11079
- onFallbackRequest: options.onFallbackRequest || (() => {
11080
- const fallbackOptions = {
11081
- applicationToken: options.applicationToken,
11082
- onSuccess: options.onSuccess,
11083
- onError: options.onError,
11084
- title: "Login Alternativo",
11085
- subtitle: "Biometria não reconhecida. Por favor, informe email e senha."
11086
- };
11087
- const modal = new EmailPasswordModal(fallbackOptions);
11088
- modal.open();
11089
- }),
12096
+ onFallbackRequest: options.onFallbackRequest || (() => openPasswordFallback(options)),
11090
12097
  onCancel: options.onCancel
11091
12098
  });
11092
12099
  } catch (error) {
@@ -12255,15 +13262,23 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12255
13262
  resolve();
12256
13263
  };
12257
13264
  });
13265
+ const guideModal = showCameraOvalGuideModal({
13266
+ stream,
13267
+ faceState: "centered",
13268
+ instruction: PHOTO_INSTRUCTIONS[0]
13269
+ });
12258
13270
  const canvas = document.createElement("canvas");
12259
13271
  canvas.width = video.videoWidth;
12260
13272
  canvas.height = video.videoHeight;
12261
13273
  const ctx = canvas.getContext("2d");
12262
13274
  if (!ctx) {
13275
+ guideModal.close();
13276
+ stream.getTracks().forEach((track) => track.stop());
12263
13277
  throw new NeoFaceError("Failed to get canvas context", ErrorType.CAPTURE_ERROR);
12264
13278
  }
12265
13279
  for (let i = 0; i < TOTAL_PHOTOS; i++) {
12266
13280
  const instruction = PHOTO_INSTRUCTIONS[i];
13281
+ guideModal.update(stream, "centered", instruction);
12267
13282
  if (options == null ? void 0 : options.onProgress) {
12268
13283
  options.onProgress(i + 1, TOTAL_PHOTOS, instruction);
12269
13284
  }
@@ -12289,6 +13304,7 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12289
13304
  options.onPhotoTaken(i + 1, blob);
12290
13305
  }
12291
13306
  }
13307
+ guideModal.close();
12292
13308
  stream.getTracks().forEach((track) => track.stop());
12293
13309
  const lastPhoto = capturedPhotos[TOTAL_PHOTOS - 1];
12294
13310
  const sessionData = {
@@ -12336,110 +13352,6 @@ const authorizeOperation = async (applicationToken, cpf, options) => {
12336
13352
  throw new NeoFaceError("Unknown error during authorization", ErrorType.UNKNOWN);
12337
13353
  }
12338
13354
  };
12339
- function Sheet({
12340
- open,
12341
- onDismiss,
12342
- dismissOnOverlay = true,
12343
- ariaLabel,
12344
- ariaLabelledBy,
12345
- children,
12346
- className,
12347
- style
12348
- }) {
12349
- useEffect(() => {
12350
- injectThemeStyles();
12351
- }, []);
12352
- if (!open) return null;
12353
- const handleOverlay = () => {
12354
- if (dismissOnOverlay && onDismiss) onDismiss();
12355
- };
12356
- return /* @__PURE__ */ jsx(
12357
- "div",
12358
- {
12359
- className: "neofaceid-root",
12360
- role: "presentation",
12361
- style: {
12362
- position: "fixed",
12363
- inset: 0,
12364
- zIndex: 10001,
12365
- display: "flex",
12366
- alignItems: "center",
12367
- justifyContent: "center",
12368
- padding: 20,
12369
- background: `var(${CSS_VAR.overlay})`,
12370
- backdropFilter: "blur(3px)",
12371
- WebkitBackdropFilter: "blur(3px)"
12372
- },
12373
- onClick: handleOverlay,
12374
- children: /* @__PURE__ */ jsx(
12375
- "div",
12376
- {
12377
- role: "dialog",
12378
- "aria-modal": "true",
12379
- "aria-label": ariaLabel,
12380
- "aria-labelledby": ariaLabelledBy,
12381
- className,
12382
- onClick: (e) => e.stopPropagation(),
12383
- style: {
12384
- background: `var(${CSS_VAR.surface})`,
12385
- color: `var(${CSS_VAR.text})`,
12386
- borderRadius: `var(${CSS_VAR.radius}, 16px)`,
12387
- padding: 28,
12388
- maxWidth: 440,
12389
- width: "100%",
12390
- boxShadow: "0 1px 3px rgba(10,19,32,.08)",
12391
- boxSizing: "border-box",
12392
- ...style
12393
- },
12394
- children
12395
- }
12396
- )
12397
- }
12398
- );
12399
- }
12400
- const BASE_STYLE = {
12401
- minHeight: 44,
12402
- minWidth: 44,
12403
- padding: "12px 20px",
12404
- borderRadius: 16,
12405
- border: "none",
12406
- fontSize: 15,
12407
- fontWeight: 600,
12408
- fontFamily: "inherit",
12409
- cursor: "pointer",
12410
- transition: "background 120ms ease-out",
12411
- display: "inline-flex",
12412
- alignItems: "center",
12413
- justifyContent: "center",
12414
- gap: 8
12415
- };
12416
- const Button = forwardRef(
12417
- ({ variant = "primary", style, type = "button", ...rest }, ref) => {
12418
- const variantStyle = variant === "primary" ? {
12419
- background: `var(${CSS_VAR.action})`,
12420
- color: `var(${CSS_VAR.actionText})`
12421
- } : variant === "secondary" ? {
12422
- background: `var(${CSS_VAR.surfaceMuted})`,
12423
- color: `var(${CSS_VAR.text})`
12424
- } : {
12425
- background: "transparent",
12426
- color: `var(${CSS_VAR.textMuted})`
12427
- };
12428
- return (
12429
- // eslint-disable-next-line react/button-has-type
12430
- /* @__PURE__ */ jsx(
12431
- "button",
12432
- {
12433
- ref,
12434
- type,
12435
- ...rest,
12436
- style: { ...BASE_STYLE, ...variantStyle, ...style }
12437
- }
12438
- )
12439
- );
12440
- }
12441
- );
12442
- Button.displayName = "Button";
12443
13355
  function FocusFrame({
12444
13356
  size = 96,
12445
13357
  color,
@@ -12667,32 +13579,6 @@ function pointsFor(g) {
12667
13579
  return ["24 24", "24 24", "24 24"];
12668
13580
  }
12669
13581
  }
12670
- function StatusPill({ tone, children, icon, className }) {
12671
- const palette = SEMANTIC[tone];
12672
- return /* @__PURE__ */ jsxs(
12673
- "span",
12674
- {
12675
- className,
12676
- style: {
12677
- display: "inline-flex",
12678
- alignItems: "center",
12679
- gap: 6,
12680
- padding: "4px 10px",
12681
- borderRadius: 9999,
12682
- background: palette.bg,
12683
- color: palette.fg,
12684
- fontSize: 13,
12685
- fontWeight: 600,
12686
- fontFamily: "inherit",
12687
- lineHeight: 1.3
12688
- },
12689
- children: [
12690
- icon,
12691
- children
12692
- ]
12693
- }
12694
- );
12695
- }
12696
13582
  const DEFAULT = {
12697
13583
  title: "Não deu certo desta vez",
12698
13584
  hint: "Tente se posicionar em um ambiente mais estável.",
@@ -14313,7 +15199,7 @@ const CONSENT_DENIED_MESSAGE = "Consentimento recusado pelo usuário.";
14313
15199
  function start(applicationToken, callbacks, options) {
14314
15200
  requestConsent({ appName: options == null ? void 0 : options.appName, flow: "verification" }).then((accepted) => {
14315
15201
  if (!accepted) {
14316
- callbacks.onError("CONSENT_DENIED", CONSENT_DENIED_MESSAGE);
15202
+ callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
14317
15203
  return;
14318
15204
  }
14319
15205
  const container = document.createElement("div");
@@ -14342,14 +15228,16 @@ function start(applicationToken, callbacks, options) {
14342
15228
  root.render(modalElement);
14343
15229
  }).catch((error) => {
14344
15230
  cleanup();
14345
- callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
15231
+ callbacks.onError(
15232
+ error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
15233
+ );
14346
15234
  });
14347
15235
  });
14348
15236
  }
14349
15237
  function startBiometricRegistration(personData, applicationToken, callbacks, options) {
14350
15238
  requestConsent({ appName: options == null ? void 0 : options.appName, flow: "registration" }).then((accepted) => {
14351
15239
  if (!accepted) {
14352
- callbacks.onError("CONSENT_DENIED", CONSENT_DENIED_MESSAGE);
15240
+ callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
14353
15241
  return;
14354
15242
  }
14355
15243
  const container = document.createElement("div");
@@ -14377,7 +15265,7 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
14377
15265
  },
14378
15266
  onError: (error) => {
14379
15267
  cleanup();
14380
- callbacks.onError("BIOMETRIC_REGISTRATION_ERROR", error);
15268
+ callbacks.onError(new NeoFaceError(error, ErrorType.UNKNOWN));
14381
15269
  },
14382
15270
  onCannotGesture: options == null ? void 0 : options.onCannotGesture
14383
15271
  });
@@ -14385,14 +15273,16 @@ function startBiometricRegistration(personData, applicationToken, callbacks, opt
14385
15273
  rootRef.render(modalElement);
14386
15274
  }).catch((error) => {
14387
15275
  cleanup();
14388
- callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
15276
+ callbacks.onError(
15277
+ error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
15278
+ );
14389
15279
  });
14390
15280
  });
14391
15281
  }
14392
15282
  function startLivenessCapture(applicationToken, callbacks, options) {
14393
15283
  requestConsent({ appName: options == null ? void 0 : options.appName, flow: "verification" }).then((accepted) => {
14394
15284
  if (!accepted) {
14395
- callbacks.onError("CONSENT_DENIED", CONSENT_DENIED_MESSAGE);
15285
+ callbacks.onError(new NeoFaceError(CONSENT_DENIED_MESSAGE, ErrorType.CONSENT_DENIED));
14396
15286
  return;
14397
15287
  }
14398
15288
  const container = document.createElement("div");
@@ -14425,7 +15315,7 @@ function startLivenessCapture(applicationToken, callbacks, options) {
14425
15315
  },
14426
15316
  onError: (error) => {
14427
15317
  cleanup();
14428
- callbacks.onError("LIVENESS_CAPTURE_ERROR", error);
15318
+ callbacks.onError(new NeoFaceError(error, ErrorType.UNKNOWN));
14429
15319
  },
14430
15320
  onCannotGesture: options == null ? void 0 : options.onCannotGesture
14431
15321
  });
@@ -14433,7 +15323,9 @@ function startLivenessCapture(applicationToken, callbacks, options) {
14433
15323
  rootRef.render(modalElement);
14434
15324
  }).catch((error) => {
14435
15325
  cleanup();
14436
- callbacks.onError("TOKEN_VALIDATION_ERROR", error.message || "Failed to validate token");
15326
+ callbacks.onError(
15327
+ error instanceof NeoFaceError ? error : new NeoFaceError(error.message || "Failed to validate token", ErrorType.INVALID_TOKEN)
15328
+ );
14437
15329
  });
14438
15330
  });
14439
15331
  }
@@ -14441,6 +15333,7 @@ export {
14441
15333
  BiometricCaptureModal,
14442
15334
  BiometricRegistrationModal,
14443
15335
  BiometricStatusOverlay,
15336
+ CameraOvalGuide,
14444
15337
  ConsentModal,
14445
15338
  DEFAULT_CONSENT_INFO,
14446
15339
  DocumentCaptureModal,
@@ -14456,15 +15349,18 @@ export {
14456
15349
  VERSION,
14457
15350
  authorize,
14458
15351
  authorizeOperation,
15352
+ awaitPushApproval,
14459
15353
  checkUserExistence,
14460
15354
  clearConsentTrail,
14461
15355
  completeOnboarding,
14462
15356
  completeOnboardingWithData,
14463
15357
  confirmPasswordReset,
15358
+ consumeSseStream,
14464
15359
  detectBiometricType,
14465
15360
  getAccent,
14466
15361
  getAppName,
14467
15362
  getApplicationToken,
15363
+ getAutoFallback,
14468
15364
  getBaseUrl,
14469
15365
  getCachedCaptureSession,
14470
15366
  getConfig,
@@ -14489,10 +15385,12 @@ export {
14489
15385
  recognizeBiometric,
14490
15386
  recognizeByPurpose,
14491
15387
  recordConsent,
15388
+ refreshSession,
14492
15389
  registerBiometric,
14493
15390
  registerPersonWithBiometric,
14494
15391
  registerPersonWithoutFace,
14495
15392
  requestConsent,
15393
+ requestDeviceEnrollmentToken,
14496
15394
  requestLivenessChallenge,
14497
15395
  requestLivenessChallengeWithSession,
14498
15396
  requestPasswordReset,