@smileid/web-components 1.0.0-beta → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +15 -0
  2. package/components/README.md +1 -1
  3. package/components/camera-permission/CameraPermission.js +6 -2
  4. package/components/camera-permission/CameraPermission.stories.js +10 -4
  5. package/components/document/src/DocumentCaptureScreens.js +41 -11
  6. package/components/document/src/DocumentCaptureScreens.stories.js +16 -12
  7. package/components/document/src/README.md +11 -8
  8. package/components/document/src/document-capture/DocumentCapture.js +299 -231
  9. package/components/document/src/document-capture/DocumentCapture.stories.js +9 -2
  10. package/components/document/src/document-capture/README.md +5 -4
  11. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +33 -33
  12. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +8 -1
  13. package/components/document/src/document-capture-review/DocumentCaptureReview.js +15 -31
  14. package/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +12 -2
  15. package/components/document/src/document-capture-review/README.md +3 -3
  16. package/components/end-user-consent/src/EndUserConsent.js +14 -31
  17. package/components/end-user-consent/src/EndUserConsent.stories.js +8 -2
  18. package/components/navigation/src/Navigation.js +10 -2
  19. package/components/selfie/README.md +28 -4
  20. package/components/selfie/src/SelfieCaptureScreens.js +36 -10
  21. package/components/selfie/src/SelfieCaptureScreens.stories.js +13 -4
  22. package/components/selfie/src/selfie-capture/SelfieCapture.js +95 -23
  23. package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +14 -1
  24. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +50 -44
  25. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +8 -2
  26. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +3 -4
  27. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +8 -1
  28. package/components/signature-pad/package-lock.json +3009 -0
  29. package/components/signature-pad/package.json +6 -6
  30. package/components/signature-pad/src/SignaturePad.js +5 -1
  31. package/components/signature-pad/src/SignaturePad.stories.js +10 -2
  32. package/components/smart-camera-web/src/README.md +207 -0
  33. package/components/smart-camera-web/src/SmartCameraWeb.js +56 -7
  34. package/components/smart-camera-web/src/SmartCameraWeb.stories.js +27 -7
  35. package/components/totp-consent/src/TotpConsent.js +15 -3
  36. package/cypress/e2e/smart-camera-web-agent-mode.cy.js +144 -0
  37. package/cypress/e2e/smart-camera-web-complete-flow.cy.js +221 -0
  38. package/cypress/e2e/smart-camera-web.cy.js +1 -1
  39. package/cypress/pages/smart-camera-web-agent-mode.html +36 -0
  40. package/cypress/pages/smart-camera-web-complete-flow.html +42 -0
  41. package/cypress/pages/smart-camera-web.html +1 -1
  42. package/domain/camera/src/SmartCamera.js +28 -0
  43. package/package.json +8 -8
  44. package/styles/src/styles.js +14 -3
@@ -5,9 +5,12 @@ import SmartCamera from '../../../domain/camera/src/SmartCamera';
5
5
  import styles from '../../../styles/src/styles';
6
6
  import { version as COMPONENTS_VERSION } from '../../../package.json';
7
7
 
