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.
Files changed (49) hide show
  1. package/dist/camera/ActionDetector.d.ts +2 -1
  2. package/dist/camera/ActionDetector.d.ts.map +1 -1
  3. package/dist/camera/ActionDetector.js +21 -5
  4. package/dist/camera/ActionDetector.js.map +1 -1
  5. package/dist/client/Smartcomply.d.ts +14 -2
  6. package/dist/client/Smartcomply.d.ts.map +1 -1
  7. package/dist/client/Smartcomply.js +15 -2
  8. package/dist/client/Smartcomply.js.map +1 -1
  9. package/dist/esm/camera/ActionDetector.d.ts +2 -1
  10. package/dist/esm/camera/ActionDetector.d.ts.map +1 -1
  11. package/dist/esm/camera/ActionDetector.js +21 -5
  12. package/dist/esm/camera/ActionDetector.js.map +1 -1
  13. package/dist/esm/client/Smartcomply.d.ts +14 -2
  14. package/dist/esm/client/Smartcomply.d.ts.map +1 -1
  15. package/dist/esm/client/Smartcomply.js +15 -2
  16. package/dist/esm/client/Smartcomply.js.map +1 -1
  17. package/dist/esm/flow/SmartComplyFlow.d.ts +24 -9
  18. package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
  19. package/dist/esm/flow/SmartComplyFlow.js +151 -25
  20. package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
  21. package/dist/esm/flow/spinner.js +14 -14
  22. package/dist/esm/index.d.ts +1 -1
  23. package/dist/esm/index.d.ts.map +1 -1
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/modules/liveness/LivenessUI.js +83 -83
  26. package/dist/esm/types/liveness.d.ts +15 -0
  27. package/dist/esm/types/liveness.d.ts.map +1 -1
  28. package/dist/flow/SmartComplyFlow.d.ts +24 -9
  29. package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
  30. package/dist/flow/SmartComplyFlow.js +152 -25
  31. package/dist/flow/SmartComplyFlow.js.map +1 -1
  32. package/dist/flow/spinner.js +14 -14
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/modules/liveness/LivenessUI.js +83 -83
  37. package/dist/smartcomply.browser.js +108 -11
  38. package/dist/smartcomply.browser.js.map +3 -3
  39. package/dist/types/liveness.d.ts +15 -0
  40. package/dist/types/liveness.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/esm/components/CameraView.d.ts +0 -59
  43. package/dist/esm/components/CameraView.d.ts.map +0 -1
  44. package/dist/esm/components/CameraView.js +0 -238
  45. package/dist/esm/components/CameraView.js.map +0 -1
  46. package/dist/esm/components/DocumentUpload.d.ts +0 -21
  47. package/dist/esm/components/DocumentUpload.d.ts.map +0 -1
  48. package/dist/esm/components/DocumentUpload.js +0 -216
  49. package/dist/esm/components/DocumentUpload.js.map +0 -1
@@ -6,6 +6,7 @@ import { requiresBackSide } from "../utils/documentRules";
6
6
  import { DEFAULT_CHALLENGE_ACTIONS } from "../utils/constants";
7
7
  import { icon } from "./icons";
8
8
  import { renderSpinner, renderInlineSpinner } from "./spinner";
9
+ import { SDKError } from "../errors/SDKError";
9
10
  // A dropped camera/network/upload error is not the user's fault and must not
10
11
  // cost them a retry attempt — only a genuine verification outcome should.
11
12
  // Classified by message rather than a typed error class since the errors
