@smileid/web-components 2.0.1 → 2.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 (61) hide show
  1. package/package.json +58 -58
  2. package/src/components/README.md +14 -14
  3. package/src/components/attribution/PoweredBySmileId.js +42 -42
  4. package/src/components/camera-permission/CameraPermission.js +140 -140
  5. package/src/components/camera-permission/CameraPermission.stories.js +27 -27
  6. package/src/components/combobox/src/Combobox.js +589 -589
  7. package/src/components/combobox/src/index.js +1 -1
  8. package/src/components/document/src/DocumentCaptureScreens.js +409 -409
  9. package/src/components/document/src/DocumentCaptureScreens.stories.js +57 -57
  10. package/src/components/document/src/README.md +111 -111
  11. package/src/components/document/src/document-capture/DocumentCapture.js +760 -760
  12. package/src/components/document/src/document-capture/DocumentCapture.stories.js +78 -78
  13. package/src/components/document/src/document-capture/README.md +90 -90
  14. package/src/components/document/src/document-capture/index.js +3 -3
  15. package/src/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +499 -499
  16. package/src/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +24 -24
  17. package/src/components/document/src/document-capture-instructions/README.md +56 -56
  18. package/src/components/document/src/document-capture-instructions/index.js +3 -3
  19. package/src/components/document/src/document-capture-review/DocumentCaptureReview.js +362 -362
  20. package/src/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +24 -24
  21. package/src/components/document/src/document-capture-review/README.md +79 -79
  22. package/src/components/document/src/document-capture-review/index.js +3 -3
  23. package/src/components/document/src/index.js +3 -3
  24. package/src/components/end-user-consent/src/EndUserConsent.js +795 -795
  25. package/src/components/end-user-consent/src/EndUserConsent.stories.js +29 -29
  26. package/src/components/end-user-consent/src/index.js +4 -4
  27. package/src/components/navigation/src/Navigation.js +171 -171
  28. package/src/components/navigation/src/Navigation.stories.js +24 -24
  29. package/src/components/navigation/src/index.js +3 -3
  30. package/src/components/selfie/README.md +225 -225
  31. package/src/components/selfie/src/SelfieCaptureScreens.js +282 -282
  32. package/src/components/selfie/src/SelfieCaptureScreens.stories.js +29 -29
  33. package/src/components/selfie/src/index.js +5 -5
  34. package/src/components/selfie/src/selfie-capture/SelfieCapture.js +1041 -1010
  35. package/src/components/selfie/src/selfie-capture/SelfieCapture.stories.js +36 -36
  36. package/src/components/selfie/src/selfie-capture/index.js +3 -3
  37. package/src/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +657 -648
  38. package/src/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +23 -23
  39. package/src/components/selfie/src/selfie-capture-instructions/index.js +3 -3
  40. package/src/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +347 -347
  41. package/src/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +24 -24
  42. package/src/components/selfie/src/selfie-capture-review/index.js +3 -3
  43. package/src/components/signature-pad/package-lock.json +3009 -3009
  44. package/src/components/signature-pad/package.json +30 -30
  45. package/src/components/signature-pad/src/SignaturePad.js +484 -484
  46. package/src/components/signature-pad/src/SignaturePad.stories.js +32 -32
  47. package/src/components/signature-pad/src/index.js +3 -3
  48. package/src/components/smart-camera-web/src/README.md +207 -207
  49. package/src/components/smart-camera-web/src/SmartCameraWeb.js +299 -299
  50. package/src/components/smart-camera-web/src/SmartCameraWeb.stories.js +57 -57
  51. package/src/components/totp-consent/src/TotpConsent.js +949 -949
  52. package/src/components/totp-consent/src/index.js +4 -4
  53. package/src/domain/camera/src/README.md +38 -38
  54. package/src/domain/camera/src/SmartCamera.js +109 -109
  55. package/src/domain/constants/src/Constants.js +27 -27
  56. package/src/domain/file-upload/README.md +35 -35
  57. package/src/domain/file-upload/src/SmartFileUpload.js +65 -65
  58. package/src/index.js +5 -5
  59. package/src/styles/README.md +3 -3
  60. package/src/styles/src/styles.js +359 -359
  61. package/src/styles/src/typography.js +52 -52
