@smileid/web-components 11.4.0 → 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-zEVFc_Kr.js +4147 -0
- package/dist/esm/DocumentCaptureScreens-zEVFc_Kr.js.map +1 -0
- package/dist/esm/{EndUserConsent-BGO3oZ-m.js → EndUserConsent-BXvS7t8z.js} +4 -4
- package/dist/esm/{EndUserConsent-BGO3oZ-m.js.map → EndUserConsent-BXvS7t8z.js.map} +1 -1
- package/dist/esm/Navigation-BRFmg7s1.js +138 -0
- package/dist/esm/Navigation-BRFmg7s1.js.map +1 -0
- package/dist/esm/{SelfieCaptureScreens-bmwnmeS9.js → SelfieCaptureScreens-DsFp21uW.js} +2520 -2906
- package/dist/esm/SelfieCaptureScreens-DsFp21uW.js.map +1 -0
- package/dist/esm/{TotpConsent-V3_Ip2Kw.js → TotpConsent-Cn2DkVza.js} +2 -2
- package/dist/esm/{TotpConsent-V3_Ip2Kw.js.map → TotpConsent-Cn2DkVza.js.map} +1 -1
- package/dist/esm/combobox.js +14 -16
- package/dist/esm/combobox.js.map +1 -1
- package/dist/esm/document.js +1 -1
- package/dist/esm/end-user-consent.js +1 -1
- package/dist/esm/{index-Dnpp-kwk.js → index-DBUdxnp9.js} +57 -57
- package/dist/esm/{index-Dnpp-kwk.js.map → index-DBUdxnp9.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-Do9oHVnx.js +565 -0
- package/dist/esm/package-Do9oHVnx.js.map +1 -0
- package/dist/esm/selfie.js +1 -1
- package/dist/esm/smart-camera-web.js +16 -11
- package/dist/esm/smart-camera-web.js.map +1 -1
- package/dist/esm/{styles-BOEZtbuc.js → styles-BTEClL7R.js} +2 -2
- package/dist/esm/{styles-BOEZtbuc.js.map → styles-BTEClL7R.js.map} +1 -1
- package/dist/esm/totp-consent.js +1 -1
- package/dist/smart-camera-web.js +445 -178
- package/dist/smart-camera-web.js.map +1 -1
- package/dist/types/main.d.ts +186 -33
- package/lib/components/combobox/src/Combobox.js +14 -12
- package/lib/components/document/src/DocumentCaptureScreens.js +15 -5
- package/lib/components/document/src/assets/icons/guidelines/greenbook/good.svg +77 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-blurry.svg +84 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-cropped.svg +83 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-reflective.svg +89 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/good.svg +87 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-blurry.svg +100 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-cropped.svg +93 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-reflective.svg +98 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/good.svg +91 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-blurry.svg +109 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-cropped.svg +102 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-reflective.svg +207 -0
- package/lib/components/document/src/assets/lottie/taking photo of ID FLIP 2D.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of ID.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of green book passport.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of passport 2.lottie +0 -0
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +123 -12
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.tsx +808 -0
- package/lib/components/document/src/document-capture-instructions/index.js +1 -0
- package/lib/components/navigation/src/Navigation.js +57 -56
- package/lib/components/navigation/src/Navigation.stories.js +7 -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-lock.json +11 -9
- package/lib/components/signature-pad/package.json +2 -2
- package/lib/components/smart-camera-web/src/SmartCameraWeb.js +7 -1
- package/lib/styles/src/styles.js +1 -1
- package/package.json +4 -3
- package/dist/README.md +0 -15
- package/dist/components/README.md +0 -14
- package/dist/components/document/src/README.md +0 -111
- package/dist/components/document/src/document-capture/README.md +0 -90
- package/dist/components/document/src/document-capture-instructions/README.md +0 -56
- package/dist/components/document/src/document-capture-review/README.md +0 -79
- package/dist/components/selfie/README.md +0 -225
- package/dist/components/smart-camera-web/src/README.md +0 -206
- package/dist/domain/camera/src/README.md +0 -38
- package/dist/domain/file-upload/README.md +0 -35
- package/dist/esm/DocumentCaptureScreens-DbU8ZxMx.js +0 -1580
- package/dist/esm/DocumentCaptureScreens-DbU8ZxMx.js.map +0 -1
- package/dist/esm/Navigation-DH44dkMT.js +0 -144
- package/dist/esm/Navigation-DH44dkMT.js.map +0 -1
- package/dist/esm/SelfieCaptureScreens-bmwnmeS9.js.map +0 -1
- package/dist/esm/package-7J5h4EOW.js +0 -90
- package/dist/esm/package-7J5h4EOW.js.map +0 -1
- package/dist/package-lock.json +0 -4948
- package/dist/package.json +0 -59
- package/dist/smart-camera-web.js.gz +0 -0
- package/dist/src/components/combobox/src/index.js +0 -425
- package/dist/src/components/combobox/src/index.js.map +0 -7
- package/dist/src/components/document/src/index.js +0 -1423
- package/dist/src/components/document/src/index.js.map +0 -7
- package/dist/src/components/end-user-consent/src/index.js +0 -1586
- package/dist/src/components/end-user-consent/src/index.js.map +0 -7
- package/dist/src/components/selfie/src/index.js +0 -1221
- package/dist/src/components/selfie/src/index.js.map +0 -7
- package/dist/src/components/signature-pad/src/index.js +0 -796
- package/dist/src/components/signature-pad/src/index.js.map +0 -7
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js +0 -2754
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js.map +0 -7
- package/dist/src/components/totp-consent/src/index.js +0 -1305
- package/dist/src/components/totp-consent/src/index.js.map +0 -7
- package/dist/src/index.js.map +0 -7
- package/dist/styles/README.md +0 -3
- package/dist/types/document-auto-capture.d.ts +0 -34
- package/dist/types/locale.d.ts +0 -19
|
@@ -4,6 +4,13 @@ class Navigation extends HTMLElement {
|
|
|
4
4
|
connectedCallback() {
|
|
5
5
|
const shadow = this.attachShadow({ mode: 'open' });
|
|
6
6
|
const direction = getDirection();
|
|
7
|
+
const hostPadding = '0px';
|
|
8
|
+
const buttonSize = '40px';
|
|
9
|
+
const buttonBackground = 'rgba(132, 130, 130, 0.9)';
|
|
10
|
+
const buttonBorder = '1px solid rgba(255, 255, 255, 0.1)';
|
|
11
|
+
const iconSize = '20px';
|
|
12
|
+
const iconColor = this.hasThemeColor ? this.themeColor : '#FFFFFF';
|
|
13
|
+
const focusColor = '#FFFFFF';
|
|
7
14
|
|
|
8
15
|
const style = document.createElement('style');
|
|
9
16
|
style.textContent = `
|
|
@@ -12,6 +19,8 @@ class Navigation extends HTMLElement {
|
|
|
12
19
|
max-inline-size: 100%;
|
|
13
20
|
justify-content: ${this.showBackButton ? 'space-between' : 'flex-end'};
|
|
14
21
|
direction: ${direction};
|
|
22
|
+
padding: var(--smileid-navigation-padding, ${hostPadding});
|
|
23
|
+
gap: 1rem;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
:host([dir="rtl"]) .back-button svg,
|
|
@@ -20,50 +29,48 @@ class Navigation extends HTMLElement {
|
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
button {
|
|
23
|
-
--button-color: var(--color-default);
|
|
24
|
-
--flow-space: 3rem;
|
|
25
32
|
-webkit-appearance: none;
|
|
26
33
|
-moz-appearance: none;
|
|
27
|
-
align-items: center;
|
|
28
34
|
appearance: none;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
border:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
display:
|
|
35
|
-
font-size: 1rem;
|
|
36
|
-
font-weight: 500;
|
|
37
|
-
inline-size: 100%;
|
|
38
|
-
justify-content: center;
|
|
39
|
-
letter-spacing: 0.05ch;
|
|
40
|
-
line-height: 1;
|
|
41
|
-
padding: 1rem 2.5rem;
|
|
42
|
-
text-align: center;
|
|
43
|
-
text-decoration: none;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
button[data-type="icon"] {
|
|
35
|
+
width: ${buttonSize};
|
|
36
|
+
height: ${buttonSize};
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
background: var(--smileid-navigation-button-bg, ${buttonBackground});
|
|
39
|
+
border: ${buttonBorder};
|
|
40
|
+
display: flex;
|
|
47
41
|
align-items: center;
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
justify-content: center;
|
|
43
|
+
color: var(--smileid-navigation-icon-color, ${iconColor});
|
|
50
44
|
cursor: pointer;
|
|
51
|
-
display: flex;
|
|
52
45
|
padding: 0;
|
|
53
|
-
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
transition: box-shadow 0.15s ease;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button:hover {
|
|
51
|
+
box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.15);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
button:focus-visible {
|
|
55
|
+
outline: 2px solid var(--smileid-navigation-focus-color, ${focusColor});
|
|
56
|
+
outline-offset: 3px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
button svg {
|
|
60
|
+
width: ${iconSize};
|
|
61
|
+
height: ${iconSize};
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
:host::part(back-button) {
|
|
57
65
|
display: flex;
|
|
58
66
|
align-items: center;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
:host::part(back-button-text) {
|
|
62
|
-
line-height: 1;
|
|
63
|
-
color: ${this.hasThemeColor ? this.themeColor : 'rgb(21, 31, 114)'} !important;
|
|
67
|
+
justify-content: center;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
:host::part(close-button) {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
.visually-hidden {
|
|
@@ -79,57 +86,51 @@ button[data-type="icon"] {
|
|
|
79
86
|
|
|
80
87
|
const backButton = document.createElement('button');
|
|
81
88
|
backButton.setAttribute('class', 'back-button');
|
|
82
|
-
backButton.setAttribute('data-type', 'icon');
|
|
83
89
|
backButton.setAttribute('part', 'back-button');
|
|
84
90
|
backButton.setAttribute('type', 'button');
|
|
91
|
+
backButton.setAttribute('aria-label', t('navigation.back'));
|
|
85
92
|
backButton.innerHTML = `
|
|
86
93
|
<svg
|
|
87
|
-
|
|
88
|
-
width="24"
|
|
89
|
-
height="24"
|
|
94
|
+
aria-hidden="true"
|
|
90
95
|
viewBox="0 0 24 24"
|
|
91
96
|
fill="none"
|
|
97
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
92
98
|
data-rtl="${direction === 'rtl'}"
|
|
93
99
|
>
|
|
94
100
|
<path
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
fill="${this.themeColor}"
|
|
101
|
-
d="M15.5 11.25h-5.19l1.72-1.72c.29-.29.29-.77 0-1.06a.754.754 0 0 0-1.06 0l-3 3c-.29.29-.29.77 0 1.06l3 3c.15.15.34.22.53.22s.38-.07.53-.22c.29-.29.29-.77 0-1.06l-1.72-1.72h5.19c.41 0 .75-.34.75-.75s-.34-.75-.75-.75Z"
|
|
101
|
+
d="M19 12H5M5 12L12 19M5 12L12 5"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
stroke-width="2"
|
|
104
|
+
stroke-linecap="round"
|
|
105
|
+
stroke-linejoin="round"
|
|
102
106
|
/>
|
|
103
107
|
</svg>
|
|
104
|
-
<span part="back-button-text">${t('navigation.back')}</span>
|
|
108
|
+
<span part="back-button-text" class="visually-hidden">${t('navigation.back')}</span>
|
|
105
109
|
`;
|
|
106
110
|
|
|
107
111
|
const closeButton = document.createElement('button');
|
|
108
112
|
closeButton.setAttribute('class', 'close-button');
|
|
109
|
-
closeButton.setAttribute('data-type', 'icon');
|
|
110
113
|
closeButton.setAttribute('part', 'close-button');
|
|
111
114
|
closeButton.setAttribute('type', 'button');
|
|
115
|
+
closeButton.setAttribute(
|
|
116
|
+
'aria-label',
|
|
117
|
+
t('navigation.closeVerificationFrame'),
|
|
118
|
+
);
|
|
112
119
|
closeButton.innerHTML = `
|
|
113
120
|
<svg
|
|
114
|
-
|
|
121
|
+
aria-hidden="true"
|
|
115
122
|
viewBox="0 0 24 24"
|
|
116
|
-
width="24"
|
|
117
|
-
height="24"
|
|
118
123
|
fill="none"
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
119
125
|
>
|
|
120
126
|
<path
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
fill="#91190F"
|
|
127
|
-
d="m13.06 12 2.3-2.3c.29-.29.29-.77 0-1.06a.754.754 0 0 0-1.06 0l-2.3 2.3-2.3-2.3a.754.754 0 0 0-1.06 0c-.29.29-.29.77 0 1.06l2.3 2.3-2.3 2.3c-.29.29-.29.77 0 1.06.15.15.34.22.53.22s.38-.07.53-.22l2.3-2.3 2.3 2.3c.15.15.34.22.53.22s.38-.07.53-.22c.29-.29.29-.77 0-1.06l-2.3-2.3Z"
|
|
127
|
+
d="M18 6L6 18M6 6L18 18"
|
|
128
|
+
stroke="currentColor"
|
|
129
|
+
stroke-width="2"
|
|
130
|
+
stroke-linecap="round"
|
|
131
|
+
stroke-linejoin="round"
|
|
128
132
|
/>
|
|
129
133
|
</svg>
|
|
130
|
-
<span class="visually-hidden"
|
|
131
|
-
>${t('navigation.closeVerificationFrame')}</span
|
|
132
|
-
>
|
|
133
134
|
`;
|
|
134
135
|
|
|
135
136
|
shadow.appendChild(style);
|
|
@@ -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
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/signature-pad",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@smileid/signature-pad",
|
|
9
|
-
"version": "11.
|
|
9
|
+
"version": "11.4.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"signature_pad": "^5.0.2"
|
|
12
12
|
},
|
|
@@ -1359,10 +1359,11 @@
|
|
|
1359
1359
|
}
|
|
1360
1360
|
},
|
|
1361
1361
|
"node_modules/flatted": {
|
|
1362
|
-
"version": "3.
|
|
1363
|
-
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.
|
|
1364
|
-
"integrity": "sha512-
|
|
1365
|
-
"dev": true
|
|
1362
|
+
"version": "3.4.2",
|
|
1363
|
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
|
1364
|
+
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
|
1365
|
+
"dev": true,
|
|
1366
|
+
"license": "ISC"
|
|
1366
1367
|
},
|
|
1367
1368
|
"node_modules/for-each": {
|
|
1368
1369
|
"version": "0.3.3",
|
|
@@ -2321,10 +2322,11 @@
|
|
|
2321
2322
|
}
|
|
2322
2323
|
},
|
|
2323
2324
|
"node_modules/picomatch": {
|
|
2324
|
-
"version": "2.3.
|
|
2325
|
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.
|
|
2326
|
-
"integrity": "sha512-
|
|
2325
|
+
"version": "2.3.2",
|
|
2326
|
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
|
2327
|
+
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
|
2327
2328
|
"dev": true,
|
|
2329
|
+
"license": "MIT",
|
|
2328
2330
|
"engines": {
|
|
2329
2331
|
"node": ">=8.6"
|
|
2330
2332
|
},
|
|
@@ -23,7 +23,7 @@ function scwTemplateString() {
|
|
|
23
23
|
${this.hideBackToHost} ${this.allowAgentMode} ${this.allowAgentModeTests} ${this.allowLegacySelfieFallback}
|
|
24
24
|
></selfie-capture-screens>
|
|
25
25
|
<document-capture-screens ${this.applyComponentThemeColor} document-type=${this.documentType} ${this.title} ${this.documentCaptureModes} ${this.showNavigation} ${this.hideAttribution}
|
|
26
|
-
${this.hideBackOfId} ${this.applyComponentThemeColor} hidden></document-capture-screens>
|
|
26
|
+
${this.hideBackOfId} ${this.newInstructions} ${this.applyComponentThemeColor} hidden></document-capture-screens>
|
|
27
27
|
</div>
|
|
28
28
|
`;
|
|
29
29
|
}
|
|
@@ -78,6 +78,7 @@ class SmartCameraWeb extends HTMLElement {
|
|
|
78
78
|
'hide-back-to-host',
|
|
79
79
|
'show-navigation',
|
|
80
80
|
'theme-color',
|
|
81
|
+
'new-instructions',
|
|
81
82
|
];
|
|
82
83
|
}
|
|
83
84
|
|
|
@@ -93,6 +94,7 @@ class SmartCameraWeb extends HTMLElement {
|
|
|
93
94
|
case 'hide-back-to-host':
|
|
94
95
|
case 'show-navigation':
|
|
95
96
|
case 'theme-color':
|
|
97
|
+
case 'new-instructions':
|
|
96
98
|
this.disconnectedCallback();
|
|
97
99
|
this.shadowRoot.innerHTML = this.render();
|
|
98
100
|
this.setUpEventListeners();
|
|
@@ -227,6 +229,10 @@ class SmartCameraWeb extends HTMLElement {
|
|
|
227
229
|
return this.hasAttribute('hide-back-of-id') ? 'hide-back-of-id' : '';
|
|
228
230
|
}
|
|
229
231
|
|
|
232
|
+
get newInstructions() {
|
|
233
|
+
return this.hasAttribute('new-instructions') ? 'new-instructions' : '';
|
|
234
|
+
}
|
|
235
|
+
|
|
230
236
|
get showNavigation() {
|
|
231
237
|
return this.hasAttribute('show-navigation') ? 'show-navigation' : '';
|
|
232
238
|
}
|
package/lib/styles/src/styles.js
CHANGED
|
@@ -290,7 +290,7 @@ ${typography}
|
|
|
290
290
|
padding-bottom: 2rem;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
smart-camera-web, selfie-capture-screens, selfie-capture-instructions, document-capture-screens, document-capture-instructions {
|
|
293
|
+
smart-camera-web, selfie-capture-screens, selfie-capture-instructions, document-capture-screens, document-capture-instructions, document-capture-instructions-v2 {
|
|
294
294
|
height: 100%;
|
|
295
295
|
display: block;
|
|
296
296
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/web-components",
|
|
3
|
-
"version": "11.4.
|
|
3
|
+
"version": "11.4.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "dist/esm/main.js",
|
|
6
6
|
"module": "dist/esm/main.js",
|
|
@@ -77,10 +77,11 @@
|
|
|
77
77
|
"type": "module",
|
|
78
78
|
"author": "SmileID <support@usesmileid.com> (https://usesmileid.com)",
|
|
79
79
|
"dependencies": {
|
|
80
|
+
"@lottiefiles/dotlottie-web": "^0.71.0",
|
|
80
81
|
"@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
|
|
81
82
|
"@preact/signals": "^2.1.1",
|
|
82
83
|
"@tabler/icons-preact": "^3.34.0",
|
|
83
|
-
"lodash": "^4.
|
|
84
|
+
"lodash": "^4.18.1",
|
|
84
85
|
"preact": "^10.27.3",
|
|
85
86
|
"preact-custom-element": "^4.3.0",
|
|
86
87
|
"preact-router": "^4.1.2",
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
"prettier": "^3.6.2",
|
|
109
110
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
110
111
|
"typescript": "^5.8.3",
|
|
111
|
-
"vite": "^7.
|
|
112
|
+
"vite": "^7.3.2",
|
|
112
113
|
"vite-plugin-dts": "^4.5.4",
|
|
113
114
|
"vite-plugin-tsconfig-paths": "^1.4.1"
|
|
114
115
|
},
|
package/dist/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Instructions
|
|
2
|
-
|
|
3
|
-
These components can be used to capture id document or liveness images
|
|
4
|
-
|
|
5
|
-
1. [`smart-camera-web`](./components/smart-camera-web/src/)
|
|
6
|
-
2. [`document-capture-screens`](./components/document/src/README.md)
|
|
7
|
-
3. [`selfie-capture-screens`](./components/selfie/README.md)
|
|
8
|
-
|
|
9
|
-
## Orchestration
|
|
10
|
-
|
|
11
|
-
To build your own flow, we have several components that can be used together
|
|
12
|
-
|
|
13
|
-
### document-capture-instructions
|
|
14
|
-
|
|
15
|
-
This is the screen used to instruct the user how to capture document using both the camera and/or upload.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Instructions
|
|
2
|
-
|
|
3
|
-
These components can be used to capture id document or liveness images
|
|
4
|
-
|
|
5
|
-
1. [`document-capture-screens`](./document/src/README.md)
|
|
6
|
-
2. [`selfie-capture-screens`](./selfie/README.md)
|
|
7
|
-
|
|
8
|
-
## Orchestration
|
|
9
|
-
|
|
10
|
-
To build your own flow, we have several components that can be used together
|
|
11
|
-
|
|
12
|
-
### document-capture-instructions
|
|
13
|
-
|
|
14
|
-
This is the screen used to instruct the user how to capture document using both the camera and/or upload.
|