smartcomply-web-sdk 1.0.80 → 1.0.82
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/camera/ActionDetector.d.ts.map +1 -1
- package/dist/camera/ActionDetector.js +7 -5
- package/dist/camera/ActionDetector.js.map +1 -1
- package/dist/client/Smartcomply.d.ts +1 -1
- package/dist/client/Smartcomply.js +1 -1
- package/dist/esm/camera/ActionDetector.d.ts.map +1 -1
- package/dist/esm/camera/ActionDetector.js +7 -5
- package/dist/esm/camera/ActionDetector.js.map +1 -1
- package/dist/esm/client/Smartcomply.d.ts +1 -1
- package/dist/esm/client/Smartcomply.js +1 -1
- package/dist/esm/flow/DocumentCapture.d.ts +42 -5
- package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/esm/flow/DocumentCapture.js +176 -19
- package/dist/esm/flow/DocumentCapture.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts +10 -0
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +53 -0
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/utils/ImageQuality.d.ts +38 -0
- package/dist/esm/utils/ImageQuality.d.ts.map +1 -1
- package/dist/esm/utils/ImageQuality.js +98 -0
- package/dist/esm/utils/ImageQuality.js.map +1 -1
- package/dist/flow/DocumentCapture.d.ts +42 -5
- package/dist/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/flow/DocumentCapture.js +175 -18
- package/dist/flow/DocumentCapture.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +10 -0
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +53 -0
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/smartcomply.browser.js +256 -11
- package/dist/smartcomply.browser.js.map +3 -3
- package/dist/utils/ImageQuality.d.ts +38 -0
- package/dist/utils/ImageQuality.d.ts.map +1 -1
- package/dist/utils/ImageQuality.js +99 -0
- package/dist/utils/ImageQuality.js.map +1 -1
- package/package.json +1 -1
|
@@ -4665,7 +4665,7 @@ var SmartComplySDK = (() => {
|
|
|
4665
4665
|
faceDetected,
|
|
4666
4666
|
blinkDetected: this.blinkDetected,
|
|
4667
4667
|
motionDetected: this.motionDetected,
|
|
4668
|
-
livenessConfirmed: this.blinkDetected
|
|
4668
|
+
livenessConfirmed: this.blinkDetected && this.motionDetected
|
|
4669
4669
|
};
|
|
4670
4670
|
}
|
|
4671
4671
|
getHeadYaw(matrix) {
|
|
@@ -5780,7 +5780,7 @@ var SmartComplySDK = (() => {
|
|
|
5780
5780
|
* CDN tag they installed with (e.g. @1, which floats to the latest
|
|
5781
5781
|
* matching release) actually resolved to the version they expect.
|
|
5782
5782
|
*/
|
|
5783
|
-
_SmartComply.VERSION = "1.0.
|
|
5783
|
+
_SmartComply.VERSION = "1.0.82";
|
|
5784
5784
|
var SmartComply = _SmartComply;
|
|
5785
5785
|
|
|
5786
5786
|
// src/flow/theme.ts
|
|
@@ -6040,6 +6040,67 @@ var SmartComplySDK = (() => {
|
|
|
6040
6040
|
quality: metrics
|
|
6041
6041
|
};
|
|
6042
6042
|
}
|
|
6043
|
+
function assessFraming(imageData, guideBoxPct) {
|
|
6044
|
+
const { width: W2, height: H2, data } = imageData;
|
|
6045
|
+
const gray = new Uint8Array(W2 * H2);
|
|
6046
|
+
for (let i2 = 0; i2 < W2 * H2; i2++) {
|
|
6047
|
+
const idx = i2 * 4;
|
|
6048
|
+
gray[i2] = Math.round(0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]);
|
|
6049
|
+
}
|
|
6050
|
+
const gx0 = Math.round(guideBoxPct.x * W2);
|
|
6051
|
+
const gy0 = Math.round(guideBoxPct.y * H2);
|
|
6052
|
+
const gx1 = Math.round((guideBoxPct.x + guideBoxPct.width) * W2);
|
|
6053
|
+
const gy1 = Math.round((guideBoxPct.y + guideBoxPct.height) * H2);
|
|
6054
|
+
const edgeAt = (x2, y2) => {
|
|
6055
|
+
if (x2 < 1 || y2 < 1 || x2 >= W2 - 1 || y2 >= H2 - 1) return 0;
|
|
6056
|
+
const gxVal = gray[y2 * W2 + x2 + 1] - gray[y2 * W2 + x2 - 1];
|
|
6057
|
+
const gyVal = gray[(y2 + 1) * W2 + x2] - gray[(y2 - 1) * W2 + x2];
|
|
6058
|
+
return Math.sqrt(gxVal * gxVal + gyVal * gyVal);
|
|
6059
|
+
};
|
|
6060
|
+
const EDGE_THRESHOLD = 40;
|
|
6061
|
+
const BORDER_BAND = Math.max(4, Math.round(Math.min(W2, H2) * 0.015));
|
|
6062
|
+
let borderPoints = 0;
|
|
6063
|
+
let borderHits = 0;
|
|
6064
|
+
const SAMPLES_PER_SIDE = 24;
|
|
6065
|
+
const sampleBand = (fixed, isX, start, end) => {
|
|
6066
|
+
for (let s2 = 0; s2 < SAMPLES_PER_SIDE; s2++) {
|
|
6067
|
+
const t2 = start + (end - start) * s2 / (SAMPLES_PER_SIDE - 1);
|
|
6068
|
+
let bestEdge = 0;
|
|
6069
|
+
for (let b2 = -BORDER_BAND; b2 <= BORDER_BAND; b2++) {
|
|
6070
|
+
const x2 = isX ? fixed + b2 : Math.round(t2);
|
|
6071
|
+
const y2 = isX ? Math.round(t2) : fixed + b2;
|
|
6072
|
+
bestEdge = Math.max(bestEdge, edgeAt(x2, y2));
|
|
6073
|
+
}
|
|
6074
|
+
borderPoints++;
|
|
6075
|
+
if (bestEdge >= EDGE_THRESHOLD) borderHits++;
|
|
6076
|
+
}
|
|
6077
|
+
};
|
|
6078
|
+
sampleBand(gy0, false, gx0, gx1);
|
|
6079
|
+
sampleBand(gy1, false, gx0, gx1);
|
|
6080
|
+
sampleBand(gx0, true, gy0, gy1);
|
|
6081
|
+
sampleBand(gx1, true, gy0, gy1);
|
|
6082
|
+
const borderEdgeCoverage = borderPoints > 0 ? borderHits / borderPoints : 0;
|
|
6083
|
+
let sum = 0, sumSq = 0, n2 = 0;
|
|
6084
|
+
const GRID = 12;
|
|
6085
|
+
const insetX = Math.round((gx1 - gx0) * 0.1);
|
|
6086
|
+
const insetY = Math.round((gy1 - gy0) * 0.1);
|
|
6087
|
+
for (let iy = 0; iy < GRID; iy++) {
|
|
6088
|
+
for (let ix = 0; ix < GRID; ix++) {
|
|
6089
|
+
const x2 = gx0 + insetX + Math.round((gx1 - gx0 - 2 * insetX) * ix / (GRID - 1));
|
|
6090
|
+
const y2 = gy0 + insetY + Math.round((gy1 - gy0 - 2 * insetY) * iy / (GRID - 1));
|
|
6091
|
+
if (x2 < 0 || y2 < 0 || x2 >= W2 || y2 >= H2) continue;
|
|
6092
|
+
const v2 = gray[y2 * W2 + x2];
|
|
6093
|
+
sum += v2;
|
|
6094
|
+
sumSq += v2 * v2;
|
|
6095
|
+
n2++;
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
6098
|
+
const mean = n2 > 0 ? sum / n2 : 0;
|
|
6099
|
+
const variance = n2 > 0 ? sumSq / n2 - mean * mean : 0;
|
|
6100
|
+
const interiorVariance = Math.min(1, Math.sqrt(Math.max(0, variance)) / 35);
|
|
6101
|
+
const isFramed = borderEdgeCoverage >= 0.55 && interiorVariance >= 0.25;
|
|
6102
|
+
return { isFramed, borderEdgeCoverage, interiorVariance };
|
|
6103
|
+
}
|
|
6043
6104
|
|
|
6044
6105
|
// src/flow/icons.ts
|
|
6045
6106
|
var PATHS = {
|
|
@@ -6130,6 +6191,9 @@ var SmartComplySDK = (() => {
|
|
|
6130
6191
|
this.backBlob = null;
|
|
6131
6192
|
this.currentSide = "front";
|
|
6132
6193
|
this.requiresBackOverride = void 0;
|
|
6194
|
+
this.framingTimer = null;
|
|
6195
|
+
this.framingStableCount = 0;
|
|
6196
|
+
this.framingAutoTriggered = false;
|
|
6133
6197
|
}
|
|
6134
6198
|
/**
|
|
6135
6199
|
* Mount the document capture UI and return a promise that resolves
|
|
@@ -6414,6 +6478,13 @@ var SmartComplySDK = (() => {
|
|
|
6414
6478
|
padding:14px 16px 32px;text-align:center;
|
|
6415
6479
|
`;
|
|
6416
6480
|
header.innerHTML = `<div style="color:#fff;font-size:${TYPE.sm}px;font-weight:600;">Align your ${this.getDocumentLabel(documentType)} within the frame</div>`;
|
|
6481
|
+
const framingStatus = document.createElement("div");
|
|
6482
|
+
framingStatus.style.cssText = `
|
|
6483
|
+
margin-top:6px;font-size:${TYPE.xs}px;font-weight:600;
|
|
6484
|
+
color:rgba(255,255,255,0.7);transition:color 0.2s ease;
|
|
6485
|
+
`;
|
|
6486
|
+
framingStatus.textContent = "Fit the document inside the frame";
|
|
6487
|
+
header.appendChild(framingStatus);
|
|
6417
6488
|
wrapper.appendChild(header);
|
|
6418
6489
|
const footer = document.createElement("div");
|
|
6419
6490
|
footer.style.cssText = `
|
|
@@ -6446,10 +6517,19 @@ var SmartComplySDK = (() => {
|
|
|
6446
6517
|
inner.style.transform = "translate(-50%,-50%) scale(1)";
|
|
6447
6518
|
});
|
|
6448
6519
|
captureBtn.addEventListener("click", () => {
|
|
6520
|
+
this.framingAutoTriggered = true;
|
|
6449
6521
|
this.captureFrame(container, theme, documentType);
|
|
6450
6522
|
});
|
|
6451
6523
|
container.appendChild(captureBtn);
|
|
6452
|
-
this.openCamera()
|
|
6524
|
+
this.openCamera().then(() => {
|
|
6525
|
+
if (this.videoEl) {
|
|
6526
|
+
this.videoEl.addEventListener(
|
|
6527
|
+
"loadedmetadata",
|
|
6528
|
+
() => this._startFramingLoop(container, theme, documentType, framingStatus),
|
|
6529
|
+
{ once: true }
|
|
6530
|
+
);
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6453
6533
|
}
|
|
6454
6534
|
/**
|
|
6455
6535
|
* Matches the video box's aspect-ratio CSS to the stream's actual
|
|
@@ -6484,6 +6564,90 @@ var SmartComplySDK = (() => {
|
|
|
6484
6564
|
this.orientationHandler = null;
|
|
6485
6565
|
}
|
|
6486
6566
|
}
|
|
6567
|
+
/**
|
|
6568
|
+
* Auto-capture-on-stability — the same pattern the liveness module already
|
|
6569
|
+
* uses for face-lock (see liveness.ts's checkFace), applied to documents:
|
|
6570
|
+
* poll the raw video frame on an interval, score it against the guide box
|
|
6571
|
+
* with assessFraming() (see utils/ImageQuality.ts), and fire the shutter
|
|
6572
|
+
* automatically once several consecutive samples score as "framed". This
|
|
6573
|
+
* is industry-standard for document capture (Persona/Onfido/Jumio-style
|
|
6574
|
+
* SDKs all auto-capture rather than trust a manual tap against a static
|
|
6575
|
+
* guide overlay) — the guide box alone never blocked a badly cropped tap.
|
|
6576
|
+
*
|
|
6577
|
+
* The manual capture button still works throughout and always wins
|
|
6578
|
+
* (framingAutoTriggered guards against a race between the two).
|
|
6579
|
+
*/
|
|
6580
|
+
_startFramingLoop(container, theme, documentType, statusEl) {
|
|
6581
|
+
if (!this.videoEl) return;
|
|
6582
|
+
this.framingStableCount = 0;
|
|
6583
|
+
this.framingAutoTriggered = false;
|
|
6584
|
+
const STABLE_REQUIRED = 3;
|
|
6585
|
+
const SAMPLE_INTERVAL_MS = 350;
|
|
6586
|
+
const MAX_WAIT_MS = 12e3;
|
|
6587
|
+
const startTime = performance.now();
|
|
6588
|
+
const boxWidthPct = 0.84;
|
|
6589
|
+
const boxAspect = 16 / 10;
|
|
6590
|
+
const tick = () => {
|
|
6591
|
+
if (!this.videoEl || this.framingAutoTriggered) return;
|
|
6592
|
+
if (performance.now() - startTime >= MAX_WAIT_MS) {
|
|
6593
|
+
statusEl.textContent = "Fit the document inside the frame";
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
6596
|
+
const w2 = this.videoEl.videoWidth;
|
|
6597
|
+
const h2 = this.videoEl.videoHeight;
|
|
6598
|
+
if (!w2 || !h2 || this.videoEl.readyState < 2) {
|
|
6599
|
+
this.framingTimer = setTimeout(tick, SAMPLE_INTERVAL_MS);
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
6602
|
+
try {
|
|
6603
|
+
const boxWidthPx = w2 * boxWidthPct;
|
|
6604
|
+
const boxHeightPx = boxWidthPx / boxAspect;
|
|
6605
|
+
const boxXPct = (1 - boxWidthPct) / 2;
|
|
6606
|
+
const boxHeightPct = boxHeightPx / h2;
|
|
6607
|
+
const boxYPct = (1 - boxHeightPct) / 2;
|
|
6608
|
+
const canvas = document.createElement("canvas");
|
|
6609
|
+
const scale = Math.min(1, 480 / w2);
|
|
6610
|
+
canvas.width = Math.round(w2 * scale);
|
|
6611
|
+
canvas.height = Math.round(h2 * scale);
|
|
6612
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
6613
|
+
if (!ctx) {
|
|
6614
|
+
this.framingTimer = setTimeout(tick, SAMPLE_INTERVAL_MS);
|
|
6615
|
+
return;
|
|
6616
|
+
}
|
|
6617
|
+
ctx.drawImage(this.videoEl, 0, 0, canvas.width, canvas.height);
|
|
6618
|
+
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
6619
|
+
const result = assessFraming(pixels, {
|
|
6620
|
+
x: boxXPct,
|
|
6621
|
+
y: boxYPct,
|
|
6622
|
+
width: boxWidthPct,
|
|
6623
|
+
height: boxHeightPct
|
|
6624
|
+
});
|
|
6625
|
+
if (result.isFramed) {
|
|
6626
|
+
this.framingStableCount++;
|
|
6627
|
+
statusEl.style.color = theme.primary;
|
|
6628
|
+
statusEl.textContent = this.framingStableCount >= STABLE_REQUIRED ? "Capturing…" : "Hold steady…";
|
|
6629
|
+
} else {
|
|
6630
|
+
this.framingStableCount = 0;
|
|
6631
|
+
statusEl.style.color = "rgba(255,255,255,0.7)";
|
|
6632
|
+
statusEl.textContent = result.interiorVariance < 0.25 ? "Move closer — fill the frame with your document" : "Align all 4 corners within the guide";
|
|
6633
|
+
}
|
|
6634
|
+
if (this.framingStableCount >= STABLE_REQUIRED) {
|
|
6635
|
+
this.framingAutoTriggered = true;
|
|
6636
|
+
this.captureFrame(container, theme, documentType);
|
|
6637
|
+
return;
|
|
6638
|
+
}
|
|
6639
|
+
} catch {
|
|
6640
|
+
}
|
|
6641
|
+
this.framingTimer = setTimeout(tick, SAMPLE_INTERVAL_MS);
|
|
6642
|
+
};
|
|
6643
|
+
this.framingTimer = setTimeout(tick, SAMPLE_INTERVAL_MS);
|
|
6644
|
+
}
|
|
6645
|
+
_stopFramingLoop() {
|
|
6646
|
+
if (this.framingTimer !== null) {
|
|
6647
|
+
clearTimeout(this.framingTimer);
|
|
6648
|
+
this.framingTimer = null;
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6487
6651
|
async openCamera() {
|
|
6488
6652
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
6489
6653
|
this.rejectCapture?.(new Error("Camera access is not supported in this browser. Please use a modern browser like Chrome, Firefox, or Safari."));
|
|
@@ -6614,7 +6778,7 @@ var SmartComplySDK = (() => {
|
|
|
6614
6778
|
URL.revokeObjectURL(currentUrl);
|
|
6615
6779
|
currentUrl = URL.createObjectURL(rotated);
|
|
6616
6780
|
img.src = currentUrl;
|
|
6617
|
-
this._checkPhotoQuality(currentBlob, qualityNotice, theme);
|
|
6781
|
+
this._checkPhotoQuality(currentBlob, qualityNotice, theme, useBtn);
|
|
6618
6782
|
} catch {
|
|
6619
6783
|
} finally {
|
|
6620
6784
|
isRotating = false;
|
|
@@ -6640,7 +6804,6 @@ var SmartComplySDK = (() => {
|
|
|
6640
6804
|
`;
|
|
6641
6805
|
qualityNotice.setAttribute("role", "status");
|
|
6642
6806
|
this.root.appendChild(qualityNotice);
|
|
6643
|
-
this._checkPhotoQuality(currentBlob, qualityNotice, theme);
|
|
6644
6807
|
const btnRow = document.createElement("div");
|
|
6645
6808
|
btnRow.style.cssText = "display:flex;gap:12px;width:100%;";
|
|
6646
6809
|
const retakeBtn = this.createButton("Retake", theme, false);
|
|
@@ -6662,20 +6825,58 @@ var SmartComplySDK = (() => {
|
|
|
6662
6825
|
btnRow.appendChild(retakeBtn);
|
|
6663
6826
|
btnRow.appendChild(useBtn);
|
|
6664
6827
|
this.root.appendChild(btnRow);
|
|
6828
|
+
this._checkPhotoQuality(currentBlob, qualityNotice, theme, useBtn);
|
|
6665
6829
|
}
|
|
6666
6830
|
/**
|
|
6667
|
-
* Runs the shared client-side quality
|
|
6668
|
-
* brightness
|
|
6669
|
-
*
|
|
6670
|
-
*
|
|
6671
|
-
*
|
|
6831
|
+
* Runs the shared client-side quality heuristics — blur/glare/contrast/
|
|
6832
|
+
* brightness (assessQuality) and document framing/completeness
|
|
6833
|
+
* (assessFraming) — against the just-captured or just-uploaded photo, and
|
|
6834
|
+
* shows a plain-language nudge on the preview screen if either scores
|
|
6835
|
+
* poorly. This is the one chokepoint both the camera and manual-upload
|
|
6836
|
+
* paths funnel through (see showPreview), so it's the only place that
|
|
6837
|
+
* reliably catches a badly cropped document regardless of how the photo
|
|
6838
|
+
* arrived — the camera's own auto-capture framing loop only guards live
|
|
6839
|
+
* capture, and does nothing for an uploaded file or a manual shutter tap
|
|
6840
|
+
* fired before the loop ever samples.
|
|
6841
|
+
*
|
|
6842
|
+
* Framing here checks the WHOLE image (guideBoxPct = full frame), not a
|
|
6843
|
+
* guide-box region — there's no guide overlay for an arbitrary uploaded
|
|
6844
|
+
* photo, so "does the document fill most of the frame" is the correct
|
|
6845
|
+
* question, matching the same border-edge + interior-variance heuristic
|
|
6846
|
+
* the camera loop already uses.
|
|
6847
|
+
*
|
|
6848
|
+
* A framing failure disables useBtn — unlike the blur/glare nudge, this
|
|
6849
|
+
* one blocks: a cropped ID reads as a confident, wrong identity downstream
|
|
6850
|
+
* (partial OCR fields, a registry check against a truncated ID number)
|
|
6851
|
+
* with no further chance to catch it, so the user must retake/re-upload
|
|
6852
|
+
* rather than being trusted to notice an advisory nudge and act on it
|
|
6853
|
+
* voluntarily. Fails open on decode error — this heuristic not running at
|
|
6854
|
+
* all must not lock the user out of a photo it never actually scored.
|
|
6672
6855
|
*/
|
|
6673
|
-
async _checkPhotoQuality(imageData, noticeEl, theme) {
|
|
6856
|
+
async _checkPhotoQuality(imageData, noticeEl, theme, useBtn) {
|
|
6674
6857
|
try {
|
|
6675
6858
|
const canvas = await this._blobToCanvas(imageData, 640);
|
|
6676
6859
|
const ctx = canvas.getContext("2d");
|
|
6677
6860
|
if (!ctx) return;
|
|
6678
6861
|
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
6862
|
+
const framing = assessFraming(pixels, { x: 0, y: 0, width: 1, height: 1 });
|
|
6863
|
+
if (!framing.isFramed) {
|
|
6864
|
+
if (noticeEl.isConnected) {
|
|
6865
|
+
noticeEl.style.display = "block";
|
|
6866
|
+
noticeEl.textContent = framing.interiorVariance < 0.25 ? "We couldn't detect a document in this photo — make sure the ID is clearly visible and try again." : "Part of the document looks cut off — make sure all four corners are visible and try again.";
|
|
6867
|
+
}
|
|
6868
|
+
if (useBtn.isConnected) {
|
|
6869
|
+
useBtn.disabled = true;
|
|
6870
|
+
useBtn.style.opacity = "0.5";
|
|
6871
|
+
useBtn.style.cursor = "not-allowed";
|
|
6872
|
+
}
|
|
6873
|
+
return;
|
|
6874
|
+
}
|
|
6875
|
+
if (useBtn.isConnected) {
|
|
6876
|
+
useBtn.disabled = false;
|
|
6877
|
+
useBtn.style.opacity = "1";
|
|
6878
|
+
useBtn.style.cursor = "pointer";
|
|
6879
|
+
}
|
|
6679
6880
|
const result = assessQuality(pixels);
|
|
6680
6881
|
if (noticeEl.isConnected && !result.isValid) {
|
|
6681
6882
|
noticeEl.style.display = "block";
|
|
@@ -6818,6 +7019,7 @@ var SmartComplySDK = (() => {
|
|
|
6818
7019
|
if (input) input.click();
|
|
6819
7020
|
}
|
|
6820
7021
|
stopCamera() {
|
|
7022
|
+
this._stopFramingLoop();
|
|
6821
7023
|
this._unwatchOrientation();
|
|
6822
7024
|
this.cameraManager.stop();
|
|
6823
7025
|
if (this.videoEl) {
|
|
@@ -8403,6 +8605,11 @@ var SmartComplySDK = (() => {
|
|
|
8403
8605
|
this._renderOcrQualityFail(container, result.ocr_rejection_reason);
|
|
8404
8606
|
return;
|
|
8405
8607
|
}
|
|
8608
|
+
if (result.status === "failed") {
|
|
8609
|
+
container.innerHTML = "";
|
|
8610
|
+
this._renderDocumentVerificationFailed(container, result.failure_reason);
|
|
8611
|
+
return;
|
|
8612
|
+
}
|
|
8406
8613
|
const ocrDone = result.ocr_status === "done" || result.ocr_status === "ocr_complete" || result.ocr_name || result.ocr_document_number || result.ocr_expiry_date;
|
|
8407
8614
|
if (ocrDone) {
|
|
8408
8615
|
this._ocrName = result.ocr_name || "";
|
|
@@ -8495,6 +8702,44 @@ var SmartComplySDK = (() => {
|
|
|
8495
8702
|
});
|
|
8496
8703
|
container.appendChild(retryBtn);
|
|
8497
8704
|
}
|
|
8705
|
+
/**
|
|
8706
|
+
* The document passed OCR's own quality gate (it was readable) but failed
|
|
8707
|
+
* the government-registry re-check — the ID number either doesn't exist
|
|
8708
|
+
* in the registry, or belongs to a different person than the name on the
|
|
8709
|
+
* document. Distinct from _renderOcrQualityFail: the photo itself may be
|
|
8710
|
+
* perfectly clear, so "retake photo" framing would be misleading — the
|
|
8711
|
+
* fix is a correct, uncropped photo of the actual document, not just a
|
|
8712
|
+
* clearer one.
|
|
8713
|
+
*/
|
|
8714
|
+
_renderDocumentVerificationFailed(container, failureReason) {
|
|
8715
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
8716
|
+
const icon2 = document.createElement("div");
|
|
8717
|
+
icon2.style.cssText = `width:72px;height:72px;border-radius:50%;background:${this.theme.errorBg};border:3px solid ${this.theme.error};display:flex;align-items:center;justify-content:center;font-size:28px;`;
|
|
8718
|
+
icon2.textContent = "⚠️";
|
|
8719
|
+
container.appendChild(icon2);
|
|
8720
|
+
const t2 = document.createElement("h2");
|
|
8721
|
+
t2.style.cssText = `color:${this.theme.text};font-size:${TYPE.heading}px;font-weight:700;margin:0;`;
|
|
8722
|
+
t2.textContent = "We Couldn't Confirm Your Details";
|
|
8723
|
+
container.appendChild(t2);
|
|
8724
|
+
const d2 = document.createElement("p");
|
|
8725
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
8726
|
+
d2.textContent = failureReason || "We couldn't confirm your details. Please re-upload a clear photo of your ID and make sure your details are correct before submitting.";
|
|
8727
|
+
container.appendChild(d2);
|
|
8728
|
+
const retryBtn = this.createPrimaryButton("Re-upload ID");
|
|
8729
|
+
retryBtn.addEventListener("click", () => {
|
|
8730
|
+
this.livenessEntryId = null;
|
|
8731
|
+
this.documentBlob = null;
|
|
8732
|
+
this.documentBackBlob = null;
|
|
8733
|
+
this.showStep("document_capture");
|
|
8734
|
+
});
|
|
8735
|
+
container.appendChild(retryBtn);
|
|
8736
|
+
const endBtn = this.createSecondaryButton("End session");
|
|
8737
|
+
endBtn.addEventListener("click", () => {
|
|
8738
|
+
this.options.onError?.(new Error("Document verification failed"));
|
|
8739
|
+
this.close();
|
|
8740
|
+
});
|
|
8741
|
+
container.appendChild(endBtn);
|
|
8742
|
+
}
|
|
8498
8743
|
/**
|
|
8499
8744
|
* OCR ran to completion but never produced a single usable field within
|
|
8500
8745
|
* the poll window — do not let the flow reach liveness with no verified
|