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
@@ -1,3 +1,11 @@
1
+ import { PermissionStatus } from "./Permission";
2
+ /** Error thrown by CameraManager.open() — carries the classified permission
3
+ * status alongside the friendly message, so callers can decide whether a
4
+ * failure is worth retrying without re-deriving it from the raw DOMException. */
5
+ export declare class CameraAccessError extends Error {
6
+ readonly status: PermissionStatus;
7
+ constructor(message: string, status: PermissionStatus);
8
+ }
1
9
  export declare class CameraManager {
2
10
  private stream;
3
11
  private onInterrupted;
@@ -1 +1 @@
1
- {"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAEA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,kBAAkB,CAAyB;IAEnD;;;OAGG;IACG,IAAI,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBtE,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI/C,OAAO,CAAC,YAAY;IAiBpB,IAAI,IAAI,IAAI;CAQb"}
1
+ {"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErE;;iFAEiF;AACjF,qBAAa,iBAAkB,SAAQ,KAAK;aACG,MAAM,EAAE,gBAAgB;gBAAzD,OAAO,EAAE,MAAM,EAAkB,MAAM,EAAE,gBAAgB;CAItE;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,kBAAkB,CAAyB;IAEnD;;;OAGG;IACG,IAAI,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBtE,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI/C,OAAO,CAAC,YAAY;IAiBpB,IAAI,IAAI,IAAI;CAQb"}
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CameraManager = void 0;
3
+ exports.CameraManager = exports.CameraAccessError = void 0;
4
4
  const Permission_1 = require("./Permission");
5
+ /** Error thrown by CameraManager.open() — carries the classified permission
6
+ * status alongside the friendly message, so callers can decide whether a
7
+ * failure is worth retrying without re-deriving it from the raw DOMException. */
8
+ class CameraAccessError extends Error {
9
+ constructor(message, status) {
10
+ super(message);
11
+ this.status = status;
12
+ this.name = "CameraAccessError";
13
+ }
14
+ }
15
+ exports.CameraAccessError = CameraAccessError;
5
16
  class CameraManager {
6
17
  constructor() {
7
18
  this.stream = null;
@@ -23,7 +34,7 @@ class CameraManager {
23
34
  };
24
35
  const { stream, permission } = await (0, Permission_1.requestCameraAccess)(constraints || defaults);
25
36
  if (!stream) {
26
- throw new Error(permission.reason || "Unable to access the camera.");
37
+ throw new CameraAccessError(permission.reason || "Unable to access the camera.", permission.status);
27
38
  }
28
39
  this.stream = stream;
29
40
  this._watchTracks();
@@ -1 +1 @@
1
- {"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":";;;AAAA,6CAAmD;AAEnD,MAAa,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;QAClC,kBAAa,GAAwB,IAAI,CAAC;QAC1C,uBAAkB,GAAsB,EAAE,CAAC;IA+ErD,CAAC;IA7EC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,WAAoC;QAC7C,MAAM,QAAQ,GAA2B;YACvC,KAAK,EAAE;gBACL,UAAU,EAAE,MAAM;gBAClB,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;gBACtB,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;aACvB;YACD,KAAK,EAAE,KAAK;SACb,CAAC;QAEF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,gCAAmB,EAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,8BAA8B,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAAmB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAoB;QACtC,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;IAChC,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,qEAAqE;gBACrE,iEAAiE;gBACjE,mEAAmE;gBACnE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;CACF;AAlFD,sCAkFC"}
1
+ {"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":";;;AAAA,6CAAqE;AAErE;;iFAEiF;AACjF,MAAa,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe,EAAkB,MAAwB;QACnE,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,WAAM,GAAN,MAAM,CAAkB;QAEnE,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AALD,8CAKC;AAED,MAAa,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;QAClC,kBAAa,GAAwB,IAAI,CAAC;QAC1C,uBAAkB,GAAsB,EAAE,CAAC;IA+ErD,CAAC;IA7EC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,WAAoC;QAC7C,MAAM,QAAQ,GAA2B;YACvC,KAAK,EAAE;gBACL,UAAU,EAAE,MAAM;gBAClB,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;gBACtB,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;aACvB;YACD,KAAK,EAAE,KAAK;SACb,CAAC;QAEF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,gCAAmB,EAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,MAAM,IAAI,8BAA8B,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACtG,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAAmB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAoB;QACtC,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;IAChC,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,qEAAqE;gBACrE,iEAAiE;gBACjE,mEAAmE;gBACnE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;CACF;AAlFD,sCAkFC"}
@@ -30,6 +30,17 @@ export declare function requestCameraAccess(constraints?: MediaStreamConstraints
30
30
  stream: MediaStream;
31
31
  permission: CameraPermissionResult;
32
32
  }>;
33
+ /**
34
+ * Whether a failed camera request is worth retrying. "denied" (user
35
+ * explicitly refused) and "unsupported" (no camera device at all) can never
36
+ * be fixed by retrying — surfacing the error immediately instead of burning
37
+ * several seconds of backoff first. Everything else (busy camera,
38
+ * unsupported constraints, anything unrecognized) is treated as
39
+ * potentially transient and retried — failing open avoids turning a
40
+ * recoverable hiccup into a hard failure on browsers that throw
41
+ * non-standard error names (notably Safari/iOS).
42
+ */
43
+ export declare function isRetryableCameraError(status: PermissionStatus): boolean;
33
44
  /**
34
45
  * Stop all tracks on a MediaStream and release the camera.
35
46
  * Safe to call with null/undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../../src/camera/Permission.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAe7E;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,CAAC,EAAE,sBAAsB,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAAC,CAkDtE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
1
+ {"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../../src/camera/Permission.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAe7E;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,CAAC,EAAE,sBAAsB,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAAC,CA4DtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAExE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
@@ -9,6 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.queryCameraPermission = queryCameraPermission;
11
11
  exports.requestCameraAccess = requestCameraAccess;
12
+ exports.isRetryableCameraError = isRetryableCameraError;
12
13
  exports.releaseCameraStream = releaseCameraStream;
13
14
  exports.isCameraSupported = isCameraSupported;
14
15
  /**
@@ -78,6 +79,15 @@ async function requestCameraAccess(constraints) {
78
79
  },
79
80
  };
80
81
  }
82
+ if (name === "OverconstrainedError") {
83
+ return {
84
+ stream: null,
85
+ permission: {
86
+ status: "unknown",
87
+ reason: "Camera constraints not supported. Trying with different settings...",
88
+ },
89
+ };
90
+ }
81
91
  return {
82
92
  stream: null,
83
93
  permission: {
@@ -87,6 +97,19 @@ async function requestCameraAccess(constraints) {
87
97
  };
88
98
  }
89
99
  }
100
+ /**
101
+ * Whether a failed camera request is worth retrying. "denied" (user
102
+ * explicitly refused) and "unsupported" (no camera device at all) can never
103
+ * be fixed by retrying — surfacing the error immediately instead of burning
104
+ * several seconds of backoff first. Everything else (busy camera,
105
+ * unsupported constraints, anything unrecognized) is treated as
106
+ * potentially transient and retried — failing open avoids turning a
107
+ * recoverable hiccup into a hard failure on browsers that throw
108
+ * non-standard error names (notably Safari/iOS).
109
+ */
110
+ function isRetryableCameraError(status) {
111
+ return status !== "denied" && status !== "unsupported";
112
+ }
90
113
  /**
91
114
  * Stop all tracks on a MediaStream and release the camera.
92
115
  * Safe to call with null/undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../src/camera/Permission.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../src/camera/Permission.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,11 @@
1
+ import { PermissionStatus } from "./Permission";
2
+ /** Error thrown by CameraManager.open() — carries the classified permission
3
+ * status alongside the friendly message, so callers can decide whether a
4
+ * failure is worth retrying without re-deriving it from the raw DOMException. */
5
+ export declare class CameraAccessError extends Error {
6
+ readonly status: PermissionStatus;
7
+ constructor(message: string, status: PermissionStatus);
8
+ }
1
9
  export declare class CameraManager {
2
10
  private stream;
3
11
  private onInterrupted;
@@ -1 +1 @@
1
- {"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAEA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,kBAAkB,CAAyB;IAEnD;;;OAGG;IACG,IAAI,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBtE,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI/C,OAAO,CAAC,YAAY;IAiBpB,IAAI,IAAI,IAAI;CAQb"}
1
+ {"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErE;;iFAEiF;AACjF,qBAAa,iBAAkB,SAAQ,KAAK;aACG,MAAM,EAAE,gBAAgB;gBAAzD,OAAO,EAAE,MAAM,EAAkB,MAAM,EAAE,gBAAgB;CAItE;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,kBAAkB,CAAyB;IAEnD;;;OAGG;IACG,IAAI,CAAC,WAAW,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBtE,SAAS,IAAI,WAAW,GAAG,IAAI;IAI/B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKpC;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI/C,OAAO,CAAC,YAAY;IAiBpB,IAAI,IAAI,IAAI;CAQb"}
@@ -1,4 +1,14 @@
1
1
  import { requestCameraAccess } from "./Permission";
2
+ /** Error thrown by CameraManager.open() — carries the classified permission
3
+ * status alongside the friendly message, so callers can decide whether a
4
+ * failure is worth retrying without re-deriving it from the raw DOMException. */
5
+ export class CameraAccessError extends Error {
6
+ constructor(message, status) {
7
+ super(message);
8
+ this.status = status;
9
+ this.name = "CameraAccessError";
10
+ }
11
+ }
2
12
  export class CameraManager {
3
13
  constructor() {
4
14
  this.stream = null;
@@ -20,7 +30,7 @@ export class CameraManager {
20
30
  };
21
31
  const { stream, permission } = await requestCameraAccess(constraints || defaults);
22
32
  if (!stream) {
23
- throw new Error(permission.reason || "Unable to access the camera.");
33
+ throw new CameraAccessError(permission.reason || "Unable to access the camera.", permission.status);
24
34
  }
25
35
  this.stream = stream;
26
36
  this._watchTracks();
@@ -1 +1 @@
1
- {"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,OAAO,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;QAClC,kBAAa,GAAwB,IAAI,CAAC;QAC1C,uBAAkB,GAAsB,EAAE,CAAC;IA+ErD,CAAC;IA7EC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,WAAoC;QAC7C,MAAM,QAAQ,GAA2B;YACvC,KAAK,EAAE;gBACL,UAAU,EAAE,MAAM;gBAClB,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;gBACtB,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;aACvB;YACD,KAAK,EAAE,KAAK;SACb,CAAC;QAEF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,mBAAmB,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,8BAA8B,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAAmB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAoB;QACtC,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;IAChC,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,qEAAqE;gBACrE,iEAAiE;gBACjE,mEAAmE;gBACnE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;CACF"}
1
+ {"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAoB,MAAM,cAAc,CAAC;AAErE;;iFAEiF;AACjF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe,EAAkB,MAAwB;QACnE,KAAK,CAAC,OAAO,CAAC,CAAC;QAD4B,WAAM,GAAN,MAAM,CAAkB;QAEnE,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;QAClC,kBAAa,GAAwB,IAAI,CAAC;QAC1C,uBAAkB,GAAsB,EAAE,CAAC;IA+ErD,CAAC;IA7EC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,WAAoC;QAC7C,MAAM,QAAQ,GAA2B;YACvC,KAAK,EAAE;gBACL,UAAU,EAAE,MAAM;gBAClB,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;gBACtB,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;aACvB;YACD,KAAK,EAAE,KAAK;SACb,CAAC;QAEF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,mBAAmB,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,MAAM,IAAI,8BAA8B,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACtG,CAAC;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,MAAmB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAoB;QACtC,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC;IAChC,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,qEAAqE;gBACrE,iEAAiE;gBACjE,mEAAmE;gBACnE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC/B,CAAC;CACF"}
@@ -30,6 +30,17 @@ export declare function requestCameraAccess(constraints?: MediaStreamConstraints
30
30
  stream: MediaStream;
31
31
  permission: CameraPermissionResult;
32
32
  }>;
33
+ /**
34
+ * Whether a failed camera request is worth retrying. "denied" (user
35
+ * explicitly refused) and "unsupported" (no camera device at all) can never
36
+ * be fixed by retrying — surfacing the error immediately instead of burning
37
+ * several seconds of backoff first. Everything else (busy camera,
38
+ * unsupported constraints, anything unrecognized) is treated as
39
+ * potentially transient and retried — failing open avoids turning a
40
+ * recoverable hiccup into a hard failure on browsers that throw
41
+ * non-standard error names (notably Safari/iOS).
42
+ */
43
+ export declare function isRetryableCameraError(status: PermissionStatus): boolean;
33
44
  /**
34
45
  * Stop all tracks on a MediaStream and release the camera.
35
46
  * Safe to call with null/undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../../../src/camera/Permission.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAe7E;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,CAAC,EAAE,sBAAsB,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAAC,CAkDtE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
1
+ {"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../../../src/camera/Permission.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAe7E;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,CAAC,EAAE,sBAAsB,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAAC,CA4DtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAExE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
@@ -72,6 +72,15 @@ export async function requestCameraAccess(constraints) {
72
72
  },
73
73
  };
74
74
  }
75
+ if (name === "OverconstrainedError") {
76
+ return {
77
+ stream: null,
78
+ permission: {
79
+ status: "unknown",
80
+ reason: "Camera constraints not supported. Trying with different settings...",
81
+ },
82
+ };
83
+ }
75
84
  return {
76
85
  stream: null,
77
86
  permission: {
@@ -81,6 +90,19 @@ export async function requestCameraAccess(constraints) {
81
90
  };
82
91
  }
83
92
  }
93
+ /**
94
+ * Whether a failed camera request is worth retrying. "denied" (user
95
+ * explicitly refused) and "unsupported" (no camera device at all) can never
96
+ * be fixed by retrying — surfacing the error immediately instead of burning
97
+ * several seconds of backoff first. Everything else (busy camera,
98
+ * unsupported constraints, anything unrecognized) is treated as
99
+ * potentially transient and retried — failing open avoids turning a
100
+ * recoverable hiccup into a hard failure on browsers that throw
101
+ * non-standard error names (notably Safari/iOS).
102
+ */
103
+ export function isRetryableCameraError(status) {
104
+ return status !== "denied" && status !== "unsupported";
105
+ }
84
106
  /**
85
107
  * Stop all tracks on a MediaStream and release the camera.
86
108
  * Safe to call with null/undefined.
@@ -1 +1 @@
1
- {"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../../src/camera/Permission.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Permission.js","sourceRoot":"","sources":["../../../src/camera/Permission.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@ export interface DocumentCaptureResult {
15
15
  export declare class DocumentCapture {
16
16
  private root;
17
17
  private videoEl;
18
- private stream;
18
+ private cameraManager;
19
19
  private resolveCapture;
20
20
  private rejectCapture;
21
21
  private frontBlob;
@@ -28,14 +28,12 @@ export declare class DocumentCapture {
28
28
  */
29
29
  capture(container: HTMLElement, theme: ThemeColors, documentType: string, requiresBack?: boolean): Promise<DocumentCaptureResult>;
30
30
  destroy(): void;
31
- private requiresBackSide;
32
31
  private shouldCaptureBackSide;
33
32
  private renderChoiceScreen;
34
33
  /** Returns an appropriate emoji icon for the document type. */
35
34
  private _docIcon;
36
35
  private renderCameraView;
37
36
  private openCamera;
38
- private getCameraErrorMessage;
39
37
  private captureFrame;
40
38
  /**
41
39
  * Runs the local sharpness/glare/contrast/brightness check against a
@@ -51,7 +49,6 @@ export declare class DocumentCapture {
51
49
  private validateFile;
52
50
  private handleFileUpload;
53
51
  private stopCamera;
54
- private createEnhancedButton;
55
52
  private showLoadingState;
56
53
  private showError;
57
54
  private createButton;
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentCapture.d.ts","sourceRoot":"","sources":["../../../src/flow/DocumentCapture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAA0D;IAChF,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,oBAAoB,CAAkC;IAE9D;;;OAGG;IACH,OAAO,CACL,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,qBAAqB,CAAC;IAgCjC,OAAO,IAAI,IAAI;IAuBf,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,kBAAkB;IAgK1B,+DAA+D;IAC/D,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,gBAAgB;YAuEV,UAAU;IAsDxB,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,YAAY;IAkCpB;;;;OAIG;YACW,mBAAmB;IAYjC,+EAA+E;YACjE,kBAAkB;IAsBhC,OAAO,CAAC,WAAW;IAiHnB,OAAO,CAAC,qBAAqB;YAqCf,wBAAwB;IAUtC,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,oBAAoB;IA0E5B,OAAO,CAAC,gBAAgB;IAwDxB,OAAO,CAAC,SAAS;IAgCjB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,aAAa;IA4ErB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,gBAAgB;CASzB"}
1
+ {"version":3,"file":"DocumentCapture.d.ts","sourceRoot":"","sources":["../../../src/flow/DocumentCapture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOtC;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAA+B;IAC3C,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,cAAc,CAA0D;IAChF,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,oBAAoB,CAAkC;IAE9D;;;OAGG;IACH,OAAO,CACL,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,qBAAqB,CAAC;IAgCjC,OAAO,IAAI,IAAI;IAuBf,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,kBAAkB;IAoK1B,+DAA+D;IAC/D,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,gBAAgB;YAuEV,UAAU;IAoExB,OAAO,CAAC,YAAY;IAkCpB;;;;OAIG;YACW,mBAAmB;IAYjC,+EAA+E;YACjE,kBAAkB;IAsBhC,OAAO,CAAC,WAAW;IAkHnB,OAAO,CAAC,qBAAqB;YAqCf,wBAAwB;IAUtC,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,gBAAgB;IAwDxB,OAAO,CAAC,SAAS;IAgCjB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,aAAa;IA4ErB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,gBAAgB;CASzB"}
@@ -1,9 +1,13 @@
1
1
  import { assessQuality } from "../utils/ImageQuality";
2
+ import { requiresBackSide, computeScaleRatio } from "../utils/documentRules";
3
+ import { CameraManager, CameraAccessError } from "../camera/CameraManager";
4
+ import { isRetryableCameraError } from "../camera/Permission";
5
+ import { DOC_CAMERA_IDEAL_WIDTH, DOC_CAMERA_IDEAL_HEIGHT } from "../utils/constants";
2
6
  export class DocumentCapture {
3
7
  constructor() {
4
8
  this.root = null;
5
9
  this.videoEl = null;
6
- this.stream = null;
10
+ this.cameraManager = new CameraManager();
7
11
  this.resolveCapture = null;
8
12
  this.rejectCapture = null;
9
13
  this.frontBlob = null;
@@ -63,26 +67,11 @@ export class DocumentCapture {
63
67
  this.backBlob = null;
64
68
  this.currentSide = 'front';
65
69
  }
66
- requiresBackSide(documentType) {
67
- // Only request back-side capture for documents where the backend OCR
68
- // actually needs the reverse face. NIN cards are single-sided for OCR
69
- // purposes — do NOT include "nin" here.
70
- const type = documentType.toLowerCase();
71
- return type.includes("voter") ||
72
- type.includes("driver") ||
73
- type.includes("license") ||
74
- type.includes("national id") ||
75
- type.includes("national id card") ||
76
- type.includes("ghana");
77
- // Passport: back side is the machine-readable zone but OCR reads from front only.
78
- // NIN / NIN with face: front only needed.
79
- // Voter's card / Driver's license / National ID card: back required.
80
- }
81
70
  shouldCaptureBackSide(documentType) {
82
- // Prefer the backend-provided flag; fall back to local heuristics only when absent.
71
+ // Prefer the backend-provided flag; fall back to the shared heuristic only when absent.
83
72
  return this.requiresBackOverride !== undefined
84
73
  ? this.requiresBackOverride
85
- : this.requiresBackSide(documentType);
74
+ : requiresBackSide(documentType);
86
75
  }
87
76
  renderChoiceScreen(container, theme, documentType) {
88
77
  this.root = document.createElement("div");
@@ -219,7 +208,11 @@ export class DocumentCapture {
219
208
  return;
220
209
  }
221
210
  this.showLoadingState("Processing image...", theme);
222
- this.compressImage(file, 1280, 960, 0.85)
211
+ // 2000x1500 @ 0.92 (was 1280x960 @ 0.85) — this document photo is
212
+ // later cropped down to just the face region for face comparison
213
+ // (see backend _crop_and_save_face), so the original needs enough
214
+ // headroom that the crop isn't left blurry from compression alone.
215
+ this.compressImage(file, 2000, 1500, 0.92)
223
216
  .then((c) => this.handleFileUploadComplete(c, container, theme, documentType))
224
217
  .catch(() => this.handleFileUploadComplete(file, container, theme, documentType));
225
218
  }
@@ -305,28 +298,32 @@ export class DocumentCapture {
305
298
  return;
306
299
  }
307
300
  const maxRetries = 3;
308
- let retryCount = 0;
309
- const attemptCameraAccess = async (constraints) => {
301
+ // Retries only transient/negotiable failures (busy camera, unsupported
302
+ // constraints, anything unrecognized) — a hard permission denial or "no
303
+ // camera exists" can never be fixed by retrying, so those short-circuit
304
+ // to an immediate error instead of burning ~6s of backoff first.
305
+ const attemptCameraAccess = async (constraints, retryCount = 0) => {
310
306
  try {
311
- return await navigator.mediaDevices.getUserMedia(constraints);
307
+ return await this.cameraManager.open(constraints);
312
308
  }
313
309
  catch (err) {
314
310
  console.warn(`Camera access attempt ${retryCount + 1} failed:`, err);
315
- if (retryCount < maxRetries - 1) {
316
- retryCount++;
317
- await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); // Exponential backoff
318
- return attemptCameraAccess(constraints);
311
+ const status = err instanceof CameraAccessError ? err.status : "unknown";
312
+ if (isRetryableCameraError(status) && retryCount < maxRetries - 1) {
313
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (retryCount + 1))); // Exponential backoff
314
+ return attemptCameraAccess(constraints, retryCount + 1);
319
315
  }
320
316
  throw err;
321
317
  }
322
318
  };
319
+ let stream;
323
320
  try {
324
321
  // Try rear camera first
325
- this.stream = await attemptCameraAccess({
322
+ stream = await attemptCameraAccess({
326
323
  video: {
327
324
  facingMode: { ideal: "environment" },
328
- width: { ideal: 1280 },
329
- height: { ideal: 960 },
325
+ width: { ideal: DOC_CAMERA_IDEAL_WIDTH },
326
+ height: { ideal: DOC_CAMERA_IDEAL_HEIGHT },
330
327
  },
331
328
  });
332
329
  }
@@ -334,48 +331,37 @@ export class DocumentCapture {
334
331
  console.warn("Rear camera failed, trying any camera:", err);
335
332
  // Fallback to any camera
336
333
  try {
337
- this.stream = await attemptCameraAccess({
338
- video: { width: { ideal: 1280 } },
334
+ stream = await attemptCameraAccess({
335
+ video: { width: { ideal: DOC_CAMERA_IDEAL_WIDTH } },
339
336
  });
340
337
  }
341
338
  catch (fallbackErr) {
342
- const errorMessage = this.getCameraErrorMessage(fallbackErr);
343
- this.rejectCapture?.(new Error(errorMessage));
339
+ this.rejectCapture?.(new Error(fallbackErr?.message || "Camera access failed. Please check your camera permissions and try again."));
344
340
  return;
345
341
  }
346
342
  }
343
+ // A capture that's already resolved (photo taken, camera intentionally
344
+ // stopped) must not surface a stale interruption from the old stream.
345
+ this.cameraManager.onStreamInterrupted(() => {
346
+ if (this.resolveCapture || this.rejectCapture) {
347
+ this.rejectCapture?.(new Error("Camera connection was lost. Please check your camera and try again."));
348
+ }
349
+ });
347
350
  if (this.videoEl) {
348
- this.videoEl.srcObject = this.stream;
349
- }
350
- }
351
- getCameraErrorMessage(error) {
352
- if (error.name === 'NotAllowedError') {
353
- return "Camera access denied. Please allow camera permissions and try again.";
354
- }
355
- else if (error.name === 'NotFoundError') {
356
- return "No camera found. Please ensure your device has a working camera.";
357
- }
358
- else if (error.name === 'NotReadableError') {
359
- return "Camera is already in use by another application. Please close other apps using the camera and try again.";
360
- }
361
- else if (error.name === 'OverconstrainedError') {
362
- return "Camera constraints not supported. Trying with different settings...";
363
- }
364
- else {
365
- return "Camera access failed. Please check your camera permissions and try again.";
351
+ this.videoEl.srcObject = stream;
366
352
  }
367
353
  }
368
354
  captureFrame(container, theme, documentType) {
369
355
  if (!this.videoEl)
370
356
  return;
371
- const MAX_W = 1280;
372
- const MAX_H = 960;
373
- const vw = this.videoEl.videoWidth || MAX_W;
374
- const vh = this.videoEl.videoHeight || MAX_H;
375
- const ratio = Math.min(MAX_W / vw, MAX_H / vh, 1);
357
+ // Draw at the video's native resolution — the downstream compressImage()
358
+ // call (see proceedWithPhoto) is the single place resolution gets capped
359
+ // (via computeScaleRatio, which never upscales). Resizing here too used
360
+ // to cap captures at 1280x960 before compressImage ever ran, silently
361
+ // undermining its higher resolution/quality target.
376
362
  const canvas = document.createElement("canvas");
377
- canvas.width = Math.round(vw * ratio);
378
- canvas.height = Math.round(vh * ratio);
363
+ canvas.width = this.videoEl.videoWidth;
364
+ canvas.height = this.videoEl.videoHeight;
379
365
  const ctx = canvas.getContext("2d");
380
366
  if (!ctx)
381
367
  return;
@@ -383,14 +369,15 @@ export class DocumentCapture {
383
369
  canvas.toBlob(async (blob) => {
384
370
  if (blob) {
385
371
  this.stopCamera();
386
- // Quality analysis runs synchronously over ~1.2MP of pixel data —
387
- // show a loading state so the capture->preview transition never
388
- // looks like a frozen/unresponsive tap on slower devices.
372
+ // Quality analysis runs synchronously over the captured frame's
373
+ // pixel data — show a loading state so the capture->preview
374
+ // transition never looks like a frozen/unresponsive tap on
375
+ // slower devices.
389
376
  this.showLoadingState("Analyzing photo...", theme);
390
377
  const quality = await this._assessImageQuality(canvas, ctx);
391
378
  this.showPreview(container, theme, blob, documentType, quality);
392
379
  }
393
- }, "image/jpeg", 0.85);
380
+ }, "image/jpeg", 0.92);
394
381
  }
395
382
  /**
396
383
  * Runs the local sharpness/glare/contrast/brightness check against a
@@ -497,7 +484,8 @@ export class DocumentCapture {
497
484
  }
498
485
  useBtn.textContent = "Processing...";
499
486
  useBtn.disabled = true;
500
- this.compressImage(imageData, 1280, 960, 0.85)
487
+ // 2000x1500 @ 0.92 — see the matching comment on the upload-path call above.
488
+ this.compressImage(imageData, 2000, 1500, 0.92)
501
489
  .then((compressed) => this.handleCaptureComplete(compressed, container, theme, documentType))
502
490
  .catch(() => this.handleCaptureComplete(imageData, container, theme, documentType)); // fallback to original
503
491
  };
@@ -582,78 +570,12 @@ export class DocumentCapture {
582
570
  input.click();
583
571
  }
584
572
  stopCamera() {
585
- if (this.stream) {
586
- this.stream.getTracks().forEach((t) => t.stop());
587
- this.stream = null;
588
- }
573
+ this.cameraManager.stop();
589
574
  if (this.videoEl) {
590
575
  this.videoEl.srcObject = null;
591
576
  this.videoEl = null;
592
577
  }
593
578
  }
594
- createEnhancedButton(label, theme, isPrimary, ariaLabel) {
595
- const btn = document.createElement("button");
596
- btn.textContent = label;
597
- btn.setAttribute("aria-label", ariaLabel);
598
- btn.style.cssText = `
599
- padding:18px 24px;border-radius:16px;font-size:16px;font-weight:600;
600
- cursor:pointer;transition:all 0.3s ease;border:none;width:100%;
601
- min-height:56px;touch-action:manipulation;-webkit-tap-highlight-color:transparent;
602
- position:relative;overflow:hidden;
603
- ${isPrimary
604
- ? `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;`
605
- : `background:${theme.inputBg};color:${theme.text};border:2px solid ${theme.border};box-shadow:0 2px 8px ${theme.border}20;`}
606
- `;
607
- // Add ripple effect
608
- btn.addEventListener("click", (e) => {
609
- const ripple = document.createElement("span");
610
- const rect = btn.getBoundingClientRect();
611
- const size = Math.max(rect.width, rect.height);
612
- const x = e.clientX - rect.left - size / 2;
613
- const y = e.clientY - rect.top - size / 2;
614
- ripple.style.cssText = `
615
- position:absolute;width:${size}px;height:${size}px;
616
- left:${x}px;top:${y}px;border-radius:50%;
617
- background:${isPrimary ? 'rgba(255,255,255,0.3)' : theme.primary + '20'};
618
- transform:scale(0);animation:ripple 0.6s ease-out;
619
- pointer-events:none;
620
- `;
621
- btn.appendChild(ripple);
622
- setTimeout(() => ripple.remove(), 600);
623
- });
624
- btn.addEventListener("mouseenter", () => {
625
- btn.style.transform = "translateY(-2px)";
626
- if (isPrimary) {
627
- btn.style.boxShadow = `0 12px 32px ${theme.primary}30, 0 6px 16px ${theme.primary}20`;
628
- }
629
- else {
630
- btn.style.boxShadow = `0 4px 16px ${theme.border}30, 0 2px 8px ${theme.border}20`;
631
- btn.style.borderColor = theme.primary;
632
- }
633
- });
634
- btn.addEventListener("mouseleave", () => {
635
- btn.style.transform = "translateY(0)";
636
- if (isPrimary) {
637
- btn.style.boxShadow = `0 8px 24px ${theme.primary}25, 0 4px 12px ${theme.primary}15`;
638
- }
639
- else {
640
- btn.style.boxShadow = `0 2px 8px ${theme.border}20`;
641
- btn.style.borderColor = theme.border;
642
- }
643
- });
644
- // Add ripple animation
645
- if (!document.head.querySelector('style[data-ripple-animation]')) {
646
- const style = document.createElement('style');
647
- style.setAttribute('data-ripple-animation', 'true');
648
- style.textContent = `
649
- @keyframes ripple {
650
- to { transform: scale(4); opacity: 0; }
651
- }
652
- `;
653
- document.head.appendChild(style);
654
- }
655
- return btn;
656
- }
657
579
  showLoadingState(message, theme) {
658
580
  if (!this.root)
659
581
  return;
@@ -769,7 +691,7 @@ export class DocumentCapture {
769
691
  const swapped = orientation >= 5 && orientation <= 8;
770
692
  const srcW = swapped ? h : w;
771
693
  const srcH = swapped ? w : h;
772
- const ratio = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
694
+ const ratio = computeScaleRatio(srcW, srcH, maxWidth, maxHeight);
773
695
  const outW = Math.round(srcW * ratio);
774
696
  const outH = Math.round(srcH * ratio);
775
697
  const canvas = document.createElement("canvas");
@@ -816,7 +738,7 @@ export class DocumentCapture {
816
738
  imgEl.onload = () => {
817
739
  URL.revokeObjectURL(url);
818
740
  let { naturalWidth: w, naturalHeight: h } = imgEl;
819
- const ratio = Math.min(maxWidth / w, maxHeight / h, 1);
741
+ const ratio = computeScaleRatio(w, h, maxWidth, maxHeight);
820
742
  const canvas = document.createElement("canvas");
821
743
  canvas.width = Math.round(w * ratio);
822
744
  canvas.height = Math.round(h * ratio);