smartcomply-web-sdk 1.0.69 → 1.0.70

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.
@@ -5999,7 +5999,8 @@ var SmartComplySDK = (() => {
5999
5999
  passport: '<rect x="5" y="2" width="14" height="20" rx="2"/><circle cx="12" cy="9" r="2.5"/><path d="M9 15h6"/>',
6000
6000
  car: '<path d="M4 16V11l2-5h12l2 5v5"/><path d="M4 16h16M7 16v2M17 16v2"/><circle cx="7.5" cy="14" r="1"/><circle cx="16.5" cy="14" r="1"/>',
6001
6001
  ballot: '<rect x="4" y="8" width="16" height="12" rx="1"/><path d="M9 8V5a3 3 0 016 0v3"/><path d="M12 12v4"/>',
6002
- refresh: '<path d="M4 4v6h6"/><path d="M20 20v-6h-6"/><path d="M20 10a8 8 0 00-14.6-4.6M4 14a8 8 0 0014.6 4.6"/>'
6002
+ refresh: '<path d="M4 4v6h6"/><path d="M20 20v-6h-6"/><path d="M20 10a8 8 0 00-14.6-4.6M4 14a8 8 0 0014.6 4.6"/>',
6003
+ rotate: '<rect x="7" y="7" width="12" height="12" rx="2"/><path d="M7 4h5a4 4 0 014 4"/><path d="M13 1.5L16 4l-3 2.5"/>'
6003
6004
  };
6004
6005
  function icon(name, size = 24) {
6005
6006
  const d2 = PATHS[name];
@@ -6250,7 +6251,7 @@ var SmartComplySDK = (() => {
6250
6251
  display:flex;align-items:center;gap:8px;
6251
6252
  font-size:12px;color:${theme.textSecondary};line-height:1.5;
6252
6253
  `;
6253
- notice.innerHTML = backOptional ? `<span style="font-size:16px;flex-shrink:0;">ℹ️</span><span>After the <strong style="color:${theme.text}">front</strong>, you can optionally add the <strong style="color:${theme.text}">back</strong> if your card has one.</span>` : `<span style="font-size:16px;flex-shrink:0;">ℹ️</span><span>You'll capture both the <strong style="color:${theme.text}">front</strong> and <strong style="color:${theme.text}">back</strong> of this document.</span>`;
6254
+ notice.innerHTML = backOptional ? `<span style="font-size:16px;flex-shrink:0;">ℹ️</span><span>After the <strong style="color:${theme.text}">front</strong>, you can optionally add the <strong style="color:${theme.text}">back</strong> if your document has one.</span>` : `<span style="font-size:16px;flex-shrink:0;">ℹ️</span><span>You'll capture both the <strong style="color:${theme.text}">front</strong> and <strong style="color:${theme.text}">back</strong> of this document.</span>`;
6254
6255
  this.root.appendChild(notice);
6255
6256
  }
6256
6257
  if (requiresBack && !isCapturingFront && backOptional) {
@@ -6261,7 +6262,7 @@ var SmartComplySDK = (() => {
6261
6262
  border:1.5px dashed ${theme.border};border-radius:10px;cursor:pointer;
6262
6263
  font-size:12.5px;font-weight:600;font-family:inherit;
6263
6264
  `;
6264
- skipBtn.textContent = "Skip — this card has no back side";
6265
+ skipBtn.textContent = "Skip — my document has no back side";
6265
6266
  skipBtn.addEventListener("click", () => {
6266
6267
  this.handleCaptureComplete(null, container, theme, documentType, false, true);
6267
6268
  });
@@ -6508,14 +6509,15 @@ var SmartComplySDK = (() => {
6508
6509
  stepBadge.textContent = "STEP 1 OF 2 · CHECK THE PHOTO";
6509
6510
  this.root.appendChild(stepBadge);
6510
6511
  const img = document.createElement("img");
6511
- const imageUrl = URL.createObjectURL(imageData);
6512
- img.src = imageUrl;
6512
+ let currentUrl = URL.createObjectURL(imageData);
6513
+ img.src = currentUrl;
6513
6514
  img.style.cssText = `
6514
6515
  width:100%;max-height:300px;object-fit:contain;border-radius:${RADIUS.md}px;
6515
6516
  border:2px solid ${theme.border};
6516
6517
  `;
6517
6518
  this.root.appendChild(img);
6518
- img._blobUrl = imageUrl;
6519
+ let currentBlob = imageData;
6520
+ let isRotating = false;
6519
6521
  const label = document.createElement("div");
6520
6522
  label.style.cssText = `color:${theme.text};font-size:${TYPE.base}px;font-weight:700;text-align:center;`;
6521
6523
  label.textContent = "Does the photo look clear?";
@@ -6524,6 +6526,35 @@ var SmartComplySDK = (() => {
6524
6526
  sublabel.style.cssText = `color:${theme.textMuted};font-size:${TYPE.xs}px;text-align:center;line-height:1.6;`;
6525
6527
  sublabel.textContent = "All text must be readable and all four corners must be visible.";
6526
6528
  this.root.appendChild(sublabel);
6529
+ const rotateBtn = document.createElement("button");
6530
+ rotateBtn.type = "button";
6531
+ rotateBtn.setAttribute("aria-label", "Rotate photo 90 degrees");
6532
+ rotateBtn.style.cssText = `
6533
+ display:flex;align-items:center;justify-content:center;gap:6px;
6534
+ margin:0 auto;padding:8px 16px;background:transparent;
6535
+ color:${theme.textSecondary};border:1.5px solid ${theme.border};
6536
+ border-radius:${RADIUS.pill}px;cursor:pointer;
6537
+ font-size:12.5px;font-weight:600;font-family:inherit;
6538
+ `;
6539
+ rotateBtn.innerHTML = `<span style="display:inline-flex;">${icon("rotate", 15)}</span><span>Rotate</span>`;
6540
+ rotateBtn.addEventListener("click", async () => {
6541
+ if (isRotating) return;
6542
+ isRotating = true;
6543
+ rotateBtn.disabled = true;
6544
+ try {
6545
+ const rotated = await this._rotateImage(currentBlob, 90);
6546
+ currentBlob = rotated;
6547
+ URL.revokeObjectURL(currentUrl);
6548
+ currentUrl = URL.createObjectURL(rotated);
6549
+ img.src = currentUrl;
6550
+ this._checkPhotoQuality(currentBlob, qualityNotice, theme);
6551
+ } catch {
6552
+ } finally {
6553
+ isRotating = false;
6554
+ rotateBtn.disabled = false;
6555
+ }
6556
+ });
6557
+ this.root.appendChild(rotateBtn);
6527
6558
  const qualityNotice = document.createElement("div");
6528
6559
  qualityNotice.style.cssText = `
6529
6560
  display:none;width:100%;padding:10px 14px;border-radius:${RADIUS.md}px;
@@ -6532,27 +6563,24 @@ var SmartComplySDK = (() => {
6532
6563
  `;
6533
6564
  qualityNotice.setAttribute("role", "status");
6534
6565
  this.root.appendChild(qualityNotice);
6535
- this._checkPhotoQuality(imageData, qualityNotice, theme);
6566
+ this._checkPhotoQuality(currentBlob, qualityNotice, theme);
6536
6567
  const btnRow = document.createElement("div");
6537
6568
  btnRow.style.cssText = "display:flex;gap:12px;width:100%;";
6538
6569
  const retakeBtn = this.createButton("Retake", theme, false);
6539
6570
  retakeBtn.style.flex = "1";
6540
6571
  retakeBtn.addEventListener("click", () => {
6541
- if (img._blobUrl) {
6542
- URL.revokeObjectURL(img._blobUrl);
6543
- }
6572
+ URL.revokeObjectURL(currentUrl);
6544
6573
  this.root.innerHTML = "";
6545
6574
  this.renderChoiceScreen(container, theme, documentType);
6546
6575
  });
6547
6576
  const useBtn = this.createButton("Use Photo ✓", theme, true);
6548
6577
  useBtn.style.flex = "1";
6549
6578
  useBtn.addEventListener("click", () => {
6550
- if (img._blobUrl) {
6551
- URL.revokeObjectURL(img._blobUrl);
6552
- }
6579
+ URL.revokeObjectURL(currentUrl);
6553
6580
  useBtn.textContent = "Confirming photo…";
6554
6581
  useBtn.disabled = true;
6555
- this.ensureUnderUploadLimit(imageData).then((c2) => this.handleCaptureComplete(c2, container, theme, documentType, viaCamera));
6582
+ rotateBtn.disabled = true;
6583
+ this.ensureUnderUploadLimit(currentBlob).then((c2) => this.handleCaptureComplete(c2, container, theme, documentType, viaCamera));
6556
6584
  });
6557
6585
  btnRow.appendChild(retakeBtn);
6558
6586
  btnRow.appendChild(useBtn);
@@ -6606,6 +6634,49 @@ var SmartComplySDK = (() => {
6606
6634
  imgEl.src = url;
6607
6635
  });
6608
6636
  }
6637
+ /**
6638
+ * Rotate an image blob by a multiple of 90° and re-encode it — used by the
6639
+ * preview screen's rotate control for a document photographed or uploaded
6640
+ * sideways/upside-down. EXIF auto-rotation (see compressImage) only fixes
6641
+ * phone photos that embed correct orientation metadata; it can't help a
6642
+ * document whose actual pixel content is sideways (e.g. the phone itself
6643
+ * was held rotated relative to the document, or a pre-cropped/scanned
6644
+ * upload with no EXIF at all) — that needs the user to say so directly.
6645
+ */
6646
+ _rotateImage(source, degrees) {
6647
+ return new Promise((resolve, reject) => {
6648
+ const url = URL.createObjectURL(source);
6649
+ const imgEl = document.createElement("img");
6650
+ imgEl.onload = () => {
6651
+ URL.revokeObjectURL(url);
6652
+ const { naturalWidth: w2, naturalHeight: h2 } = imgEl;
6653
+ const swapped = degrees === 90 || degrees === 270;
6654
+ const canvas = document.createElement("canvas");
6655
+ canvas.width = swapped ? h2 : w2;
6656
+ canvas.height = swapped ? w2 : h2;
6657
+ const ctx = canvas.getContext("2d");
6658
+ if (!ctx) {
6659
+ reject(new Error("Canvas unavailable"));
6660
+ return;
6661
+ }
6662
+ ctx.translate(canvas.width / 2, canvas.height / 2);
6663
+ ctx.rotate(degrees * Math.PI / 180);
6664
+ ctx.drawImage(imgEl, -w2 / 2, -h2 / 2);
6665
+ canvas.toBlob(
6666
+ (blob) => {
6667
+ blob ? resolve(blob) : reject(new Error("Rotation failed"));
6668
+ },
6669
+ "image/jpeg",
6670
+ 0.95
6671
+ );
6672
+ };
6673
+ imgEl.onerror = () => {
6674
+ URL.revokeObjectURL(url);
6675
+ reject(new Error("Image load failed"));
6676
+ };
6677
+ imgEl.src = url;
6678
+ });
6679
+ }
6609
6680
  handleCaptureComplete(imageData, container, theme, documentType, viaCamera, skipped = false) {
6610
6681
  const requiresBack = this.shouldCaptureBackSide(documentType);
6611
6682
  const wasCapturingFront = this.currentSide === "front";
@@ -6880,8 +6951,9 @@ var SmartComplySDK = (() => {
6880
6951
  if (t2.includes("passport")) return "passport";
6881
6952
  if (t2.includes("driver") || t2.includes("licen")) return "driver's license";
6882
6953
  if (t2.includes("voter") || t2.includes("pvc")) return "voter's card";
6883
- if (t2.includes("national") || t2.includes("nin") || t2.includes("national id")) return "national ID card";
6884
- if (t2.includes("bvn")) return "BVN card";
6954
+ if (t2.includes("nin")) return "NIN document";
6955
+ if (t2.includes("national")) return "national ID card";
6956
+ if (t2.includes("bvn")) return "BVN document";
6885
6957
  return "identity document";
6886
6958
  }
6887
6959
  };
@@ -6927,7 +6999,7 @@ var SmartComplySDK = (() => {
6927
6999
  if (u2.includes("NIN") || u2.includes("NATIONAL IDENTITY NUMBER")) {
6928
7000
  return {
6929
7001
  icon: "🆔",
6930
- desc: isDocumentFlow ? "Capture or upload your NIN card (back optional)" : "Enter your 11-digit National Identification Number (NIN)",
7002
+ desc: isDocumentFlow ? "Capture or upload your NIN (slip or card) — add the back if your document has one" : "Enter your 11-digit National Identification Number (NIN)",
6931
7003
  requiresBack: isDocumentFlow ? "optional" : false
6932
7004
  };
6933
7005
  }