smartcomply-web-sdk 1.0.90 → 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 +2 -2
- package/dist/client/Smartcomply.js +2 -2
- 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 +2 -2
- package/dist/esm/client/Smartcomply.js +2 -2
- package/dist/esm/flow/SmartComplyFlow.d.ts +10 -0
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +92 -32
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/flow/spinner.js +14 -14
- package/dist/esm/modules/liveness/LivenessUI.js +83 -83
- package/dist/flow/SmartComplyFlow.d.ts +10 -0
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +93 -32
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/flow/spinner.js +14 -14
- package/dist/modules/liveness/LivenessUI.js +83 -83
- package/dist/smartcomply.browser.js +46 -15
- package/dist/smartcomply.browser.js.map +2 -2
- 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
|
@@ -3,30 +3,30 @@ function injectLivenessKeyframes() {
|
|
|
3
3
|
return;
|
|
4
4
|
const s = document.createElement("style");
|
|
5
5
|
s.id = "sc-liveness-kf";
|
|
6
|
-
s.textContent = `
|
|
7
|
-
@keyframes sc-pop-in {
|
|
8
|
-
0% { transform:scale(0.7); opacity:0; }
|
|
9
|
-
60% { transform:scale(1.05); }
|
|
10
|
-
100% { transform:scale(1); opacity:1; }
|
|
11
|
-
}
|
|
12
|
-
@keyframes sc-scan-dot {
|
|
13
|
-
0%,100% { opacity:0.3; transform:scale(0.85); }
|
|
14
|
-
50% { opacity:1; transform:scale(1); }
|
|
15
|
-
}
|
|
16
|
-
@keyframes sc-scanline-sweep {
|
|
17
|
-
0% { transform:translateY(-4%); opacity:0; }
|
|
18
|
-
8% { opacity:0.9; }
|
|
19
|
-
92% { opacity:0.9; }
|
|
20
|
-
100% { transform:translateY(104%); opacity:0; }
|
|
21
|
-
}
|
|
22
|
-
@keyframes sc-corner-pulse {
|
|
23
|
-
0%,100% { opacity:0.55; }
|
|
24
|
-
50% { opacity:1; }
|
|
25
|
-
}
|
|
26
|
-
@keyframes sc-oval-breathe {
|
|
27
|
-
0%,100% { opacity:0.5; }
|
|
28
|
-
50% { opacity:0.85; }
|
|
29
|
-
}
|
|
6
|
+
s.textContent = `
|
|
7
|
+
@keyframes sc-pop-in {
|
|
8
|
+
0% { transform:scale(0.7); opacity:0; }
|
|
9
|
+
60% { transform:scale(1.05); }
|
|
10
|
+
100% { transform:scale(1); opacity:1; }
|
|
11
|
+
}
|
|
12
|
+
@keyframes sc-scan-dot {
|
|
13
|
+
0%,100% { opacity:0.3; transform:scale(0.85); }
|
|
14
|
+
50% { opacity:1; transform:scale(1); }
|
|
15
|
+
}
|
|
16
|
+
@keyframes sc-scanline-sweep {
|
|
17
|
+
0% { transform:translateY(-4%); opacity:0; }
|
|
18
|
+
8% { opacity:0.9; }
|
|
19
|
+
92% { opacity:0.9; }
|
|
20
|
+
100% { transform:translateY(104%); opacity:0; }
|
|
21
|
+
}
|
|
22
|
+
@keyframes sc-corner-pulse {
|
|
23
|
+
0%,100% { opacity:0.55; }
|
|
24
|
+
50% { opacity:1; }
|
|
25
|
+
}
|
|
26
|
+
@keyframes sc-oval-breathe {
|
|
27
|
+
0%,100% { opacity:0.5; }
|
|
28
|
+
50% { opacity:0.85; }
|
|
29
|
+
}
|
|
30
30
|
`;
|
|
31
31
|
document.head.appendChild(s);
|
|
32
32
|
}
|
|
@@ -56,38 +56,38 @@ export class LivenessUI {
|
|
|
56
56
|
mount(container, challenge) {
|
|
57
57
|
injectLivenessKeyframes();
|
|
58
58
|
this.root = document.createElement("div");
|
|
59
|
-
this.root.style.cssText = `
|
|
60
|
-
position:relative;width:100%;max-width:420px;margin:0 auto;
|
|
61
|
-
background:#0a0a0a;border-radius:16px;overflow:hidden;
|
|
62
|
-
font-family:system-ui,-apple-system,sans-serif;
|
|
63
|
-
box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
|
64
|
-
aspect-ratio:3/4;
|
|
59
|
+
this.root.style.cssText = `
|
|
60
|
+
position:relative;width:100%;max-width:420px;margin:0 auto;
|
|
61
|
+
background:#0a0a0a;border-radius:16px;overflow:hidden;
|
|
62
|
+
font-family:system-ui,-apple-system,sans-serif;
|
|
63
|
+
box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
|
64
|
+
aspect-ratio:3/4;
|
|
65
65
|
`;
|
|
66
66
|
this.videoEl = document.createElement("video");
|
|
67
67
|
this.videoEl.autoplay = true;
|
|
68
68
|
this.videoEl.playsInline = true;
|
|
69
69
|
this.videoEl.muted = true;
|
|
70
70
|
this.videoEl.setAttribute("aria-label", "Live camera preview for face verification");
|
|
71
|
-
this.videoEl.style.cssText = `
|
|
72
|
-
width:100%;height:100%;display:block;
|
|
73
|
-
filter:brightness(1.03) contrast(1.03);
|
|
74
|
-
transform:scaleX(-1);object-fit:cover;
|
|
71
|
+
this.videoEl.style.cssText = `
|
|
72
|
+
width:100%;height:100%;display:block;
|
|
73
|
+
filter:brightness(1.03) contrast(1.03);
|
|
74
|
+
transform:scaleX(-1);object-fit:cover;
|
|
75
75
|
`;
|
|
76
76
|
// Dim everything outside the oval so the guide reads as a scan aperture
|
|
77
77
|
// rather than a shape floating over the raw feed.
|
|
78
78
|
const vignette = document.createElement("div");
|
|
79
|
-
vignette.style.cssText = `
|
|
80
|
-
position:absolute;inset:0;pointer-events:none;
|
|
81
|
-
background:radial-gradient(ellipse 34% 44% at 50% 47%, transparent 55%, rgba(0,0,0,0.55) 100%);
|
|
79
|
+
vignette.style.cssText = `
|
|
80
|
+
position:absolute;inset:0;pointer-events:none;
|
|
81
|
+
background:radial-gradient(ellipse 34% 44% at 50% 47%, transparent 55%, rgba(0,0,0,0.55) 100%);
|
|
82
82
|
`;
|
|
83
83
|
// Oval guide — centred
|
|
84
84
|
const guideWrap = document.createElement("div");
|
|
85
|
-
guideWrap.style.cssText = `
|
|
86
|
-
position:absolute;top:50%;left:50%;
|
|
87
|
-
transform:translate(-50%,-52%);
|
|
88
|
-
width:64%;aspect-ratio:140/180;pointer-events:none;
|
|
89
|
-
filter:drop-shadow(0 0 18px rgba(59,130,246,0.35));
|
|
90
|
-
transition:filter 0.3s ease;
|
|
85
|
+
guideWrap.style.cssText = `
|
|
86
|
+
position:absolute;top:50%;left:50%;
|
|
87
|
+
transform:translate(-50%,-52%);
|
|
88
|
+
width:64%;aspect-ratio:140/180;pointer-events:none;
|
|
89
|
+
filter:drop-shadow(0 0 18px rgba(59,130,246,0.35));
|
|
90
|
+
transition:filter 0.3s ease;
|
|
91
91
|
`;
|
|
92
92
|
this.guideWrapEl = guideWrap;
|
|
93
93
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
@@ -176,49 +176,49 @@ export class LivenessUI {
|
|
|
176
176
|
// Scan-line sweep — clipped to the oval, gives a visible "actively
|
|
177
177
|
// scanning" motion cue instead of a static outline.
|
|
178
178
|
const scanlineClipWrap = document.createElement("div");
|
|
179
|
-
scanlineClipWrap.style.cssText = `
|
|
180
|
-
position:absolute;inset:0;
|
|
181
|
-
clip-path:ellipse(45.7% 47.8% at 50% 50%);
|
|
182
|
-
overflow:hidden;
|
|
179
|
+
scanlineClipWrap.style.cssText = `
|
|
180
|
+
position:absolute;inset:0;
|
|
181
|
+
clip-path:ellipse(45.7% 47.8% at 50% 50%);
|
|
182
|
+
overflow:hidden;
|
|
183
183
|
`;
|
|
184
184
|
const scanline = document.createElement("div");
|
|
185
|
-
scanline.style.cssText = `
|
|
186
|
-
position:absolute;left:0;right:0;height:26%;top:0;
|
|
187
|
-
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%);
|
|
188
|
-
animation:sc-scanline-sweep 2.6s ease-in-out infinite;
|
|
185
|
+
scanline.style.cssText = `
|
|
186
|
+
position:absolute;left:0;right:0;height:26%;top:0;
|
|
187
|
+
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%);
|
|
188
|
+
animation:sc-scanline-sweep 2.6s ease-in-out infinite;
|
|
189
189
|
`;
|
|
190
190
|
this.scanlineEl = scanline;
|
|
191
191
|
scanlineClipWrap.appendChild(scanline);
|
|
192
192
|
guideWrap.appendChild(scanlineClipWrap);
|
|
193
193
|
// Instruction — top, gradient scrim
|
|
194
194
|
const camTop = document.createElement("div");
|
|
195
|
-
camTop.style.cssText = `
|
|
196
|
-
position:absolute;top:0;left:0;right:0;
|
|
197
|
-
background:linear-gradient(180deg,rgba(0,0,0,0.8) 0%,transparent 100%);
|
|
198
|
-
padding:14px 16px 32px;
|
|
195
|
+
camTop.style.cssText = `
|
|
196
|
+
position:absolute;top:0;left:0;right:0;
|
|
197
|
+
background:linear-gradient(180deg,rgba(0,0,0,0.8) 0%,transparent 100%);
|
|
198
|
+
padding:14px 16px 32px;
|
|
199
199
|
`;
|
|
200
200
|
this.instructionEl = document.createElement("div");
|
|
201
201
|
this.instructionEl.setAttribute("aria-live", "polite");
|
|
202
202
|
this.instructionEl.setAttribute("role", "status");
|
|
203
|
-
this.instructionEl.style.cssText = `
|
|
204
|
-
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
203
|
+
this.instructionEl.style.cssText = `
|
|
204
|
+
color:#fff;font-size:14px;font-weight:600;text-align:center;line-height:1.4;
|
|
205
205
|
`;
|
|
206
206
|
this.instructionEl.textContent = challenge.instruction;
|
|
207
207
|
camTop.appendChild(this.instructionEl);
|
|
208
208
|
// Scan progress dots — bottom
|
|
209
209
|
const camBottom = document.createElement("div");
|
|
210
|
-
camBottom.style.cssText = `
|
|
211
|
-
position:absolute;bottom:0;left:0;right:0;
|
|
212
|
-
background:linear-gradient(0deg,rgba(0,0,0,0.7) 0%,transparent 100%);
|
|
213
|
-
padding:28px 10px 18px;display:flex;justify-content:center;
|
|
210
|
+
camBottom.style.cssText = `
|
|
211
|
+
position:absolute;bottom:0;left:0;right:0;
|
|
212
|
+
background:linear-gradient(0deg,rgba(0,0,0,0.7) 0%,transparent 100%);
|
|
213
|
+
padding:28px 10px 18px;display:flex;justify-content:center;
|
|
214
214
|
`;
|
|
215
215
|
this.dotsEl = document.createElement("div");
|
|
216
216
|
this.dotsEl.style.cssText = "display:flex;gap:6px;";
|
|
217
217
|
for (let i = 0; i < 3; i++) {
|
|
218
218
|
const dot = document.createElement("div");
|
|
219
|
-
dot.style.cssText = `
|
|
220
|
-
width:6px;height:6px;border-radius:50%;background:${this.colors.primary};
|
|
221
|
-
animation:sc-scan-dot 1.1s ease-in-out ${i * 0.18}s infinite;
|
|
219
|
+
dot.style.cssText = `
|
|
220
|
+
width:6px;height:6px;border-radius:50%;background:${this.colors.primary};
|
|
221
|
+
animation:sc-scan-dot 1.1s ease-in-out ${i * 0.18}s infinite;
|
|
222
222
|
`;
|
|
223
223
|
this.dotsEl.appendChild(dot);
|
|
224
224
|
}
|
|
@@ -230,10 +230,10 @@ export class LivenessUI {
|
|
|
230
230
|
this.root.appendChild(camBottom);
|
|
231
231
|
// Result overlay
|
|
232
232
|
this.overlayEl = document.createElement("div");
|
|
233
|
-
this.overlayEl.style.cssText = `
|
|
234
|
-
position:absolute;inset:0;display:none;align-items:center;
|
|
235
|
-
justify-content:center;z-index:10;
|
|
236
|
-
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
|
233
|
+
this.overlayEl.style.cssText = `
|
|
234
|
+
position:absolute;inset:0;display:none;align-items:center;
|
|
235
|
+
justify-content:center;z-index:10;
|
|
236
|
+
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
|
237
237
|
`;
|
|
238
238
|
this.root.appendChild(this.overlayEl);
|
|
239
239
|
container.appendChild(this.root);
|
|
@@ -294,15 +294,15 @@ export class LivenessUI {
|
|
|
294
294
|
}
|
|
295
295
|
const ok = status === "processing";
|
|
296
296
|
this.overlayEl.style.display = "flex";
|
|
297
|
-
this.overlayEl.innerHTML = `
|
|
298
|
-
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
299
|
-
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 16px;display:flex;align-items:center;justify-content:center;font-size:32px;
|
|
300
|
-
background:${ok ? "rgba(34,197,94,0.2)" : "rgba(239,68,68,0.2)"};
|
|
301
|
-
border:3px solid ${ok ? this.colors.success : this.colors.error};">
|
|
302
|
-
${ok ? "✓" : "✕"}
|
|
303
|
-
</div>
|
|
304
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
305
|
-
<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>
|
|
297
|
+
this.overlayEl.innerHTML = `
|
|
298
|
+
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
299
|
+
<div style="width:72px;height:72px;border-radius:50%;margin:0 auto 16px;display:flex;align-items:center;justify-content:center;font-size:32px;
|
|
300
|
+
background:${ok ? "rgba(34,197,94,0.2)" : "rgba(239,68,68,0.2)"};
|
|
301
|
+
border:3px solid ${ok ? this.colors.success : this.colors.error};">
|
|
302
|
+
${ok ? "✓" : "✕"}
|
|
303
|
+
</div>
|
|
304
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">${ok ? "Verification submitted" : "Submission failed"}</div>
|
|
305
|
+
<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>
|
|
306
306
|
</div>`;
|
|
307
307
|
setTimeout(resolve, 1800);
|
|
308
308
|
});
|
|
@@ -314,12 +314,12 @@ export class LivenessUI {
|
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
this.overlayEl.style.display = "flex";
|
|
317
|
-
this.overlayEl.innerHTML = `
|
|
318
|
-
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
319
|
-
<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>
|
|
320
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">Time's Up</div>
|
|
321
|
-
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">We couldn't confirm liveness in time. Please try again.</div>
|
|
322
|
-
<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>
|
|
317
|
+
this.overlayEl.innerHTML = `
|
|
318
|
+
<div style="text-align:center;color:#fff;animation:sc-pop-in 0.35s ease;padding:24px;">
|
|
319
|
+
<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>
|
|
320
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:8px;">Time's Up</div>
|
|
321
|
+
<div style="font-size:12px;opacity:0.6;margin-bottom:18px;">We couldn't confirm liveness in time. Please try again.</div>
|
|
322
|
+
<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>
|
|
323
323
|
</div>`;
|
|
324
324
|
const btn = this.overlayEl.querySelector("#sc-retry-btn");
|
|
325
325
|
if (btn) {
|
|
@@ -40,6 +40,16 @@ export interface FlowResult {
|
|
|
40
40
|
submittedAt: string | null;
|
|
41
41
|
verificationResult?: VerifyIdentityResponse;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether a showErrorStep() message is about an expired/invalid resume or
|
|
45
|
+
* resubmit link (RESUME_TARGET_UNAVAILABLE / NOT_A_RESUME_TOKEN — see
|
|
46
|
+
* SmartComplyFlow._resolveResumeStep), rather than a plain session timeout.
|
|
47
|
+
* Extracted to a standalone function so this specific classification is
|
|
48
|
+
* independently testable, same as resolveIdTypeInfo below — showErrorStep's
|
|
49
|
+
* own regex-based classification block is otherwise tightly coupled to DOM
|
|
50
|
+
* rendering it doesn't make sense to extract wholesale for this fix.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isResumeLinkErrorMessage(message: string): boolean;
|
|
43
53
|
export declare function resolveIdTypeInfo(typeName: string, isDocumentFlow: boolean): {
|
|
44
54
|
icon: string;
|
|
45
55
|
desc: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIxF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAoB,YAAY,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SmartComplyFlow = void 0;
|
|
4
|
+
exports.isResumeLinkErrorMessage = isResumeLinkErrorMessage;
|
|
4
5
|
exports.resolveIdTypeInfo = resolveIdTypeInfo;
|
|
5
6
|
const Smartcomply_1 = require("../client/Smartcomply");
|
|
6
7
|
const theme_1 = require("./theme");
|
|
@@ -10,6 +11,7 @@ const documentRules_1 = require("../utils/documentRules");
|
|
|
10
11
|
const constants_1 = require("../utils/constants");
|
|
11
12
|
const icons_1 = require("./icons");
|
|
12
13
|
const spinner_1 = require("./spinner");
|
|
14
|
+
const SDKError_1 = require("../errors/SDKError");
|
|
13
15
|
// A dropped camera/network/upload error is not the user's fault and must not
|
|
14
16
|
// cost them a retry attempt — only a genuine verification outcome should.
|
|
15
17
|
// Classified by message rather than a typed error class since the errors
|
|
@@ -70,6 +72,18 @@ const COUNTRY_LABELS = {
|
|
|
70
72
|
function isUploadFieldType(type) {
|
|
71
73
|
return type === "upload" || type === "image" || type === "file";
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether a showErrorStep() message is about an expired/invalid resume or
|
|
77
|
+
* resubmit link (RESUME_TARGET_UNAVAILABLE / NOT_A_RESUME_TOKEN — see
|
|
78
|
+
* SmartComplyFlow._resolveResumeStep), rather than a plain session timeout.
|
|
79
|
+
* Extracted to a standalone function so this specific classification is
|
|
80
|
+
* independently testable, same as resolveIdTypeInfo below — showErrorStep's
|
|
81
|
+
* own regex-based classification block is otherwise tightly coupled to DOM
|
|
82
|
+
* rendering it doesn't make sense to extract wholesale for this fix.
|
|
83
|
+
*/
|
|
84
|
+
function isResumeLinkErrorMessage(message) {
|
|
85
|
+
return /resume link|resubmit link/i.test(message);
|
|
86
|
+
}
|
|
73
87
|
function resolveIdTypeInfo(typeName, isDocumentFlow) {
|
|
74
88
|
const u = typeName.toUpperCase();
|
|
75
89
|
const advanced = u.includes("ADVANCED");
|
|
@@ -658,7 +672,18 @@ class SmartComplyFlow {
|
|
|
658
672
|
}
|
|
659
673
|
if (this.isDestroyed)
|
|
660
674
|
return;
|
|
661
|
-
|
|
675
|
+
if (this.options.resumeToken) {
|
|
676
|
+
// null means _resolveResumeStep already rendered an error itself
|
|
677
|
+
// (a provably expired/invalid resume link — see that method) and
|
|
678
|
+
// there is no step left to show; calling showStep() in that case
|
|
679
|
+
// would wipe the error message straight back out.
|
|
680
|
+
const resumeStep = await this._resolveResumeStep();
|
|
681
|
+
if (resumeStep !== null)
|
|
682
|
+
this.showStep(resumeStep);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
this.showStep("welcome");
|
|
686
|
+
}
|
|
662
687
|
}
|
|
663
688
|
catch (err) {
|
|
664
689
|
this.showErrorStep(err.message || "Failed to initialize. Please try again.");
|
|
@@ -669,9 +694,23 @@ class SmartComplyFlow {
|
|
|
669
694
|
// same state renderCountrySelect/renderIdTypeSelect's own click handlers
|
|
670
695
|
// set) so document_capture/liveness render correctly instead of assuming
|
|
671
696
|
// state that was never set. Falls back to "welcome" on anything that
|
|
672
|
-
// doesn't resolve cleanly
|
|
673
|
-
//
|
|
674
|
-
//
|
|
697
|
+
// doesn't resolve cleanly for an UNCERTAIN reason (network error, timeout)
|
|
698
|
+
// — this is a UX improvement, not a requirement: the backend's own
|
|
699
|
+
// resume-match at create/ time still works correctly even if the widget
|
|
700
|
+
// can't figure out which screen to pre-select.
|
|
701
|
+
//
|
|
702
|
+
// Returns null instead when the lookup fails for a CERTAIN, known reason
|
|
703
|
+
// — the resume link itself is provably expired or already used
|
|
704
|
+
// (RESUME_TARGET_UNAVAILABLE) or was never a resume link at all
|
|
705
|
+
// (NOT_A_RESUME_TOKEN). Unlike a transient failure, there is no "try the
|
|
706
|
+
// real request and it'll still work" fallback for these: create/'s own
|
|
707
|
+
// resume-match reads the exact same resume_target_entry binding this
|
|
708
|
+
// lookup just found missing/invalid, so falling through to a generic
|
|
709
|
+
// welcome screen would silently walk the customer into a brand-new,
|
|
710
|
+
// unrelated verification attempt with no explanation — worse than telling
|
|
711
|
+
// them plainly that their link no longer works. The caller shows this
|
|
712
|
+
// error itself (via showErrorStep, called here directly) rather than
|
|
713
|
+
// rendering a step.
|
|
675
714
|
async _resolveResumeStep() {
|
|
676
715
|
try {
|
|
677
716
|
const info = await this.sdk.getResumeInfo();
|
|
@@ -723,10 +762,21 @@ class SmartComplyFlow {
|
|
|
723
762
|
// server-side regardless of which screen the widget shows first.
|
|
724
763
|
return "welcome";
|
|
725
764
|
}
|
|
726
|
-
catch {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
765
|
+
catch (err) {
|
|
766
|
+
const code = err instanceof SDKError_1.SDKError ? err.errorCode : undefined;
|
|
767
|
+
if (code === "RESUME_TARGET_UNAVAILABLE" || code === "NOT_A_RESUME_TOKEN") {
|
|
768
|
+
// A certain, known failure — this specific link is provably no
|
|
769
|
+
// longer usable (already used, expired, or was never a resume link
|
|
770
|
+
// at all). Tell the customer plainly rather than silently starting
|
|
771
|
+
// a new, unrelated verification on a welcome screen with no
|
|
772
|
+
// explanation — see this method's own docstring above.
|
|
773
|
+
this.showErrorStep("This resume link has expired or is no longer valid.");
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
// Anything else (network error, timeout, an SDKError with a
|
|
777
|
+
// different/absent code) — never block the flow on this lookup.
|
|
778
|
+
// create/'s own resume-match still runs correctly once the customer
|
|
779
|
+
// gets there, so falling through to a generic welcome screen is safe.
|
|
730
780
|
return "welcome";
|
|
731
781
|
}
|
|
732
782
|
}
|
|
@@ -2334,7 +2384,14 @@ class SmartComplyFlow {
|
|
|
2334
2384
|
// Classify the error into a user-friendly category
|
|
2335
2385
|
const isCameraErr = /camera|permission|notallowed|notfound/i.test(message);
|
|
2336
2386
|
const isNetworkErr = /network|timeout|connection|fetch/i.test(message);
|
|
2337
|
-
|
|
2387
|
+
// Checked before the generic isAuthErr below — a resume/resubmit link
|
|
2388
|
+
// (RESUME_TARGET_UNAVAILABLE / NOT_A_RESUME_TOKEN from the backend, see
|
|
2389
|
+
// _resolveResumeStep) also matches "token...expired"/"link" wording, but
|
|
2390
|
+
// the corrective action is different from a plain session timeout: the
|
|
2391
|
+
// customer can't just "start again" themselves, they need a fresh link
|
|
2392
|
+
// from whoever sent them this one.
|
|
2393
|
+
const isResumeLinkErr = isResumeLinkErrorMessage(message);
|
|
2394
|
+
const isAuthErr = !isResumeLinkErr && /session.*expired|token.*expired|authentication/i.test(message);
|
|
2338
2395
|
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
2339
2396
|
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
2340
2397
|
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
@@ -2344,36 +2401,40 @@ class SmartComplyFlow {
|
|
|
2344
2401
|
const isBillingErr = /wallet|balance|insufficient.*fund|payment.*required/i.test(message);
|
|
2345
2402
|
const errorIconName = isCameraErr ? "camera"
|
|
2346
2403
|
: isNetworkErr ? "network"
|
|
2347
|
-
:
|
|
2348
|
-
:
|
|
2349
|
-
:
|
|
2350
|
-
:
|
|
2351
|
-
:
|
|
2352
|
-
: "
|
|
2404
|
+
: isResumeLinkErr ? "clock"
|
|
2405
|
+
: isAuthErr ? "clock"
|
|
2406
|
+
: isDocQuality ? "search"
|
|
2407
|
+
: isExpiredDoc ? "calendar"
|
|
2408
|
+
: isFaceMatch ? "face"
|
|
2409
|
+
: isBillingErr ? "warning"
|
|
2410
|
+
: "close";
|
|
2353
2411
|
const errorTitle = isCameraErr ? "Camera Access Required"
|
|
2354
2412
|
: isNetworkErr ? "Connection Problem"
|
|
2355
|
-
:
|
|
2356
|
-
:
|
|
2357
|
-
:
|
|
2358
|
-
:
|
|
2359
|
-
:
|
|
2360
|
-
: "Verification
|
|
2413
|
+
: isResumeLinkErr ? "Link No Longer Valid"
|
|
2414
|
+
: isAuthErr ? "Session Expired"
|
|
2415
|
+
: isDocQuality ? "Document Photo Unclear"
|
|
2416
|
+
: isExpiredDoc ? "Document Expired"
|
|
2417
|
+
: isFaceMatch ? "Face Match Failed"
|
|
2418
|
+
: isBillingErr ? "Verification Unavailable"
|
|
2419
|
+
: "Verification Unsuccessful";
|
|
2361
2420
|
// Map to user-facing guidance — never show raw internal message
|
|
2362
2421
|
const userMessage = isCameraErr
|
|
2363
2422
|
? "Please allow camera access in your browser settings and try again."
|
|
2364
2423
|
: isNetworkErr
|
|
2365
2424
|
? "Check your internet connection and try again."
|
|
2366
|
-
:
|
|
2367
|
-
? "
|
|
2368
|
-
:
|
|
2369
|
-
?
|
|
2370
|
-
:
|
|
2371
|
-
? "
|
|
2372
|
-
:
|
|
2373
|
-
? "
|
|
2374
|
-
:
|
|
2375
|
-
? "
|
|
2376
|
-
:
|
|
2425
|
+
: isResumeLinkErr
|
|
2426
|
+
? "This link has expired or has already been used. Please contact us to request a new one."
|
|
2427
|
+
: isAuthErr
|
|
2428
|
+
? "Your session has expired. Please start again."
|
|
2429
|
+
: isDocQuality
|
|
2430
|
+
? _sanitizeUserMessage(message) || "Please retake your document photo in good lighting with no glare."
|
|
2431
|
+
: isExpiredDoc
|
|
2432
|
+
? "The document you submitted has expired. Please use a valid, current ID."
|
|
2433
|
+
: isFaceMatch
|
|
2434
|
+
? "Your selfie did not match the photo on your ID. Please ensure you are using your own valid document and face the camera directly."
|
|
2435
|
+
: isBillingErr
|
|
2436
|
+
? "Verification is temporarily unavailable. Please contact support."
|
|
2437
|
+
: _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
|
|
2377
2438
|
const iconWrap = document.createElement("div");
|
|
2378
2439
|
iconWrap.style.cssText = `
|
|
2379
2440
|
width:72px;height:72px;border-radius:50%;
|