smartcomply-web-sdk 1.0.33 → 1.0.35
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.
- package/dist/esm/flow/DocumentCapture.d.ts +2 -0
- package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/esm/flow/DocumentCapture.js +122 -113
- package/dist/esm/flow/DocumentCapture.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts +13 -2
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +20 -8
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/flow/DocumentCapture.d.ts +2 -0
- package/dist/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/flow/DocumentCapture.js +122 -113
- package/dist/flow/DocumentCapture.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts +13 -2
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +20 -8
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +136 -115
- package/dist/smartcomply.browser.js.map +3 -3
- package/package.json +1 -1
|
@@ -5178,7 +5178,7 @@ var SmartComplySDK = (() => {
|
|
|
5178
5178
|
let attempts = 0;
|
|
5179
5179
|
let stableFrames = 0;
|
|
5180
5180
|
let missedFrames = 0;
|
|
5181
|
-
const STABLE_REQUIRED =
|
|
5181
|
+
const STABLE_REQUIRED = 4;
|
|
5182
5182
|
const MISS_TOLERANCE = 2;
|
|
5183
5183
|
const MAX_WAIT_MS = 8e3;
|
|
5184
5184
|
const startTime = performance.now();
|
|
@@ -5210,7 +5210,7 @@ var SmartComplySDK = (() => {
|
|
|
5210
5210
|
requestAnimationFrame(checkFace);
|
|
5211
5211
|
});
|
|
5212
5212
|
ui2.updateInstruction("Hold still...");
|
|
5213
|
-
await new Promise((r2) => setTimeout(r2,
|
|
5213
|
+
await new Promise((r2) => setTimeout(r2, 500));
|
|
5214
5214
|
const autoshotBlob = await this.captureFrame(videoForDetection);
|
|
5215
5215
|
tempVideo.pause();
|
|
5216
5216
|
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
@@ -5352,10 +5352,16 @@ var SmartComplySDK = (() => {
|
|
|
5352
5352
|
* Capture a single sharp frame from a video element as a JPEG Blob.
|
|
5353
5353
|
* Retries up to maxAttempts times (300 ms apart) if the Laplacian blur
|
|
5354
5354
|
* score is below threshold, then falls back to the best frame captured.
|
|
5355
|
+
*
|
|
5356
|
+
* Threshold calibration:
|
|
5357
|
+
* The server rejects images with OpenCV Laplacian variance < 80.
|
|
5358
|
+
* This SDK uses sum-of-squared Laplacian (different normalisation) —
|
|
5359
|
+
* empirically ~100 here corresponds to ~80 server-side. We aim for 120
|
|
5360
|
+
* to give a comfortable safety margin.
|
|
5355
5361
|
*/
|
|
5356
|
-
async captureFrame(video, maxAttempts =
|
|
5357
|
-
const MIN_BLUR_SCORE =
|
|
5358
|
-
const RETRY_DELAY_MS2 =
|
|
5362
|
+
async captureFrame(video, maxAttempts = 5) {
|
|
5363
|
+
const MIN_BLUR_SCORE = 120;
|
|
5364
|
+
const RETRY_DELAY_MS2 = 200;
|
|
5359
5365
|
let bestBlob = null;
|
|
5360
5366
|
let bestScore = -1;
|
|
5361
5367
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
@@ -5382,8 +5388,13 @@ var SmartComplySDK = (() => {
|
|
|
5382
5388
|
throw new Error("Failed to capture frame");
|
|
5383
5389
|
}
|
|
5384
5390
|
/**
|
|
5385
|
-
* Laplacian
|
|
5386
|
-
* Higher score = sharper image.
|
|
5391
|
+
* Sum-of-squared Laplacian blur metric on a centre-cropped region (320×240 max).
|
|
5392
|
+
* Higher score = sharper image.
|
|
5393
|
+
*
|
|
5394
|
+
* NOTE: This is sum-of-squares, NOT Laplacian variance (which OpenCV uses).
|
|
5395
|
+
* The two scales differ — empirically, a score of ~120 here corresponds to
|
|
5396
|
+
* ~80 on the server side (OpenCV cv2.Laplacian().var()). Keep MIN_BLUR_SCORE
|
|
5397
|
+
* in captureFrame() calibrated against this.
|
|
5387
5398
|
*/
|
|
5388
5399
|
_computeBlurScore(canvas) {
|
|
5389
5400
|
const ctx = canvas.getContext("2d");
|
|
@@ -5651,126 +5662,128 @@ var SmartComplySDK = (() => {
|
|
|
5651
5662
|
}
|
|
5652
5663
|
renderChoiceScreen(container, theme, documentType) {
|
|
5653
5664
|
this.root = document.createElement("div");
|
|
5654
|
-
this.root.style.cssText =
|
|
5665
|
+
this.root.style.cssText = `
|
|
5666
|
+
display:flex;flex-direction:column;gap:0;padding:0;width:100%;
|
|
5667
|
+
font-family:system-ui,-apple-system,sans-serif;
|
|
5668
|
+
`;
|
|
5655
5669
|
const docLabel = this.getDocumentLabel(documentType);
|
|
5656
5670
|
const requiresBack = this.shouldCaptureBackSide(documentType);
|
|
5657
5671
|
const isCapturingFront = this.currentSide === "front";
|
|
5658
|
-
const
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5672
|
+
const sideLabel = requiresBack ? isCapturingFront ? " \u2014 Front side" : " \u2014 Back side" : "";
|
|
5673
|
+
const headerRow = document.createElement("div");
|
|
5674
|
+
headerRow.style.cssText = `
|
|
5675
|
+
display:flex;align-items:center;gap:14px;
|
|
5676
|
+
padding:16px 20px 14px;
|
|
5677
|
+
`;
|
|
5678
|
+
const iconBox = document.createElement("div");
|
|
5679
|
+
iconBox.style.cssText = `
|
|
5680
|
+
width:48px;height:48px;border-radius:14px;flex-shrink:0;
|
|
5663
5681
|
display:flex;align-items:center;justify-content:center;
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
box-shadow:0 8px 32px ${theme.primary}20;
|
|
5682
|
+
font-size:24px;
|
|
5683
|
+
background:${theme.primary}15;
|
|
5684
|
+
border:1.5px solid ${theme.primary}30;
|
|
5668
5685
|
`;
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
<div style="display:flex;gap:8px;align-items:center;">
|
|
5707
|
-
<div style="width:12px;height:12px;border-radius:50%;background:${isCapturingFront ? theme.primary : theme.border};transition:all 0.3s ease;"></div>
|
|
5708
|
-
<div style="width:32px;height:2px;background:${theme.border};border-radius:1px;"></div>
|
|
5709
|
-
<div style="width:12px;height:12px;border-radius:50%;background:${!isCapturingFront ? theme.primary : theme.border};transition:all 0.3s ease;"></div>
|
|
5710
|
-
</div>
|
|
5711
|
-
<span style="color:${theme.text};font-size:13px;font-weight:600;">${isCapturingFront ? "Step 1 of 2" : "Step 2 of 2"}</span>
|
|
5712
|
-
`;
|
|
5713
|
-
this.root.appendChild(progress);
|
|
5714
|
-
}
|
|
5715
|
-
const actionsContainer = document.createElement("div");
|
|
5716
|
-
actionsContainer.style.cssText = "display:flex;flex-direction:column;gap:16px;width:100%;max-width:320px;";
|
|
5717
|
-
const cameraBtn = this.createEnhancedButton(
|
|
5718
|
-
"\u{1F4F7} Take Photo with Camera",
|
|
5719
|
-
theme,
|
|
5720
|
-
true,
|
|
5721
|
-
"Open camera to capture document"
|
|
5722
|
-
);
|
|
5686
|
+
iconBox.textContent = this._docIcon(documentType);
|
|
5687
|
+
const titleBlock = document.createElement("div");
|
|
5688
|
+
const titleEl = document.createElement("div");
|
|
5689
|
+
titleEl.style.cssText = `color:${theme.text};font-size:16px;font-weight:700;line-height:1.3;`;
|
|
5690
|
+
titleEl.textContent = `Capture your ${docLabel}`;
|
|
5691
|
+
const subEl = document.createElement("div");
|
|
5692
|
+
subEl.style.cssText = `color:${theme.textSecondary};font-size:12px;margin-top:2px;`;
|
|
5693
|
+
subEl.textContent = requiresBack ? isCapturingFront ? "Step 1 of 2 \u2014 front side" : "Step 2 of 2 \u2014 back side" : "Choose how to provide your document";
|
|
5694
|
+
titleBlock.appendChild(titleEl);
|
|
5695
|
+
titleBlock.appendChild(subEl);
|
|
5696
|
+
headerRow.appendChild(iconBox);
|
|
5697
|
+
headerRow.appendChild(titleBlock);
|
|
5698
|
+
this.root.appendChild(headerRow);
|
|
5699
|
+
const btnRow = document.createElement("div");
|
|
5700
|
+
btnRow.style.cssText = `
|
|
5701
|
+
display:grid;grid-template-columns:1fr 1fr;gap:10px;
|
|
5702
|
+
padding:0 20px 14px;
|
|
5703
|
+
`;
|
|
5704
|
+
const cameraBtn = document.createElement("button");
|
|
5705
|
+
cameraBtn.style.cssText = `
|
|
5706
|
+
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
5707
|
+
gap:8px;padding:16px 12px;
|
|
5708
|
+
background:${theme.primary};color:#fff;
|
|
5709
|
+
border:none;border-radius:14px;cursor:pointer;
|
|
5710
|
+
font-size:13px;font-weight:600;font-family:inherit;
|
|
5711
|
+
transition:all 0.15s ease;min-height:80px;
|
|
5712
|
+
box-shadow:0 4px 14px ${theme.primary}30;
|
|
5713
|
+
`;
|
|
5714
|
+
cameraBtn.innerHTML = `<span style="font-size:26px;">\u{1F4F7}</span><span>Take Photo</span><span style="font-size:10px;opacity:0.8;font-weight:500;">Use camera</span>`;
|
|
5715
|
+
cameraBtn.addEventListener("mouseenter", () => {
|
|
5716
|
+
cameraBtn.style.opacity = "0.9";
|
|
5717
|
+
cameraBtn.style.transform = "translateY(-1px)";
|
|
5718
|
+
});
|
|
5719
|
+
cameraBtn.addEventListener("mouseleave", () => {
|
|
5720
|
+
cameraBtn.style.opacity = "1";
|
|
5721
|
+
cameraBtn.style.transform = "none";
|
|
5722
|
+
});
|
|
5723
5723
|
cameraBtn.addEventListener("click", () => {
|
|
5724
|
-
this.showLoadingState("Opening camera...", theme);
|
|
5725
5724
|
this.root.innerHTML = "";
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5725
|
+
this.renderCameraView(this.root, theme, documentType);
|
|
5726
|
+
});
|
|
5727
|
+
const uploadBtn = document.createElement("button");
|
|
5728
|
+
uploadBtn.style.cssText = `
|
|
5729
|
+
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
5730
|
+
gap:8px;padding:16px 12px;
|
|
5731
|
+
background:${theme.cardBg || theme.inputBg};color:${theme.text};
|
|
5732
|
+
border:2px solid ${theme.border};border-radius:14px;cursor:pointer;
|
|
5733
|
+
font-size:13px;font-weight:600;font-family:inherit;
|
|
5734
|
+
transition:all 0.15s ease;min-height:80px;
|
|
5735
|
+
`;
|
|
5736
|
+
uploadBtn.innerHTML = `<span style="font-size:26px;">\u{1F5BC}\uFE0F</span><span>From Gallery</span><span style="font-size:10px;color:${theme.textMuted || theme.textSecondary};font-weight:500;">Upload file</span>`;
|
|
5737
|
+
uploadBtn.addEventListener("mouseenter", () => {
|
|
5738
|
+
uploadBtn.style.borderColor = theme.primary;
|
|
5739
|
+
uploadBtn.style.transform = "translateY(-1px)";
|
|
5740
|
+
});
|
|
5741
|
+
uploadBtn.addEventListener("mouseleave", () => {
|
|
5742
|
+
uploadBtn.style.borderColor = theme.border;
|
|
5743
|
+
uploadBtn.style.transform = "none";
|
|
5729
5744
|
});
|
|
5730
|
-
actionsContainer.appendChild(cameraBtn);
|
|
5731
|
-
const uploadBtn = this.createEnhancedButton(
|
|
5732
|
-
"\u{1F4C1} Choose from Gallery",
|
|
5733
|
-
theme,
|
|
5734
|
-
false,
|
|
5735
|
-
"Upload document image from device"
|
|
5736
|
-
);
|
|
5737
5745
|
uploadBtn.addEventListener("click", () => {
|
|
5738
5746
|
this.handleFileUpload(container, theme);
|
|
5739
5747
|
});
|
|
5740
|
-
|
|
5741
|
-
|
|
5748
|
+
btnRow.appendChild(cameraBtn);
|
|
5749
|
+
btnRow.appendChild(uploadBtn);
|
|
5750
|
+
this.root.appendChild(btnRow);
|
|
5742
5751
|
const tips = document.createElement("div");
|
|
5743
5752
|
tips.style.cssText = `
|
|
5744
|
-
|
|
5745
|
-
background:${theme.shimmer
|
|
5746
|
-
|
|
5753
|
+
margin:0 20px 16px;padding:12px 14px;
|
|
5754
|
+
background:${theme.shimmer || theme.cardBg};
|
|
5755
|
+
border:1px solid ${theme.border};border-radius:12px;
|
|
5747
5756
|
`;
|
|
5748
|
-
const
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
tips.appendChild(tipsTitle);
|
|
5752
|
-
const tipsList = document.createElement("div");
|
|
5753
|
-
tipsList.style.cssText = `display:grid;gap:8px;font-size:13px;color:${theme.textSecondary};line-height:1.6;`;
|
|
5754
|
-
tipsList.innerHTML = `
|
|
5755
|
-
<div style="display:flex;gap:8px;align-items:flex-start;">
|
|
5756
|
-
<span style="color:${theme.primary};font-weight:bold;">\u2713</span>
|
|
5757
|
-
<span>Lay document flat on a solid surface</span>
|
|
5758
|
-
</div>
|
|
5759
|
-
<div style="display:flex;gap:8px;align-items:flex-start;">
|
|
5760
|
-
<span style="color:${theme.primary};font-weight:bold;">\u2713</span>
|
|
5761
|
-
<span>Ensure all four corners are visible</span>
|
|
5762
|
-
</div>
|
|
5763
|
-
<div style="display:flex;gap:8px;align-items:flex-start;">
|
|
5764
|
-
<span style="color:${theme.primary};font-weight:bold;">\u2713</span>
|
|
5765
|
-
<span>Use good lighting, avoid glare</span>
|
|
5766
|
-
</div>
|
|
5767
|
-
<div style="display:flex;gap:8px;align-items:flex-start;">
|
|
5768
|
-
<span style="color:${theme.primary};font-weight:bold;">\u2713</span>
|
|
5769
|
-
<span>Make sure all text is clearly readable</span>
|
|
5770
|
-
</div>
|
|
5757
|
+
const tipsGrid = document.createElement("div");
|
|
5758
|
+
tipsGrid.style.cssText = `
|
|
5759
|
+
display:grid;grid-template-columns:1fr 1fr;gap:6px 12px;
|
|
5771
5760
|
`;
|
|
5772
|
-
|
|
5761
|
+
const tipItems = [
|
|
5762
|
+
"Lay document flat",
|
|
5763
|
+
"All 4 corners visible",
|
|
5764
|
+
"Good lighting, no glare",
|
|
5765
|
+
"All text clearly readable"
|
|
5766
|
+
];
|
|
5767
|
+
tipItems.forEach((t2) => {
|
|
5768
|
+
const row = document.createElement("div");
|
|
5769
|
+
row.style.cssText = `display:flex;align-items:center;gap:5px;font-size:11px;color:${theme.textSecondary};`;
|
|
5770
|
+
row.innerHTML = `<span style="color:${theme.primary};font-weight:700;flex-shrink:0;">\u2713</span><span>${t2}</span>`;
|
|
5771
|
+
tipsGrid.appendChild(row);
|
|
5772
|
+
});
|
|
5773
|
+
tips.appendChild(tipsGrid);
|
|
5773
5774
|
this.root.appendChild(tips);
|
|
5775
|
+
if (requiresBack && isCapturingFront) {
|
|
5776
|
+
const notice = document.createElement("div");
|
|
5777
|
+
notice.style.cssText = `
|
|
5778
|
+
margin:0 20px 16px;padding:10px 14px;
|
|
5779
|
+
background:${theme.primary}0D;
|
|
5780
|
+
border:1px solid ${theme.primary}25;border-radius:10px;
|
|
5781
|
+
display:flex;align-items:center;gap:8px;
|
|
5782
|
+
font-size:12px;color:${theme.textSecondary};line-height:1.5;
|
|
5783
|
+
`;
|
|
5784
|
+
notice.innerHTML = `<span style="font-size:16px;flex-shrink:0;">\u2139\uFE0F</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>`;
|
|
5785
|
+
this.root.appendChild(notice);
|
|
5786
|
+
}
|
|
5774
5787
|
const fileInput = document.createElement("input");
|
|
5775
5788
|
fileInput.type = "file";
|
|
5776
5789
|
fileInput.accept = "image/jpeg,image/png";
|
|
@@ -5779,18 +5792,26 @@ var SmartComplySDK = (() => {
|
|
|
5779
5792
|
fileInput.addEventListener("change", (e2) => {
|
|
5780
5793
|
const file = e2.target.files?.[0];
|
|
5781
5794
|
if (file) {
|
|
5782
|
-
const
|
|
5783
|
-
if (
|
|
5784
|
-
this.showError(
|
|
5795
|
+
const err = this.validateFile(file);
|
|
5796
|
+
if (err) {
|
|
5797
|
+
this.showError(err);
|
|
5785
5798
|
return;
|
|
5786
5799
|
}
|
|
5787
5800
|
this.showLoadingState("Processing image...", theme);
|
|
5788
|
-
this.compressImage(file, 1280, 960, 0.85).then((
|
|
5801
|
+
this.compressImage(file, 1280, 960, 0.85).then((c2) => this.handleFileUploadComplete(c2, container, theme, documentType)).catch(() => this.handleFileUploadComplete(file, container, theme, documentType));
|
|
5789
5802
|
}
|
|
5790
5803
|
});
|
|
5791
5804
|
this.root.appendChild(fileInput);
|
|
5792
5805
|
container.appendChild(this.root);
|
|
5793
5806
|
}
|
|
5807
|
+
/** Returns an appropriate emoji icon for the document type. */
|
|
5808
|
+
_docIcon(documentType) {
|
|
5809
|
+
const t2 = documentType.toLowerCase();
|
|
5810
|
+
if (t2.includes("passport")) return "\u{1F4D8}";
|
|
5811
|
+
if (t2.includes("driver") || t2.includes("licen")) return "\u{1F697}";
|
|
5812
|
+
if (t2.includes("voter") || t2.includes("pvc")) return "\u{1F5F3}\uFE0F";
|
|
5813
|
+
return "\u{1FAAA}";
|
|
5814
|
+
}
|
|
5794
5815
|
renderCameraView(container, theme, documentType) {
|
|
5795
5816
|
const wrapper = document.createElement("div");
|
|
5796
5817
|
wrapper.style.cssText = "position:relative;width:100%;border-radius:12px;overflow:hidden;background:#000;";
|