smartcomply-web-sdk 1.0.42 → 1.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/flow/SmartComplyFlow.d.ts +2 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +542 -257
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts +9 -5
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +36 -10
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +2 -1
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +542 -257
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts +9 -5
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +36 -10
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +450 -224
- package/dist/smartcomply.browser.js.map +3 -3
- package/package.json +1 -1
|
@@ -5125,14 +5125,20 @@ var SmartComplySDK = (() => {
|
|
|
5125
5125
|
* 1. Take autoshot selfie from camera
|
|
5126
5126
|
* 2. Create liveness entry on backend (with challenge actions + selfie + optional doc)
|
|
5127
5127
|
* 3. Run action detection loop (camera + MediaPipe)
|
|
5128
|
-
* 4. Record video
|
|
5129
|
-
* 5.
|
|
5128
|
+
* 4. Record video (document/liveness flows) OR skip recording (data verification)
|
|
5129
|
+
* 5. Submit to backend and return response (status: "processing")
|
|
5130
5130
|
*
|
|
5131
|
-
*
|
|
5132
|
-
*
|
|
5133
|
-
*
|
|
5131
|
+
* For data_verification the backend uses COMPARISON mode (autoshot vs govt DB photo)
|
|
5132
|
+
* so video recording is unnecessary — we skip it to save bandwidth and latency.
|
|
5133
|
+
*
|
|
5134
|
+
* @param container - DOM element to mount the liveness UI into
|
|
5135
|
+
* @param params - identifier info and optional document image
|
|
5136
|
+
* @param actions - override challenge actions (default: BLINK, TURN_HEAD, OPEN_MOUTH)
|
|
5137
|
+
* @param existingEntryId - reuse an already-created LivenessEntry (avoids double charge)
|
|
5134
5138
|
*/
|
|
5135
5139
|
async startCheck(container, params, actions, existingEntryId) {
|
|
5140
|
+
const isDataVerification = params.verification_type === "data_verification" || !params.document && !params.document_front && !params.id_file && !!params.identity_check;
|
|
5141
|
+
const skipVideo = isDataVerification;
|
|
5136
5142
|
const camera = new CameraManager();
|
|
5137
5143
|
const recorder = new VideoRecorder();
|
|
5138
5144
|
const faceEngine = new FaceDetectorEngine();
|
|
@@ -5255,7 +5261,9 @@ var SmartComplySDK = (() => {
|
|
|
5255
5261
|
videoEl.srcObject = stream;
|
|
5256
5262
|
await videoEl.play();
|
|
5257
5263
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
5258
|
-
|
|
5264
|
+
if (!skipVideo) {
|
|
5265
|
+
recorder.start(stream);
|
|
5266
|
+
}
|
|
5259
5267
|
ui2.updateInstruction(uiChallenge.instruction);
|
|
5260
5268
|
const actionDetector = new ActionDetector(entry.challenge_actions);
|
|
5261
5269
|
let lastAction = null;
|
|
@@ -5329,14 +5337,30 @@ var SmartComplySDK = (() => {
|
|
|
5329
5337
|
faceEngine.destroy();
|
|
5330
5338
|
camera.stop();
|
|
5331
5339
|
ui2.unmount();
|
|
5332
|
-
this.startCheck(
|
|
5340
|
+
this.startCheck(
|
|
5341
|
+
container,
|
|
5342
|
+
{ ...params, id_file: capturedDocBlob },
|
|
5343
|
+
actions,
|
|
5344
|
+
entry.id
|
|
5345
|
+
).then(resolveRetry).catch(rejectRetry);
|
|
5333
5346
|
});
|
|
5334
5347
|
});
|
|
5335
5348
|
}
|
|
5336
5349
|
ui2.updateInstruction("Almost done \u2014 hold still...");
|
|
5337
5350
|
await new Promise((r2) => setTimeout(r2, 800));
|
|
5338
5351
|
const snapshotBlob = await this.captureFrame(videoEl);
|
|
5339
|
-
|
|
5352
|
+
let videoBlob;
|
|
5353
|
+
let durationSeconds;
|
|
5354
|
+
if (skipVideo) {
|
|
5355
|
+
recorder.stop().catch(() => {
|
|
5356
|
+
});
|
|
5357
|
+
videoBlob = autoshotBlob;
|
|
5358
|
+
durationSeconds = void 0;
|
|
5359
|
+
} else {
|
|
5360
|
+
const recorded = await recorder.stop();
|
|
5361
|
+
videoBlob = recorded.blob;
|
|
5362
|
+
durationSeconds = recorded.durationSeconds;
|
|
5363
|
+
}
|
|
5340
5364
|
const submitResult = await this.submit(
|
|
5341
5365
|
entry.id,
|
|
5342
5366
|
videoBlob,
|
|
@@ -6566,6 +6590,9 @@ var SmartComplySDK = (() => {
|
|
|
6566
6590
|
united_states: "\u{1F1FA}\u{1F1F8} United States",
|
|
6567
6591
|
united_kingdom: "\u{1F1EC}\u{1F1E7} United Kingdom"
|
|
6568
6592
|
};
|
|
6593
|
+
function isUploadFieldType(type) {
|
|
6594
|
+
return type === "upload" || type === "image" || type === "file";
|
|
6595
|
+
}
|
|
6569
6596
|
function resolveIdTypeInfo(typeName, isDocumentFlow) {
|
|
6570
6597
|
const u2 = typeName.toUpperCase();
|
|
6571
6598
|
const advanced = u2.includes("ADVANCED");
|
|
@@ -6626,6 +6653,62 @@ var SmartComplySDK = (() => {
|
|
|
6626
6653
|
requiresBack: false
|
|
6627
6654
|
};
|
|
6628
6655
|
}
|
|
6656
|
+
function _sanitizeUserMessage(raw) {
|
|
6657
|
+
if (!raw) return null;
|
|
6658
|
+
const lower = raw.toLowerCase();
|
|
6659
|
+
const blocklist = [
|
|
6660
|
+
"smartcomply",
|
|
6661
|
+
"face comparison service",
|
|
6662
|
+
"session id",
|
|
6663
|
+
"session_id",
|
|
6664
|
+
"provider",
|
|
6665
|
+
"ocr",
|
|
6666
|
+
"textract",
|
|
6667
|
+
"rekognition",
|
|
6668
|
+
"deepface",
|
|
6669
|
+
"ivs",
|
|
6670
|
+
"celery",
|
|
6671
|
+
"exception",
|
|
6672
|
+
"traceback",
|
|
6673
|
+
"str(e)",
|
|
6674
|
+
"error:",
|
|
6675
|
+
"failed:",
|
|
6676
|
+
"django",
|
|
6677
|
+
"python",
|
|
6678
|
+
"http ",
|
|
6679
|
+
"comparison mode",
|
|
6680
|
+
"liveness mode",
|
|
6681
|
+
"callback",
|
|
6682
|
+
"face_comparison",
|
|
6683
|
+
"face_match",
|
|
6684
|
+
"confidence_percentage",
|
|
6685
|
+
"threshold_percentage",
|
|
6686
|
+
"polling_fallback",
|
|
6687
|
+
"resolved_by"
|
|
6688
|
+
];
|
|
6689
|
+
for (const blocked of blocklist) {
|
|
6690
|
+
if (lower.includes(blocked)) return null;
|
|
6691
|
+
}
|
|
6692
|
+
if (/[a-z]+_[a-z]+/.test(raw) && !/\s/.test(raw.trim())) return null;
|
|
6693
|
+
if (raw.trim()[0] === raw.trim()[0].toLowerCase() && /^[a-z]/.test(raw.trim())) return null;
|
|
6694
|
+
return raw.trim();
|
|
6695
|
+
}
|
|
6696
|
+
function _formatDocType(code) {
|
|
6697
|
+
const map = {
|
|
6698
|
+
passport: "Passport",
|
|
6699
|
+
national_id: "National ID",
|
|
6700
|
+
nin_slip: "NIN Slip",
|
|
6701
|
+
nin_with_face: "National Identity Number",
|
|
6702
|
+
drivers_license: "Driver's Licence",
|
|
6703
|
+
voters_card: "Voter's Card",
|
|
6704
|
+
kenya_id: "Kenya National ID",
|
|
6705
|
+
ghana_id: "Ghana Card"
|
|
6706
|
+
};
|
|
6707
|
+
if (!code) return "";
|
|
6708
|
+
const key = code.toLowerCase().replace(/[\s-]/g, "_");
|
|
6709
|
+
if (map[key]) return map[key];
|
|
6710
|
+
return code.replace(/_/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase());
|
|
6711
|
+
}
|
|
6629
6712
|
var style = document.createElement("style");
|
|
6630
6713
|
style.textContent = `
|
|
6631
6714
|
@keyframes sc-spin {
|
|
@@ -6674,6 +6757,7 @@ var SmartComplySDK = (() => {
|
|
|
6674
6757
|
this._pollStatusTimer = null;
|
|
6675
6758
|
// Components
|
|
6676
6759
|
this.docCapture = null;
|
|
6760
|
+
this._livenessSubmitResult = null;
|
|
6677
6761
|
this.options = options;
|
|
6678
6762
|
this.sdk = new SmartComply({
|
|
6679
6763
|
apiKey: options.apiKey,
|
|
@@ -6746,6 +6830,9 @@ var SmartComplySDK = (() => {
|
|
|
6746
6830
|
`;
|
|
6747
6831
|
this.modal = document.createElement("div");
|
|
6748
6832
|
this.modal.id = "sc-flow-modal";
|
|
6833
|
+
this.modal.setAttribute("role", "dialog");
|
|
6834
|
+
this.modal.setAttribute("aria-modal", "true");
|
|
6835
|
+
this.modal.setAttribute("aria-label", "Identity verification");
|
|
6749
6836
|
this.modal.style.cssText = `
|
|
6750
6837
|
width:100%;max-width:580px;max-height:95vh;
|
|
6751
6838
|
background:${this.theme.bg};
|
|
@@ -6768,6 +6855,9 @@ var SmartComplySDK = (() => {
|
|
|
6768
6855
|
this.modal.appendChild(progressWrap);
|
|
6769
6856
|
this.contentArea = document.createElement("div");
|
|
6770
6857
|
this.contentArea.id = "sc-flow-content";
|
|
6858
|
+
this.contentArea.setAttribute("role", "main");
|
|
6859
|
+
this.contentArea.setAttribute("aria-live", "polite");
|
|
6860
|
+
this.contentArea.setAttribute("aria-atomic", "false");
|
|
6771
6861
|
this.contentArea.style.cssText = `
|
|
6772
6862
|
flex:1;overflow-y:auto;padding:16px 20px;
|
|
6773
6863
|
-webkit-overflow-scrolling:touch;
|
|
@@ -6852,7 +6942,7 @@ var SmartComplySDK = (() => {
|
|
|
6852
6942
|
this.renderDocumentCapture(wrapper);
|
|
6853
6943
|
break;
|
|
6854
6944
|
case "processing":
|
|
6855
|
-
this.renderProcessing(wrapper);
|
|
6945
|
+
this.renderProcessing(wrapper, this.livenessEntryId || void 0);
|
|
6856
6946
|
break;
|
|
6857
6947
|
case "liveness":
|
|
6858
6948
|
this.renderLiveness(wrapper);
|
|
@@ -7048,7 +7138,7 @@ var SmartComplySDK = (() => {
|
|
|
7048
7138
|
const typeName = typeof idType === "string" ? idType : idType.name;
|
|
7049
7139
|
const typeCode = typeof idType === "string" ? idType : idType.code || idType.name;
|
|
7050
7140
|
const channelFields = typeof idType !== "string" ? idType.fields || [] : [];
|
|
7051
|
-
const isThisDocFlow = channelFields.some((f2) => f2.type
|
|
7141
|
+
const isThisDocFlow = channelFields.some((f2) => isUploadFieldType(f2.type)) || globalDocFlow;
|
|
7052
7142
|
const info = resolveIdTypeInfo(typeName, isThisDocFlow);
|
|
7053
7143
|
const requiresBackFromBackend = typeof idType !== "string" ? idType.requires_back_side : void 0;
|
|
7054
7144
|
const requiresBack = requiresBackFromBackend ?? info.requiresBack;
|
|
@@ -7077,7 +7167,7 @@ var SmartComplySDK = (() => {
|
|
|
7077
7167
|
this.selectedChannelId = idType.id;
|
|
7078
7168
|
this.selectedChannelFields = idType.fields || [];
|
|
7079
7169
|
}
|
|
7080
|
-
const hasUpload = this.selectedChannelFields.some((f2) => f2.type
|
|
7170
|
+
const hasUpload = this.selectedChannelFields.some((f2) => isUploadFieldType(f2.type));
|
|
7081
7171
|
const vType = this.sdkConfig?.verification_type;
|
|
7082
7172
|
const vTypes = vType ? Array.isArray(vType) ? vType : [vType] : [];
|
|
7083
7173
|
const isDocChannel = hasUpload || vTypes.length > 0 && vTypes.includes("document_verification");
|
|
@@ -7113,12 +7203,44 @@ var SmartComplySDK = (() => {
|
|
|
7113
7203
|
container.appendChild(autoBtn);
|
|
7114
7204
|
return;
|
|
7115
7205
|
}
|
|
7206
|
+
const _FIELD_RULES = [
|
|
7207
|
+
{ match: "BVN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "BVN must be exactly 11 digits" } },
|
|
7208
|
+
{ match: "NIN", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7209
|
+
{ match: "NATIONAL IDENTITY", rule: { minLen: 11, maxLen: 11, numeric: true, hint: "NIN must be exactly 11 digits" } },
|
|
7210
|
+
{ match: "PASSPORT", rule: { minLen: 6, maxLen: 20, alphanumeric: true, hint: "Passport number must be 6\u201320 alphanumeric characters" } },
|
|
7211
|
+
{ match: "DRIVER", rule: { minLen: 3, maxLen: 30, alphanumeric: true, hint: "Licence number must be 3\u201330 alphanumeric characters" } },
|
|
7212
|
+
{ match: "VOTER", rule: { minLen: 6, maxLen: 30, alphanumeric: true, hint: "Voter ID must be 6\u201330 alphanumeric characters" } },
|
|
7213
|
+
{ match: "GHANA", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "Ghana Card number must be 8\u201320 alphanumeric characters" } },
|
|
7214
|
+
{ match: "SSNIT", rule: { minLen: 7, maxLen: 15, alphanumeric: true, hint: "SSNIT number must be 7\u201315 alphanumeric characters" } },
|
|
7215
|
+
{ match: "TIN", rule: { minLen: 8, maxLen: 20, alphanumeric: true, hint: "TIN must be 8\u201320 characters" } },
|
|
7216
|
+
{ match: "KRA", rule: { minLen: 10, maxLen: 12, alphanumeric: true, hint: "KRA PIN must be 10\u201312 characters" } }
|
|
7217
|
+
];
|
|
7218
|
+
function getFieldRule(label) {
|
|
7219
|
+
const upper = label.toUpperCase();
|
|
7220
|
+
for (const entry of _FIELD_RULES) {
|
|
7221
|
+
if (upper.includes(entry.match)) return entry.rule;
|
|
7222
|
+
}
|
|
7223
|
+
return null;
|
|
7224
|
+
}
|
|
7225
|
+
function validateFieldValue(label, value) {
|
|
7226
|
+
const rule = getFieldRule(label);
|
|
7227
|
+
if (!rule) return null;
|
|
7228
|
+
const v2 = value.trim();
|
|
7229
|
+
if (rule.numeric && !/^\d+$/.test(v2)) return `${label} must contain digits only`;
|
|
7230
|
+
if (rule.alphanumeric && !/^[A-Za-z0-9]+$/.test(v2)) return `${label} must be alphanumeric (letters and digits only)`;
|
|
7231
|
+
if (rule.minLen && v2.length < rule.minLen) return rule.hint;
|
|
7232
|
+
if (rule.maxLen && v2.length > rule.maxLen) return rule.hint;
|
|
7233
|
+
return null;
|
|
7234
|
+
}
|
|
7116
7235
|
const inputEls = [];
|
|
7117
7236
|
for (const field of dataFields) {
|
|
7118
7237
|
const fieldKey = field.label;
|
|
7119
7238
|
const fieldWrap = document.createElement("div");
|
|
7120
7239
|
fieldWrap.style.cssText = "display:flex;flex-direction:column;gap:6px;";
|
|
7240
|
+
const labelId = `sc-field-label-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7121
7241
|
const label = document.createElement("label");
|
|
7242
|
+
label.id = labelId;
|
|
7243
|
+
label.htmlFor = `sc-field-input-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7122
7244
|
label.style.cssText = `color:${this.theme.textMuted};font-size:10px;font-weight:700;letter-spacing:0.6px;text-transform:uppercase;`;
|
|
7123
7245
|
label.textContent = field.label;
|
|
7124
7246
|
fieldWrap.appendChild(label);
|
|
@@ -7191,10 +7313,17 @@ var SmartComplySDK = (() => {
|
|
|
7191
7313
|
fieldWrap.appendChild(fileInput);
|
|
7192
7314
|
inputEls.push({ field, el: fileInput });
|
|
7193
7315
|
} else {
|
|
7316
|
+
const inputId = `sc-field-input-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7317
|
+
const hintId = `sc-field-hint-${fieldKey.replace(/\s+/g, "-").toLowerCase()}`;
|
|
7318
|
+
const rule = getFieldRule(field.label);
|
|
7194
7319
|
const input = document.createElement("input");
|
|
7195
7320
|
input.type = "text";
|
|
7321
|
+
input.id = inputId;
|
|
7322
|
+
input.setAttribute("aria-labelledby", labelId);
|
|
7323
|
+
input.setAttribute("aria-describedby", hintId);
|
|
7196
7324
|
input.placeholder = `Enter ${field.label}`;
|
|
7197
|
-
input.maxLength = 50;
|
|
7325
|
+
input.maxLength = rule?.maxLen ? rule.maxLen + 2 : 50;
|
|
7326
|
+
if (rule?.numeric) input.inputMode = "numeric";
|
|
7198
7327
|
input.style.cssText = `
|
|
7199
7328
|
width:100%;padding:14px 16px;border-radius:12px;
|
|
7200
7329
|
font-size:16px;font-weight:500;
|
|
@@ -7205,17 +7334,46 @@ var SmartComplySDK = (() => {
|
|
|
7205
7334
|
box-sizing:border-box;
|
|
7206
7335
|
letter-spacing:0.5px;
|
|
7207
7336
|
`;
|
|
7337
|
+
const hintEl = document.createElement("div");
|
|
7338
|
+
hintEl.id = hintId;
|
|
7339
|
+
hintEl.setAttribute("aria-live", "polite");
|
|
7340
|
+
hintEl.style.cssText = `font-size:11px;min-height:14px;margin-top:-2px;`;
|
|
7341
|
+
if (rule) {
|
|
7342
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7343
|
+
hintEl.textContent = rule.hint;
|
|
7344
|
+
}
|
|
7208
7345
|
input.addEventListener("focus", () => {
|
|
7209
7346
|
input.style.borderColor = this.theme.primary;
|
|
7210
7347
|
});
|
|
7211
7348
|
input.addEventListener("blur", () => {
|
|
7212
|
-
|
|
7349
|
+
const v2 = input.value.trim();
|
|
7350
|
+
if (v2) {
|
|
7351
|
+
const err = validateFieldValue(field.label, v2);
|
|
7352
|
+
if (err) {
|
|
7353
|
+
input.style.borderColor = this.theme.error;
|
|
7354
|
+
hintEl.style.color = this.theme.error;
|
|
7355
|
+
hintEl.textContent = err;
|
|
7356
|
+
input.setAttribute("aria-invalid", "true");
|
|
7357
|
+
} else {
|
|
7358
|
+
input.style.borderColor = this.theme.success || this.theme.primary;
|
|
7359
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7360
|
+
hintEl.textContent = rule?.hint || "";
|
|
7361
|
+
input.setAttribute("aria-invalid", "false");
|
|
7362
|
+
}
|
|
7363
|
+
} else {
|
|
7364
|
+
input.style.borderColor = this.theme.border;
|
|
7365
|
+
}
|
|
7213
7366
|
});
|
|
7214
7367
|
input.addEventListener("input", () => {
|
|
7368
|
+
input.style.borderColor = this.theme.primary;
|
|
7369
|
+
hintEl.style.color = this.theme.textMuted;
|
|
7370
|
+
hintEl.textContent = rule?.hint || "";
|
|
7371
|
+
input.removeAttribute("aria-invalid");
|
|
7215
7372
|
this.collectedFields[fieldKey] = input.value.trim();
|
|
7216
7373
|
updateSubmitState();
|
|
7217
7374
|
});
|
|
7218
7375
|
fieldWrap.appendChild(input);
|
|
7376
|
+
fieldWrap.appendChild(hintEl);
|
|
7219
7377
|
inputEls.push({ field, el: input });
|
|
7220
7378
|
}
|
|
7221
7379
|
container.appendChild(fieldWrap);
|
|
@@ -7232,8 +7390,13 @@ var SmartComplySDK = (() => {
|
|
|
7232
7390
|
submitBtn.style.opacity = "0.5";
|
|
7233
7391
|
const updateSubmitState = () => {
|
|
7234
7392
|
const allFilled = inputEls.every(({ el }) => el.value.trim().length > 0);
|
|
7235
|
-
|
|
7236
|
-
|
|
7393
|
+
const allValid = inputEls.every(({ field, el }) => {
|
|
7394
|
+
if (el.tagName === "SELECT") return el.value.trim().length > 0;
|
|
7395
|
+
return !validateFieldValue(field.label, el.value.trim());
|
|
7396
|
+
});
|
|
7397
|
+
const ok = allFilled && allValid;
|
|
7398
|
+
submitBtn.disabled = !ok;
|
|
7399
|
+
submitBtn.style.opacity = ok ? "1" : "0.5";
|
|
7237
7400
|
};
|
|
7238
7401
|
submitBtn.addEventListener("click", async () => {
|
|
7239
7402
|
submitBtn.disabled = true;
|
|
@@ -7250,7 +7413,7 @@ var SmartComplySDK = (() => {
|
|
|
7250
7413
|
this.identityCheckId = this.verificationResult.data?.identity_check_id || null;
|
|
7251
7414
|
this.showStep("liveness");
|
|
7252
7415
|
} else {
|
|
7253
|
-
errorEl.textContent = this.verificationResult.message || "
|
|
7416
|
+
errorEl.textContent = this.verificationResult.message || "We could not verify your details. Please check and try again.";
|
|
7254
7417
|
errorEl.style.display = "block";
|
|
7255
7418
|
submitBtn.textContent = "Verify & Continue";
|
|
7256
7419
|
submitBtn.disabled = false;
|
|
@@ -7259,10 +7422,16 @@ var SmartComplySDK = (() => {
|
|
|
7259
7422
|
} catch (err) {
|
|
7260
7423
|
const fieldErrors = err.data?.errors;
|
|
7261
7424
|
if (Array.isArray(fieldErrors) && fieldErrors.length > 0) {
|
|
7262
|
-
const
|
|
7263
|
-
errorEl.textContent =
|
|
7425
|
+
const firstMsg = fieldErrors[0]?.message || fieldErrors[0]?.detail || "";
|
|
7426
|
+
errorEl.textContent = _sanitizeUserMessage(firstMsg) || "Please check your details and try again.";
|
|
7427
|
+
} else if (err.code === "VERIFICATION_FAILED") {
|
|
7428
|
+
errorEl.textContent = "We could not verify those details. Please check your ID number and try again.";
|
|
7429
|
+
} else if (err.code === "CHANNEL_NOT_CONFIGURED") {
|
|
7430
|
+
errorEl.textContent = "This ID type is not currently available. Please choose a different option.";
|
|
7431
|
+
} else if (err.code === "INSUFFICIENT_BALANCE") {
|
|
7432
|
+
errorEl.textContent = "Verification is temporarily unavailable. Please contact support.";
|
|
7264
7433
|
} else {
|
|
7265
|
-
errorEl.textContent = err.message || "
|
|
7434
|
+
errorEl.textContent = _sanitizeUserMessage(err.message) || "Something went wrong. Please try again.";
|
|
7266
7435
|
}
|
|
7267
7436
|
errorEl.style.display = "block";
|
|
7268
7437
|
submitBtn.textContent = "Verify & Continue";
|
|
@@ -7312,121 +7481,112 @@ var SmartComplySDK = (() => {
|
|
|
7312
7481
|
this.showErrorStep(err.message || "Document capture failed. Please try again.");
|
|
7313
7482
|
});
|
|
7314
7483
|
}
|
|
7315
|
-
// ── Processing
|
|
7316
|
-
renderProcessing(container) {
|
|
7317
|
-
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:
|
|
7318
|
-
const
|
|
7319
|
-
|
|
7320
|
-
const
|
|
7321
|
-
|
|
7322
|
-
position:absolute;inset:0;border-radius:50%;
|
|
7323
|
-
border:3px solid ${this.theme.border};
|
|
7324
|
-
border-top-color:${this.theme.primary};
|
|
7325
|
-
animation:sc-spin 0.9s linear infinite;
|
|
7326
|
-
`;
|
|
7327
|
-
animationWrap.appendChild(spinner);
|
|
7484
|
+
// ── Processing (real polling-driven, document flow only) ────────────
|
|
7485
|
+
renderProcessing(container, entryId) {
|
|
7486
|
+
container.style.cssText += "display:flex;flex-direction:column;align-items:center;gap:16px;padding:16px;";
|
|
7487
|
+
const spinnerWrap = document.createElement("div");
|
|
7488
|
+
spinnerWrap.style.cssText = "position:relative;width:56px;height:56px;flex-shrink:0;";
|
|
7489
|
+
const ring = document.createElement("div");
|
|
7490
|
+
ring.id = "sc-proc-ring";
|
|
7491
|
+
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;`;
|
|
7328
7492
|
const centerIcon = document.createElement("div");
|
|
7329
|
-
centerIcon.style.cssText =
|
|
7330
|
-
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
|
|
7331
|
-
font-size:20px;
|
|
7332
|
-
`;
|
|
7493
|
+
centerIcon.style.cssText = "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:22px;";
|
|
7333
7494
|
centerIcon.textContent = "\u{1F50D}";
|
|
7334
|
-
|
|
7335
|
-
|
|
7495
|
+
spinnerWrap.appendChild(ring);
|
|
7496
|
+
spinnerWrap.appendChild(centerIcon);
|
|
7497
|
+
container.appendChild(spinnerWrap);
|
|
7336
7498
|
const title = document.createElement("div");
|
|
7337
7499
|
title.style.cssText = `color:${this.theme.text};font-size:15px;font-weight:700;text-align:center;`;
|
|
7338
|
-
title.textContent = "
|
|
7500
|
+
title.textContent = "Verifying your document";
|
|
7339
7501
|
container.appendChild(title);
|
|
7340
|
-
const
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
{ id: "
|
|
7350
|
-
{ id: "
|
|
7351
|
-
{ id: "
|
|
7352
|
-
{ id: "analyzing", text: "Checking quality", icon: "\u{1F52C}" }
|
|
7502
|
+
const statusLine = document.createElement("div");
|
|
7503
|
+
statusLine.id = "sc-proc-status";
|
|
7504
|
+
statusLine.setAttribute("aria-live", "polite");
|
|
7505
|
+
statusLine.setAttribute("aria-atomic", "true");
|
|
7506
|
+
statusLine.style.cssText = `color:${this.theme.textSecondary};font-size:12px;text-align:center;max-width:260px;line-height:1.5;`;
|
|
7507
|
+
statusLine.textContent = "Scanning document\u2026";
|
|
7508
|
+
container.appendChild(statusLine);
|
|
7509
|
+
const stages = [
|
|
7510
|
+
{ id: "ocr", label: "Reading document", icon: "\u{1F4C4}" },
|
|
7511
|
+
{ id: "expiry", label: "Checking expiry", icon: "\u{1F4C5}" },
|
|
7512
|
+
{ id: "face", label: "Face match", icon: "\u{1F933}" },
|
|
7513
|
+
{ id: "result", label: "Finalising result", icon: "\u2713" }
|
|
7353
7514
|
];
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
transition:all 0.3s ease
|
|
7515
|
+
const grid = document.createElement("div");
|
|
7516
|
+
grid.style.cssText = "width:100%;display:grid;grid-template-columns:1fr 1fr;gap:8px;";
|
|
7517
|
+
stages.forEach((s2) => {
|
|
7518
|
+
const el = document.createElement("div");
|
|
7519
|
+
el.id = `sc-stage-${s2.id}`;
|
|
7520
|
+
el.style.cssText = `display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:10px;background:${this.theme.cardBg};border:1px solid ${this.theme.border};transition:all 0.3s ease;`;
|
|
7521
|
+
el.innerHTML = `
|
|
7522
|
+
<div style="width:26px;height:26px;border-radius:7px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;background:${this.theme.shimmer};">${s2.icon}</div>
|
|
7523
|
+
<div style="flex:1;color:${this.theme.textSecondary};font-size:11px;font-weight:500;">${s2.label}</div>
|
|
7524
|
+
<div id="sc-stage-status-${s2.id}" style="width:14px;height:14px;border-radius:50%;border:2px solid ${this.theme.border};flex-shrink:0;"></div>
|
|
7360
7525
|
`;
|
|
7361
|
-
|
|
7362
|
-
const iconEl = document.createElement("div");
|
|
7363
|
-
iconEl.style.cssText = `
|
|
7364
|
-
width:26px;height:26px;border-radius:7px;flex-shrink:0;
|
|
7365
|
-
display:flex;align-items:center;justify-content:center;
|
|
7366
|
-
font-size:12px;background:${this.theme.shimmer};
|
|
7367
|
-
`;
|
|
7368
|
-
iconEl.textContent = step.icon;
|
|
7369
|
-
iconEl.id = `step-icon-${step.id}`;
|
|
7370
|
-
const textEl = document.createElement("div");
|
|
7371
|
-
textEl.style.cssText = `flex:1;color:${this.theme.textSecondary};font-size:11px;font-weight:500;`;
|
|
7372
|
-
textEl.textContent = step.text;
|
|
7373
|
-
textEl.id = `step-text-${step.id}`;
|
|
7374
|
-
const statusEl = document.createElement("div");
|
|
7375
|
-
statusEl.style.cssText = `width:16px;height:16px;border-radius:50%;border:2px solid ${this.theme.border};position:relative;flex-shrink:0;`;
|
|
7376
|
-
statusEl.id = `step-status-${step.id}`;
|
|
7377
|
-
stepEl.appendChild(iconEl);
|
|
7378
|
-
stepEl.appendChild(textEl);
|
|
7379
|
-
stepEl.appendChild(statusEl);
|
|
7380
|
-
stepsContainer.appendChild(stepEl);
|
|
7526
|
+
grid.appendChild(el);
|
|
7381
7527
|
});
|
|
7382
|
-
container.appendChild(
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
const
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
const
|
|
7392
|
-
const statusEl = document.getElementById(`
|
|
7393
|
-
if (
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7528
|
+
container.appendChild(grid);
|
|
7529
|
+
if (entryId) {
|
|
7530
|
+
this._pollProcessingStages(entryId, statusLine, grid, stages);
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
_pollProcessingStages(entryId, statusLine, _grid, stages) {
|
|
7534
|
+
const stageTimings = [0, 4e3, 1e4, 18e3];
|
|
7535
|
+
const startTime = Date.now();
|
|
7536
|
+
const setStageActive = (stageId) => {
|
|
7537
|
+
const el = document.getElementById(`sc-stage-${stageId}`);
|
|
7538
|
+
const statusEl = document.getElementById(`sc-stage-status-${stageId}`);
|
|
7539
|
+
if (!el || !statusEl) return;
|
|
7540
|
+
el.style.borderColor = this.theme.primary;
|
|
7541
|
+
el.style.background = `${this.theme.primary}10`;
|
|
7542
|
+
statusEl.innerHTML = `<div style="position:absolute;inset:1px;border-radius:50%;border:2px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.8s linear infinite;"></div>`;
|
|
7543
|
+
statusEl.style.position = "relative";
|
|
7544
|
+
};
|
|
7545
|
+
const setStageComplete = (stageId) => {
|
|
7546
|
+
const el = document.getElementById(`sc-stage-${stageId}`);
|
|
7547
|
+
const statusEl = document.getElementById(`sc-stage-status-${stageId}`);
|
|
7548
|
+
if (!el || !statusEl) return;
|
|
7549
|
+
el.style.borderColor = this.theme.success || "#22c55e";
|
|
7550
|
+
el.style.background = `${this.theme.success || "#22c55e"}10`;
|
|
7551
|
+
statusEl.innerHTML = `<div style="position:absolute;inset:2px;border-radius:50%;background:${this.theme.success || "#22c55e"};display:flex;align-items:center;justify-content:center;color:#fff;font-size:8px;font-weight:bold;">\u2713</div>`;
|
|
7552
|
+
statusEl.style.position = "relative";
|
|
7553
|
+
};
|
|
7554
|
+
stages.forEach((s2, i2) => {
|
|
7409
7555
|
setTimeout(() => {
|
|
7410
|
-
if (
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7556
|
+
if (this.isDestroyed) return;
|
|
7557
|
+
setStageActive(s2.id);
|
|
7558
|
+
if (i2 > 0) setStageComplete(stages[i2 - 1].id);
|
|
7559
|
+
statusLine.textContent = s2.id === "ocr" ? "Scanning document\u2026" : s2.id === "expiry" ? "Checking expiry date\u2026" : s2.id === "face" ? "Running face match\u2026" : "Finalising result\u2026";
|
|
7560
|
+
}, stageTimings[i2]);
|
|
7561
|
+
});
|
|
7562
|
+
const MAX_POLLS = 30;
|
|
7563
|
+
let polls = 0;
|
|
7564
|
+
const poll = async () => {
|
|
7565
|
+
if (this.isDestroyed) return;
|
|
7566
|
+
polls++;
|
|
7567
|
+
try {
|
|
7568
|
+
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7569
|
+
if (result.status === "passed" || result.status === "failed") {
|
|
7570
|
+
stages.forEach((s2) => setStageComplete(s2.id));
|
|
7571
|
+
statusLine.textContent = result.status === "passed" ? "Verification complete \u2713" : "Verification finished";
|
|
7572
|
+
setTimeout(() => {
|
|
7573
|
+
if (!this.isDestroyed) {
|
|
7574
|
+
this._livenessSubmitResult = result;
|
|
7575
|
+
this.showResult(result);
|
|
7576
|
+
}
|
|
7577
|
+
}, 800);
|
|
7578
|
+
return;
|
|
7426
7579
|
}
|
|
7427
|
-
}
|
|
7580
|
+
} catch {
|
|
7581
|
+
}
|
|
7582
|
+
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
7583
|
+
this._pollStatusTimer = setTimeout(poll, 3e3);
|
|
7584
|
+
} else if (!this.isDestroyed) {
|
|
7585
|
+
stages.forEach((s2) => setStageComplete(s2.id));
|
|
7586
|
+
statusLine.textContent = "Still processing \u2014 check back soon";
|
|
7587
|
+
}
|
|
7428
7588
|
};
|
|
7429
|
-
|
|
7589
|
+
this._pollStatusTimer = setTimeout(poll, 5e3);
|
|
7430
7590
|
}
|
|
7431
7591
|
/**
|
|
7432
7592
|
* Process document capture result with front/back support
|
|
@@ -7463,7 +7623,7 @@ var SmartComplySDK = (() => {
|
|
|
7463
7623
|
<div style="color:${this.theme.text};font-size:14px;font-weight:600;">Scanning document...</div>
|
|
7464
7624
|
<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>
|
|
7465
7625
|
</div>`;
|
|
7466
|
-
const identifier = `DOC-${Date.now()}`;
|
|
7626
|
+
const identifier = Object.values(this.collectedFields)[0] || this.idNumber || this.selectedIdType || `DOC-${Date.now()}`;
|
|
7467
7627
|
const COUNTRY_CODES = {
|
|
7468
7628
|
nigeria: "NG",
|
|
7469
7629
|
ghana: "GH",
|
|
@@ -7532,47 +7692,53 @@ var SmartComplySDK = (() => {
|
|
|
7532
7692
|
await attemptProcessing();
|
|
7533
7693
|
}
|
|
7534
7694
|
handleDocumentProcessingError(error, captureContainer) {
|
|
7535
|
-
let
|
|
7695
|
+
let userMessage = "We could not verify your document. Please try again.";
|
|
7536
7696
|
let canRetry = true;
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7697
|
+
const raw = (error?.message || "").toLowerCase();
|
|
7698
|
+
if (raw.includes("expired")) {
|
|
7699
|
+
userMessage = "The document you submitted has expired. Please use a valid, current ID.";
|
|
7700
|
+
canRetry = false;
|
|
7701
|
+
} else if (raw.includes("quality too low") || raw.includes("blurry") || raw.includes("blur")) {
|
|
7702
|
+
userMessage = "The document photo isn't clear enough. Please retake it in good lighting with no glare.";
|
|
7703
|
+
} else if (raw.includes("not detected") || raw.includes("document_not_detected")) {
|
|
7704
|
+
userMessage = "We could not detect a document in the photo. Please ensure the full ID is visible and try again.";
|
|
7705
|
+
} else if (raw.includes("network") || raw.includes("fetch") || raw.includes("connection")) {
|
|
7706
|
+
userMessage = "A connection error occurred. Please check your internet and try again.";
|
|
7707
|
+
} else if (raw.includes("too large") || raw.includes("413")) {
|
|
7708
|
+
userMessage = "The document image is too large. Please retake the photo.";
|
|
7709
|
+
} else if (raw.includes("format") || raw.includes("415")) {
|
|
7710
|
+
userMessage = "Unsupported image format. Please use JPEG or PNG.";
|
|
7711
|
+
} else if (raw.includes("type mismatch") || raw.includes("document_type_mismatch")) {
|
|
7712
|
+
userMessage = "The document does not match the selected ID type. Please choose the correct ID type.";
|
|
7550
7713
|
}
|
|
7551
7714
|
if (captureContainer) {
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7715
|
+
const wrap = document.createElement("div");
|
|
7716
|
+
wrap.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:16px;padding:28px 20px;text-align:center;";
|
|
7717
|
+
wrap.setAttribute("role", "alert");
|
|
7718
|
+
const icon = document.createElement("div");
|
|
7719
|
+
icon.style.cssText = `width:56px;height:56px;border-radius:50%;background:${this.theme.errorBg};border:3px solid ${this.theme.error};display:flex;align-items:center;justify-content:center;font-size:26px;`;
|
|
7720
|
+
icon.textContent = "\u{1F50D}";
|
|
7721
|
+
wrap.appendChild(icon);
|
|
7722
|
+
const titleEl = document.createElement("div");
|
|
7723
|
+
titleEl.style.cssText = `color:${this.theme.text};font-size:14px;font-weight:700;`;
|
|
7724
|
+
titleEl.textContent = "Document Could Not Be Verified";
|
|
7725
|
+
wrap.appendChild(titleEl);
|
|
7726
|
+
const msgEl = document.createElement("div");
|
|
7727
|
+
msgEl.style.cssText = `color:${this.theme.textSecondary};font-size:13px;max-width:260px;line-height:1.6;`;
|
|
7728
|
+
msgEl.textContent = userMessage;
|
|
7729
|
+
wrap.appendChild(msgEl);
|
|
7566
7730
|
if (canRetry) {
|
|
7567
|
-
const retryBtn =
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7731
|
+
const retryBtn = document.createElement("button");
|
|
7732
|
+
retryBtn.textContent = "Try Again";
|
|
7733
|
+
retryBtn.setAttribute("aria-label", "Retake document photo");
|
|
7734
|
+
retryBtn.style.cssText = `margin-top:8px;padding:12px 24px;border-radius:8px;background:${this.theme.primary};color:#fff;border:none;font-size:14px;font-weight:600;cursor:pointer;`;
|
|
7735
|
+
retryBtn.addEventListener("click", () => this.showStep("document_capture"));
|
|
7736
|
+
wrap.appendChild(retryBtn);
|
|
7573
7737
|
}
|
|
7738
|
+
captureContainer.innerHTML = "";
|
|
7739
|
+
captureContainer.appendChild(wrap);
|
|
7574
7740
|
} else {
|
|
7575
|
-
this.showErrorStep(
|
|
7741
|
+
this.showErrorStep(userMessage);
|
|
7576
7742
|
}
|
|
7577
7743
|
}
|
|
7578
7744
|
// ── Liveness ────────────────────────────────────────────────────
|
|
@@ -7587,7 +7753,7 @@ var SmartComplySDK = (() => {
|
|
|
7587
7753
|
const livenessContainer = document.createElement("div");
|
|
7588
7754
|
livenessContainer.style.cssText = "margin:8px 0;width:100%;";
|
|
7589
7755
|
container.appendChild(livenessContainer);
|
|
7590
|
-
const identifier = this.idNumber || `DOC-${Date.now()}`;
|
|
7756
|
+
const identifier = this.idNumber || Object.values(this.collectedFields)[0] || this.selectedIdType || `DOC-${Date.now()}`;
|
|
7591
7757
|
const COUNTRY_CODES = {
|
|
7592
7758
|
nigeria: "NG",
|
|
7593
7759
|
ghana: "GH",
|
|
@@ -7606,12 +7772,20 @@ var SmartComplySDK = (() => {
|
|
|
7606
7772
|
country,
|
|
7607
7773
|
document: this.documentBlob || void 0,
|
|
7608
7774
|
document_back: this.documentBackBlob || void 0,
|
|
7609
|
-
identity_check: this.identityCheckId || void 0
|
|
7775
|
+
identity_check: this.identityCheckId || void 0,
|
|
7776
|
+
// Tell the module which flow we're in so it can skip video for data verification
|
|
7777
|
+
verification_type: this.identityCheckId ? "data_verification" : this.documentBlob ? "document_verification" : "liveness_only"
|
|
7610
7778
|
},
|
|
7611
7779
|
["BLINK", "TURN_HEAD", "OPEN_MOUTH"],
|
|
7612
7780
|
this.livenessEntryId || void 0
|
|
7613
7781
|
).then((result) => {
|
|
7614
|
-
this.
|
|
7782
|
+
const isAsync = !!(this.documentBlob || this.identityCheckId);
|
|
7783
|
+
if (isAsync && result.id) {
|
|
7784
|
+
this.livenessEntryId = result.id;
|
|
7785
|
+
this.showStep("processing");
|
|
7786
|
+
} else {
|
|
7787
|
+
this.showResult(result);
|
|
7788
|
+
}
|
|
7615
7789
|
}).catch((err) => {
|
|
7616
7790
|
this.showErrorStep(err.message || "Liveness verification failed. Please try again.");
|
|
7617
7791
|
});
|
|
@@ -7623,6 +7797,8 @@ var SmartComplySDK = (() => {
|
|
|
7623
7797
|
if (!this.contentArea) return;
|
|
7624
7798
|
this.contentArea.innerHTML = "";
|
|
7625
7799
|
const wrapper = document.createElement("div");
|
|
7800
|
+
wrapper.setAttribute("role", "region");
|
|
7801
|
+
wrapper.setAttribute("aria-label", "Verification result");
|
|
7626
7802
|
wrapper.style.cssText = `
|
|
7627
7803
|
display:flex;flex-direction:column;align-items:center;
|
|
7628
7804
|
text-align:center;gap:16px;padding:20px 0;
|
|
@@ -7635,54 +7811,59 @@ var SmartComplySDK = (() => {
|
|
|
7635
7811
|
font-size:36px;
|
|
7636
7812
|
background:${this.theme.cardBg};
|
|
7637
7813
|
border:3px solid ${this.theme.border};
|
|
7814
|
+
transition:all 0.4s ease;
|
|
7638
7815
|
`;
|
|
7639
7816
|
iconWrap.innerHTML = `<div style="width:32px;height:32px;border-radius:50%;border:3px solid ${this.theme.border};border-top-color:${this.theme.primary};animation:sc-spin 0.9s linear infinite;"></div>`;
|
|
7640
7817
|
wrapper.appendChild(iconWrap);
|
|
7641
7818
|
const title = document.createElement("h2");
|
|
7819
|
+
title.setAttribute("aria-live", "polite");
|
|
7642
7820
|
title.style.cssText = `color:${this.theme.text};font-size:20px;font-weight:700;margin:0;`;
|
|
7643
|
-
title.textContent = "
|
|
7821
|
+
title.textContent = "Checking your identity\u2026";
|
|
7644
7822
|
wrapper.appendChild(title);
|
|
7645
7823
|
const desc = document.createElement("p");
|
|
7824
|
+
desc.setAttribute("aria-live", "polite");
|
|
7646
7825
|
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7647
|
-
desc.textContent = "
|
|
7826
|
+
desc.textContent = "This usually takes a few seconds. Please wait.";
|
|
7648
7827
|
wrapper.appendChild(desc);
|
|
7649
|
-
const
|
|
7650
|
-
|
|
7828
|
+
const resultCard = document.createElement("div");
|
|
7829
|
+
resultCard.style.cssText = `
|
|
7651
7830
|
width:100%;padding:16px;border-radius:12px;
|
|
7652
7831
|
background:${this.theme.cardBg};border:1px solid ${this.theme.border};
|
|
7653
|
-
text-align:left;
|
|
7832
|
+
text-align:left;display:flex;flex-direction:column;gap:10px;
|
|
7833
|
+
transition:border-color 0.3s ease;
|
|
7654
7834
|
`;
|
|
7655
7835
|
const statusBadge = document.createElement("span");
|
|
7836
|
+
statusBadge.setAttribute("role", "status");
|
|
7837
|
+
statusBadge.setAttribute("aria-live", "polite");
|
|
7838
|
+
statusBadge.setAttribute("aria-atomic", "true");
|
|
7656
7839
|
statusBadge.style.cssText = `font-size:12px;font-weight:600;color:${this.theme.warning};`;
|
|
7657
7840
|
statusBadge.textContent = "Processing\u2026";
|
|
7658
7841
|
const statusRow = document.createElement("div");
|
|
7659
|
-
statusRow.style.cssText = "display:flex;justify-content:space-between;
|
|
7660
|
-
statusRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;">Status</span>`;
|
|
7842
|
+
statusRow.style.cssText = "display:flex;justify-content:space-between;align-items:center;";
|
|
7843
|
+
statusRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">Status</span>`;
|
|
7661
7844
|
statusRow.appendChild(statusBadge);
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
submittedRow.innerHTML = `
|
|
7673
|
-
<span style="color:${this.theme.textMuted};font-size:12px;">Submitted</span>
|
|
7674
|
-
<span style="color:${this.theme.text};font-size:12px;">${submitResult.submitted_at ? new Date(submitResult.submitted_at).toLocaleString() : "Just now"}</span>
|
|
7675
|
-
`;
|
|
7676
|
-
details.appendChild(submittedRow);
|
|
7677
|
-
wrapper.appendChild(details);
|
|
7845
|
+
resultCard.appendChild(statusRow);
|
|
7846
|
+
const nameRow = document.createElement("div");
|
|
7847
|
+
nameRow.style.cssText = "display:none;justify-content:space-between;align-items:center;";
|
|
7848
|
+
nameRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">Name</span><span id="sc-result-name" style="color:${this.theme.text};font-size:12px;font-weight:600;text-align:right;max-width:60%;"></span>`;
|
|
7849
|
+
resultCard.appendChild(nameRow);
|
|
7850
|
+
const docTypeRow = document.createElement("div");
|
|
7851
|
+
docTypeRow.style.cssText = "display:none;justify-content:space-between;align-items:center;";
|
|
7852
|
+
docTypeRow.innerHTML = `<span style="color:${this.theme.textMuted};font-size:12px;font-weight:500;">ID Type</span><span id="sc-result-doctype" style="color:${this.theme.text};font-size:12px;font-weight:600;"></span>`;
|
|
7853
|
+
resultCard.appendChild(docTypeRow);
|
|
7854
|
+
wrapper.appendChild(resultCard);
|
|
7678
7855
|
const failureMsg = document.createElement("div");
|
|
7856
|
+
failureMsg.setAttribute("role", "alert");
|
|
7679
7857
|
failureMsg.style.cssText = `
|
|
7680
7858
|
display:none;width:100%;padding:12px 14px;border-radius:10px;
|
|
7681
7859
|
background:${this.theme.errorBg};border:1px solid ${this.theme.error};
|
|
7682
|
-
color:${this.theme.error};font-size:
|
|
7860
|
+
color:${this.theme.error};font-size:13px;text-align:left;line-height:1.6;
|
|
7683
7861
|
`;
|
|
7684
7862
|
wrapper.appendChild(failureMsg);
|
|
7863
|
+
const btnRow = document.createElement("div");
|
|
7864
|
+
btnRow.style.cssText = "display:flex;flex-direction:column;gap:8px;width:100%;";
|
|
7685
7865
|
const doneBtn = this.createPrimaryButton("Done");
|
|
7866
|
+
doneBtn.setAttribute("aria-label", "Close verification and continue");
|
|
7686
7867
|
let finalStatus = submitResult.status;
|
|
7687
7868
|
doneBtn.addEventListener("click", () => {
|
|
7688
7869
|
if (this._pollStatusTimer) {
|
|
@@ -7698,7 +7879,19 @@ var SmartComplySDK = (() => {
|
|
|
7698
7879
|
});
|
|
7699
7880
|
this.close();
|
|
7700
7881
|
});
|
|
7701
|
-
|
|
7882
|
+
btnRow.appendChild(doneBtn);
|
|
7883
|
+
const retryBtn = this.createSecondaryButton("Try Again");
|
|
7884
|
+
retryBtn.setAttribute("aria-label", "Start verification again");
|
|
7885
|
+
retryBtn.style.display = "none";
|
|
7886
|
+
retryBtn.addEventListener("click", () => {
|
|
7887
|
+
if (this._pollStatusTimer) {
|
|
7888
|
+
clearTimeout(this._pollStatusTimer);
|
|
7889
|
+
this._pollStatusTimer = null;
|
|
7890
|
+
}
|
|
7891
|
+
this.initialize();
|
|
7892
|
+
});
|
|
7893
|
+
btnRow.appendChild(retryBtn);
|
|
7894
|
+
wrapper.appendChild(btnRow);
|
|
7702
7895
|
this.contentArea.appendChild(wrapper);
|
|
7703
7896
|
requestAnimationFrame(() => {
|
|
7704
7897
|
wrapper.style.opacity = "1";
|
|
@@ -7710,13 +7903,17 @@ var SmartComplySDK = (() => {
|
|
|
7710
7903
|
statusBadge,
|
|
7711
7904
|
title,
|
|
7712
7905
|
desc,
|
|
7906
|
+
resultCard,
|
|
7907
|
+
nameRow,
|
|
7908
|
+
docTypeRow,
|
|
7713
7909
|
failureMsg,
|
|
7910
|
+
retryBtn,
|
|
7714
7911
|
(resolved) => {
|
|
7715
7912
|
finalStatus = resolved;
|
|
7716
7913
|
}
|
|
7717
7914
|
);
|
|
7718
7915
|
}
|
|
7719
|
-
_startStatusPolling(entryId, iconWrap, statusBadge, title, desc, failureMsg, onResolved) {
|
|
7916
|
+
_startStatusPolling(entryId, iconWrap, statusBadge, title, desc, resultCard, nameRow, docTypeRow, failureMsg, retryBtn, onResolved) {
|
|
7720
7917
|
const MAX_POLLS = 25;
|
|
7721
7918
|
let polls = 0;
|
|
7722
7919
|
let uiTimeoutId = setTimeout(() => {
|
|
@@ -7733,43 +7930,56 @@ var SmartComplySDK = (() => {
|
|
|
7733
7930
|
clearTimeout(this._pollStatusTimer);
|
|
7734
7931
|
this._pollStatusTimer = null;
|
|
7735
7932
|
}
|
|
7736
|
-
iconWrap.innerHTML = "\
|
|
7737
|
-
iconWrap.style.background = this.theme.cardBg;
|
|
7933
|
+
iconWrap.innerHTML = "\u23F3";
|
|
7738
7934
|
iconWrap.style.border = `3px solid ${this.theme.warning}`;
|
|
7739
|
-
iconWrap.style.color = this.theme.warning;
|
|
7740
|
-
iconWrap.style.fontSize = "30px";
|
|
7741
7935
|
statusBadge.style.color = this.theme.textMuted;
|
|
7742
7936
|
statusBadge.textContent = "Still processing\u2026";
|
|
7743
|
-
title.textContent = "
|
|
7744
|
-
desc.textContent = "
|
|
7937
|
+
title.textContent = "Still verifying";
|
|
7938
|
+
desc.textContent = "This is taking a little longer than usual. You can safely close this \u2014 your result will be available in your account.";
|
|
7745
7939
|
};
|
|
7746
|
-
const applyPassed = () => {
|
|
7940
|
+
const applyPassed = (result) => {
|
|
7747
7941
|
clearUiTimeout();
|
|
7748
7942
|
iconWrap.innerHTML = "\u2713";
|
|
7749
|
-
iconWrap.style.
|
|
7750
|
-
|
|
7751
|
-
iconWrap.style.color = this.theme.success;
|
|
7752
|
-
iconWrap.style.fontSize = "36px";
|
|
7943
|
+
iconWrap.style.cssText += `background:${this.theme.successBg};border:3px solid ${this.theme.success};color:${this.theme.success};font-size:36px;font-weight:700;`;
|
|
7944
|
+
resultCard.style.borderColor = this.theme.success;
|
|
7753
7945
|
statusBadge.style.color = this.theme.success;
|
|
7754
7946
|
statusBadge.textContent = "Verified \u2713";
|
|
7755
7947
|
title.textContent = "Identity Verified";
|
|
7756
|
-
desc.textContent = "Your identity
|
|
7948
|
+
desc.textContent = "Your identity has been confirmed successfully.";
|
|
7949
|
+
const profile = result?.customer_profile || result?.data || {};
|
|
7950
|
+
const docResult = result?.document || {};
|
|
7951
|
+
const ocr = docResult?.ocr_result || {};
|
|
7952
|
+
const firstName = (profile?.first_name || ocr?.first_name || docResult?.first_name || "").trim();
|
|
7953
|
+
const lastName = (profile?.last_name || profile?.surname || ocr?.last_name || ocr?.surname || docResult?.last_name || "").trim();
|
|
7954
|
+
const fullName = [firstName, lastName].filter(Boolean).join(" ");
|
|
7955
|
+
const nameEl = document.getElementById("sc-result-name");
|
|
7956
|
+
if (fullName && nameEl) {
|
|
7957
|
+
nameEl.textContent = fullName;
|
|
7958
|
+
nameRow.style.display = "flex";
|
|
7959
|
+
}
|
|
7960
|
+
const rawDocType = docResult?.document_type || this.selectedIdType || result?.identifier_type || "";
|
|
7961
|
+
const docTypeLabel = _formatDocType(rawDocType);
|
|
7962
|
+
const docTypeEl = document.getElementById("sc-result-doctype");
|
|
7963
|
+
if (docTypeLabel && docTypeEl) {
|
|
7964
|
+
docTypeEl.textContent = docTypeLabel;
|
|
7965
|
+
docTypeRow.style.display = "flex";
|
|
7966
|
+
}
|
|
7757
7967
|
};
|
|
7758
7968
|
const applyFailed = (reason) => {
|
|
7759
7969
|
clearUiTimeout();
|
|
7760
7970
|
iconWrap.innerHTML = "\u2715";
|
|
7761
|
-
iconWrap.style.
|
|
7762
|
-
|
|
7763
|
-
iconWrap.style.color = this.theme.error;
|
|
7764
|
-
iconWrap.style.fontSize = "32px";
|
|
7971
|
+
iconWrap.style.cssText += `background:${this.theme.errorBg};border:3px solid ${this.theme.error};color:${this.theme.error};font-size:32px;`;
|
|
7972
|
+
resultCard.style.borderColor = this.theme.error;
|
|
7765
7973
|
statusBadge.style.color = this.theme.error;
|
|
7766
|
-
statusBadge.textContent = "
|
|
7767
|
-
title.textContent = "Verification
|
|
7768
|
-
desc.textContent = "
|
|
7769
|
-
|
|
7770
|
-
|
|
7974
|
+
statusBadge.textContent = "Not verified";
|
|
7975
|
+
title.textContent = "Verification Unsuccessful";
|
|
7976
|
+
desc.textContent = "We were unable to confirm your identity this time.";
|
|
7977
|
+
const safeReason = _sanitizeUserMessage(reason);
|
|
7978
|
+
if (safeReason) {
|
|
7979
|
+
failureMsg.textContent = safeReason;
|
|
7771
7980
|
failureMsg.style.display = "block";
|
|
7772
7981
|
}
|
|
7982
|
+
retryBtn.style.display = "block";
|
|
7773
7983
|
};
|
|
7774
7984
|
const poll = async () => {
|
|
7775
7985
|
if (this.isDestroyed) return;
|
|
@@ -7778,13 +7988,12 @@ var SmartComplySDK = (() => {
|
|
|
7778
7988
|
const result = await this.sdk.liveness.pollResult(entryId);
|
|
7779
7989
|
if (result.status === "passed") {
|
|
7780
7990
|
onResolved("passed");
|
|
7781
|
-
applyPassed();
|
|
7991
|
+
applyPassed(result);
|
|
7782
7992
|
return;
|
|
7783
7993
|
}
|
|
7784
7994
|
if (result.status === "failed") {
|
|
7785
|
-
onResolved(
|
|
7786
|
-
|
|
7787
|
-
applyFailed(reason);
|
|
7995
|
+
onResolved("failed");
|
|
7996
|
+
applyFailed(result.failure_reason || null);
|
|
7788
7997
|
return;
|
|
7789
7998
|
}
|
|
7790
7999
|
if (polls < MAX_POLLS && !this.isDestroyed) {
|
|
@@ -7810,20 +8019,25 @@ var SmartComplySDK = (() => {
|
|
|
7810
8019
|
if (!this.contentArea) return;
|
|
7811
8020
|
this.contentArea.innerHTML = "";
|
|
7812
8021
|
const wrapper = document.createElement("div");
|
|
8022
|
+
wrapper.setAttribute("role", "alert");
|
|
7813
8023
|
wrapper.style.cssText = `
|
|
7814
8024
|
display:flex;flex-direction:column;align-items:center;
|
|
7815
8025
|
text-align:center;gap:16px;padding:20px 0;
|
|
7816
8026
|
`;
|
|
7817
8027
|
const isCameraErr = /camera|permission|notallowed|notfound/i.test(message);
|
|
7818
8028
|
const isNetworkErr = /network|timeout|connection|fetch/i.test(message);
|
|
7819
|
-
const isAuthErr = /session|token
|
|
7820
|
-
const
|
|
7821
|
-
const
|
|
8029
|
+
const isAuthErr = /session.*expired|token.*expired|authentication/i.test(message);
|
|
8030
|
+
const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
|
|
8031
|
+
const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
|
|
8032
|
+
const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
|
|
8033
|
+
const errorIcon = isCameraErr ? "\u{1F4F7}" : isNetworkErr ? "\u{1F4E1}" : isAuthErr ? "\u23F1" : isDocQuality ? "\u{1F50D}" : isExpiredDoc ? "\u{1F4C5}" : isFaceMatch ? "\u{1F933}" : "\u2715";
|
|
8034
|
+
const errorTitle = isCameraErr ? "Camera Access Required" : isNetworkErr ? "Connection Problem" : isAuthErr ? "Session Expired" : isDocQuality ? "Document Photo Unclear" : isExpiredDoc ? "Document Expired" : isFaceMatch ? "Face Match Failed" : "Verification Unsuccessful";
|
|
8035
|
+
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.";
|
|
7822
8036
|
const iconWrap = document.createElement("div");
|
|
7823
8037
|
iconWrap.style.cssText = `
|
|
7824
8038
|
width:72px;height:72px;border-radius:50%;
|
|
7825
8039
|
display:flex;align-items:center;justify-content:center;
|
|
7826
|
-
font-size
|
|
8040
|
+
font-size:28px;
|
|
7827
8041
|
background:${this.theme.errorBg};
|
|
7828
8042
|
border:3px solid ${this.theme.error};
|
|
7829
8043
|
`;
|
|
@@ -7835,18 +8049,16 @@ var SmartComplySDK = (() => {
|
|
|
7835
8049
|
wrapper.appendChild(title);
|
|
7836
8050
|
const desc = document.createElement("p");
|
|
7837
8051
|
desc.style.cssText = `color:${this.theme.textSecondary};font-size:14px;line-height:1.6;margin:0;max-width:300px;`;
|
|
7838
|
-
desc.textContent =
|
|
8052
|
+
desc.textContent = userMessage;
|
|
7839
8053
|
wrapper.appendChild(desc);
|
|
7840
8054
|
const retryBtn = this.createPrimaryButton("Try Again");
|
|
8055
|
+
retryBtn.setAttribute("aria-label", "Try verification again");
|
|
7841
8056
|
retryBtn.addEventListener("click", () => {
|
|
7842
|
-
|
|
7843
|
-
this.showStep("welcome");
|
|
7844
|
-
} else {
|
|
7845
|
-
this.initialize();
|
|
7846
|
-
}
|
|
8057
|
+
this.initialize();
|
|
7847
8058
|
});
|
|
7848
8059
|
wrapper.appendChild(retryBtn);
|
|
7849
8060
|
const closeBtn = this.createSecondaryButton("Close");
|
|
8061
|
+
closeBtn.setAttribute("aria-label", "Close verification widget");
|
|
7850
8062
|
closeBtn.addEventListener("click", () => {
|
|
7851
8063
|
this.options.onError?.(new Error(message));
|
|
7852
8064
|
this.close();
|
|
@@ -7859,6 +8071,7 @@ var SmartComplySDK = (() => {
|
|
|
7859
8071
|
// ─────────────────────────────────────────────────────────────────
|
|
7860
8072
|
createHeader() {
|
|
7861
8073
|
const header = document.createElement("div");
|
|
8074
|
+
header.setAttribute("role", "banner");
|
|
7862
8075
|
header.style.cssText = `
|
|
7863
8076
|
display:flex;align-items:center;justify-content:space-between;
|
|
7864
8077
|
padding:11px 20px;border-bottom:1px solid ${this.theme.border};
|
|
@@ -7867,6 +8080,7 @@ var SmartComplySDK = (() => {
|
|
|
7867
8080
|
leftArea.style.cssText = "display:flex;align-items:center;gap:8px;min-width:0;";
|
|
7868
8081
|
this.headerBackBtn = document.createElement("button");
|
|
7869
8082
|
this.headerBackBtn.innerHTML = "←";
|
|
8083
|
+
this.headerBackBtn.setAttribute("aria-label", "Go back to previous step");
|
|
7870
8084
|
this.headerBackBtn.style.cssText = `
|
|
7871
8085
|
width:30px;height:30px;border-radius:8px;flex-shrink:0;
|
|
7872
8086
|
display:none;align-items:center;justify-content:center;
|
|
@@ -7875,6 +8089,7 @@ var SmartComplySDK = (() => {
|
|
|
7875
8089
|
background:${this.theme.cardBg};color:${this.theme.text};
|
|
7876
8090
|
transition:all 0.2s ease;
|
|
7877
8091
|
`;
|
|
8092
|
+
this.headerBackBtn.style.cssText += `:focus-visible{outline:2px solid ${this.theme.primary};outline-offset:2px;}`;
|
|
7878
8093
|
this.headerBackBtn.addEventListener("mouseenter", () => {
|
|
7879
8094
|
if (this.headerBackBtn) {
|
|
7880
8095
|
this.headerBackBtn.style.background = this.theme.shimmer;
|
|
@@ -7887,6 +8102,7 @@ var SmartComplySDK = (() => {
|
|
|
7887
8102
|
});
|
|
7888
8103
|
leftArea.appendChild(this.headerBackBtn);
|
|
7889
8104
|
this.brandLabel = document.createElement("div");
|
|
8105
|
+
this.brandLabel.setAttribute("aria-label", "Verification provider");
|
|
7890
8106
|
this.brandLabel.style.cssText = `
|
|
7891
8107
|
font-size:15px;font-weight:700;color:${this.theme.text};
|
|
7892
8108
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
@@ -7895,6 +8111,8 @@ var SmartComplySDK = (() => {
|
|
|
7895
8111
|
leftArea.appendChild(this.brandLabel);
|
|
7896
8112
|
header.appendChild(leftArea);
|
|
7897
8113
|
this.stepLabel = document.createElement("div");
|
|
8114
|
+
this.stepLabel.setAttribute("aria-live", "polite");
|
|
8115
|
+
this.stepLabel.setAttribute("aria-atomic", "true");
|
|
7898
8116
|
this.stepLabel.style.cssText = `
|
|
7899
8117
|
font-size:12px;color:${this.theme.textMuted};
|
|
7900
8118
|
font-variant-numeric:tabular-nums;flex-shrink:0;
|
|
@@ -7902,6 +8120,7 @@ var SmartComplySDK = (() => {
|
|
|
7902
8120
|
header.appendChild(this.stepLabel);
|
|
7903
8121
|
const closeBtn = document.createElement("button");
|
|
7904
8122
|
closeBtn.innerHTML = "\u2715";
|
|
8123
|
+
closeBtn.setAttribute("aria-label", "Close identity verification");
|
|
7905
8124
|
closeBtn.style.cssText = `
|
|
7906
8125
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
7907
8126
|
display:flex;align-items:center;justify-content:center;
|
|
@@ -7948,6 +8167,7 @@ var SmartComplySDK = (() => {
|
|
|
7948
8167
|
}
|
|
7949
8168
|
createOptionCard(title, subtitle, onClick) {
|
|
7950
8169
|
const card = document.createElement("button");
|
|
8170
|
+
card.setAttribute("aria-label", subtitle ? `${title}: ${subtitle}` : title);
|
|
7951
8171
|
card.style.cssText = `
|
|
7952
8172
|
width:100%;padding:13px 16px;border-radius:12px;
|
|
7953
8173
|
background:${this.theme.cardBg};
|
|
@@ -8033,7 +8253,7 @@ var SmartComplySDK = (() => {
|
|
|
8033
8253
|
}
|
|
8034
8254
|
const channels = this.sdkConfig?.channels || {};
|
|
8035
8255
|
const allFields = Object.values(channels).flat().flatMap((ch) => ch.fields || []);
|
|
8036
|
-
return allFields.some((f2) => f2.type
|
|
8256
|
+
return allFields.some((f2) => isUploadFieldType(f2.type));
|
|
8037
8257
|
}
|
|
8038
8258
|
updateProgress() {
|
|
8039
8259
|
const isDocFlow = this.isDocumentFlow();
|
|
@@ -8112,6 +8332,12 @@ var SmartComplySDK = (() => {
|
|
|
8112
8332
|
#sc-flow-modal * {
|
|
8113
8333
|
box-sizing: border-box;
|
|
8114
8334
|
}
|
|
8335
|
+
#sc-flow-modal button:focus-visible,
|
|
8336
|
+
#sc-flow-modal select:focus-visible,
|
|
8337
|
+
#sc-flow-modal input:focus-visible {
|
|
8338
|
+
outline: 2px solid #3b82f6;
|
|
8339
|
+
outline-offset: 2px;
|
|
8340
|
+
}
|
|
8115
8341
|
#sc-flow-modal::-webkit-scrollbar {
|
|
8116
8342
|
width: 4px;
|
|
8117
8343
|
}
|