smartcomply-web-sdk 1.0.61 → 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 +47 -130
  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 +47 -130
  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 +60 -111
  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;IAoK1B,+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;IAkHnB,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");
@@ -309,28 +298,32 @@ export class DocumentCapture {
309
298
  return;
310
299
  }
311
300
  const maxRetries = 3;
312
- let retryCount = 0;
313
- 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) => {
314
306
  try {
315
- return await navigator.mediaDevices.getUserMedia(constraints);
307
+ return await this.cameraManager.open(constraints);
316
308
  }
317
309
  catch (err) {
318
310
  console.warn(`Camera access attempt ${retryCount + 1} failed:`, err);
319
- if (retryCount < maxRetries - 1) {
320
- retryCount++;
321
- await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); // Exponential backoff
322
- 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);
323
315
  }
324
316
  throw err;
325
317
  }
326
318
  };
319
+ let stream;
327
320
  try {
328
321
  // Try rear camera first
329
- this.stream = await attemptCameraAccess({
322
+ stream = await attemptCameraAccess({
330
323
  video: {
331
324
  facingMode: { ideal: "environment" },
332
- width: { ideal: 1280 },
333
- height: { ideal: 960 },
325
+ width: { ideal: DOC_CAMERA_IDEAL_WIDTH },
326
+ height: { ideal: DOC_CAMERA_IDEAL_HEIGHT },
334
327
  },
335
328
  });
336
329
  }
@@ -338,48 +331,37 @@ export class DocumentCapture {
338
331
  console.warn("Rear camera failed, trying any camera:", err);
339
332
  // Fallback to any camera
340
333
  try {
341
- this.stream = await attemptCameraAccess({
342
- video: { width: { ideal: 1280 } },
334
+ stream = await attemptCameraAccess({
335
+ video: { width: { ideal: DOC_CAMERA_IDEAL_WIDTH } },
343
336
  });
344
337
  }
345
338
  catch (fallbackErr) {
346
- const errorMessage = this.getCameraErrorMessage(fallbackErr);
347
- this.rejectCapture?.(new Error(errorMessage));
339
+ this.rejectCapture?.(new Error(fallbackErr?.message || "Camera access failed. Please check your camera permissions and try again."));
348
340
  return;
349
341
  }
350
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
+ });
351
350
  if (this.videoEl) {
352
- this.videoEl.srcObject = this.stream;
353
- }
354
- }
355
- getCameraErrorMessage(error) {
356
- if (error.name === 'NotAllowedError') {
357
- return "Camera access denied. Please allow camera permissions and try again.";
358
- }
359
- else if (error.name === 'NotFoundError') {
360
- return "No camera found. Please ensure your device has a working camera.";
361
- }
362
- else if (error.name === 'NotReadableError') {
363
- return "Camera is already in use by another application. Please close other apps using the camera and try again.";
364
- }
365
- else if (error.name === 'OverconstrainedError') {
366
- return "Camera constraints not supported. Trying with different settings...";
367
- }
368
- else {
369
- return "Camera access failed. Please check your camera permissions and try again.";
351
+ this.videoEl.srcObject = stream;
370
352
  }
371
353
  }
372
354
  captureFrame(container, theme, documentType) {
373
355
  if (!this.videoEl)
374
356
  return;
375
- const MAX_W = 1280;
376
- const MAX_H = 960;
377
- const vw = this.videoEl.videoWidth || MAX_W;
378
- const vh = this.videoEl.videoHeight || MAX_H;
379
- 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.
380
362
  const canvas = document.createElement("canvas");
381
- canvas.width = Math.round(vw * ratio);
382
- canvas.height = Math.round(vh * ratio);
363
+ canvas.width = this.videoEl.videoWidth;
364
+ canvas.height = this.videoEl.videoHeight;
383
365
  const ctx = canvas.getContext("2d");
384
366
  if (!ctx)
385
367
  return;
@@ -387,14 +369,15 @@ export class DocumentCapture {
387
369
  canvas.toBlob(async (blob) => {
388
370
  if (blob) {
389
371
  this.stopCamera();
390
- // Quality analysis runs synchronously over ~1.2MP of pixel data —
391
- // show a loading state so the capture->preview transition never
392
- // 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.
393
376
  this.showLoadingState("Analyzing photo...", theme);
394
377
  const quality = await this._assessImageQuality(canvas, ctx);
395
378
  this.showPreview(container, theme, blob, documentType, quality);
396
379
  }
397
- }, "image/jpeg", 0.85);
380
+ }, "image/jpeg", 0.92);
398
381
  }
399
382
  /**
400
383
  * Runs the local sharpness/glare/contrast/brightness check against a
@@ -587,78 +570,12 @@ export class DocumentCapture {
587
570
  input.click();
588
571
  }
589
572
  stopCamera() {
590
- if (this.stream) {
591
- this.stream.getTracks().forEach((t) => t.stop());
592
- this.stream = null;
593
- }
573
+ this.cameraManager.stop();
594
574
  if (this.videoEl) {
595
575
  this.videoEl.srcObject = null;
596
576
  this.videoEl = null;
597
577
  }
598
578
  }
599
- createEnhancedButton(label, theme, isPrimary, ariaLabel) {
600
- const btn = document.createElement("button");
601
- btn.textContent = label;
602
- btn.setAttribute("aria-label", ariaLabel);
603
- btn.style.cssText = `
604
- padding:18px 24px;border-radius:16px;font-size:16px;font-weight:600;
605
- cursor:pointer;transition:all 0.3s ease;border:none;width:100%;
606
- min-height:56px;touch-action:manipulation;-webkit-tap-highlight-color:transparent;
607
- position:relative;overflow:hidden;
608
- ${isPrimary
609
- ? `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;`
610
- : `background:${theme.inputBg};color:${theme.text};border:2px solid ${theme.border};box-shadow:0 2px 8px ${theme.border}20;`}
611
- `;
612
- // Add ripple effect
613
- btn.addEventListener("click", (e) => {
614
- const ripple = document.createElement("span");
615
- const rect = btn.getBoundingClientRect();
616
- const size = Math.max(rect.width, rect.height);
617
- const x = e.clientX - rect.left - size / 2;
618
- const y = e.clientY - rect.top - size / 2;
619
- ripple.style.cssText = `
620
- position:absolute;width:${size}px;height:${size}px;
621
- left:${x}px;top:${y}px;border-radius:50%;
622
- background:${isPrimary ? 'rgba(255,255,255,0.3)' : theme.primary + '20'};
623
- transform:scale(0);animation:ripple 0.6s ease-out;
624
- pointer-events:none;
625
- `;
626
- btn.appendChild(ripple);
627
- setTimeout(() => ripple.remove(), 600);
628
- });
629
- btn.addEventListener("mouseenter", () => {
630
- btn.style.transform = "translateY(-2px)";
631
- if (isPrimary) {
632
- btn.style.boxShadow = `0 12px 32px ${theme.primary}30, 0 6px 16px ${theme.primary}20`;
633
- }
634
- else {
635
- btn.style.boxShadow = `0 4px 16px ${theme.border}30, 0 2px 8px ${theme.border}20`;
636
- btn.style.borderColor = theme.primary;
637
- }
638
- });
639
- btn.addEventListener("mouseleave", () => {
640
- btn.style.transform = "translateY(0)";
641
- if (isPrimary) {
642
- btn.style.boxShadow = `0 8px 24px ${theme.primary}25, 0 4px 12px ${theme.primary}15`;
643
- }
644
- else {
645
- btn.style.boxShadow = `0 2px 8px ${theme.border}20`;
646
- btn.style.borderColor = theme.border;
647
- }
648
- });
649
- // Add ripple animation
650
- if (!document.head.querySelector('style[data-ripple-animation]')) {
651
- const style = document.createElement('style');
652
- style.setAttribute('data-ripple-animation', 'true');
653
- style.textContent = `
654
- @keyframes ripple {
655
- to { transform: scale(4); opacity: 0; }
656
- }
657
- `;
658
- document.head.appendChild(style);
659
- }
660
- return btn;
661
- }
662
579
  showLoadingState(message, theme) {
663
580
  if (!this.root)
664
581
  return;
@@ -774,7 +691,7 @@ export class DocumentCapture {
774
691
  const swapped = orientation >= 5 && orientation <= 8;
775
692
  const srcW = swapped ? h : w;
776
693
  const srcH = swapped ? w : h;
777
- const ratio = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
694
+ const ratio = computeScaleRatio(srcW, srcH, maxWidth, maxHeight);
778
695
  const outW = Math.round(srcW * ratio);
779
696
  const outH = Math.round(srcH * ratio);
780
697
  const canvas = document.createElement("canvas");
@@ -821,7 +738,7 @@ export class DocumentCapture {
821
738
  imgEl.onload = () => {
822
739
  URL.revokeObjectURL(url);
823
740
  let { naturalWidth: w, naturalHeight: h } = imgEl;
824
- const ratio = Math.min(maxWidth / w, maxHeight / h, 1);
741
+ const ratio = computeScaleRatio(w, h, maxWidth, maxHeight);
825
742
  const canvas = document.createElement("canvas");
826
743
  canvas.width = Math.round(w * ratio);
827
744
  canvas.height = Math.round(h * ratio);