@smileid/web-components 11.3.0 → 11.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/esm/{DocumentCaptureScreens-N-0o7eE5.js → DocumentCaptureScreens-DbU8ZxMx.js} +4 -4
  2. package/dist/esm/{DocumentCaptureScreens-N-0o7eE5.js.map → DocumentCaptureScreens-DbU8ZxMx.js.map} +1 -1
  3. package/dist/esm/{EndUserConsent-BgKCCMMn.js → EndUserConsent-BGO3oZ-m.js} +3 -3
  4. package/dist/esm/{EndUserConsent-BgKCCMMn.js.map → EndUserConsent-BGO3oZ-m.js.map} +1 -1
  5. package/dist/esm/{Navigation-DOFYqTZt.js → Navigation-DH44dkMT.js} +2 -2
  6. package/dist/esm/{Navigation-DOFYqTZt.js.map → Navigation-DH44dkMT.js.map} +1 -1
  7. package/dist/esm/{SelfieCaptureScreens-DKd0f7K8.js → SelfieCaptureScreens-bmwnmeS9.js} +2169 -2142
  8. package/dist/esm/SelfieCaptureScreens-bmwnmeS9.js.map +1 -0
  9. package/dist/esm/{TotpConsent-BQm8j4-u.js → TotpConsent-V3_Ip2Kw.js} +2 -2
  10. package/dist/esm/{TotpConsent-BQm8j4-u.js.map → TotpConsent-V3_Ip2Kw.js.map} +1 -1
  11. package/dist/esm/combobox.js +1 -1
  12. package/dist/esm/document.js +1 -1
  13. package/dist/esm/end-user-consent.js +1 -1
  14. package/dist/esm/{index-Cjzs1eA_.js → index-Dnpp-kwk.js} +41 -41
  15. package/dist/esm/{index-Cjzs1eA_.js.map → index-Dnpp-kwk.js.map} +1 -1
  16. package/dist/esm/localisation.js +1 -1
  17. package/dist/esm/main.js +6 -6
  18. package/dist/esm/navigation.js +1 -1
  19. package/dist/esm/{package-KQ2l43v1.js → package-7J5h4EOW.js} +3 -3
  20. package/dist/esm/{package-KQ2l43v1.js.map → package-7J5h4EOW.js.map} +1 -1
  21. package/dist/esm/selfie.js +1 -1
  22. package/dist/esm/smart-camera-web.js +5 -5
  23. package/dist/esm/totp-consent.js +1 -1
  24. package/dist/smart-camera-web.js +38 -38
  25. package/dist/smart-camera-web.js.map +1 -1
  26. package/dist/types/document-auto-capture.d.ts +34 -0
  27. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +3 -15
  28. package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +6 -10
  29. package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +7 -1
  30. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +33 -19
  31. package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +0 -1
  32. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +127 -30
  33. package/lib/components/signature-pad/package-lock.json +15 -12
  34. package/lib/components/signature-pad/package.json +1 -1
  35. package/package.json +2 -1
  36. package/dist/esm/SelfieCaptureScreens-DKd0f7K8.js.map +0 -1
@@ -0,0 +1,34 @@
1
+ import { FunctionComponent } from 'preact';
2
+
3
+ declare const SmartDocumentCapture: FunctionComponent<SmartDocumentCaptureProps>;
4
+ export default SmartDocumentCapture;
5
+
6
+ declare interface SmartDocumentCaptureProps {
7
+ 'document-type'?: string;
8
+ 'side-of-id'?: string;
9
+ 'show-navigation'?: string | boolean;
10
+ 'theme-color'?: string;
11
+ 'hide-attribution'?: string | boolean;
12
+ }
13
+
14
+ export { }
15
+
16
+
17
+
18
+ declare module 'signature_pad' {
19
+ export default class SignaturePad {
20
+ constructor(canvas: HTMLCanvasElement, options?: any);
21
+
22
+ clear(): void;
23
+
24
+ toDataURL(type?: string): string;
25
+
26
+ fromDataURL(dataURL: string): void;
27
+
28
+ isEmpty(): boolean;
29
+
30
+ on(event: string, callback: (...args: unknown[]) => void): void;
31
+
32
+ off(event: string, callback: (...args: unknown[]) => void): void;
33
+ }
34
+ }
@@ -1,8 +1,6 @@
1
1
  import { useRef, useEffect } from 'preact/hooks';
