smartcomply-web-sdk 1.0.39 → 1.0.41

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 (33) hide show
  1. package/dist/esm/flow/DocumentCapture.d.ts +8 -0
  2. package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
  3. package/dist/esm/flow/DocumentCapture.js +74 -8
  4. package/dist/esm/flow/DocumentCapture.js.map +1 -1
  5. package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
  6. package/dist/esm/flow/SmartComplyFlow.js +26 -19
  7. package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
  8. package/dist/esm/types/liveness.d.ts +30 -2
  9. package/dist/esm/types/liveness.d.ts.map +1 -1
  10. package/dist/esm/types/onboarding.d.ts +6 -2
  11. package/dist/esm/types/onboarding.d.ts.map +1 -1
  12. package/dist/esm/utils/constants.d.ts +0 -8
  13. package/dist/esm/utils/constants.d.ts.map +1 -1
  14. package/dist/esm/utils/constants.js +0 -9
  15. package/dist/esm/utils/constants.js.map +1 -1
  16. package/dist/flow/DocumentCapture.d.ts +8 -0
  17. package/dist/flow/DocumentCapture.d.ts.map +1 -1
  18. package/dist/flow/DocumentCapture.js +74 -8
  19. package/dist/flow/DocumentCapture.js.map +1 -1
  20. package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
  21. package/dist/flow/SmartComplyFlow.js +26 -19
  22. package/dist/flow/SmartComplyFlow.js.map +1 -1
  23. package/dist/smartcomply.browser.js +203 -25
  24. package/dist/smartcomply.browser.js.map +4 -4
  25. package/dist/types/liveness.d.ts +30 -2
  26. package/dist/types/liveness.d.ts.map +1 -1
  27. package/dist/types/onboarding.d.ts +6 -2
  28. package/dist/types/onboarding.d.ts.map +1 -1
  29. package/dist/utils/constants.d.ts +0 -8
  30. package/dist/utils/constants.d.ts.map +1 -1
  31. package/dist/utils/constants.js +1 -10
  32. package/dist/utils/constants.js.map +1 -1
  33. package/package.json +1 -1
