@smileid/web-components 11.4.3 → 11.4.5
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/dist/esm/{DocumentCaptureScreens-D1oMAv4n.js → DocumentCaptureScreens-D2G0NOQr.js} +4 -4
- package/dist/esm/{DocumentCaptureScreens-D1oMAv4n.js.map → DocumentCaptureScreens-D2G0NOQr.js.map} +1 -1
- package/dist/esm/{EndUserConsent-D26UoVk5.js → EndUserConsent-uHfA3txP.js} +3 -3
- package/dist/esm/{EndUserConsent-D26UoVk5.js.map → EndUserConsent-uHfA3txP.js.map} +1 -1
- package/dist/esm/{Navigation-nvehze1F.js → Navigation-Bb7MPLE8.js} +2 -2
- package/dist/esm/{Navigation-nvehze1F.js.map → Navigation-Bb7MPLE8.js.map} +1 -1
- package/dist/esm/{SelfieCaptureScreens-CC-y0CpT.js → SelfieCaptureScreens-Dr7VzON7.js} +2322 -2187
- package/dist/esm/SelfieCaptureScreens-Dr7VzON7.js.map +1 -0
- package/dist/esm/{TotpConsent-owUOdKzP.js → TotpConsent-Depzg0ti.js} +2 -2
- package/dist/esm/{TotpConsent-owUOdKzP.js.map → TotpConsent-Depzg0ti.js.map} +1 -1
- package/dist/esm/combobox.js +1 -1
- package/dist/esm/document.js +1 -1
- package/dist/esm/end-user-consent.js +1 -1
- package/dist/esm/{index-5Nn2kzHI.js → index-C4RTMbgw.js} +74 -74
- package/dist/esm/{index-5Nn2kzHI.js.map → index-C4RTMbgw.js.map} +1 -1
- package/dist/esm/localisation.js +1 -1
- package/dist/esm/main.js +6 -6
- package/dist/esm/navigation.js +1 -1
- package/dist/esm/{package-BxstV9r_.js → package-D6YrpMcO.js} +3 -3
- package/dist/esm/{package-BxstV9r_.js.map → package-D6YrpMcO.js.map} +1 -1
- package/dist/esm/selfie.js +1 -1
- package/dist/esm/smart-camera-web.js +9 -12
- package/dist/esm/smart-camera-web.js.map +1 -1
- package/dist/esm/totp-consent.js +1 -1
- package/dist/smart-camera-web.js +51 -51
- package/dist/smart-camera-web.js.map +1 -1
- package/dist/types/main.d.ts +2 -0
- package/lib/components/selfie/src/SelfieCaptureScreens.js +83 -0
- package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +275 -88
- package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +145 -9
- package/lib/components/signature-pad/package.json +1 -1
- package/lib/components/smart-camera-web/src/SmartCameraWeb.js +6 -4
- package/package.json +2 -2
- package/dist/esm/SelfieCaptureScreens-CC-y0CpT.js.map +0 -1
|
@@ -122,6 +122,62 @@ export class UnsupportedMediapipeEnvironmentError extends Error {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @description Thrown when `FaceLandmarker.createFromOptions` does not settle
|
|
127
|
+
* within {@link MEDIAPIPE_INIT_TIMEOUT_MS}. The WASM and model assets download
|
|
128
|
+
* over the network, but the subsequent WASM compile + GPU/CPU graph
|
|
129
|
+
* initialization runs on-device and can stall indefinitely on some drivers.
|
|
130
|
+
* Without a timeout the cached `loading` promise never resolves nor rejects,
|
|
131
|
+
* which (a) keeps the loading UI spinning until the wrapper's hard deadline and
|
|
132
|
+
* (b) poisons the singleton so retries/remounts await the same stuck promise.
|
|
133
|
+
* Treated as a transient failure by callers so the bounded retry can re-run.
|
|
134
|
+
*/
|
|
135
|
+
export class MediapipeInitTimeoutError extends Error {
|
|
136
|
+
constructor(message: string) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = 'MediapipeInitTimeoutError';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Last-resort hang guard for `createFromOptions`. This call also downloads the
|
|
143
|
+
// ~3MB model, so the budget must cover a slow/uncached fetch as well as the
|
|
144
|
+
// on-device init — hence it is deliberately generous. It exists only so a truly
|
|
145
|
+
// wedged init eventually rejects (letting the wrapper's bounded retry / hard
|
|
146
|
+
// deadline take over) rather than hanging forever; it is NOT used to decide the
|
|
147
|
+
// GPU→CPU fallback (that keys off real init errors — see getMediapipeInstance).
|
|
148
|
+
const MEDIAPIPE_INIT_TIMEOUT_MS = 45000;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @description Races a promise against a timeout. On timeout, rejects with a
|
|
152
|
+
* {@link MediapipeInitTimeoutError}. The underlying promise is not (and cannot
|
|
153
|
+
* be) aborted — we just stop awaiting it so callers can recover.
|
|
154
|
+
* @param {Promise<T>} promise The work to bound.
|
|
155
|
+
* @param {number} ms Timeout in milliseconds.
|
|
156
|
+
* @param {string} message Message for the timeout error.
|
|
157
|
+
* @returns {Promise<T>} Resolves/rejects with the promise, or rejects on timeout.
|
|
158
|
+
*/
|
|
159
|
+
const withTimeout = <T>(
|
|
160
|
+
promise: Promise<T>,
|
|
161
|
+
ms: number,
|
|
162
|
+
message: string,
|
|
163
|
+
): Promise<T> =>
|
|
164
|
+
new Promise<T>((resolve, reject) => {
|
|
165
|
+
const timeoutId = setTimeout(() => {
|
|
166
|
+
reject(new MediapipeInitTimeoutError(message));
|
|
167
|
+
}, ms);
|
|
168
|
+
|
|
169
|
+
promise.then(
|
|
170
|
+
(value) => {
|
|
171
|
+
clearTimeout(timeoutId);
|
|
172
|
+
resolve(value);
|
|
173
|
+
},
|
|
174
|
+
(error) => {
|
|
175
|
+
clearTimeout(timeoutId);
|
|
176
|
+
reject(error);
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
125
181
|
/**
|
|
126
182
|
* @description Reads system architecture hints from User-Agent Client Hints.
|
|
127
183
|
* @returns {Promise<string | null>} Lower-cased hint string or null when hints are unavailable.
|
|
@@ -207,6 +263,28 @@ const hasFP16Support = () => {
|
|
|
207
263
|
return !!(hasHalfFloatExt && hasColorBufferHalfFloat && hasHalfFloatLinear);
|
|
208
264
|
};
|
|
209
265
|
|
|
266
|
+
/**
|
|
267
|
+
* @description Creates a FaceLandmarker with the given compute delegate.
|
|
268
|
+
* Extracted so the init can be retried with a different delegate without
|
|
269
|
+
* duplicating the options.
|
|
270
|
+
* @param {Awaited<ReturnType<typeof FilesetResolver.forVisionTasks>>} vision Resolved WASM fileset.
|
|
271
|
+
* @param {'CPU' | 'GPU'} delegate Compute delegate to use.
|
|
272
|
+
* @returns {Promise<FaceLandmarker>} The created FaceLandmarker.
|
|
273
|
+
*/
|
|
274
|
+
const createLandmarker = (
|
|
275
|
+
vision: Awaited<ReturnType<typeof FilesetResolver.forVisionTasks>>,
|
|
276
|
+
delegate: 'CPU' | 'GPU',
|
|
277
|
+
): Promise<FaceLandmarker> =>
|
|
278
|
+
FaceLandmarker.createFromOptions(vision, {
|
|
279
|
+
baseOptions: {
|
|
280
|
+
modelAssetPath: `https://web-models.smileidentity.com/face_landmarker/face_landmarker.task`,
|
|
281
|
+
delegate,
|
|
282
|
+
},
|
|
283
|
+
outputFaceBlendshapes: true,
|
|
284
|
+
runningMode: 'VIDEO',
|
|
285
|
+
numFaces: 2,
|
|
286
|
+
});
|
|
287
|
+
|
|
210
288
|
export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
|
|
211
289
|
if (!window.__smileIdentityMediapipe) {
|
|
212
290
|
window.__smileIdentityMediapipe = {
|
|
@@ -249,15 +327,69 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
|
|
|
249
327
|
const delegate =
|
|
250
328
|
gpuDelegate === 'CPU' || !hasFP16Support() ? 'CPU' : 'GPU';
|
|
251
329
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
330
|
+
// A GPU-delegate init can throw on some drivers (WebGL context/shader
|
|
331
|
+
// failures) even though the model already downloaded; on a genuine GPU
|
|
332
|
+
// *error* we retry once on CPU before giving up. We deliberately do NOT
|
|
333
|
+
// fall back on a timeout: the timeout also covers the model download, so
|
|
334
|
+
// a slow fetch could trip it while a healthy GPU init is still in
|
|
335
|
+
// progress — abandoning it to start a redundant CPU init only makes
|
|
336
|
+
// things worse. A timeout therefore propagates as a transient failure for
|
|
337
|
+
// the wrapper's bounded retry / hard deadline to handle.
|
|
338
|
+
//
|
|
339
|
+
// Orphan handling: when `withTimeout` fires, the underlying
|
|
340
|
+
// `createLandmarker` promise is still in flight (it cannot be aborted).
|
|
341
|
+
// If it eventually resolves, the resulting `FaceLandmarker` would leak a
|
|
342
|
+
// GPU/WebGL context, so we attach a best-effort `.close()` cleanup to the
|
|
343
|
+
// original promise reference for both the GPU and CPU init paths.
|
|
344
|
+
const closeOrphan = (orphan: FaceLandmarker) => {
|
|
345
|
+
try {
|
|
346
|
+
orphan.close();
|
|
347
|
+
} catch {
|
|
348
|
+
/* best effort */
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
let faceLandmarker: FaceLandmarker;
|
|
353
|
+
const initPromise = createLandmarker(vision, delegate);
|
|
354
|
+
try {
|
|
355
|
+
faceLandmarker = await withTimeout(
|
|
356
|
+
initPromise,
|
|
357
|
+
MEDIAPIPE_INIT_TIMEOUT_MS,
|
|
358
|
+
`MediaPipe initialization timed out after ${MEDIAPIPE_INIT_TIMEOUT_MS}ms (delegate: ${delegate}).`,
|
|
359
|
+
);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const isTimeout = error instanceof MediapipeInitTimeoutError;
|
|
362
|
+
if (isTimeout) {
|
|
363
|
+
// Stop awaiting the in-flight init, but if it eventually resolves,
|
|
364
|
+
// close the orphaned instance to avoid leaking a GPU/WebGL context.
|
|
365
|
+
initPromise.then(closeOrphan, () => {
|
|
366
|
+
/* already failed; nothing to clean up */
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
if (delegate === 'GPU' && !isTimeout) {
|
|
370
|
+
console.warn(
|
|
371
|
+
'[SmileID] GPU MediaPipe init failed; retrying with CPU delegate.',
|
|
372
|
+
error,
|
|
373
|
+
);
|
|
374
|
+
const cpuInitPromise = createLandmarker(vision, 'CPU');
|
|
375
|
+
try {
|
|
376
|
+
faceLandmarker = await withTimeout(
|
|
377
|
+
cpuInitPromise,
|
|
378
|
+
MEDIAPIPE_INIT_TIMEOUT_MS,
|
|
379
|
+
`MediaPipe CPU initialization timed out after ${MEDIAPIPE_INIT_TIMEOUT_MS}ms.`,
|
|
380
|
+
);
|
|
381
|
+
} catch (cpuError) {
|
|
382
|
+
if (cpuError instanceof MediapipeInitTimeoutError) {
|
|
383
|
+
cpuInitPromise.then(closeOrphan, () => {
|
|
384
|
+
/* already failed; nothing to clean up */
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
throw cpuError;
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
261
393
|
|
|
262
394
|
mediapipeGlobal.instance = faceLandmarker;
|
|
263
395
|
mediapipeGlobal.loaded = true;
|
|
@@ -265,6 +397,8 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
|
|
|
265
397
|
|
|
266
398
|
return faceLandmarker;
|
|
267
399
|
} catch (error) {
|
|
400
|
+
// Always clear the poisoned promise so the wrapper's bounded retry — and
|
|
401
|
+
// any later remount — can re-attempt instead of awaiting a dead promise.
|
|
268
402
|
mediapipeGlobal.loading = null;
|
|
269
403
|
throw error;
|
|
270
404
|
}
|
|
@@ -277,4 +411,6 @@ export const __testUtils = {
|
|
|
277
411
|
matchesExcludedGpu,
|
|
278
412
|
getDelegateFromGpuDetection,
|
|
279
413
|
supportsWasmReftypes,
|
|
414
|
+
withTimeout,
|
|
415
|
+
MEDIAPIPE_INIT_TIMEOUT_MS,
|
|
280
416
|
};
|
|
@@ -166,11 +166,13 @@ class SmartCameraWeb extends HTMLElement {
|
|
|
166
166
|
this.documentCapture.addEventListener(
|
|
167
167
|
'document-capture-screens.cancelled',
|
|
168
168
|
() => {
|
|
169
|
-
this.SelfieCaptureScreens.setAttribute(
|
|
170
|
-
'initial-screen',
|
|
171
|
-
'selfie-capture',
|
|
172
|
-
);
|
|
173
169
|
this.setActiveScreen(this.SelfieCaptureScreens);
|
|
170
|
+
// Land on a clean selfie capture screen by driving the navigation
|
|
171
|
+
// explicitly. Previously this set `initial-screen="selfie-capture"`,
|
|
172
|
+
// whose side effect was a full SelfieCaptureScreens rebuild — re-fired
|
|
173
|
+
// on every back-navigation (setAttribute invokes attributeChangedCallback
|
|
174
|
+
// even when the value is unchanged).
|
|
175
|
+
this.SelfieCaptureScreens.restartSelfieCapture();
|
|
174
176
|
this.SelfieCaptureScreens.removeAttribute('data-camera-error');
|
|
175
177
|
this.SelfieCaptureScreens.setAttribute('data-camera-ready', true);
|
|
176
178
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/web-components",
|
|
3
|
-
"version": "11.4.
|
|
3
|
+
"version": "11.4.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "dist/esm/main.js",
|
|
6
6
|
"module": "dist/esm/main.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
106
106
|
"@typescript-eslint/parser": "^8.49.0",
|
|
107
107
|
"cross-env": "^7.0.3",
|
|
108
|
-
"cypress": "^13.
|
|
108
|
+
"cypress": "^13.17.0",
|
|
109
109
|
"eslint": "^8.57.0",
|
|
110
110
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
111
111
|
"eslint-config-prettier": "^9.1.0",
|