@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.
Files changed (98) hide show
  1. package/dist/esm/DocumentCaptureScreens-zEVFc_Kr.js +4147 -0
  2. package/dist/esm/DocumentCaptureScreens-zEVFc_Kr.js.map +1 -0
  3. package/dist/esm/{EndUserConsent-BGO3oZ-m.js → EndUserConsent-BXvS7t8z.js} +4 -4
  4. package/dist/esm/{EndUserConsent-BGO3oZ-m.js.map → EndUserConsent-BXvS7t8z.js.map} +1 -1
  5. package/dist/esm/Navigation-BRFmg7s1.js +138 -0
  6. package/dist/esm/Navigation-BRFmg7s1.js.map +1 -0
  7. package/dist/esm/{SelfieCaptureScreens-bmwnmeS9.js → SelfieCaptureScreens-DsFp21uW.js} +2520 -2906
  8. package/dist/esm/SelfieCaptureScreens-DsFp21uW.js.map +1 -0
  9. package/dist/esm/{TotpConsent-V3_Ip2Kw.js → TotpConsent-Cn2DkVza.js} +2 -2
  10. package/dist/esm/{TotpConsent-V3_Ip2Kw.js.map → TotpConsent-Cn2DkVza.js.map} +1 -1
  11. package/dist/esm/combobox.js +14 -16
  12. package/dist/esm/combobox.js.map +1 -1
  13. package/dist/esm/document.js +1 -1
  14. package/dist/esm/end-user-consent.js +1 -1
  15. package/dist/esm/{index-Dnpp-kwk.js → index-DBUdxnp9.js} +57 -57
  16. package/dist/esm/{index-Dnpp-kwk.js.map → index-DBUdxnp9.js.map} +1 -1
  17. package/dist/esm/localisation.js +1 -1
  18. package/dist/esm/main.js +6 -6
  19. package/dist/esm/navigation.js +1 -1
  20. package/dist/esm/package-Do9oHVnx.js +565 -0
  21. package/dist/esm/package-Do9oHVnx.js.map +1 -0
  22. package/dist/esm/selfie.js +1 -1
  23. package/dist/esm/smart-camera-web.js +16 -11
  24. package/dist/esm/smart-camera-web.js.map +1 -1
  25. package/dist/esm/{styles-BOEZtbuc.js → styles-BTEClL7R.js} +2 -2
  26. package/dist/esm/{styles-BOEZtbuc.js.map → styles-BTEClL7R.js.map} +1 -1
  27. package/dist/esm/totp-consent.js +1 -1
  28. package/dist/smart-camera-web.js +445 -178
  29. package/dist/smart-camera-web.js.map +1 -1
  30. package/dist/types/main.d.ts +186 -33
  31. package/lib/components/combobox/src/Combobox.js +14 -12
  32. package/lib/components/document/src/DocumentCaptureScreens.js +15 -5
  33. package/lib/components/document/src/assets/icons/guidelines/greenbook/good.svg +77 -0
  34. package/lib/components/document/src/assets/icons/guidelines/greenbook/not-blurry.svg +84 -0
  35. package/lib/components/document/src/assets/icons/guidelines/greenbook/not-cropped.svg +83 -0
  36. package/lib/components/document/src/assets/icons/guidelines/greenbook/not-reflective.svg +89 -0
  37. package/lib/components/document/src/assets/icons/guidelines/id-card/good.svg +87 -0
  38. package/lib/components/document/src/assets/icons/guidelines/id-card/not-blurry.svg +100 -0
  39. package/lib/components/document/src/assets/icons/guidelines/id-card/not-cropped.svg +93 -0
  40. package/lib/components/document/src/assets/icons/guidelines/id-card/not-reflective.svg +98 -0
  41. package/lib/components/document/src/assets/icons/guidelines/passport/good.svg +91 -0
  42. package/lib/components/document/src/assets/icons/guidelines/passport/not-blurry.svg +109 -0
  43. package/lib/components/document/src/assets/icons/guidelines/passport/not-cropped.svg +102 -0
  44. package/lib/components/document/src/assets/icons/guidelines/passport/not-reflective.svg +207 -0
  45. package/lib/components/document/src/assets/lottie/taking photo of ID FLIP 2D.lottie +0 -0
  46. package/lib/components/document/src/assets/lottie/taking photo of ID.lottie +0 -0
  47. package/lib/components/document/src/assets/lottie/taking photo of green book passport.lottie +0 -0
  48. package/lib/components/document/src/assets/lottie/taking photo of passport 2.lottie +0 -0
  49. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +123 -12
  50. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.tsx +808 -0
  51. package/lib/components/document/src/document-capture-instructions/index.js +1 -0
  52. package/lib/components/navigation/src/Navigation.js +57 -56
  53. package/lib/components/navigation/src/Navigation.stories.js +7 -0
  54. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +113 -8
  55. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +65 -0
  56. package/lib/components/signature-pad/package-lock.json +11 -9
  57. package/lib/components/signature-pad/package.json +2 -2
  58. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +7 -1
  59. package/lib/styles/src/styles.js +1 -1
  60. package/package.json +4 -3
  61. package/dist/README.md +0 -15
  62. package/dist/components/README.md +0 -14
  63. package/dist/components/document/src/README.md +0 -111
  64. package/dist/components/document/src/document-capture/README.md +0 -90
  65. package/dist/components/document/src/document-capture-instructions/README.md +0 -56
  66. package/dist/components/document/src/document-capture-review/README.md +0 -79
  67. package/dist/components/selfie/README.md +0 -225
  68. package/dist/components/smart-camera-web/src/README.md +0 -206
  69. package/dist/domain/camera/src/README.md +0 -38
  70. package/dist/domain/file-upload/README.md +0 -35
  71. package/dist/esm/DocumentCaptureScreens-DbU8ZxMx.js +0 -1580
  72. package/dist/esm/DocumentCaptureScreens-DbU8ZxMx.js.map +0 -1
  73. package/dist/esm/Navigation-DH44dkMT.js +0 -144
  74. package/dist/esm/Navigation-DH44dkMT.js.map +0 -1
  75. package/dist/esm/SelfieCaptureScreens-bmwnmeS9.js.map +0 -1
  76. package/dist/esm/package-7J5h4EOW.js +0 -90
  77. package/dist/esm/package-7J5h4EOW.js.map +0 -1
  78. package/dist/package-lock.json +0 -4948
  79. package/dist/package.json +0 -59
  80. package/dist/smart-camera-web.js.gz +0 -0
  81. package/dist/src/components/combobox/src/index.js +0 -425
  82. package/dist/src/components/combobox/src/index.js.map +0 -7
  83. package/dist/src/components/document/src/index.js +0 -1423
  84. package/dist/src/components/document/src/index.js.map +0 -7
  85. package/dist/src/components/end-user-consent/src/index.js +0 -1586
  86. package/dist/src/components/end-user-consent/src/index.js.map +0 -7
  87. package/dist/src/components/selfie/src/index.js +0 -1221
  88. package/dist/src/components/selfie/src/index.js.map +0 -7
  89. package/dist/src/components/signature-pad/src/index.js +0 -796
  90. package/dist/src/components/signature-pad/src/index.js.map +0 -7
  91. package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js +0 -2754
  92. package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js.map +0 -7
  93. package/dist/src/components/totp-consent/src/index.js +0 -1305
  94. package/dist/src/components/totp-consent/src/index.js.map +0 -7
  95. package/dist/src/index.js.map +0 -7
  96. package/dist/styles/README.md +0 -3
  97. package/dist/types/document-auto-capture.d.ts +0 -34
  98. package/dist/types/locale.d.ts +0 -19