2
- import { useSignal } from '@preact/signals';
3
2
  import register from 'preact-custom-element';
4
3
  import type { FunctionComponent } from 'preact';
5
- import throttle from 'lodash/throttle';
6
4
 
7
5
  import { getBoolProp } from '../../../../utils/props';
8
6
  import { useFaceCapture, useCamera } from './hooks';
@@ -50,13 +48,6 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
50
48
  const initialFacingMode = allowAgentMode ? 'environment' : 'user';
51
49
  const camera = useCamera(initialFacingMode);
52
50
 
53
- const throttledMultipleFaces = useSignal(false);
54
- const updateMultipleFacesUI = useRef(
55
- throttle((value: boolean) => {
56
- throttledMultipleFaces.value = value;
57
- }, 100),
58
- ).current;
59
-
60
51
  const faceCapture = useFaceCapture({
61
52
  videoRef: camera.videoRef,
62
53
  canvasRef,
@@ -106,14 +97,9 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
106
97
  faceCapture.stopDetectionLoop();
107
98
  camera.stopCamera();
108
99
  faceCapture.cleanup();
109
- updateMultipleFacesUI.cancel();
110
100
  };
111
101
  }, []);
112
102
 
113
- useEffect(() => {
114
- updateMultipleFacesUI(faceCapture.multipleFaces.value);
115
- }, [faceCapture.multipleFaces.value]);
116
-
117
103
  useEffect(() => {
118
104
  const navigation = navigationRef.current;
119
105
 
@@ -160,7 +146,6 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
160
146
  videoRef={camera.videoRef}
161
147
  canvasRef={canvasRef}
162
148
  facingMode={camera.facingMode}
163
- multipleFaces={throttledMultipleFaces.value}
164
149
  progress={
165
150
  faceCapture.capturesTaken.value > 0
166
151
  ? faceCapture.capturesTaken.value / faceCapture.totalCaptures.value
@@ -178,6 +163,9 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
178
163
  isCapturing={faceCapture.isCapturing.value}
179
164
  hasFinishedCapture={faceCapture.hasFinishedCapture.value}
180
165
  isReadyToCapture={faceCapture.isReadyToCapture.value}
166
+ captureButtonFallbackEnabled={
167
+ faceCapture.captureButtonFallbackEnabled.value
168
+ }
181
169
  allowAgentMode={allowAgentMode}
182
170
  agentSupported={camera.agentSupported}
183
171
  showAgentModeForTests={showAgentModeForTests}
@@ -5,7 +5,6 @@ interface CameraPreviewProps {
5
5
  videoRef: Ref<HTMLVideoElement>;
6
6
  canvasRef: Ref<HTMLCanvasElement>;
7
7
  facingMode: 'user' | 'environment';
8
- multipleFaces: boolean;
9
8
  progress: number;
10
9
  interval: number;
11
10
  themeColor: string;
@@ -15,7 +14,6 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
15
14
  videoRef,
16
15
  canvasRef,
17
16
  facingMode,
18
- multipleFaces,
19
17
  progress,
20
18
  interval,
21
19
  themeColor,
@@ -25,7 +23,7 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
25
23
  <div
26
24
  className="video-wrapper"
27
25
  style={{
28
- clipPath: multipleFaces ? 'none' : 'url(#selfie-clip-path)',
26
+ clipPath: 'url(#selfie-clip-path)',
29
27
  }}
30
28
  >
31
29
  <div className="video-container">
@@ -42,13 +40,11 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
42
40
  />
43
41
  </div>
44
42
  </div>
45
- {!multipleFaces && (
46
- <OvalProgress
47
- progress={progress}
48
- duration={interval}
49
- themeColor={themeColor}
50
- />
51
- )}
43
+ <OvalProgress
44
+ progress={progress}
45
+ duration={interval}
46
+ themeColor={themeColor}
47
+ />
52
48
  </div>
53
49
 
54
50
  <style>{`
@@ -5,6 +5,7 @@ interface CaptureControlsProps {
5
5
  isCapturing: boolean;
6
6
  hasFinishedCapture: boolean;
7
7
  isReadyToCapture: boolean;
8
+ captureButtonFallbackEnabled: boolean;
8
9
  allowAgentMode: boolean;
9
10
  agentSupported: boolean;
10
11
  showAgentModeForTests: boolean;
@@ -18,6 +19,7 @@ export const CaptureControls: FunctionComponent<CaptureControlsProps> = ({
18
19
  isCapturing,
19
20
  hasFinishedCapture,
20
21
  isReadyToCapture,
22
+ captureButtonFallbackEnabled,
21
23
  allowAgentMode,
22
24
  agentSupported,
23
25
  showAgentModeForTests,
@@ -32,7 +34,11 @@ export const CaptureControls: FunctionComponent<CaptureControlsProps> = ({
32
34
  id="start-image-capture"
33
35
  class="btn-primary"
34
36
  onClick={onStartCapture}
35
- disabled={isCapturing || hasFinishedCapture || !isReadyToCapture}
37
+ disabled={
38
+ isCapturing ||
39
+ hasFinishedCapture ||
40
+ (!isReadyToCapture && !captureButtonFallbackEnabled)
41
+ }
36
42
  >
37
43
  {t('selfie.capture.button.startCapture')}
38
44
  </button>
@@ -51,11 +51,11 @@ export const useFaceCapture = ({
51
51
  const animationFrameRef = useRef<number | null>(null);
52
52
  const captureTimerRef = useRef<NodeJS.Timeout | null>(null);
53
53
  const resumeCaptureRef = useRef<(() => void) | null>(null);
54
+ const fallbackTimerRef = useRef<NodeJS.Timeout | null>(null);
54
55
 
55
56
  const faceDetected = useSignal(false);
56
57
  const faceInBounds = useSignal(false);
57
58
  const faceProximity = useSignal<'too-close' | 'too-far' | 'good'>('good');
58
- const multipleFaces = useSignal(false);
59
59
  const videoAspectRatio = useSignal(16 / 9);
60
60
  const faceLandmarks = useSignal<any[]>([]);
61
61
  const currentSmileScore = useSignal(0);
@@ -64,6 +64,7 @@ export const useFaceCapture = ({
64
64
  const lastSmileTime = useSignal(0);
65
65
  const alertTitle = useSignal('');
66
66
  const isInitializing = useSignal(true);
67
+ const captureButtonFallbackEnabled = useSignal(false);
67
68
 
68
69
  const isCapturing = useSignal(false);
69
70
  const isPaused = useSignal(false);
@@ -83,8 +84,7 @@ export const useFaceCapture = ({
83
84
  () =>
84
85
  faceDetected.value &&
85
86
  faceInBounds.value &&
86
- faceProximity.value === 'good' &&
87
- !multipleFaces.value,
87
+ faceProximity.value === 'good',
88
88
  );
89
89
 
90
90
  const updateAlertImmediate = (messageKey: MessageKey | null) => {
@@ -101,6 +101,19 @@ export const useFaceCapture = ({
101
101
  }, 600),
102
102
  ).current;
103
103
 
104
+ const CAPTURE_FALLBACK_TIMEOUT_MS = 10000;
105
+
106
+ const startFallbackTimer = () => {
107
+ if (fallbackTimerRef.current) {
108
+ clearTimeout(fallbackTimerRef.current);
109
+ }
110
+ fallbackTimerRef.current = setTimeout(() => {
111
+ if (!isReadyToCapture.value) {
112
+ captureButtonFallbackEnabled.value = true;
113
+ }
114
+ }, CAPTURE_FALLBACK_TIMEOUT_MS);
115
+ };
116
+
104
117
  const initializeFaceLandmarker = async () => {
105
118
  try {
106
119
  const isAlreadyLoaded =
@@ -114,10 +127,15 @@ export const useFaceCapture = ({
114
127
 
115
128
  faceLandmarkerRef.current = await getMediapipeInstance();
116
129
  isInitializing.value = false;
130
+ startFallbackTimer();
117
131
  } catch (error) {
118
132
  console.error('Failed to initialize MediaPipe:', error);
119
133
  isInitializing.value = false;
134
+ // MediaPipe failed — start the fallback timer so the button eventually
135
+ // enables and the user isn't permanently stuck.
136
+ startFallbackTimer();
120
137
  }
138
+ startFallbackTimer();
121
139
  };
122
140
 
123
141
  const setupCanvas = () => {
@@ -165,8 +183,6 @@ export const useFaceCapture = ({
165
183
  const updateAlerts = () => {
166
184
  if (isInitializing.value) {
167
185
  updateAlertImmediate('initializing');
168
- } else if (multipleFaces.value) {
169
- updateAlert('multiple-faces');
170
186
  } else if (!faceDetected.value) {
171
187
  updateAlert('no-face');
172
188
  } else if (faceProximity.value === 'too-close') {
@@ -260,13 +276,12 @@ export const useFaceCapture = ({
260
276
 
261
277
  // Check number of faces
262
278
  const numFaces = results.faceLandmarks ? results.faceLandmarks.length : 0;
263
- multipleFaces.value = numFaces > 1;
264
279
 
265
280
  // Check if face is detected
266
281
  const hasFace =
267
282
  results.faceBlendshapes &&
268
283
  results.faceBlendshapes.length > 0 &&
269
- numFaces === 1;
284
+ numFaces >= 1;
270
285
  faceDetected.value = hasFace;
271
286
 
272
287
  if (hasFace && results.faceLandmarks) {
@@ -322,7 +337,7 @@ export const useFaceCapture = ({
322
337
  }
323
338
  }
324
339
  } else {
325
- // No face detected or multiple faces - reset values
340
+ // No face detected - reset values
326
341
  currentSmileScore.value = 0;
327
342
  currentFaceSize.value = 0;
328
343
  currentMouthOpen.value = 0;
@@ -334,7 +349,6 @@ export const useFaceCapture = ({
334
349
  } catch {
335
350
  faceDetected.value = false;
336
351
  faceInBounds.value = false;
337
- multipleFaces.value = false;
338
352
  faceProximity.value = 'good';
339
353
  currentMouthOpen.value = 0;
340
354
 
@@ -417,7 +431,6 @@ export const useFaceCapture = ({
417
431
  isPaused.value = true;
418
432
 
419
433
  if (
420
- !multipleFaces.value &&
421
434
  faceDetected.value &&
422
435
  faceInBounds.value &&
423
436
  faceProximity.value === 'good'
@@ -437,11 +450,6 @@ export const useFaceCapture = ({
437
450
  return;
438
451
  }
439
452
 
440
- if (multipleFaces.value) {
441
- pauseCapture();
442
- return;
443
- }
444
-
445
453
  if (!faceDetected.value) {
446
454
  return;
447
455
  }
@@ -474,8 +482,7 @@ export const useFaceCapture = ({
474
482
  if (
475
483
  faceDetected.value &&
476
484
  faceProximity.value === 'good' &&
477
- faceInBounds.value &&
478
- !multipleFaces.value
485
+ faceInBounds.value
479
486
  ) {
480
487
  const isInSmileZone = capturesTaken.value >= smileCheckpoint.value;
481
488
  if (isInSmileZone) {
@@ -556,6 +563,9 @@ export const useFaceCapture = ({
556
563
  if (captureTimerRef.current) {
557
564
  clearInterval(captureTimerRef.current);
558
565
  }
566
+ if (fallbackTimerRef.current) {
567
+ clearTimeout(fallbackTimerRef.current);
568
+ }
559
569
  stopDetectionLoop();
560
570
  updateAlert.cancel();
561
571
  };
@@ -564,12 +574,16 @@ export const useFaceCapture = ({
564
574
  faceDetected.value = false;
565
575
  faceInBounds.value = false;
566
576
  faceProximity.value = 'good';
567
- multipleFaces.value = false;
568
577
  faceLandmarks.value = [];
569
578
  currentSmileScore.value = 0;
570
579
  currentFaceSize.value = 0;
571
580
  currentMouthOpen.value = 0;
572
581
  lastSmileTime.value = 0;
582
+ captureButtonFallbackEnabled.value = false;
583
+ if (fallbackTimerRef.current) {
584
+ clearTimeout(fallbackTimerRef.current);
585
+ fallbackTimerRef.current = null;
586
+ }
573
587
 
574
588
  if (canvasRef.current) {
575
589
  clearCanvas(canvasRef.current);
@@ -580,7 +594,6 @@ export const useFaceCapture = ({
580
594
  faceDetected,
581
595
  faceInBounds,
582
596
  faceProximity,
583
- multipleFaces,
584
597
  videoAspectRatio,
585
598
  faceLandmarks,
586
599
  currentSmileScore,
@@ -590,6 +603,7 @@ export const useFaceCapture = ({
590
603
  alertTitle,
591
604
  isInitializing,
592
605
  isReadyToCapture,
606
+ captureButtonFallbackEnabled,
593
607
 
594
608
  isCapturing,
595
609
  isPaused,
@@ -1,7 +1,6 @@
1
1
  import { t } from '../../../../../domain/localisation';
2
2
 
3
3
  export const MESSAGES = {
4
- 'multiple-faces': () => t('selfie.smart.alert.multipleFaces'),
5
4
  'no-face': () => t('selfie.smart.alert.noFace'),
6
5
  'out-of-bounds': () => t('selfie.smart.alert.outOfBounds'),
7
6
  'too-close': () => t('selfie.smart.alert.tooClose'),
@@ -1,7 +1,65 @@
1
1
  import { FaceLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
2
2
 
3
- // SM-S931 (for the standard S25), SM-S936 (for the S25+), and SM-S938 (for the S25 Ultra)
4
- const EXCLUDED_DEVICES = ['sm-s936', 'sm-s931', 'sm-s938'];
3
+ const EXCLUDED_GPUS = [
4
+ 'adreno-830',
5
+ 'adreno-8xx',
6
+ 'adreno-9xx',
7
+ 'adreno-840',
8
+ 'adreno-810',
9
+ ];
10
+
11
+ const normalizeGpuText = (value: string): string =>
12
+ value
13
+ .toLowerCase()
14
+ .replace(/\(tm\)|\btm\b/g, '')
15
+ .replace(/[^a-z0-9]/g, '');
16
+
17
+ const matchesExcludedGpu = (value: string): boolean => {
18
+ const normalizedValue = normalizeGpuText(value);
19
+
20
+ return EXCLUDED_GPUS.some((gpuPattern) => {
21
+ const normalizedPattern = normalizeGpuText(gpuPattern);
22
+
23
+ if (normalizedPattern.endsWith('xx')) {
24
+ const familyPrefix = normalizedPattern.slice(0, -2);
25
+ return new RegExp(`${familyPrefix}\\d{2}`).test(normalizedValue);
26
+ }
27
+
28
+ return normalizedValue.includes(normalizedPattern);
29
+ });
30
+ };
31
+
32
+ /**
33
+ * @description Gets the GPU renderer string using WebGL debug info extension.
34
+ * @returns {string | null} The GPU renderer string or null if unavailable.
35
+ */
36
+ const getGpuRenderer = (): string | null => {
37
+ try {
38
+ const canvas = document.createElement('canvas');
39
+ const gl =
40
+ canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
41
+ if (!gl || !(gl instanceof WebGLRenderingContext)) return null;
42
+
43
+ const ext = gl.getExtension('WEBGL_debug_renderer_info');
44
+ if (!ext) return null;
45
+
46
+ return gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) as string | null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ };
51
+
52
+ /**
53
+ * @description Checks if the GPU renderer matches any excluded GPU.
54
+ * @param {string | null} [renderer] Optional GPU renderer string to use. If not provided, it will be fetched via WebGL.
55
+ * @returns {boolean} True if the GPU is excluded.
56
+ */
57
+ const isExcludedGpuFromWebGL = (renderer?: string | null): boolean => {
58
+ const rendererString = (renderer ?? getGpuRenderer())?.toLowerCase() ?? '';
59
+ if (!rendererString) return false;
60
+
61
+ return matchesExcludedGpu(rendererString);
62
+ };
5
63
 
6
64
  declare global {
7
65
  interface Window {
@@ -14,38 +72,70 @@ declare global {
14
72
  }
15
73
 
16
74
  /**
17
- * @description Detects if the user is on an excluded device using the modern and more accurate
18
- * User-Agent Client Hints (UA-CH) API to get the device model.
19
- * @returns {Promise<boolean>} - True if the device model is in the exclusion list.
75
+ * @description Reads system architecture hints from User-Agent Client Hints.
76
+ * @returns {Promise<string | null>} Lower-cased hint string or null when hints are unavailable.
20
77
  */
21
- const isExcludedDeviceUsingHints = async (): Promise<boolean> => {
22
- // Check for User-Agent Client Hints API support
23
- if (typeof navigator !== 'undefined' && navigator.userAgentData) {
24
- try {
25
- // Request the 'model' high-entropy value and destructure it directly
26
- const { model } = await navigator.userAgentData.getHighEntropyValues([
27
- 'model',
28
- ]);
78
+ const getSystemArchitectureHints = async (): Promise<string | null> => {
79
+ if (typeof navigator === 'undefined' || !(navigator as any).userAgentData) {
80
+ return null;
81
+ }
29
82
 
30
- if (!model) {
31
- return false;
32
- }
83
+ try {
84
+ const hints = await (navigator as any).userAgentData.getHighEntropyValues([
85
+ 'architecture',
86
+ 'model',
87
+ 'platform',
88
+ 'platformVersion',
89
+ 'fullVersionList',
90
+ ]);
91
+
92
+ return JSON.stringify(hints).toLowerCase();
93
+ } catch (error) {
94
+ console.warn('UA-CH architecture hints fetch failed.', error);
95
+ return null;
96
+ }
97
+ };
98
+
99
+ /**
100
+ * @description Determines the MediaPipe delegate based on WebGL renderer info and UA-CH hints.
101
+ * Uses WebGL renderer as primary detection, UA-CH hints as secondary.
102
+ * @returns {Promise<'CPU' | 'GPU'>} CPU when excluded GPU is detected; otherwise GPU.
103
+ */
104
+ const getDelegateFromGpuDetection = async (): Promise<'CPU' | 'GPU'> => {
105
+ const renderer = getGpuRenderer();
106
+ const hasWebGlRendererInfo = !!renderer;
107
+
108
+ // Primary check: WebGL renderer info (most reliable for GPU detection)
109
+ if (isExcludedGpuFromWebGL(renderer)) {
110
+ console.info(`[SmileID] Excluded GPU via WebGL: ${renderer}. Using CPU.`);
111
+ return 'CPU';
112
+ }
33
113
 
34
- const lowerModel = model.toLowerCase();
114
+ // Secondary check: UA-CH hints (may contain GPU info in some browsers)
115
+ const hintString = await getSystemArchitectureHints();
116
+ const hasUaHints = !!hintString;
35
117
 
36
- // Check if the extracted model string matches any of the excluded prefixes
37
- return EXCLUDED_DEVICES.some((prefix) => lowerModel.includes(prefix));
38
- } catch (error) {
39
- // Log the error but fail safe (return false)
40
- console.warn(
41
- 'UA-CH model fetch failed, falling back to UA string check.',
42
- error,
43
- );
44
- return false;
118
+ if (hintString) {
119
+ const hasExcludedGpuInHints = matchesExcludedGpu(hintString);
120
+
121
+ if (hasExcludedGpuInHints) {
122
+ console.info(`[SmileID] Excluded GPU via UA-CH hints. Using CPU.`);
123
+ return 'CPU';
45
124
  }
46
125
  }
47
- // If API is not supported, return false (rely on synchronous isExcludedDevice)
48
- return false;
126
+
127
+ if (!hasWebGlRendererInfo && !hasUaHints) {
128
+ console.info(
129
+ '[SmileID] No WebGL renderer or UA-CH hints available. Using CPU.',
130
+ );
131
+ return 'CPU';
132
+ }
133
+
134
+ // Default to GPU when no exclusion is detected
135
+ console.info(
136
+ `[SmileID] No excluded GPU detected. WebGL renderer: ${renderer ?? 'unavailable'}. Using GPU.`,
137
+ );
138
+ return 'GPU';
49
139
  };
50
140
 
51
141
  // this was added because devices (mostly older) that do not support FP16 will fail to load the model.
@@ -91,12 +181,14 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
91
181
  'https://web-models.smileidentity.com/mediapipe-tasks-vision-wasm',
92
182
  );
93
183
 
94
- const isExcluded = await isExcludedDeviceUsingHints();
184
+ const gpuDelegate = await getDelegateFromGpuDetection();
185
+ const delegate =
186
+ gpuDelegate === 'CPU' || !hasFP16Support() ? 'CPU' : 'GPU';
95
187
 
96
188
  const faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
97
189
  baseOptions: {
98
190
  modelAssetPath: `https://web-models.smileidentity.com/face_landmarker/face_landmarker.task`,
99
- delegate: isExcluded || !hasFP16Support() ? 'CPU' : 'GPU',
191
+ delegate,
100
192
  },
101
193
  outputFaceBlendshapes: true,
102
194
  runningMode: 'VIDEO',
@@ -116,3 +208,8 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
116
208
 
117
209
  return mediapipeGlobal.loading;
118
210
  };
211
+
212
+ export const __testUtils = {
213
+ matchesExcludedGpu,
214
+ getDelegateFromGpuDetection,
215
+ };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@smileid/signature-pad",
3
- "version": "11.0.2",
3
+ "version": "11.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@smileid/signature-pad",
9
- "version": "11.0.2",
9
+ "version": "11.3.0",
10
10
  "dependencies": {
11
11
  "signature_pad": "^5.0.2"
12
12
  },
@@ -224,21 +224,23 @@
224
224
  }
225
225
  },
226
226
  "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
227
- "version": "2.0.1",
228
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
229
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
227
+ "version": "2.0.2",
228
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
229
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
230
230
  "dev": true,
231
+ "license": "MIT",
231
232
  "dependencies": {
232
233
  "balanced-match": "^1.0.0"
233
234
  }
234
235
  },