@@ -66,6 +67,18 @@ const COUNTRY_LABELS = {
66
67
  function isUploadFieldType(type) {
67
68
  return type === "upload" || type === "image" || type === "file";
68
69
  }
70
+ /**
71
+ * Whether a showErrorStep() message is about an expired/invalid resume or
72
+ * resubmit link (RESUME_TARGET_UNAVAILABLE / NOT_A_RESUME_TOKEN — see
73
+ * SmartComplyFlow._resolveResumeStep), rather than a plain session timeout.
74
+ * Extracted to a standalone function so this specific classification is
75
+ * independently testable, same as resolveIdTypeInfo below — showErrorStep's
76
+ * own regex-based classification block is otherwise tightly coupled to DOM
77
+ * rendering it doesn't make sense to extract wholesale for this fix.
78
+ */
79
+ export function isResumeLinkErrorMessage(message) {
80
+ return /resume link|resubmit link/i.test(message);
81
+ }
69
82
  export function resolveIdTypeInfo(typeName, isDocumentFlow) {
70
83
  const u = typeName.toUpperCase();
71
84
  const advanced = u.includes("ADVANCED");
@@ -654,12 +667,114 @@ export class SmartComplyFlow {
654
667
  }
655
668
  if (this.isDestroyed)
656
669
  return;
657
- this.showStep("welcome");
670
+ if (this.options.resumeToken) {
671
+ // null means _resolveResumeStep already rendered an error itself
672
+ // (a provably expired/invalid resume link — see that method) and
673
+ // there is no step left to show; calling showStep() in that case
674
+ // would wipe the error message straight back out.
675
+ const resumeStep = await this._resolveResumeStep();
676
+ if (resumeStep !== null)
677
+ this.showStep(resumeStep);
678
+ }
679
+ else {
680
+ this.showStep("welcome");
681
+ }
658
682
  }
659
683
  catch (err) {
660
684
  this.showErrorStep(err.message || "Failed to initialize. Please try again.");
661
685
  }
662
686
  }
