smartcomply-web-sdk 1.0.64 → 1.0.66
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 +16 -9
- package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/esm/flow/DocumentCapture.js +77 -121
- package/dist/esm/flow/DocumentCapture.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +17 -5
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +13 -9
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/esm/utils/documentRules.d.ts +9 -2
- package/dist/esm/utils/documentRules.d.ts.map +1 -1
- package/dist/esm/utils/documentRules.js +7 -2
- package/dist/esm/utils/documentRules.js.map +1 -1
- package/dist/flow/DocumentCapture.d.ts +16 -9
- package/dist/flow/DocumentCapture.d.ts.map +1 -1
- package/dist/flow/DocumentCapture.js +76 -120
- package/dist/flow/DocumentCapture.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +17 -5
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +13 -9
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +77 -223
- package/dist/smartcomply.browser.js.map +4 -4
- package/dist/utils/documentRules.d.ts +9 -2
- package/dist/utils/documentRules.d.ts.map +1 -1
- package/dist/utils/documentRules.js +7 -2
- package/dist/utils/documentRules.js.map +1 -1
- package/package.json +1 -1
|
@@ -5372,7 +5372,7 @@ var SmartComplySDK = (() => {
|
|
|
5372
5372
|
tempVideo.muted = true;
|
|
5373
5373
|
tempVideo.playsInline = true;
|
|
5374
5374
|
await tempVideo.play();
|
|
5375
|
-
ui2.mount(container, { time_limit_seconds: 0, instruction: "
|
|
5375
|
+
ui2.mount(container, { time_limit_seconds: 0, instruction: "Fit your face inside the oval outline" });
|
|
5376
5376
|
const uiVideo = ui2.getVideoElement();
|
|
5377
5377
|
if (uiVideo) {
|
|
5378
5378
|
uiVideo.srcObject = stream;
|
|
@@ -5382,7 +5382,7 @@ var SmartComplySDK = (() => {
|
|
|
5382
5382
|
container.appendChild(tempVideo);
|
|
5383
5383
|
}
|
|
5384
5384
|
await faceEngine.init();
|
|
5385
|
-
ui2.updateInstruction("
|
|
5385
|
+
ui2.updateInstruction("Fit your face inside the oval outline");
|
|
5386
5386
|
const videoForDetection = uiVideo || tempVideo;
|
|
5387
5387
|
await new Promise((resolve) => {
|
|
5388
5388
|
let stableFrames = 0;
|
|
@@ -5402,7 +5402,7 @@ var SmartComplySDK = (() => {
|
|
|
5402
5402
|
if (detection.faceDetected) {
|
|
5403
5403
|
stableFrames++;
|
|
5404
5404
|
missedFrames = 0;
|
|
5405
|
-
ui2.updateInstruction("
|
|
5405
|
+
ui2.updateInstruction("Good — hold that position");
|
|
5406
5406
|
if (stableFrames >= STABLE_REQUIRED) {
|
|
5407
5407
|
resolve();
|
|
5408
5408
|
return;
|
|
@@ -5411,7 +5411,7 @@ var SmartComplySDK = (() => {
|
|
|
5411
5411
|
missedFrames++;
|
|
5412
5412
|
if (missedFrames > MISS_TOLERANCE) {
|
|
5413
5413
|
stableFrames = 0;
|
|
5414
|
-
ui2.updateInstruction("Move your face
|
|
5414
|
+
ui2.updateInstruction("Move closer so your whole face fits in the oval");
|
|
5415
5415
|
}
|
|
5416
5416
|
}
|
|
5417
5417
|
}
|
|
@@ -5419,7 +5419,7 @@ var SmartComplySDK = (() => {
|
|
|
5419
5419
|
};
|
|
5420
5420
|
requestAnimationFrame(checkFace);
|
|
5421
5421
|
});
|
|
5422
|
-
ui2.updateInstruction("
|
|
5422
|
+
ui2.updateInstruction("Getting ready…");
|
|
5423
5423
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
5424
5424
|
const challengeActions = actions || [...DEFAULT_CHALLENGE_ACTIONS];
|
|
5425
5425
|
let entry;
|
|
@@ -5448,10 +5448,10 @@ var SmartComplySDK = (() => {
|
|
|
5448
5448
|
tempVideo.remove();
|
|
5449
5449
|
const uiChallenge = {
|
|
5450
5450
|
time_limit_seconds: LIVENESS_TIME_LIMIT_SECONDS,
|
|
5451
|
-
instruction: "Blink and look
|
|
5451
|
+
instruction: "Blink naturally and look slowly left, right, up or down"
|
|
5452
5452
|
};
|
|
5453
5453
|
ui2.unmount();
|
|
5454
|
-
const videoEl = ui2.mount(container, uiChallenge);
|
|
5454
|
+
const videoEl = ui2.mount(container, { ...uiChallenge, instruction: "Starting scan…" });
|
|
5455
5455
|
videoEl.srcObject = stream;
|
|
5456
5456
|
await videoEl.play();
|
|
5457
5457
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
@@ -5476,7 +5476,7 @@ var SmartComplySDK = (() => {
|
|
|
5476
5476
|
ui2.updateScanProgress(scan.livenessConfirmed);
|
|
5477
5477
|
ui2.setFaceTracking(detection.faceDetected);
|
|
5478
5478
|
if (!detection.faceDetected) {
|
|
5479
|
-
ui2.updateInstruction("Move your face
|
|
5479
|
+
ui2.updateInstruction("Move closer so your whole face fits in the oval");
|
|
5480
5480
|
} else {
|
|
5481
5481
|
ui2.updateInstruction(uiChallenge.instruction);
|
|
5482
5482
|
}
|
|
@@ -5845,129 +5845,12 @@ var SmartComplySDK = (() => {
|
|
|
5845
5845
|
return `#${(r2 << 16 | g2 << 8 | b2).toString(16).padStart(6, "0")}`;
|
|
5846
5846
|
}
|
|
5847
5847
|
|
|
5848
|
-
// src/utils/ImageQuality.ts
|
|
5849
|
-
function calculateSharpness(imageData) {
|
|
5850
|
-
const { width, height, data } = imageData;
|
|
5851
|
-
const gray = new Uint8Array(width * height);
|
|
5852
|
-
for (let i2 = 0; i2 < width * height; i2++) {
|
|
5853
|
-
const idx = i2 * 4;
|
|
5854
|
-
gray[i2] = Math.round(0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]);
|
|
5855
|
-
}
|
|
5856
|
-
const laplacian = new Float32Array(width * height);
|
|
5857
|
-
let sum = 0;
|
|
5858
|
-
let sumSq = 0;
|
|
5859
|
-
let count = 0;
|
|
5860
|
-
for (let y2 = 1; y2 < height - 1; y2++) {
|
|
5861
|
-
for (let x2 = 1; x2 < width - 1; x2++) {
|
|
5862
|
-
const idx = y2 * width + x2;
|
|
5863
|
-
const val = -1 * gray[idx - width] + -1 * gray[idx - 1] + 4 * gray[idx] + -1 * gray[idx + 1] + -1 * gray[idx + width];
|
|
5864
|
-
laplacian[idx] = val;
|
|
5865
|
-
sum += val;
|
|
5866
|
-
sumSq += val * val;
|
|
5867
|
-
count++;
|
|
5868
|
-
}
|
|
5869
|
-
}
|
|
5870
|
-
const mean = sum / count;
|
|
5871
|
-
const variance = sumSq / count - mean * mean;
|
|
5872
|
-
return Math.abs(variance);
|
|
5873
|
-
}
|
|
5874
|
-
function detectGlare(imageData, threshold = 250) {
|
|
5875
|
-
const { data } = imageData;
|
|
5876
|
-
let overexposed = 0;
|
|
5877
|
-
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5878
|
-
const brightness = (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
|
|
5879
|
-
if (brightness > threshold) {
|
|
5880
|
-
overexposed++;
|
|
5881
|
-
}
|
|
5882
|
-
}
|
|
5883
|
-
return overexposed / (data.length / 4);
|
|
5884
|
-
}
|
|
5885
|
-
function calculateContrast(imageData) {
|
|
5886
|
-
const { data } = imageData;
|
|
5887
|
-
let sum = 0;
|
|
5888
|
-
let sumSq = 0;
|
|
5889
|
-
const pixelCount = data.length / 4;
|
|
5890
|
-
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5891
|
-
const gray = 0.299 * data[i2] + 0.587 * data[i2 + 1] + 0.114 * data[i2 + 2];
|
|
5892
|
-
sum += gray;
|
|
5893
|
-
sumSq += gray * gray;
|
|
5894
|
-
}
|
|
5895
|
-
const mean = sum / pixelCount;
|
|
5896
|
-
const variance = sumSq / pixelCount - mean * mean;
|
|
5897
|
-
return Math.sqrt(variance);
|
|
5898
|
-
}
|
|
5899
|
-
function calculateBrightness(imageData) {
|
|
5900
|
-
const { data } = imageData;
|
|
5901
|
-
let sum = 0;
|
|
5902
|
-
for (let i2 = 0; i2 < data.length; i2 += 4) {
|
|
5903
|
-
sum += (data[i2] + data[i2 + 1] + data[i2 + 2]) / 3;
|
|
5904
|
-
}
|
|
5905
|
-
return sum / (data.length / 4);
|
|
5906
|
-
}
|
|
5907
|
-
function assessQuality(imageData) {
|
|
5908
|
-
const sharpness = calculateSharpness(imageData);
|
|
5909
|
-
const glare = detectGlare(imageData);
|
|
5910
|
-
const contrast = calculateContrast(imageData);
|
|
5911
|
-
const brightness = calculateBrightness(imageData);
|
|
5912
|
-
const isBlurry = sharpness < 80;
|
|
5913
|
-
const hasGlare = glare > 0.15;
|
|
5914
|
-
const isTooDark = brightness < 50;
|
|
5915
|
-
const isTooBright = brightness > 200;
|
|
5916
|
-
const lowContrast = contrast < 30;
|
|
5917
|
-
let score = 100;
|
|
5918
|
-
if (isBlurry) score -= 40;
|
|
5919
|
-
if (hasGlare) score -= 25;
|
|
5920
|
-
if (isTooDark) score -= 20;
|
|
5921
|
-
if (isTooBright) score -= 20;
|
|
5922
|
-
if (lowContrast) score -= 15;
|
|
5923
|
-
const metrics = {
|
|
5924
|
-
sharpness: Math.round(sharpness * 10) / 10,
|
|
5925
|
-
contrast: Math.round(contrast * 10) / 10,
|
|
5926
|
-
brightness: Math.round(brightness * 10) / 10,
|
|
5927
|
-
isBlurry,
|
|
5928
|
-
hasGlare,
|
|
5929
|
-
overallScore: Math.max(0, score)
|
|
5930
|
-
};
|
|
5931
|
-
const issues = [];
|
|
5932
|
-
const recommendations = [];
|
|
5933
|
-
if (isBlurry) {
|
|
5934
|
-
issues.push("Too blurry");
|
|
5935
|
-
recommendations.push("Hold camera steady");
|
|
5936
|
-
recommendations.push("Ensure good lighting");
|
|
5937
|
-
}
|
|
5938
|
-
if (hasGlare) {
|
|
5939
|
-
issues.push("Too much glare");
|
|
5940
|
-
recommendations.push("Avoid direct light on document");
|
|
5941
|
-
recommendations.push("Tilt document slightly");
|
|
5942
|
-
}
|
|
5943
|
-
if (isTooDark) {
|
|
5944
|
-
issues.push("Too dark");
|
|
5945
|
-
recommendations.push("Move to brighter area");
|
|
5946
|
-
recommendations.push("Turn on more lights");
|
|
5947
|
-
}
|
|
5948
|
-
if (isTooBright) {
|
|
5949
|
-
issues.push("Too bright");
|
|
5950
|
-
recommendations.push("Reduce lighting");
|
|
5951
|
-
recommendations.push("Move away from direct sun");
|
|
5952
|
-
}
|
|
5953
|
-
if (lowContrast) {
|
|
5954
|
-
issues.push("Low contrast");
|
|
5955
|
-
recommendations.push("Ensure even lighting");
|
|
5956
|
-
}
|
|
5957
|
-
const isValid = score >= 60;
|
|
5958
|
-
const message = issues.length > 0 ? issues.join(", ") : "Good quality";
|
|
5959
|
-
return {
|
|
5960
|
-
isValid,
|
|
5961
|
-
message,
|
|
5962
|
-
recommendations: recommendations.slice(0, 3),
|
|
5963
|
-
// Max 3 recommendations
|
|
5964
|
-
quality: metrics
|
|
5965
|
-
};
|
|
5966
|
-
}
|
|
5967
|
-
|
|
5968
5848
|
// src/utils/documentRules.ts
|
|
5969
5849
|
function requiresBackSide(documentType) {
|
|
5970
5850
|
const type = documentType.toLowerCase();
|
|
5851
|
+
if (type.includes("nin")) {
|
|
5852
|
+
return "optional";
|
|
5853
|
+
}
|
|
5971
5854
|
return type.includes("voter") || type.includes("driver") || type.includes("license") || type.includes("national id") || type.includes("national id card") || type.includes("ghana");
|
|
5972
5855
|
}
|
|
5973
5856
|
function computeScaleRatio(srcWidth, srcHeight, maxWidth, maxHeight) {
|
|
@@ -6110,9 +5993,17 @@ var SmartComplySDK = (() => {
|
|
|
6110
5993
|
this.backBlob = null;
|
|
6111
5994
|
this.currentSide = "front";
|
|
6112
5995
|
}
|
|
6113
|
-
|
|
5996
|
+
getBackSideMode(documentType) {
|
|
6114
5997
|
return this.requiresBackOverride !== void 0 ? this.requiresBackOverride : requiresBackSide(documentType);
|
|
6115
5998
|
}
|
|
5999
|
+
/** Whether a back-side step should be shown at all (required OR optional). */
|
|
6000
|
+
shouldCaptureBackSide(documentType) {
|
|
6001
|
+
return this.getBackSideMode(documentType) !== false;
|
|
6002
|
+
}
|
|
6003
|
+
/** Whether the back-side step, once shown, can be skipped by the user. */
|
|
6004
|
+
isBackSideOptional(documentType) {
|
|
6005
|
+
return this.getBackSideMode(documentType) === "optional";
|
|
6006
|
+
}
|
|
6116
6007
|
renderChoiceScreen(container, theme, documentType) {
|
|
6117
6008
|
this.root = document.createElement("div");
|
|
6118
6009
|
this.root.style.cssText = `
|
|
@@ -6121,6 +6012,7 @@ var SmartComplySDK = (() => {
|
|
|
6121
6012
|
`;
|
|
6122
6013
|
const docLabel = this.getDocumentLabel(documentType);
|
|
6123
6014
|
const requiresBack = this.shouldCaptureBackSide(documentType);
|
|
6015
|
+
const backOptional = this.isBackSideOptional(documentType);
|
|
6124
6016
|
const isCapturingFront = this.currentSide === "front";
|
|
6125
6017
|
const sideLabel = requiresBack ? isCapturingFront ? " — Front side" : " — Back side" : "";
|
|
6126
6018
|
const headerRow = document.createElement("div");
|
|
@@ -6143,7 +6035,7 @@ var SmartComplySDK = (() => {
|
|
|
6143
6035
|
titleEl.textContent = `Capture your ${docLabel}`;
|
|
6144
6036
|
const subEl = document.createElement("div");
|
|
6145
6037
|
subEl.style.cssText = `color:${theme.textSecondary};font-size:12px;margin-top:2px;`;
|
|
6146
|
-
subEl.textContent = requiresBack ? isCapturingFront ? "Step 1 of 2 — front side" : "Step 2 of 2 — back side" : "Choose how to provide your document";
|
|
6038
|
+
subEl.textContent = requiresBack ? isCapturingFront ? "Step 1 of 2 — front side" : backOptional ? "Step 2 of 2 — back side (optional)" : "Step 2 of 2 — back side" : "Choose how to provide your document";
|
|
6147
6039
|
titleBlock.appendChild(titleEl);
|
|
6148
6040
|
titleBlock.appendChild(subEl);
|
|
6149
6041
|
headerRow.appendChild(iconBox);
|
|
@@ -6234,9 +6126,23 @@ var SmartComplySDK = (() => {
|
|
|
6234
6126
|
display:flex;align-items:center;gap:8px;
|
|
6235
6127
|
font-size:12px;color:${theme.textSecondary};line-height:1.5;
|
|
6236
6128
|
`;
|
|
6237
|
-
notice.innerHTML = `<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>`;
|
|
6129
|
+
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>`;
|
|
6238
6130
|
this.root.appendChild(notice);
|
|
6239
6131
|
}
|
|
6132
|
+
if (requiresBack && !isCapturingFront && backOptional) {
|
|
6133
|
+
const skipBtn = document.createElement("button");
|
|
6134
|
+
skipBtn.style.cssText = `
|
|
6135
|
+
display:block;margin:0 20px 16px;width:calc(100% - 40px);
|
|
6136
|
+
padding:10px 12px;background:transparent;color:${theme.textSecondary};
|
|
6137
|
+
border:1.5px dashed ${theme.border};border-radius:10px;cursor:pointer;
|
|
6138
|
+
font-size:12.5px;font-weight:600;font-family:inherit;
|
|
6139
|
+
`;
|
|
6140
|
+
skipBtn.textContent = "Skip — this card has no back side";
|
|
6141
|
+
skipBtn.addEventListener("click", () => {
|
|
6142
|
+
this.handleCaptureComplete(null, container, theme, documentType, false, true);
|
|
6143
|
+
});
|
|
6144
|
+
this.root.appendChild(skipBtn);
|
|
6145
|
+
}
|
|
6240
6146
|
const fileInput = document.createElement("input");
|
|
6241
6147
|
fileInput.type = "file";
|
|
6242
6148
|
fileInput.accept = "image/jpeg,image/png";
|
|
@@ -6250,8 +6156,7 @@ var SmartComplySDK = (() => {
|
|
|
6250
6156
|
this.showError(err);
|
|
6251
6157
|
return;
|
|
6252
6158
|
}
|
|
6253
|
-
this.
|
|
6254
|
-
this.compressImage(file, 2e3, 1500, 0.92).then((c2) => this.handleFileUploadComplete(c2, container, theme, documentType)).catch(() => this.handleFileUploadComplete(file, container, theme, documentType));
|
|
6159
|
+
this.ensureUnderUploadLimit(file).then((c2) => this.handleFileUploadComplete(c2, container, theme, documentType));
|
|
6255
6160
|
}
|
|
6256
6161
|
});
|
|
6257
6162
|
this.root.appendChild(fileInput);
|
|
@@ -6452,54 +6357,17 @@ var SmartComplySDK = (() => {
|
|
|
6452
6357
|
if (!ctx) return;
|
|
6453
6358
|
ctx.drawImage(this.videoEl, 0, 0, canvas.width, canvas.height);
|
|
6454
6359
|
canvas.toBlob(
|
|
6455
|
-
|
|
6360
|
+
(blob) => {
|
|
6456
6361
|
if (blob) {
|
|
6457
6362
|
this.stopCamera();
|
|
6458
|
-
this.
|
|
6459
|
-
const quality = await this._assessImageQuality(canvas, ctx);
|
|
6460
|
-
this.showPreview(container, theme, blob, documentType, quality, true);
|
|
6363
|
+
this.showPreview(container, theme, blob, documentType, true);
|
|
6461
6364
|
}
|
|
6462
6365
|
},
|
|
6463
6366
|
"image/jpeg",
|
|
6464
6367
|
0.92
|
|
6465
6368
|
);
|
|
6466
6369
|
}
|
|
6467
|
-
|
|
6468
|
-
* Runs the local sharpness/glare/contrast/brightness check against a
|
|
6469
|
-
* captured frame. Returns null (never blocks) if quality can't be computed
|
|
6470
|
-
* — e.g. a tainted canvas from a cross-origin file, or an unsupported browser.
|
|
6471
|
-
*/
|
|
6472
|
-
async _assessImageQuality(canvas, ctx) {
|
|
6473
|
-
try {
|
|
6474
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
6475
|
-
return assessQuality(imageData);
|
|
6476
|
-
} catch {
|
|
6477
|
-
return null;
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
|
-
/** Same as _assessImageQuality but starting from a Blob/File (upload path). */
|
|
6481
|
-
async _assessBlobQuality(blob) {
|
|
6482
|
-
try {
|
|
6483
|
-
const url = URL.createObjectURL(blob);
|
|
6484
|
-
const imgEl = document.createElement("img");
|
|
6485
|
-
await new Promise((resolve, reject) => {
|
|
6486
|
-
imgEl.onload = () => resolve();
|
|
6487
|
-
imgEl.onerror = () => reject(new Error("Image load failed"));
|
|
6488
|
-
imgEl.src = url;
|
|
6489
|
-
});
|
|
6490
|
-
const canvas = document.createElement("canvas");
|
|
6491
|
-
canvas.width = imgEl.naturalWidth;
|
|
6492
|
-
canvas.height = imgEl.naturalHeight;
|
|
6493
|
-
const ctx = canvas.getContext("2d");
|
|
6494
|
-
URL.revokeObjectURL(url);
|
|
6495
|
-
if (!ctx) return null;
|
|
6496
|
-
ctx.drawImage(imgEl, 0, 0);
|
|
6497
|
-
return this._assessImageQuality(canvas, ctx);
|
|
6498
|
-
} catch {
|
|
6499
|
-
return null;
|
|
6500
|
-
}
|
|
6501
|
-
}
|
|
6502
|
-
showPreview(container, theme, imageData, documentType, quality, viaCamera) {
|
|
6370
|
+
showPreview(container, theme, imageData, documentType, viaCamera) {
|
|
6503
6371
|
if (this.root) this.root.innerHTML = "";
|
|
6504
6372
|
else {
|
|
6505
6373
|
this.root = document.createElement("div");
|
|
@@ -6532,29 +6400,9 @@ var SmartComplySDK = (() => {
|
|
|
6532
6400
|
sublabel.style.cssText = `color:${theme.textMuted};font-size:${TYPE.xs}px;text-align:center;line-height:1.6;`;
|
|
6533
6401
|
sublabel.textContent = "All text must be readable and all four corners must be visible.";
|
|
6534
6402
|
this.root.appendChild(sublabel);
|
|
6535
|
-
const qualityIsPoor = !!quality && !quality.isValid;
|
|
6536
|
-
if (qualityIsPoor && quality) {
|
|
6537
|
-
const warn = document.createElement("div");
|
|
6538
|
-
warn.style.cssText = `
|
|
6539
|
-
width:100%;box-sizing:border-box;padding:10px 14px;border-radius:10px;
|
|
6540
|
-
background:#eab30815;border:1px solid #eab30840;
|
|
6541
|
-
color:${theme.text};font-size:12px;line-height:1.5;
|
|
6542
|
-
`;
|
|
6543
|
-
const issueLine = document.createElement("div");
|
|
6544
|
-
issueLine.style.cssText = "font-weight:700;color:#b8860b;";
|
|
6545
|
-
issueLine.textContent = `⚠️ ${quality.message}`;
|
|
6546
|
-
warn.appendChild(issueLine);
|
|
6547
|
-
if (quality.recommendations.length > 0) {
|
|
6548
|
-
const rec = document.createElement("div");
|
|
6549
|
-
rec.style.cssText = "margin-top:4px;color:" + theme.textSecondary + ";";
|
|
6550
|
-
rec.textContent = quality.recommendations.join(" · ");
|
|
6551
|
-
warn.appendChild(rec);
|
|
6552
|
-
}
|
|
6553
|
-
this.root.appendChild(warn);
|
|
6554
|
-
}
|
|
6555
6403
|
const btnRow = document.createElement("div");
|
|
6556
6404
|
btnRow.style.cssText = "display:flex;gap:12px;width:100%;";
|
|
6557
|
-
const retakeBtn = this.createButton("Retake", theme,
|
|
6405
|
+
const retakeBtn = this.createButton("Retake", theme, false);
|
|
6558
6406
|
retakeBtn.style.flex = "1";
|
|
6559
6407
|
retakeBtn.addEventListener("click", () => {
|
|
6560
6408
|
if (img._blobUrl) {
|
|
@@ -6563,40 +6411,27 @@ var SmartComplySDK = (() => {
|
|
|
6563
6411
|
this.root.innerHTML = "";
|
|
6564
6412
|
this.renderChoiceScreen(container, theme, documentType);
|
|
6565
6413
|
});
|
|
6566
|
-
const
|
|
6414
|
+
const useBtn = this.createButton("Use Photo ✓", theme, true);
|
|
6415
|
+
useBtn.style.flex = "1";
|
|
6416
|
+
useBtn.addEventListener("click", () => {
|
|
6567
6417
|
if (img._blobUrl) {
|
|
6568
6418
|
URL.revokeObjectURL(img._blobUrl);
|
|
6569
6419
|
}
|
|
6570
6420
|
useBtn.textContent = "Processing...";
|
|
6571
6421
|
useBtn.disabled = true;
|
|
6572
|
-
this.
|
|
6573
|
-
};
|
|
6574
|
-
const useBtn = this.createButton(qualityIsPoor ? "Use Anyway ⚠" : "Use Photo ✓", theme, !qualityIsPoor);
|
|
6575
|
-
useBtn.style.flex = "1";
|
|
6576
|
-
if (qualityIsPoor) {
|
|
6577
|
-
let confirming = false;
|
|
6578
|
-
useBtn.addEventListener("click", () => {
|
|
6579
|
-
if (!confirming) {
|
|
6580
|
-
confirming = true;
|
|
6581
|
-
useBtn.textContent = "Tap again to confirm ⚠";
|
|
6582
|
-
return;
|
|
6583
|
-
}
|
|
6584
|
-
proceedWithPhoto();
|
|
6585
|
-
});
|
|
6586
|
-
} else {
|
|
6587
|
-
useBtn.addEventListener("click", proceedWithPhoto);
|
|
6588
|
-
}
|
|
6422
|
+
this.ensureUnderUploadLimit(imageData).then((c2) => this.handleCaptureComplete(c2, container, theme, documentType, viaCamera));
|
|
6423
|
+
});
|
|
6589
6424
|
btnRow.appendChild(retakeBtn);
|
|
6590
6425
|
btnRow.appendChild(useBtn);
|
|
6591
6426
|
this.root.appendChild(btnRow);
|
|
6592
6427
|
}
|
|
6593
|
-
handleCaptureComplete(imageData, container, theme, documentType, viaCamera) {
|
|
6428
|
+
handleCaptureComplete(imageData, container, theme, documentType, viaCamera, skipped = false) {
|
|
6594
6429
|
const requiresBack = this.shouldCaptureBackSide(documentType);
|
|
6595
6430
|
const wasCapturingFront = this.currentSide === "front";
|
|
6596
6431
|
if (wasCapturingFront) {
|
|
6597
6432
|
this.frontBlob = imageData;
|
|
6598
6433
|
this.frontViaCamera = viaCamera;
|
|
6599
|
-
} else {
|
|
6434
|
+
} else if (!skipped) {
|
|
6600
6435
|
this.backBlob = imageData;
|
|
6601
6436
|
}
|
|
6602
6437
|
if (requiresBack && wasCapturingFront) {
|
|
@@ -6616,24 +6451,39 @@ var SmartComplySDK = (() => {
|
|
|
6616
6451
|
};
|
|
6617
6452
|
this.resolveCapture?.(result);
|
|
6618
6453
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
this.showPreview(container, theme, imageData, documentType, quality, false);
|
|
6454
|
+
handleFileUploadComplete(imageData, container, theme, documentType) {
|
|
6455
|
+
this.showPreview(container, theme, imageData, documentType, false);
|
|
6622
6456
|
}
|
|
6623
6457
|
validateFile(file) {
|
|
6624
6458
|
const allowedTypes = ["image/jpeg", "image/jpg", "image/png"];
|
|
6625
6459
|
if (!allowedTypes.includes(file.type)) {
|
|
6626
6460
|
return "Invalid file type. Please upload a JPEG or PNG image.";
|
|
6627
6461
|
}
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
return "File is too large. Please upload an image smaller than 10MB.";
|
|
6462
|
+
if (file.size > MAX_DOCUMENT_BYTES) {
|
|
6463
|
+
return `File is too large. Please upload an image smaller than ${Math.round(MAX_DOCUMENT_BYTES / (1024 * 1024))}MB.`;
|
|
6631
6464
|
}
|
|
6632
6465
|
if (file.size < 1024) {
|
|
6633
6466
|
return "File is too small. Please upload a valid image file.";
|
|
6634
6467
|
}
|
|
6635
6468
|
return null;
|
|
6636
6469
|
}
|
|
6470
|
+
/**
|
|
6471
|
+
* Only shrinks/re-encodes when the file exceeds the backend's real upload
|
|
6472
|
+
* ceiling — otherwise returns the original bytes completely untouched.
|
|
6473
|
+
* Document photos become the face-comparison reference image later
|
|
6474
|
+
* (see backend _crop_and_save_face), so any avoidable re-encode is a
|
|
6475
|
+
* quality loss with no upside; compression here exists solely to keep
|
|
6476
|
+
* oversized captures under MAX_DOCUMENT_BYTES, not to "optimize" images
|
|
6477
|
+
* that already fit.
|
|
6478
|
+
*/
|
|
6479
|
+
async ensureUnderUploadLimit(source) {
|
|
6480
|
+
if (source.size <= MAX_DOCUMENT_BYTES) return source;
|
|
6481
|
+
try {
|
|
6482
|
+
return await this.compressImage(source, DOC_CAMERA_IDEAL_WIDTH, DOC_CAMERA_IDEAL_HEIGHT, 0.92);
|
|
6483
|
+
} catch {
|
|
6484
|
+
return source;
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6637
6487
|
handleFileUpload(container, theme) {
|
|
6638
6488
|
const input = this.root?.querySelector("#sc-doc-file-input");
|
|
6639
6489
|
if (input) input.click();
|
|
@@ -6870,7 +6720,11 @@ var SmartComplySDK = (() => {
|
|
|
6870
6720
|
return { icon: "🏦", desc: "Enter your 11-digit Bank Verification Number (BVN)", requiresBack: false };
|
|
6871
6721
|
}
|
|
6872
6722
|
if (u2.includes("NIN") || u2.includes("NATIONAL IDENTITY NUMBER")) {
|
|
6873
|
-
return {
|
|
6723
|
+
return {
|
|
6724
|
+
icon: "🆔",
|
|
6725
|
+
desc: isDocumentFlow ? "Capture or upload your NIN card (back optional)" : "Enter your 11-digit National Identification Number (NIN)",
|
|
6726
|
+
requiresBack: isDocumentFlow ? "optional" : false
|
|
6727
|
+
};
|
|
6874
6728
|
}
|
|
6875
6729
|
if (u2.includes("GHANA")) {
|
|
6876
6730
|
if (isDocumentFlow) return { icon: "🪪", desc: "Capture or upload your Ghana Card", requiresBack: requiresBackSide(typeName) };
|
|
@@ -7525,7 +7379,7 @@ var SmartComplySDK = (() => {
|
|
|
7525
7379
|
const requiresBackFromBackend = typeof idType !== "string" ? idType.requires_back_side : void 0;
|
|
7526
7380
|
const requiresBack = requiresBackFromBackend ?? info.requiresBack;
|
|
7527
7381
|
let subtitle2 = info.desc;
|
|
7528
|
-
if (requiresBack && isThisDocFlow) {
|
|
7382
|
+
if (requiresBack === true && isThisDocFlow) {
|
|
7529
7383
|
subtitle2 += " ⚠️ Both sides required";
|
|
7530
7384
|
}
|
|
7531
7385
|
const displayName = typeName.replace(/\s*with\s+face\s*/gi, " ").replace(/\s*\(NIN\)\s*/gi, " ").replace(/\s*\(BVN\)\s*/gi, " ").replace(/\s{2,}/g, " ").trim();
|
|
@@ -8390,7 +8244,7 @@ var SmartComplySDK = (() => {
|
|
|
8390
8244
|
container.appendChild(title);
|
|
8391
8245
|
const desc = document.createElement("div");
|
|
8392
8246
|
desc.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin-top:-8px;line-height:1.5;`;
|
|
8393
|
-
desc.textContent = "
|
|
8247
|
+
desc.textContent = "Fit your face inside the oval, then blink naturally while looking around for a few seconds.";
|
|
8394
8248
|
container.appendChild(desc);
|
|
8395
8249
|
const livenessContainer = document.createElement("div");
|
|
8396
8250
|
livenessContainer.style.cssText = "margin:8px 0;width:100%;";
|