@@ -5625,6 +5625,126 @@ var SmartComplySDK = (() => {
5625
5625
  return THEMES[name] || THEMES.default;
5626
5626
  }
5627
5627
 
5628
+ // src/utils/ImageQuality.ts
5629
+ function calculateSharpness(imageData) {
5630
+ const { width, height, data } = imageData;
5631
+ const gray = new Uint8Array(width * height);
5632
+ for (let i2 = 0; i2 < width * height; i2++) {
5633
+ const idx = i2 * 4;
5634
+ gray[i2] = Math.round(0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]);
5635
+ }
5636
+ const laplacian = new Float32Array(width * height);
5637
+ let sum = 0;
5638
+ let sumSq = 0;
5639
+ let count = 0;
5640
+ for (let y2 = 1; y2 < height - 1; y2++) {
5641
+ for (let x2 = 1; x2 < width - 1; x2++) {
5642
+ const idx = y2 * width + x2;
5643
+ const val = -1 * gray[idx - width] + -1 * gray[idx - 1] + 4 * gray[idx] + -1 * gray[idx + 1] + -1 * gray[idx + width];
5644
+ laplacian[idx] = val;
5645
+ sum += val;
5646
+ sumSq += val * val;
5647
+ count++;
5648
+ }
5649
+ }
5650
+ const mean = sum / count;
5651
+ const variance = sumSq / count - mean * mean;
5652
+ return Math.abs(variance);
5653
+ }
5654
+ function detectGlare(imageData, threshold = 250) {
5655
+ const { data } = imageData;
5656
+ let overexposed = 0;
5657
+ for (let i2 = 0; i2 < data.length; i2 += 4) {
5658
+ const brightness = (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
5659
+ if (brightness > threshold) {
5660
+ overexposed++;
5661
+ }
5662
+ }
5663
+ return overexposed / (data.length / 4);
5664
+ }
5665
+ function calculateContrast(imageData) {
5666
+ const { data } = imageData;
5667
+ let sum = 0;
5668
+ let sumSq = 0;
5669
+ const pixelCount = data.length / 4;
5670
+ for (let i2 = 0; i2 < data.length; i2 += 4) {
5671
+ const gray = 0.299 * data[i2] + 0.587 * data[i2 + 1] + 0.114 * data[i2 + 2];
5672
+ sum += gray;
5673
+ sumSq += gray * gray;
5674
+ }
5675
+ const mean = sum / pixelCount;
5676
+ const variance = sumSq / pixelCount - mean * mean;
5677
+ return Math.sqrt(variance);
5678
+ }
5679
+ function calculateBrightness(imageData) {
5680
+ const { data } = imageData;
5681
+ let sum = 0;
5682
+ for (let i2 = 0; i2 < data.length; i2 += 4) {
5683
+ sum += (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
5684
+ }
5685
+ return sum / (data.length / 4);
5686
+ }
5687
+ function assessQuality(imageData) {
5688
+ const sharpness = calculateSharpness(imageData);
5689
+ const glare = detectGlare(imageData);
5690
+ const contrast = calculateContrast(imageData);
5691
+ const brightness = calculateBrightness(imageData);
5692
+ const isBlurry = sharpness < 80;
5693
+ const hasGlare = glare > 0.15;
5694
+ const isTooDark = brightness < 50;
5695
+ const isTooBright = brightness > 200;
5696
+ const lowContrast = contrast < 30;
5697
+ let score = 100;
5698
+ if (isBlurry) score -= 40;
5699
+ if (hasGlare) score -= 25;
5700
+ if (isTooDark) score -= 20;
5701
+ if (isTooBright) score -= 20;
5702
+ if (lowContrast) score -= 15;
5703
+ const metrics = {
5704
+ sharpness: Math.round(sharpness * 10) / 10,
5705
+ contrast: Math.round(contrast * 10) / 10,
5706
+ brightness: Math.round(brightness * 10) / 10,
5707
+ isBlurry,
5708
+ hasGlare,
5709
+ overallScore: Math.max(0, score)
5710
+ };
5711
+ const issues = [];
5712
+ const recommendations = [];
5713
+ if (isBlurry) {
5714
+ issues.push("Too blurry");
5715
+ recommendations.push("Hold camera steady");
5716
+ recommendations.push("Ensure good lighting");
5717
+ }
5718
+ if (hasGlare) {
5719
+ issues.push("Too much glare");
5720
+ recommendations.push("Avoid direct light on document");
5721
+ recommendations.push("Tilt document slightly");
5722
+ }
5723
+ if (isTooDark) {
5724
+ issues.push("Too dark");
5725
+ recommendations.push("Move to brighter area");
5726
+ recommendations.push("Turn on more lights");
5727
+ }
5728
+ if (isTooBright) {
5729
+ issues.push("Too bright");
5730
+ recommendations.push("Reduce lighting");
5731
+ recommendations.push("Move away from direct sun");
5732
+ }
5733
+ if (lowContrast) {
5734
+ issues.push("Low contrast");
5735
+ recommendations.push("Ensure even lighting");
5736
+ }
5737
+ const isValid = score >= 60;
5738
+ const message = issues.length > 0 ? issues.join(", ") : "Good quality";
5739
+ return {
5740
+ isValid,
5741
+ message,
5742
+ recommendations: recommendations.slice(0, 3),
5743
+ // Max 3 recommendations
5744
+ quality: metrics
5745
+ };
5746
+ }
5747
+
5628
5748
  // src/flow/DocumentCapture.ts
5629
5749
  var DocumentCapture = class {
5630
5750
  constructor() {
@@ -5970,17 +6090,54 @@ var SmartComplySDK = (() => {
5970
6090
  if (!ctx) return;
5971
6091
  ctx.drawImage(this.videoEl, 0, 0, canvas.width, canvas.height);
5972
6092
  canvas.toBlob(
5973
- (blob) => {
6093
+ async (blob) => {
5974
6094
  if (blob) {
5975
6095
  this.stopCamera();
5976
- this.showPreview(container, theme, blob, documentType);
6096
+ this.showLoadingState("Analyzing photo...", theme);
6097
+ const quality = await this._assessImageQuality(canvas, ctx);
6098
+ this.showPreview(container, theme, blob, documentType, quality);
5977
6099
  }
5978
6100
  },
5979
6101
  "image/jpeg",
5980
6102
  0.85
5981
6103
  );
5982
6104
  }
5983
- showPreview(container, theme, imageData, documentType) {
6105
+ /**
6106
+ * Runs the local sharpness/glare/contrast/brightness check against a
6107
+ * captured frame. Returns null (never blocks) if quality can't be computed
6108
+ * — e.g. a tainted canvas from a cross-origin file, or an unsupported browser.
6109
+ */
6110
+ async _assessImageQuality(canvas, ctx) {
6111
+ try {
6112
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
6113
+ return assessQuality(imageData);
6114
+ } catch {
6115
+ return null;
6116
+ }
6117
+ }
6118
+ /** Same as _assessImageQuality but starting from a Blob/File (upload path). */
6119
+ async _assessBlobQuality(blob) {
6120
+ try {
6121
+ const url = URL.createObjectURL(blob);
6122
+ const imgEl = document.createElement("img");
6123
+ await new Promise((resolve, reject) => {
6124
+ imgEl.onload = () => resolve();
6125
+ imgEl.onerror = () => reject(new Error("Image load failed"));
6126
+ imgEl.src = url;
6127
+ });
6128
+ const canvas = document.createElement("canvas");
6129
+ canvas.width = imgEl.naturalWidth;
6130
+ canvas.height = imgEl.naturalHeight;
6131
+ const ctx = canvas.getContext("2d");
6132
+ URL.revokeObjectURL(url);
6133
+ if (!ctx) return null;
6134
+ ctx.drawImage(imgEl, 0, 0);
6135
+ return this._assessImageQuality(canvas, ctx);
6136
+ } catch {
6137
+ return null;
6138
+ }
6139
+ }
6140
+ showPreview(container, theme, imageData, documentType, quality) {
5984
6141
  if (this.root) this.root.innerHTML = "";
5985
6142
  else {
5986
6143
  this.root = document.createElement("div");
@@ -6004,9 +6161,29 @@ var SmartComplySDK = (() => {
6004
6161
  sublabel.style.cssText = `color:${theme.textMuted};font-size:11px;text-align:center;line-height:1.6;`;
6005
6162
  sublabel.textContent = "All text must be readable and all four corners must be visible.";
6006
6163
  this.root.appendChild(sublabel);
6164
+ const qualityIsPoor = !!quality && !quality.isValid;
6165
+ if (qualityIsPoor && quality) {
6166
+ const warn = document.createElement("div");
6167
+ warn.style.cssText = `
6168
+ width:100%;box-sizing:border-box;padding:10px 14px;border-radius:10px;
6169
+ background:#eab30815;border:1px solid #eab30840;
6170
+ color:${theme.text};font-size:12px;line-height:1.5;
6171
+ `;
6172
+ const issueLine = document.createElement("div");
6173
+ issueLine.style.cssText = "font-weight:700;color:#b8860b;";
6174
+ issueLine.textContent = `\u26A0\uFE0F ${quality.message}`;
6175
+ warn.appendChild(issueLine);
6176
+ if (quality.recommendations.length > 0) {
6177
+ const rec = document.createElement("div");
6178
+ rec.style.cssText = "margin-top:4px;color:" + theme.textSecondary + ";";
6179
+ rec.textContent = quality.recommendations.join(" \xB7 ");
6180
+ warn.appendChild(rec);
6181
+ }
6182
+ this.root.appendChild(warn);
6183
+ }
6007
6184
  const btnRow = document.createElement("div");
6008
6185
  btnRow.style.cssText = "display:flex;gap:12px;width:100%;";
6009
- const retakeBtn = this.createButton("Retake", theme, false);
6186
+ const retakeBtn = this.createButton("Retake", theme, qualityIsPoor);
6010
6187
  retakeBtn.style.flex = "1";
6011
6188
  retakeBtn.addEventListener("click", () => {
6012
6189
  if (img._blobUrl) {
@@ -6015,7 +6192,7 @@ var SmartComplySDK = (() => {
6015
6192
  this.root.innerHTML = "";
6016
6193
  this.renderChoiceScreen(container, theme, documentType);
6017
6194
  });
6018
- const useBtn = this.createButton("Use Photo \u2713", theme, true);
6195
+ const useBtn = this.createButton(qualityIsPoor ? "Use Anyway \u26A0" : "Use Photo \u2713", theme, !qualityIsPoor);
6019
6196
  useBtn.style.flex = "1";
6020
6197
  useBtn.addEventListener("click", () => {
6021
6198
  if (img._blobUrl) {
@@ -6053,8 +6230,9 @@ var SmartComplySDK = (() => {
6053
6230
  };
6054
6231
  this.resolveCapture?.(result);
6055
6232
  }
6056
- handleFileUploadComplete(imageData, container, theme, documentType) {
6057
- this.showPreview(container, theme, imageData, documentType);
6233
+ async handleFileUploadComplete(imageData, container, theme, documentType) {
6234
+ const quality = await this._assessBlobQuality(imageData);
6235
+ this.showPreview(container, theme, imageData, documentType, quality);
6058
6236
  }
6059
6237
  validateFile(file) {
6060
6238
  const allowedTypes = ["image/jpeg", "image/jpg", "image/png"];
@@ -6569,7 +6747,7 @@ var SmartComplySDK = (() => {
6569
6747
  this.modal = document.createElement("div");
6570
6748
  this.modal.id = "sc-flow-modal";
6571
6749
  this.modal.style.cssText = `
6572
- width:100%;max-width:580px;max-height:92vh;
6750
+ width:100%;max-width:580px;max-height:95vh;
6573
6751
  background:${this.theme.bg};
6574
6752
  border-radius:20px;overflow:hidden;
6575
6753
  box-shadow:${this.theme.shadow};
@@ -6591,7 +6769,7 @@ var SmartComplySDK = (() => {
6591
6769
  this.contentArea = document.createElement("div");
6592
6770
  this.contentArea.id = "sc-flow-content";
6593
6771
  this.contentArea.style.cssText = `
6594
- flex:1;overflow-y:auto;padding:20px 24px;
6772
+ flex:1;overflow-y:auto;padding:16px 20px;
6595
6773
  -webkit-overflow-scrolling:touch;
6596
6774
  `;
6597
6775
  this.modal.appendChild(this.contentArea);
@@ -6724,15 +6902,15 @@ var SmartComplySDK = (() => {
6724
6902
  }
6725
6903
  // ── Welcome ────────────────────────────────────────────────────
6726
6904
  renderWelcome(container) {
6727
- container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;";
6905
+ container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;";
6728
6906
  const isDocFlow = this.isDocumentFlow();
6729
6907
  const topRow = document.createElement("div");
6730
- topRow.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:10px;";
6908
+ topRow.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:8px;";
6731
6909
  const iconWrap = document.createElement("div");
6732
6910
  iconWrap.style.cssText = `
6733
- width:58px;height:58px;border-radius:16px;
6911
+ width:50px;height:50px;border-radius:14px;
6734
6912
  display:flex;align-items:center;justify-content:center;
6735
- font-size:26px;
6913
+ font-size:22px;
6736
6914
  background:linear-gradient(135deg,${this.theme.primary}22,${this.theme.primaryGlow});
6737
6915
  border:1.5px solid ${this.theme.primary}33;
6738
6916
  `;
@@ -6786,7 +6964,7 @@ var SmartComplySDK = (() => {
6786
6964
  const row = document.createElement("div");
6787
6965
  row.style.cssText = `
6788
6966
  display:flex;align-items:center;gap:12px;
6789
- padding:9px 14px;
6967
+ padding:8px 14px;
6790
6968
  background:${this.theme.cardBg};
6791
6969
  ${i2 < steps.length - 1 ? `border-bottom:1px solid ${this.theme.border};` : ""}
6792
6970
  `;
@@ -6823,12 +7001,12 @@ var SmartComplySDK = (() => {
6823
7001
  display:flex;align-items:center;gap:6px;
6824
7002
  color:${this.theme.textMuted};font-size:10.5px;
6825
7003
  `;
6826
- trust.textContent = "\u{1F512} End-to-end encrypted \xB7 Powered by Adhere";
7004
+ trust.textContent = "\u{1F512} End-to-end encrypted";
6827
7005
  container.appendChild(trust);
6828
7006
  }
6829
7007
  // ── Country Select ──────────────────────────────────────────────
6830
7008
  renderCountrySelect(container) {
6831
- container.style.cssText += "display:flex;flex-direction:column;gap:16px;";
7009
+ container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
6832
7010
  const channels = this.sdkConfig?.channels || {};
6833
7011
  const countries = Object.keys(channels);
6834
7012
  if (countries.length === 1) {
@@ -6856,7 +7034,7 @@ var SmartComplySDK = (() => {
6856
7034
  }
6857
7035
  // ── ID Type Select ──────────────────────────────────────────────
6858
7036
  renderIdTypeSelect(container) {
6859
- container.style.cssText += "display:flex;flex-direction:column;gap:16px;";
7037
+ container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
6860
7038
  const globalDocFlow = this.isDocumentFlow();
6861
7039
  const title = this.createStepTitle(globalDocFlow ? "Select document type" : "Select ID type");
6862
7040
  container.appendChild(title);
@@ -6911,7 +7089,7 @@ var SmartComplySDK = (() => {
6911
7089
  }
6912
7090
  // ── ID Input (data verification — dynamic fields) ─────────────
6913
7091
  renderIdInput(container) {
6914
- container.style.cssText += "display:flex;flex-direction:column;gap:16px;";
7092
+ container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
6915
7093
  const dataFields = this.selectedChannelFields.filter((f2) => f2.type !== "upload");
6916
7094
  const info = resolveIdTypeInfo(this.selectedIdTypeName || this.selectedIdType, false);
6917
7095
  const cleanStepTitle = (this.selectedIdTypeName || this.selectedIdType).replace(/\s*with\s+face\s*/gi, " ").replace(/\s*\(NIN\)\s*/gi, " ").replace(/\s*\(BVN\)\s*/gi, " ").replace(/\s{2,}/g, " ").trim();
@@ -6937,7 +7115,7 @@ var SmartComplySDK = (() => {
6937
7115
  }
6938
7116
  const inputEls = [];
6939
7117
  for (const field of dataFields) {
6940
- const fieldKey = field.label.toLowerCase().replace(/[^a-z0-9]+/g, "_");
7118
+ const fieldKey = field.label;
6941
7119
  const fieldWrap = document.createElement("div");
6942
7120
  fieldWrap.style.cssText = "display:flex;flex-direction:column;gap:6px;";
6943
7121
  const label = document.createElement("label");
@@ -7066,7 +7244,7 @@ var SmartComplySDK = (() => {
7066
7244
  identity_type_id: this.selectedChannelId,
7067
7245
  fields: { ...this.collectedFields }
7068
7246
  });
7069
- if (this.verificationResult.status === "verified" || this.verificationResult.status === "success") {
7247
+ if (this.verificationResult.status === "success") {
7070
7248
  const fieldValues = Object.values(this.collectedFields);
7071
7249
  this.idNumber = fieldValues[0] || "";
7072
7250
  this.identityCheckId = this.verificationResult.data?.identity_check_id || null;
@@ -7100,7 +7278,7 @@ var SmartComplySDK = (() => {
7100
7278
  }
7101
7279
  // ── Document Capture ────────────────────────────────────────────
7102
7280
  renderDocumentCapture(container) {
7103
- container.style.cssText += "display:flex;flex-direction:column;gap:16px;";
7281
+ container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
7104
7282
  const rawName = this.selectedIdTypeName || this.selectedIdType || "your document";
7105
7283
  const cleanDocTitle = rawName.replace(/\s*with\s+face\s*/gi, " ").replace(/\s*\(NIN\)\s*/gi, " ").replace(/\s*\(BVN\)\s*/gi, " ").replace(/\s{2,}/g, " ").trim();
7106
7284
  const title = this.createStepTitle(`Capture ${cleanDocTitle}`);
@@ -7399,7 +7577,7 @@ var SmartComplySDK = (() => {
7399
7577
  }
7400
7578
  // ── Liveness ────────────────────────────────────────────────────
7401
7579
  renderLiveness(container) {
7402
- container.style.cssText += "display:flex;flex-direction:column;gap:16px;";
7580
+ container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
7403
7581
  const title = this.createStepTitle("Face verification");
7404
7582
  container.appendChild(title);
7405
7583
  const desc = document.createElement("div");
@@ -7679,7 +7857,7 @@ var SmartComplySDK = (() => {
7679
7857
  const header = document.createElement("div");
7680
7858
  header.style.cssText = `
7681
7859
  display:flex;align-items:center;justify-content:space-between;
7682
- padding:14px 20px;border-bottom:1px solid ${this.theme.border};
7860
+ padding:11px 20px;border-bottom:1px solid ${this.theme.border};
7683
7861
  `;
7684
7862
  const leftArea = document.createElement("div");
7685
7863
  leftArea.style.cssText = "display:flex;align-items:center;gap:8px;min-width:0;";
@@ -7742,7 +7920,7 @@ var SmartComplySDK = (() => {
7742
7920
  createFooter() {
7743
7921
  const footer = document.createElement("div");
7744
7922
  footer.style.cssText = `
7745
- padding:12px 20px;border-top:1px solid ${this.theme.border};
7923
+ padding:9px 20px;border-top:1px solid ${this.theme.border};
7746
7924
  display:flex;align-items:center;justify-content:center;gap:6px;
7747
7925
  `;
7748
7926
  const powered = document.createElement("span");
@@ -7767,7 +7945,7 @@ var SmartComplySDK = (() => {
7767
7945
  createOptionCard(title, subtitle, onClick) {
7768
7946
  const card = document.createElement("button");
7769
7947
  card.style.cssText = `
7770
- width:100%;padding:16px;border-radius:12px;
7948
+ width:100%;padding:13px 16px;border-radius:12px;
7771
7949
  background:${this.theme.cardBg};
7772
7950
  border:2px solid ${this.theme.border};
7773
7951
  cursor:pointer;text-align:left;