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.
@@ -4531,35 +4531,69 @@ var SmartComplySDK = (() => {
4531
4531
  }, nh.POSE_CONNECTIONS = Dc;
4532
4532
 
4533
4533
  // src/camera/FaceDetector.ts
4534
- var WASM_CDN = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm";
4535
- var MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task";
4536
- var _cachedVision = null;
4537
- var _cachedModelBuffer = null;
4534
+ var MEDIAPIPE_VERSION = "0.10.32";
4535
+ var WASM_CDN = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${MEDIAPIPE_VERSION}/wasm`;
4536
+ var MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
4537
+ var _visionPromise = null;
4538
+ var _modelBufferPromise = null;
4539
+ var INIT_TIMEOUT_MS = 2e4;
4540
+ function withTimeout(promise, ms2, label) {
4541
+ return new Promise((resolve, reject) => {
4542
+ const timer = setTimeout(
4543
+ () => reject(new Error(`${label} timed out. Please check your connection and try again.`)),
4544
+ ms2
4545
+ );
4546
+ promise.then(
4547
+ (value) => {
4548
+ clearTimeout(timer);
4549
+ resolve(value);
4550
+ },
4551
+ (err) => {
4552
+ clearTimeout(timer);
4553
+ reject(err);
4554
+ }
4555
+ );
4556
+ });
4557
+ }
4538
4558
  var FaceDetectorEngine = class {
4539
4559
  constructor() {
4540
4560
  this.landmarker = null;
4541
4561
  }
4542
4562
  async init() {
4543
- if (!_cachedVision) {
4544
- _cachedVision = await na.forVisionTasks(WASM_CDN);
4563
+ if (!_visionPromise) {
4564
+ _visionPromise = na.forVisionTasks(WASM_CDN);
4565
+ _visionPromise.catch(() => {
4566
+ _visionPromise = null;
4567
+ });
4545
4568
  }
4546
- if (!_cachedModelBuffer) {
4547
- const res = await fetch(MODEL_URL);
4548
- if (!res.ok) {
4549
- throw new Error(`Failed to download face model (${res.status})`);
4550
- }
4551
- _cachedModelBuffer = await res.arrayBuffer();
4569
+ if (!_modelBufferPromise) {
4570
+ _modelBufferPromise = fetch(MODEL_URL).then((res) => {
4571
+ if (!res.ok) throw new Error(`Failed to download face model (${res.status})`);
4572
+ return res.arrayBuffer();
4573
+ });
4574
+ _modelBufferPromise.catch(() => {
4575
+ _modelBufferPromise = null;
4576
+ });
4552
4577
  }
4553
- this.landmarker = await Ic.createFromOptions(_cachedVision, {
4554
- baseOptions: {
4555
- // Pass a copy — MediaPipe takes ownership of the buffer
4556
- modelAssetBuffer: new Uint8Array(_cachedModelBuffer.slice(0))
4557
- },
4558
- runningMode: "VIDEO",
4559
- outputFaceBlendshapes: true,
4560
- outputFacialTransformationMatrixes: true,
4561
- numFaces: 1
4562
- });
4578
+ const [vision, modelBuffer] = await withTimeout(
4579
+ Promise.all([_visionPromise, _modelBufferPromise]),
4580
+ INIT_TIMEOUT_MS,
4581
+ "Face detection setup"
4582
+ );
4583
+ this.landmarker = await withTimeout(
4584
+ Ic.createFromOptions(vision, {
4585
+ baseOptions: {
4586
+ // Pass a copy — MediaPipe takes ownership of the buffer
4587
+ modelAssetBuffer: new Uint8Array(modelBuffer.slice(0))
4588
+ },
4589
+ runningMode: "VIDEO",
4590
+ outputFaceBlendshapes: true,
4591
+ outputFacialTransformationMatrixes: true,
4592
+ numFaces: 1
4593
+ }),
4594
+ INIT_TIMEOUT_MS,
4595
+ "Face detection setup"
4596
+ );
4563
4597
  }
4564
4598
  detect(video, timestamp) {
4565
4599
  if (!this.landmarker) {
@@ -5210,7 +5244,9 @@ var SmartComplySDK = (() => {
5210
5244
  */
5211
5245
  async create(params) {
5212
5246
  const formData = new FormData();
5213
- if (!params.identity_check) {
5247
+ const hasDocument = !!(params.document || params.document_front || params.id_file);
5248
+ if (params.identity_check || !hasDocument && !params.identifier) {
5249
+ } else {
5214
5250
  const rawIdentifier = params.identifier || `ID-${Date.now()}`;
5215
5251
  const safeIdentifier = rawIdentifier.length > 20 ? rawIdentifier.slice(0, 20) : rawIdentifier;
5216
5252
  formData.append("identifier", safeIdentifier);
@@ -5360,6 +5396,9 @@ var SmartComplySDK = (() => {
5360
5396
  interrupted.catch(() => {
5361
5397
  });
5362
5398
  try {
5399
+ const faceInitPromise = faceEngine.init();
5400
+ faceInitPromise.catch(() => {
5401
+ });
5363
5402
  let stream;
5364
5403
  if (params.prewarmedStream && params.prewarmedStream.active) {
5365
5404
  stream = params.prewarmedStream;
@@ -5385,7 +5424,7 @@ var SmartComplySDK = (() => {
5385
5424
  tempVideo.style.cssText = "width:100%;border-radius:12px;transform:scaleX(-1);";
5386
5425
  container.appendChild(tempVideo);
5387
5426
  }
5388
- await faceEngine.init();
5427
+ await faceInitPromise;
5389
5428
  ui2.updateInstruction("Fit your face inside the oval outline");
5390
5429
  const videoForDetection = uiVideo || tempVideo;
5391
5430
  await new Promise((resolve) => {
@@ -6982,7 +7021,12 @@ var SmartComplySDK = (() => {
6982
7021
  "failed after all retries",
6983
7022
  "camera access is not supported",
6984
7023
  "camera access failed",
6985
- "recording failed unexpectedly"
7024
+ "recording failed unexpectedly",
7025
+ // FaceDetectorEngine.init() failures (FaceDetector.ts) — a slow/blocked
7026
+ // CDN or model download is a connectivity issue, not a failed face scan,
7027
+ // and must not burn one of the user's limited liveness retry attempts.
7028
+ "face detection setup timed out",
7029
+ "failed to download face model"
6986
7030
  ];
6987
7031
  function _isInfrastructureError(err) {
6988
7032
  const msg = String(err?.message || "").toLowerCase();
@@ -7601,7 +7645,8 @@ var SmartComplySDK = (() => {
7601
7645
  // ── Welcome ────────────────────────────────────────────────────
7602
7646
  renderWelcome(container) {
7603
7647
  container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;";
7604
- const isDocFlow = this.isDocumentFlow();
7648
+ const isLivenessOnly = this.isLivenessOnlyFlow();
7649
+ const isDocFlow = isLivenessOnly ? false : this.isDocumentFlow();
7605
7650
  const topRow = document.createElement("div");
7606
7651
  topRow.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:8px;";
7607
7652
  const iconWrap = document.createElement("div");
@@ -7623,7 +7668,7 @@ var SmartComplySDK = (() => {
7623
7668
  color:${this.theme.primary};
7624
7669
  border:1px solid ${this.theme.primary}30;
7625
7670
  `;
7626
- badge.textContent = isDocFlow ? "📄 Document Verification" : "🔍 Data Verification";
7671
+ badge.textContent = isLivenessOnly ? "🤳 Liveness Check" : isDocFlow ? "📄 Document Verification" : "🔍 Data Verification";
7627
7672
  topRow.appendChild(badge);
7628
7673
  container.appendChild(topRow);
7629
7674
  const titleWrap = document.createElement("div");
@@ -7641,10 +7686,12 @@ var SmartComplySDK = (() => {
7641
7686
  color:${this.theme.textSecondary};font-size:13px;line-height:1.5;
7642
7687
  margin:0 auto;max-width:300px;text-align:center;
7643
7688
  `;
7644
- desc.textContent = isDocFlow ? "You'll need your document and camera access. This takes about 2 minutes." : "You'll need your ID number and camera access. This takes about 2 minutes.";
7689
+ desc.textContent = isLivenessOnly ? "You'll just need camera access. This takes about 30 seconds." : isDocFlow ? "You'll need your document and camera access. This takes about 2 minutes." : "You'll need your ID number and camera access. This takes about 2 minutes.";
7645
7690
  titleWrap.appendChild(desc);
7646
7691
  container.appendChild(titleWrap);
7647
- const steps = isDocFlow ? [
7692
+ const steps = isLivenessOnly ? [
7693
+ { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" }
7694
+ ] : isDocFlow ? [
7648
7695
  { icon: "🪪", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
7649
7696
  { icon: "📷", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
7650
7697
  { icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" }
@@ -7693,7 +7740,14 @@ var SmartComplySDK = (() => {
7693
7740
  container.appendChild(stepsWrap);
7694
7741
  const btn = this.createPrimaryButton("Get Started →");
7695
7742
  btn.style.cssText += "margin-top:2px;";
7696
- btn.addEventListener("click", () => this.showStep("country"));
7743
+ btn.addEventListener("click", () => {
7744
+ if (isLivenessOnly) {
7745
+ this.selectedCountry = this.selectedCountry || "global";
7746
+ this.showStep("liveness");
7747
+ } else {
7748
+ this.showStep("country");
7749
+ }
7750
+ });
7697
7751
  container.appendChild(btn);
7698
7752
  const trust = document.createElement("div");
7699
7753
  trust.style.cssText = `
@@ -8984,6 +9038,20 @@ var SmartComplySDK = (() => {
8984
9038
  const allFields = Object.values(channels).flat().flatMap((ch) => ch.fields || []);
8985
9039
  return allFields.some((f2) => isUploadFieldType(f2.type));
8986
9040
  }
9041
+ /**
9042
+ * True when the SDK Config is set to skip data/document verification
9043
+ * entirely and only run the liveness/selfie check — no country picker,
9044
+ * no ID-type picker, no ID-number entry or document capture. Unlike
9045
+ * isDocumentFlow(), there's no channel-shape fallback to infer this from:
9046
+ * a liveness-only config carries no channels at all, so the only signal
9047
+ * is verification_type itself.
9048
+ */
9049
+ isLivenessOnlyFlow() {
9050
+ const vType = this.sdkConfig?.verification_type;
9051
+ if (!vType) return false;
9052
+ const types = Array.isArray(vType) ? vType : [vType];
9053
+ return types.includes("liveness_only");
9054
+ }
8987
9055
  /**
8988
9056
  * The "go back one step" target for a given step, if that step is
8989
9057
  * navigable — shared by the header back button and the browser
@@ -9009,10 +9077,11 @@ var SmartComplySDK = (() => {
9009
9077
  return backTargets[step];
9010
9078
  }
9011
9079
  updateProgress() {
9012
- const isDocFlow = this.isDocumentFlow();
9080
+ const isLivenessOnly = this.isLivenessOnlyFlow();
9081
+ const isDocFlow = isLivenessOnly ? false : this.isDocumentFlow();
9013
9082
  const countries = Object.keys(this.sdkConfig?.channels || {});
9014
9083
  const singleCountry = countries.length <= 1;
9015
- const stage1Steps = isDocFlow ? ["country", "id_type", "document_capture", "ocr_gate", "document_confirm"].filter((s2) => !(s2 === "country" && singleCountry)) : ["country", "id_type", "id_input", "confirm_identity"].filter((s2) => !(s2 === "country" && singleCountry));
9084
+ const stage1Steps = isLivenessOnly ? [] : isDocFlow ? ["country", "id_type", "document_capture", "ocr_gate", "document_confirm"].filter((s2) => !(s2 === "country" && singleCountry)) : ["country", "id_type", "id_input", "confirm_identity"].filter((s2) => !(s2 === "country" && singleCountry));
9016
9085
  const stage2Steps = ["liveness"];
9017
9086
  const allActionSteps = [...stage1Steps, ...stage2Steps];
9018
9087
  const totalSteps = allActionSteps.length;
@@ -9027,7 +9096,7 @@ var SmartComplySDK = (() => {
9027
9096
  } else if (this.currentStep === "done") {
9028
9097
  this.stepLabel.textContent = "Complete ✓";
9029
9098
  } else if (stage2Steps.includes(this.currentStep)) {
9030
- this.stepLabel.textContent = "Stage 2 of 2: Face Check";
9099
+ this.stepLabel.textContent = isLivenessOnly ? "Face Check" : "Stage 2 of 2: Face Check";
9031
9100
  } else if (stage1Steps.includes(this.currentStep)) {
9032
9101
  this.stepLabel.textContent = isDocFlow ? "Stage 1 of 2: Scan Document" : "Stage 1 of 2: Verify Identity";
9033
9102
  } else {