smartcomply-web-sdk 1.0.58 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/camera/CameraManager.d.ts +16 -0
- package/dist/camera/CameraManager.d.ts.map +1 -1
- package/dist/camera/CameraManager.js +44 -1
- package/dist/camera/CameraManager.js.map +1 -1
- package/dist/camera/VideoRecorder.d.ts +3 -0
- package/dist/camera/VideoRecorder.d.ts.map +1 -1
- package/dist/camera/VideoRecorder.js +10 -1
- package/dist/camera/VideoRecorder.js.map +1 -1
- package/dist/esm/camera/CameraManager.d.ts +16 -0
- package/dist/esm/camera/CameraManager.d.ts.map +1 -1
- package/dist/esm/camera/CameraManager.js +44 -1
- package/dist/esm/camera/CameraManager.js.map +1 -1
- package/dist/esm/camera/VideoRecorder.d.ts +3 -0
- package/dist/esm/camera/VideoRecorder.d.ts.map +1 -1
- package/dist/esm/camera/VideoRecorder.js +10 -1
- package/dist/esm/camera/VideoRecorder.js.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts +4 -1
- package/dist/esm/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/esm/flow/SmartComplyFlow.js +78 -22
- package/dist/esm/flow/SmartComplyFlow.js.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/esm/modules/liveness/LivenessUI.js +10 -1
- package/dist/esm/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/esm/modules/liveness/liveness.d.ts +3 -1
- package/dist/esm/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/esm/modules/liveness/liveness.js +54 -25
- package/dist/esm/modules/liveness/liveness.js.map +1 -1
- package/dist/esm/utils/constants.d.ts +6 -2
- package/dist/esm/utils/constants.d.ts.map +1 -1
- package/dist/esm/utils/constants.js +6 -2
- package/dist/esm/utils/constants.js.map +1 -1
- package/dist/flow/SmartComplyFlow.d.ts +4 -1
- package/dist/flow/SmartComplyFlow.d.ts.map +1 -1
- package/dist/flow/SmartComplyFlow.js +78 -22
- package/dist/flow/SmartComplyFlow.js.map +1 -1
- package/dist/modules/liveness/LivenessUI.d.ts.map +1 -1
- package/dist/modules/liveness/LivenessUI.js +10 -1
- package/dist/modules/liveness/LivenessUI.js.map +1 -1
- package/dist/modules/liveness/liveness.d.ts +3 -1
- package/dist/modules/liveness/liveness.d.ts.map +1 -1
- package/dist/modules/liveness/liveness.js +54 -25
- package/dist/modules/liveness/liveness.js.map +1 -1
- package/dist/smartcomply.browser.js +391 -254
- package/dist/smartcomply.browser.js.map +4 -4
- package/dist/utils/constants.d.ts +6 -2
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +6 -2
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export declare class CameraManager {
|
|
2
2
|
private stream;
|
|
3
|
+
private onInterrupted;
|
|
4
|
+
private trackEndedHandlers;
|
|
5
|
+
/**
|
|
6
|
+
* Opens the camera via requestCameraAccess(), which maps browser errors
|
|
7
|
+
* (denied, no device, in-use-by-another-app) to a clean message thrown here.
|
|
8
|
+
*/
|
|
3
9
|
open(constraints?: MediaStreamConstraints): Promise<MediaStream>;
|
|
4
10
|
getStream(): MediaStream | null;
|
|
5
11
|
/**
|
|
@@ -9,6 +15,16 @@ export declare class CameraManager {
|
|
|
9
15
|
* stop() becomes a no-op for it, leaking the camera.
|
|
10
16
|
*/
|
|
11
17
|
setStream(stream: MediaStream): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register a callback fired the moment any track in the current stream
|
|
20
|
+
* ends unexpectedly (OS revokes camera permission mid-session, another
|
|
21
|
+
* app steals the camera, device sleeps and the driver drops the feed).
|
|
22
|
+
* Without this, a dead stream is silently invisible — the video element
|
|
23
|
+
* just freezes on its last frame and face detection quietly fails
|
|
24
|
+
* forever with no indication anything went wrong.
|
|
25
|
+
*/
|
|
26
|
+
onStreamInterrupted(callback: () => void): void;
|
|
27
|
+
private _watchTracks;
|
|
12
28
|
stop(): void;
|
|
13
29
|
}
|
|
14
30
|
//# sourceMappingURL=CameraManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":"
|
|
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,10 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CameraManager = void 0;
|
|
4
|
+
const Permission_1 = require("./Permission");
|
|
4
5
|
class CameraManager {
|
|
5
6
|
constructor() {
|
|
6
7
|
this.stream = null;
|
|
8
|
+
this.onInterrupted = null;
|
|
9
|
+
this.trackEndedHandlers = [];
|
|
7
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Opens the camera via requestCameraAccess(), which maps browser errors
|
|
13
|
+
* (denied, no device, in-use-by-another-app) to a clean message thrown here.
|
|
14
|
+
*/
|
|
8
15
|
async open(constraints) {
|
|
9
16
|
const defaults = {
|
|
10
17
|
video: {
|
|
@@ -14,7 +21,12 @@ class CameraManager {
|
|
|
14
21
|
},
|
|
15
22
|
audio: false,
|
|
16
23
|
};
|
|
17
|
-
|
|
24
|
+
const { stream, permission } = await (0, Permission_1.requestCameraAccess)(constraints || defaults);
|
|
25
|
+
if (!stream) {
|
|
26
|
+
throw new Error(permission.reason || "Unable to access the camera.");
|
|
27
|
+
}
|
|
28
|
+
this.stream = stream;
|
|
29
|
+
this._watchTracks();
|
|
18
30
|
return this.stream;
|
|
19
31
|
}
|
|
20
32
|
getStream() {
|
|
@@ -28,12 +40,43 @@ class CameraManager {
|
|
|
28
40
|
*/
|
|
29
41
|
setStream(stream) {
|
|
30
42
|
this.stream = stream;
|
|
43
|
+
this._watchTracks();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register a callback fired the moment any track in the current stream
|
|
47
|
+
* ends unexpectedly (OS revokes camera permission mid-session, another
|
|
48
|
+
* app steals the camera, device sleeps and the driver drops the feed).
|
|
49
|
+
* Without this, a dead stream is silently invisible — the video element
|
|
50
|
+
* just freezes on its last frame and face detection quietly fails
|
|
51
|
+
* forever with no indication anything went wrong.
|
|
52
|
+
*/
|
|
53
|
+
onStreamInterrupted(callback) {
|
|
54
|
+
this.onInterrupted = callback;
|
|
55
|
+
}
|
|
56
|
+
_watchTracks() {
|
|
57
|
+
this.trackEndedHandlers = [];
|
|
58
|
+
if (!this.stream)
|
|
59
|
+
return;
|
|
60
|
+
this.stream.getTracks().forEach((track) => {
|
|
61
|
+
const handler = () => {
|
|
62
|
+
// A track we intentionally stopped (via stop()) also fires "ended" —
|
|
63
|
+
// only treat this as an interruption if we still think we have a
|
|
64
|
+
// live stream (stop() nulls this.stream before that would happen).
|
|
65
|
+
if (this.stream) {
|
|
66
|
+
this.onInterrupted?.();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
track.addEventListener("ended", handler);
|
|
70
|
+
this.trackEndedHandlers.push(() => track.removeEventListener("ended", handler));
|
|
71
|
+
});
|
|
31
72
|
}
|
|
32
73
|
stop() {
|
|
33
74
|
if (this.stream) {
|
|
34
75
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
35
76
|
this.stream = null;
|
|
36
77
|
}
|
|
78
|
+
this.trackEndedHandlers.forEach((remove) => remove());
|
|
79
|
+
this.trackEndedHandlers = [];
|
|
37
80
|
}
|
|
38
81
|
}
|
|
39
82
|
exports.CameraManager = CameraManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../src/camera/CameraManager.ts"],"names":[],"mappings":";;;AAAA,MAAa,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;
|
|
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"}
|
|
@@ -2,6 +2,9 @@ export declare class VideoRecorder {
|
|
|
2
2
|
private recorder;
|
|
3
3
|
private chunks;
|
|
4
4
|
private startedAt;
|
|
5
|
+
private onRecordingError;
|
|
6
|
+
/** Fires immediately on a mid-recording error, unlike stop()'s onerror which only fires when stop() is later called. */
|
|
7
|
+
onError(callback: () => void): void;
|
|
5
8
|
start(stream: MediaStream): void;
|
|
6
9
|
stop(): Promise<{
|
|
7
10
|
blob: Blob;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"AAEA,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,gBAAgB,CAA6B;IAErD,wHAAwH;IACxH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IA0BhC,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IA0BxD,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VideoRecorder = void 0;
|
|
4
|
+
const constants_1 = require("../utils/constants");
|
|
4
5
|
class VideoRecorder {
|
|
5
6
|
constructor() {
|
|
6
7
|
this.recorder = null;
|
|
7
8
|
this.chunks = [];
|
|
8
9
|
this.startedAt = 0;
|
|
10
|
+
this.onRecordingError = null;
|
|
11
|
+
}
|
|
12
|
+
/** Fires immediately on a mid-recording error, unlike stop()'s onerror which only fires when stop() is later called. */
|
|
13
|
+
onError(callback) {
|
|
14
|
+
this.onRecordingError = callback;
|
|
9
15
|
}
|
|
10
16
|
start(stream) {
|
|
11
17
|
this.chunks = [];
|
|
@@ -14,13 +20,16 @@ class VideoRecorder {
|
|
|
14
20
|
: "video/webm";
|
|
15
21
|
this.recorder = new MediaRecorder(stream, {
|
|
16
22
|
mimeType,
|
|
17
|
-
videoBitsPerSecond:
|
|
23
|
+
videoBitsPerSecond: constants_1.RECORDING_BITS_PER_SECOND,
|
|
18
24
|
});
|
|
19
25
|
this.recorder.ondataavailable = (e) => {
|
|
20
26
|
if (e.data.size > 0) {
|
|
21
27
|
this.chunks.push(e.data);
|
|
22
28
|
}
|
|
23
29
|
};
|
|
30
|
+
this.recorder.addEventListener("error", () => {
|
|
31
|
+
this.onRecordingError?.();
|
|
32
|
+
});
|
|
24
33
|
this.startedAt = Date.now();
|
|
25
34
|
this.recorder.start();
|
|
26
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.js","sourceRoot":"","sources":["../../src/camera/VideoRecorder.ts"],"names":[],"mappings":";;;AAAA,MAAa,aAAa;IAA1B;QACU,aAAQ,GAAyB,IAAI,CAAC;QACtC,WAAM,GAAW,EAAE,CAAC;QACpB,cAAS,GAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoRecorder.js","sourceRoot":"","sources":["../../src/camera/VideoRecorder.ts"],"names":[],"mappings":";;;AAAA,kDAA+D;AAE/D,MAAa,aAAa;IAA1B;QACU,aAAQ,GAAyB,IAAI,CAAC;QACtC,WAAM,GAAW,EAAE,CAAC;QACpB,cAAS,GAAW,CAAC,CAAC;QACtB,qBAAgB,GAAwB,IAAI,CAAC;IA8DvD,CAAC;IA5DC,wHAAwH;IACxH,OAAO,CAAC,QAAoB;QAC1B,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,MAAmB;QACvB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QAEjB,MAAM,QAAQ,GAAG,aAAa,CAAC,eAAe,CAAC,uBAAuB,CAAC;YACrE,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,YAAY,CAAC;QAEjB,IAAI,CAAC,QAAQ,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE;YACxC,QAAQ;YACR,kBAAkB,EAAE,qCAAyB;SAC9C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAC,EAAE,EAAE;YACpC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3C,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACzD,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;gBAC5C,OAAO;YACT,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;gBAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;gBAC3D,MAAM,eAAe,GAAG,UAAU,CAChC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CACjD,CAAC;gBACF,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;gBACjB,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,GAAG,EAAE;gBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACxC,CAAC,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK,WAAW,CAAC;IAC9C,CAAC;CACF;AAlED,sCAkEC"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export declare class CameraManager {
|
|
2
2
|
private stream;
|
|
3
|
+
private onInterrupted;
|
|
4
|
+
private trackEndedHandlers;
|
|
5
|
+
/**
|
|
6
|
+
* Opens the camera via requestCameraAccess(), which maps browser errors
|
|
7
|
+
* (denied, no device, in-use-by-another-app) to a clean message thrown here.
|
|
8
|
+
*/
|
|
3
9
|
open(constraints?: MediaStreamConstraints): Promise<MediaStream>;
|
|
4
10
|
getStream(): MediaStream | null;
|
|
5
11
|
/**
|
|
@@ -9,6 +15,16 @@ export declare class CameraManager {
|
|
|
9
15
|
* stop() becomes a no-op for it, leaking the camera.
|
|
10
16
|
*/
|
|
11
17
|
setStream(stream: MediaStream): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register a callback fired the moment any track in the current stream
|
|
20
|
+
* ends unexpectedly (OS revokes camera permission mid-session, another
|
|
21
|
+
* app steals the camera, device sleeps and the driver drops the feed).
|
|
22
|
+
* Without this, a dead stream is silently invisible — the video element
|
|
23
|
+
* just freezes on its last frame and face detection quietly fails
|
|
24
|
+
* forever with no indication anything went wrong.
|
|
25
|
+
*/
|
|
26
|
+
onStreamInterrupted(callback: () => void): void;
|
|
27
|
+
private _watchTracks;
|
|
12
28
|
stop(): void;
|
|
13
29
|
}
|
|
14
30
|
//# sourceMappingURL=CameraManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraManager.d.ts","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"
|
|
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,7 +1,14 @@
|
|
|
1
|
+
import { requestCameraAccess } from "./Permission";
|
|
1
2
|
export class CameraManager {
|
|
2
3
|
constructor() {
|
|
3
4
|
this.stream = null;
|
|
5
|
+
this.onInterrupted = null;
|
|
6
|
+
this.trackEndedHandlers = [];
|
|
4
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Opens the camera via requestCameraAccess(), which maps browser errors
|
|
10
|
+
* (denied, no device, in-use-by-another-app) to a clean message thrown here.
|
|
11
|
+
*/
|
|
5
12
|
async open(constraints) {
|
|
6
13
|
const defaults = {
|
|
7
14
|
video: {
|
|
@@ -11,7 +18,12 @@ export class CameraManager {
|
|
|
11
18
|
},
|
|
12
19
|
audio: false,
|
|
13
20
|
};
|
|
14
|
-
|
|
21
|
+
const { stream, permission } = await requestCameraAccess(constraints || defaults);
|
|
22
|
+
if (!stream) {
|
|
23
|
+
throw new Error(permission.reason || "Unable to access the camera.");
|
|
24
|
+
}
|
|
25
|
+
this.stream = stream;
|
|
26
|
+
this._watchTracks();
|
|
15
27
|
return this.stream;
|
|
16
28
|
}
|
|
17
29
|
getStream() {
|
|
@@ -25,12 +37,43 @@ export class CameraManager {
|
|
|
25
37
|
*/
|
|
26
38
|
setStream(stream) {
|
|
27
39
|
this.stream = stream;
|
|
40
|
+
this._watchTracks();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Register a callback fired the moment any track in the current stream
|
|
44
|
+
* ends unexpectedly (OS revokes camera permission mid-session, another
|
|
45
|
+
* app steals the camera, device sleeps and the driver drops the feed).
|
|
46
|
+
* Without this, a dead stream is silently invisible — the video element
|
|
47
|
+
* just freezes on its last frame and face detection quietly fails
|
|
48
|
+
* forever with no indication anything went wrong.
|
|
49
|
+
*/
|
|
50
|
+
onStreamInterrupted(callback) {
|
|
51
|
+
this.onInterrupted = callback;
|
|
52
|
+
}
|
|
53
|
+
_watchTracks() {
|
|
54
|
+
this.trackEndedHandlers = [];
|
|
55
|
+
if (!this.stream)
|
|
56
|
+
return;
|
|
57
|
+
this.stream.getTracks().forEach((track) => {
|
|
58
|
+
const handler = () => {
|
|
59
|
+
// A track we intentionally stopped (via stop()) also fires "ended" —
|
|
60
|
+
// only treat this as an interruption if we still think we have a
|
|
61
|
+
// live stream (stop() nulls this.stream before that would happen).
|
|
62
|
+
if (this.stream) {
|
|
63
|
+
this.onInterrupted?.();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
track.addEventListener("ended", handler);
|
|
67
|
+
this.trackEndedHandlers.push(() => track.removeEventListener("ended", handler));
|
|
68
|
+
});
|
|
28
69
|
}
|
|
29
70
|
stop() {
|
|
30
71
|
if (this.stream) {
|
|
31
72
|
this.stream.getTracks().forEach((track) => track.stop());
|
|
32
73
|
this.stream = null;
|
|
33
74
|
}
|
|
75
|
+
this.trackEndedHandlers.forEach((remove) => remove());
|
|
76
|
+
this.trackEndedHandlers = [];
|
|
34
77
|
}
|
|
35
78
|
}
|
|
36
79
|
//# sourceMappingURL=CameraManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraManager.js","sourceRoot":"","sources":["../../../src/camera/CameraManager.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAa;IAA1B;QACU,WAAM,GAAuB,IAAI,CAAC;
|
|
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"}
|
|
@@ -2,6 +2,9 @@ export declare class VideoRecorder {
|
|
|
2
2
|
private recorder;
|
|
3
3
|
private chunks;
|
|
4
4
|
private startedAt;
|
|
5
|
+
private onRecordingError;
|
|
6
|
+
/** Fires immediately on a mid-recording error, unlike stop()'s onerror which only fires when stop() is later called. */
|
|
7
|
+
onError(callback: () => void): void;
|
|
5
8
|
start(stream: MediaStream): void;
|
|
6
9
|
stop(): Promise<{
|
|
7
10
|
blob: Blob;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"AAEA,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,gBAAgB,CAA6B;IAErD,wHAAwH;IACxH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IA0BhC,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IA0BxD,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { RECORDING_BITS_PER_SECOND } from "../utils/constants";
|
|
1
2
|
export class VideoRecorder {
|
|
2
3
|
constructor() {
|
|
3
4
|
this.recorder = null;
|
|
4
5
|
this.chunks = [];
|
|
5
6
|
this.startedAt = 0;
|
|
7
|
+
this.onRecordingError = null;
|
|
8
|
+
}
|
|
9
|
+
/** Fires immediately on a mid-recording error, unlike stop()'s onerror which only fires when stop() is later called. */
|
|
10
|
+
onError(callback) {
|
|
11
|
+
this.onRecordingError = callback;
|
|
6
12
|
}
|
|
7
13
|
start(stream) {
|
|
8
14
|
this.chunks = [];
|
|
@@ -11,13 +17,16 @@ export class VideoRecorder {
|
|
|
11
17
|
: "video/webm";
|
|
12
18
|
this.recorder = new MediaRecorder(stream, {
|
|
13
19
|
mimeType,
|
|
14
|
-
videoBitsPerSecond:
|
|
20
|
+
videoBitsPerSecond: RECORDING_BITS_PER_SECOND,
|
|
15
21
|
});
|
|
16
22
|
this.recorder.ondataavailable = (e) => {
|
|
17
23
|
if (e.data.size > 0) {
|
|
18
24
|
this.chunks.push(e.data);
|
|
19
25
|
}
|
|
20
26
|
};
|
|
27
|
+
this.recorder.addEventListener("error", () => {
|
|
28
|
+
this.onRecordingError?.();
|
|
29
|
+
});
|
|
21
30
|
this.startedAt = Date.now();
|
|
22
31
|
this.recorder.start();
|
|
23
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.js","sourceRoot":"","sources":["../../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAa;IAA1B;QACU,aAAQ,GAAyB,IAAI,CAAC;QACtC,WAAM,GAAW,EAAE,CAAC;QACpB,cAAS,GAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoRecorder.js","sourceRoot":"","sources":["../../../src/camera/VideoRecorder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,MAAM,OAAO,aAAa;IAA1B;QACU,aAAQ,GAAyB,IAAI,CAAC;QACtC,WAAM,GAAW,EAAE,CAAC;QACpB,cAAS,GAAW,CAAC,CAAC;QACtB,qBAAgB,GAAwB,IAAI,CAAC;IA8DvD,CAAC;IA5DC,wHAAwH;IACxH,OAAO,CAAC,QAAoB;QAC1B,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,MAAmB;QACvB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QAEjB,MAAM,QAAQ,GAAG,aAAa,CAAC,eAAe,CAAC,uBAAuB,CAAC;YACrE,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,YAAY,CAAC;QAEjB,IAAI,CAAC,QAAQ,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE;YACxC,QAAQ;YACR,kBAAkB,EAAE,yBAAyB;SAC9C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAC,EAAE,EAAE;YACpC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3C,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACzD,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;gBAC5C,OAAO;YACT,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;gBAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;gBAC3D,MAAM,eAAe,GAAG,UAAU,CAChC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CACjD,CAAC;gBACF,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;gBACjB,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,OAAO,GAAG,GAAG,EAAE;gBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACxC,CAAC,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK,WAAW,CAAC;IAC9C,CAAC;CACF"}
|
|
@@ -44,6 +44,7 @@ export declare class SmartComplyFlow {
|
|
|
44
44
|
private livenessEntryId;
|
|
45
45
|
private isDestroyed;
|
|
46
46
|
private headerBackBtn;
|
|
47
|
+
private _keydownHandler;
|
|
47
48
|
private _confirmRetries;
|
|
48
49
|
private _livenessFailures;
|
|
49
50
|
private static readonly MAX_RETRIES;
|
|
@@ -82,6 +83,7 @@ export declare class SmartComplyFlow {
|
|
|
82
83
|
close(): void;
|
|
83
84
|
private mount;
|
|
84
85
|
private initialize;
|
|
86
|
+
private _showExpiryWarningWhenSafe;
|
|
85
87
|
private showStep;
|
|
86
88
|
private renderStep;
|
|
87
89
|
private renderLoading;
|
|
@@ -108,7 +110,6 @@ export declare class SmartComplyFlow {
|
|
|
108
110
|
private processDocumentBlob;
|
|
109
111
|
private handleDocumentProcessingError;
|
|
110
112
|
private renderLiveness;
|
|
111
|
-
private renderResult;
|
|
112
113
|
private showErrorStep;
|
|
113
114
|
private createHeader;
|
|
114
115
|
private createFooter;
|
|
@@ -116,6 +117,8 @@ export declare class SmartComplyFlow {
|
|
|
116
117
|
private createOptionCard;
|
|
117
118
|
private createPrimaryButton;
|
|
118
119
|
private createSecondaryButton;
|
|
120
|
+
/** Click handler that fires once and disables the button, preventing a double-tap double-dispatch. */
|
|
121
|
+
private _onceClick;
|
|
119
122
|
private isDocumentFlow;
|
|
120
123
|
private updateProgress;
|
|
121
124
|
private applyTheme;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIxF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CAC7C;
|
|
1
|
+
{"version":3,"file":"SmartComplyFlow.d.ts","sourceRoot":"","sources":["../../../src/flow/SmartComplyFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,6 @@ const STEP_ORDER = [
|
|
|
14
14
|
"document_confirm",
|
|
15
15
|
"liveness",
|
|
16
16
|
"done",
|
|
17
|
-
"result",
|
|
18
17
|
];
|
|
19
18
|
const COUNTRY_LABELS = {
|
|
20
19
|
nigeria: "🇳🇬 Nigeria",
|
|
@@ -279,6 +278,7 @@ export class SmartComplyFlow {
|
|
|
279
278
|
this.livenessEntryId = null;
|
|
280
279
|
this.isDestroyed = false;
|
|
281
280
|
this.headerBackBtn = null;
|
|
281
|
+
this._keydownHandler = null;
|
|
282
282
|
// Retry safety — max 3 confirmation rejections or liveness failures per session
|
|
283
283
|
this._confirmRetries = 0;
|
|
284
284
|
this._livenessFailures = 0;
|
|
@@ -336,6 +336,10 @@ export class SmartComplyFlow {
|
|
|
336
336
|
*/
|
|
337
337
|
close() {
|
|
338
338
|
this.isDestroyed = true;
|
|
339
|
+
if (this._keydownHandler) {
|
|
340
|
+
document.removeEventListener("keydown", this._keydownHandler);
|
|
341
|
+
this._keydownHandler = null;
|
|
342
|
+
}
|
|
339
343
|
this.docCapture?.destroy();
|
|
340
344
|
// Release any pre-warmed camera stream
|
|
341
345
|
if (this._prewarmedStream) {
|
|
@@ -442,6 +446,30 @@ export class SmartComplyFlow {
|
|
|
442
446
|
if (this.modal)
|
|
443
447
|
this.modal.style.transform = "translateY(0) scale(1)";
|
|
444
448
|
});
|
|
449
|
+
// Escape closes the dialog; Tab is trapped inside it (standard WCAG dialog pattern).
|
|
450
|
+
this._keydownHandler = (e) => {
|
|
451
|
+
if (e.key === "Escape") {
|
|
452
|
+
e.preventDefault();
|
|
453
|
+
this.close();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (e.key === "Tab" && this.modal) {
|
|
457
|
+
const focusable = this.modal.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');
|
|
458
|
+
if (focusable.length === 0)
|
|
459
|
+
return;
|
|
460
|
+
const first = focusable[0];
|
|
461
|
+
const last = focusable[focusable.length - 1];
|
|
462
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
463
|
+
e.preventDefault();
|
|
464
|
+
last.focus();
|
|
465
|
+
}
|
|
466
|
+
else if (!e.shiftKey && document.activeElement === last) {
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
first.focus();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
document.addEventListener("keydown", this._keydownHandler);
|
|
445
473
|
}
|
|
446
474
|
async initialize() {
|
|
447
475
|
this.showStep("loading");
|
|
@@ -453,11 +481,7 @@ export class SmartComplyFlow {
|
|
|
453
481
|
if (expiresAt) {
|
|
454
482
|
const expiresMs = new Date(expiresAt).getTime() - Date.now();
|
|
455
483
|
const warnMs = Math.max(expiresMs - 120000, 0); // warn 2 min before
|
|
456
|
-
this.sessionExpiryTimer = setTimeout(() =>
|
|
457
|
-
if (!this.isDestroyed && this.currentStep !== "result") {
|
|
458
|
-
this.showErrorStep("Your session is about to expire. Please complete verification quickly or restart.");
|
|
459
|
-
}
|
|
460
|
-
}, warnMs);
|
|
484
|
+
this.sessionExpiryTimer = setTimeout(() => this._showExpiryWarningWhenSafe(), warnMs);
|
|
461
485
|
}
|
|
462
486
|
// Load config
|
|
463
487
|
this.sdkConfig = await this.sdk.initializeConfig();
|
|
@@ -478,6 +502,19 @@ export class SmartComplyFlow {
|
|
|
478
502
|
this.showErrorStep(err.message || "Failed to initialize. Please try again.");
|
|
479
503
|
}
|
|
480
504
|
}
|
|
505
|
+
// Defers the expiry warning while the user is on a camera-active step
|
|
506
|
+
// (document_capture/liveness) — showing it there would blow away a live
|
|
507
|
+
// video element and in-progress MediaRecorder mid-scan.
|
|
508
|
+
_showExpiryWarningWhenSafe() {
|
|
509
|
+
if (this.isDestroyed)
|
|
510
|
+
return;
|
|
511
|
+
const cameraActiveSteps = ["document_capture", "liveness"];
|
|
512
|
+
if (cameraActiveSteps.includes(this.currentStep)) {
|
|
513
|
+
this.sessionExpiryTimer = setTimeout(() => this._showExpiryWarningWhenSafe(), 5000);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.showErrorStep("Your session is about to expire. Please complete verification quickly or restart.");
|
|
517
|
+
}
|
|
481
518
|
// ─────────────────────────────────────────────────────────────────
|
|
482
519
|
// Step rendering
|
|
483
520
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -547,18 +584,12 @@ export class SmartComplyFlow {
|
|
|
547
584
|
case "document_confirm":
|
|
548
585
|
this.renderDocumentConfirm(wrapper);
|
|
549
586
|
break;
|
|
550
|
-
case "processing":
|
|
551
|
-
// C1: processing step is dead code — flow now goes to 'done' after submit
|
|
552
|
-
break;
|
|
553
587
|
case "liveness":
|
|
554
588
|
this.renderLiveness(wrapper);
|
|
555
589
|
break;
|
|
556
590
|
case "done":
|
|
557
591
|
this.renderDone(wrapper);
|
|
558
592
|
break;
|
|
559
|
-
case "result":
|
|
560
|
-
// C2: result step is dead code — showResult/renderResult removed
|
|
561
|
-
break;
|
|
562
593
|
case "error":
|
|
563
594
|
break;
|
|
564
595
|
}
|
|
@@ -568,6 +599,23 @@ export class SmartComplyFlow {
|
|
|
568
599
|
wrapper.style.opacity = "1";
|
|
569
600
|
wrapper.style.transform = "translateX(0)";
|
|
570
601
|
});
|
|
602
|
+
// Move focus to the new step's heading so keyboard/screen-reader users
|
|
603
|
+
// aren't left on a now-detached button. Skipped for camera-active steps
|
|
604
|
+
// and id_input, which already focuses its first field itself.
|
|
605
|
+
const skipAutoFocusSteps = ["document_capture", "liveness", "id_input"];
|
|
606
|
+
if (!skipAutoFocusSteps.includes(step)) {
|
|
607
|
+
requestAnimationFrame(() => {
|
|
608
|
+
const heading = wrapper.querySelector("h1, h2, h3");
|
|
609
|
+
const target = heading ||
|
|
610
|
+
wrapper.querySelector('button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])');
|
|
611
|
+
if (target) {
|
|
612
|
+
if (heading && !heading.hasAttribute("tabindex")) {
|
|
613
|
+
heading.setAttribute("tabindex", "-1");
|
|
614
|
+
}
|
|
615
|
+
target.focus({ preventScroll: true });
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
571
619
|
}
|
|
572
620
|
// ── Loading ─────────────────────────────────────────────────────
|
|
573
621
|
renderLoading(container) {
|
|
@@ -1239,14 +1287,14 @@ export class SmartComplyFlow {
|
|
|
1239
1287
|
container.appendChild(sub);
|
|
1240
1288
|
}
|
|
1241
1289
|
const yesBtn = this.createPrimaryButton("Yes, that's me — continue");
|
|
1242
|
-
|
|
1290
|
+
this._onceClick(yesBtn, () => {
|
|
1243
1291
|
// B5: Reset retry counter on successful confirmation
|
|
1244
1292
|
this._confirmRetries = 0;
|
|
1245
1293
|
this.showStep("liveness");
|
|
1246
1294
|
});
|
|
1247
1295
|
container.appendChild(yesBtn);
|
|
1248
1296
|
const noBtn = this.createSecondaryButton("No, use a different ID");
|
|
1249
|
-
|
|
1297
|
+
this._onceClick(noBtn, () => {
|
|
1250
1298
|
this._confirmRetries++;
|
|
1251
1299
|
this._ivsName = "";
|
|
1252
1300
|
this._ivsGender = "";
|
|
@@ -1332,14 +1380,14 @@ export class SmartComplyFlow {
|
|
|
1332
1380
|
}
|
|
1333
1381
|
container.appendChild(card);
|
|
1334
1382
|
const yesBtn = this.createPrimaryButton("Yes, continue");
|
|
1335
|
-
|
|
1383
|
+
this._onceClick(yesBtn, () => {
|
|
1336
1384
|
// B5: Reset retry counter on successful confirmation
|
|
1337
1385
|
this._confirmRetries = 0;
|
|
1338
1386
|
this.showStep("liveness");
|
|
1339
1387
|
});
|
|
1340
1388
|
container.appendChild(yesBtn);
|
|
1341
1389
|
const noBtn = this.createSecondaryButton("No, retake document");
|
|
1342
|
-
|
|
1390
|
+
this._onceClick(noBtn, () => {
|
|
1343
1391
|
this._confirmRetries++;
|
|
1344
1392
|
this._ocrName = "";
|
|
1345
1393
|
this._ocrDocNumber = "";
|
|
@@ -1840,9 +1888,6 @@ export class SmartComplyFlow {
|
|
|
1840
1888
|
this.showErrorStep(err.message || "Liveness verification failed. Please try again.");
|
|
1841
1889
|
});
|
|
1842
1890
|
}
|
|
1843
|
-
renderResult(_container) {
|
|
1844
|
-
// C2: Dead code — flow routes to 'done' step, not 'result'. Kept as stub.
|
|
1845
|
-
}
|
|
1846
1891
|
// ── Error ───────────────────────────────────────────────────────
|
|
1847
1892
|
showErrorStep(message) {
|
|
1848
1893
|
this.currentStep = "error";
|
|
@@ -2128,6 +2173,17 @@ export class SmartComplyFlow {
|
|
|
2128
2173
|
});
|
|
2129
2174
|
return btn;
|
|
2130
2175
|
}
|
|
2176
|
+
/** Click handler that fires once and disables the button, preventing a double-tap double-dispatch. */
|
|
2177
|
+
_onceClick(btn, handler) {
|
|
2178
|
+
btn.addEventListener("click", () => {
|
|
2179
|
+
if (btn.disabled)
|
|
2180
|
+
return;
|
|
2181
|
+
btn.disabled = true;
|
|
2182
|
+
btn.style.opacity = "0.6";
|
|
2183
|
+
btn.style.cursor = "default";
|
|
2184
|
+
handler();
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2131
2187
|
isDocumentFlow() {
|
|
2132
2188
|
// 1. Check verification_type — handle both string and array from backend
|
|
2133
2189
|
const vType = this.sdkConfig?.verification_type;
|
|
@@ -2157,7 +2213,7 @@ export class SmartComplyFlow {
|
|
|
2157
2213
|
const allActionSteps = [...stage1Steps, ...stage2Steps];
|
|
2158
2214
|
const totalSteps = allActionSteps.length;
|
|
2159
2215
|
const actionIndex = allActionSteps.indexOf(this.currentStep);
|
|
2160
|
-
const pct = this.currentStep === "done"
|
|
2216
|
+
const pct = this.currentStep === "done" ? 100
|
|
2161
2217
|
: actionIndex >= 0 ? ((actionIndex + 1) / totalSteps) * 100
|
|
2162
2218
|
: 0;
|
|
2163
2219
|
if (this.progressBar) {
|
|
@@ -2168,7 +2224,7 @@ export class SmartComplyFlow {
|
|
|
2168
2224
|
if (this.currentStep === "loading" || this.currentStep === "welcome") {
|
|
2169
2225
|
this.stepLabel.textContent = "";
|
|
2170
2226
|
}
|
|
2171
|
-
else if (this.currentStep === "done"
|
|
2227
|
+
else if (this.currentStep === "done") {
|
|
2172
2228
|
this.stepLabel.textContent = "Complete ✓";
|
|
2173
2229
|
}
|
|
2174
2230
|
else if (stage2Steps.includes(this.currentStep)) {
|
|
@@ -2186,7 +2242,7 @@ export class SmartComplyFlow {
|
|
|
2186
2242
|
const dots = Array.from(this.stageDots.children);
|
|
2187
2243
|
const inStage1 = stage1Steps.includes(this.currentStep);
|
|
2188
2244
|
const inStage2 = stage2Steps.includes(this.currentStep);
|
|
2189
|
-
const isComplete = this.currentStep === "done"
|
|
2245
|
+
const isComplete = this.currentStep === "done";
|
|
2190
2246
|
const stage1Active = inStage1 || inStage2 || isComplete;
|
|
2191
2247
|
const stage2Active = inStage2 || isComplete;
|
|
2192
2248
|
if (dots[0])
|