@neofaceid/web-sdk 1.42.3 → 1.44.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.
@@ -5678,7 +5678,6 @@ const loginWithEmail = async (email2, password, applicationToken) => {
5678
5678
  }
5679
5679
  };
5680
5680
  const requestDeviceEnrollmentToken = async (userAccessToken, applicationToken) => {
5681
- var _a2, _b, _c;
5682
5681
  if (!userAccessToken) {
5683
5682
  throw new NeoFaceError(
5684
5683
  "Inscrição de aparelho exige um usuário autenticado (access token ausente).",
@@ -5697,23 +5696,36 @@ const requestDeviceEnrollmentToken = async (userAccessToken, applicationToken) =
5697
5696
  signal: controller.signal
5698
5697
  });
5699
5698
  if (!response.ok) {
5700
- if (response.status === 401 || response.status === 403) {
5699
+ if (response.status === 401) {
5701
5700
  throw new NeoFaceError(
5702
- "Não foi possível inscrever o aparelho: sessão expirada ou conta sem permissão. Faça login novamente.",
5701
+ "Sua sessão expirou. Entre novamente para inscrever o aparelho.",
5702
+ ErrorType.INVALID_TOKEN
5703
+ );
5704
+ }
5705
+ if (response.status === 403) {
5706
+ throw new NeoFaceError(
5707
+ "Esta conta não pode inscrever aparelhos. Procure o suporte.",
5703
5708
  ErrorType.INVALID_TOKEN
5704
5709
  );
5705
5710
  }
5706
5711
  throw new NeoFaceError(`API Error ${response.status}`, ErrorType.API_ERROR);
5707
5712
  }
5708
5713
  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);
5714
+ const body = data.data ?? data;
5715
+ const token = body.token;
5716
+ const handle = body.handle;
5717
+ if (!token || !handle) {
5718
+ throw new NeoFaceError(
5719
+ "Resposta de inscrição incompleta (token ou handle ausente)",
5720
+ ErrorType.API_ERROR
5721
+ );
5712
5722
  }
5713
5723
  return {
5724
+ handle,
5725
+ jti: body.jti ?? "",
5714
5726
  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"
5727
+ expiresIn: body.expires_in ?? 300,
5728
+ purpose: body.purpose ?? "device-enrollment"
5717
5729
  };
5718
5730
  } catch (error) {
5719
5731
  if (error instanceof NeoFaceError) throw error;
@@ -8886,8 +8898,8 @@ function BiometricRegistrationModal({
8886
8898
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
8887
8899
  ] });
8888
8900
  }
8889
- const VERSION = "1.42.3";
8890
- const RELEASE_DATE = "2026-09-14";
8901
+ const VERSION = "1.44.0";
8902
+ const RELEASE_DATE = "2026-09-16";
8891
8903
  const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
8892
8904
  const CONSENT_TRAIL_MAX_LIMIT = 100;
8893
8905
  async function hashString(inputStr) {
@@ -9599,8 +9611,8 @@ function StatusPill({ tone, children, icon, className }) {
9599
9611
  }
9600
9612
  );
9601
9613
  }