687
+ // Resolves a resume_token to the step the customer should land on,
688
+ // pre-selecting the same country/ID type the original attempt used (the
689
+ // same state renderCountrySelect/renderIdTypeSelect's own click handlers
690
+ // set) so document_capture/liveness render correctly instead of assuming
691
+ // state that was never set. Falls back to "welcome" on anything that
692
+ // doesn't resolve cleanly for an UNCERTAIN reason (network error, timeout)
693
+ // — this is a UX improvement, not a requirement: the backend's own
694
+ // resume-match at create/ time still works correctly even if the widget
695
+ // can't figure out which screen to pre-select.
696
+ //
697
+ // Returns null instead when the lookup fails for a CERTAIN, known reason
698
+ // — the resume link itself is provably expired or already used
699
+ // (RESUME_TARGET_UNAVAILABLE) or was never a resume link at all
700
+ // (NOT_A_RESUME_TOKEN). Unlike a transient failure, there is no "try the
701
+ // real request and it'll still work" fallback for these: create/'s own
702
+ // resume-match reads the exact same resume_target_entry binding this
703
+ // lookup just found missing/invalid, so falling through to a generic
704
+ // welcome screen would silently walk the customer into a brand-new,
705
+ // unrelated verification attempt with no explanation — worse than telling
706
+ // them plainly that their link no longer works. The caller shows this
707
+ // error itself (via showErrorStep, called here directly) rather than
708
+ // rendering a step.
709
+ async _resolveResumeStep() {
710
+ try {
711
+ const info = await this.sdk.getResumeInfo();
712
+ if (info.verification_type === "liveness_only" ||
713
+ info.failure_stage === "liveness" ||
714
+ // data_match is always a selfie-vs-identity-photo mismatch (see the
715
+ // backend's own comment in _process_identity_face_match) — fixed by
716
+ // redoing the selfie/video, same as a "liveness" failure. There is
717
+ // no document step in data verification at all, so this can never
718
+ // legitimately need document_capture.
719
+ info.failure_stage === "data_match") {
720
+ // Nothing to re-select — there's no document channel to pick, just
721
+ // liveness itself to redo. selectedCountry still needs a non-blank
722
+ // value (renderLiveness sends it as a required field) the same way
723
+ // the liveness-only welcome-screen path already handles this.
724
+ this.selectedCountry = info.country || this.selectedCountry || "global";
725
+ return "liveness";
726
+ }
727
+ if (info.failure_stage === "document" && info.country && info.identifier_type) {
728
+ const channels = this.sdkConfig?.channels || {};
729
+ const idTypes = channels[info.country] || [];
730
+ const match = idTypes.find((idType) => {
731
+ const code = typeof idType === "string" ? idType : (idType.code || idType.name);
732
+ return code === info.identifier_type;
733
+ });
734
+ if (match) {
735
+ const typeName = typeof match === "string" ? match : match.name;
736
+ const typeCode = typeof match === "string" ? match : (match.code || match.name);
737
+ const channelFields = typeof match !== "string" ? (match.fields || []) : [];
738
+ const isDocFlow = channelFields.some((f) => isUploadFieldType(f.type)) || this.isDocumentFlow();
739
+ const info2 = resolveIdTypeInfo(typeName, isDocFlow);
740
+ const requiresBackFromBackend = typeof match !== "string" ? match.requires_back_side : undefined;
741
+ this.selectedCountry = info.country;
742
+ this.selectedIdType = typeCode;
743
+ this.selectedIdTypeName = typeName;
744
+ this.selectedRequiresBack = requiresBackFromBackend ?? info2.requiresBack;
745
+ if (typeof match !== "string") {
746
+ this.selectedChannelId = match.id;
747
+ this.selectedChannelFields = match.fields || [];
748
+ }
749
+ return "document_capture";
750
+ }
751
+ // Channel from the original submission no longer exists in this
752
+ // config (e.g. it was disabled since) — fall through to welcome
753
+ // rather than render document_capture with no title/back-side info.
754
+ }
755
+ // Nothing above matched cleanly (e.g. a document channel that no
756
+ // longer exists in this config) — the entry is still correctly bound
757
+ // server-side regardless of which screen the widget shows first.
758
+ return "welcome";
759
+ }
760
+ catch (err) {
761
+ const code = err instanceof SDKError ? err.errorCode : undefined;
762
+ if (code === "RESUME_TARGET_UNAVAILABLE" || code === "NOT_A_RESUME_TOKEN") {
763
+ // A certain, known failure — this specific link is provably no
764
+ // longer usable (already used, expired, or was never a resume link
765
+ // at all). Tell the customer plainly rather than silently starting
766
+ // a new, unrelated verification on a welcome screen with no
767
+ // explanation — see this method's own docstring above.
768
+ this.showErrorStep("This resume link has expired or is no longer valid.");
769
+ return null;
770
+ }
771
+ // Anything else (network error, timeout, an SDKError with a
772
+ // different/absent code) — never block the flow on this lookup.
773
+ // create/'s own resume-match still runs correctly once the customer
774
+ // gets there, so falling through to a generic welcome screen is safe.
775
+ return "welcome";
776
+ }
777
+ }
663
778
  // Defers the expiry warning while the user is on a camera-active step
664
779
  // (document_capture/liveness) — showing it there would blow away a live
665
780
  // video element and in-progress MediaRecorder mid-scan.
