face-validator-sdk 1.1.1 → 1.2.1
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/CHANGELOG.md +13 -0
- package/README.md +119 -113
- package/dist/face-validator-sdk.cjs.js +2 -1
- package/dist/face-validator-sdk.cjs.js.LICENSE.txt +9 -0
- package/dist/face-validator-sdk.cjs.js.map +1 -1
- package/dist/face-validator-sdk.esm.js +2 -1
- package/dist/face-validator-sdk.esm.js.LICENSE.txt +9 -0
- package/dist/face-validator-sdk.esm.js.map +1 -1
- package/dist/face-validator-sdk.umd.js +2 -1
- package/dist/face-validator-sdk.umd.js.LICENSE.txt +9 -0
- package/dist/face-validator-sdk.umd.js.map +1 -1
- package/dist/types/ReactSelfieCapture.d.ts +29 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.1] – 2026-02-18
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **ReactSelfieCapture**: Fixed circular import dependency that caused `ValidationStatus` to be undefined on load.
|
|
13
|
+
- **Message throttling**: Implemented proper delay between instruction messages (1.5s minimum) to improve readability and user experience. Previous implementation was canceling timeouts preventing the delay from working correctly.
|
|
14
|
+
- **Message display logic**: Messages now stay visible for a minimum configurable time (`MESSAGE_DELAY_MS`), with critical statuses (SUCCESS, ERROR, CAPTURING, INITIALIZING) still displaying immediately.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Smooth transitions**: Added CSS transitions (`0.4s ease-in-out`) to message banner for smoother visual updates.
|
|
19
|
+
- **Import structure**: Changed ReactSelfieCapture imports to use direct module paths instead of index barrel to avoid circular dependencies.
|
|
20
|
+
|
|
8
21
|
## [1.1.0] – 2026-02-06
|
|
9
22
|
|
|
10
23
|
### Changed
|
package/README.md
CHANGED
|
@@ -26,15 +26,17 @@ Real-time selfie validation SDK with face detection, powered by **MediaPipe**. D
|
|
|
26
26
|
- 📦 **Multiple builds**: ESM, CJS, UMD
|
|
27
27
|
- 🚀 **GPU accelerated**: Powered by MediaPipe with GPU support
|
|
28
28
|
|
|
29
|
-
## 📦 Installation
|
|
29
|
+
## 📦 Installation (Core SDK)
|
|
30
|
+
|
|
31
|
+
For any web application (React, Angular, Vue, vanilla JS, Java backend with JS frontend, etc.) that wants to use the **core validator**:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
npm install face-validator-sdk
|
|
34
|
+
npm install face-validator-sdk
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
> The SDK declares `@mediapipe/tasks-vision` (^0.10.15) as a regular dependency, so it is installed automatically when you install `face-validator-sdk`.
|
|
36
38
|
|
|
37
|
-
## 🚀 Quick Start
|
|
39
|
+
## 🚀 Quick Start (Core API)
|
|
38
40
|
|
|
39
41
|
```typescript
|
|
40
42
|
import { FaceValidator, ValidationStatus } from 'face-validator-sdk';
|
|
@@ -119,35 +121,131 @@ interface FaceValidatorOptions {
|
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 🧩 React Component: `ReactSelfieCapture`
|
|
127
|
+
|
|
128
|
+
If you are building a **React** application and want a **ready‑to‑use selfie capture UI**, the SDK exposes an optional React component that encapsulates:
|
|
129
|
+
|
|
130
|
+
- Camera access (`getUserMedia`)
|
|
131
|
+
- Validation loop (`FaceValidator`)
|
|
132
|
+
- Overlay drawing (oval + feedback)
|
|
133
|
+
- Preview step (photo + buttons)
|
|
134
|
+
- i18n for pt-BR, en, es
|
|
123
135
|
|
|
124
|
-
###
|
|
125
|
-
Visit: **[https://face-validator-sdk.vercel.app](https://face-validator-sdk.vercel.app)**
|
|
136
|
+
### Installation (React project)
|
|
126
137
|
|
|
127
|
-
|
|
138
|
+
Your React app should already have `react` and `react-dom` installed. Then:
|
|
128
139
|
|
|
129
140
|
```bash
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
cd face-validator-sdk
|
|
141
|
+
npm install face-validator-sdk
|
|
142
|
+
```
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
> `@mediapipe/tasks-vision` is installed automatically as a dependency of the SDK.
|
|
145
|
+
> `react` and `react-dom` are declared as **peerDependencies** – they are **not** bundled inside the package.
|
|
146
|
+
> Non‑React applications should use the **core `FaceValidator` API** shown above instead of `ReactSelfieCapture`.
|
|
147
|
+
|
|
148
|
+
### Basic usage
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { ReactSelfieCapture } from 'face-validator-sdk';
|
|
152
|
+
|
|
153
|
+
function SelfieExample() {
|
|
154
|
+
const handleCapture = (imageBase64: string | null) => {
|
|
155
|
+
if (imageBase64) {
|
|
156
|
+
// send to API, store, etc.
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<ReactSelfieCapture
|
|
162
|
+
locale={navigator.language} // 'pt-BR' | 'en' | 'es' (auto-normalized)
|
|
163
|
+
onCapture={handleCapture}
|
|
164
|
+
onDismiss={() => console.log('Modal closed')}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
136
169
|
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
### Props
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
type SupportedLocale = 'pt-BR' | 'en' | 'es';
|
|
174
|
+
|
|
175
|
+
type SelfieCaptureStyles = {
|
|
176
|
+
container?: React.CSSProperties;
|
|
177
|
+
media?: React.CSSProperties;
|
|
178
|
+
messageBanner?: React.CSSProperties;
|
|
179
|
+
primaryButton?: React.CSSProperties;
|
|
180
|
+
secondaryButton?: React.CSSProperties;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
type SelfieCaptureUILabelOverrides = Partial<{
|
|
184
|
+
previewQuestion: string;
|
|
185
|
+
savePhoto: string;
|
|
186
|
+
tryAgain: string;
|
|
187
|
+
cancel: string;
|
|
188
|
+
}>;
|
|
189
|
+
|
|
190
|
+
interface ReactSelfieCaptureProps {
|
|
191
|
+
onCapture: (image: string | null) => void; // base64 data URL or null on cancel
|
|
192
|
+
onDismiss?: () => void;
|
|
193
|
+
|
|
194
|
+
// Behaviour
|
|
195
|
+
locale?: SupportedLocale | string; // Default: 'pt-BR' (auto-normalized)
|
|
196
|
+
videoWidth?: number; // Default: 512
|
|
197
|
+
videoHeight?: number; // Default: 384
|
|
198
|
+
debugMode?: boolean; // Default: false
|
|
199
|
+
modelPath?: string; // Optional MediaPipe WASM path; if omitted, uses internal CDN default
|
|
200
|
+
|
|
201
|
+
// Visual customization (inline styles)
|
|
202
|
+
styles?: SelfieCaptureStyles;
|
|
203
|
+
|
|
204
|
+
// Optional UI labels override (per-locale defaults exist)
|
|
205
|
+
labels?: SelfieCaptureUILabelOverrides;
|
|
206
|
+
}
|
|
139
207
|
```
|
|
140
208
|
|
|
141
|
-
###
|
|
209
|
+
### Labels and i18n
|
|
142
210
|
|
|
143
|
-
|
|
144
|
-
# Build SDK + Demo
|
|
145
|
-
npm run build
|
|
146
|
-
npm run build:demo
|
|
211
|
+
By default, the component renders UI labels in **Portuguese (pt-BR)**, **English (en)** or **Spanish (es)**:
|
|
147
212
|
|
|
148
|
-
|
|
213
|
+
- Preview question (“O que você achou?” / “What do you think?” / “¿Qué te pareció?”)
|
|
214
|
+
- Buttons (“Salvar foto”, “Tentar novamente”, “Cancelar”, etc.)
|
|
215
|
+
|
|
216
|
+
You can override any of these without having to set up an external i18n layer:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
<ReactSelfieCapture
|
|
220
|
+
locale="pt-BR"
|
|
221
|
+
onCapture={handleCapture}
|
|
222
|
+
labels={{
|
|
223
|
+
previewQuestion: 'Confira sua selfie antes de salvar',
|
|
224
|
+
savePhoto: 'Confirmar selfie',
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
149
227
|
```
|
|
150
228
|
|
|
229
|
+
### Styling
|
|
230
|
+
|
|
231
|
+
The component ships with a sensible default layout, but you can tweak it via the `styles` prop:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
<ReactSelfieCapture
|
|
235
|
+
onCapture={handleCapture}
|
|
236
|
+
styles={{
|
|
237
|
+
container: { borderRadius: 24 },
|
|
238
|
+
media: { borderRadius: 16 },
|
|
239
|
+
messageBanner: { backgroundColor: '#f0f9ff', color: '#0369a1' },
|
|
240
|
+
primaryButton: { backgroundColor: '#2563eb', borderColor: '#2563eb' },
|
|
241
|
+
secondaryButton: { borderRadius: 9999 },
|
|
242
|
+
}}
|
|
243
|
+
/>;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
> Tip: you can wrap `ReactSelfieCapture` in your own modal/dialog and pass `onDismiss` to close it from the **Cancel** button.
|
|
247
|
+
|
|
248
|
+
|
|
151
249
|
## 🏗️ Architecture
|
|
152
250
|
|
|
153
251
|
### MediaPipe Integration
|
|
@@ -157,28 +255,6 @@ The SDK uses two MediaPipe models running in parallel:
|
|
|
157
255
|
1. **FaceLandmarker**: 478 facial landmarks + face detection
|
|
158
256
|
2. **HandLandmarker**: 21 hand landmarks per hand
|
|
159
257
|
|
|
160
|
-
```
|
|
161
|
-
┌─────────────────────────────────────────┐
|
|
162
|
-
│ FaceValidator │
|
|
163
|
-
├─────────────────────────────────────────┤
|
|
164
|
-
│ ┌─────────────────┐ ┌──────────────┐ │
|
|
165
|
-
│ │ FaceLandmarker │ │ HandLandmarker│ │
|
|
166
|
-
│ │ (478 points) │ │ (21 pts/hand) │ │
|
|
167
|
-
│ └─────────────────┘ └──────────────┘ │
|
|
168
|
-
│ ↓ ↓ │
|
|
169
|
-
│ ┌──────────────────────────────────┐ │
|
|
170
|
-
│ │ Validation Pipeline │ │
|
|
171
|
-
│ │ 1. Distance │ │
|
|
172
|
-
│ │ 2. Centering │ │
|
|
173
|
-
│ │ 3. Face geometry │ │
|
|
174
|
-
│ │ 4. Head pose │ │
|
|
175
|
-
│ │ 5. Hand proximity ⭐NEW │ │
|
|
176
|
-
│ │ 6. Illumination │ │
|
|
177
|
-
│ │ 7. Stability │ │
|
|
178
|
-
│ └──────────────────────────────────┘ │
|
|
179
|
-
└─────────────────────────────────────────┘
|
|
180
|
-
```
|
|
181
|
-
|
|
182
258
|
## 📚 Why MediaPipe?
|
|
183
259
|
|
|
184
260
|
Migrated from face-api.js (discontinued 2021) to MediaPipe (Google):
|
|
@@ -192,64 +268,6 @@ Migrated from face-api.js (discontinued 2021) to MediaPipe (Google):
|
|
|
192
268
|
| Accuracy | ~60-70% | ✅ **~90-95%** |
|
|
193
269
|
| Model size | ~8MB | ~15MB |
|
|
194
270
|
|
|
195
|
-
## 🔧 Development
|
|
196
|
-
|
|
197
|
-
### Scripts
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
npm run dev # Start local dev server (webpack)
|
|
201
|
-
npm run build # Build SDK (CJS, ESM, UMD)
|
|
202
|
-
npm run build:demo # Build production demo
|
|
203
|
-
npm run lint # Run ESLint
|
|
204
|
-
npm run format # Format code with Prettier
|
|
205
|
-
npm run test # Run tests (Jest)
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Project Structure
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
face-validator-sdk/
|
|
212
|
-
├── src/
|
|
213
|
-
│ ├── FaceValidator.ts # Main validator class
|
|
214
|
-
│ ├── types.ts # TypeScript types
|
|
215
|
-
│ ├── utils.ts # Validation functions
|
|
216
|
-
│ ├── i18n.ts # Internationalization
|
|
217
|
-
│ └── index.ts # Public API exports
|
|
218
|
-
├── demo/
|
|
219
|
-
│ ├── demo.ts # Local dev demo
|
|
220
|
-
│ ├── demo-standalone.ts # Production demo
|
|
221
|
-
│ └── public/
|
|
222
|
-
│ └── index.html # Demo HTML
|
|
223
|
-
├── dist/ # SDK build output
|
|
224
|
-
│ ├── face-validator-sdk.esm.js
|
|
225
|
-
│ ├── face-validator-sdk.cjs.js
|
|
226
|
-
│ ├── face-validator-sdk.umd.js
|
|
227
|
-
│ └── types/ # TypeScript declarations
|
|
228
|
-
├── .github/
|
|
229
|
-
│ └── workflows/
|
|
230
|
-
│ ├── ci.yml # CI/CD pipeline
|
|
231
|
-
│ └── deploy-vercel.yml # Vercel deployment
|
|
232
|
-
└── vercel.json # Vercel configuration
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## 🚀 Deployment
|
|
236
|
-
|
|
237
|
-
### Vercel (Automatic)
|
|
238
|
-
|
|
239
|
-
1. Connect repository to Vercel
|
|
240
|
-
2. Add secrets to GitHub:
|
|
241
|
-
- `VERCEL_TOKEN`
|
|
242
|
-
- `VERCEL_ORG_ID`
|
|
243
|
-
- `VERCEL_PROJECT_ID`
|
|
244
|
-
3. Push to `main` branch → auto-deploy
|
|
245
|
-
|
|
246
|
-
### Manual Deployment
|
|
247
|
-
|
|
248
|
-
```bash
|
|
249
|
-
npm run build:demo
|
|
250
|
-
# Deploy demo/dist/ to any static host
|
|
251
|
-
```
|
|
252
|
-
|
|
253
271
|
## 🤝 Contributing
|
|
254
272
|
|
|
255
273
|
Contributions are welcome! Please:
|
|
@@ -260,17 +278,6 @@ Contributions are welcome! Please:
|
|
|
260
278
|
4. Push to branch: `git push origin feature/amazing-feature`
|
|
261
279
|
5. Open a Pull Request
|
|
262
280
|
|
|
263
|
-
### Commit Convention
|
|
264
|
-
|
|
265
|
-
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
|
266
|
-
|
|
267
|
-
- `feat:` New feature
|
|
268
|
-
- `fix:` Bug fix
|
|
269
|
-
- `docs:` Documentation changes
|
|
270
|
-
- `chore:` Maintenance tasks
|
|
271
|
-
- `refactor:` Code refactoring
|
|
272
|
-
- `test:` Add/update tests
|
|
273
|
-
|
|
274
281
|
## 📄 License
|
|
275
282
|
|
|
276
283
|
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -288,4 +295,3 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
288
295
|
|
|
289
296
|
---
|
|
290
297
|
|
|
291
|
-
Made with ❤️ using MediaPipe
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
(()=>{"use strict";var e={d:(t,n)=>{for(var a in n)e.o(n,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:n[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{FaceValidator:()=>T,ValidationStatus:()=>a,default:()=>S,getLoadingModelsMessage:()=>l,getMessage:()=>r,getValidationMessages:()=>s});const n=require("@mediapipe/tasks-vision");var a;!function(e){e.INITIALIZING="INITIALIZING",e.NO_FACE_DETECTED="NO_FACE_DETECTED",e.FACE_DETECTED="FACE_DETECTED",e.TOO_CLOSE="TOO_CLOSE",e.TOO_FAR="TOO_FAR",e.OFF_CENTER="OFF_CENTER",e.FACE_OBSTRUCTED="FACE_OBSTRUCTED",e.HEAD_NOT_STRAIGHT="HEAD_NOT_STRAIGHT",e.MULTIPLE_FACES="MULTIPLE_FACES",e.POOR_ILLUMINATION="POOR_ILLUMINATION",e.NOT_NEUTRAL_EXPRESSION="NOT_NEUTRAL_EXPRESSION",e.DARK_GLASSES="DARK_GLASSES",e.STAY_STILL="STAY_STILL",e.CAPTURING="CAPTURING",e.SUCCESS="SUCCESS",e.ERROR="ERROR"}(a||(a={}));const i={"pt-BR":{[a.INITIALIZING]:"Inicializando câmera e detector...",[a.NO_FACE_DETECTED]:"Posicione seu rosto no centro do oval.",[a.FACE_DETECTED]:"Analisando...",[a.TOO_CLOSE]:"Afaste-se um pouco",[a.TOO_FAR]:"Aproxime-se da câmera",[a.OFF_CENTER]:"Centralize o rosto no centro do oval",[a.FACE_OBSTRUCTED]:"Mantenha o rosto totalmente visível. Remova as mãos do rosto.",[a.HEAD_NOT_STRAIGHT]:"Olhe diretamente para a câmera e mantenha a cabeça reta.",[a.MULTIPLE_FACES]:"Mantenha apenas uma pessoa no quadro.",[a.POOR_ILLUMINATION]:"Procure um ambiente com boa iluminação e centralize seu rosto no centro do oval.",[a.NOT_NEUTRAL_EXPRESSION]:"Mantenha expressão neutra: boca fechada, sem sorrir e olhos abertos.",[a.DARK_GLASSES]:"Remova os óculos escuros. Óculos de grau são permitidos.",[a.STAY_STILL]:"Fique imóvel para capturar a foto",[a.CAPTURING]:"Capturando...",[a.SUCCESS]:"Captura realizada!",[a.ERROR]:"Ocorreu um erro."},en:{[a.INITIALIZING]:"Initializing camera and detector...",[a.NO_FACE_DETECTED]:"Position your face in the center of the oval.",[a.FACE_DETECTED]:"Analyzing...",[a.TOO_CLOSE]:"Move back a little",[a.TOO_FAR]:"Move closer to the camera",[a.OFF_CENTER]:"Center your face in the center of the oval",[a.FACE_OBSTRUCTED]:"Keep your face fully visible. Remove your hands from your face.",[a.HEAD_NOT_STRAIGHT]:"Look directly at the camera and keep your head straight.",[a.MULTIPLE_FACES]:"Keep only one person in the frame.",[a.POOR_ILLUMINATION]:"Find a well-lit environment and center your face in the oval.",[a.NOT_NEUTRAL_EXPRESSION]:"Keep a neutral expression: mouth closed, no smiling, and eyes open.",[a.DARK_GLASSES]:"Remove sunglasses. Prescription glasses are allowed.",[a.STAY_STILL]:"Stay still to capture the photo",[a.CAPTURING]:"Capturing...",[a.SUCCESS]:"Capture complete!",[a.ERROR]:"An error occurred."},es:{[a.INITIALIZING]:"Inicializando cámara y detector...",[a.NO_FACE_DETECTED]:"Coloque su rostro en el centro del óvalo.",[a.FACE_DETECTED]:"Analizando...",[a.TOO_CLOSE]:"Aléjese un poco",[a.TOO_FAR]:"Acérquese a la cámara",[a.OFF_CENTER]:"Centre el rostro en el centro del óvalo",[a.FACE_OBSTRUCTED]:"Mantenga el rostro totalmente visible. Quite las manos del rostro.",[a.HEAD_NOT_STRAIGHT]:"Mire directamente a la cámara y mantenga la cabeza recta.",[a.MULTIPLE_FACES]:"Mantenga solo una persona en el encuadre.",[a.POOR_ILLUMINATION]:"Busque un ambiente con buena iluminación y centre su rostro en el óvalo.",[a.NOT_NEUTRAL_EXPRESSION]:"Mantenga expresión neutra: boca cerrada, sin sonreír y ojos abiertos.",[a.DARK_GLASSES]:"Quite las gafas de sol. Las gafas graduadas están permitidas.",[a.STAY_STILL]:"Permanezca quieto para capturar la foto",[a.CAPTURING]:"Capturando...",[a.SUCCESS]:"¡Captura realizada!",[a.ERROR]:"Ocurrió un error."}},o={"pt-BR":"Status desconhecido.",en:"Unknown status.",es:"Estado desconhecido."};function s(e){return Object.assign({},i[e])}function r(e,t){var n;return null!==(n=i[t][e])&&void 0!==n?n:o[t]}function l(e){return{"pt-BR":"Carregando...",en:"Loading...",es:"Cargando..."}[e]}function c(e){const t=e.data;let n=0;for(let e=0;e<t.length;e+=4)n+=.2126*t[e]+.7152*t[e+1]+.0722*t[e+2];return n/(t.length/4)}const h=[33,133,159,145],d=[263,362,386,374],u=[61,291,0,17,39,269,270,409],m=.34;var E=function(e,t,n,a){return new(n||(n=Promise))(function(i,o){function s(e){try{l(a.next(e))}catch(e){o(e)}}function r(e){try{l(a.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,r)}l((a=a.apply(e,t||[])).next())})};const g={overlayCanvasElement:void 0,videoWidth:512,videoHeight:384,minDetectionConfidence:.4,minIlluminationThreshold:50,minFaceSizeFactor:.15,maxFaceSizeFactor:.75,stabilizationTimeThreshold:1e3,stabilityMovementThreshold:5,minFaceVisibilityScore:.4,maxHeadTiltDegrees:30,maxHandFaceDistance:.15,debugMode:!1,locale:"en",customMessages:{}};class T{constructor(e){this.faceLandmarker=null,this.handLandmarker=null,this.animationFrameId=null,this.lastDetection=null,this.stableSince=null,this.isCapturing=!1,this.options=this.resolveOptions(e),this.setStatus(a.INITIALIZING),this.init()}resolveOptions(e){const t=e.modelPath||"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm";return Object.assign(Object.assign(Object.assign({},g),e),{modelPath:t,locale:e.locale||"en",customMessages:e.customMessages||{}})}init(){return E(this,void 0,void 0,function*(){try{const e=l(this.options.locale);this.setStatus(a.INITIALIZING,void 0,e);const t=yield n.FilesetResolver.forVisionTasks(this.options.modelPath);this.faceLandmarker=yield n.FaceLandmarker.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task",delegate:"GPU"},runningMode:"VIDEO",numFaces:2,minFaceDetectionConfidence:this.options.minDetectionConfidence,minFacePresenceConfidence:this.options.minFaceVisibilityScore,minTrackingConfidence:this.options.minFaceVisibilityScore}),this.handLandmarker=yield n.HandLandmarker.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",delegate:"GPU"},runningMode:"VIDEO",numHands:2,minHandDetectionConfidence:.5,minHandPresenceConfidence:.5,minTrackingConfidence:.5}),this.startDetectionLoop()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.setStatus(a.ERROR,t)}})}getMessageForStatus(e,t){return t||(this.options.customMessages[e]?this.options.customMessages[e]:r(e,this.options.locale))}setStatus(e,t,n){const i=this.getMessageForStatus(e,n);this.options.onStatusUpdate(e,i),e===a.ERROR&&t&&this.options.onError(e,t)}startDetectionLoop(){const e=this.options.videoElement,t=this.options.videoWidth||640,n=this.options.videoHeight||480,i=()=>E(this,void 0,void 0,function*(){var o;if(this.faceLandmarker&&this.handLandmarker&&e.videoWidth){try{const i=performance.now();let s=a.NO_FACE_DETECTED,r=null,l=[];const E=this.faceLandmarker.detectForVideo(e,i),g=this.handLandmarker.detectForVideo(e,i);if(g.landmarks&&g.landmarks.length>0&&(l=g.landmarks.map((e,t)=>{var n,a,i;return{landmarks:e,handedness:(null===(i=null===(a=null===(n=g.handednesses)||void 0===n?void 0:n[t])||void 0===a?void 0:a[0])||void 0===i?void 0:i.categoryName)||"Unknown"}})),E.faceLandmarks&&E.faceLandmarks.length>1){s=a.MULTIPLE_FACES,this.stableSince=null;const e=E.faceLandmarks[0],t=(null===(o=E.faceBlendshapes)||void 0===o?void 0:o[0])?this.estimateBoundingBox(e):null;t&&(r={boundingBox:t,landmarks:e,timestamp:i})}else if(E.faceLandmarks&&1===E.faceLandmarks.length){const o=E.faceLandmarks[0],g=this.estimateBoundingBox(o);r={boundingBox:g,landmarks:o,timestamp:i};const T=function(e,t=.18,n=.7){const a=e.width;return a<t?"TOO_FAR":a>n?"TOO_CLOSE":"OK"}(g,this.options.minFaceSizeFactor,this.options.maxFaceSizeFactor);if("OK"!==T)s="TOO_CLOSE"===T?a.TOO_CLOSE:a.TOO_FAR,this.stableSince=null;else{const E=o[4],T=function(e,t,n,a){const i=(e*n-n/2)/(.2*n),o=(t*a-a/2)/(a*m);return i*i+o*o<=1}(E.x,E.y,t,n);if(function(e,t,n){const a=t/2,i=n/2,o=.2*t,s=n*m,r=e.xMin*t,l=(e.xMin+e.width)*t,c=e.yMin*n,h=(e.yMin+e.height)*n,d=((r+l)/2-a)/o,u=((c+h)/2-i)/s;if(d*d+u*u>1)return!1;const E=[{x:r,y:c},{x:l,y:c},{x:r,y:h},{x:l,y:h}];for(const e of E){const t=(e.x-a)/o,n=(e.y-i)/s;t*t+n*n>1.2&&0}}(g,t,n),T)if(function(e,t){if(e.length<478)return!1;const n=e[4],a=u.map(t=>e[t]),i=a.reduce((e,t)=>e+t.y,0)/a.length,o=Math.min(...a.map(e=>e.y)),s=Math.max(...a.map(e=>e.y))-o,r=t.height;return!(i<n.y-.01||i-n.y<.06*r||s<.02*r)}(o,g))if(function(e,t=25){if(e.length<478)return!1;const n=e[h[0]],a=e[d[0]],i=e[4],o=e[13],s=e[14],r=e[152],l=e[10],c=Math.abs(n.y-a.y),u=Math.abs(n.x-a.x);if(u<.01)return!1;const m=c/u;if(Math.atan(m)*(180/Math.PI)>t)return!1;const E=(n.x+a.x)/2,g=i.x-E,T=Math.abs(n.x-a.x);if(T<.01)return!1;const S=Math.abs(g)/T;if(Math.atan(S)*(180/Math.PI)>t)return!1;if(!function(e){if(e.length<478)return!1;const t=e[234],n=e[454],a=e[4],i=Math.abs(t.x-a.x),o=Math.abs(n.x-a.x);return!((i>.01&&o>.01?Math.max(i,o)/Math.min(i,o):1)>1.4||void 0!==t.z&&void 0!==n.z&&Math.abs(t.z-n.z)>.05)}(e))return!1;const f=(n.y+a.y)/2,O=(o.y+s.y)/2,p=r.y-l.y;if(p<.1)return!1;if(l.y>f+.02)return!1;if(f>i.y+.02)return!1;if(i.y>O+.02)return!1;if(O>=r.y)return!1;const I=(f-l.y)/p,y=(i.y-f)/p,C=(O-i.y)/p,A=(r.y-O)/p;return!(I<.06||I>.38||y<.03||y>.3||C<.02||C>.25||A<.04||A>.38)}(o,this.options.maxHeadTiltDegrees))if(l.length>0&&function(e,t,n=.15){const a=t.xMin+t.width/2,i=t.yMin+t.height/2;for(const t of e.landmarks){const e=t.x-a,o=t.y-i;if(Math.sqrt(e*e+o*o)<n)return!0}return!1}(l[0],g,this.options.maxHandFaceDistance))s=a.FACE_OBSTRUCTED,this.stableSince=null;else if(function(e){if(e.length<478)return!1;const t=e[159],n=e[144],a=e[386],i=e[373],o=Math.abs(t.y-n.y),s=Math.abs(a.y-i.y);if(o<.01||s<.01)return!1;const r=e[13],l=e[14];if(Math.abs(r.y-l.y)>.025)return!1;const c=e[61],h=e[291],d=e[4];return!((c.y+h.y)/2-d.y<.05)}(o))if(function(e,t){if(t.length<478)return!1;try{const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)return!1;const i=e.videoWidth,o=e.videoHeight,s=[t[33],t[133],t[159],t[144],t[145]],r=[t[263],t[362],t[386],t[373],t[374]],l=e=>{const t=e.map(e=>e.x*i),n=e.map(e=>e.y*o),a=Math.max(0,Math.min(...t)-5),s=Math.min(i,Math.max(...t)+5),r=Math.max(0,Math.min(...n)-5);return{x:a,y:r,width:s-a,height:Math.min(o,Math.max(...n)+5)-r}},h=t=>(n.width=t.width,n.height=t.height,a.drawImage(e,t.x,t.y,t.width,t.height,0,0,t.width,t.height),c(a.getImageData(0,0,t.width,t.height))),d=l(s),u=l(r);return(h(d)+h(u))/2<35}catch(e){return console.warn("Erro ao detectar óculos escuros:",e),!1}}(e,o))s=a.DARK_GLASSES,this.stableSince=null;else{const o=document.createElement("canvas"),l=g.xMin*e.videoWidth,h=g.yMin*e.videoHeight,d=g.width*e.videoWidth,u=g.height*e.videoHeight;o.width=d,o.height=u;const m=o.getContext("2d",{willReadFrequently:!0});if(m){m.drawImage(e,l,h,d,u,0,0,d,u);c(m.getImageData(0,0,o.width,o.height))<this.options.minIlluminationThreshold?(s=a.POOR_ILLUMINATION,this.stableSince=null):function(e,t,n=5,a=512,i=384){if(!e||!t)return!1;const o=(e.boundingBox.xMin+e.boundingBox.width/2)*a,s=(e.boundingBox.yMin+e.boundingBox.height/2)*i,r=(t.boundingBox.xMin+t.boundingBox.width/2)*a,l=(t.boundingBox.yMin+t.boundingBox.height/2)*i,c=Math.abs(o-r),h=Math.abs(s-l),d=Math.abs(e.boundingBox.width-t.boundingBox.width)*a,u=Math.abs(e.boundingBox.height-t.boundingBox.height)*i;return c<=n&&h<=n&&d<=2*n&&u<=2*n}(r,this.lastDetection,this.options.stabilityMovementThreshold,t,n)?(this.stableSince||(this.stableSince=i),s=i-this.stableSince>=this.options.stabilizationTimeThreshold?a.CAPTURING:a.STAY_STILL):(this.stableSince=null,s=a.STAY_STILL)}else s=a.FACE_DETECTED,this.stableSince=null}else s=a.NOT_NEUTRAL_EXPRESSION,this.stableSince=null;else s=a.HEAD_NOT_STRAIGHT,this.stableSince=null;else s=a.FACE_OBSTRUCTED,this.stableSince=null;else s=a.OFF_CENTER,this.stableSince=null}}else this.lastDetection=null,this.stableSince=null;if(this.lastDetection=r,this.setStatus(s),this.options.overlayCanvasElement&&function(e,t,n,i,o){const s=e.getContext("2d");if(!s)return;const r=e.width,l=e.height,c=r/2,u=l/2;s.clearRect(0,0,r,l);const E=.2*r,g=l*m;if(s.fillStyle="rgba(255, 255, 255, 0.35)",s.fillRect(0,0,r,l),s.save(),s.beginPath(),s.ellipse(c,u,E,g,0,0,2*Math.PI),s.closePath(),s.globalCompositeOperation="destination-out",s.fill(),s.restore(),s.strokeStyle="rgba(255, 255, 255, 0.9)",s.lineWidth=3,s.beginPath(),s.ellipse(c,u,E,g,0,0,2*Math.PI),s.stroke(),s.strokeStyle="rgba(255, 255, 255, 0.45)",s.lineWidth=1,s.beginPath(),s.moveTo(c-6,u),s.lineTo(c+6,u),s.moveTo(c,u-6),s.lineTo(c,u+6),s.stroke(),t&&i){const e=i.landmarks;if(e.length>=478){const t=e[10],i=e[152],o=e[234],c=e[454],u=e.map(e=>e.x),m=e.map(e=>e.y),E=Math.min(...u),g=Math.max(...u),T=Math.min(...m),S=g-E,f=Math.max(...m)-T,O=.08,p=(E-S*O)*r,I=(T-f*O)*l,y=S*(1+2*O)*r,C=f*(1+2*O)*l;let A="red";n===a.STAY_STILL||n===a.CAPTURING?A="lime":n===a.FACE_DETECTED&&(A="yellow"),s.strokeStyle=A,s.lineWidth=3,s.strokeRect(p,I,y,C);const _=e[4];e[h[0]],e[d[0]],s.fillStyle="cyan",s.beginPath(),s.arc(_.x*r,_.y*l,5,0,2*Math.PI),s.fill(),s.fillStyle="magenta",s.beginPath(),s.arc(t.x*r,t.y*l,4,0,2*Math.PI),s.fill(),s.fillStyle="lime",s.beginPath(),s.arc(i.x*r,i.y*l,4,0,2*Math.PI),s.fill(),s.fillStyle="yellow",[e[33],e[133],e[159],e[144],e[145]].forEach(e=>{s.beginPath(),s.arc(e.x*r,e.y*l,3,0,2*Math.PI),s.fill()}),s.fillStyle="yellow",[e[263],e[362],e[386],e[373],e[374]].forEach(e=>{s.beginPath(),s.arc(e.x*r,e.y*l,3,0,2*Math.PI),s.fill()}),s.fillStyle="purple",s.beginPath(),s.arc(o.x*r,o.y*l,3,0,2*Math.PI),s.fill(),s.beginPath(),s.arc(c.x*r,c.y*l,3,0,2*Math.PI),s.fill()}}t&&o&&o.length>0&&o.forEach(e=>{s.fillStyle="orange",e.landmarks.forEach(e=>{s.beginPath(),s.arc(e.x*r,e.y*l,3,0,2*Math.PI),s.fill()})})}(this.options.overlayCanvasElement,this.options.debugMode||!1,s,r||void 0,l.length>0?l:void 0),s===a.CAPTURING&&!this.isCapturing)return this.isCapturing=!0,yield this.captureImage(),this.setStatus(a.SUCCESS),void this.stop()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.setStatus(a.ERROR,t)}this.animationFrameId=requestAnimationFrame(i)}else this.animationFrameId=requestAnimationFrame(i)});this.animationFrameId=requestAnimationFrame(i)}estimateBoundingBox(e){const t=e.map(e=>e.x),n=e.map(e=>e.y),a=Math.min(...t),i=Math.max(...t),o=Math.min(...n);return{xMin:a,yMin:o,width:i-a,height:Math.max(...n)-o}}captureImage(){return E(this,void 0,void 0,function*(){const e=this.options.videoElement,t=document.createElement("canvas");t.width=e.videoWidth,t.height=e.videoHeight;const n=t.getContext("2d");n?(n.drawImage(e,0,0,t.width,t.height),t.toBlob(e=>{e?this.options.onCaptureSuccess(e):this.setStatus(a.ERROR,new Error("Failed to generate image blob"))},"image/jpeg",.95)):this.setStatus(a.ERROR,new Error("Failed to get canvas context"))})}stop(){null!==this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.faceLandmarker&&this.faceLandmarker.close(),this.handLandmarker&&this.handLandmarker.close()}}const S=T;module.exports=t})();
|
|
1
|
+
/*! For license information please see face-validator-sdk.cjs.js.LICENSE.txt */
|
|
2
|
+
(()=>{"use strict";var e={20(e,t,n){var a=n(953),i=Symbol.for("react.element"),o=Symbol.for("react.fragment"),r=Object.prototype.hasOwnProperty,s=a.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,n){var a,o={},c=null,d=null;for(a in void 0!==n&&(c=""+n),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(d=t.ref),t)r.call(t,a)&&!l.hasOwnProperty(a)&&(o[a]=t[a]);if(e&&e.defaultProps)for(a in t=e.defaultProps)void 0===o[a]&&(o[a]=t[a]);return{$$typeof:i,type:e,key:c,ref:d,props:o,_owner:s.current}}t.Fragment=o,t.jsx=c,t.jsxs=c},848(e,t,n){e.exports=n(20)},953(e){e.exports=require("react")}},t={};function n(a){var i=t[a];if(void 0!==i)return i.exports;var o=t[a]={exports:{}};return e[a](o,o.exports,n),o.exports}n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};n.r(a),n.d(a,{FaceValidator:()=>S,ReactSelfieCapture:()=>v,ValidationStatus:()=>o,default:()=>I,getLoadingModelsMessage:()=>d,getMessage:()=>c,getValidationMessages:()=>l});const i=require("@mediapipe/tasks-vision");var o;!function(e){e.INITIALIZING="INITIALIZING",e.NO_FACE_DETECTED="NO_FACE_DETECTED",e.FACE_DETECTED="FACE_DETECTED",e.TOO_CLOSE="TOO_CLOSE",e.TOO_FAR="TOO_FAR",e.OFF_CENTER="OFF_CENTER",e.FACE_OBSTRUCTED="FACE_OBSTRUCTED",e.HEAD_NOT_STRAIGHT="HEAD_NOT_STRAIGHT",e.MULTIPLE_FACES="MULTIPLE_FACES",e.POOR_ILLUMINATION="POOR_ILLUMINATION",e.NOT_NEUTRAL_EXPRESSION="NOT_NEUTRAL_EXPRESSION",e.DARK_GLASSES="DARK_GLASSES",e.STAY_STILL="STAY_STILL",e.CAPTURING="CAPTURING",e.SUCCESS="SUCCESS",e.ERROR="ERROR"}(o||(o={}));const r={"pt-BR":{[o.INITIALIZING]:"Inicializando câmera e detector...",[o.NO_FACE_DETECTED]:"Posicione seu rosto no centro do oval.",[o.FACE_DETECTED]:"Analisando...",[o.TOO_CLOSE]:"Afaste-se um pouco",[o.TOO_FAR]:"Aproxime-se da câmera",[o.OFF_CENTER]:"Centralize o rosto no centro do oval",[o.FACE_OBSTRUCTED]:"Mantenha o rosto totalmente visível. Remova as mãos do rosto.",[o.HEAD_NOT_STRAIGHT]:"Olhe diretamente para a câmera e mantenha a cabeça reta.",[o.MULTIPLE_FACES]:"Mantenha apenas uma pessoa no quadro.",[o.POOR_ILLUMINATION]:"Procure um ambiente com boa iluminação.",[o.NOT_NEUTRAL_EXPRESSION]:"Mantenha expressão neutra: boca fechada, sem sorrir e olhos abertos.",[o.DARK_GLASSES]:"Remova os óculos escuros. Óculos de grau são permitidos.",[o.STAY_STILL]:"Fique imóvel para capturar a foto",[o.CAPTURING]:"Capturando...",[o.SUCCESS]:"Captura realizada!",[o.ERROR]:"Ocorreu um erro."},en:{[o.INITIALIZING]:"Initializing camera and detector...",[o.NO_FACE_DETECTED]:"Position your face in the center of the oval.",[o.FACE_DETECTED]:"Analyzing...",[o.TOO_CLOSE]:"Move back a little",[o.TOO_FAR]:"Move closer to the camera",[o.OFF_CENTER]:"Center your face in the center of the oval",[o.FACE_OBSTRUCTED]:"Keep your face fully visible. Remove your hands from your face.",[o.HEAD_NOT_STRAIGHT]:"Look directly at the camera and keep your head straight.",[o.MULTIPLE_FACES]:"Keep only one person in the frame.",[o.POOR_ILLUMINATION]:"Find a well-lit environment and center your face in the oval.",[o.NOT_NEUTRAL_EXPRESSION]:"Keep a neutral expression: mouth closed, no smiling, and eyes open.",[o.DARK_GLASSES]:"Remove sunglasses. Prescription glasses are allowed.",[o.STAY_STILL]:"Stay still to capture the photo",[o.CAPTURING]:"Capturing...",[o.SUCCESS]:"Capture complete!",[o.ERROR]:"An error occurred."},es:{[o.INITIALIZING]:"Inicializando cámara y detector...",[o.NO_FACE_DETECTED]:"Coloque su rostro en el centro del óvalo.",[o.FACE_DETECTED]:"Analizando...",[o.TOO_CLOSE]:"Aléjese un poco",[o.TOO_FAR]:"Acérquese a la cámara",[o.OFF_CENTER]:"Centre el rostro en el centro del óvalo",[o.FACE_OBSTRUCTED]:"Mantenga el rostro totalmente visible. Quite las manos del rostro.",[o.HEAD_NOT_STRAIGHT]:"Mire directamente a la cámara y mantenga la cabeza recta.",[o.MULTIPLE_FACES]:"Mantenga solo una persona en el encuadre.",[o.POOR_ILLUMINATION]:"Busque un ambiente con buena iluminación y centre su rostro en el óvalo.",[o.NOT_NEUTRAL_EXPRESSION]:"Mantenga expresión neutra: boca cerrada, sin sonreír y ojos abiertos.",[o.DARK_GLASSES]:"Quite las gafas de sol. Las gafas graduadas están permitidas.",[o.STAY_STILL]:"Permanezca quieto para capturar la foto",[o.CAPTURING]:"Capturando...",[o.SUCCESS]:"¡Captura realizada!",[o.ERROR]:"Ocurrió un error."}},s={"pt-BR":"Status desconhecido.",en:"Unknown status.",es:"Estado desconhecido."};function l(e){return Object.assign({},r[e])}function c(e,t){var n;return null!==(n=r[t][e])&&void 0!==n?n:s[t]}function d(e){return{"pt-BR":"Carregando...",en:"Loading...",es:"Cargando..."}[e]}function u(e){const t=e.data;let n=0;for(let e=0;e<t.length;e+=4)n+=.2126*t[e]+.7152*t[e+1]+.0722*t[e+2];return n/(t.length/4)}const h=[33,133,159,145],m=[263,362,386,374],f=[61,291,0,17,39,269,270,409],g=.34;var E=function(e,t,n,a){return new(n||(n=Promise))(function(i,o){function r(e){try{l(a.next(e))}catch(e){o(e)}}function s(e){try{l(a.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(r,s)}l((a=a.apply(e,t||[])).next())})};const p={overlayCanvasElement:void 0,videoWidth:512,videoHeight:384,minDetectionConfidence:.4,minIlluminationThreshold:50,minFaceSizeFactor:.15,maxFaceSizeFactor:.75,stabilizationTimeThreshold:1e3,stabilityMovementThreshold:5,minFaceVisibilityScore:.4,maxHeadTiltDegrees:30,maxHandFaceDistance:.15,debugMode:!1,locale:"en",customMessages:{}};class S{constructor(e){this.faceLandmarker=null,this.handLandmarker=null,this.animationFrameId=null,this.lastDetection=null,this.stableSince=null,this.isCapturing=!1,this.options=this.resolveOptions(e),this.setStatus(o.INITIALIZING),this.init()}resolveOptions(e){const t=e.modelPath||"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm";return Object.assign(Object.assign(Object.assign({},p),e),{modelPath:t,locale:e.locale||"en",customMessages:e.customMessages||{}})}init(){return E(this,void 0,void 0,function*(){try{const e=d(this.options.locale);this.setStatus(o.INITIALIZING,void 0,e);const t=yield i.FilesetResolver.forVisionTasks(this.options.modelPath);this.faceLandmarker=yield i.FaceLandmarker.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task",delegate:"GPU"},runningMode:"VIDEO",numFaces:2,minFaceDetectionConfidence:this.options.minDetectionConfidence,minFacePresenceConfidence:this.options.minFaceVisibilityScore,minTrackingConfidence:this.options.minFaceVisibilityScore}),this.handLandmarker=yield i.HandLandmarker.createFromOptions(t,{baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",delegate:"GPU"},runningMode:"VIDEO",numHands:2,minHandDetectionConfidence:.5,minHandPresenceConfidence:.5,minTrackingConfidence:.5}),this.startDetectionLoop()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.setStatus(o.ERROR,t)}})}getMessageForStatus(e,t){return t||(this.options.customMessages[e]?this.options.customMessages[e]:c(e,this.options.locale))}setStatus(e,t,n){const a=this.getMessageForStatus(e,n);this.options.onStatusUpdate(e,a),e===o.ERROR&&t&&this.options.onError(e,t)}startDetectionLoop(){const e=this.options.videoElement,t=this.options.videoWidth||640,n=this.options.videoHeight||480,a=()=>E(this,void 0,void 0,function*(){var i;if(this.faceLandmarker&&this.handLandmarker&&e.videoWidth){try{const a=performance.now();let r=o.NO_FACE_DETECTED,s=null,l=[];const c=this.faceLandmarker.detectForVideo(e,a),d=this.handLandmarker.detectForVideo(e,a);if(d.landmarks&&d.landmarks.length>0&&(l=d.landmarks.map((e,t)=>{var n,a,i;return{landmarks:e,handedness:(null===(i=null===(a=null===(n=d.handednesses)||void 0===n?void 0:n[t])||void 0===a?void 0:a[0])||void 0===i?void 0:i.categoryName)||"Unknown"}})),c.faceLandmarks&&c.faceLandmarks.length>1){r=o.MULTIPLE_FACES,this.stableSince=null;const e=c.faceLandmarks[0],t=(null===(i=c.faceBlendshapes)||void 0===i?void 0:i[0])?this.estimateBoundingBox(e):null;t&&(s={boundingBox:t,landmarks:e,timestamp:a})}else if(c.faceLandmarks&&1===c.faceLandmarks.length){const i=c.faceLandmarks[0],d=this.estimateBoundingBox(i);s={boundingBox:d,landmarks:i,timestamp:a};const E=function(e,t=.18,n=.7){const a=e.width;return a<t?"TOO_FAR":a>n?"TOO_CLOSE":"OK"}(d,this.options.minFaceSizeFactor,this.options.maxFaceSizeFactor);if("OK"!==E)r="TOO_CLOSE"===E?o.TOO_CLOSE:o.TOO_FAR,this.stableSince=null;else{const c=i[4],E=function(e,t,n,a){const i=(e*n-n/2)/(.2*n),o=(t*a-a/2)/(a*g);return i*i+o*o<=.6}(c.x,c.y,t,n),p=function(e,t,n){const a=t/2,i=n/2,o=.2*t,r=n*g,s=e.xMin*t,l=(e.xMin+e.width)*t,c=e.yMin*n,d=(e.yMin+e.height)*n,u=((s+l)/2-a)/o,h=((c+d)/2-i)/r;if(u*u+h*h>1)return!1;const m=[{x:s,y:c},{x:l,y:c},{x:s,y:d},{x:l,y:d}];let f=0;for(const e of m){const t=(e.x-a)/o,n=(e.y-i)/r;t*t+n*n>1&&f++}return 0===f}(d,t,n);if(E&&p)if(function(e,t){if(e.length<478)return!1;const n=e[4],a=f.map(t=>e[t]),i=a.reduce((e,t)=>e+t.y,0)/a.length,o=Math.min(...a.map(e=>e.y)),r=Math.max(...a.map(e=>e.y))-o,s=t.height;return!(i<n.y-.01||i-n.y<.06*s||r<.02*s)}(i,d))if(function(e,t=25){if(e.length<478)return!1;const n=e[h[0]],a=e[m[0]],i=e[4],o=e[13],r=e[14],s=e[152],l=e[10],c=Math.abs(n.y-a.y),d=Math.abs(n.x-a.x);if(d<.01)return!1;const u=c/d;if(Math.atan(u)*(180/Math.PI)>t)return!1;const f=(n.x+a.x)/2,g=i.x-f,E=Math.abs(n.x-a.x);if(E<.01)return!1;const p=Math.abs(g)/E;if(Math.atan(p)*(180/Math.PI)>t)return!1;if(!function(e){if(e.length<478)return!1;const t=e[234],n=e[454],a=e[4],i=Math.abs(t.x-a.x),o=Math.abs(n.x-a.x);return!((i>.01&&o>.01?Math.max(i,o)/Math.min(i,o):1)>1.4||void 0!==t.z&&void 0!==n.z&&Math.abs(t.z-n.z)>.05)}(e))return!1;const S=(n.y+a.y)/2,T=(o.y+r.y)/2,y=s.y-l.y;if(y<.1)return!1;if(l.y>S+.02)return!1;if(S>i.y+.02)return!1;if(i.y>T+.02)return!1;if(T>=s.y)return!1;const b=(S-l.y)/y,O=(i.y-S)/y,v=(T-i.y)/y,I=(s.y-T)/y;return!(b<.06||b>.38||O<.03||O>.3||v<.02||v>.25||I<.04||I>.38)}(i,this.options.maxHeadTiltDegrees))if(l.length>0&&function(e,t,n=.15){const a=t.xMin+t.width/2,i=t.yMin+t.height/2;for(const t of e.landmarks){const e=t.x-a,o=t.y-i;if(Math.sqrt(e*e+o*o)<n)return!0}return!1}(l[0],d,this.options.maxHandFaceDistance))r=o.FACE_OBSTRUCTED,this.stableSince=null;else if(function(e){if(e.length<478)return!1;const t=e[159],n=e[144],a=e[386],i=e[373],o=Math.abs(t.y-n.y),r=Math.abs(a.y-i.y);if(o<.01||r<.01)return!1;const s=e[13],l=e[14];if(Math.abs(s.y-l.y)>.025)return!1;const c=e[61],d=e[291],u=e[4];return!((c.y+d.y)/2-u.y<.05)}(i))if(function(e,t){if(t.length<478)return!1;try{const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)return!1;const i=e.videoWidth,o=e.videoHeight,r=[t[33],t[133],t[159],t[144],t[145]],s=[t[263],t[362],t[386],t[373],t[374]],l=e=>{const t=e.map(e=>e.x*i),n=e.map(e=>e.y*o),a=Math.max(0,Math.min(...t)-5),r=Math.min(i,Math.max(...t)+5),s=Math.max(0,Math.min(...n)-5);return{x:a,y:s,width:r-a,height:Math.min(o,Math.max(...n)+5)-s}},c=t=>(n.width=t.width,n.height=t.height,a.drawImage(e,t.x,t.y,t.width,t.height,0,0,t.width,t.height),u(a.getImageData(0,0,t.width,t.height))),d=l(r),h=l(s);return(c(d)+c(h))/2<35}catch(e){return console.warn("Erro ao detectar óculos escuros:",e),!1}}(e,i))r=o.DARK_GLASSES,this.stableSince=null;else{const i=document.createElement("canvas"),l=d.xMin*e.videoWidth,c=d.yMin*e.videoHeight,h=d.width*e.videoWidth,m=d.height*e.videoHeight;i.width=h,i.height=m;const f=i.getContext("2d",{willReadFrequently:!0});if(f){f.drawImage(e,l,c,h,m,0,0,h,m);u(f.getImageData(0,0,i.width,i.height))<this.options.minIlluminationThreshold?(r=o.POOR_ILLUMINATION,this.stableSince=null):function(e,t,n=5,a=512,i=384){if(!e||!t)return!1;const o=(e.boundingBox.xMin+e.boundingBox.width/2)*a,r=(e.boundingBox.yMin+e.boundingBox.height/2)*i,s=(t.boundingBox.xMin+t.boundingBox.width/2)*a,l=(t.boundingBox.yMin+t.boundingBox.height/2)*i,c=Math.abs(o-s),d=Math.abs(r-l),u=Math.abs(e.boundingBox.width-t.boundingBox.width)*a,h=Math.abs(e.boundingBox.height-t.boundingBox.height)*i;return c<=n&&d<=n&&u<=2*n&&h<=2*n}(s,this.lastDetection,this.options.stabilityMovementThreshold,t,n)?(this.stableSince||(this.stableSince=a),r=a-this.stableSince>=this.options.stabilizationTimeThreshold?o.CAPTURING:o.STAY_STILL):(this.stableSince=null,r=o.STAY_STILL)}else r=o.FACE_DETECTED,this.stableSince=null}else r=o.NOT_NEUTRAL_EXPRESSION,this.stableSince=null;else r=o.HEAD_NOT_STRAIGHT,this.stableSince=null;else r=o.FACE_OBSTRUCTED,this.stableSince=null;else r=o.OFF_CENTER,this.stableSince=null}}else this.lastDetection=null,this.stableSince=null;if(this.lastDetection=s,this.setStatus(r),this.options.overlayCanvasElement&&function(e,t,n,a,i){const r=e.getContext("2d");if(!r)return;const s=e.width,l=e.height,c=s/2,d=l/2;r.clearRect(0,0,s,l);const u=.2*s,f=l*g;if(r.fillStyle="rgba(255, 255, 255, 0.35)",r.fillRect(0,0,s,l),r.save(),r.beginPath(),r.ellipse(c,d,u,f,0,0,2*Math.PI),r.closePath(),r.globalCompositeOperation="destination-out",r.fill(),r.restore(),r.strokeStyle="rgba(255, 255, 255, 0.9)",r.lineWidth=3,r.beginPath(),r.ellipse(c,d,u,f,0,0,2*Math.PI),r.stroke(),r.strokeStyle="rgba(255, 255, 255, 0.45)",r.lineWidth=1,r.beginPath(),r.moveTo(c-6,d),r.lineTo(c+6,d),r.moveTo(c,d-6),r.lineTo(c,d+6),r.stroke(),t&&a){const e=a.landmarks;if(e.length>=478){const t=e[10],a=e[152],i=e[234],c=e[454],d=e.map(e=>e.x),u=e.map(e=>e.y),f=Math.min(...d),g=Math.max(...d),E=Math.min(...u),p=g-f,S=Math.max(...u)-E,T=.08,y=(f-p*T)*s,b=(E-S*T)*l,O=p*(1+2*T)*s,v=S*(1+2*T)*l;let I="red";n===o.STAY_STILL||n===o.CAPTURING?I="lime":n===o.FACE_DETECTED&&(I="yellow"),r.strokeStyle=I,r.lineWidth=3,r.strokeRect(y,b,O,v);const C=e[4];e[h[0]],e[m[0]],r.fillStyle="cyan",r.beginPath(),r.arc(C.x*s,C.y*l,5,0,2*Math.PI),r.fill(),r.fillStyle="magenta",r.beginPath(),r.arc(t.x*s,t.y*l,4,0,2*Math.PI),r.fill(),r.fillStyle="lime",r.beginPath(),r.arc(a.x*s,a.y*l,4,0,2*Math.PI),r.fill(),r.fillStyle="yellow",[e[33],e[133],e[159],e[144],e[145]].forEach(e=>{r.beginPath(),r.arc(e.x*s,e.y*l,3,0,2*Math.PI),r.fill()}),r.fillStyle="yellow",[e[263],e[362],e[386],e[373],e[374]].forEach(e=>{r.beginPath(),r.arc(e.x*s,e.y*l,3,0,2*Math.PI),r.fill()}),r.fillStyle="purple",r.beginPath(),r.arc(i.x*s,i.y*l,3,0,2*Math.PI),r.fill(),r.beginPath(),r.arc(c.x*s,c.y*l,3,0,2*Math.PI),r.fill()}}t&&i&&i.length>0&&i.forEach(e=>{r.fillStyle="orange",e.landmarks.forEach(e=>{r.beginPath(),r.arc(e.x*s,e.y*l,3,0,2*Math.PI),r.fill()})})}(this.options.overlayCanvasElement,this.options.debugMode||!1,r,s||void 0,l.length>0?l:void 0),r===o.CAPTURING&&!this.isCapturing)return this.isCapturing=!0,yield this.captureImage(),this.setStatus(o.SUCCESS),void this.stop()}catch(e){const t=e instanceof Error?e:new Error(String(e));this.setStatus(o.ERROR,t)}this.animationFrameId=requestAnimationFrame(a)}else this.animationFrameId=requestAnimationFrame(a)});this.animationFrameId=requestAnimationFrame(a)}estimateBoundingBox(e){const t=e.map(e=>e.x),n=e.map(e=>e.y),a=Math.min(...t),i=Math.max(...t),o=Math.min(...n);return{xMin:a,yMin:o,width:i-a,height:Math.max(...n)-o}}captureImage(){return E(this,void 0,void 0,function*(){const e=this.options.videoElement,t=document.createElement("canvas");t.width=e.videoWidth,t.height=e.videoHeight;const n=t.getContext("2d");n?(n.drawImage(e,0,0,t.width,t.height),t.toBlob(e=>{e?this.options.onCaptureSuccess(e):this.setStatus(o.ERROR,new Error("Failed to generate image blob"))},"image/jpeg",.95)):this.setStatus(o.ERROR,new Error("Failed to get canvas context"))})}stop(){null!==this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.faceLandmarker&&this.faceLandmarker.close(),this.handLandmarker&&this.handLandmarker.close()}}var T=n(848),y=n(953);const b=[o.SUCCESS,o.ERROR,o.CAPTURING,o.INITIALIZING],O={"pt-BR":{previewQuestion:"O que você achou?",savePhoto:"Salvar foto",tryAgain:"Tentar novamente",cancel:"Cancelar"},en:{previewQuestion:"What do you think?",savePhoto:"Save photo",tryAgain:"Try again",cancel:"Cancel"},es:{previewQuestion:"¿Qué te pareció?",savePhoto:"Guardar foto",tryAgain:"Intentar de nuevo",cancel:"Cancelar"}},v=({onCapture:e,onDismiss:t,locale:n,videoWidth:a=512,videoHeight:i=384,debugMode:r=!1,modelPath:s,styles:l,labels:d})=>{const u=(e=>{if(!e)return"pt-BR";const t=e.toLowerCase();return t.startsWith("en")?"en":t.startsWith("es")?"es":"pt-BR"})(n),h=O[u],m=Object.assign(Object.assign({},h),null!=d?d:{}),f=(0,y.useRef)(null),g=(0,y.useRef)(null),E=(0,y.useRef)(null),p=(0,y.useRef)(null),v=(0,y.useRef)(0),I=(0,y.useRef)(""),[C,R]=(0,y.useState)(o.INITIALIZING),[_,x]=(0,y.useState)(c(o.INITIALIZING,u)),[A,M]=(0,y.useState)(c(o.INITIALIZING,u)),[L,F]=(0,y.useState)(!0),[N,P]=(0,y.useState)(null),k=(0,y.useCallback)((e,t)=>{R(e),x(t)},[]);(0,y.useEffect)(()=>{if(I.current=_,_===A)return;if(b.includes(C))return p.current&&(clearTimeout(p.current),p.current=null),M(_),void(v.current=Date.now());const e=Date.now(),t=e-v.current;if(t>=1500)return M(_),void(v.current=e);if(p.current)return;const n=1500-t;p.current=setTimeout(()=>{M(I.current),v.current=Date.now(),p.current=null},n)},[_,C,A]);const w=(0,y.useCallback)(e=>{var t;R(o.SUCCESS),x(c(o.SUCCESS,u)),(t=e,new Promise((e,n)=>{const a=new FileReader;a.onloadend=()=>e(a.result),a.onerror=n,a.readAsDataURL(t)})).then(e=>{P(e),F(!1)})},[u]),D=(0,y.useCallback)(()=>{e(null!=N?N:null),null==t||t()},[e,t,N]),j=(0,y.useCallback)(()=>{P(null),R(o.INITIALIZING),x(c(o.INITIALIZING,u)),F(!0)},[u]),U=(0,y.useCallback)((e,t)=>{x(t.message),R(o.ERROR)},[]);(0,y.useEffect)(()=>{const e=f.current;if(!L)return E.current&&(E.current.stop(),E.current=null),void(e&&e.srcObject&&(e.srcObject.getTracks().forEach(e=>e.stop()),e.srcObject=null));if(!e)return;let t=!1;return(n=void 0,l=void 0,c=void 0,d=function*(){var n;try{if(!e.srcObject){const n=yield navigator.mediaDevices.getUserMedia({video:{width:a,height:i}});if(t)return;e.srcObject=n,yield e.play()}const o=g.current;if(o){const t=e.videoWidth||a,n=e.videoHeight||i;o.width=t,o.height=n}if(E.current||t)return;const l={videoElement:e,overlayCanvasElement:null!==(n=g.current)&&void 0!==n?n:void 0,locale:u,debugMode:r,onStatusUpdate:k,onCaptureSuccess:w,onError:U,videoWidth:a,videoHeight:i};s&&(l.modelPath=s);const c=new S(l);E.current=c}catch(e){if(t)return;x(e.message),R(o.ERROR)}},new(c||(c=Promise))(function(e,t){function a(e){try{o(d.next(e))}catch(e){t(e)}}function i(e){try{o(d.throw(e))}catch(e){t(e)}}function o(t){var n;t.done?e(t.value):(n=t.value,n instanceof c?n:new c(function(e){e(n)})).then(a,i)}o((d=d.apply(n,l||[])).next())})).catch(()=>{}),()=>{t=!0,E.current&&(E.current.stop(),E.current=null),e&&e.srcObject&&(e.srcObject.getTracks().forEach(e=>e.stop()),e.srcObject=null)};var n,l,c,d},[L,u,a,i,r,s,k,w,U]);const B=!L&&Boolean(N),G=L,H=Object.assign({width:"100%",maxWidth:640,boxSizing:"border-box"},null==l?void 0:l.container),z=Object.assign({textAlign:"center",display:"flex",alignItems:"center",justifyContent:"center",fontSize:16,padding:"10px 12px",marginBottom:10,borderRadius:10,fontWeight:600,marginTop:30,height:52,boxSizing:"border-box",transition:"all 0.4s ease-in-out"},null==l?void 0:l.messageBanner),W=Object.assign(Object.assign({},z),{backgroundColor:"#fff7df",color:"#b67219"});G&&(C===o.SUCCESS?(W.backgroundColor="#e0ffdf",W.color="#26c026"):C===o.ERROR&&(W.backgroundColor="#ffdfdf",W.color="#c02626"));const q=Object.assign({width:"100%",maxWidth:512,height:384,margin:"0 auto",borderRadius:10,overflow:"hidden",position:"relative",backgroundColor:"#1a1a1a"},null==l?void 0:l.media),Z={display:"block",width:"100%",padding:"8px 12px",borderRadius:4,border:"1px solid #d9d9d9",backgroundColor:"#ffffff",cursor:"pointer",fontSize:14},K=Object.assign(Object.assign(Object.assign({},Z),{backgroundColor:"#1dbe32",color:"#ffffff",borderColor:"#1dbe32"}),null==l?void 0:l.primaryButton),V=Object.assign(Object.assign({},Z),null==l?void 0:l.secondaryButton);return(0,T.jsxs)("div",{style:H,children:[(0,T.jsx)("div",{style:W,children:B?m.previewQuestion:A}),(0,T.jsx)("div",{style:q,children:G?(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)("video",{ref:f,autoPlay:!0,playsInline:!0,muted:!0,style:{width:"100%",height:"100%",objectFit:"cover",transform:"scaleX(-1)"}}),(0,T.jsx)("canvas",{ref:g,style:{position:"absolute",top:0,left:0,width:"100%",height:"100%",pointerEvents:"none",transform:"scaleX(-1)"}})]}):B&&N?(0,T.jsx)("img",{src:N,alt:"Selfie preview",style:{display:"block",width:"100%",height:"100%",objectFit:"cover",transform:"scaleX(-1)",backgroundColor:"transparent",border:"none"}}):(0,T.jsx)("div",{"aria-hidden":!0})}),(0,T.jsx)("div",{style:{marginTop:10},children:B?(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)("button",{type:"button",onClick:j,style:V,children:m.tryAgain}),(0,T.jsx)("div",{style:{height:8}}),(0,T.jsx)("button",{type:"button",onClick:D,disabled:!N,style:Object.assign(Object.assign({},K),{opacity:N?1:.6,cursor:N?"pointer":"not-allowed"}),children:m.savePhoto})]}):(0,T.jsx)("button",{type:"button",onClick:()=>{null==t||t(),e(null),F(!1)},style:V,children:m.cancel})})]})},I=S;module.exports=a})();
|
|
2
3
|
//# sourceMappingURL=face-validator-sdk.cjs.js.map
|