smartcomply-web-sdk 1.0.60 → 1.0.62

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/CameraManager.d.ts +8 -0
  2. package/dist/camera/CameraManager.d.ts.map +1 -1
  3. package/dist/camera/CameraManager.js +13 -2
  4. package/dist/camera/CameraManager.js.map +1 -1
  5. package/dist/camera/Permission.d.ts +11 -0
  6. package/dist/camera/Permission.d.ts.map +1 -1
  7. package/dist/camera/Permission.js +23 -0
  8. package/dist/camera/Permission.js.map +1 -1
  9. package/dist/esm/camera/CameraManager.d.ts +8 -0
  10. package/dist/esm/camera/CameraManager.d.ts.map +1 -1
  11. package/dist/esm/camera/CameraManager.js +11 -1
  12. package/dist/esm/camera/CameraManager.js.map +1 -1
  13. package/dist/esm/camera/Permission.d.ts +11 -0
  14. package/dist/esm/camera/Permission.d.ts.map +1 -1
  15. package/dist/esm/camera/Permission.js +22 -0
  16. package/dist/esm/camera/Permission.js.map +1 -1
  17. package/dist/esm/flow/DocumentCapture.d.ts +1 -4
  18. package/dist/esm/flow/DocumentCapture.d.ts.map +1 -1
  19. package/dist/esm/flow/DocumentCapture.js +54 -132
  20. package/dist/esm/flow/DocumentCapture.js.map +1 -1
  21. package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
  22. package/dist/esm/flow/SmartComplyFlow.js +8 -13
  23. package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
  24. package/dist/esm/utils/constants.d.ts +11 -3
  25. package/dist/esm/utils/constants.d.ts.map +1 -1
  26. package/dist/esm/utils/constants.js +11 -3
  27. package/dist/esm/utils/constants.js.map +1 -1
  28. package/dist/esm/utils/documentRules.d.ts +15 -0
  29. package/dist/esm/utils/documentRules.d.ts.map +1 -0
  30. package/dist/esm/utils/documentRules.js +27 -0
  31. package/dist/esm/utils/documentRules.js.map +1 -0
  32. package/dist/flow/DocumentCapture.d.ts +1 -4
  33. package/dist/flow/DocumentCapture.d.ts.map +1 -1
  34. package/dist/flow/DocumentCapture.js +54 -132
  35. package/dist/flow/DocumentCapture.js.map +1 -1
  36. package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
  37. package/dist/flow/SmartComplyFlow.js +11 -16
  38. package/dist/flow/SmartComplyFlow.js.map +1 -1
  39. package/dist/smartcomply.browser.js +62 -113
  40. package/dist/smartcomply.browser.js.map +4 -4
  41. package/dist/utils/constants.d.ts +11 -3
  42. package/dist/utils/constants.d.ts.map +1 -1
  43. package/dist/utils/constants.js +11 -3
  44. package/dist/utils/constants.js.map +1 -1
  45. package/dist/utils/documentRules.d.ts +15 -0
  46. package/dist/utils/documentRules.d.ts.map +1 -0
  47. package/dist/utils/documentRules.js +31 -0
  48. package/dist/utils/documentRules.js.map +1 -0
  49. package/package.json +6 -3
