smartcomply-web-sdk 1.0.43 → 1.0.47
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/SmartComplyFlow.d.ts +22 -4
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +904 -492
- 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 +10 -5
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +87 -43
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +22 -4
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +904 -492
- 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 +10 -5
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +87 -43
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +828 -461
- package/dist/smartcomply.browser.js.map +3 -3
- package/package.json +1 -1
|
@@ -4842,7 +4842,7 @@ var SmartComplySDK = (() => {
|
|
|
4842
4842
|
}
|
|
4843
4843
|
if (this.timerEl) this.timerEl.textContent = "";
|
|
4844
4844
|
}
|
|
4845
|
-
showResult(status,
|
|
4845
|
+
showResult(status, _verificationId) {
|
|
4846
4846
|
return new Promise((resolve) => {
|
|
4847
4847
|
if (!this.overlayEl) {
|
|
4848
4848
|
resolve();
|
|
@@ -4859,11 +4859,10 @@ var SmartComplySDK = (() => {
|
|
|
4859
4859
|
border:3px solid ${ok ? "#22c55e" : "#ef4444"};">
|
|
4860
4860
|
${ok ? "\u2713" : "\u2715"}
|
|
4861
4861
|
</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>` : ""}
|
|
4862
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
4863
|
+
<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
4864
|
</div>`;
|
|
4866
|
-
setTimeout(resolve,
|
|
4865
|
+
setTimeout(resolve, 1800);
|
|
4867
4866
|
});
|
|
4868
4867
|
}
|
|
4869
4868
|
showTimeout(onRetry) {
|
|
@@ -5034,8 +5033,9 @@ var SmartComplySDK = (() => {
|
|
|
5034
5033
|
*/
|
|
5035
5034
|
async create(params) {
|
|
5036
5035
|
const formData = new FormData();
|
|
5037
|
-
const
|
|
5038
|
-
|
|
5036
|
+
const rawIdentifier = params.identifier || `ID-${Date.now()}`;
|
|
5037
|
+
const safeIdentifier = rawIdentifier.length > 20 ? rawIdentifier.slice(0, 20) : rawIdentifier;
|
|
5038
|
+
formData.append("identifier", safeIdentifier);
|
|
5039
5039
|
formData.append("identifier_type", params.identifier_type);
|
|
5040
5040
|
formData.append("country", params.country);
|
|
5041
5041
|
formData.append(
|
|
@@ -5125,14 +5125,20 @@ var SmartComplySDK = (() => {
|
|
|
5125
5125
|
* 1. Take autoshot selfie from camera
|
|
5126
5126
|
* 2. Create liveness entry on backend (with challenge actions + selfie + optional doc)
|
|
5127
5127
|
* 3. Run action detection loop (camera + MediaPipe)
|
|
5128
|
-
* 4. Record video
|
|
5129
|
-
* 5.
|
|
5128
|
+
* 4. Record video (document/liveness flows) OR skip recording (data verification)
|
|
5129
|
+
* 5. Submit to backend and return response (status: "processing")
|
|
5130
5130
|
*
|
|
5131
|
-
*
|
|
5132
|
-
*
|
|
5133
|
-
*
|
|
5131
|
+
* For data_verification the backend uses COMPARISON mode (autoshot vs govt DB photo)
|
|
5132
|
+
* so video recording is unnecessary — we skip it to save bandwidth and latency.
|
|
5133
|
+
*
|
|
5134
|
+
* @param container - DOM element to mount the liveness UI into
|
|
5135
|
+
* @param params - identifier info and optional document image
|
|
5136
|
+
* @param actions - override challenge actions (default: BLINK, TURN_HEAD, OPEN_MOUTH)
|
|
5137
|
+
* @param existingEntryId - reuse an already-created LivenessEntry (avoids double charge)
|
|
5134
5138
|
*/
|
|
5135
5139
|
async startCheck(container, params, actions, existingEntryId) {
|
|
5140
|
+
const isDataVerification = params.verification_type === "data_verification" || !params.document && !params.document_front && !params.id_file && !!params.identity_check;
|
|
5141
|
+
const skipVideo = isDataVerification;
|
|
5136
5142
|
const camera = new CameraManager();
|
|
5137
5143
|
const recorder = new VideoRecorder();
|
|
5138
5144
|
const faceEngine = new FaceDetectorEngine();
|
|
@@ -5147,16 +5153,20 @@ var SmartComplySDK = (() => {
|
|
|
5147
5153
|
};
|
|
5148
5154
|
try {
|
|
5149
5155
|
let stream;
|
|
5150
|
-
|
|
5151
|
-
stream =
|
|
5152
|
-
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
}
|
|
5156
|
-
|
|
5157
|
-
|
|
5156
|
+
if (params.prewarmedStream && params.prewarmedStream.active) {
|
|
5157
|
+
stream = params.prewarmedStream;
|
|
5158
|
+
} else {
|
|
5159
|
+
try {
|
|
5160
|
+
stream = await camera.open();
|
|
5161
|
+
} catch (camErr) {
|
|
5162
|
+
if (camErr.name === "NotAllowedError" || camErr.name === "PermissionDeniedError") {
|
|
5163
|
+
throw new Error("Camera access denied. Please allow camera permission in your browser settings and try again.");
|
|
5164
|
+
}
|
|
5165
|
+
if (camErr.name === "NotFoundError" || camErr.name === "DevicesNotFoundError") {
|
|
5166
|
+
throw new Error("No camera found. Please connect a camera and try again.");
|
|
5167
|
+
}
|
|
5168
|
+
throw new Error(`Camera error: ${camErr.message || "Unable to access camera"}`);
|
|
5158
5169
|
}
|
|
5159
|
-
throw new Error(`Camera error: ${camErr.message || "Unable to access camera"}`);
|
|
5160
5170
|
}
|
|
5161
5171
|
let capturedDocBlob = params.id_file;
|
|
5162
5172
|
if (!capturedDocBlob && params.verification_type === "document_verification") {
|
|
@@ -5216,11 +5226,6 @@ var SmartComplySDK = (() => {
|
|
|
5216
5226
|
});
|
|
5217
5227
|
ui2.updateInstruction("Hold still...");
|
|
5218
5228
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
5219
|
-
const autoshotBlob = await this.captureFrame(videoForDetection);
|
|
5220
|
-
tempVideo.pause();
|
|
5221
|
-
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
5222
|
-
throw new Error("Failed to capture autoshot selfie. Please ensure camera is working.");
|
|
5223
|
-
}
|
|
5224
5229
|
const challengeActions = actions || [...DEFAULT_CHALLENGE_ACTIONS];
|
|
5225
5230
|
let entry;
|
|
5226
5231
|
if (existingEntryId) {
|
|
@@ -5232,7 +5237,8 @@ var SmartComplySDK = (() => {
|
|
|
5232
5237
|
identifier_type: params.identifier_type,
|
|
5233
5238
|
country: params.country,
|
|
5234
5239
|
challenge_actions: challengeActions,
|
|
5235
|
-
|
|
5240
|
+
// W9: No autoshot at create time — real one captured post-challenge
|
|
5241
|
+
autoshot_file: void 0,
|
|
5236
5242
|
document: params.document || params.document_front || params.id_file || capturedDocBlob,
|
|
5237
5243
|
document_back: params.document_back,
|
|
5238
5244
|
identity_check: params.identity_check,
|
|
@@ -5242,6 +5248,9 @@ var SmartComplySDK = (() => {
|
|
|
5242
5248
|
fingerprint_id: deviceMeta.fingerprint_id
|
|
5243
5249
|
});
|
|
5244
5250
|
}
|
|
5251
|
+
tempVideo.pause();
|
|
5252
|
+
tempVideo.srcObject = null;
|
|
5253
|
+
tempVideo.remove();
|
|
5245
5254
|
const uiChallenge = {
|
|
5246
5255
|
actions: entry.challenge_actions,
|
|
5247
5256
|
time_limit_seconds: 45,
|
|
@@ -5255,7 +5264,9 @@ var SmartComplySDK = (() => {
|
|
|
5255
5264
|
videoEl.srcObject = stream;
|
|
5256
5265
|
await videoEl.play();
|
|
5257
5266
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
5258
|
-
|
|
5267
|
+
if (!skipVideo) {
|
|
5268
|
+
recorder.start(stream);
|
|
5269
|
+
}
|
|
5259
5270
|
ui2.updateInstruction(uiChallenge.instruction);
|
|
5260
5271
|
const actionDetector = new ActionDetector(entry.challenge_actions);
|
|
5261
5272
|
let lastAction = null;
|
|
@@ -5272,8 +5283,11 @@ var SmartComplySDK = (() => {
|
|
|
5272
5283
|
const getLabel = (action) => uiChallenge.instructions?.[action] || this.describeAction(action);
|
|
5273
5284
|
const detectLoop = () => {
|
|
5274
5285
|
const now = performance.now();
|
|
5275
|
-
|
|
5276
|
-
|
|
5286
|
+
const remaining = pauseUntil - now;
|
|
5287
|
+
if (remaining > 0) {
|
|
5288
|
+
setTimeout(() => {
|
|
5289
|
+
animFrameId = requestAnimationFrame(detectLoop);
|
|
5290
|
+
}, remaining);
|
|
5277
5291
|
return;
|
|
5278
5292
|
}
|
|
5279
5293
|
if (pendingNextAction) {
|
|
@@ -5329,14 +5343,34 @@ var SmartComplySDK = (() => {
|
|
|
5329
5343
|
faceEngine.destroy();
|
|
5330
5344
|
camera.stop();
|
|
5331
5345
|
ui2.unmount();
|
|
5332
|
-
this.startCheck(
|
|
5346
|
+
this.startCheck(
|
|
5347
|
+
container,
|
|
5348
|
+
{ ...params, id_file: capturedDocBlob },
|
|
5349
|
+
actions,
|
|
5350
|
+
entry.id
|
|
5351
|
+
).then(resolveRetry).catch(rejectRetry);
|
|
5333
5352
|
});
|
|
5334
5353
|
});
|
|
5335
5354
|
}
|
|
5336
5355
|
ui2.updateInstruction("Almost done \u2014 hold still...");
|
|
5337
5356
|
await new Promise((r2) => setTimeout(r2, 800));
|
|
5357
|
+
const autoshotBlob = await this.captureFrame(videoEl);
|
|
5358
|
+
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
5359
|
+
throw new Error("Failed to capture selfie. Please ensure your face is visible and try again.");
|
|
5360
|
+
}
|
|
5338
5361
|
const snapshotBlob = await this.captureFrame(videoEl);
|
|
5339
|
-
|
|
5362
|
+
let videoBlob;
|
|
5363
|
+
let durationSeconds;
|
|
5364
|
+
if (skipVideo) {
|
|
5365
|
+
recorder.stop().catch(() => {
|
|
5366
|
+
});
|
|
5367
|
+
videoBlob = autoshotBlob;
|
|
5368
|
+
durationSeconds = void 0;
|
|
5369
|
+
} else {
|
|
5370
|
+
const recorded = await recorder.stop();
|
|
5371
|
+
videoBlob = recorded.blob;
|
|
5372
|
+
durationSeconds = recorded.durationSeconds;
|
|
5373
|
+
}
|
|
5340
5374
|
const submitResult = await this.submit(
|
|
5341
5375
|
entry.id,
|
|
5342
5376
|
videoBlob,
|
|
@@ -5621,8 +5655,25 @@ var SmartComplySDK = (() => {
|
|
|
5621
5655
|
isDark: false
|
|
5622
5656
|
}
|
|
5623
5657
|
};
|
|
5624
|
-
function getTheme(name) {
|
|
5625
|
-
|
|
5658
|
+
function getTheme(name, hex) {
|
|
5659
|
+
const base = { ...THEMES[name] || THEMES.default };
|
|
5660
|
+
if (hex && /^#[0-9A-Fa-f]{6}$/.test(hex.trim())) {
|
|
5661
|
+
const h2 = hex.trim();
|
|
5662
|
+
const hover = _darkenHex(h2, 15);
|
|
5663
|
+
base.primary = h2;
|
|
5664
|
+
base.primaryHover = hover;
|
|
5665
|
+
base.primaryGlow = `${h2}40`;
|
|
5666
|
+
base.borderFocus = h2;
|
|
5667
|
+
base.shimmer = `${h2}14`;
|
|
5668
|
+
}
|
|
5669
|
+
return base;
|
|
5670
|
+
}
|
|
5671
|
+
function _darkenHex(hex, amount) {
|
|
5672
|
+
const n2 = parseInt(hex.slice(1), 16);
|
|
5673
|
+
const r2 = Math.max(0, (n2 >> 16) - amount);
|
|
5674
|
+
const g2 = Math.max(0, (n2 >> 8 & 255) - amount);
|
|
5675
|
+
const b2 = Math.max(0, (n2 & 255) - amount);
|
|
5676
|
+
return `#${(r2 << 16 | g2 << 8 | b2).toString(16).padStart(6, "0")}`;
|
|
5626
5677
|
}
|
|
5627
5678
|
|
|
5628
5679
|
// src/utils/ImageQuality.ts
|
|
@@ -6558,13 +6609,13 @@ var SmartComplySDK = (() => {
|
|
|
6558
6609
|
|
|
6559
6610
|
// src/flow/SmartComplyFlow.ts
|
|
6560
6611
|
var COUNTRY_LABELS = {
|
|
6561
|
-
nigeria: "\
|
|
6562
|
-
global: "\
|
|
6563
|
-
ghana: "\
|
|
6564
|
-
kenya: "\
|
|
6565
|
-
south_africa: "\
|
|
6566
|
-
united_states: "\
|
|
6567
|
-
united_kingdom: "\
|
|
6612
|
+
nigeria: "\xF0\u0178\u2021\xB3\xF0\u0178\u2021\xAC Nigeria",
|
|
6613
|
+
global: "\xF0\u0178\u0152\x8D Other Countries",
|
|
6614
|
+
ghana: "\xF0\u0178\u2021\xAC\xF0\u0178\u2021\xAD Ghana",
|
|
6615
|
+
kenya: "\xF0\u0178\u2021\xB0\xF0\u0178\u2021\xAA Kenya",
|
|
6616
|
+
south_africa: "\xF0\u0178\u2021\xBF\xF0\u0178\u2021\xA6 South Africa",
|
|
6617
|
+
united_states: "\xF0\u0178\u2021\xBA\xF0\u0178\u2021\xB8 United States",
|
|
6618
|
+
united_kingdom: "\xF0\u0178\u2021\xAC\xF0\u0178\u2021\xA7 United Kingdom"
|
|
6568
6619
|
};
|
|
6569
6620
|
function isUploadFieldType(type) {
|
|
6570
6621
|
return type === "upload" || type === "image" || type === "file";
|
|
@@ -6578,37 +6629,37 @@ var SmartComplySDK = (() => {
|
|
|
6578
6629
|
return t2.includes("voter") || t2.includes("driver") || t2.includes("license") || t2.includes("national id") || t2.includes("national id card");
|
|
6579
6630
|
};
|
|
6580
6631
|
if (u2.includes("BVN") || u2.includes("BANK VERIFICATION")) {
|
|
6581
|
-
return { icon: "\
|
|
6632
|
+
return { icon: "\xF0\u0178\x8F\xA6", desc: "Enter your 11-digit Bank Verification Number (BVN)", requiresBack: false };
|
|
6582
6633
|
}
|
|
6583
6634
|
if (u2.includes("NIN") || u2.includes("NATIONAL IDENTITY NUMBER")) {
|
|
6584
|
-
return { icon: "\
|
|
6635
|
+
return { icon: "\xF0\u0178\u2020\u201D", desc: "Enter your 11-digit National Identification Number (NIN)", requiresBack: false };
|
|
6585
6636
|
}
|
|
6586
6637
|
if (u2.includes("GHANA")) {
|
|
6587
|
-
if (isDocumentFlow) return { icon: "\
|
|
6588
|
-
return { icon: "\
|
|
6638
|
+
if (isDocumentFlow) return { icon: "\xF0\u0178\xAA\xAA", desc: "Capture or upload your Ghana Card", requiresBack: true };
|
|
6639
|
+
return { icon: "\xF0\u0178\xAA\xAA", desc: "Enter your Ghana Card number", requiresBack: false };
|
|
6589
6640
|
}
|
|
6590
6641
|
if (u2.includes("SSNIT"))
|
|
6591
|
-
return { icon: "\
|
|
6642
|
+
return { icon: "\xF0\u0178\x8F\u203A\xEF\xB8\x8F", desc: "Enter your SSNIT number", requiresBack: false };
|
|
6592
6643
|
if (u2.includes("KENYA") || u2.includes("NATIONAL") && u2.includes("KENYA"))
|
|
6593
|
-
return { icon: "\
|
|
6644
|
+
return { icon: "\xF0\u0178\u2020\u201D", desc: "Enter your Kenya National ID number", requiresBack: false };
|
|
6594
6645
|
if (u2.includes("TIN") || u2.includes("TAX IDENTIFICATION"))
|
|
6595
|
-
return { icon: "\
|
|
6646
|
+
return { icon: "\xF0\u0178\xA7\xBE", desc: "Enter your Tax Identification Number (TIN)", requiresBack: false };
|
|
6596
6647
|
if (u2.includes("CAC") || u2.includes("CORPORATE AFFAIRS") || u2.includes("COMPANY"))
|
|
6597
|
-
return { icon: "\
|
|
6648
|
+
return { icon: "\xF0\u0178\x8F\xA2", desc: advanced ? "Enter RC number, company name & type" : "Enter your company registration number", requiresBack: false };
|
|
6598
6649
|
if (u2.includes("VOTER") || u2.includes("PVC")) {
|
|
6599
6650
|
const requiresBack = requiresBackSide(typeName);
|
|
6600
6651
|
return {
|
|
6601
|
-
icon: "\
|
|
6652
|
+
icon: "\xF0\u0178\u2014\xB3\xEF\xB8\x8F",
|
|
6602
6653
|
desc: isDocumentFlow ? `Capture or upload your voter's card${requiresBack ? " (front & back)" : ""}` : "Enter your voter ID number",
|
|
6603
6654
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6604
6655
|
};
|
|
6605
6656
|
}
|
|
6606
6657
|
if (u2.includes("PASSPORT"))
|
|
6607
|
-
return { icon: "\
|
|
6658
|
+
return { icon: "\xF0\u0178\u201C\u02DC", desc: isDocumentFlow ? "Capture or upload your passport" : "Enter your passport number", requiresBack: false };
|
|
6608
6659
|
if (u2.includes("DRIVER") || u2.includes("LICENSE") || u2.includes("LICENCE")) {
|
|
6609
6660
|
const requiresBack = requiresBackSide(typeName);
|
|
6610
6661
|
return {
|
|
6611
|
-
icon: "\
|
|
6662
|
+
icon: "\xF0\u0178\u0161\u2014",
|
|
6612
6663
|
desc: isDocumentFlow ? `Capture or upload your driver's license${requiresBack ? " (front & back)" : ""}` : "Enter your driver's license number",
|
|
6613
6664
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6614
6665
|
};
|
|
@@ -6616,19 +6667,109 @@ var SmartComplySDK = (() => {
|
|
|
6616
6667
|
if (u2.includes("NATIONAL ID") || u2.includes("NATIONAL") && u2.includes("CARD")) {
|
|
6617
6668
|
const requiresBack = requiresBackSide(typeName);
|
|
6618
6669
|
return {
|
|
6619
|
-
icon: "\
|
|
6670
|
+
icon: "\xF0\u0178\xAA\xAA",
|
|
6620
6671
|
desc: isDocumentFlow ? `Capture or upload your ID card${requiresBack ? " (front & back)" : ""}` : "Enter your National ID number",
|
|
6621
6672
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6622
6673
|
};
|
|
6623
6674
|
}
|
|
6624
6675
|
if (u2.includes("LIVENESS") || u2.includes("FACE COMPARISON") || u2.includes("FACE MATCH"))
|
|
6625
|
-
return { icon: "\
|
|
6676
|
+
return { icon: "\xF0\u0178\xA4\xB3", desc: "We'll capture a short selfie video to verify you", requiresBack: false };
|
|
6626
6677
|
return {
|
|
6627
|
-
icon: isDocumentFlow ? "\
|
|
6678
|
+
icon: isDocumentFlow ? "\xF0\u0178\u201C\u201E" : "\xF0\u0178\u2020\u201D",
|
|
6628
6679
|
desc: isDocumentFlow ? "Capture or upload your document" : "Enter your ID number",
|
|
6629
6680
|
requiresBack: false
|
|
6630
6681
|
};
|
|
6631
6682
|
}
|
|
6683
|
+
function _sanitizeUserMessage(raw) {
|
|
6684
|
+
if (!raw) return null;
|
|
6685
|
+
const lower = raw.toLowerCase();
|
|
6686
|
+
const blocklist = [
|
|
6687
|
+
"smartcomply",
|
|
6688
|
+
"face comparison service",
|
|
6689
|
+
"session id",
|
|
6690
|
+
"session_id",
|
|
6691
|
+
"provider",
|
|
6692
|
+
"ocr",
|
|
6693
|
+
"textract",
|
|
6694
|
+
"rekognition",
|
|
6695
|
+
"deepface",
|
|
6696
|
+
"ivs",
|
|
6697
|
+
"celery",
|
|
6698
|
+
"exception",
|
|
6699
|
+
"traceback",
|
|
6700
|
+
"str(e)",
|
|
6701
|
+
"error:",
|
|
6702
|
+
"failed:",
|
|
6703
|
+
"django",
|
|
6704
|
+
"python",
|
|
6705
|
+
"http ",
|
|
6706
|
+
"comparison mode",
|
|
6707
|
+
"liveness mode",
|
|
6708
|
+
"callback",
|
|
6709
|
+
"face_comparison",
|
|
6710
|
+
"face_match",
|
|
6711
|
+
"confidence_percentage",
|
|
6712
|
+
"threshold_percentage",
|
|
6713
|
+
"polling_fallback",
|
|
6714
|
+
"resolved_by"
|
|
6715
|
+
];
|
|
6716
|
+
for (const blocked of blocklist) {
|
|
6717
|
+
if (lower.includes(blocked)) return null;
|
|
6718
|
+
}
|
|
6719
|
+
if (/[a-z]+_[a-z]+/.test(raw) && !/\s/.test(raw.trim())) return null;
|
|
6720
|
+
if (raw.trim()[0] === raw.trim()[0].toLowerCase() && /^[a-z]/.test(raw.trim())) return null;
|
|
6721
|
+
return raw.trim();
|
|
6722
|
+
}
|
|
6723
|
+
function _parseOcrDate(raw) {
|
|
6724
|
+
if (!raw) return null;
|
|
6725
|
+
const s2 = raw.trim();
|
|
6726
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(s2)) return new Date(s2.slice(0, 10));
|
|
6727
|
+
const dmy = s2.match(/^(\d{2})[\/\-](\d{2})[\/\-](\d{4})/);
|
|
6728
|
+
if (dmy) return /* @__PURE__ */ new Date(`${dmy[3]}-${dmy[2]}-${dmy[1]}`);
|
|
6729
|
+
const mdy = s2.match(/^(\d{2})\/(\d{2})\/(\d{4})/);
|
|
6730
|
+
if (mdy) {
|
|
6731
|
+
if (parseInt(mdy[1], 10) > 12) return /* @__PURE__ */ new Date(`${mdy[3]}-${mdy[2]}-${mdy[1]}`);
|
|
6732
|
+
return /* @__PURE__ */ new Date(`${mdy[3]}-${mdy[1]}-${mdy[2]}`);
|
|
6733
|
+
}
|
|
6734
|
+
const dMonY = s2.match(/^(\d{1,2})\s+([A-Za-z]{3,})\s+(\d{4})/);
|
|
6735
|
+
if (dMonY) return /* @__PURE__ */ new Date(`${dMonY[2]} ${dMonY[1]} ${dMonY[3]}`);
|
|
6736
|
+
const d2 = new Date(s2);
|
|
6737
|
+
return isNaN(d2.getTime()) ? null : d2;
|
|
6738
|
+
}
|
|
6739
|
+
async function _compressImage(blob, maxWidth = 1600, quality = 0.85) {
|
|
6740
|
+
try {
|
|
6741
|
+
const bmp = await createImageBitmap(blob);
|
|
6742
|
+
const scale = Math.min(1, maxWidth / bmp.width);
|
|
6743
|
+
const w2 = Math.round(bmp.width * scale);
|
|
6744
|
+
const h2 = Math.round(bmp.height * scale);
|
|
6745
|
+
const canvas = document.createElement("canvas");
|
|
6746
|
+
canvas.width = w2;
|
|
6747
|
+
canvas.height = h2;
|
|
6748
|
+
canvas.getContext("2d").drawImage(bmp, 0, 0, w2, h2);
|
|
6749
|
+
bmp.close();
|
|
6750
|
+
return await new Promise(
|
|
6751
|
+
(res, rej) => canvas.toBlob((b2) => b2 ? res(b2) : rej(new Error("Compression failed")), "image/jpeg", quality)
|
|
6752
|
+
);
|
|
6753
|
+
} catch {
|
|
6754
|
+
return blob;
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
function _formatDocType(code) {
|
|
6758
|
+
const map = {
|
|
6759
|
+
passport: "Passport",
|
|
6760
|
+
national_id: "National ID",
|
|
6761
|
+
nin_slip: "NIN Slip",
|
|
6762
|
+
nin_with_face: "National Identity Number",
|
|
6763
|
+
drivers_license: "Driver's Licence",
|
|
6764
|
+
voters_card: "Voter's Card",
|
|
6765
|
+
kenya_id: "Kenya National ID",
|
|
6766
|
+
ghana_id: "Ghana Card"
|
|
6767
|
+
};
|
|
6768
|
+
if (!code) return "";
|
|
6769
|
+
const key = code.toLowerCase().replace(/[\s-]/g, "_");
|
|
6770
|
+
if (map[key]) return map[key];
|
|
6771
|
+
return code.replace(/_/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
|
|
6772
|
+
}
|
|
6632
6773
|
var style = document.createElement("style");
|
|
6633
6774
|
style.textContent = `
|
|
6634
6775
|
@keyframes sc-spin {
|
|
@@ -6640,7 +6781,7 @@ var SmartComplySDK = (() => {
|
|
|
6640
6781
|
style.setAttribute("data-sc-animations", "true");
|
|
6641
6782
|
document.head.appendChild(style);
|
|
6642
6783
|
}
|
|
6643
|
-
var
|
|
6784
|
+
var _SmartComplyFlow = class _SmartComplyFlow {
|
|
6644
6785
|
constructor(options) {
|
|
6645
6786
|
this.theme = getTheme("default");
|
|
6646
6787
|
// DOM
|
|
@@ -6671,6 +6812,25 @@ var SmartComplySDK = (() => {
|
|
|
6671
6812
|
this.livenessEntryId = null;
|
|
6672
6813
|
this.isDestroyed = false;
|
|
6673
6814
|
this.headerBackBtn = null;
|
|
6815
|
+
// Retry safety — max 3 confirmation rejections or liveness failures per session
|
|
6816
|
+
this._confirmRetries = 0;
|
|
6817
|
+
this._livenessFailures = 0;
|
|
6818
|
+
// IVS result stored for confirm_identity screen
|
|
6819
|
+
this._ivsName = "";
|
|
6820
|
+
this._ivsGender = "";
|
|
6821
|
+
// OCR result stored for document_confirm screen
|
|
6822
|
+
this._ocrName = "";
|
|
6823
|
+
this._ocrDocNumber = "";
|
|
6824
|
+
this._ocrExpiry = "";
|
|
6825
|
+
this._ocrFaceImageUrl = "";
|
|
6826
|
+
this._ocrIsExpired = false;
|
|
6827
|
+
this._ocrExpirySoon = false;
|
|
6828
|
+
// Camera pre-warm stream (created during confirmation screen, used in liveness)
|
|
6829
|
+
this._prewarmedStream = null;
|
|
6830
|
+
// OCR gate poll timer — stored so it can be cancelled on navigation or close
|
|
6831
|
+
this._ocrPollTimer = null;
|
|
6832
|
+
// UI timeout from _startStatusPolling — stored so close() can cancel it
|
|
6833
|
+
this._uiTimeoutId = null;
|
|
6674
6834
|
// Session expiry
|
|
6675
6835
|
this.sessionExpiryTimer = null;
|
|
6676
6836
|
// Status polling (result screen)
|
|
@@ -6710,6 +6870,10 @@ var SmartComplySDK = (() => {
|
|
|
6710
6870
|
close() {
|
|
6711
6871
|
this.isDestroyed = true;
|
|
6712
6872
|
this.docCapture?.destroy();
|
|
6873
|
+
if (this._prewarmedStream) {
|
|
6874
|
+
this._prewarmedStream.getTracks().forEach((t2) => t2.stop());
|
|
6875
|
+
this._prewarmedStream = null;
|
|
6876
|
+
}
|
|
6713
6877
|
if (this.sessionExpiryTimer) {
|
|
6714
6878
|
clearTimeout(this.sessionExpiryTimer);
|
|
6715
6879
|
this.sessionExpiryTimer = null;
|
|
@@ -6718,6 +6882,14 @@ var SmartComplySDK = (() => {
|
|
|
6718
6882
|
clearTimeout(this._pollStatusTimer);
|
|
6719
6883
|
this._pollStatusTimer = null;
|
|
6720
6884
|
}
|
|
6885
|
+
if (this._ocrPollTimer) {
|
|
6886
|
+
clearTimeout(this._ocrPollTimer);
|
|
6887
|
+
this._ocrPollTimer = null;
|
|
6888
|
+
}
|
|
6889
|
+
if (this._uiTimeoutId) {
|
|
6890
|
+
clearTimeout(this._uiTimeoutId);
|
|
6891
|
+
this._uiTimeoutId = null;
|
|
6892
|
+
}
|
|
6721
6893
|
if (this.overlay) {
|
|
6722
6894
|
this.overlay.style.opacity = "0";
|
|
6723
6895
|
setTimeout(() => {
|
|
@@ -6732,9 +6904,9 @@ var SmartComplySDK = (() => {
|
|
|
6732
6904
|
}
|
|
6733
6905
|
this.options.onClose?.();
|
|
6734
6906
|
}
|
|
6735
|
-
//
|
|
6907
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6736
6908
|
// Initialization
|
|
6737
|
-
//
|
|
6909
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6738
6910
|
mount() {
|
|
6739
6911
|
this.injectStyles();
|
|
6740
6912
|
this.overlay = document.createElement("div");
|
|
@@ -6749,6 +6921,9 @@ var SmartComplySDK = (() => {
|
|
|
6749
6921
|
`;
|
|
6750
6922
|
this.modal = document.createElement("div");
|
|
6751
6923
|
this.modal.id = "sc-flow-modal";
|
|
6924
|
+
this.modal.setAttribute("role", "dialog");
|
|
6925
|
+
this.modal.setAttribute("aria-modal", "true");
|
|
6926
|
+
this.modal.setAttribute("aria-label", "Identity verification");
|
|
6752
6927
|
this.modal.style.cssText = `
|
|
6753
6928
|
width:100%;max-width:580px;max-height:95vh;
|
|
6754
6929
|
background:${this.theme.bg};
|
|
@@ -6771,6 +6946,9 @@ var SmartComplySDK = (() => {
|
|
|
6771
6946
|
this.modal.appendChild(progressWrap);
|
|
6772
6947
|
this.contentArea = document.createElement("div");
|
|
6773
6948
|
this.contentArea.id = "sc-flow-content";
|
|
6949
|
+
this.contentArea.setAttribute("role", "main");
|
|
6950
|
+
this.contentArea.setAttribute("aria-live", "polite");
|
|
6951
|
+
this.contentArea.setAttribute("aria-atomic", "false");
|
|
6774
6952
|
this.contentArea.style.cssText = `
|
|
6775
6953
|
flex:1;overflow-y:auto;padding:16px 20px;
|
|
6776
6954
|
-webkit-overflow-scrolling:touch;
|
|
@@ -6801,7 +6979,7 @@ var SmartComplySDK = (() => {
|
|
|
6801
6979
|
}
|
|
6802
6980
|
this.sdkConfig = await this.sdk.initializeConfig();
|
|
6803
6981
|
if (this.sdkConfig.theme) {
|
|
6804
|
-
this.theme = getTheme(this.sdkConfig.theme);
|
|
6982
|
+
this.theme = getTheme(this.sdkConfig.theme, this.sdkConfig.theme_hex || null);
|
|
6805
6983
|
this.applyTheme();
|
|
6806
6984
|
}
|
|
6807
6985
|
if (this.brandLabel && this.sdkConfig.brand_name) {
|
|
@@ -6813,12 +6991,16 @@ var SmartComplySDK = (() => {
|
|
|
6813
6991
|
this.showErrorStep(err.message || "Failed to initialize. Please try again.");
|
|
6814
6992
|
}
|
|
6815
6993
|
}
|
|
6816
|
-
//
|
|
6994
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6817
6995
|
// Step rendering
|
|
6818
|
-
//
|
|
6996
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6819
6997
|
showStep(step) {
|
|
6820
6998
|
this.currentStep = step;
|
|
6821
6999
|
if (!this.contentArea) return;
|
|
7000
|
+
if (this._ocrPollTimer) {
|
|
7001
|
+
clearTimeout(this._ocrPollTimer);
|
|
7002
|
+
this._ocrPollTimer = null;
|
|
7003
|
+
}
|
|
6822
7004
|
this.updateProgress();
|
|
6823
7005
|
const oldContent = this.contentArea.firstChild;
|
|
6824
7006
|
if (oldContent) {
|
|
@@ -6851,17 +7033,27 @@ var SmartComplySDK = (() => {
|
|
|
6851
7033
|
case "id_input":
|
|
6852
7034
|
this.renderIdInput(wrapper);
|
|
6853
7035
|
break;
|
|
7036
|
+
case "confirm_identity":
|
|
7037
|
+
this.renderConfirmIdentity(wrapper);
|
|
7038
|
+
break;
|
|
6854
7039
|
case "document_capture":
|
|
6855
7040
|
this.renderDocumentCapture(wrapper);
|
|
6856
7041
|
break;
|
|
7042
|
+
case "ocr_gate":
|
|
7043
|
+
this.renderOcrGate(wrapper);
|
|
7044
|
+
break;
|
|
7045
|
+
case "document_confirm":
|
|
7046
|
+
this.renderDocumentConfirm(wrapper);
|
|
7047
|
+
break;
|
|
6857
7048
|
case "processing":
|
|
6858
|
-
this.renderProcessing(wrapper);
|
|
6859
7049
|
break;
|
|
6860
7050
|
case "liveness":
|
|
6861
7051
|
this.renderLiveness(wrapper);
|
|
6862
7052
|
break;
|
|
7053
|
+
case "done":
|
|
7054
|
+
this.renderDone(wrapper);
|
|
7055
|
+
break;
|
|
6863
7056
|
case "result":
|
|
6864
|
-
this.renderResult(wrapper);
|
|
6865
7057
|
break;
|
|
6866
7058
|
case "error":
|
|
6867
7059
|
break;
|
|
@@ -6872,7 +7064,7 @@ var SmartComplySDK = (() => {
|
|
|
6872
7064
|
wrapper.style.transform = "translateX(0)";
|
|
6873
7065
|
});
|
|
6874
7066
|
}
|
|
6875
|
-
//
|
|
7067
|
+
// ── Loading ─────────────────────────────────────────────────────
|
|
6876
7068
|
renderLoading(container) {
|
|
6877
7069
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;gap:16px;";
|
|
6878
7070
|
const spinnerWrap = document.createElement("div");
|
|
@@ -6903,7 +7095,7 @@ var SmartComplySDK = (() => {
|
|
|
6903
7095
|
sub.textContent = "Connecting to the verification service...";
|
|
6904
7096
|
container.appendChild(sub);
|
|
6905
7097
|
}
|
|
6906
|
-
//
|
|
7098
|
+
// ── Welcome ────────────────────────────────────────────────────
|
|
6907
7099
|
renderWelcome(container) {
|
|
6908
7100
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;";
|
|
6909
7101
|
const isDocFlow = this.isDocumentFlow();
|
|
@@ -6917,7 +7109,7 @@ var SmartComplySDK = (() => {
|
|
|
6917
7109
|
background:linear-gradient(135deg,${this.theme.primary}22,${this.theme.primaryGlow});
|
|
6918
7110
|
border:1.5px solid ${this.theme.primary}33;
|
|
6919
7111
|
`;
|
|
6920
|
-
iconWrap.textContent = isDocFlow ? "\
|
|
7112
|
+
iconWrap.textContent = isDocFlow ? "\xF0\u0178\xAA\xAA" : "\xF0\u0178\u203A\xA1\xEF\xB8\x8F";
|
|
6921
7113
|
topRow.appendChild(iconWrap);
|
|
6922
7114
|
const badge = document.createElement("div");
|
|
6923
7115
|
badge.style.cssText = `
|
|
@@ -6928,7 +7120,7 @@ var SmartComplySDK = (() => {
|
|
|
6928
7120
|
color:${this.theme.primary};
|
|
6929
7121
|
border:1px solid ${this.theme.primary}30;
|
|
6930
7122
|
`;
|
|
6931
|
-
badge.textContent = isDocFlow ? "\
|
|
7123
|
+
badge.textContent = isDocFlow ? "\xF0\u0178\u201C\u201E Document Verification" : "\xF0\u0178\u201D\x8D Data Verification";
|
|
6932
7124
|
topRow.appendChild(badge);
|
|
6933
7125
|
container.appendChild(topRow);
|
|
6934
7126
|
const titleWrap = document.createElement("div");
|
|
@@ -6938,24 +7130,25 @@ var SmartComplySDK = (() => {
|
|
|
6938
7130
|
color:${this.theme.text};font-size:20px;font-weight:700;
|
|
6939
7131
|
margin:0;line-height:1.2;
|
|
6940
7132
|
`;
|
|
6941
|
-
|
|
7133
|
+
const brandName = this.sdkConfig?.brand_name;
|
|
7134
|
+
title.textContent = brandName ? `Verify your identity for ${brandName}` : "Identity Verification";
|
|
6942
7135
|
titleWrap.appendChild(title);
|
|
6943
7136
|
const desc = document.createElement("p");
|
|
6944
7137
|
desc.style.cssText = `
|
|
6945
7138
|
color:${this.theme.textSecondary};font-size:13px;line-height:1.5;
|
|
6946
7139
|
margin:0;max-width:300px;
|
|
6947
7140
|
`;
|
|
6948
|
-
desc.textContent = isDocFlow ? "
|
|
7141
|
+
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.";
|
|
6949
7142
|
titleWrap.appendChild(desc);
|
|
6950
7143
|
container.appendChild(titleWrap);
|
|
6951
7144
|
const steps = isDocFlow ? [
|
|
6952
|
-
{ icon: "\
|
|
6953
|
-
{ icon: "\
|
|
6954
|
-
{ icon: "\
|
|
7145
|
+
{ icon: "\xF0\u0178\xAA\xAA", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
|
|
7146
|
+
{ icon: "\xF0\u0178\u201C\xB7", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
|
|
7147
|
+
{ icon: "\xF0\u0178\xA4\xB3", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
6955
7148
|
] : [
|
|
6956
|
-
{ icon: "\
|
|
6957
|
-
{ icon: "\
|
|
6958
|
-
{ icon: "\
|
|
7149
|
+
{ icon: "\xF0\u0178\u2020\u201D", text: "Choose your ID type", sub: "NIN, BVN, Voter's Card and more" },
|
|
7150
|
+
{ icon: "\xF0\u0178\u201C\x9D", text: "Enter your ID details", sub: "Exactly as shown on your ID" },
|
|
7151
|
+
{ icon: "\xF0\u0178\xA4\xB3", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
6959
7152
|
];
|
|
6960
7153
|
const stepsWrap = document.createElement("div");
|
|
6961
7154
|
stepsWrap.style.cssText = `
|
|
@@ -6995,7 +7188,7 @@ var SmartComplySDK = (() => {
|
|
|
6995
7188
|
stepsWrap.appendChild(row);
|
|
6996
7189
|
});
|
|
6997
7190
|
container.appendChild(stepsWrap);
|
|
6998
|
-
const btn = this.createPrimaryButton("Get Started \
|
|
7191
|
+
const btn = this.createPrimaryButton("Get Started \xE2\u2020\u2019");
|
|
6999
7192
|
btn.style.cssText += "margin-top:2px;";
|
|
7000
7193
|
btn.addEventListener("click", () => this.showStep("country"));
|
|
7001
7194
|
container.appendChild(btn);
|
|
@@ -7004,10 +7197,10 @@ var SmartComplySDK = (() => {
|
|
|
7004
7197
|
display:flex;align-items:center;gap:6px;
|
|
7005
7198
|
color:${this.theme.textMuted};font-size:10.5px;
|
|
7006
7199
|
`;
|
|
7007
|
-
trust.textContent = "\
|
|
7200
|
+
trust.textContent = "\xF0\u0178\u201D\u2019 End-to-end encrypted";
|
|
7008
7201
|
container.appendChild(trust);
|
|
7009
7202
|
}
|
|
7010
|
-
//
|
|
7203
|
+
// ── Country Select ──────────────────────────────────────────────
|
|
7011
7204
|
renderCountrySelect(container) {
|
|
7012
7205
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7013
7206
|
const channels = this.sdkConfig?.channels || {};
|
|
@@ -7035,7 +7228,7 @@ var SmartComplySDK = (() => {
|
|
|
7035
7228
|
container.appendChild(card);
|
|
7036
7229
|
}
|
|
7037
7230
|
}
|
|
7038
|
-
//
|
|
7231
|
+
// ── ID Type Select ──────────────────────────────────────────────
|
|
7039
7232
|
renderIdTypeSelect(container) {
|
|
7040
7233
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7041
7234
|
const globalDocFlow = this.isDocumentFlow();
|
|
@@ -7057,7 +7250,7 @@ var SmartComplySDK = (() => {
|
|
|
7057
7250
|
const requiresBack = requiresBackFromBackend ?? info.requiresBack;
|
|
7058
7251
|
let subtitle2 = info.desc;
|
|
7059
7252
|
if (requiresBack && isThisDocFlow) {
|
|
7060
|
-
subtitle2 += " \
|
|
7253
|
+
subtitle2 += " \xE2\u0161\xA0\xEF\xB8\x8F Both sides required";
|
|
7061
7254
|
}
|
|
7062
7255
|
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();
|
|
7063
7256
|
const card = this.createOptionCard(
|
|
@@ -7090,7 +7283,7 @@ var SmartComplySDK = (() => {
|
|
|
7090
7283
|
container.appendChild(card);
|
|
7091
7284
|
}
|
|
7092
7285
|
}
|
|
7093
|
-
//
|
|
7286
|
+
// ── ID Input (data verification — dynamic fields) ─────────────
|
|
7094
7287
|
renderIdInput(container) {
|
|
7095
7288
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7096
7289
|
const dataFields = this.selectedChannelFields.filter((f2) => f2.type !== "upload");
|
|
@@ -7116,12 +7309,44 @@ var SmartComplySDK = (() => {
|
|
|
7116
7309
|
container.appendChild(autoBtn);
|
|
7117
7310
|
return;
|
|
7118
7311
|
}
|
|
7312
|
+
const _FIELD_RULES = [
|
|
7313
|
+
{ match: "BVN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "BVN must be exactly 11 digits" } },
|
|
7314
|
+
{ match: "NIN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7315
|
+
{ match: "NATIONAL IDENTITY", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7316
|
+
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6\xE2\u20AC\u201C20 alphanumeric characters" } },
|
|
7317
|
+
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3\xE2\u20AC\u201C30 alphanumeric characters" } },
|
|
7318
|
+
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6\xE2\u20AC\u201C30 alphanumeric characters" } },
|
|
7319
|
+
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8\xE2\u20AC\u201C20 alphanumeric characters" } },
|
|
7320
|
+
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7\xE2\u20AC\u201C15 alphanumeric characters" } },
|
|
7321
|
+
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8\xE2\u20AC\u201C20 characters" } },
|
|
7322
|
+
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10\xE2\u20AC\u201C12 characters" } }
|
|
7323
|
+
];
|
|
7324
|
+
function getFieldRule(label) {
|
|
7325
|
+
const upper = label.toUpperCase();
|
|
7326
|
+
for (const entry of _FIELD_RULES) {
|
|
7327
|
+
if (upper.includes(entry.match)) return entry.rule;
|
|
7328
|
+
}
|
|
7329
|
+
return null;
|
|
7330
|
+
}
|
|
7331
|
+
function validateFieldValue(label, value) {
|
|
7332
|
+
const rule = getFieldRule(label);
|
|
7333
|
+
if (!rule) return null;
|
|
7334
|
+
const v2 = value.trim();
|
|
7335
|
+
if (rule.numeric && !/^\d+$/.test(v2)) return `${label} must contain digits only`;
|
|
7336
|
+
if (rule.alphanumeric && !/^[A-Za-z0-9]+$/.test(v2)) return `${label} must be alphanumeric (letters and digits only)`;
|
|
7337
|
+
if (rule.minLen && v2.length < rule.minLen) return rule.hint;
|
|
7338
|
+
if (rule.maxLen && v2.length > rule.maxLen) return rule.hint;
|
|
7339
|
+
return null;
|
|
7340
|
+
}
|
|
7119
7341
|
const inputEls = [];
|
|
7120
7342
|
for (const field of dataFields) {
|
|
7121
7343
|
const fieldKey = field.label;
|
|
7122
7344
|
const fieldWrap = document.createElement("div");
|
|
7123
7345
|
fieldWrap.style.cssText = "display:flex;flex-direction:column;gap:6px;";
|
|
7346
|
+
const labelId = `sc-field-label-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7124
7347
|
const label = document.createElement("label");
|
|
7348
|
+
label.id = labelId;
|
|
7349
|
+
label.htmlFor = `sc-field-input-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7125
7350
|
label.style.cssText = `color:${this.theme.textMuted};font-size:10px;font-weight:700;letter-spacing:0.6px;text-transform:uppercase;`;
|
|
7126
7351
|
label.textContent = field.label;
|
|
7127
7352
|
fieldWrap.appendChild(label);
|
|
@@ -7194,10 +7419,17 @@ var SmartComplySDK = (() => {
|
|
|
7194
7419
|
fieldWrap.appendChild(fileInput);
|
|
7195
7420
|
inputEls.push({ field, el: fileInput });
|
|
7196
7421
|
} else {
|
|
7422
|
+
const inputId = `sc-field-input-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7423
|
+
const hintId = `sc-field-hint-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7424
|
+
const rule = getFieldRule(field.label);
|
|
7197
7425
|
const input = document.createElement("input");
|
|
7198
7426
|
input.type = "text";
|
|
7427
|
+
input.id = inputId;
|
|
7428
|
+
input.setAttribute("aria-labelledby", labelId);
|
|
7429
|
+
input.setAttribute("aria-describedby", hintId);
|
|
7199
7430
|
input.placeholder = `Enter ${field.label}`;
|
|
7200
|
-
input.maxLength = 50;
|
|
7431
|
+
input.maxLength = rule?.maxLen ? rule.maxLen + 2 : 50;
|
|
7432
|
+
if (rule?.numeric) input.inputMode = "numeric";
|
|
7201
7433
|
input.style.cssText = `
|
|
7202
7434
|
width:100%;padding:14px 16px;border-radius:12px;
|
|
7203
7435
|
font-size:16px;font-weight:500;
|
|
@@ -7208,17 +7440,46 @@ var SmartComplySDK = (() => {
|
|
|
7208
7440
|
box-sizing:border-box;
|
|
7209
7441
|
letter-spacing:0.5px;
|
|
7210
7442
|
`;
|
|
7443
|
+
const hintEl = document.createElement("div");
|
|
7444
|
+
hintEl.id = hintId;
|
|
7445
|
+
hintEl.setAttribute("aria-live", "polite");
|
|
7446
|
+
hintEl.style.cssText = `font-size:11px;min-height:14px;margin-top:-2px;`;
|
|
7447
|
+
if (rule) {
|
|
7448
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7449
|
+
hintEl.textContent = rule.hint;
|
|
7450
|
+
}
|
|
7211
7451
|
input.addEventListener("focus", () => {
|
|
7212
7452
|
input.style.borderColor = this.theme.primary;
|
|
7213
7453
|
});
|
|
7214
7454
|
input.addEventListener("blur", () => {
|
|
7215
|
-
|
|
7455
|
+
const v2 = input.value.trim();
|
|
7456
|
+
if (v2) {
|
|
7457
|
+
const err = validateFieldValue(field.label, v2);
|
|
7458
|
+
if (err) {
|
|
7459
|
+
input.style.borderColor = this.theme.error;
|
|
7460
|
+
hintEl.style.color = this.theme.error;
|
|
7461
|
+
hintEl.textContent = err;
|
|
7462
|
+
input.setAttribute("aria-invalid", "true");
|
|
7463
|
+
} else {
|
|
7464
|
+
input.style.borderColor = this.theme.success || this.theme.primary;
|
|
7465
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7466
|
+
hintEl.textContent = rule?.hint || "";
|
|
7467
|
+
input.setAttribute("aria-invalid", "false");
|
|
7468
|
+
}
|
|
7469
|
+
} else {
|
|
7470
|
+
input.style.borderColor = this.theme.border;
|
|
7471
|
+
}
|
|
7216
7472
|
});
|
|
7217
7473
|
input.addEventListener("input", () => {
|
|
7474
|
+
input.style.borderColor = this.theme.primary;
|
|
7475
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7476
|
+
hintEl.textContent = rule?.hint || "";
|
|
7477
|
+
input.removeAttribute("aria-invalid");
|
|
7218
7478
|
this.collectedFields[fieldKey] = input.value.trim();
|
|
7219
7479
|
updateSubmitState();
|
|
7220
7480
|
});
|
|
7221
7481
|
fieldWrap.appendChild(input);
|
|
7482
|
+
fieldWrap.appendChild(hintEl);
|
|
7222
7483
|
inputEls.push({ field, el: input });
|
|
7223
7484
|
}
|
|
7224
7485
|
container.appendChild(fieldWrap);
|
|
@@ -7235,8 +7496,13 @@ var SmartComplySDK = (() => {
|
|
|
7235
7496
|
submitBtn.style.opacity = "0.5";
|
|
7236
7497
|
const updateSubmitState = () => {
|
|
7237
7498
|
const allFilled = inputEls.every(({ el }) => el.value.trim().length > 0);
|
|
7238
|
-
|
|
7239
|
-
|
|
7499
|
+
const allValid = inputEls.every(({ field, el }) => {
|
|
7500
|
+
if (el.tagName === "SELECT") return el.value.trim().length > 0;
|
|
7501
|
+
return !validateFieldValue(field.label, el.value.trim());
|
|
7502
|
+
});
|
|
7503
|
+
const ok = allFilled && allValid;
|
|
7504
|
+
submitBtn.disabled = !ok;
|
|
7505
|
+
submitBtn.style.opacity = ok ? "1" : "0.5";
|
|
7240
7506
|
};
|
|
7241
7507
|
submitBtn.addEventListener("click", async () => {
|
|
7242
7508
|
submitBtn.disabled = true;
|
|
@@ -7251,9 +7517,13 @@ var SmartComplySDK = (() => {
|
|
|
7251
7517
|
const fieldValues = Object.values(this.collectedFields);
|
|
7252
7518
|
this.idNumber = fieldValues[0] || "";
|
|
7253
7519
|
this.identityCheckId = this.verificationResult.data?.identity_check_id || null;
|
|
7254
|
-
this.
|
|
7520
|
+
const d2 = this.verificationResult.data || {};
|
|
7521
|
+
const nd = d2.normalized_data || d2.data || d2;
|
|
7522
|
+
this._ivsName = [nd.first_name || nd.firstname || "", nd.last_name || nd.lastname || nd.surname || ""].map((s2) => String(s2 || "").trim()).filter(Boolean).join(" ");
|
|
7523
|
+
this._ivsGender = nd.gender || "";
|
|
7524
|
+
this.showStep("confirm_identity");
|
|
7255
7525
|
} else {
|
|
7256
|
-
errorEl.textContent = this.verificationResult.message || "
|
|
7526
|
+
errorEl.textContent = this.verificationResult.message || "We could not verify your details. Please check and try again.";
|
|
7257
7527
|
errorEl.style.display = "block";
|
|
7258
7528
|
submitBtn.textContent = "Verify & Continue";
|
|
7259
7529
|
submitBtn.disabled = false;
|
|
@@ -7262,10 +7532,16 @@ var SmartComplySDK = (() => {
|
|
|
7262
7532
|
} catch (err) {
|
|
7263
7533
|
const fieldErrors = err.data?.errors;
|
|
7264
7534
|
if (Array.isArray(fieldErrors) && fieldErrors.length > 0) {
|
|
7265
|
-
const
|
|
7266
|
-
errorEl.textContent =
|
|
7535
|
+
const firstMsg = fieldErrors[0]?.message || fieldErrors[0]?.detail || "";
|
|
7536
|
+
errorEl.textContent = _sanitizeUserMessage(firstMsg) || "Please check your details and try again.";
|
|
7537
|
+
} else if (err.code === "VERIFICATION_FAILED") {
|
|
7538
|
+
errorEl.textContent = "We could not verify those details. Please check your ID number and try again.";
|
|
7539
|
+
} else if (err.code === "CHANNEL_NOT_CONFIGURED") {
|
|
7540
|
+
errorEl.textContent = "This ID type is not currently available. Please choose a different option.";
|
|
7541
|
+
} else if (err.code === "INSUFFICIENT_BALANCE") {
|
|
7542
|
+
errorEl.textContent = "Verification is temporarily unavailable. Please contact support.";
|
|
7267
7543
|
} else {
|
|
7268
|
-
errorEl.textContent = err.message || "
|
|
7544
|
+
errorEl.textContent = _sanitizeUserMessage(err.message) || "Something went wrong. Please try again.";
|
|
7269
7545
|
}
|
|
7270
7546
|
errorEl.style.display = "block";
|
|
7271
7547
|
submitBtn.textContent = "Verify & Continue";
|
|
@@ -7279,7 +7555,7 @@ var SmartComplySDK = (() => {
|
|
|
7279
7555
|
setTimeout(() => firstInput.focus(), 350);
|
|
7280
7556
|
}
|
|
7281
7557
|
}
|
|
7282
|
-
//
|
|
7558
|
+
// ── Document Capture ────────────────────────────────────────────
|
|
7283
7559
|
renderDocumentCapture(container) {
|
|
7284
7560
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7285
7561
|
const rawName = this.selectedIdTypeName || this.selectedIdType || "your document";
|
|
@@ -7315,132 +7591,366 @@ var SmartComplySDK = (() => {
|
|
|
7315
7591
|
this.showErrorStep(err.message || "Document capture failed. Please try again.");
|
|
7316
7592
|
});
|
|
7317
7593
|
}
|
|
7318
|
-
//
|
|
7319
|
-
|
|
7320
|
-
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
border-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
|
|
7334
|
-
font-size:20px;
|
|
7335
|
-
`;
|
|
7336
|
-
centerIcon.textContent = "\u{1F50D}";
|
|
7337
|
-
animationWrap.appendChild(centerIcon);
|
|
7338
|
-
container.appendChild(animationWrap);
|
|
7339
|
-
const title = document.createElement("div");
|
|
7340
|
-
title.style.cssText = `color:${this.theme.text};font-size:15px;font-weight:700;text-align:center;`;
|
|
7341
|
-
title.textContent = "Processing your document";
|
|
7594
|
+
// ── W3: Identity Confirmation (data verification) ───────────────
|
|
7595
|
+
renderConfirmIdentity(container) {
|
|
7596
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:16px;padding:8px 0;";
|
|
7597
|
+
if (this._confirmRetries >= _SmartComplyFlow.MAX_RETRIES) {
|
|
7598
|
+
this._renderRetryLimitReached(container);
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7601
|
+
this._prewarmCamera();
|
|
7602
|
+
const iconWrap = document.createElement("div");
|
|
7603
|
+
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;`;
|
|
7604
|
+
iconWrap.textContent = "\xF0\u0178\u203A\xA1\xEF\xB8\x8F";
|
|
7605
|
+
container.appendChild(iconWrap);
|
|
7606
|
+
const title = document.createElement("h2");
|
|
7607
|
+
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7608
|
+
title.textContent = this._ivsName ? `Is this you?` : "Identity Confirmed";
|
|
7342
7609
|
container.appendChild(title);
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
{ id: "uploading", text: "Uploading", icon: "\u{1F4E4}" },
|
|
7353
|
-
{ id: "scanning", text: "Scanning", icon: "\u{1F50D}" },
|
|
7354
|
-
{ id: "verifying", text: "Verifying", icon: "\u2713" },
|
|
7355
|
-
{ id: "analyzing", text: "Checking quality", icon: "\u{1F52C}" }
|
|
7356
|
-
];
|
|
7357
|
-
steps.forEach((step) => {
|
|
7358
|
-
const stepEl = document.createElement("div");
|
|
7359
|
-
stepEl.style.cssText = `
|
|
7360
|
-
display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:10px;
|
|
7361
|
-
background:${this.theme.cardBg};border:1px solid ${this.theme.border};
|
|
7362
|
-
transition:all 0.3s ease;
|
|
7363
|
-
`;
|
|
7364
|
-
stepEl.id = `processing-step-${step.id}`;
|
|
7365
|
-
const iconEl = document.createElement("div");
|
|
7366
|
-
iconEl.style.cssText = `
|
|
7367
|
-
width:26px;height:26px;border-radius:7px;flex-shrink:0;
|
|
7368
|
-
display:flex;align-items:center;justify-content:center;
|
|
7369
|
-
font-size:12px;background:${this.theme.shimmer};
|
|
7370
|
-
`;
|
|
7371
|
-
iconEl.textContent = step.icon;
|
|
7372
|
-
iconEl.id = `step-icon-${step.id}`;
|
|
7373
|
-
const textEl = document.createElement("div");
|
|
7374
|
-
textEl.style.cssText = `flex:1;color:${this.theme.textSecondary};font-size:11px;font-weight:500;`;
|
|
7375
|
-
textEl.textContent = step.text;
|
|
7376
|
-
textEl.id = `step-text-${step.id}`;
|
|
7377
|
-
const statusEl = document.createElement("div");
|
|
7378
|
-
statusEl.style.cssText = `width:16px;height:16px;border-radius:50%;border:2px solid ${this.theme.border};position:relative;flex-shrink:0;`;
|
|
7379
|
-
statusEl.id = `step-status-${step.id}`;
|
|
7380
|
-
stepEl.appendChild(iconEl);
|
|
7381
|
-
stepEl.appendChild(textEl);
|
|
7382
|
-
stepEl.appendChild(statusEl);
|
|
7383
|
-
stepsContainer.appendChild(stepEl);
|
|
7384
|
-
});
|
|
7385
|
-
container.appendChild(stepsContainer);
|
|
7386
|
-
this.startProcessingAnimation();
|
|
7387
|
-
}
|
|
7388
|
-
startProcessingAnimation() {
|
|
7389
|
-
const steps = ["uploading", "scanning", "verifying", "analyzing"];
|
|
7390
|
-
let currentStepIndex = 0;
|
|
7391
|
-
const animateStep = (stepId) => {
|
|
7392
|
-
const stepEl = document.getElementById(`processing-step-${stepId}`);
|
|
7393
|
-
const iconEl = document.getElementById(`step-icon-${stepId}`);
|
|
7394
|
-
const textEl = document.getElementById(`step-text-${stepId}`);
|
|
7395
|
-
const statusEl = document.getElementById(`step-status-${stepId}`);
|
|
7396
|
-
if (stepEl && iconEl && textEl && statusEl) {
|
|
7397
|
-
stepEl.style.borderColor = this.theme.primary;
|
|
7398
|
-
stepEl.style.background = `${this.theme.primary}10`;
|
|
7399
|
-
iconEl.style.background = this.theme.primary;
|
|
7400
|
-
iconEl.style.color = "white";
|
|
7401
|
-
textEl.style.color = this.theme.text;
|
|
7402
|
-
textEl.style.fontWeight = "600";
|
|
7403
|
-
statusEl.innerHTML = `
|
|
7404
|
-
<div style="
|
|
7405
|
-
position:absolute;inset:2px;border-radius:50%;
|
|
7406
|
-
border:2px solid ${this.theme.border};
|
|
7407
|
-
border-top-color:${this.theme.primary};
|
|
7408
|
-
animation:sc-spin 0.8s linear infinite;
|
|
7409
|
-
"></div>
|
|
7410
|
-
`;
|
|
7610
|
+
if (this._ivsName) {
|
|
7611
|
+
const card = document.createElement("div");
|
|
7612
|
+
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;`;
|
|
7613
|
+
const rows = [
|
|
7614
|
+
{ label: "Name", value: this._ivsName }
|
|
7615
|
+
];
|
|
7616
|
+
if (this._ivsGender) {
|
|
7617
|
+
const gLabel = this._ivsGender.toLowerCase();
|
|
7618
|
+
rows.push({ label: "Gender", value: gLabel === "m" || gLabel === "male" || gLabel === "1" ? "Male" : gLabel === "f" || gLabel === "female" || gLabel === "2" ? "Female" : this._ivsGender });
|
|
7411
7619
|
}
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7620
|
+
rows.push({ label: "ID Type", value: _formatDocType(this.selectedIdType) || this.selectedIdTypeName || this.selectedIdType });
|
|
7621
|
+
rows.forEach(({ label, value }) => {
|
|
7622
|
+
const row = document.createElement("div");
|
|
7623
|
+
row.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7624
|
+
const lEl = document.createElement("span");
|
|
7625
|
+
lEl.style.cssText = `color:${this.theme.textMuted};font-size:12px;font-weight:500;`;
|
|
7626
|
+
lEl.textContent = label;
|
|
7627
|
+
const vEl = document.createElement("span");
|
|
7628
|
+
vEl.style.cssText = `color:${this.theme.text};font-size:13px;font-weight:600;max-width:60%;text-align:right;`;
|
|
7629
|
+
vEl.textContent = value;
|
|
7630
|
+
row.appendChild(lEl);
|
|
7631
|
+
row.appendChild(vEl);
|
|
7632
|
+
card.appendChild(row);
|
|
7633
|
+
});
|
|
7634
|
+
container.appendChild(card);
|
|
7635
|
+
} else {
|
|
7636
|
+
const sub = document.createElement("p");
|
|
7637
|
+
sub.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7638
|
+
sub.textContent = "Your details have been confirmed. Please continue to the face check.";
|
|
7639
|
+
container.appendChild(sub);
|
|
7640
|
+
}
|
|
7641
|
+
const yesBtn = this.createPrimaryButton("Yes, that's me \xE2\u20AC\u201D continue");
|
|
7642
|
+
yesBtn.addEventListener("click", () => {
|
|
7643
|
+
this._confirmRetries = 0;
|
|
7644
|
+
this.showStep("liveness");
|
|
7645
|
+
});
|
|
7646
|
+
container.appendChild(yesBtn);
|
|
7647
|
+
const noBtn = this.createSecondaryButton("No, use a different ID");
|
|
7648
|
+
noBtn.addEventListener("click", () => {
|
|
7649
|
+
this._confirmRetries++;
|
|
7650
|
+
this._ivsName = "";
|
|
7651
|
+
this._ivsGender = "";
|
|
7652
|
+
this.identityCheckId = null;
|
|
7653
|
+
this.verificationResult = null;
|
|
7654
|
+
this.showStep("id_input");
|
|
7655
|
+
});
|
|
7656
|
+
container.appendChild(noBtn);
|
|
7657
|
+
}
|
|
7658
|
+
// ── W4: Document Confirmation (document verification) ────────────
|
|
7659
|
+
renderDocumentConfirm(container) {
|
|
7660
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:8px 0;";
|
|
7661
|
+
if (this._confirmRetries >= _SmartComplyFlow.MAX_RETRIES) {
|
|
7662
|
+
this._renderRetryLimitReached(container);
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
7665
|
+
this._prewarmCamera();
|
|
7666
|
+
const iconWrap = document.createElement("div");
|
|
7667
|
+
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;`;
|
|
7668
|
+
iconWrap.textContent = "\xF0\u0178\u201C\u201E";
|
|
7669
|
+
container.appendChild(iconWrap);
|
|
7670
|
+
const title = document.createElement("h2");
|
|
7671
|
+
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7672
|
+
title.textContent = "Are these your details?";
|
|
7673
|
+
container.appendChild(title);
|
|
7674
|
+
const sub = document.createElement("p");
|
|
7675
|
+
sub.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.5;`;
|
|
7676
|
+
sub.textContent = "We read the following from your document. Please confirm they are correct before continuing.";
|
|
7677
|
+
container.appendChild(sub);
|
|
7678
|
+
if (this._ocrFaceImageUrl) {
|
|
7679
|
+
const faceWrap = document.createElement("div");
|
|
7680
|
+
faceWrap.style.cssText = `width:80px;height:80px;border-radius:50%;overflow:hidden;border:3px solid ${this.theme.primary};flex-shrink:0;margin:0 auto;`;
|
|
7681
|
+
const faceImg = document.createElement("img");
|
|
7682
|
+
faceImg.src = this._ocrFaceImageUrl;
|
|
7683
|
+
faceImg.alt = "Photo from your document";
|
|
7684
|
+
faceImg.style.cssText = "width:100%;height:100%;object-fit:cover;";
|
|
7685
|
+
faceWrap.appendChild(faceImg);
|
|
7686
|
+
container.appendChild(faceWrap);
|
|
7687
|
+
}
|
|
7688
|
+
if (this._ocrExpirySoon && !this._ocrIsExpired) {
|
|
7689
|
+
const warn = document.createElement("div");
|
|
7690
|
+
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;`;
|
|
7691
|
+
warn.textContent = "\xE2\u0161\xA0\xEF\xB8\x8F Your document expires soon. Some services may not accept it, but you can still continue.";
|
|
7692
|
+
container.appendChild(warn);
|
|
7693
|
+
}
|
|
7694
|
+
const card = document.createElement("div");
|
|
7695
|
+
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;`;
|
|
7696
|
+
const docRows = [];
|
|
7697
|
+
if (this._ocrName) docRows.push({ label: "Name", value: this._ocrName });
|
|
7698
|
+
if (this._ocrDocNumber) docRows.push({ label: "Document No", value: this._ocrDocNumber });
|
|
7699
|
+
if (this._ocrExpiry) docRows.push({ label: "Expires", value: this._ocrExpiry });
|
|
7700
|
+
docRows.push({ label: "Document Type", value: _formatDocType(this.selectedIdType) || this.selectedIdTypeName });
|
|
7701
|
+
if (docRows.length === 0) {
|
|
7702
|
+
const fallback = document.createElement("div");
|
|
7703
|
+
fallback.style.cssText = `color:${this.theme.textSecondary};font-size:12px;`;
|
|
7704
|
+
fallback.textContent = "Document scanned successfully.";
|
|
7705
|
+
card.appendChild(fallback);
|
|
7706
|
+
} else {
|
|
7707
|
+
docRows.forEach(({ label, value }) => {
|
|
7708
|
+
if (!value) return;
|
|
7709
|
+
const row = document.createElement("div");
|
|
7710
|
+
row.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7711
|
+
const lEl = document.createElement("span");
|
|
7712
|
+
lEl.style.cssText = `color:${this.theme.textMuted};font-size:12px;font-weight:500;`;
|
|
7713
|
+
lEl.textContent = label;
|
|
7714
|
+
const vEl = document.createElement("span");
|
|
7715
|
+
vEl.style.cssText = `color:${this.theme.text};font-size:13px;font-weight:600;max-width:60%;text-align:right;`;
|
|
7716
|
+
vEl.textContent = value;
|
|
7717
|
+
row.appendChild(lEl);
|
|
7718
|
+
row.appendChild(vEl);
|
|
7719
|
+
card.appendChild(row);
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
container.appendChild(card);
|
|
7723
|
+
const yesBtn = this.createPrimaryButton("Yes, continue");
|
|
7724
|
+
yesBtn.addEventListener("click", () => {
|
|
7725
|
+
this._confirmRetries = 0;
|
|
7726
|
+
this.showStep("liveness");
|
|
7727
|
+
});
|
|
7728
|
+
container.appendChild(yesBtn);
|
|
7729
|
+
const noBtn = this.createSecondaryButton("No, retake document");
|
|
7730
|
+
noBtn.addEventListener("click", () => {
|
|
7731
|
+
this._confirmRetries++;
|
|
7732
|
+
this._ocrName = "";
|
|
7733
|
+
this._ocrDocNumber = "";
|
|
7734
|
+
this._ocrExpiry = "";
|
|
7735
|
+
this._ocrFaceImageUrl = "";
|
|
7736
|
+
this._ocrIsExpired = false;
|
|
7737
|
+
this._ocrExpirySoon = false;
|
|
7738
|
+
this.livenessEntryId = null;
|
|
7739
|
+
this.documentBlob = null;
|
|
7740
|
+
this.documentBackBlob = null;
|
|
7741
|
+
this.showStep("document_capture");
|
|
7742
|
+
});
|
|
7743
|
+
container.appendChild(noBtn);
|
|
7744
|
+
}
|
|
7745
|
+
// ── W6+W7+W8: OCR Gate (document verification) ───────────────────
|
|
7746
|
+
renderOcrGate(container) {
|
|
7747
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:14px;padding:20px 0;text-align:center;";
|
|
7748
|
+
const spinnerWrap = document.createElement("div");
|
|
7749
|
+
spinnerWrap.style.cssText = "position:relative;width:52px;height:52px;flex-shrink:0;";
|
|
7750
|
+
const ring = document.createElement("div");
|
|
7751
|
+
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;`;
|
|
7752
|
+
const centre = document.createElement("div");
|
|
7753
|
+
centre.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;";
|
|
7754
|
+
centre.textContent = "\xF0\u0178\u201C\u201E";
|
|
7755
|
+
spinnerWrap.appendChild(ring);
|
|
7756
|
+
spinnerWrap.appendChild(centre);
|
|
7757
|
+
container.appendChild(spinnerWrap);
|
|
7758
|
+
const statusEl = document.createElement("div");
|
|
7759
|
+
statusEl.setAttribute("aria-live", "polite");
|
|
7760
|
+
statusEl.style.cssText = `color:${this.theme.text};font-size:15px;font-weight:600;`;
|
|
7761
|
+
statusEl.textContent = "Reading your document\xE2\u20AC\xA6";
|
|
7762
|
+
container.appendChild(statusEl);
|
|
7763
|
+
const subEl = document.createElement("div");
|
|
7764
|
+
subEl.style.cssText = `color:${this.theme.textSecondary};font-size:12px;max-width:260px;line-height:1.5;`;
|
|
7765
|
+
subEl.textContent = "This usually takes 5\xE2\u20AC\u201C15 seconds. Please wait.";
|
|
7766
|
+
container.appendChild(subEl);
|
|
7767
|
+
if (!this.livenessEntryId) {
|
|
7768
|
+
this.showErrorStep("Document upload failed. Please try again.");
|
|
7769
|
+
return;
|
|
7770
|
+
}
|
|
7771
|
+
const entryId = this.livenessEntryId;
|
|
7772
|
+
const MAX_POLLS = 10;
|
|
7773
|
+
let polls = 0;
|
|
7774
|
+
const poll = async () => {
|
|
7775
|
+
if (this.isDestroyed) return;
|
|
7776
|
+
polls++;
|
|
7777
|
+
try {
|
|
7778
|
+
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7779
|
+
if (result.is_expired === true) {
|
|
7780
|
+
container.innerHTML = "";
|
|
7781
|
+
this._renderExpiredDocument(container);
|
|
7782
|
+
return;
|
|
7783
|
+
}
|
|
7784
|
+
if (result.ocr_quality_failed === true) {
|
|
7785
|
+
container.innerHTML = "";
|
|
7786
|
+
this._renderOcrQualityFail(container, result.ocr_rejection_reason);
|
|
7787
|
+
return;
|
|
7788
|
+
}
|
|
7789
|
+
const ocrDone = result.ocr_status === "done" || result.ocr_status === "ocr_complete" || result.ocr_name || result.ocr_document_number || result.ocr_expiry_date;
|
|
7790
|
+
if (ocrDone) {
|
|
7791
|
+
this._ocrName = result.ocr_name || "";
|
|
7792
|
+
this._ocrDocNumber = result.ocr_document_number || "";
|
|
7793
|
+
this._ocrExpiry = result.ocr_expiry_date || "";
|
|
7794
|
+
this._ocrFaceImageUrl = result.ocr_face_image_url || "";
|
|
7795
|
+
this._ocrIsExpired = result.is_expired === true;
|
|
7796
|
+
if (this._ocrExpiry) {
|
|
7797
|
+
const expDate = _parseOcrDate(this._ocrExpiry);
|
|
7798
|
+
if (expDate) {
|
|
7799
|
+
const sixMonths = /* @__PURE__ */ new Date();
|
|
7800
|
+
sixMonths.setMonth(sixMonths.getMonth() + 6);
|
|
7801
|
+
this._ocrExpirySoon = expDate <= sixMonths && expDate > /* @__PURE__ */ new Date();
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
this.showStep("document_confirm");
|
|
7805
|
+
return;
|
|
7425
7806
|
}
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7807
|
+
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7808
|
+
this._ocrPollTimer = setTimeout(poll, 2e3);
|
|
7809
|
+
} else if (!this.isDestroyed) {
|
|
7810
|
+
statusEl.textContent = "Taking longer than usual\xE2\u20AC\xA6";
|
|
7811
|
+
subEl.textContent = "We'll continue \xE2\u20AC\u201D you can confirm your details on the next screen.";
|
|
7812
|
+
this._ocrPollTimer = setTimeout(() => {
|
|
7813
|
+
if (!this.isDestroyed) this.showStep("document_confirm");
|
|
7814
|
+
}, 2e3);
|
|
7429
7815
|
}
|
|
7430
|
-
}
|
|
7816
|
+
} catch {
|
|
7817
|
+
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7818
|
+
this._ocrPollTimer = setTimeout(poll, 3e3);
|
|
7819
|
+
} else if (!this.isDestroyed) {
|
|
7820
|
+
this.showErrorStep("We could not read your document. Please check your connection and try again.");
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7431
7823
|
};
|
|
7432
|
-
|
|
7824
|
+
this._ocrPollTimer = setTimeout(poll, 3e3);
|
|
7825
|
+
}
|
|
7826
|
+
_renderExpiredDocument(container) {
|
|
7827
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7828
|
+
const icon = document.createElement("div");
|
|
7829
|
+
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;`;
|
|
7830
|
+
icon.textContent = "\xF0\u0178\u201C\u2026";
|
|
7831
|
+
container.appendChild(icon);
|
|
7832
|
+
const t2 = document.createElement("h2");
|
|
7833
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7834
|
+
t2.textContent = "Document Expired";
|
|
7835
|
+
container.appendChild(t2);
|
|
7836
|
+
const d2 = document.createElement("p");
|
|
7837
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7838
|
+
d2.textContent = "The document you submitted has expired. Please use a valid, current ID.";
|
|
7839
|
+
container.appendChild(d2);
|
|
7840
|
+
const retryBtn = this.createPrimaryButton("Try a different document");
|
|
7841
|
+
retryBtn.addEventListener("click", () => {
|
|
7842
|
+
this.livenessEntryId = null;
|
|
7843
|
+
this.documentBlob = null;
|
|
7844
|
+
this.documentBackBlob = null;
|
|
7845
|
+
this.showStep("document_capture");
|
|
7846
|
+
});
|
|
7847
|
+
container.appendChild(retryBtn);
|
|
7848
|
+
const endBtn = this.createSecondaryButton("End session");
|
|
7849
|
+
endBtn.addEventListener("click", () => {
|
|
7850
|
+
this.options.onError?.(new Error("Document expired"));
|
|
7851
|
+
this.close();
|
|
7852
|
+
});
|
|
7853
|
+
container.appendChild(endBtn);
|
|
7854
|
+
}
|
|
7855
|
+
_renderOcrQualityFail(container, reason) {
|
|
7856
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7857
|
+
const icon = document.createElement("div");
|
|
7858
|
+
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;`;
|
|
7859
|
+
icon.textContent = "\xF0\u0178\u201D\x8D";
|
|
7860
|
+
container.appendChild(icon);
|
|
7861
|
+
const t2 = document.createElement("h2");
|
|
7862
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7863
|
+
t2.textContent = "Document Photo Unclear";
|
|
7864
|
+
container.appendChild(t2);
|
|
7865
|
+
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.";
|
|
7866
|
+
const d2 = document.createElement("p");
|
|
7867
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7868
|
+
d2.textContent = msg;
|
|
7869
|
+
container.appendChild(d2);
|
|
7870
|
+
const retryBtn = this.createPrimaryButton("Retake photo");
|
|
7871
|
+
retryBtn.addEventListener("click", () => {
|
|
7872
|
+
this.livenessEntryId = null;
|
|
7873
|
+
this.documentBlob = null;
|
|
7874
|
+
this.documentBackBlob = null;
|
|
7875
|
+
this.showStep("document_capture");
|
|
7876
|
+
});
|
|
7877
|
+
container.appendChild(retryBtn);
|
|
7878
|
+
}
|
|
7879
|
+
// ── W11: You're all done (end-user final screen) ─────────────────
|
|
7880
|
+
renderDone(container) {
|
|
7881
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:16px;padding:24px 0;";
|
|
7882
|
+
container.setAttribute("role", "status");
|
|
7883
|
+
this.options.onComplete?.({
|
|
7884
|
+
entryId: this.livenessEntryId || 0,
|
|
7885
|
+
sessionId: this.sdk.sessionToken,
|
|
7886
|
+
status: "processing",
|
|
7887
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7888
|
+
verificationResult: this.verificationResult || void 0
|
|
7889
|
+
});
|
|
7890
|
+
const iconWrap = document.createElement("div");
|
|
7891
|
+
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;`;
|
|
7892
|
+
iconWrap.textContent = "\xE2\u0153\u201C";
|
|
7893
|
+
container.appendChild(iconWrap);
|
|
7894
|
+
const title = document.createElement("h2");
|
|
7895
|
+
title.style.cssText = `color:${this.theme.text};font-size:22px;font-weight:700;margin:0;`;
|
|
7896
|
+
title.textContent = "You're all done!";
|
|
7897
|
+
container.appendChild(title);
|
|
7898
|
+
const brandName = this.sdkConfig?.brand_name || "the service";
|
|
7899
|
+
const desc = document.createElement("p");
|
|
7900
|
+
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7901
|
+
desc.textContent = `Your verification is complete. ${brandName} will review and be in touch shortly.`;
|
|
7902
|
+
container.appendChild(desc);
|
|
7903
|
+
const info = document.createElement("div");
|
|
7904
|
+
info.style.cssText = `color:${this.theme.textMuted};font-size:11px;display:flex;align-items:center;gap:4px;`;
|
|
7905
|
+
info.textContent = "\xF0\u0178\u201D\u2019 Your data is encrypted and secure";
|
|
7906
|
+
container.appendChild(info);
|
|
7907
|
+
const doneBtn = this.createPrimaryButton("Close");
|
|
7908
|
+
doneBtn.addEventListener("click", () => {
|
|
7909
|
+
this.close();
|
|
7910
|
+
});
|
|
7911
|
+
container.appendChild(doneBtn);
|
|
7912
|
+
}
|
|
7913
|
+
// ── W14: Retry limit reached ─────────────────────────────────────
|
|
7914
|
+
_renderRetryLimitReached(container) {
|
|
7915
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:24px 0;";
|
|
7916
|
+
const icon = document.createElement("div");
|
|
7917
|
+
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;`;
|
|
7918
|
+
icon.textContent = "\xE2\u0161\xA0\xEF\xB8\x8F";
|
|
7919
|
+
container.appendChild(icon);
|
|
7920
|
+
const t2 = document.createElement("h2");
|
|
7921
|
+
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
7922
|
+
t2.textContent = "Unable to Complete Verification";
|
|
7923
|
+
container.appendChild(t2);
|
|
7924
|
+
const d2 = document.createElement("p");
|
|
7925
|
+
d2.style.cssText = `color:${this.theme.textSecondary};font-size:13px;margin:0;max-width:280px;line-height:1.6;`;
|
|
7926
|
+
d2.textContent = "We were unable to complete your verification at this time. Please contact support or try again later.";
|
|
7927
|
+
container.appendChild(d2);
|
|
7928
|
+
const closeBtn = this.createPrimaryButton("Close");
|
|
7929
|
+
closeBtn.addEventListener("click", () => {
|
|
7930
|
+
this.options.onError?.(new Error("Retry limit reached"));
|
|
7931
|
+
this.close();
|
|
7932
|
+
});
|
|
7933
|
+
container.appendChild(closeBtn);
|
|
7934
|
+
}
|
|
7935
|
+
// ── W13: Camera pre-warm ─────────────────────────────────────────
|
|
7936
|
+
_prewarmCamera() {
|
|
7937
|
+
if (this._prewarmedStream || this.isDestroyed) return;
|
|
7938
|
+
navigator.mediaDevices?.getUserMedia?.({ video: { facingMode: "user" } }).then((stream) => {
|
|
7939
|
+
if (this.isDestroyed) {
|
|
7940
|
+
stream.getTracks().forEach((t2) => t2.stop());
|
|
7941
|
+
return;
|
|
7942
|
+
}
|
|
7943
|
+
this._prewarmedStream = stream;
|
|
7944
|
+
}).catch(() => {
|
|
7945
|
+
});
|
|
7433
7946
|
}
|
|
7434
7947
|
/**
|
|
7435
7948
|
* Process document capture result with front/back support
|
|
7436
7949
|
*/
|
|
7437
7950
|
async processDocumentCaptureResult(result, captureContainer, container) {
|
|
7438
|
-
this.documentBlob = result.front;
|
|
7439
|
-
this.documentBackBlob = result.back
|
|
7440
|
-
await this.processDocumentBlob(
|
|
7441
|
-
if (result.back) {
|
|
7442
|
-
console.log("Back image captured, size:", result.back.size);
|
|
7443
|
-
}
|
|
7951
|
+
this.documentBlob = await _compressImage(result.front, 1600, 0.85);
|
|
7952
|
+
this.documentBackBlob = result.back ? await _compressImage(result.back, 1600, 0.85) : null;
|
|
7953
|
+
await this.processDocumentBlob(this.documentBlob, captureContainer, container);
|
|
7444
7954
|
}
|
|
7445
7955
|
/**
|
|
7446
7956
|
* Process document blob - common method for both CameraView and DocumentCapture
|
|
@@ -7461,12 +7971,12 @@ var SmartComplySDK = (() => {
|
|
|
7461
7971
|
<div style="display:flex;flex-direction:column;align-items:center;gap:14px;padding:28px 20px;">
|
|
7462
7972
|
<div style="position:relative;width:52px;height:52px;">
|
|
7463
7973
|
<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>
|
|
7464
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\
|
|
7974
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\xF0\u0178\u201D\x8D</div>
|
|
7465
7975
|
</div>
|
|
7466
7976
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Scanning document...</div>
|
|
7467
7977
|
<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>
|
|
7468
7978
|
</div>`;
|
|
7469
|
-
const identifier = `DOC-${Date.now()}`;
|
|
7979
|
+
const identifier = Object.values(this.collectedFields)[0] || this.idNumber || this.selectedIdType || `DOC-${Date.now()}`;
|
|
7470
7980
|
const COUNTRY_CODES = {
|
|
7471
7981
|
nigeria: "NG",
|
|
7472
7982
|
ghana: "GH",
|
|
@@ -7508,7 +8018,7 @@ var SmartComplySDK = (() => {
|
|
|
7508
8018
|
}
|
|
7509
8019
|
}
|
|
7510
8020
|
this.livenessEntryId = createResult.id;
|
|
7511
|
-
this.showStep("
|
|
8021
|
+
this.showStep("ocr_gate");
|
|
7512
8022
|
} catch (err) {
|
|
7513
8023
|
console.error("Document processing failed:", err);
|
|
7514
8024
|
if (retryCount < maxRetries) {
|
|
@@ -7519,7 +8029,7 @@ var SmartComplySDK = (() => {
|
|
|
7519
8029
|
<div style="display:flex;flex-direction:column;align-items:center;gap:16px;padding:28px 20px;text-align:center;">
|
|
7520
8030
|
<div style="position:relative;width:52px;height:52px;">
|
|
7521
8031
|
<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>
|
|
7522
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\
|
|
8032
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">\xF0\u0178\u201D\u201E</div>
|
|
7523
8033
|
</div>
|
|
7524
8034
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Retrying...</div>
|
|
7525
8035
|
<div style="color:${this.theme.textSecondary};font-size:12px;max-width:240px;line-height:1.5;">Attempt ${retryCount} of ${maxRetries}</div>
|
|
@@ -7535,50 +8045,56 @@ var SmartComplySDK = (() => {
|
|
|
7535
8045
|
await attemptProcessing();
|
|
7536
8046
|
}
|
|
7537
8047
|
handleDocumentProcessingError(error, captureContainer) {
|
|
7538
|
-
let
|
|
8048
|
+
let userMessage = "We could not verify your document. Please try again.";
|
|
7539
8049
|
let canRetry = true;
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
8050
|
+
const raw = (error?.message || "").toLowerCase();
|
|
8051
|
+
if (raw.includes("expired")) {
|
|
8052
|
+
userMessage = "The document you submitted has expired. Please use a valid, current ID.";
|
|
8053
|
+
canRetry = false;
|
|
8054
|
+
} else if (raw.includes("quality too low") || raw.includes("blurry") || raw.includes("blur")) {
|
|
8055
|
+
userMessage = "The document photo isn't clear enough. Please retake it in good lighting with no glare.";
|
|
8056
|
+
} else if (raw.includes("not detected") || raw.includes("document_not_detected")) {
|
|
8057
|
+
userMessage = "We could not detect a document in the photo. Please ensure the full ID is visible and try again.";
|
|
8058
|
+
} else if (raw.includes("network") || raw.includes("fetch") || raw.includes("connection")) {
|
|
8059
|
+
userMessage = "A connection error occurred. Please check your internet and try again.";
|
|
8060
|
+
} else if (raw.includes("too large") || raw.includes("413")) {
|
|
8061
|
+
userMessage = "The document image is too large. Please retake the photo.";
|
|
8062
|
+
} else if (raw.includes("format") || raw.includes("415")) {
|
|
8063
|
+
userMessage = "Unsupported image format. Please use JPEG or PNG.";
|
|
8064
|
+
} else if (raw.includes("type mismatch") || raw.includes("document_type_mismatch")) {
|
|
8065
|
+
userMessage = "The document does not match the selected ID type. Please choose the correct ID type.";
|
|
7553
8066
|
}
|
|
7554
8067
|
if (captureContainer) {
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
8068
|
+
const wrap = document.createElement("div");
|
|
8069
|
+
wrap.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:16px;padding:28px 20px;text-align:center;";
|
|
8070
|
+
wrap.setAttribute("role", "alert");
|
|
8071
|
+
const icon = document.createElement("div");
|
|
8072
|
+
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;`;
|
|
8073
|
+
icon.textContent = "\xF0\u0178\u201D\x8D";
|
|
8074
|
+
wrap.appendChild(icon);
|
|
8075
|
+
const titleEl = document.createElement("div");
|
|
8076
|
+
titleEl.style.cssText = `color:${this.theme.text};font-size:14px;font-weight:700;`;
|
|
8077
|
+
titleEl.textContent = "Document Could Not Be Verified";
|
|
8078
|
+
wrap.appendChild(titleEl);
|
|
8079
|
+
const msgEl = document.createElement("div");
|
|
8080
|
+
msgEl.style.cssText = `color:${this.theme.textSecondary};font-size:13px;max-width:260px;line-height:1.6;`;
|
|
8081
|
+
msgEl.textContent = userMessage;
|
|
8082
|
+
wrap.appendChild(msgEl);
|
|
7569
8083
|
if (canRetry) {
|
|
7570
|
-
const retryBtn =
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
8084
|
+
const retryBtn = document.createElement("button");
|
|
8085
|
+
retryBtn.textContent = "Try Again";
|
|
8086
|
+
retryBtn.setAttribute("aria-label", "Retake document photo");
|
|
8087
|
+
retryBtn.style.cssText = `margin-top:8px;padding:12px 24px;border-radius:8px;background:${this.theme.primary};color:#fff;border:none;font-size:14px;font-weight:600;cursor:pointer;`;
|
|
8088
|
+
retryBtn.addEventListener("click", () => this.showStep("document_capture"));
|
|
8089
|
+
wrap.appendChild(retryBtn);
|
|
7576
8090
|
}
|
|
8091
|
+
captureContainer.innerHTML = "";
|
|
8092
|
+
captureContainer.appendChild(wrap);
|
|
7577
8093
|
} else {
|
|
7578
|
-
this.showErrorStep(
|
|
8094
|
+
this.showErrorStep(userMessage);
|
|
7579
8095
|
}
|
|
7580
8096
|
}
|
|
7581
|
-
//
|
|
8097
|
+
// ── Liveness ────────────────────────────────────────────────────
|
|
7582
8098
|
renderLiveness(container) {
|
|
7583
8099
|
container.style.cssText += "display:flex;flex-direction:column;gap:14px;";
|
|
7584
8100
|
const title = this.createStepTitle("Face verification");
|
|
@@ -7590,7 +8106,7 @@ var SmartComplySDK = (() => {
|
|
|
7590
8106
|
const livenessContainer = document.createElement("div");
|
|
7591
8107
|
livenessContainer.style.cssText = "margin:8px 0;width:100%;";
|
|
7592
8108
|
container.appendChild(livenessContainer);
|
|
7593
|
-
const identifier = this.idNumber || `DOC-${Date.now()}`;
|
|
8109
|
+
const identifier = this.identityCheckId ? "" : this.idNumber || Object.values(this.collectedFields)[0] || this.selectedIdType || `DOC-${Date.now()}`;
|
|
7594
8110
|
const COUNTRY_CODES = {
|
|
7595
8111
|
nigeria: "NG",
|
|
7596
8112
|
ghana: "GH",
|
|
@@ -7609,224 +8125,53 @@ var SmartComplySDK = (() => {
|
|
|
7609
8125
|
country,
|
|
7610
8126
|
document: this.documentBlob || void 0,
|
|
7611
8127
|
document_back: this.documentBackBlob || void 0,
|
|
7612
|
-
identity_check: this.identityCheckId || void 0
|
|
8128
|
+
identity_check: this.identityCheckId || void 0,
|
|
8129
|
+
verification_type: this.identityCheckId ? "data_verification" : this.documentBlob ? "document_verification" : "liveness_only",
|
|
8130
|
+
// B4: pass pre-warmed stream so camera.open() is skipped
|
|
8131
|
+
prewarmedStream: this._prewarmedStream || void 0
|
|
7613
8132
|
},
|
|
7614
8133
|
["BLINK", "TURN_HEAD", "OPEN_MOUTH"],
|
|
7615
8134
|
this.livenessEntryId || void 0
|
|
7616
8135
|
).then((result) => {
|
|
7617
|
-
|
|
8136
|
+
if (result.status === "failed") {
|
|
8137
|
+
this._livenessFailures++;
|
|
8138
|
+
}
|
|
8139
|
+
if (result.id) {
|
|
8140
|
+
this.livenessEntryId = result.id;
|
|
8141
|
+
}
|
|
8142
|
+
this.showStep("done");
|
|
7618
8143
|
}).catch((err) => {
|
|
8144
|
+
this._livenessFailures++;
|
|
7619
8145
|
this.showErrorStep(err.message || "Liveness verification failed. Please try again.");
|
|
7620
8146
|
});
|
|
7621
8147
|
}
|
|
7622
|
-
// ── Result ──────────────────────────────────────────────────────
|
|
7623
|
-
showResult(submitResult) {
|
|
7624
|
-
this.currentStep = "result";
|
|
7625
|
-
this.updateProgress();
|
|
7626
|
-
if (!this.contentArea) return;
|
|
7627
|
-
this.contentArea.innerHTML = "";
|
|
7628
|
-
const wrapper = document.createElement("div");
|
|
7629
|
-
wrapper.style.cssText = `
|
|
7630
|
-
display:flex;flex-direction:column;align-items:center;
|
|
7631
|
-
text-align:center;gap:16px;padding:20px 0;
|
|
7632
|
-
opacity:0;transform:scale(0.95);transition:all 0.4s ease;
|
|
7633
|
-
`;
|
|
7634
|
-
const iconWrap = document.createElement("div");
|
|
7635
|
-
iconWrap.style.cssText = `
|
|
7636
|
-
width:80px;height:80px;border-radius:50%;
|
|
7637
|
-
display:flex;align-items:center;justify-content:center;
|
|
7638
|
-
font-size:36px;
|
|
7639
|
-
background:${this.theme.cardBg};
|
|
7640
|
-
border:3px solid ${this.theme.border};
|
|
7641
|
-
`;
|
|
7642
|
-
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>`;
|
|
7643
|
-
wrapper.appendChild(iconWrap);
|
|
7644
|
-
const title = document.createElement("h2");
|
|
7645
|
-
title.style.cssText = `color:${this.theme.text};font-size:20px;font-weight:700;margin:0;`;
|
|
7646
|
-
title.textContent = "Verification Submitted";
|
|
7647
|
-
wrapper.appendChild(title);
|
|
7648
|
-
const desc = document.createElement("p");
|
|
7649
|
-
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7650
|
-
desc.textContent = "Processing your identity check. This takes around 30 seconds.";
|
|
7651
|
-
wrapper.appendChild(desc);
|
|
7652
|
-
const details = document.createElement("div");
|
|
7653
|
-
details.style.cssText = `
|
|
7654
|
-
width:100%;padding:16px;border-radius:12px;
|
|
7655
|
-
background:${this.theme.cardBg};border:1px solid ${this.theme.border};
|
|
7656
|
-
text-align:left;
|
|
7657
|
-
`;
|
|
7658
|
-
const statusBadge = document.createElement("span");
|
|
7659
|
-
statusBadge.style.cssText = `font-size:12px;font-weight:600;color:${this.theme.warning};`;
|
|
7660
|
-
statusBadge.textContent = "Processing\u2026";
|
|
7661
|
-
const statusRow = document.createElement("div");
|
|
7662
|
-
statusRow.style.cssText = "display:flex;justify-content:space-between;margin-bottom:8px;";
|
|
7663
|
-
statusRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;">Status</span>`;
|
|
7664
|
-
statusRow.appendChild(statusBadge);
|
|
7665
|
-
const entryRow = document.createElement("div");
|
|
7666
|
-
entryRow.style.cssText = "display:flex;justify-content:space-between;margin-bottom:8px;";
|
|
7667
|
-
entryRow.innerHTML = `
|
|
7668
|
-
<span style="color:${this.theme.textMuted};font-size:12px;">Entry ID</span>
|
|
7669
|
-
<span style="color:${this.theme.text};font-size:12px;font-weight:600;">${submitResult.id}</span>
|
|
7670
|
-
`;
|
|
7671
|
-
details.appendChild(entryRow);
|
|
7672
|
-
details.appendChild(statusRow);
|
|
7673
|
-
const submittedRow = document.createElement("div");
|
|
7674
|
-
submittedRow.style.cssText = "display:flex;justify-content:space-between;margin-top:8px;";
|
|
7675
|
-
submittedRow.innerHTML = `
|
|
7676
|
-
<span style="color:${this.theme.textMuted};font-size:12px;">Submitted</span>
|
|
7677
|
-
<span style="color:${this.theme.text};font-size:12px;">${submitResult.submitted_at ? new Date(submitResult.submitted_at).toLocaleString() : "Just now"}</span>
|
|
7678
|
-
`;
|
|
7679
|
-
details.appendChild(submittedRow);
|
|
7680
|
-
wrapper.appendChild(details);
|
|
7681
|
-
const failureMsg = document.createElement("div");
|
|
7682
|
-
failureMsg.style.cssText = `
|
|
7683
|
-
display:none;width:100%;padding:12px 14px;border-radius:10px;
|
|
7684
|
-
background:${this.theme.errorBg};border:1px solid ${this.theme.error};
|
|
7685
|
-
color:${this.theme.error};font-size:12px;text-align:left;line-height:1.5;
|
|
7686
|
-
`;
|
|
7687
|
-
wrapper.appendChild(failureMsg);
|
|
7688
|
-
const doneBtn = this.createPrimaryButton("Done");
|
|
7689
|
-
let finalStatus = submitResult.status;
|
|
7690
|
-
doneBtn.addEventListener("click", () => {
|
|
7691
|
-
if (this._pollStatusTimer) {
|
|
7692
|
-
clearTimeout(this._pollStatusTimer);
|
|
7693
|
-
this._pollStatusTimer = null;
|
|
7694
|
-
}
|
|
7695
|
-
this.options.onComplete?.({
|
|
7696
|
-
entryId: submitResult.id,
|
|
7697
|
-
sessionId: this.sdk.sessionToken,
|
|
7698
|
-
status: finalStatus,
|
|
7699
|
-
submittedAt: submitResult.submitted_at,
|
|
7700
|
-
verificationResult: this.verificationResult || void 0
|
|
7701
|
-
});
|
|
7702
|
-
this.close();
|
|
7703
|
-
});
|
|
7704
|
-
wrapper.appendChild(doneBtn);
|
|
7705
|
-
this.contentArea.appendChild(wrapper);
|
|
7706
|
-
requestAnimationFrame(() => {
|
|
7707
|
-
wrapper.style.opacity = "1";
|
|
7708
|
-
wrapper.style.transform = "scale(1)";
|
|
7709
|
-
});
|
|
7710
|
-
this._startStatusPolling(
|
|
7711
|
-
submitResult.id,
|
|
7712
|
-
iconWrap,
|
|
7713
|
-
statusBadge,
|
|
7714
|
-
title,
|
|
7715
|
-
desc,
|
|
7716
|
-
failureMsg,
|
|
7717
|
-
(resolved) => {
|
|
7718
|
-
finalStatus = resolved;
|
|
7719
|
-
}
|
|
7720
|
-
);
|
|
7721
|
-
}
|
|
7722
|
-
_startStatusPolling(entryId, iconWrap, statusBadge, title, desc, failureMsg, onResolved) {
|
|
7723
|
-
const MAX_POLLS = 25;
|
|
7724
|
-
let polls = 0;
|
|
7725
|
-
let uiTimeoutId = setTimeout(() => {
|
|
7726
|
-
if (!this.isDestroyed) applyTimeout();
|
|
7727
|
-
}, 3e4);
|
|
7728
|
-
const clearUiTimeout = () => {
|
|
7729
|
-
if (uiTimeoutId) {
|
|
7730
|
-
clearTimeout(uiTimeoutId);
|
|
7731
|
-
uiTimeoutId = null;
|
|
7732
|
-
}
|
|
7733
|
-
};
|
|
7734
|
-
const applyTimeout = () => {
|
|
7735
|
-
if (this._pollStatusTimer) {
|
|
7736
|
-
clearTimeout(this._pollStatusTimer);
|
|
7737
|
-
this._pollStatusTimer = null;
|
|
7738
|
-
}
|
|
7739
|
-
iconWrap.innerHTML = "\u23F1";
|
|
7740
|
-
iconWrap.style.background = this.theme.cardBg;
|
|
7741
|
-
iconWrap.style.border = `3px solid ${this.theme.warning}`;
|
|
7742
|
-
iconWrap.style.color = this.theme.warning;
|
|
7743
|
-
iconWrap.style.fontSize = "30px";
|
|
7744
|
-
statusBadge.style.color = this.theme.textMuted;
|
|
7745
|
-
statusBadge.textContent = "Still processing\u2026";
|
|
7746
|
-
title.textContent = "Verification In Progress";
|
|
7747
|
-
desc.textContent = "Your verification is being processed. Your result will be delivered via your dashboard or webhook. You can safely close this.";
|
|
7748
|
-
};
|
|
7749
|
-
const applyPassed = () => {
|
|
7750
|
-
clearUiTimeout();
|
|
7751
|
-
iconWrap.innerHTML = "\u2713";
|
|
7752
|
-
iconWrap.style.background = this.theme.successBg;
|
|
7753
|
-
iconWrap.style.border = `3px solid ${this.theme.success}`;
|
|
7754
|
-
iconWrap.style.color = this.theme.success;
|
|
7755
|
-
iconWrap.style.fontSize = "36px";
|
|
7756
|
-
statusBadge.style.color = this.theme.success;
|
|
7757
|
-
statusBadge.textContent = "Verified \u2713";
|
|
7758
|
-
title.textContent = "Identity Verified";
|
|
7759
|
-
desc.textContent = "Your identity check passed successfully.";
|
|
7760
|
-
};
|
|
7761
|
-
const applyFailed = (reason) => {
|
|
7762
|
-
clearUiTimeout();
|
|
7763
|
-
iconWrap.innerHTML = "\u2715";
|
|
7764
|
-
iconWrap.style.background = this.theme.errorBg;
|
|
7765
|
-
iconWrap.style.border = `3px solid ${this.theme.error}`;
|
|
7766
|
-
iconWrap.style.color = this.theme.error;
|
|
7767
|
-
iconWrap.style.fontSize = "32px";
|
|
7768
|
-
statusBadge.style.color = this.theme.error;
|
|
7769
|
-
statusBadge.textContent = "Failed";
|
|
7770
|
-
title.textContent = "Verification Failed";
|
|
7771
|
-
desc.textContent = "Your identity check could not be completed.";
|
|
7772
|
-
if (reason) {
|
|
7773
|
-
failureMsg.textContent = reason;
|
|
7774
|
-
failureMsg.style.display = "block";
|
|
7775
|
-
}
|
|
7776
|
-
};
|
|
7777
|
-
const poll = async () => {
|
|
7778
|
-
if (this.isDestroyed) return;
|
|
7779
|
-
polls++;
|
|
7780
|
-
try {
|
|
7781
|
-
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7782
|
-
if (result.status === "passed") {
|
|
7783
|
-
onResolved("passed");
|
|
7784
|
-
applyPassed();
|
|
7785
|
-
return;
|
|
7786
|
-
}
|
|
7787
|
-
if (result.status === "failed") {
|
|
7788
|
-
onResolved(result.status);
|
|
7789
|
-
const reason = result.failure_reason || "Verification could not be completed. Please try again.";
|
|
7790
|
-
applyFailed(reason);
|
|
7791
|
-
return;
|
|
7792
|
-
}
|
|
7793
|
-
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7794
|
-
this._pollStatusTimer = setTimeout(poll, 3e3);
|
|
7795
|
-
} else if (!this.isDestroyed) {
|
|
7796
|
-
applyTimeout();
|
|
7797
|
-
}
|
|
7798
|
-
} catch {
|
|
7799
|
-
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7800
|
-
this._pollStatusTimer = setTimeout(poll, 5e3);
|
|
7801
|
-
} else if (!this.isDestroyed) {
|
|
7802
|
-
applyTimeout();
|
|
7803
|
-
}
|
|
7804
|
-
}
|
|
7805
|
-
};
|
|
7806
|
-
this._pollStatusTimer = setTimeout(poll, 4e3);
|
|
7807
|
-
}
|
|
7808
8148
|
renderResult(_container) {
|
|
7809
8149
|
}
|
|
7810
|
-
//
|
|
8150
|
+
// ── Error ───────────────────────────────────────────────────────
|
|
7811
8151
|
showErrorStep(message) {
|
|
7812
8152
|
this.currentStep = "error";
|
|
7813
8153
|
if (!this.contentArea) return;
|
|
7814
8154
|
this.contentArea.innerHTML = "";
|
|
7815
8155
|
const wrapper = document.createElement("div");
|
|
8156
|
+
wrapper.setAttribute("role", "alert");
|
|
7816
8157
|
wrapper.style.cssText = `
|
|
7817
8158
|
display:flex;flex-direction:column;align-items:center;
|
|
7818
8159
|
text-align:center;gap:16px;padding:20px 0;
|
|
7819
8160
|
`;
|
|
7820
8161
|
const isCameraErr = /camera|permission|notallowed|notfound/i.test(message);
|
|
7821
8162
|
const isNetworkErr = /network|timeout|connection|fetch/i.test(message);
|
|
7822
|
-
const isAuthErr = /session|token
|
|
7823
|
-
const
|
|
7824
|
-
const
|
|
8163
|
+
const isAuthErr = /session.*expired|token.*expired|authentication/i.test(message);
|
|
8164
|
+
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
8165
|
+
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
8166
|
+
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
8167
|
+
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";
|
|
8168
|
+
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";
|
|
8169
|
+
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.";
|
|
7825
8170
|
const iconWrap = document.createElement("div");
|
|
7826
8171
|
iconWrap.style.cssText = `
|
|
7827
8172
|
width:72px;height:72px;border-radius:50%;
|
|
7828
8173
|
display:flex;align-items:center;justify-content:center;
|
|
7829
|
-
font-size
|
|
8174
|
+
font-size:28px;
|
|
7830
8175
|
background:${this.theme.errorBg};
|
|
7831
8176
|
border:3px solid ${this.theme.error};
|
|
7832
8177
|
`;
|
|
@@ -7838,18 +8183,16 @@ var SmartComplySDK = (() => {
|
|
|
7838
8183
|
wrapper.appendChild(title);
|
|
7839
8184
|
const desc = document.createElement("p");
|
|
7840
8185
|
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7841
|
-
desc.textContent =
|
|
8186
|
+
desc.textContent = userMessage;
|
|
7842
8187
|
wrapper.appendChild(desc);
|
|
7843
8188
|
const retryBtn = this.createPrimaryButton("Try Again");
|
|
8189
|
+
retryBtn.setAttribute("aria-label", "Try verification again");
|
|
7844
8190
|
retryBtn.addEventListener("click", () => {
|
|
7845
|
-
|
|
7846
|
-
this.showStep("welcome");
|
|
7847
|
-
} else {
|
|
7848
|
-
this.initialize();
|
|
7849
|
-
}
|
|
8191
|
+
this.initialize();
|
|
7850
8192
|
});
|
|
7851
8193
|
wrapper.appendChild(retryBtn);
|
|
7852
8194
|
const closeBtn = this.createSecondaryButton("Close");
|
|
8195
|
+
closeBtn.setAttribute("aria-label", "Close verification widget");
|
|
7853
8196
|
closeBtn.addEventListener("click", () => {
|
|
7854
8197
|
this.options.onError?.(new Error(message));
|
|
7855
8198
|
this.close();
|
|
@@ -7857,11 +8200,12 @@ var SmartComplySDK = (() => {
|
|
|
7857
8200
|
wrapper.appendChild(closeBtn);
|
|
7858
8201
|
this.contentArea.appendChild(wrapper);
|
|
7859
8202
|
}
|
|
7860
|
-
//
|
|
8203
|
+
// ─────────────────────────────────────────────────────────────────
|
|
7861
8204
|
// UI Helpers
|
|
7862
|
-
//
|
|
8205
|
+
// ─────────────────────────────────────────────────────────────────
|
|
7863
8206
|
createHeader() {
|
|
7864
8207
|
const header = document.createElement("div");
|
|
8208
|
+
header.setAttribute("role", "banner");
|
|
7865
8209
|
header.style.cssText = `
|
|
7866
8210
|
display:flex;align-items:center;justify-content:space-between;
|
|
7867
8211
|
padding:11px 20px;border-bottom:1px solid ${this.theme.border};
|
|
@@ -7870,6 +8214,7 @@ var SmartComplySDK = (() => {
|
|
|
7870
8214
|
leftArea.style.cssText = "display:flex;align-items:center;gap:8px;min-width:0;";
|
|
7871
8215
|
this.headerBackBtn = document.createElement("button");
|
|
7872
8216
|
this.headerBackBtn.innerHTML = "←";
|
|
8217
|
+
this.headerBackBtn.setAttribute("aria-label", "Go back to previous step");
|
|
7873
8218
|
this.headerBackBtn.style.cssText = `
|
|
7874
8219
|
width:30px;height:30px;border-radius:8px;flex-shrink:0;
|
|
7875
8220
|
display:none;align-items:center;justify-content:center;
|
|
@@ -7878,6 +8223,7 @@ var SmartComplySDK = (() => {
|
|
|
7878
8223
|
background:${this.theme.cardBg};color:${this.theme.text};
|
|
7879
8224
|
transition:all 0.2s ease;
|
|
7880
8225
|
`;
|
|
8226
|
+
this.headerBackBtn.style.cssText += `:focus-visible{outline:2px solid ${this.theme.primary};outline-offset:2px;}`;
|
|
7881
8227
|
this.headerBackBtn.addEventListener("mouseenter", () => {
|
|
7882
8228
|
if (this.headerBackBtn) {
|
|
7883
8229
|
this.headerBackBtn.style.background = this.theme.shimmer;
|
|
@@ -7890,6 +8236,7 @@ var SmartComplySDK = (() => {
|
|
|
7890
8236
|
});
|
|
7891
8237
|
leftArea.appendChild(this.headerBackBtn);
|
|
7892
8238
|
this.brandLabel = document.createElement("div");
|
|
8239
|
+
this.brandLabel.setAttribute("aria-label", "Verification provider");
|
|
7893
8240
|
this.brandLabel.style.cssText = `
|
|
7894
8241
|
font-size:15px;font-weight:700;color:${this.theme.text};
|
|
7895
8242
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
@@ -7898,13 +8245,16 @@ var SmartComplySDK = (() => {
|
|
|
7898
8245
|
leftArea.appendChild(this.brandLabel);
|
|
7899
8246
|
header.appendChild(leftArea);
|
|
7900
8247
|
this.stepLabel = document.createElement("div");
|
|
8248
|
+
this.stepLabel.setAttribute("aria-live", "polite");
|
|
8249
|
+
this.stepLabel.setAttribute("aria-atomic", "true");
|
|
7901
8250
|
this.stepLabel.style.cssText = `
|
|
7902
8251
|
font-size:12px;color:${this.theme.textMuted};
|
|
7903
8252
|
font-variant-numeric:tabular-nums;flex-shrink:0;
|
|
7904
8253
|
`;
|
|
7905
8254
|
header.appendChild(this.stepLabel);
|
|
7906
8255
|
const closeBtn = document.createElement("button");
|
|
7907
|
-
closeBtn.innerHTML = "\
|
|
8256
|
+
closeBtn.innerHTML = "\xE2\u0153\u2022";
|
|
8257
|
+
closeBtn.setAttribute("aria-label", "Close identity verification");
|
|
7908
8258
|
closeBtn.style.cssText = `
|
|
7909
8259
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
7910
8260
|
display:flex;align-items:center;justify-content:center;
|
|
@@ -7951,6 +8301,7 @@ var SmartComplySDK = (() => {
|
|
|
7951
8301
|
}
|
|
7952
8302
|
createOptionCard(title, subtitle, onClick) {
|
|
7953
8303
|
const card = document.createElement("button");
|
|
8304
|
+
card.setAttribute("aria-label", subtitle ? `${title}: ${subtitle}` : title);
|
|
7954
8305
|
card.style.cssText = `
|
|
7955
8306
|
width:100%;padding:13px 16px;border-radius:12px;
|
|
7956
8307
|
background:${this.theme.cardBg};
|
|
@@ -7983,7 +8334,7 @@ var SmartComplySDK = (() => {
|
|
|
7983
8334
|
}
|
|
7984
8335
|
const arrow = document.createElement("span");
|
|
7985
8336
|
arrow.style.cssText = `color:${this.theme.textMuted};font-size:18px;flex-shrink:0;`;
|
|
7986
|
-
arrow.textContent = "\
|
|
8337
|
+
arrow.textContent = "\xE2\u2020\u2019";
|
|
7987
8338
|
card.appendChild(textWrap);
|
|
7988
8339
|
card.appendChild(arrow);
|
|
7989
8340
|
return card;
|
|
@@ -8042,33 +8393,41 @@ var SmartComplySDK = (() => {
|
|
|
8042
8393
|
const isDocFlow = this.isDocumentFlow();
|
|
8043
8394
|
const countries = Object.keys(this.sdkConfig?.channels || {});
|
|
8044
8395
|
const singleCountry = countries.length <= 1;
|
|
8045
|
-
const
|
|
8046
|
-
const
|
|
8047
|
-
const
|
|
8048
|
-
const
|
|
8396
|
+
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));
|
|
8397
|
+
const stage2Steps = ["liveness"];
|
|
8398
|
+
const allActionSteps = [...stage1Steps, ...stage2Steps];
|
|
8399
|
+
const totalSteps = allActionSteps.length;
|
|
8400
|
+
const actionIndex = allActionSteps.indexOf(this.currentStep);
|
|
8401
|
+
const pct = this.currentStep === "done" || this.currentStep === "result" ? 100 : actionIndex >= 0 ? (actionIndex + 1) / totalSteps * 100 : 0;
|
|
8049
8402
|
if (this.progressBar) {
|
|
8050
8403
|
this.progressBar.style.width = `${pct}%`;
|
|
8051
8404
|
}
|
|
8052
8405
|
if (this.stepLabel) {
|
|
8053
8406
|
if (this.currentStep === "loading" || this.currentStep === "welcome") {
|
|
8054
8407
|
this.stepLabel.textContent = "";
|
|
8055
|
-
} else if (this.currentStep === "result") {
|
|
8056
|
-
this.stepLabel.textContent = "Complete \
|
|
8057
|
-
} else if (
|
|
8058
|
-
this.stepLabel.textContent =
|
|
8408
|
+
} else if (this.currentStep === "done" || this.currentStep === "result") {
|
|
8409
|
+
this.stepLabel.textContent = "Complete \xE2\u0153\u201C";
|
|
8410
|
+
} else if (stage2Steps.includes(this.currentStep)) {
|
|
8411
|
+
this.stepLabel.textContent = "Stage 2 of 2: Face Check";
|
|
8412
|
+
} else if (stage1Steps.includes(this.currentStep)) {
|
|
8413
|
+
this.stepLabel.textContent = isDocFlow ? "Stage 1 of 2: Scan Document" : "Stage 1 of 2: Verify Identity";
|
|
8059
8414
|
} else {
|
|
8060
8415
|
this.stepLabel.textContent = "";
|
|
8061
8416
|
}
|
|
8062
8417
|
}
|
|
8063
8418
|
if (this.headerBackBtn) {
|
|
8064
|
-
const countries2 = Object.keys(this.sdkConfig?.channels || {});
|
|
8065
8419
|
const backTargets = {
|
|
8066
|
-
id_type: () => this.showStep(
|
|
8420
|
+
id_type: () => this.showStep(countries.length > 1 ? "country" : "welcome"),
|
|
8067
8421
|
country: () => this.showStep("welcome"),
|
|
8068
8422
|
id_input: () => this.showStep("id_type"),
|
|
8423
|
+
confirm_identity: () => this.showStep("id_input"),
|
|
8069
8424
|
document_capture: () => {
|
|
8070
8425
|
this.docCapture?.destroy();
|
|
8071
8426
|
this.showStep("id_type");
|
|
8427
|
+
},
|
|
8428
|
+
document_confirm: () => {
|
|
8429
|
+
this.docCapture?.destroy();
|
|
8430
|
+
this.showStep("document_capture");
|
|
8072
8431
|
}
|
|
8073
8432
|
};
|
|
8074
8433
|
const backAction = backTargets[this.currentStep];
|
|
@@ -8115,6 +8474,12 @@ var SmartComplySDK = (() => {
|
|
|
8115
8474
|
#sc-flow-modal * {
|
|
8116
8475
|
box-sizing: border-box;
|
|
8117
8476
|
}
|
|
8477
|
+
#sc-flow-modal button:focus-visible,
|
|
8478
|
+
#sc-flow-modal select:focus-visible,
|
|
8479
|
+
#sc-flow-modal input:focus-visible {
|
|
8480
|
+
outline: 2px solid #3b82f6;
|
|
8481
|
+
outline-offset: 2px;
|
|
8482
|
+
}
|
|
8118
8483
|
#sc-flow-modal::-webkit-scrollbar {
|
|
8119
8484
|
width: 4px;
|
|
8120
8485
|
}
|
|
@@ -8129,6 +8494,8 @@ var SmartComplySDK = (() => {
|
|
|
8129
8494
|
document.head.appendChild(style2);
|
|
8130
8495
|
}
|
|
8131
8496
|
};
|
|
8497
|
+
_SmartComplyFlow.MAX_RETRIES = 3;
|
|
8498
|
+
var SmartComplyFlow = _SmartComplyFlow;
|
|
8132
8499
|
|
|
8133
8500
|
// src/utils/logger.ts
|
|
8134
8501
|
var PREFIX = "[SmartComply SDK]";
|