@simprints/simface-sdk 0.10.1 → 0.12.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/README.md +171 -98
- package/dist/components/simface-capture.d.ts +5 -0
- package/dist/components/simface-capture.js +61 -36
- package/dist/components/simface-capture.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/services/camera.d.ts +3 -3
- package/dist/services/camera.js +17 -30
- package/dist/services/camera.js.map +1 -1
- package/dist/shared/capture-flow.d.ts +11 -5
- package/dist/shared/capture-flow.js +14 -8
- package/dist/shared/capture-flow.js.map +1 -1
- package/dist/simface-sdk.js +1239 -1225
- package/dist/simface-sdk.umd.cjs +55 -54
- package/dist/types/index.d.ts +16 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SimFace SDK
|
|
2
2
|
|
|
3
|
-
SimFace SDK provides facial recognition for web-based KYC workflows. It
|
|
3
|
+
SimFace SDK provides facial recognition for web-based KYC workflows. It exposes one primary JavaScript API for enrollment and verification, plus a lower-level Web Component for advanced UI control.
|
|
4
4
|
|
|
5
5
|
Works in: all modern browsers, WhatsApp in-app browser, and mobile WebViews.
|
|
6
6
|
|
|
@@ -8,7 +8,20 @@ This repository is the public frontend SDK and demo repo for SimFace. For fronte
|
|
|
8
8
|
|
|
9
9
|
The backend API, infrastructure, and TensorFlow Lite runtime live in the separate private backend repository.
|
|
10
10
|
|
|
11
|
-
The capture flow is planned explicitly as: auto camera -> manual camera -> media picker.
|
|
11
|
+
The capture flow is planned explicitly as: auto camera -> manual camera -> media picker. The primary API supports two UI modes:
|
|
12
|
+
- popup capture: the SDK opens and manages its own modal capture flow
|
|
13
|
+
- embedded capture: the SDK runs capture inside a host-provided `simface-capture` element
|
|
14
|
+
|
|
15
|
+
## Face Quality Checks
|
|
16
|
+
|
|
17
|
+
The SDK automatically performs these checks on captured images before submission:
|
|
18
|
+
|
|
19
|
+
1. Face presence - at least one face must be detected.
|
|
20
|
+
2. Single face - only one face should be in the frame.
|
|
21
|
+
3. Face size - face must not be too close or too far.
|
|
22
|
+
4. Centering - face must be approximately centered in the frame.
|
|
23
|
+
|
|
24
|
+
If a check fails, the user is prompted with specific guidance and asked to retake the photo.
|
|
12
25
|
|
|
13
26
|
## Quick Start
|
|
14
27
|
|
|
@@ -75,70 +88,138 @@ if (result.match) {
|
|
|
75
88
|
}
|
|
76
89
|
```
|
|
77
90
|
|
|
78
|
-
### 5.
|
|
91
|
+
### 5. Choose the capture UI mode
|
|
79
92
|
|
|
80
|
-
|
|
93
|
+
`enroll()` and `verify()` are the main SDK entry points. They both use the same capture workflow and backend API. The only UI difference is where the capture UI is rendered.
|
|
81
94
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// 'embedded' renders the capture component inline in a host container.
|
|
86
|
-
presentation: 'popup',
|
|
95
|
+
Both functions accept:
|
|
96
|
+
- `workflowOptions`: optional capture behavior that applies to both popup and embedded flows
|
|
97
|
+
- `captureElement`: optional existing `simface-capture` element; if this argument is present, the SDK uses embedded mode
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
#### Popup capture
|
|
100
|
+
|
|
101
|
+
If you omit `captureElement`, the SDK opens its popup capture UI:
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
```javascript
|
|
104
|
+
const workflowOptions = {
|
|
105
|
+
capturePreference: 'auto-preferred',
|
|
94
106
|
allowMediaPickerFallback: true,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const enrollResult = await enroll(config, 'unique-user-id', workflowOptions);
|
|
110
|
+
const verifyResult = await verify(config, 'unique-user-id', workflowOptions);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Embedded capture
|
|
114
|
+
|
|
115
|
+
If you want capture inline in your page, create a `simface-capture` element and pass it as `captureElement`. The SDK still owns the capture lifecycle; it just renders the UI inline instead of in a popup.
|
|
95
116
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
```html
|
|
118
|
+
<simface-capture
|
|
119
|
+
embedded
|
|
120
|
+
capture-preference="auto-preferred"
|
|
121
|
+
label="Take a selfie for verification"
|
|
122
|
+
idle-feedback-label="Start verification to see camera guidance here."
|
|
123
|
+
capture-label="Snap photo"
|
|
124
|
+
retake-label="Take another"
|
|
125
|
+
confirm-label="Use this photo"
|
|
126
|
+
retry-label="Start over"
|
|
127
|
+
></simface-capture>
|
|
128
|
+
```
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
```javascript
|
|
131
|
+
const workflowOptions = {
|
|
132
|
+
capturePreference: 'auto-preferred',
|
|
133
|
+
allowMediaPickerFallback: true,
|
|
103
134
|
};
|
|
104
135
|
|
|
105
|
-
const
|
|
106
|
-
|
|
136
|
+
const captureElement = document.querySelector('simface-capture');
|
|
137
|
+
|
|
138
|
+
const enrollResult = await enroll(config, 'unique-user-id', workflowOptions, captureElement);
|
|
139
|
+
const verifyResult = await verify(config, 'unique-user-id', workflowOptions, captureElement);
|
|
107
140
|
```
|
|
108
141
|
|
|
109
|
-
|
|
|
142
|
+
| workflowOptions | Type | Default | Notes |
|
|
110
143
|
|--------|------|---------|-------|
|
|
111
|
-
| `presentation` | `'popup' \| 'embedded'` | `'popup'` | Popup opens a fullscreen modal; embedded renders inline |
|
|
112
144
|
| `capturePreference` | `'auto-preferred' \| 'manual-only'` | `'auto-preferred'` | Controls auto vs manual shutter |
|
|
113
145
|
| `allowMediaPickerFallback` | `boolean` | `true` | Falls back to file picker if camera is unavailable |
|
|
114
|
-
| `container` | `HTMLElement \| string` | — | Required for embedded via top-level helpers; ignored for popup |
|
|
115
|
-
| `label` | `string` | `'Capturing Face'` | Instructional text shown during capture |
|
|
116
|
-
| `confirmLabel` | `string` | `'Accept'` | Confirm button label in preview state |
|
|
117
146
|
|
|
118
|
-
##
|
|
147
|
+
## API Reference
|
|
119
148
|
|
|
120
|
-
|
|
149
|
+
### Primary SDK API
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
The main integration surface is:
|
|
152
|
+
- `enroll(config, clientId, workflowOptions?, captureElement?)`
|
|
153
|
+
- `verify(config, clientId, workflowOptions?, captureElement?)`
|
|
125
154
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
These functions:
|
|
156
|
+
- run the camera capture workflow
|
|
157
|
+
- manage popup or embedded capture UI
|
|
158
|
+
- perform face quality validation
|
|
159
|
+
- call the backend API for enrollment or verification
|
|
130
160
|
|
|
131
|
-
|
|
161
|
+
### `enroll(config, clientId, workflowOptions?, captureElement?): Promise<EnrollResult>`
|
|
162
|
+
|
|
163
|
+
Opens the camera, captures a face image with quality validation, and enrolls the user.
|
|
164
|
+
|
|
165
|
+
Parameters:
|
|
166
|
+
|
|
167
|
+
| Parameter | Type | Description |
|
|
168
|
+
|-----------|------|-------------|
|
|
169
|
+
| `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
|
|
170
|
+
| `clientId` | `string` | Unique identifier for the user |
|
|
171
|
+
| `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
|
|
172
|
+
| `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
|
|
173
|
+
|
|
174
|
+
Returns: `EnrollResult`
|
|
175
|
+
|
|
176
|
+
### `verify(config, clientId, workflowOptions?, captureElement?): Promise<VerifyResult>`
|
|
132
177
|
|
|
133
|
-
|
|
178
|
+
Opens the camera, captures a face image, and verifies against the enrolled face.
|
|
179
|
+
|
|
180
|
+
Parameters:
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Description |
|
|
183
|
+
|-----------|------|-------------|
|
|
184
|
+
| `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
|
|
185
|
+
| `clientId` | `string` | Unique identifier for the user |
|
|
186
|
+
| `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
|
|
187
|
+
| `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
|
|
188
|
+
|
|
189
|
+
Returns: `VerifyResult`
|
|
134
190
|
|
|
135
|
-
|
|
191
|
+
### `SimFaceAPIClient` and the backend REST interface
|
|
192
|
+
|
|
193
|
+
`SimFaceAPIClient` is the lower-level HTTP client used internally by `enroll()` and `verify()`. Use it when you want direct control over when capture happens and when backend calls are made.
|
|
194
|
+
|
|
195
|
+
Typical cases for using `SimFaceAPIClient` directly:
|
|
196
|
+
- advanced UI flows driven by your own application state
|
|
197
|
+
- direct use of the `simface-capture` component
|
|
198
|
+
- custom orchestration where capture and backend submission happen in separate steps
|
|
199
|
+
|
|
200
|
+
At a high level:
|
|
201
|
+
- `enroll()` and `verify()` = capture UI + quality checks + backend submission
|
|
202
|
+
- `SimFaceAPIClient` = backend submission only
|
|
203
|
+
|
|
204
|
+
`SimFaceAPIClient` maps directly to the backend REST interface:
|
|
205
|
+
- `validateAPIKey()` -> `POST /api/v1/auth/validate`
|
|
206
|
+
- `enroll(clientId, imageBlob)` -> `POST /api/v1/enroll`
|
|
207
|
+
- `verify(clientId, imageBlob)` -> `POST /api/v1/verify`
|
|
208
|
+
|
|
209
|
+
## Advanced: Direct `simface-capture` control
|
|
210
|
+
|
|
211
|
+
Use the `simface-capture` Web Component directly when you want the host application to manage capture state itself instead of letting `enroll()` or `verify()` orchestrate it. In this mode, the component is also the source of truth for embedded UI copy.
|
|
136
212
|
|
|
137
213
|
```html
|
|
138
214
|
<simface-capture
|
|
139
215
|
embedded
|
|
140
216
|
capture-preference="auto-preferred"
|
|
141
217
|
label="Take a selfie for verification"
|
|
218
|
+
idle-feedback-label="Start verification to see camera guidance here."
|
|
219
|
+
capture-label="Snap photo"
|
|
220
|
+
retake-label="Take another"
|
|
221
|
+
confirm-label="Use this photo"
|
|
222
|
+
retry-label="Start over"
|
|
142
223
|
></simface-capture>
|
|
143
224
|
|
|
144
225
|
<script type="module">
|
|
@@ -165,16 +246,30 @@ For more control over the UI, use the `<simface-capture>` Web Component directly
|
|
|
165
246
|
captureEl.addEventListener('simface-error', (e) => {
|
|
166
247
|
console.error('Capture error:', e.detail.error);
|
|
167
248
|
});
|
|
249
|
+
|
|
250
|
+
await captureEl.startCapture();
|
|
168
251
|
</script>
|
|
169
252
|
```
|
|
170
253
|
|
|
254
|
+
In this advanced flow:
|
|
255
|
+
1. the host renders the component
|
|
256
|
+
2. the host starts capture with `startCapture()` or by setting `active = true`
|
|
257
|
+
3. the component emits capture events
|
|
258
|
+
4. the host decides what backend call to make with `SimFaceAPIClient`
|
|
259
|
+
|
|
260
|
+
This is more flexible, but it also means the host owns more of the workflow.
|
|
261
|
+
|
|
171
262
|
### Component Attributes
|
|
172
263
|
|
|
173
264
|
| Attribute | Type | Default | Description |
|
|
174
265
|
|-----------|------|---------|-------------|
|
|
175
|
-
| `label` | String | `"Capturing Face"` |
|
|
266
|
+
| `label` | String | `"Capturing Face"` | Primary instructional text shown by the component |
|
|
267
|
+
| `idle-feedback-label` | String | `"Start a capture to see camera guidance here."` | Idle guidance text shown in the feedback area before capture begins |
|
|
176
268
|
| `embedded` | Boolean | `false` | Runs the component inline instead of delegating to the popup capture service |
|
|
269
|
+
| `capture-label` | String | `"Take photo"` | Manual capture button label |
|
|
270
|
+
| `retake-label` | String | `"Retake"` | Preview retake button label |
|
|
177
271
|
| `confirm-label` | String | `"Accept"` | Confirm button label used in preview state |
|
|
272
|
+
| `retry-label` | String | `"Try again"` | Error-state retry button label |
|
|
178
273
|
| `capture-preference` | `"auto-preferred" \| "manual-only"` | `"auto-preferred"` | Whether auto capture should be preferred or disabled |
|
|
179
274
|
| `allow-media-picker-fallback` | Boolean | `true` | Whether the component may fall back to the media picker if camera capture is unavailable |
|
|
180
275
|
|
|
@@ -186,23 +281,31 @@ For more control over the UI, use the `<simface-capture>` Web Component directly
|
|
|
186
281
|
| `simface-cancelled` | - | Fires when the user cancels the capture flow |
|
|
187
282
|
| `simface-error` | `{ error: string }` | Fires on capture/detection errors |
|
|
188
283
|
|
|
189
|
-
##
|
|
284
|
+
## Type Definitions
|
|
190
285
|
|
|
191
|
-
### `
|
|
286
|
+
### `SimFaceConfig`
|
|
192
287
|
|
|
193
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
interface SimFaceConfig {
|
|
290
|
+
apiUrl: string;
|
|
291
|
+
projectId: string;
|
|
292
|
+
apiKey: string;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
194
295
|
|
|
195
|
-
|
|
296
|
+
### `SimFaceWorkflowOptions`
|
|
196
297
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
298
|
+
```typescript
|
|
299
|
+
interface SimFaceWorkflowOptions {
|
|
300
|
+
capturePreference?: 'auto-preferred' | 'manual-only';
|
|
301
|
+
allowMediaPickerFallback?: boolean;
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### `EnrollResult`
|
|
202
306
|
|
|
203
|
-
Returns `EnrollResult`:
|
|
204
307
|
```typescript
|
|
205
|
-
{
|
|
308
|
+
interface EnrollResult {
|
|
206
309
|
success: boolean;
|
|
207
310
|
clientId: string;
|
|
208
311
|
message?: string;
|
|
@@ -210,21 +313,10 @@ Returns `EnrollResult`:
|
|
|
210
313
|
}
|
|
211
314
|
```
|
|
212
315
|
|
|
213
|
-
### `
|
|
214
|
-
|
|
215
|
-
Opens the camera, captures a face image, and verifies against the enrolled face.
|
|
216
|
-
|
|
217
|
-
Parameters:
|
|
218
|
-
|
|
219
|
-
| Parameter | Type | Description |
|
|
220
|
-
|-----------|------|-------------|
|
|
221
|
-
| `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) |
|
|
222
|
-
| `clientId` | `string` | Unique identifier for the user |
|
|
223
|
-
| `captureOptions` | `SimFaceCaptureOptions` | Optional capture presentation/fallback overrides |
|
|
316
|
+
### `VerifyResult`
|
|
224
317
|
|
|
225
|
-
Returns `VerifyResult`:
|
|
226
318
|
```typescript
|
|
227
|
-
{
|
|
319
|
+
interface VerifyResult {
|
|
228
320
|
match: boolean;
|
|
229
321
|
score: number;
|
|
230
322
|
threshold: number;
|
|
@@ -233,29 +325,6 @@ Returns `VerifyResult`:
|
|
|
233
325
|
}
|
|
234
326
|
```
|
|
235
327
|
|
|
236
|
-
### `SimFaceConfig`
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
{
|
|
240
|
-
apiUrl: string;
|
|
241
|
-
projectId: string;
|
|
242
|
-
apiKey: string;
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### `SimFaceCaptureOptions`
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
{
|
|
250
|
-
presentation?: 'popup' | 'embedded';
|
|
251
|
-
capturePreference?: 'auto-preferred' | 'manual-only';
|
|
252
|
-
allowMediaPickerFallback?: boolean;
|
|
253
|
-
container?: HTMLElement | string;
|
|
254
|
-
label?: string;
|
|
255
|
-
confirmLabel?: string;
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
328
|
## Backend API Endpoints
|
|
260
329
|
|
|
261
330
|
For clients integrating directly with the REST API:
|
|
@@ -283,17 +352,6 @@ Health check endpoint.
|
|
|
283
352
|
|
|
284
353
|
Response: `{ "status": "ok" }`
|
|
285
354
|
|
|
286
|
-
## Face Quality Checks
|
|
287
|
-
|
|
288
|
-
The SDK automatically performs these checks on captured images before submission:
|
|
289
|
-
|
|
290
|
-
1. Face presence - at least one face must be detected.
|
|
291
|
-
2. Single face - only one face should be in the frame.
|
|
292
|
-
3. Face size - face must not be too close or too far.
|
|
293
|
-
4. Centering - face must be approximately centered in the frame.
|
|
294
|
-
|
|
295
|
-
If a check fails, the user is prompted with specific guidance and asked to retake the photo.
|
|
296
|
-
|
|
297
355
|
## Browser Compatibility
|
|
298
356
|
|
|
299
357
|
| Browser | Camera Capture | Face Detection |
|
|
@@ -305,3 +363,18 @@ If a check fails, the user is prompted with specific guidance and asked to retak
|
|
|
305
363
|
| Samsung Internet | Yes | Yes |
|
|
306
364
|
|
|
307
365
|
> Note: In WhatsApp's in-app browser, the camera opens via the device's native camera app rather than an in-browser preview. Face quality checks run after the photo is taken.
|
|
366
|
+
|
|
367
|
+
## Try the local demo
|
|
368
|
+
|
|
369
|
+
Build the SDK at the repository root, then run the demo:
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
npm install
|
|
373
|
+
npm run build
|
|
374
|
+
|
|
375
|
+
cd demo
|
|
376
|
+
npm install
|
|
377
|
+
npm run dev
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The demo runs at `http://localhost:4173` and consumes the built SDK artifact from `dist/`. To enable HTTPS (required for camera access from other devices on the local network), set `DEMO_USE_HTTPS=true` before starting the demo.
|
|
@@ -10,9 +10,13 @@ import type { CapturePreference } from '../types/index.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class SimFaceCapture extends LitElement {
|
|
12
12
|
label: string;
|
|
13
|
+
idleFeedbackLabel: string;
|
|
13
14
|
embedded: boolean;
|
|
14
15
|
active: boolean;
|
|
15
16
|
confirmLabel: string;
|
|
17
|
+
captureLabel: string;
|
|
18
|
+
retakeLabel: string;
|
|
19
|
+
retryLabel: string;
|
|
16
20
|
capturePreference: CapturePreference;
|
|
17
21
|
allowMediaPickerFallback: boolean;
|
|
18
22
|
private captureState;
|
|
@@ -31,6 +35,7 @@ export declare class SimFaceCapture extends LitElement {
|
|
|
31
35
|
private pendingActiveSync;
|
|
32
36
|
static styles: import("lit").CSSResult;
|
|
33
37
|
disconnectedCallback(): void;
|
|
38
|
+
protected willUpdate(changedProperties: Map<string, unknown>): void;
|
|
34
39
|
updated(changedProperties: Map<string, unknown>): void;
|
|
35
40
|
render(): import("lit-html").TemplateResult<1>;
|
|
36
41
|
startCapture(): Promise<void>;
|
|
@@ -8,7 +8,7 @@ import { LitElement, html, css } from 'lit';
|
|
|
8
8
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
9
9
|
import { assessFaceQuality } from '../services/face-detection.js';
|
|
10
10
|
import { CAPTURE_GUIDE_MASK_PATH, CAPTURE_GUIDE_PATH, } from '../shared/auto-capture.js';
|
|
11
|
-
import { buildCapturePlan, normalizeCaptureOptions, resolveCaptureCapabilities, DEFAULT_LABEL, DEFAULT_CONFIRM_LABEL, } from '../shared/capture-flow.js';
|
|
11
|
+
import { buildCapturePlan, normalizeCaptureOptions, resolveCaptureCapabilities, DEFAULT_CAPTURE_LABEL, DEFAULT_IDLE_FEEDBACK_LABEL, DEFAULT_LABEL, DEFAULT_CONFIRM_LABEL, DEFAULT_RETAKE_LABEL, DEFAULT_RETRY_LABEL, } from '../shared/capture-flow.js';
|
|
12
12
|
import { CameraCaptureSessionController, } from '../shared/capture-session.js';
|
|
13
13
|
import { CameraAccessError, blobToImage, captureFromFileInput, openUserFacingCameraStream, } from '../shared/capture-runtime.js';
|
|
14
14
|
/**
|
|
@@ -23,14 +23,18 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
23
23
|
constructor() {
|
|
24
24
|
super(...arguments);
|
|
25
25
|
this.label = DEFAULT_LABEL;
|
|
26
|
+
this.idleFeedbackLabel = DEFAULT_IDLE_FEEDBACK_LABEL;
|
|
26
27
|
this.embedded = false;
|
|
27
28
|
this.active = false;
|
|
28
29
|
this.confirmLabel = DEFAULT_CONFIRM_LABEL;
|
|
30
|
+
this.captureLabel = DEFAULT_CAPTURE_LABEL;
|
|
31
|
+
this.retakeLabel = DEFAULT_RETAKE_LABEL;
|
|
32
|
+
this.retryLabel = DEFAULT_RETRY_LABEL;
|
|
29
33
|
this.capturePreference = 'auto-preferred';
|
|
30
34
|
this.allowMediaPickerFallback = true;
|
|
31
35
|
this.captureState = 'idle';
|
|
32
36
|
this.errorMessage = '';
|
|
33
|
-
this.feedbackMessage =
|
|
37
|
+
this.feedbackMessage = DEFAULT_IDLE_FEEDBACK_LABEL;
|
|
34
38
|
this.feedbackTone = 'neutral';
|
|
35
39
|
this.previewUrl = '';
|
|
36
40
|
this.qualityResult = null;
|
|
@@ -46,6 +50,11 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
46
50
|
super.disconnectedCallback();
|
|
47
51
|
this.stopSession();
|
|
48
52
|
}
|
|
53
|
+
willUpdate(changedProperties) {
|
|
54
|
+
if (changedProperties.has('idleFeedbackLabel') && this.captureState === 'idle') {
|
|
55
|
+
this.feedbackMessage = this.idleFeedbackLabel;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
49
58
|
updated(changedProperties) {
|
|
50
59
|
if (!changedProperties.has('active') || this.pendingActiveSync) {
|
|
51
60
|
return;
|
|
@@ -90,9 +99,8 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
90
99
|
: ''}
|
|
91
100
|
|
|
92
101
|
<p class="capture-copy">${this.label}</p>
|
|
93
|
-
|
|
94
102
|
${this.captureState === 'idle'
|
|
95
|
-
?
|
|
103
|
+
? ''
|
|
96
104
|
: html `
|
|
97
105
|
<div class="stage">
|
|
98
106
|
<video
|
|
@@ -127,13 +135,13 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
127
135
|
${this.captureState === 'live'
|
|
128
136
|
? html `
|
|
129
137
|
${this.captureMode === 'manual'
|
|
130
|
-
? html `<button class="btn btn-primary" data-simface-action="capture" ?disabled=${!this.canTakePhoto} @click=${this.handleManualCapture}
|
|
138
|
+
? html `<button class="btn btn-primary" data-simface-action="capture" ?disabled=${!this.canTakePhoto} @click=${this.handleManualCapture}>${this.captureLabel}</button>`
|
|
131
139
|
: ''}
|
|
132
140
|
`
|
|
133
141
|
: ''}
|
|
134
142
|
${this.captureState === 'preview'
|
|
135
143
|
? html `
|
|
136
|
-
<button class="btn btn-retake" data-simface-action="retake" @click=${this.handleRetake}
|
|
144
|
+
<button class="btn btn-retake" data-simface-action="retake" @click=${this.handleRetake}>${this.retakeLabel}</button>
|
|
137
145
|
${this.qualityResult?.passesQualityChecks === false
|
|
138
146
|
? ''
|
|
139
147
|
: html `<button class="btn btn-confirm" data-simface-action="confirm" @click=${this.handleConfirm}>${this.confirmLabel}</button>`}
|
|
@@ -141,7 +149,7 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
141
149
|
: ''}
|
|
142
150
|
${this.captureState === 'error'
|
|
143
151
|
? html `
|
|
144
|
-
<button class="btn btn-primary" data-simface-action="retry" @click=${this.beginCapture}
|
|
152
|
+
<button class="btn btn-primary" data-simface-action="retry" @click=${this.beginCapture}>${this.retryLabel}</button>
|
|
145
153
|
`
|
|
146
154
|
: ''}
|
|
147
155
|
</div>
|
|
@@ -157,12 +165,15 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
157
165
|
this.feedbackMessage = 'Requesting camera access...';
|
|
158
166
|
this.feedbackTone = 'neutral';
|
|
159
167
|
const options = normalizeCaptureOptions({
|
|
160
|
-
presentation: 'embedded',
|
|
161
168
|
capturePreference: this.capturePreference,
|
|
162
169
|
allowMediaPickerFallback: this.allowMediaPickerFallback,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
}, this);
|
|
171
|
+
options.label = this.label;
|
|
172
|
+
options.idleFeedbackLabel = this.idleFeedbackLabel;
|
|
173
|
+
options.confirmLabel = this.confirmLabel;
|
|
174
|
+
options.captureLabel = this.captureLabel;
|
|
175
|
+
options.retakeLabel = this.retakeLabel;
|
|
176
|
+
options.retryLabel = this.retryLabel;
|
|
166
177
|
const capabilities = await resolveCaptureCapabilities({
|
|
167
178
|
capturePreference: options.capturePreference,
|
|
168
179
|
});
|
|
@@ -328,7 +339,7 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
328
339
|
this.clearPreviewUrl();
|
|
329
340
|
this.captureState = 'idle';
|
|
330
341
|
this.errorMessage = '';
|
|
331
|
-
this.feedbackMessage =
|
|
342
|
+
this.feedbackMessage = this.idleFeedbackLabel;
|
|
332
343
|
this.feedbackTone = 'neutral';
|
|
333
344
|
this.syncProgress(0);
|
|
334
345
|
this.qualityResult = null;
|
|
@@ -393,7 +404,8 @@ let SimFaceCapture = class SimFaceCapture extends LitElement {
|
|
|
393
404
|
SimFaceCapture.styles = css `
|
|
394
405
|
:host {
|
|
395
406
|
display: block;
|
|
396
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
407
|
+
font-family: 'Mulish', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
408
|
+
color: #212529;
|
|
397
409
|
max-width: 400px;
|
|
398
410
|
margin: 0 auto;
|
|
399
411
|
text-align: center;
|
|
@@ -408,9 +420,9 @@ SimFaceCapture.styles = css `
|
|
|
408
420
|
.container {
|
|
409
421
|
position: relative;
|
|
410
422
|
padding: 16px;
|
|
411
|
-
border: 1px solid #
|
|
423
|
+
border: 1px solid #E0E0E0;
|
|
412
424
|
border-radius: 16px;
|
|
413
|
-
background: #
|
|
425
|
+
background: #F6F6F6;
|
|
414
426
|
}
|
|
415
427
|
|
|
416
428
|
.close-btn {
|
|
@@ -427,7 +439,7 @@ SimFaceCapture.styles = css `
|
|
|
427
439
|
border: none;
|
|
428
440
|
border-radius: 50%;
|
|
429
441
|
background: rgba(0, 0, 0, 0.06);
|
|
430
|
-
color: #
|
|
442
|
+
color: #5D5E5E;
|
|
431
443
|
cursor: pointer;
|
|
432
444
|
transition: background-color 0.15s;
|
|
433
445
|
}
|
|
@@ -450,7 +462,7 @@ SimFaceCapture.styles = css `
|
|
|
450
462
|
|
|
451
463
|
.capture-copy {
|
|
452
464
|
margin: 0;
|
|
453
|
-
color: #
|
|
465
|
+
color: #5D5E5E;
|
|
454
466
|
text-align: center;
|
|
455
467
|
width: 100%;
|
|
456
468
|
}
|
|
@@ -462,8 +474,8 @@ SimFaceCapture.styles = css `
|
|
|
462
474
|
aspect-ratio: 3 / 4;
|
|
463
475
|
border-radius: 22px;
|
|
464
476
|
background:
|
|
465
|
-
radial-gradient(circle at top, rgba(
|
|
466
|
-
linear-gradient(180deg, #
|
|
477
|
+
radial-gradient(circle at top, rgba(0, 179, 209, 0.16), transparent 30%),
|
|
478
|
+
linear-gradient(180deg, #1a1a1a, #0d0d0d);
|
|
467
479
|
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.2);
|
|
468
480
|
}
|
|
469
481
|
|
|
@@ -495,7 +507,7 @@ SimFaceCapture.styles = css `
|
|
|
495
507
|
}
|
|
496
508
|
|
|
497
509
|
.guide-mask {
|
|
498
|
-
fill: rgba(
|
|
510
|
+
fill: rgba(42, 42, 42, 0.75);
|
|
499
511
|
fill-rule: evenodd;
|
|
500
512
|
}
|
|
501
513
|
|
|
@@ -509,7 +521,7 @@ SimFaceCapture.styles = css `
|
|
|
509
521
|
|
|
510
522
|
.ring-progress {
|
|
511
523
|
fill: none;
|
|
512
|
-
stroke: #
|
|
524
|
+
stroke: #00B3D1;
|
|
513
525
|
stroke-width: 2.8;
|
|
514
526
|
stroke-linecap: round;
|
|
515
527
|
stroke-linejoin: round;
|
|
@@ -533,24 +545,25 @@ SimFaceCapture.styles = css `
|
|
|
533
545
|
border: none;
|
|
534
546
|
border-radius: 100px;
|
|
535
547
|
font-size: 15px;
|
|
536
|
-
font-weight:
|
|
537
|
-
letter-spacing: 0.
|
|
548
|
+
font-weight: 700;
|
|
549
|
+
letter-spacing: 0.04em;
|
|
550
|
+
text-transform: var(--simface-button-text-transform, uppercase);
|
|
538
551
|
cursor: pointer;
|
|
539
552
|
transition: background-color 0.15s, box-shadow 0.15s;
|
|
540
553
|
}
|
|
541
554
|
|
|
542
555
|
.btn-primary {
|
|
543
|
-
background: #
|
|
556
|
+
background: #00B3D1;
|
|
544
557
|
color: white;
|
|
545
558
|
}
|
|
546
559
|
|
|
547
560
|
.btn-primary:hover {
|
|
548
|
-
background: #
|
|
549
|
-
box-shadow: 0 1px 3px rgba(
|
|
561
|
+
background: #009DB8;
|
|
562
|
+
box-shadow: 0 1px 3px rgba(0, 179, 209, 0.3);
|
|
550
563
|
}
|
|
551
564
|
|
|
552
565
|
.btn-primary:disabled {
|
|
553
|
-
background: #
|
|
566
|
+
background: #80D9E8;
|
|
554
567
|
cursor: not-allowed;
|
|
555
568
|
box-shadow: none;
|
|
556
569
|
}
|
|
@@ -576,12 +589,12 @@ SimFaceCapture.styles = css `
|
|
|
576
589
|
}
|
|
577
590
|
|
|
578
591
|
.btn-secondary {
|
|
579
|
-
background: #
|
|
580
|
-
color: #
|
|
592
|
+
background: #E0E0E0;
|
|
593
|
+
color: #212529;
|
|
581
594
|
}
|
|
582
595
|
|
|
583
596
|
.btn-secondary:hover {
|
|
584
|
-
background: #
|
|
597
|
+
background: #D0D0D0;
|
|
585
598
|
}
|
|
586
599
|
|
|
587
600
|
.quality-msg {
|
|
@@ -608,21 +621,21 @@ SimFaceCapture.styles = css `
|
|
|
608
621
|
}
|
|
609
622
|
|
|
610
623
|
.quality-neutral {
|
|
611
|
-
background: #
|
|
612
|
-
color: #
|
|
624
|
+
background: #E0E0E0;
|
|
625
|
+
color: #212529;
|
|
613
626
|
}
|
|
614
627
|
|
|
615
628
|
.quality-manual {
|
|
616
|
-
background: #
|
|
617
|
-
color: #
|
|
629
|
+
background: #E0F7FB;
|
|
630
|
+
color: #212529;
|
|
618
631
|
}
|
|
619
632
|
|
|
620
633
|
.spinner {
|
|
621
634
|
display: inline-block;
|
|
622
635
|
width: 24px;
|
|
623
636
|
height: 24px;
|
|
624
|
-
border: 3px solid #
|
|
625
|
-
border-top: 3px solid #
|
|
637
|
+
border: 3px solid #E0E0E0;
|
|
638
|
+
border-top: 3px solid #00B3D1;
|
|
626
639
|
border-radius: 50%;
|
|
627
640
|
animation: spin 0.8s linear infinite;
|
|
628
641
|
margin: 12px auto;
|
|
@@ -641,6 +654,9 @@ SimFaceCapture.styles = css `
|
|
|
641
654
|
__decorate([
|
|
642
655
|
property({ type: String })
|
|
643
656
|
], SimFaceCapture.prototype, "label", void 0);
|
|
657
|
+
__decorate([
|
|
658
|
+
property({ type: String, attribute: 'idle-feedback-label' })
|
|
659
|
+
], SimFaceCapture.prototype, "idleFeedbackLabel", void 0);
|
|
644
660
|
__decorate([
|
|
645
661
|
property({ type: Boolean, reflect: true })
|
|
646
662
|
], SimFaceCapture.prototype, "embedded", void 0);
|
|
@@ -650,6 +666,15 @@ __decorate([
|
|
|
650
666
|
__decorate([
|
|
651
667
|
property({ type: String, attribute: 'confirm-label' })
|
|
652
668
|
], SimFaceCapture.prototype, "confirmLabel", void 0);
|
|
669
|
+
__decorate([
|
|
670
|
+
property({ type: String, attribute: 'capture-label' })
|
|
671
|
+
], SimFaceCapture.prototype, "captureLabel", void 0);
|
|
672
|
+
__decorate([
|
|
673
|
+
property({ type: String, attribute: 'retake-label' })
|
|
674
|
+
], SimFaceCapture.prototype, "retakeLabel", void 0);
|
|
675
|
+
__decorate([
|
|
676
|
+
property({ type: String, attribute: 'retry-label' })
|
|
677
|
+
], SimFaceCapture.prototype, "retryLabel", void 0);
|
|
653
678
|
__decorate([
|
|
654
679
|
property({ type: String, attribute: 'capture-preference' })
|
|
655
680
|
], SimFaceCapture.prototype, "capturePreference", void 0);
|