smartcomply-web-sdk 1.0.76 → 1.0.78

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.
@@ -137,6 +137,15 @@ export declare class SmartComplyFlow {
137
137
  /** Click handler that fires once and disables the button, preventing a double-tap double-dispatch. */
138
138
  private _onceClick;
139
139
  private isDocumentFlow;
140
+ /**
141
+ * True when the SDK Config is set to skip data/document verification
142
+ * entirely and only run the liveness/selfie check — no country picker,
143
+ * no ID-type picker, no ID-number entry or document capture. Unlike
144
+ * isDocumentFlow(), there's no channel-shape fallback to infer this from:
145
+ * a liveness-only config carries no channels at all, so the only signal
146
+ * is verification_type itself.
147
+ */
148
+ private isLivenessOnlyFlow;
140
149
  /**
141
150
  * The "go back one step" target for a given step, if that step is
142
151
  * navigable — shared by the header back button and the browser
@@ -1 +1 @@
1
- {"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIxF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAwC/C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CAC7C;AAsRD,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAc;IACzB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,KAAK,CAAoC;IAGjD,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,UAAU,CAA+B;IAGjD,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,qBAAqB,CAA6D;IAC1F,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,kBAAkB,CAAc;IACxC,OAAO,CAAC,oBAAoB,CAAuC;IACnE,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAqB;IAK7C,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,kBAAkB,CAAuC;IACjE,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,eAAe,CAA6C;IACpE,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,mBAAmB,CAAS;IAGpC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IAGxC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,UAAU,CAAc;IAGhC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,cAAc,CAAkB;IAGxC,OAAO,CAAC,gBAAgB,CAA4B;IAGpD,OAAO,CAAC,aAAa,CAA8C;IAGnE,OAAO,CAAC,YAAY,CAA8C;IAGlE,OAAO,CAAC,kBAAkB,CAA8C;IACxE,OAAO,CAAC,mBAAmB,CAA8C;IAGzE,OAAO,CAAC,gBAAgB,CAA8C;IAGtE,OAAO,CAAC,UAAU,CAAgC;IAElD,OAAO;IAUP;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe;IAOlD;;OAEG;IACH,KAAK,IAAI,IAAI;IAwEb,OAAO,CAAC,KAAK;YAwJC,UAAU;IAwDxB,OAAO,CAAC,0BAA0B;IAgBlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAM;IAClD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,QAAQ;IAmChB,OAAO,CAAC,UAAU;IA4ElB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IA4IrB,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,kBAAkB;IAsF1B,OAAO,CAAC,aAAa;IAuWrB,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,qBAAqB;IA+E7B,OAAO,CAAC,qBAAqB;IAuI7B,OAAO,CAAC,aAAa;IA8GrB,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,qBAAqB;IA0C7B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,UAAU;IA+ClB,OAAO,CAAC,wBAAwB;IAqBhC,OAAO,CAAC,cAAc;IAUtB;;OAEG;YACW,4BAA4B;IAiB1C;;OAEG;YACW,mBAAmB;IA+GjC,OAAO,CAAC,6BAA6B;IA8DrC,OAAO,CAAC,cAAc;IA0GtB,OAAO,CAAC,aAAa;IAsGrB,OAAO,CAAC,YAAY;IAmGpB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,gBAAgB;IAmDxB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,qBAAqB;IAmB7B,sGAAsG;IACtG,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,cAAc;IAgBtB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,cAAc;IA0EtB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,YAAY;CAiCrB"}
1
+ {"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,11 @@ const _INFRASTRUCTURE_ERROR_PATTERNS = [
21
21
  "camera access is not supported",
22
22
  "camera access failed",
23
23
  "recording failed unexpectedly",
24
+ // FaceDetectorEngine.init() failures (FaceDetector.ts) — a slow/blocked
25
+ // CDN or model download is a connectivity issue, not a failed face scan,
26
+ // and must not burn one of the user's limited liveness retry attempts.
27
+ "face detection setup timed out",
28
+ "failed to download face model",
24
29
  ];
25
30
  function _isInfrastructureError(err) {
26
31
  const msg = String(err?.message || "").toLowerCase();
@@ -770,7 +775,8 @@ class SmartComplyFlow {
770
775
  // ── Welcome ────────────────────────────────────────────────────
771
776
  renderWelcome(container) {
772
777
  container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;";
773
- const isDocFlow = this.isDocumentFlow();
778
+ const isLivenessOnly = this.isLivenessOnlyFlow();
779
+ const isDocFlow = isLivenessOnly ? false : this.isDocumentFlow();
774
780
  // Icon + badge row (side by side to save vertical space)
775
781
  const topRow = document.createElement("div");
776
782
  topRow.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:8px;";
@@ -793,7 +799,9 @@ class SmartComplyFlow {
793
799
  color:${this.theme.primary};
794
800
  border:1px solid ${this.theme.primary}30;
795
801
  `;
796
- badge.textContent = isDocFlow ? "📄 Document Verification" : "🔍 Data Verification";
802
+ badge.textContent = isLivenessOnly
803
+ ? "🤳 Liveness Check"
804
+ : isDocFlow ? "📄 Document Verification" : "🔍 Data Verification";
797
805
  topRow.appendChild(badge);
798
806
  container.appendChild(topRow);
799
807
  // Title + subtitle (compact)
@@ -815,23 +823,29 @@ class SmartComplyFlow {
815
823
  color:${this.theme.textSecondary};font-size:13px;line-height:1.5;
816
824
  margin:0 auto;max-width:300px;text-align:center;
817
825
  `;
818
- desc.textContent = isDocFlow
819
- ? "You'll need your document and camera access. This takes about 2 minutes."
820
- : "You'll need your ID number and camera access. This takes about 2 minutes.";
826
+ desc.textContent = isLivenessOnly
827
+ ? "You'll just need camera access. This takes about 30 seconds."
828
+ : isDocFlow
829
+ ? "You'll need your document and camera access. This takes about 2 minutes."
830
+ : "You'll need your ID number and camera access. This takes about 2 minutes.";
821
831
  titleWrap.appendChild(desc);
822
832
  container.appendChild(titleWrap);
823
833
  // Steps preview — compact rows
824
- const steps = isDocFlow
834
+ const steps = isLivenessOnly
825
835
  ? [
826
- { icon: "🪪", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
827
- { icon: "📷", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
828
836
  { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" },
829
837
  ]
830
- : [
831
- { icon: "🆔", text: "Choose your ID type", sub: "NIN, BVN, Voter's Card and more" },
832
- { icon: "📝", text: "Enter your ID details", sub: "Exactly as shown on your ID" },
833
- { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" },
834
- ];
838
+ : isDocFlow
839
+ ? [
840
+ { icon: "🪪", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
841
+ { icon: "📷", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
842
+ { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" },
843
+ ]
844
+ : [
845
+ { icon: "🆔", text: "Choose your ID type", sub: "NIN, BVN, Voter's Card and more" },
846
+ { icon: "📝", text: "Enter your ID details", sub: "Exactly as shown on your ID" },
847
+ { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" },
848
+ ];
835
849
  const stepsWrap = document.createElement("div");
836
850
  stepsWrap.style.cssText = `
837
851
  width:100%;display:flex;flex-direction:column;gap:0;
@@ -873,7 +887,27 @@ class SmartComplyFlow {
873
887
  // CTA
874
888
  const btn = this.createPrimaryButton("Get Started →");
875
889
  btn.style.cssText += "margin-top:2px;";
876
- btn.addEventListener("click", () => this.showStep("country"));
890
+ btn.addEventListener("click", () => {
891
+ if (isLivenessOnly) {
892
+ // Liveness-only has no channels at all, so renderCountrySelect's
893
+ // single-country auto-skip (countries.length === 1) never fires for
894
+ // it — going through "country" here would render an empty,
895
+ // dead-end country-picker screen. Go straight to liveness instead.
896
+ //
897
+ // selectedCountry defaults to "" and is otherwise only ever set by
898
+ // renderCountrySelect, which this mode never visits — renderLiveness
899
+ // sends it as a required, non-blank `country` field, so leaving it
900
+ // empty would fail create() validation before the scan even starts.
901
+ // There's no real country to report here (no identity lookup runs
902
+ // in this mode), so this is a neutral placeholder, not a claim
903
+ // about the user's actual location.
904
+ this.selectedCountry = this.selectedCountry || "global";
905
+ this.showStep("liveness");
906
+ }
907
+ else {
908
+ this.showStep("country");
909
+ }
910
+ });
877
911
  container.appendChild(btn);
878
912
  // Plain-language line on what the camera/photo is for and what happens
879
913
  // to it — shown before the "Get Started" tap that leads to the first
@@ -2417,6 +2451,21 @@ class SmartComplyFlow {
2417
2451
  .flatMap((ch) => ch.fields || []);
2418
2452
  return allFields.some((f) => isUploadFieldType(f.type));
2419
2453
  }
2454
+ /**
2455
+ * True when the SDK Config is set to skip data/document verification
2456
+ * entirely and only run the liveness/selfie check — no country picker,
2457
+ * no ID-type picker, no ID-number entry or document capture. Unlike
2458
+ * isDocumentFlow(), there's no channel-shape fallback to infer this from:
2459
+ * a liveness-only config carries no channels at all, so the only signal
2460
+ * is verification_type itself.
2461
+ */
2462
+ isLivenessOnlyFlow() {
2463
+ const vType = this.sdkConfig?.verification_type;
2464
+ if (!vType)
2465
+ return false;
2466
+ const types = Array.isArray(vType) ? vType : [vType];
2467
+ return types.includes("liveness_only");
2468
+ }
2420
2469
  /**
2421
2470
  * The "go back one step" target for a given step, if that step is
2422
2471
  * navigable — shared by the header back button and the browser
@@ -2433,16 +2482,28 @@ class SmartComplyFlow {
2433
2482
  document_capture: () => { this.docCapture?.destroy(); this.showStep("id_type"); },
2434
2483
  document_confirm: () => { this.docCapture?.destroy(); this.showStep("document_capture"); },
2435
2484
  };
2485
+ // liveness-only skips straight from welcome to liveness with nothing in
2486
+ // between — deliberately no backTargets["liveness"] entry added here,
2487
+ // so the back button stays hidden on that step in this mode, same as
2488
+ // it already is for every other mode (liveness has no back target for
2489
+ // document/data flows either — nothing to go back to except closing).
2436
2490
  return backTargets[step];
2437
2491
  }
2438
2492
  updateProgress() {
2439
- const isDocFlow = this.isDocumentFlow();
2493
+ const isLivenessOnly = this.isLivenessOnlyFlow();
2494
+ const isDocFlow = isLivenessOnly ? false : this.isDocumentFlow();
2440
2495
  const countries = Object.keys(this.sdkConfig?.channels || {});
2441
2496
  const singleCountry = countries.length <= 1;
2442
- // Two-stage action steps — Stage 1: validate identity, Stage 2: liveness
2443
- const stage1Steps = isDocFlow
2444
- ? ["country", "id_type", "document_capture", "ocr_gate", "document_confirm"].filter(s => !(s === "country" && singleCountry))
2445
- : ["country", "id_type", "id_input", "confirm_identity"].filter(s => !(s === "country" && singleCountry));
2497
+ // Two-stage action steps — Stage 1: validate identity, Stage 2: liveness.
2498
+ // Liveness-only has no Stage 1 at all — welcome goes straight to
2499
+ // liveness — so stage1Steps stays empty and the progress bar reflects
2500
+ // a genuinely single-stage flow instead of implying a step that never
2501
+ // happens in this mode.
2502
+ const stage1Steps = isLivenessOnly
2503
+ ? []
2504
+ : isDocFlow
2505
+ ? ["country", "id_type", "document_capture", "ocr_gate", "document_confirm"].filter(s => !(s === "country" && singleCountry))
2506
+ : ["country", "id_type", "id_input", "confirm_identity"].filter(s => !(s === "country" && singleCountry));
2446
2507
  const stage2Steps = ["liveness"];
2447
2508
  const allActionSteps = [...stage1Steps, ...stage2Steps];
2448
2509
  const totalSteps = allActionSteps.length;
@@ -2454,6 +2515,8 @@ class SmartComplyFlow {
2454
2515
  this.progressBar.style.width = `${pct}%`;
2455
2516
  }
2456
2517
  // Stage label: "Stage 1 of 2: Verify Identity" / "Stage 2 of 2: Face Check"
2518
+ // — liveness-only has no Stage 1, so it just says "Face Check" with no
2519
+ // "of 2" framing, since there's only ever the one stage in this mode.
2457
2520
  if (this.stepLabel) {
2458
2521
  if (this.currentStep === "loading" || this.currentStep === "welcome") {
2459
2522
  this.stepLabel.textContent = "";
@@ -2462,7 +2525,7 @@ class SmartComplyFlow {
2462
2525
  this.stepLabel.textContent = "Complete ✓";
2463
2526
  }
2464
2527
  else if (stage2Steps.includes(this.currentStep)) {
2465
- this.stepLabel.textContent = "Stage 2 of 2: Face Check";
2528
+ this.stepLabel.textContent = isLivenessOnly ? "Face Check" : "Stage 2 of 2: Face Check";
2466
2529
  }
2467
2530
  else if (stage1Steps.includes(this.currentStep)) {
2468
2531
  this.stepLabel.textContent = isDocFlow ? "Stage 1 of 2: Scan Document" : "Stage 1 of 2: Verify Identity";