@smileid/web-components 1.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +72 -0
- package/components/README.md +14 -0
- package/components/attribution/PoweredBySmileId.js +42 -0
- package/components/camera-permission/CameraPermission.js +136 -0
- package/components/camera-permission/CameraPermission.stories.js +21 -0
- package/components/combobox/src/Combobox.js +586 -0
- package/components/combobox/src/index.js +1 -0
- package/components/document/src/DocumentCaptureScreens.js +317 -0
- package/components/document/src/DocumentCaptureScreens.stories.js +51 -0
- package/components/document/src/README.md +108 -0
- package/components/document/src/document-capture/DocumentCapture.js +677 -0
- package/components/document/src/document-capture/DocumentCapture.stories.js +71 -0
- package/components/document/src/document-capture/README.md +89 -0
- package/components/document/src/document-capture/index.js +3 -0
- package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +499 -0
- package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +17 -0
- package/components/document/src/document-capture-instructions/README.md +56 -0
- package/components/document/src/document-capture-instructions/index.js +3 -0
- package/components/document/src/document-capture-review/DocumentCaptureReview.js +378 -0
- package/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +14 -0
- package/components/document/src/document-capture-review/README.md +79 -0
- package/components/document/src/document-capture-review/index.js +3 -0
- package/components/document/src/index.js +3 -0
- package/components/end-user-consent/src/EndUserConsent.js +809 -0
- package/components/end-user-consent/src/EndUserConsent.stories.js +23 -0
- package/components/end-user-consent/src/index.js +4 -0
- package/components/navigation/src/Navigation.js +160 -0
- package/components/navigation/src/Navigation.stories.js +24 -0
- package/components/navigation/src/index.js +3 -0
- package/components/selfie/README.md +201 -0
- package/components/selfie/src/SelfieCaptureScreens.js +224 -0
- package/components/selfie/src/SelfieCaptureScreens.stories.js +21 -0
- package/components/selfie/src/index.js +5 -0
- package/components/selfie/src/selfie-capture/SelfieCapture.js +878 -0
- package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +23 -0
- package/components/selfie/src/selfie-capture/index.js +3 -0
- package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +638 -0
- package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +17 -0
- package/components/selfie/src/selfie-capture-instructions/index.js +3 -0
- package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +348 -0
- package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +17 -0
- package/components/selfie/src/selfie-capture-review/index.js +3 -0
- package/components/signature-pad/package.json +30 -0
- package/components/signature-pad/src/SignaturePad.js +477 -0
- package/components/signature-pad/src/SignaturePad.stories.js +24 -0
- package/components/signature-pad/src/index.js +3 -0
- package/components/smart-camera-web/src/SmartCameraWeb.js +246 -0
- package/components/smart-camera-web/src/SmartCameraWeb.stories.js +35 -0
- package/components/totp-consent/src/TotpConsent.js +935 -0
- package/components/totp-consent/src/index.js +4 -0
- package/cypress/e2e/smart-camera-web-attribution.cy.js +21 -0
- package/cypress/e2e/smart-camera-web-back-press.cy.js +481 -0
- package/cypress/e2e/smart-camera-web-hide-instructions.cy.js +334 -0
- package/cypress/e2e/smart-camera-web.cy.js +309 -0
- package/cypress/fixtures/example.json +5 -0
- package/cypress/pages/capture-back-of-id-hide-attribution.html +44 -0
- package/cypress/pages/capture-back-of-id-navigation.html +72 -0
- package/cypress/pages/smart-camera-web-hide-instructions.html +38 -0
- package/cypress/pages/smart-camera-web.html +38 -0
- package/cypress/support/commands.js +144 -0
- package/cypress/support/e2e.js +20 -0
- package/cypress.config.js +11 -0
- package/domain/camera/src/README.md +38 -0
- package/domain/camera/src/SmartCamera.js +81 -0
- package/domain/constants/src/Constants.js +27 -0
- package/domain/file-upload/README.md +35 -0
- package/domain/file-upload/src/SmartFileUpload.js +65 -0
- package/esbuild.js +119 -0
- package/index.js +5 -0
- package/package.json +46 -0
- package/styles/README.md +3 -0
- package/styles/src/styles.js +348 -0
- package/styles/src/typography.js +52 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import SmartCamera from '../../../../domain/camera/src/SmartCamera';
|
|
2
|
+
import styles from '../../../../styles/src/styles';
|
|
3
|
+
import {
|
|
4
|
+
PORTRAIT_ID_PREVIEW_HEIGHT,
|
|
5
|
+
PORTRAIT_ID_PREVIEW_WIDTH,
|
|
6
|
+
} from '../../../../domain/constants/src/Constants';
|
|
7
|
+
import '../../../navigation/src';
|
|
8
|
+
|
|
9
|
+
function hasMoreThanNColors(data, n = 16) {
|
|
10
|
+
const colors = new Set();
|
|
11
|
+
for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
|
|
12
|
+
// eslint-disable-next-line no-bitwise
|
|
13
|
+
colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
|
|
14
|
+
if (colors.size > n) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function templateString() {
|
|
22
|
+
return `
|
|
23
|
+
${styles}
|
|
24
|
+
<style>
|
|
25
|
+
.visually-hidden {
|
|
26
|
+
border: 0;
|
|
27
|
+
clip: rect(1px 1px 1px 1px);
|
|
28
|
+
clip: rect(1px, 1px, 1px, 1px);
|
|
29
|
+
height: auto;
|
|
30
|
+
margin: 0;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
padding: 0;
|
|
33
|
+
position: absolute;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
width: 1px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.mobile-camera-screen video {
|
|
39
|
+
display: block;
|
|
40
|
+
object-fit: cover;
|
|
41
|
+
object-position: center;
|
|
42
|
+
width: 100%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.id-video.mobile-camera-screen {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: stretch;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
max-height: 200px;
|
|
50
|
+
height: 10rem;
|
|
51
|
+
width: 100%;
|
|
52
|
+
overflow: visible;
|
|
53
|
+
margin: 0 auto;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media (max-width: 600px) {
|
|
57
|
+
.section {
|
|
58
|
+
width: 100%;
|
|
59
|
+
height: 100vh;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
#document-capture-screen,
|
|
67
|
+
#back-of-document-capture-screen {
|
|
68
|
+
block-size: 45rem;
|
|
69
|
+
padding-block: 2rem;
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
max-block-size: 100%;
|
|
73
|
+
max-inline-size: 40ch;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#document-capture-screen header p {
|
|
77
|
+
margin-block: 0 !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.padding-bottom-2 {
|
|
81
|
+
padding-bottom: 2rem;
|
|
82
|
+
}
|
|
83
|
+
@media (min-width: 600px) {
|
|
84
|
+
video {
|
|
85
|
+
object-fit: contain;
|
|
86
|
+
-webkit-tap-highlight-color: transparent;
|
|
87
|
+
content: normal;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.id-video {
|
|
91
|
+
width: 100%;
|
|
92
|
+
text-align: center;
|
|
93
|
+
position: relative;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
}
|
|
96
|
+
.video-overlay {
|
|
97
|
+
position: absolute;
|
|
98
|
+
border-style: solid;
|
|
99
|
+
border-color: rgba(0, 0, 0, 0.48);
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
inset: -1px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.id-video-container {
|
|
105
|
+
margin: auto;
|
|
106
|
+
padding: 0px;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.id-video {
|
|
111
|
+
width: 100%;
|
|
112
|
+
text-align: center;
|
|
113
|
+
position: relative;
|
|
114
|
+
background: white;
|
|
115
|
+
}
|
|
116
|
+
.video-overlay {
|
|
117
|
+
position: absolute;
|
|
118
|
+
border-style: solid;
|
|
119
|
+
border-color: rgba(0, 0, 0, 0.48);
|
|
120
|
+
box-sizing: border-box;
|
|
121
|
+
inset: 0px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.video-overlay .inner-border {
|
|
125
|
+
position: absolute;
|
|
126
|
+
border-width: 0.25rem;
|
|
127
|
+
border-color: #9394ab;
|
|
128
|
+
border-style: solid;
|
|
129
|
+
border-radius: 0.25rem;
|
|
130
|
+
inset: -1px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.description {
|
|
134
|
+
align-self: center;
|
|
135
|
+
padding-bottom: 1.75rem;
|
|
136
|
+
}
|
|
137
|
+
.reset-margin-block {
|
|
138
|
+
margin-block: 0;
|
|
139
|
+
}
|
|
140
|
+
.align-items-center {
|
|
141
|
+
align-items: center;
|
|
142
|
+
}
|
|
143
|
+
.id-side {
|
|
144
|
+
padding-bottom: 0.5rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.circle-progress {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
height: 10rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.portrait .sticky {
|
|
156
|
+
position: -webkit-sticky; /* Safari */
|
|
157
|
+
position: sticky;
|
|
158
|
+
bottom: 0;
|
|
159
|
+
}
|
|
160
|
+
.video-footer {
|
|
161
|
+
background-color: rgba(255, 255, 255, 0.17);
|
|
162
|
+
padding-top: 10px;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
165
|
+
<div id='document-capture-screen' class='flow center flex-column'>
|
|
166
|
+
<smileid-navigation ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
|
|
167
|
+
<h2 class='text-base font-bold color-digital-blue'>${this.documentName}</h2>
|
|
168
|
+
<div class="circle-progress" id="loader">
|
|
169
|
+
${this.cameraError ? '' : '<p class="spinner"></p>'}
|
|
170
|
+
${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
|
|
171
|
+
</div>
|
|
172
|
+
<div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}'>
|
|
173
|
+
<div class='id-video-container'>
|
|
174
|
+
<div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' >
|
|
175
|
+
</div>
|
|
176
|
+
<div class='video-footer sticky'>
|
|
177
|
+
<h2 class='text-base font-bold color-digital-blue reset-margin-block id-side'>${this.title}</h2>
|
|
178
|
+
<h4 class='text-base font-normal color-digital-blue description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
|
|
179
|
+
<div class='actions' hidden>
|
|
180
|
+
<button id='capture-id-image' class='button icon-btn | center' type='button'>
|
|
181
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70" fill="none" aria-hidden="true" focusable="false">
|
|
182
|
+
<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="#001096"/>
|
|
183
|
+
</svg>
|
|
184
|
+
<span class='visually-hidden'>Capture Document</span>
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const fixedAspectRatio = 1.53;
|
|
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: SmartCamera.environmentOptions,
|
|
227
|
+
});
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error(error.constraint);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.handleIDStream(SmartCamera.stream);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_captureIDImage() {
|
|
236
|
+
const imageDetails = this._drawIDImage();
|
|
237
|
+
this._stopIDVideoStream();
|
|
238
|
+
|
|
239
|
+
this.dispatchEvent(
|
|
240
|
+
new CustomEvent('document-capture.publish', {
|
|
241
|
+
detail: {
|
|
242
|
+
...imageDetails,
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_drawIDImage(video = this._IDVideo) {
|
|
249
|
+
const canvas = document.createElement('canvas');
|
|
250
|
+
if (this.isPortraitCaptureView) {
|
|
251
|
+
canvas.width = video.videoWidth;
|
|
252
|
+
canvas.height = video.videoHeight;
|
|
253
|
+
|
|
254
|
+
// Draw the video frame onto the canvas
|
|
255
|
+
const ctx = canvas.getContext('2d');
|
|
256
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
257
|
+
|
|
258
|
+
// Get the dimensions of the video preview frame
|
|
259
|
+
const previewWidth = PORTRAIT_ID_PREVIEW_WIDTH;
|
|
260
|
+
const previewHeight = PORTRAIT_ID_PREVIEW_HEIGHT;
|
|
261
|
+
|
|
262
|
+
// Define the padding value
|
|
263
|
+
const paddingPercent = 1.1; // 110% of the preview dimensions because we previously scaled the image, old value was 50%;
|
|
264
|
+
const paddedWidth = previewWidth * (1 + paddingPercent);
|
|
265
|
+
const paddedHeight = previewHeight * (1 + paddingPercent);
|
|
266
|
+
|
|
267
|
+
// Calculate the dimensions of the cropped image based on the padded preview frame dimensions
|
|
268
|
+
const cropWidth = paddedWidth;
|
|
269
|
+
const cropHeight = paddedHeight;
|
|
270
|
+
const cropLeft = (canvas.width - cropWidth) / 2;
|
|
271
|
+
const cropTop = (canvas.height - cropHeight) / 2;
|
|
272
|
+
|
|
273
|
+
// Create a new canvas element for the cropped image
|
|
274
|
+
const croppedCanvas = document.createElement('canvas');
|
|
275
|
+
croppedCanvas.width = cropWidth;
|
|
276
|
+
croppedCanvas.height = cropHeight;
|
|
277
|
+
|
|
278
|
+
// Draw the cropped image onto the new canvas
|
|
279
|
+
const croppedCtx = croppedCanvas.getContext('2d');
|
|
280
|
+
croppedCtx.drawImage(
|
|
281
|
+
canvas,
|
|
282
|
+
cropLeft,
|
|
283
|
+
cropTop,
|
|
284
|
+
cropWidth,
|
|
285
|
+
cropHeight,
|
|
286
|
+
0,
|
|
287
|
+
0,
|
|
288
|
+
cropWidth,
|
|
289
|
+
cropHeight,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const image = croppedCanvas.toDataURL('image/jpeg');
|
|
293
|
+
console.warn('this.idCardRegion', this.idCardRegion);
|
|
294
|
+
|
|
295
|
+
const videoContainer = this.shadowRoot.querySelector(
|
|
296
|
+
'.id-video-container',
|
|
297
|
+
);
|
|
298
|
+
const oldCroppedImage = videoContainer.querySelector(
|
|
299
|
+
'image#preview-cropped-image',
|
|
300
|
+
);
|
|
301
|
+
if (oldCroppedImage) {
|
|
302
|
+
videoContainer.removeChild(oldCroppedImage);
|
|
303
|
+
}
|
|
304
|
+
const croppedImage = document.createElement('img');
|
|
305
|
+
croppedImage.id = 'preview-cropped-image';
|
|
306
|
+
croppedImage.src = image;
|
|
307
|
+
videoContainer.appendChild(croppedImage);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
image: croppedCanvas.toDataURL('image/jpeg'),
|
|
311
|
+
originalHeight: canvas.height,
|
|
312
|
+
originalWidth: canvas.width,
|
|
313
|
+
...this.idCardRegion,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
canvas.width = 2240;
|
|
318
|
+
canvas.height = 1260;
|
|
319
|
+
|
|
320
|
+
const context = canvas.getContext('2d');
|
|
321
|
+
|
|
322
|
+
const { aspectRatio } = this._calculateVideoOffset(video);
|
|
323
|
+
|
|
324
|
+
if (aspectRatio < 1) {
|
|
325
|
+
canvas.width = video.videoWidth;
|
|
326
|
+
canvas.height = video.videoHeight;
|
|
327
|
+
|
|
328
|
+
// Draw the video frame onto the canvas
|
|
329
|
+
const ctx = canvas.getContext('2d');
|
|
330
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
331
|
+
|
|
332
|
+
const paddedWidth = canvas.width;
|
|
333
|
+
const paddedHeight = canvas.width / fixedAspectRatio;
|
|
334
|
+
|
|
335
|
+
// Calculate the dimensions of the cropped image based on the padded preview frame dimensions
|
|
336
|
+
const cropWidth = paddedWidth;
|
|
337
|
+
const cropHeight = paddedHeight;
|
|
338
|
+
const cropLeft = 0;
|
|
339
|
+
const cropTop = canvas.height / 2 - paddedHeight / 2;
|
|
340
|
+
|
|
341
|
+
// Create a new canvas element for the cropped image
|
|
342
|
+
const croppedCanvas = document.createElement('canvas');
|
|
343
|
+
croppedCanvas.width = cropWidth;
|
|
344
|
+
croppedCanvas.height = cropHeight;
|
|
345
|
+
|
|
346
|
+
// Draw the cropped image onto the new canvas
|
|
347
|
+
const croppedCtx = croppedCanvas.getContext('2d');
|
|
348
|
+
croppedCtx.drawImage(
|
|
349
|
+
canvas,
|
|
350
|
+
cropLeft,
|
|
351
|
+
cropTop,
|
|
352
|
+
cropWidth,
|
|
353
|
+
cropHeight,
|
|
354
|
+
0,
|
|
355
|
+
0,
|
|
356
|
+
cropWidth,
|
|
357
|
+
cropHeight,
|
|
358
|
+
);
|
|
359
|
+
const image = croppedCanvas.toDataURL('image/jpeg');
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
image,
|
|
363
|
+
originalHeight: canvas.height,
|
|
364
|
+
originalWidth: canvas.width,
|
|
365
|
+
...this.idCardRegion,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const height = canvas.width / (video.videoWidth / video.videoHeight);
|
|
370
|
+
canvas.height = height;
|
|
371
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
image: canvas.toDataURL('image/jpeg'),
|
|
375
|
+
originalHeight: canvas.height,
|
|
376
|
+
originalWidth: canvas.width,
|
|
377
|
+
...this.idCardRegion,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
_drawImage(canvas, enableImageTests = true, video = SmartCamera.stream) {
|
|
382
|
+
this.resetErrorMessage();
|
|
383
|
+
const context = canvas.getContext('2d');
|
|
384
|
+
|
|
385
|
+
context.drawImage(
|
|
386
|
+
video,
|
|
387
|
+
0,
|
|
388
|
+
0,
|
|
389
|
+
video.videoWidth,
|
|
390
|
+
video.videoHeight,
|
|
391
|
+
0,
|
|
392
|
+
0,
|
|
393
|
+
canvas.width,
|
|
394
|
+
canvas.height,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (enableImageTests) {
|
|
398
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
399
|
+
|
|
400
|
+
const hasEnoughColors = hasMoreThanNColors(imageData.data);
|
|
401
|
+
|
|
402
|
+
if (hasEnoughColors) {
|
|
403
|
+
return context;
|
|
404
|
+
}
|
|
405
|
+
throw new Error(
|
|
406
|
+
'Unable to capture webcam images - Please try another device',
|
|
407
|
+
);
|
|
408
|
+
} else {
|
|
409
|
+
return context;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
handleIDStream(stream) {
|
|
414
|
+
const videoExists = this.shadowRoot.querySelector('video');
|
|
415
|
+
let video = null;
|
|
416
|
+
if (videoExists) {
|
|
417
|
+
video = this.shadowRoot.querySelector('video');
|
|
418
|
+
} else {
|
|
419
|
+
video = document.createElement('video');
|
|
420
|
+
}
|
|
421
|
+
const videoContainer = this.shadowRoot.querySelector('.id-video');
|
|
422
|
+
const MIN_WIDTH = 272;
|
|
423
|
+
const width =
|
|
424
|
+
videoContainer.clientWidth < MIN_WIDTH
|
|
425
|
+
? MIN_WIDTH
|
|
426
|
+
: videoContainer.clientWidth;
|
|
427
|
+
video.style.width = `${width}px`;
|
|
428
|
+
video.style.height = '0px';
|
|
429
|
+
video.style.display = 'block';
|
|
430
|
+
video.muted = true;
|
|
431
|
+
video.setAttribute('muted', 'true');
|
|
432
|
+
if (this.isPortraitCaptureView) {
|
|
433
|
+
video.style.objectFit = 'cover';
|
|
434
|
+
video.style.scale = '1';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
video.autoplay = true;
|
|
438
|
+
video.playsInline = true;
|
|
439
|
+
if ('srcObject' in video) {
|
|
440
|
+
video.srcObject = stream;
|
|
441
|
+
} else {
|
|
442
|
+
video.src = window.URL.createObjectURL(stream);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
video.onloadedmetadata = () => {
|
|
446
|
+
video.play();
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const onVideoStart = () => {
|
|
450
|
+
const {
|
|
451
|
+
aspectRatio,
|
|
452
|
+
offsetHeight,
|
|
453
|
+
offsetWidth,
|
|
454
|
+
videoHeight,
|
|
455
|
+
videoWidth,
|
|
456
|
+
} = this._calculateVideoOffset(video);
|
|
457
|
+
const portrait = aspectRatio < 1;
|
|
458
|
+
|
|
459
|
+
if (portrait) {
|
|
460
|
+
videoContainer.classList.add('mobile-camera-screen');
|
|
461
|
+
videoContainer.style.height = `${videoHeight}px`;
|
|
462
|
+
}
|
|
463
|
+
videoContainer.style.width = `${videoWidth}px`;
|
|
464
|
+
videoContainer.style.maxHeight = `${videoHeight}px`;
|
|
465
|
+
video.style.height = `${videoHeight}px`;
|
|
466
|
+
const idCardRegionWidth = videoWidth - offsetWidth;
|
|
467
|
+
const idCardRegionHeight = videoHeight - offsetHeight;
|
|
468
|
+
|
|
469
|
+
const rightLeftBorderSize = 20;
|
|
470
|
+
const topBottomBorderSize = 20;
|
|
471
|
+
this.idCardRegion = {
|
|
472
|
+
height: idCardRegionHeight,
|
|
473
|
+
rightLeftBorderSize,
|
|
474
|
+
topBottomBorderSize,
|
|
475
|
+
width: idCardRegionWidth,
|
|
476
|
+
x: offsetWidth / 2,
|
|
477
|
+
y: offsetHeight / 2,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
let videoOverlay = videoContainer.querySelector('.video-overlay');
|
|
481
|
+
if (videoOverlay) {
|
|
482
|
+
videoOverlay.remove();
|
|
483
|
+
}
|
|
484
|
+
videoOverlay = document.createElement('div');
|
|
485
|
+
const shadeColor = 'white';
|
|
486
|
+
videoOverlay.classList.add('video-overlay');
|
|
487
|
+
|
|
488
|
+
videoOverlay.style.borderLeft = `${rightLeftBorderSize}px solid ${shadeColor}`;
|
|
489
|
+
videoOverlay.style.borderRight = `${rightLeftBorderSize}px solid ${shadeColor}`;
|
|
490
|
+
videoOverlay.style.borderTop = `${topBottomBorderSize}px solid ${shadeColor}`;
|
|
491
|
+
videoOverlay.style.borderBottom = `${topBottomBorderSize}px solid ${shadeColor}`;
|
|
492
|
+
videoOverlay.style.top = '0px';
|
|
493
|
+
videoOverlay.style.bottom = '0px';
|
|
494
|
+
videoOverlay.style.left = '0px';
|
|
495
|
+
videoOverlay.style.right = '0px';
|
|
496
|
+
videoOverlay.style.inset = '-1px';
|
|
497
|
+
|
|
498
|
+
const innerBorder = document.createElement('div');
|
|
499
|
+
innerBorder.classList.add('inner-border');
|
|
500
|
+
videoOverlay.appendChild(innerBorder);
|
|
501
|
+
videoContainer.appendChild(videoOverlay);
|
|
502
|
+
this.videoOverlay = videoOverlay;
|
|
503
|
+
this.shadowRoot.querySelector('#loader').hidden = true;
|
|
504
|
+
this.shadowRoot.querySelector('.id-video').hidden = false;
|
|
505
|
+
this.shadowRoot.querySelector('.actions').hidden = false;
|
|
506
|
+
video.removeEventListener('playing', onVideoStart);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
video.addEventListener('playing', onVideoStart);
|
|
510
|
+
|
|
511
|
+
if (!videoExists) {
|
|
512
|
+
videoContainer.prepend(video);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
this._IDStream = stream;
|
|
516
|
+
this._IDVideo = video;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
_calculateVideoOffset(video) {
|
|
520
|
+
const offset = 30;
|
|
521
|
+
const aspectRatio = video.videoWidth / video.videoHeight;
|
|
522
|
+
const calculatedAspectRatio = this.isPortraitCaptureView
|
|
523
|
+
? PORTRAIT_ID_PREVIEW_WIDTH / PORTRAIT_ID_PREVIEW_HEIGHT
|
|
524
|
+
: fixedAspectRatio;
|
|
525
|
+
const portrait = aspectRatio < 1;
|
|
526
|
+
const videoWidth = video.clientWidth;
|
|
527
|
+
const videoHeight = video.clientWidth / calculatedAspectRatio;
|
|
528
|
+
const originalWidth = video.videoWidth;
|
|
529
|
+
const originalHeight = video.videoWidth / calculatedAspectRatio;
|
|
530
|
+
|
|
531
|
+
const offsetHeight = videoHeight * ((portrait ? 5 : offset) / 100);
|
|
532
|
+
const offsetWidth = videoWidth * (offset / 100);
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
aspectRatio,
|
|
536
|
+
offsetHeight,
|
|
537
|
+
offsetWidth,
|
|
538
|
+
originalHeight,
|
|
539
|
+
originalWidth,
|
|
540
|
+
videoHeight,
|
|
541
|
+
videoWidth,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
_stopIDVideoStream(stream = this._IDStream) {
|
|
546
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
setUpEventListeners() {
|
|
550
|
+
this.captureIDImage = this.shadowRoot.querySelector('#capture-id-image');
|
|
551
|
+
this.navigation = this.shadowRoot.querySelector('smileid-navigation');
|
|
552
|
+
|
|
553
|
+
if (SmartCamera.stream) {
|
|
554
|
+
this.handleIDStream(SmartCamera.stream);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
this.navigation.addEventListener('navigation.back', () => {
|
|
558
|
+
this.handleBackEvents();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
this.navigation.addEventListener('navigation.close', () => {
|
|
562
|
+
this.handleCloseEvents();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
this.captureIDImage.addEventListener('click', () => {
|
|
566
|
+
this._captureIDImage();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
this.getUserMedia();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
get hideBack() {
|
|
573
|
+
return this.hasAttribute('hide-back-to-host');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
get showNavigation() {
|
|
577
|
+
return this.hasAttribute('show-navigation');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
get themeColor() {
|
|
581
|
+
return this.getAttribute('theme-color') || '#043C93';
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
get hideAttribution() {
|
|
585
|
+
return this.hasAttribute('hide-attribution');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
get documentCaptureModes() {
|
|
589
|
+
return this.getAttribute('document-capture-modes') || 'camera';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
get supportBothCaptureModes() {
|
|
593
|
+
const value = this.documentCaptureModes;
|
|
594
|
+
return value.includes('camera') && value.includes('upload');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
get title() {
|
|
598
|
+
return (
|
|
599
|
+
this.getAttribute('title') ||
|
|
600
|
+
`${this.IdSides[this.sideOfId]} of ${this.documentName}`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
get hidden() {
|
|
605
|
+
return this.getAttribute('hidden');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
get sideOfId() {
|
|
609
|
+
return (this.getAttribute('side-of-id') || 'front').toLowerCase();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
get isFrontOfId() {
|
|
613
|
+
return this.sideOfId === 'front';
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
get isBackOfId() {
|
|
617
|
+
return !this.isFrontOfId;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
get documentType() {
|
|
621
|
+
return this.getAttribute('document-type') || '';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
get documentName() {
|
|
625
|
+
return this.getAttribute('document-name') || 'Document';
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
get isPortraitCaptureView() {
|
|
629
|
+
return this.getAttribute('document-type') === 'GREEN_BOOK';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
get cameraError() {
|
|
633
|
+
return this.getAttribute('data-camera-error');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
static get observedAttributes() {
|
|
637
|
+
return [
|
|
638
|
+
'data-camera-error',
|
|
639
|
+
'data-camera-ready',
|
|
640
|
+
'document-name',
|
|
641
|
+
'document-type',
|
|
642
|
+
'hidden',
|
|
643
|
+
'hide-back-to-host',
|
|
644
|
+
'show-navigation',
|
|
645
|
+
'title',
|
|
646
|
+
];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
attributeChangedCallback(name) {
|
|
650
|
+
switch (name) {
|
|
651
|
+
case 'data-camera-error':
|
|
652
|
+
case 'data-camera-ready':
|
|
653
|
+
case 'document-name':
|
|
654
|
+
case 'document-type':
|
|
655
|
+
case 'hidden':
|
|
656
|
+
case 'title':
|
|
657
|
+
this.connectedCallback();
|
|
658
|
+
break;
|
|
659
|
+
default:
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
handleBackEvents() {
|
|
665
|
+
this.dispatchEvent(new CustomEvent('document-capture.cancelled'));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
handleCloseEvents() {
|
|
669
|
+
this.dispatchEvent(new CustomEvent('document-capture.close'));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if ('customElements' in window && !customElements.get('document-capture')) {
|
|
674
|
+
window.customElements.define('document-capture', DocumentCapture);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export default DocumentCapture;
|