smartcomply-web-sdk 1.0.68 → 1.0.69
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 +10 -0
- package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/esm/flow/DocumentCapture.js +82 -4
- package/dist/esm/flow/DocumentCapture.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts +12 -0
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +158 -17
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.js +13 -3
- package/dist/esm/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/esm/utils/ImageQuality.d.ts +12 -1
- package/dist/esm/utils/ImageQuality.d.ts.map +1 -1
- package/dist/esm/utils/ImageQuality.js +12 -1
- package/dist/esm/utils/ImageQuality.js.map +1 -1
- package/dist/flow/DocumentCapture.d.ts +10 -0
- package/dist/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/flow/DocumentCapture.js +82 -4
- package/dist/flow/DocumentCapture.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +12 -0
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +158 -17
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/modules/liveness/LivenessUI.js +13 -3
- package/dist/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/smartcomply.browser.js +305 -28
- package/dist/smartcomply.browser.js.map +4 -4
- package/dist/utils/ImageQuality.d.ts +12 -1
- package/dist/utils/ImageQuality.d.ts.map +1 -1
- package/dist/utils/ImageQuality.js +12 -1
- package/dist/utils/ImageQuality.js.map +1 -1
- package/package.json +1 -1
|
@@ -4709,6 +4709,7 @@ var SmartComplySDK = (() => {
|
|
|
4709
4709
|
this.videoEl.autoplay = true;
|
|
4710
4710
|
this.videoEl.playsInline = true;
|
|
4711
4711
|
this.videoEl.muted = true;
|
|
4712
|
+
this.videoEl.setAttribute("aria-label", "Live camera preview for face verification");
|
|
4712
4713
|
this.videoEl.style.cssText = `
|
|
4713
4714
|
width:100%;height:100%;display:block;
|
|
4714
4715
|
filter:brightness(1.03) contrast(1.03);
|
|
@@ -4730,6 +4731,7 @@ var SmartComplySDK = (() => {
|
|
|
4730
4731
|
this.guideWrapEl = guideWrap;
|
|
4731
4732
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
4732
4733
|
svg.setAttribute("viewBox", "0 0 140 180");
|
|
4734
|
+
svg.setAttribute("aria-hidden", "true");
|
|
4733
4735
|
svg.style.cssText = "width:100%;height:100%;overflow:visible;";
|
|
4734
4736
|
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
4735
4737
|
const clip = document.createElementNS("http://www.w3.org/2000/svg", "clipPath");
|
|
@@ -4769,12 +4771,12 @@ var SmartComplySDK = (() => {
|
|
|
4769
4771
|
confOval.setAttribute("ry", "86");
|
|
4770
4772
|
confOval.setAttribute("fill", "none");
|
|
4771
4773
|
confOval.setAttribute("stroke", this.colors.primary);
|
|
4772
|
-
confOval.setAttribute("stroke-width", "
|
|
4773
|
-
confOval.setAttribute("stroke-linecap", "
|
|
4774
|
+
confOval.setAttribute("stroke-width", "3");
|
|
4775
|
+
confOval.setAttribute("stroke-linecap", "butt");
|
|
4774
4776
|
const circ = 2 * Math.PI * Math.sqrt((64 * 64 + 86 * 86) / 2);
|
|
4775
4777
|
confOval.setAttribute("stroke-dasharray", `${circ}`);
|
|
4776
4778
|
confOval.setAttribute("stroke-dashoffset", `${circ}`);
|
|
4777
|
-
confOval.style.cssText = "transition:stroke-dashoffset 0.2s linear,stroke 0.3s ease;transform:rotate(-90deg);transform-origin:70px 90px;";
|
|
4779
|
+
confOval.style.cssText = "transition:stroke-dashoffset 0.2s linear,stroke 0.3s ease;transform:rotate(-90deg);transform-origin:70px 90px;filter:drop-shadow(0 0 3px rgba(0,0,0,0.25));";
|
|
4778
4780
|
this.confidenceRingEl = confOval;
|
|
4779
4781
|
const bracketGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
4780
4782
|
bracketGroup.setAttribute("stroke", this.colors.primary);
|
|
@@ -4822,6 +4824,8 @@ var SmartComplySDK = (() => {
|
|
|
4822
4824
|
padding:14px 16px 32px;
|
|
4823
4825
|
`;
|
|
4824
4826
|
this.instructionEl = document.createElement("div");
|
|
4827
|
+
this.instructionEl.setAttribute("aria-live", "polite");
|
|
4828
|
+
this.instructionEl.setAttribute("role", "status");
|
|
4825
4829
|
this.instructionEl.style.cssText = `
|
|
4826
4830
|
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
4827
4831
|
`;
|
|
@@ -5857,6 +5861,126 @@ var SmartComplySDK = (() => {
|
|
|
5857
5861
|
return Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);
|
|
5858
5862
|
}
|
|
5859
5863
|
|
|
5864
|
+
// src/utils/ImageQuality.ts
|
|
5865
|
+
function calculateSharpness(imageData) {
|
|
5866
|
+
const { width, height, data } = imageData;
|
|
5867
|
+
const gray = new Uint8Array(width * height);
|
|
5868
|
+
for (let i2 = 0; i2 < width * height; i2++) {
|
|
5869
|
+
const idx = i2 * 4;
|
|
5870
|
+
gray[i2] = Math.round(0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]);
|
|
5871
|
+
}
|
|
5872
|
+
const laplacian = new Float32Array(width * height);
|
|
5873
|
+
let sum = 0;
|
|
5874
|
+
let sumSq = 0;
|
|
5875
|
+
let count = 0;
|
|
5876
|
+
for (let y2 = 1; y2 < height - 1; y2++) {
|
|
5877
|
+
for (let x2 = 1; x2 < width - 1; x2++) {
|
|
5878
|
+
const idx = y2 * width + x2;
|
|
5879
|
+
const val = -1 * gray[idx - width] + -1 * gray[idx - 1] + 4 * gray[idx] + -1 * gray[idx + 1] + -1 * gray[idx + width];
|
|
5880
|
+
laplacian[idx] = val;
|
|
5881
|
+
sum += val;
|
|
5882
|
+
sumSq += val * val;
|
|
5883
|
+
count++;
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
const mean = sum / count;
|
|
5887
|
+
const variance = sumSq / count - mean * mean;
|
|
5888
|
+
return Math.abs(variance);
|
|
5889
|
+
}
|
|
5890
|
+
function detectGlare(imageData, threshold = 250) {
|
|
5891
|
+
const { data } = imageData;
|
|
5892
|
+
let overexposed = 0;
|
|
5893
|
+
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5894
|
+
const brightness = (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
|
|
5895
|
+
if (brightness > threshold) {
|
|
5896
|
+
overexposed++;
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
return overexposed / (data.length / 4);
|
|
5900
|
+
}
|
|
5901
|
+
function calculateContrast(imageData) {
|
|
5902
|
+
const { data } = imageData;
|
|
5903
|
+
let sum = 0;
|
|
5904
|
+
let sumSq = 0;
|
|
5905
|
+
const pixelCount = data.length / 4;
|
|
5906
|
+
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5907
|
+
const gray = 0.299 * data[i2] + 0.587 * data[i2 + 1] + 0.114 * data[i2 + 2];
|
|
5908
|
+
sum += gray;
|
|
5909
|
+
sumSq += gray * gray;
|
|
5910
|
+
}
|
|
5911
|
+
const mean = sum / pixelCount;
|
|
5912
|
+
const variance = sumSq / pixelCount - mean * mean;
|
|
5913
|
+
return Math.sqrt(variance);
|
|
5914
|
+
}
|
|
5915
|
+
function calculateBrightness(imageData) {
|
|
5916
|
+
const { data } = imageData;
|
|
5917
|
+
let sum = 0;
|
|
5918
|
+
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5919
|
+
sum += (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
|
|
5920
|
+
}
|
|
5921
|
+
return sum / (data.length / 4);
|
|
5922
|
+
}
|
|
5923
|
+
function assessQuality(imageData) {
|
|
5924
|
+
const sharpness = calculateSharpness(imageData);
|
|
5925
|
+
const glare = detectGlare(imageData);
|
|
5926
|
+
const contrast = calculateContrast(imageData);
|
|
5927
|
+
const brightness = calculateBrightness(imageData);
|
|
5928
|
+
const isBlurry = sharpness < 80;
|
|
5929
|
+
const hasGlare = glare > 0.15;
|
|
5930
|
+
const isTooDark = brightness < 50;
|
|
5931
|
+
const isTooBright = brightness > 200;
|
|
5932
|
+
const lowContrast = contrast < 30;
|
|
5933
|
+
let score = 100;
|
|
5934
|
+
if (isBlurry) score -= 40;
|
|
5935
|
+
if (hasGlare) score -= 25;
|
|
5936
|
+
if (isTooDark) score -= 20;
|
|
5937
|
+
if (isTooBright) score -= 20;
|
|
5938
|
+
if (lowContrast) score -= 15;
|
|
5939
|
+
const metrics = {
|
|
5940
|
+
sharpness: Math.round(sharpness * 10) / 10,
|
|
5941
|
+
contrast: Math.round(contrast * 10) / 10,
|
|
5942
|
+
brightness: Math.round(brightness * 10) / 10,
|
|
5943
|
+
isBlurry,
|
|
5944
|
+
hasGlare,
|
|
5945
|
+
overallScore: Math.max(0, score)
|
|
5946
|
+
};
|
|
5947
|
+
const issues = [];
|
|
5948
|
+
const recommendations = [];
|
|
5949
|
+
if (isBlurry) {
|
|
5950
|
+
issues.push("Too blurry");
|
|
5951
|
+
recommendations.push("Hold camera steady");
|
|
5952
|
+
recommendations.push("Ensure good lighting");
|
|
5953
|
+
}
|
|
5954
|
+
if (hasGlare) {
|
|
5955
|
+
issues.push("Too much glare");
|
|
5956
|
+
recommendations.push("Avoid direct light on document");
|
|
5957
|
+
recommendations.push("Tilt document slightly");
|
|
5958
|
+
}
|
|
5959
|
+
if (isTooDark) {
|
|
5960
|
+
issues.push("Too dark");
|
|
5961
|
+
recommendations.push("Move to brighter area");
|
|
5962
|
+
recommendations.push("Turn on more lights");
|
|
5963
|
+
}
|
|
5964
|
+
if (isTooBright) {
|
|
5965
|
+
issues.push("Too bright");
|
|
5966
|
+
recommendations.push("Reduce lighting");
|
|
5967
|
+
recommendations.push("Move away from direct sun");
|
|
5968
|
+
}
|
|
5969
|
+
if (lowContrast) {
|
|
5970
|
+
issues.push("Low contrast");
|
|
5971
|
+
recommendations.push("Ensure even lighting");
|
|
5972
|
+
}
|
|
5973
|
+
const isValid = score >= 60;
|
|
5974
|
+
const message = issues.length > 0 ? issues.join(", ") : "Good quality";
|
|
5975
|
+
return {
|
|
5976
|
+
isValid,
|
|
5977
|
+
message,
|
|
5978
|
+
recommendations: recommendations.slice(0, 3),
|
|
5979
|
+
// Max 3 recommendations
|
|
5980
|
+
quality: metrics
|
|
5981
|
+
};
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5860
5984
|
// src/flow/icons.ts
|
|
5861
5985
|
var PATHS = {
|
|
5862
5986
|
shield: '<path d="M12 2l8 3v6c0 5-3.5 8.5-8 11-4.5-2.5-8-6-8-11V5l8-3z"/>',
|
|
@@ -6400,6 +6524,15 @@ var SmartComplySDK = (() => {
|
|
|
6400
6524
|
sublabel.style.cssText = `color:${theme.textMuted};font-size:${TYPE.xs}px;text-align:center;line-height:1.6;`;
|
|
6401
6525
|
sublabel.textContent = "All text must be readable and all four corners must be visible.";
|
|
6402
6526
|
this.root.appendChild(sublabel);
|
|
6527
|
+
const qualityNotice = document.createElement("div");
|
|
6528
|
+
qualityNotice.style.cssText = `
|
|
6529
|
+
display:none;width:100%;padding:10px 14px;border-radius:${RADIUS.md}px;
|
|
6530
|
+
background:${theme.warning}15;border:1px solid ${theme.warning}35;
|
|
6531
|
+
color:${theme.text};font-size:12px;text-align:left;line-height:1.5;
|
|
6532
|
+
`;
|
|
6533
|
+
qualityNotice.setAttribute("role", "status");
|
|
6534
|
+
this.root.appendChild(qualityNotice);
|
|
6535
|
+
this._checkPhotoQuality(imageData, qualityNotice, theme);
|
|
6403
6536
|
const btnRow = document.createElement("div");
|
|
6404
6537
|
btnRow.style.cssText = "display:flex;gap:12px;width:100%;";
|
|
6405
6538
|
const retakeBtn = this.createButton("Retake", theme, false);
|
|
@@ -6425,6 +6558,54 @@ var SmartComplySDK = (() => {
|
|
|
6425
6558
|
btnRow.appendChild(useBtn);
|
|
6426
6559
|
this.root.appendChild(btnRow);
|
|
6427
6560
|
}
|
|
6561
|
+
/**
|
|
6562
|
+
* Runs the shared client-side quality heuristic (blur/glare/contrast/
|
|
6563
|
+
* brightness — see utils/ImageQuality.ts) against the just-captured photo
|
|
6564
|
+
* and, if it scores poorly, shows a plain-language nudge on the preview
|
|
6565
|
+
* screen. Fails silently on decode error — this is an advisory extra, not
|
|
6566
|
+
* a required step, so a browser that can't run it must not block capture.
|
|
6567
|
+
*/
|
|
6568
|
+
async _checkPhotoQuality(imageData, noticeEl, theme) {
|
|
6569
|
+
try {
|
|
6570
|
+
const canvas = await this._blobToCanvas(imageData, 640);
|
|
6571
|
+
const ctx = canvas.getContext("2d");
|
|
6572
|
+
if (!ctx) return;
|
|
6573
|
+
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
6574
|
+
const result = assessQuality(pixels);
|
|
6575
|
+
if (noticeEl.isConnected && !result.isValid) {
|
|
6576
|
+
noticeEl.style.display = "block";
|
|
6577
|
+
noticeEl.textContent = "This photo might be hard to read — " + (result.recommendations[0] || "try retaking it in good lighting") + ".";
|
|
6578
|
+
}
|
|
6579
|
+
} catch {
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
/** Decodes to a canvas, optionally downscaled to maxDim on the long edge (never upscales). */
|
|
6583
|
+
_blobToCanvas(source, maxDim) {
|
|
6584
|
+
return new Promise((resolve, reject) => {
|
|
6585
|
+
const url = URL.createObjectURL(source);
|
|
6586
|
+
const imgEl = document.createElement("img");
|
|
6587
|
+
imgEl.onload = () => {
|
|
6588
|
+
URL.revokeObjectURL(url);
|
|
6589
|
+
const { naturalWidth: w2, naturalHeight: h2 } = imgEl;
|
|
6590
|
+
const ratio = maxDim ? computeScaleRatio(w2, h2, maxDim, maxDim) : 1;
|
|
6591
|
+
const canvas = document.createElement("canvas");
|
|
6592
|
+
canvas.width = Math.round(w2 * ratio);
|
|
6593
|
+
canvas.height = Math.round(h2 * ratio);
|
|
6594
|
+
const ctx = canvas.getContext("2d");
|
|
6595
|
+
if (!ctx) {
|
|
6596
|
+
reject(new Error("Canvas unavailable"));
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6599
|
+
ctx.drawImage(imgEl, 0, 0, canvas.width, canvas.height);
|
|
6600
|
+
resolve(canvas);
|
|
6601
|
+
};
|
|
6602
|
+
imgEl.onerror = () => {
|
|
6603
|
+
URL.revokeObjectURL(url);
|
|
6604
|
+
reject(new Error("Image load failed"));
|
|
6605
|
+
};
|
|
6606
|
+
imgEl.src = url;
|
|
6607
|
+
});
|
|
6608
|
+
}
|
|
6428
6609
|
handleCaptureComplete(imageData, container, theme, documentType, viaCamera, skipped = false) {
|
|
6429
6610
|
const requiresBack = this.shouldCaptureBackSide(documentType);
|
|
6430
6611
|
const wasCapturingFront = this.currentSide === "front";
|
|
@@ -6501,6 +6682,8 @@ var SmartComplySDK = (() => {
|
|
|
6501
6682
|
if (!this.root) return;
|
|
6502
6683
|
this.root.innerHTML = "";
|
|
6503
6684
|
this.root.style.cssText = "display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;padding:40px 20px;min-height:300px;";
|
|
6685
|
+
this.root.setAttribute("aria-live", "polite");
|
|
6686
|
+
this.root.setAttribute("role", "status");
|
|
6504
6687
|
this.root.appendChild(renderSpinner(theme, { size: 80, icon: icon("document", 28) }));
|
|
6505
6688
|
const loadingText = document.createElement("div");
|
|
6506
6689
|
loadingText.style.cssText = `color:${theme.text};font-size:${TYPE.heading}px;font-weight:600;text-align:center;`;
|
|
@@ -6510,15 +6693,19 @@ var SmartComplySDK = (() => {
|
|
|
6510
6693
|
showError(message) {
|
|
6511
6694
|
if (!this.root) return;
|
|
6512
6695
|
const errorContainer = document.createElement("div");
|
|
6696
|
+
errorContainer.setAttribute("role", "alert");
|
|
6513
6697
|
errorContainer.style.cssText = `
|
|
6514
6698
|
display:flex;align-items:center;gap:12px;padding:16px;
|
|
6515
6699
|
background:#ff444415;border:1px solid #ff444430;border-radius:${RADIUS.md}px;
|
|
6516
6700
|
color:#ff4444;font-size:14px;margin-top:16px;animation:shake 0.5s ease-in-out;
|
|
6517
6701
|
`;
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6702
|
+
const errIcon = document.createElement("span");
|
|
6703
|
+
errIcon.style.fontSize = "18px";
|
|
6704
|
+
errIcon.textContent = "⚠️";
|
|
6705
|
+
const errText = document.createElement("span");
|
|
6706
|
+
errText.textContent = message;
|
|
6707
|
+
errorContainer.appendChild(errIcon);
|
|
6708
|
+
errorContainer.appendChild(errText);
|
|
6522
6709
|
if (!document.head.querySelector("style[data-shake-animation]")) {
|
|
6523
6710
|
const style = document.createElement("style");
|
|
6524
6711
|
style.setAttribute("data-shake-animation", "true");
|
|
@@ -6700,6 +6887,24 @@ var SmartComplySDK = (() => {
|
|
|
6700
6887
|
};
|
|
6701
6888
|
|
|
6702
6889
|
// src/flow/SmartComplyFlow.ts
|
|
6890
|
+
var _INFRASTRUCTURE_ERROR_PATTERNS = [
|
|
6891
|
+
"camera connection was lost",
|
|
6892
|
+
"network error",
|
|
6893
|
+
"failed after all retries",
|
|
6894
|
+
"camera access is not supported",
|
|
6895
|
+
"camera access failed",
|
|
6896
|
+
"recording failed unexpectedly"
|
|
6897
|
+
];
|
|
6898
|
+
function _isInfrastructureError(err) {
|
|
6899
|
+
const msg = String(err?.message || "").toLowerCase();
|
|
6900
|
+
return _INFRASTRUCTURE_ERROR_PATTERNS.some((p2) => msg.includes(p2));
|
|
6901
|
+
}
|
|
6902
|
+
function _userFacingLivenessError(err) {
|
|
6903
|
+
if (_isInfrastructureError(err)) {
|
|
6904
|
+
return "We lost connection during your scan — this wasn't anything you did. Please check your camera and internet connection, then try again.";
|
|
6905
|
+
}
|
|
6906
|
+
return err?.message || "We couldn't verify your face. Please try again.";
|
|
6907
|
+
}
|
|
6703
6908
|
var COUNTRY_LABELS = {
|
|
6704
6909
|
nigeria: "🇳🇬 Nigeria",
|
|
6705
6910
|
global: "🌍 Other Countries",
|
|
@@ -6907,9 +7112,14 @@ var SmartComplySDK = (() => {
|
|
|
6907
7112
|
this.isDestroyed = false;
|
|
6908
7113
|
this.headerBackBtn = null;
|
|
6909
7114
|
this._keydownHandler = null;
|
|
7115
|
+
this._visibilityHandler = null;
|
|
7116
|
+
this._hiddenAtMs = null;
|
|
7117
|
+
this._popstateHandler = null;
|
|
7118
|
+
this._historyGuardActive = false;
|
|
6910
7119
|
// Retry safety — max 3 confirmation rejections or liveness failures per session
|
|
6911
7120
|
this._confirmRetries = 0;
|
|
6912
7121
|
this._livenessFailures = 0;
|
|
7122
|
+
this._livenessAttemptId = 0;
|
|
6913
7123
|
// IVS result stored for confirm_identity screen
|
|
6914
7124
|
this._ivsName = "";
|
|
6915
7125
|
this._ivsGender = "";
|
|
@@ -6968,6 +7178,21 @@ var SmartComplySDK = (() => {
|
|
|
6968
7178
|
document.removeEventListener("keydown", this._keydownHandler);
|
|
6969
7179
|
this._keydownHandler = null;
|
|
6970
7180
|
}
|
|
7181
|
+
if (this._visibilityHandler) {
|
|
7182
|
+
document.removeEventListener("visibilitychange", this._visibilityHandler);
|
|
7183
|
+
this._visibilityHandler = null;
|
|
7184
|
+
}
|
|
7185
|
+
if (this._popstateHandler) {
|
|
7186
|
+
window.removeEventListener("popstate", this._popstateHandler);
|
|
7187
|
+
this._popstateHandler = null;
|
|
7188
|
+
}
|
|
7189
|
+
if (this._historyGuardActive) {
|
|
7190
|
+
this._historyGuardActive = false;
|
|
7191
|
+
try {
|
|
7192
|
+
if (history.state?.scFlowGuard) history.back();
|
|
7193
|
+
} catch {
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
6971
7196
|
this.docCapture?.destroy();
|
|
6972
7197
|
if (this._prewarmedStream) {
|
|
6973
7198
|
this._prewarmedStream.getTracks().forEach((t2) => t2.stop());
|
|
@@ -7084,6 +7309,43 @@ var SmartComplySDK = (() => {
|
|
|
7084
7309
|
}
|
|
7085
7310
|
};
|
|
7086
7311
|
document.addEventListener("keydown", this._keydownHandler);
|
|
7312
|
+
const REBACKGROUND_RESTART_MS = 3e3;
|
|
7313
|
+
this._visibilityHandler = () => {
|
|
7314
|
+
if (this.isDestroyed) return;
|
|
7315
|
+
if (document.hidden) {
|
|
7316
|
+
this._hiddenAtMs = Date.now();
|
|
7317
|
+
return;
|
|
7318
|
+
}
|
|
7319
|
+
const hiddenAt = this._hiddenAtMs;
|
|
7320
|
+
this._hiddenAtMs = null;
|
|
7321
|
+
if (hiddenAt === null) return;
|
|
7322
|
+
const awayMs = Date.now() - hiddenAt;
|
|
7323
|
+
if (awayMs >= REBACKGROUND_RESTART_MS && this.currentStep === "liveness") {
|
|
7324
|
+
this.showStep("liveness");
|
|
7325
|
+
}
|
|
7326
|
+
};
|
|
7327
|
+
document.addEventListener("visibilitychange", this._visibilityHandler);
|
|
7328
|
+
this._historyGuardActive = true;
|
|
7329
|
+
try {
|
|
7330
|
+
history.pushState({ scFlowGuard: true }, "");
|
|
7331
|
+
} catch {
|
|
7332
|
+
this._historyGuardActive = false;
|
|
7333
|
+
}
|
|
7334
|
+
this._popstateHandler = () => {
|
|
7335
|
+
if (this.isDestroyed || !this._historyGuardActive) return;
|
|
7336
|
+
const backAction = this._getBackAction(this.currentStep);
|
|
7337
|
+
if (backAction) {
|
|
7338
|
+
backAction();
|
|
7339
|
+
} else {
|
|
7340
|
+
this.close();
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
7343
|
+
try {
|
|
7344
|
+
history.pushState({ scFlowGuard: true }, "");
|
|
7345
|
+
} catch {
|
|
7346
|
+
}
|
|
7347
|
+
};
|
|
7348
|
+
window.addEventListener("popstate", this._popstateHandler);
|
|
7087
7349
|
}
|
|
7088
7350
|
async initialize() {
|
|
7089
7351
|
this.showStep("loading");
|
|
@@ -7324,10 +7586,10 @@ var SmartComplySDK = (() => {
|
|
|
7324
7586
|
container.appendChild(btn);
|
|
7325
7587
|
const trust = document.createElement("div");
|
|
7326
7588
|
trust.style.cssText = `
|
|
7327
|
-
display:flex;align-items:
|
|
7328
|
-
color:${this.theme.textMuted};font-size:10.5px;
|
|
7589
|
+
display:flex;align-items:flex-start;gap:6px;text-align:left;
|
|
7590
|
+
color:${this.theme.textMuted};font-size:10.5px;line-height:1.5;
|
|
7329
7591
|
`;
|
|
7330
|
-
trust.
|
|
7592
|
+
trust.innerHTML = `<span style="flex-shrink:0;">🔒</span><span>Your photo and camera scan are used only to verify it's really you, and are kept encrypted.</span>`;
|
|
7331
7593
|
container.appendChild(trust);
|
|
7332
7594
|
}
|
|
7333
7595
|
// ── Country Select ──────────────────────────────────────────────
|
|
@@ -8270,6 +8532,7 @@ var SmartComplySDK = (() => {
|
|
|
8270
8532
|
this._renderRetryLimitReached(container);
|
|
8271
8533
|
return;
|
|
8272
8534
|
}
|
|
8535
|
+
const attemptId = ++this._livenessAttemptId;
|
|
8273
8536
|
const title = this.createStepTitle("Face verification");
|
|
8274
8537
|
container.appendChild(title);
|
|
8275
8538
|
const desc = document.createElement("div");
|
|
@@ -8311,9 +8574,10 @@ var SmartComplySDK = (() => {
|
|
|
8311
8574
|
warning: this.theme.warning
|
|
8312
8575
|
}
|
|
8313
8576
|
},
|
|
8314
|
-
[
|
|
8577
|
+
[...DEFAULT_CHALLENGE_ACTIONS],
|
|
8315
8578
|
this.livenessEntryId || void 0
|
|
8316
8579
|
).then((result) => {
|
|
8580
|
+
if (attemptId !== this._livenessAttemptId) return;
|
|
8317
8581
|
if (result.status === "failed") {
|
|
8318
8582
|
this._livenessFailures++;
|
|
8319
8583
|
}
|
|
@@ -8322,8 +8586,11 @@ var SmartComplySDK = (() => {
|
|
|
8322
8586
|
}
|
|
8323
8587
|
this.showStep("done");
|
|
8324
8588
|
}).catch((err) => {
|
|
8325
|
-
this.
|
|
8326
|
-
|
|
8589
|
+
if (attemptId !== this._livenessAttemptId) return;
|
|
8590
|
+
if (!_isInfrastructureError(err)) {
|
|
8591
|
+
this._livenessFailures++;
|
|
8592
|
+
}
|
|
8593
|
+
this.showErrorStep(_userFacingLivenessError(err));
|
|
8327
8594
|
});
|
|
8328
8595
|
}
|
|
8329
8596
|
// ── Error ───────────────────────────────────────────────────────
|
|
@@ -8605,6 +8872,30 @@ var SmartComplySDK = (() => {
|
|
|
8605
8872
|
const allFields = Object.values(channels).flat().flatMap((ch) => ch.fields || []);
|
|
8606
8873
|
return allFields.some((f2) => isUploadFieldType(f2.type));
|
|
8607
8874
|
}
|
|
8875
|
+
/**
|
|
8876
|
+
* The "go back one step" target for a given step, if that step is
|
|
8877
|
+
* navigable — shared by the header back button and the browser
|
|
8878
|
+
* back-button (popstate) handler in mount() so both agree on the same
|
|
8879
|
+
* step order.
|
|
8880
|
+
*/
|
|
8881
|
+
_getBackAction(step) {
|
|
8882
|
+
const countries = Object.keys(this.sdkConfig?.channels || {});
|
|
8883
|
+
const backTargets = {
|
|
8884
|
+
id_type: () => this.showStep(countries.length > 1 ? "country" : "welcome"),
|
|
8885
|
+
country: () => this.showStep("welcome"),
|
|
8886
|
+
id_input: () => this.showStep("id_type"),
|
|
8887
|
+
confirm_identity: () => this.showStep("id_input"),
|
|
8888
|
+
document_capture: () => {
|
|
8889
|
+
this.docCapture?.destroy();
|
|
8890
|
+
this.showStep("id_type");
|
|
8891
|
+
},
|
|
8892
|
+
document_confirm: () => {
|
|
8893
|
+
this.docCapture?.destroy();
|
|
8894
|
+
this.showStep("document_capture");
|
|
8895
|
+
}
|
|
8896
|
+
};
|
|
8897
|
+
return backTargets[step];
|
|
8898
|
+
}
|
|
8608
8899
|
updateProgress() {
|
|
8609
8900
|
const isDocFlow = this.isDocumentFlow();
|
|
8610
8901
|
const countries = Object.keys(this.sdkConfig?.channels || {});
|
|
@@ -8642,21 +8933,7 @@ var SmartComplySDK = (() => {
|
|
|
8642
8933
|
if (dots[1]) dots[1].style.background = stage2Active ? this.theme.primary : this.theme.border;
|
|
8643
8934
|
}
|
|
8644
8935
|
if (this.headerBackBtn) {
|
|
8645
|
-
const
|
|
8646
|
-
id_type: () => this.showStep(countries.length > 1 ? "country" : "welcome"),
|
|
8647
|
-
country: () => this.showStep("welcome"),
|
|
8648
|
-
id_input: () => this.showStep("id_type"),
|
|
8649
|
-
confirm_identity: () => this.showStep("id_input"),
|
|
8650
|
-
document_capture: () => {
|
|
8651
|
-
this.docCapture?.destroy();
|
|
8652
|
-
this.showStep("id_type");
|
|
8653
|
-
},
|
|
8654
|
-
document_confirm: () => {
|
|
8655
|
-
this.docCapture?.destroy();
|
|
8656
|
-
this.showStep("document_capture");
|
|
8657
|
-
}
|
|
8658
|
-
};
|
|
8659
|
-
const backAction = backTargets[this.currentStep];
|
|
8936
|
+
const backAction = this._getBackAction(this.currentStep);
|
|
8660
8937
|
if (backAction) {
|
|
8661
8938
|
this.headerBackBtn.style.display = "flex";
|
|
8662
8939
|
const newBtn = this.headerBackBtn.cloneNode(true);
|