@@ -1,3 +1,4 @@
1
1
  import DocumentInstruction from './DocumentCaptureInstructions';
2
+ import './DocumentCaptureInstructions.tsx';
2
3
 
3
4
  export default DocumentInstruction;
@@ -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
- background-color: transparent;
30
- border-radius: 2.5rem;
31
- border: none;
32
- color: #ffffff;
33
- cursor: pointer;
34
- display: inline-flex;
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
- background-color: transparent;
49
- border: 0;
42
+ justify-content: center;
43
+ color: var(--smileid-navigation-icon-color, ${iconColor});
50
44
  cursor: pointer;
51
- display: flex;
52
45
  padding: 0;
53
- width: auto;
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
- xmlns="http://www.w3.org/2000/svg"
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
- fill="#DBDBC4"
96
- d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"
97
- opacity=".4"
98
- />
99
- <path
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
- xmlns="http://www.w3.org/2000/svg"
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
- fill="#DBDBC4"
122
- d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"
123
- opacity=".4"
124
- />
125
- <path
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);
@@ -12,6 +12,13 @@ const meta = {
12
12
  },
13
13
  },
14
14
  component: 'smileid-navigation',
15
+ decorators: [
16
+ (story) => `
17
+ <div style="background: #1a1a2e; padding: 24px; min-height: 100px;">
18
+ ${story()}
19
+ </div>
20
+ `,
21
+ ],
15
22
  };
16
23
 
17
24
  export default meta;
@@ -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 { getMediapipeInstance } from '../smartselfie-capture/utils/mediapipeManager';
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 (once). If Mediapipe is already ready, loading,
79
- // or running under Cypress, skip the attempt. This side-effect flips
80
- // mediapipeReady/mediapipeLoading flags which control which component is used.
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 (mediapipeReady || mediapipeLoading || isCypress) return;
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
- }, [mediapipeReady, mediapipeLoading]);
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.0",
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.3.0",
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.3.1",
1363
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
1364
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
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.1",
2325
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
2326
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
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
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smileid/signature-pad",
3
- "version": "11.4.0",
4
- "private": "true",
3
+ "version": "11.4.2",
4
+ "private": true,
5
5
  "exports": {
6
6
  ".": "./index.js"
7
7
  },
@@ -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
  }
@@ -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.0",
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.17.23",
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.2.2",
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.