@@ -2264,7 +2379,14 @@ export class SmartComplyFlow {
2264
2379
  // Classify the error into a user-friendly category
2265
2380
  const isCameraErr = /camera|permission|notallowed|notfound/i.test(message);
2266
2381
  const isNetworkErr = /network|timeout|connection|fetch/i.test(message);
2267
- const isAuthErr = /session.*expired|token.*expired|authentication/i.test(message);
2382
+ // Checked before the generic isAuthErr below — a resume/resubmit link
2383
+ // (RESUME_TARGET_UNAVAILABLE / NOT_A_RESUME_TOKEN from the backend, see
2384
+ // _resolveResumeStep) also matches "token...expired"/"link" wording, but
2385
+ // the corrective action is different from a plain session timeout: the
2386
+ // customer can't just "start again" themselves, they need a fresh link
2387
+ // from whoever sent them this one.
2388
+ const isResumeLinkErr = isResumeLinkErrorMessage(message);
2389
+ const isAuthErr = !isResumeLinkErr && /session.*expired|token.*expired|authentication/i.test(message);
2268
2390
  const isDocQuality = /blurry|blur|quality|glare|dark|bright|lighting/i.test(message);
2269
2391
  const isExpiredDoc = /document.*expired|expired.*document/i.test(message);
2270
2392
  const isFaceMatch = /face.*match|selfie.*match|does not match/i.test(message);
@@ -2274,36 +2396,40 @@ export class SmartComplyFlow {
2274
2396
  const isBillingErr = /wallet|balance|insufficient.*fund|payment.*required/i.test(message);
2275
2397
  const errorIconName = isCameraErr ? "camera"
2276
2398
  : isNetworkErr ? "network"
2277
- : isAuthErr ? "clock"
2278
- : isDocQuality ? "search"
2279
- : isExpiredDoc ? "calendar"
2280
- : isFaceMatch ? "face"
2281
- : isBillingErr ? "warning"
2282
- : "close";
2399
+ : isResumeLinkErr ? "clock"
2400
+ : isAuthErr ? "clock"
2401
+ : isDocQuality ? "search"
2402
+ : isExpiredDoc ? "calendar"
2403
+ : isFaceMatch ? "face"
2404
+ : isBillingErr ? "warning"
2405
+ : "close";
2283
2406
  const errorTitle = isCameraErr ? "Camera Access Required"
2284
2407
  : isNetworkErr ? "Connection Problem"
2285
- : isAuthErr ? "Session Expired"
2286
- : isDocQuality ? "Document Photo Unclear"
2287
- : isExpiredDoc ? "Document Expired"
2288
- : isFaceMatch ? "Face Match Failed"
2289
- : isBillingErr ? "Verification Unavailable"
2290
- : "Verification Unsuccessful";
2408
+ : isResumeLinkErr ? "Link No Longer Valid"
2409
+ : isAuthErr ? "Session Expired"
2410
+ : isDocQuality ? "Document Photo Unclear"
2411
+ : isExpiredDoc ? "Document Expired"
2412
+ : isFaceMatch ? "Face Match Failed"
2413
+ : isBillingErr ? "Verification Unavailable"
2414
+ : "Verification Unsuccessful";
2291
2415
  // Map to user-facing guidance — never show raw internal message
2292
2416
  const userMessage = isCameraErr
2293
2417
  ? "Please allow camera access in your browser settings and try again."
2294
2418
  : isNetworkErr
2295
2419
  ? "Check your internet connection and try again."
2296
- : isAuthErr
2297
- ? "Your session has expired. Please start again."
2298
- : isDocQuality
2299
- ? _sanitizeUserMessage(message) || "Please retake your document photo in good lighting with no glare."
2300
- : isExpiredDoc
2301
- ? "The document you submitted has expired. Please use a valid, current ID."
2302
- : isFaceMatch
2303
- ? "Your selfie did not match the photo on your ID. Please ensure you are using your own valid document and face the camera directly."
2304
- : isBillingErr
2305
- ? "Verification is temporarily unavailable. Please contact support."
2306
- : _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
2420
+ : isResumeLinkErr
2421
+ ? "This link has expired or has already been used. Please contact us to request a new one."
2422
+ : isAuthErr
2423
+ ? "Your session has expired. Please start again."
2424
+ : isDocQuality
2425
+ ? _sanitizeUserMessage(message) || "Please retake your document photo in good lighting with no glare."
2426
+ : isExpiredDoc
2427
+ ? "The document you submitted has expired. Please use a valid, current ID."
2428
+ : isFaceMatch
2429
+ ? "Your selfie did not match the photo on your ID. Please ensure you are using your own valid document and face the camera directly."
2430
+ : isBillingErr
2431
+ ? "Verification is temporarily unavailable. Please contact support."
2432
+ : _sanitizeUserMessage(message) || "We were unable to complete your verification. Please try again.";
2307
2433
  const iconWrap = document.createElement("div");
2308
2434
  iconWrap.style.cssText = `
2309
2435
  width:72px;height:72px;border-radius:50%;