@@ -298,6 +298,8 @@ var SmartComplySDK = (() => {
298
298
  var MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024;
299
299
  var DEFAULT_CHALLENGE_ACTIONS = ["BLINK"];
300
300
  var LIVENESS_TIME_LIMIT_SECONDS = 12;
301
+ var DOC_CAMERA_IDEAL_WIDTH = 2e3;
302
+ var DOC_CAMERA_IDEAL_HEIGHT = 1500;
301
303
  var RECORDING_BITS_PER_SECOND = 8e5;
302
304
 
303
305
  // src/camera/Permission.ts
@@ -352,6 +354,15 @@ var SmartComplySDK = (() => {
352
354
  }
353
355
  };
354
356
  }
357
+ if (name === "OverconstrainedError") {
358
+ return {
359
+ stream: null,
360
+ permission: {
361
+ status: "unknown",
362
+ reason: "Camera constraints not supported. Trying with different settings..."
363
+ }
364
+ };
365
+ }
355
366
  return {
356
367
  stream: null,
357
368
  permission: {
@@ -361,6 +372,9 @@ var SmartComplySDK = (() => {
361
372
  };
362
373
  }
363
374
  }
375
+ function isRetryableCameraError(status) {
376
+ return status !== "denied" && status !== "unsupported";
377
+ }
364
378
  function releaseCameraStream(stream) {
365
379
  if (!stream) return;
366
380
  stream.getTracks().forEach((t2) => t2.stop());
@@ -370,6 +384,13 @@ var SmartComplySDK = (() => {
370
384
  }
371
385
 
372
386
  // src/camera/CameraManager.ts
387
+ var CameraAccessError = class extends Error {
388
+ constructor(message, status) {
389
+ super(message);
390
+ this.status = status;
391
+ this.name = "CameraAccessError";
392
+ }
393
+ };
373
394
  var CameraManager = class {
374
395
  constructor() {
375
396
  this.stream = null;
@@ -391,7 +412,7 @@ var SmartComplySDK = (() => {
391
412
  };
392
413
  const { stream, permission } = await requestCameraAccess(constraints || defaults);
393
414
  if (!stream) {
394
- throw new Error(permission.reason || "Unable to access the camera.");
415
+ throw new CameraAccessError(permission.reason || "Unable to access the camera.", permission.status);
395
416
  }
396
417
  this.stream = stream;
397
418
  this._watchTracks();
@@ -5923,12 +5944,21 @@ var SmartComplySDK = (() => {
5923
5944
  };
5924
5945
  }
5925
5946
 
5947
+ // src/utils/documentRules.ts
5948
+ function requiresBackSide(documentType) {
5949
+ const type = documentType.toLowerCase();
5950
+ return type.includes("voter") || type.includes("driver") || type.includes("license") || type.includes("national id") || type.includes("national id card") || type.includes("ghana");
5951
+ }
5952
+ function computeScaleRatio(srcWidth, srcHeight, maxWidth, maxHeight) {
5953
+ return Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);
5954
+ }
5955
+
5926
5956
  // src/flow/DocumentCapture.ts
5927
5957
  var DocumentCapture = class {
5928
5958
  constructor() {
5929
5959
  this.root = null;
5930
5960
  this.videoEl = null;
5931
- this.stream = null;
5961
+ this.cameraManager = new CameraManager();
5932
5962
  this.resolveCapture = null;
5933
5963
  this.rejectCapture = null;
5934
5964
  this.frontBlob = null;
@@ -5982,12 +6012,8 @@ var SmartComplySDK = (() => {
5982
6012
  this.backBlob = null;
5983
6013
  this.currentSide = "front";
5984
6014
  }
5985
- requiresBackSide(documentType) {
5986
- const type = documentType.toLowerCase();
5987
- return type.includes("voter") || type.includes("driver") || type.includes("license") || type.includes("national id") || type.includes("national id card") || type.includes("ghana");
5988
- }
5989
6015
  shouldCaptureBackSide(documentType) {
5990
- return this.requiresBackOverride !== void 0 ? this.requiresBackOverride : this.requiresBackSide(documentType);
6016
+ return this.requiresBackOverride !== void 0 ? this.requiresBackOverride : requiresBackSide(documentType);
5991
6017
  }
5992
6018
  renderChoiceScreen(container, theme, documentType) {
5993
6019
  this.root = document.createElement("div");
@@ -6127,7 +6153,7 @@ var SmartComplySDK = (() => {
6127
6153
  return;
6128
6154
  }
6129
6155
  this.showLoadingState("Processing image...", theme);
6130
- this.compressImage(file, 1280, 960, 0.85).then((c2) => this.handleFileUploadComplete(c2, container, theme, documentType)).catch(() => this.handleFileUploadComplete(file, container, theme, documentType));
6156
+ this.compressImage(file, 2e3, 1500, 0.92).then((c2) => this.handleFileUploadComplete(c2, container, theme, documentType)).catch(() => this.handleFileUploadComplete(file, container, theme, documentType));
6131
6157
  }
6132
6158
  });
6133
6159
  this.root.appendChild(fileInput);
@@ -6203,67 +6229,53 @@ var SmartComplySDK = (() => {
6203
6229
  return;
6204
6230
  }
6205
6231
  const maxRetries = 3;
6206
- let retryCount = 0;
6207
- const attemptCameraAccess = async (constraints) => {
6232
+ const attemptCameraAccess = async (constraints, retryCount = 0) => {
6208
6233
  try {
6209
- return await navigator.mediaDevices.getUserMedia(constraints);
6234
+ return await this.cameraManager.open(constraints);
6210
6235
  } catch (err) {
6211
6236
  console.warn(`Camera access attempt ${retryCount + 1} failed:`, err);
6212
- if (retryCount < maxRetries - 1) {
6213
- retryCount++;
6214
- await new Promise((resolve) => setTimeout(resolve, 1e3 * retryCount));
6215
- return attemptCameraAccess(constraints);
6237
+ const status = err instanceof CameraAccessError ? err.status : "unknown";
6238
+ if (isRetryableCameraError(status) && retryCount < maxRetries - 1) {
6239
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (retryCount + 1)));
6240
+ return attemptCameraAccess(constraints, retryCount + 1);
6216
6241
  }
6217
6242
  throw err;
6218
6243
  }
6219
6244
  };
6245
+ let stream;
6220
6246
  try {
6221
- this.stream = await attemptCameraAccess({
6247
+ stream = await attemptCameraAccess({
6222
6248
  video: {
6223
6249
  facingMode: { ideal: "environment" },
6224
- width: { ideal: 1280 },
6225
- height: { ideal: 960 }
6250
+ width: { ideal: DOC_CAMERA_IDEAL_WIDTH },
6251
+ height: { ideal: DOC_CAMERA_IDEAL_HEIGHT }
6226
6252
  }
6227
6253
  });
6228
6254
  } catch (err) {
6229
6255
  console.warn("Rear camera failed, trying any camera:", err);
6230
6256
  try {
6231
- this.stream = await attemptCameraAccess({
6232
- video: { width: { ideal: 1280 } }
6257
+ stream = await attemptCameraAccess({
6258
+ video: { width: { ideal: DOC_CAMERA_IDEAL_WIDTH } }
6233
6259
  });
6234
6260
  } catch (fallbackErr) {
6235
- const errorMessage = this.getCameraErrorMessage(fallbackErr);
6236
- this.rejectCapture?.(new Error(errorMessage));
6261
+ this.rejectCapture?.(new Error(fallbackErr?.message || "Camera access failed. Please check your camera permissions and try again."));
6237
6262
  return;
6238
6263
  }
6239
6264
  }
6265
+ this.cameraManager.onStreamInterrupted(() => {
6266
+ if (this.resolveCapture || this.rejectCapture) {
6267
+ this.rejectCapture?.(new Error("Camera connection was lost. Please check your camera and try again."));
6268
+ }
6269
+ });
6240
6270
  if (this.videoEl) {
6241
- this.videoEl.srcObject = this.stream;
6242
- }
6243
- }
6244
- getCameraErrorMessage(error) {
6245
- if (error.name === "NotAllowedError") {
6246
- return "Camera access denied. Please allow camera permissions and try again.";
6247
- } else if (error.name === "NotFoundError") {
6248
- return "No camera found. Please ensure your device has a working camera.";
6249
- } else if (error.name === "NotReadableError") {
6250
- return "Camera is already in use by another application. Please close other apps using the camera and try again.";
6251
- } else if (error.name === "OverconstrainedError") {
6252
- return "Camera constraints not supported. Trying with different settings...";
6253
- } else {
6254
- return "Camera access failed. Please check your camera permissions and try again.";
6271
+ this.videoEl.srcObject = stream;
6255
6272
  }
6256
6273
  }
6257
6274
  captureFrame(container, theme, documentType) {
6258
6275
  if (!this.videoEl) return;
6259
- const MAX_W = 1280;
6260
- const MAX_H = 960;
6261
- const vw = this.videoEl.videoWidth || MAX_W;
6262
- const vh = this.videoEl.videoHeight || MAX_H;
6263
- const ratio = Math.min(MAX_W / vw, MAX_H / vh, 1);
6264
6276
  const canvas = document.createElement("canvas");
6265
- canvas.width = Math.round(vw * ratio);
6266
- canvas.height = Math.round(vh * ratio);
6277
+ canvas.width = this.videoEl.videoWidth;
6278
+ canvas.height = this.videoEl.videoHeight;
6267
6279
  const ctx = canvas.getContext("2d");
6268
6280
  if (!ctx) return;
6269
6281
  ctx.drawImage(this.videoEl, 0, 0, canvas.width, canvas.height);
@@ -6277,7 +6289,7 @@ var SmartComplySDK = (() => {
6277
6289
  }
6278
6290
  },
6279
6291
  "image/jpeg",
6280
- 0.85
6292
+ 0.92
6281
6293
  );
6282
6294
  }
6283
6295
  /**
@@ -6376,7 +6388,7 @@ var SmartComplySDK = (() => {
6376
6388
  }
6377
6389
  useBtn.textContent = "Processing...";
6378
6390
  useBtn.disabled = true;
6379
- this.compressImage(imageData, 1280, 960, 0.85).then((compressed) => this.handleCaptureComplete(compressed, container, theme, documentType)).catch(() => this.handleCaptureComplete(imageData, container, theme, documentType));
6391
+ this.compressImage(imageData, 2e3, 1500, 0.92).then((compressed) => this.handleCaptureComplete(compressed, container, theme, documentType)).catch(() => this.handleCaptureComplete(imageData, container, theme, documentType));
6380
6392
  };
6381
6393
  const useBtn = this.createButton(qualityIsPoor ? "Use Anyway ⚠" : "Use Photo ✓", theme, !qualityIsPoor);
6382
6394
  useBtn.style.flex = "1";
@@ -6444,72 +6456,12 @@ var SmartComplySDK = (() => {
6444
6456
  if (input) input.click();
6445
6457
  }
6446
6458
  stopCamera() {
6447
- if (this.stream) {
6448
- this.stream.getTracks().forEach((t2) => t2.stop());
6449
- this.stream = null;
6450
- }
6459
+ this.cameraManager.stop();
6451
6460
  if (this.videoEl) {
6452
6461
  this.videoEl.srcObject = null;
6453
6462
  this.videoEl = null;
6454
6463
  }
6455
6464
  }
6456
- createEnhancedButton(label, theme, isPrimary, ariaLabel) {
6457
- const btn = document.createElement("button");
6458
- btn.textContent = label;
6459
- btn.setAttribute("aria-label", ariaLabel);
6460
- btn.style.cssText = `
6461
- padding:18px 24px;border-radius:16px;font-size:16px;font-weight:600;
6462
- cursor:pointer;transition:all 0.3s ease;border:none;width:100%;
6463
- min-height:56px;touch-action:manipulation;-webkit-tap-highlight-color:transparent;
6464
- position:relative;overflow:hidden;
6465
- ${isPrimary ? `background:linear-gradient(135deg, ${theme.primary}, ${theme.primary}DD);color:#fff;box-shadow:0 8px 24px ${theme.primary}25, 0 4px 12px ${theme.primary}15;` : `background:${theme.inputBg};color:${theme.text};border:2px solid ${theme.border};box-shadow:0 2px 8px ${theme.border}20;`}
6466
- `;
6467
- btn.addEventListener("click", (e2) => {
6468
- const ripple = document.createElement("span");
6469
- const rect = btn.getBoundingClientRect();
6470
- const size = Math.max(rect.width, rect.height);
6471
- const x2 = e2.clientX - rect.left - size / 2;
6472
- const y2 = e2.clientY - rect.top - size / 2;
6473
- ripple.style.cssText = `
6474
- position:absolute;width:${size}px;height:${size}px;
6475
- left:${x2}px;top:${y2}px;border-radius:50%;
6476
- background:${isPrimary ? "rgba(255,255,255,0.3)" : theme.primary + "20"};
6477
- transform:scale(0);animation:ripple 0.6s ease-out;
6478
- pointer-events:none;
6479
- `;
6480
- btn.appendChild(ripple);
6481
- setTimeout(() => ripple.remove(), 600);
6482
- });
6483
- btn.addEventListener("mouseenter", () => {
6484
- btn.style.transform = "translateY(-2px)";
6485
- if (isPrimary) {
6486
- btn.style.boxShadow = `0 12px 32px ${theme.primary}30, 0 6px 16px ${theme.primary}20`;
6487
- } else {
6488
- btn.style.boxShadow = `0 4px 16px ${theme.border}30, 0 2px 8px ${theme.border}20`;
6489
- btn.style.borderColor = theme.primary;
6490
- }
6491
- });
6492
- btn.addEventListener("mouseleave", () => {
6493
- btn.style.transform = "translateY(0)";
6494
- if (isPrimary) {
6495
- btn.style.boxShadow = `0 8px 24px ${theme.primary}25, 0 4px 12px ${theme.primary}15`;
6496
- } else {
6497
- btn.style.boxShadow = `0 2px 8px ${theme.border}20`;
6498
- btn.style.borderColor = theme.border;
6499
- }
6500
- });
6501
- if (!document.head.querySelector("style[data-ripple-animation]")) {
6502
- const style2 = document.createElement("style");
6503
- style2.setAttribute("data-ripple-animation", "true");
6504
- style2.textContent = `
6505
- @keyframes ripple {
6506
- to { transform: scale(4); opacity: 0; }
6507
- }
6508
- `;
6509
- document.head.appendChild(style2);
6510
- }
6511
- return btn;
6512
- }
6513
6465
  showLoadingState(message, theme) {
6514
6466
  if (!this.root) return;
6515
6467
  this.root.innerHTML = "";
@@ -6612,7 +6564,7 @@ var SmartComplySDK = (() => {
6612
6564
  const swapped = orientation >= 5 && orientation <= 8;
6613
6565
  const srcW = swapped ? h2 : w2;
6614
6566
  const srcH = swapped ? w2 : h2;
6615
- const ratio = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
6567
+ const ratio = computeScaleRatio(srcW, srcH, maxWidth, maxHeight);
6616
6568
  const outW = Math.round(srcW * ratio);
6617
6569
  const outH = Math.round(srcH * ratio);
6618
6570
  const canvas = document.createElement("canvas");
@@ -6660,7 +6612,7 @@ var SmartComplySDK = (() => {
6660
6612
  imgEl.onload = () => {
6661
6613
  URL.revokeObjectURL(url);
6662
6614
  let { naturalWidth: w2, naturalHeight: h2 } = imgEl;
6663
- const ratio = Math.min(maxWidth / w2, maxHeight / h2, 1);
6615
+ const ratio = computeScaleRatio(w2, h2, maxWidth, maxHeight);
6664
6616
  const canvas = document.createElement("canvas");
6665
6617
  canvas.width = Math.round(w2 * ratio);
6666
6618
  canvas.height = Math.round(h2 * ratio);
@@ -6764,10 +6716,6 @@ var SmartComplySDK = (() => {
6764
6716
  const u2 = typeName.toUpperCase();
6765
6717
  const advanced = u2.includes("ADVANCED");
6766
6718
  const cleanName = typeName.replace(/\s*with\s+face\s*/gi, " ").replace(/\s*\(NIN\)\s*/gi, " ").replace(/\s*\(BVN\)\s*/gi, " ").replace(/\s{2,}/g, " ").trim();
6767
- const requiresBackSide = (type) => {
6768
- const t2 = type.toLowerCase();
6769
- return t2.includes("voter") || t2.includes("driver") || t2.includes("license") || t2.includes("national id") || t2.includes("national id card");
6770
- };
6771
6719
  if (u2.includes("BVN") || u2.includes("BANK VERIFICATION")) {
6772
6720
  return { icon: "🏦", desc: "Enter your 11-digit Bank Verification Number (BVN)", requiresBack: false };
6773
6721
  }
@@ -6775,7 +6723,7 @@ var SmartComplySDK = (() => {
6775
6723
  return { icon: "🆔", desc: "Enter your 11-digit National Identification Number (NIN)", requiresBack: false };
6776
6724
  }
6777
6725
  if (u2.includes("GHANA")) {
6778
- if (isDocumentFlow) return { icon: "🪪", desc: "Capture or upload your Ghana Card", requiresBack: true };
6726
+ if (isDocumentFlow) return { icon: "🪪", desc: "Capture or upload your Ghana Card", requiresBack: requiresBackSide(typeName) };
6779
6727
  return { icon: "🪪", desc: "Enter your Ghana Card number", requiresBack: false };
6780
6728
  }
6781
6729
  if (u2.includes("SSNIT"))
@@ -7940,6 +7888,7 @@ var SmartComplySDK = (() => {
7940
7888
  container.appendChild(yesBtn);
7941
7889
  const noBtn = this.createSecondaryButton("No, retake document");
7942
7890
  this._onceClick(noBtn, () => {
7891
+ this.docCapture?.destroy();
7943
7892
  this._confirmRetries++;
7944
7893
  this._ocrName = "";
7945
7894
  this._ocrDocNumber = "";