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

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.cjs CHANGED
@@ -2064,6 +2064,7 @@ var KEYFRAME_MIME = "image/jpeg";
2064
2064
  var TRANSPORT_REPLAY_ATTEMPTS = 2;
2065
2065
  var TRANSPORT_REPLAY_DELAY_MS = 1e3;
2066
2066
  var MAX_RETRY_AFTER_MS = 1e4;
2067
+ var RESYNC_REENTRY_MAX = 3;
2067
2068
  var TRANSPORT_CODES = /* @__PURE__ */ new Set([
2068
2069
  "network_error",
2069
2070
  "timeout",
@@ -2124,6 +2125,7 @@ async function runSubmission(rt, executor, hooks = {}) {
2124
2125
  }
2125
2126
  }
2126
2127
  }
2128
+ var LIVENESS_CHAINABLE = /* @__PURE__ */ new Set(["passed", "review", "processing", "skipped"]);
2127
2129
  async function runLivenessFlow(deps) {
2128
2130
  const { artifacts } = deps;
2129
2131
  const clip = artifacts.clip;
@@ -2131,67 +2133,87 @@ async function runLivenessFlow(deps) {
2131
2133
  if (artifacts.degraded === "NO_RECORDER" || clip === void 0 || !CLIP_MIME_ALLOWLIST.includes(bareMime)) {
2132
2134
  return { kind: "unavailable", reason: "no_recorder" };
2133
2135
  }
2134
- try {
2135
- const livenessWasExecutable = isExecutable(stepOf(deps.getView(), "liveness"));
2136
- if (livenessWasExecutable) {
2137
- let clipKey = null;
2138
- let attestation = null;
2139
- const response = await runSubmission(
2140
- deps,
2141
- async (client, idempotencyKey) => {
2142
- if (clipKey === null) {
2143
- deps.onPhase("uploading");
2144
- const presigned = await client.presign("clip", bareMime, clip.size);
2145
- await client.uploadToSpaces(presigned.uploadUrl, clip, bareMime);
2146
- clipKey = (await client.completeMedia(presigned.key)).key;
2147
- }
2148
- if (attestation === null) {
2149
- try {
2150
- attestation = await deps.getAttestation();
2151
- } catch (err) {
2152
- throw new AttestationError(err instanceof Error ? err.message : String(err));
2153
- }
2154
- }
2155
- deps.onPhase("checking");
2156
- return client.submitCheck(
2157
- "liveness",
2158
- { clipKey, attestation, captureChannel: artifacts.captureChannel },
2159
- { idempotencyKey }
2160
- );
2161
- },
2162
- {
2163
- onAttestationInvalid: () => {
2164
- attestation = null;
2165
- }
2166
- }
2167
- );
2168
- if (deps.isCancelled()) return { kind: "cancelled" };
2169
- deps.applySubmit(response, "liveness");
2170
- }
2136
+ const submitLiveness = buildLivenessSubmit(deps, clip, bareMime);
2137
+ const submitFaceMatch = buildFaceMatchSubmit(deps, artifacts.keyframe);
2138
+ let livenessSubmitted = false;
2139
+ let faceMatchSubmitted = false;
2140
+ let livenessChainable = true;
2141
+ let resyncs = 0;
2142
+ for (; ; ) {
2143
+ if (deps.isCancelled()) return { kind: "cancelled" };
2171
2144
  const view = deps.getView();
2172
- const liveness = stepOf(view, "liveness");
2173
- const livenessOk = !livenessWasExecutable || liveness === void 0 || liveness.state === "passed" || liveness.state === "review" || liveness.state === "processing" || liveness.state === "skipped";
2174
- if (!isTerminalView(view) && livenessOk && isExecutable(stepOf(view, "face_match"))) {
2175
- const keyframe = artifacts.keyframe;
2176
- let imageKey = null;
2177
- const response = await runSubmission(deps, async (client, idempotencyKey) => {
2178
- if (imageKey === null) {
2179
- deps.onPhase("uploading");
2180
- const presigned = await client.presign("image", KEYFRAME_MIME, keyframe.size);
2181
- await client.uploadToSpaces(presigned.uploadUrl, keyframe, KEYFRAME_MIME);
2182
- imageKey = (await client.completeMedia(presigned.key)).key;
2183
- }
2184
- deps.onPhase("checking");
2185
- return client.submitCheck("face_match", { imageKey }, { idempotencyKey });
2186
- });
2145
+ if (isTerminalView(view)) return { kind: "settled" };
2146
+ const frontier = frontierStep(view)?.checkType;
2147
+ const next = frontier === "liveness" && !livenessSubmitted ? "liveness" : frontier === "face_match" && !faceMatchSubmitted && livenessChainable ? "face_match" : null;
2148
+ if (next === null) return { kind: "settled" };
2149
+ try {
2150
+ if (next === "liveness") {
2151
+ livenessSubmitted = true;
2152
+ const response = await submitLiveness();
2153
+ if (deps.isCancelled()) return { kind: "cancelled" };
2154
+ deps.applySubmit(response, "liveness");
2155
+ const state = stepOf(deps.getView(), "liveness")?.state;
2156
+ livenessChainable = state === void 0 || LIVENESS_CHAINABLE.has(state);
2157
+ } else {
2158
+ faceMatchSubmitted = true;
2159
+ const response = await submitFaceMatch();
2160
+ if (deps.isCancelled()) return { kind: "cancelled" };
2161
+ deps.applySubmit(response, "face_match");
2162
+ }
2163
+ } catch (err) {
2187
2164
  if (deps.isCancelled()) return { kind: "cancelled" };
2188
- deps.applySubmit(response, "face_match");
2165
+ const outcome = classifyFailure(err);
2166
+ if (outcome.kind !== "resync" || resyncs >= RESYNC_REENTRY_MAX) return outcome;
2167
+ resyncs += 1;
2168
+ await deps.refreshView();
2169
+ }
2170
+ }
2171
+ }
2172
+ function buildLivenessSubmit(deps, clip, bareMime) {
2173
+ let clipKey = null;
2174
+ let attestation = null;
2175
+ return () => runSubmission(
2176
+ deps,
2177
+ async (client, idempotencyKey) => {
2178
+ if (clipKey === null) {
2179
+ deps.onPhase("uploading");
2180
+ const presigned = await client.presign("clip", bareMime, clip.size);
2181
+ await client.uploadToSpaces(presigned.uploadUrl, clip, bareMime);
2182
+ clipKey = (await client.completeMedia(presigned.key)).key;
2183
+ }
2184
+ if (attestation === null) {
2185
+ try {
2186
+ attestation = await deps.getAttestation();
2187
+ } catch (err) {
2188
+ throw new AttestationError(err instanceof Error ? err.message : String(err));
2189
+ }
2190
+ }
2191
+ deps.onPhase("checking");
2192
+ return client.submitCheck(
2193
+ "liveness",
2194
+ { clipKey, attestation, captureChannel: deps.artifacts.captureChannel },
2195
+ { idempotencyKey }
2196
+ );
2197
+ },
2198
+ {
2199
+ onAttestationInvalid: () => {
2200
+ attestation = null;
2201
+ }
2189
2202
  }
2190
- return { kind: "settled" };
2191
- } catch (err) {
2192
- if (deps.isCancelled()) return { kind: "cancelled" };
2193
- return classifyFailure(err);
2194
- }
2203
+ );
2204
+ }
2205
+ function buildFaceMatchSubmit(deps, keyframe) {
2206
+ let imageKey = null;
2207
+ return () => runSubmission(deps, async (client, idempotencyKey) => {
2208
+ if (imageKey === null) {
2209
+ deps.onPhase("uploading");
2210
+ const presigned = await client.presign("image", KEYFRAME_MIME, keyframe.size);
2211
+ await client.uploadToSpaces(presigned.uploadUrl, keyframe, KEYFRAME_MIME);
2212
+ imageKey = (await client.completeMedia(presigned.key)).key;
2213
+ }
2214
+ deps.onPhase("checking");
2215
+ return client.submitCheck("face_match", { imageKey }, { idempotencyKey });
2216
+ });
2195
2217
  }
2196
2218
  function classifyFailure(err) {
2197
2219
  if (err instanceof AttestationError) return { kind: "unavailable", reason: "attestation" };
@@ -2427,7 +2449,8 @@ function createVerificationInternal(options, internals) {
2427
2449
  };
2428
2450
  };
2429
2451
  let active = null;
2430
- let flowToken = 0;
2452
+ let chain = Promise.resolve();
2453
+ let chainDepth = 0;
2431
2454
  const settleProcessing = async () => {
2432
2455
  let current = await ensureView();
2433
2456
  for (let i = 0; i < PROCESSING_POLL_MAX && !destroyed && !isTerminalView(current) && frontierStep(current) === void 0 && current.steps.some((step) => step.state === "processing"); i += 1) {
@@ -2437,11 +2460,17 @@ function createVerificationInternal(options, internals) {
2437
2460
  }
2438
2461
  return current;
2439
2462
  };
2440
- const runFlow = async (capture, artifacts, token) => {
2441
- const isCancelled = () => destroyed || token !== flowToken || active !== capture;
2463
+ const runFlow = async (bound, artifacts, capturedAttempt) => {
2464
+ const { capture } = bound;
2465
+ const isCancelled = () => destroyed;
2466
+ const ownsHandle = () => bound.alive && bound.attempt === capturedAttempt;
2467
+ const setPhase = (phase) => {
2468
+ if (ownsHandle()) capture.setFlowState(phase);
2469
+ };
2442
2470
  const finish = (result) => {
2443
2471
  if (isCancelled()) return;
2444
2472
  emitResult(result);
2473
+ if (!ownsHandle()) return;
2445
2474
  if (result.status === "retry" && rearm === "auto") {
2446
2475
  capture.handle.retry();
2447
2476
  return;
@@ -2453,7 +2482,7 @@ function createVerificationInternal(options, internals) {
2453
2482
  finish(unavailable(sessionId, "no_recorder"));
2454
2483
  return;
2455
2484
  }
2456
- capture.setFlowState("uploading");
2485
+ setPhase("uploading");
2457
2486
  try {
2458
2487
  await startRegistry();
2459
2488
  if (isCancelled()) return;
@@ -2475,10 +2504,9 @@ function createVerificationInternal(options, internals) {
2475
2504
  artifacts,
2476
2505
  getAttestation,
2477
2506
  getView: () => view ?? current,
2507
+ refreshView,
2478
2508
  applySubmit,
2479
- onPhase: (phase) => {
2480
- if (!isCancelled()) capture.setFlowState(phase);
2481
- },
2509
+ onPhase: setPhase,
2482
2510
  isCancelled
2483
2511
  });
2484
2512
  if (isCancelled()) return;
@@ -2495,18 +2523,43 @@ function createVerificationInternal(options, internals) {
2495
2523
  finish(unavailable(view?.sessionId ?? "", "network"));
2496
2524
  }
2497
2525
  };
2526
+ const enqueueFlow = (bound, artifacts) => {
2527
+ const capturedAttempt = bound.attempt;
2528
+ if (chainDepth > 0 && bound.alive) {
2529
+ bound.capture.setFlowState("uploading");
2530
+ }
2531
+ chainDepth += 1;
2532
+ chain = chain.then(() => runFlow(bound, artifacts, capturedAttempt)).finally(() => {
2533
+ chainDepth -= 1;
2534
+ }).catch(() => void 0);
2535
+ };
2498
2536
  const liveness = (captureOptions) => {
2499
- active?.handle.destroy();
2537
+ active?.capture.handle.destroy();
2538
+ if (active !== null) active.alive = false;
2500
2539
  const capture = createLivenessCaptureInternal(captureOptions, internals.capture);
2501
- active = capture;
2540
+ const bound = { capture, alive: true, attempt: 0 };
2541
+ active = bound;
2502
2542
  capture.handle.on("state", (state) => {
2503
- if (state === "starting") flowToken += 1;
2543
+ if (state === "starting") bound.attempt += 1;
2504
2544
  });
2505
2545
  capture.handle.on("captured", (artifacts) => {
2506
- const token = ++flowToken;
2507
- void Promise.resolve().then(() => runFlow(capture, artifacts, token));
2546
+ void Promise.resolve().then(() => enqueueFlow(bound, artifacts));
2508
2547
  });
2509
- return capture.handle;
2548
+ const inner = capture.handle;
2549
+ return {
2550
+ get state() {
2551
+ return inner.state;
2552
+ },
2553
+ on: (event, handler) => inner.on(event, handler),
2554
+ start: () => inner.start(),
2555
+ retry: () => inner.retry(),
2556
+ switchCamera: () => inner.switchCamera(),
2557
+ destroy: () => {
2558
+ bound.alive = false;
2559
+ if (active === bound) active = null;
2560
+ inner.destroy();
2561
+ }
2562
+ };
2510
2563
  };
2511
2564
  return {
2512
2565
  consent: () => ensureConsent(),
@@ -2516,8 +2569,11 @@ function createVerificationInternal(options, internals) {
2516
2569
  destroy: () => {
2517
2570
  destroyed = true;
2518
2571
  lifetime.abort();
2519
- active?.handle.destroy();
2520
- active = null;
2572
+ if (active !== null) {
2573
+ active.alive = false;
2574
+ active.capture.handle.destroy();
2575
+ active = null;
2576
+ }
2521
2577
  emitter.clear();
2522
2578
  }
2523
2579
  };