smartcomply-web-sdk 1.0.49 → 1.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/camera/ActionDetector.d.ts +15 -21
- package/dist/camera/ActionDetector.d.ts.map +1 -1
- package/dist/camera/ActionDetector.js +51 -104
- package/dist/camera/ActionDetector.js.map +1 -1
- package/dist/esm/camera/ActionDetector.d.ts +15 -21
- package/dist/esm/camera/ActionDetector.d.ts.map +1 -1
- package/dist/esm/camera/ActionDetector.js +51 -104
- package/dist/esm/camera/ActionDetector.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +3 -4
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.d.ts +19 -15
- package/dist/esm/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.js +99 -311
- package/dist/esm/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts +10 -9
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +57 -104
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/esm/utils/constants.d.ts +12 -6
- package/dist/esm/utils/constants.d.ts.map +1 -1
- package/dist/esm/utils/constants.js +13 -7
- package/dist/esm/utils/constants.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +3 -4
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/modules/liveness/LivenessUI.d.ts +19 -15
- package/dist/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/modules/liveness/LivenessUI.js +99 -311
- package/dist/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts +10 -9
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +56 -103
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +213 -513
- package/dist/smartcomply.browser.js.map +3 -3
- package/dist/utils/constants.d.ts +12 -6
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +13 -7
- package/dist/utils/constants.js.map +1 -1
- package/package.json +2 -2
|
@@ -296,7 +296,8 @@ var SmartComplySDK = (() => {
|
|
|
296
296
|
var MAX_DOCUMENT_BYTES = 5 * 1024 * 1024;
|
|
297
297
|
var MAX_VIDEO_BYTES = 50 * 1024 * 1024;
|
|
298
298
|
var MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024;
|
|
299
|
-
var DEFAULT_CHALLENGE_ACTIONS = ["BLINK"
|
|
299
|
+
var DEFAULT_CHALLENGE_ACTIONS = ["BLINK"];
|
|
300
|
+
var LIVENESS_TIME_LIMIT_SECONDS = 12;
|
|
300
301
|
|
|
301
302
|
// src/camera/CameraManager.ts
|
|
302
303
|
var CameraManager = class {
|
|
@@ -3635,7 +3636,7 @@ var SmartComplySDK = (() => {
|
|
|
3635
3636
|
Pn(s2, 1, (i2.left + i2.right) / 2), Pn(s2, 2, (i2.top + i2.bottom) / 2), Pn(s2, 4, i2.right - i2.left), Pn(s2, 3, i2.bottom - i2.top);
|
|
3636
3637
|
} else Pn(s2, 1, 0.5), Pn(s2, 2, 0.5), Pn(s2, 4, 1), Pn(s2, 3, 1);
|
|
3637
3638
|
if (n2?.rotationDegrees) {
|
|
3638
|
-
if (n2?.rotationDegrees % 90 != 0) throw Error("Expected rotation to be a multiple of 90
|
|
3639
|
+
if (n2?.rotationDegrees % 90 != 0) throw Error("Expected rotation to be a multiple of 90°.");
|
|
3639
3640
|
if (Pn(s2, 5, -Math.PI * n2.rotationDegrees / 180), n2?.rotationDegrees % 180 != 0) {
|
|
3640
3641
|
const [t3, r3] = sa(e2);
|
|
3641
3642
|
n2 = Fn(s2, 3) * r3 / t3, i2 = Fn(s2, 4) * t3 / r3, Pn(s2, 4, n2), Pn(s2, 3, i2);
|
|
@@ -4455,158 +4456,83 @@ var SmartComplySDK = (() => {
|
|
|
4455
4456
|
};
|
|
4456
4457
|
|
|
4457
4458
|
// src/camera/ActionDetector.ts
|
|
4458
|
-
var
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
TURN_RIGHT: { threshold: 0.3, holdFrames: 3 },
|
|
4462
|
-
TURN_HEAD: { threshold: 0.9, holdFrames: 5 },
|
|
4463
|
-
OPEN_MOUTH: { threshold: 0.3, holdFrames: 2 }
|
|
4464
|
-
};
|
|
4465
|
-
var DEFAULT_CONFIG = { threshold: 0.4, holdFrames: 4 };
|
|
4459
|
+
var BLINK_THRESHOLD = 0.25;
|
|
4460
|
+
var BLINK_HOLD_FRAMES = 2;
|
|
4461
|
+
var YAW_MOTION_THRESHOLD_DEG = 6;
|
|
4466
4462
|
var ActionDetector = class {
|
|
4467
|
-
|
|
4468
|
-
constructor(
|
|
4469
|
-
this.
|
|
4470
|
-
this.
|
|
4471
|
-
this.
|
|
4472
|
-
this.
|
|
4473
|
-
this.YAW_HISTORY_SIZE = 18;
|
|
4463
|
+
/** Kept for API compatibility with callers that still pass a list in. Unused internally. */
|
|
4464
|
+
constructor(_requiredActions) {
|
|
4465
|
+
this.blinkHoldCount = 0;
|
|
4466
|
+
this.blinkDetected = false;
|
|
4467
|
+
this.motionDetected = false;
|
|
4468
|
+
this.baselineYaw = null;
|
|
4474
4469
|
}
|
|
4475
4470
|
check(result) {
|
|
4476
|
-
const currentAction = this.getCurrentAction();
|
|
4477
4471
|
if (!result.faceDetected) {
|
|
4478
|
-
return this.
|
|
4472
|
+
return this.buildProgress(false);
|
|
4479
4473
|
}
|
|
4480
4474
|
const bs2 = result.blendshapes;
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
if (count >= cfg.holdFrames) {
|
|
4488
|
-
this.completedActions.add(currentAction);
|
|
4489
|
-
this.holdCounters.delete(currentAction);
|
|
4475
|
+
const blinkConf = ((bs2.get("eyeBlinkLeft") || 0) + (bs2.get("eyeBlinkRight") || 0)) / 2;
|
|
4476
|
+
if (!this.blinkDetected) {
|
|
4477
|
+
if (blinkConf >= BLINK_THRESHOLD) {
|
|
4478
|
+
this.blinkHoldCount++;
|
|
4479
|
+
if (this.blinkHoldCount >= BLINK_HOLD_FRAMES) {
|
|
4480
|
+
this.blinkDetected = true;
|
|
4490
4481
|
}
|
|
4491
4482
|
} else {
|
|
4492
|
-
this.
|
|
4483
|
+
this.blinkHoldCount = 0;
|
|
4493
4484
|
}
|
|
4494
|
-
return this.buildStates(currentAction, conf);
|
|
4495
|
-
}
|
|
4496
|
-
return this.buildStates(currentAction, 0);
|
|
4497
|
-
}
|
|
4498
|
-
getCurrentAction() {
|
|
4499
|
-
for (const action of this.requiredActions) {
|
|
4500
|
-
if (!this.completedActions.has(action)) return action;
|
|
4501
4485
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
active: action === currentAction,
|
|
4509
|
-
confidence: action === currentAction ? currentConf : 0
|
|
4510
|
-
}));
|
|
4511
|
-
}
|
|
4512
|
-
getConfidence(action, bs2, matrix) {
|
|
4513
|
-
switch (action) {
|
|
4514
|
-
case "BLINK": {
|
|
4515
|
-
const left = bs2.get("eyeBlinkLeft") || 0;
|
|
4516
|
-
const right = bs2.get("eyeBlinkRight") || 0;
|
|
4517
|
-
return (left + right) / 2;
|
|
4518
|
-
}
|
|
4519
|
-
case "TURN_LEFT": {
|
|
4520
|
-
const yaw = Math.abs(this.getHeadYaw(matrix));
|
|
4521
|
-
return yaw > 5 ? Math.min(1, yaw / 20) : 0;
|
|
4522
|
-
}
|
|
4523
|
-
case "TURN_RIGHT": {
|
|
4524
|
-
const yaw = Math.abs(this.getHeadYaw(matrix));
|
|
4525
|
-
return yaw > 5 ? Math.min(1, yaw / 20) : 0;
|
|
4486
|
+
if (!this.motionDetected) {
|
|
4487
|
+
const yaw = this.getHeadYaw(result.transformMatrix);
|
|
4488
|
+
if (this.baselineYaw === null) {
|
|
4489
|
+
this.baselineYaw = yaw;
|
|
4490
|
+
} else if (Math.abs(yaw - this.baselineYaw) >= YAW_MOTION_THRESHOLD_DEG) {
|
|
4491
|
+
this.motionDetected = true;
|
|
4526
4492
|
}
|
|
4527
|
-
case "TURN_HEAD": {
|
|
4528
|
-
const yaw = this.getHeadYaw(matrix);
|
|
4529
|
-
this.yawHistory.push(yaw);
|
|
4530
|
-
if (this.yawHistory.length > this.YAW_HISTORY_SIZE) {
|
|
4531
|
-
this.yawHistory.shift();
|
|
4532
|
-
}
|
|
4533
|
-
if (this.yawHistory.length < this.YAW_HISTORY_SIZE) {
|
|
4534
|
-
return 0;
|
|
4535
|
-
}
|
|
4536
|
-
const left = this.yawHistory.some((y2) => y2 <= -10);
|
|
4537
|
-
const right = this.yawHistory.some((y2) => y2 >= 10);
|
|
4538
|
-
if (left && right) return 1;
|
|
4539
|
-
if (left || right) return 0.5;
|
|
4540
|
-
return 0;
|
|
4541
|
-
}
|
|
4542
|
-
case "OPEN_MOUTH":
|
|
4543
|
-
return bs2.get("jawOpen") || 0;
|
|
4544
|
-
default:
|
|
4545
|
-
return 0;
|
|
4546
4493
|
}
|
|
4494
|
+
return this.buildProgress(true);
|
|
4495
|
+
}
|
|
4496
|
+
buildProgress(faceDetected) {
|
|
4497
|
+
return {
|
|
4498
|
+
faceDetected,
|
|
4499
|
+
blinkDetected: this.blinkDetected,
|
|
4500
|
+
motionDetected: this.motionDetected,
|
|
4501
|
+
livenessConfirmed: this.blinkDetected || this.motionDetected,
|
|
4502
|
+
progress: 0
|
|
4503
|
+
// caller fills in time-based progress
|
|
4504
|
+
};
|
|
4547
4505
|
}
|
|
4548
4506
|
getHeadYaw(matrix) {
|
|
4549
4507
|
if (!matrix || matrix.length < 16) return 0;
|
|
4550
4508
|
return Math.atan2(-matrix[2], matrix[0]) * (180 / Math.PI);
|
|
4551
4509
|
}
|
|
4552
|
-
|
|
4553
|
-
return this.
|
|
4554
|
-
}
|
|
4555
|
-
completedCount() {
|
|
4556
|
-
return this.completedActions.size;
|
|
4510
|
+
livenessConfirmed() {
|
|
4511
|
+
return this.blinkDetected || this.motionDetected;
|
|
4557
4512
|
}
|
|
4558
4513
|
reset() {
|
|
4559
|
-
this.
|
|
4560
|
-
this.
|
|
4561
|
-
this.
|
|
4514
|
+
this.blinkHoldCount = 0;
|
|
4515
|
+
this.blinkDetected = false;
|
|
4516
|
+
this.motionDetected = false;
|
|
4517
|
+
this.baselineYaw = null;
|
|
4562
4518
|
}
|
|
4563
4519
|
};
|
|
4564
4520
|
|
|
4565
4521
|
// src/modules/liveness/LivenessUI.ts
|
|
4566
|
-
var ICONS = {
|
|
4567
|
-
BLINK: "\u{1F441}",
|
|
4568
|
-
TURN_LEFT: "\u2194",
|
|
4569
|
-
TURN_RIGHT: "\u2194",
|
|
4570
|
-
TURN_HEAD: "\u2194",
|
|
4571
|
-
OPEN_MOUTH: "\u{1F62E}"
|
|
4572
|
-
};
|
|
4573
|
-
var ACTION_CUES = {
|
|
4574
|
-
BLINK: `<div style="font-size:52px;line-height:1;animation:sc-blink 1.2s ease-in-out infinite;">\u{1F441}\uFE0F</div>
|
|
4575
|
-
<div style="font-size:12px;font-weight:600;color:#fff;margin-top:6px;letter-spacing:0.5px;">BLINK YOUR EYES</div>`,
|
|
4576
|
-
TURN_LEFT: `<div style="font-size:52px;line-height:1;animation:sc-turn-head 1.4s ease-in-out infinite;">\u2194\uFE0F</div>
|
|
4577
|
-
<div style="font-size:12px;font-weight:600;color:#fff;margin-top:6px;letter-spacing:0.5px;">TURN HEAD SIDEWAYS</div>`,
|
|
4578
|
-
TURN_RIGHT: `<div style="font-size:52px;line-height:1;animation:sc-turn-head 1.4s ease-in-out infinite;">\u2194\uFE0F</div>
|
|
4579
|
-
<div style="font-size:12px;font-weight:600;color:#fff;margin-top:6px;letter-spacing:0.5px;">TURN HEAD SIDEWAYS</div>`,
|
|
4580
|
-
TURN_HEAD: `<div style="font-size:52px;line-height:1;animation:sc-turn-head 1.4s ease-in-out infinite;">\u2194\uFE0F</div>
|
|
4581
|
-
<div style="font-size:12px;font-weight:600;color:#fff;margin-top:6px;letter-spacing:0.5px;">TURN HEAD SIDE TO SIDE</div>`,
|
|
4582
|
-
OPEN_MOUTH: `<div style="font-size:52px;line-height:1;animation:sc-mouth 1.2s ease-in-out infinite;">\u{1F62E}</div>
|
|
4583
|
-
<div style="font-size:12px;font-weight:600;color:#fff;margin-top:6px;letter-spacing:0.5px;">OPEN YOUR MOUTH</div>`
|
|
4584
|
-
};
|
|
4585
4522
|
function injectLivenessKeyframes() {
|
|
4586
4523
|
if (document.getElementById("sc-liveness-kf")) return;
|
|
4587
4524
|
const s2 = document.createElement("style");
|
|
4588
4525
|
s2.id = "sc-liveness-kf";
|
|
4589
4526
|
s2.textContent = `
|
|
4590
|
-
@keyframes sc-blink {
|
|
4591
|
-
0%,100% { transform:scaleY(1); }
|
|
4592
|
-
40% { transform:scaleY(0.1); }
|
|
4593
|
-
50% { transform:scaleY(1); }
|
|
4594
|
-
}
|
|
4595
|
-
@keyframes sc-turn-head {
|
|
4596
|
-
0%,100% { transform:translateX(0); }
|
|
4597
|
-
25% { transform:translateX(-12px); }
|
|
4598
|
-
75% { transform:translateX(12px); }
|
|
4599
|
-
}
|
|
4600
|
-
@keyframes sc-mouth {
|
|
4601
|
-
0%,100% { transform:scaleY(1); }
|
|
4602
|
-
40% { transform:scaleY(1.25); }
|
|
4603
|
-
60% { transform:scaleY(1); }
|
|
4604
|
-
}
|
|
4605
4527
|
@keyframes sc-pop-in {
|
|
4606
4528
|
0% { transform:scale(0.7); opacity:0; }
|
|
4607
4529
|
60% { transform:scale(1.05); }
|
|
4608
4530
|
100% { transform:scale(1); opacity:1; }
|
|
4609
4531
|
}
|
|
4532
|
+
@keyframes sc-scan-dot {
|
|
4533
|
+
0%,100% { opacity:0.3; transform:scale(0.85); }
|
|
4534
|
+
50% { opacity:1; transform:scale(1); }
|
|
4535
|
+
}
|
|
4610
4536
|
`;
|
|
4611
4537
|
document.head.appendChild(s2);
|
|
4612
4538
|
}
|
|
@@ -4615,39 +4541,22 @@ var SmartComplySDK = (() => {
|
|
|
4615
4541
|
this.root = null;
|
|
4616
4542
|
this.videoEl = null;
|
|
4617
4543
|
this.instructionEl = null;
|
|
4618
|
-
this.stepsEl = null;
|
|
4619
|
-
this.progressBarEl = null;
|
|
4620
4544
|
this.confidenceRingEl = null;
|
|
4621
|
-
this.timerEl = null;
|
|
4622
4545
|
this.overlayEl = null;
|
|
4623
|
-
this.
|
|
4624
|
-
this.timerInterval = null;
|
|
4625
|
-
this.totalActions = 0;
|
|
4626
|
-
this.currentCueAction = null;
|
|
4546
|
+
this.dotsEl = null;
|
|
4627
4547
|
this.docCaptureRoot = null;
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
// element (avoids a querySelector per action per frame) and its last
|
|
4631
|
-
// rendered detected/active combination (avoids rewriting the same style
|
|
4632
|
-
// properties every frame when nothing about that action changed).
|
|
4633
|
-
this.stepEls = null;
|
|
4634
|
-
this.lastActionRender = /* @__PURE__ */ new Map();
|
|
4548
|
+
this.scanStartMs = 0;
|
|
4549
|
+
this.scanDurationMs = 0;
|
|
4635
4550
|
}
|
|
4636
4551
|
mount(container, challenge) {
|
|
4637
4552
|
injectLivenessKeyframes();
|
|
4638
|
-
this.totalActions = challenge.actions.length;
|
|
4639
4553
|
this.root = document.createElement("div");
|
|
4640
4554
|
this.root.style.cssText = `
|
|
4641
|
-
|
|
4555
|
+
position:relative;width:100%;max-width:420px;margin:0 auto;
|
|
4642
4556
|
background:#0a0a0a;border-radius:16px;overflow:hidden;
|
|
4643
4557
|
font-family:system-ui,-apple-system,sans-serif;
|
|
4644
4558
|
box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
|
4645
|
-
|
|
4646
|
-
`;
|
|
4647
|
-
const camCol = document.createElement("div");
|
|
4648
|
-
camCol.style.cssText = `
|
|
4649
|
-
position:relative;flex:0 0 55%;
|
|
4650
|
-
background:#000;overflow:hidden;
|
|
4559
|
+
aspect-ratio:3/4;
|
|
4651
4560
|
`;
|
|
4652
4561
|
this.videoEl = document.createElement("video");
|
|
4653
4562
|
this.videoEl.autoplay = true;
|
|
@@ -4656,13 +4565,12 @@ var SmartComplySDK = (() => {
|
|
|
4656
4565
|
this.videoEl.style.cssText = `
|
|
4657
4566
|
width:100%;height:100%;display:block;
|
|
4658
4567
|
transform:scaleX(-1);object-fit:cover;
|
|
4659
|
-
min-height:320px;
|
|
4660
4568
|
`;
|
|
4661
4569
|
const guideWrap = document.createElement("div");
|
|
4662
4570
|
guideWrap.style.cssText = `
|
|
4663
4571
|
position:absolute;top:50%;left:50%;
|
|
4664
|
-
transform:translate(-50%,-
|
|
4665
|
-
width:
|
|
4572
|
+
transform:translate(-50%,-52%);
|
|
4573
|
+
width:64%;aspect-ratio:140/180;pointer-events:none;
|
|
4666
4574
|
`;
|
|
4667
4575
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
4668
4576
|
svg.setAttribute("viewBox", "0 0 140 180");
|
|
@@ -4673,9 +4581,8 @@ var SmartComplySDK = (() => {
|
|
|
4673
4581
|
bgOval.setAttribute("rx", "64");
|
|
4674
4582
|
bgOval.setAttribute("ry", "86");
|
|
4675
4583
|
bgOval.setAttribute("fill", "none");
|
|
4676
|
-
bgOval.setAttribute("stroke", "rgba(255,255,255,0.
|
|
4584
|
+
bgOval.setAttribute("stroke", "rgba(255,255,255,0.35)");
|
|
4677
4585
|
bgOval.setAttribute("stroke-width", "2");
|
|
4678
|
-
bgOval.setAttribute("stroke-dasharray", "6 3");
|
|
4679
4586
|
const confOval = document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
|
|
4680
4587
|
confOval.setAttribute("cx", "70");
|
|
4681
4588
|
confOval.setAttribute("cy", "90");
|
|
@@ -4687,7 +4594,7 @@ var SmartComplySDK = (() => {
|
|
|
4687
4594
|
const circ = 2 * Math.PI * Math.sqrt((64 * 64 + 86 * 86) / 2);
|
|
4688
4595
|
confOval.setAttribute("stroke-dasharray", `${circ}`);
|
|
4689
4596
|
confOval.setAttribute("stroke-dashoffset", `${circ}`);
|
|
4690
|
-
confOval.style.cssText = "transition:stroke-dashoffset 0.
|
|
4597
|
+
confOval.style.cssText = "transition:stroke-dashoffset 0.2s linear;transform:rotate(-90deg);transform-origin:70px 90px;";
|
|
4691
4598
|
this.confidenceRingEl = confOval;
|
|
4692
4599
|
svg.appendChild(bgOval);
|
|
4693
4600
|
svg.appendChild(confOval);
|
|
@@ -4696,11 +4603,11 @@ var SmartComplySDK = (() => {
|
|
|
4696
4603
|
camTop.style.cssText = `
|
|
4697
4604
|
position:absolute;top:0;left:0;right:0;
|
|
4698
4605
|
background:linear-gradient(180deg,rgba(0,0,0,0.8) 0%,transparent 100%);
|
|
4699
|
-
padding:
|
|
4606
|
+
padding:14px 16px 32px;
|
|
4700
4607
|
`;
|
|
4701
4608
|
this.instructionEl = document.createElement("div");
|
|
4702
4609
|
this.instructionEl.style.cssText = `
|
|
4703
|
-
color:#fff;font-size:
|
|
4610
|
+
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
4704
4611
|
`;
|
|
4705
4612
|
this.instructionEl.textContent = challenge.instruction;
|
|
4706
4613
|
camTop.appendChild(this.instructionEl);
|
|
@@ -4708,177 +4615,62 @@ var SmartComplySDK = (() => {
|
|
|
4708
4615
|
camBottom.style.cssText = `
|
|
4709
4616
|
position:absolute;bottom:0;left:0;right:0;
|
|
4710
4617
|
background:linear-gradient(0deg,rgba(0,0,0,0.7) 0%,transparent 100%);
|
|
4711
|
-
padding:
|
|
4712
|
-
`;
|
|
4713
|
-
this.timerEl = document.createElement("div");
|
|
4714
|
-
this.timerEl.style.cssText = `
|
|
4715
|
-
color:rgba(255,255,255,0.5);font-size:11px;font-variant-numeric:tabular-nums;
|
|
4716
|
-
`;
|
|
4717
|
-
camBottom.appendChild(this.timerEl);
|
|
4718
|
-
this.actionCueEl = document.createElement("div");
|
|
4719
|
-
this.actionCueEl.style.cssText = `
|
|
4720
|
-
position:absolute;bottom:36px;left:50%;transform:translateX(-50%);
|
|
4721
|
-
z-index:5;pointer-events:none;display:none;text-align:center;
|
|
4722
|
-
background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);
|
|
4723
|
-
border-radius:14px;padding:10px 18px;min-width:170px;
|
|
4724
|
-
border:1.5px solid rgba(255,255,255,0.12);
|
|
4618
|
+
padding:28px 10px 18px;display:flex;justify-content:center;
|
|
4725
4619
|
`;
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
flex:1;display:flex;flex-direction:column;
|
|
4734
|
-
padding:16px 14px;gap:8px;
|
|
4735
|
-
background:#111;border-left:1px solid rgba(255,255,255,0.06);
|
|
4736
|
-
justify-content:space-between;
|
|
4737
|
-
`;
|
|
4738
|
-
const progressWrap = document.createElement("div");
|
|
4739
|
-
progressWrap.style.cssText = `
|
|
4740
|
-
height:3px;background:rgba(255,255,255,0.1);border-radius:2px;overflow:hidden;
|
|
4741
|
-
margin-bottom:4px;
|
|
4742
|
-
`;
|
|
4743
|
-
this.progressBarEl = document.createElement("div");
|
|
4744
|
-
this.progressBarEl.style.cssText = `
|
|
4745
|
-
height:100%;width:0%;background:linear-gradient(90deg,#22c55e,#4ade80);
|
|
4746
|
-
border-radius:2px;transition:width 0.4s ease;
|
|
4747
|
-
`;
|
|
4748
|
-
progressWrap.appendChild(this.progressBarEl);
|
|
4749
|
-
this.stepsEl = document.createElement("div");
|
|
4750
|
-
this.stepsEl.style.cssText = "display:flex;flex-direction:column;gap:6px;flex:1;";
|
|
4751
|
-
this.stepEls = /* @__PURE__ */ new Map();
|
|
4752
|
-
this.lastActionRender = /* @__PURE__ */ new Map();
|
|
4753
|
-
for (let i2 = 0; i2 < challenge.actions.length; i2++) {
|
|
4754
|
-
const action = challenge.actions[i2];
|
|
4755
|
-
const step = document.createElement("div");
|
|
4756
|
-
step.dataset.action = action;
|
|
4757
|
-
step.style.cssText = `
|
|
4758
|
-
display:flex;align-items:center;gap:8px;
|
|
4759
|
-
padding:8px 10px;border-radius:10px;
|
|
4760
|
-
background:rgba(255,255,255,0.04);
|
|
4761
|
-
border:1px solid rgba(255,255,255,0.06);
|
|
4762
|
-
transition:all 0.3s ease;
|
|
4763
|
-
opacity:${i2 === 0 ? "1" : "0.45"};
|
|
4764
|
-
`;
|
|
4765
|
-
const num = document.createElement("div");
|
|
4766
|
-
num.className = "sc-step-num";
|
|
4767
|
-
num.style.cssText = `
|
|
4768
|
-
width:24px;height:24px;border-radius:50%;flex-shrink:0;
|
|
4769
|
-
display:flex;align-items:center;justify-content:center;
|
|
4770
|
-
font-size:12px;font-weight:700;color:#fff;
|
|
4771
|
-
background:rgba(255,255,255,0.1);
|
|
4772
|
-
border:1.5px solid rgba(255,255,255,0.2);
|
|
4773
|
-
transition:all 0.3s ease;
|
|
4620
|
+
this.dotsEl = document.createElement("div");
|
|
4621
|
+
this.dotsEl.style.cssText = "display:flex;gap:6px;";
|
|
4622
|
+
for (let i2 = 0; i2 < 3; i2++) {
|
|
4623
|
+
const dot = document.createElement("div");
|
|
4624
|
+
dot.style.cssText = `
|
|
4625
|
+
width:6px;height:6px;border-radius:50%;background:#22c55e;
|
|
4626
|
+
animation:sc-scan-dot 1.1s ease-in-out ${i2 * 0.18}s infinite;
|
|
4774
4627
|
`;
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
step.appendChild(num);
|
|
4783
|
-
step.appendChild(label);
|
|
4784
|
-
step.appendChild(icon);
|
|
4785
|
-
this.stepsEl.appendChild(step);
|
|
4786
|
-
this.stepEls.set(action, { step, num });
|
|
4787
|
-
}
|
|
4788
|
-
stepsCol.appendChild(progressWrap);
|
|
4789
|
-
stepsCol.appendChild(this.stepsEl);
|
|
4628
|
+
this.dotsEl.appendChild(dot);
|
|
4629
|
+
}
|
|
4630
|
+
camBottom.appendChild(this.dotsEl);
|
|
4631
|
+
this.root.appendChild(this.videoEl);
|
|
4632
|
+
this.root.appendChild(guideWrap);
|
|
4633
|
+
this.root.appendChild(camTop);
|
|
4634
|
+
this.root.appendChild(camBottom);
|
|
4790
4635
|
this.overlayEl = document.createElement("div");
|
|
4791
4636
|
this.overlayEl.style.cssText = `
|
|
4792
4637
|
position:absolute;inset:0;display:none;align-items:center;
|
|
4793
4638
|
justify-content:center;z-index:10;
|
|
4794
4639
|
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
|
4795
4640
|
`;
|
|
4796
|
-
this.root.appendChild(camCol);
|
|
4797
|
-
this.root.appendChild(stepsCol);
|
|
4798
4641
|
this.root.appendChild(this.overlayEl);
|
|
4799
4642
|
container.appendChild(this.root);
|
|
4800
|
-
this.
|
|
4643
|
+
this.scanDurationMs = challenge.time_limit_seconds * 1e3;
|
|
4644
|
+
this.startScanClock();
|
|
4801
4645
|
return this.videoEl;
|
|
4802
4646
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
num.style.borderColor = "#22c55e";
|
|
4821
|
-
num.textContent = "\u2713";
|
|
4822
|
-
}
|
|
4823
|
-
} else if (state.active) {
|
|
4824
|
-
step.style.opacity = "1";
|
|
4825
|
-
step.style.background = "rgba(59,130,246,0.12)";
|
|
4826
|
-
step.style.borderColor = "rgba(59,130,246,0.3)";
|
|
4827
|
-
if (num) {
|
|
4828
|
-
num.style.borderColor = "#3b82f6";
|
|
4829
|
-
num.style.background = "rgba(59,130,246,0.3)";
|
|
4830
|
-
}
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
const activeState = states.find((s2) => s2.active && !s2.detected) ?? null;
|
|
4834
|
-
const pct = this.totalActions > 0 ? completed / this.totalActions * 100 : 0;
|
|
4835
|
-
this.progressBarEl.style.width = `${pct}%`;
|
|
4836
|
-
if (this.confidenceRingEl) {
|
|
4837
|
-
const circ = 2 * Math.PI * Math.sqrt((64 * 64 + 86 * 86) / 2);
|
|
4838
|
-
const conf = activeState ? activeState.confidence : 0;
|
|
4839
|
-
this.confidenceRingEl.setAttribute("stroke-dashoffset", `${circ * (1 - conf)}`);
|
|
4840
|
-
this.confidenceRingEl.setAttribute("stroke", conf > 0.6 ? "#22c55e" : "#3b82f6");
|
|
4841
|
-
}
|
|
4842
|
-
this.updateActionCue(activeState?.action ?? null);
|
|
4843
|
-
}
|
|
4844
|
-
updateActionCue(action) {
|
|
4845
|
-
if (!this.actionCueEl) return;
|
|
4846
|
-
if (!action) {
|
|
4847
|
-
this.actionCueEl.style.display = "none";
|
|
4848
|
-
this.currentCueAction = null;
|
|
4849
|
-
return;
|
|
4850
|
-
}
|
|
4851
|
-
if (action === this.currentCueAction) return;
|
|
4852
|
-
this.currentCueAction = action;
|
|
4853
|
-
const cueHtml = ACTION_CUES[action];
|
|
4854
|
-
if (!cueHtml) {
|
|
4855
|
-
this.actionCueEl.style.display = "none";
|
|
4856
|
-
return;
|
|
4857
|
-
}
|
|
4858
|
-
this.actionCueEl.innerHTML = cueHtml;
|
|
4859
|
-
this.actionCueEl.style.display = "block";
|
|
4860
|
-
this.actionCueEl.style.animation = "none";
|
|
4861
|
-
void this.actionCueEl.offsetWidth;
|
|
4862
|
-
this.actionCueEl.style.animation = "sc-pop-in 0.3s ease forwards";
|
|
4647
|
+
/**
|
|
4648
|
+
* Resets the progress ring's clock to now. The caller (liveness.ts) mounts
|
|
4649
|
+
* the UI, then waits ~1s for camera AE/AF to settle before the detection
|
|
4650
|
+
* loop actually starts — call this again right when detection begins so
|
|
4651
|
+
* the ring's timing matches the real scan window instead of running ahead.
|
|
4652
|
+
*/
|
|
4653
|
+
startScanClock() {
|
|
4654
|
+
this.scanStartMs = performance.now();
|
|
4655
|
+
}
|
|
4656
|
+
/** Advances the scan progress ring based on elapsed time in the window. */
|
|
4657
|
+
updateScanProgress(livenessConfirmed) {
|
|
4658
|
+
if (!this.confidenceRingEl) return;
|
|
4659
|
+
const elapsed = performance.now() - this.scanStartMs;
|
|
4660
|
+
const pct = this.scanDurationMs > 0 ? Math.min(1, elapsed / this.scanDurationMs) : 0;
|
|
4661
|
+
const circ = 2 * Math.PI * Math.sqrt((64 * 64 + 86 * 86) / 2);
|
|
4662
|
+
this.confidenceRingEl.setAttribute("stroke-dashoffset", `${circ * (1 - pct)}`);
|
|
4663
|
+
this.confidenceRingEl.setAttribute("stroke", livenessConfirmed ? "#22c55e" : "#3b82f6");
|
|
4863
4664
|
}
|
|
4864
4665
|
updateInstruction(text) {
|
|
4865
4666
|
if (this.instructionEl) this.instructionEl.textContent = text;
|
|
4866
4667
|
}
|
|
4867
|
-
stopTimer() {
|
|
4868
|
-
if (this.timerInterval) {
|
|
4869
|
-
clearInterval(this.timerInterval);
|
|
4870
|
-
this.timerInterval = null;
|
|
4871
|
-
}
|
|
4872
|
-
if (this.timerEl) this.timerEl.textContent = "";
|
|
4873
|
-
}
|
|
4874
4668
|
showResult(status, _verificationId) {
|
|
4875
4669
|
return new Promise((resolve) => {
|
|
4876
4670
|
if (!this.overlayEl) {
|
|
4877
4671
|
resolve();
|
|
4878
4672
|
return;
|
|
4879
4673
|
}
|
|
4880
|
-
if (this.actionCueEl) this.actionCueEl.style.display = "none";
|
|
4881
|
-
this.stopTimer();
|
|
4882
4674
|
const ok = status === "processing";
|
|
4883
4675
|
this.overlayEl.style.display = "flex";
|
|
4884
4676
|
this.overlayEl.innerHTML = `
|
|
@@ -4886,10 +4678,10 @@ var SmartComplySDK = (() => {
|
|
|
4886
4678
|
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 16px;display:flex;align-items:center;justify-content:center;font-size:32px;
|
|
4887
4679
|
background:${ok ? "rgba(34,197,94,0.2)" : "rgba(239,68,68,0.2)"};
|
|
4888
4680
|
border:3px solid ${ok ? "#22c55e" : "#ef4444"};">
|
|
4889
|
-
${ok ? "
|
|
4681
|
+
${ok ? "✓" : "✕"}
|
|
4890
4682
|
</div>
|
|
4891
4683
|
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
4892
|
-
<div style="font-size:12px;opacity:0.6;line-height:1.6;">${ok ? "Hold on while we wrap up
|
|
4684
|
+
<div style="font-size:12px;opacity:0.6;line-height:1.6;">${ok ? "Hold on while we wrap up…" : "Something went wrong. Please try again."}</div>
|
|
4893
4685
|
</div>`;
|
|
4894
4686
|
setTimeout(resolve, 1800);
|
|
4895
4687
|
});
|
|
@@ -4900,14 +4692,12 @@ var SmartComplySDK = (() => {
|
|
|
4900
4692
|
resolve();
|
|
4901
4693
|
return;
|
|
4902
4694
|
}
|
|
4903
|
-
if (this.actionCueEl) this.actionCueEl.style.display = "none";
|
|
4904
|
-
this.stopTimer();
|
|
4905
4695
|
this.overlayEl.style.display = "flex";
|
|
4906
4696
|
this.overlayEl.innerHTML = `
|
|
4907
4697
|
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
4908
|
-
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 14px;display:flex;align-items:center;justify-content:center;font-size:32px;background:rgba(251,191,36,0.15);border:3px solid #fbbf24;"
|
|
4698
|
+
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 14px;display:flex;align-items:center;justify-content:center;font-size:32px;background:rgba(251,191,36,0.15);border:3px solid #fbbf24;">⏱</div>
|
|
4909
4699
|
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">Time's Up</div>
|
|
4910
|
-
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">
|
|
4700
|
+
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">We couldn't confirm liveness in time. Please try again.</div>
|
|
4911
4701
|
<button id="sc-retry-btn" style="padding:11px 28px;border-radius:10px;background:#3b82f6;color:#fff;border:none;cursor:pointer;font-size:14px;font-weight:600;width:100%;max-width:180px;">Try Again</button>
|
|
4912
4702
|
</div>`;
|
|
4913
4703
|
const btn = this.overlayEl.querySelector("#sc-retry-btn");
|
|
@@ -4944,7 +4734,7 @@ var SmartComplySDK = (() => {
|
|
|
4944
4734
|
header.innerHTML = `<div style="color:#fff;font-size:16px;font-weight:700;margin-bottom:6px;">Scan Your ID Document</div><div style="color:rgba(255,255,255,0.6);font-size:12px;line-height:1.6;">Fit your document inside the frame.<br>Make sure all text and corners are visible.</div>`;
|
|
4945
4735
|
const bottomPanel = document.createElement("div");
|
|
4946
4736
|
bottomPanel.style.cssText = "position:absolute;bottom:0;left:0;right:0;background:linear-gradient(0deg,rgba(0,0,0,0.92) 0%,rgba(0,0,0,0.6) 70%,transparent 100%);padding:16px;";
|
|
4947
|
-
bottomPanel.innerHTML = `<div style="font-size:11px;color:rgba(255,255,255,0.4);text-align:center;margin-bottom:12px;"
|
|
4737
|
+
bottomPanel.innerHTML = `<div style="font-size:11px;color:rgba(255,255,255,0.4);text-align:center;margin-bottom:12px;">💡 Good lighting · No glare · All 4 corners visible</div><button id="sc-capture-doc-btn" style="width:100%;padding:12px;border-radius:12px;background:#3b82f6;color:#fff;border:none;cursor:pointer;font-size:15px;font-weight:700;margin-bottom:8px;">Capture Document</button><label style="width:100%;display:flex;align-items:center;justify-content:center;gap:6px;padding:9px;border-radius:12px;border:1.5px solid rgba(255,255,255,0.18);cursor:pointer;color:rgba(255,255,255,0.6);font-size:13px;font-weight:500;"><span>📁</span><span>Upload from gallery instead</span><input id="sc-upload-doc-input" type="file" accept="image/*" style="display:none;" /></label>`;
|
|
4948
4738
|
docRoot.appendChild(docVideo);
|
|
4949
4739
|
docRoot.appendChild(guideOverlay);
|
|
4950
4740
|
docRoot.appendChild(header);
|
|
@@ -4998,7 +4788,6 @@ var SmartComplySDK = (() => {
|
|
|
4998
4788
|
});
|
|
4999
4789
|
}
|
|
5000
4790
|
unmount() {
|
|
5001
|
-
this.stopTimer();
|
|
5002
4791
|
try {
|
|
5003
4792
|
this.docCaptureRoot?.remove();
|
|
5004
4793
|
} catch (_2) {
|
|
@@ -5011,42 +4800,9 @@ var SmartComplySDK = (() => {
|
|
|
5011
4800
|
this.root = null;
|
|
5012
4801
|
this.videoEl = null;
|
|
5013
4802
|
this.instructionEl = null;
|
|
5014
|
-
this.stepsEl = null;
|
|
5015
|
-
this.progressBarEl = null;
|
|
5016
4803
|
this.confidenceRingEl = null;
|
|
5017
|
-
this.timerEl = null;
|
|
5018
4804
|
this.overlayEl = null;
|
|
5019
|
-
this.
|
|
5020
|
-
this.stepEls = null;
|
|
5021
|
-
this.lastActionRender.clear();
|
|
5022
|
-
}
|
|
5023
|
-
startTimer(seconds) {
|
|
5024
|
-
let remaining = seconds;
|
|
5025
|
-
const update = () => {
|
|
5026
|
-
if (!this.timerEl) return;
|
|
5027
|
-
const m2 = Math.floor(remaining / 60);
|
|
5028
|
-
const s2 = remaining % 60;
|
|
5029
|
-
this.timerEl.textContent = `${m2 > 0 ? `${m2}:${String(s2).padStart(2, "0")}` : `${s2}s`} remaining`;
|
|
5030
|
-
if (remaining <= 5) this.timerEl.style.color = "#ef4444";
|
|
5031
|
-
};
|
|
5032
|
-
update();
|
|
5033
|
-
this.timerInterval = setInterval(() => {
|
|
5034
|
-
remaining--;
|
|
5035
|
-
update();
|
|
5036
|
-
if (remaining <= 0 && this.timerInterval) {
|
|
5037
|
-
clearInterval(this.timerInterval);
|
|
5038
|
-
}
|
|
5039
|
-
}, 1e3);
|
|
5040
|
-
}
|
|
5041
|
-
describeAction(action) {
|
|
5042
|
-
const labels = {
|
|
5043
|
-
BLINK: "Blink your eyes",
|
|
5044
|
-
TURN_LEFT: "Turn your head to the side",
|
|
5045
|
-
TURN_RIGHT: "Turn your head to the side",
|
|
5046
|
-
TURN_HEAD: "Turn your head side to side",
|
|
5047
|
-
OPEN_MOUTH: "Open your mouth wide"
|
|
5048
|
-
};
|
|
5049
|
-
return labels[action] || action.replace(/_/g, " ").toLowerCase();
|
|
4805
|
+
this.dotsEl = null;
|
|
5050
4806
|
}
|
|
5051
4807
|
};
|
|
5052
4808
|
|
|
@@ -5158,23 +4914,23 @@ var SmartComplySDK = (() => {
|
|
|
5158
4914
|
/**
|
|
5159
4915
|
* Full liveness check — orchestrates the entire flow:
|
|
5160
4916
|
*
|
|
5161
|
-
* 1.
|
|
5162
|
-
*
|
|
5163
|
-
*
|
|
5164
|
-
*
|
|
5165
|
-
*
|
|
4917
|
+
* 1. Run action detection loop (camera + MediaPipe) — challenge actions only,
|
|
4918
|
+
* not recorded (fast head/mouth motion produces blurry frames).
|
|
4919
|
+
* 2. Create liveness entry on backend (with challenge actions + optional doc)
|
|
4920
|
+
* 3. Record a short, clean, post-challenge clip (neutral pose + a natural
|
|
4921
|
+
* blink) — this is what's sent for face comparison in LIVENESS mode.
|
|
4922
|
+
* 4. Submit to backend and return response (status: "processing")
|
|
5166
4923
|
*
|
|
5167
|
-
*
|
|
5168
|
-
*
|
|
4924
|
+
* Backend runs LIVENESS mode (blink/motion check, then its own comparison
|
|
4925
|
+
* frame extracted from this same video) for both data and document
|
|
4926
|
+
* verification — see FACE_COMPARISON_USER_GUIDE.md.
|
|
5169
4927
|
*
|
|
5170
4928
|
* @param container - DOM element to mount the liveness UI into
|
|
5171
4929
|
* @param params - identifier info and optional document image
|
|
5172
|
-
* @param actions - override
|
|
4930
|
+
* @param actions - override the challenge_actions tag sent to the backend (default: BLINK)
|
|
5173
4931
|
* @param existingEntryId - reuse an already-created LivenessEntry (avoids double charge)
|
|
5174
4932
|
*/
|
|
5175
4933
|
async startCheck(container, params, actions, existingEntryId) {
|
|
5176
|
-
const isDataVerification = params.verification_type === "data_verification" || !params.document && !params.document_front && !params.id_file && !!params.identity_check;
|
|
5177
|
-
const skipVideo = isDataVerification;
|
|
5178
4934
|
const camera = new CameraManager();
|
|
5179
4935
|
const recorder = new VideoRecorder();
|
|
5180
4936
|
const faceEngine = new FaceDetectorEngine();
|
|
@@ -5290,76 +5046,39 @@ var SmartComplySDK = (() => {
|
|
|
5290
5046
|
tempVideo.remove();
|
|
5291
5047
|
const uiChallenge = {
|
|
5292
5048
|
actions: entry.challenge_actions,
|
|
5293
|
-
time_limit_seconds:
|
|
5294
|
-
instruction: "
|
|
5295
|
-
// Use backend-provided instructions if available (mirror-corrected),
|
|
5296
|
-
// otherwise fall back to the frontend labels.
|
|
5297
|
-
instructions: entry.challenge_instructions
|
|
5049
|
+
time_limit_seconds: LIVENESS_TIME_LIMIT_SECONDS,
|
|
5050
|
+
instruction: "Blink and look around naturally"
|
|
5298
5051
|
};
|
|
5299
5052
|
ui2.unmount();
|
|
5300
5053
|
const videoEl = ui2.mount(container, uiChallenge);
|
|
5301
5054
|
videoEl.srcObject = stream;
|
|
5302
5055
|
await videoEl.play();
|
|
5303
5056
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
5304
|
-
if (!skipVideo) {
|
|
5305
|
-
recorder.start(stream);
|
|
5306
|
-
}
|
|
5307
5057
|
ui2.updateInstruction(uiChallenge.instruction);
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
let pendingNextLabel = null;
|
|
5058
|
+
recorder.start(stream);
|
|
5059
|
+
ui2.startScanClock();
|
|
5060
|
+
const MIN_SCAN_MS = 2e3;
|
|
5061
|
+
const actionDetector = new ActionDetector();
|
|
5313
5062
|
const detectionResult = await new Promise(
|
|
5314
5063
|
(resolve) => {
|
|
5315
5064
|
let animFrameId;
|
|
5065
|
+
const scanStart = performance.now();
|
|
5316
5066
|
const timeout = setTimeout(() => {
|
|
5317
5067
|
cancelAnimationFrame(animFrameId);
|
|
5318
5068
|
resolve("timeout");
|
|
5319
5069
|
}, uiChallenge.time_limit_seconds * 1e3);
|
|
5320
|
-
const getLabel = (action) => uiChallenge.instructions?.[action] || this.describeAction(action);
|
|
5321
5070
|
const detectLoop = () => {
|
|
5322
|
-
const now = performance.now();
|
|
5323
|
-
const remaining = pauseUntil - now;
|
|
5324
|
-
if (remaining > 0) {
|
|
5325
|
-
setTimeout(() => {
|
|
5326
|
-
animFrameId = requestAnimationFrame(detectLoop);
|
|
5327
|
-
}, remaining);
|
|
5328
|
-
return;
|
|
5329
|
-
}
|
|
5330
|
-
if (pendingNextAction) {
|
|
5331
|
-
lastAction = pendingNextAction;
|
|
5332
|
-
if (pendingNextLabel) ui2.updateInstruction(pendingNextLabel);
|
|
5333
|
-
pendingNextAction = null;
|
|
5334
|
-
pendingNextLabel = null;
|
|
5335
|
-
}
|
|
5336
5071
|
if (videoEl.readyState >= 2) {
|
|
5337
|
-
const detection = faceEngine.detect(
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
);
|
|
5341
|
-
const states = actionDetector.check(detection);
|
|
5342
|
-
ui2.updateActions(states);
|
|
5343
|
-
const current = actionDetector.getCurrentAction();
|
|
5072
|
+
const detection = faceEngine.detect(videoEl, performance.now());
|
|
5073
|
+
const scan = actionDetector.check(detection);
|
|
5074
|
+
ui2.updateScanProgress(scan.livenessConfirmed);
|
|
5344
5075
|
if (!detection.faceDetected) {
|
|
5345
5076
|
ui2.updateInstruction("Move your face into the oval");
|
|
5346
|
-
} else if (current) {
|
|
5347
|
-
if (current !== lastAction) {
|
|
5348
|
-
if (lastAction !== null) {
|
|
5349
|
-
pauseUntil = now + 600;
|
|
5350
|
-
pendingNextAction = current;
|
|
5351
|
-
pendingNextLabel = getLabel(current);
|
|
5352
|
-
ui2.updateInstruction(`Great! ${pendingNextLabel}`);
|
|
5353
|
-
animFrameId = requestAnimationFrame(detectLoop);
|
|
5354
|
-
return;
|
|
5355
|
-
}
|
|
5356
|
-
lastAction = current;
|
|
5357
|
-
ui2.updateInstruction(getLabel(current));
|
|
5358
|
-
}
|
|
5359
5077
|
} else {
|
|
5360
|
-
ui2.updateInstruction(
|
|
5078
|
+
ui2.updateInstruction(uiChallenge.instruction);
|
|
5361
5079
|
}
|
|
5362
|
-
|
|
5080
|
+
const elapsed = performance.now() - scanStart;
|
|
5081
|
+
if (detection.faceDetected && scan.livenessConfirmed && elapsed >= MIN_SCAN_MS) {
|
|
5363
5082
|
clearTimeout(timeout);
|
|
5364
5083
|
cancelAnimationFrame(animFrameId);
|
|
5365
5084
|
resolve("completed");
|
|
@@ -5373,10 +5092,10 @@ var SmartComplySDK = (() => {
|
|
|
5373
5092
|
);
|
|
5374
5093
|
if (detectionResult === "timeout") {
|
|
5375
5094
|
cleanedUp = true;
|
|
5376
|
-
recorder.stop().catch(() => {
|
|
5377
|
-
});
|
|
5378
5095
|
return new Promise((resolveRetry, rejectRetry) => {
|
|
5379
5096
|
ui2.showTimeout(() => {
|
|
5097
|
+
recorder.stop().catch(() => {
|
|
5098
|
+
});
|
|
5380
5099
|
faceEngine.destroy();
|
|
5381
5100
|
camera.stop();
|
|
5382
5101
|
ui2.unmount();
|
|
@@ -5389,25 +5108,17 @@ var SmartComplySDK = (() => {
|
|
|
5389
5108
|
});
|
|
5390
5109
|
});
|
|
5391
5110
|
}
|
|
5392
|
-
ui2.updateInstruction("
|
|
5393
|
-
await new Promise((r2) => setTimeout(r2, 800));
|
|
5111
|
+
ui2.updateInstruction("Verifying...");
|
|
5394
5112
|
const autoshotBlob = await this.captureFrame(videoEl);
|
|
5395
5113
|
if (!autoshotBlob || autoshotBlob.size === 0) {
|
|
5396
|
-
throw new Error("Failed to capture selfie. Please ensure your face is visible and try again.");
|
|
5397
|
-
}
|
|
5398
|
-
const snapshotBlob = await this.captureFrame(videoEl);
|
|
5399
|
-
let videoBlob;
|
|
5400
|
-
let durationSeconds;
|
|
5401
|
-
if (skipVideo) {
|
|
5402
5114
|
recorder.stop().catch(() => {
|
|
5403
5115
|
});
|
|
5404
|
-
|
|
5405
|
-
durationSeconds = void 0;
|
|
5406
|
-
} else {
|
|
5407
|
-
const recorded = await recorder.stop();
|
|
5408
|
-
videoBlob = recorded.blob;
|
|
5409
|
-
durationSeconds = recorded.durationSeconds;
|
|
5116
|
+
throw new Error("Failed to capture selfie. Please ensure your face is visible and try again.");
|
|
5410
5117
|
}
|
|
5118
|
+
const snapshotBlob = await this.captureFrame(videoEl);
|
|
5119
|
+
const recorded = await recorder.stop();
|
|
5120
|
+
const videoBlob = recorded.blob;
|
|
5121
|
+
const durationSeconds = recorded.durationSeconds;
|
|
5411
5122
|
const submitResult = await this.submit(
|
|
5412
5123
|
entry.id,
|
|
5413
5124
|
videoBlob,
|
|
@@ -5521,16 +5232,6 @@ var SmartComplySDK = (() => {
|
|
|
5521
5232
|
}
|
|
5522
5233
|
return n2 > 0 ? sum / n2 : 0;
|
|
5523
5234
|
}
|
|
5524
|
-
describeAction(action) {
|
|
5525
|
-
const descriptions = {
|
|
5526
|
-
BLINK: "Blink your eyes",
|
|
5527
|
-
TURN_LEFT: "Turn your head to the side",
|
|
5528
|
-
TURN_RIGHT: "Turn your head to the side",
|
|
5529
|
-
TURN_HEAD: "Turn your head side to side",
|
|
5530
|
-
OPEN_MOUTH: "Open your mouth wide"
|
|
5531
|
-
};
|
|
5532
|
-
return descriptions[action] || action.replace(/_/g, " ").toLowerCase();
|
|
5533
|
-
}
|
|
5534
5235
|
};
|
|
5535
5236
|
|
|
5536
5237
|
// src/client/Smartcomply.ts
|
|
@@ -5908,7 +5609,7 @@ var SmartComplySDK = (() => {
|
|
|
5908
5609
|
const docLabel = this.getDocumentLabel(documentType);
|
|
5909
5610
|
const requiresBack = this.shouldCaptureBackSide(documentType);
|
|
5910
5611
|
const isCapturingFront = this.currentSide === "front";
|
|
5911
|
-
const sideLabel = requiresBack ? isCapturingFront ? "
|
|
5612
|
+
const sideLabel = requiresBack ? isCapturingFront ? " — Front side" : " — Back side" : "";
|
|
5912
5613
|
const headerRow = document.createElement("div");
|
|
5913
5614
|
headerRow.style.cssText = `
|
|
5914
5615
|
display:flex;align-items:center;gap:14px;
|
|
@@ -5929,7 +5630,7 @@ var SmartComplySDK = (() => {
|
|
|
5929
5630
|
titleEl.textContent = `Capture your ${docLabel}`;
|
|
5930
5631
|
const subEl = document.createElement("div");
|
|
5931
5632
|
subEl.style.cssText = `color:${theme.textSecondary};font-size:12px;margin-top:2px;`;
|
|
5932
|
-
subEl.textContent = requiresBack ? isCapturingFront ? "Step 1 of 2
|
|
5633
|
+
subEl.textContent = requiresBack ? isCapturingFront ? "Step 1 of 2 — front side" : "Step 2 of 2 — back side" : "Choose how to provide your document";
|
|
5933
5634
|
titleBlock.appendChild(titleEl);
|
|
5934
5635
|
titleBlock.appendChild(subEl);
|
|
5935
5636
|
headerRow.appendChild(iconBox);
|
|
@@ -5950,7 +5651,7 @@ var SmartComplySDK = (() => {
|
|
|
5950
5651
|
transition:all 0.15s ease;min-height:80px;
|
|
5951
5652
|
box-shadow:0 4px 14px ${theme.primary}30;
|
|
5952
5653
|
`;
|
|
5953
|
-
cameraBtn.innerHTML = `<span style="font-size:26px;"
|
|
5654
|
+
cameraBtn.innerHTML = `<span style="font-size:26px;">📷</span><span>Take Photo</span><span style="font-size:10px;opacity:0.8;font-weight:500;">Use camera</span>`;
|
|
5954
5655
|
cameraBtn.addEventListener("mouseenter", () => {
|
|
5955
5656
|
cameraBtn.style.opacity = "0.9";
|
|
5956
5657
|
cameraBtn.style.transform = "translateY(-1px)";
|
|
@@ -5972,7 +5673,7 @@ var SmartComplySDK = (() => {
|
|
|
5972
5673
|
font-size:13px;font-weight:600;font-family:inherit;
|
|
5973
5674
|
transition:all 0.15s ease;min-height:80px;
|
|
5974
5675
|
`;
|
|
5975
|
-
uploadBtn.innerHTML = `<span style="font-size:26px;"
|
|
5676
|
+
uploadBtn.innerHTML = `<span style="font-size:26px;">🖼️</span><span>From Gallery</span><span style="font-size:10px;color:${theme.textMuted || theme.textSecondary};font-weight:500;">Upload file</span>`;
|
|
5976
5677
|
uploadBtn.addEventListener("mouseenter", () => {
|
|
5977
5678
|
uploadBtn.style.borderColor = theme.primary;
|
|
5978
5679
|
uploadBtn.style.transform = "translateY(-1px)";
|
|
@@ -6006,7 +5707,7 @@ var SmartComplySDK = (() => {
|
|
|
6006
5707
|
tipItems.forEach((t2) => {
|
|
6007
5708
|
const row = document.createElement("div");
|
|
6008
5709
|
row.style.cssText = `display:flex;align-items:center;gap:5px;font-size:11px;color:${theme.textSecondary};`;
|
|
6009
|
-
row.innerHTML = `<span style="color:${theme.primary};font-weight:700;flex-shrink:0;"
|
|
5710
|
+
row.innerHTML = `<span style="color:${theme.primary};font-weight:700;flex-shrink:0;">✓</span><span>${t2}</span>`;
|
|
6010
5711
|
tipsGrid.appendChild(row);
|
|
6011
5712
|
});
|
|
6012
5713
|
tips.appendChild(tipsGrid);
|
|
@@ -6020,7 +5721,7 @@ var SmartComplySDK = (() => {
|
|
|
6020
5721
|
display:flex;align-items:center;gap:8px;
|
|
6021
5722
|
font-size:12px;color:${theme.textSecondary};line-height:1.5;
|
|
6022
5723
|
`;
|
|
6023
|
-
notice.innerHTML = `<span style="font-size:16px;flex-shrink:0;"
|
|
5724
|
+
notice.innerHTML = `<span style="font-size:16px;flex-shrink:0;">ℹ️</span><span>You'll capture both the <strong style="color:${theme.text}">front</strong> and <strong style="color:${theme.text}">back</strong> of this document.</span>`;
|
|
6024
5725
|
this.root.appendChild(notice);
|
|
6025
5726
|
}
|
|
6026
5727
|
const fileInput = document.createElement("input");
|
|
@@ -6046,10 +5747,10 @@ var SmartComplySDK = (() => {
|
|
|
6046
5747
|
/** Returns an appropriate emoji icon for the document type. */
|
|
6047
5748
|
_docIcon(documentType) {
|
|
6048
5749
|
const t2 = documentType.toLowerCase();
|
|
6049
|
-
if (t2.includes("passport")) return "
|
|
6050
|
-
if (t2.includes("driver") || t2.includes("licen")) return "
|
|
6051
|
-
if (t2.includes("voter") || t2.includes("pvc")) return "
|
|
6052
|
-
return "
|
|
5750
|
+
if (t2.includes("passport")) return "📘";
|
|
5751
|
+
if (t2.includes("driver") || t2.includes("licen")) return "🚗";
|
|
5752
|
+
if (t2.includes("voter") || t2.includes("pvc")) return "🗳️";
|
|
5753
|
+
return "🪪";
|
|
6053
5754
|
}
|
|
6054
5755
|
renderCameraView(container, theme, documentType) {
|
|
6055
5756
|
const wrapper = document.createElement("div");
|
|
@@ -6259,12 +5960,12 @@ var SmartComplySDK = (() => {
|
|
|
6259
5960
|
`;
|
|
6260
5961
|
const issueLine = document.createElement("div");
|
|
6261
5962
|
issueLine.style.cssText = "font-weight:700;color:#b8860b;";
|
|
6262
|
-
issueLine.textContent =
|
|
5963
|
+
issueLine.textContent = `⚠️ ${quality.message}`;
|
|
6263
5964
|
warn.appendChild(issueLine);
|
|
6264
5965
|
if (quality.recommendations.length > 0) {
|
|
6265
5966
|
const rec = document.createElement("div");
|
|
6266
5967
|
rec.style.cssText = "margin-top:4px;color:" + theme.textSecondary + ";";
|
|
6267
|
-
rec.textContent = quality.recommendations.join("
|
|
5968
|
+
rec.textContent = quality.recommendations.join(" · ");
|
|
6268
5969
|
warn.appendChild(rec);
|
|
6269
5970
|
}
|
|
6270
5971
|
this.root.appendChild(warn);
|
|
@@ -6280,7 +5981,7 @@ var SmartComplySDK = (() => {
|
|
|
6280
5981
|
this.root.innerHTML = "";
|
|
6281
5982
|
this.renderChoiceScreen(container, theme, documentType);
|
|
6282
5983
|
});
|
|
6283
|
-
const useBtn = this.createButton(qualityIsPoor ? "Use Anyway
|
|
5984
|
+
const useBtn = this.createButton(qualityIsPoor ? "Use Anyway ⚠" : "Use Photo ✓", theme, !qualityIsPoor);
|
|
6284
5985
|
useBtn.style.flex = "1";
|
|
6285
5986
|
useBtn.addEventListener("click", () => {
|
|
6286
5987
|
if (img._blobUrl) {
|
|
@@ -6426,7 +6127,7 @@ var SmartComplySDK = (() => {
|
|
|
6426
6127
|
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
|
|
6427
6128
|
font-size:28px;animation:pulse 2s ease-in-out infinite;
|
|
6428
6129
|
`;
|
|
6429
|
-
centerIcon.textContent = "
|
|
6130
|
+
centerIcon.textContent = "📄";
|
|
6430
6131
|
spinnerContainer.appendChild(centerIcon);
|
|
6431
6132
|
this.root.appendChild(spinnerContainer);
|
|
6432
6133
|
const loadingText = document.createElement("div");
|
|
@@ -6461,7 +6162,7 @@ var SmartComplySDK = (() => {
|
|
|
6461
6162
|
color:#ff4444;font-size:14px;margin-top:16px;animation:shake 0.5s ease-in-out;
|
|
6462
6163
|
`;
|
|
6463
6164
|
errorContainer.innerHTML = `
|
|
6464
|
-
<span style="font-size:18px;"
|
|
6165
|
+
<span style="font-size:18px;">⚠️</span>
|
|
6465
6166
|
<span>${message}</span>
|
|
6466
6167
|
`;
|
|
6467
6168
|
if (!document.head.querySelector("style[data-shake-animation]")) {
|
|
@@ -6646,13 +6347,13 @@ var SmartComplySDK = (() => {
|
|
|
6646
6347
|
|
|
6647
6348
|
// src/flow/SmartComplyFlow.ts
|
|
6648
6349
|
var COUNTRY_LABELS = {
|
|
6649
|
-
nigeria: "
|
|
6650
|
-
global: "
|
|
6651
|
-
ghana: "
|
|
6652
|
-
kenya: "
|
|
6653
|
-
south_africa: "
|
|
6654
|
-
united_states: "
|
|
6655
|
-
united_kingdom: "
|
|
6350
|
+
nigeria: "🇳🇬 Nigeria",
|
|
6351
|
+
global: "🌠Other Countries",
|
|
6352
|
+
ghana: "🇬🇠Ghana",
|
|
6353
|
+
kenya: "🇰🇪 Kenya",
|
|
6354
|
+
south_africa: "🇿🇦 South Africa",
|
|
6355
|
+
united_states: "🇺🇸 United States",
|
|
6356
|
+
united_kingdom: "🇬🇧 United Kingdom"
|
|
6656
6357
|
};
|
|
6657
6358
|
function isUploadFieldType(type) {
|
|
6658
6359
|
return type === "upload" || type === "image" || type === "file";
|
|
@@ -6666,37 +6367,37 @@ var SmartComplySDK = (() => {
|
|
|
6666
6367
|
return t2.includes("voter") || t2.includes("driver") || t2.includes("license") || t2.includes("national id") || t2.includes("national id card");
|
|
6667
6368
|
};
|
|
6668
6369
|
if (u2.includes("BVN") || u2.includes("BANK VERIFICATION")) {
|
|
6669
|
-
return { icon: "
|
|
6370
|
+
return { icon: "ðŸ¦", desc: "Enter your 11-digit Bank Verification Number (BVN)", requiresBack: false };
|
|
6670
6371
|
}
|
|
6671
6372
|
if (u2.includes("NIN") || u2.includes("NATIONAL IDENTITY NUMBER")) {
|
|
6672
|
-
return { icon: "
|
|
6373
|
+
return { icon: "🆔", desc: "Enter your 11-digit National Identification Number (NIN)", requiresBack: false };
|
|
6673
6374
|
}
|
|
6674
6375
|
if (u2.includes("GHANA")) {
|
|
6675
|
-
if (isDocumentFlow) return { icon: "
|
|
6676
|
-
return { icon: "
|
|
6376
|
+
if (isDocumentFlow) return { icon: "🪪", desc: "Capture or upload your Ghana Card", requiresBack: true };
|
|
6377
|
+
return { icon: "🪪", desc: "Enter your Ghana Card number", requiresBack: false };
|
|
6677
6378
|
}
|
|
6678
6379
|
if (u2.includes("SSNIT"))
|
|
6679
|
-
return { icon: "
|
|
6380
|
+
return { icon: "ðŸ›ï¸", desc: "Enter your SSNIT number", requiresBack: false };
|
|
6680
6381
|
if (u2.includes("KENYA") || u2.includes("NATIONAL") && u2.includes("KENYA"))
|
|
6681
|
-
return { icon: "
|
|
6382
|
+
return { icon: "🆔", desc: "Enter your Kenya National ID number", requiresBack: false };
|
|
6682
6383
|
if (u2.includes("TIN") || u2.includes("TAX IDENTIFICATION"))
|
|
6683
|
-
return { icon: "
|
|
6384
|
+
return { icon: "🧾", desc: "Enter your Tax Identification Number (TIN)", requiresBack: false };
|
|
6684
6385
|
if (u2.includes("CAC") || u2.includes("CORPORATE AFFAIRS") || u2.includes("COMPANY"))
|
|
6685
|
-
return { icon: "
|
|
6386
|
+
return { icon: "ðŸ¢", desc: advanced ? "Enter RC number, company name & type" : "Enter your company registration number", requiresBack: false };
|
|
6686
6387
|
if (u2.includes("VOTER") || u2.includes("PVC")) {
|
|
6687
6388
|
const requiresBack = requiresBackSide(typeName);
|
|
6688
6389
|
return {
|
|
6689
|
-
icon: "
|
|
6390
|
+
icon: "🗳ï¸",
|
|
6690
6391
|
desc: isDocumentFlow ? `Capture or upload your voter's card${requiresBack ? " (front & back)" : ""}` : "Enter your voter ID number",
|
|
6691
6392
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6692
6393
|
};
|
|
6693
6394
|
}
|
|
6694
6395
|
if (u2.includes("PASSPORT"))
|
|
6695
|
-
return { icon: "
|
|
6396
|
+
return { icon: "📘", desc: isDocumentFlow ? "Capture or upload your passport" : "Enter your passport number", requiresBack: false };
|
|
6696
6397
|
if (u2.includes("DRIVER") || u2.includes("LICENSE") || u2.includes("LICENCE")) {
|
|
6697
6398
|
const requiresBack = requiresBackSide(typeName);
|
|
6698
6399
|
return {
|
|
6699
|
-
icon: "
|
|
6400
|
+
icon: "🚗",
|
|
6700
6401
|
desc: isDocumentFlow ? `Capture or upload your driver's license${requiresBack ? " (front & back)" : ""}` : "Enter your driver's license number",
|
|
6701
6402
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6702
6403
|
};
|
|
@@ -6704,15 +6405,15 @@ var SmartComplySDK = (() => {
|
|
|
6704
6405
|
if (u2.includes("NATIONAL ID") || u2.includes("NATIONAL") && u2.includes("CARD")) {
|
|
6705
6406
|
const requiresBack = requiresBackSide(typeName);
|
|
6706
6407
|
return {
|
|
6707
|
-
icon: "
|
|
6408
|
+
icon: "🪪",
|
|
6708
6409
|
desc: isDocumentFlow ? `Capture or upload your ID card${requiresBack ? " (front & back)" : ""}` : "Enter your National ID number",
|
|
6709
6410
|
requiresBack: isDocumentFlow ? requiresBack : false
|
|
6710
6411
|
};
|
|
6711
6412
|
}
|
|
6712
6413
|
if (u2.includes("LIVENESS") || u2.includes("FACE COMPARISON") || u2.includes("FACE MATCH"))
|
|
6713
|
-
return { icon: "
|
|
6414
|
+
return { icon: "🤳", desc: "We'll capture a short selfie video to verify you", requiresBack: false };
|
|
6714
6415
|
return {
|
|
6715
|
-
icon: isDocumentFlow ? "
|
|
6416
|
+
icon: isDocumentFlow ? "📄" : "🆔",
|
|
6716
6417
|
desc: isDocumentFlow ? "Capture or upload your document" : "Enter your ID number",
|
|
6717
6418
|
requiresBack: false
|
|
6718
6419
|
};
|
|
@@ -7124,7 +6825,7 @@ var SmartComplySDK = (() => {
|
|
|
7124
6825
|
display:flex;align-items:center;justify-content:center;
|
|
7125
6826
|
font-size:22px;
|
|
7126
6827
|
`;
|
|
7127
|
-
centre.textContent = "
|
|
6828
|
+
centre.textContent = "🛡️";
|
|
7128
6829
|
spinnerWrap.appendChild(ring);
|
|
7129
6830
|
spinnerWrap.appendChild(centre);
|
|
7130
6831
|
container.appendChild(spinnerWrap);
|
|
@@ -7151,7 +6852,7 @@ var SmartComplySDK = (() => {
|
|
|
7151
6852
|
background:linear-gradient(135deg,${this.theme.primary}22,${this.theme.primaryGlow});
|
|
7152
6853
|
border:1.5px solid ${this.theme.primary}33;
|
|
7153
6854
|
`;
|
|
7154
|
-
iconWrap.textContent = isDocFlow ? "
|
|
6855
|
+
iconWrap.textContent = isDocFlow ? "🪪" : "🛡ï¸";
|
|
7155
6856
|
topRow.appendChild(iconWrap);
|
|
7156
6857
|
const badge = document.createElement("div");
|
|
7157
6858
|
badge.style.cssText = `
|
|
@@ -7162,7 +6863,7 @@ var SmartComplySDK = (() => {
|
|
|
7162
6863
|
color:${this.theme.primary};
|
|
7163
6864
|
border:1px solid ${this.theme.primary}30;
|
|
7164
6865
|
`;
|
|
7165
|
-
badge.textContent = isDocFlow ? "
|
|
6866
|
+
badge.textContent = isDocFlow ? "📄 Document Verification" : "🔠Data Verification";
|
|
7166
6867
|
topRow.appendChild(badge);
|
|
7167
6868
|
container.appendChild(topRow);
|
|
7168
6869
|
const titleWrap = document.createElement("div");
|
|
@@ -7184,13 +6885,13 @@ var SmartComplySDK = (() => {
|
|
|
7184
6885
|
titleWrap.appendChild(desc);
|
|
7185
6886
|
container.appendChild(titleWrap);
|
|
7186
6887
|
const steps = isDocFlow ? [
|
|
7187
|
-
{ icon: "
|
|
7188
|
-
{ icon: "
|
|
7189
|
-
{ icon: "
|
|
6888
|
+
{ icon: "🪪", text: "Choose your document type", sub: "Passport, National ID, Driver License" },
|
|
6889
|
+
{ icon: "📷", text: "Scan your document", sub: "Upload or capture a photo of your ID" },
|
|
6890
|
+
{ icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
7190
6891
|
] : [
|
|
7191
|
-
{ icon: "
|
|
7192
|
-
{ icon: "
|
|
7193
|
-
{ icon: "
|
|
6892
|
+
{ icon: "🆔", text: "Choose your ID type", sub: "NIN, BVN, Voter's Card and more" },
|
|
6893
|
+
{ icon: "ðŸ“", text: "Enter your ID details", sub: "Exactly as shown on your ID" },
|
|
6894
|
+
{ icon: "🤳", text: "Face check", sub: "Quick selfie to confirm it's you" }
|
|
7194
6895
|
];
|
|
7195
6896
|
const stepsWrap = document.createElement("div");
|
|
7196
6897
|
stepsWrap.style.cssText = `
|
|
@@ -7230,7 +6931,7 @@ var SmartComplySDK = (() => {
|
|
|
7230
6931
|
stepsWrap.appendChild(row);
|
|
7231
6932
|
});
|
|
7232
6933
|
container.appendChild(stepsWrap);
|
|
7233
|
-
const btn = this.createPrimaryButton("Get Started
|
|
6934
|
+
const btn = this.createPrimaryButton("Get Started →");
|
|
7234
6935
|
btn.style.cssText += "margin-top:2px;";
|
|
7235
6936
|
btn.addEventListener("click", () => this.showStep("country"));
|
|
7236
6937
|
container.appendChild(btn);
|
|
@@ -7239,7 +6940,7 @@ var SmartComplySDK = (() => {
|
|
|
7239
6940
|
display:flex;align-items:center;gap:6px;
|
|
7240
6941
|
color:${this.theme.textMuted};font-size:10.5px;
|
|
7241
6942
|
`;
|
|
7242
|
-
trust.textContent = "
|
|
6943
|
+
trust.textContent = "🔒 End-to-end encrypted";
|
|
7243
6944
|
container.appendChild(trust);
|
|
7244
6945
|
}
|
|
7245
6946
|
// ── Country Select ──────────────────────────────────────────────
|
|
@@ -7292,7 +6993,7 @@ var SmartComplySDK = (() => {
|
|
|
7292
6993
|
const requiresBack = requiresBackFromBackend ?? info.requiresBack;
|
|
7293
6994
|
let subtitle2 = info.desc;
|
|
7294
6995
|
if (requiresBack && isThisDocFlow) {
|
|
7295
|
-
subtitle2 += "
|
|
6996
|
+
subtitle2 += " âš ï¸ Both sides required";
|
|
7296
6997
|
}
|
|
7297
6998
|
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();
|
|
7298
6999
|
const card = this.createOptionCard(
|
|
@@ -7355,13 +7056,13 @@ var SmartComplySDK = (() => {
|
|
|
7355
7056
|
{ match: "BVN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "BVN must be exactly 11 digits" } },
|
|
7356
7057
|
{ match: "NIN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7357
7058
|
{ match: "NATIONAL IDENTITY", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7358
|
-
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6
|
|
7359
|
-
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3
|
|
7360
|
-
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6
|
|
7361
|
-
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8
|
|
7362
|
-
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7
|
|
7363
|
-
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8
|
|
7364
|
-
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10
|
|
7059
|
+
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6–20 alphanumeric characters" } },
|
|
7060
|
+
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3–30 alphanumeric characters" } },
|
|
7061
|
+
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6–30 alphanumeric characters" } },
|
|
7062
|
+
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8–20 alphanumeric characters" } },
|
|
7063
|
+
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7–15 alphanumeric characters" } },
|
|
7064
|
+
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8–20 characters" } },
|
|
7065
|
+
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10–12 characters" } }
|
|
7365
7066
|
];
|
|
7366
7067
|
function getFieldRule(label) {
|
|
7367
7068
|
const upper = label.toUpperCase();
|
|
@@ -7647,7 +7348,7 @@ var SmartComplySDK = (() => {
|
|
|
7647
7348
|
this._prewarmCamera();
|
|
7648
7349
|
const iconWrap = document.createElement("div");
|
|
7649
7350
|
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;`;
|
|
7650
|
-
iconWrap.textContent = "
|
|
7351
|
+
iconWrap.textContent = "🛡ï¸";
|
|
7651
7352
|
container.appendChild(iconWrap);
|
|
7652
7353
|
const title = document.createElement("h2");
|
|
7653
7354
|
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
@@ -7684,7 +7385,7 @@ var SmartComplySDK = (() => {
|
|
|
7684
7385
|
sub.textContent = "Your details have been confirmed. Please continue to the face check.";
|
|
7685
7386
|
container.appendChild(sub);
|
|
7686
7387
|
}
|
|
7687
|
-
const yesBtn = this.createPrimaryButton("Yes, that's me
|
|
7388
|
+
const yesBtn = this.createPrimaryButton("Yes, that's me — continue");
|
|
7688
7389
|
yesBtn.addEventListener("click", () => {
|
|
7689
7390
|
this._confirmRetries = 0;
|
|
7690
7391
|
this.showStep("liveness");
|
|
@@ -7711,7 +7412,7 @@ var SmartComplySDK = (() => {
|
|
|
7711
7412
|
this._prewarmCamera();
|
|
7712
7413
|
const iconWrap = document.createElement("div");
|
|
7713
7414
|
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;`;
|
|
7714
|
-
iconWrap.textContent = "
|
|
7415
|
+
iconWrap.textContent = "📄";
|
|
7715
7416
|
container.appendChild(iconWrap);
|
|
7716
7417
|
const title = document.createElement("h2");
|
|
7717
7418
|
title.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
@@ -7734,7 +7435,7 @@ var SmartComplySDK = (() => {
|
|
|
7734
7435
|
if (this._ocrExpirySoon && !this._ocrIsExpired) {
|
|
7735
7436
|
const warn = document.createElement("div");
|
|
7736
7437
|
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;`;
|
|
7737
|
-
warn.textContent = "
|
|
7438
|
+
warn.textContent = "âš ï¸ Your document expires soon. Some services may not accept it, but you can still continue.";
|
|
7738
7439
|
container.appendChild(warn);
|
|
7739
7440
|
}
|
|
7740
7441
|
const card = document.createElement("div");
|
|
@@ -7797,18 +7498,18 @@ var SmartComplySDK = (() => {
|
|
|
7797
7498
|
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;`;
|
|
7798
7499
|
const centre = document.createElement("div");
|
|
7799
7500
|
centre.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;";
|
|
7800
|
-
centre.textContent = "
|
|
7501
|
+
centre.textContent = "📄";
|
|
7801
7502
|
spinnerWrap.appendChild(ring);
|
|
7802
7503
|
spinnerWrap.appendChild(centre);
|
|
7803
7504
|
container.appendChild(spinnerWrap);
|
|
7804
7505
|
const statusEl = document.createElement("div");
|
|
7805
7506
|
statusEl.setAttribute("aria-live", "polite");
|
|
7806
7507
|
statusEl.style.cssText = `color:${this.theme.text};font-size:15px;font-weight:600;`;
|
|
7807
|
-
statusEl.textContent = "Reading your document
|
|
7508
|
+
statusEl.textContent = "Reading your document…";
|
|
7808
7509
|
container.appendChild(statusEl);
|
|
7809
7510
|
const subEl = document.createElement("div");
|
|
7810
7511
|
subEl.style.cssText = `color:${this.theme.textSecondary};font-size:12px;max-width:260px;line-height:1.5;`;
|
|
7811
|
-
subEl.textContent = "This usually takes 5
|
|
7512
|
+
subEl.textContent = "This usually takes 5–15 seconds. Please wait.";
|
|
7812
7513
|
container.appendChild(subEl);
|
|
7813
7514
|
if (!this.livenessEntryId) {
|
|
7814
7515
|
this.showErrorStep("Document upload failed. Please try again.");
|
|
@@ -7853,8 +7554,8 @@ var SmartComplySDK = (() => {
|
|
|
7853
7554
|
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7854
7555
|
this._ocrPollTimer = setTimeout(poll, 2e3);
|
|
7855
7556
|
} else if (!this.isDestroyed) {
|
|
7856
|
-
statusEl.textContent = "Taking longer than usual
|
|
7857
|
-
subEl.textContent = "We'll continue
|
|
7557
|
+
statusEl.textContent = "Taking longer than usual…";
|
|
7558
|
+
subEl.textContent = "We'll continue — you can confirm your details on the next screen.";
|
|
7858
7559
|
this._ocrPollTimer = setTimeout(() => {
|
|
7859
7560
|
if (!this.isDestroyed) this.showStep("document_confirm");
|
|
7860
7561
|
}, 2e3);
|
|
@@ -7873,7 +7574,7 @@ var SmartComplySDK = (() => {
|
|
|
7873
7574
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7874
7575
|
const icon = document.createElement("div");
|
|
7875
7576
|
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;`;
|
|
7876
|
-
icon.textContent = "
|
|
7577
|
+
icon.textContent = "📅";
|
|
7877
7578
|
container.appendChild(icon);
|
|
7878
7579
|
const t2 = document.createElement("h2");
|
|
7879
7580
|
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
@@ -7902,7 +7603,7 @@ var SmartComplySDK = (() => {
|
|
|
7902
7603
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:20px 0;";
|
|
7903
7604
|
const icon = document.createElement("div");
|
|
7904
7605
|
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;`;
|
|
7905
|
-
icon.textContent = "
|
|
7606
|
+
icon.textContent = "ðŸ”";
|
|
7906
7607
|
container.appendChild(icon);
|
|
7907
7608
|
const t2 = document.createElement("h2");
|
|
7908
7609
|
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
@@ -7935,20 +7636,19 @@ var SmartComplySDK = (() => {
|
|
|
7935
7636
|
});
|
|
7936
7637
|
const iconWrap = document.createElement("div");
|
|
7937
7638
|
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;`;
|
|
7938
|
-
iconWrap.textContent = "
|
|
7639
|
+
iconWrap.textContent = "✓";
|
|
7939
7640
|
container.appendChild(iconWrap);
|
|
7940
7641
|
const title = document.createElement("h2");
|
|
7941
7642
|
title.style.cssText = `color:${this.theme.text};font-size:22px;font-weight:700;margin:0;`;
|
|
7942
7643
|
title.textContent = "You're all done!";
|
|
7943
7644
|
container.appendChild(title);
|
|
7944
|
-
const brandName = this.sdkConfig?.brand_name || "the service";
|
|
7945
7645
|
const desc = document.createElement("p");
|
|
7946
7646
|
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7947
|
-
desc.textContent =
|
|
7647
|
+
desc.textContent = "Your verification is complete.";
|
|
7948
7648
|
container.appendChild(desc);
|
|
7949
7649
|
const info = document.createElement("div");
|
|
7950
7650
|
info.style.cssText = `color:${this.theme.textMuted};font-size:11px;display:flex;align-items:center;gap:4px;`;
|
|
7951
|
-
info.textContent = "
|
|
7651
|
+
info.textContent = "🔒 Your data is encrypted and secure";
|
|
7952
7652
|
container.appendChild(info);
|
|
7953
7653
|
const doneBtn = this.createPrimaryButton("Close");
|
|
7954
7654
|
doneBtn.addEventListener("click", () => {
|
|
@@ -7961,7 +7661,7 @@ var SmartComplySDK = (() => {
|
|
|
7961
7661
|
container.style.cssText += "display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:24px 0;";
|
|
7962
7662
|
const icon = document.createElement("div");
|
|
7963
7663
|
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;`;
|
|
7964
|
-
icon.textContent = "
|
|
7664
|
+
icon.textContent = "âš ï¸";
|
|
7965
7665
|
container.appendChild(icon);
|
|
7966
7666
|
const t2 = document.createElement("h2");
|
|
7967
7667
|
t2.style.cssText = `color:${this.theme.text};font-size:18px;font-weight:700;margin:0;`;
|
|
@@ -8017,7 +7717,7 @@ var SmartComplySDK = (() => {
|
|
|
8017
7717
|
<div style="display:flex;flex-direction:column;align-items:center;gap:14px;padding:28px 20px;">
|
|
8018
7718
|
<div style="position:relative;width:52px;height:52px;">
|
|
8019
7719
|
<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>
|
|
8020
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;"
|
|
7720
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">ðŸ”</div>
|
|
8021
7721
|
</div>
|
|
8022
7722
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Scanning document...</div>
|
|
8023
7723
|
<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>
|
|
@@ -8040,7 +7740,7 @@ var SmartComplySDK = (() => {
|
|
|
8040
7740
|
identifier,
|
|
8041
7741
|
identifier_type: this.selectedIdType,
|
|
8042
7742
|
country,
|
|
8043
|
-
challenge_actions: ["BLINK"
|
|
7743
|
+
challenge_actions: ["BLINK"],
|
|
8044
7744
|
document: blob,
|
|
8045
7745
|
document_back: this.documentBackBlob || void 0,
|
|
8046
7746
|
device_type: deviceMeta.device_type,
|
|
@@ -8075,7 +7775,7 @@ var SmartComplySDK = (() => {
|
|
|
8075
7775
|
<div style="display:flex;flex-direction:column;align-items:center;gap:16px;padding:28px 20px;text-align:center;">
|
|
8076
7776
|
<div style="position:relative;width:52px;height:52px;">
|
|
8077
7777
|
<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>
|
|
8078
|
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;"
|
|
7778
|
+
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:20px;">🔄</div>
|
|
8079
7779
|
</div>
|
|
8080
7780
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Retrying...</div>
|
|
8081
7781
|
<div style="color:${this.theme.textSecondary};font-size:12px;max-width:240px;line-height:1.5;">Attempt ${retryCount} of ${maxRetries}</div>
|
|
@@ -8116,7 +7816,7 @@ var SmartComplySDK = (() => {
|
|
|
8116
7816
|
wrap.setAttribute("role", "alert");
|
|
8117
7817
|
const icon = document.createElement("div");
|
|
8118
7818
|
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;`;
|
|
8119
|
-
icon.textContent = "
|
|
7819
|
+
icon.textContent = "ðŸ”";
|
|
8120
7820
|
wrap.appendChild(icon);
|
|
8121
7821
|
const titleEl = document.createElement("div");
|
|
8122
7822
|
titleEl.style.cssText = `color:${this.theme.text};font-size:14px;font-weight:700;`;
|
|
@@ -8182,7 +7882,7 @@ var SmartComplySDK = (() => {
|
|
|
8182
7882
|
// B4: pass pre-warmed stream so camera.open() is skipped
|
|
8183
7883
|
prewarmedStream: this._prewarmedStream || void 0
|
|
8184
7884
|
},
|
|
8185
|
-
["BLINK"
|
|
7885
|
+
["BLINK"],
|
|
8186
7886
|
this.livenessEntryId || void 0
|
|
8187
7887
|
).then((result) => {
|
|
8188
7888
|
if (result.status === "failed") {
|
|
@@ -8216,7 +7916,7 @@ var SmartComplySDK = (() => {
|
|
|
8216
7916
|
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
8217
7917
|
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
8218
7918
|
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
8219
|
-
const errorIcon = isCameraErr ? "
|
|
7919
|
+
const errorIcon = isCameraErr ? "📷" : isNetworkErr ? "📡" : isAuthErr ? "â±" : isDocQuality ? "ðŸ”" : isExpiredDoc ? "📅" : isFaceMatch ? "🤳" : "✕";
|
|
8220
7920
|
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";
|
|
8221
7921
|
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.";
|
|
8222
7922
|
const iconWrap = document.createElement("div");
|
|
@@ -8321,7 +8021,7 @@ var SmartComplySDK = (() => {
|
|
|
8321
8021
|
stepIndicator.appendChild(this.stepLabel);
|
|
8322
8022
|
header.appendChild(stepIndicator);
|
|
8323
8023
|
const closeBtn = document.createElement("button");
|
|
8324
|
-
closeBtn.innerHTML = "
|
|
8024
|
+
closeBtn.innerHTML = "✕";
|
|
8325
8025
|
closeBtn.setAttribute("aria-label", "Close identity verification");
|
|
8326
8026
|
closeBtn.style.cssText = `
|
|
8327
8027
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
@@ -8402,7 +8102,7 @@ var SmartComplySDK = (() => {
|
|
|
8402
8102
|
}
|
|
8403
8103
|
const arrow = document.createElement("span");
|
|
8404
8104
|
arrow.style.cssText = `color:${this.theme.textMuted};font-size:18px;flex-shrink:0;`;
|
|
8405
|
-
arrow.textContent = "
|
|
8105
|
+
arrow.textContent = "→";
|
|
8406
8106
|
card.appendChild(textWrap);
|
|
8407
8107
|
card.appendChild(arrow);
|
|
8408
8108
|
return card;
|
|
@@ -8474,7 +8174,7 @@ var SmartComplySDK = (() => {
|
|
|
8474
8174
|
if (this.currentStep === "loading" || this.currentStep === "welcome") {
|
|
8475
8175
|
this.stepLabel.textContent = "";
|
|
8476
8176
|
} else if (this.currentStep === "done" || this.currentStep === "result") {
|
|
8477
|
-
this.stepLabel.textContent = "Complete
|
|
8177
|
+
this.stepLabel.textContent = "Complete ✓";
|
|
8478
8178
|
} else if (stage2Steps.includes(this.currentStep)) {
|
|
8479
8179
|
this.stepLabel.textContent = "Stage 2 of 2: Face Check";
|
|
8480
8180
|
} else if (stage1Steps.includes(this.currentStep)) {
|
|
@@ -8588,20 +8288,20 @@ var SmartComplySDK = (() => {
|
|
|
8588
8288
|
/** Emit an informational message (debug mode only). */
|
|
8589
8289
|
info(message, ...args) {
|
|
8590
8290
|
if (!isDebugEnabled()) return;
|
|
8591
|
-
console.info(`${PREFIX}
|
|
8291
|
+
console.info(`${PREFIX} ℹ ${message}`, ...args);
|
|
8592
8292
|
},
|
|
8593
8293
|
/** Emit a warning (always visible). */
|
|
8594
8294
|
warn(message, ...args) {
|
|
8595
|
-
console.warn(`${PREFIX}
|
|
8295
|
+
console.warn(`${PREFIX} ⚠ ${message}`, ...args);
|
|
8596
8296
|
},
|
|
8597
8297
|
/** Emit an error (always visible). */
|
|
8598
8298
|
error(message, ...args) {
|
|
8599
|
-
console.error(`${PREFIX}
|
|
8299
|
+
console.error(`${PREFIX} ✖ ${message}`, ...args);
|
|
8600
8300
|
},
|
|
8601
8301
|
/** Emit a debug trace (debug mode only — lowest priority). */
|
|
8602
8302
|
debug(message, ...args) {
|
|
8603
8303
|
if (!isDebugEnabled()) return;
|
|
8604
|
-
console.debug(`${PREFIX}
|
|
8304
|
+
console.debug(`${PREFIX} ⬡ ${message}`, ...args);
|
|
8605
8305
|
},
|
|
8606
8306
|
/** Log a timed group of related messages (debug mode only). */
|
|
8607
8307
|
group(label, fn2) {
|
|
@@ -8759,7 +8459,7 @@ var SmartComplySDK = (() => {
|
|
|
8759
8459
|
}
|
|
8760
8460
|
};
|
|
8761
8461
|
xhr.onerror = async () => {
|
|
8762
|
-
SDKLogger.warn(`Upload to ${path} failed
|
|
8462
|
+
SDKLogger.warn(`Upload to ${path} failed — network error`, { retriesLeft });
|
|
8763
8463
|
if (retriesLeft > 0) {
|
|
8764
8464
|
await new Promise((r2) => setTimeout(r2, RETRY_DELAY_MS));
|
|
8765
8465
|
try {
|