smartcomply-web-sdk 1.0.45 → 1.0.48
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/CameraManager.d.ts +7 -0
- package/dist/camera/CameraManager.d.ts.map +1 -1
- package/dist/camera/CameraManager.js +9 -0
- package/dist/camera/CameraManager.js.map +1 -1
- package/dist/esm/camera/CameraManager.d.ts +7 -0
- package/dist/esm/camera/CameraManager.d.ts.map +1 -1
- package/dist/esm/camera/CameraManager.js +9 -0
- package/dist/esm/camera/CameraManager.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts +23 -5
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +698 -507
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/flow/theme.d.ts +7 -1
- package/dist/esm/flow/theme.d.ts.map +1 -1
- package/dist/esm/flow/theme.js +27 -2
- package/dist/esm/flow/theme.js.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.d.ts +1 -1
- package/dist/esm/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.js +5 -5
- package/dist/esm/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts +1 -0
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +55 -33
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +23 -5
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +698 -507
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/flow/theme.d.ts +7 -1
- package/dist/flow/theme.d.ts.map +1 -1
- package/dist/flow/theme.js +27 -2
- package/dist/flow/theme.js.map +1 -1
- package/dist/modules/liveness/LivenessUI.d.ts +1 -1
- package/dist/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/modules/liveness/LivenessUI.js +5 -5
- package/dist/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts +1 -0
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +55 -33
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +625 -430
- package/dist/smartcomply.browser.js.map +3 -3
- package/package.json +1 -1
|
@@ -311,6 +311,15 @@ var SmartComplySDK = (() => {
|
|
|
311
311
|
getStream() {
|
|
312
312
|
return this.stream;
|
|
313
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Adopt an externally-obtained stream (e.g. a pre-warmed getUserMedia
|
|
316
|
+
* stream) so stop() also releases it. Without this, a stream assigned
|
|
317
|
+
* directly by a caller instead of via open() is never tracked here and
|
|
318
|
+
* stop() becomes a no-op for it, leaking the camera.
|
|
319
|
+
*/
|
|
320
|
+
setStream(stream) {
|
|
321
|
+
this.stream = stream;
|
|
322
|
+
}
|
|
314
323
|
stop() {
|
|
315
324
|
if (this.stream) {
|
|
316
325
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
@@ -4842,7 +4851,7 @@ var SmartComplySDK = (() => {
|
|
|
4842
4851
|
}
|
|
4843
4852
|
if (this.timerEl) this.timerEl.textContent = "";
|
|
4844
4853
|
}
|
|
4845
|
-
showResult(status,
|
|
4854
|
+
showResult(status, _verificationId) {
|
|
4846
4855
|
return new Promise((resolve) => {
|
|
4847
4856
|
if (!this.overlayEl) {
|
|
4848
4857
|
resolve();
|
|
@@ -4859,11 +4868,10 @@ var SmartComplySDK = (() => {
|
|
|
4859
4868
|
border:3px solid ${ok ? "#22c55e" : "#ef4444"};">
|
|
4860
4869
|
${ok ? "\u2713" : "\u2715"}
|
|
4861
4870
|
</div>
|
|
4862
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "
|
|
4863
|
-
<div style="font-size:12px;opacity:0.6;line-height:1.6;">${ok ? "
|
|
4864
|
-
${verificationId ? `<div style="margin-top:12px;padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.07);display:inline-block;font-size:11px;color:rgba(255,255,255,0.4);">Ref: #${verificationId}</div>` : ""}
|
|
4871
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
4872
|
+
<div style="font-size:12px;opacity:0.6;line-height:1.6;">${ok ? "Hold on while we wrap up\u2026" : "Something went wrong. Please try again."}</div>
|
|
4865
4873
|
</div>`;
|
|
4866
|
-
setTimeout(resolve,
|
|
4874
|
+
setTimeout(resolve, 1800);
|
|
4867
4875
|
});
|
|
4868
4876
|
}
|
|
4869
4877
|
showTimeout(onRetry) {
|
|
@@ -5034,8 +5042,9 @@ var SmartComplySDK = (() => {
|
|
|
5034
5042
|
*/
|
|
5035
5043
|
async create(params) {
|
|
5036
5044
|
const formData = new FormData();
|
|
5037
|
-
const
|
|
5038
|
-
|
|
5045
|
+
const rawIdentifier = params.identifier || `ID-${Date.now()}`;
|
|
5046
|
+
const safeIdentifier = rawIdentifier.length > 20 ? rawIdentifier.slice(0, 20) : rawIdentifier;
|
|
5047
|
+
formData.append("identifier", safeIdentifier);
|
|
5039
5048
|
formData.append("identifier_type", params.identifier_type);
|
|
5040
5049
|
formData.append("country", params.country);
|
|
5041
5050
|
formData.append(
|
|
@@ -5153,16 +5162,21 @@ var SmartComplySDK = (() => {
|
|
|
5153
5162
|
};
|
|
5154
5163
|
try {
|
|
5155
5164
|
let stream;
|
|
5156
|
-
|
|
5157
|
-
stream =
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5165
|
+
if (params.prewarmedStream && params.prewarmedStream.active) {
|
|
5166
|
+
stream = params.prewarmedStream;
|
|
5167
|
+
camera.setStream(stream);
|
|
5168
|
+
} else {
|
|
5169
|
+
try {
|
|
5170
|
+
stream = await camera.open();
|
|
5171
|
+
} catch (camErr) {
|
|
5172
|
+
if (camErr.name === "NotAllowedError" || camErr.name === "PermissionDeniedError") {
|
|
5173
|
+
throw new Error("Camera access denied. Please allow camera permission in your browser settings and try again.");
|
|
5174
|
+
}
|
|
5175
|
+
if (camErr.name === "NotFoundError" || camErr.name === "DevicesNotFoundError") {
|
|
5176
|
+
throw new Error("No camera found. Please connect a camera and try again.");
|
|
5177
|
+
}
|
|
5178
|
+
throw new Error(`Camera error: ${camErr.message || "Unable to access camera"}`);
|
|
5164
5179
|
}
|
|
5165
|
-
throw new Error(`Camera error: ${camErr.message || "Unable to access camera"}`);
|
|
5166
5180
|
}
|
|
5167
5181
|
let capturedDocBlob = params.id_file;
|
|
5168
5182
|
if (!capturedDocBlob && params.verification_type === "document_verification") {
|
|
@@ -5222,11 +5236,6 @@ var SmartComplySDK = (() => {
|
|
|
5222
5236
|
});
|
|
5223
5237
|
ui2.updateInstruction("Hold still...");
|
|
5224
5238
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
5225
|
-
const autoshotBlob = await this.captureFrame(videoForDetection);
|
|
5226
|
-
tempVideo.pause();
|
|
5227
|
-
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
5228
|
-
throw new Error("Failed to capture autoshot selfie. Please ensure camera is working.");
|
|
5229
|
-
}
|
|
5230
5239
|
const challengeActions = actions || [...DEFAULT_CHALLENGE_ACTIONS];
|
|
5231
5240
|
let entry;
|
|
5232
5241
|
if (existingEntryId) {
|
|
@@ -5238,7 +5247,8 @@ var SmartComplySDK = (() => {
|
|
|
5238
5247
|
identifier_type: params.identifier_type,
|
|
5239
5248
|
country: params.country,
|
|
5240
5249
|
challenge_actions: challengeActions,
|
|
5241
|
-
|
|
5250
|
+
// W9: No autoshot at create time — real one captured post-challenge
|
|
5251
|
+
autoshot_file: void 0,
|
|
5242
5252
|
document: params.document || params.document_front || params.id_file || capturedDocBlob,
|
|
5243
5253
|
document_back: params.document_back,
|
|
5244
5254
|
identity_check: params.identity_check,
|
|
@@ -5248,6 +5258,9 @@ var SmartComplySDK = (() => {
|
|
|
5248
5258
|
fingerprint_id: deviceMeta.fingerprint_id
|
|
5249
5259
|
});
|
|
5250
5260
|
}
|
|
5261
|
+
tempVideo.pause();
|
|
5262
|
+
tempVideo.srcObject = null;
|
|
5263
|
+
tempVideo.remove();
|
|
5251
5264
|
const uiChallenge = {
|
|
5252
5265
|
actions: entry.challenge_actions,
|
|
5253
5266
|
time_limit_seconds: 45,
|
|
@@ -5280,8 +5293,11 @@ var SmartComplySDK = (() => {
|
|
|
5280
5293
|
const getLabel = (action) => uiChallenge.instructions?.[action] || this.describeAction(action);
|
|
5281
5294
|
const detectLoop = () => {
|
|
5282
5295
|
const now = performance.now();
|
|
5283
|
-
|
|
5284
|
-
|
|
5296
|
+
const remaining = pauseUntil - now;
|
|
5297
|
+
if (remaining > 0) {
|
|
5298
|
+
setTimeout(() => {
|
|
5299
|
+
animFrameId = requestAnimationFrame(detectLoop);
|
|
5300
|
+
}, remaining);
|
|
5285
5301
|
return;
|
|
5286
5302
|
}
|
|
5287
5303
|
if (pendingNextAction) {
|
|
@@ -5348,6 +5364,10 @@ var SmartComplySDK = (() => {
|
|
|
5348
5364
|
}
|
|
5349
5365
|
ui2.updateInstruction("Almost done \u2014 hold still...");
|
|
5350
5366
|
await new Promise((r2) => setTimeout(r2, 800));
|
|
5367
|
+
const autoshotBlob = await this.captureFrame(videoEl);
|
|
5368
|
+
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
5369
|
+
throw new Error("Failed to capture selfie. Please ensure your face is visible and try again.");
|
|
5370
|
+
}
|
|
5351
5371
|
const snapshotBlob = await this.captureFrame(videoEl);
|
|
5352
5372
|
let videoBlob;
|
|
5353
5373
|
let durationSeconds;
|
|
@@ -5645,8 +5665,25 @@ var SmartComplySDK = (() => {
|
|
|
5645
5665
|
isDark: false
|
|
5646
5666
|
}
|
|
5647
5667
|
};
|
|
5648
|
-
function getTheme(name) {
|
|
5649
|
-
|
|
5668
|
+
function getTheme(name, hex) {
|
|
5669
|
+
const base = { ...THEMES[name] || THEMES.default };
|
|
5670
|
+
if (hex && /^#[0-9A-Fa-f]{6}$/.test(hex.trim())) {
|
|
5671
|
+
const h2 = hex.trim();
|
|
5672
|
+
const hover = _darkenHex(h2, 15);
|
|
5673
|
+
base.primary = h2;
|
|
5674
|
+
base.primaryHover = hover;
|
|
5675
|
+
base.primaryGlow = `${h2}40`;
|
|
5676
|
+
base.borderFocus = h2;
|
|
5677
|
+
base.shimmer = `${h2}14`;
|
|
5678
|
+
}
|
|
5679
|
+
return base;
|
|
5680
|
+
}
|
|
5681
|
+
function _darkenHex(hex, amount) {
|
|
5682
|
+
const n2 = parseInt(hex.slice(1), 16);
|
|
5683
|
+
const r2 = Math.max(0, (n2 >> 16) - amount);
|
|
5684
|
+
const g2 = Math.max(0, (n2 >> 8 & 255) - amount);
|
|
5685
|
+
const b2 = Math.max(0, (n2 & 255) - amount);
|
|
5686
|
+
return `#${(r2 << 16 | g2 << 8 | b2).toString(16).padStart(6, "0")}`;
|
|
5650
5687
|
}
|
|
5651
5688
|
|
|
5652
5689
|
// src/utils/ImageQuality.ts
|
|
@@ -6582,13 +6619,13 @@ var SmartComplySDK = (() => {
|
|
|
6582
6619
|
|
|
6583
6620
|
// src/flow/SmartComplyFlow.ts
|
|
6584
6621
|
var COUNTRY_LABELS = {
|
|
6585
|
-
nigeria: "\
|
|
6586
|
-
global: "\
|
|
6587
|
-
ghana: "\
|
|
6588
|
-
kenya: "\
|
|
6589
|
-
south_africa: "\
|
|
6590
|
-
united_states: "\
|
|
6591
|
-
united_kingdom: "\
|
|
6622
|
+
nigeria: "\xF0\u0178\u2021\xB3\xF0\u0178\u2021\xAC Nigeria",
|
|
6623
|
+
global: "\xF0\u0178\u0152\x8D Other Countries",
|
|
6624
|
+
ghana: "\xF0\u0178\u2021\xAC\xF0\u0178\u2021\xAD Ghana",
|
|
6625
|
+
kenya: "\xF0\u0178\u2021\xB0\xF0\u0178\u2021\xAA Kenya",
|
|
6626
|
+
south_africa: "\xF0\u0178\u2021\xBF\xF0\u0178\u2021\xA6 South Africa",
|
|
6627
|
+
united_states: "\xF0\u0178\u2021\xBA\xF0\u0178\u2021\xB8 United States",
|
|
6628
|
+
united_kingdom: "\xF0\u0178\u2021\xAC\xF0\u0178\u2021\xA7 United Kingdom"
|
|
6592
6629
|
};
|
|
6593
6630
|
function isUploadFieldType(type) {
|
|
6594
6631
|
return type === "upload" || type === "image" || type === "file";
|
|
@@ -6602,37 +6639,37 @@ var SmartComplySDK = (() => {
|
|
|
6602
6639
|
return t2.includes("voter") || t2.includes("driver") || t2.includes("license") || t2.includes("national id") || t2.includes("national id card");
|
|
6603
6640
|
};
|
|
6604
6641
|
if (u2.includes("BVN") || u2.includes("BANK VERIFICATION")) {
|
|
6605
|
-
return { icon: "\
|
|
6642
|
+
return { icon: "\xF0\u0178\x8F\xA6", desc: "Enter your 11-digit Bank Verification Number (BVN)", requiresBack: false };
|
|
6606
6643
|
}
|
|
6607
6644
|
if (u2.includes("NIN") || u2.includes("NATIONAL IDENTITY NUMBER")) {
|
|
6608
|
-
return { icon: "\
|
|
6645
|
+
return { icon: "\xF0\u0178\u2020\u201D", desc: "Enter your 11-digit National Identification Number (NIN)", requiresBack: false };
|
|
6609
6646
|
}
|
|
6610
6647
|
if (u2.includes("GHANA")) {
|
|
6611
|
-
if (isDocumentFlow) return { icon: "\
|
|
6612
|
-
return { icon: "\
|
|
6648
|
+
if (isDocumentFlow) return { icon: "\xF0\u0178\xAA\xAA", desc: "Capture or upload your Ghana Card", requiresBack: true };
|
|
6649
|
+
return { icon: "\xF0\u0178\xAA\xAA", desc: "Enter your Ghana Card number", requiresBack: false };
|
|
6613
6650
|
}
|
|
6614
6651
|
if (u2.includes("SSNIT"))
|
|
6615
|
-
return { icon: "\
|
|
6652
|
+
return { icon: "\xF0\u0178\x8F\u203A\xEF\xB8\x8F", desc: "Enter your SSNIT number", requiresBack: false };
|
|
6616
6653
|
if (u2.includes("KENYA") || u2.includes("NATIONAL") && u2.includes("KENYA"))
|
|
6617
|
-
return { icon: "\
|
|
6654
|
+
return { icon: "\xF0\u0178\u2020\u201D", desc: "Enter your Kenya National ID number", requiresBack: false };
|
|
6618
6655
|
if (u2.includes("TIN") || u2.includes("TAX IDENTIFICATION"))
|
|
6619
|
-
return { icon: "\
|
|
6656
|
+
return { icon: "\xF0\u0178\xA7\xBE", desc: "Enter your Tax Identification Number (TIN)", requiresBack: false };
|
|
6620
6657
|
if (u2.includes("CAC") || u2.includes("CORPORATE AFFAIRS") || u2.includes("COMPANY"))
|
|
6621
|
-
return { icon: "\
|
|
6658
|
+
return { icon: "\xF0\u0178\x8F\xA2", desc: advanced ? "Enter RC number, company name & type" : "Enter your company registration number", requiresBack: false };
|
|
6622
6659
|
if (u2.includes("VOTER") || u2.includes("PVC")) {
|
|
6623
6660
|
const requiresBack = requiresBackSide(typeName);
|
|
6624
6661
|
return {
|
|
6625
|
-
icon: "\
|
|
6662
|
+
icon: "\xF0\u0178\u2014\xB3\xEF\xB8\x8F",
|
|
6626
6663
|
desc: isDocumentFlow ? `Capture or upload your voter's card${requiresBack ? " (front & back)" : ""}` : "Enter your voter ID number",
|
|
6627
6664
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6628
6665
|
};
|
|
6629
6666
|
}
|
|
6630
6667
|
if (u2.includes("PASSPORT"))
|
|
6631
|
-
return { icon: "\
|
|
6668
|
+
return { icon: "\xF0\u0178\u201C\u02DC", desc: isDocumentFlow ? "Capture or upload your passport" : "Enter your passport number", requiresBack: false };
|
|
6632
6669
|
if (u2.includes("DRIVER") || u2.includes("LICENSE") || u2.includes("LICENCE")) {
|
|
6633
6670
|
const requiresBack = requiresBackSide(typeName);
|
|
6634
6671
|
return {
|
|
6635
|
-
icon: "\
|
|
6672
|
+
icon: "\xF0\u0178\u0161\u2014",
|
|
6636
6673
|
desc: isDocumentFlow ? `Capture or upload your driver's license${requiresBack ? " (front & back)" : ""}` : "Enter your driver's license number",
|
|
6637
6674
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6638
6675
|
};
|
|
@@ -6640,15 +6677,15 @@ var SmartComplySDK = (() => {
|
|
|
6640
6677
|
if (u2.includes("NATIONAL ID") || u2.includes("NATIONAL") && u2.includes("CARD")) {
|
|
6641
6678
|
const requiresBack = requiresBackSide(typeName);
|
|
6642
6679
|
return {
|
|
6643
|
-
icon: "\
|
|
6680
|
+
icon: "\xF0\u0178\xAA\xAA",
|
|
6644
6681
|
desc: isDocumentFlow ? `Capture or upload your ID card${requiresBack ? " (front & back)" : ""}` : "Enter your National ID number",
|
|
6645
6682
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6646
6683
|
};
|
|
6647
6684
|
}
|
|
6648
6685
|
if (u2.includes("LIVENESS") || u2.includes("FACE COMPARISON") || u2.includes("FACE MATCH"))
|
|
6649
|
-
return { icon: "\
|
|
6686
|
+
return { icon: "\xF0\u0178\xA4\xB3", desc: "We'll capture a short selfie video to verify you", requiresBack: false };
|
|
6650
6687
|
return {
|
|
6651
|
-
icon: isDocumentFlow ? "\
|
|
6688
|
+
icon: isDocumentFlow ? "\xF0\u0178\u201C\u201E" : "\xF0\u0178\u2020\u201D",
|
|
6652
6689
|
desc: isDocumentFlow ? "Capture or upload your document" : "Enter your ID number",
|
|
6653
6690
|
requiresBack: false
|
|
6654
6691
|
};
|
|
@@ -6693,6 +6730,40 @@ var SmartComplySDK = (() => {
|
|
|
6693
6730
|
if (raw.trim()[0] === raw.trim()[0].toLowerCase() && /^[a-z]/.test(raw.trim())) return null;
|
|
6694
6731
|
return raw.trim();
|
|
6695
6732
|
}
|
|
6733
|
+
function _parseOcrDate(raw) {
|
|
6734
|
+
if (!raw) return null;
|
|
6735
|
+
const s2 = raw.trim();
|
|
6736
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(s2)) return new Date(s2.slice(0, 10));
|
|
6737
|
+
const dmy = s2.match(/^(\d{2})[\/\-](\d{2})[\/\-](\d{4})/);
|
|
6738
|
+
if (dmy) return /* @__PURE__ */ new Date(`${dmy[3]}-${dmy[2]}-${dmy[1]}`);
|
|
6739
|
+
const mdy = s2.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
|
|
6740
|
+
if (mdy) {
|
|
6741
|
+
if (parseInt(mdy[1], 10) > 12) return /* @__PURE__ */ new Date(`${mdy[3]}-${mdy[2]}-${mdy[1]}`);
|
|
6742
|
+
return /* @__PURE__ */ new Date(`${mdy[3]}-${mdy[1]}-${mdy[2]}`);
|
|
6743
|
+
}
|
|
6744
|
+
const dMonY = s2.match(/^(\d{1,2})\s+([A-Za-z]{3,})\s+(\d{4})/);
|
|
6745
|
+
if (dMonY) return /* @__PURE__ */ new Date(`${dMonY[2]} ${dMonY[1]} ${dMonY[3]}`);
|
|
6746
|
+
const d2 = new Date(s2);
|
|
6747
|
+
return isNaN(d2.getTime()) ? null : d2;
|
|
6748
|
+
}
|
|
6749
|
+
async function _compressImage(blob, maxWidth = 1600, quality = 0.85) {
|
|
6750
|
+
try {
|
|
6751
|
+
const bmp = await createImageBitmap(blob);
|
|
6752
|
+
const scale = Math.min(1, maxWidth / bmp.width);
|
|
6753
|
+
const w2 = Math.round(bmp.width * scale);
|
|
6754
|
+
const h2 = Math.round(bmp.height * scale);
|
|
6755
|
+
const canvas = document.createElement("canvas");
|
|
6756
|
+
canvas.width = w2;
|
|
6757
|
+
canvas.height = h2;
|
|
6758
|
+
canvas.getContext("2d").drawImage(bmp, 0, 0, w2, h2);
|
|
6759
|
+
bmp.close();
|
|
6760
|
+
return await new Promise(
|
|
6761
|
+
(res, rej) => canvas.toBlob((b2) => b2 ? res(b2) : rej(new Error("Compression failed")), "image/jpeg", quality)
|
|
6762
|
+
);
|
|
6763
|
+
} catch {
|
|
6764
|
+
return blob;
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6696
6767
|
function _formatDocType(code) {
|
|
6697
6768
|
const map = {
|
|
6698
6769
|
passport: "Passport",
|
|
@@ -6720,7 +6791,7 @@ var SmartComplySDK = (() => {
|
|
|
6720
6791
|
style.setAttribute("data-sc-animations", "true");
|
|
6721
6792
|
document.head.appendChild(style);
|
|
6722
6793
|
}
|
|
6723
|
-
var
|
|
6794
|
+
var _SmartComplyFlow = class _SmartComplyFlow {
|
|
6724
6795
|
constructor(options) {
|
|
6725
6796
|
this.theme = getTheme("default");
|
|
6726
6797
|
// DOM
|
|
@@ -6729,6 +6800,7 @@ var SmartComplySDK = (() => {
|
|
|
6729
6800
|
this.contentArea = null;
|
|
6730
6801
|
this.progressBar = null;
|
|
6731
6802
|
this.stepLabel = null;
|
|
6803
|
+
this.stageDots = null;
|
|
6732
6804
|
this.brandLabel = null;
|
|
6733
6805
|
// State
|
|
6734
6806
|
this.sdkConfig = null;
|
|
@@ -6751,13 +6823,31 @@ var SmartComplySDK = (() => {
|
|
|
6751
6823
|
this.livenessEntryId = null;
|
|
6752
6824
|
this.isDestroyed = false;
|
|
6753
6825
|
this.headerBackBtn = null;
|
|
6826
|
+
// Retry safety — max 3 confirmation rejections or liveness failures per session
|
|
6827
|
+
this._confirmRetries = 0;
|
|
6828
|
+
this._livenessFailures = 0;
|
|
6829
|
+
// IVS result stored for confirm_identity screen
|
|
6830
|
+
this._ivsName = "";
|
|
6831
|
+
this._ivsGender = "";
|
|
6832
|
+
// OCR result stored for document_confirm screen
|
|
6833
|
+
this._ocrName = "";
|
|
6834
|
+
this._ocrDocNumber = "";
|
|
6835
|
+
this._ocrExpiry = "";
|
|
6836
|
+
this._ocrFaceImageUrl = "";
|
|
6837
|
+
this._ocrIsExpired = false;
|
|
6838
|
+
this._ocrExpirySoon = false;
|
|
6839
|
+
// Camera pre-warm stream (created during confirmation screen, used in liveness)
|
|
6840
|
+
this._prewarmedStream = null;
|
|
6841
|
+
// OCR gate poll timer — stored so it can be cancelled on navigation or close
|
|
6842
|
+
this._ocrPollTimer = null;
|
|
6843
|
+
// UI timeout from _startStatusPolling — stored so close() can cancel it
|
|
6844
|
+
this._uiTimeoutId = null;
|
|
6754
6845
|
// Session expiry
|
|
6755
6846
|
this.sessionExpiryTimer = null;
|
|
6756
6847
|
// Status polling (result screen)
|
|
6757
6848
|
this._pollStatusTimer = null;
|
|
6758
6849
|
// Components
|
|
6759
6850
|
this.docCapture = null;
|
|
6760
|
-
this._livenessSubmitResult = null;
|
|
6761
6851
|
this.options = options;
|
|
6762
6852
|
this.sdk = new SmartComply({
|
|
6763
6853
|
apiKey: options.apiKey,
|
|
@@ -6791,6 +6881,10 @@ var SmartComplySDK = (() => {
|
|
|
6791
6881
|
close() {
|
|
6792
6882
|
this.isDestroyed = true;
|
|
6793
6883
|
this.docCapture?.destroy();
|
|
6884
|
+
if (this._prewarmedStream) {
|
|
6885
|
+
this._prewarmedStream.getTracks().forEach((t2) => t2.stop());
|
|
6886
|
+
this._prewarmedStream = null;
|
|
6887
|
+
}
|
|
6794
6888
|
if (this.sessionExpiryTimer) {
|
|
6795
6889
|
clearTimeout(this.sessionExpiryTimer);
|
|
6796
6890
|
this.sessionExpiryTimer = null;
|
|
@@ -6799,6 +6893,14 @@ var SmartComplySDK = (() => {
|
|
|
6799
6893
|
clearTimeout(this._pollStatusTimer);
|
|
6800
6894
|
this._pollStatusTimer = null;
|
|
6801
6895
|
}
|
|
6896
|
+
if (this._ocrPollTimer) {
|
|
6897
|
+
clearTimeout(this._ocrPollTimer);
|
|
6898
|
+
this._ocrPollTimer = null;
|
|
6899
|
+
}
|
|
6900
|
+
if (this._uiTimeoutId) {
|
|
6901
|
+
clearTimeout(this._uiTimeoutId);
|
|
6902
|
+
this._uiTimeoutId = null;
|
|
6903
|
+
}
|
|
6802
6904
|
if (this.overlay) {
|
|
6803
6905
|
this.overlay.style.opacity = "0";
|
|
6804
6906
|
setTimeout(() => {
|
|
@@ -6813,9 +6915,9 @@ var SmartComplySDK = (() => {
|
|
|
6813
6915
|
}
|
|
6814
6916
|
this.options.onClose?.();
|
|
6815
6917
|
}
|
|
6816
|
-
//
|
|
6918
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6817
6919
|
// Initialization
|
|
6818
|
-
//
|
|
6920
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6819
6921
|
mount() {
|
|
6820
6922
|
this.injectStyles();
|
|
6821
6923
|
this.overlay = document.createElement("div");
|
|
@@ -6888,7 +6990,7 @@ var SmartComplySDK = (() => {
|
|
|
6888
6990
|
}
|
|
6889
6991
|
this.sdkConfig = await this.sdk.initializeConfig();
|
|
6890
6992
|
if (this.sdkConfig.theme) {
|
|
6891
|
-
this.theme = getTheme(this.sdkConfig.theme);
|
|
6993
|
+
this.theme = getTheme(this.sdkConfig.theme, this.sdkConfig.theme_hex || null);
|
|
6892
6994
|
this.applyTheme();
|
|
6893
6995
|
}
|
|
6894
6996
|
if (this.brandLabel && this.sdkConfig.brand_name) {
|
|
@@ -6900,12 +7002,20 @@ var SmartComplySDK = (() => {
|
|
|
6900
7002
|
this.showErrorStep(err.message || "Failed to initialize. Please try again.");
|
|
6901
7003
|
}
|
|
6902
7004
|
}
|
|
6903
|
-
//
|
|
7005
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6904
7006
|
// Step rendering
|
|
6905
|
-
//
|
|
7007
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6906
7008
|
showStep(step) {
|
|
6907
7009
|
this.currentStep = step;
|
|
6908
7010
|
if (!this.contentArea) return;
|
|
7011
|
+
if (this._ocrPollTimer) {
|
|
7012
|
+
clearTimeout(this._ocrPollTimer);
|
|
7013
|
+
this._ocrPollTimer = null;
|
|
7014
|
+
}
|
|
7015
|
+
if (step !== "liveness" && this._prewarmedStream) {
|
|
7016
|
+
this._prewarmedStream.getTracks().forEach((t2) => t2.stop());
|
|
7017
|
+
this._prewarmedStream = null;
|
|
7018
|
+
}
|
|
6909
7019
|
this.updateProgress();
|
|
6910
7020
|
const oldContent = this.contentArea.firstChild;
|
|
6911
7021
|
if (oldContent) {
|
|
@@ -6938,17 +7048,27 @@ var SmartComplySDK = (() => {
|
|
|
6938
7048
|
case "id_input":
|
|
6939
7049
|
this.renderIdInput(wrapper);
|
|
6940
7050
|
break;
|
|
7051
|
+
case "confirm_identity":
|
|
7052
|
+
this.renderConfirmIdentity(wrapper);
|
|
7053
|
+
break;
|
|
6941
7054
|
case "document_capture":
|
|
6942
7055
|
this.renderDocumentCapture(wrapper);
|
|
6943
7056
|
break;
|
|
7057
|
+
case "ocr_gate":
|
|
7058
|
+
this.renderOcrGate(wrapper);
|
|
7059
|
+
break;
|
|
7060
|
+
case "document_confirm":
|
|
7061
|
+
this.renderDocumentConfirm(wrapper);
|
|
7062
|
+
break;
|
|
6944
7063
|
case "processing":
|
|
6945
|
-
this.renderProcessing(wrapper, this.livenessEntryId || void 0);
|
|
6946
7064
|
break;
|
|
6947
7065
|
case "liveness":
|
|
6948
7066
|
this.renderLiveness(wrapper);
|
|
6949
7067
|
break;
|
|
7068
|
+
case "done":
|
|
7069
|
+
this.renderDone(wrapper);
|
|
7070
|
+
break;
|
|
6950
7071
|
case "result":
|
|
6951
|
-
this.renderResult(wrapper);
|
|
6952
7072
|
break;
|
|
6953
7073
|
case "error":
|
|
6954
7074
|
break;
|
|
@@ -6959,7 +7079,7 @@ var SmartComplySDK = (() => {
|
|
|
6959
7079
|
wrapper.style.transform = "translateX(0)";
|
|
6960
7080
|
});
|
|
6961
7081
|
}
|
|
6962
|
-
//
|
|
7082
|
+
// ── Loading ─────────────────────────────────────────────────────
|
|
6963
7083
|
renderLoading(container) {
|
|
6964
7084
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;gap:16px;";
|
|
6965
7085
|
const spinnerWrap = document.createElement("div");
|
|
@@ -6990,7 +7110,7 @@ var SmartComplySDK = (() => {
|
|
|
6990
7110
|
sub.textContent = "Connecting to the verification service...";
|
|
6991
7111
|
container.appendChild(sub);
|
|
6992
7112
|
}
|
|
6993
|
-
//
|
|
7113
|
+
// ── Welcome ────────────────────────────────────────────────────
|
|
6994
7114
|
renderWelcome(container) {
|
|
6995
7115
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;";
|
|
6996
7116
|
const isDocFlow = this.isDocumentFlow();
|
|
@@ -7004,7 +7124,7 @@ var SmartComplySDK = (() => {
|
|
|
7004
7124
|
background:linear-gradient(135deg,${this.theme.primary}22,${this.theme.primaryGlow});
|
|
7005
7125
|
border:1.5px solid ${this.theme.primary}33;
|
|
7006
7126
|
`;
|
|
7007
|
-
iconWrap.textContent = isDocFlow ? "\
|
|
7127
|
+
iconWrap.textContent = isDocFlow ? "\xF0\u0178\xAA\xAA" : "\xF0\u0178\u203A\xA1\xEF\xB8\x8F";
|
|
7008
7128
|
topRow.appendChild(iconWrap);
|
|
7009
7129
|
const badge = document.createElement("div");
|
|
7010
7130
|
badge.style.cssText = `
|
|
@@ -7015,7 +7135,7 @@ var SmartComplySDK = (() => {
|
|
|
7015
7135
|
color:${this.theme.primary};
|
|
7016
7136
|
border:1px solid ${this.theme.primary}30;
|
|
7017
7137
|
`;
|
|
7018
|
-
badge.textContent = isDocFlow ? "\
|
|
7138
|
+
badge.textContent = isDocFlow ? "\xF0\u0178\u201C\u201E Document Verification" : "\xF0\u0178\u201D\x8D Data Verification";
|
|
7019
7139
|
topRow.appendChild(badge);
|
|
7020
7140
|
container.appendChild(topRow);
|
|
7021
7141
|
const titleWrap = document.createElement("div");
|
|
@@ -7025,24 +7145,25 @@ var SmartComplySDK = (() => {
|
|
|
7025
7145
|
color:${this.theme.text};font-size:20px;font-weight:700;
|
|
7026
7146
|
margin:0;line-height:1.2;
|
|
7027
7147
|
`;
|
|
7028
|
-
|
|
7148
|
+
const brandName = this.sdkConfig?.brand_name;
|
|
7149
|
+
title.textContent = brandName ? `${brandName} Identity Verification` : "Verify Your Identity";
|
|
7029
7150
|
titleWrap.appendChild(title);
|
|
7030
7151
|
const desc = document.createElement("p");
|
|
7031
7152
|
desc.style.cssText = `
|
|
7032
7153
|
color:${this.theme.textSecondary};font-size:13px;line-height:1.5;
|
|
7033
7154
|
margin:0;max-width:300px;
|
|
7034
7155
|
`;
|
|
7035
|
-
desc.textContent = isDocFlow ? "
|
|
7156
|
+
desc.textContent = isDocFlow ? "You'll need your document and camera access. This takes about 2 minutes." : "You'll need your ID number and camera access. This takes about 2 minutes.";
|
|
7036
7157
|
titleWrap.appendChild(desc);
|
|
7037
7158
|
container.appendChild(titleWrap);
|
|
7038
7159
|
const steps = isDocFlow ? [
|
|
7039
|
-
{ icon: "\
|
|
7040
|
-
{ icon: "\
|
|
7041
|
-
{ icon: "\
|
|
7160
|
+
{ icon: "\xF0\u0178\xAA\xAA", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
|
|
7161
|
+
{ icon: "\xF0\u0178\u201C\xB7", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
|
|
7162
|
+
{ icon: "\xF0\u0178\xA4\xB3", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
7042
7163
|
] : [
|
|
7043
|
-
{ icon: "\
|
|
7044
|
-
{ icon: "\
|
|
7045
|
-
{ icon: "\
|
|
7164
|
+
{ icon: "\xF0\u0178\u2020\u201D", text: "Choose your ID type", sub: "NIN, BVN, Voter's Card and more" },
|
|
7165
|
+
{ icon: "\xF0\u0178\u201C\x9D", text: "Enter your ID details", sub: "Exactly as shown on your ID" },
|
|
7166
|
+
{ icon: "\xF0\u0178\xA4\xB3", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
7046
7167
|
];
|
|
7047
7168
|
const stepsWrap = document.createElement("div");
|
|
7048
7169
|
stepsWrap.style.cssText = `
|
|
@@ -7082,7 +7203,7 @@ var SmartComplySDK = (() => {
|
|
|
7082
7203
|
stepsWrap.appendChild(row);
|
|
7083
7204
|
});
|
|
7084
7205
|
container.appendChild(stepsWrap);
|
|
7085
|
-
const btn = this.createPrimaryButton("Get Started \
|
|
7206
|
+
const btn = this.createPrimaryButton("Get Started \xE2\u2020\u2019");
|
|
7086
7207
|
btn.style.cssText += "margin-top:2px;";
|
|
7087
7208
|
btn.addEventListener("click", () => this.showStep("country"));
|
|
7088
7209
|
container.appendChild(btn);
|
|
@@ -7091,10 +7212,10 @@ var SmartComplySDK = (() => {
|
|
|
7091
7212
|
display:flex;align-items:center;gap:6px;
|
|
7092
7213
|
color:${this.theme.textMuted};font-size:10.5px;
|
|
7093
7214
|
`;
|
|
7094
|
-
trust.textContent = "\
|
|
7215
|
+
trust.textContent = "\xF0\u0178\u201D\u2019 End-to-end encrypted";
|
|
7095
7216
|
container.appendChild(trust);
|
|
7096
7217
|
}
|
|
7097
|
-
//
|
|
7218
|
+
// ── Country Select ──────────────────────────────────────────────
|
|
7098
7219
|
renderCountrySelect(container) {
|
|
7099
7220
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7100
7221
|
const channels = this.sdkConfig?.channels || {};
|
|
@@ -7122,7 +7243,7 @@ var SmartComplySDK = (() => {
|
|
|
7122
7243
|
container.appendChild(card);
|
|
7123
7244
|
}
|
|
7124
7245
|
}
|
|
7125
|
-
//
|
|
7246
|
+
// ── ID Type Select ──────────────────────────────────────────────
|
|
7126
7247
|
renderIdTypeSelect(container) {
|
|
7127
7248
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7128
7249
|
const globalDocFlow = this.isDocumentFlow();
|
|
@@ -7144,7 +7265,7 @@ var SmartComplySDK = (() => {
|
|
|
7144
7265
|
const requiresBack = requiresBackFromBackend ?? info.requiresBack;
|
|
7145
7266
|
let subtitle2 = info.desc;
|
|
7146
7267
|
if (requiresBack && isThisDocFlow) {
|
|
7147
|
-
subtitle2 += " \
|
|
7268
|
+
subtitle2 += " \xE2\u0161\xA0\xEF\xB8\x8F Both sides required";
|
|
7148
7269
|
}
|
|
7149
7270
|
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();
|
|
7150
7271
|
const card = this.createOptionCard(
|
|
@@ -7177,7 +7298,7 @@ var SmartComplySDK = (() => {
|
|
|
7177
7298
|
container.appendChild(card);
|
|
7178
7299
|
}
|
|
7179
7300
|
}
|
|
7180
|
-
//
|
|
7301
|
+
// ── ID Input (data verification — dynamic fields) ─────────────
|
|
7181
7302
|
renderIdInput(container) {
|
|
7182
7303
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7183
7304
|
const dataFields = this.selectedChannelFields.filter((f2) => f2.type !== "upload");
|
|
@@ -7207,13 +7328,13 @@ var SmartComplySDK = (() => {
|
|
|
7207
7328
|
{ match: "BVN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "BVN must be exactly 11 digits" } },
|
|
7208
7329
|
{ match: "NIN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7209
7330
|
{ match: "NATIONAL IDENTITY", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7210
|
-
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6\
|
|
7211
|
-
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3\
|
|
7212
|
-
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6\
|
|
7213
|
-
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8\
|
|
7214
|
-
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7\
|
|
7215
|
-
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8\
|
|
7216
|
-
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10\
|
|
7331
|
+
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6\xE2\u20AC\u201C20 alphanumeric characters" } },
|
|
7332
|
+
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3\xE2\u20AC\u201C30 alphanumeric characters" } },
|
|
7333
|
+
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6\xE2\u20AC\u201C30 alphanumeric characters" } },
|
|
7334
|
+
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8\xE2\u20AC\u201C20 alphanumeric characters" } },
|
|
7335
|
+
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7\xE2\u20AC\u201C15 alphanumeric characters" } },
|
|
7336
|
+
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8\xE2\u20AC\u201C20 characters" } },
|
|
7337
|
+
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10\xE2\u20AC\u201C12 characters" } }
|
|
7217
7338
|
];
|
|
7218
7339
|
function getFieldRule(label) {
|
|
7219
7340
|
const upper = label.toUpperCase();
|
|
@@ -7411,7 +7532,15 @@ var SmartComplySDK = (() => {
|
|
|
7411
7532
|
const fieldValues = Object.values(this.collectedFields);
|
|
7412
7533
|
this.idNumber = fieldValues[0] || "";
|
|
7413
7534
|
this.identityCheckId = this.verificationResult.data?.identity_check_id || null;
|
|
7414
|
-
this.
|
|
7535
|
+
const d2 = this.verificationResult.data || {};
|
|
7536
|
+
const nd = d2.normalized_data || d2.data || d2;
|
|
7537
|
+
this._ivsName = [nd.first_name || nd.firstname || "", nd.last_name || nd.lastname || nd.surname || ""].map((s2) => String(s2 || "").trim()).filter(Boolean).join(" ");
|
|
7538
|
+
this._ivsGender = nd.gender || "";
|
|
7539
|
+
if (!this._ivsName) {
|
|
7540
|
+
this.showStep("liveness");
|
|
7541
|
+
return;
|
|
7542
|
+
}
|
|
7543
|
+
this.showStep("confirm_identity");
|
|
7415
7544
|
} else {
|
|
7416
7545
|
errorEl.textContent = this.verificationResult.message || "We could not verify your details. Please check and try again.";
|
|
7417
7546
|
errorEl.style.display = "block";
|
|
@@ -7445,7 +7574,7 @@ var SmartComplySDK = (() => {
|
|
|
7445
7574
|
setTimeout(() => firstInput.focus(), 350);
|
|
7446
7575
|
}
|
|
7447
7576
|
}
|
|
7448
|
-
//
|
|
7577
|
+
// ── Document Capture ────────────────────────────────────────────
|
|
7449
7578
|
renderDocumentCapture(container) {
|
|
7450
7579
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7451
7580
|
const rawName = this.selectedIdTypeName || this.selectedIdType || "your document";
|
|
@@ -7481,123 +7610,366 @@ var SmartComplySDK = (() => {
|
|
|
7481
7610
|
this.showErrorStep(err.message || "Document capture failed. Please try again.");
|
|
7482
7611
|
});
|
|
7483
7612
|
}
|
|
7484
|
-
//
|
|
7485
|
-
|
|
7486
|
-
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:16px;padding:
|
|
7613
|
+
// ── W3: Identity Confirmation (data verification) ───────────────
|
|
7614
|
+
renderConfirmIdentity(container) {
|
|
7615
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:16px;padding:8px 0;";
|
|
7616
|
+
if (this._confirmRetries >= _SmartComplyFlow.MAX_RETRIES) {
|
|
7617
|
+
this._renderRetryLimitReached(container);
|
|
7618
|
+
return;
|
|
7619
|
+
}
|
|
7620
|
+
this._prewarmCamera();
|
|
7621
|
+
const iconWrap = document.createElement("div");
|
|
7622
|
+
iconWrap.style.cssText = `width:64px;height:64px;border-radius:50%;background:${this.theme.primary}15;border:2px solid ${this.theme.primary}30;display:flex;align-items:center;justify-content:center;font-size:28px;`;
|
|
7623
|
+
iconWrap.textContent = "\xF0\u0178\u203A\xA1\xEF\xB8\x8F";
|
|
7624
|
+
container.appendChild(iconWrap);
|
|
7625
|
+
const title = document.createElement("h2");
|
|
7626
|
+
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7627
|
+
title.textContent = this._ivsName ? `Is this you?` : "Identity Confirmed";
|
|
7628
|
+
container.appendChild(title);
|
|
7629
|
+
if (this._ivsName) {
|
|
7630
|
+
const card = document.createElement("div");
|
|
7631
|
+
card.style.cssText = `width:100%;padding:16px;border-radius:14px;background:${this.theme.cardBg};border:1.5px solid ${this.theme.border};text-align:left;display:flex;flex-direction:column;gap:10px;`;
|
|
7632
|
+
const rows = [
|
|
7633
|
+
{ label: "Name", value: this._ivsName }
|
|
7634
|
+
];
|
|
7635
|
+
if (this._ivsGender) {
|
|
7636
|
+
const gLabel = this._ivsGender.toLowerCase();
|
|
7637
|
+
rows.push({ label: "Gender", value: gLabel === "m" || gLabel === "male" || gLabel === "1" ? "Male" : gLabel === "f" || gLabel === "female" || gLabel === "2" ? "Female" : this._ivsGender });
|
|
7638
|
+
}
|
|
7639
|
+
rows.push({ label: "ID Type", value: _formatDocType(this.selectedIdType) || this.selectedIdTypeName || this.selectedIdType });
|
|
7640
|
+
rows.forEach(({ label, value }) => {
|
|
7641
|
+
const row = document.createElement("div");
|
|
7642
|
+
row.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7643
|
+
const lEl = document.createElement("span");
|
|
7644
|
+
lEl.style.cssText = `color:${this.theme.textMuted};font-size:12px;font-weight:500;`;
|
|
7645
|
+
lEl.textContent = label;
|
|
7646
|
+
const vEl = document.createElement("span");
|
|
7647
|
+
vEl.style.cssText = `color:${this.theme.text};font-size:13px;font-weight:600;max-width:60%;text-align:right;`;
|
|
7648
|
+
vEl.textContent = value;
|
|
7649
|
+
row.appendChild(lEl);
|
|
7650
|
+
row.appendChild(vEl);
|
|
7651
|
+
card.appendChild(row);
|
|
7652
|
+
});
|
|
7653
|
+
container.appendChild(card);
|
|
7654
|
+
} else {
|
|
7655
|
+
const sub = document.createElement("p");
|
|
7656
|
+
sub.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7657
|
+
sub.textContent = "Your details have been confirmed. Please continue to the face check.";
|
|
7658
|
+
container.appendChild(sub);
|
|
7659
|
+
}
|
|
7660
|
+
const yesBtn = this.createPrimaryButton("Yes, that's me \xE2\u20AC\u201D continue");
|
|
7661
|
+
yesBtn.addEventListener("click", () => {
|
|
7662
|
+
this._confirmRetries = 0;
|
|
7663
|
+
this.showStep("liveness");
|
|
7664
|
+
});
|
|
7665
|
+
container.appendChild(yesBtn);
|
|
7666
|
+
const noBtn = this.createSecondaryButton("No, use a different ID");
|
|
7667
|
+
noBtn.addEventListener("click", () => {
|
|
7668
|
+
this._confirmRetries++;
|
|
7669
|
+
this._ivsName = "";
|
|
7670
|
+
this._ivsGender = "";
|
|
7671
|
+
this.identityCheckId = null;
|
|
7672
|
+
this.verificationResult = null;
|
|
7673
|
+
this.showStep("id_input");
|
|
7674
|
+
});
|
|
7675
|
+
container.appendChild(noBtn);
|
|
7676
|
+
}
|
|
7677
|
+
// ── W4: Document Confirmation (document verification) ────────────
|
|
7678
|
+
renderDocumentConfirm(container) {
|
|
7679
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:8px 0;";
|
|
7680
|
+
if (this._confirmRetries >= _SmartComplyFlow.MAX_RETRIES) {
|
|
7681
|
+
this._renderRetryLimitReached(container);
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
this._prewarmCamera();
|
|
7685
|
+
const iconWrap = document.createElement("div");
|
|
7686
|
+
iconWrap.style.cssText = `width:64px;height:64px;border-radius:50%;background:${this.theme.primary}15;border:2px solid ${this.theme.primary}30;display:flex;align-items:center;justify-content:center;font-size:28px;`;
|
|
7687
|
+
iconWrap.textContent = "\xF0\u0178\u201C\u201E";
|
|
7688
|
+
container.appendChild(iconWrap);
|
|
7689
|
+
const title = document.createElement("h2");
|
|
7690
|
+
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7691
|
+
title.textContent = "Are these your details?";
|
|
7692
|
+
container.appendChild(title);
|
|
7693
|
+
const sub = document.createElement("p");
|
|
7694
|
+
sub.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.5;`;
|
|
7695
|
+
sub.textContent = "We read the following from your document. Please confirm they are correct before continuing.";
|
|
7696
|
+
container.appendChild(sub);
|
|
7697
|
+
if (this._ocrFaceImageUrl) {
|
|
7698
|
+
const faceWrap = document.createElement("div");
|
|
7699
|
+
faceWrap.style.cssText = `width:80px;height:80px;border-radius:50%;overflow:hidden;border:3px solid ${this.theme.primary};flex-shrink:0;margin:0 auto;`;
|
|
7700
|
+
const faceImg = document.createElement("img");
|
|
7701
|
+
faceImg.src = this._ocrFaceImageUrl;
|
|
7702
|
+
faceImg.alt = "Photo from your document";
|
|
7703
|
+
faceImg.style.cssText = "width:100%;height:100%;object-fit:cover;";
|
|
7704
|
+
faceWrap.appendChild(faceImg);
|
|
7705
|
+
container.appendChild(faceWrap);
|
|
7706
|
+
}
|
|
7707
|
+
if (this._ocrExpirySoon && !this._ocrIsExpired) {
|
|
7708
|
+
const warn = document.createElement("div");
|
|
7709
|
+
warn.style.cssText = `width:100%;padding:10px 14px;border-radius:10px;background:${this.theme.warning}18;border:1px solid ${this.theme.warning}40;color:${this.theme.warning};font-size:12px;text-align:left;line-height:1.5;`;
|
|
7710
|
+
warn.textContent = "\xE2\u0161\xA0\xEF\xB8\x8F Your document expires soon. Some services may not accept it, but you can still continue.";
|
|
7711
|
+
container.appendChild(warn);
|
|
7712
|
+
}
|
|
7713
|
+
const card = document.createElement("div");
|
|
7714
|
+
card.style.cssText = `width:100%;padding:14px 16px;border-radius:14px;background:${this.theme.cardBg};border:1.5px solid ${this.theme.border};text-align:left;display:flex;flex-direction:column;gap:10px;`;
|
|
7715
|
+
const docRows = [];
|
|
7716
|
+
if (this._ocrName) docRows.push({ label: "Name", value: this._ocrName });
|
|
7717
|
+
if (this._ocrDocNumber) docRows.push({ label: "Document No", value: this._ocrDocNumber });
|
|
7718
|
+
if (this._ocrExpiry) docRows.push({ label: "Expires", value: this._ocrExpiry });
|
|
7719
|
+
docRows.push({ label: "Document Type", value: _formatDocType(this.selectedIdType) || this.selectedIdTypeName });
|
|
7720
|
+
if (docRows.length === 0) {
|
|
7721
|
+
const fallback = document.createElement("div");
|
|
7722
|
+
fallback.style.cssText = `color:${this.theme.textSecondary};font-size:12px;`;
|
|
7723
|
+
fallback.textContent = "Document scanned successfully.";
|
|
7724
|
+
card.appendChild(fallback);
|
|
7725
|
+
} else {
|
|
7726
|
+
docRows.forEach(({ label, value }) => {
|
|
7727
|
+
if (!value) return;
|
|
7728
|
+
const row = document.createElement("div");
|
|
7729
|
+
row.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7730
|
+
const lEl = document.createElement("span");
|
|
7731
|
+
lEl.style.cssText = `color:${this.theme.textMuted};font-size:12px;font-weight:500;`;
|
|
7732
|
+
lEl.textContent = label;
|
|
7733
|
+
const vEl = document.createElement("span");
|
|
7734
|
+
vEl.style.cssText = `color:${this.theme.text};font-size:13px;font-weight:600;max-width:60%;text-align:right;`;
|
|
7735
|
+
vEl.textContent = value;
|
|
7736
|
+
row.appendChild(lEl);
|
|
7737
|
+
row.appendChild(vEl);
|
|
7738
|
+
card.appendChild(row);
|
|
7739
|
+
});
|
|
7740
|
+
}
|
|
7741
|
+
container.appendChild(card);
|
|
7742
|
+
const yesBtn = this.createPrimaryButton("Yes, continue");
|
|
7743
|
+
yesBtn.addEventListener("click", () => {
|
|
7744
|
+
this._confirmRetries = 0;
|
|
7745
|
+
this.showStep("liveness");
|
|
7746
|
+
});
|
|
7747
|
+
container.appendChild(yesBtn);
|
|
7748
|
+
const noBtn = this.createSecondaryButton("No, retake document");
|
|
7749
|
+
noBtn.addEventListener("click", () => {
|
|
7750
|
+
this._confirmRetries++;
|
|
7751
|
+
this._ocrName = "";
|
|
7752
|
+
this._ocrDocNumber = "";
|
|
7753
|
+
this._ocrExpiry = "";
|
|
7754
|
+
this._ocrFaceImageUrl = "";
|
|
7755
|
+
this._ocrIsExpired = false;
|
|
7756
|
+
this._ocrExpirySoon = false;
|
|
7757
|
+
this.livenessEntryId = null;
|
|
7758
|
+
this.documentBlob = null;
|
|
7759
|
+
this.documentBackBlob = null;
|
|
7760
|
+
this.showStep("document_capture");
|
|
7761
|
+
});
|
|
7762
|
+
container.appendChild(noBtn);
|
|
7763
|
+
}
|
|
7764
|
+
// ── W6+W7+W8: OCR Gate (document verification) ───────────────────
|
|
7765
|
+
renderOcrGate(container) {
|
|
7766
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:14px;padding:20px 0;text-align:center;";
|
|
7487
7767
|
const spinnerWrap = document.createElement("div");
|
|
7488
|
-
spinnerWrap.style.cssText = "position:relative;width:
|
|
7768
|
+
spinnerWrap.style.cssText = "position:relative;width:52px;height:52px;flex-shrink:0;";
|
|
7489
7769
|
const ring = document.createElement("div");
|
|
7490
|
-
ring.id = "sc-proc-ring";
|
|
7491
7770
|
ring.style.cssText = `position:absolute;inset:0;border-radius:50%;border:3px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.9s linear infinite;`;
|
|
7492
|
-
const
|
|
7493
|
-
|
|
7494
|
-
|
|
7771
|
+
const centre = document.createElement("div");
|
|
7772
|
+
centre.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;";
|
|
7773
|
+
centre.textContent = "\xF0\u0178\u201C\u201E";
|
|
7495
7774
|
spinnerWrap.appendChild(ring);
|
|
7496
|
-
spinnerWrap.appendChild(
|
|
7775
|
+
spinnerWrap.appendChild(centre);
|
|
7497
7776
|
container.appendChild(spinnerWrap);
|
|
7498
|
-
const
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
{ id: "result", label: "Finalising result", icon: "\u2713" }
|
|
7514
|
-
];
|
|
7515
|
-
const grid = document.createElement("div");
|
|
7516
|
-
grid.style.cssText = "width:100%;display:grid;grid-template-columns:1fr 1fr;gap:8px;";
|
|
7517
|
-
stages.forEach((s2) => {
|
|
7518
|
-
const el = document.createElement("div");
|
|
7519
|
-
el.id = `sc-stage-${s2.id}`;
|
|
7520
|
-
el.style.cssText = `display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:10px;background:${this.theme.cardBg};border:1px solid ${this.theme.border};transition:all 0.3s ease;`;
|
|
7521
|
-
el.innerHTML = `
|
|
7522
|
-
<div style="width:26px;height:26px;border-radius:7px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;background:${this.theme.shimmer};">${s2.icon}</div>
|
|
7523
|
-
<div style="flex:1;color:${this.theme.textSecondary};font-size:11px;font-weight:500;">${s2.label}</div>
|
|
7524
|
-
<div id="sc-stage-status-${s2.id}" style="width:14px;height:14px;border-radius:50%;border:2px solid ${this.theme.border};flex-shrink:0;"></div>
|
|
7525
|
-
`;
|
|
7526
|
-
grid.appendChild(el);
|
|
7527
|
-
});
|
|
7528
|
-
container.appendChild(grid);
|
|
7529
|
-
if (entryId) {
|
|
7530
|
-
this._pollProcessingStages(entryId, statusLine, grid, stages);
|
|
7531
|
-
}
|
|
7532
|
-
}
|
|
7533
|
-
_pollProcessingStages(entryId, statusLine, _grid, stages) {
|
|
7534
|
-
const stageTimings = [0, 4e3, 1e4, 18e3];
|
|
7535
|
-
const startTime = Date.now();
|
|
7536
|
-
const setStageActive = (stageId) => {
|
|
7537
|
-
const el = document.getElementById(`sc-stage-${stageId}`);
|
|
7538
|
-
const statusEl = document.getElementById(`sc-stage-status-${stageId}`);
|
|
7539
|
-
if (!el || !statusEl) return;
|
|
7540
|
-
el.style.borderColor = this.theme.primary;
|
|
7541
|
-
el.style.background = `${this.theme.primary}10`;
|
|
7542
|
-
statusEl.innerHTML = `<div style="position:absolute;inset:1px;border-radius:50%;border:2px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.8s linear infinite;"></div>`;
|
|
7543
|
-
statusEl.style.position = "relative";
|
|
7544
|
-
};
|
|
7545
|
-
const setStageComplete = (stageId) => {
|
|
7546
|
-
const el = document.getElementById(`sc-stage-${stageId}`);
|
|
7547
|
-
const statusEl = document.getElementById(`sc-stage-status-${stageId}`);
|
|
7548
|
-
if (!el || !statusEl) return;
|
|
7549
|
-
el.style.borderColor = this.theme.success || "#22c55e";
|
|
7550
|
-
el.style.background = `${this.theme.success || "#22c55e"}10`;
|
|
7551
|
-
statusEl.innerHTML = `<div style="position:absolute;inset:2px;border-radius:50%;background:${this.theme.success || "#22c55e"};display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:bold;">\u2713</div>`;
|
|
7552
|
-
statusEl.style.position = "relative";
|
|
7553
|
-
};
|
|
7554
|
-
stages.forEach((s2, i2) => {
|
|
7555
|
-
setTimeout(() => {
|
|
7556
|
-
if (this.isDestroyed) return;
|
|
7557
|
-
setStageActive(s2.id);
|
|
7558
|
-
if (i2 > 0) setStageComplete(stages[i2 - 1].id);
|
|
7559
|
-
statusLine.textContent = s2.id === "ocr" ? "Scanning document\u2026" : s2.id === "expiry" ? "Checking expiry date\u2026" : s2.id === "face" ? "Running face match\u2026" : "Finalising result\u2026";
|
|
7560
|
-
}, stageTimings[i2]);
|
|
7561
|
-
});
|
|
7562
|
-
const MAX_POLLS = 30;
|
|
7777
|
+
const statusEl = document.createElement("div");
|
|
7778
|
+
statusEl.setAttribute("aria-live", "polite");
|
|
7779
|
+
statusEl.style.cssText = `color:${this.theme.text};font-size:15px;font-weight:600;`;
|
|
7780
|
+
statusEl.textContent = "Reading your document\xE2\u20AC\xA6";
|
|
7781
|
+
container.appendChild(statusEl);
|
|
7782
|
+
const subEl = document.createElement("div");
|
|
7783
|
+
subEl.style.cssText = `color:${this.theme.textSecondary};font-size:12px;max-width:260px;line-height:1.5;`;
|
|
7784
|
+
subEl.textContent = "This usually takes 5\xE2\u20AC\u201C15 seconds. Please wait.";
|
|
7785
|
+
container.appendChild(subEl);
|
|
7786
|
+
if (!this.livenessEntryId) {
|
|
7787
|
+
this.showErrorStep("Document upload failed. Please try again.");
|
|
7788
|
+
return;
|
|
7789
|
+
}
|
|
7790
|
+
const entryId = this.livenessEntryId;
|
|
7791
|
+
const MAX_POLLS = 10;
|
|
7563
7792
|
let polls = 0;
|
|
7564
7793
|
const poll = async () => {
|
|
7565
7794
|
if (this.isDestroyed) return;
|
|
7566
7795
|
polls++;
|
|
7567
7796
|
try {
|
|
7568
7797
|
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7569
|
-
if (result.
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7798
|
+
if (result.is_expired === true) {
|
|
7799
|
+
container.innerHTML = "";
|
|
7800
|
+
this._renderExpiredDocument(container);
|
|
7801
|
+
return;
|
|
7802
|
+
}
|
|
7803
|
+
if (result.ocr_quality_failed === true) {
|
|
7804
|
+
container.innerHTML = "";
|
|
7805
|
+
this._renderOcrQualityFail(container, result.ocr_rejection_reason);
|
|
7806
|
+
return;
|
|
7807
|
+
}
|
|
7808
|
+
const ocrDone = result.ocr_status === "done" || result.ocr_status === "ocr_complete" || result.ocr_name || result.ocr_document_number || result.ocr_expiry_date;
|
|
7809
|
+
if (ocrDone) {
|
|
7810
|
+
this._ocrName = result.ocr_name || "";
|
|
7811
|
+
this._ocrDocNumber = result.ocr_document_number || "";
|
|
7812
|
+
this._ocrExpiry = result.ocr_expiry_date || "";
|
|
7813
|
+
this._ocrFaceImageUrl = result.ocr_face_image_url || "";
|
|
7814
|
+
this._ocrIsExpired = result.is_expired === true;
|
|
7815
|
+
if (this._ocrExpiry) {
|
|
7816
|
+
const expDate = _parseOcrDate(this._ocrExpiry);
|
|
7817
|
+
if (expDate) {
|
|
7818
|
+
const sixMonths = /* @__PURE__ */ new Date();
|
|
7819
|
+
sixMonths.setMonth(sixMonths.getMonth() + 6);
|
|
7820
|
+
this._ocrExpirySoon = expDate <= sixMonths && expDate > /* @__PURE__ */ new Date();
|
|
7576
7821
|
}
|
|
7577
|
-
}
|
|
7822
|
+
}
|
|
7823
|
+
this.showStep("document_confirm");
|
|
7578
7824
|
return;
|
|
7579
7825
|
}
|
|
7826
|
+
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7827
|
+
this._ocrPollTimer = setTimeout(poll, 2e3);
|
|
7828
|
+
} else if (!this.isDestroyed) {
|
|
7829
|
+
statusEl.textContent = "Taking longer than usual\xE2\u20AC\xA6";
|
|
7830
|
+
subEl.textContent = "We'll continue \xE2\u20AC\u201D you can confirm your details on the next screen.";
|
|
7831
|
+
this._ocrPollTimer = setTimeout(() => {
|
|
7832
|
+
if (!this.isDestroyed) this.showStep("document_confirm");
|
|
7833
|
+
}, 2e3);
|
|
7834
|
+
}
|
|
7580
7835
|
} catch {
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
statusLine.textContent = "Still processing \u2014 check back soon";
|
|
7836
|
+
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7837
|
+
this._ocrPollTimer = setTimeout(poll, 3e3);
|
|
7838
|
+
} else if (!this.isDestroyed) {
|
|
7839
|
+
this.showErrorStep("We could not read your document. Please check your connection and try again.");
|
|
7840
|
+
}
|
|
7587
7841
|
}
|
|
7588
7842
|
};
|
|
7589
|
-
this.
|
|
7843
|
+
this._ocrPollTimer = setTimeout(poll, 3e3);
|
|
7844
|
+
}
|
|
7845
|
+
_renderExpiredDocument(container) {
|
|
7846
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7847
|
+
const icon = document.createElement("div");
|
|
7848
|
+
icon.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;`;
|
|
7849
|
+
icon.textContent = "\xF0\u0178\u201C\u2026";
|
|
7850
|
+
container.appendChild(icon);
|
|
7851
|
+
const t2 = document.createElement("h2");
|
|
7852
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7853
|
+
t2.textContent = "Document Expired";
|
|
7854
|
+
container.appendChild(t2);
|
|
7855
|
+
const d2 = document.createElement("p");
|
|
7856
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7857
|
+
d2.textContent = "The document you submitted has expired. Please use a valid, current ID.";
|
|
7858
|
+
container.appendChild(d2);
|
|
7859
|
+
const retryBtn = this.createPrimaryButton("Try a different document");
|
|
7860
|
+
retryBtn.addEventListener("click", () => {
|
|
7861
|
+
this.livenessEntryId = null;
|
|
7862
|
+
this.documentBlob = null;
|
|
7863
|
+
this.documentBackBlob = null;
|
|
7864
|
+
this.showStep("document_capture");
|
|
7865
|
+
});
|
|
7866
|
+
container.appendChild(retryBtn);
|
|
7867
|
+
const endBtn = this.createSecondaryButton("End session");
|
|
7868
|
+
endBtn.addEventListener("click", () => {
|
|
7869
|
+
this.options.onError?.(new Error("Document expired"));
|
|
7870
|
+
this.close();
|
|
7871
|
+
});
|
|
7872
|
+
container.appendChild(endBtn);
|
|
7873
|
+
}
|
|
7874
|
+
_renderOcrQualityFail(container, reason) {
|
|
7875
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7876
|
+
const icon = document.createElement("div");
|
|
7877
|
+
icon.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;`;
|
|
7878
|
+
icon.textContent = "\xF0\u0178\u201D\x8D";
|
|
7879
|
+
container.appendChild(icon);
|
|
7880
|
+
const t2 = document.createElement("h2");
|
|
7881
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7882
|
+
t2.textContent = "Document Photo Unclear";
|
|
7883
|
+
container.appendChild(t2);
|
|
7884
|
+
const msg = reason === "image_too_blurry" ? "Your document photo is too blurry. Please retake it in good lighting with a steady hand." : reason === "image_too_dark" ? "Your document photo is too dark. Please retake it in better lighting." : reason === "image_too_bright" ? "Your document photo has too much glare. Please reduce reflections and retake." : reason === "document_not_detected" ? "We could not detect a document in the photo. Please ensure the full ID is visible." : "We could not read your document clearly. Please retake the photo in good lighting with no glare.";
|
|
7885
|
+
const d2 = document.createElement("p");
|
|
7886
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7887
|
+
d2.textContent = msg;
|
|
7888
|
+
container.appendChild(d2);
|
|
7889
|
+
const retryBtn = this.createPrimaryButton("Retake photo");
|
|
7890
|
+
retryBtn.addEventListener("click", () => {
|
|
7891
|
+
this.livenessEntryId = null;
|
|
7892
|
+
this.documentBlob = null;
|
|
7893
|
+
this.documentBackBlob = null;
|
|
7894
|
+
this.showStep("document_capture");
|
|
7895
|
+
});
|
|
7896
|
+
container.appendChild(retryBtn);
|
|
7897
|
+
}
|
|
7898
|
+
// ── W11: You're all done (end-user final screen) ─────────────────
|
|
7899
|
+
renderDone(container) {
|
|
7900
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:16px;padding:24px 0;";
|
|
7901
|
+
container.setAttribute("role", "status");
|
|
7902
|
+
this.options.onComplete?.({
|
|
7903
|
+
entryId: this.livenessEntryId || 0,
|
|
7904
|
+
sessionId: this.sdk.sessionToken,
|
|
7905
|
+
status: "processing",
|
|
7906
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7907
|
+
verificationResult: this.verificationResult || void 0
|
|
7908
|
+
});
|
|
7909
|
+
const iconWrap = document.createElement("div");
|
|
7910
|
+
iconWrap.style.cssText = `width:80px;height:80px;border-radius:50%;background:${this.theme.successBg};border:3px solid ${this.theme.success};display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:700;color:${this.theme.success};animation:sc-pop 0.4s ease;`;
|
|
7911
|
+
iconWrap.textContent = "\xE2\u0153\u201C";
|
|
7912
|
+
container.appendChild(iconWrap);
|
|
7913
|
+
const title = document.createElement("h2");
|
|
7914
|
+
title.style.cssText = `color:${this.theme.text};font-size:22px;font-weight:700;margin:0;`;
|
|
7915
|
+
title.textContent = "You're all done!";
|
|
7916
|
+
container.appendChild(title);
|
|
7917
|
+
const brandName = this.sdkConfig?.brand_name || "the service";
|
|
7918
|
+
const desc = document.createElement("p");
|
|
7919
|
+
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7920
|
+
desc.textContent = `Your verification is complete. ${brandName} will review and be in touch shortly.`;
|
|
7921
|
+
container.appendChild(desc);
|
|
7922
|
+
const info = document.createElement("div");
|
|
7923
|
+
info.style.cssText = `color:${this.theme.textMuted};font-size:11px;display:flex;align-items:center;gap:4px;`;
|
|
7924
|
+
info.textContent = "\xF0\u0178\u201D\u2019 Your data is encrypted and secure";
|
|
7925
|
+
container.appendChild(info);
|
|
7926
|
+
const doneBtn = this.createPrimaryButton("Close");
|
|
7927
|
+
doneBtn.addEventListener("click", () => {
|
|
7928
|
+
this.close();
|
|
7929
|
+
});
|
|
7930
|
+
container.appendChild(doneBtn);
|
|
7931
|
+
}
|
|
7932
|
+
// ── W14: Retry limit reached ─────────────────────────────────────
|
|
7933
|
+
_renderRetryLimitReached(container) {
|
|
7934
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:24px 0;";
|
|
7935
|
+
const icon = document.createElement("div");
|
|
7936
|
+
icon.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;`;
|
|
7937
|
+
icon.textContent = "\xE2\u0161\xA0\xEF\xB8\x8F";
|
|
7938
|
+
container.appendChild(icon);
|
|
7939
|
+
const t2 = document.createElement("h2");
|
|
7940
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7941
|
+
t2.textContent = "Unable to Complete Verification";
|
|
7942
|
+
container.appendChild(t2);
|
|
7943
|
+
const d2 = document.createElement("p");
|
|
7944
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7945
|
+
d2.textContent = "We were unable to complete your verification at this time. Please contact support or try again later.";
|
|
7946
|
+
container.appendChild(d2);
|
|
7947
|
+
const closeBtn = this.createPrimaryButton("Close");
|
|
7948
|
+
closeBtn.addEventListener("click", () => {
|
|
7949
|
+
this.options.onError?.(new Error("Retry limit reached"));
|
|
7950
|
+
this.close();
|
|
7951
|
+
});
|
|
7952
|
+
container.appendChild(closeBtn);
|
|
7953
|
+
}
|
|
7954
|
+
// ── W13: Camera pre-warm ─────────────────────────────────────────
|
|
7955
|
+
_prewarmCamera() {
|
|
7956
|
+
if (this._prewarmedStream || this.isDestroyed) return;
|
|
7957
|
+
navigator.mediaDevices?.getUserMedia?.({ video: { facingMode: "user" } }).then((stream) => {
|
|
7958
|
+
if (this.isDestroyed) {
|
|
7959
|
+
stream.getTracks().forEach((t2) => t2.stop());
|
|
7960
|
+
return;
|
|
7961
|
+
}
|
|
7962
|
+
this._prewarmedStream = stream;
|
|
7963
|
+
}).catch(() => {
|
|
7964
|
+
});
|
|
7590
7965
|
}
|
|
7591
7966
|
/**
|
|
7592
7967
|
* Process document capture result with front/back support
|
|
7593
7968
|
*/
|
|
7594
7969
|
async processDocumentCaptureResult(result, captureContainer, container) {
|
|
7595
|
-
this.documentBlob = result.front;
|
|
7596
|
-
this.documentBackBlob = result.back
|
|
7597
|
-
await this.processDocumentBlob(
|
|
7598
|
-
if (result.back) {
|
|
7599
|
-
console.log("Back image captured, size:", result.back.size);
|
|
7600
|
-
}
|
|
7970
|
+
this.documentBlob = await _compressImage(result.front, 1600, 0.85);
|
|
7971
|
+
this.documentBackBlob = result.back ? await _compressImage(result.back, 1600, 0.85) : null;
|
|
7972
|
+
await this.processDocumentBlob(this.documentBlob, captureContainer, container);
|
|
7601
7973
|
}
|
|
7602
7974
|
/**
|
|
7603
7975
|
* Process document blob - common method for both CameraView and DocumentCapture
|
|
@@ -7618,7 +7990,7 @@ var SmartComplySDK = (() => {
|
|
|
7618
7990
|
<div style="display:flex;flex-direction:column;align-items:center;gap:14px;padding:28px 20px;">
|
|
7619
7991
|
<div style="position:relative;width:52px;height:52px;">
|
|
7620
7992
|
<div style="position:absolute;inset:0;border-radius:50%;border:3px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.8s linear infinite;"></div>
|
|
7621
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\
|
|
7993
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\xF0\u0178\u201D\x8D</div>
|
|
7622
7994
|
</div>
|
|
7623
7995
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Scanning document...</div>
|
|
7624
7996
|
<div style="color:${this.theme.textSecondary};font-size:12px;text-align:center;max-width:200px;line-height:1.5;">Running OCR and verifying your identity</div>
|
|
@@ -7665,7 +8037,7 @@ var SmartComplySDK = (() => {
|
|
|
7665
8037
|
}
|
|
7666
8038
|
}
|
|
7667
8039
|
this.livenessEntryId = createResult.id;
|
|
7668
|
-
this.showStep("
|
|
8040
|
+
this.showStep("ocr_gate");
|
|
7669
8041
|
} catch (err) {
|
|
7670
8042
|
console.error("Document processing failed:", err);
|
|
7671
8043
|
if (retryCount < maxRetries) {
|
|
@@ -7676,7 +8048,7 @@ var SmartComplySDK = (() => {
|
|
|
7676
8048
|
<div style="display:flex;flex-direction:column;align-items:center;gap:16px;padding:28px 20px;text-align:center;">
|
|
7677
8049
|
<div style="position:relative;width:52px;height:52px;">
|
|
7678
8050
|
<div style="position:absolute;inset:0;border-radius:50%;border:3px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.8s linear infinite;"></div>
|
|
7679
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\
|
|
8051
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\xF0\u0178\u201D\u201E</div>
|
|
7680
8052
|
</div>
|
|
7681
8053
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Retrying...</div>
|
|
7682
8054
|
<div style="color:${this.theme.textSecondary};font-size:12px;max-width:240px;line-height:1.5;">Attempt ${retryCount} of ${maxRetries}</div>
|
|
@@ -7717,7 +8089,7 @@ var SmartComplySDK = (() => {
|
|
|
7717
8089
|
wrap.setAttribute("role", "alert");
|
|
7718
8090
|
const icon = document.createElement("div");
|
|
7719
8091
|
icon.style.cssText = `width:56px;height:56px;border-radius:50%;background:${this.theme.errorBg};border:3px solid ${this.theme.error};display:flex;align-items:center;justify-content:center;font-size:26px;`;
|
|
7720
|
-
icon.textContent = "\
|
|
8092
|
+
icon.textContent = "\xF0\u0178\u201D\x8D";
|
|
7721
8093
|
wrap.appendChild(icon);
|
|
7722
8094
|
const titleEl = document.createElement("div");
|
|
7723
8095
|
titleEl.style.cssText = `color:${this.theme.text};font-size:14px;font-weight:700;`;
|
|
@@ -7741,9 +8113,13 @@ var SmartComplySDK = (() => {
|
|
|
7741
8113
|
this.showErrorStep(userMessage);
|
|
7742
8114
|
}
|
|
7743
8115
|
}
|
|
7744
|
-
//
|
|
8116
|
+
// ── Liveness ────────────────────────────────────────────────────
|
|
7745
8117
|
renderLiveness(container) {
|
|
7746
8118
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
8119
|
+
if (this._livenessFailures >= _SmartComplyFlow.MAX_RETRIES) {
|
|
8120
|
+
this._renderRetryLimitReached(container);
|
|
8121
|
+
return;
|
|
8122
|
+
}
|
|
7747
8123
|
const title = this.createStepTitle("Face verification");
|
|
7748
8124
|
container.appendChild(title);
|
|
7749
8125
|
const desc = document.createElement("div");
|
|
@@ -7753,7 +8129,9 @@ var SmartComplySDK = (() => {
|
|
|
7753
8129
|
const livenessContainer = document.createElement("div");
|
|
7754
8130
|
livenessContainer.style.cssText = "margin:8px 0;width:100%;";
|
|
7755
8131
|
container.appendChild(livenessContainer);
|
|
7756
|
-
const
|
|
8132
|
+
const isDataVerification = !!this.identityCheckId;
|
|
8133
|
+
const identifier = isDataVerification ? `SES-${Date.now()}` : this.idNumber || Object.values(this.collectedFields)[0] || this.selectedIdType || `DOC-${Date.now()}`;
|
|
8134
|
+
const identifierType = isDataVerification ? "session" : this.selectedIdType;
|
|
7757
8135
|
const COUNTRY_CODES = {
|
|
7758
8136
|
nigeria: "NG",
|
|
7759
8137
|
ghana: "GH",
|
|
@@ -7768,252 +8146,33 @@ var SmartComplySDK = (() => {
|
|
|
7768
8146
|
livenessContainer,
|
|
7769
8147
|
{
|
|
7770
8148
|
identifier,
|
|
7771
|
-
identifier_type:
|
|
8149
|
+
identifier_type: identifierType,
|
|
7772
8150
|
country,
|
|
7773
8151
|
document: this.documentBlob || void 0,
|
|
7774
8152
|
document_back: this.documentBackBlob || void 0,
|
|
7775
8153
|
identity_check: this.identityCheckId || void 0,
|
|
7776
|
-
|
|
7777
|
-
|
|
8154
|
+
verification_type: this.identityCheckId ? "data_verification" : this.documentBlob ? "document_verification" : "liveness_only",
|
|
8155
|
+
// B4: pass pre-warmed stream so camera.open() is skipped
|
|
8156
|
+
prewarmedStream: this._prewarmedStream || void 0
|
|
7778
8157
|
},
|
|
7779
8158
|
["BLINK", "TURN_HEAD", "OPEN_MOUTH"],
|
|
7780
8159
|
this.livenessEntryId || void 0
|
|
7781
8160
|
).then((result) => {
|
|
7782
|
-
|
|
7783
|
-
|
|
8161
|
+
if (result.status === "failed") {
|
|
8162
|
+
this._livenessFailures++;
|
|
8163
|
+
}
|
|
8164
|
+
if (result.id) {
|
|
7784
8165
|
this.livenessEntryId = result.id;
|
|
7785
|
-
this.showStep("processing");
|
|
7786
|
-
} else {
|
|
7787
|
-
this.showResult(result);
|
|
7788
8166
|
}
|
|
8167
|
+
this.showStep("done");
|
|
7789
8168
|
}).catch((err) => {
|
|
8169
|
+
this._livenessFailures++;
|
|
7790
8170
|
this.showErrorStep(err.message || "Liveness verification failed. Please try again.");
|
|
7791
8171
|
});
|
|
7792
8172
|
}
|
|
7793
|
-
// ── Result ──────────────────────────────────────────────────────
|
|
7794
|
-
showResult(submitResult) {
|
|
7795
|
-
this.currentStep = "result";
|
|
7796
|
-
this.updateProgress();
|
|
7797
|
-
if (!this.contentArea) return;
|
|
7798
|
-
this.contentArea.innerHTML = "";
|
|
7799
|
-
const wrapper = document.createElement("div");
|
|
7800
|
-
wrapper.setAttribute("role", "region");
|
|
7801
|
-
wrapper.setAttribute("aria-label", "Verification result");
|
|
7802
|
-
wrapper.style.cssText = `
|
|
7803
|
-
display:flex;flex-direction:column;align-items:center;
|
|
7804
|
-
text-align:center;gap:16px;padding:20px 0;
|
|
7805
|
-
opacity:0;transform:scale(0.95);transition:all 0.4s ease;
|
|
7806
|
-
`;
|
|
7807
|
-
const iconWrap = document.createElement("div");
|
|
7808
|
-
iconWrap.style.cssText = `
|
|
7809
|
-
width:80px;height:80px;border-radius:50%;
|
|
7810
|
-
display:flex;align-items:center;justify-content:center;
|
|
7811
|
-
font-size:36px;
|
|
7812
|
-
background:${this.theme.cardBg};
|
|
7813
|
-
border:3px solid ${this.theme.border};
|
|
7814
|
-
transition:all 0.4s ease;
|
|
7815
|
-
`;
|
|
7816
|
-
iconWrap.innerHTML = `<div style="width:32px;height:32px;border-radius:50%;border:3px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.9s linear infinite;"></div>`;
|
|
7817
|
-
wrapper.appendChild(iconWrap);
|
|
7818
|
-
const title = document.createElement("h2");
|
|
7819
|
-
title.setAttribute("aria-live", "polite");
|
|
7820
|
-
title.style.cssText = `color:${this.theme.text};font-size:20px;font-weight:700;margin:0;`;
|
|
7821
|
-
title.textContent = "Checking your identity\u2026";
|
|
7822
|
-
wrapper.appendChild(title);
|
|
7823
|
-
const desc = document.createElement("p");
|
|
7824
|
-
desc.setAttribute("aria-live", "polite");
|
|
7825
|
-
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7826
|
-
desc.textContent = "This usually takes a few seconds. Please wait.";
|
|
7827
|
-
wrapper.appendChild(desc);
|
|
7828
|
-
const resultCard = document.createElement("div");
|
|
7829
|
-
resultCard.style.cssText = `
|
|
7830
|
-
width:100%;padding:16px;border-radius:12px;
|
|
7831
|
-
background:${this.theme.cardBg};border:1px solid ${this.theme.border};
|
|
7832
|
-
text-align:left;display:flex;flex-direction:column;gap:10px;
|
|
7833
|
-
transition:border-color 0.3s ease;
|
|
7834
|
-
`;
|
|
7835
|
-
const statusBadge = document.createElement("span");
|
|
7836
|
-
statusBadge.setAttribute("role", "status");
|
|
7837
|
-
statusBadge.setAttribute("aria-live", "polite");
|
|
7838
|
-
statusBadge.setAttribute("aria-atomic", "true");
|
|
7839
|
-
statusBadge.style.cssText = `font-size:12px;font-weight:600;color:${this.theme.warning};`;
|
|
7840
|
-
statusBadge.textContent = "Processing\u2026";
|
|
7841
|
-
const statusRow = document.createElement("div");
|
|
7842
|
-
statusRow.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7843
|
-
statusRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">Status</span>`;
|
|
7844
|
-
statusRow.appendChild(statusBadge);
|
|
7845
|
-
resultCard.appendChild(statusRow);
|
|
7846
|
-
const nameRow = document.createElement("div");
|
|
7847
|
-
nameRow.style.cssText = "display:none;justify-content:space-between;align-items:center;";
|
|
7848
|
-
nameRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">Name</span><span id="sc-result-name" style="color:${this.theme.text};font-size:12px;font-weight:600;text-align:right;max-width:60%;"></span>`;
|
|
7849
|
-
resultCard.appendChild(nameRow);
|
|
7850
|
-
const docTypeRow = document.createElement("div");
|
|
7851
|
-
docTypeRow.style.cssText = "display:none;justify-content:space-between;align-items:center;";
|
|
7852
|
-
docTypeRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">ID Type</span><span id="sc-result-doctype" style="color:${this.theme.text};font-size:12px;font-weight:600;"></span>`;
|
|
7853
|
-
resultCard.appendChild(docTypeRow);
|
|
7854
|
-
wrapper.appendChild(resultCard);
|
|
7855
|
-
const failureMsg = document.createElement("div");
|
|
7856
|
-
failureMsg.setAttribute("role", "alert");
|
|
7857
|
-
failureMsg.style.cssText = `
|
|
7858
|
-
display:none;width:100%;padding:12px 14px;border-radius:10px;
|
|
7859
|
-
background:${this.theme.errorBg};border:1px solid ${this.theme.error};
|
|
7860
|
-
color:${this.theme.error};font-size:13px;text-align:left;line-height:1.6;
|
|
7861
|
-
`;
|
|
7862
|
-
wrapper.appendChild(failureMsg);
|
|
7863
|
-
const btnRow = document.createElement("div");
|
|
7864
|
-
btnRow.style.cssText = "display:flex;flex-direction:column;gap:8px;width:100%;";
|
|
7865
|
-
const doneBtn = this.createPrimaryButton("Done");
|
|
7866
|
-
doneBtn.setAttribute("aria-label", "Close verification and continue");
|
|
7867
|
-
let finalStatus = submitResult.status;
|
|
7868
|
-
doneBtn.addEventListener("click", () => {
|
|
7869
|
-
if (this._pollStatusTimer) {
|
|
7870
|
-
clearTimeout(this._pollStatusTimer);
|
|
7871
|
-
this._pollStatusTimer = null;
|
|
7872
|
-
}
|
|
7873
|
-
this.options.onComplete?.({
|
|
7874
|
-
entryId: submitResult.id,
|
|
7875
|
-
sessionId: this.sdk.sessionToken,
|
|
7876
|
-
status: finalStatus,
|
|
7877
|
-
submittedAt: submitResult.submitted_at,
|
|
7878
|
-
verificationResult: this.verificationResult || void 0
|
|
7879
|
-
});
|
|
7880
|
-
this.close();
|
|
7881
|
-
});
|
|
7882
|
-
btnRow.appendChild(doneBtn);
|
|
7883
|
-
const retryBtn = this.createSecondaryButton("Try Again");
|
|
7884
|
-
retryBtn.setAttribute("aria-label", "Start verification again");
|
|
7885
|
-
retryBtn.style.display = "none";
|
|
7886
|
-
retryBtn.addEventListener("click", () => {
|
|
7887
|
-
if (this._pollStatusTimer) {
|
|
7888
|
-
clearTimeout(this._pollStatusTimer);
|
|
7889
|
-
this._pollStatusTimer = null;
|
|
7890
|
-
}
|
|
7891
|
-
this.initialize();
|
|
7892
|
-
});
|
|
7893
|
-
btnRow.appendChild(retryBtn);
|
|
7894
|
-
wrapper.appendChild(btnRow);
|
|
7895
|
-
this.contentArea.appendChild(wrapper);
|
|
7896
|
-
requestAnimationFrame(() => {
|
|
7897
|
-
wrapper.style.opacity = "1";
|
|
7898
|
-
wrapper.style.transform = "scale(1)";
|
|
7899
|
-
});
|
|
7900
|
-
this._startStatusPolling(
|
|
7901
|
-
submitResult.id,
|
|
7902
|
-
iconWrap,
|
|
7903
|
-
statusBadge,
|
|
7904
|
-
title,
|
|
7905
|
-
desc,
|
|
7906
|
-
resultCard,
|
|
7907
|
-
nameRow,
|
|
7908
|
-
docTypeRow,
|
|
7909
|
-
failureMsg,
|
|
7910
|
-
retryBtn,
|
|
7911
|
-
(resolved) => {
|
|
7912
|
-
finalStatus = resolved;
|
|
7913
|
-
}
|
|
7914
|
-
);
|
|
7915
|
-
}
|
|
7916
|
-
_startStatusPolling(entryId, iconWrap, statusBadge, title, desc, resultCard, nameRow, docTypeRow, failureMsg, retryBtn, onResolved) {
|
|
7917
|
-
const MAX_POLLS = 25;
|
|
7918
|
-
let polls = 0;
|
|
7919
|
-
let uiTimeoutId = setTimeout(() => {
|
|
7920
|
-
if (!this.isDestroyed) applyTimeout();
|
|
7921
|
-
}, 3e4);
|
|
7922
|
-
const clearUiTimeout = () => {
|
|
7923
|
-
if (uiTimeoutId) {
|
|
7924
|
-
clearTimeout(uiTimeoutId);
|
|
7925
|
-
uiTimeoutId = null;
|
|
7926
|
-
}
|
|
7927
|
-
};
|
|
7928
|
-
const applyTimeout = () => {
|
|
7929
|
-
if (this._pollStatusTimer) {
|
|
7930
|
-
clearTimeout(this._pollStatusTimer);
|
|
7931
|
-
this._pollStatusTimer = null;
|
|
7932
|
-
}
|
|
7933
|
-
iconWrap.innerHTML = "\u23F3";
|
|
7934
|
-
iconWrap.style.border = `3px solid ${this.theme.warning}`;
|
|
7935
|
-
statusBadge.style.color = this.theme.textMuted;
|
|
7936
|
-
statusBadge.textContent = "Still processing\u2026";
|
|
7937
|
-
title.textContent = "Still verifying";
|
|
7938
|
-
desc.textContent = "This is taking a little longer than usual. You can safely close this \u2014 your result will be available in your account.";
|
|
7939
|
-
};
|
|
7940
|
-
const applyPassed = (result) => {
|
|
7941
|
-
clearUiTimeout();
|
|
7942
|
-
iconWrap.innerHTML = "\u2713";
|
|
7943
|
-
iconWrap.style.cssText += `background:${this.theme.successBg};border:3px solid ${this.theme.success};color:${this.theme.success};font-size:36px;font-weight:700;`;
|
|
7944
|
-
resultCard.style.borderColor = this.theme.success;
|
|
7945
|
-
statusBadge.style.color = this.theme.success;
|
|
7946
|
-
statusBadge.textContent = "Verified \u2713";
|
|
7947
|
-
title.textContent = "Identity Verified";
|
|
7948
|
-
desc.textContent = "Your identity has been confirmed successfully.";
|
|
7949
|
-
const profile = result?.customer_profile || result?.data || {};
|
|
7950
|
-
const docResult = result?.document || {};
|
|
7951
|
-
const ocr = docResult?.ocr_result || {};
|
|
7952
|
-
const firstName = (profile?.first_name || ocr?.first_name || docResult?.first_name || "").trim();
|
|
7953
|
-
const lastName = (profile?.last_name || profile?.surname || ocr?.last_name || ocr?.surname || docResult?.last_name || "").trim();
|
|
7954
|
-
const fullName = [firstName, lastName].filter(Boolean).join(" ");
|
|
7955
|
-
const nameEl = document.getElementById("sc-result-name");
|
|
7956
|
-
if (fullName && nameEl) {
|
|
7957
|
-
nameEl.textContent = fullName;
|
|
7958
|
-
nameRow.style.display = "flex";
|
|
7959
|
-
}
|
|
7960
|
-
const rawDocType = docResult?.document_type || this.selectedIdType || result?.identifier_type || "";
|
|
7961
|
-
const docTypeLabel = _formatDocType(rawDocType);
|
|
7962
|
-
const docTypeEl = document.getElementById("sc-result-doctype");
|
|
7963
|
-
if (docTypeLabel && docTypeEl) {
|
|
7964
|
-
docTypeEl.textContent = docTypeLabel;
|
|
7965
|
-
docTypeRow.style.display = "flex";
|
|
7966
|
-
}
|
|
7967
|
-
};
|
|
7968
|
-
const applyFailed = (reason) => {
|
|
7969
|
-
clearUiTimeout();
|
|
7970
|
-
iconWrap.innerHTML = "\u2715";
|
|
7971
|
-
iconWrap.style.cssText += `background:${this.theme.errorBg};border:3px solid ${this.theme.error};color:${this.theme.error};font-size:32px;`;
|
|
7972
|
-
resultCard.style.borderColor = this.theme.error;
|
|
7973
|
-
statusBadge.style.color = this.theme.error;
|
|
7974
|
-
statusBadge.textContent = "Not verified";
|
|
7975
|
-
title.textContent = "Verification Unsuccessful";
|
|
7976
|
-
desc.textContent = "We were unable to confirm your identity this time.";
|
|
7977
|
-
const safeReason = _sanitizeUserMessage(reason);
|
|
7978
|
-
if (safeReason) {
|
|
7979
|
-
failureMsg.textContent = safeReason;
|
|
7980
|
-
failureMsg.style.display = "block";
|
|
7981
|
-
}
|
|
7982
|
-
retryBtn.style.display = "block";
|
|
7983
|
-
};
|
|
7984
|
-
const poll = async () => {
|
|
7985
|
-
if (this.isDestroyed) return;
|
|
7986
|
-
polls++;
|
|
7987
|
-
try {
|
|
7988
|
-
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7989
|
-
if (result.status === "passed") {
|
|
7990
|
-
onResolved("passed");
|
|
7991
|
-
applyPassed(result);
|
|
7992
|
-
return;
|
|
7993
|
-
}
|
|
7994
|
-
if (result.status === "failed") {
|
|
7995
|
-
onResolved("failed");
|
|
7996
|
-
applyFailed(result.failure_reason || null);
|
|
7997
|
-
return;
|
|
7998
|
-
}
|
|
7999
|
-
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
8000
|
-
this._pollStatusTimer = setTimeout(poll, 3e3);
|
|
8001
|
-
} else if (!this.isDestroyed) {
|
|
8002
|
-
applyTimeout();
|
|
8003
|
-
}
|
|
8004
|
-
} catch {
|
|
8005
|
-
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
8006
|
-
this._pollStatusTimer = setTimeout(poll, 5e3);
|
|
8007
|
-
} else if (!this.isDestroyed) {
|
|
8008
|
-
applyTimeout();
|
|
8009
|
-
}
|
|
8010
|
-
}
|
|
8011
|
-
};
|
|
8012
|
-
this._pollStatusTimer = setTimeout(poll, 4e3);
|
|
8013
|
-
}
|
|
8014
8173
|
renderResult(_container) {
|
|
8015
8174
|
}
|
|
8016
|
-
//
|
|
8175
|
+
// ── Error ───────────────────────────────────────────────────────
|
|
8017
8176
|
showErrorStep(message) {
|
|
8018
8177
|
this.currentStep = "error";
|
|
8019
8178
|
if (!this.contentArea) return;
|
|
@@ -8030,7 +8189,7 @@ var SmartComplySDK = (() => {
|
|
|
8030
8189
|
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
8031
8190
|
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
8032
8191
|
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
8033
|
-
const errorIcon = isCameraErr ? "\
|
|
8192
|
+
const errorIcon = isCameraErr ? "\xF0\u0178\u201C\xB7" : isNetworkErr ? "\xF0\u0178\u201C\xA1" : isAuthErr ? "\xE2\x8F\xB1" : isDocQuality ? "\xF0\u0178\u201D\x8D" : isExpiredDoc ? "\xF0\u0178\u201C\u2026" : isFaceMatch ? "\xF0\u0178\xA4\xB3" : "\xE2\u0153\u2022";
|
|
8034
8193
|
const errorTitle = isCameraErr ? "Camera Access Required" : isNetworkErr ? "Connection Problem" : isAuthErr ? "Session Expired" : isDocQuality ? "Document Photo Unclear" : isExpiredDoc ? "Document Expired" : isFaceMatch ? "Face Match Failed" : "Verification Unsuccessful";
|
|
8035
8194
|
const userMessage = isCameraErr ? "Please allow camera access in your browser settings and try again." : isNetworkErr ? "Check your internet connection and try again." : isAuthErr ? "Your session has expired. Please start again." : isDocQuality ? _sanitizeUserMessage(message) || "Please retake your document photo in good lighting with no glare." : isExpiredDoc ? "The document you submitted has expired. Please use a valid, current ID." : isFaceMatch ? "Your selfie did not match the photo on your ID. Please ensure you are using your own valid document and face the camera directly." : _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
|
|
8036
8195
|
const iconWrap = document.createElement("div");
|
|
@@ -8066,9 +8225,9 @@ var SmartComplySDK = (() => {
|
|
|
8066
8225
|
wrapper.appendChild(closeBtn);
|
|
8067
8226
|
this.contentArea.appendChild(wrapper);
|
|
8068
8227
|
}
|
|
8069
|
-
//
|
|
8228
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8070
8229
|
// UI Helpers
|
|
8071
|
-
//
|
|
8230
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8072
8231
|
createHeader() {
|
|
8073
8232
|
const header = document.createElement("div");
|
|
8074
8233
|
header.setAttribute("role", "banner");
|
|
@@ -8110,6 +8269,21 @@ var SmartComplySDK = (() => {
|
|
|
8110
8269
|
this.brandLabel.textContent = this.sdkConfig?.brand_name || "SmartComply";
|
|
8111
8270
|
leftArea.appendChild(this.brandLabel);
|
|
8112
8271
|
header.appendChild(leftArea);
|
|
8272
|
+
const stepIndicator = document.createElement("div");
|
|
8273
|
+
stepIndicator.style.cssText = `
|
|
8274
|
+
display:flex;align-items:center;gap:8px;flex-shrink:0;
|
|
8275
|
+
`;
|
|
8276
|
+
this.stageDots = document.createElement("div");
|
|
8277
|
+
this.stageDots.style.cssText = `display:flex;align-items:center;gap:4px;`;
|
|
8278
|
+
for (let i2 = 0; i2 < 2; i2++) {
|
|
8279
|
+
const dot = document.createElement("span");
|
|
8280
|
+
dot.style.cssText = `
|
|
8281
|
+
width:6px;height:6px;border-radius:50%;
|
|
8282
|
+
background:${this.theme.border};transition:background 0.2s ease;
|
|
8283
|
+
`;
|
|
8284
|
+
this.stageDots.appendChild(dot);
|
|
8285
|
+
}
|
|
8286
|
+
stepIndicator.appendChild(this.stageDots);
|
|
8113
8287
|
this.stepLabel = document.createElement("div");
|
|
8114
8288
|
this.stepLabel.setAttribute("aria-live", "polite");
|
|
8115
8289
|
this.stepLabel.setAttribute("aria-atomic", "true");
|
|
@@ -8117,9 +8291,10 @@ var SmartComplySDK = (() => {
|
|
|
8117
8291
|
font-size:12px;color:${this.theme.textMuted};
|
|
8118
8292
|
font-variant-numeric:tabular-nums;flex-shrink:0;
|
|
8119
8293
|
`;
|
|
8120
|
-
|
|
8294
|
+
stepIndicator.appendChild(this.stepLabel);
|
|
8295
|
+
header.appendChild(stepIndicator);
|
|
8121
8296
|
const closeBtn = document.createElement("button");
|
|
8122
|
-
closeBtn.innerHTML = "\
|
|
8297
|
+
closeBtn.innerHTML = "\xE2\u0153\u2022";
|
|
8123
8298
|
closeBtn.setAttribute("aria-label", "Close identity verification");
|
|
8124
8299
|
closeBtn.style.cssText = `
|
|
8125
8300
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
@@ -8200,7 +8375,7 @@ var SmartComplySDK = (() => {
|
|
|
8200
8375
|
}
|
|
8201
8376
|
const arrow = document.createElement("span");
|
|
8202
8377
|
arrow.style.cssText = `color:${this.theme.textMuted};font-size:18px;flex-shrink:0;`;
|
|
8203
|
-
arrow.textContent = "\
|
|
8378
|
+
arrow.textContent = "\xE2\u2020\u2019";
|
|
8204
8379
|
card.appendChild(textWrap);
|
|
8205
8380
|
card.appendChild(arrow);
|
|
8206
8381
|
return card;
|
|
@@ -8259,33 +8434,51 @@ var SmartComplySDK = (() => {
|
|
|
8259
8434
|
const isDocFlow = this.isDocumentFlow();
|
|
8260
8435
|
const countries = Object.keys(this.sdkConfig?.channels || {});
|
|
8261
8436
|
const singleCountry = countries.length <= 1;
|
|
8262
|
-
const
|
|
8263
|
-
const
|
|
8264
|
-
const
|
|
8265
|
-
const
|
|
8437
|
+
const stage1Steps = isDocFlow ? ["country", "id_type", "document_capture", "ocr_gate", "document_confirm"].filter((s2) => !(s2 === "country" && singleCountry)) : ["country", "id_type", "id_input", "confirm_identity"].filter((s2) => !(s2 === "country" && singleCountry));
|
|
8438
|
+
const stage2Steps = ["liveness"];
|
|
8439
|
+
const allActionSteps = [...stage1Steps, ...stage2Steps];
|
|
8440
|
+
const totalSteps = allActionSteps.length;
|
|
8441
|
+
const actionIndex = allActionSteps.indexOf(this.currentStep);
|
|
8442
|
+
const pct = this.currentStep === "done" || this.currentStep === "result" ? 100 : actionIndex >= 0 ? (actionIndex + 1) / totalSteps * 100 : 0;
|
|
8266
8443
|
if (this.progressBar) {
|
|
8267
8444
|
this.progressBar.style.width = `${pct}%`;
|
|
8268
8445
|
}
|
|
8269
8446
|
if (this.stepLabel) {
|
|
8270
8447
|
if (this.currentStep === "loading" || this.currentStep === "welcome") {
|
|
8271
8448
|
this.stepLabel.textContent = "";
|
|
8272
|
-
} else if (this.currentStep === "result") {
|
|
8273
|
-
this.stepLabel.textContent = "Complete \
|
|
8274
|
-
} else if (
|
|
8275
|
-
this.stepLabel.textContent =
|
|
8449
|
+
} else if (this.currentStep === "done" || this.currentStep === "result") {
|
|
8450
|
+
this.stepLabel.textContent = "Complete \xE2\u0153\u201C";
|
|
8451
|
+
} else if (stage2Steps.includes(this.currentStep)) {
|
|
8452
|
+
this.stepLabel.textContent = "Stage 2 of 2: Face Check";
|
|
8453
|
+
} else if (stage1Steps.includes(this.currentStep)) {
|
|
8454
|
+
this.stepLabel.textContent = isDocFlow ? "Stage 1 of 2: Scan Document" : "Stage 1 of 2: Verify Identity";
|
|
8276
8455
|
} else {
|
|
8277
8456
|
this.stepLabel.textContent = "";
|
|
8278
8457
|
}
|
|
8279
8458
|
}
|
|
8459
|
+
if (this.stageDots) {
|
|
8460
|
+
const dots = Array.from(this.stageDots.children);
|
|
8461
|
+
const inStage1 = stage1Steps.includes(this.currentStep);
|
|
8462
|
+
const inStage2 = stage2Steps.includes(this.currentStep);
|
|
8463
|
+
const isComplete = this.currentStep === "done" || this.currentStep === "result";
|
|
8464
|
+
const stage1Active = inStage1 || inStage2 || isComplete;
|
|
8465
|
+
const stage2Active = inStage2 || isComplete;
|
|
8466
|
+
if (dots[0]) dots[0].style.background = stage1Active ? this.theme.primary : this.theme.border;
|
|
8467
|
+
if (dots[1]) dots[1].style.background = stage2Active ? this.theme.primary : this.theme.border;
|
|
8468
|
+
}
|
|
8280
8469
|
if (this.headerBackBtn) {
|
|
8281
|
-
const countries2 = Object.keys(this.sdkConfig?.channels || {});
|
|
8282
8470
|
const backTargets = {
|
|
8283
|
-
id_type: () => this.showStep(
|
|
8471
|
+
id_type: () => this.showStep(countries.length > 1 ? "country" : "welcome"),
|
|
8284
8472
|
country: () => this.showStep("welcome"),
|
|
8285
8473
|
id_input: () => this.showStep("id_type"),
|
|
8474
|
+
confirm_identity: () => this.showStep("id_input"),
|
|
8286
8475
|
document_capture: () => {
|
|
8287
8476
|
this.docCapture?.destroy();
|
|
8288
8477
|
this.showStep("id_type");
|
|
8478
|
+
},
|
|
8479
|
+
document_confirm: () => {
|
|
8480
|
+
this.docCapture?.destroy();
|
|
8481
|
+
this.showStep("document_capture");
|
|
8289
8482
|
}
|
|
8290
8483
|
};
|
|
8291
8484
|
const backAction = backTargets[this.currentStep];
|
|
@@ -8352,6 +8545,8 @@ var SmartComplySDK = (() => {
|
|
|
8352
8545
|
document.head.appendChild(style2);
|
|
8353
8546
|
}
|
|
8354
8547
|
};
|
|
8548
|
+
_SmartComplyFlow.MAX_RETRIES = 3;
|
|
8549
|
+
var SmartComplyFlow = _SmartComplyFlow;
|
|
8355
8550
|
|
|
8356
8551
|
// src/utils/logger.ts
|
|
8357
8552
|
var PREFIX = "[SmartComply SDK]";
|