@smileid/web-components 11.0.0 → 11.0.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 (141) hide show
  1. package/README.md +15 -15
  2. package/dist/README.md +15 -0
  3. package/dist/components/README.md +14 -0
  4. package/dist/components/document/src/README.md +111 -0
  5. package/dist/components/document/src/document-capture/README.md +90 -0
  6. package/dist/components/document/src/document-capture-instructions/README.md +56 -0
  7. package/dist/components/document/src/document-capture-review/README.md +79 -0
  8. package/dist/components/selfie/README.md +225 -0
  9. package/dist/components/smart-camera-web/src/README.md +207 -0
  10. package/dist/domain/camera/src/README.md +38 -0
  11. package/dist/domain/file-upload/README.md +35 -0
  12. package/dist/esm/{DocumentCaptureScreens-RECPb0wH.js → DocumentCaptureScreens-CRx5ymqY.js} +2 -2
  13. package/dist/esm/DocumentCaptureScreens-CRx5ymqY.js.map +1 -0
  14. package/dist/esm/EndUserConsent-D4fd1ovG.js.map +1 -1
  15. package/dist/esm/Navigation-CTjK6tLU.js.map +1 -1
  16. package/dist/esm/PoweredBySmileId-CxbaihMu.js.map +1 -1
  17. package/dist/esm/{SelfieCaptureScreens-CqBVGEJk.js → SelfieCaptureScreens-DuQjKt_K.js} +274 -257
  18. package/dist/esm/SelfieCaptureScreens-DuQjKt_K.js.map +1 -0
  19. package/dist/esm/SignaturePad-C7MtmT8m.js.map +1 -1
  20. package/dist/esm/TotpConsent-CQU5jQi4.js.map +1 -1
  21. package/dist/esm/combobox.js.map +1 -1
  22. package/dist/esm/document.js +1 -1
  23. package/dist/esm/main.js +2 -2
  24. package/dist/esm/{package-BDJnoIAU.js → package-C_cKNrUu.js} +2 -2
  25. package/dist/esm/package-C_cKNrUu.js.map +1 -0
  26. package/dist/esm/selfie.js +1 -1
  27. package/dist/esm/smart-camera-web.js +3 -3
  28. package/dist/esm/smart-camera-web.js.map +1 -1
  29. package/dist/esm/styles-BOEZtbuc.js.map +1 -1
  30. package/dist/package-lock.json +4948 -0
  31. package/dist/package.json +59 -0
  32. package/dist/smart-camera-web.js +23 -23
  33. package/dist/smart-camera-web.js.gz +0 -0
  34. package/dist/smart-camera-web.js.map +1 -1
  35. package/dist/src/components/combobox/src/index.js +2 -0
  36. package/dist/src/components/combobox/src/index.js.map +7 -0
  37. package/dist/src/components/document/src/index.js +2 -0
  38. package/dist/src/components/document/src/index.js.map +7 -0
  39. package/dist/src/components/end-user-consent/src/index.js +14 -0
  40. package/dist/src/components/end-user-consent/src/index.js.map +7 -0
  41. package/dist/src/components/selfie/src/index.js +2 -0
  42. package/dist/src/components/selfie/src/index.js.map +7 -0
  43. package/dist/src/components/signature-pad/src/index.js +10 -0
  44. package/dist/src/components/signature-pad/src/index.js.map +7 -0
  45. package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js +2 -0
  46. package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js.map +7 -0
  47. package/dist/src/components/totp-consent/src/index.js +14 -0
  48. package/dist/src/components/totp-consent/src/index.js.map +7 -0
  49. package/dist/src/index.js.map +7 -0
  50. package/dist/styles/README.md +3 -0
  51. package/dist/types/combobox.d.ts +19 -19
  52. package/dist/types/document.d.ts +19 -19
  53. package/dist/types/end-user-consent.d.ts +19 -19
  54. package/dist/types/main.d.ts +24 -20
  55. package/dist/types/navigation.d.ts +19 -19
  56. package/dist/types/selfie.d.ts +19 -19
  57. package/dist/types/signature-pad.d.ts +19 -19
  58. package/dist/types/smart-camera-web.d.ts +19 -19
  59. package/dist/types/totp-consent.d.ts +19 -19
  60. package/lib/components/README.md +14 -14
  61. package/lib/components/attribution/PoweredBySmileId.js +42 -42
  62. package/lib/components/camera-permission/CameraPermission.js +139 -139
  63. package/lib/components/camera-permission/CameraPermission.stories.js +27 -27
  64. package/lib/components/combobox/src/Combobox.js +589 -589
  65. package/lib/components/combobox/src/index.js +1 -1
  66. package/lib/components/document/src/DocumentCaptureScreens.js +410 -410
  67. package/lib/components/document/src/DocumentCaptureScreens.stories.js +57 -57
  68. package/lib/components/document/src/README.md +111 -111
  69. package/lib/components/document/src/document-capture/DocumentCapture.js +760 -760
  70. package/lib/components/document/src/document-capture/DocumentCapture.stories.js +78 -78
  71. package/lib/components/document/src/document-capture/README.md +90 -90
  72. package/lib/components/document/src/document-capture/index.js +3 -3
  73. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +545 -545
  74. package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +24 -24
  75. package/lib/components/document/src/document-capture-instructions/README.md +56 -56
  76. package/lib/components/document/src/document-capture-instructions/index.js +3 -3
  77. package/lib/components/document/src/document-capture-review/DocumentCaptureReview.js +360 -360
  78. package/lib/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +24 -24
  79. package/lib/components/document/src/document-capture-review/README.md +79 -79
  80. package/lib/components/document/src/document-capture-review/index.js +3 -3
  81. package/lib/components/document/src/index.js +3 -3
  82. package/lib/components/end-user-consent/src/EndUserConsent.js +795 -795
  83. package/lib/components/end-user-consent/src/EndUserConsent.stories.js +29 -29
  84. package/lib/components/end-user-consent/src/index.js +4 -4
  85. package/lib/components/navigation/src/Navigation.js +171 -171
  86. package/lib/components/navigation/src/Navigation.stories.js +24 -24
  87. package/lib/components/navigation/src/index.js +3 -3
  88. package/lib/components/selfie/README.md +225 -225
  89. package/lib/components/selfie/src/SelfieCaptureScreens.js +420 -420
  90. package/lib/components/selfie/src/SelfieCaptureScreens.stories.js +29 -29
  91. package/lib/components/selfie/src/index.js +3 -3
  92. package/lib/components/selfie/src/selfie-capture/SelfieCapture.js +1099 -1099
  93. package/lib/components/selfie/src/selfie-capture/SelfieCapture.stories.js +36 -36
  94. package/lib/components/selfie/src/selfie-capture/index.js +3 -3
  95. package/lib/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +689 -689
  96. package/lib/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +23 -23
  97. package/lib/components/selfie/src/selfie-capture-instructions/index.js +3 -3
  98. package/lib/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +209 -209
  99. package/lib/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +24 -24
  100. package/lib/components/selfie/src/selfie-capture-review/index.js +3 -3
  101. package/lib/components/selfie/src/selfie-capture-wrapper/SelfieCaptureWrapper.tsx +281 -256
  102. package/lib/components/selfie/src/selfie-capture-wrapper/index.ts +1 -1
  103. package/lib/components/selfie/src/smartselfie-capture/OvalProgress.tsx +81 -81
  104. package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +265 -265
  105. package/lib/components/selfie/src/smartselfie-capture/components/AlertDisplay.tsx +34 -34
  106. package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +97 -97
  107. package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +78 -78
  108. package/lib/components/selfie/src/smartselfie-capture/components/index.ts +3 -3
  109. package/lib/components/selfie/src/smartselfie-capture/constants.ts +23 -23
  110. package/lib/components/selfie/src/smartselfie-capture/hooks/index.ts +2 -2
  111. package/lib/components/selfie/src/smartselfie-capture/hooks/useCamera.ts +238 -238
  112. package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +618 -618
  113. package/lib/components/selfie/src/smartselfie-capture/index.ts +1 -1
  114. package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +13 -13
  115. package/lib/components/selfie/src/smartselfie-capture/utils/canvas.ts +105 -105
  116. package/lib/components/selfie/src/smartselfie-capture/utils/faceDetection.ts +129 -129
  117. package/lib/components/selfie/src/smartselfie-capture/utils/imageCapture.ts +64 -64
  118. package/lib/components/selfie/src/smartselfie-capture/utils/index.ts +4 -4
  119. package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +118 -77
  120. package/lib/components/signature-pad/package-lock.json +3009 -3009
  121. package/lib/components/signature-pad/package.json +30 -30
  122. package/lib/components/signature-pad/src/SignaturePad.js +484 -484
  123. package/lib/components/signature-pad/src/SignaturePad.stories.js +32 -32
  124. package/lib/components/signature-pad/src/index.js +3 -3
  125. package/lib/components/smart-camera-web/src/README.md +206 -206
  126. package/lib/components/smart-camera-web/src/SmartCameraWeb.js +305 -305
  127. package/lib/components/smart-camera-web/src/SmartCameraWeb.stories.js +57 -57
  128. package/lib/components/totp-consent/src/TotpConsent.js +949 -949
  129. package/lib/components/totp-consent/src/index.js +4 -4
  130. package/lib/domain/camera/src/README.md +38 -38
  131. package/lib/domain/camera/src/SmartCamera.js +109 -109
  132. package/lib/domain/constants/src/Constants.js +27 -27
  133. package/lib/domain/file-upload/README.md +35 -35
  134. package/lib/domain/file-upload/src/SmartFileUpload.js +65 -65
  135. package/lib/styles/README.md +3 -3
  136. package/lib/styles/src/styles.js +372 -372
  137. package/lib/styles/src/typography.js +52 -52
  138. package/package.json +111 -112
  139. package/dist/esm/DocumentCaptureScreens-RECPb0wH.js.map +0 -1
  140. package/dist/esm/SelfieCaptureScreens-CqBVGEJk.js.map +0 -1
  141. package/dist/esm/package-BDJnoIAU.js.map +0 -1