9602
- const STYLE_ID$2 = "neofaceid-awaiting-push-styles";
9603
- const CSS$1 = `
9614
+ const STYLE_ID$3 = "neofaceid-awaiting-push-styles";
9615
+ const CSS$2 = `
9604
9616
  .neofaceid-root .nfid-push {
9605
9617
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
9606
9618
  padding: 8px 8px 4px;
@@ -9651,7 +9663,7 @@ const CSS$1 = `
9651
9663
  .neofaceid-root .nfid-push-pulse { animation: none; }
9652
9664
  }
9653
9665
  `;
9654
- function formatRemaining(seconds) {
9666
+ function formatRemaining$1(seconds) {
9655
9667
  const safe = Math.max(0, seconds);
9656
9668
  const mm = Math.floor(safe / 60);
9657
9669
  const ss = safe % 60;
@@ -9665,7 +9677,7 @@ function AwaitingPushView({
9665
9677
  }) {
9666
9678
  useEffect(() => {
9667
9679
  injectThemeStyles();
9668
- injectScopedStyles(STYLE_ID$2, CSS$1);
9680
+ injectScopedStyles(STYLE_ID$3, CSS$2);
9669
9681
  }, []);
9670
9682
  return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "status", "aria-live": "polite", children: [
9671
9683
  /* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando confirmação" }),
@@ -9705,7 +9717,7 @@ function AwaitingPushView({
9705
9717
  ] }),
9706
9718
  /* @__PURE__ */ jsxs("p", { className: "nfid-push-countdown", children: [
9707
9719
  "Expira em ",
9708
- formatRemaining(remainingSeconds)
9720
+ formatRemaining$1(remainingSeconds)
9709
9721
  ] }),
9710
9722
  /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
9711
9723
  /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
@@ -9714,7 +9726,7 @@ function AwaitingPushView({
9714
9726
  function PushExpiredView({ onFallback, onCancel }) {
9715
9727
  useEffect(() => {
9716
9728
  injectThemeStyles();
9717
- injectScopedStyles(STYLE_ID$2, CSS$1);
9729
+ injectScopedStyles(STYLE_ID$3, CSS$2);
9718
9730
  }, []);
9719
9731
  return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9720
9732
  /* @__PURE__ */ jsxs(
@@ -9744,10 +9756,52 @@ function PushExpiredView({ onFallback, onCancel }) {
9744
9756
  /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9745
9757
  ] });
9746
9758
  }
9759
+ function PushCancelledView({
9760
+ appName,
9761
+ onFallback,
9762
+ onCancel
9763
+ }) {
9764
+ useEffect(() => {
9765
+ injectThemeStyles();
9766
+ injectScopedStyles(STYLE_ID$3, CSS$2);
9767
+ }, []);
9768
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9769
+ /* @__PURE__ */ jsxs(
9770
+ "svg",
9771
+ {
9772
+ className: "nfid-push-phone-icon",
9773
+ viewBox: "0 0 24 24",
9774
+ fill: "none",
9775
+ stroke: "currentColor",
9776
+ strokeWidth: "1.75",
9777
+ strokeLinecap: "round",
9778
+ strokeLinejoin: "round",
9779
+ "aria-hidden": "true",
9780
+ style: { color: "var(--nfid-text-muted)" },
9781
+ children: [
9782
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
9783
+ /* @__PURE__ */ jsx("path", { d: "M8 12h8" })
9784
+ ]
9785
+ }
9786
+ ),
9787
+ /* @__PURE__ */ jsx("h2", { children: "Solicitação cancelada" }),
9788
+ /* @__PURE__ */ jsxs("p", { children: [
9789
+ "O pedido de confirmação foi cancelado antes de ser respondido. Você pode tentar de novo pelo",
9790
+ " ",
9791
+ appName,
9792
+ "."
9793
+ ] }),
9794
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-actions", children: onFallback ? /* @__PURE__ */ jsxs(Fragment, { children: [
9795
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Entrar de outro jeito" }),
9796
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
9797
+ ] }) : /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onCancel, children: "Entendi" }) }),
9798
+ /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9799
+ ] });
9800
+ }
9747
9801
  function PushDeniedView({ appName, onCancel }) {
9748
9802
  useEffect(() => {
9749
9803
  injectThemeStyles();
9750
- injectScopedStyles(STYLE_ID$2, CSS$1);
9804
+ injectScopedStyles(STYLE_ID$3, CSS$2);
9751
9805
  }, []);
9752
9806
  return /* @__PURE__ */ jsxs("div", { className: "nfid-push", role: "alert", "aria-live": "assertive", children: [
9753
9807
  /* @__PURE__ */ jsxs(
@@ -9778,9 +9832,9 @@ function PushDeniedView({ appName, onCancel }) {
9778
9832
  /* @__PURE__ */ jsx("div", { className: "nfid-push-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
9779
9833
  ] });
9780
9834
  }
9781
- const CONTAINER_ID = "neofaceid-push-approval-container";
9835
+ const CONTAINER_ID$1 = "neofaceid-push-approval-container";
9782
9836
  function classifyAuthorizationFrame(frame) {
9783
- var _a2, _b;
9837
+ var _a2, _b, _c;
9784
9838
  switch (frame.status) {
9785
9839
  case "approved":
9786
9840
  case "success":
@@ -9792,12 +9846,26 @@ function classifyAuthorizationFrame(frame) {
9792
9846
  };
9793
9847
  case "expired":
9794
9848
  return { kind: "resolve", value: { status: "expired" } };
9849
+ // Quinto terminal do core: a origem desistiu, ou o Push cancelou a
9850
+ // solicitação. Não é traduzido para `expired` de propósito — o prazo não
9851
+ // estourou, alguém decidiu parar, e a tela precisa dizer coisas diferentes
9852
+ // nos dois casos. Sem este ramo o frame cairia em `pending` e o fluxo
9853
+ // morreria no tempo limite.
9854
+ case "cancelled":
9855
+ return {
9856
+ kind: "resolve",
9857
+ value: {
9858
+ status: "cancelled",
9859
+ source: "server",
9860
+ reason: ((_b = frame.data) == null ? void 0 : _b.reason) ?? frame.message
9861
+ }
9862
+ };
9795
9863
  case "failed":
9796
9864
  case "error":
9797
9865
  return {
9798
9866
  kind: "reject",
9799
9867
  error: new NeoFaceError(
9800
- frame.message ?? ((_b = frame.data) == null ? void 0 : _b.message) ?? "Falha na confirmação",
9868
+ frame.message ?? ((_c = frame.data) == null ? void 0 : _c.message) ?? "Falha na confirmação",
9801
9869
  ErrorType.API_ERROR
9802
9870
  )
9803
9871
  };
@@ -9864,6 +9932,10 @@ function PushApprovalModal({
9864
9932
  setPhase({ kind: "expired" });
9865
9933
  return;
9866
9934
  }
9935
+ if (outcome.status === "cancelled") {
9936
+ setPhase({ kind: "cancelled", reason: outcome.reason });
9937
+ return;
9938
+ }
9867
9939
  onSettle(outcome);
9868
9940
  }).catch(() => {
9869
9941
  if (done || controller.signal.aborted) return;
@@ -9878,6 +9950,19 @@ function PushApprovalModal({
9878
9950
  if (phase.kind === "denied") {
9879
9951
  return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Confirmação recusada", children: /* @__PURE__ */ jsx(PushDeniedView, { appName: headerAppName, onCancel: () => onSettle({ status: "denied" }) }) });
9880
9952
  }
9953
+ if (phase.kind === "cancelled") {
9954
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Solicitação cancelada", children: /* @__PURE__ */ jsx(
9955
+ PushCancelledView,
9956
+ {
9957
+ appName: headerAppName,
9958
+ onFallback: onFallback ? () => {
9959
+ onFallback();
9960
+ onSettle({ status: "cancelled", source: "server", reason: phase.reason });
9961
+ } : void 0,
9962
+ onCancel: () => onSettle({ status: "cancelled", source: "server", reason: phase.reason })
9963
+ }
9964
+ ) });
9965
+ }
9881
9966
  if (phase.kind === "expired") {
9882
9967
  return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Tempo esgotado", children: /* @__PURE__ */ jsx(
9883
9968
  PushExpiredView,
@@ -9896,14 +9981,14 @@ function PushApprovalModal({
9896
9981
  appName: headerAppName,
9897
9982
  numericCode,
9898
9983
  remainingSeconds: remaining,
9899
- onCancel: () => onSettle({ status: "cancelled" })
9984
+ onCancel: () => onSettle({ status: "cancelled", source: "user" })
9900
9985
  }
9901
9986
  ) });
9902
9987
  }
9903
9988
  function awaitPushApproval(options) {
9904
9989
  return new Promise((resolve) => {
9905
9990
  const container = document.createElement("div");
9906
- container.id = CONTAINER_ID;
9991
+ container.id = CONTAINER_ID$1;
9907
9992
  document.body.appendChild(container);
9908
9993
  const root = createRoot(container);
9909
9994
  const settle = (outcome) => {
@@ -9914,6 +9999,392 @@ function awaitPushApproval(options) {
9914
9999
  root.render(/* @__PURE__ */ jsx(PushApprovalModal, { ...options, onSettle: settle }));
9915
10000
  });
9916
10001
  }
10002
+ const STYLE_ID$2 = "neofaceid-device-enrollment-styles";
10003
+ const CSS$1 = `
10004
+ .neofaceid-root .nfid-enroll {
10005
+ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
10006
+ padding: 8px 8px 4px;
10007
+ }
10008
+ .neofaceid-root .nfid-enroll h2 {
10009
+ margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
10010
+ letter-spacing: -0.015em;
10011
+ }
10012
+ .neofaceid-root .nfid-enroll p {
10013
+ margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
10014
+ }
10015
+ /* O QR é sempre escuro sobre branco, independente do tema: leitor de código
10016
+ falha com contraste invertido, e a moldura clara faz a zona de silêncio. */
10017
+ .neofaceid-root .nfid-enroll-qr {
10018
+ background: #FFFFFF; padding: 14px; border-radius: 16px; margin-top: 12px;
10019
+ line-height: 0; box-shadow: 0 0 0 1px var(--nfid-line);
10020
+ }
10021
+ .neofaceid-root .nfid-enroll-qr svg { display: block; width: 184px; height: 184px; }
10022
+ .neofaceid-root .nfid-enroll-handle {
10023
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
10024
+ margin-top: 14px; padding: 12px 24px;
10025
+ background: var(--nfid-surface-muted); border-radius: 12px; width: 100%;
10026
+ }
10027
+ .neofaceid-root .nfid-enroll-handle-label {
10028
+ font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
10029
+ color: var(--nfid-text-subtle);
10030
+ }
10031
+ .neofaceid-root .nfid-enroll-handle-value {
10032
+ font-size: 26px; font-weight: 800; line-height: 1.1; letter-spacing: 0.12em;
10033
+ color: var(--nfid-text); font-variant-numeric: tabular-nums;
10034
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
10035
+ }
10036
+ .neofaceid-root .nfid-enroll-countdown {
10037
+ font-size: 13px; color: var(--nfid-text-muted); font-variant-numeric: tabular-nums;
10038
+ margin-top: 10px;
10039
+ }
10040
+ .neofaceid-root .nfid-enroll-actions {
10041
+ display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 16px;
10042
+ }
10043
+ .neofaceid-root .nfid-enroll-note {
10044
+ font-size: 12px; line-height: 1.45; color: var(--nfid-text-muted);
10045
+ background: var(--nfid-surface-muted); border-radius: 10px;
10046
+ padding: 10px 14px; margin-top: 12px; text-align: left; width: 100%;
10047
+ }
10048
+ .neofaceid-root .nfid-enroll-icon { width: 56px; height: 56px; }
10049
+ .neofaceid-root .nfid-enroll-footer {
10050
+ display: flex; justify-content: center; margin-top: 16px; padding-top: 12px;
10051
+ border-top: 1px solid var(--nfid-line); width: 100%;
10052
+ }
10053
+ `;
10054
+ function formatRemaining(seconds) {
10055
+ const safe = Math.max(0, seconds);
10056
+ const mm = Math.floor(safe / 60);
10057
+ const ss = safe % 60;
10058
+ return `${mm}:${String(ss).padStart(2, "0")}`;
10059
+ }
10060
+ function QrCode({ value }) {
10061
+ const [path, setPath] = useState(null);
10062
+ useEffect(() => {
10063
+ let cancelled = false;
10064
+ import("./qrcode-DYfGiWu6.js").then((mod) => {
10065
+ if (cancelled) return;
10066
+ const qrcode = mod.default ?? mod;
10067
+ const qr = qrcode(0, "M");
10068
+ qr.addData(value);
10069
+ qr.make();
10070
+ const count = qr.getModuleCount();
10071
+ const parts = [];
10072
+ for (let r = 0; r < count; r += 1) {
10073
+ for (let c = 0; c < count; c += 1) {
10074
+ if (qr.isDark(r, c)) parts.push(`M${c} ${r}h1v1h-1z`);
10075
+ }
10076
+ }
10077
+ setPath({ d: parts.join(""), count });
10078
+ }).catch(() => {
10079
+ if (!cancelled) setPath(null);
10080
+ });
10081
+ return () => {
10082
+ cancelled = true;
10083
+ };
10084
+ }, [value]);
10085
+ if (!path) {
10086
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 21 21", "aria-hidden": "true" });
10087
+ }
10088
+ return /* @__PURE__ */ jsx(
10089
+ "svg",
10090
+ {
10091
+ viewBox: `0 0 ${path.count} ${path.count}`,
10092
+ shapeRendering: "crispEdges",
10093
+ role: "img",
10094
+ "aria-label": `Código para inscrição do aparelho: ${value.split("").join(" ")}`,
10095
+ children: /* @__PURE__ */ jsx("path", { d: path.d, fill: "#000000" })
10096
+ }
10097
+ );
10098
+ }
10099
+ function DeviceEnrollmentView({
10100
+ appName,
10101
+ handle,
10102
+ remainingSeconds,
10103
+ onCancel
10104
+ }) {
10105
+ useEffect(() => {
10106
+ injectThemeStyles();
10107
+ injectScopedStyles(STYLE_ID$2, CSS$1);
10108
+ }, []);
10109
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-enroll", role: "status", "aria-live": "polite", children: [
10110
+ /* @__PURE__ */ jsx(StatusPill, { tone: "attention", children: "Aguardando o aparelho" }),
10111
+ /* @__PURE__ */ jsx("h2", { children: "Vincule seu celular" }),
10112
+ /* @__PURE__ */ jsxs("p", { children: [
10113
+ "Abra o NeoFaceID Push no celular e aponte para este código para vinculá-lo à sua conta no",
10114
+ " ",
10115
+ appName,
10116
+ "."
10117
+ ] }),
10118
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-qr", children: /* @__PURE__ */ jsx(QrCode, { value: handle }) }),
10119
+ /* @__PURE__ */ jsxs("div", { className: "nfid-enroll-handle", children: [
10120
+ /* @__PURE__ */ jsx("span", { className: "nfid-enroll-handle-label", children: "Ou digite este código" }),
10121
+ /* @__PURE__ */ jsx("span", { className: "nfid-enroll-handle-value", children: handle })
10122
+ ] }),
10123
+ /* @__PURE__ */ jsxs("p", { className: "nfid-enroll-countdown", children: [
10124
+ "O código expira em ",
10125
+ formatRemaining(remainingSeconds)
10126
+ ] }),
10127
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-actions", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Cancelar" }) }),
10128
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
10129
+ ] });
10130
+ }
10131
+ function DeviceEnrolledView({ deviceLabel, onDone }) {
10132
+ useEffect(() => {
10133
+ injectThemeStyles();
10134
+ injectScopedStyles(STYLE_ID$2, CSS$1);
10135
+ }, []);
10136
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-enroll", role: "alert", "aria-live": "assertive", children: [
10137
+ /* @__PURE__ */ jsxs(
10138
+ "svg",
10139
+ {
10140
+ className: "nfid-enroll-icon",
10141
+ viewBox: "0 0 24 24",
10142
+ fill: "none",
10143
+ stroke: "currentColor",
10144
+ strokeWidth: "1.75",
10145
+ strokeLinecap: "round",
10146
+ strokeLinejoin: "round",
10147
+ "aria-hidden": "true",
10148
+ style: { color: "#1E9E6A" },
10149
+ children: [
10150
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
10151
+ /* @__PURE__ */ jsx("path", { d: "M8.5 12.5l2.5 2.5 4.5-5" })
10152
+ ]
10153
+ }
10154
+ ),
10155
+ /* @__PURE__ */ jsx("h2", { children: "Aparelho vinculado" }),
10156
+ deviceLabel ? /* @__PURE__ */ jsxs("p", { children: [
10157
+ /* @__PURE__ */ jsx("strong", { children: deviceLabel }),
10158
+ " agora pode confirmar suas operações."
10159
+ ] }) : /* @__PURE__ */ jsx("p", { children: "Um aparelho acabou de ser vinculado à sua conta." }),
10160
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-note", children: deviceLabel ? /* @__PURE__ */ jsx(Fragment, { children: "Se este não é o seu aparelho, procure o suporte agora — alguém pode ter vinculado um celular à sua conta." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
10161
+ /* @__PURE__ */ jsx("strong", { children: "Não conseguimos identificar qual aparelho foi vinculado." }),
10162
+ " Se não foi você quem acabou de fazer isso, procure o suporte agora — alguém pode ter vinculado um celular à sua conta."
10163
+ ] }) }),
10164
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onDone, children: "Entendi" }) }),
10165
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
10166
+ ] });
10167
+ }
10168
+ function DeviceEnrollmentExpiredView({
10169
+ onRetry,
10170
+ onCancel
10171
+ }) {
10172
+ useEffect(() => {
10173
+ injectThemeStyles();
10174
+ injectScopedStyles(STYLE_ID$2, CSS$1);
10175
+ }, []);
10176
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-enroll", role: "alert", "aria-live": "assertive", children: [
10177
+ /* @__PURE__ */ jsxs(
10178
+ "svg",
10179
+ {
10180
+ className: "nfid-enroll-icon",
10181
+ viewBox: "0 0 24 24",
10182
+ fill: "none",
10183
+ stroke: "currentColor",
10184
+ strokeWidth: "1.75",
10185
+ strokeLinecap: "round",
10186
+ strokeLinejoin: "round",
10187
+ "aria-hidden": "true",
10188
+ style: { color: "var(--nfid-text-muted)" },
10189
+ children: [
10190
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
10191
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
10192
+ ]
10193
+ }
10194
+ ),
10195
+ /* @__PURE__ */ jsx("h2", { children: "O código expirou" }),
10196
+ /* @__PURE__ */ jsx("p", { children: "Códigos de vinculação valem poucos minutos, por segurança. Gere um novo para tentar." }),
10197
+ /* @__PURE__ */ jsxs("div", { className: "nfid-enroll-actions", children: [
10198
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onRetry, children: "Gerar novo código" }),
10199
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
10200
+ ] }),
10201
+ /* @__PURE__ */ jsx("div", { className: "nfid-enroll-footer", children: /* @__PURE__ */ jsx(BrandFooter, {}) })
10202
+ ] });
10203
+ }
10204
+ const CONTAINER_ID = "neofaceid-device-enrollment-container";
10205
+ function classifyEnrollmentFrame(frame) {
10206
+ var _a2, _b, _c;
10207
+ switch (frame.status) {
10208
+ case "enrolled":
10209
+ return {
10210
+ kind: "resolve",
10211
+ value: {
10212
+ status: "enrolled",
10213
+ // Vem `null` enquanto a PUSH-133 não sai. Propagamos o nulo em vez de
10214
+ // inventar um rótulo: a tela precisa poder dizer que não identificou.
10215
+ deviceLabel: ((_a2 = frame.data) == null ? void 0 : _a2.device_label) ?? null,
10216
+ enrolledAt: (_b = frame.data) == null ? void 0 : _b.enrolled_at
10217
+ }
10218
+ };
10219
+ case "expired":
10220
+ return { kind: "resolve", value: { status: "expired" } };
10221
+ case "error":
10222
+ case "failed":
10223
+ return {
10224
+ kind: "reject",
10225
+ error: new NeoFaceError(
10226
+ frame.message ?? ((_c = frame.data) == null ? void 0 : _c.message) ?? "Falha ao inscrever o aparelho",
10227
+ ErrorType.API_ERROR
10228
+ )
10229
+ };
10230
+ default:
10231
+ return { kind: "pending" };
10232
+ }
10233
+ }
10234
+ function DeviceEnrollmentModal({
10235
+ userAccessToken,
10236
+ applicationToken,
10237
+ appName,
10238
+ attempt,
10239
+ onSettle,
10240
+ onRetry,
10241
+ onFatal
10242
+ }) {
10243
+ const headerAppName = appName ?? getAppName() ?? "sua aplicação";
10244
+ const [phase, setPhase] = useState({ kind: "loading" });
10245
+ const [remaining, setRemaining] = useState(0);
10246
+ useEffect(() => {
10247
+ const controller = new AbortController();
10248
+ let done = false;
10249
+ (async () => {
10250
+ let issued;
10251
+ try {
10252
+ issued = await requestDeviceEnrollmentToken(userAccessToken, applicationToken);
10253
+ } catch (err) {
10254
+ if (!done) onFatal(err);
10255
+ return;
10256
+ }
10257
+ if (done) return;
10258
+ setPhase({
10259
+ kind: "waiting",
10260
+ handle: issued.handle,
10261
+ jti: issued.jti,
10262
+ expiresIn: issued.expiresIn
10263
+ });
10264
+ setRemaining(issued.expiresIn);
10265
+ const base = getBaseUrl();
10266
+ const channel = `${base}/api/v1/devices/enrollment/${encodeURIComponent(issued.jti)}/events/`;
10267
+ try {
10268
+ const outcome = await consumeSseStream({
10269
+ signal: controller.signal,
10270
+ timeoutMs: (issued.expiresIn + 30) * 1e3,
10271
+ requestTicket: async () => {
10272
+ var _a2;
10273
+ const res = await fetch(`${channel}ticket/`, {
10274
+ method: "POST",
10275
+ headers: { "Content-Type": "application/json", "X-App-Token": applicationToken }
10276
+ });
10277
+ if (!res.ok) {
10278
+ throw new NeoFaceError(
10279
+ `Falha ao abrir o canal de inscrição: ${res.status}`,
10280
+ res.status === 401 || res.status === 403 ? ErrorType.INVALID_TOKEN : ErrorType.NETWORK
10281
+ );
10282
+ }
10283
+ const body = await res.json();
10284
+ const ticket = ((_a2 = body == null ? void 0 : body.data) == null ? void 0 : _a2.ticket) ?? (body == null ? void 0 : body.ticket);
10285
+ if (!ticket) {
10286
+ throw new NeoFaceError("Canal de inscrição sem ticket", ErrorType.API_ERROR);
10287
+ }
10288
+ return ticket;
10289
+ },
10290
+ buildUrl: (ticket) => `${channel}?ticket=${encodeURIComponent(ticket)}`,
10291
+ classify: classifyEnrollmentFrame
10292
+ });
10293
+ if (done) return;
10294
+ if (outcome.status === "enrolled") {
10295
+ setPhase({
10296
+ kind: "enrolled",
10297
+ deviceLabel: outcome.deviceLabel,
10298
+ enrolledAt: outcome.enrolledAt
10299
+ });
10300
+ return;
10301
+ }
10302
+ setPhase({ kind: "expired" });
10303
+ } catch {
10304
+ if (done || controller.signal.aborted) return;
10305
+ setPhase({ kind: "expired" });
10306
+ }
10307
+ })();
10308
+ return () => {
10309
+ done = true;
10310
+ controller.abort();
10311
+ };
10312
+ }, [attempt]);
10313
+ useEffect(() => {
10314
+ if (phase.kind !== "waiting") return void 0;
10315
+ const id = setInterval(() => setRemaining((r) => r > 0 ? r - 1 : 0), 1e3);
10316
+ return () => clearInterval(id);
10317
+ }, [phase.kind]);
10318
+ if (phase.kind === "loading") return null;
10319
+ if (phase.kind === "enrolled") {
10320
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Aparelho vinculado", children: /* @__PURE__ */ jsx(
10321
+ DeviceEnrolledView,
10322
+ {
10323
+ deviceLabel: phase.deviceLabel,
10324
+ onDone: () => onSettle({
10325
+ status: "enrolled",
10326
+ deviceLabel: phase.deviceLabel,
10327
+ enrolledAt: phase.enrolledAt
10328
+ })
10329
+ }
10330
+ ) });
10331
+ }
10332
+ if (phase.kind === "expired") {
10333
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Código expirado", children: /* @__PURE__ */ jsx(
10334
+ DeviceEnrollmentExpiredView,
10335
+ {
10336
+ onRetry,
10337
+ onCancel: () => onSettle({ status: "expired" })
10338
+ }
10339
+ ) });
10340
+ }
10341
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Vincular aparelho", children: /* @__PURE__ */ jsx(
10342
+ DeviceEnrollmentView,
10343
+ {
10344
+ appName: headerAppName,
10345
+ handle: phase.handle,
10346
+ remainingSeconds: remaining,
10347
+ onCancel: () => onSettle({ status: "cancelled" })
10348
+ }
10349
+ ) });
10350
+ }
10351
+ function startDeviceEnrollment(options) {
10352
+ return new Promise((resolve, reject) => {
10353
+ const container = document.createElement("div");
10354
+ container.id = CONTAINER_ID;
10355
+ document.body.appendChild(container);
10356
+ const root = createRoot(container);
10357
+ let attempt = 0;
10358
+ const cleanup = () => {
10359
+ root.unmount();
10360
+ if (document.body.contains(container)) document.body.removeChild(container);
10361
+ };
10362
+ const render = () => {
10363
+ root.render(
10364
+ /* @__PURE__ */ jsx(
10365
+ DeviceEnrollmentModal,
10366
+ {
10367
+ ...options,
10368
+ attempt,
10369
+ onSettle: (outcome) => {
10370
+ cleanup();
10371
+ resolve(outcome);
10372
+ },
10373
+ onRetry: () => {
10374
+ attempt += 1;
10375
+ render();
10376
+ },
10377
+ onFatal: (error) => {
10378
+ cleanup();
10379
+ reject(error);
10380
+ }
10381
+ }
10382
+ )
10383
+ );
10384
+ };
10385
+ render();
10386
+ });
10387
+ }
9917
10388
  function evaluateFacePosition(box, videoWidth, videoHeight) {
9918
10389
  if (!box || !videoWidth || !videoHeight) {
9919
10390
  return {
@@ -15430,6 +15901,7 @@ export {
15430
15901
  start,
15431
15902
  startAutoLogin,
15432
15903
  startBiometricRegistration,
15904
+ startDeviceEnrollment,
15433
15905
  startDocumentCapture,
15434
15906
  startFaceLogin,
15435
15907
  startHandLogin,