smartcomply-web-sdk 1.0.89 → 1.0.91
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 +2 -1
- package/dist/camera/ActionDetector.d.ts.map +1 -1
- package/dist/camera/ActionDetector.js +21 -5
- package/dist/camera/ActionDetector.js.map +1 -1
- package/dist/client/Smartcomply.d.ts +14 -2
- package/dist/client/Smartcomply.d.ts.map +1 -1
- package/dist/client/Smartcomply.js +15 -2
- package/dist/client/Smartcomply.js.map +1 -1
- package/dist/esm/camera/ActionDetector.d.ts +2 -1
- package/dist/esm/camera/ActionDetector.d.ts.map +1 -1
- package/dist/esm/camera/ActionDetector.js +21 -5
- package/dist/esm/camera/ActionDetector.js.map +1 -1
- package/dist/esm/client/Smartcomply.d.ts +14 -2
- package/dist/esm/client/Smartcomply.d.ts.map +1 -1
- package/dist/esm/client/Smartcomply.js +15 -2
- package/dist/esm/client/Smartcomply.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts +24 -9
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +151 -25
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/flow/spinner.js +14 -14
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.js +83 -83
- package/dist/esm/types/liveness.d.ts +15 -0
- package/dist/esm/types/liveness.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +24 -9
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +152 -25
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/flow/spinner.js +14 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/liveness/LivenessUI.js +83 -83
- package/dist/smartcomply.browser.js +108 -11
- package/dist/smartcomply.browser.js.map +3 -3
- package/dist/types/liveness.d.ts +15 -0
- package/dist/types/liveness.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/components/CameraView.d.ts +0 -59
- package/dist/esm/components/CameraView.d.ts.map +0 -1
- package/dist/esm/components/CameraView.js +0 -238
- package/dist/esm/components/CameraView.js.map +0 -1
- package/dist/esm/components/DocumentUpload.d.ts +0 -21
- package/dist/esm/components/DocumentUpload.d.ts.map +0 -1
- package/dist/esm/components/DocumentUpload.js +0 -216
- package/dist/esm/components/DocumentUpload.js.map +0 -1
|
@@ -6,30 +6,30 @@ function injectLivenessKeyframes() {
|
|
|
6
6
|
return;
|
|
7
7
|
const s = document.createElement("style");
|
|
8
8
|
s.id = "sc-liveness-kf";
|
|
9
|
-
s.textContent = `
|
|
10
|
-
@keyframes sc-pop-in {
|
|
11
|
-
0% { transform:scale(0.7); opacity:0; }
|
|
12
|
-
60% { transform:scale(1.05); }
|
|
13
|
-
100% { transform:scale(1); opacity:1; }
|
|
14
|
-
}
|
|
15
|
-
@keyframes sc-scan-dot {
|
|
16
|
-
0%,100% { opacity:0.3; transform:scale(0.85); }
|
|
17
|
-
50% { opacity:1; transform:scale(1); }
|
|
18
|
-
}
|
|
19
|
-
@keyframes sc-scanline-sweep {
|
|
20
|
-
0% { transform:translateY(-4%); opacity:0; }
|
|
21
|
-
8% { opacity:0.9; }
|
|
22
|
-
92% { opacity:0.9; }
|
|
23
|
-
100% { transform:translateY(104%); opacity:0; }
|
|
24
|
-
}
|
|
25
|
-
@keyframes sc-corner-pulse {
|
|
26
|
-
0%,100% { opacity:0.55; }
|
|
27
|
-
50% { opacity:1; }
|
|
28
|
-
}
|
|
29
|
-
@keyframes sc-oval-breathe {
|
|
30
|
-
0%,100% { opacity:0.5; }
|
|
31
|
-
50% { opacity:0.85; }
|
|
32
|
-
}
|
|
9
|
+
s.textContent = `
|
|
10
|
+
@keyframes sc-pop-in {
|
|
11
|
+
0% { transform:scale(0.7); opacity:0; }
|
|
12
|
+
60% { transform:scale(1.05); }
|
|
13
|
+
100% { transform:scale(1); opacity:1; }
|
|
14
|
+
}
|
|
15
|
+
@keyframes sc-scan-dot {
|
|
16
|
+
0%,100% { opacity:0.3; transform:scale(0.85); }
|
|
17
|
+
50% { opacity:1; transform:scale(1); }
|
|
18
|
+
}
|
|
19
|
+
@keyframes sc-scanline-sweep {
|
|
20
|
+
0% { transform:translateY(-4%); opacity:0; }
|
|
21
|
+
8% { opacity:0.9; }
|
|
22
|
+
92% { opacity:0.9; }
|
|
23
|
+
100% { transform:translateY(104%); opacity:0; }
|
|
24
|
+
}
|
|
25
|
+
@keyframes sc-corner-pulse {
|
|
26
|
+
0%,100% { opacity:0.55; }
|
|
27
|
+
50% { opacity:1; }
|
|
28
|
+
}
|
|
29
|
+
@keyframes sc-oval-breathe {
|
|
30
|
+
0%,100% { opacity:0.5; }
|
|
31
|
+
50% { opacity:0.85; }
|
|
32
|
+
}
|
|
33
33
|
`;
|
|
34
34
|
document.head.appendChild(s);
|
|
35
35
|
}
|
|
@@ -59,38 +59,38 @@ class LivenessUI {
|
|
|
59
59
|
mount(container, challenge) {
|
|
60
60
|
injectLivenessKeyframes();
|
|
61
61
|
this.root = document.createElement("div");
|
|
62
|
-
this.root.style.cssText = `
|
|
63
|
-
position:relative;width:100%;max-width:420px;margin:0 auto;
|
|
64
|
-
background:#0a0a0a;border-radius:16px;overflow:hidden;
|
|
65
|
-
font-family:system-ui,-apple-system,sans-serif;
|
|
66
|
-
box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
|
67
|
-
aspect-ratio:3/4;
|
|
62
|
+
this.root.style.cssText = `
|
|
63
|
+
position:relative;width:100%;max-width:420px;margin:0 auto;
|
|
64
|
+
background:#0a0a0a;border-radius:16px;overflow:hidden;
|
|
65
|
+
font-family:system-ui,-apple-system,sans-serif;
|
|
66
|
+
box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
|
67
|
+
aspect-ratio:3/4;
|
|
68
68
|
`;
|
|
69
69
|
this.videoEl = document.createElement("video");
|
|
70
70
|
this.videoEl.autoplay = true;
|
|
71
71
|
this.videoEl.playsInline = true;
|
|
72
72
|
this.videoEl.muted = true;
|
|
73
73
|
this.videoEl.setAttribute("aria-label", "Live camera preview for face verification");
|
|
74
|
-
this.videoEl.style.cssText = `
|
|
75
|
-
width:100%;height:100%;display:block;
|
|
76
|
-
filter:brightness(1.03) contrast(1.03);
|
|
77
|
-
transform:scaleX(-1);object-fit:cover;
|
|
74
|
+
this.videoEl.style.cssText = `
|
|
75
|
+
width:100%;height:100%;display:block;
|
|
76
|
+
filter:brightness(1.03) contrast(1.03);
|
|
77
|
+
transform:scaleX(-1);object-fit:cover;
|
|
78
78
|
`;
|
|
79
79
|
// Dim everything outside the oval so the guide reads as a scan aperture
|
|
80
80
|
// rather than a shape floating over the raw feed.
|
|
81
81
|
const vignette = document.createElement("div");
|
|
82
|
-
vignette.style.cssText = `
|
|
83
|
-
position:absolute;inset:0;pointer-events:none;
|
|
84
|
-
background:radial-gradient(ellipse 34% 44% at 50% 47%, transparent 55%, rgba(0,0,0,0.55) 100%);
|
|
82
|
+
vignette.style.cssText = `
|
|
83
|
+
position:absolute;inset:0;pointer-events:none;
|
|
84
|
+
background:radial-gradient(ellipse 34% 44% at 50% 47%, transparent 55%, rgba(0,0,0,0.55) 100%);
|
|
85
85
|
`;
|
|
86
86
|
// Oval guide — centred
|
|
87
87
|
const guideWrap = document.createElement("div");
|
|
88
|
-
guideWrap.style.cssText = `
|
|
89
|
-
position:absolute;top:50%;left:50%;
|
|
90
|
-
transform:translate(-50%,-52%);
|
|
91
|
-
width:64%;aspect-ratio:140/180;pointer-events:none;
|
|
92
|
-
filter:drop-shadow(0 0 18px rgba(59,130,246,0.35));
|
|
93
|
-
transition:filter 0.3s ease;
|
|
88
|
+
guideWrap.style.cssText = `
|
|
89
|
+
position:absolute;top:50%;left:50%;
|
|
90
|
+
transform:translate(-50%,-52%);
|
|
91
|
+
width:64%;aspect-ratio:140/180;pointer-events:none;
|
|
92
|
+
filter:drop-shadow(0 0 18px rgba(59,130,246,0.35));
|
|
93
|
+
transition:filter 0.3s ease;
|
|
94
94
|
`;
|
|
95
95
|
this.guideWrapEl = guideWrap;
|
|
96
96
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
@@ -179,49 +179,49 @@ class LivenessUI {
|
|
|
179
179
|
// Scan-line sweep — clipped to the oval, gives a visible "actively
|
|
180
180
|
// scanning" motion cue instead of a static outline.
|
|
181
181
|
const scanlineClipWrap = document.createElement("div");
|
|
182
|
-
scanlineClipWrap.style.cssText = `
|
|
183
|
-
position:absolute;inset:0;
|
|
184
|
-
clip-path:ellipse(45.7% 47.8% at 50% 50%);
|
|
185
|
-
overflow:hidden;
|
|
182
|
+
scanlineClipWrap.style.cssText = `
|
|
183
|
+
position:absolute;inset:0;
|
|
184
|
+
clip-path:ellipse(45.7% 47.8% at 50% 50%);
|
|
185
|
+
overflow:hidden;
|
|
186
186
|
`;
|
|
187
187
|
const scanline = document.createElement("div");
|
|
188
|
-
scanline.style.cssText = `
|
|
189
|
-
position:absolute;left:0;right:0;height:26%;top:0;
|
|
190
|
-
background:linear-gradient(180deg,transparent 0%,rgba(59,130,246,0.32) 45%,rgba(147,197,253,0.55) 50%,rgba(59,130,246,0.32) 55%,transparent 100%);
|
|
191
|
-
animation:sc-scanline-sweep 2.6s ease-in-out infinite;
|
|
188
|
+
scanline.style.cssText = `
|
|
189
|
+
position:absolute;left:0;right:0;height:26%;top:0;
|
|
190
|
+
background:linear-gradient(180deg,transparent 0%,rgba(59,130,246,0.32) 45%,rgba(147,197,253,0.55) 50%,rgba(59,130,246,0.32) 55%,transparent 100%);
|
|
191
|
+
animation:sc-scanline-sweep 2.6s ease-in-out infinite;
|
|
192
192
|
`;
|
|
193
193
|
this.scanlineEl = scanline;
|
|
194
194
|
scanlineClipWrap.appendChild(scanline);
|
|
195
195
|
guideWrap.appendChild(scanlineClipWrap);
|
|
196
196
|
// Instruction — top, gradient scrim
|
|
197
197
|
const camTop = document.createElement("div");
|
|
198
|
-
camTop.style.cssText = `
|
|
199
|
-
position:absolute;top:0;left:0;right:0;
|
|
200
|
-
background:linear-gradient(180deg,rgba(0,0,0,0.8) 0%,transparent 100%);
|
|
201
|
-
padding:14px 16px 32px;
|
|
198
|
+
camTop.style.cssText = `
|
|
199
|
+
position:absolute;top:0;left:0;right:0;
|
|
200
|
+
background:linear-gradient(180deg,rgba(0,0,0,0.8) 0%,transparent 100%);
|
|
201
|
+
padding:14px 16px 32px;
|
|
202
202
|
`;
|
|
203
203
|
this.instructionEl = document.createElement("div");
|
|
204
204
|
this.instructionEl.setAttribute("aria-live", "polite");
|
|
205
205
|
this.instructionEl.setAttribute("role", "status");
|
|
206
|
-
this.instructionEl.style.cssText = `
|
|
207
|
-
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
206
|
+
this.instructionEl.style.cssText = `
|
|
207
|
+
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
208
208
|
`;
|
|
209
209
|
this.instructionEl.textContent = challenge.instruction;
|
|
210
210
|
camTop.appendChild(this.instructionEl);
|
|
211
211
|
// Scan progress dots — bottom
|
|
212
212
|
const camBottom = document.createElement("div");
|
|
213
|
-
camBottom.style.cssText = `
|
|
214
|
-
position:absolute;bottom:0;left:0;right:0;
|
|
215
|
-
background:linear-gradient(0deg,rgba(0,0,0,0.7) 0%,transparent 100%);
|
|
216
|
-
padding:28px 10px 18px;display:flex;justify-content:center;
|
|
213
|
+
camBottom.style.cssText = `
|
|
214
|
+
position:absolute;bottom:0;left:0;right:0;
|
|
215
|
+
background:linear-gradient(0deg,rgba(0,0,0,0.7) 0%,transparent 100%);
|
|
216
|
+
padding:28px 10px 18px;display:flex;justify-content:center;
|
|
217
217
|
`;
|
|
218
218
|
this.dotsEl = document.createElement("div");
|
|
219
219
|
this.dotsEl.style.cssText = "display:flex;gap:6px;";
|
|
220
220
|
for (let i = 0; i < 3; i++) {
|
|
221
221
|
const dot = document.createElement("div");
|
|
222
|
-
dot.style.cssText = `
|
|
223
|
-
width:6px;height:6px;border-radius:50%;background:${this.colors.primary};
|
|
224
|
-
animation:sc-scan-dot 1.1s ease-in-out ${i * 0.18}s infinite;
|
|
222
|
+
dot.style.cssText = `
|
|
223
|
+
width:6px;height:6px;border-radius:50%;background:${this.colors.primary};
|
|
224
|
+
animation:sc-scan-dot 1.1s ease-in-out ${i * 0.18}s infinite;
|
|
225
225
|
`;
|
|
226
226
|
this.dotsEl.appendChild(dot);
|
|
227
227
|
}
|
|
@@ -233,10 +233,10 @@ class LivenessUI {
|
|
|
233
233
|
this.root.appendChild(camBottom);
|
|
234
234
|
// Result overlay
|
|
235
235
|
this.overlayEl = document.createElement("div");
|
|
236
|
-
this.overlayEl.style.cssText = `
|
|
237
|
-
position:absolute;inset:0;display:none;align-items:center;
|
|
238
|
-
justify-content:center;z-index:10;
|
|
239
|
-
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
|
236
|
+
this.overlayEl.style.cssText = `
|
|
237
|
+
position:absolute;inset:0;display:none;align-items:center;
|
|
238
|
+
justify-content:center;z-index:10;
|
|
239
|
+
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
|
240
240
|
`;
|
|
241
241
|
this.root.appendChild(this.overlayEl);
|
|
242
242
|
container.appendChild(this.root);
|
|
@@ -297,15 +297,15 @@ class LivenessUI {
|
|
|
297
297
|
}
|
|
298
298
|
const ok = status === "processing";
|
|
299
299
|
this.overlayEl.style.display = "flex";
|
|
300
|
-
this.overlayEl.innerHTML = `
|
|
301
|
-
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
302
|
-
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 16px;display:flex;align-items:center;justify-content:center;font-size:32px;
|
|
303
|
-
background:${ok ? "rgba(34,197,94,0.2)" : "rgba(239,68,68,0.2)"};
|
|
304
|
-
border:3px solid ${ok ? this.colors.success : this.colors.error};">
|
|
305
|
-
${ok ? "✓" : "✕"}
|
|
306
|
-
</div>
|
|
307
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
308
|
-
<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>
|
|
300
|
+
this.overlayEl.innerHTML = `
|
|
301
|
+
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
302
|
+
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 16px;display:flex;align-items:center;justify-content:center;font-size:32px;
|
|
303
|
+
background:${ok ? "rgba(34,197,94,0.2)" : "rgba(239,68,68,0.2)"};
|
|
304
|
+
border:3px solid ${ok ? this.colors.success : this.colors.error};">
|
|
305
|
+
${ok ? "✓" : "✕"}
|
|
306
|
+
</div>
|
|
307
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
308
|
+
<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>
|
|
309
309
|
</div>`;
|
|
310
310
|
setTimeout(resolve, 1800);
|
|
311
311
|
});
|
|
@@ -317,12 +317,12 @@ class LivenessUI {
|
|
|
317
317
|
return;
|
|
318
318
|
}
|
|
319
319
|
this.overlayEl.style.display = "flex";
|
|
320
|
-
this.overlayEl.innerHTML = `
|
|
321
|
-
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
322
|
-
<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 ${this.colors.warning};">⏱</div>
|
|
323
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">Time's Up</div>
|
|
324
|
-
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">We couldn't confirm liveness in time. Please try again.</div>
|
|
325
|
-
<button id="sc-retry-btn" style="padding:11px 28px;border-radius:10px;background:${this.colors.primary};color:#fff;border:none;cursor:pointer;font-size:14px;font-weight:600;width:100%;max-width:180px;">Try Again</button>
|
|
320
|
+
this.overlayEl.innerHTML = `
|
|
321
|
+
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
322
|
+
<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 ${this.colors.warning};">⏱</div>
|
|
323
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">Time's Up</div>
|
|
324
|
+
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">We couldn't confirm liveness in time. Please try again.</div>
|
|
325
|
+
<button id="sc-retry-btn" style="padding:11px 28px;border-radius:10px;background:${this.colors.primary};color:#fff;border:none;cursor:pointer;font-size:14px;font-weight:600;width:100%;max-width:180px;">Try Again</button>
|
|
326
326
|
</div>`;
|
|
327
327
|
const btn = this.overlayEl.querySelector("#sc-retry-btn");
|
|
328
328
|
if (btn) {
|
|
@@ -4632,7 +4632,10 @@ var SmartComplySDK = (() => {
|
|
|
4632
4632
|
this.blinkHoldCount = 0;
|
|
4633
4633
|
this.blinkDetected = false;
|
|
4634
4634
|
this.motionDetected = false;
|
|
4635
|
-
|
|
4635
|
+
// Running min/max yaw seen so far in the scan, not a single fixed
|
|
4636
|
+
// first-frame baseline (see check() below for why).
|
|
4637
|
+
this.minYaw = null;
|
|
4638
|
+
this.maxYaw = null;
|
|
4636
4639
|
}
|
|
4637
4640
|
check(result) {
|
|
4638
4641
|
if (!result.faceDetected) {
|
|
@@ -4652,9 +4655,9 @@ var SmartComplySDK = (() => {
|
|
|
4652
4655
|
}
|
|
4653
4656
|
if (!this.motionDetected) {
|
|
4654
4657
|
const yaw = this.getHeadYaw(result.transformMatrix);
|
|
4655
|
-
if (this.
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
+
if (this.minYaw === null || yaw < this.minYaw) this.minYaw = yaw;
|
|
4659
|
+
if (this.maxYaw === null || yaw > this.maxYaw) this.maxYaw = yaw;
|
|
4660
|
+
if (this.maxYaw - this.minYaw >= YAW_MOTION_THRESHOLD_DEG) {
|
|
4658
4661
|
this.motionDetected = true;
|
|
4659
4662
|
}
|
|
4660
4663
|
}
|
|
@@ -5779,7 +5782,7 @@ var SmartComplySDK = (() => {
|
|
|
5779
5782
|
* the request, so the resume_token has to be the one in use throughout.
|
|
5780
5783
|
*
|
|
5781
5784
|
* No expires_at is set here — the resume_token's own issuance response
|
|
5782
|
-
* only carries resume_expires_at (a
|
|
5785
|
+
* only carries resume_expires_at (a 24h invite-link window), not the
|
|
5783
5786
|
* session's own runtime expiry the way createSession() returns one. The
|
|
5784
5787
|
* backend still enforces the real expiry server-side regardless; this
|
|
5785
5788
|
* only means the client-side "session about to expire" warning timer
|
|
@@ -5808,6 +5811,22 @@ var SmartComplySDK = (() => {
|
|
|
5808
5811
|
this._sdkConfig = config;
|
|
5809
5812
|
return config;
|
|
5810
5813
|
}
|
|
5814
|
+
/**
|
|
5815
|
+
* Resolve a resume_token to the entry/step it's resuming, without
|
|
5816
|
+
* consuming the single-use resume binding (unlike create/, which does).
|
|
5817
|
+
* Only meaningful after resumeSession() — a normal createSession()
|
|
5818
|
+
* session was never issued for a resume, so this returns a 404
|
|
5819
|
+
* (NOT_A_RESUME_TOKEN) for it, same as the backend.
|
|
5820
|
+
*
|
|
5821
|
+
* GET /v1/sdk/liveness/resume-info/
|
|
5822
|
+
* Auth: x-access-token: <sessionToken>
|
|
5823
|
+
*/
|
|
5824
|
+
async getResumeInfo() {
|
|
5825
|
+
return this.http.sessionRequest(
|
|
5826
|
+
"GET",
|
|
5827
|
+
"/v1/sdk/liveness/resume-info/"
|
|
5828
|
+
);
|
|
5829
|
+
}
|
|
5811
5830
|
};
|
|
5812
5831
|
/**
|
|
5813
5832
|
* Package version, hardcoded to match package.json — bump this alongside
|
|
@@ -5818,7 +5837,7 @@ var SmartComplySDK = (() => {
|
|
|
5818
5837
|
* CDN tag they installed with (e.g. @1, which floats to the latest
|
|
5819
5838
|
* matching release) actually resolved to the version they expect.
|
|
5820
5839
|
*/
|
|
5821
|
-
_SmartComply.VERSION = "1.0.
|
|
5840
|
+
_SmartComply.VERSION = "1.0.91";
|
|
5822
5841
|
var SmartComply = _SmartComply;
|
|
5823
5842
|
|
|
5824
5843
|
// src/flow/theme.ts
|
|
@@ -7342,6 +7361,9 @@ var SmartComplySDK = (() => {
|
|
|
7342
7361
|
function isUploadFieldType(type) {
|
|
7343
7362
|
return type === "upload" || type === "image" || type === "file";
|
|
7344
7363
|
}
|
|
7364
|
+
function isResumeLinkErrorMessage(message) {
|
|
7365
|
+
return /resume link|resubmit link/i.test(message);
|
|
7366
|
+
}
|
|
7345
7367
|
function resolveIdTypeInfo(typeName, isDocumentFlow) {
|
|
7346
7368
|
const u2 = typeName.toUpperCase();
|
|
7347
7369
|
const advanced = u2.includes("ADVANCED");
|
|
@@ -7819,11 +7841,85 @@ var SmartComplySDK = (() => {
|
|
|
7819
7841
|
this.brandLogoImg.style.display = "block";
|
|
7820
7842
|
}
|
|
7821
7843
|
if (this.isDestroyed) return;
|
|
7822
|
-
this.
|
|
7844
|
+
if (this.options.resumeToken) {
|
|
7845
|
+
const resumeStep = await this._resolveResumeStep();
|
|
7846
|
+
if (resumeStep !== null) this.showStep(resumeStep);
|
|
7847
|
+
} else {
|
|
7848
|
+
this.showStep("welcome");
|
|
7849
|
+
}
|
|
7823
7850
|
} catch (err) {
|
|
7824
7851
|
this.showErrorStep(err.message || "Failed to initialize. Please try again.");
|
|
7825
7852
|
}
|
|
7826
7853
|
}
|
|
7854
|
+
// Resolves a resume_token to the step the customer should land on,
|
|
7855
|
+
// pre-selecting the same country/ID type the original attempt used (the
|
|
7856
|
+
// same state renderCountrySelect/renderIdTypeSelect's own click handlers
|
|
7857
|
+
// set) so document_capture/liveness render correctly instead of assuming
|
|
7858
|
+
// state that was never set. Falls back to "welcome" on anything that
|
|
7859
|
+
// doesn't resolve cleanly for an UNCERTAIN reason (network error, timeout)
|
|
7860
|
+
// — this is a UX improvement, not a requirement: the backend's own
|
|
7861
|
+
// resume-match at create/ time still works correctly even if the widget
|
|
7862
|
+
// can't figure out which screen to pre-select.
|
|
7863
|
+
//
|
|
7864
|
+
// Returns null instead when the lookup fails for a CERTAIN, known reason
|
|
7865
|
+
// — the resume link itself is provably expired or already used
|
|
7866
|
+
// (RESUME_TARGET_UNAVAILABLE) or was never a resume link at all
|
|
7867
|
+
// (NOT_A_RESUME_TOKEN). Unlike a transient failure, there is no "try the
|
|
7868
|
+
// real request and it'll still work" fallback for these: create/'s own
|
|
7869
|
+
// resume-match reads the exact same resume_target_entry binding this
|
|
7870
|
+
// lookup just found missing/invalid, so falling through to a generic
|
|
7871
|
+
// welcome screen would silently walk the customer into a brand-new,
|
|
7872
|
+
// unrelated verification attempt with no explanation — worse than telling
|
|
7873
|
+
// them plainly that their link no longer works. The caller shows this
|
|
7874
|
+
// error itself (via showErrorStep, called here directly) rather than
|
|
7875
|
+
// rendering a step.
|
|
7876
|
+
async _resolveResumeStep() {
|
|
7877
|
+
try {
|
|
7878
|
+
const info = await this.sdk.getResumeInfo();
|
|
7879
|
+
if (info.verification_type === "liveness_only" || info.failure_stage === "liveness" || // data_match is always a selfie-vs-identity-photo mismatch (see the
|
|
7880
|
+
// backend's own comment in _process_identity_face_match) — fixed by
|
|
7881
|
+
// redoing the selfie/video, same as a "liveness" failure. There is
|
|
7882
|
+
// no document step in data verification at all, so this can never
|
|
7883
|
+
// legitimately need document_capture.
|
|
7884
|
+
info.failure_stage === "data_match") {
|
|
7885
|
+
this.selectedCountry = info.country || this.selectedCountry || "global";
|
|
7886
|
+
return "liveness";
|
|
7887
|
+
}
|
|
7888
|
+
if (info.failure_stage === "document" && info.country && info.identifier_type) {
|
|
7889
|
+
const channels = this.sdkConfig?.channels || {};
|
|
7890
|
+
const idTypes = channels[info.country] || [];
|
|
7891
|
+
const match = idTypes.find((idType) => {
|
|
7892
|
+
const code = typeof idType === "string" ? idType : idType.code || idType.name;
|
|
7893
|
+
return code === info.identifier_type;
|
|
7894
|
+
});
|
|
7895
|
+
if (match) {
|
|
7896
|
+
const typeName = typeof match === "string" ? match : match.name;
|
|
7897
|
+
const typeCode = typeof match === "string" ? match : match.code || match.name;
|
|
7898
|
+
const channelFields = typeof match !== "string" ? match.fields || [] : [];
|
|
7899
|
+
const isDocFlow = channelFields.some((f2) => isUploadFieldType(f2.type)) || this.isDocumentFlow();
|
|
7900
|
+
const info2 = resolveIdTypeInfo(typeName, isDocFlow);
|
|
7901
|
+
const requiresBackFromBackend = typeof match !== "string" ? match.requires_back_side : void 0;
|
|
7902
|
+
this.selectedCountry = info.country;
|
|
7903
|
+
this.selectedIdType = typeCode;
|
|
7904
|
+
this.selectedIdTypeName = typeName;
|
|
7905
|
+
this.selectedRequiresBack = requiresBackFromBackend ?? info2.requiresBack;
|
|
7906
|
+
if (typeof match !== "string") {
|
|
7907
|
+
this.selectedChannelId = match.id;
|
|
7908
|
+
this.selectedChannelFields = match.fields || [];
|
|
7909
|
+
}
|
|
7910
|
+
return "document_capture";
|
|
7911
|
+
}
|
|
7912
|
+
}
|
|
7913
|
+
return "welcome";
|
|
7914
|
+
} catch (err) {
|
|
7915
|
+
const code = err instanceof SDKError ? err.errorCode : void 0;
|
|
7916
|
+
if (code === "RESUME_TARGET_UNAVAILABLE" || code === "NOT_A_RESUME_TOKEN") {
|
|
7917
|
+
this.showErrorStep("This resume link has expired or is no longer valid.");
|
|
7918
|
+
return null;
|
|
7919
|
+
}
|
|
7920
|
+
return "welcome";
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7827
7923
|
// Defers the expiry warning while the user is on a camera-active step
|
|
7828
7924
|
// (document_capture/liveness) — showing it there would blow away a live
|
|
7829
7925
|
// video element and in-progress MediaRecorder mid-scan.
|
|
@@ -9143,14 +9239,15 @@ var SmartComplySDK = (() => {
|
|
|
9143
9239
|
`;
|
|
9144
9240
|
const isCameraErr = /camera|permission|notallowed|notfound/i.test(message);
|
|
9145
9241
|
const isNetworkErr = /network|timeout|connection|fetch/i.test(message);
|
|
9146
|
-
const
|
|
9242
|
+
const isResumeLinkErr = isResumeLinkErrorMessage(message);
|
|
9243
|
+
const isAuthErr = !isResumeLinkErr && /session.*expired|token.*expired|authentication/i.test(message);
|
|
9147
9244
|
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
9148
9245
|
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
9149
9246
|
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
9150
9247
|
const isBillingErr = /wallet|balance|insufficient.*fund|payment.*required/i.test(message);
|
|
9151
|
-
const errorIconName = isCameraErr ? "camera" : isNetworkErr ? "network" : isAuthErr ? "clock" : isDocQuality ? "search" : isExpiredDoc ? "calendar" : isFaceMatch ? "face" : isBillingErr ? "warning" : "close";
|
|
9152
|
-
const errorTitle = isCameraErr ? "Camera Access Required" : isNetworkErr ? "Connection Problem" : isAuthErr ? "Session Expired" : isDocQuality ? "Document Photo Unclear" : isExpiredDoc ? "Document Expired" : isFaceMatch ? "Face Match Failed" : isBillingErr ? "Verification Unavailable" : "Verification Unsuccessful";
|
|
9153
|
-
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." : isBillingErr ? "Verification is temporarily unavailable. Please contact support." : _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
|
|
9248
|
+
const errorIconName = isCameraErr ? "camera" : isNetworkErr ? "network" : isResumeLinkErr ? "clock" : isAuthErr ? "clock" : isDocQuality ? "search" : isExpiredDoc ? "calendar" : isFaceMatch ? "face" : isBillingErr ? "warning" : "close";
|
|
9249
|
+
const errorTitle = isCameraErr ? "Camera Access Required" : isNetworkErr ? "Connection Problem" : isResumeLinkErr ? "Link No Longer Valid" : isAuthErr ? "Session Expired" : isDocQuality ? "Document Photo Unclear" : isExpiredDoc ? "Document Expired" : isFaceMatch ? "Face Match Failed" : isBillingErr ? "Verification Unavailable" : "Verification Unsuccessful";
|
|
9250
|
+
const userMessage = isCameraErr ? "Please allow camera access in your browser settings and try again." : isNetworkErr ? "Check your internet connection and try again." : isResumeLinkErr ? "This link has expired or has already been used. Please contact us to request a new one." : 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." : isBillingErr ? "Verification is temporarily unavailable. Please contact support." : _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
|
|
9154
9251
|
const iconWrap = document.createElement("div");
|
|
9155
9252
|
iconWrap.style.cssText = `
|
|
9156
9253
|
width:72px;height:72px;border-radius:50%;
|