@@ -1,1099 +1,1099 @@
1
- import { IMAGE_TYPE } from '../../../../domain/constants/src/Constants';
2
- import SmartCamera from '../../../../domain/camera/src/SmartCamera';
3
- import styles from '../../../../styles/src/styles';
4
- import packageJson from '../../../../../package.json';
5
- import '../../../navigation/src';
6
-
7
- const COMPONENTS_VERSION = packageJson.version;
8
-
9
- const DEFAULT_NO_OF_LIVENESS_FRAMES = 8;
10
-
11
- function hasMoreThanNColors(data, n = 16) {
12
- const colors = new Set();
13
- for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
14
- // eslint-disable-next-line no-bitwise
15
- colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
16
- if (colors.size > n) {
17
- return true;
18
- }
19
- }
20
- return false;
21
- }
22
-
23
- function getLivenessFramesIndices(
24
- totalNoOfFrames,
25
- numberOfFramesRequired = DEFAULT_NO_OF_LIVENESS_FRAMES,
26
- ) {
27
- const selectedFrames = [];
28
-
29
- if (totalNoOfFrames < numberOfFramesRequired) {
30
- throw new Error(
31
- 'SmartCameraWeb: Minimum required no of frames is ',
32
- numberOfFramesRequired,
33
- );
34
- }
35
-
36
- const frameDivisor = numberOfFramesRequired - 1;
37
- const frameInterval = Math.floor(totalNoOfFrames / frameDivisor);
38
-
39
- // NOTE: when we have satisfied our required 8 frames, but have good
40
- // candidates, we need to start replacing from the second frame
41
- let replacementFrameIndex = 1;
42
-
43
- for (let i = 0; i < totalNoOfFrames; i += frameInterval) {
44
- if (selectedFrames.length < 8) {
45
- selectedFrames.push(i);
46
- } else {
47
- // ACTION: replace frame, then sort selectedframes
48
- selectedFrames[replacementFrameIndex] = i;
49
- selectedFrames.sort((a, b) => a - b);
50
-
51
- // ACTION: update replacement frame index
52
- replacementFrameIndex += 1;
53
- }
54
- }
55
-
56
- // INFO: if we don't satisfy our requirement, we add the last index
57
- const lastFrameIndex = totalNoOfFrames - 1;
58
-
59
- if (selectedFrames.length < 8 && !selectedFrames.includes(lastFrameIndex)) {
60
- selectedFrames.push(lastFrameIndex);
61
- }
62
-
63
- return selectedFrames;
64
- }
65
-
66
- function templateString() {
67
- return `
68
- ${styles(this.themeColor)}
69
- <style>
70
- :host {
71
- --theme-color: ${this.themeColor || '#001096'};
72
- --color-active: #001096;
73
- --color-default: #2D2B2A;
74
- --color-disabled: #848282;
75
- }
76
-
77
- * {
78
- font-family: 'DM Sans', sans-serif;
79
- }
80
-
81
- [hidden] {
82
- display: none !important;
83
- }
84
-
85
- [disabled] {
86
- cursor: not-allowed !important;
87
- filter: grayscale(75%);
88
- }
89
-
90
- .visually-hidden {
91
- border: 0;
92
- clip: rect(1px 1px 1px 1px);
93
- clip: rect(1px, 1px, 1px, 1px);
94
- height: auto;
95
- margin: 0;
96
- overflow: hidden;
97
- padding: 0;
98
- position: absolute;
99
- white-space: nowrap;
100
- width: 1px;
101
- }
102
-
103
- img {
104
- height: auto;
105
- max-width: 100%;
106
- transform: scaleX(-1);
107
- }
108
-
109
- video {
110
- background-color: black;
111
- }
112
-
113
- a {
114
- color: currentColor;
115
- text-decoration: none;
116
- }
117
-
118
- svg {
119
- max-width: 100%;
120
- }
121
-
122
- .color-gray {
123
- color: #797979;
124
- }
125
-
126
- .color-red {
127
- color: red;
128
- }
129
-
130
- .color-richblue {
131
- color: #4E6577;
132
- }
133
-
134
- .color-richblue-shade {
135
- color: #0E1B42;
136
- }
137
-
138
- .color-digital-blue {
139
- color: #001096 !important;
140
- }
141
-
142
- .color-deep-blue {
143
- color: #001096;
144
- }
145
-
146
- .title-color {
147
- color: ${this.themeColor};
148
- }
149
-
150
- .theme-color {
151
- color: ${this.themeColor};
152
- }
153
-
154
- .center {
155
- text-align: center;
156
- margin-left: auto;
157
- margin-right: auto;
158
- }
159
-
160
- .font-size-small {
161
- font-size: .75rem;
162
- }
163
-
164
- .font-size-large {
165
- font-size: 1.5rem;
166
- }
167
-
168
- .text-transform-uppercase {
169
- text-transform: uppercase;
170
- }
171
-
172
- [id*=-"screen"] {
173
- min-block-size: 100%;
174
- }
175
-
176
- [data-variant~="full-width"] {
177
- inline-size: 100%;
178
- }
179
-
180
- .flow > * + * {
181
- margin-top: 1rem;
182
- }
183
-
184
- .button {
185
- --button-color: ${this.themeColor};
186
- -webkit-appearance: none;
187
- appearance: none;
188
- border-radius: 2.5rem;
189
- border: 0;
190
- background-color: transparent;
191
- color: #fff;
192
- cursor: pointer;
193
- display: block;
194
- font-size: 18px;
195
- font-weight: 600;
196
- padding: .75rem 1.5rem;
197
- text-align: center;
198
- }
199
-
200
- .button:hover,
201
- .button:focus,
202
- .button:active {
203
- --button-color: var(--color-default);
204
- }
205
-
206
- .button:disabled {
207
- --button-color: var(--color-disabled);
208
- }
209
-
210
- .button[data-variant~='solid'] {
211
- background-color: var(--button-color);
212
- border: 2px solid var(--button-color);
213
- }
214
-
215
- .button[data-variant~='outline'] {
216
- color: var(--button-color);
217
- border: 2px solid var(--button-color);
218
- }
219
-
220
- .button[data-variant~='ghost'] {
221
- padding: 0px;
222
- color: var(--button-color);
223
- background-color: transparent;
224
- }
225
-
226
- .icon-btn {
227
- appearance: none;
228
- background: none;
229
- border: none;
230
- color: hsl(0deg 0% 94%);
231
- cursor: pointer;
232
- display: flex;
233
- align-items: center;
234
- justify-content: center;
235
- padding: 4px 8px;
236
- }
237
- .justify-right {
238
- justify-content: end !important;
239
- }
240
- .nav {
241
- display: flex;
242
- justify-content: space-between;
243
- }
244
-
245
- .back-wrapper {
246
- display: flex;
247
- align-items: center;
248
- }
249
-
250
- .back-button {
251
- display: block !important;
252
- }
253
- .back-button-text {
254
- font-size: 11px;
255
- line-height: 11px;
256
- color: rgb(21, 31, 114);
257
- }
258
- .section {
259
- border-radius: .5rem;
260
- margin-left: auto;
261
- margin-right: auto;
262
- max-width: 35ch;
263
- padding: 1rem;
264
- }
265
-
266
- .selfie-capture-review-image {
267
- overflow: hidden;
268
- aspect-ratio: 1/1;
269
- }
270
-
271
- #review-image {
272
- scale: 1.75;
273
- }
274
-
275
- @media (max-aspect-ratio: 1/1) {
276
- #review-image {
277
- transform: scaleX(-1) translateY(-10%);
278
- }
279
- }
280
-
281
- .tips,
282
- .powered-by {
283
- align-items: center;
284
- border-radius: .25rem;
285
- display: flex;
286
- justify-content: center;
287
- letter-spacing: .075em;
288
- }
289
-
290
- .powered-by {
291
- box-shadow: 0px 2.57415px 2.57415px rgba(0, 0, 0, 0.06);
292
- display: inline-flex;
293
- font-size: .5rem;
294
- }
295
-
296
- .tips {
297
- margin-left: auto;
298
- margin-right: auto;
299
- max-width: 17rem;
300
- font-size: 0.875rem;
301
- font-weight: 600;
302
- }
303
-
304
- .tips > * + *,
305
- .powered-by > * + * {
306
- display: inline-block;
307
- margin-left: .5em;
308
- }
309
-
310
- .powered-by .company {
311
- color: #18406D;
312
- font-weight: 700;
313
- letter-spacing: .15rem;
314
- }
315
-
316
- .logo-mark {
317
- background-color: #004071;
318
- display: inline-block;
319
- padding: .25em .5em;
320
- }
321
-
322
- .logo-mark svg {
323
- height: auto;
324
- justify-self: center;
325
- width: .75em;
326
- }
327
-
328
- .id-video-container.portrait {
329
- width: 100%;
330
- position: relative;
331
- height: calc(200px * 1.4);
332
- }
333
-
334
- .id-video-container.portrait video {
335
- width: calc(213px + 0.9rem);
336
- height: 100%;
337
- position: absolute;
338
- top: 239px;
339
- left: 161px;
340
- padding-bottom: calc((214px * 1.4) / 3);
341
- padding-top: calc((191px * 1.4) / 3);
342
- object-fit: cover;
343
-
344
- transform: translateX(-50%) translateY(-50%);
345
- z-index: 1;
346
- block-size: 100%;
347
- }
348
-
349
- .video-container,
350
- .id-video-container.landscape {
351
- position: relative;
352
- z-index: 1;
353
- width: 100%;
354
- overflow: hidden;
355
- }
356
-
357
- .video-container video,
358
- .id-video-container.landscape video {
359
- left: 50%;
360
- min-width: auto;
361
- position: absolute;
362
- top: 50%;
363
- transform: translateX(-50%) translateY(50%);
364
- }
365
-
366
- .video-container #smile-cta-box {
367
- color: #fff;
368
- width: 100%;
369
- position: absolute;
370
- top: 0;
371
- left: 0;
372
- padding: 2rem 0;
373
- opacity: 0;
374
- transition: opacity 0.5s ease-in-out;
375
- }
376
-
377
- .video-container #smile-cta {
378
- font-size: 1.2rem;
379
- font-weight: bold;
380
- margin-top: 0;
381
- margin-bottom: 0;
382
- }
383
-
384
- .video-container video {
385
- height: 150%;
386
- transform: scaleX(-1) translateX(50%) translateY(-50%);
387
- }
388
-
389
- .video-container video.agent-mode {
390
- min-height: 100%;
391
- transform: scaleX(1) translateX(-50%) translateY(-50%);
392
- }
393
-
394
- .video-container .video {
395
- background-color: black;
396
- position: absolute;
397
- left: 50%;
398
- height: calc(100% - 6px);
399
- clip-path: ellipse(101px 118px);
400
- }
401
-
402
- .id-video-container.landscape {
403
- min-height: calc((2 * 10rem) + 198px);
404
- height: auto;
405
- }
406
-
407
- .id-video-container.portrait .image-frame-portrait {
408
- border-width: 0.9rem;
409
- border-color: rgba(0, 0, 0, 0.7);
410
- border-style: solid;
411
- height: auto;
412
- position: absolute;
413
- top: 80px;
414
- left: 47px;
415
- z-index: 2;
416
- width: 200px;
417
- height: calc(200px * 1.4);
418
- }
419
-
420
- .id-video-container.landscape .image-frame {
421
- border-width: 10rem 1rem;
422
- border-color: rgba(0, 0, 0, 0.7);
423
- border-style: solid;
424
- height: auto;
425
- width: 90%;
426
- position: absolute;
427
- top: 0;
428
- left: 0;
429
- z-index: 2;
430
- }
431
-
432
- .id-video-container.landscape video {
433
- width: 100%;
434
- transform: translateX(-50%) translateY(-50%);
435
- z-index: 1;
436
- height: 100%;
437
- block-size: 100%;
438
- }
439
-
440
- .id-video-container.landscape img {
441
- position: absolute;
442
- top: 50%;
443
- left: 50%;
444
- transform: translateX(-50%) translateY(-50%);
445
- max-width: 90%;
446
- }
447
-
448
- .actions {
449
- background-color: rgba(0, 0, 0, .7);
450
- bottom: 0;
451
- display: flex;
452
- justify-content: space-between;
453
- padding: 1rem;
454
- position: absolute;
455
- width: 90%;
456
- z-index: 2;
457
- }
458
-
459
- #back-of-id-camera-screen .id-video-container.portrait .actions,
460
- #id-camera-screen .id-video-container.portrait .actions {
461
- top: 145%;
462
- width: calc(200px * 1.4);
463
- }
464
-
465
- #back-of-id-camera-screen .section.portrait, #id-camera-screen .section.portrait {
466
- min-height: calc((200px * 1.4) + 260px);
467
- }
468
-
469
- #selfie-capture-screen,
470
- #back-of-id-entry-screen {
471
- box-sizing: border-box;
472
- display: flex;
473
- flex-direction: column;
474
- max-block-size: 100%;
475
- max-inline-size: 40ch;
476
- padding: 1rem;
477
- }
478
-
479
- #selfie-capture-screen header p {
480
- margin-block: 0 !important;
481
- }
482
-
483
- .document-tips {
484
- margin-block-start: 1.5rem;
485
- display: flex;
486
- align-items: center;
487
- text-align: initial;
488
- }
489
-
490
- .document-tips svg {
491
- flex-shrink: 0;
492
- margin-inline-end: 1rem;
493
- }
494
-
495
- .document-tips p {
496
- margin-block: 0;
497
- }
498
-
499
- .document-tips p:first-of-type {
500
- font-size; 1.875rem;
501
- font-weight: bold
502
- }
503
-
504
- [type='file'] {
505
- display: none;
506
- }
507
-
508
- .document-tips > * + * {
509
- margin-inline-start; 1em;
510
- }
511
- </style>
512
- <div id='selfie-capture-screen' class='flow center'>
513
- <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
514
-
515
- <div class='tips'>Fit your head inside the oval frame</div>
516
-
517
- <div className="error">
518
- ${this.cameraError ? `<p class="color-red">${this.cameraError}</p>` : ''}
519
- </div>
520
- <div class='section | flow' ${this.cameraError ? 'hidden' : ''}>
521
- <div class='video-container'>
522
- <div class='video'>
523
- </div>
524
- <svg id="image-outline" width="215" height="245" viewBox="0 0 215 245" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: relative;">
525
- <path d="M210.981 122.838C210.981 188.699 164.248 241.268 107.55 241.268C50.853 241.268 4.12018 188.699 4.12018 122.838C4.12018 56.9763 50.853 4.40771 107.55 4.40771C164.248 4.40771 210.981 56.9763 210.981 122.838Z" stroke="${this.themeColor}" stroke-width="7.13965"/>
526
- </svg>
527
- <div id="smile-cta-box">
528
- <div>
529
- <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" viewBox="70 70 360 360" fill="currentColor">
530
- <path d="M354.02,247.69c4.12-12.26,6.39-24.53,6.39-35.75,0-54.63-50.19-99.08-111.88-99.08s-111.95,44.45-111.95,99.08c0,11.22,2.28,23.49,6.39,35.75-6.6,4.66-10.94,12.33-10.94,21.04,0,13.45,10.36,24.4,23.46,25.51.52,26.93,12.56,53.51,35.04,71.64v.73c.07.03.14.05.21.08,3.66,1.63,6.74,4.28,9.92,6.66.08.06.17.12.25.17,0-.08-.01-.16-.01-.25,15.23,9.12,32.95,13.89,51.85,12.96h0c15.54-.72,30.01-5.37,42.59-12.99,2.91-2.85,7.01-4.93,10.42-6.71.25-.16.51-.28.77-.43v-.76c20.36-16.83,33.62-42.32,34.85-71.11,13.15-1.05,23.57-12.03,23.57-25.53,0-8.7-4.35-16.38-10.94-21.04ZM155.53,283.54c-7.18-1.09-12.71-7.31-12.71-14.85s5.53-13.75,12.71-14.85v29.7ZM330.53,289.9c0,45.26-33.98,83.65-78.48,85.72-47.29,2.31-86.52-35.65-86.52-83.18v-40.7c15.63-4.03,38.45-15.61,65.91-46.31.08.59.13,1.21.11,1.87-.13,4.43-3.09,9.32-8.58,14.14-1.79,1.57-2.32,4.13-1.31,6.28,1.01,2.14,3.29,3.35,5.68,3,2.19-.35,48.6-8.13,71.88-38.36,3.84,13.25,12.48,33.59,31.31,48.79v48.75ZM339.42,234.31c-27.83-21.03-32.1-56.22-32.15-56.57-.26-2.37-2.08-4.28-4.43-4.67-2.33-.39-4.68.83-5.69,2.99-11.08,23.62-39.34,35.42-56.72,40.63,1.35-2.97,2.08-6,2.16-9.06.26-9.11-5.24-14.81-5.88-15.43-1.06-1.05-2.53-1.59-3.99-1.53-1.48.07-2.87.75-3.83,1.88-32.09,37.93-57.18,47.4-69.28,49.67-.68.13-1.29.39-1.84.74-.11,0-.18-.03-.29-.03-1.65,0-3.26.19-4.82.49-3.58-10.9-5.56-21.72-5.56-31.57,0-48.78,45.46-88.46,101.33-88.46s101.26,39.68,101.26,88.46c0,9.84-1.98,20.67-5.56,31.57-.85-.16-1.73-.19-2.61-.27v-4.6c0-1.67-.78-3.25-2.12-4.26ZM341.53,283.54v-29.7c7.18,1.09,12.72,7.3,12.72,14.85s-5.53,13.76-12.72,14.85Z"/>
531
- <path d="M259.05,305.3c2.08-2.08,2.08-5.46,0-7.55-2.09-2.08-5.47-2.07-7.54,0-1.75,1.75-4.79,1.75-6.54,0-2.08-2.08-5.45-2.09-7.54,0-2.08,2.08-2.08,5.46,0,7.55,5.98,5.98,15.65,5.98,21.63,0h0Z"/>
532
- <path id="mouth"
533
- d="m 213.88314,319.4551 c -1.58,0.97 -0.35309,9.33393 1.50671,9.30586 6.05679,-0.0914 16.11631,0.17227 34.57066,0.13346 18.45435,-0.0388 28.15778,-0.0418 31.09964,-0.79956 1.80122,-0.46394 2.75061,-7.48365 1.16061,-8.45365 -1.6,-1.74874 -2.96432,-0.94348 -6.77747,-1.56441 -12.83012,0.04 -36.52534,0.50197 -41.29469,0.43262 -2.51525,-0.0713 -18.41588,-0.61 -20.01588,0.35 z m 57.29363,1.36599 c -9.24417,-2.23757 -8.08363,-2.42362 -20.78363,-2.42362 -12.7,0 -17.77931,2.69528 -26.84042,5.36549 12.57883,3.28731 33.57775,-4.29887 49.70067,2.24964 z">
534
- <animate
535
- id="mouthAnim"
536
- attributeName="d"
537
- begin="indefinite"
538
- dur="1s"
539
- fill="freeze"
540
- to="m 211.72,312.36 c -1.58,0.97 -2.56,2.86 -2.56,4.72 0,21.54 17.40021,38.7239 38.94021,38.7239 21.54,0 39.18979,-17.1739 39.18979,-39.0439 0,-1.86 -0.97,-3.59 -2.56,-4.56 -1.6,-0.97 -3.57,-1.03 -5.22,-0.18 -21.07,10.85 -41.53,10.85 -62.58,0 -1.65,-0.85 -3.62,-0.61 -5.22,0.35 z m 63.61,13.22 c -3.62,11.52 -14.4,19.9 -27.1,19.9 -12.7,0 -23.49,-8.38 -27.1,-19.9 18.03,6.94 36.2,6.94 54.2,0 z" />
541
- </path>
542
- <circle fill="none" stroke="currentColor" stroke-width="10px" cx="287.3" cy="263.68" r="14"/>
543
- <circle fill="none" stroke="currentColor" stroke-width="10px" cx="209.16" cy="263.68" r="14"/>
544
- </svg>
545
- </div>
546
- <div>
547
- <p id='smile-cta'>SMILE</p>
548
- </div>
549
- </div>
550
- </div>
551
-
552
- ${this.allowAgentMode ? `<button data-variant='outline small' id='switch-camera' class='button | center' type='button'>${this.inAgentMode ? 'Agent Mode On' : 'Agent Mode Off'}</button>` : ''}
553
-
554
- <button data-variant='solid' id='start-image-capture' class='button | center' type='button'>
555
- Take Selfie
556
- </button>
557
-
558
- ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
559
- </div>
560
- </div>
561
- `;
562
- }
563
-
564
- async function getPermissions(
565
- captureScreen,
566
- constraints = { facingMode: 'user' },
567
- ) {
568
- try {
569
- const stream = await SmartCamera.getMedia({
570
- audio: false,
571
- video: constraints,
572
- });
573
- const devices = await navigator.mediaDevices.enumerateDevices();
574
- const videoDevice = devices.find(
575
- (device) =>
576
- device.kind === 'videoinput' &&
577
- stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
578
- );
579
- const smartCameraWeb = document.querySelector('smart-camera-web');
580
- smartCameraWeb?.dispatchEvent(
581
- new CustomEvent('metadata.camera-name', {
582
- detail: { cameraName: videoDevice?.label },
583
- }),
584
- );
585
- captureScreen?.removeAttribute('data-camera-error');
586
- captureScreen?.setAttribute('data-camera-ready', true);
587
- } catch (error) {
588
- captureScreen?.removeAttribute('data-camera-ready');
589
- captureScreen?.setAttribute(
590
- 'data-camera-error',
591
- SmartCamera.handleCameraError(error),
592
- );
593
- }
594
- }
595
-
596
- class SelfieCaptureScreen extends HTMLElement {
597
- constructor() {
598
- super();
599
- this.templateString = templateString.bind(this);
600
- this.render = () => this.templateString();
601
-
602
- this.attachShadow({ mode: 'open' });
603
- this.facingMode = 'user';
604
- if (this.allowAgentMode) {
605
- this.facingMode = 'environment';
606
- }
607
- }
608
-
609
- connectedCallback() {
610
- const template = document.createElement('template');
611
- template.innerHTML = this.render();
612
- this.shadowRoot.innerHTML = '';
613
- this.shadowRoot.appendChild(template.content.cloneNode(true));
614
- this.videoContainer = this.shadowRoot.querySelector(
615
- '.video-container > .video',
616
- );
617
- this.init();
618
- }
619
-
620
- init() {
621
- this._videoStreamDurationInMS = 7800;
622
- this._imageCaptureIntervalInMS = 200;
623
-
624
- this._data = {
625
- images: [],
626
- meta: {
627
- libraryVersion: COMPONENTS_VERSION,
628
- },
629
- };
630
- this._rawImages = [];
631
-
632
- this.setUpEventListeners();
633
- }
634
-
635
- reset() {
636
- this.disconnectedCallback();
637
- this.connectedCallback();
638
- }
639
-
640
- _startImageCapture() {
641
- this.startImageCapture.disabled = true;
642
- if (this.switchCamera) {
643
- this.switchCamera.disabled = true;
644
- }
645
-
646
- const smartCameraWeb = document.querySelector('smart-camera-web');
647
- smartCameraWeb?.dispatchEvent(
648
- new CustomEvent('metadata.selfie-capture-start'),
649
- );
650
- smartCameraWeb?.dispatchEvent(
651
- new CustomEvent('metadata.selfie-origin', {
652
- detail: {
653
- imageOrigin: {
654
- environment: 'back_camera',
655
- user: 'front_camera',
656
- }[this.inAgentMode ? 'environment' : 'user'],
657
- },
658
- }),
659
- );
660
-
661
- /**
662
- * this was culled from https://jakearchibald.com/2013/animated-line-drawing-svg/
663
- */
664
- // NOTE: initialise image outline
665
- const imageOutlineLength = this.imageOutline.getTotalLength();
666
- // Clear any previous transition
667
- this.imageOutline.style.transition = 'none';
668
- // Set up the starting positions
669
- this.imageOutline.style.strokeDasharray = `${imageOutlineLength} ${imageOutlineLength}`;
670
- this.imageOutline.style.strokeDashoffset = imageOutlineLength;
671
- // Trigger a layout so styles are calculated & the browser
672
- // picks up the starting position before animating
673
- this.imageOutline.getBoundingClientRect();
674
- // Define our transition
675
- this.imageOutline.style.transition = `stroke-dashoffset ${this._videoStreamDurationInMS / 1000}s ease-in-out`;
676
- // Go!
677
- this.imageOutline.style.strokeDashoffset = '0';
678
-
679
- setTimeout(() => {
680
- this.smileCTABox.style.opacity = 1;
681
- this.mouthAnim.beginElement();
682
- }, 1500);
683
-
684
- setTimeout(() => {
685
- this.smileCTABox.style.opacity = 0;
686
- }, 3500);
687
-
688
- setTimeout(() => {
689
- this.smileCTABox.style.opacity = 1;
690
- this.smileCTA.textContent = 'WIDER SMILE';
691
- this.mouth.setAttribute(
692
- 'd',
693
- 'm 213.88314,319.4551 c -1.58,0.97 -0.35309,9.33393 1.50671,9.30586 6.05679,-0.0914 16.11631,0.17227 34.57066,0.13346 18.45435,-0.0388 28.15778,-0.0418 31.09964,-0.79956 1.80122,-0.46394 2.75061,-7.48365 1.16061,-8.45365 -1.6,-1.74874 -2.96432,-0.94348 -6.77747,-1.56441 -12.83012,0.04 -36.52534,0.50197 -41.29469,0.43262 -2.51525,-0.0713 -18.41588,-0.61 -20.01588,0.35 z m 57.29363,1.36599 c -9.24417,-2.23757 -8.08363,-2.42362 -20.78363,-2.42362 -12.7,0 -17.77931,2.69528 -26.84042,5.36549 12.57883,3.28731 33.57775,-4.29887 49.70067,2.24964 z',
694
- );
695
- this.mouthAnim.beginElement();
696
- }, 4000);
697
-
698
- this._imageCaptureInterval = setInterval(() => {
699
- this._capturePOLPhoto();
700
- }, this._imageCaptureIntervalInMS);
701
-
702
- this._videoStreamTimeout = setTimeout(() => {
703
- this._stopVideoStream();
704
- }, this._videoStreamDurationInMS);
705
- }
706
-
707
- async _switchCamera() {
708
- this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
709
- if (this.facingMode === 'user') {
710
- this.shadowRoot.querySelector('video').classList.remove('agent-mode');
711
- } else {
712
- this.shadowRoot.querySelector('video').classList.add('agent-mode');
713
- }
714
- this.startImageCapture.disabled = true;
715
- this.switchCamera.disabled = true;
716
- SmartCamera.stopMedia();
717
- await getPermissions(this, { facingMode: this.facingMode });
718
- this.handleStream(SmartCamera.stream);
719
- }
720
-
721
- _stopVideoStream() {
722
- try {
723
- clearTimeout(this._videoStreamTimeout);
724
- clearInterval(this._imageCaptureInterval);
725
- clearInterval(this._drawingInterval);
726
-
727
- this._capturePOLPhoto(); // NOTE: capture the last photo
728
- this._captureReferencePhoto();
729
- SmartCamera.stopMedia();
730
-
731
- const totalNoOfFrames = this._rawImages.length;
732
- this._data.referenceImage = this._referenceImage;
733
- this._data.previewImage = this._referenceImage;
734
-
735
- const livenessFramesIndices = getLivenessFramesIndices(totalNoOfFrames);
736
-
737
- this._data.images = this._data.images.concat(
738
- livenessFramesIndices.map((imageIndex) => ({
739
- image: this._rawImages[imageIndex].split(',')[1],
740
- image_type_id: IMAGE_TYPE.LIVENESS_IMAGE_BASE64,
741
- })),
742
- );
743
-
744
- this._publishImages();
745
- } catch (error) {
746
- console.error(error);
747
- // Todo: handle error
748
- }
749
- }
750
-
751
- _capturePOLPhoto() {
752
- const canvas = document.createElement('canvas');
753
- // Determine orientation of the video
754
- const isPortrait = this._video.videoHeight > this._video.videoWidth;
755
-
756
- // Set dimensions based on orientation, ensuring minimums
757
- if (isPortrait) {
758
- // Portrait orientation (taller than wide)
759
- canvas.width = 240;
760
- canvas.height = Math.max(
761
- 320,
762
- (canvas.width * this._video.videoHeight) / this._video.videoWidth,
763
- );
764
- } else {
765
- // Landscape orientation (wider than tall)
766
- canvas.height = 240;
767
- canvas.width = Math.max(
768
- 320,
769
- (canvas.height * this._video.videoWidth) / this._video.videoHeight,
770
- );
771
- }
772
-
773
- // NOTE: we do not want to test POL images
774
- this._drawImage(canvas, false);
775
-
776
- this._rawImages.push(canvas.toDataURL('image/jpeg'));
777
- }
778
-
779
- _captureReferencePhoto() {
780
- const canvas = document.createElement('canvas');
781
- // Determine orientation of the video
782
- const isPortrait = this._video.videoHeight > this._video.videoWidth;
783
-
784
- // Set dimensions based on orientation, ensuring minimums
785
- if (isPortrait) {
786
- // Portrait orientation (taller than wide)
787
- canvas.width = 480;
788
- canvas.height = Math.max(
789
- 640,
790
- (canvas.width * this._video.videoHeight) / this._video.videoWidth,
791
- );
792
- } else {
793
- // Landscape orientation (wider than tall)
794
- canvas.height = 480;
795
- canvas.width = Math.max(
796
- 640,
797
- (canvas.height * this._video.videoWidth) / this._video.videoHeight,
798
- );
799
- }
800
-
801
- // NOTE: we want to test the image quality of the reference photo
802
- this._drawImage(canvas, !this.disableImageTests);
803
-
804
- const image = canvas.toDataURL('image/jpeg');
805
-
806
- this._referenceImage = image;
807
-
808
- this._data.images.push({
809
- image: image.split(',')[1],
810
- image_type_id: IMAGE_TYPE.SELFIE_IMAGE_BASE64,
811
- });
812
- }
813
-
814
- _publishImages() {
815
- const eventDetail = {
816
- ...this._data,
817
- facingMode: this.facingMode,
818
- };
819
-
820
- this.dispatchEvent(
821
- new CustomEvent('selfie-capture.publish', {
822
- detail: eventDetail,
823
- }),
824
- );
825
- }
826
-
827
- resetErrorMessage() {
828
- this.errorMessage.textContent = '';
829
- }
830
-
831
- _drawImage(canvas, enableImageTests = true, video = this._video) {
832
- // this.resetErrorMessage();
833
- const context = canvas.getContext('2d');
834
-
835
- context.drawImage(
836
- video,
837
- 0,
838
- 0,
839
- video.videoWidth,
840
- video.videoHeight,
841
- 0,
842
- 0,
843
- canvas.width,
844
- canvas.height,
845
- );
846
-
847
- if (enableImageTests) {
848
- const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
849
-
850
- const hasEnoughColors = hasMoreThanNColors(imageData.data);
851
-
852
- if (hasEnoughColors) {
853
- return context;
854
- }
855
- throw new Error(
856
- 'Unable to capture webcam images - Please try another device',
857
- );
858
- } else {
859
- return context;
860
- }
861
- }
862
-
863
- handleStream(stream) {
864
- try {
865
- const videoContainer = this.shadowRoot.querySelector('.video');
866
- if (!videoContainer) {
867
- return;
868
- }
869
-
870
- const videoExists = this.shadowRoot.querySelector('video');
871
- let video = null;
872
- if (videoExists) {
873
- video = this.shadowRoot.querySelector('video');
874
- } else {
875
- video = document.createElement('video');
876
- }
877
-
878
- video.autoplay = true;
879
- video.playsInline = true;
880
- video.muted = true;
881
-
882
- if ('srcObject' in video) {
883
- video.srcObject = stream;
884
- } else {
885
- video.src = window.URL.createObjectURL(stream);
886
- }
887
-
888
- video.onloadedmetadata = () => {
889
- video.play();
890
- };
891
-
892
- this._video = video;
893
- this._data.permissionGranted = true;
894
-
895
- if (!videoExists) {
896
- videoContainer.prepend(video);
897
- }
898
- } catch (error) {
899
- this.setAttribute(
900
- 'data-camera-error',
901
- SmartCamera.handleCameraError(error),
902
- );
903
- if (error.name !== 'AbortError') {
904
- console.error(error);
905
- }
906
- SmartCamera.stopMedia();
907
- }
908
- }
909
-
910
- setUpEventListeners() {
911
- this.navigation = this.shadowRoot.querySelector('smileid-navigation');
912
-
913
- this.startImageCapture = this.shadowRoot.querySelector(
914
- '#start-image-capture',
915
- );
916
-
917
- this.switchCamera = this.shadowRoot.querySelector('#switch-camera');
918
- this.imageOutline = this.shadowRoot.querySelector('#image-outline path');
919
- this.smileCTABox = this.shadowRoot.querySelector('#smile-cta-box');
920
- this.smileCTA = this.shadowRoot.querySelector('#smile-cta');
921
- this.mouth = this.shadowRoot.querySelector('#mouth');
922
- this.mouthAnim = this.shadowRoot.querySelector('#mouthAnim');
923
-
924
- this.startImageCapture.addEventListener('click', () => {
925
- this._startImageCapture();
926
- });
927
-
928
- this.switchCamera?.addEventListener('click', () => {
929
- this._switchCamera();
930
- });
931
-
932
- this.navigation.addEventListener('navigation.back', () => {
933
- this.handleBackEvents();
934
- });
935
-
936
- this.navigation.addEventListener('navigation.close', () => {
937
- this.closeWindow();
938
- });
939
-
940
- if (SmartCamera.stream) {
941
- this.handleStream(SmartCamera.stream);
942
- } else if (
943
- this.hasAttribute('data-camera-ready') ||
944
- !this.hasAttribute('data-camera-error')
945
- ) {
946
- getPermissions(this, { facingMode: this.facingMode });
947
- }
948
-
949
- this.setupAgentMode();
950
- }
951
-
952
- disconnectedCallback() {
953
- SmartCamera.stopMedia();
954
- clearTimeout(this._videoStreamTimeout);
955
- }
956
-
957
- get hideBack() {
958
- return this.hasAttribute('hide-back');
959
- }
960
-
961
- get showNavigation() {
962
- return this.hasAttribute('show-navigation');
963
- }
964
-
965
- get themeColor() {
966
- return this.getAttribute('theme-color') || '#001096';
967
- }
968
-
969
- get hideAttribution() {
970
- return this.hasAttribute('hide-attribution');
971
- }
972
-
973
- async setupAgentMode() {
974
- if (!this.allowAgentMode) {
975
- return;
976
- }
977
-
978
- const supportAgentMode = await SmartCamera.supportsAgentMode();
979
-
980
- if (supportAgentMode || this.hasAttribute('show-agent-mode-for-tests')) {
981
- this.switchCamera.hidden = false;
982
- if (this.facingMode === 'user') {
983
- this.shadowRoot.querySelector('video')?.classList?.remove('agent-mode');
984
- } else {
985
- this.shadowRoot.querySelector('video')?.classList?.add('agent-mode');
986
- }
987
- } else {
988
- this.switchCamera.hidden = true;
989
- }
990
- }
991
-
992
- get hasAgentSupport() {
993
- return this.hasAttribute('has-agent-support');
994
- }
995
-
996
- get title() {
997
- return this.getAttribute('title') || 'Submit Front of ID';
998
- }
999
-
1000
- get hidden() {
1001
- return this.getAttribute('hidden');
1002
- }
1003
-
1004
- get cameraError() {
1005
- return this.getAttribute('data-camera-error');
1006
- }
1007
-
1008
- get disableImageTests() {
1009
- return this.hasAttribute('disable-image-tests');
1010
- }
1011
-
1012
- get allowAgentMode() {
1013
- return this.getAttribute('allow-agent-mode') === 'true';
1014
- }
1015
-
1016
- get inAgentMode() {
1017
- return this.facingMode === 'environment';
1018
- }
1019
-
1020
- static get observedAttributes() {
1021
- return [
1022
- 'allow-agent-mode',
1023
- 'data-camera-error',
1024
- 'data-camera-ready',
1025
- 'disable-image-tests',
1026
- 'hidden',
1027
- 'hide-back-to-host',
1028
- 'show-navigation',
1029
- 'title',
1030
- ];
1031
- }
1032
-
1033
- attributeChangedCallback(name) {
1034
- switch (name) {
1035
- case 'data-camera-error':
1036
- case 'hidden':
1037
- case 'title':
1038
- this.shadowRoot.innerHTML = this.render();
1039
- this.init();
1040
- break;
1041
- case 'allow-agent-mode':
1042
- // only re-render if the shadowRoot is empty or not initialized
1043
- if (!this.shadowRoot.innerHTML.trim()) {
1044
- this.shadowRoot.innerHTML = this.render();
1045
- this.init();
1046
- } else {
1047
- // update the setupAgentMode
1048
- this.setupAgentMode();
1049
- }
1050
- break;
1051
- case 'show-navigation':
1052
- // update the navigation element if it exists
1053
- if (this.shadowRoot.innerHTML.trim()) {
1054
- const navigation =
1055
- this.shadowRoot.querySelector('smileid-navigation');
1056
- if (navigation) {
1057
- if (this.showNavigation) {
1058
- navigation.setAttribute('show-navigation', '');
1059
- } else {
1060
- navigation.removeAttribute('show-navigation');
1061
- }
1062
- }
1063
- } else {
1064
- this.shadowRoot.innerHTML = this.render();
1065
- this.init();
1066
- }
1067
- break;
1068
- case 'data-camera-ready':
1069
- // don't re-render, just handle the stream
1070
- if (this.hasAttribute('data-camera-ready') && SmartCamera.stream) {
1071
- this.handleStream(SmartCamera.stream);
1072
- }
1073
- break;
1074
- default:
1075
- break;
1076
- }
1077
- }
1078
-
1079
- handleBackEvents() {
1080
- this.stopMedia();
1081
- this.dispatchEvent(new CustomEvent('selfie-capture.cancelled'));
1082
- }
1083
-
1084
- closeWindow() {
1085
- this.stopMedia();
1086
- this.dispatchEvent(new CustomEvent('selfie-capture.close'));
1087
- }
1088
-
1089
- stopMedia() {
1090
- this.removeAttribute('data-camera-ready');
1091
- SmartCamera.stopMedia();
1092
- }
1093
- }
1094
-
1095
- if ('customElements' in window && !customElements.get('selfie-capture')) {
1096
- window.customElements.define('selfie-capture', SelfieCaptureScreen);
1097
- }
1098
-
1099
- export default SelfieCaptureScreen;
1
+ import { IMAGE_TYPE } from '../../../../domain/constants/src/Constants';
2
+ import SmartCamera from '../../../../domain/camera/src/SmartCamera';
3
+ import styles from '../../../../styles/src/styles';
4
+ import packageJson from '../../../../../package.json';
5
+ import '../../../navigation/src';
6
+
7
+ const COMPONENTS_VERSION = packageJson.version;
8
+
9
+ const DEFAULT_NO_OF_LIVENESS_FRAMES = 8;
10
+
11
+ function hasMoreThanNColors(data, n = 16) {
12
+ const colors = new Set();
13
+ for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
14
+ // eslint-disable-next-line no-bitwise
15
+ colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
16
+ if (colors.size > n) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+
23
+ function getLivenessFramesIndices(
24
+ totalNoOfFrames,
25
+ numberOfFramesRequired = DEFAULT_NO_OF_LIVENESS_FRAMES,
26
+ ) {
27
+ const selectedFrames = [];
28
+
29
+ if (totalNoOfFrames < numberOfFramesRequired) {
30
+ throw new Error(
31
+ 'SmartCameraWeb: Minimum required no of frames is ',
32
+ numberOfFramesRequired,
33
+ );
34
+ }
35
+
36
+ const frameDivisor = numberOfFramesRequired - 1;
37
+ const frameInterval = Math.floor(totalNoOfFrames / frameDivisor);
38
+
39
+ // NOTE: when we have satisfied our required 8 frames, but have good
40
+ // candidates, we need to start replacing from the second frame
41
+ let replacementFrameIndex = 1;
42
+
43
+ for (let i = 0; i < totalNoOfFrames; i += frameInterval) {
44
+ if (selectedFrames.length < 8) {
45
+ selectedFrames.push(i);
46
+ } else {
47
+ // ACTION: replace frame, then sort selectedframes
48
+ selectedFrames[replacementFrameIndex] = i;
49
+ selectedFrames.sort((a, b) => a - b);
50
+
51
+ // ACTION: update replacement frame index
52
+ replacementFrameIndex += 1;
53
+ }
54
+ }
55
+
56
+ // INFO: if we don't satisfy our requirement, we add the last index
57
+ const lastFrameIndex = totalNoOfFrames - 1;
58
+
59
+ if (selectedFrames.length < 8 && !selectedFrames.includes(lastFrameIndex)) {
60
+ selectedFrames.push(lastFrameIndex);
61
+ }
62
+
63
+ return selectedFrames;
64
+ }
65
+
66
+ function templateString() {
67
+ return `
68
+ ${styles(this.themeColor)}
69
+ <style>
70
+ :host {
71
+ --theme-color: ${this.themeColor || '#001096'};
72
+ --color-active: #001096;
73
+ --color-default: #2D2B2A;
74
+ --color-disabled: #848282;
75
+ }
76
+
77
+ * {
78
+ font-family: 'DM Sans', sans-serif;
79
+ }
80
+
81
+ [hidden] {
82
+ display: none !important;
83
+ }
84
+
85
+ [disabled] {
86
+ cursor: not-allowed !important;
87
+ filter: grayscale(75%);
88
+ }
89
+
90
+ .visually-hidden {
91
+ border: 0;
92
+ clip: rect(1px 1px 1px 1px);
93
+ clip: rect(1px, 1px, 1px, 1px);
94
+ height: auto;
95
+ margin: 0;
96
+ overflow: hidden;
97
+ padding: 0;
98
+ position: absolute;
99
+ white-space: nowrap;
100
+ width: 1px;
101
+ }
102
+
103
+ img {
104
+ height: auto;
105
+ max-width: 100%;
106
+ transform: scaleX(-1);
107
+ }
108
+
109
+ video {
110
+ background-color: black;
111
+ }
112
+
113
+ a {
114
+ color: currentColor;
115
+ text-decoration: none;
116
+ }
117
+
118
+ svg {
119
+ max-width: 100%;
120
+ }
121
+
122
+ .color-gray {
123
+ color: #797979;
124
+ }
125
+
126
+ .color-red {
127
+ color: red;
128
+ }
129
+
130
+ .color-richblue {
131
+ color: #4E6577;
132
+ }
133
+
134
+ .color-richblue-shade {
135
+ color: #0E1B42;
136
+ }
137
+
138
+ .color-digital-blue {
139
+ color: #001096 !important;
140
+ }
141
+
142
+ .color-deep-blue {
143
+ color: #001096;
144
+ }
145
+
146
+ .title-color {
147
+ color: ${this.themeColor};
148
+ }
149
+
150
+ .theme-color {
151
+ color: ${this.themeColor};
152
+ }
153
+
154
+ .center {
155
+ text-align: center;
156
+ margin-left: auto;
157
+ margin-right: auto;
158
+ }
159
+
160
+ .font-size-small {
161
+ font-size: .75rem;
162
+ }
163
+
164
+ .font-size-large {
165
+ font-size: 1.5rem;
166
+ }
167
+
168
+ .text-transform-uppercase {
169
+ text-transform: uppercase;
170
+ }
171
+
172
+ [id*=-"screen"] {
173
+ min-block-size: 100%;
174
+ }
175
+
176
+ [data-variant~="full-width"] {
177
+ inline-size: 100%;
178
+ }
179
+
180
+ .flow > * + * {
181
+ margin-top: 1rem;
182
+ }
183
+
184
+ .button {
185
+ --button-color: ${this.themeColor};
186
+ -webkit-appearance: none;
187
+ appearance: none;
188
+ border-radius: 2.5rem;
189
+ border: 0;
190
+ background-color: transparent;
191
+ color: #fff;
192
+ cursor: pointer;
193
+ display: block;
194
+ font-size: 18px;
195
+ font-weight: 600;
196
+ padding: .75rem 1.5rem;
197
+ text-align: center;
198
+ }
199
+
200
+ .button:hover,
201
+ .button:focus,
202
+ .button:active {
203
+ --button-color: var(--color-default);
204
+ }
205
+
206
+ .button:disabled {
207
+ --button-color: var(--color-disabled);
208
+ }
209
+
210
+ .button[data-variant~='solid'] {
211
+ background-color: var(--button-color);
212
+ border: 2px solid var(--button-color);
213
+ }
214
+
215
+ .button[data-variant~='outline'] {
216
+ color: var(--button-color);
217
+ border: 2px solid var(--button-color);
218
+ }
219
+
220
+ .button[data-variant~='ghost'] {
221
+ padding: 0px;
222
+ color: var(--button-color);
223
+ background-color: transparent;
224
+ }
225
+
226
+ .icon-btn {
227
+ appearance: none;
228
+ background: none;
229
+ border: none;
230
+ color: hsl(0deg 0% 94%);
231
+ cursor: pointer;
232
+ display: flex;
233
+ align-items: center;
234
+ justify-content: center;
235
+ padding: 4px 8px;
236
+ }
237
+ .justify-right {
238
+ justify-content: end !important;
239
+ }
240
+ .nav {
241
+ display: flex;
242
+ justify-content: space-between;
243
+ }
244
+
245
+ .back-wrapper {
246
+ display: flex;
247
+ align-items: center;
248
+ }
249
+
250
+ .back-button {
251
+ display: block !important;
252
+ }
253
+ .back-button-text {
254
+ font-size: 11px;
255
+ line-height: 11px;
256
+ color: rgb(21, 31, 114);
257
+ }
258
+ .section {
259
+ border-radius: .5rem;
260
+ margin-left: auto;
261
+ margin-right: auto;
262
+ max-width: 35ch;
263
+ padding: 1rem;
264
+ }
265
+
266
+ .selfie-capture-review-image {
267
+ overflow: hidden;
268
+ aspect-ratio: 1/1;
269
+ }
270
+
271
+ #review-image {
272
+ scale: 1.75;
273
+ }
274
+
275
+ @media (max-aspect-ratio: 1/1) {
276
+ #review-image {
277
+ transform: scaleX(-1) translateY(-10%);
278
+ }
279
+ }
280
+
281
+ .tips,
282
+ .powered-by {
283
+ align-items: center;
284
+ border-radius: .25rem;
285
+ display: flex;
286
+ justify-content: center;
287
+ letter-spacing: .075em;
288
+ }
289
+
290
+ .powered-by {
291
+ box-shadow: 0px 2.57415px 2.57415px rgba(0, 0, 0, 0.06);
292
+ display: inline-flex;
293
+ font-size: .5rem;
294
+ }
295
+
296
+ .tips {
297
+ margin-left: auto;
298
+ margin-right: auto;
299
+ max-width: 17rem;
300
+ font-size: 0.875rem;
301
+ font-weight: 600;
302
+ }
303
+
304
+ .tips > * + *,
305
+ .powered-by > * + * {
306
+ display: inline-block;
307
+ margin-left: .5em;
308
+ }
309
+
310
+ .powered-by .company {
311
+ color: #18406D;
312
+ font-weight: 700;
313
+ letter-spacing: .15rem;
314
+ }
315
+
316
+ .logo-mark {
317
+ background-color: #004071;
318
+ display: inline-block;
319
+ padding: .25em .5em;
320
+ }
321
+
322
+ .logo-mark svg {
323
+ height: auto;
324
+ justify-self: center;
325
+ width: .75em;
326
+ }
327
+
328
+ .id-video-container.portrait {
329
+ width: 100%;
330
+ position: relative;
331
+ height: calc(200px * 1.4);
332
+ }
333
+
334
+ .id-video-container.portrait video {
335
+ width: calc(213px + 0.9rem);
336
+ height: 100%;
337
+ position: absolute;
338
+ top: 239px;
339
+ left: 161px;
340
+ padding-bottom: calc((214px * 1.4) / 3);
341
+ padding-top: calc((191px * 1.4) / 3);
342
+ object-fit: cover;
343
+
344
+ transform: translateX(-50%) translateY(-50%);
345
+ z-index: 1;
346
+ block-size: 100%;
347
+ }
348
+
349
+ .video-container,
350
+ .id-video-container.landscape {
351
+ position: relative;
352
+ z-index: 1;
353
+ width: 100%;
354
+ overflow: hidden;
355
+ }
356
+
357
+ .video-container video,
358
+ .id-video-container.landscape video {
359
+ left: 50%;
360
+ min-width: auto;
361
+ position: absolute;
362
+ top: 50%;
363
+ transform: translateX(-50%) translateY(50%);
364
+ }
365
+
366
+ .video-container #smile-cta-box {
367
+ color: #fff;
368
+ width: 100%;
369
+ position: absolute;
370
+ top: 0;
371
+ left: 0;
372
+ padding: 2rem 0;
373
+ opacity: 0;
374
+ transition: opacity 0.5s ease-in-out;
375
+ }
376
+
377
+ .video-container #smile-cta {
378
+ font-size: 1.2rem;
379
+ font-weight: bold;
380
+ margin-top: 0;
381
+ margin-bottom: 0;
382
+ }
383
+
384
+ .video-container video {
385
+ height: 150%;
386
+ transform: scaleX(-1) translateX(50%) translateY(-50%);
387
+ }
388
+
389
+ .video-container video.agent-mode {
390
+ min-height: 100%;
391
+ transform: scaleX(1) translateX(-50%) translateY(-50%);
392
+ }
393
+
394
+ .video-container .video {
395
+ background-color: black;
396
+ position: absolute;
397
+ left: 50%;
398
+ height: calc(100% - 6px);
399
+ clip-path: ellipse(101px 118px);
400
+ }
401
+
402
+ .id-video-container.landscape {
403
+ min-height: calc((2 * 10rem) + 198px);
404
+ height: auto;
405
+ }
406
+
407
+ .id-video-container.portrait .image-frame-portrait {
408
+ border-width: 0.9rem;
409
+ border-color: rgba(0, 0, 0, 0.7);
410
+ border-style: solid;
411
+ height: auto;
412
+ position: absolute;
413
+ top: 80px;
414
+ left: 47px;
415
+ z-index: 2;
416
+ width: 200px;
417
+ height: calc(200px * 1.4);
418
+ }
419
+
420
+ .id-video-container.landscape .image-frame {
421
+ border-width: 10rem 1rem;
422
+ border-color: rgba(0, 0, 0, 0.7);
423
+ border-style: solid;
424
+ height: auto;
425
+ width: 90%;
426
+ position: absolute;
427
+ top: 0;
428
+ left: 0;
429
+ z-index: 2;
430
+ }
431
+
432
+ .id-video-container.landscape video {
433
+ width: 100%;
434
+ transform: translateX(-50%) translateY(-50%);
435
+ z-index: 1;
436
+ height: 100%;
437
+ block-size: 100%;
438
+ }
439
+
440
+ .id-video-container.landscape img {
441
+ position: absolute;
442
+ top: 50%;
443
+ left: 50%;
444
+ transform: translateX(-50%) translateY(-50%);
445
+ max-width: 90%;
446
+ }
447
+
448
+ .actions {
449
+ background-color: rgba(0, 0, 0, .7);
450
+ bottom: 0;
451
+ display: flex;
452
+ justify-content: space-between;
453
+ padding: 1rem;
454
+ position: absolute;
455
+ width: 90%;
456
+ z-index: 2;
457
+ }
458
+
459
+ #back-of-id-camera-screen .id-video-container.portrait .actions,
460
+ #id-camera-screen .id-video-container.portrait .actions {
461
+ top: 145%;
462
+ width: calc(200px * 1.4);
463
+ }
464
+
465
+ #back-of-id-camera-screen .section.portrait, #id-camera-screen .section.portrait {
466
+ min-height: calc((200px * 1.4) + 260px);
467
+ }
468
+
469
+ #selfie-capture-screen,
470
+ #back-of-id-entry-screen {
471
+ box-sizing: border-box;
472
+ display: flex;
473
+ flex-direction: column;
474
+ max-block-size: 100%;
475
+ max-inline-size: 40ch;
476
+ padding: 1rem;
477
+ }
478
+
479
+ #selfie-capture-screen header p {
480
+ margin-block: 0 !important;
481
+ }
482
+
483
+ .document-tips {
484
+ margin-block-start: 1.5rem;
485
+ display: flex;
486
+ align-items: center;
487
+ text-align: initial;
488
+ }
489
+
490
+ .document-tips svg {
491
+ flex-shrink: 0;
492
+ margin-inline-end: 1rem;
493
+ }
494
+
495
+ .document-tips p {
496
+ margin-block: 0;
497
+ }
498
+
499
+ .document-tips p:first-of-type {
500
+ font-size; 1.875rem;
501
+ font-weight: bold
502
+ }
503
+
504
+ [type='file'] {
505
+ display: none;
506
+ }
507
+
508
+ .document-tips > * + * {
509
+ margin-inline-start; 1em;
510
+ }
511
+ </style>
512
+ <div id='selfie-capture-screen' class='flow center'>
513
+ <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
514
+
515
+ <div class='tips'>Fit your head inside the oval frame</div>
516
+
517
+ <div className="error">
518
+ ${this.cameraError ? `<p class="color-red">${this.cameraError}</p>` : ''}
519
+ </div>
520
+ <div class='section | flow' ${this.cameraError ? 'hidden' : ''}>
521
+ <div class='video-container'>
522
+ <div class='video'>
523
+ </div>
524
+ <svg id="image-outline" width="215" height="245" viewBox="0 0 215 245" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: relative;">
525
+ <path d="M210.981 122.838C210.981 188.699 164.248 241.268 107.55 241.268C50.853 241.268 4.12018 188.699 4.12018 122.838C4.12018 56.9763 50.853 4.40771 107.55 4.40771C164.248 4.40771 210.981 56.9763 210.981 122.838Z" stroke="${this.themeColor}" stroke-width="7.13965"/>
526
+ </svg>
527
+ <div id="smile-cta-box">
528
+ <div>
529
+ <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" viewBox="70 70 360 360" fill="currentColor">
530
+ <path d="M354.02,247.69c4.12-12.26,6.39-24.53,6.39-35.75,0-54.63-50.19-99.08-111.88-99.08s-111.95,44.45-111.95,99.08c0,11.22,2.28,23.49,6.39,35.75-6.6,4.66-10.94,12.33-10.94,21.04,0,13.45,10.36,24.4,23.46,25.51.52,26.93,12.56,53.51,35.04,71.64v.73c.07.03.14.05.21.08,3.66,1.63,6.74,4.28,9.92,6.66.08.06.17.12.25.17,0-.08-.01-.16-.01-.25,15.23,9.12,32.95,13.89,51.85,12.96h0c15.54-.72,30.01-5.37,42.59-12.99,2.91-2.85,7.01-4.93,10.42-6.71.25-.16.51-.28.77-.43v-.76c20.36-16.83,33.62-42.32,34.85-71.11,13.15-1.05,23.57-12.03,23.57-25.53,0-8.7-4.35-16.38-10.94-21.04ZM155.53,283.54c-7.18-1.09-12.71-7.31-12.71-14.85s5.53-13.75,12.71-14.85v29.7ZM330.53,289.9c0,45.26-33.98,83.65-78.48,85.72-47.29,2.31-86.52-35.65-86.52-83.18v-40.7c15.63-4.03,38.45-15.61,65.91-46.31.08.59.13,1.21.11,1.87-.13,4.43-3.09,9.32-8.58,14.14-1.79,1.57-2.32,4.13-1.31,6.28,1.01,2.14,3.29,3.35,5.68,3,2.19-.35,48.6-8.13,71.88-38.36,3.84,13.25,12.48,33.59,31.31,48.79v48.75ZM339.42,234.31c-27.83-21.03-32.1-56.22-32.15-56.57-.26-2.37-2.08-4.28-4.43-4.67-2.33-.39-4.68.83-5.69,2.99-11.08,23.62-39.34,35.42-56.72,40.63,1.35-2.97,2.08-6,2.16-9.06.26-9.11-5.24-14.81-5.88-15.43-1.06-1.05-2.53-1.59-3.99-1.53-1.48.07-2.87.75-3.83,1.88-32.09,37.93-57.18,47.4-69.28,49.67-.68.13-1.29.39-1.84.74-.11,0-.18-.03-.29-.03-1.65,0-3.26.19-4.82.49-3.58-10.9-5.56-21.72-5.56-31.57,0-48.78,45.46-88.46,101.33-88.46s101.26,39.68,101.26,88.46c0,9.84-1.98,20.67-5.56,31.57-.85-.16-1.73-.19-2.61-.27v-4.6c0-1.67-.78-3.25-2.12-4.26ZM341.53,283.54v-29.7c7.18,1.09,12.72,7.3,12.72,14.85s-5.53,13.76-12.72,14.85Z"/>
531
+ <path d="M259.05,305.3c2.08-2.08,2.08-5.46,0-7.55-2.09-2.08-5.47-2.07-7.54,0-1.75,1.75-4.79,1.75-6.54,0-2.08-2.08-5.45-2.09-7.54,0-2.08,2.08-2.08,5.46,0,7.55,5.98,5.98,15.65,5.98,21.63,0h0Z"/>
532
+ <path id="mouth"
533
+ d="m 213.88314,319.4551 c -1.58,0.97 -0.35309,9.33393 1.50671,9.30586 6.05679,-0.0914 16.11631,0.17227 34.57066,0.13346 18.45435,-0.0388 28.15778,-0.0418 31.09964,-0.79956 1.80122,-0.46394 2.75061,-7.48365 1.16061,-8.45365 -1.6,-1.74874 -2.96432,-0.94348 -6.77747,-1.56441 -12.83012,0.04 -36.52534,0.50197 -41.29469,0.43262 -2.51525,-0.0713 -18.41588,-0.61 -20.01588,0.35 z m 57.29363,1.36599 c -9.24417,-2.23757 -8.08363,-2.42362 -20.78363,-2.42362 -12.7,0 -17.77931,2.69528 -26.84042,5.36549 12.57883,3.28731 33.57775,-4.29887 49.70067,2.24964 z">
534
+ <animate
535
+ id="mouthAnim"
536
+ attributeName="d"
537
+ begin="indefinite"
538
+ dur="1s"
539
+ fill="freeze"
540
+ to="m 211.72,312.36 c -1.58,0.97 -2.56,2.86 -2.56,4.72 0,21.54 17.40021,38.7239 38.94021,38.7239 21.54,0 39.18979,-17.1739 39.18979,-39.0439 0,-1.86 -0.97,-3.59 -2.56,-4.56 -1.6,-0.97 -3.57,-1.03 -5.22,-0.18 -21.07,10.85 -41.53,10.85 -62.58,0 -1.65,-0.85 -3.62,-0.61 -5.22,0.35 z m 63.61,13.22 c -3.62,11.52 -14.4,19.9 -27.1,19.9 -12.7,0 -23.49,-8.38 -27.1,-19.9 18.03,6.94 36.2,6.94 54.2,0 z" />
541
+ </path>
542
+ <circle fill="none" stroke="currentColor" stroke-width="10px" cx="287.3" cy="263.68" r="14"/>
543
+ <circle fill="none" stroke="currentColor" stroke-width="10px" cx="209.16" cy="263.68" r="14"/>
544
+ </svg>
545
+ </div>
546
+ <div>
547
+ <p id='smile-cta'>SMILE</p>
548
+ </div>
549
+ </div>
550
+ </div>
551
+
552
+ ${this.allowAgentMode ? `<button data-variant='outline small' id='switch-camera' class='button | center' type='button'>${this.inAgentMode ? 'Agent Mode On' : 'Agent Mode Off'}</button>` : ''}
553
+
554
+ <button data-variant='solid' id='start-image-capture' class='button | center' type='button'>
555
+ Take Selfie
556
+ </button>
557
+
558
+ ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
559
+ </div>
560
+ </div>
561
+ `;
562
+ }
563
+
564
+ async function getPermissions(
565
+ captureScreen,
566
+ constraints = { facingMode: 'user' },
567
+ ) {
568
+ try {
569
+ const stream = await SmartCamera.getMedia({
570
+ audio: false,
571
+ video: constraints,
572
+ });
573
+ const devices = await navigator.mediaDevices.enumerateDevices();
574
+ const videoDevice = devices.find(
575
+ (device) =>
576
+ device.kind === 'videoinput' &&
577
+ stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
578
+ );
579
+ const smartCameraWeb = document.querySelector('smart-camera-web');
580
+ smartCameraWeb?.dispatchEvent(
581
+ new CustomEvent('metadata.camera-name', {
582
+ detail: { cameraName: videoDevice?.label },
583
+ }),
584
+ );
585
+ captureScreen?.removeAttribute('data-camera-error');
586
+ captureScreen?.setAttribute('data-camera-ready', true);
587
+ } catch (error) {
588
+ captureScreen?.removeAttribute('data-camera-ready');
589
+ captureScreen?.setAttribute(
590
+ 'data-camera-error',
591
+ SmartCamera.handleCameraError(error),
592
+ );
593
+ }
594
+ }
595
+
596
+ class SelfieCaptureScreen extends HTMLElement {
597
+ constructor() {
598
+ super();
599
+ this.templateString = templateString.bind(this);
600
+ this.render = () => this.templateString();
601
+
602
+ this.attachShadow({ mode: 'open' });
603
+ this.facingMode = 'user';
604
+ if (this.allowAgentMode) {
605
+ this.facingMode = 'environment';
606
+ }
607
+ }
608
+
609
+ connectedCallback() {
610
+ const template = document.createElement('template');
611
+ template.innerHTML = this.render();
612
+ this.shadowRoot.innerHTML = '';
613
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
614
+ this.videoContainer = this.shadowRoot.querySelector(
615
+ '.video-container > .video',
616
+ );
617
+ this.init();
618
+ }
619
+
620
+ init() {
621
+ this._videoStreamDurationInMS = 7800;
622
+ this._imageCaptureIntervalInMS = 200;
623
+
624
+ this._data = {
625
+ images: [],
626
+ meta: {
627
+ libraryVersion: COMPONENTS_VERSION,
628
+ },
629
+ };
630
+ this._rawImages = [];
631
+
632
+ this.setUpEventListeners();
633
+ }
634
+
635
+ reset() {
636
+ this.disconnectedCallback();
637
+ this.connectedCallback();
638
+ }
639
+
640
+ _startImageCapture() {
641
+ this.startImageCapture.disabled = true;
642
+ if (this.switchCamera) {
643
+ this.switchCamera.disabled = true;
644
+ }
645
+
646
+ const smartCameraWeb = document.querySelector('smart-camera-web');
647
+ smartCameraWeb?.dispatchEvent(
648
+ new CustomEvent('metadata.selfie-capture-start'),
649
+ );
650
+ smartCameraWeb?.dispatchEvent(
651
+ new CustomEvent('metadata.selfie-origin', {
652
+ detail: {
653
+ imageOrigin: {
654
+ environment: 'back_camera',
655
+ user: 'front_camera',
656
+ }[this.inAgentMode ? 'environment' : 'user'],
657
+ },
658
+ }),
659
+ );
660
+
661
+ /**
662
+ * this was culled from https://jakearchibald.com/2013/animated-line-drawing-svg/
663
+ */
664
+ // NOTE: initialise image outline
665
+ const imageOutlineLength = this.imageOutline.getTotalLength();
666
+ // Clear any previous transition
667
+ this.imageOutline.style.transition = 'none';
668
+ // Set up the starting positions
669
+ this.imageOutline.style.strokeDasharray = `${imageOutlineLength} ${imageOutlineLength}`;
670
+ this.imageOutline.style.strokeDashoffset = imageOutlineLength;
671
+ // Trigger a layout so styles are calculated & the browser
672
+ // picks up the starting position before animating
673
+ this.imageOutline.getBoundingClientRect();
674
+ // Define our transition
675
+ this.imageOutline.style.transition = `stroke-dashoffset ${this._videoStreamDurationInMS / 1000}s ease-in-out`;
676
+ // Go!
677
+ this.imageOutline.style.strokeDashoffset = '0';
678
+
679
+ setTimeout(() => {
680
+ this.smileCTABox.style.opacity = 1;
681
+ this.mouthAnim.beginElement();
682
+ }, 1500);
683
+
684
+ setTimeout(() => {
685
+ this.smileCTABox.style.opacity = 0;
686
+ }, 3500);
687
+
688
+ setTimeout(() => {
689
+ this.smileCTABox.style.opacity = 1;
690
+ this.smileCTA.textContent = 'WIDER SMILE';
691
+ this.mouth.setAttribute(
692
+ 'd',
693
+ 'm 213.88314,319.4551 c -1.58,0.97 -0.35309,9.33393 1.50671,9.30586 6.05679,-0.0914 16.11631,0.17227 34.57066,0.13346 18.45435,-0.0388 28.15778,-0.0418 31.09964,-0.79956 1.80122,-0.46394 2.75061,-7.48365 1.16061,-8.45365 -1.6,-1.74874 -2.96432,-0.94348 -6.77747,-1.56441 -12.83012,0.04 -36.52534,0.50197 -41.29469,0.43262 -2.51525,-0.0713 -18.41588,-0.61 -20.01588,0.35 z m 57.29363,1.36599 c -9.24417,-2.23757 -8.08363,-2.42362 -20.78363,-2.42362 -12.7,0 -17.77931,2.69528 -26.84042,5.36549 12.57883,3.28731 33.57775,-4.29887 49.70067,2.24964 z',
694
+ );
695
+ this.mouthAnim.beginElement();
696
+ }, 4000);
697
+
698
+ this._imageCaptureInterval = setInterval(() => {
699
+ this._capturePOLPhoto();
700
+ }, this._imageCaptureIntervalInMS);
701
+
702
+ this._videoStreamTimeout = setTimeout(() => {
703
+ this._stopVideoStream();
704
+ }, this._videoStreamDurationInMS);
705
+ }
706
+
707
+ async _switchCamera() {
708
+ this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
709
+ if (this.facingMode === 'user') {
710
+ this.shadowRoot.querySelector('video').classList.remove('agent-mode');
711
+ } else {
712
+ this.shadowRoot.querySelector('video').classList.add('agent-mode');
713
+ }
714
+ this.startImageCapture.disabled = true;
715
+ this.switchCamera.disabled = true;
716
+ SmartCamera.stopMedia();
717
+ await getPermissions(this, { facingMode: this.facingMode });
718
+ this.handleStream(SmartCamera.stream);
719
+ }
720
+
721
+ _stopVideoStream() {
722
+ try {
723
+ clearTimeout(this._videoStreamTimeout);
724
+ clearInterval(this._imageCaptureInterval);
725
+ clearInterval(this._drawingInterval);
726
+
727
+ this._capturePOLPhoto(); // NOTE: capture the last photo
728
+ this._captureReferencePhoto();
729
+ SmartCamera.stopMedia();
730
+
731
+ const totalNoOfFrames = this._rawImages.length;
732
+ this._data.referenceImage = this._referenceImage;
733
+ this._data.previewImage = this._referenceImage;
734
+
735
+ const livenessFramesIndices = getLivenessFramesIndices(totalNoOfFrames);
736
+
737
+ this._data.images = this._data.images.concat(
738
+ livenessFramesIndices.map((imageIndex) => ({
739
+ image: this._rawImages[imageIndex].split(',')[1],
740
+ image_type_id: IMAGE_TYPE.LIVENESS_IMAGE_BASE64,
741
+ })),
742
+ );
743
+
744
+ this._publishImages();
745
+ } catch (error) {
746
+ console.error(error);
747
+ // Todo: handle error
748
+ }
749
+ }
750
+
751
+ _capturePOLPhoto() {
752
+ const canvas = document.createElement('canvas');
753
+ // Determine orientation of the video
754
+ const isPortrait = this._video.videoHeight > this._video.videoWidth;
755
+
756
+ // Set dimensions based on orientation, ensuring minimums
757
+ if (isPortrait) {
758
+ // Portrait orientation (taller than wide)
759
+ canvas.width = 240;
760
+ canvas.height = Math.max(
761
+ 320,
762
+ (canvas.width * this._video.videoHeight) / this._video.videoWidth,
763
+ );
764
+ } else {
765
+ // Landscape orientation (wider than tall)
766
+ canvas.height = 240;
767
+ canvas.width = Math.max(
768
+ 320,
769
+ (canvas.height * this._video.videoWidth) / this._video.videoHeight,
770
+ );
771
+ }
772
+
773
+ // NOTE: we do not want to test POL images
774
+ this._drawImage(canvas, false);
775
+
776
+ this._rawImages.push(canvas.toDataURL('image/jpeg'));
777
+ }
778
+
779
+ _captureReferencePhoto() {
780
+ const canvas = document.createElement('canvas');
781
+ // Determine orientation of the video
782
+ const isPortrait = this._video.videoHeight > this._video.videoWidth;
783
+
784
+ // Set dimensions based on orientation, ensuring minimums
785
+ if (isPortrait) {
786
+ // Portrait orientation (taller than wide)
787
+ canvas.width = 480;
788
+ canvas.height = Math.max(
789
+ 640,
790
+ (canvas.width * this._video.videoHeight) / this._video.videoWidth,
791
+ );
792
+ } else {
793
+ // Landscape orientation (wider than tall)
794
+ canvas.height = 480;
795
+ canvas.width = Math.max(
796
+ 640,
797
+ (canvas.height * this._video.videoWidth) / this._video.videoHeight,
798
+ );
799
+ }
800
+
801
+ // NOTE: we want to test the image quality of the reference photo
802
+ this._drawImage(canvas, !this.disableImageTests);
803
+
804
+ const image = canvas.toDataURL('image/jpeg');
805
+
806
+ this._referenceImage = image;
807
+
808
+ this._data.images.push({
809
+ image: image.split(',')[1],
810
+ image_type_id: IMAGE_TYPE.SELFIE_IMAGE_BASE64,
811
+ });
812
+ }
813
+
814
+ _publishImages() {
815
+ const eventDetail = {
816
+ ...this._data,
817
+ facingMode: this.facingMode,
818
+ };
819
+
820
+ this.dispatchEvent(
821
+ new CustomEvent('selfie-capture.publish', {
822
+ detail: eventDetail,
823
+ }),
824
+ );
825
+ }
826
+
827
+ resetErrorMessage() {
828
+ this.errorMessage.textContent = '';
829
+ }
830
+
831
+ _drawImage(canvas, enableImageTests = true, video = this._video) {
832
+ // this.resetErrorMessage();
833
+ const context = canvas.getContext('2d');
834
+
835
+ context.drawImage(
836
+ video,
837
+ 0,
838
+ 0,
839
+ video.videoWidth,
840
+ video.videoHeight,
841
+ 0,
842
+ 0,
843
+ canvas.width,
844
+ canvas.height,
845
+ );
846
+
847
+ if (enableImageTests) {
848
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
849
+
850
+ const hasEnoughColors = hasMoreThanNColors(imageData.data);
851
+
852
+ if (hasEnoughColors) {
853
+ return context;
854
+ }
855
+ throw new Error(
856
+ 'Unable to capture webcam images - Please try another device',
857
+ );
858
+ } else {
859
+ return context;
860
+ }
861
+ }
862
+
863
+ handleStream(stream) {
864
+ try {
865
+ const videoContainer = this.shadowRoot.querySelector('.video');
866
+ if (!videoContainer) {
867
+ return;
868
+ }
869
+
870
+ const videoExists = this.shadowRoot.querySelector('video');
871
+ let video = null;
872
+ if (videoExists) {
873
+ video = this.shadowRoot.querySelector('video');
874
+ } else {
875
+ video = document.createElement('video');
876
+ }
877
+
878
+ video.autoplay = true;
879
+ video.playsInline = true;
880
+ video.muted = true;
881
+
882
+ if ('srcObject' in video) {
883
+ video.srcObject = stream;
884
+ } else {
885
+ video.src = window.URL.createObjectURL(stream);
886
+ }
887
+
888
+ video.onloadedmetadata = () => {
889
+ video.play();
890
+ };
891
+
892
+ this._video = video;
893
+ this._data.permissionGranted = true;
894
+
895
+ if (!videoExists) {
896
+ videoContainer.prepend(video);
897
+ }
898
+ } catch (error) {
899
+ this.setAttribute(
900
+ 'data-camera-error',
901
+ SmartCamera.handleCameraError(error),
902
+ );
903
+ if (error.name !== 'AbortError') {
904
+ console.error(error);
905
+ }
906
+ SmartCamera.stopMedia();
907
+ }
908
+ }
909
+
910
+ setUpEventListeners() {
911
+ this.navigation = this.shadowRoot.querySelector('smileid-navigation');
912
+
913
+ this.startImageCapture = this.shadowRoot.querySelector(
914
+ '#start-image-capture',
915
+ );
916
+
917
+ this.switchCamera = this.shadowRoot.querySelector('#switch-camera');
918
+ this.imageOutline = this.shadowRoot.querySelector('#image-outline path');
919
+ this.smileCTABox = this.shadowRoot.querySelector('#smile-cta-box');
920
+ this.smileCTA = this.shadowRoot.querySelector('#smile-cta');
921
+ this.mouth = this.shadowRoot.querySelector('#mouth');
922
+ this.mouthAnim = this.shadowRoot.querySelector('#mouthAnim');
923
+
924
+ this.startImageCapture.addEventListener('click', () => {
925
+ this._startImageCapture();
926
+ });
927
+
928
+ this.switchCamera?.addEventListener('click', () => {
929
+ this._switchCamera();
930
+ });
931
+
932
+ this.navigation.addEventListener('navigation.back', () => {
933
+ this.handleBackEvents();
934
+ });
935
+
936
+ this.navigation.addEventListener('navigation.close', () => {
937
+ this.closeWindow();
938
+ });
939
+
940
+ if (SmartCamera.stream) {
941
+ this.handleStream(SmartCamera.stream);
942
+ } else if (
943
+ this.hasAttribute('data-camera-ready') ||
944
+ !this.hasAttribute('data-camera-error')
945
+ ) {
946
+ getPermissions(this, { facingMode: this.facingMode });
947
+ }
948
+
949
+ this.setupAgentMode();
950
+ }
951
+
952
+ disconnectedCallback() {
953
+ SmartCamera.stopMedia();
954
+ clearTimeout(this._videoStreamTimeout);
955
+ }
956
+
957
+ get hideBack() {
958
+ return this.hasAttribute('hide-back');
959
+ }
960
+
961
+ get showNavigation() {
962
+ return this.hasAttribute('show-navigation');
963
+ }
964
+
965
+ get themeColor() {
966
+ return this.getAttribute('theme-color') || '#001096';
967
+ }
968
+
969
+ get hideAttribution() {
970
+ return this.hasAttribute('hide-attribution');
971
+ }
972
+
973
+ async setupAgentMode() {
974
+ if (!this.allowAgentMode) {
975
+ return;
976
+ }
977
+
978
+ const supportAgentMode = await SmartCamera.supportsAgentMode();
979
+
980
+ if (supportAgentMode || this.hasAttribute('show-agent-mode-for-tests')) {
981
+ this.switchCamera.hidden = false;
982
+ if (this.facingMode === 'user') {
983
+ this.shadowRoot.querySelector('video')?.classList?.remove('agent-mode');
984
+ } else {
985
+ this.shadowRoot.querySelector('video')?.classList?.add('agent-mode');
986
+ }
987
+ } else {
988
+ this.switchCamera.hidden = true;
989
+ }
990
+ }
991
+
992
+ get hasAgentSupport() {
993
+ return this.hasAttribute('has-agent-support');
994
+ }
995
+
996
+ get title() {
997
+ return this.getAttribute('title') || 'Submit Front of ID';
998
+ }
999
+
1000
+ get hidden() {
1001
+ return this.getAttribute('hidden');
1002
+ }
1003
+
1004
+ get cameraError() {
1005
+ return this.getAttribute('data-camera-error');
1006
+ }
1007
+
1008
+ get disableImageTests() {
1009
+ return this.hasAttribute('disable-image-tests');
1010
+ }
1011
+
1012
+ get allowAgentMode() {
1013
+ return this.getAttribute('allow-agent-mode') === 'true';
1014
+ }
1015
+
1016
+ get inAgentMode() {
1017
+ return this.facingMode === 'environment';
1018
+ }
1019
+
1020
+ static get observedAttributes() {
1021
+ return [
1022
+ 'allow-agent-mode',
1023
+ 'data-camera-error',
1024
+ 'data-camera-ready',
1025
+ 'disable-image-tests',
1026
+ 'hidden',
1027
+ 'hide-back-to-host',
1028
+ 'show-navigation',
1029
+ 'title',
1030
+ ];
1031
+ }
1032
+
1033
+ attributeChangedCallback(name) {
1034
+ switch (name) {
1035
+ case 'data-camera-error':
1036
+ case 'hidden':
1037
+ case 'title':
1038
+ this.shadowRoot.innerHTML = this.render();
1039
+ this.init();
1040
+ break;
1041
+ case 'allow-agent-mode':
1042
+ // only re-render if the shadowRoot is empty or not initialized
1043
+ if (!this.shadowRoot.innerHTML.trim()) {
1044
+ this.shadowRoot.innerHTML = this.render();
1045
+ this.init();
1046
+ } else {
1047
+ // update the setupAgentMode
1048
+ this.setupAgentMode();
1049
+ }
1050
+ break;
1051
+ case 'show-navigation':
1052
+ // update the navigation element if it exists
1053
+ if (this.shadowRoot.innerHTML.trim()) {
1054
+ const navigation =
1055
+ this.shadowRoot.querySelector('smileid-navigation');
1056
+ if (navigation) {
1057
+ if (this.showNavigation) {
1058
+ navigation.setAttribute('show-navigation', '');
1059
+ } else {
1060
+ navigation.removeAttribute('show-navigation');
1061
+ }
1062
+ }
1063
+ } else {
1064
+ this.shadowRoot.innerHTML = this.render();
1065
+ this.init();
1066
+ }
1067
+ break;
1068
+ case 'data-camera-ready':
1069
+ // don't re-render, just handle the stream
1070
+ if (this.hasAttribute('data-camera-ready') && SmartCamera.stream) {
1071
+ this.handleStream(SmartCamera.stream);
1072
+ }
1073
+ break;
1074
+ default:
1075
+ break;
1076
+ }
1077
+ }
1078
+
1079
+ handleBackEvents() {
1080
+ this.stopMedia();
1081
+ this.dispatchEvent(new CustomEvent('selfie-capture.cancelled'));
1082
+ }
1083
+
1084
+ closeWindow() {
1085
+ this.stopMedia();
1086
+ this.dispatchEvent(new CustomEvent('selfie-capture.close'));
1087
+ }
1088
+
1089
+ stopMedia() {
1090
+ this.removeAttribute('data-camera-ready');
1091
+ SmartCamera.stopMedia();
1092
+ }
1093
+ }
1094
+
1095
+ if ('customElements' in window && !customElements.get('selfie-capture')) {
1096
+ window.customElements.define('selfie-capture', SelfieCaptureScreen);
1097
+ }
1098
+
1099
+ export default SelfieCaptureScreen;