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

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/index.d.cts CHANGED
@@ -1,6 +1,6 @@
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--ldwakXV.cjs';
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';
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
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-CAoWM8iT.cjs';
5
+ import './capture-session-v3-COIO_F2w.cjs';
6
6
  import './detect-worker-CKXHlXx9.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
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-BlAPWeW5.js';
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';
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
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-BJ8ciaWl.js';
5
+ import './capture-session-v3-CvIfcwST.js';
6
6
  import './detect-worker-CKXHlXx9.js';
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  DEFAULT_API_ORIGIN,
7
7
  createLivenessCapture,
8
8
  createVerification
9
- } from "./chunk-NU4YOLCN.js";
10
- import "./chunk-LOTAJDRE.js";
9
+ } from "./chunk-FIXX2AXC.js";
10
+ import "./chunk-7TC6TOZ4.js";
11
11
  import {
12
12
  HostedApiError,
13
13
  KNOWN_CODES,
@@ -2466,10 +2466,12 @@ var V3_GATE_DEFAULTS = Object.freeze({
2466
2466
  MOTION_TAU_S: 0.4,
2467
2467
  MOTION_DEADBAND_IOD_PER_S: 0.1,
2468
2468
  READY_WINDOW_MS: 500,
2469
- // 0.7→0.75 with the trimmed window: trimming forgives exactly ONE flaky
2470
- // tick; the raised mean keeps a SECOND bad tick (a real sustained wobble)
2471
- // from diluting through at 10 Hz cadences.
2472
- READY_MEAN_Q: 0.75,
2469
+ // (READY_MEAN_Q, the former 0.75 window MEAN, is gone — 2026-10-17 field
2470
+ // bug: a steady q anywhere in [Q_GOOD, 0.75) saturated p at 1, passed every
2471
+ // guide ("hold still") and could never reach the mean, so the ring sat full
2472
+ // and green with nothing recording. Readiness now checks the window against
2473
+ // Q_GOOD, which keeps the one-flaky-tick forgiveness and the second-bad-tick
2474
+ // block without a dead band above the rise threshold.)
2473
2475
  GUIDE_SIZE: 0.6,
2474
2476
  GUIDE_CENTERING: 0.6,
2475
2477
  GUIDE_POSE: 0.6,
@@ -2547,8 +2549,7 @@ var FaceGateV3 = class {
2547
2549
  const worstIndex = windowSamples.indexOf(worst);
2548
2550
  windowSamples = windowSamples.filter((_2, index) => index !== worstIndex);
2549
2551
  }
2550
- const windowMean = windowSamples.reduce((sum, value) => sum + value, 0) / Math.max(1, windowSamples.length);
2551
- const ready = this.p >= 1 && windowMean >= this.t.READY_MEAN_Q;
2552
+ const ready = this.p >= 1 && windowSamples.every((sample) => sample >= this.t.Q_GOOD);
2552
2553
  return {
2553
2554
  at: timestampMs,
2554
2555
  q,
@@ -3167,6 +3168,14 @@ function createLivenessCaptureInternal(options, internals = {}) {
3167
3168
  let animations = "full";
3168
3169
  let recordingStartedAt = null;
3169
3170
  let frozenAt = null;
3171
+ let frameWidth = 0;
3172
+ let frameHeight = 0;
3173
+ const latchFrameSize = () => {
3174
+ if (options.video.videoWidth > 0 && options.video.videoHeight > 0) {
3175
+ frameWidth = options.video.videoWidth;
3176
+ frameHeight = options.video.videoHeight;
3177
+ }
3178
+ };
3170
3179
  const setState = (next) => {
3171
3180
  state = next;
3172
3181
  emitter.emit("state", next);
@@ -3187,10 +3196,13 @@ function createLivenessCaptureInternal(options, internals = {}) {
3187
3196
  faceDetected = false;
3188
3197
  recordingStartedAt = null;
3189
3198
  frozenAt = null;
3199
+ frameWidth = 0;
3200
+ frameHeight = 0;
3190
3201
  }
3191
3202
  setState(next);
3192
3203
  syncProfile();
3193
3204
  if (next === "recording") {
3205
+ latchFrameSize();
3194
3206
  recordingStartedAt = now();
3195
3207
  hint = "hold";
3196
3208
  progress = 1;
@@ -3198,6 +3210,7 @@ function createLivenessCaptureInternal(options, internals = {}) {
3198
3210
  }
3199
3211
  }),
3200
3212
  engine.on("checks", (payload) => {
3213
+ latchFrameSize();
3201
3214
  progress = payload.progress;
3202
3215
  faceDetected = payload.factors.presence > 0;
3203
3216
  emitGate();
@@ -3217,8 +3230,8 @@ function createLivenessCaptureInternal(options, internals = {}) {
3217
3230
  keyframe: artifacts.keyframe,
3218
3231
  ...artifacts.clip !== void 0 ? { clip: artifacts.clip } : {},
3219
3232
  ...artifacts.clipMimeType !== void 0 ? { clipMime: artifacts.clipMimeType } : {},
3220
- width: options.video.videoWidth,
3221
- height: options.video.videoHeight,
3233
+ width: options.video.videoWidth || frameWidth,
3234
+ height: options.video.videoHeight || frameHeight,
3222
3235
  ...clipMs !== void 0 ? { clipMs } : {},
3223
3236
  captureChannel: detectIsMobile() ? "mobile" : "desktop",
3224
3237
  ...degraded ? { degraded: "NO_RECORDER" } : {}
@@ -3593,6 +3606,7 @@ var KEYFRAME_MIME = "image/jpeg";
3593
3606
  var TRANSPORT_REPLAY_ATTEMPTS = 2;
3594
3607
  var TRANSPORT_REPLAY_DELAY_MS = 1e3;
3595
3608
  var MAX_RETRY_AFTER_MS = 1e4;
3609
+ var RESYNC_REENTRY_MAX = 3;
3596
3610
  var TRANSPORT_CODES = /* @__PURE__ */ new Set([
3597
3611
  "network_error",
3598
3612
  "timeout",
@@ -3653,6 +3667,7 @@ async function runSubmission(rt, executor, hooks = {}) {
3653
3667
  }
3654
3668
  }
3655
3669
  }
3670
+ var LIVENESS_CHAINABLE = /* @__PURE__ */ new Set(["passed", "review", "processing", "skipped"]);
3656
3671
  async function runLivenessFlow(deps) {
3657
3672
  const { artifacts } = deps;
3658
3673
  const clip = artifacts.clip;
@@ -3660,68 +3675,88 @@ async function runLivenessFlow(deps) {
3660
3675
  if (artifacts.degraded === "NO_RECORDER" || clip === void 0 || !CLIP_MIME_ALLOWLIST.includes(bareMime)) {
3661
3676
  return { kind: "unavailable", reason: "no_recorder" };
3662
3677
  }
3663
- try {
3664
- const livenessWasExecutable = isExecutable(stepOf(deps.getView(), "liveness"));
3665
- if (livenessWasExecutable) {
3666
- let clipKey = null;
3667
- let attestation = null;
3668
- const response = await runSubmission(
3669
- deps,
3670
- async (client, idempotencyKey) => {
3671
- if (clipKey === null) {
3672
- deps.onPhase("uploading");
3673
- const presigned = await client.presign("clip", bareMime, clip.size);
3674
- await client.uploadToSpaces(presigned.uploadUrl, clip, bareMime);
3675
- clipKey = (await client.completeMedia(presigned.key)).key;
3676
- }
3677
- if (attestation === null) {
3678
- try {
3679
- attestation = await deps.getAttestation();
3680
- } catch (err) {
3681
- throw new AttestationError(err instanceof Error ? err.message : String(err));
3682
- }
3683
- }
3684
- deps.onPhase("checking");
3685
- return client.submitCheck(
3686
- "liveness",
3687
- { clipKey, attestation, captureChannel: artifacts.captureChannel },
3688
- { idempotencyKey }
3689
- );
3690
- },
3691
- {
3692
- onAttestationInvalid: () => {
3693
- attestation = null;
3694
- }
3695
- }
3696
- );
3697
- if (deps.isCancelled()) return { kind: "cancelled" };
3698
- deps.applySubmit(response, "liveness");
3699
- }
3678
+ const submitLiveness = buildLivenessSubmit(deps, clip, bareMime);
3679
+ const submitFaceMatch = buildFaceMatchSubmit(deps, artifacts.keyframe);
3680
+ let livenessSubmitted = false;
3681
+ let faceMatchSubmitted = false;
3682
+ let livenessChainable = true;
3683
+ let resyncs = 0;
3684
+ for (; ; ) {
3685
+ if (deps.isCancelled()) return { kind: "cancelled" };
3700
3686
  const view = deps.getView();
3701
- const liveness = stepOf(view, "liveness");
3702
- const livenessOk = !livenessWasExecutable || liveness === void 0 || liveness.state === "passed" || liveness.state === "review" || liveness.state === "processing" || liveness.state === "skipped";
3703
- if (!isTerminalView(view) && livenessOk && isExecutable(stepOf(view, "face_match"))) {
3704
- const keyframe = artifacts.keyframe;
3705
- let imageKey = null;
3706
- const response = await runSubmission(deps, async (client, idempotencyKey) => {
3707
- if (imageKey === null) {
3708
- deps.onPhase("uploading");
3709
- const presigned = await client.presign("image", KEYFRAME_MIME, keyframe.size);
3710
- await client.uploadToSpaces(presigned.uploadUrl, keyframe, KEYFRAME_MIME);
3711
- imageKey = (await client.completeMedia(presigned.key)).key;
3712
- }
3713
- deps.onPhase("checking");
3714
- return client.submitCheck("face_match", { imageKey }, { idempotencyKey });
3715
- });
3687
+ if (isTerminalView(view)) return { kind: "settled" };
3688
+ const frontier = frontierStep(view)?.checkType;
3689
+ const next = frontier === "liveness" && !livenessSubmitted ? "liveness" : frontier === "face_match" && !faceMatchSubmitted && livenessChainable ? "face_match" : null;
3690
+ if (next === null) return { kind: "settled" };
3691
+ try {
3692
+ if (next === "liveness") {
3693
+ livenessSubmitted = true;
3694
+ const response = await submitLiveness();
3695
+ if (deps.isCancelled()) return { kind: "cancelled" };
3696
+ deps.applySubmit(response, "liveness");
3697
+ const state = stepOf(deps.getView(), "liveness")?.state;
3698
+ livenessChainable = state === void 0 || LIVENESS_CHAINABLE.has(state);
3699
+ } else {
3700
+ faceMatchSubmitted = true;
3701
+ const response = await submitFaceMatch();
3702
+ if (deps.isCancelled()) return { kind: "cancelled" };
3703
+ deps.applySubmit(response, "face_match");
3704
+ }
3705
+ } catch (err) {
3716
3706
  if (deps.isCancelled()) return { kind: "cancelled" };
3717
- deps.applySubmit(response, "face_match");
3707
+ const outcome = classifyFailure(err);
3708
+ if (outcome.kind !== "resync" || resyncs >= RESYNC_REENTRY_MAX) return outcome;
3709
+ resyncs += 1;
3710
+ await deps.refreshView();
3718
3711
  }
3719
- return { kind: "settled" };
3720
- } catch (err) {
3721
- if (deps.isCancelled()) return { kind: "cancelled" };
3722
- return classifyFailure(err);
3723
3712
  }
3724
3713
  }
3714
+ function buildLivenessSubmit(deps, clip, bareMime) {
3715
+ let clipKey = null;
3716
+ let attestation = null;
3717
+ return () => runSubmission(
3718
+ deps,
3719
+ async (client, idempotencyKey) => {
3720
+ if (clipKey === null) {
3721
+ deps.onPhase("uploading");
3722
+ const presigned = await client.presign("clip", bareMime, clip.size);
3723
+ await client.uploadToSpaces(presigned.uploadUrl, clip, bareMime);
3724
+ clipKey = (await client.completeMedia(presigned.key)).key;
3725
+ }
3726
+ if (attestation === null) {
3727
+ try {
3728
+ attestation = await deps.getAttestation();
3729
+ } catch (err) {
3730
+ throw new AttestationError(err instanceof Error ? err.message : String(err));
3731
+ }
3732
+ }
3733
+ deps.onPhase("checking");
3734
+ return client.submitCheck(
3735
+ "liveness",
3736
+ { clipKey, attestation, captureChannel: deps.artifacts.captureChannel },
3737
+ { idempotencyKey }
3738
+ );
3739
+ },
3740
+ {
3741
+ onAttestationInvalid: () => {
3742
+ attestation = null;
3743
+ }
3744
+ }
3745
+ );
3746
+ }
3747
+ function buildFaceMatchSubmit(deps, keyframe) {
3748
+ let imageKey = null;
3749
+ return () => runSubmission(deps, async (client, idempotencyKey) => {
3750
+ if (imageKey === null) {
3751
+ deps.onPhase("uploading");
3752
+ const presigned = await client.presign("image", KEYFRAME_MIME, keyframe.size);
3753
+ await client.uploadToSpaces(presigned.uploadUrl, keyframe, KEYFRAME_MIME);
3754
+ imageKey = (await client.completeMedia(presigned.key)).key;
3755
+ }
3756
+ deps.onPhase("checking");
3757
+ return client.submitCheck("face_match", { imageKey }, { idempotencyKey });
3758
+ });
3759
+ }
3725
3760
  function classifyFailure(err) {
3726
3761
  if (err instanceof AttestationError) return { kind: "unavailable", reason: "attestation" };
3727
3762
  if (err instanceof HostedApiError) {
@@ -3956,7 +3991,8 @@ function createVerificationInternal(options, internals) {
3956
3991
  };
3957
3992
  };
3958
3993
  let active = null;
3959
- let flowToken = 0;
3994
+ let chain = Promise.resolve();
3995
+ let chainDepth = 0;
3960
3996
  const settleProcessing = async () => {
3961
3997
  let current = await ensureView();
3962
3998
  for (let i2 = 0; i2 < PROCESSING_POLL_MAX && !destroyed && !isTerminalView(current) && frontierStep(current) === void 0 && current.steps.some((step) => step.state === "processing"); i2 += 1) {
@@ -3966,11 +4002,17 @@ function createVerificationInternal(options, internals) {
3966
4002
  }
3967
4003
  return current;
3968
4004
  };
3969
- const runFlow = async (capture, artifacts, token) => {
3970
- const isCancelled = () => destroyed || token !== flowToken || active !== capture;
4005
+ const runFlow = async (bound, artifacts, capturedAttempt) => {
4006
+ const { capture } = bound;
4007
+ const isCancelled = () => destroyed;
4008
+ const ownsHandle = () => bound.alive && bound.attempt === capturedAttempt;
4009
+ const setPhase = (phase) => {
4010
+ if (ownsHandle()) capture.setFlowState(phase);
4011
+ };
3971
4012
  const finish = (result) => {
3972
4013
  if (isCancelled()) return;
3973
4014
  emitResult(result);
4015
+ if (!ownsHandle()) return;
3974
4016
  if (result.status === "retry" && rearm === "auto") {
3975
4017
  capture.handle.retry();
3976
4018
  return;
@@ -3982,7 +4024,7 @@ function createVerificationInternal(options, internals) {
3982
4024
  finish(unavailable(sessionId, "no_recorder"));
3983
4025
  return;
3984
4026
  }
3985
- capture.setFlowState("uploading");
4027
+ setPhase("uploading");
3986
4028
  try {
3987
4029
  await startRegistry();
3988
4030
  if (isCancelled()) return;
@@ -4004,10 +4046,9 @@ function createVerificationInternal(options, internals) {
4004
4046
  artifacts,
4005
4047
  getAttestation,
4006
4048
  getView: () => view ?? current,
4049
+ refreshView,
4007
4050
  applySubmit,
4008
- onPhase: (phase) => {
4009
- if (!isCancelled()) capture.setFlowState(phase);
4010
- },
4051
+ onPhase: setPhase,
4011
4052
  isCancelled
4012
4053
  });
4013
4054
  if (isCancelled()) return;
@@ -4024,18 +4065,43 @@ function createVerificationInternal(options, internals) {
4024
4065
  finish(unavailable(view?.sessionId ?? "", "network"));
4025
4066
  }
4026
4067
  };
4068
+ const enqueueFlow = (bound, artifacts) => {
4069
+ const capturedAttempt = bound.attempt;
4070
+ if (chainDepth > 0 && bound.alive) {
4071
+ bound.capture.setFlowState("uploading");
4072
+ }
4073
+ chainDepth += 1;
4074
+ chain = chain.then(() => runFlow(bound, artifacts, capturedAttempt)).finally(() => {
4075
+ chainDepth -= 1;
4076
+ }).catch(() => void 0);
4077
+ };
4027
4078
  const liveness = (captureOptions) => {
4028
- active?.handle.destroy();
4079
+ active?.capture.handle.destroy();
4080
+ if (active !== null) active.alive = false;
4029
4081
  const capture = createLivenessCaptureInternal(captureOptions, internals.capture);
4030
- active = capture;
4082
+ const bound = { capture, alive: true, attempt: 0 };
4083
+ active = bound;
4031
4084
  capture.handle.on("state", (state) => {
4032
- if (state === "starting") flowToken += 1;
4085
+ if (state === "starting") bound.attempt += 1;
4033
4086
  });
4034
4087
  capture.handle.on("captured", (artifacts) => {
4035
- const token = ++flowToken;
4036
- void Promise.resolve().then(() => runFlow(capture, artifacts, token));
4088
+ void Promise.resolve().then(() => enqueueFlow(bound, artifacts));
4037
4089
  });
4038
- return capture.handle;
4090
+ const inner = capture.handle;
4091
+ return {
4092
+ get state() {
4093
+ return inner.state;
4094
+ },
4095
+ on: (event, handler) => inner.on(event, handler),
4096
+ start: () => inner.start(),
4097
+ retry: () => inner.retry(),
4098
+ switchCamera: () => inner.switchCamera(),
4099
+ destroy: () => {
4100
+ bound.alive = false;
4101
+ if (active === bound) active = null;
4102
+ inner.destroy();
4103
+ }
4104
+ };
4039
4105
  };
4040
4106
  return {
4041
4107
  consent: () => ensureConsent(),
@@ -4045,8 +4111,11 @@ function createVerificationInternal(options, internals) {
4045
4111
  destroy: () => {
4046
4112
  destroyed = true;
4047
4113
  lifetime.abort();
4048
- active?.handle.destroy();
4049
- active = null;
4114
+ if (active !== null) {
4115
+ active.alive = false;
4116
+ active.capture.handle.destroy();
4117
+ active = null;
4118
+ }
4050
4119
  emitter.clear();
4051
4120
  }
4052
4121
  };