@neofaceid/web-sdk 2.1.2 → 2.1.3
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.
- package/README.md +23 -1
- package/dist/index.d.ts +44 -3
- package/dist/neoface-id-sdk.es.js +88 -7
- package/dist/neoface-id-sdk.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,19 @@ vai consumi-lo:
|
|
|
63
63
|
|
|
64
64
|
Sem esses arquivos em `/models`, qualquer fluxo que dependa de detecção
|
|
65
65
|
facial local (blink detection do liveness, `biometricDetection`) falha ao
|
|
66
|
-
carregar o modelo.
|
|
66
|
+
carregar o modelo. Se o manifest não for encontrado (404), o console
|
|
67
|
+
mostra um `console.error` acionável, com os 4 arquivos e o destino:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
[NeoFace ID SDK] Modelos de detecção facial não encontrados em /models/.
|
|
71
|
+
Copie os 4 arquivos abaixo para public/models/ (ou equivalente do seu bundler):
|
|
72
|
+
- tiny_face_detector_model-weights_manifest.json
|
|
73
|
+
- tiny_face_detector_model-shard1
|
|
74
|
+
- face_landmark_68_model-weights_manifest.json
|
|
75
|
+
- face_landmark_68_model-shard1
|
|
76
|
+
Fonte: https://github.com/vladmandic/face-api/tree/master/model
|
|
77
|
+
Sem esses arquivos em /models, o fluxo de detecção facial falhará silenciosamente.
|
|
78
|
+
```
|
|
67
79
|
3. **Ícone/logo do SDK não precisa de asset externo.** Desde a 1.29.0 a marca
|
|
68
80
|
é renderizada via SVG inline (`src/assets/brand.ts`) — não há mais PNG
|
|
69
81
|
para hospedar.
|
|
@@ -158,6 +170,7 @@ await startFaceLogin({
|
|
|
158
170
|
appName: 'Minha Aplicação',
|
|
159
171
|
onSuccess: result => console.log('Login ok:', result),
|
|
160
172
|
onError: err => console.error(err.type, err.getFriendlyMessage()),
|
|
173
|
+
onAttemptError: (err, attempt) => analytics.track('biometric_attempt_failed', { attempt, type: err.type }),
|
|
161
174
|
onCancel: () => console.log('Usuário cancelou'),
|
|
162
175
|
onFallbackRequest: () => {
|
|
163
176
|
/* opcional — default já abre EmailPasswordModal */
|
|
@@ -165,6 +178,15 @@ await startFaceLogin({
|
|
|
165
178
|
});
|
|
166
179
|
```
|
|
167
180
|
|
|
181
|
+
> `onError` × `onAttemptError` (`BiometricLoginOptions`, NEO-563): `onError` é
|
|
182
|
+
> chamado quando o fluxo guiado **termina** — usuário cancelou, escolheu
|
|
183
|
+
> email/senha, ou erro fora do loop de tentativas (ex.: `CONSENT_DENIED`
|
|
184
|
+
> antes da câmera abrir). Já `onAttemptError` (opcional) é chamado a cada
|
|
185
|
+
> tentativa individual que falha **dentro** do loop (401, 429, rosto não
|
|
186
|
+
> detectado, etc.), antes de o `FallbackPrompt` interno aparecer — útil para
|
|
187
|
+
> registrar cada falha no seu próprio sistema de monitoramento sem esperar o
|
|
188
|
+
> fluxo encerrar.
|
|
189
|
+
|
|
168
190
|
### Login por mão / automático (`startHandLogin`, `startAutoLogin`)
|
|
169
191
|
|
|
170
192
|
Mesma assinatura de `startFaceLogin` (`BiometricLoginOptions`); `startAutoLogin`
|
package/dist/index.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export declare interface AuthorizationResult {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* authorize — autoriza uma operação por biometria, orquestrando
|
|
67
67
|
* consent + captura + desafio de vivacidade (via runLivenessChallenge, NEO-408).
|
|
68
68
|
*
|
|
69
69
|
* O servidor sorteia a sequência de gestos, valida cada frame enviado e decide
|
|
@@ -199,7 +199,37 @@ export declare interface BiometricLoginOptions {
|
|
|
199
199
|
*/
|
|
200
200
|
appName?: string;
|
|
201
201
|
onSuccess: (result: BiometricLoginResult) => void;
|
|
202
|
+
/**
|
|
203
|
+
* Chamado quando o fluxo guiado **termina** — ou seja, quando o usuário
|
|
204
|
+
* cancela, escolhe entrar por email/senha, ou ocorre um erro fora do loop
|
|
205
|
+
* de tentativas (ex.: `CONSENT_DENIED` antes da câmera abrir).
|
|
206
|
+
*
|
|
207
|
+
* Nas falhas de reconhecimento que acontecem **dentro** do loop de
|
|
208
|
+
* tentativas (401, 429, rosto não detectado, etc.), o SDK exibe o
|
|
209
|
+
* `FallbackPrompt` internamente antes de propagar ao host. Use
|
|
210
|
+
* `onAttemptError` para ser notificado dessas falhas individuais.
|
|
211
|
+
*/
|
|
202
212
|
onError: (error: NeoFaceError) => void;
|
|
213
|
+
/**
|
|
214
|
+
* NEO-563 — Chamado a cada tentativa de login que falha, **antes** de o
|
|
215
|
+
* `FallbackPrompt` aparecer. Permite ao integrador registrar falhas
|
|
216
|
+
* individuais no próprio sistema de monitoramento.
|
|
217
|
+
*
|
|
218
|
+
* @param error O erro da tentativa que falhou.
|
|
219
|
+
* @param attempt Número da tentativa (começa em 1).
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* startFaceLogin({
|
|
224
|
+
* applicationToken,
|
|
225
|
+
* onSuccess: r => console.log('ok', r),
|
|
226
|
+
* onError: err => console.error('fluxo encerrado', err),
|
|
227
|
+
* onAttemptError: (err, attempt) =>
|
|
228
|
+
* analytics.track('biometric_attempt_failed', { attempt, type: err.type }),
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
onAttemptError?: (error: NeoFaceError, attempt: number) => void;
|
|
203
233
|
onCancel?: () => void;
|
|
204
234
|
onFallbackRequest?: () => void;
|
|
205
235
|
countdown?: number;
|
|
@@ -981,6 +1011,11 @@ export declare const loginWithEmail: (email: string, password: string, applicati
|
|
|
981
1011
|
*/
|
|
982
1012
|
export declare class NeoFaceError extends Error {
|
|
983
1013
|
type: ErrorType;
|
|
1014
|
+
/**
|
|
1015
|
+
* NEO-562 · Presente quando o erro veio de um HTTP 429 (rate limit) — tempo
|
|
1016
|
+
* em ms sugerido pelo `Retry-After` da API antes de uma nova tentativa.
|
|
1017
|
+
*/
|
|
1018
|
+
retryAfterMs?: number;
|
|
984
1019
|
/**
|
|
985
1020
|
* Constrói um erro do SDK com mensagem e tipo categórico.
|
|
986
1021
|
* @param message Mensagem descritiva do erro (pode ser técnica)
|
|
@@ -1058,7 +1093,13 @@ export declare interface PersonalDataItem {
|
|
|
1058
1093
|
}
|
|
1059
1094
|
|
|
1060
1095
|
/**
|
|
1061
|
-
* Pré-carrega modelos
|
|
1096
|
+
* Pré-carrega modelos de detecção facial (chame no início da aplicação para
|
|
1097
|
+
* evitar o delay do primeiro `loadFromUri` durante a interação do usuário).
|
|
1098
|
+
*
|
|
1099
|
+
* Os modelos não são publicados no bundle do SDK — precisam ser auto-hospedados
|
|
1100
|
+
* pelo consumer em `/models/` (relativo à origem do app). Se os arquivos não
|
|
1101
|
+
* forem encontrados, um `console.error` acionável é emitido indicando o que
|
|
1102
|
+
* copiar e para onde.
|
|
1062
1103
|
*/
|
|
1063
1104
|
export declare function preloadFaceDetectionModels(): Promise<void>;
|
|
1064
1105
|
|
|
@@ -1866,7 +1907,7 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
|
|
|
1866
1907
|
* MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
|
|
1867
1908
|
* PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
|
|
1868
1909
|
*/
|
|
1869
|
-
export declare const VERSION = "2.1.
|
|
1910
|
+
export declare const VERSION = "2.1.3";
|
|
1870
1911
|
|
|
1871
1912
|
/**
|
|
1872
1913
|
* Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
|
|
@@ -2614,6 +2614,11 @@ class NeoFaceError extends Error {
|
|
|
2614
2614
|
constructor(message, type) {
|
|
2615
2615
|
super(message);
|
|
2616
2616
|
__publicField(this, "type");
|
|
2617
|
+
/**
|
|
2618
|
+
* NEO-562 · Presente quando o erro veio de um HTTP 429 (rate limit) — tempo
|
|
2619
|
+
* em ms sugerido pelo `Retry-After` da API antes de uma nova tentativa.
|
|
2620
|
+
*/
|
|
2621
|
+
__publicField(this, "retryAfterMs");
|
|
2617
2622
|
this.name = type;
|
|
2618
2623
|
this.type = type;
|
|
2619
2624
|
}
|
|
@@ -2645,6 +2650,7 @@ class NeoFaceError extends Error {
|
|
|
2645
2650
|
case "GestureNotDetectedError":
|
|
2646
2651
|
return "Não detectamos o polegar para cima. Mostre o gesto claramente para a câmera e tente novamente.";
|
|
2647
2652
|
case "ApiError":
|
|
2653
|
+
if (this.retryAfterMs !== void 0) return this.message;
|
|
2648
2654
|
if (this.message.includes("400")) return "Requisição inválida. Tente novamente.";
|
|
2649
2655
|
if (this.message.includes("500"))
|
|
2650
2656
|
return "Ocorreu um erro interno no sistema. Tente mais tarde.";
|
|
@@ -7595,6 +7601,28 @@ const loginWithBiometric = async (image, applicationToken) => {
|
|
|
7595
7601
|
if (response.status === 401 || response.status === 403) {
|
|
7596
7602
|
throw new NeoFaceError("Invalid application token", ErrorType.INVALID_TOKEN);
|
|
7597
7603
|
}
|
|
7604
|
+
if (response.status === 429) {
|
|
7605
|
+
const raw = response.headers.get("Retry-After");
|
|
7606
|
+
let retryAfterMs = 6e4;
|
|
7607
|
+
if (raw) {
|
|
7608
|
+
const seconds = Number(raw);
|
|
7609
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
7610
|
+
retryAfterMs = seconds * 1e3;
|
|
7611
|
+
} else {
|
|
7612
|
+
const date2 = Date.parse(raw);
|
|
7613
|
+
if (Number.isFinite(date2)) {
|
|
7614
|
+
const delta = date2 - Date.now();
|
|
7615
|
+
if (delta > 0) retryAfterMs = delta;
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7619
|
+
const err = new NeoFaceError(
|
|
7620
|
+
`Muitas tentativas. Aguarde ${Math.ceil(retryAfterMs / 1e3)} segundo(s) antes de tentar novamente.`,
|
|
7621
|
+
ErrorType.API_ERROR
|
|
7622
|
+
);
|
|
7623
|
+
err.retryAfterMs = retryAfterMs;
|
|
7624
|
+
throw err;
|
|
7625
|
+
}
|
|
7598
7626
|
if (response.status >= 500) {
|
|
7599
7627
|
throw new NeoFaceError(`Server Error ${response.status}`, ErrorType.API_ERROR);
|
|
7600
7628
|
}
|
|
@@ -11483,7 +11511,7 @@ function BiometricRegistrationModal({
|
|
|
11483
11511
|
/* @__PURE__ */ jsx("canvas", { ref: canvasRef })
|
|
11484
11512
|
] });
|
|
11485
11513
|
}
|
|
11486
|
-
const VERSION = "2.1.
|
|
11514
|
+
const VERSION = "2.1.3";
|
|
11487
11515
|
const RELEASE_DATE = "2026-09-23";
|
|
11488
11516
|
const CONSENT_TRAIL_STORAGE_KEY = "neoface_consent_trail_v1";
|
|
11489
11517
|
const CONSENT_TRAIL_MAX_LIMIT = 100;
|
|
@@ -11919,7 +11947,7 @@ function requestConsent(info = {}) {
|
|
|
11919
11947
|
const appName = info.appName ?? globalAppName ?? DEFAULT_APP_NAME;
|
|
11920
11948
|
if (!info.appName && !globalAppName) {
|
|
11921
11949
|
console.warn(
|
|
11922
|
-
'[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via
|
|
11950
|
+
'[NeoFaceID SDK] requestConsent sem appName — usando fallback "sua aplicação". Passe via init({ appName }) ou opts do caller.'
|
|
11923
11951
|
);
|
|
11924
11952
|
}
|
|
11925
11953
|
const flow = info.flow ?? "verification";
|
|
@@ -13544,6 +13572,7 @@ class BiometricStatusOverlay {
|
|
|
13544
13572
|
}
|
|
13545
13573
|
}
|
|
13546
13574
|
}
|
|
13575
|
+
const MIN_RETRY_COOLDOWN_MS = 3e3;
|
|
13547
13576
|
function FallbackPromptComponent({
|
|
13548
13577
|
error,
|
|
13549
13578
|
onRetry,
|
|
@@ -13551,10 +13580,48 @@ function FallbackPromptComponent({
|
|
|
13551
13580
|
onCancel,
|
|
13552
13581
|
applicationToken
|
|
13553
13582
|
}) {
|
|
13583
|
+
const [cooldownSec, setCooldownSec] = useState(0);
|
|
13584
|
+
const [retrying, setRetrying] = useState(false);
|
|
13585
|
+
const cooldownIntervalRef = useRef(null);
|
|
13586
|
+
const stopCooldown = useCallback(() => {
|
|
13587
|
+
if (cooldownIntervalRef.current) {
|
|
13588
|
+
clearInterval(cooldownIntervalRef.current);
|
|
13589
|
+
cooldownIntervalRef.current = null;
|
|
13590
|
+
}
|
|
13591
|
+
}, []);
|
|
13592
|
+
useEffect(() => stopCooldown, [stopCooldown]);
|
|
13593
|
+
const handleRetry = useCallback(() => {
|
|
13594
|
+
if (retrying) return;
|
|
13595
|
+
setRetrying(true);
|
|
13596
|
+
const waitMs = Math.max(
|
|
13597
|
+
(error == null ? void 0 : error.retryAfterMs) ?? 0,
|
|
13598
|
+
MIN_RETRY_COOLDOWN_MS
|
|
13599
|
+
);
|
|
13600
|
+
const totalSec = Math.ceil(waitMs / 1e3);
|
|
13601
|
+
setCooldownSec(totalSec);
|
|
13602
|
+
let remaining = totalSec;
|
|
13603
|
+
cooldownIntervalRef.current = setInterval(() => {
|
|
13604
|
+
remaining -= 1;
|
|
13605
|
+
setCooldownSec(remaining);
|
|
13606
|
+
if (remaining <= 0) {
|
|
13607
|
+
stopCooldown();
|
|
13608
|
+
setRetrying(false);
|
|
13609
|
+
onRetry();
|
|
13610
|
+
}
|
|
13611
|
+
}, 1e3);
|
|
13612
|
+
}, [retrying, error, onRetry, stopCooldown]);
|
|
13554
13613
|
const handleForgotPassword = () => {
|
|
13555
13614
|
const modal = new ForgotPasswordModal(applicationToken);
|
|
13556
13615
|
modal.open();
|
|
13557
13616
|
};
|
|
13617
|
+
const handleCredentials = useCallback(() => {
|
|
13618
|
+
stopCooldown();
|
|
13619
|
+
onCredentials();
|
|
13620
|
+
}, [stopCooldown, onCredentials]);
|
|
13621
|
+
const handleCancel = useCallback(() => {
|
|
13622
|
+
stopCooldown();
|
|
13623
|
+
onCancel == null ? void 0 : onCancel();
|
|
13624
|
+
}, [stopCooldown, onCancel]);
|
|
13558
13625
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13559
13626
|
/* @__PURE__ */ jsx("style", { children: `
|
|
13560
13627
|
@keyframes slideUp {
|
|
@@ -13650,6 +13717,10 @@ function FallbackPromptComponent({
|
|
|
13650
13717
|
outline: 2px solid var(--nfid-focus-ring);
|
|
13651
13718
|
outline-offset: 2px;
|
|
13652
13719
|
}
|
|
13720
|
+
.neofaceid-fallback-button-primary:disabled {
|
|
13721
|
+
opacity: 0.55;
|
|
13722
|
+
cursor: not-allowed;
|
|
13723
|
+
}
|
|
13653
13724
|
|
|
13654
13725
|
.neofaceid-fallback-button-secondary {
|
|
13655
13726
|
background: var(--nfid-surface-muted);
|
|
@@ -13709,8 +13780,10 @@ function FallbackPromptComponent({
|
|
|
13709
13780
|
{
|
|
13710
13781
|
type: "button",
|
|
13711
13782
|
className: "neofaceid-fallback-button neofaceid-fallback-button-primary",
|
|
13712
|
-
onClick:
|
|
13713
|
-
|
|
13783
|
+
onClick: handleRetry,
|
|
13784
|
+
disabled: retrying,
|
|
13785
|
+
"aria-disabled": retrying,
|
|
13786
|
+
children: retrying ? `Aguarde ${cooldownSec}s...` : "Tentar Novamente"
|
|
13714
13787
|
}
|
|
13715
13788
|
),
|
|
13716
13789
|
/* @__PURE__ */ jsx(
|
|
@@ -13718,7 +13791,7 @@ function FallbackPromptComponent({
|
|
|
13718
13791
|
{
|
|
13719
13792
|
type: "button",
|
|
13720
13793
|
className: "neofaceid-fallback-button neofaceid-fallback-button-secondary",
|
|
13721
|
-
onClick:
|
|
13794
|
+
onClick: handleCredentials,
|
|
13722
13795
|
children: "Entrar com Email e Senha"
|
|
13723
13796
|
}
|
|
13724
13797
|
),
|
|
@@ -13736,7 +13809,7 @@ function FallbackPromptComponent({
|
|
|
13736
13809
|
{
|
|
13737
13810
|
type: "button",
|
|
13738
13811
|
className: "neofaceid-fallback-button neofaceid-fallback-button-ghost",
|
|
13739
|
-
onClick:
|
|
13812
|
+
onClick: handleCancel,
|
|
13740
13813
|
children: "Cancelar"
|
|
13741
13814
|
}
|
|
13742
13815
|
)
|
|
@@ -14120,7 +14193,10 @@ async function preloadFaceDetectionModels() {
|
|
|
14120
14193
|
modelsLoaded = true;
|
|
14121
14194
|
console.log("✅ Face detection models preloaded");
|
|
14122
14195
|
} catch (error) {
|
|
14123
|
-
console.
|
|
14196
|
+
console.error(
|
|
14197
|
+
"[NeoFace ID SDK] Modelos de detecção facial não encontrados em /models/.\nCopie os 4 arquivos abaixo para public/models/ (ou equivalente do seu bundler):\n - tiny_face_detector_model-weights_manifest.json\n - tiny_face_detector_model-shard1\n - face_landmark_68_model-weights_manifest.json\n - face_landmark_68_model-shard1\nFonte: https://github.com/vladmandic/face-api/tree/master/model\nSem esses arquivos em /models, o fluxo de detecção facial falhará silenciosamente.",
|
|
14198
|
+
error
|
|
14199
|
+
);
|
|
14124
14200
|
}
|
|
14125
14201
|
})();
|
|
14126
14202
|
return modelsLoading;
|
|
@@ -14487,6 +14563,7 @@ async function executeBiometricLoginFlow(options) {
|
|
|
14487
14563
|
applicationToken,
|
|
14488
14564
|
onSuccess,
|
|
14489
14565
|
onError,
|
|
14566
|
+
onAttemptError,
|
|
14490
14567
|
onFallbackRequest,
|
|
14491
14568
|
onCancel,
|
|
14492
14569
|
fastMode = false
|
|
@@ -14508,6 +14585,9 @@ async function executeBiometricLoginFlow(options) {
|
|
|
14508
14585
|
error instanceof Error ? error.message : "Erro desconhecido",
|
|
14509
14586
|
ErrorType.UNKNOWN
|
|
14510
14587
|
);
|
|
14588
|
+
if (onAttemptError) {
|
|
14589
|
+
onAttemptError(lastError, attempts);
|
|
14590
|
+
}
|
|
14511
14591
|
if (attempts < MAX_ATTEMPTS$1) {
|
|
14512
14592
|
setTimeout(() => {
|
|
14513
14593
|
tryLogin();
|
|
@@ -15179,6 +15259,7 @@ async function startFaceLogin(options) {
|
|
|
15179
15259
|
applicationToken: options.applicationToken,
|
|
15180
15260
|
onSuccess: options.onSuccess,
|
|
15181
15261
|
onError: options.onError,
|
|
15262
|
+
onAttemptError: options.onAttemptError,
|
|
15182
15263
|
onFallbackRequest: options.onFallbackRequest || (() => openPasswordFallback(options)),
|
|
15183
15264
|
onCancel: options.onCancel
|
|
15184
15265
|
});
|