235
236
  "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
236
- "version": "9.0.5",
237
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
238
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
237
+ "version": "9.0.9",
238
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
239
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
239
240
  "dev": true,
241
+ "license": "ISC",
240
242
  "dependencies": {
241
- "brace-expansion": "^2.0.1"
243
+ "brace-expansion": "^2.0.2"
242
244
  },
243
245
  "engines": {
244
246
  "node": ">=16 || 14 >=14.17"
@@ -2073,10 +2075,11 @@
2073
2075
  }
2074
2076
  },
2075
2077
  "node_modules/minimatch": {
2076
- "version": "3.1.2",
2077
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2078
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2078
+ "version": "3.1.5",
2079
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
2080
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
2079
2081
  "dev": true,
2082
+ "license": "ISC",
2080
2083
  "dependencies": {
2081
2084
  "brace-expansion": "^1.1.7"
2082
2085
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/signature-pad",
3
- "version": "11.3.0",
3
+ "version": "11.4.0",
4
4
  "private": "true",
5
5
  "exports": {
6
6
  ".": "./index.js"
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@smileid/web-components",
3
- "version": "11.3.0",
3
+ "version": "11.4.0",
4
+ "private": false,
4
5
  "main": "dist/esm/main.js",
5
6
  "module": "dist/esm/main.js",
6
7
  "types": "dist/types/main.d.ts",