@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.cjs CHANGED
@@ -36,8 +36,15 @@ __export(src_exports, {
36
36
  SEKYRO_EVENTS: () => SEKYRO_EVENTS,
37
37
  createLivenessCapture: () => createLivenessCapture,
38
38
  createVerification: () => createVerification,
39
+ getLivenessAssetsStatus: () => getLivenessAssetsStatus,
40
+ hasMultipleCameras: () => hasMultipleCameras,
39
41
  isKnownCode: () => isKnownCode,
40
- openHosted: () => openHosted
42
+ openHosted: () => openHosted,
43
+ preloadLivenessAssets: () => preloadLivenessAssets,
44
+ queryCameraPermission: () => queryCameraPermission,
45
+ releaseLivenessAssets: () => releaseLivenessAssets,
46
+ subscribeCameraPermission: () => subscribeCameraPermission,
47
+ subscribeLivenessAssets: () => subscribeLivenessAssets
41
48
  });
42
49
  module.exports = __toCommonJS(src_exports);
43
50
 
@@ -952,7 +959,8 @@ var FaceGateV3 = class {
952
959
  p: this.p,
953
960
  factors,
954
961
  guidanceKey: this.guidanceOf(factors, quality, q),
955
- ready
962
+ ready,
963
+ accepted: q >= this.t.Q_GOOD
956
964
  };
957
965
  }
