@smileid/web-components 11.4.4 → 11.5.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 (84) hide show
  1. package/dist/esm/{DocumentCaptureScreens-bLFW-yEM.js → DocumentCaptureScreens-ucJDu5nH.js} +555 -2470
  2. package/dist/esm/DocumentCaptureScreens-ucJDu5nH.js.map +1 -0
  3. package/dist/esm/{EndUserConsent-D26UoVk5.js → EndUserConsent-CsiwoThZ.js} +3 -3
  4. package/dist/esm/{EndUserConsent-D26UoVk5.js.map → EndUserConsent-CsiwoThZ.js.map} +1 -1
  5. package/dist/esm/{Navigation-nvehze1F.js → Navigation-Xg565kcu.js} +28 -22
  6. package/dist/esm/Navigation-Xg565kcu.js.map +1 -0
  7. package/dist/esm/SelfieCaptureScreens-D3KuMzZA.js +11471 -0
  8. package/dist/esm/SelfieCaptureScreens-D3KuMzZA.js.map +1 -0
  9. package/dist/esm/{TotpConsent-owUOdKzP.js → TotpConsent-CRtmtudl.js} +2 -2
  10. package/dist/esm/{TotpConsent-owUOdKzP.js.map → TotpConsent-CRtmtudl.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-CUwa6MPI.js +1363 -0
  15. package/dist/esm/{index-5Nn2kzHI.js.map → index-CUwa6MPI.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-BmVbDNny.js +2535 -0
  20. package/dist/esm/package-BmVbDNny.js.map +1 -0
  21. package/dist/esm/selfie.js +1 -1
  22. package/dist/esm/smart-camera-web.js +67 -40
  23. package/dist/esm/smart-camera-web.js.map +1 -1
  24. package/dist/esm/totp-consent.js +1 -1
  25. package/dist/smart-camera-web.js +877 -122
  26. package/dist/smart-camera-web.js.map +1 -1
  27. package/dist/types/main.d.ts +13 -0
  28. package/lib/components/navigation/src/Navigation.js +27 -8
  29. package/lib/components/selfie/src/SelfieCaptureScreens.js +139 -8
  30. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieCapture.tsx +684 -0
  31. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieConsent.tsx +71 -0
  32. package/lib/components/selfie/src/enhanced-smartselfie-capture/EnhancedSmartSelfieSubmission.tsx +181 -0
  33. package/lib/components/selfie/src/enhanced-smartselfie-capture/OvalProgress.tsx +87 -0
  34. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/Icon.svg +8 -0
  35. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/accessories.svg +77 -0
  36. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/active_liveness_animation.lottie +0 -0
  37. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device.svg +12 -0
  38. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/device_orientation.lottie +0 -0
  39. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/good.svg +52 -0
  40. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/id-card.svg +9 -0
  41. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/illustrations.tsx +852 -0
  42. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/instructions-img.svg +3 -0
  43. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/multiple-faces.svg +69 -0
  44. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/person.svg +6 -0
  45. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/phone.svg +8 -0
  46. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/poor-lighting.svg +53 -0
  47. package/lib/components/selfie/src/enhanced-smartselfie-capture/assets/too_dark_animation.lottie +0 -0
  48. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ActiveLivenessOverlay.tsx +226 -0
  49. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/AlertDisplay.tsx +38 -0
  50. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/BackNavigation.tsx +45 -0
  51. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CameraPreview.tsx +96 -0
  52. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureControls.tsx +97 -0
  53. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/CaptureGuidelines.tsx +374 -0
  54. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/ConsentView.tsx +460 -0
  55. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/SubmissionView.tsx +426 -0
  56. package/lib/components/selfie/src/enhanced-smartselfie-capture/components/index.ts +3 -0
  57. package/lib/components/selfie/src/enhanced-smartselfie-capture/constants.ts +23 -0
  58. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/index.ts +2 -0
  59. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useCamera.ts +238 -0
  60. package/lib/components/selfie/src/enhanced-smartselfie-capture/hooks/useFaceCapture.ts +1075 -0
  61. package/lib/components/selfie/src/enhanced-smartselfie-capture/index.ts +1 -0
  62. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/alertMessages.ts +20 -0
  63. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/canvas.ts +108 -0
  64. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/faceDetection.ts +545 -0
  65. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageCapture.ts +66 -0
  66. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/imageQuality.ts +151 -0
  67. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/index.ts +5 -0
  68. package/lib/components/selfie/src/enhanced-smartselfie-capture/utils/mediapipeManager.ts +215 -0
  69. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +163 -17
  70. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +2 -2
  71. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +15 -7
  72. package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +4 -6
  73. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +145 -9
  74. package/lib/components/signature-pad/package.json +1 -1
  75. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +70 -11
  76. package/lib/domain/localisation/index.js +2 -2
  77. package/package.json +3 -3
  78. package/dist/esm/DocumentCaptureScreens-bLFW-yEM.js.map +0 -1
  79. package/dist/esm/Navigation-nvehze1F.js.map +0 -1
  80. package/dist/esm/SelfieCaptureScreens-BXIs6_tl.js +0 -7522
  81. package/dist/esm/SelfieCaptureScreens-BXIs6_tl.js.map +0 -1
  82. package/dist/esm/index-5Nn2kzHI.js +0 -1360
  83. package/dist/esm/package-DmH-I6GW.js +0 -565
  84. package/dist/esm/package-DmH-I6GW.js.map +0 -1
@@ -0,0 +1,66 @@
1
+ import { JPEG_QUALITY } from '../../../../../domain/constants/src/Constants';
2
+
3
+ export const captureImageFromVideo = (
4
+ videoElement: HTMLVideoElement,
5
+ isReference: boolean = false,
6
+ ): string | null => {
7
+ const canvas = document.createElement('canvas');
8
+ const ctx = canvas.getContext('2d');
9
+ if (!ctx) return null;
10
+
11
+ const isPortrait = videoElement.videoHeight > videoElement.videoWidth;
12
+
13
+ if (isReference) {
14
+ if (isPortrait) {
15
+ canvas.width = 480;
16
+ canvas.height = Math.max(
17
+ 640,
18
+ (canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
19
+ );
20
+ } else {
21
+ canvas.width = 640;
22
+ canvas.height = Math.max(
23
+ 480,
24
+ (canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
25
+ );
26
+ }
27
+ } else if (isPortrait) {
28
+ canvas.width = 240;
29
+ canvas.height = Math.max(
30
+ 320,
31
+ (canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
32
+ );
33
+ } else {
34
+ canvas.width = 320;
35
+ canvas.height = Math.max(
36
+ 240,
37
+ (canvas.width * videoElement.videoHeight) / videoElement.videoWidth,
38
+ );
39
+ }
40
+
41
+ // capture more of the user's head and avoid clipping
42
+ const zoomOutFactor = 1;
43
+ const sourceWidth = videoElement.videoWidth * zoomOutFactor;
44
+ const sourceHeight = videoElement.videoHeight * zoomOutFactor;
45
+
46
+ // center the zoomed out area
47
+ const offsetX = (sourceWidth - videoElement.videoWidth) / 2;
48
+ const offsetY = (sourceHeight - videoElement.videoHeight) / 2;
49
+
50
+ // vertical offset to shift up and capture full head
51
+ const verticalOffset = 0;
52
+
53
+ ctx.drawImage(
54
+ videoElement,
55
+ -offsetX,
56
+ -offsetY - verticalOffset,
57
+ sourceWidth,
58
+ sourceHeight,
59
+ 0,
60
+ 0,
61
+ canvas.width,
62
+ canvas.height,
63
+ );
64
+
65
+ return canvas.toDataURL('image/jpeg', JPEG_QUALITY);
66
+ };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Lightweight image quality checks used by the Enhanced SmartSelfie active
3
+ * liveness flow. Both routines work on small ROIs so they can run inside the
4
+ * face detection loop without noticeable cost.
5
+ */
6
+
7
+ const SAMPLE_STEP = 4; // Sample every 4th pixel — keeps work to ~6% of full ROI.
8
+
9
+ /**
10
+ * Module-scoped scratch canvases reused across frames. Allocating a fresh
11
+ * `<canvas>` per call during the capture loop (~10 fps × 2 routines) creates
12
+ * meaningful GC churn and re-enters the `willReadFrequently` software path on
13
+ * every frame. We keep one canvas per routine and only resize when the
14
+ * computed downsample target changes — assigning `width`/`height` also clears
15
+ * the bitmap, so no manual clear is required.
16
+ */
17
+ let luminanceCanvas: HTMLCanvasElement | null = null;
18
+ let luminanceCtx: CanvasRenderingContext2D | null = null;
19
+ let blurCanvas: HTMLCanvasElement | null = null;
20
+ let blurCtx: CanvasRenderingContext2D | null = null;
21
+
22
+ const getScratchContext = (
23
+ which: 'luminance' | 'blur',
24
+ dw: number,
25
+ dh: number,
26
+ ): CanvasRenderingContext2D | null => {
27
+ let canvas = which === 'luminance' ? luminanceCanvas : blurCanvas;
28
+ let ctx = which === 'luminance' ? luminanceCtx : blurCtx;
29
+
30
+ if (!canvas) {
31
+ canvas = document.createElement('canvas');
32
+ ctx = canvas.getContext('2d', { willReadFrequently: true });
33
+ if (which === 'luminance') {
34
+ luminanceCanvas = canvas;
35
+ luminanceCtx = ctx;
36
+ } else {
37
+ blurCanvas = canvas;
38
+ blurCtx = ctx;
39
+ }
40
+ }
41
+ if (!ctx) return null;
42
+
43
+ if (canvas.width !== dw) canvas.width = dw;
44
+ if (canvas.height !== dh) canvas.height = dh;
45
+ return ctx;
46
+ };
47
+
48
+ /**
49
+ * Average BT.601 luma (0–255) over a region of the video frame.
50
+ *
51
+ * @param video live video element
52
+ * @param region optional normalised ROI (defaults to centre 60%)
53
+ */
54
+ export const calculateLuminance = (
55
+ video: HTMLVideoElement,
56
+ region?: { x: number; y: number; width: number; height: number },
57
+ ): number => {
58
+ if (!video || video.videoWidth === 0 || video.videoHeight === 0) return 0;
59
+
60
+ const w = video.videoWidth;
61
+ const h = video.videoHeight;
62
+ const r = region ?? { x: 0.2, y: 0.2, width: 0.6, height: 0.6 };
63
+
64
+ const sx = Math.max(0, Math.floor(r.x * w));
65
+ const sy = Math.max(0, Math.floor(r.y * h));
66
+ const sw = Math.min(w - sx, Math.floor(r.width * w));
67
+ const sh = Math.min(h - sy, Math.floor(r.height * h));
68
+ if (sw <= 0 || sh <= 0) return 0;
69
+
70
+ // Downsample heavily — 64x64 max — to keep this near-free.
71
+ const targetSize = 64;
72
+ const scale = Math.min(1, targetSize / Math.max(sw, sh));
73
+ const dw = Math.max(1, Math.round(sw * scale));
74
+ const dh = Math.max(1, Math.round(sh * scale));
75
+
76
+ const ctx = getScratchContext('luminance', dw, dh);
77
+ if (!ctx) return 0;
78
+
79
+ ctx.drawImage(video, sx, sy, sw, sh, 0, 0, dw, dh);
80
+ const { data } = ctx.getImageData(0, 0, dw, dh);
81
+
82
+ let total = 0;
83
+ let count = 0;
84
+ for (let i = 0; i < data.length; i += 4 * SAMPLE_STEP) {
85
+ // BT.601 luma — same approximation OpenCV uses for cvtColor RGB→GRAY.
86
+ total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
87
+ count += 1;
88
+ }
89
+ return count === 0 ? 0 : total / count;
90
+ };
91
+
92
+ /**
93
+ * Estimate sharpness as the variance of a 3x3 Laplacian filter applied to the
94
+ * grayscaled ROI. Higher = sharper. Empirically values < ~30 indicate
95
+ * meaningful motion blur for a downsampled webcam frame.
96
+ */
97
+ export const calculateBlurScore = (
98
+ video: HTMLVideoElement,
99
+ region?: { x: number; y: number; width: number; height: number },
100
+ ): number => {
101
+ if (!video || video.videoWidth === 0 || video.videoHeight === 0) return 0;
102
+
103
+ const w = video.videoWidth;
104
+ const h = video.videoHeight;
105
+ const r = region ?? { x: 0.25, y: 0.25, width: 0.5, height: 0.5 };
106
+
107
+ const sx = Math.max(0, Math.floor(r.x * w));
108
+ const sy = Math.max(0, Math.floor(r.y * h));
109
+ const sw = Math.min(w - sx, Math.floor(r.width * w));
110
+ const sh = Math.min(h - sy, Math.floor(r.height * h));
111
+ if (sw <= 0 || sh <= 0) return 0;
112
+
113
+ const targetSize = 96;
114
+ const scale = Math.min(1, targetSize / Math.max(sw, sh));
115
+ const dw = Math.max(3, Math.round(sw * scale));
116
+ const dh = Math.max(3, Math.round(sh * scale));
117
+
118
+ const ctx = getScratchContext('blur', dw, dh);
119
+ if (!ctx) return 0;
120
+
121
+ ctx.drawImage(video, sx, sy, sw, sh, 0, 0, dw, dh);
122
+ const { data } = ctx.getImageData(0, 0, dw, dh);
123
+
124
+ // Convert to grayscale once.
125
+ const gray = new Float32Array(dw * dh);
126
+ for (let i = 0; i < gray.length; i += 1) {
127
+ const o = i * 4;
128
+ gray[i] = 0.299 * data[o] + 0.587 * data[o + 1] + 0.114 * data[o + 2];
129
+ }
130
+
131
+ // Variance of 3x3 Laplacian (skip 1-px border).
132
+ let sum = 0;
133
+ let sumSq = 0;
134
+ let n = 0;
135
+ for (let y = 1; y < dh - 1; y += 1) {
136
+ for (let x = 1; x < dw - 1; x += 1) {
137
+ const i = y * dw + x;
138
+ const lap =
139
+ 4 * gray[i] - gray[i - 1] - gray[i + 1] - gray[i - dw] - gray[i + dw];
140
+ sum += lap;
141
+ sumSq += lap * lap;
142
+ n += 1;
143
+ }
144
+ }
145
+ if (n === 0) return 0;
146
+ const mean = sum / n;
147
+ return sumSq / n - mean * mean;
148
+ };
149
+
150
+ export const DEFAULT_LUMINANCE_MIN = 85; // average luma below this = "too dark"
151
+ export const DEFAULT_BLUR_MIN = 80; // Laplacian variance below this = "too blurry"
@@ -0,0 +1,5 @@
1
+ export * from './faceDetection';
2
+ export * from './canvas';
3
+ export * from './imageCapture';
4
+ export * from './imageQuality';
5
+ export * from './alertMessages';
@@ -0,0 +1,215 @@
1
+ import { FaceLandmarker, FilesetResolver } from '@mediapipe/tasks-vision';
2
+
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
+ };
63
+
64
+ declare global {
65
+ interface Window {
66
+ __smileIdentityMediapipe?: {
67
+ instance: FaceLandmarker | null;
68
+ loading: Promise<FaceLandmarker> | null;
69
+ loaded: boolean;
70
+ };
71
+ }
72
+ }
73
+
74
+ /**
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.
77
+ */
78
+ const getSystemArchitectureHints = async (): Promise<string | null> => {
79
+ if (typeof navigator === 'undefined' || !(navigator as any).userAgentData) {
80
+ return null;
81
+ }
82
+
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
+ }
113
+
114
+ // Secondary check: UA-CH hints (may contain GPU info in some browsers)
115
+ const hintString = await getSystemArchitectureHints();
116
+ const hasUaHints = !!hintString;
117
+
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';
124
+ }
125
+ }
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';
139
+ };
140
+
141
+ // this was added because devices (mostly older) that do not support FP16 will fail to load the model.
142
+ const hasFP16Support = () => {
143
+ const canvas = document.createElement('canvas');
144
+ const gl =
145
+ canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
146
+ if (!gl) return false;
147
+
148
+ const hasHalfFloatExt = (gl as any).getExtension('OES_texture_half_float');
149
+ const hasHalfFloatLinear = (gl as any).getExtension(
150
+ 'OES_texture_half_float_linear',
151
+ );
152
+ const hasColorBufferHalfFloat = (gl as any).getExtension(
153
+ 'EXT_color_buffer_half_float',
154
+ );
155
+
156
+ return !!(hasHalfFloatExt && hasColorBufferHalfFloat && hasHalfFloatLinear);
157
+ };
158
+
159
+ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
160
+ if (!window.__smileIdentityMediapipe) {
161
+ window.__smileIdentityMediapipe = {
162
+ instance: null,
163
+ loading: null,
164
+ loaded: false,
165
+ };
166
+ }
167
+
168
+ const mediapipeGlobal = window.__smileIdentityMediapipe;
169
+
170
+ if (mediapipeGlobal.loaded && mediapipeGlobal.instance) {
171
+ return mediapipeGlobal.instance;
172
+ }
173
+
174
+ if (mediapipeGlobal.loading) {
175
+ return mediapipeGlobal.loading;
176
+ }
177
+
178
+ mediapipeGlobal.loading = (async () => {
179
+ try {
180
+ const vision = await FilesetResolver.forVisionTasks(
181
+ 'https://web-models.smileidentity.com/mediapipe-tasks-vision-wasm',
182
+ );
183
+
184
+ const gpuDelegate = await getDelegateFromGpuDetection();
185
+ const delegate =
186
+ gpuDelegate === 'CPU' || !hasFP16Support() ? 'CPU' : 'GPU';
187
+
188
+ const faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
189
+ baseOptions: {
190
+ modelAssetPath: `https://web-models.smileidentity.com/face_landmarker/face_landmarker.task`,
191
+ delegate,
192
+ },
193
+ outputFaceBlendshapes: true,
194
+ runningMode: 'VIDEO',
195
+ numFaces: 2,
196
+ });
197
+
198
+ mediapipeGlobal.instance = faceLandmarker;
199
+ mediapipeGlobal.loaded = true;
200
+ mediapipeGlobal.loading = null;
201
+
202
+ return faceLandmarker;
203
+ } catch (error) {
204
+ mediapipeGlobal.loading = null;
205
+ throw error;
206
+ }
207
+ })();
208
+
209
+ return mediapipeGlobal.loading;
210
+ };
211
+
212
+ export const __testUtils = {
213
+ matchesExcludedGpu,
214
+ getDelegateFromGpuDetection,
215
+ };