@smileid/web-components 11.4.1 → 11.4.2
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-wvJcTVkA.js → DocumentCaptureScreens-zEVFc_Kr.js} +2 -2
- package/dist/esm/{DocumentCaptureScreens-wvJcTVkA.js.map → DocumentCaptureScreens-zEVFc_Kr.js.map} +1 -1
- package/dist/esm/{SelfieCaptureScreens-BkJBfzHv.js → SelfieCaptureScreens-DsFp21uW.js} +1623 -1547
- package/dist/esm/SelfieCaptureScreens-DsFp21uW.js.map +1 -0
- package/dist/esm/document.js +1 -1
- package/dist/esm/main.js +2 -2
- package/dist/esm/{package-Dax8ezDK.js → package-Do9oHVnx.js} +2 -2
- package/dist/esm/{package-Dax8ezDK.js.map → package-Do9oHVnx.js.map} +1 -1
- package/dist/esm/selfie.js +1 -1
- package/dist/esm/smart-camera-web.js +3 -3
- package/dist/smart-camera-web.js +52 -52
- package/dist/smart-camera-web.js.map +1 -1
- package/dist/types/main.d.ts +3 -0
- package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +113 -8
- package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +65 -0
- package/lib/components/signature-pad/package.json +2 -2
- package/package.json +1 -1
- package/dist/esm/SelfieCaptureScreens-BkJBfzHv.js.map +0 -1
package/dist/types/main.d.ts
CHANGED
|
@@ -88,6 +88,7 @@ export declare class DocumentCaptureScreens extends HTMLElement {
|
|
|
88
88
|
_publishSelectedImages(): void;
|
|
89
89
|
get hideInstructions(): boolean;
|
|
90
90
|
get autoCapture(): boolean;
|
|
91
|
+
get autoCaptureMode(): string;
|
|
91
92
|
get hideBackOfId(): boolean;
|
|
92
93
|
get showNavigation(): "" | "show-navigation";
|
|
93
94
|
get title(): string;
|
|
@@ -335,6 +336,8 @@ export declare class SmartCameraWeb extends HTMLElement {
|
|
|
335
336
|
get allowAgentModeTests(): "" | "show-agent-mode-for-tests";
|
|
336
337
|
get title(): string;
|
|
337
338
|
get documentCaptureModes(): string;
|
|
339
|
+
get autoCapture(): string;
|
|
340
|
+
get autoCaptureMode(): string;
|
|
338
341
|
get disableImageTests(): "" | "disable-image-tests";
|
|
339
342
|
get allowLegacySelfieFallback(): string;
|
|
340
343
|
get hideAttribution(): "" | "hide-attribution";
|
|
@@ -9,7 +9,25 @@ import SmartSelfieCapture from '../smartselfie-capture/SmartSelfieCapture';
|
|
|
9
9
|
// Legacy web component fallback (used when Mediapipe isn't available)
|
|
10
10
|
import '../selfie-capture/SelfieCapture';
|
|
11
11
|
// Mediapipe loader/manager used by SmartSelfieCapture
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getMediapipeInstance,
|
|
14
|
+
UnsupportedMediapipeEnvironmentError,
|
|
15
|
+
} from '../smartselfie-capture/utils/mediapipeManager';
|
|
16
|
+
|
|
17
|
+
// Minimal typing for the optional Sentry SDK that host pages may expose on
|
|
18
|
+
// `window`. We only depend on `captureException`, so keep the surface tight.
|
|
19
|
+
// Sentry tag values are expected to be strings, so the type enforces that.
|
|
20
|
+
type SentryTags = Record<string, string>;
|
|
21
|
+
declare global {
|
|
22
|
+
interface Window {
|
|
23
|
+
Sentry?: {
|
|
24
|
+
captureException: (
|
|
25
|
+
error: unknown,
|
|
26
|
+
context?: { tags?: SentryTags },
|
|
27
|
+
) => void;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
13
31
|
|
|
14
32
|
interface Props {
|
|
15
33
|
timeout?: number;
|
|
@@ -29,6 +47,12 @@ interface Props {
|
|
|
29
47
|
|
|
30
48
|
const DEFAULT_MEDIAPIPE_WAIT_MS = 90 * 1000; // For when legacy fallback is NOT allowed, we wait the full 90s for mediapipe to load before showing an error.
|
|
31
49
|
const DEFAULT_WAIT_MS = 20 * 1000; // default for when legacy fallback is allowed we wait for 20s
|
|
50
|
+
// Cap retries on transient init failures so we don't spin forever, while still
|
|
51
|
+
// allowing recovery from short-lived issues (e.g. CDN hiccups while the
|
|
52
|
+
// wrapper is preloading in a hidden state). Retries are spaced with
|
|
53
|
+
// exponential backoff (base * 2^(attempt-1)) so we don't hammer the CDN.
|
|
54
|
+
const MAX_MEDIAPIPE_INIT_ATTEMPTS = 3;
|
|
55
|
+
const MEDIAPIPE_RETRY_BASE_DELAY_MS = 500;
|
|
32
56
|
|
|
33
57
|
// Wrapper component that decides whether to use the modern
|
|
34
58
|
// SmartSelfieCapture (Mediapipe-based) or fallback to the legacy `selfie-capture`
|
|
@@ -73,29 +97,110 @@ const SelfieCaptureWrapper: FunctionComponent<Props> = ({
|
|
|
73
97
|
const [loadingProgress, setLoadingProgress] = useState(isCypress ? 100 : 0);
|
|
74
98
|
const [initialSessionCompleted, setInitialSessionCompleted] = useState(false);
|
|
75
99
|
const [mediapipeLoading, setMediapipeLoading] = useState(false);
|
|
100
|
+
// `unsupportedEnvironment` is a permanent, one-shot signal: we know
|
|
101
|
+
// MediaPipe cannot run here, so stop trying.
|
|
102
|
+
const [unsupportedEnvironment, setUnsupportedEnvironment] = useState(false);
|
|
103
|
+
// Bounded retry counter for transient init failures.
|
|
104
|
+
const [mediapipeInitAttempts, setMediapipeInitAttempts] = useState(0);
|
|
105
|
+
// Dedup flag so we only report a given init failure to Sentry once per
|
|
106
|
+
// wrapper instance, even if we end up retrying.
|
|
107
|
+
const [mediapipeInitReported, setMediapipeInitReported] = useState(false);
|
|
76
108
|
const [usingSelfieCapture, setUsingSelfieCapture] = useState(false);
|
|
77
109
|
|
|
78
|
-
// Attempt to load Mediapipe (
|
|
79
|
-
//
|
|
80
|
-
//
|
|
110
|
+
// Attempt to load Mediapipe (with a small bounded retry budget). If
|
|
111
|
+
// Mediapipe is already ready, currently loading, the environment is
|
|
112
|
+
// definitively unsupported, we've exhausted our retry budget, or we're
|
|
113
|
+
// running under Cypress, skip the attempt. On transient failure we wait
|
|
114
|
+
// (exponential backoff) before allowing the effect to re-run.
|
|
81
115
|
useEffect(() => {
|
|
82
|
-
if (
|
|
116
|
+
if (
|
|
117
|
+
mediapipeReady ||
|
|
118
|
+
mediapipeLoading ||
|
|
119
|
+
unsupportedEnvironment ||
|
|
120
|
+
mediapipeInitAttempts >= MAX_MEDIAPIPE_INIT_ATTEMPTS ||
|
|
121
|
+
isCypress
|
|
122
|
+
)
|
|
123
|
+
return undefined;
|
|
124
|
+
|
|
125
|
+
let cancelled = false;
|
|
126
|
+
let retryTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
83
127
|
|
|
84
128
|
const loadMediapipe = async () => {
|
|
85
129
|
setMediapipeLoading(true);
|
|
130
|
+
const attemptNumber = mediapipeInitAttempts + 1;
|
|
131
|
+
setMediapipeInitAttempts(attemptNumber);
|
|
86
132
|
try {
|
|
87
133
|
await getMediapipeInstance();
|
|
134
|
+
if (cancelled) return;
|
|
88
135
|
setMediapipeReady(true);
|
|
136
|
+
setMediapipeLoading(false);
|
|
89
137
|
} catch (error) {
|
|
138
|
+
if (cancelled) return;
|
|
90
139
|
// Loading failed; we'll fall back to the legacy selfie-capture component
|
|
91
|
-
// after the loadingProgress reaches 100
|
|
140
|
+
// after the loadingProgress reaches 100% (or sooner for definitively
|
|
141
|
+
// unsupported environments — see below).
|
|
92
142
|
console.error('Failed to load Mediapipe:', error);
|
|
143
|
+
const isUnsupportedEnvironment =
|
|
144
|
+
error instanceof UnsupportedMediapipeEnvironmentError;
|
|
145
|
+
// Report to Sentry (when the host page has exposed it on window) so we
|
|
146
|
+
// can observe how often users land on the fallback path and which
|
|
147
|
+
// environments are affected. Dedup so retries don't flood Sentry.
|
|
148
|
+
if (!mediapipeInitReported) {
|
|
149
|
+
setMediapipeInitReported(true);
|
|
150
|
+
window.Sentry?.captureException(error, {
|
|
151
|
+
tags: {
|
|
152
|
+
area: 'mediapipe_init',
|
|
153
|
+
mediapipe_unsupported_environment: isUnsupportedEnvironment
|
|
154
|
+
? 'true'
|
|
155
|
+
: 'false',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// When the environment definitively cannot run MediaPipe (e.g. no
|
|
160
|
+
// WebAssembly reftypes support), there is no point retrying or keeping
|
|
161
|
+
// the user staring at the loading spinner for the full countdown —
|
|
162
|
+
// mark as unsupported and short-circuit to the fallback decision
|
|
163
|
+
// immediately.
|
|
164
|
+
if (isUnsupportedEnvironment) {
|
|
165
|
+
setUnsupportedEnvironment(true);
|
|
166
|
+
setLoadingProgress(100);
|
|
167
|
+
setMediapipeLoading(false);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Transient failure: wait with exponential backoff before allowing the
|
|
171
|
+
// effect to re-run by flipping mediapipeLoading back to false. If
|
|
172
|
+
// we've exhausted our retry budget, just release the loading flag so
|
|
173
|
+
// the countdown / fallback UI can proceed.
|
|
174
|
+
const hasRetriesLeft = attemptNumber < MAX_MEDIAPIPE_INIT_ATTEMPTS;
|
|
175
|
+
if (!hasRetriesLeft) {
|
|
176
|
+
setMediapipeLoading(false);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const backoffMs =
|
|
180
|
+
MEDIAPIPE_RETRY_BASE_DELAY_MS * 2 ** (attemptNumber - 1);
|
|
181
|
+
retryTimeoutId = setTimeout(() => {
|
|
182
|
+
retryTimeoutId = null;
|
|
183
|
+
if (cancelled) return;
|
|
184
|
+
setMediapipeLoading(false);
|
|
185
|
+
}, backoffMs);
|
|
93
186
|
}
|
|
94
|
-
setMediapipeLoading(false);
|
|
95
187
|
};
|
|
96
188
|
|
|
97
189
|
loadMediapipe();
|
|
98
|
-
|
|
190
|
+
|
|
191
|
+
return () => {
|
|
192
|
+
cancelled = true;
|
|
193
|
+
if (retryTimeoutId !== null) {
|
|
194
|
+
clearTimeout(retryTimeoutId);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}, [
|
|
198
|
+
mediapipeReady,
|
|
199
|
+
mediapipeLoading,
|
|
200
|
+
unsupportedEnvironment,
|
|
201
|
+
mediapipeInitAttempts,
|
|
202
|
+
mediapipeInitReported,
|
|
203
|
+
]);
|
|
99
204
|
|
|
100
205
|
// When using the loading countdown (startCountdown), increment the
|
|
101
206
|
// visible loading progress. This is only used while mediapipe hasn't
|
|
@@ -67,10 +67,61 @@ declare global {
|
|
|
67
67
|
instance: FaceLandmarker | null;
|
|
68
68
|
loading: Promise<FaceLandmarker> | null;
|
|
69
69
|
loaded: boolean;
|
|
70
|
+
supportsWasmReftypes?: boolean;
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* @description Detects whether the current runtime supports the WebAssembly
|
|
77
|
+
* reference-types proposal (the `externref` value type). MediaPipe Tasks
|
|
78
|
+
* Vision ships a .wasm that uses `externref`; on older engines (e.g. Chrome
|
|
79
|
+
* < 96, Safari < 15, Firefox < 79) `WebAssembly.instantiate` throws
|
|
80
|
+
* `CompileError: invalid value type 'externref'`. We probe support once with
|
|
81
|
+
* `WebAssembly.validate` against a tiny module whose only feature is an
|
|
82
|
+
* `externref`-typed global so callers can short-circuit and fall back to the
|
|
83
|
+
* legacy selfie capture flow instead of triggering an unhandled rejection.
|
|
84
|
+
* @returns {boolean} True if the runtime accepts reftypes / externref.
|
|
85
|
+
*/
|
|
86
|
+
const supportsWasmReftypes = (): boolean => {
|
|
87
|
+
if (typeof WebAssembly === 'undefined' || !WebAssembly.validate) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Minimal module: magic + version + global section with one externref
|
|
93
|
+
// global (value type 0x6f) initialized to ref.null extern (0xd0 0x6f 0x0b).
|
|
94
|
+
const bytes = new Uint8Array([
|
|
95
|
+
0x00,
|
|
96
|
+
0x61,
|
|
97
|
+
0x73,
|
|
98
|
+
0x6d, // \0asm magic
|
|
99
|
+
0x01,
|
|
100
|
+
0x00,
|
|
101
|
+
0x00,
|
|
102
|
+
0x00, // version 1
|
|
103
|
+
0x06,
|
|
104
|
+
0x06,
|
|
105
|
+
0x01, // global section, 6 bytes, 1 global
|
|
106
|
+
0x6f,
|
|
107
|
+
0x00, // externref, immutable
|
|
108
|
+
0xd0,
|
|
109
|
+
0x6f,
|
|
110
|
+
0x0b, // ref.null extern; end
|
|
111
|
+
]);
|
|
112
|
+
return WebAssembly.validate(bytes);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export class UnsupportedMediapipeEnvironmentError extends Error {
|
|
119
|
+
constructor(message: string) {
|
|
120
|
+
super(message);
|
|
121
|
+
this.name = 'UnsupportedMediapipeEnvironmentError';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
74
125
|
/**
|
|
75
126
|
* @description Reads system architecture hints from User-Agent Client Hints.
|
|
76
127
|
* @returns {Promise<string | null>} Lower-cased hint string or null when hints are unavailable.
|
|
@@ -175,6 +226,19 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
|
|
|
175
226
|
return mediapipeGlobal.loading;
|
|
176
227
|
}
|
|
177
228
|
|
|
229
|
+
// Fail fast on engines that don't support WebAssembly reftypes/externref.
|
|
230
|
+
// The MediaPipe Tasks Vision .wasm uses externref globals; instantiating it
|
|
231
|
+
// on older browsers throws an unhandled `CompileError`. We detect once and
|
|
232
|
+
// cache the result so callers fall back to the legacy capture flow.
|
|
233
|
+
if (mediapipeGlobal.supportsWasmReftypes === undefined) {
|
|
234
|
+
mediapipeGlobal.supportsWasmReftypes = supportsWasmReftypes();
|
|
235
|
+
}
|
|
236
|
+
if (!mediapipeGlobal.supportsWasmReftypes) {
|
|
237
|
+
throw new UnsupportedMediapipeEnvironmentError(
|
|
238
|
+
'WebAssembly reference types (externref) are not supported in this browser; MediaPipe Tasks Vision cannot be loaded.',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
178
242
|
mediapipeGlobal.loading = (async () => {
|
|
179
243
|
try {
|
|
180
244
|
const vision = await FilesetResolver.forVisionTasks(
|
|
@@ -212,4 +276,5 @@ export const getMediapipeInstance = async (): Promise<FaceLandmarker> => {
|
|
|
212
276
|
export const __testUtils = {
|
|
213
277
|
matchesExcludedGpu,
|
|
214
278
|
getDelegateFromGpuDetection,
|
|
279
|
+
supportsWasmReftypes,
|
|
215
280
|
};
|