8
- async function getPermissions(captureScreen) {
8
+ async function getPermissions(captureScreen, facingMode = 'user') {
9
9
  try {
10
- await SmartCamera.getMedia({ audio: false, video: true });
10
+ await SmartCamera.getMedia({
11
+ audio: false,
12
+ video: { facingMode },
13
+ });
11
14
  captureScreen.removeAttribute('data-camera-error');
12
15
  captureScreen.setAttribute('data-camera-ready', true);
13
16
  } catch (error) {
@@ -27,11 +30,11 @@ class SelfieCaptureScreens extends HTMLElement {
27
30
 
28
31
  connectedCallback() {
29
32
  this.innerHTML = `
30
- ${styles}
33
+ ${styles(this.themeColor)}
31
34
  <div>
32
- <selfie-capture-instructions ${this.showNavigation} ${this.hideAttribution} ${this.hideBack} hidden></selfie-capture-instructions>
33
- <selfie-capture ${this.showNavigation} ${this.hideAttribution} ${this.disableImageTests} hidden></selfie-capture>
34
- <selfie-capture-review ${this.showNavigation} ${this.hideAttribution} hidden></selfie-capture-review>
35
+ <selfie-capture-instructions theme-color='${this.themeColor}' ${this.showNavigation} ${this.hideAttribution} ${this.hideBack} hidden></selfie-capture-instructions>
36
+ <selfie-capture theme-color='${this.themeColor}' ${this.showNavigation} ${this.allowAgentMode} ${this.allowAgentModeTests} ${this.hideAttribution} ${this.disableImageTests} hidden></selfie-capture>
37
+ <selfie-capture-review theme-color='${this.themeColor}' ${this.showNavigation} ${this.hideAttribution} hidden></selfie-capture-review>
35
38
  </div>
36
39
  `;
37
40
 
@@ -47,11 +50,12 @@ class SelfieCaptureScreens extends HTMLElement {
47
50
  this.selfieReview = this.querySelector('selfie-capture-review');
48
51
 
49
52
  if (this.hideInstructions && !this.hasAttribute('hidden')) {
50
- getPermissions(this.selfieCapture);
53
+ getPermissions(this.selfieCapture, this.getAgentMode());
51
54
  }
52
55
 
56
+ // If the initial screen is selfie-capture, we need to get permissions
53
57
  if (this.getAttribute('initial-screen') === 'selfie-capture') {
54
- getPermissions(this.selfieCapture);
58
+ getPermissions(this.selfieCapture, this.getAgentMode());
55
59
  this.setActiveScreen(this.selfieCapture);
56
60
  } else if (this.hideInstructions) {
57
61
  this.setActiveScreen(this.selfieCapture);
@@ -62,6 +66,10 @@ class SelfieCaptureScreens extends HTMLElement {
62
66
  this.setUpEventListeners();
63
67
  }
64
68
 
69
+ getAgentMode() {
70
+ return this.inAgentMode ? 'environment' : 'user';
71
+ }
72
+
65
73
  disconnectedCallback() {
66
74
  SmartCamera.stopMedia();
67
75
  if (this.activeScreen) {
@@ -75,7 +83,7 @@ class SelfieCaptureScreens extends HTMLElement {
75
83
  this.selfieInstruction.addEventListener(
76
84
  'selfie-capture-instructions.capture',
77
85
  async () => {
78
- await getPermissions(this.selfieCapture);
86
+ await getPermissions(this.selfieCapture, this.getAgentMode());
79
87
  this.setActiveScreen(this.selfieCapture);
80
88
  },
81
89
  );
@@ -119,7 +127,7 @@ class SelfieCaptureScreens extends HTMLElement {
119
127
  this._data.images = [];
120
128
  if (this.hideInstructions) {
121
129
  this.setActiveScreen(this.selfieCapture);
122
- await getPermissions(this.selfieCapture);
130
+ await getPermissions(this.selfieCapture, this.getAgentMode());
123
131
  } else {
124
132
  this.setActiveScreen(this.selfieInstruction);
125
133
  }
@@ -167,6 +175,20 @@ class SelfieCaptureScreens extends HTMLElement {
167
175
  return this.hasAttribute('show-navigation') ? 'show-navigation' : '';
168
176
  }
169
177
 
178
+ get inAgentMode() {
179
+ return this.getAttribute('allow-agent-mode') === 'true';
180
+ }
181
+
182
+ get allowAgentMode() {
183
+ return this.inAgentMode ? "allow-agent-mode='true'" : '';
184
+ }
185
+
186
+ get allowAgentModeTests() {
187
+ return this.hasAttribute('show-agent-mode-for-tests')
188
+ ? 'show-agent-mode-for-tests'
189
+ : '';
190
+ }
191
+
170
192
  get hideBack() {
171
193
  return this.hasAttribute('hide-back-to-host') ? 'hide-back' : '';
172
194
  }
@@ -177,6 +199,10 @@ class SelfieCaptureScreens extends HTMLElement {
177
199
  : '';
178
200
  }
179
201
 
202
+ get themeColor() {
203
+ return this.getAttribute('theme-color') || '#001096';
204
+ }
205
+
180
206
  setActiveScreen(screen) {
181
207
  this.activeScreen?.setAttribute('hidden', '');
182
208
  screen.removeAttribute('hidden');
@@ -1,21 +1,30 @@
1
1
  import './SelfieCaptureScreens';
2
2
 
3
3
  const meta = {
4
+ argTypes: {
5
+ 'theme-color': { control: 'color' },
6
+ },
4
7
  component: 'selfie-capture-screens',
5
8
  };
6
9
 
7
10
  export default meta;
8
11
 
9
12
  export const SelfieCaptureFlow = {
10
- render: () => `
11
- <selfie-capture-screens>
13
+ args: {
14
+ 'theme-color': '#001096',
15
+ },
16
+ render: (args) => `
17
+ <selfie-capture-screens theme-color='${args['theme-color']}'>
12
18
  </selfie-capture-screens>
13
19
  `,
14
20
  };
15
21
 
16
22
  export const SelfieCaptureFlowHiddenInstructions = {
17
- render: () => `
18
- <selfie-capture-screens hide-instructions >
23
+ args: {
24
+ 'theme-color': '#001096',
25
+ },
26
+ render: (args) => `
27
+ <selfie-capture-screens hide-instructions theme-color='${args['theme-color']}'>
19
28
  </selfie-capture-screens>
20
29
  `,
21
30
  };
@@ -63,11 +63,12 @@ function getLivenessFramesIndices(
63
63
 
64
64
  function templateString() {
65
65
  return `
66
- ${styles}
66
+ ${styles(this.themeColor)}
67
67
  <style>
68
68
  :host {
69
- --color-active: #2D2B2A;
70
- --color-default: #001096;
69
+ --theme-color: ${this.themeColor || '#001096'};
70
+ --color-active: #001096;
71
+ --color-default: #2D2B2A;
71
72
  --color-disabled: #848282;
72
73
  }
73
74
 
@@ -140,6 +141,14 @@ function templateString() {
140
141
  color: #001096;
141
142
  }
142
143
 
144
+ .title-color {
145
+ color: ${this.themeColor};
146
+ }
147
+
148
+ .theme-color {
149
+ color: ${this.themeColor};
150
+ }
151
+
143
152
  .center {
144
153
  text-align: center;
145
154
  margin-left: auto;
@@ -171,7 +180,7 @@ function templateString() {
171
180
  }
172
181
 
173
182
  .button {
174
- --button-color: var(--color-default);
183
+ --button-color: ${this.themeColor};
175
184
  -webkit-appearance: none;
176
185
  appearance: none;
177
186
  border-radius: 2.5rem;
@@ -189,7 +198,7 @@ function templateString() {
189
198
  .button:hover,
190
199
  .button:focus,
191
200
  .button:active {
192
- --button-color: var(--color-active);
201
+ --button-color: var(--color-default);
193
202
  }
194
203
 
195
204
  .button:disabled {
@@ -378,6 +387,11 @@ function templateString() {
378
387
  transform: scaleX(-1) translateX(50%) translateY(-50%);
379
388
  }
380
389
 
390
+ .video-container video.agent-mode {
391
+ min-height: 100%;
392
+ transform: scaleX(1) translateX(-50%) translateY(-50%);
393
+ }
394
+
381
395
  .video-container .video {
382
396
  background-color: black;
383
397
  position: absolute;
@@ -497,15 +511,15 @@ function templateString() {
497
511
  }
498
512
  </style>
499
513
  <div id='selfie-capture-screen' class='flow center'>
500
- <smileid-navigation ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
501
- <h1 class='text-2xl color-digital-blue font-bold'>Take a Selfie</h1>
514
+ <smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
515
+ <h1 class='text-2xl title-color font-bold'>Take a Selfie</h1>
502
516
 
503
517
  <div class='section | flow'>
504
518
  <div class='video-container'>
505
519
  <div class='video'>
506
520
  </div>
507
521
  <svg id="image-outline" width="215" height="245" viewBox="0 0 215 245" fill="none" xmlns="http://www.w3.org/2000/svg">
508
- <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="var(--color-default)" stroke-width="7.13965"/>
522
+ <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"/>
509
523
  </svg>
510
524
  <p id='smile-cta' class='color-gray'>SMILE</p>
511
525
  </div>
@@ -517,27 +531,26 @@ function templateString() {
517
531
  </svg>
518
532
  <span>Tips: Put your face inside the oval frame and click to "take selfie"</span> </small>
519
533
 
534
+ ${this.allowAgentMode ? `<button data-variant='outline small' id='switch-camera' class='button | center' type='button'>${this.inAgentMode ? 'Agent Mode On' : 'Agent Mode Off'}</button>` : ''}
535
+
520
536
  <button data-variant='solid' id='start-image-capture' class='button | center' type='button'>
521
537
  Take Selfie
522
538
  </button>
523
539
 
524
- ${
525
- this.hideAttribution
526
- ? ''
527
- : `
528
- <powered-by-smile-id></powered-by-smile-id>
529
- `
530
- }
540
+ ${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
531
541
  </div>
532
542
  </div>
533
543
  `;
534
544
  }
535
545
 
536
- async function getPermissions(captureScreen) {
546
+ async function getPermissions(
547
+ captureScreen,
548
+ constraints = { facingMode: 'user' },
549
+ ) {
537
550
  try {
538
551
  await SmartCamera.getMedia({
539
552
  audio: false,
540
- video: true,
553
+ video: constraints,
541
554
  });
542
555
  captureScreen?.removeAttribute('data-camera-error');
543
556
  captureScreen?.setAttribute('data-camera-ready', true);
@@ -557,12 +570,16 @@ class SelfieCaptureScreen extends HTMLElement {
557
570
  this.render = () => this.templateString();
558
571
 
559
572
  this.attachShadow({ mode: 'open' });
573
+ this.facingMode = 'user';
574
+ if (this.allowAgentMode) {
575
+ this.facingMode = 'environment';
576
+ }
560
577
  }
561
578
 
562
579
  connectedCallback() {
563
580
  const template = document.createElement('template');
564
581
  template.innerHTML = this.render();
565
-
582
+ this.shadowRoot.innerHTML = '';
566
583
  this.shadowRoot.appendChild(template.content.cloneNode(true));
567
584
  this.videoContainer = this.shadowRoot.querySelector(
568
585
  '.video-container > .video',
@@ -592,6 +609,9 @@ class SelfieCaptureScreen extends HTMLElement {
592
609
 
593
610
  _startImageCapture() {
594
611
  this.startImageCapture.disabled = true;
612
+ if (this.switchCamera) {
613
+ this.switchCamera.disabled = true;
614
+ }
595
615
 
596
616
  /**
597
617
  * this was culled from https://jakearchibald.com/2013/animated-line-drawing-svg/
@@ -622,6 +642,20 @@ class SelfieCaptureScreen extends HTMLElement {
622
642
  }, this._videoStreamDurationInMS);
623
643
  }
624
644
 
645
+ async _switchCamera() {
646
+ this.facingMode = this.facingMode === 'user' ? 'environment' : 'user';
647
+ if (this.facingMode === 'user') {
648
+ this.shadowRoot.querySelector('video').classList.remove('agent-mode');
649
+ } else {
650
+ this.shadowRoot.querySelector('video').classList.add('agent-mode');
651
+ }
652
+ this.startImageCapture.disabled = true;
653
+ this.switchCamera.disabled = true;
654
+ SmartCamera.stopMedia();
655
+ await getPermissions(this, { facingMode: this.facingMode });
656
+ this.handleStream(SmartCamera.stream);
657
+ }
658
+
625
659
  _stopVideoStream() {
626
660
  try {
627
661
  clearTimeout(this._videoStreamTimeout);
@@ -635,6 +669,7 @@ class SelfieCaptureScreen extends HTMLElement {
635
669
 
636
670
  const totalNoOfFrames = this._rawImages.length;
637
671
  this._data.referenceImage = this._referenceImage;
672
+ this._data.previewImage = this._referenceImage;
638
673
 
639
674
  const livenessFramesIndices = getLivenessFramesIndices(totalNoOfFrames);
640
675
 
@@ -772,6 +807,8 @@ class SelfieCaptureScreen extends HTMLElement {
772
807
  this.startImageCapture = this.shadowRoot.querySelector(
773
808
  '#start-image-capture',
774
809
  );
810
+
811
+ this.switchCamera = this.shadowRoot.querySelector('#switch-camera');
775
812
  this.imageOutline = this.shadowRoot.querySelector('#image-outline path');
776
813
  this.smileCTA = this.shadowRoot.querySelector('#smile-cta');
777
814
 
@@ -779,6 +816,10 @@ class SelfieCaptureScreen extends HTMLElement {
779
816
  this._startImageCapture();
780
817
  });
781
818
 
819
+ this.switchCamera?.addEventListener('click', () => {
820
+ this._switchCamera();
821
+ });
822
+
782
823
  this.navigation.addEventListener('navigation.back', () => {
783
824
  this.handleBackEvents();
784
825
  });
@@ -790,8 +831,10 @@ class SelfieCaptureScreen extends HTMLElement {
790
831
  if (SmartCamera.stream) {
791
832
  this.handleStream(SmartCamera.stream);
792
833
  } else if (this.hasAttribute('data-camera-ready')) {
793
- getPermissions(this);
834
+ getPermissions(this, { facingMode: this.facingMode });
794
835
  }
836
+
837
+ this.setupAgentMode();
795
838
  }
796
839
 
797
840
  disconnectedCallback() {
@@ -808,16 +851,34 @@ class SelfieCaptureScreen extends HTMLElement {
808
851
  }
809
852
 
810
853
  get themeColor() {
811
- return this.getAttribute('theme-color') || '#043C93';
854
+ return this.getAttribute('theme-color') || '#001096';
812
855
  }
813
856
 
814
857
  get hideAttribution() {
815
858
  return this.hasAttribute('hide-attribution');
816
859
  }
817
860
 
818
- get supportBothCaptureModes() {
819
- const value = this.documentCaptureModes;
820
- return value.includes('camera') && value.includes('upload');
861
+ async setupAgentMode() {
862
+ if (!this.allowAgentMode) {
863
+ return;
864
+ }
865
+
866
+ const supportAgentMode = await SmartCamera.supportsAgentMode();
867
+
868
+ if (supportAgentMode || this.hasAttribute('show-agent-mode-for-tests')) {
869
+ this.switchCamera.hidden = false;
870
+ if (this.facingMode === 'user') {
871
+ this.shadowRoot.querySelector('video')?.classList?.remove('agent-mode');
872
+ } else {
873
+ this.shadowRoot.querySelector('video')?.classList?.add('agent-mode');
874
+ }
875
+ } else {
876
+ this.switchCamera.hidden = true;
877
+ }
878
+ }
879
+
880
+ get hasAgentSupport() {
881
+ return this.hasAttribute('has-agent-support');
821
882
  }
822
883
 
823
884
  get title() {
@@ -836,10 +897,20 @@ class SelfieCaptureScreen extends HTMLElement {
836
897
  return this.hasAttribute('disable-image-tests');
837
898
  }
838
899
 
900
+ get allowAgentMode() {
901
+ return this.getAttribute('allow-agent-mode') === 'true';
902
+ }
903
+
904
+ get inAgentMode() {
905
+ return this.facingMode === 'environment';
906
+ }
907
+
839
908
  static get observedAttributes() {
840
909
  return [
910
+ 'allow-agent-mode',
841
911
  'data-camera-error',
842
912
  'data-camera-ready',
913
+ 'disable-image-tests',
843
914
  'hidden',
844
915
  'hide-back-to-host',
845
916
  'show-navigation',
@@ -853,6 +924,7 @@ class SelfieCaptureScreen extends HTMLElement {
853
924
  case 'data-camera-ready':
854
925
  case 'hidden':
855
926
  case 'title':
927
+ case 'allow-agent-mode':
856
928
  this.shadowRoot.innerHTML = this.render();
857
929
  this.init();
858
930
  break;
@@ -2,12 +2,18 @@ import SmartCamera from '../../../../domain/camera/src/SmartCamera';
2
2
  import './SelfieCapture';
3
3
 
4
4
  const meta = {
5
+ argTypes: {
6
+ 'theme-color': { control: 'color' },
7
+ },
5
8
  component: 'selfie-capture',
6
9
  };
7
10
 
8
11
  export default meta;
9
12
 
10
13
  export const SelfieCapture = {
14
+ args: {
15
+ 'theme-color': '#001096',
16
+ },
11
17
  loaders: [
12
18
  async () => ({
13
19
  permissionGranted: await SmartCamera.getMedia({
@@ -16,8 +22,15 @@ export const SelfieCapture = {
16
22
  }),
17
23
  }),
18
24
  ],
25
+ render: (args) => `
26
+ <selfie-capture theme-color='${args['theme-color']}'>
27
+ </selfie-capture>
28
+ `,
29
+ };
30
+
31
+ export const SelfieCaptureAgentMode = {
19
32
  render: () => `
20
- <selfie-capture>
33
+ <selfie-capture allow-agent-mode='true' data-camera-ready show-agent-mode-for-tests>
21
34
  </selfie-capture>
22
35
  `,
23
36
  };