@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,760 +1,760 @@
1
- import SmartCamera from '../../../../domain/camera/src/SmartCamera';
2
- import styles from '../../../../styles/src/styles';
3
- import '../../../navigation/src';
4
-
5
- function hasMoreThanNColors(data, n = 16) {
6
- const colors = new Set();
7
- for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
8
- // eslint-disable-next-line no-bitwise
9
- colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
10
- if (colors.size > n) {
11
- return true;
12
- }
13
- }
14
- return false;
15
- }
16
-
17
- function templateString() {
18
- return `
19
- <style>
20
- .visually-hidden {
21
- border: 0;
22
- clip: rect(1px 1px 1px 1px);
23
- clip: rect(1px, 1px, 1px, 1px);
24
- height: auto;
25
- margin: 0;
26
- overflow: hidden;
27
- padding: 0;
28
- position: absolute;
29
- white-space: nowrap;
30
- width: 1px;
31
- }
32
-
33
- .mobile-camera-screen video {
34
- display: block;
35
- object-fit: cover;
36
- object-position: center;
37
- width: 100%;
38
- }
39
-
40
- .id-video.mobile-camera-screen {
41
- display: flex;
42
- align-items: stretch;
43
- justify-content: center;
44
- max-height: 300px;
45
- height: 15rem;
46
- width: 100%;
47
- overflow: visible;
48
- margin: 0 auto;
49
- }
50
-
51
- @media (max-width: 600px) {
52
- .section {
53
- width: 99%;
54
- height: 100vh;
55
- justify-content: center;
56
- }
57
- }
58
-
59
-
60
-
61
- #document-capture-screen,
62
- #back-of-document-capture-screen {
63
- block-size: 45rem;
64
- padding-block: 2rem;
65
- display: flex;
66
- flex-direction: column;
67
- max-block-size: 100%;
68
- max-inline-size: 40ch;
69
- }
70
-
71
- #document-capture-screen header p {
72
- margin-block: 0 !important;
73
- }
74
-
75
- .padding-bottom-2 {
76
- padding-bottom: 2rem;
77
- }
78
- @media (min-width: 600px) {
79
- video {
80
- object-fit: contain;
81
- -webkit-tap-highlight-color: transparent;
82
- content: normal;
83
- }
84
-
85
- .id-video {
86
- width: 99%;
87
- text-align: center;
88
- position: relative;
89
- overflow: hidden;
90
- }
91
-
92
- .id-video-container {
93
- margin: auto;
94
- padding: 0px;
95
- }
96
- }
97
- .id-video-container {
98
- display: flex;
99
- flex-direction: column;
100
- align-items: center;
101
- }
102
- .id-video {
103
- width: 100%;
104
- text-align: center;
105
- position: relative;
106
- background: white;
107
- }
108
- .video-overlay {
109
- position: absolute;
110
- border-style: solid;
111
- border-color: rgba(0, 0, 0, 0.48);
112
- box-sizing: border-box;
113
- inset: 0px;
114
- }
115
-
116
- .video-overlay .inner-border {
117
- position: absolute;
118
- border-width: 0.25rem;
119
- border-color: #9394ab;
120
- border-style: solid;
121
- border-radius: 0.25rem;
122
- inset: -1px;
123
- }
124
- canvas {
125
- border-width: 0.25rem;
126
- border-color: #9394ab;
127
- border-style: solid;
128
- border-radius: 0.25rem;
129
- }
130
-
131
- .description {
132
- align-self: center;
133
- padding-bottom: 1.75rem;
134
- }
135
- .reset-margin-block {
136
- margin-block: 0;
137
- }
138
- .align-items-center {
139
- align-items: center;
140
- }
141
- .id-side {
142
- padding-bottom: 0.5rem;
143
- }
144
-
145
- .circle-progress {
146
- display: flex;
147
- flex-direction: column;
148
- align-items: center;
149
- justify-content: center;
150
- height: 10rem;
151
- }
152
-
153
- .portrait .sticky {
154
- position: -webkit-sticky; /* Safari */
155
- position: sticky;
156
- bottom: 0;
157
- }
158
- .video-footer {
159
- background-color: rgba(255, 255, 255, 0.17);
160
- padding-top: 10px;
161
- }
162
- </style>
163
- ${styles(this.themeColor)}
164
- <div id='document-capture-screen' class='flow center flex-column'>
165
- <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
166
- <h2 class='text-base font-bold title-color'>${this.documentName}</h2>
167
- <div class="circle-progress" id="loader">
168
- ${this.cameraError ? '' : '<p class="spinner"></p>'}
169
- ${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
170
- </div>
171
- <div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' ${this.cameraError ? 'hidden' : ''}>
172
- <div class='id-video-container'>
173
- <div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' hidden>
174
- </div>
175
- <div class='video-footer sticky'>
176
- <h2 class='text-base font-bold title-color reset-margin-block id-side'>${this.title}</h2>
177
- <h4 class='text-base font-normal title-color description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
178
- <div class='actions' hidden>
179
- <button id='capture-id-image' class='button icon-btn | center' type='button'>
180
- <svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70" fill="none" aria-hidden="true" focusable="false">
181
- <path fill-rule="evenodd" clip-rule="evenodd" d="M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70ZM61 35C61 49.3594 49.3594 61 35 61C20.6406 61 9 49.3594 9 35C9 20.6406 20.6406 9 35 9C49.3594 9 61 20.6406 61 35ZM65 35C65 51.5685 51.5685 65 35 65C18.4315 65 5 51.5685 5 35C5 18.4315 18.4315 5 35 5C51.5685 5 65 18.4315 65 35Z" fill="${this.themeColor}"/>
182
- </svg>
183
- <span class='visually-hidden'>Capture Document</span>
184
- </button>
185
- </div>
186
- ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
187
- </div>
188
- </div>
189
- </div>
190
- </div>
191
- `;
192
- }
193
-
194
- const documentCaptureScale = 0.6;
195
-
196
- class DocumentCapture extends HTMLElement {
197
- constructor() {
198
- super();
199
- this.templateString = templateString.bind(this);
200
- this.render = () => this.templateString();
201
-
202
- this.attachShadow({ mode: 'open' });
203
- this.IdSides = {
204
- back: 'Back',
205
- front: 'Front',
206
- };
207
- }
208
-
209
- connectedCallback() {
210
- const template = document.createElement('template');
211
- template.innerHTML = this.render();
212
- this.shadowRoot.innerHTML = '';
213
- this.shadowRoot.appendChild(template.content.cloneNode(true));
214
- this.setUpEventListeners();
215
- }
216
-
217
- async getUserMedia() {
218
- if (SmartCamera.stream) {
219
- return;
220
- }
221
- if (!this.hasAttribute('data-camera-error')) return;
222
-
223
- try {
224
- await SmartCamera.getMedia({
225
- audio: false,
226
- video: {
227
- ...SmartCamera.environmentOptions,
228
- aspectRatio: { ideal: 16 / 9 },
229
- },
230
- });
231
- } catch (error) {
232
- console.error(error.constraint);
233
- console.error(error.message);
234
- }
235
-
236
- this.handleIDStream(SmartCamera.stream);
237
- }
238
-
239
- _captureIDImage() {
240
- const imageDetails = this._drawIDImage();
241
- this._stopIDVideoStream();
242
-
243
- this.dispatchEvent(
244
- new CustomEvent('document-capture.publish', {
245
- detail: {
246
- ...imageDetails,
247
- },
248
- }),
249
- );
250
- }
251
-
252
- _drawIDImage(video = this._IDVideo) {
253
- const canvas = document.createElement('canvas');
254
- if (this.isPortraitCaptureView) {
255
- canvas.width = video.videoWidth;
256
- canvas.height = (canvas.width * 16) / 9;
257
-
258
- const previewCanvas = document.createElement('canvas');
259
- previewCanvas.width = canvas.width;
260
- previewCanvas.height = canvas.height;
261
-
262
- this.updatePortraitId(canvas, video, 1, 1);
263
- this.updatePortraitId(previewCanvas, video);
264
- const image = canvas.toDataURL('image/jpeg');
265
- const previewImage = previewCanvas.toDataURL('image/jpeg');
266
- return {
267
- image,
268
- originalHeight: canvas.height,
269
- originalWidth: canvas.width,
270
- previewImage,
271
- ...this.idCardRegion,
272
- };
273
- }
274
-
275
- canvas.width = 2240;
276
- canvas.height = 1260;
277
-
278
- const height = canvas.width / (video.videoWidth / video.videoHeight);
279
- canvas.height = height;
280
-
281
- const previewCanvas = document.createElement('canvas');
282
- previewCanvas.height = canvas.height;
283
- previewCanvas.width = canvas.width;
284
- const isPortrait = video.videoWidth < video.videoHeight;
285
- if (isPortrait) {
286
- const intermediateCanvas = document.createElement('canvas');
287
- previewCanvas.height = canvas.width / 1.75;
288
- canvas.width = 2240;
289
- canvas.height = canvas.width / 1.77;
290
- this._capturePortraitToLandscapeImage(intermediateCanvas, video);
291
- this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas, 1, 1);
292
- this._drawLandscapeImageFromCanvas(previewCanvas, intermediateCanvas);
293
- } else {
294
- this._drawLandscapeImage(canvas, video, 1, 1);
295
- this._drawLandscapeImage(previewCanvas, video);
296
- }
297
- const image = canvas.toDataURL('image/jpeg');
298
-
299
- const previewImage = previewCanvas.toDataURL('image/jpeg');
300
- return {
301
- image,
302
- originalHeight: canvas.height,
303
- originalWidth: canvas.width,
304
- previewImage,
305
- ...this.idCardRegion,
306
- };
307
- }
308
-
309
- _drawImage(canvas, enableImageTests = true, video = SmartCamera.stream) {
310
- this.resetErrorMessage();
311
- const context = canvas.getContext('2d');
312
-
313
- context.drawImage(
314
- video,
315
- 0,
316
- 0,
317
- video.videoWidth,
318
- video.videoHeight,
319
- 0,
320
- 0,
321
- canvas.width,
322
- canvas.height,
323
- );
324
-
325
- if (enableImageTests) {
326
- const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
327
-
328
- const hasEnoughColors = hasMoreThanNColors(imageData.data);
329
-
330
- if (hasEnoughColors) {
331
- return context;
332
- }
333
- throw new Error(
334
- 'Unable to capture webcam images - Please try another device',
335
- );
336
- } else {
337
- return context;
338
- }
339
- }
340
-
341
- handleIDStream(stream) {
342
- try {
343
- const videoExists = this.shadowRoot.querySelector('canvas');
344
- if (videoExists) {
345
- // remove canvas
346
- videoExists.remove();
347
- }
348
- let video = null;
349
- let canvas = null;
350
- video = document.createElement('video');
351
- canvas = document.createElement('canvas');
352
- const videoContainer = this.shadowRoot.querySelector(
353
- '.id-video-container',
354
- );
355
-
356
- video.muted = true;
357
- video.setAttribute('muted', 'true');
358
-
359
- video.autoplay = true;
360
- video.playsInline = true;
361
- if ('srcObject' in video) {
362
- video.srcObject = stream;
363
- } else {
364
- video.src = window.URL.createObjectURL(stream);
365
- }
366
-
367
- canvas.width = videoContainer.clientWidth;
368
- canvas.height = (videoContainer.clientWidth * 9) / 16;
369
- if (this.isPortraitCaptureView) {
370
- canvas.height = (videoContainer.clientWidth * 16) / 9;
371
- }
372
-
373
- video.onloadedmetadata = () => {
374
- video.play();
375
-
376
- this.shadowRoot.querySelector('#loader').hidden = true;
377
- this.shadowRoot.querySelector('.id-video').hidden = false;
378
- this.shadowRoot.querySelector('.actions').hidden = false;
379
- if (!videoExists) {
380
- videoContainer.prepend(canvas);
381
- }
382
- };
383
-
384
- const onVideoStart = () => {
385
- if (video.paused || video.ended) return;
386
- video.removeEventListener('playing', onVideoStart);
387
- const aspectRatio = video.videoWidth / video.videoHeight;
388
- const portrait = aspectRatio < 1;
389
- if (this.isPortraitCaptureView) {
390
- this.updatePortraitId(canvas, video);
391
- requestAnimationFrame(onVideoStart);
392
- return;
393
- }
394
-
395
- if (portrait) {
396
- videoContainer.classList.add('mobile-camera-screen');
397
- const intermediateCanvas = document.createElement('canvas');
398
- this._capturePortraitToLandscapeImage(intermediateCanvas, video);
399
- this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas);
400
- } else {
401
- this._drawLandscapeImage(canvas, video);
402
- }
403
- requestAnimationFrame(onVideoStart);
404
- };
405
-
406
- video.addEventListener('playing', onVideoStart);
407
-
408
- this._IDStream = stream;
409
- this._IDVideo = video;
410
- } catch (error) {
411
- this.setAttribute(
412
- 'data-camera-error',
413
- SmartCamera.handleCameraError(error),
414
- );
415
- if (error.name !== 'AbortError') {
416
- console.error(error);
417
- }
418
- SmartCamera.stopMedia();
419
- }
420
- }
421
-
422
- _drawLandscapeImage(
423
- canvas,
424
- video = this._IDVideo,
425
- scaleHeight = documentCaptureScale,
426
- scaleWidth = documentCaptureScale,
427
- ) {
428
- const heightScaleFactor = this.height
429
- ? this.height / video.videoHeight
430
- : scaleHeight;
431
- const widthScaleFactor = this.width
432
- ? this.width / video.videoWidth
433
- : scaleWidth;
434
- const scaleHeightOffset = (1 - scaleHeight) / 2;
435
- const scaleWidthOffset = (1 - scaleWidth) / 2;
436
- const width = video.videoWidth * widthScaleFactor;
437
- const height = video.videoHeight * heightScaleFactor;
438
- const startX = video.videoWidth * scaleWidthOffset;
439
- const startY = video.videoHeight * scaleHeightOffset;
440
-
441
- canvas
442
- .getContext('2d')
443
- .drawImage(
444
- video,
445
- startX,
446
- startY,
447
- width,
448
- height,
449
- 0,
450
- 0,
451
- canvas.width,
452
- canvas.height,
453
- );
454
- }
455
-
456
- _capturePortraitToLandscapeImage(canvas, video = this._IDVideo) {
457
- const { videoHeight, videoWidth } = video;
458
- const cropWidth = videoWidth;
459
- const cropHeight = (videoWidth * 9) / 16; // convert to landscape aspect ratio
460
- const startX = 0;
461
- const startY = (videoHeight - cropHeight) / 2;
462
-
463
- canvas.width = cropWidth;
464
- canvas.height = cropHeight;
465
-
466
- canvas
467
- .getContext('2d')
468
- .drawImage(
469
- video,
470
- startX,
471
- startY,
472
- cropWidth,
473
- cropHeight,
474
- 0,
475
- 0,
476
- canvas.width,
477
- canvas.height,
478
- );
479
- }
480
-
481
- _drawLandscapeImageFromCanvas(
482
- canvas,
483
- sourceCanvas,
484
- scaleHeight = documentCaptureScale,
485
- scaleWidth = documentCaptureScale,
486
- ) {
487
- const heightScaleFactor = this.height
488
- ? this.height / sourceCanvas.height
489
- : scaleHeight;
490
- const widthScaleFactor = this.width
491
- ? this.width / sourceCanvas.width
492
- : scaleWidth;
493
- const scaleHeightOffset = (1 - scaleHeight) / 2;
494
- const scaleWidthOffset = (1 - scaleWidth) / 2;
495
- const width = sourceCanvas.width * widthScaleFactor;
496
- const height = sourceCanvas.height * heightScaleFactor;
497
- const startX = sourceCanvas.width * scaleWidthOffset;
498
- const startY = sourceCanvas.height * scaleHeightOffset;
499
-
500
- canvas
501
- .getContext('2d')
502
- .drawImage(
503
- sourceCanvas,
504
- startX,
505
- startY,
506
- width,
507
- height,
508
- 0,
509
- 0,
510
- canvas.width,
511
- canvas.height,
512
- );
513
- }
514
-
515
- _drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
516
- const { videoHeight, videoWidth } = video;
517
- const cropWidth = 600;
518
- const cropHeight = 400;
519
-
520
- canvas.width = cropWidth;
521
- canvas.height = cropHeight;
522
-
523
- const startX = (videoWidth - cropWidth) / 2;
524
- const startY = (videoHeight - cropHeight) / 2;
525
-
526
- canvas
527
- .getContext('2d')
528
- .drawImage(
529
- video,
530
- startX,
531
- startY,
532
- cropWidth,
533
- cropHeight,
534
- 0,
535
- 0,
536
- canvas.width,
537
- canvas.height,
538
- );
539
- }
540
-
541
- updatePortraitId(
542
- destinationCanvas,
543
- video = this._IDVideo,
544
- scaleHeight = documentCaptureScale,
545
- scaleWidth = documentCaptureScale,
546
- ) {
547
- const { videoWidth, videoHeight } = video;
548
-
549
- if (videoWidth && videoHeight) {
550
- const intermediateCanvas = document.createElement('canvas');
551
- const aspectRatio = 9 / 16;
552
- let cropWidth;
553
- let cropHeight;
554
- let offsetX;
555
- let offsetY;
556
-
557
- if (videoWidth / videoHeight > aspectRatio) {
558
- // we scale the canvas to portrait aspect ratio
559
- cropHeight = videoHeight;
560
- cropWidth = cropHeight * aspectRatio;
561
- offsetX = (videoWidth - cropWidth) / 2;
562
- offsetY = 0;
563
- } else {
564
- // video already has portrait aspect ratio
565
- cropWidth = videoWidth;
566
- cropHeight = cropWidth;
567
- offsetX = 0;
568
- offsetY = 0;
569
- }
570
-
571
- intermediateCanvas.height = cropHeight;
572
- intermediateCanvas.width = cropWidth;
573
- // draw the video frame onto the intermediate canvas
574
- intermediateCanvas
575
- .getContext('2d')
576
- .drawImage(
577
- video,
578
- offsetX,
579
- offsetY,
580
- cropWidth,
581
- cropHeight,
582
- 0,
583
- 0,
584
- intermediateCanvas.width,
585
- intermediateCanvas.height,
586
- );
587
-
588
- // draw the intermediate canvas onto the destination canvas
589
- // we scale image based on the scaleHeight and scaleWidth
590
- const heightScaleFactor = this.height
591
- ? this.height / cropWidth
592
- : scaleHeight;
593
- const widthScaleFactor = this.width
594
- ? this.width / cropHeight
595
- : scaleWidth;
596
- const scaleHeightOffset = (1 - scaleHeight) / 2;
597
- const scaleWidthOffset = (1 - scaleWidth) / 2;
598
- const width = cropWidth * widthScaleFactor;
599
- const height = cropHeight * heightScaleFactor;
600
- const startX = cropWidth * scaleWidthOffset;
601
- const startY = cropHeight * scaleHeightOffset;
602
- destinationCanvas
603
- .getContext('2d')
604
- .drawImage(
605
- intermediateCanvas,
606
- startX,
607
- startY,
608
- width,
609
- height,
610
- 0,
611
- 0,
612
- destinationCanvas.width,
613
- destinationCanvas.height,
614
- );
615
- }
616
- }
617
-
618
- _stopIDVideoStream(stream = this._IDStream) {
619
- stream.getTracks().forEach((track) => track.stop());
620
- }
621
-
622
- setUpEventListeners() {
623
- this.captureIDImage = this.shadowRoot.querySelector('#capture-id-image');
624
- this.navigation = this.shadowRoot.querySelector('smileid-navigation');
625
-
626
- if (SmartCamera.stream) {
627
- this.handleIDStream(SmartCamera.stream);
628
- }
629
-
630
- this.navigation.addEventListener('navigation.back', () => {
631
- this.handleBackEvents();
632
- });
633
-
634
- this.navigation.addEventListener('navigation.close', () => {
635
- this.handleCloseEvents();
636
- });
637
-
638
- this.captureIDImage.addEventListener('click', () => {
639
- this._captureIDImage();
640
- });
641
-
642
- this.getUserMedia();
643
- }
644
-
645
- get hideBack() {
646
- return this.hasAttribute('hide-back-to-host');
647
- }
648
-
649
- get showNavigation() {
650
- return this.hasAttribute('show-navigation');
651
- }
652
-
653
- get themeColor() {
654
- return this.getAttribute('theme-color') || '#001096';
655
- }
656
-
657
- get hideAttribution() {
658
- return this.hasAttribute('hide-attribution');
659
- }
660
-
661
- get documentCaptureModes() {
662
- return this.getAttribute('document-capture-modes') || 'camera';
663
- }
664
-
665
- get supportBothCaptureModes() {
666
- const value = this.documentCaptureModes;
667
- return value.includes('camera') && value.includes('upload');
668
- }
669
-
670
- get title() {
671
- return (
672
- this.getAttribute('title') ||
673
- `${this.IdSides[this.sideOfId]} of ${this.documentName}`
674
- );
675
- }
676
-
677
- get height() {
678
- return this.getAttribute('height');
679
- }
680
-
681
- get width() {
682
- return this.getAttribute('width');
683
- }
684
-
685
- get hidden() {
686
- return this.getAttribute('hidden');
687
- }
688
-
689
- get sideOfId() {
690
- return (this.getAttribute('side-of-id') || 'front').toLowerCase();
691
- }
692
-
693
- get isFrontOfId() {
694
- return this.sideOfId === 'front';
695
- }
696
-
697
- get isBackOfId() {
698
- return !this.isFrontOfId;
699
- }
700
-
701
- get documentType() {
702
- return this.getAttribute('document-type') || '';
703
- }
704
-
705
- get documentName() {
706
- return this.getAttribute('document-name') || 'Document';
707
- }
708
-
709
- get isPortraitCaptureView() {
710
- return this.getAttribute('document-type') === 'GREEN_BOOK';
711
- }
712
-
713
- get cameraError() {
714
- return this.getAttribute('data-camera-error');
715
- }
716
-
717
- static get observedAttributes() {
718
- return [
719
- 'data-camera-error',
720
- 'data-camera-ready',
721
- 'document-name',
722
- 'document-type',
723
- 'hidden',
724
- 'hide-back-to-host',
725
- 'show-navigation',
726
- 'title',
727
- ];
728
- }
729
-
730
- attributeChangedCallback(name) {
731
- switch (name) {
732
- case 'data-camera-error':
733
- case 'data-camera-ready':
734
- case 'document-name':
735
- case 'document-type':
736
- case 'hidden':
737
- case 'title':
738
- this.connectedCallback();
739
- break;
740
- default:
741
- break;
742
- }
743
- }
744
-
745
- handleBackEvents() {
746
- this.dispatchEvent(new CustomEvent('document-capture.cancelled'));
747
- SmartCamera.stopMedia();
748
- }
749
-
750
- handleCloseEvents() {
751
- this.dispatchEvent(new CustomEvent('document-capture.close'));
752
- SmartCamera.stopMedia();
753
- }
754
- }
755
-
756
- if ('customElements' in window && !customElements.get('document-capture')) {
757
- window.customElements.define('document-capture', DocumentCapture);
758
- }
759
-
760
- export default DocumentCapture;
1
+ import SmartCamera from '../../../../domain/camera/src/SmartCamera';
2
+ import styles from '../../../../styles/src/styles';
3
+ import '../../../navigation/src';
4
+
5
+ function hasMoreThanNColors(data, n = 16) {
6
+ const colors = new Set();
7
+ for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
8
+ // eslint-disable-next-line no-bitwise
9
+ colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
10
+ if (colors.size > n) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ }
16
+
17
+ function templateString() {
18
+ return `
19
+ <style>
20
+ .visually-hidden {
21
+ border: 0;
22
+ clip: rect(1px 1px 1px 1px);
23
+ clip: rect(1px, 1px, 1px, 1px);
24
+ height: auto;
25
+ margin: 0;
26
+ overflow: hidden;
27
+ padding: 0;
28
+ position: absolute;
29
+ white-space: nowrap;
30
+ width: 1px;
31
+ }
32
+
33
+ .mobile-camera-screen video {
34
+ display: block;
35
+ object-fit: cover;
36
+ object-position: center;
37
+ width: 100%;
38
+ }
39
+
40
+ .id-video.mobile-camera-screen {
41
+ display: flex;
42
+ align-items: stretch;
43
+ justify-content: center;
44
+ max-height: 300px;
45
+ height: 15rem;
46
+ width: 100%;
47
+ overflow: visible;
48
+ margin: 0 auto;
49
+ }
50
+
51
+ @media (max-width: 600px) {
52
+ .section {
53
+ width: 99%;
54
+ height: 100vh;
55
+ justify-content: center;
56
+ }
57
+ }
58
+
59
+
60
+
61
+ #document-capture-screen,
62
+ #back-of-document-capture-screen {
63
+ block-size: 45rem;
64
+ padding-block: 2rem;
65
+ display: flex;
66
+ flex-direction: column;
67
+ max-block-size: 100%;
68
+ max-inline-size: 40ch;
69
+ }
70
+
71
+ #document-capture-screen header p {
72
+ margin-block: 0 !important;
73
+ }
74
+
75
+ .padding-bottom-2 {
76
+ padding-bottom: 2rem;
77
+ }
78
+ @media (min-width: 600px) {
79
+ video {
80
+ object-fit: contain;
81
+ -webkit-tap-highlight-color: transparent;
82
+ content: normal;
83
+ }
84
+
85
+ .id-video {
86
+ width: 99%;
87
+ text-align: center;
88
+ position: relative;
89
+ overflow: hidden;
90
+ }
91
+
92
+ .id-video-container {
93
+ margin: auto;
94
+ padding: 0px;
95
+ }
96
+ }
97
+ .id-video-container {
98
+ display: flex;
99
+ flex-direction: column;
100
+ align-items: center;
101
+ }
102
+ .id-video {
103
+ width: 100%;
104
+ text-align: center;
105
+ position: relative;
106
+ background: white;
107
+ }
108
+ .video-overlay {
109
+ position: absolute;
110
+ border-style: solid;
111
+ border-color: rgba(0, 0, 0, 0.48);
112
+ box-sizing: border-box;
113
+ inset: 0px;
114
+ }
115
+
116
+ .video-overlay .inner-border {
117
+ position: absolute;
118
+ border-width: 0.25rem;
119
+ border-color: #9394ab;
120
+ border-style: solid;
121
+ border-radius: 0.25rem;
122
+ inset: -1px;
123
+ }
124
+ canvas {
125
+ border-width: 0.25rem;
126
+ border-color: #9394ab;
127
+ border-style: solid;
128
+ border-radius: 0.25rem;
129
+ }
130
+
131
+ .description {
132
+ align-self: center;
133
+ padding-bottom: 1.75rem;
134
+ }
135
+ .reset-margin-block {
136
+ margin-block: 0;
137
+ }
138
+ .align-items-center {
139
+ align-items: center;
140
+ }
141
+ .id-side {
142
+ padding-bottom: 0.5rem;
143
+ }
144
+
145
+ .circle-progress {
146
+ display: flex;
147
+ flex-direction: column;
148
+ align-items: center;
149
+ justify-content: center;
150
+ height: 10rem;
151
+ }
152
+
153
+ .portrait .sticky {
154
+ position: -webkit-sticky; /* Safari */
155
+ position: sticky;
156
+ bottom: 0;
157
+ }
158
+ .video-footer {
159
+ background-color: rgba(255, 255, 255, 0.17);
160
+ padding-top: 10px;
161
+ }
162
+ </style>
163
+ ${styles(this.themeColor)}
164
+ <div id='document-capture-screen' class='flow center flex-column'>
165
+ <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
166
+ <h2 class='text-base font-bold title-color'>${this.documentName}</h2>
167
+ <div class="circle-progress" id="loader">
168
+ ${this.cameraError ? '' : '<p class="spinner"></p>'}
169
+ ${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
170
+ </div>
171
+ <div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' ${this.cameraError ? 'hidden' : ''}>
172
+ <div class='id-video-container'>
173
+ <div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' hidden>
174
+ </div>
175
+ <div class='video-footer sticky'>
176
+ <h2 class='text-base font-bold title-color reset-margin-block id-side'>${this.title}</h2>
177
+ <h4 class='text-base font-normal title-color description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
178
+ <div class='actions' hidden>
179
+ <button id='capture-id-image' class='button icon-btn | center' type='button'>
180
+ <svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70" fill="none" aria-hidden="true" focusable="false">
181
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70ZM61 35C61 49.3594 49.3594 61 35 61C20.6406 61 9 49.3594 9 35C9 20.6406 20.6406 9 35 9C49.3594 9 61 20.6406 61 35ZM65 35C65 51.5685 51.5685 65 35 65C18.4315 65 5 51.5685 5 35C5 18.4315 18.4315 5 35 5C51.5685 5 65 18.4315 65 35Z" fill="${this.themeColor}"/>
182
+ </svg>
183
+ <span class='visually-hidden'>Capture Document</span>
184
+ </button>
185
+ </div>
186
+ ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ `;
192
+ }
193
+
194
+ const documentCaptureScale = 0.6;
195
+
196
+ class DocumentCapture extends HTMLElement {
197
+ constructor() {
198
+ super();
199
+ this.templateString = templateString.bind(this);
200
+ this.render = () => this.templateString();
201
+
202
+ this.attachShadow({ mode: 'open' });
203
+ this.IdSides = {
204
+ back: 'Back',
205
+ front: 'Front',
206
+ };
207
+ }
208
+
209
+ connectedCallback() {
210
+ const template = document.createElement('template');
211
+ template.innerHTML = this.render();
212
+ this.shadowRoot.innerHTML = '';
213
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
214
+ this.setUpEventListeners();
215
+ }
216
+
217
+ async getUserMedia() {
218
+ if (SmartCamera.stream) {
219
+ return;
220
+ }
221
+ if (!this.hasAttribute('data-camera-error')) return;
222
+
223
+ try {
224
+ await SmartCamera.getMedia({
225
+ audio: false,
226
+ video: {
227
+ ...SmartCamera.environmentOptions,
228
+ aspectRatio: { ideal: 16 / 9 },
229
+ },
230
+ });
231
+ } catch (error) {
232
+ console.error(error.constraint);
233
+ console.error(error.message);
234
+ }
235
+
236
+ this.handleIDStream(SmartCamera.stream);
237
+ }
238
+
239
+ _captureIDImage() {
240
+ const imageDetails = this._drawIDImage();
241
+ this._stopIDVideoStream();
242
+
243
+ this.dispatchEvent(
244
+ new CustomEvent('document-capture.publish', {
245
+ detail: {
246
+ ...imageDetails,
247
+ },
248
+ }),
249
+ );
250
+ }
251
+
252
+ _drawIDImage(video = this._IDVideo) {
253
+ const canvas = document.createElement('canvas');
254
+ if (this.isPortraitCaptureView) {
255
+ canvas.width = video.videoWidth;
256
+ canvas.height = (canvas.width * 16) / 9;
257
+
258
+ const previewCanvas = document.createElement('canvas');
259
+ previewCanvas.width = canvas.width;
260
+ previewCanvas.height = canvas.height;
261
+
262
+ this.updatePortraitId(canvas, video, 1, 1);
263
+ this.updatePortraitId(previewCanvas, video);
264
+ const image = canvas.toDataURL('image/jpeg');
265
+ const previewImage = previewCanvas.toDataURL('image/jpeg');
266
+ return {
267
+ image,
268
+ originalHeight: canvas.height,
269
+ originalWidth: canvas.width,
270
+ previewImage,
271
+ ...this.idCardRegion,
272
+ };
273
+ }
274
+
275
+ canvas.width = 2240;
276
+ canvas.height = 1260;
277
+
278
+ const height = canvas.width / (video.videoWidth / video.videoHeight);
279
+ canvas.height = height;
280
+
281
+ const previewCanvas = document.createElement('canvas');
282
+ previewCanvas.height = canvas.height;
283
+ previewCanvas.width = canvas.width;
284
+ const isPortrait = video.videoWidth < video.videoHeight;
285
+ if (isPortrait) {
286
+ const intermediateCanvas = document.createElement('canvas');
287
+ previewCanvas.height = canvas.width / 1.75;
288
+ canvas.width = 2240;
289
+ canvas.height = canvas.width / 1.77;
290
+ this._capturePortraitToLandscapeImage(intermediateCanvas, video);
291
+ this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas, 1, 1);
292
+ this._drawLandscapeImageFromCanvas(previewCanvas, intermediateCanvas);
293
+ } else {
294
+ this._drawLandscapeImage(canvas, video, 1, 1);
295
+ this._drawLandscapeImage(previewCanvas, video);
296
+ }
297
+ const image = canvas.toDataURL('image/jpeg');
298
+
299
+ const previewImage = previewCanvas.toDataURL('image/jpeg');
300
+ return {
301
+ image,
302
+ originalHeight: canvas.height,
303
+ originalWidth: canvas.width,
304
+ previewImage,
305
+ ...this.idCardRegion,
306
+ };
307
+ }
308
+
309
+ _drawImage(canvas, enableImageTests = true, video = SmartCamera.stream) {
310
+ this.resetErrorMessage();
311
+ const context = canvas.getContext('2d');
312
+
313
+ context.drawImage(
314
+ video,
315
+ 0,
316
+ 0,
317
+ video.videoWidth,
318
+ video.videoHeight,
319
+ 0,
320
+ 0,
321
+ canvas.width,
322
+ canvas.height,
323
+ );
324
+
325
+ if (enableImageTests) {
326
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
327
+
328
+ const hasEnoughColors = hasMoreThanNColors(imageData.data);
329
+
330
+ if (hasEnoughColors) {
331
+ return context;
332
+ }
333
+ throw new Error(
334
+ 'Unable to capture webcam images - Please try another device',
335
+ );
336
+ } else {
337
+ return context;
338
+ }
339
+ }
340
+
341
+ handleIDStream(stream) {
342
+ try {
343
+ const videoExists = this.shadowRoot.querySelector('canvas');
344
+ if (videoExists) {
345
+ // remove canvas
346
+ videoExists.remove();
347
+ }
348
+ let video = null;
349
+ let canvas = null;
350
+ video = document.createElement('video');
351
+ canvas = document.createElement('canvas');
352
+ const videoContainer = this.shadowRoot.querySelector(
353
+ '.id-video-container',
354
+ );
355
+
356
+ video.muted = true;
357
+ video.setAttribute('muted', 'true');
358
+
359
+ video.autoplay = true;
360
+ video.playsInline = true;
361
+ if ('srcObject' in video) {
362
+ video.srcObject = stream;
363
+ } else {
364
+ video.src = window.URL.createObjectURL(stream);
365
+ }
366
+
367
+ canvas.width = videoContainer.clientWidth;
368
+ canvas.height = (videoContainer.clientWidth * 9) / 16;
369
+ if (this.isPortraitCaptureView) {
370
+ canvas.height = (videoContainer.clientWidth * 16) / 9;
371
+ }
372
+
373
+ video.onloadedmetadata = () => {
374
+ video.play();
375
+
376
+ this.shadowRoot.querySelector('#loader').hidden = true;
377
+ this.shadowRoot.querySelector('.id-video').hidden = false;
378
+ this.shadowRoot.querySelector('.actions').hidden = false;
379
+ if (!videoExists) {
380
+ videoContainer.prepend(canvas);
381
+ }
382
+ };
383
+
384
+ const onVideoStart = () => {
385
+ if (video.paused || video.ended) return;
386
+ video.removeEventListener('playing', onVideoStart);
387
+ const aspectRatio = video.videoWidth / video.videoHeight;
388
+ const portrait = aspectRatio < 1;
389
+ if (this.isPortraitCaptureView) {
390
+ this.updatePortraitId(canvas, video);
391
+ requestAnimationFrame(onVideoStart);
392
+ return;
393
+ }
394
+
395
+ if (portrait) {
396
+ videoContainer.classList.add('mobile-camera-screen');
397
+ const intermediateCanvas = document.createElement('canvas');
398
+ this._capturePortraitToLandscapeImage(intermediateCanvas, video);
399
+ this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas);
400
+ } else {
401
+ this._drawLandscapeImage(canvas, video);
402
+ }
403
+ requestAnimationFrame(onVideoStart);
404
+ };
405
+
406
+ video.addEventListener('playing', onVideoStart);
407
+
408
+ this._IDStream = stream;
409
+ this._IDVideo = video;
410
+ } catch (error) {
411
+ this.setAttribute(
412
+ 'data-camera-error',
413
+ SmartCamera.handleCameraError(error),
414
+ );
415
+ if (error.name !== 'AbortError') {
416
+ console.error(error);
417
+ }
418
+ SmartCamera.stopMedia();
419
+ }
420
+ }
421
+
422
+ _drawLandscapeImage(
423
+ canvas,
424
+ video = this._IDVideo,
425
+ scaleHeight = documentCaptureScale,
426
+ scaleWidth = documentCaptureScale,
427
+ ) {
428
+ const heightScaleFactor = this.height
429
+ ? this.height / video.videoHeight
430
+ : scaleHeight;
431
+ const widthScaleFactor = this.width
432
+ ? this.width / video.videoWidth
433
+ : scaleWidth;
434
+ const scaleHeightOffset = (1 - scaleHeight) / 2;
435
+ const scaleWidthOffset = (1 - scaleWidth) / 2;
436
+ const width = video.videoWidth * widthScaleFactor;
437
+ const height = video.videoHeight * heightScaleFactor;
438
+ const startX = video.videoWidth * scaleWidthOffset;
439
+ const startY = video.videoHeight * scaleHeightOffset;
440
+
441
+ canvas
442
+ .getContext('2d')
443
+ .drawImage(
444
+ video,
445
+ startX,
446
+ startY,
447
+ width,
448
+ height,
449
+ 0,
450
+ 0,
451
+ canvas.width,
452
+ canvas.height,
453
+ );
454
+ }
455
+
456
+ _capturePortraitToLandscapeImage(canvas, video = this._IDVideo) {
457
+ const { videoHeight, videoWidth } = video;
458
+ const cropWidth = videoWidth;
459
+ const cropHeight = (videoWidth * 9) / 16; // convert to landscape aspect ratio
460
+ const startX = 0;
461
+ const startY = (videoHeight - cropHeight) / 2;
462
+
463
+ canvas.width = cropWidth;
464
+ canvas.height = cropHeight;
465
+
466
+ canvas
467
+ .getContext('2d')
468
+ .drawImage(
469
+ video,
470
+ startX,
471
+ startY,
472
+ cropWidth,
473
+ cropHeight,
474
+ 0,
475
+ 0,
476
+ canvas.width,
477
+ canvas.height,
478
+ );
479
+ }
480
+
481
+ _drawLandscapeImageFromCanvas(
482
+ canvas,
483
+ sourceCanvas,
484
+ scaleHeight = documentCaptureScale,
485
+ scaleWidth = documentCaptureScale,
486
+ ) {
487
+ const heightScaleFactor = this.height
488
+ ? this.height / sourceCanvas.height
489
+ : scaleHeight;
490
+ const widthScaleFactor = this.width
491
+ ? this.width / sourceCanvas.width
492
+ : scaleWidth;
493
+ const scaleHeightOffset = (1 - scaleHeight) / 2;
494
+ const scaleWidthOffset = (1 - scaleWidth) / 2;
495
+ const width = sourceCanvas.width * widthScaleFactor;
496
+ const height = sourceCanvas.height * heightScaleFactor;
497
+ const startX = sourceCanvas.width * scaleWidthOffset;
498
+ const startY = sourceCanvas.height * scaleHeightOffset;
499
+
500
+ canvas
501
+ .getContext('2d')
502
+ .drawImage(
503
+ sourceCanvas,
504
+ startX,
505
+ startY,
506
+ width,
507
+ height,
508
+ 0,
509
+ 0,
510
+ canvas.width,
511
+ canvas.height,
512
+ );
513
+ }
514
+
515
+ _drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
516
+ const { videoHeight, videoWidth } = video;
517
+ const cropWidth = 600;
518
+ const cropHeight = 400;
519
+
520
+ canvas.width = cropWidth;
521
+ canvas.height = cropHeight;
522
+
523
+ const startX = (videoWidth - cropWidth) / 2;
524
+ const startY = (videoHeight - cropHeight) / 2;
525
+
526
+ canvas
527
+ .getContext('2d')
528
+ .drawImage(
529
+ video,
530
+ startX,
531
+ startY,
532
+ cropWidth,
533
+ cropHeight,
534
+ 0,
535
+ 0,
536
+ canvas.width,
537
+ canvas.height,
538
+ );
539
+ }
540
+
541
+ updatePortraitId(
542
+ destinationCanvas,
543
+ video = this._IDVideo,
544
+ scaleHeight = documentCaptureScale,
545
+ scaleWidth = documentCaptureScale,
546
+ ) {
547
+ const { videoWidth, videoHeight } = video;
548
+
549
+ if (videoWidth && videoHeight) {
550
+ const intermediateCanvas = document.createElement('canvas');
551
+ const aspectRatio = 9 / 16;
552
+ let cropWidth;
553
+ let cropHeight;
554
+ let offsetX;
555
+ let offsetY;
556
+
557
+ if (videoWidth / videoHeight > aspectRatio) {
558
+ // we scale the canvas to portrait aspect ratio
559
+ cropHeight = videoHeight;
560
+ cropWidth = cropHeight * aspectRatio;
561
+ offsetX = (videoWidth - cropWidth) / 2;
562
+ offsetY = 0;
563
+ } else {
564
+ // video already has portrait aspect ratio
565
+ cropWidth = videoWidth;
566
+ cropHeight = cropWidth;
567
+ offsetX = 0;
568
+ offsetY = 0;
569
+ }
570
+
571
+ intermediateCanvas.height = cropHeight;
572
+ intermediateCanvas.width = cropWidth;
573
+ // draw the video frame onto the intermediate canvas
574
+ intermediateCanvas
575
+ .getContext('2d')
576
+ .drawImage(
577
+ video,
578
+ offsetX,
579
+ offsetY,
580
+ cropWidth,
581
+ cropHeight,
582
+ 0,
583
+ 0,
584
+ intermediateCanvas.width,
585
+ intermediateCanvas.height,
586
+ );
587
+
588
+ // draw the intermediate canvas onto the destination canvas
589
+ // we scale image based on the scaleHeight and scaleWidth
590
+ const heightScaleFactor = this.height
591
+ ? this.height / cropWidth
592
+ : scaleHeight;
593
+ const widthScaleFactor = this.width
594
+ ? this.width / cropHeight
595
+ : scaleWidth;
596
+ const scaleHeightOffset = (1 - scaleHeight) / 2;
597
+ const scaleWidthOffset = (1 - scaleWidth) / 2;
598
+ const width = cropWidth * widthScaleFactor;
599
+ const height = cropHeight * heightScaleFactor;
600
+ const startX = cropWidth * scaleWidthOffset;
601
+ const startY = cropHeight * scaleHeightOffset;
602
+ destinationCanvas
603
+ .getContext('2d')
604
+ .drawImage(
605
+ intermediateCanvas,
606
+ startX,
607
+ startY,
608
+ width,
609
+ height,
610
+ 0,
611
+ 0,
612
+ destinationCanvas.width,
613
+ destinationCanvas.height,
614
+ );
615
+ }
616
+ }
617
+
618
+ _stopIDVideoStream(stream = this._IDStream) {
619
+ stream.getTracks().forEach((track) => track.stop());
620
+ }
621
+
622
+ setUpEventListeners() {
623
+ this.captureIDImage = this.shadowRoot.querySelector('#capture-id-image');
624
+ this.navigation = this.shadowRoot.querySelector('smileid-navigation');
625
+
626
+ if (SmartCamera.stream) {
627
+ this.handleIDStream(SmartCamera.stream);
628
+ }
629
+
630
+ this.navigation.addEventListener('navigation.back', () => {
631
+ this.handleBackEvents();
632
+ });
633
+
634
+ this.navigation.addEventListener('navigation.close', () => {
635
+ this.handleCloseEvents();
636
+ });
637
+
638
+ this.captureIDImage.addEventListener('click', () => {
639
+ this._captureIDImage();
640
+ });
641
+
642
+ this.getUserMedia();
643
+ }
644
+
645
+ get hideBack() {
646
+ return this.hasAttribute('hide-back-to-host');
647
+ }
648
+
649
+ get showNavigation() {
650
+ return this.hasAttribute('show-navigation');
651
+ }
652
+
653
+ get themeColor() {
654
+ return this.getAttribute('theme-color') || '#001096';
655
+ }
656
+
657
+ get hideAttribution() {
658
+ return this.hasAttribute('hide-attribution');
659
+ }
660
+
661
+ get documentCaptureModes() {
662
+ return this.getAttribute('document-capture-modes') || 'camera';
663
+ }
664
+
665
+ get supportBothCaptureModes() {
666
+ const value = this.documentCaptureModes;
667
+ return value.includes('camera') && value.includes('upload');
668
+ }
669
+
670
+ get title() {
671
+ return (
672
+ this.getAttribute('title') ||
673
+ `${this.IdSides[this.sideOfId]} of ${this.documentName}`
674
+ );
675
+ }
676
+
677
+ get height() {
678
+ return this.getAttribute('height');
679
+ }
680
+
681
+ get width() {
682
+ return this.getAttribute('width');
683
+ }
684
+
685
+ get hidden() {
686
+ return this.getAttribute('hidden');
687
+ }
688
+
689
+ get sideOfId() {
690
+ return (this.getAttribute('side-of-id') || 'front').toLowerCase();
691
+ }
692
+
693
+ get isFrontOfId() {
694
+ return this.sideOfId === 'front';
695
+ }
696
+
697
+ get isBackOfId() {
698
+ return !this.isFrontOfId;
699
+ }
700
+
701
+ get documentType() {
702
+ return this.getAttribute('document-type') || '';
703
+ }
704
+
705
+ get documentName() {
706
+ return this.getAttribute('document-name') || 'Document';
707
+ }
708
+
709
+ get isPortraitCaptureView() {
710
+ return this.getAttribute('document-type') === 'GREEN_BOOK';
711
+ }
712
+
713
+ get cameraError() {
714
+ return this.getAttribute('data-camera-error');
715
+ }
716
+
717
+ static get observedAttributes() {
718
+ return [
719
+ 'data-camera-error',
720
+ 'data-camera-ready',
721
+ 'document-name',
722
+ 'document-type',
723
+ 'hidden',
724
+ 'hide-back-to-host',
725
+ 'show-navigation',
726
+ 'title',
727
+ ];
728
+ }
729
+
730
+ attributeChangedCallback(name) {
731
+ switch (name) {
732
+ case 'data-camera-error':
733
+ case 'data-camera-ready':
734
+ case 'document-name':
735
+ case 'document-type':
736
+ case 'hidden':
737
+ case 'title':
738
+ this.connectedCallback();
739
+ break;
740
+ default:
741
+ break;
742
+ }
743
+ }
744
+
745
+ handleBackEvents() {
746
+ this.dispatchEvent(new CustomEvent('document-capture.cancelled'));
747
+ SmartCamera.stopMedia();
748
+ }
749
+
750
+ handleCloseEvents() {
751
+ this.dispatchEvent(new CustomEvent('document-capture.close'));
752
+ SmartCamera.stopMedia();
753
+ }
754
+ }
755
+
756
+ if ('customElements' in window && !customElements.get('document-capture')) {
757
+ window.customElements.define('document-capture', DocumentCapture);
758
+ }
759
+
760
+ export default DocumentCapture;