@neofaceid/web-sdk 1.44.0 → 2.0.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ Veja o [`CHANGELOG.md`](CHANGELOG.md) para o histórico de versões.
20
20
  - [Onboarding completo (`startOnboarding`)](#onboarding-completo-startonboarding)
21
21
  - [Captura de documento (`startDocumentCapture`)](#captura-de-documento-startdocumentcapture)
22
22
  - [Autorização por biometria (`authorize` / `authorizeOperation`)](#autorização-por-biometria-authorize--authorizeoperation)
23
- - [Classe `NeoFaceID` (`proofOfLife` e afins)](#classe-neofaceid-proofoflife-e-afins)
23
+ - [Prova de vida, captura de frames e operações avulsas](#prova-de-vida-captura-de-frames-e-operações-avulsas)
24
24
  - [Tratamento de erros](#tratamento-de-erros)
25
25
  - [Componentes React exportados](#componentes-react-exportados)
26
26
  - [Sessão de captura e desafio de liveness (baixo nível)](#sessão-de-captura-e-desafio-de-liveness-baixo-nível)
@@ -289,44 +289,47 @@ const result = await authorizeOperation('seu-token-de-aplicacao', '12345678900',
289
289
  });
290
290
  ```
291
291
 
292
- ### Classe `NeoFaceID` (`proofOfLife` e afins)
292
+ ### Prova de vida, captura de frames e operações avulsas
293
293
 
294
- `NeoFaceID` reúne operações que não têm (ainda) uma função `start*`
295
- dedicada: prova de vida com gravação de vídeo, captura de múltiplos frames,
296
- login com assinatura para integrações externas, registro de aplicação e
297
- registro de documento pós-cadastro.
294
+ Operações que não abrem um fluxo `start*` completo. Até a 1.x viviam na
295
+ classe `NeoFaceID`; desde a 2.0.0 são funções, como o resto do SDK — ver a
296
+ tabela de migração no [CHANGELOG](CHANGELOG.md#200---2026-09-19--neo-244-neo-245).
298
297
 
299
298
  ```ts
300
- import { NeoFaceID } from '@neofaceid/web-sdk';
301
-
302
- const sdk = new NeoFaceID({ appToken: 'seu-token-de-aplicacao' });
299
+ import {
300
+ proofOfLife,
301
+ captureFaceFrames,
302
+ recognizeByPurpose,
303
+ registerDocumentByImage,
304
+ } from '@neofaceid/web-sdk';
303
305
 
304
- // Prova de vida: grava vídeo curto, envia para o backend, faz polling do resultado
305
- const proof = await sdk.proofOfLife({
306
+ // Prova de vida: grava vídeo curto, envia ao core e acompanha até o resultado.
307
+ // `applicationToken` pode ser omitido se já foi passado a init().
308
+ const proof = await proofOfLife({
309
+ applicationToken: 'seu-token-de-aplicacao',
306
310
  videoDurationMs: 3000,
307
311
  onRecordingProgress: progress => console.log(`Gravando: ${progress}%`),
308
312
  onTaskStatusChange: (status, progress) => console.log(status, progress),
309
313
  });
314
+ // Reprovar na prova é desfecho, não exceção: confira `success` e `isLive`.
310
315
  if (proof.success && proof.isLive) {
311
316
  console.log('Liveness score:', proof.livenessScore);
312
317
  console.log('Dados da pessoa:', proof.personalData);
313
318
  }
314
319
 
315
- // Login com assinatura (integrações externas — ex.: sistemas que já geram HMAC)
316
- const sdkExterno = new NeoFaceID({
317
- appToken: 'seu-token-de-aplicacao',
318
- signature: signatureFromBackend, // HMAC-SHA256 hex, mínimo 64 chars
319
- sessionData: { email: 'user@exemplo.com', cpf: '12345678900', sessionId: 'session-uuid' },
320
- });
321
- const frames = await sdkExterno.captureFaceFrames({ numFrames: 5, livenessCheck: true });
322
- const login = await sdkExterno.loginRecognition({
323
- biometricData: frames,
324
- typeOfIdentification: 'FACE',
325
- purpose: 'LOGIN',
326
- });
320
+ // Login com assinatura (integrações externas que já geram HMAC)
321
+ const [frame] = await captureFaceFrames({ numFrames: 1, livenessCheck: false });
322
+ const login = await recognizeByPurpose(
323
+ frame,
324
+ 'seu-token-de-aplicacao',
325
+ 'LOGIN',
326
+ 0.8, // limiar de confiança
327
+ signatureFromBackend, // HMAC-SHA256 hex, mínimo 64 caracteres
328
+ { email: 'user@exemplo.com', cpf: '12345678900', sessionId: 'session-uuid' }
329
+ );
327
330
 
328
331
  // Registro de documento para pessoa já cadastrada
329
- const docResult = await sdk.registerDocumentByImage(personId, userJwtToken);
332
+ const docResult = await registerDocumentByImage(personId, userJwtToken);
330
333
  console.log('Task ID:', docResult.taskId);
331
334
  ```
332
335
 
@@ -0,0 +1,154 @@
1
+ async function detectBiometricType(imageData) {
2
+ try {
3
+ const img = await loadImageFromBase64(imageData);
4
+ const faceResult = await detectFace(img);
5
+ if (faceResult.confidence > 0.7) {
6
+ return {
7
+ type: "face",
8
+ confidence: faceResult.confidence,
9
+ details: faceResult.details
10
+ };
11
+ }
12
+ const handResult = await detectHand(img);
13
+ if (handResult.confidence > 0.6) {
14
+ return {
15
+ type: "hand",
16
+ confidence: handResult.confidence,
17
+ details: handResult.details
18
+ };
19
+ }
20
+ return {
21
+ type: "unknown",
22
+ confidence: Math.max(faceResult.confidence, handResult.confidence)
23
+ };
24
+ } catch (error) {
25
+ console.warn("Erro na detecção biométrica:", error);
26
+ return {
27
+ type: "face",
28
+ confidence: 0.5
29
+ };
30
+ }
31
+ }
32
+ async function detectFace(img) {
33
+ try {
34
+ if (typeof window !== "undefined" && window.faceapi) {
35
+ const { faceapi } = window;
36
+ await loadFaceApiModels();
37
+ const tinyOptions = new faceapi.TinyFaceDetectorOptions({
38
+ inputSize: 320,
39
+ scoreThreshold: 0.5
40
+ });
41
+ const detections = await faceapi.detectAllFaces(img, tinyOptions).withFaceLandmarks();
42
+ if (detections && detections.length > 0) {
43
+ const bestDetection = detections.reduce(
44
+ (best, current) => current.detection.score > best.detection.score ? current : best
45
+ );
46
+ return {
47
+ confidence: bestDetection.detection.score,
48
+ details: {
49
+ faces: detections.length,
50
+ landmarks: bestDetection.landmarks,
51
+ box: bestDetection.detection.box
52
+ }
53
+ };
54
+ }
55
+ }
56
+ return { confidence: 0 };
57
+ } catch (error) {
58
+ console.warn("Erro na detecção facial:", error);
59
+ return { confidence: 0 };
60
+ }
61
+ }
62
+ async function detectHand(img) {
63
+ try {
64
+ const canvas = document.createElement("canvas");
65
+ const ctx = canvas.getContext("2d");
66
+ if (!ctx) {
67
+ return { confidence: 0 };
68
+ }
69
+ canvas.width = img.width;
70
+ canvas.height = img.height;
71
+ ctx.drawImage(img, 0, 0);
72
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
73
+ const handFeatures = analyzeHandFeatures(imageData);
74
+ return {
75
+ confidence: handFeatures.confidence,
76
+ details: handFeatures
77
+ };
78
+ } catch (error) {
79
+ console.warn("Erro na detecção de mão:", error);
80
+ return { confidence: 0 };
81
+ }
82
+ }
83
+ function analyzeHandFeatures(imageData) {
84
+ const { data, width, height } = imageData;
85
+ let skinPixels = 0;
86
+ let totalPixels = 0;
87
+ for (let i = 0; i < data.length; i += 4) {
88
+ const r = data[i];
89
+ const g = data[i + 1];
90
+ const b = data[i + 2];
91
+ if (isSkinColor(r, g, b)) {
92
+ skinPixels++;
93
+ }
94
+ totalPixels++;
95
+ }
96
+ const skinRatio = skinPixels / totalPixels;
97
+ const aspectRatio = width / height;
98
+ const isHandAspectRatio = aspectRatio > 0.6 && aspectRatio < 1.8;
99
+ let confidence = 0;
100
+ if (skinRatio > 0.15 && skinRatio < 0.7) {
101
+ confidence += 0.4 * (skinRatio / 0.7);
102
+ }
103
+ if (isHandAspectRatio) {
104
+ confidence += 0.3;
105
+ }
106
+ const imageSize = width * height;
107
+ if (imageSize > 1e4 && imageSize < 5e5) {
108
+ confidence += 0.3;
109
+ }
110
+ return {
111
+ confidence: Math.min(confidence, 0.8),
112
+ // Máximo 80% para detecção básica
113
+ skinRatio,
114
+ aspectRatio,
115
+ imageSize,
116
+ skinPixels,
117
+ totalPixels
118
+ };
119
+ }
120
+ function isSkinColor(r, g, b) {
121
+ const y = 0.299 * r + 0.587 * g + 0.114 * b;
122
+ const cb = -0.169 * r - 0.331 * g + 0.5 * b + 128;
123
+ const cr = 0.5 * r - 0.419 * g - 0.081 * b + 128;
124
+ return y > 80 && y < 255 && cb > 85 && cb < 135 && cr > 135 && cr < 180;
125
+ }
126
+ function loadImageFromBase64(base64Data) {
127
+ return new Promise((resolve, reject) => {
128
+ const img = new Image();
129
+ img.onload = () => resolve(img);
130
+ img.onerror = () => reject(new Error("Erro ao carregar imagem"));
131
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:image/jpeg;base64,${base64Data}`;
132
+ img.src = dataUrl;
133
+ });
134
+ }
135
+ async function loadFaceApiModels() {
136
+ if (typeof window === "undefined" || !window.faceapi) {
137
+ return;
138
+ }
139
+ const { faceapi } = window;
140
+ if (faceapi.nets.tinyFaceDetector.isLoaded) {
141
+ return;
142
+ }
143
+ try {
144
+ await Promise.all([
145
+ faceapi.nets.tinyFaceDetector.loadFromUri("/models"),
146
+ faceapi.nets.faceLandmark68Net.loadFromUri("/models")
147
+ ]);
148
+ } catch (error) {
149
+ console.warn("Não foi possível carregar modelos do face-api.js:", error);
150
+ }
151
+ }
152
+ export {
153
+ detectBiometricType
154
+ };