@sekyro/web 0.1.0-rc.3 → 0.1.0-rc.5

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 (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +44 -0
  3. package/dist/{capture-session-v3-CvIfcwST.d.ts → capture-session-v3-Okq0EQHG.d.ts} +38 -2
  4. package/dist/{capture-session-v3-COIO_F2w.d.cts → capture-session-v3-UQU_WjO2.d.cts} +38 -2
  5. package/dist/{chunk-FIXX2AXC.js → chunk-SGDXLAXR.js} +309 -10
  6. package/dist/chunk-SGDXLAXR.js.map +1 -0
  7. package/dist/{chunk-7TC6TOZ4.js → chunk-TG5IE552.js} +37 -4
  8. package/dist/chunk-TG5IE552.js.map +1 -0
  9. package/dist/client/index.cjs.map +1 -1
  10. package/dist/client/index.d.cts +1 -1
  11. package/dist/client/index.d.ts +1 -1
  12. package/dist/core/index.cjs +36 -3
  13. package/dist/core/index.cjs.map +1 -1
  14. package/dist/core/index.d.cts +2 -2
  15. package/dist/core/index.d.ts +2 -2
  16. package/dist/core/index.js +1 -1
  17. package/dist/{create-verification-CswF2As-.d.ts → create-verification-CA-3szPI.d.cts} +108 -8
  18. package/dist/{create-verification-CP18eq1Q.d.cts → create-verification-c98uAxbl.d.ts} +108 -8
  19. package/dist/{hosted-client-BpvBqu9f.d.cts → hosted-client-DWzCRYJK.d.cts} +14 -1
  20. package/dist/{hosted-client-BpvBqu9f.d.ts → hosted-client-DWzCRYJK.d.ts} +14 -1
  21. package/dist/index.cjs +384 -13
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +25 -3
  24. package/dist/index.d.ts +25 -3
  25. package/dist/index.js +61 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal/index.cjs +261 -11
  28. package/dist/internal/index.cjs.map +1 -1
  29. package/dist/internal/index.d.cts +4 -4
  30. package/dist/internal/index.d.ts +4 -4
  31. package/dist/internal/index.js +12 -2
  32. package/dist/internal/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-7TC6TOZ4.js.map +0 -1
  35. package/dist/chunk-FIXX2AXC.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,6 +1,28 @@
1
- export { C as CaptureChannel, a as CaptureError, b as CaptureErrorKind, c as CaptureState, d as CapturedArtifacts, D as DEFAULT_API_ORIGIN, G as GateHint, e as GateUpdate, L as LivenessCaptureEvents, f as LivenessCaptureHandle, g as LivenessCaptureOptions, R as ReasonCode, S as SdkFailureReason, V as Verification, h as VerificationEvents, i as VerificationLocale, j as VerificationOptions, k as VerificationResult, l as VerificationStatus, m as createLivenessCapture, n as createVerification } from './create-verification-CP18eq1Q.cjs';
1
+ export { C as CameraFacing, a as CaptureChannel, b as CaptureEngineId, c as CaptureError, d as CaptureErrorKind, e as CaptureState, f as CapturedArtifacts, D as DEFAULT_API_ORIGIN, G as GateHint, g as GateUpdate, L as LivenessAssetsStatus, h as LivenessCaptureEvents, i as LivenessCaptureHandle, j as LivenessCaptureOptions, k as LivenessStartOptions, P as PreloadLivenessAssetsOptions, R as ReasonCode, S as SdkFailureReason, V as Verification, l as VerificationEvents, m as VerificationLocale, n as VerificationOptions, o as VerificationResult, p as VerificationStatus, q as createLivenessCapture, r as createVerification, s as getLivenessAssetsStatus, t as preloadLivenessAssets, u as releaseLivenessAssets, v as subscribeLivenessAssets } from './create-verification-CA-3szPI.cjs';
2
2
  export { OpenHostedOptions, PopupBlocked, PopupHandle, SEKYRO_EVENTS, SekyroEvent, SekyroEventName, openHosted } from './popup/index.cjs';
3
3
  export { HostedApiError, KNOWN_CODES, KnownCode, isKnownCode } from './client/index.cjs';
4
- export { C as CheckReason, a as CheckState, b as CheckType, D as DocReason, F as FaceMatchReason, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, L as LivenessReason, P as PhoneOtpReason, e as PopulationReason, S as SessionOutcome, f as SessionStatus } from './hosted-client-BpvBqu9f.cjs';
5
- import './capture-session-v3-COIO_F2w.cjs';
4
+ export { C as CheckReason, a as CheckState, b as CheckType, D as DocReason, F as FaceMatchReason, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, L as LivenessReason, P as PhoneOtpReason, e as PopulationReason, S as SessionOutcome, f as SessionStatus } from './hosted-client-DWzCRYJK.cjs';
6
5
  import './detect-worker-CKXHlXx9.cjs';
6
+ import './capture-session-v3-UQU_WjO2.cjs';
7
+
8
+ /** Camera permission as the Permissions API reports it; `'unknown'` where it cannot tell. */
9
+ type CameraPermissionState = "granted" | "prompt" | "denied" | "unknown";
10
+ /** One-shot camera permission read. */
11
+ declare function queryCameraPermission(): Promise<CameraPermissionState>;
12
+ /**
13
+ * Subscribe to camera-permission changes; returns the unsubscribe function.
14
+ * Unlike {@link subscribeLivenessAssets}, it DOES emit the current state first
15
+ * (as soon as the Permissions API answers — asynchronously, `'unknown'` where
16
+ * unsupported), then on every change. Not a `useSyncExternalStore` source:
17
+ * there is no synchronous snapshot — drive a `useState` from it.
18
+ */
19
+ declare function subscribeCameraPermission(listener: (state: CameraPermissionState) => void): () => void;
20
+ /**
21
+ * Whether a front/back choice makes sense: phones and tablets (UA, incl. iPadOS's
22
+ * desktop UA) always have both; otherwise ≥ 2 `videoinput` devices. Before a
23
+ * permission was ever granted some desktops report a single placeholder — the
24
+ * in-capture switch remains available either way.
25
+ */
26
+ declare function hasMultipleCameras(): Promise<boolean>;
27
+
28
+ export { type CameraPermissionState, hasMultipleCameras, queryCameraPermission, subscribeCameraPermission };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,28 @@
1
- export { C as CaptureChannel, a as CaptureError, b as CaptureErrorKind, c as CaptureState, d as CapturedArtifacts, D as DEFAULT_API_ORIGIN, G as GateHint, e as GateUpdate, L as LivenessCaptureEvents, f as LivenessCaptureHandle, g as LivenessCaptureOptions, R as ReasonCode, S as SdkFailureReason, V as Verification, h as VerificationEvents, i as VerificationLocale, j as VerificationOptions, k as VerificationResult, l as VerificationStatus, m as createLivenessCapture, n as createVerification } from './create-verification-CswF2As-.js';
1
+ export { C as CameraFacing, a as CaptureChannel, b as CaptureEngineId, c as CaptureError, d as CaptureErrorKind, e as CaptureState, f as CapturedArtifacts, D as DEFAULT_API_ORIGIN, G as GateHint, g as GateUpdate, L as LivenessAssetsStatus, h as LivenessCaptureEvents, i as LivenessCaptureHandle, j as LivenessCaptureOptions, k as LivenessStartOptions, P as PreloadLivenessAssetsOptions, R as ReasonCode, S as SdkFailureReason, V as Verification, l as VerificationEvents, m as VerificationLocale, n as VerificationOptions, o as VerificationResult, p as VerificationStatus, q as createLivenessCapture, r as createVerification, s as getLivenessAssetsStatus, t as preloadLivenessAssets, u as releaseLivenessAssets, v as subscribeLivenessAssets } from './create-verification-c98uAxbl.js';
2
2
  export { OpenHostedOptions, PopupBlocked, PopupHandle, SEKYRO_EVENTS, SekyroEvent, SekyroEventName, openHosted } from './popup/index.js';
3
3
  export { HostedApiError, KNOWN_CODES, KnownCode, isKnownCode } from './client/index.js';
4
- export { C as CheckReason, a as CheckState, b as CheckType, D as DocReason, F as FaceMatchReason, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, L as LivenessReason, P as PhoneOtpReason, e as PopulationReason, S as SessionOutcome, f as SessionStatus } from './hosted-client-BpvBqu9f.js';
5
- import './capture-session-v3-CvIfcwST.js';
4
+ export { C as CheckReason, a as CheckState, b as CheckType, D as DocReason, F as FaceMatchReason, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, L as LivenessReason, P as PhoneOtpReason, e as PopulationReason, S as SessionOutcome, f as SessionStatus } from './hosted-client-DWzCRYJK.js';
6
5
  import './detect-worker-CKXHlXx9.js';
6
+ import './capture-session-v3-Okq0EQHG.js';
7
+
8
+ /** Camera permission as the Permissions API reports it; `'unknown'` where it cannot tell. */
9
+ type CameraPermissionState = "granted" | "prompt" | "denied" | "unknown";
10
+ /** One-shot camera permission read. */
11
+ declare function queryCameraPermission(): Promise<CameraPermissionState>;
12
+ /**
13
+ * Subscribe to camera-permission changes; returns the unsubscribe function.
14
+ * Unlike {@link subscribeLivenessAssets}, it DOES emit the current state first
15
+ * (as soon as the Permissions API answers — asynchronously, `'unknown'` where
16
+ * unsupported), then on every change. Not a `useSyncExternalStore` source:
17
+ * there is no synchronous snapshot — drive a `useState` from it.
18
+ */
19
+ declare function subscribeCameraPermission(listener: (state: CameraPermissionState) => void): () => void;
20
+ /**
21
+ * Whether a front/back choice makes sense: phones and tablets (UA, incl. iPadOS's
22
+ * desktop UA) always have both; otherwise ≥ 2 `videoinput` devices. Before a
23
+ * permission was ever granted some desktops report a single placeholder — the
24
+ * in-capture switch remains available either way.
25
+ */
26
+ declare function hasMultipleCameras(): Promise<boolean>;
27
+
28
+ export { type CameraPermissionState, hasMultipleCameras, queryCameraPermission, subscribeCameraPermission };
package/dist/index.js CHANGED
@@ -5,9 +5,13 @@ import {
5
5
  import {
6
6
  DEFAULT_API_ORIGIN,
7
7
  createLivenessCapture,
8
- createVerification
9
- } from "./chunk-FIXX2AXC.js";
10
- import "./chunk-7TC6TOZ4.js";
8
+ createVerification,
9
+ getLivenessAssetsStatus,
10
+ preloadLivenessAssets,
11
+ releaseLivenessAssets,
12
+ subscribeLivenessAssets
13
+ } from "./chunk-SGDXLAXR.js";
14
+ import "./chunk-TG5IE552.js";
11
15
  import {
12
16
  HostedApiError,
13
17
  KNOWN_CODES,
@@ -15,6 +19,52 @@ import {
15
19
  } from "./chunk-YVFF4Q4G.js";
16
20
  import "./chunk-NWF46H7K.js";
17
21
  import "./chunk-MWNFSNMP.js";
22
+
23
+ // src/capture/camera-probe.ts
24
+ async function permissionStatus() {
25
+ try {
26
+ if (typeof navigator === "undefined" || typeof navigator.permissions?.query !== "function") return null;
27
+ return await navigator.permissions.query({ name: "camera" });
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function asState(state) {
33
+ return state === "granted" || state === "prompt" || state === "denied" ? state : "unknown";
34
+ }
35
+ async function queryCameraPermission() {
36
+ const status = await permissionStatus();
37
+ return status === null ? "unknown" : asState(status.state);
38
+ }
39
+ function subscribeCameraPermission(listener) {
40
+ let stopped = false;
41
+ let status = null;
42
+ const onChange = () => {
43
+ if (!stopped && status !== null) listener(asState(status.state));
44
+ };
45
+ void permissionStatus().then((resolved) => {
46
+ if (stopped) return;
47
+ status = resolved;
48
+ listener(resolved === null ? "unknown" : asState(resolved.state));
49
+ resolved?.addEventListener?.("change", onChange);
50
+ });
51
+ return () => {
52
+ stopped = true;
53
+ status?.removeEventListener?.("change", onChange);
54
+ };
55
+ }
56
+ async function hasMultipleCameras() {
57
+ if (typeof navigator === "undefined") return false;
58
+ const ua = String(navigator.userAgent ?? "");
59
+ if (/Android|iPhone|iPad|iPod/i.test(ua)) return true;
60
+ if (/Macintosh/.test(ua) && (navigator.maxTouchPoints ?? 0) > 1) return true;
61
+ try {
62
+ const devices = await navigator.mediaDevices?.enumerateDevices?.() ?? [];
63
+ return devices.filter((device) => device.kind === "videoinput").length >= 2;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
18
68
  export {
19
69
  DEFAULT_API_ORIGIN,
20
70
  HostedApiError,
@@ -22,7 +72,14 @@ export {
22
72
  SEKYRO_EVENTS,
23
73
  createLivenessCapture,
24
74
  createVerification,
75
+ getLivenessAssetsStatus,
76
+ hasMultipleCameras,
25
77
  isKnownCode,
26
- openHosted
78
+ openHosted,
79
+ preloadLivenessAssets,
80
+ queryCameraPermission,
81
+ releaseLivenessAssets,
82
+ subscribeCameraPermission,
83
+ subscribeLivenessAssets
27
84
  };
28
85
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../src/capture/camera-probe.ts"],"sourcesContent":["// Camera probes for pre-capture UI (spec D6, D7). Every function is SSR-safe and\n// never throws: a missing / throwing API (Safari < 16 on {name:'camera'},\n// Firefox) degrades to 'unknown' / false.\n\n/** Camera permission as the Permissions API reports it; `'unknown'` where it cannot tell. */\nexport type CameraPermissionState = \"granted\" | \"prompt\" | \"denied\" | \"unknown\";\n\nasync function permissionStatus(): Promise<PermissionStatus | null> {\n try {\n if (typeof navigator === \"undefined\" || typeof navigator.permissions?.query !== \"function\") return null;\n return await navigator.permissions.query({ name: \"camera\" as PermissionName });\n } catch {\n return null;\n }\n}\n\nfunction asState(state: string): CameraPermissionState {\n return state === \"granted\" || state === \"prompt\" || state === \"denied\" ? state : \"unknown\";\n}\n\n/** One-shot camera permission read. */\nexport async function queryCameraPermission(): Promise<CameraPermissionState> {\n const status = await permissionStatus();\n return status === null ? \"unknown\" : asState(status.state);\n}\n\n/**\n * Subscribe to camera-permission changes; returns the unsubscribe function.\n * Unlike {@link subscribeLivenessAssets}, it DOES emit the current state first\n * (as soon as the Permissions API answers — asynchronously, `'unknown'` where\n * unsupported), then on every change. Not a `useSyncExternalStore` source:\n * there is no synchronous snapshot — drive a `useState` from it.\n */\nexport function subscribeCameraPermission(listener: (state: CameraPermissionState) => void): () => void {\n let stopped = false;\n let status: PermissionStatus | null = null;\n const onChange = (): void => {\n if (!stopped && status !== null) listener(asState(status.state));\n };\n void permissionStatus().then((resolved) => {\n if (stopped) return;\n status = resolved;\n listener(resolved === null ? \"unknown\" : asState(resolved.state));\n resolved?.addEventListener?.(\"change\", onChange);\n });\n return () => {\n stopped = true;\n status?.removeEventListener?.(\"change\", onChange);\n };\n}\n\n/**\n * Whether a front/back choice makes sense: phones and tablets (UA, incl. iPadOS's\n * desktop UA) always have both; otherwise ≥ 2 `videoinput` devices. Before a\n * permission was ever granted some desktops report a single placeholder — the\n * in-capture switch remains available either way.\n */\nexport async function hasMultipleCameras(): Promise<boolean> {\n if (typeof navigator === \"undefined\") return false;\n const ua = String(navigator.userAgent ?? \"\");\n if (/Android|iPhone|iPad|iPod/i.test(ua)) return true;\n if (/Macintosh/.test(ua) && (navigator.maxTouchPoints ?? 0) > 1) return true;\n try {\n const devices = (await navigator.mediaDevices?.enumerateDevices?.()) ?? [];\n return devices.filter((device) => device.kind === \"videoinput\").length >= 2;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAOA,eAAe,mBAAqD;AAClE,MAAI;AACF,QAAI,OAAO,cAAc,eAAe,OAAO,UAAU,aAAa,UAAU,WAAY,QAAO;AACnG,WAAO,MAAM,UAAU,YAAY,MAAM,EAAE,MAAM,SAA2B,CAAC;AAAA,EAC/E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,QAAQ,OAAsC;AACrD,SAAO,UAAU,aAAa,UAAU,YAAY,UAAU,WAAW,QAAQ;AACnF;AAGA,eAAsB,wBAAwD;AAC5E,QAAM,SAAS,MAAM,iBAAiB;AACtC,SAAO,WAAW,OAAO,YAAY,QAAQ,OAAO,KAAK;AAC3D;AASO,SAAS,0BAA0B,UAA8D;AACtG,MAAI,UAAU;AACd,MAAI,SAAkC;AACtC,QAAM,WAAW,MAAY;AAC3B,QAAI,CAAC,WAAW,WAAW,KAAM,UAAS,QAAQ,OAAO,KAAK,CAAC;AAAA,EACjE;AACA,OAAK,iBAAiB,EAAE,KAAK,CAAC,aAAa;AACzC,QAAI,QAAS;AACb,aAAS;AACT,aAAS,aAAa,OAAO,YAAY,QAAQ,SAAS,KAAK,CAAC;AAChE,cAAU,mBAAmB,UAAU,QAAQ;AAAA,EACjD,CAAC;AACD,SAAO,MAAM;AACX,cAAU;AACV,YAAQ,sBAAsB,UAAU,QAAQ;AAAA,EAClD;AACF;AAQA,eAAsB,qBAAuC;AAC3D,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,KAAK,OAAO,UAAU,aAAa,EAAE;AAC3C,MAAI,4BAA4B,KAAK,EAAE,EAAG,QAAO;AACjD,MAAI,YAAY,KAAK,EAAE,MAAM,UAAU,kBAAkB,KAAK,EAAG,QAAO;AACxE,MAAI;AACF,UAAM,UAAW,MAAM,UAAU,cAAc,mBAAmB,KAAM,CAAC;AACzE,WAAO,QAAQ,OAAO,CAAC,WAAW,OAAO,SAAS,YAAY,EAAE,UAAU;AAAA,EAC5E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1105,6 +1105,8 @@ __export(internal_exports, {
1105
1105
  IN_CIRCLE_FACTOR: () => IN_CIRCLE_FACTOR,
1106
1106
  MRZ_BAND_DEFAULTS: () => MRZ_BAND_DEFAULTS,
1107
1107
  MR_DOC_CATALOG: () => MR_DOC_CATALOG,
1108
+ PRELOAD_BLAZE_INIT_TIMEOUT_MS: () => PRELOAD_BLAZE_INIT_TIMEOUT_MS,
1109
+ PRELOAD_BUDGET_MS: () => PRELOAD_BUDGET_MS,
1108
1110
  PROCESSING_POLL_DELAY_MS: () => PROCESSING_POLL_DELAY_MS,
1109
1111
  PROCESSING_POLL_MAX: () => PROCESSING_POLL_MAX,
1110
1112
  REGISTRY_RETRY_DELAY_MS: () => REGISTRY_RETRY_DELAY_MS,
@@ -1129,14 +1131,17 @@ __export(internal_exports, {
1129
1131
  mapGuidanceHint: () => mapGuidanceHint,
1130
1132
  mapSessionResult: () => mapSessionResult,
1131
1133
  mergeSessionView: () => mergeSessionView,
1134
+ preloadedDetectorFactory: () => preloadedDetectorFactory,
1132
1135
  quadAreaFrac: () => quadAreaFrac,
1133
1136
  quadBounds: () => quadBounds,
1134
1137
  quadIou: () => quadIou,
1135
1138
  quadTiltRatio: () => quadTiltRatio,
1136
1139
  randomUuid: () => randomUuid,
1140
+ resetLivenessAssetsForTests: () => resetLivenessAssetsForTests,
1137
1141
  resetTurnstileForTests: () => resetTurnstileForTests,
1138
1142
  runLivenessFlow: () => runLivenessFlow,
1139
1143
  runSubmission: () => runSubmission,
1144
+ setPreloadLoaderForTests: () => setPreloadLoaderForTests,
1140
1145
  specularFraction: () => specularFraction,
1141
1146
  stepOf: () => stepOf,
1142
1147
  warmDocDetector: () => warmDocDetector
@@ -2556,7 +2561,8 @@ var FaceGateV3 = class {
2556
2561
  p: this.p,
2557
2562
  factors,
2558
2563
  guidanceKey: this.guidanceOf(factors, quality, q),
2559
- ready
2564
+ ready,
2565
+ accepted: q >= this.t.Q_GOOD
2560
2566
  };
2561
2567
  }
2562
2568
  geometryOf(observation, input) {
@@ -2675,6 +2681,11 @@ var CaptureSessionV3 = class {
2675
2681
  this.profiler = null;
2676
2682
  this.lastGuidanceKey = null;
2677
2683
  this.positioningStartedAt = 0;
2684
+ // Face-capture timing marks of the CURRENT attempt (engine clock; reset per run).
2685
+ this.cameraRequestedAt = null;
2686
+ this.cameraReadyAt = null;
2687
+ this.faceAcquiredAt = null;
2688
+ this.captureCompleteAt = null;
2678
2689
  this.fired = false;
2679
2690
  this.carriedDegrade = 0;
2680
2691
  this.carriedMeans = [];
@@ -2701,10 +2712,15 @@ var CaptureSessionV3 = class {
2701
2712
  handler(payload);
2702
2713
  }
2703
2714
  }
2704
- async start() {
2715
+ async start(options = {}) {
2716
+ if (options.facingMode !== void 0) this.facing = options.facingMode;
2705
2717
  this.attempt += 1;
2706
2718
  await this.run(this.attempt);
2707
2719
  }
2720
+ /** The camera the current/next attempt requests (start option, switchCamera toggles). */
2721
+ get facingMode() {
2722
+ return this.facing;
2723
+ }
2708
2724
  /** Full reset + camera restart — the documented recovery path (v1 parity). */
2709
2725
  retry() {
2710
2726
  this.teardownAttempt();
@@ -2729,6 +2745,10 @@ var CaptureSessionV3 = class {
2729
2745
  this.lastGuidanceKey = null;
2730
2746
  this.carriedDegrade = 0;
2731
2747
  this.carriedMeans = [];
2748
+ this.cameraRequestedAt = null;
2749
+ this.cameraReadyAt = null;
2750
+ this.faceAcquiredAt = null;
2751
+ this.captureCompleteAt = null;
2732
2752
  this.setState("preflight");
2733
2753
  const preflight = cameraPreflight();
2734
2754
  if (preflight !== "OK") {
@@ -2738,6 +2758,7 @@ var CaptureSessionV3 = class {
2738
2758
  this.setState("acquiring");
2739
2759
  const getUserMedia = options.getUserMedia ?? ((constraints) => navigator.mediaDevices.getUserMedia(constraints));
2740
2760
  let stream;
2761
+ this.cameraRequestedAt = this.now();
2741
2762
  try {
2742
2763
  stream = await getUserMedia(buildCaptureConstraints(detectIsMobile(), this.facing));
2743
2764
  } catch (err) {
@@ -2753,6 +2774,7 @@ var CaptureSessionV3 = class {
2753
2774
  options.video.srcObject = stream;
2754
2775
  await options.video.play().catch(() => void 0);
2755
2776
  if (attempt !== this.attempt) return;
2777
+ this.cameraReadyAt = this.now();
2756
2778
  if (this.detector === null) {
2757
2779
  const ladderOptions = {
2758
2780
  ...options.v3AssetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.v3AssetBaseUrl } : {}
@@ -2852,6 +2874,7 @@ var CaptureSessionV3 = class {
2852
2874
  frameWidth: video.videoWidth,
2853
2875
  frameHeight: video.videoHeight
2854
2876
  });
2877
+ if (tick.accepted && this.faceAcquiredAt === null) this.faceAcquiredAt = tick.at;
2855
2878
  this.emit("checks", {
2856
2879
  engine: "v3",
2857
2880
  progress: tick.p,
@@ -2865,6 +2888,7 @@ var CaptureSessionV3 = class {
2865
2888
  }
2866
2889
  if (tick.ready && this.stateValue === "positioning") {
2867
2890
  this.fired = true;
2891
+ this.captureCompleteAt = tick.at;
2868
2892
  void this.fire(attempt);
2869
2893
  }
2870
2894
  }
@@ -2959,7 +2983,21 @@ var CaptureSessionV3 = class {
2959
2983
  finishCapture(artifacts) {
2960
2984
  this.stopTracks();
2961
2985
  this.setState("captured");
2962
- this.emit("artifacts", artifacts);
2986
+ const timing = this.captureTiming();
2987
+ this.emit("artifacts", timing === void 0 ? artifacts : { ...artifacts, timing });
2988
+ }
2989
+ /** The attempt's capture-phase timing (whole ms, ≥ 0); undefined when no mark landed. */
2990
+ captureTiming() {
2991
+ const span = (from, to) => from !== null && to !== null ? Math.max(0, Math.round(to - from)) : void 0;
2992
+ const cameraReadyMs = span(this.cameraRequestedAt, this.cameraReadyAt);
2993
+ const faceAcquireMs = span(this.cameraReadyAt, this.faceAcquiredAt);
2994
+ const captureMs = span(this.faceAcquiredAt, this.captureCompleteAt);
2995
+ const timing = {
2996
+ ...cameraReadyMs !== void 0 ? { cameraReadyMs } : {},
2997
+ ...faceAcquireMs !== void 0 ? { faceAcquireMs } : {},
2998
+ ...captureMs !== void 0 ? { captureMs } : {}
2999
+ };
3000
+ return Object.keys(timing).length > 0 ? timing : void 0;
2963
3001
  }
2964
3002
  // ── defaults for the canvas-bound seams ────────────────────────────────────
2965
3003
  async defaultKeyframe(video) {
@@ -3090,6 +3128,179 @@ function createEmitter() {
3090
3128
  };
3091
3129
  }
3092
3130
 
3131
+ // src/capture/preload.ts
3132
+ var PRELOAD_BLAZE_INIT_TIMEOUT_MS = 12e4;
3133
+ var PRELOAD_BUDGET_MS = 13e4;
3134
+ var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@sekyro/web.preload.v1");
3135
+ function registry() {
3136
+ const host = globalThis;
3137
+ host[REGISTRY_KEY] ?? (host[REGISTRY_KEY] = /* @__PURE__ */ new Map());
3138
+ return host[REGISTRY_KEY];
3139
+ }
3140
+ function normalizeBase(assetBaseUrl) {
3141
+ const base = (assetBaseUrl ?? DEFAULT_V3_ASSET_BASE_URL).replace(/\/+$/, "");
3142
+ return base === "" ? "/" : base;
3143
+ }
3144
+ function setStatus(entry, next) {
3145
+ if (entry.status === next) return;
3146
+ entry.status = next;
3147
+ for (const listener of [...entry.listeners]) listener(next);
3148
+ }
3149
+ var defaultLoader = (base) => createDetector({ preferred: ["blaze"], v3AssetBaseUrl: base, blazeInitTimeoutMs: PRELOAD_BLAZE_INIT_TIMEOUT_MS });
3150
+ var loader = defaultLoader;
3151
+ function setPreloadLoaderForTests(next) {
3152
+ loader = next ?? defaultLoader;
3153
+ }
3154
+ function resetLivenessAssetsForTests() {
3155
+ for (const entry of registry().values()) entry.detector?.destroy();
3156
+ registry().clear();
3157
+ loader = defaultLoader;
3158
+ }
3159
+ function begin(entry, base) {
3160
+ if ((entry.status === "loading" || entry.status === "ready") && entry.promise !== null) return entry.promise;
3161
+ setStatus(entry, "loading");
3162
+ const detectorPromise = new Promise((resolve) => resolve(loader(base)));
3163
+ entry.detectorPromise = detectorPromise;
3164
+ let timer;
3165
+ const budget = new Promise((_2, reject) => {
3166
+ timer = setTimeout(() => {
3167
+ detectorPromise.then((late) => late.destroy(), () => void 0);
3168
+ reject(new Error(`detector preload exceeded ${PRELOAD_BUDGET_MS} ms`));
3169
+ }, PRELOAD_BUDGET_MS);
3170
+ });
3171
+ const settled = Promise.race([detectorPromise, budget]).then(
3172
+ (detector) => {
3173
+ if (entry.detectorPromise !== detectorPromise) {
3174
+ detector.destroy();
3175
+ return entry.status;
3176
+ }
3177
+ entry.detector = detector;
3178
+ setStatus(entry, "ready");
3179
+ return "ready";
3180
+ },
3181
+ (err) => {
3182
+ if (entry.detectorPromise === detectorPromise) {
3183
+ entry.detectorPromise = null;
3184
+ entry.detector = null;
3185
+ setStatus(entry, "failed");
3186
+ console.warn("[@sekyro/web] face-detector preload failed:", err);
3187
+ }
3188
+ return entry.status;
3189
+ }
3190
+ );
3191
+ const promise = settled.finally(() => clearTimeout(timer));
3192
+ entry.promise = promise;
3193
+ entry.scheduled?.settle(promise);
3194
+ return promise;
3195
+ }
3196
+ function nonOwningView(shared) {
3197
+ return {
3198
+ get tier() {
3199
+ return shared.tier;
3200
+ },
3201
+ get fallbackReason() {
3202
+ return shared.fallbackReason;
3203
+ },
3204
+ detect: (...args) => shared.detect(...args),
3205
+ destroy: () => {
3206
+ }
3207
+ };
3208
+ }
3209
+ function preloadedDetectorFactory(assetBaseUrl) {
3210
+ const base = normalizeBase(assetBaseUrl);
3211
+ const entry = registry().get(base);
3212
+ if (entry === void 0 || entry.detector === null && entry.detectorPromise === null && entry.scheduled === null) {
3213
+ return void 0;
3214
+ }
3215
+ return async () => {
3216
+ if (entry.scheduled !== null) void begin(entry, base);
3217
+ let waitingOn = entry.promise;
3218
+ while (entry.detector === null && waitingOn !== null) {
3219
+ await waitingOn;
3220
+ if (entry.promise === waitingOn) break;
3221
+ waitingOn = entry.promise;
3222
+ }
3223
+ return entry.detector !== null ? nonOwningView(entry.detector) : createDetector({ v3AssetBaseUrl: base });
3224
+ };
3225
+ }
3226
+
3227
+ // src/capture/wake-lock.ts
3228
+ function wakeLockApi() {
3229
+ if (typeof navigator === "undefined" || !("wakeLock" in navigator)) return null;
3230
+ const api = navigator.wakeLock;
3231
+ return typeof api?.request === "function" ? api : null;
3232
+ }
3233
+ function isVisible() {
3234
+ return typeof document === "undefined" || document.visibilityState === "visible";
3235
+ }
3236
+ function releaseQuietly(sentinel) {
3237
+ try {
3238
+ void sentinel.release().catch(() => void 0);
3239
+ } catch {
3240
+ }
3241
+ }
3242
+ function createScreenWakeLock(options = {}) {
3243
+ if (options.enabled === false) return { setActive: () => void 0 };
3244
+ let wanted = false;
3245
+ let sentinel = null;
3246
+ let inFlight = false;
3247
+ let listening = false;
3248
+ const acquire = () => {
3249
+ if (!wanted || inFlight || sentinel !== null && !sentinel.released) return;
3250
+ const api = wakeLockApi();
3251
+ if (api === null || !isVisible()) return;
3252
+ let request;
3253
+ try {
3254
+ request = api.request("screen");
3255
+ } catch {
3256
+ return;
3257
+ }
3258
+ inFlight = true;
3259
+ request.then(
3260
+ (granted) => {
3261
+ inFlight = false;
3262
+ if (!wanted) {
3263
+ releaseQuietly(granted);
3264
+ return;
3265
+ }
3266
+ sentinel = granted;
3267
+ granted.addEventListener?.("release", () => {
3268
+ if (sentinel === granted) sentinel = null;
3269
+ });
3270
+ },
3271
+ () => {
3272
+ inFlight = false;
3273
+ }
3274
+ );
3275
+ };
3276
+ const onVisibilityChange = () => {
3277
+ if (isVisible()) acquire();
3278
+ };
3279
+ const listen = (on) => {
3280
+ if (typeof document === "undefined" || on === listening) return;
3281
+ listening = on;
3282
+ if (on) document.addEventListener("visibilitychange", onVisibilityChange);
3283
+ else document.removeEventListener("visibilitychange", onVisibilityChange);
3284
+ };
3285
+ return {
3286
+ setActive: (active) => {
3287
+ wanted = active;
3288
+ if (active) {
3289
+ if (wakeLockApi() === null) return;
3290
+ listen(true);
3291
+ acquire();
3292
+ return;
3293
+ }
3294
+ listen(false);
3295
+ if (sentinel !== null) {
3296
+ const held = sentinel;
3297
+ sentinel = null;
3298
+ releaseQuietly(held);
3299
+ }
3300
+ }
3301
+ };
3302
+ }
3303
+
3093
3304
  // src/capture/create-liveness-capture.ts
3094
3305
  function mapGuidanceHint(key) {
3095
3306
  switch (key) {
@@ -3153,12 +3364,22 @@ function createLivenessCaptureInternal(options, internals = {}) {
3153
3364
  const now = internals.now ?? (() => performance.now());
3154
3365
  const emitter = createEmitter();
3155
3366
  const degraded = !isMediaRecorderSupported();
3367
+ const hostSeams = internals.engineOptions;
3368
+ const preloadAware = hostSeams?.createDetector === void 0 && hostSeams?.createWorker === void 0 ? {
3369
+ createDetector: () => {
3370
+ const warmed = preloadedDetectorFactory(options.assetBaseUrl);
3371
+ return warmed !== void 0 ? warmed() : createDetector(
3372
+ options.assetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.assetBaseUrl } : {}
3373
+ );
3374
+ }
3375
+ } : {};
3156
3376
  const engineOptions = {
3157
3377
  video: options.video,
3158
3378
  mode: degraded ? "photo" : "clip",
3159
3379
  ...options.facing !== void 0 ? { facingMode: options.facing } : {},
3160
3380
  ...options.assetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.assetBaseUrl } : {},
3161
- ...internals.engineOptions ?? {}
3381
+ ...internals.engineOptions ?? {},
3382
+ ...preloadAware
3162
3383
  };
3163
3384
  const engine = internals.createEngine?.(engineOptions) ?? new CaptureSessionV3(engineOptions);
3164
3385
  let state = "idle";
@@ -3176,8 +3397,10 @@ function createLivenessCaptureInternal(options, internals = {}) {
3176
3397
  frameHeight = options.video.videoHeight;
3177
3398
  }
3178
3399
  };
3400
+ const wakeLock = createScreenWakeLock({ enabled: options.keepScreenAwake !== false });
3179
3401
  const setState = (next) => {
3180
3402
  state = next;
3403
+ wakeLock.setActive(next === "starting" || next === "gating" || next === "recording");
3181
3404
  emitter.emit("state", next);
3182
3405
  };
3183
3406
  const emitGate = () => emitter.emit("gate", { progress, hint, faceDetected });
@@ -3234,7 +3457,12 @@ function createLivenessCaptureInternal(options, internals = {}) {
3234
3457
  height: options.video.videoHeight || frameHeight,
3235
3458
  ...clipMs !== void 0 ? { clipMs } : {},
3236
3459
  captureChannel: detectIsMobile() ? "mobile" : "desktop",
3237
- ...degraded ? { degraded: "NO_RECORDER" } : {}
3460
+ ...degraded ? { degraded: "NO_RECORDER" } : {},
3461
+ engine: "v3",
3462
+ // Face-capture timing: the engine's marks, forwarded flat (absent → absent).
3463
+ ...artifacts.timing?.cameraReadyMs !== void 0 ? { cameraReadyMs: artifacts.timing.cameraReadyMs } : {},
3464
+ ...artifacts.timing?.faceAcquireMs !== void 0 ? { faceAcquireMs: artifacts.timing.faceAcquireMs } : {},
3465
+ ...artifacts.timing?.captureMs !== void 0 ? { captureMs: artifacts.timing.captureMs } : {}
3238
3466
  });
3239
3467
  }),
3240
3468
  engine.on("error", ({ kind, retryable }) => {
@@ -3246,11 +3474,12 @@ function createLivenessCaptureInternal(options, internals = {}) {
3246
3474
  return state;
3247
3475
  },
3248
3476
  on: (event, handler) => emitter.on(event, handler),
3249
- start: () => engine.start(),
3477
+ start: (startOptions) => engine.start(startOptions?.facing !== void 0 ? { facingMode: startOptions.facing } : {}),
3250
3478
  retry: () => engine.retry(),
3251
3479
  switchCamera: () => engine.switchCamera(),
3252
3480
  destroy: () => {
3253
3481
  for (const unsubscribe of unsubscribes) unsubscribe();
3482
+ wakeLock.setActive(false);
3254
3483
  engine.destroy();
3255
3484
  emitter.clear();
3256
3485
  }
@@ -3733,7 +3962,12 @@ function buildLivenessSubmit(deps, clip, bareMime) {
3733
3962
  deps.onPhase("checking");
3734
3963
  return client.submitCheck(
3735
3964
  "liveness",
3736
- { clipKey, attestation, captureChannel: deps.artifacts.captureChannel },
3965
+ {
3966
+ clipKey,
3967
+ attestation,
3968
+ captureChannel: deps.artifacts.captureChannel,
3969
+ capture: captureInputOf(deps.artifacts)
3970
+ },
3737
3971
  { idempotencyKey }
3738
3972
  );
3739
3973
  },
@@ -3744,6 +3978,17 @@ function buildLivenessSubmit(deps, clip, bareMime) {
3744
3978
  }
3745
3979
  );
3746
3980
  }
3981
+ function captureInputOf(artifacts) {
3982
+ return {
3983
+ ...artifacts.engine !== void 0 ? { engine: artifacts.engine } : {},
3984
+ width: artifacts.width,
3985
+ height: artifacts.height,
3986
+ ...artifacts.clipMs !== void 0 ? { clipMs: artifacts.clipMs } : {},
3987
+ ...artifacts.cameraReadyMs !== void 0 ? { cameraReadyMs: artifacts.cameraReadyMs } : {},
3988
+ ...artifacts.faceAcquireMs !== void 0 ? { faceAcquireMs: artifacts.faceAcquireMs } : {},
3989
+ ...artifacts.captureMs !== void 0 ? { captureMs: artifacts.captureMs } : {}
3990
+ };
3991
+ }
3747
3992
  function buildFaceMatchSubmit(deps, keyframe) {
3748
3993
  let imageKey = null;
3749
3994
  return () => runSubmission(deps, async (client, idempotencyKey) => {
@@ -3942,7 +4187,7 @@ function createVerificationInternal(options, internals) {
3942
4187
  }
3943
4188
  emitter.emit("result", result);
3944
4189
  };
3945
- let registry = null;
4190
+ let registry2 = null;
3946
4191
  let registryFailed = false;
3947
4192
  let registryUnbound = false;
3948
4193
  const submitRegistry = () => runSubmission(
@@ -3977,8 +4222,8 @@ function createVerificationInternal(options, internals) {
3977
4222
  if (view !== null && isTerminalView(view)) emitResult(mapSessionResult(view, { autoRetry }));
3978
4223
  };
3979
4224
  const startRegistry = () => {
3980
- registry ?? (registry = runRegistry());
3981
- return registry;
4225
+ registry2 ?? (registry2 = runRegistry());
4226
+ return registry2;
3982
4227
  };
3983
4228
  const getAttestation = async () => {
3984
4229
  const current = await ensureView();
@@ -4093,7 +4338,7 @@ function createVerificationInternal(options, internals) {
4093
4338
  return inner.state;
4094
4339
  },
4095
4340
  on: (event, handler) => inner.on(event, handler),
4096
- start: () => inner.start(),
4341
+ start: (startOptions) => inner.start(startOptions),
4097
4342
  retry: () => inner.retry(),
4098
4343
  switchCamera: () => inner.switchCamera(),
4099
4344
  destroy: () => {
@@ -4140,6 +4385,8 @@ function createVerificationInternal(options, internals) {
4140
4385
  IN_CIRCLE_FACTOR,
4141
4386
  MRZ_BAND_DEFAULTS,
4142
4387
  MR_DOC_CATALOG,
4388
+ PRELOAD_BLAZE_INIT_TIMEOUT_MS,
4389
+ PRELOAD_BUDGET_MS,
4143
4390
  PROCESSING_POLL_DELAY_MS,
4144
4391
  PROCESSING_POLL_MAX,
4145
4392
  REGISTRY_RETRY_DELAY_MS,
@@ -4164,14 +4411,17 @@ function createVerificationInternal(options, internals) {
4164
4411
  mapGuidanceHint,
4165
4412
  mapSessionResult,
4166
4413
  mergeSessionView,
4414
+ preloadedDetectorFactory,
4167
4415
  quadAreaFrac,
4168
4416
  quadBounds,
4169
4417
  quadIou,
4170
4418
  quadTiltRatio,
4171
4419
  randomUuid,
4420
+ resetLivenessAssetsForTests,
4172
4421
  resetTurnstileForTests,
4173
4422
  runLivenessFlow,
4174
4423
  runSubmission,
4424
+ setPreloadLoaderForTests,
4175
4425
  specularFraction,
4176
4426
  stepOf,
4177
4427
  warmDocDetector