@@ -1,1010 +1,1041 @@
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
- color: #4E6577;
286
- display: flex;
287
- justify-content: center;
288
- letter-spacing: .075em;
289
- }
290
-
291
- .powered-by {
292
- box-shadow: 0px 2.57415px 2.57415px rgba(0, 0, 0, 0.06);
293
- display: inline-flex;
294
- font-size: .5rem;
295
- }
296
-
297
- .tips {
298
- margin-left: auto;
299
- margin-right: auto;
300
- max-width: 17rem;
301
- }
302
-
303
- .tips > * + *,
304
- .powered-by > * + * {
305
- display: inline-block;
306
- margin-left: .5em;
307
- }
308
-
309
- .powered-by .company {
310
- color: #18406D;
311
- font-weight: 700;
312
- letter-spacing: .15rem;
313
- }
314
-
315
- .logo-mark {
316
- background-color: #004071;
317
- display: inline-block;
318
- padding: .25em .5em;
319
- }
320
-
321
- .logo-mark svg {
322
- height: auto;
323
- justify-self: center;
324
- width: .75em;
325
- }
326
-
327
- @keyframes fadeInOut {
328
- 12.5% {
329
- opacity: 0;
330
- }
331
-
332
- 50% {
333
- opacity: 1;
334
- }
335
-
336
- 87.5% {
337
- opacity: 0;
338
- }
339
- }
340
-
341
- .id-video-container.portrait {
342
- width: 100%;
343
- position: relative;
344
- height: calc(200px * 1.4);
345
- }
346
-
347
- .id-video-container.portrait video {
348
- width: calc(213px + 0.9rem);
349
- height: 100%;
350
- position: absolute;
351
- top: 239px;
352
- left: 161px;
353
- padding-bottom: calc((214px * 1.4) / 3);
354
- padding-top: calc((191px * 1.4) / 3);
355
- object-fit: cover;
356
-
357
- transform: translateX(-50%) translateY(-50%);
358
- z-index: 1;
359
- block-size: 100%;
360
- }
361
-
362
- .video-container,
363
- .id-video-container.landscape {
364
- position: relative;
365
- z-index: 1;
366
- width: 100%;
367
- }
368
-
369
- .video-container #smile-cta,
370
- .video-container video,
371
- .id-video-container.landscape video {
372
- left: 50%;
373
- min-width: auto;
374
- position: absolute;
375
- top: calc(50% - 3px);
376
- transform: translateX(-50%) translateY(50%);
377
- }
378
-
379
- .video-container #smile-cta {
380
- color: white;
381
- font-size: 2rem;
382
- font-weight: bold;
383
- opacity: 0;
384
- top: calc(50% - 3rem);
385
- }
386
-
387
- .video-container video {
388
- min-height: 100%;
389
- transform: scaleX(-1) translateX(50%) translateY(-50%);
390
- }
391
-
392
- .video-container video.agent-mode {
393
- min-height: 100%;
394
- transform: scaleX(1) translateX(-50%) translateY(-50%);
395
- }
396
-
397
- .video-container .video {
398
- background-color: black;
399
- position: absolute;
400
- left: 50%;
401
- height: calc(100% - 6px);
402
- clip-path: ellipse(101px 118px);
403
- }
404
-
405
- .id-video-container.landscape {
406
- min-height: calc((2 * 10rem) + 198px);
407
- height: auto;
408
- }
409
-
410
- .id-video-container.portrait .image-frame-portrait {
411
- border-width: 0.9rem;
412
- border-color: rgba(0, 0, 0, 0.7);
413
- border-style: solid;
414
- height: auto;
415
- position: absolute;
416
- top: 80px;
417
- left: 47px;
418
- z-index: 2;
419
- width: 200px;
420
- height: calc(200px * 1.4);
421
- }
422
-
423
- .id-video-container.landscape .image-frame {
424
- border-width: 10rem 1rem;
425
- border-color: rgba(0, 0, 0, 0.7);
426
- border-style: solid;
427
- height: auto;
428
- width: 90%;
429
- position: absolute;
430
- top: 0;
431
- left: 0;
432
- z-index: 2;
433
- }
434
-
435
- .id-video-container.landscape video {
436
- width: 100%;
437
- transform: translateX(-50%) translateY(-50%);
438
- z-index: 1;
439
- height: 100%;
440
- block-size: 100%;
441
- }
442
-
443
- .id-video-container.landscape img {
444
- position: absolute;
445
- top: 50%;
446
- left: 50%;
447
- transform: translateX(-50%) translateY(-50%);
448
- max-width: 90%;
449
- }
450
-
451
- .actions {
452
- background-color: rgba(0, 0, 0, .7);
453
- bottom: 0;
454
- display: flex;
455
- justify-content: space-between;
456
- padding: 1rem;
457
- position: absolute;
458
- width: 90%;
459
- z-index: 2;
460
- }
461
-
462
- #back-of-id-camera-screen .id-video-container.portrait .actions,
463
- #id-camera-screen .id-video-container.portrait .actions {
464
- top: 145%;
465
- width: calc(200px * 1.4);
466
- }
467
-
468
- #back-of-id-camera-screen .section.portrait, #id-camera-screen .section.portrait {
469
- min-height: calc((200px * 1.4) + 260px);
470
- }
471
-
472
- #selfie-capture-screen,
473
- #back-of-id-entry-screen {
474
- block-size: 45rem;
475
- padding-block: 2rem;
476
- display: flex;
477
- flex-direction: column;
478
- max-block-size: 100%;
479
- max-inline-size: 40ch;
480
- }
481
-
482
- #selfie-capture-screen header p {
483
- margin-block: 0 !important;
484
- }
485
-
486
- .document-tips {
487
- margin-block-start: 1.5rem;
488
- display: flex;
489
- align-items: center;
490
- text-align: initial;
491
- }
492
-
493
- .document-tips svg {
494
- flex-shrink: 0;
495
- margin-inline-end: 1rem;
496
- }
497
-
498
- .document-tips p {
499
- margin-block: 0;
500
- }
501
-
502
- .document-tips p:first-of-type {
503
- font-size; 1.875rem;
504
- font-weight: bold
505
- }
506
-
507
- [type='file'] {
508
- display: none;
509
- }
510
-
511
- .document-tips > * + * {
512
- margin-inline-start; 1em;
513
- }
514
- </style>
515
- <div id='selfie-capture-screen' class='flow center'>
516
- <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
517
- <h1 class='text-2xl title-color font-bold'>Take a Selfie</h1>
518
-
519
- <div className="error">
520
- ${this.cameraError ? `<p class="color-red">${this.cameraError}</p>` : ''}
521
- </div>
522
- <div class='section | flow' ${this.cameraError ? 'hidden' : ''}>
523
- <div class='video-container'>
524
- <div class='video'>
525
- </div>
526
- <svg id="image-outline" width="215" height="245" viewBox="0 0 215 245" fill="none" xmlns="http://www.w3.org/2000/svg">
527
- <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"/>
528
- </svg>
529
- <p id='smile-cta' class='color-gray'>SMILE</p>
530
- </div>
531
-
532
- <small class='tips'>
533
- <svg width='44' xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 40 40">
534
- <path fill="#F8F8FA" fill-rule="evenodd" d="M17.44 0h4.2c4.92 0 7.56.68 9.95 1.96a13.32 13.32 0 015.54 5.54c1.27 2.39 1.95 5.02 1.95 9.94v4.2c0 4.92-.68 7.56-1.95 9.95a13.32 13.32 0 01-5.54 5.54c-2.4 1.27-5.03 1.95-9.95 1.95h-4.2c-4.92 0-7.55-.68-9.94-1.95a13.32 13.32 0 01-5.54-5.54C.68 29.19 0 26.56 0 21.64v-4.2C0 12.52.68 9.9 1.96 7.5A13.32 13.32 0 017.5 1.96C9.89.68 12.52 0 17.44 0z" clip-rule="evenodd"/>
535
- <path fill="#AEB6CB" d="M19.95 10.58a.71.71 0 000 1.43.71.71 0 000-1.43zm-5.54 2.3a.71.71 0 000 1.43.71.71 0 000-1.43zm11.08 0a.71.71 0 000 1.43.71.71 0 000-1.43zm-5.63 1.27a4.98 4.98 0 00-2.05 9.48v1.2a2.14 2.14 0 004.28 0v-1.2a4.99 4.99 0 00-2.23-9.48zm-7.75 4.27a.71.71 0 000 1.43.71.71 0 000-1.43zm15.68 0a.71.71 0 000 1.43.71.71 0 000-1.43z"/>
536
- </svg>
537
- <span>Tips: Put your face inside the oval frame and click to "take selfie"</span> </small>
538
-
539
- ${this.allowAgentMode ? `<button data-variant='outline small' id='switch-camera' class='button | center' type='button'>${this.inAgentMode ? 'Agent Mode On' : 'Agent Mode Off'}</button>` : ''}
540
-
541
- <button data-variant='solid' id='start-image-capture' class='button | center' type='button'>
542
- Take Selfie
543
- </button>
544
-
545
- ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
546
- </div>
547
- </div>
548
- `;
549
- }
550
-
551
- async function getPermissions(
552
- captureScreen,
553
- constraints = { facingMode: 'user' },
554
- ) {
555
- try {
556
- const stream = await SmartCamera.getMedia({
557
- audio: false,
558
- video: constraints,
559
- });
560
- const devices = await navigator.mediaDevices.enumerateDevices();
561
- const videoDevice = devices.find(
562
- (device) =>
563
- device.kind === 'videoinput' &&
564
- stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
565
- );
566
- window.dispatchEvent(
567
- new CustomEvent('metadata.camera-name', {
568
- detail: { cameraName: videoDevice?.label },
569
- }),
570
- );
571
- captureScreen?.removeAttribute('data-camera-error');
572
- captureScreen?.setAttribute('data-camera-ready', true);
573
- } catch (error) {
574
- captureScreen?.removeAttribute('data-camera-ready');
575
- captureScreen?.setAttribute(
576
- 'data-camera-error',
577
- SmartCamera.handleCameraError(error),
578
- );
579
- }
580
- }
581
-
582
- class SelfieCaptureScreen extends HTMLElement {
583
- constructor() {
584
- super();
585
- this.templateString = templateString.bind(this);
586
- this.render = () => this.templateString();
587
-
588
- this.attachShadow({ mode: 'open' });
589
- this.facingMode = 'user';
590
- if (this.allowAgentMode) {
591
- this.facingMode = 'environment';
592
- }
593
- }
594
-
595
- connectedCallback() {
596
- const template = document.createElement('template');
597
- template.innerHTML = this.render();
598
- this.shadowRoot.innerHTML = '';
599
- this.shadowRoot.appendChild(template.content.cloneNode(true));
600
- this.videoContainer = this.shadowRoot.querySelector(
601
- '.video-container > .video',
602
- );
603
- this.init();
604
- }
605
-
606
- init() {
607
- this._videoStreamDurationInMS = 7800;
608
- this._imageCaptureIntervalInMS = 200;
609
-
610
- this._data = {
611
- images: [],
612
- meta: {
613
- libraryVersion: COMPONENTS_VERSION,
614
- },
615
- };
616
- this._rawImages = [];
617
-
618
- this.setUpEventListeners();
619
- }
620
-
621
- reset() {
622
- this.disconnectedCallback();
623
- this.connectedCallback();
624
- }
625
-
626
- _startImageCapture() {
627
- this.startImageCapture.disabled = true;
628
- if (this.switchCamera) {
629
- this.switchCamera.disabled = true;
630
- }
631
-
632
- /**
633
- * this was culled from https://jakearchibald.com/2013/animated-line-drawing-svg/
634
- */
635
- // NOTE: initialise image outline
636
- const imageOutlineLength = this.imageOutline.getTotalLength();
637
- // Clear any previous transition
638
- this.imageOutline.style.transition = 'none';
639
- // Set up the starting positions
640
- this.imageOutline.style.strokeDasharray = `${imageOutlineLength} ${imageOutlineLength}`;
641
- this.imageOutline.style.strokeDashoffset = imageOutlineLength;
642
- // Trigger a layout so styles are calculated & the browser
643
- // picks up the starting position before animating
644
- this.imageOutline.getBoundingClientRect();
645
- // Define our transition
646
- this.imageOutline.style.transition = `stroke-dashoffset ${this._videoStreamDurationInMS / 1000}s ease-in-out`;
647
- // Go!
648
- this.imageOutline.style.strokeDashoffset = '0';
649
-
650
- this.smileCTA.style.animation = `fadeInOut ease ${this._videoStreamDurationInMS / 1000}s`;
651
-
652
- this._imageCaptureInterval = setInterval(() => {
653
- this._capturePOLPhoto();
654
- }, this._imageCaptureIntervalInMS);
655
-
656
- this._videoStreamTimeout = setTimeout(() => {
657
- this._stopVideoStream();
658
- }, this._videoStreamDurationInMS);
659
- }
660
-
661
- async _switchCamera() {
662
- this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
663
- if (this.facingMode === 'user') {
664
- this.shadowRoot.querySelector('video').classList.remove('agent-mode');
665
- } else {
666
- this.shadowRoot.querySelector('video').classList.add('agent-mode');
667
- }
668
- this.startImageCapture.disabled = true;
669
- this.switchCamera.disabled = true;
670
- SmartCamera.stopMedia();
671
- await getPermissions(this, { facingMode: this.facingMode });
672
- this.handleStream(SmartCamera.stream);
673
- }
674
-
675
- _stopVideoStream() {
676
- try {
677
- clearTimeout(this._videoStreamTimeout);
678
- clearInterval(this._imageCaptureInterval);
679
- clearInterval(this._drawingInterval);
680
- this.smileCTA.style.animation = 'none';
681
-
682
- this._capturePOLPhoto(); // NOTE: capture the last photo
683
- this._captureReferencePhoto();
684
- SmartCamera.stopMedia();
685
-
686
- const totalNoOfFrames = this._rawImages.length;
687
- this._data.referenceImage = this._referenceImage;
688
- this._data.previewImage = this._referenceImage;
689
-
690
- const livenessFramesIndices = getLivenessFramesIndices(totalNoOfFrames);
691
-
692
- this._data.images = this._data.images.concat(
693
- livenessFramesIndices.map((imageIndex) => ({
694
- image: this._rawImages[imageIndex].split(',')[1],
695
- image_type_id: IMAGE_TYPE.LIVENESS_IMAGE_BASE64,
696
- })),
697
- );
698
-
699
- this._publishImages();
700
- } catch (error) {
701
- console.error(error);
702
- // Todo: handle error
703
- }
704
- }
705
-
706
- _capturePOLPhoto() {
707
- const canvas = document.createElement('canvas');
708
- // Determine orientation of the video
709
- const isPortrait = this._video.videoHeight > this._video.videoWidth;
710
-
711
- // Set dimensions based on orientation, ensuring minimums
712
- if (isPortrait) {
713
- // Portrait orientation (taller than wide)
714
- canvas.width = 240;
715
- canvas.height = Math.max(
716
- 320,
717
- (canvas.width * this._video.videoHeight) / this._video.videoWidth,
718
- );
719
- } else {
720
- // Landscape orientation (wider than tall)
721
- canvas.height = 240;
722
- canvas.width = Math.max(
723
- 320,
724
- (canvas.height * this._video.videoWidth) / this._video.videoHeight,
725
- );
726
- }
727
-
728
- // NOTE: we do not want to test POL images
729
- this._drawImage(canvas, false);
730
-
731
- this._rawImages.push(canvas.toDataURL('image/jpeg'));
732
- }
733
-
734
- _captureReferencePhoto() {
735
- const canvas = document.createElement('canvas');
736
- // Determine orientation of the video
737
- const isPortrait = this._video.videoHeight > this._video.videoWidth;
738
-
739
- // Set dimensions based on orientation, ensuring minimums
740
- if (isPortrait) {
741
- // Portrait orientation (taller than wide)
742
- canvas.width = 480;
743
- canvas.height = Math.max(
744
- 640,
745
- (canvas.width * this._video.videoHeight) / this._video.videoWidth,
746
- );
747
- } else {
748
- // Landscape orientation (wider than tall)
749
- canvas.height = 480;
750
- canvas.width = Math.max(
751
- 640,
752
- (canvas.height * this._video.videoWidth) / this._video.videoHeight,
753
- );
754
- }
755
-
756
- // NOTE: we want to test the image quality of the reference photo
757
- this._drawImage(canvas, !this.disableImageTests);
758
-
759
- const image = canvas.toDataURL('image/jpeg');
760
-
761
- this._referenceImage = image;
762
-
763
- this._data.images.push({
764
- image: image.split(',')[1],
765
- image_type_id: IMAGE_TYPE.SELFIE_IMAGE_BASE64,
766
- });
767
- }
768
-
769
- _publishImages() {
770
- this.dispatchEvent(
771
- new CustomEvent('selfie-capture.publish', {
772
- detail: this._data,
773
- }),
774
- );
775
- }
776
-
777
- resetErrorMessage() {
778
- this.errorMessage.textContent = '';
779
- }
780
-
781
- _drawImage(canvas, enableImageTests = true, video = this._video) {
782
- // this.resetErrorMessage();
783
- const context = canvas.getContext('2d');
784
-
785
- context.drawImage(
786
- video,
787
- 0,
788
- 0,
789
- video.videoWidth,
790
- video.videoHeight,
791
- 0,
792
- 0,
793
- canvas.width,
794
- canvas.height,
795
- );
796
-
797
- if (enableImageTests) {
798
- const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
799
-
800
- const hasEnoughColors = hasMoreThanNColors(imageData.data);
801
-
802
- if (hasEnoughColors) {
803
- return context;
804
- }
805
- throw new Error(
806
- 'Unable to capture webcam images - Please try another device',
807
- );
808
- } else {
809
- return context;
810
- }
811
- }
812
-
813
- handleStream(stream) {
814
- try {
815
- const videoExists = this.shadowRoot.querySelector('video');
816
- let video = null;
817
- if (videoExists) {
818
- video = this.shadowRoot.querySelector('video');
819
- } else {
820
- video = document.createElement('video');
821
- }
822
-
823
- video.autoplay = true;
824
- video.playsInline = true;
825
- video.muted = true;
826
-
827
- if ('srcObject' in video) {
828
- video.srcObject = stream;
829
- } else {
830
- video.src = window.URL.createObjectURL(stream);
831
- }
832
-
833
- video.onloadedmetadata = () => {
834
- video.play();
835
- };
836
-
837
- this._video = video;
838
- const videoContainer = this.shadowRoot.querySelector(
839
- '.video-container > .video',
840
- );
841
- this._data.permissionGranted = true;
842
-
843
- if (!videoExists) {
844
- videoContainer.prepend(video);
845
- }
846
- } catch (error) {
847
- this.setAttribute(
848
- 'data-camera-error',
849
- SmartCamera.handleCameraError(error),
850
- );
851
- if (error.name !== 'AbortError') {
852
- console.error(error);
853
- }
854
- SmartCamera.stopMedia();
855
- }
856
- }
857
-
858
- setUpEventListeners() {
859
- this.navigation = this.shadowRoot.querySelector('smileid-navigation');
860
-
861
- this.startImageCapture = this.shadowRoot.querySelector(
862
- '#start-image-capture',
863
- );
864
-
865
- this.switchCamera = this.shadowRoot.querySelector('#switch-camera');
866
- this.imageOutline = this.shadowRoot.querySelector('#image-outline path');
867
- this.smileCTA = this.shadowRoot.querySelector('#smile-cta');
868
-
869
- this.startImageCapture.addEventListener('click', () => {
870
- this._startImageCapture();
871
- });
872
-
873
- this.switchCamera?.addEventListener('click', () => {
874
- this._switchCamera();
875
- });
876
-
877
- this.navigation.addEventListener('navigation.back', () => {
878
- this.handleBackEvents();
879
- });
880
-
881
- this.navigation.addEventListener('navigation.close', () => {
882
- this.closeWindow();
883
- });
884
-
885
- if (SmartCamera.stream) {
886
- this.handleStream(SmartCamera.stream);
887
- } else if (this.hasAttribute('data-camera-ready')) {
888
- getPermissions(this, { facingMode: this.facingMode });
889
- }
890
-
891
- this.setupAgentMode();
892
- }
893
-
894
- disconnectedCallback() {
895
- SmartCamera.stopMedia();
896
- clearTimeout(this._videoStreamTimeout);
897
- }
898
-
899
- get hideBack() {
900
- return this.hasAttribute('hide-back');
901
- }
902
-
903
- get showNavigation() {
904
- return this.hasAttribute('show-navigation');
905
- }
906
-
907
- get themeColor() {
908
- return this.getAttribute('theme-color') || '#001096';
909
- }
910
-
911
- get hideAttribution() {
912
- return this.hasAttribute('hide-attribution');
913
- }
914
-
915
- async setupAgentMode() {
916
- if (!this.allowAgentMode) {
917
- return;
918
- }
919
-
920
- const supportAgentMode = await SmartCamera.supportsAgentMode();
921
-
922
- if (supportAgentMode || this.hasAttribute('show-agent-mode-for-tests')) {
923
- this.switchCamera.hidden = false;
924
- if (this.facingMode === 'user') {
925
- this.shadowRoot.querySelector('video')?.classList?.remove('agent-mode');
926
- } else {
927
- this.shadowRoot.querySelector('video')?.classList?.add('agent-mode');
928
- }
929
- } else {
930
- this.switchCamera.hidden = true;
931
- }
932
- }
933
-
934
- get hasAgentSupport() {
935
- return this.hasAttribute('has-agent-support');
936
- }
937
-
938
- get title() {
939
- return this.getAttribute('title') || 'Submit Front of ID';
940
- }
941
-
942
- get hidden() {
943
- return this.getAttribute('hidden');
944
- }
945
-
946
- get cameraError() {
947
- return this.getAttribute('data-camera-error');
948
- }
949
-
950
- get disableImageTests() {
951
- return this.hasAttribute('disable-image-tests');
952
- }
953
-
954
- get allowAgentMode() {
955
- return this.getAttribute('allow-agent-mode') === 'true';
956
- }
957
-
958
- get inAgentMode() {
959
- return this.facingMode === 'environment';
960
- }
961
-
962
- static get observedAttributes() {
963
- return [
964
- 'allow-agent-mode',
965
- 'data-camera-error',
966
- 'data-camera-ready',
967
- 'disable-image-tests',
968
- 'hidden',
969
- 'hide-back-to-host',
970
- 'show-navigation',
971
- 'title',
972
- ];
973
- }
974
-
975
- attributeChangedCallback(name) {
976
- switch (name) {
977
- case 'data-camera-error':
978
- case 'data-camera-ready':
979
- case 'hidden':
980
- case 'title':
981
- case 'allow-agent-mode':
982
- this.shadowRoot.innerHTML = this.render();
983
- this.init();
984
- break;
985
- default:
986
- break;
987
- }
988
- }
989
-
990
- handleBackEvents() {
991
- this.stopMedia();
992
- this.dispatchEvent(new CustomEvent('selfie-capture.cancelled'));
993
- }
994
-
995
- closeWindow() {
996
- this.stopMedia();
997
- this.dispatchEvent(new CustomEvent('selfie-capture.close'));
998
- }
999
-
1000
- stopMedia() {
1001
- this.removeAttribute('data-camera-ready');
1002
- SmartCamera.stopMedia();
1003
- }
1004
- }
1005
-
1006
- if ('customElements' in window && !customElements.get('selfie-capture')) {
1007
- window.customElements.define('selfie-capture', SelfieCaptureScreen);
1008
- }
1009
-
1010
- 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
+ }
355
+
356
+ .video-container video,
357
+ .id-video-container.landscape video {
358
+ left: 50%;
359
+ min-width: auto;
360
+ position: absolute;
361
+ top: calc(50% - 3px);
362
+ transform: translateX(-50%) translateY(50%);
363
+ }
364
+
365
+ .video-container #smile-cta-box {
366
+ color: #fff;
367
+ width: 100%;
368
+ position: absolute;
369
+ top: 0;
370
+ left: 0;
371
+ padding: 2rem 0;
372
+ opacity: 0;
373
+ transition: opacity 0.5s ease-in-out;
374
+ }
375
+
376
+ .video-container #smile-cta {
377
+ font-size: 1.2rem;
378
+ font-weight: bold;
379
+ margin-top: 0;
380
+ margin-bottom: 0;
381
+ }
382
+
383
+ .video-container video {
384
+ min-height: 100%;
385
+ transform: scaleX(-1) translateX(50%) translateY(-50%);
386
+ }
387
+
388
+ .video-container video.agent-mode {
389
+ min-height: 100%;
390
+ transform: scaleX(1) translateX(-50%) translateY(-50%);
391
+ }
392
+
393
+ .video-container .video {
394
+ background-color: black;
395
+ position: absolute;
396
+ left: 50%;
397
+ height: calc(100% - 6px);
398
+ clip-path: ellipse(101px 118px);
399
+ }
400
+
401
+ .id-video-container.landscape {
402
+ min-height: calc((2 * 10rem) + 198px);
403
+ height: auto;
404
+ }
405
+
406
+ .id-video-container.portrait .image-frame-portrait {
407
+ border-width: 0.9rem;
408
+ border-color: rgba(0, 0, 0, 0.7);
409
+ border-style: solid;
410
+ height: auto;
411
+ position: absolute;
412
+ top: 80px;
413
+ left: 47px;
414
+ z-index: 2;
415
+ width: 200px;
416
+ height: calc(200px * 1.4);
417
+ }
418
+
419
+ .id-video-container.landscape .image-frame {
420
+ border-width: 10rem 1rem;
421
+ border-color: rgba(0, 0, 0, 0.7);
422
+ border-style: solid;
423
+ height: auto;
424
+ width: 90%;
425
+ position: absolute;
426
+ top: 0;
427
+ left: 0;
428
+ z-index: 2;
429
+ }
430
+
431
+ .id-video-container.landscape video {
432
+ width: 100%;
433
+ transform: translateX(-50%) translateY(-50%);
434
+ z-index: 1;
435
+ height: 100%;
436
+ block-size: 100%;
437
+ }
438
+
439
+ .id-video-container.landscape img {
440
+ position: absolute;
441
+ top: 50%;
442
+ left: 50%;
443
+ transform: translateX(-50%) translateY(-50%);
444
+ max-width: 90%;
445
+ }
446
+
447
+ .actions {
448
+ background-color: rgba(0, 0, 0, .7);
449
+ bottom: 0;
450
+ display: flex;
451
+ justify-content: space-between;
452
+ padding: 1rem;
453
+ position: absolute;
454
+ width: 90%;
455
+ z-index: 2;
456
+ }
457
+
458
+ #back-of-id-camera-screen .id-video-container.portrait .actions,
459
+ #id-camera-screen .id-video-container.portrait .actions {
460
+ top: 145%;
461
+ width: calc(200px * 1.4);
462
+ }
463
+
464
+ #back-of-id-camera-screen .section.portrait, #id-camera-screen .section.portrait {
465
+ min-height: calc((200px * 1.4) + 260px);
466
+ }
467
+
468
+ #selfie-capture-screen,
469
+ #back-of-id-entry-screen {
470
+ block-size: 45rem;
471
+ padding-block: 2rem;
472
+ display: flex;
473
+ flex-direction: column;
474
+ max-block-size: 100%;
475
+ max-inline-size: 40ch;
476
+ }
477
+
478
+ #selfie-capture-screen header p {
479
+ margin-block: 0 !important;
480
+ }
481
+
482
+ .document-tips {
483
+ margin-block-start: 1.5rem;
484
+ display: flex;
485
+ align-items: center;
486
+ text-align: initial;
487
+ }
488
+
489
+ .document-tips svg {
490
+ flex-shrink: 0;
491
+ margin-inline-end: 1rem;
492
+ }
493
+
494
+ .document-tips p {
495
+ margin-block: 0;
496
+ }
497
+
498
+ .document-tips p:first-of-type {
499
+ font-size; 1.875rem;
500
+ font-weight: bold
501
+ }
502
+
503
+ [type='file'] {
504
+ display: none;
505
+ }
506
+
507
+ .document-tips > * + * {
508
+ margin-inline-start; 1em;
509
+ }
510
+ </style>
511
+ <div id='selfie-capture-screen' class='flow center'>
512
+ <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
513
+
514
+ <div class='tips'>Fit your head inside the oval frame</div>
515
+
516
+ <div className="error">
517
+ ${this.cameraError ? `<p class="color-red">${this.cameraError}</p>` : ''}
518
+ </div>
519
+ <div class='section | flow' ${this.cameraError ? 'hidden' : ''}>
520
+ <div class='video-container'>
521
+ <div class='video'>
522
+ </div>
523
+ <svg id="image-outline" width="215" height="245" viewBox="0 0 215 245" fill="none" xmlns="http://www.w3.org/2000/svg">
524
+ <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"/>
525
+ </svg>
526
+ <div id="smile-cta-box">
527
+ <div>
528
+ <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" viewBox="70 70 360 360" fill="currentColor">
529
+ <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"/>
530
+ <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"/>
531
+ <path id="mouth"
532
+ 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">
533
+ <animate
534
+ id="mouthAnim"
535
+ attributeName="d"
536
+ begin="indefinite"
537
+ dur="1s"
538
+ fill="freeze"
539
+ 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" />
540
+ </path>
541
+ <circle fill="none" stroke="currentColor" stroke-width="10px" cx="287.3" cy="263.68" r="14"/>
542
+ <circle fill="none" stroke="currentColor" stroke-width="10px" cx="209.16" cy="263.68" r="14"/>
543
+ </svg>
544
+ </div>
545
+ <div>
546
+ <p id='smile-cta'>SMILE</p>
547
+ </div>
548
+ </div>
549
+ </div>
550
+
551
+ ${this.allowAgentMode ? `<button data-variant='outline small' id='switch-camera' class='button | center' type='button'>${this.inAgentMode ? 'Agent Mode On' : 'Agent Mode Off'}</button>` : ''}
552
+
553
+ <button data-variant='solid' id='start-image-capture' class='button | center' type='button'>
554
+ Take Selfie
555
+ </button>
556
+
557
+ ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
558
+ </div>
559
+ </div>
560
+ `;
561
+ }
562
+
563
+ async function getPermissions(
564
+ captureScreen,
565
+ constraints = { facingMode: 'user' },
566
+ ) {
567
+ try {
568
+ const stream = await SmartCamera.getMedia({
569
+ audio: false,
570
+ video: constraints,
571
+ });
572
+ const devices = await navigator.mediaDevices.enumerateDevices();
573
+ const videoDevice = devices.find(
574
+ (device) =>
575
+ device.kind === 'videoinput' &&
576
+ stream.getVideoTracks()[0].getSettings().deviceId === device.deviceId,
577
+ );
578
+ window.dispatchEvent(
579
+ new CustomEvent('metadata.camera-name', {
580
+ detail: { cameraName: videoDevice?.label },
581
+ }),
582
+ );
583
+ captureScreen?.removeAttribute('data-camera-error');
584
+ captureScreen?.setAttribute('data-camera-ready', true);
585
+ } catch (error) {
586
+ captureScreen?.removeAttribute('data-camera-ready');
587
+ captureScreen?.setAttribute(
588
+ 'data-camera-error',
589
+ SmartCamera.handleCameraError(error),
590
+ );
591
+ }
592
+ }
593
+
594
+ class SelfieCaptureScreen extends HTMLElement {
595
+ constructor() {
596
+ super();
597
+ this.templateString = templateString.bind(this);
598
+ this.render = () => this.templateString();
599
+
600
+ this.attachShadow({ mode: 'open' });
601
+ this.facingMode = 'user';
602
+ if (this.allowAgentMode) {
603
+ this.facingMode = 'environment';
604
+ }
605
+ }
606
+
607
+ connectedCallback() {
608
+ const template = document.createElement('template');
609
+ template.innerHTML = this.render();
610
+ this.shadowRoot.innerHTML = '';
611
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
612
+ this.videoContainer = this.shadowRoot.querySelector(
613
+ '.video-container > .video',
614
+ );
615
+ this.init();
616
+ }
617
+
618
+ init() {
619
+ this._videoStreamDurationInMS = 7800;
620
+ this._imageCaptureIntervalInMS = 200;
621
+
622
+ this._data = {
623
+ images: [],
624
+ meta: {
625
+ libraryVersion: COMPONENTS_VERSION,
626
+ },
627
+ };
628
+ this._rawImages = [];
629
+
630
+ this.setUpEventListeners();
631
+ }
632
+
633
+ reset() {
634
+ this.disconnectedCallback();
635
+ this.connectedCallback();
636
+ }
637
+
638
+ _startImageCapture() {
639
+ this.startImageCapture.disabled = true;
640
+ if (this.switchCamera) {
641
+ this.switchCamera.disabled = true;
642
+ }
643
+
644
+ /**
645
+ * this was culled from https://jakearchibald.com/2013/animated-line-drawing-svg/
646
+ */
647
+ // NOTE: initialise image outline
648
+ const imageOutlineLength = this.imageOutline.getTotalLength();
649
+ // Clear any previous transition
650
+ this.imageOutline.style.transition = 'none';
651
+ // Set up the starting positions
652
+ this.imageOutline.style.strokeDasharray = `${imageOutlineLength} ${imageOutlineLength}`;
653
+ this.imageOutline.style.strokeDashoffset = imageOutlineLength;
654
+ // Trigger a layout so styles are calculated & the browser
655
+ // picks up the starting position before animating
656
+ this.imageOutline.getBoundingClientRect();
657
+ // Define our transition
658
+ this.imageOutline.style.transition = `stroke-dashoffset ${this._videoStreamDurationInMS / 1000}s ease-in-out`;
659
+ // Go!
660
+ this.imageOutline.style.strokeDashoffset = '0';
661
+
662
+ setTimeout(() => {
663
+ this.smileCTABox.style.opacity = 1;
664
+ this.mouthAnim.beginElement();
665
+ }, 1500);
666
+
667
+ setTimeout(() => {
668
+ this.smileCTABox.style.opacity = 0;
669
+ }, 3500);
670
+
671
+ setTimeout(() => {
672
+ this.smileCTABox.style.opacity = 1;
673
+ this.smileCTA.textContent = 'BIGGER SMILE';
674
+ this.mouth.setAttribute(
675
+ 'd',
676
+ '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',
677
+ );
678
+ this.mouthAnim.beginElement();
679
+ }, 4000);
680
+
681
+ this._imageCaptureInterval = setInterval(() => {
682
+ this._capturePOLPhoto();
683
+ }, this._imageCaptureIntervalInMS);
684
+
685
+ this._videoStreamTimeout = setTimeout(() => {
686
+ this._stopVideoStream();
687
+ }, this._videoStreamDurationInMS);
688
+ }
689
+
690
+ async _switchCamera() {
691
+ this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
692
+ if (this.facingMode === 'user') {
693
+ this.shadowRoot.querySelector('video').classList.remove('agent-mode');
694
+ } else {
695
+ this.shadowRoot.querySelector('video').classList.add('agent-mode');
696
+ }
697
+ this.startImageCapture.disabled = true;
698
+ this.switchCamera.disabled = true;
699
+ SmartCamera.stopMedia();
700
+ await getPermissions(this, { facingMode: this.facingMode });
701
+ this.handleStream(SmartCamera.stream);
702
+ }
703
+
704
+ _stopVideoStream() {
705
+ try {
706
+ clearTimeout(this._videoStreamTimeout);
707
+ clearInterval(this._imageCaptureInterval);
708
+ clearInterval(this._drawingInterval);
709
+
710
+ this._capturePOLPhoto(); // NOTE: capture the last photo
711
+ this._captureReferencePhoto();
712
+ SmartCamera.stopMedia();
713
+
714
+ const totalNoOfFrames = this._rawImages.length;
715
+ this._data.referenceImage = this._referenceImage;
716
+ this._data.previewImage = this._referenceImage;
717
+
718
+ const livenessFramesIndices = getLivenessFramesIndices(totalNoOfFrames);
719
+
720
+ this._data.images = this._data.images.concat(
721
+ livenessFramesIndices.map((imageIndex) => ({
722
+ image: this._rawImages[imageIndex].split(',')[1],
723
+ image_type_id: IMAGE_TYPE.LIVENESS_IMAGE_BASE64,
724
+ })),
725
+ );
726
+
727
+ this._publishImages();
728
+ } catch (error) {
729
+ console.error(error);
730
+ // Todo: handle error
731
+ }
732
+ }
733
+
734
+ _capturePOLPhoto() {
735
+ const canvas = document.createElement('canvas');
736
+ // Determine orientation of the video
737
+ const isPortrait = this._video.videoHeight > this._video.videoWidth;
738
+
739
+ // Set dimensions based on orientation, ensuring minimums
740
+ if (isPortrait) {
741
+ // Portrait orientation (taller than wide)
742
+ canvas.width = 240;
743
+ canvas.height = Math.max(
744
+ 320,
745
+ (canvas.width * this._video.videoHeight) / this._video.videoWidth,
746
+ );
747
+ } else {
748
+ // Landscape orientation (wider than tall)
749
+ canvas.height = 240;
750
+ canvas.width = Math.max(
751
+ 320,
752
+ (canvas.height * this._video.videoWidth) / this._video.videoHeight,
753
+ );
754
+ }
755
+
756
+ // NOTE: we do not want to test POL images
757
+ this._drawImage(canvas, false);
758
+
759
+ this._rawImages.push(canvas.toDataURL('image/jpeg'));
760
+ }
761
+
762
+ _captureReferencePhoto() {
763
+ const canvas = document.createElement('canvas');
764
+ // Determine orientation of the video
765
+ const isPortrait = this._video.videoHeight > this._video.videoWidth;
766
+
767
+ // Set dimensions based on orientation, ensuring minimums
768
+ if (isPortrait) {
769
+ // Portrait orientation (taller than wide)
770
+ canvas.width = 480;
771
+ canvas.height = Math.max(
772
+ 640,
773
+ (canvas.width * this._video.videoHeight) / this._video.videoWidth,
774
+ );
775
+ } else {
776
+ // Landscape orientation (wider than tall)
777
+ canvas.height = 480;
778
+ canvas.width = Math.max(
779
+ 640,
780
+ (canvas.height * this._video.videoWidth) / this._video.videoHeight,
781
+ );
782
+ }
783
+
784
+ // NOTE: we want to test the image quality of the reference photo
785
+ this._drawImage(canvas, !this.disableImageTests);
786
+
787
+ const image = canvas.toDataURL('image/jpeg');
788
+
789
+ this._referenceImage = image;
790
+
791
+ this._data.images.push({
792
+ image: image.split(',')[1],
793
+ image_type_id: IMAGE_TYPE.SELFIE_IMAGE_BASE64,
794
+ });
795
+ }
796
+
797
+ _publishImages() {
798
+ this.dispatchEvent(
799
+ new CustomEvent('selfie-capture.publish', {
800
+ detail: this._data,
801
+ }),
802
+ );
803
+ }
804
+
805
+ resetErrorMessage() {
806
+ this.errorMessage.textContent = '';
807
+ }
808
+
809
+ _drawImage(canvas, enableImageTests = true, video = this._video) {
810
+ // this.resetErrorMessage();
811
+ const context = canvas.getContext('2d');
812
+
813
+ context.drawImage(
814
+ video,
815
+ 0,
816
+ 0,
817
+ video.videoWidth,
818
+ video.videoHeight,
819
+ 0,
820
+ 0,
821
+ canvas.width,
822
+ canvas.height,
823
+ );
824
+
825
+ if (enableImageTests) {
826
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
827
+
828
+ const hasEnoughColors = hasMoreThanNColors(imageData.data);
829
+
830
+ if (hasEnoughColors) {
831
+ return context;
832
+ }
833
+ throw new Error(
834
+ 'Unable to capture webcam images - Please try another device',
835
+ );
836
+ } else {
837
+ return context;
838
+ }
839
+ }
840
+
841
+ handleStream(stream) {
842
+ try {
843
+ const videoExists = this.shadowRoot.querySelector('video');
844
+ let video = null;
845
+ if (videoExists) {
846
+ video = this.shadowRoot.querySelector('video');
847
+ } else {
848
+ video = document.createElement('video');
849
+ }
850
+
851
+ video.autoplay = true;
852
+ video.playsInline = true;
853
+ video.muted = true;
854
+
855
+ if ('srcObject' in video) {
856
+ video.srcObject = stream;
857
+ } else {
858
+ video.src = window.URL.createObjectURL(stream);
859
+ }
860
+
861
+ video.onloadedmetadata = () => {
862
+ video.play();
863
+ };
864
+
865
+ this._video = video;
866
+ const videoContainer = this.shadowRoot.querySelector(
867
+ '.video-container > .video',
868
+ );
869
+ this._data.permissionGranted = true;
870
+
871
+ if (!videoExists) {
872
+ videoContainer.prepend(video);
873
+ }
874
+ } catch (error) {
875
+ this.setAttribute(
876
+ 'data-camera-error',
877
+ SmartCamera.handleCameraError(error),
878
+ );
879
+ if (error.name !== 'AbortError') {
880
+ console.error(error);
881
+ }
882
+ SmartCamera.stopMedia();
883
+ }
884
+ }
885
+
886
+ setUpEventListeners() {
887
+ this.navigation = this.shadowRoot.querySelector('smileid-navigation');
888
+
889
+ this.startImageCapture = this.shadowRoot.querySelector(
890
+ '#start-image-capture',
891
+ );
892
+
893
+ this.switchCamera = this.shadowRoot.querySelector('#switch-camera');
894
+ this.imageOutline = this.shadowRoot.querySelector('#image-outline path');
895
+ this.smileCTABox = this.shadowRoot.querySelector('#smile-cta-box');
896
+ this.smileCTA = this.shadowRoot.querySelector('#smile-cta');
897
+ this.mouth = this.shadowRoot.querySelector('#mouth');
898
+ this.mouthAnim = this.shadowRoot.querySelector('#mouthAnim');
899
+
900
+ this.startImageCapture.addEventListener('click', () => {
901
+ this._startImageCapture();
902
+ });
903
+
904
+ this.switchCamera?.addEventListener('click', () => {
905
+ this._switchCamera();
906
+ });
907
+
908
+ this.navigation.addEventListener('navigation.back', () => {
909
+ this.handleBackEvents();
910
+ });
911
+
912
+ this.navigation.addEventListener('navigation.close', () => {
913
+ this.closeWindow();
914
+ });
915
+
916
+ if (SmartCamera.stream) {
917
+ this.handleStream(SmartCamera.stream);
918
+ } else if (this.hasAttribute('data-camera-ready')) {
919
+ getPermissions(this, { facingMode: this.facingMode });
920
+ }
921
+
922
+ this.setupAgentMode();
923
+ }
924
+
925
+ disconnectedCallback() {
926
+ SmartCamera.stopMedia();
927
+ clearTimeout(this._videoStreamTimeout);
928
+ }
929
+
930
+ get hideBack() {
931
+ return this.hasAttribute('hide-back');
932
+ }
933
+
934
+ get showNavigation() {
935
+ return this.hasAttribute('show-navigation');
936
+ }
937
+
938
+ get themeColor() {
939
+ return this.getAttribute('theme-color') || '#001096';
940
+ }
941
+
942
+ get hideAttribution() {
943
+ return this.hasAttribute('hide-attribution');
944
+ }
945
+
946
+ async setupAgentMode() {
947
+ if (!this.allowAgentMode) {
948
+ return;
949
+ }
950
+
951
+ const supportAgentMode = await SmartCamera.supportsAgentMode();
952
+
953
+ if (supportAgentMode || this.hasAttribute('show-agent-mode-for-tests')) {
954
+ this.switchCamera.hidden = false;
955
+ if (this.facingMode === 'user') {
956
+ this.shadowRoot.querySelector('video')?.classList?.remove('agent-mode');
957
+ } else {
958
+ this.shadowRoot.querySelector('video')?.classList?.add('agent-mode');
959
+ }
960
+ } else {
961
+ this.switchCamera.hidden = true;
962
+ }
963
+ }
964
+
965
+ get hasAgentSupport() {
966
+ return this.hasAttribute('has-agent-support');
967
+ }
968
+
969
+ get title() {
970
+ return this.getAttribute('title') || 'Submit Front of ID';
971
+ }
972
+
973
+ get hidden() {
974
+ return this.getAttribute('hidden');
975
+ }
976
+
977
+ get cameraError() {
978
+ return this.getAttribute('data-camera-error');
979
+ }
980
+
981
+ get disableImageTests() {
982
+ return this.hasAttribute('disable-image-tests');
983
+ }
984
+
985
+ get allowAgentMode() {
986
+ return this.getAttribute('allow-agent-mode') === 'true';
987
+ }
988
+
989
+ get inAgentMode() {
990
+ return this.facingMode === 'environment';
991
+ }
992
+
993
+ static get observedAttributes() {
994
+ return [
995
+ 'allow-agent-mode',
996
+ 'data-camera-error',
997
+ 'data-camera-ready',
998
+ 'disable-image-tests',
999
+ 'hidden',
1000
+ 'hide-back-to-host',
1001
+ 'show-navigation',
1002
+ 'title',
1003
+ ];
1004
+ }
1005
+
1006
+ attributeChangedCallback(name) {
1007
+ switch (name) {
1008
+ case 'data-camera-error':
1009
+ case 'data-camera-ready':
1010
+ case 'hidden':
1011
+ case 'title':
1012
+ case 'allow-agent-mode':
1013
+ this.shadowRoot.innerHTML = this.render();
1014
+ this.init();
1015
+ break;
1016
+ default:
1017
+ break;
1018
+ }
1019
+ }
1020
+
1021
+ handleBackEvents() {
1022
+ this.stopMedia();
1023
+ this.dispatchEvent(new CustomEvent('selfie-capture.cancelled'));
1024
+ }
1025
+
1026
+ closeWindow() {
1027
+ this.stopMedia();
1028
+ this.dispatchEvent(new CustomEvent('selfie-capture.close'));
1029
+ }
1030
+
1031
+ stopMedia() {
1032
+ this.removeAttribute('data-camera-ready');
1033
+ SmartCamera.stopMedia();
1034
+ }
1035
+ }
1036
+
1037
+ if ('customElements' in window && !customElements.get('selfie-capture')) {
1038
+ window.customElements.define('selfie-capture', SelfieCaptureScreen);
1039
+ }
1040
+
1041
+ export default SelfieCaptureScreen;