958
966
  geometryOf(observation, input) {
@@ -1070,6 +1078,11 @@ var CaptureSessionV3 = class {
1070
1078
  this.profiler = null;
1071
1079
  this.lastGuidanceKey = null;
1072
1080
  this.positioningStartedAt = 0;
1081
+ // Face-capture timing marks of the CURRENT attempt (engine clock; reset per run).
1082
+ this.cameraRequestedAt = null;
1083
+ this.cameraReadyAt = null;
1084
+ this.faceAcquiredAt = null;
1085
+ this.captureCompleteAt = null;
1073
1086
  this.fired = false;
1074
1087
  this.carriedDegrade = 0;
1075
1088
  this.carriedMeans = [];
@@ -1096,10 +1109,15 @@ var CaptureSessionV3 = class {
1096
1109
  handler(payload);
1097
1110
  }
1098
1111
  }
1099
- async start() {
1112
+ async start(options = {}) {
1113
+ if (options.facingMode !== void 0) this.facing = options.facingMode;
1100
1114
  this.attempt += 1;
1101
1115
  await this.run(this.attempt);
1102
1116
  }
1117
+ /** The camera the current/next attempt requests (start option, switchCamera toggles). */
1118
+ get facingMode() {
1119
+ return this.facing;
1120
+ }
1103
1121
  /** Full reset + camera restart — the documented recovery path (v1 parity). */
1104
1122
  retry() {
1105
1123
  this.teardownAttempt();
@@ -1124,6 +1142,10 @@ var CaptureSessionV3 = class {
1124
1142
  this.lastGuidanceKey = null;
1125
1143
  this.carriedDegrade = 0;
1126
1144
  this.carriedMeans = [];
1145
+ this.cameraRequestedAt = null;
1146
+ this.cameraReadyAt = null;
1147
+ this.faceAcquiredAt = null;
1148
+ this.captureCompleteAt = null;
1127
1149
  this.setState("preflight");
1128
1150
  const preflight = cameraPreflight();
1129
1151
  if (preflight !== "OK") {
@@ -1133,6 +1155,7 @@ var CaptureSessionV3 = class {
1133
1155
  this.setState("acquiring");
1134
1156
  const getUserMedia = options.getUserMedia ?? ((constraints) => navigator.mediaDevices.getUserMedia(constraints));
1135
1157
  let stream;
1158
+ this.cameraRequestedAt = this.now();
1136
1159
  try {
1137
1160
  stream = await getUserMedia(buildCaptureConstraints(detectIsMobile(), this.facing));
1138
1161
  } catch (err) {
@@ -1148,6 +1171,7 @@ var CaptureSessionV3 = class {
1148
1171
  options.video.srcObject = stream;
1149
1172
  await options.video.play().catch(() => void 0);
1150
1173
  if (attempt !== this.attempt) return;
1174
+ this.cameraReadyAt = this.now();
1151
1175
  if (this.detector === null) {
1152
1176
  const ladderOptions = {
1153
1177
  ...options.v3AssetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.v3AssetBaseUrl } : {}
@@ -1247,6 +1271,7 @@ var CaptureSessionV3 = class {
1247
1271
  frameWidth: video.videoWidth,
1248
1272
  frameHeight: video.videoHeight
1249
1273
  });
1274
+ if (tick.accepted && this.faceAcquiredAt === null) this.faceAcquiredAt = tick.at;
1250
1275
  this.emit("checks", {
1251
1276
  engine: "v3",
1252
1277
  progress: tick.p,
@@ -1260,6 +1285,7 @@ var CaptureSessionV3 = class {
1260
1285
  }
1261
1286
  if (tick.ready && this.stateValue === "positioning") {
1262
1287
  this.fired = true;
1288
+ this.captureCompleteAt = tick.at;
1263
1289
  void this.fire(attempt);
1264
1290
  }
1265
1291
  }
@@ -1354,7 +1380,21 @@ var CaptureSessionV3 = class {
1354
1380
  finishCapture(artifacts) {
1355
1381
  this.stopTracks();
1356
1382
  this.setState("captured");
1357
- this.emit("artifacts", artifacts);
1383
+ const timing = this.captureTiming();
1384
+ this.emit("artifacts", timing === void 0 ? artifacts : { ...artifacts, timing });
1385
+ }
1386
+ /** The attempt's capture-phase timing (whole ms, ≥ 0); undefined when no mark landed. */
1387
+ captureTiming() {
1388
+ const span = (from, to) => from !== null && to !== null ? Math.max(0, Math.round(to - from)) : void 0;
1389
+ const cameraReadyMs = span(this.cameraRequestedAt, this.cameraReadyAt);
1390
+ const faceAcquireMs = span(this.cameraReadyAt, this.faceAcquiredAt);
1391
+ const captureMs = span(this.faceAcquiredAt, this.captureCompleteAt);
1392
+ const timing = {
1393
+ ...cameraReadyMs !== void 0 ? { cameraReadyMs } : {},
1394
+ ...faceAcquireMs !== void 0 ? { faceAcquireMs } : {},
1395
+ ...captureMs !== void 0 ? { captureMs } : {}
1396
+ };
1397
+ return Object.keys(timing).length > 0 ? timing : void 0;
1358
1398
  }
1359
1399
  // ── defaults for the canvas-bound seams ────────────────────────────────────
1360
1400
  async defaultKeyframe(video) {
@@ -1485,6 +1525,250 @@ function createEmitter() {
1485
1525
  };
1486
1526
  }
1487
1527
 
1528
+ // src/capture/preload.ts
1529
+ var PRELOAD_BLAZE_INIT_TIMEOUT_MS = 12e4;
1530
+ var PRELOAD_BUDGET_MS = 13e4;
1531
+ var IDLE_TIMEOUT_MS = 3e3;
1532
+ var IDLE_FALLBACK_MS = 1200;
1533
+ var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@sekyro/web.preload.v1");
1534
+ function registry() {
1535
+ const host = globalThis;
1536
+ host[REGISTRY_KEY] ?? (host[REGISTRY_KEY] = /* @__PURE__ */ new Map());
1537
+ return host[REGISTRY_KEY];
1538
+ }
1539
+ function normalizeBase(assetBaseUrl) {
1540
+ const base = (assetBaseUrl ?? DEFAULT_V3_ASSET_BASE_URL).replace(/\/+$/, "");
1541
+ return base === "" ? "/" : base;
1542
+ }
1543
+ function entryFor(base) {
1544
+ const map = registry();
1545
+ let entry = map.get(base);
1546
+ if (entry === void 0) {
1547
+ entry = { status: "idle", promise: null, scheduled: null, detectorPromise: null, detector: null, listeners: /* @__PURE__ */ new Set() };
1548
+ map.set(base, entry);
1549
+ }
1550
+ return entry;
1551
+ }
1552
+ function setStatus(entry, next) {
1553
+ if (entry.status === next) return;
1554
+ entry.status = next;
1555
+ for (const listener of [...entry.listeners]) listener(next);
1556
+ }
1557
+ var defaultLoader = (base) => createDetector({ preferred: ["blaze"], v3AssetBaseUrl: base, blazeInitTimeoutMs: PRELOAD_BLAZE_INIT_TIMEOUT_MS });
1558
+ var loader = defaultLoader;
1559
+ function canCapture() {
1560
+ return typeof window !== "undefined" && typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function";
1561
+ }
1562
+ function saveDataOn() {
1563
+ return navigator.connection?.saveData === true;
1564
+ }
1565
+ function scheduleIdle(run) {
1566
+ const w = window;
1567
+ if (typeof w.requestIdleCallback === "function") {
1568
+ const handle = w.requestIdleCallback(run, { timeout: IDLE_TIMEOUT_MS });
1569
+ return () => w.cancelIdleCallback?.(handle);
1570
+ }
1571
+ const timer = setTimeout(run, IDLE_FALLBACK_MS);
1572
+ return () => clearTimeout(timer);
1573
+ }
1574
+ function schedule(entry, base) {
1575
+ let resolve;
1576
+ const promise = new Promise((res) => {
1577
+ resolve = res;
1578
+ });
1579
+ let done = false;
1580
+ let cancel = () => void 0;
1581
+ const scheduled = {
1582
+ promise,
1583
+ settle: (outcome) => {
1584
+ if (done) return;
1585
+ done = true;
1586
+ cancel();
1587
+ if (entry.scheduled === scheduled) entry.scheduled = null;
1588
+ resolve(outcome);
1589
+ }
1590
+ };
1591
+ entry.scheduled = scheduled;
1592
+ cancel = scheduleIdle(() => {
1593
+ if (done) return;
1594
+ scheduled.settle(begin(entry, base));
1595
+ });
1596
+ return promise;
1597
+ }
1598
+ function begin(entry, base) {
1599
+ if ((entry.status === "loading" || entry.status === "ready") && entry.promise !== null) return entry.promise;
1600
+ setStatus(entry, "loading");
1601
+ const detectorPromise = new Promise((resolve) => resolve(loader(base)));
1602
+ entry.detectorPromise = detectorPromise;
1603
+ let timer;
1604
+ const budget = new Promise((_, reject) => {
1605
+ timer = setTimeout(() => {
1606
+ detectorPromise.then((late) => late.destroy(), () => void 0);
1607
+ reject(new Error(`detector preload exceeded ${PRELOAD_BUDGET_MS} ms`));
1608
+ }, PRELOAD_BUDGET_MS);
1609
+ });
1610
+ const settled = Promise.race([detectorPromise, budget]).then(
1611
+ (detector) => {
1612
+ if (entry.detectorPromise !== detectorPromise) {
1613
+ detector.destroy();
1614
+ return entry.status;
1615
+ }
1616
+ entry.detector = detector;
1617
+ setStatus(entry, "ready");
1618
+ return "ready";
1619
+ },
1620
+ (err) => {
1621
+ if (entry.detectorPromise === detectorPromise) {
1622
+ entry.detectorPromise = null;
1623
+ entry.detector = null;
1624
+ setStatus(entry, "failed");
1625
+ console.warn("[@sekyro/web] face-detector preload failed:", err);
1626
+ }
1627
+ return entry.status;
1628
+ }
1629
+ );
1630
+ const promise = settled.finally(() => clearTimeout(timer));
1631
+ entry.promise = promise;
1632
+ entry.scheduled?.settle(promise);
1633
+ return promise;
1634
+ }
1635
+ function preloadLivenessAssets(options = {}) {
1636
+ if (!canCapture()) return Promise.resolve("idle");
1637
+ const base = normalizeBase(options.assetBaseUrl);
1638
+ const entry = entryFor(base);
1639
+ if (options.respectSaveData === true && saveDataOn()) return Promise.resolve(entry.status);
1640
+ if ((entry.status === "loading" || entry.status === "ready") && entry.promise !== null) return entry.promise;
1641
+ if (options.idle === true) return entry.scheduled?.promise ?? schedule(entry, base);
1642
+ return begin(entry, base);
1643
+ }
1644
+ function getLivenessAssetsStatus(assetBaseUrl) {
1645
+ return registry().get(normalizeBase(assetBaseUrl))?.status ?? "idle";
1646
+ }
1647
+ function subscribeLivenessAssets(listener, assetBaseUrl) {
1648
+ const entry = entryFor(normalizeBase(assetBaseUrl));
1649
+ entry.listeners.add(listener);
1650
+ return () => {
1651
+ entry.listeners.delete(listener);
1652
+ };
1653
+ }
1654
+ function releaseLivenessAssets(assetBaseUrl) {
1655
+ const entry = registry().get(normalizeBase(assetBaseUrl));
1656
+ if (entry === void 0) return;
1657
+ entry.scheduled?.settle("idle");
1658
+ entry.detector?.destroy();
1659
+ entry.detector = null;
1660
+ entry.detectorPromise = null;
1661
+ entry.promise = null;
1662
+ setStatus(entry, "idle");
1663
+ }
1664
+ function nonOwningView(shared) {
1665
+ return {
1666
+ get tier() {
1667
+ return shared.tier;
1668
+ },
1669
+ get fallbackReason() {
1670
+ return shared.fallbackReason;
1671
+ },
1672
+ detect: (...args) => shared.detect(...args),
1673
+ destroy: () => {
1674
+ }
1675
+ };
1676
+ }
1677
+ function preloadedDetectorFactory(assetBaseUrl) {
1678
+ const base = normalizeBase(assetBaseUrl);
1679
+ const entry = registry().get(base);
1680
+ if (entry === void 0 || entry.detector === null && entry.detectorPromise === null && entry.scheduled === null) {
1681
+ return void 0;
1682
+ }
1683
+ return async () => {
1684
+ if (entry.scheduled !== null) void begin(entry, base);
1685
+ let waitingOn = entry.promise;
1686
+ while (entry.detector === null && waitingOn !== null) {
1687
+ await waitingOn;
1688
+ if (entry.promise === waitingOn) break;
1689
+ waitingOn = entry.promise;
1690
+ }
1691
+ return entry.detector !== null ? nonOwningView(entry.detector) : createDetector({ v3AssetBaseUrl: base });
1692
+ };
1693
+ }
1694
+
1695
+ // src/capture/wake-lock.ts
1696
+ function wakeLockApi() {
1697
+ if (typeof navigator === "undefined" || !("wakeLock" in navigator)) return null;
1698
+ const api = navigator.wakeLock;
1699
+ return typeof api?.request === "function" ? api : null;
1700
+ }
1701
+ function isVisible() {
1702
+ return typeof document === "undefined" || document.visibilityState === "visible";
1703
+ }
1704
+ function releaseQuietly(sentinel) {
1705
+ try {
1706
+ void sentinel.release().catch(() => void 0);
1707
+ } catch {
1708
+ }
1709
+ }
1710
+ function createScreenWakeLock(options = {}) {
1711
+ if (options.enabled === false) return { setActive: () => void 0 };
1712
+ let wanted = false;
1713
+ let sentinel = null;
1714
+ let inFlight = false;
1715
+ let listening = false;
1716
+ const acquire = () => {
1717
+ if (!wanted || inFlight || sentinel !== null && !sentinel.released) return;
1718
+ const api = wakeLockApi();
1719
+ if (api === null || !isVisible()) return;
1720
+ let request;
1721
+ try {
1722
+ request = api.request("screen");
1723
+ } catch {
1724
+ return;
1725
+ }
1726
+ inFlight = true;
1727
+ request.then(
1728
+ (granted) => {
1729
+ inFlight = false;
1730
+ if (!wanted) {
1731
+ releaseQuietly(granted);
1732
+ return;
1733
+ }
1734
+ sentinel = granted;
1735
+ granted.addEventListener?.("release", () => {
1736
+ if (sentinel === granted) sentinel = null;
1737
+ });
1738
+ },
1739
+ () => {
1740
+ inFlight = false;
1741
+ }
1742
+ );
1743
+ };
1744
+ const onVisibilityChange = () => {
1745
+ if (isVisible()) acquire();
1746
+ };
1747
+ const listen = (on) => {
1748
+ if (typeof document === "undefined" || on === listening) return;
1749
+ listening = on;
1750
+ if (on) document.addEventListener("visibilitychange", onVisibilityChange);
1751
+ else document.removeEventListener("visibilitychange", onVisibilityChange);
1752
+ };
1753
+ return {
1754
+ setActive: (active) => {
1755
+ wanted = active;
1756
+ if (active) {
1757
+ if (wakeLockApi() === null) return;
1758
+ listen(true);
1759
+ acquire();
1760
+ return;
1761
+ }
1762
+ listen(false);
1763
+ if (sentinel !== null) {
1764
+ const held = sentinel;
1765
+ sentinel = null;
1766
+ releaseQuietly(held);
1767
+ }
1768
+ }
1769
+ };
1770
+ }
1771
+
1488
1772
  // src/capture/create-liveness-capture.ts
1489
1773
  function mapGuidanceHint(key) {
1490
1774
  switch (key) {
@@ -1548,12 +1832,22 @@ function createLivenessCaptureInternal(options, internals = {}) {
1548
1832
  const now = internals.now ?? (() => performance.now());
1549
1833
  const emitter = createEmitter();
1550
1834
  const degraded = !isMediaRecorderSupported();
1835
+ const hostSeams = internals.engineOptions;
1836
+ const preloadAware = hostSeams?.createDetector === void 0 && hostSeams?.createWorker === void 0 ? {
1837
+ createDetector: () => {
1838
+ const warmed = preloadedDetectorFactory(options.assetBaseUrl);
1839
+ return warmed !== void 0 ? warmed() : createDetector(
1840
+ options.assetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.assetBaseUrl } : {}
1841
+ );
1842
+ }
1843
+ } : {};
1551
1844
  const engineOptions = {
1552
1845
  video: options.video,
1553
1846
  mode: degraded ? "photo" : "clip",
1554
1847
  ...options.facing !== void 0 ? { facingMode: options.facing } : {},
1555
1848
  ...options.assetBaseUrl !== void 0 ? { v3AssetBaseUrl: options.assetBaseUrl } : {},
1556
- ...internals.engineOptions ?? {}
1849
+ ...internals.engineOptions ?? {},
1850
+ ...preloadAware
1557
1851
  };
1558
1852
  const engine = internals.createEngine?.(engineOptions) ?? new CaptureSessionV3(engineOptions);
1559
1853
  let state = "idle";
@@ -1571,8 +1865,10 @@ function createLivenessCaptureInternal(options, internals = {}) {
1571
1865
  frameHeight = options.video.videoHeight;
1572
1866
  }
1573
1867
  };
1868
+ const wakeLock = createScreenWakeLock({ enabled: options.keepScreenAwake !== false });
1574
1869
  const setState = (next) => {
1575
1870
  state = next;
1871
+ wakeLock.setActive(next === "starting" || next === "gating" || next === "recording");
1576
1872
  emitter.emit("state", next);
1577
1873
  };
1578
1874
  const emitGate = () => emitter.emit("gate", { progress, hint, faceDetected });
@@ -1629,7 +1925,12 @@ function createLivenessCaptureInternal(options, internals = {}) {
1629
1925
  height: options.video.videoHeight || frameHeight,
1630
1926
  ...clipMs !== void 0 ? { clipMs } : {},
1631
1927
  captureChannel: detectIsMobile() ? "mobile" : "desktop",
1632
- ...degraded ? { degraded: "NO_RECORDER" } : {}
1928
+ ...degraded ? { degraded: "NO_RECORDER" } : {},
1929
+ engine: "v3",
1930
+ // Face-capture timing: the engine's marks, forwarded flat (absent → absent).
1931
+ ...artifacts.timing?.cameraReadyMs !== void 0 ? { cameraReadyMs: artifacts.timing.cameraReadyMs } : {},
1932
+ ...artifacts.timing?.faceAcquireMs !== void 0 ? { faceAcquireMs: artifacts.timing.faceAcquireMs } : {},
1933
+ ...artifacts.timing?.captureMs !== void 0 ? { captureMs: artifacts.timing.captureMs } : {}
1633
1934
  });
1634
1935
  }),
1635
1936
  engine.on("error", ({ kind, retryable }) => {
@@ -1641,11 +1942,12 @@ function createLivenessCaptureInternal(options, internals = {}) {
1641
1942
  return state;
1642
1943
  },
1643
1944
  on: (event, handler) => emitter.on(event, handler),
1644
- start: () => engine.start(),
1945
+ start: (startOptions) => engine.start(startOptions?.facing !== void 0 ? { facingMode: startOptions.facing } : {}),
1645
1946
  retry: () => engine.retry(),
1646
1947
  switchCamera: () => engine.switchCamera(),
1647
1948
  destroy: () => {
1648
1949
  for (const unsubscribe of unsubscribes) unsubscribe();
1950
+ wakeLock.setActive(false);
1649
1951
  engine.destroy();
1650
1952
  emitter.clear();
1651
1953
  }
@@ -1661,6 +1963,52 @@ function createLivenessCapture(options) {
1661
1963
  return createLivenessCaptureInternal(options).handle;
1662
1964
  }
1663
1965
 
1966
+ // src/capture/camera-probe.ts
1967
+ async function permissionStatus() {
1968
+ try {
1969
+ if (typeof navigator === "undefined" || typeof navigator.permissions?.query !== "function") return null;
1970
+ return await navigator.permissions.query({ name: "camera" });
1971
+ } catch {
1972
+ return null;
1973
+ }
1974
+ }
1975
+ function asState(state) {
1976
+ return state === "granted" || state === "prompt" || state === "denied" ? state : "unknown";
1977
+ }
1978
+ async function queryCameraPermission() {
1979
+ const status = await permissionStatus();
1980
+ return status === null ? "unknown" : asState(status.state);
1981
+ }
1982
+ function subscribeCameraPermission(listener) {
1983
+ let stopped = false;
1984
+ let status = null;
1985
+ const onChange = () => {
1986
+ if (!stopped && status !== null) listener(asState(status.state));
1987
+ };
1988
+ void permissionStatus().then((resolved) => {
1989
+ if (stopped) return;
1990
+ status = resolved;
1991
+ listener(resolved === null ? "unknown" : asState(resolved.state));
1992
+ resolved?.addEventListener?.("change", onChange);
1993
+ });
1994
+ return () => {
1995
+ stopped = true;
1996
+ status?.removeEventListener?.("change", onChange);
1997
+ };
1998
+ }
1999
+ async function hasMultipleCameras() {
2000
+ if (typeof navigator === "undefined") return false;
2001
+ const ua = String(navigator.userAgent ?? "");
2002
+ if (/Android|iPhone|iPad|iPod/i.test(ua)) return true;
2003
+ if (/Macintosh/.test(ua) && (navigator.maxTouchPoints ?? 0) > 1) return true;
2004
+ try {
2005
+ const devices = await navigator.mediaDevices?.enumerateDevices?.() ?? [];
2006
+ return devices.filter((device) => device.kind === "videoinput").length >= 2;
2007
+ } catch {
2008
+ return false;
2009
+ }
2010
+ }
2011
+
1664
2012
  // src/client/errors.ts
1665
2013
  var KNOWN_CODES = [
1666
2014
  // auth / addressing (session-token.guard, controllers' own-session guards)
@@ -2204,7 +2552,12 @@ function buildLivenessSubmit(deps, clip, bareMime) {
2204
2552
  deps.onPhase("checking");
2205
2553
  return client.submitCheck(
2206
2554
  "liveness",
2207
- { clipKey, attestation, captureChannel: deps.artifacts.captureChannel },
2555
+ {
2556
+ clipKey,
2557
+ attestation,
2558
+ captureChannel: deps.artifacts.captureChannel,
2559
+ capture: captureInputOf(deps.artifacts)
2560
+ },
2208
2561
  { idempotencyKey }
2209
2562
  );
2210
2563
  },
@@ -2215,6 +2568,17 @@ function buildLivenessSubmit(deps, clip, bareMime) {
2215
2568
  }
2216
2569
  );
2217
2570
  }
2571
+ function captureInputOf(artifacts) {
2572
+ return {
2573
+ ...artifacts.engine !== void 0 ? { engine: artifacts.engine } : {},
2574
+ width: artifacts.width,
2575
+ height: artifacts.height,
2576
+ ...artifacts.clipMs !== void 0 ? { clipMs: artifacts.clipMs } : {},
2577
+ ...artifacts.cameraReadyMs !== void 0 ? { cameraReadyMs: artifacts.cameraReadyMs } : {},
2578
+ ...artifacts.faceAcquireMs !== void 0 ? { faceAcquireMs: artifacts.faceAcquireMs } : {},
2579
+ ...artifacts.captureMs !== void 0 ? { captureMs: artifacts.captureMs } : {}
2580
+ };
2581
+ }
2218
2582
  function buildFaceMatchSubmit(deps, keyframe) {
2219
2583
  let imageKey = null;
2220
2584
  return () => runSubmission(deps, async (client, idempotencyKey) => {
@@ -2413,7 +2777,7 @@ function createVerificationInternal(options, internals) {
2413
2777
  }
2414
2778
  emitter.emit("result", result);
2415
2779
  };
2416
- let registry = null;
2780
+ let registry2 = null;
2417
2781
  let registryFailed = false;
2418
2782
  let registryUnbound = false;
2419
2783
  const submitRegistry = () => runSubmission(
@@ -2448,8 +2812,8 @@ function createVerificationInternal(options, internals) {
2448
2812
  if (view !== null && isTerminalView(view)) emitResult(mapSessionResult(view, { autoRetry }));
2449
2813
  };
2450
2814
  const startRegistry = () => {
2451
- registry ?? (registry = runRegistry());
2452
- return registry;
2815
+ registry2 ?? (registry2 = runRegistry());
2816
+ return registry2;
2453
2817
  };
2454
2818
  const getAttestation = async () => {
2455
2819
  const current = await ensureView();
@@ -2564,7 +2928,7 @@ function createVerificationInternal(options, internals) {
2564
2928
  return inner.state;
2565
2929
  },
2566
2930
  on: (event, handler) => inner.on(event, handler),
2567
- start: () => inner.start(),
2931
+ start: (startOptions) => inner.start(startOptions),
2568
2932
  retry: () => inner.retry(),
2569
2933
  switchCamera: () => inner.switchCamera(),
2570
2934
  destroy: () => {
@@ -2643,7 +3007,14 @@ function openHosted(hostedUrl, opts) {
2643
3007
  SEKYRO_EVENTS,
2644
3008
  createLivenessCapture,
2645
3009
  createVerification,
3010
+ getLivenessAssetsStatus,
3011
+ hasMultipleCameras,
2646
3012
  isKnownCode,
2647
- openHosted
3013
+ openHosted,
3014
+ preloadLivenessAssets,
3015
+ queryCameraPermission,
3016
+ releaseLivenessAssets,
3017
+ subscribeCameraPermission,
3018
+ subscribeLivenessAssets
2648
3019
  });
2649
3020
  //# sourceMappingURL=index.cjs.map