@incodetech/core 0.0.0-dev-20260729-9e7b030a → 0.0.0-dev-20260729-a31df3b7

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 (87) hide show
  1. package/dist/{FaceDetectionProvider-BHweGBEM.d.ts → FaceDetectionProvider-Mh6q3EbA.d.ts} +1 -2
  2. package/dist/IIdCaptureCapability-Cj_aceph.d.ts +231 -0
  3. package/dist/{IStorageCapability-B8IGxCwe.d.ts → IStorageCapability-ORkQfK3F.d.ts} +112 -1
  4. package/dist/ae-signature.esm.js +1 -1
  5. package/dist/{apiError-CzR7Y6Ua.esm.js → apiError-CS9ZMDr2.esm.js} +1 -1
  6. package/dist/authentication.d.ts +3 -3
  7. package/dist/authentication.esm.js +2 -2
  8. package/dist/{authenticationManager-CmkHvt7_.esm.js → authenticationManager-Cd4tefr2.esm.js} +2 -2
  9. package/dist/{authenticationManager-BGM8bpD0.d.ts → authenticationManager-Y2_MuGXy.d.ts} +1 -1
  10. package/dist/{authenticationStateMachine-O3acuOqq.esm.js → authenticationStateMachine-BnH1mizY.esm.js} +2 -2
  11. package/dist/consent.d.ts +1 -1
  12. package/dist/{consentManager-uhDPdU6i.d.ts → consentManager-5HWbGwOa.d.ts} +9 -9
  13. package/dist/{deepsightLoader-CaUpu0Dy.esm.js → deepsightLoader-CN4B5ugn.esm.js} +3 -3
  14. package/dist/{deepsightService-CR-CYdka.d.ts → deepsightService-DLVQS_RH.d.ts} +2 -112
  15. package/dist/document-capture.d.ts +1 -1
  16. package/dist/document-upload.d.ts +4 -4
  17. package/dist/{documentCaptureManager-pEqMY_Oo.d.ts → documentCaptureManager-DeSYkGWc.d.ts} +70 -70
  18. package/dist/dynamic-forms.esm.js +2 -2
  19. package/dist/{dynamicFormsManager-BxUgij0L.esm.js → dynamicFormsManager-B80PGYEF.esm.js} +1 -1
  20. package/dist/{dynamicFormsStateMachine-D0cEsDMH.esm.js → dynamicFormsStateMachine-B7R09HRI.esm.js} +1 -1
  21. package/dist/ekyb.esm.js +3 -3
  22. package/dist/{ekybManager-LicVPYug.esm.js → ekybManager-Cg_boKM5.esm.js} +1 -1
  23. package/dist/{ekybStateMachine-IgLa1nXx.esm.js → ekybStateMachine-CAXjJ2gA.esm.js} +1 -1
  24. package/dist/electronic-signature.esm.js +1 -1
  25. package/dist/{electronicSignatureManager-DqPJCXMG.esm.js → electronicSignatureManager-JuycNykG.esm.js} +1 -1
  26. package/dist/extensibility.d.ts +14 -14
  27. package/dist/extensibility.esm.js +8 -8
  28. package/dist/{faceCaptureManagerFactory-2W_XduCs.esm.js → faceCaptureManagerFactory-Cd1uQnGS.esm.js} +1 -1
  29. package/dist/{faceCaptureManagerFactory-B92LFT16.d.ts → faceCaptureManagerFactory-K8iLKaRl.d.ts} +5 -4
  30. package/dist/{faceCaptureSetup-D-9s0vUS.esm.js → faceCaptureSetup-CS7exiIh.esm.js} +3 -2
  31. package/dist/flow.d.ts +5 -5
  32. package/dist/flow.esm.js +3 -3
  33. package/dist/{flowCompletionService-D_DJ1dNA.esm.js → flowCompletionService-C77_-GvG.esm.js} +1 -1
  34. package/dist/{flowServices-ZgW3UGcz.esm.js → flowServices-BuSZYjB9.esm.js} +3 -3
  35. package/dist/geolocation.esm.js +2 -2
  36. package/dist/{geolocationManager-oLdeMbdy.esm.js → geolocationManager-D2ptN5BO.esm.js} +1 -1
  37. package/dist/{geolocationStateMachine-CSDKlrHE.esm.js → geolocationStateMachine-fOQUnMxw.esm.js} +1 -1
  38. package/dist/id-ocr.d.ts +1 -1
  39. package/dist/id-selfie-flow.d.ts +179 -0
  40. package/dist/id-selfie-flow.esm.js +917 -0
  41. package/dist/id.d.ts +3 -2
  42. package/dist/id.esm.js +3 -2
  43. package/dist/{idCaptureManager-DAsfVDWq.esm.js → idCaptureManager-BokR8KKO.esm.js} +2 -1
  44. package/dist/{idCaptureManager-B2w2CGuV.d.ts → idCaptureManager-wKz4MXR8.d.ts} +10 -2
  45. package/dist/idCaptureServices-C3nAZ64o.esm.js +353 -0
  46. package/dist/{idCaptureStateMachine-BMZCf9M7.esm.js → idCaptureStateMachine-CM8EP_MM.esm.js} +38 -374
  47. package/dist/{idOcrManager-DXc_xRN7.d.ts → idOcrManager-dLuxey6y.d.ts} +1 -1
  48. package/dist/identity-reuse.d.ts +1 -1
  49. package/dist/{identityReuseManager-v2TH5g7e.d.ts → identityReuseManager-D6pD4hcZ.d.ts} +1 -1
  50. package/dist/index.esm.js +1 -1
  51. package/dist/mandatory-consent.d.ts +2 -2
  52. package/dist/{mandatoryConsentManager-DOZSFZA2.d.ts → mandatoryConsentManager-DXlHM88Z.d.ts} +6 -6
  53. package/dist/manualReviewService-BnFXpAg9.esm.js +15 -0
  54. package/dist/{onDeviceFaceResults-D8FzOXsM.esm.js → onDeviceFaceResults-DFEU5wcC.esm.js} +3 -3
  55. package/dist/personhood.d.ts +4 -4
  56. package/dist/personhood.esm.js +3 -3
  57. package/dist/qe-signature.esm.js +1 -1
  58. package/dist/{recordingService-D6Hbc408.esm.js → recordingService-BwaHFQSV.esm.js} +2 -16
  59. package/dist/redirect-to-mobile.d.ts +2 -2
  60. package/dist/{redirectToMobileManager-BiVH2Ymm.d.ts → redirectToMobileManager-BD2PvT5B.d.ts} +1 -1
  61. package/dist/{runChildModule-CVKUUVl2.esm.js → runChildModule-1jsZfyb9.esm.js} +1 -1
  62. package/dist/selfie.d.ts +4 -4
  63. package/dist/selfie.esm.js +4 -4
  64. package/dist/{selfieManager-Cnw55Hp_.esm.js → selfieManager-hwv10r0j.esm.js} +2 -2
  65. package/dist/{selfieManager-CxeMb2tM.d.ts → selfieManager-tMxWiUk6.d.ts} +1 -1
  66. package/dist/{selfieStateMachine-D-wolitw.esm.js → selfieStateMachine-Cwb5y4OR.esm.js} +2 -2
  67. package/dist/{session-XugVyrIq.esm.js → session-DWfXLkyx.esm.js} +4 -4
  68. package/dist/session.esm.js +4 -4
  69. package/dist/{sessionInitializer-B3g2uAsl.esm.js → sessionInitializer-B58TmxdW.esm.js} +2 -2
  70. package/dist/{setup-BVuwm-QH.esm.js → setup-sxUVsl_J.esm.js} +4 -4
  71. package/dist/trueSight-D7YiTge6.esm.js +1 -0
  72. package/dist/{trueSightService-DGkNMx-E.esm.js → trueSightService-BIbE2pK1.esm.js} +1 -1
  73. package/dist/{types-4-pU2Nhm.d.ts → types-BsTkSXlg2.d.ts} +13 -232
  74. package/dist/{types-CrUhpMDP.d.ts → types-EELWyWpd.d.ts} +1 -1
  75. package/dist/{types-BBQjIaK4.d.ts → types-Hmd7U0JA.d.ts} +1 -1
  76. package/dist/video-selfie.d.ts +8 -100
  77. package/dist/video-selfie.esm.js +17 -608
  78. package/dist/videoSelfieCameraWarmer-lG-12ONu.esm.js +614 -0
  79. package/dist/videoSelfieStepDetector-144x423X.d.ts +117 -0
  80. package/dist/wasm.esm.js +1 -1
  81. package/dist/workflow.d.ts +1 -1
  82. package/dist/workflow.esm.js +14 -14
  83. package/package.json +8 -3
  84. package/dist/trueSight-DWfPmAL_.esm.js +0 -1
  85. /package/dist/{types-BX_XkMsQ.d.ts → types-BX_XkMsQ2.d.ts} +0 -0
  86. /package/dist/{types-BfgmV7s72.d.ts → types-BfgmV7s7.d.ts} +0 -0
  87. /package/dist/{types-DPIDuhut2.d.ts → types-DPIDuhut.d.ts} +0 -0
@@ -0,0 +1,614 @@
1
+ import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
2
+ import { t as api } from "./api-Bx3gKW_y.esm.js";
3
+ import { t as endpoints } from "./endpoints-DOfmJylF.esm.js";
4
+ import { l as facingModeForStep, u as resolveVideoSelfieSteps } from "./flowServices-BuSZYjB9.esm.js";
5
+ import { t as checkPermission } from "./permissionServices-GFikEK6S.esm.js";
6
+ import { i as requestCameraWithFallback, r as requestCameraAccess } from "./camera-BmFEyPP_.esm.js";
7
+ import { _ as StreamCanvasProcessingSession, a as encryptSelfieImage, l as startDetection, v as FaceDetectionProvider } from "./onDeviceFaceResults-DFEU5wcC.esm.js";
8
+ import { n as getBackCameraStream } from "./backCameraStream-BqiXcU28.esm.js";
9
+ import { n as getWindowDimensions } from "./getDeviceClass-CeBiEayS.esm.js";
10
+ import { t as FACE_ERROR_CODES } from "./types-BN5RPPIZ.esm.js";
11
+ import { i as uploadSelfie, r as processFace, t as getFaceErrorCodeFromUnknown } from "./selfieErrorUtils-DCRRRj38.esm.js";
12
+ import { t as StreamCanvasCapture } from "./StreamCanvasCapture-D2XcXCZ4.esm.js";
13
+ import { n as DEFAULT_ID_CAPTURE_THRESHOLDS, r as IdCaptureProvider, t as DEFAULT_ID_CAPTURE_GEOMETRY } from "./idCaptureDefaults-BDS43fFL.esm.js";
14
+ //#region src/modules/video-selfie/videoSelfieCompareService.ts
15
+ async function postVideoSelfieCompare(endpoint, base64Image) {
16
+ return (await api.post(endpoint, { base64Image })).data;
17
+ }
18
+ /** Compare a front-ID frame from the video selfie against the captured ID. */
19
+ const compareVideoSelfieId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareId, base64Image);
20
+ /** Compare OCR from a front-ID frame against the captured ID's OCR. */
21
+ const compareVideoSelfieOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareOcr, base64Image);
22
+ /** Compare a back-ID frame from the video selfie against the captured ID. */
23
+ const compareVideoSelfieBackId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackId, base64Image);
24
+ /** Compare OCR from a back-ID frame against the captured ID's OCR. */
25
+ const compareVideoSelfieBackOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackOcr, base64Image);
26
+ //#endregion
27
+ //#region src/modules/video-selfie/videoSelfieStepDetector.ts
28
+ /** Default autocapture window for the selfie step when the config omits one. */
29
+ const SELFIE_AUTOCAPTURE_MS = 1e4;
30
+ /**
31
+ * Whether the backend reported a confident face match on a front-ID compare.
32
+ * The backend normalizes `faceRecognitionConfidence` to 0 (no match) or 1 (match).
33
+ */
34
+ function isFaceRecognitionMatch(confidence) {
35
+ if (typeof confidence !== "number") return true;
36
+ return confidence >= 1;
37
+ }
38
+ const SELFIE_SUCCESS_DWELL_MS = 900;
39
+ /**
40
+ * How long the ID step holds on the "success" state (green check) after the
41
+ * backend validates a side, before advancing. Gives the user a beat to see the
42
+ * capture succeeded instead of the step jumping silently. Mirrors V1's
43
+ * IdVideoSelfie success flash.
44
+ */
45
+ const ID_SUCCESS_DWELL_MS = 900;
46
+ const SELFIE_VALIDATION_ERROR_CODES = [
47
+ FACE_ERROR_CODES.FACE_OCCLUDED,
48
+ FACE_ERROR_CODES.LIVENESS,
49
+ FACE_ERROR_CODES.BRIGHTNESS,
50
+ FACE_ERROR_CODES.LENSES,
51
+ FACE_ERROR_CODES.MASK,
52
+ FACE_ERROR_CODES.CLOSED_EYES,
53
+ FACE_ERROR_CODES.HEAD_COVER,
54
+ FACE_ERROR_CODES.FACE_NOT_FOUND,
55
+ FACE_ERROR_CODES.MULTIPLE_FACES,
56
+ FACE_ERROR_CODES.TOO_BLURRY,
57
+ FACE_ERROR_CODES.TOO_DARK,
58
+ FACE_ERROR_CODES.USER_IS_NOT_RECOGNIZED,
59
+ FACE_ERROR_CODES.SPOOF_ATTEMPT_DETECTED,
60
+ FACE_ERROR_CODES.FACE_TOO_DARK,
61
+ FACE_ERROR_CODES.LENSES_DETECTED,
62
+ FACE_ERROR_CODES.FACE_MASK_DETECTED,
63
+ FACE_ERROR_CODES.CLOSED_EYES_DETECTED,
64
+ FACE_ERROR_CODES.HEAD_COVER_DETECTED,
65
+ FACE_ERROR_CODES.FACE_CROPPING_FAILED,
66
+ FACE_ERROR_CODES.FACE_TOO_SMALL,
67
+ FACE_ERROR_CODES.FACE_TOO_BLURRY,
68
+ FACE_ERROR_CODES.BAD_PHOTO_QUALITY,
69
+ FACE_ERROR_CODES.SELFIE_IMAGE_LOW_QUALITY
70
+ ];
71
+ const SELFIE_VALIDATION_STATUS_CODES = [
72
+ 3004,
73
+ 3005,
74
+ 3006,
75
+ 3007,
76
+ 4010,
77
+ 4019,
78
+ 4077,
79
+ 4078
80
+ ];
81
+ function getErrorStatus(error) {
82
+ if (typeof error !== "object" || error === null) return;
83
+ if ("data" in error) {
84
+ const data = error.data;
85
+ if (typeof data === "object" && data !== null && "status" in data && typeof data.status === "number") return data.status;
86
+ }
87
+ if ("status" in error && typeof error.status === "number") return error.status;
88
+ }
89
+ function isSelfieValidationError(error) {
90
+ const errorCode = getFaceErrorCodeFromUnknown(error);
91
+ if (errorCode !== void 0 && SELFIE_VALIDATION_ERROR_CODES.includes(errorCode)) return true;
92
+ const status = getErrorStatus(error);
93
+ return status !== void 0 && SELFIE_VALIDATION_STATUS_CODES.includes(status);
94
+ }
95
+ function isIdComparisonValidationError(error) {
96
+ return typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" && error.status > 0;
97
+ }
98
+ function abortError() {
99
+ return /* @__PURE__ */ new Error("aborted");
100
+ }
101
+ function throwIfAborted(signal) {
102
+ if (signal.aborted) throw abortError();
103
+ }
104
+ /** Resolves after `ms`, or rejects early if `signal` aborts. */
105
+ function delay(ms, signal) {
106
+ if (signal.aborted) return Promise.reject(abortError());
107
+ const timerProvider = BrowserTimerProvider.getInstance();
108
+ return new Promise((resolve, reject) => {
109
+ const timer = timerProvider.setTimeout(() => {
110
+ signal.removeEventListener("abort", onAbort);
111
+ resolve();
112
+ }, ms);
113
+ const onAbort = () => {
114
+ timerProvider.clearTimeout(timer);
115
+ reject(abortError());
116
+ };
117
+ signal.addEventListener("abort", onAbort, { once: true });
118
+ if (signal.aborted) onAbort();
119
+ });
120
+ }
121
+ function isWrongVideoSelfieIdSide(step, reportedSide) {
122
+ const side = reportedSide.toLowerCase();
123
+ if (side === "wrong") return true;
124
+ const isFront = side.includes("front") && !side.includes("back");
125
+ const isBack = side.includes("back") && !side.includes("front");
126
+ return step === "frontId" ? isBack : isFront;
127
+ }
128
+ /**
129
+ * Builds the default per-step detector from the module config. Today it
130
+ * auto-captures the `selfie` step (face detection → `add/face` → `process/face`);
131
+ * ID / proof-of-address / questions / speech advance manually until their
132
+ * detectors land.
133
+ */
134
+ function createDefaultStepDetector(config, deps) {
135
+ return ({ step, stream, signal, onStatus, onOrientation }) => {
136
+ if (step === "selfie") return detectSelfie({
137
+ config,
138
+ deps,
139
+ stream,
140
+ signal,
141
+ onStatus
142
+ });
143
+ if (step === "frontId" || step === "backId") return detectId({
144
+ step,
145
+ config,
146
+ stream,
147
+ signal,
148
+ onStatus,
149
+ onOrientation
150
+ });
151
+ return new Promise(() => {});
152
+ };
153
+ }
154
+ async function detectSelfie(params) {
155
+ const { config, deps, stream, signal, onStatus } = params;
156
+ const provider = new FaceDetectionProvider();
157
+ await provider.initialize({
158
+ autocaptureInterval: SELFIE_AUTOCAPTURE_MS,
159
+ useOnDeviceWorkflow: false,
160
+ videoSelfie: true
161
+ });
162
+ if (signal.aborted) {
163
+ provider.dispose();
164
+ throw abortError();
165
+ }
166
+ provider.setChecksEnabled({
167
+ lenses: config.validateLenses ?? false,
168
+ mask: config.validateFaceMask ?? false,
169
+ closedEyes: config.validateClosedEyes ?? false,
170
+ headWear: config.validateHeadCover ?? false,
171
+ occlusion: false
172
+ });
173
+ const imageType = config.useAsSelfie === true ? "selfie" : "videoSelfie";
174
+ const capturer = new StreamCanvasCapture(stream);
175
+ const detectionConfig = {
176
+ showTutorial: false,
177
+ validateBrightness: false,
178
+ validateLenses: config.validateLenses ?? false,
179
+ validateFaceMask: config.validateFaceMask ?? false,
180
+ validateClosedEyes: config.validateClosedEyes ?? false,
181
+ validateHeadCover: config.validateHeadCover ?? false,
182
+ autoCaptureTimeout: SELFIE_AUTOCAPTURE_MS / 1e3
183
+ };
184
+ const runAttempt = () => new Promise((resolve, reject) => {
185
+ if (signal.aborted) {
186
+ reject(abortError());
187
+ return;
188
+ }
189
+ let settled = false;
190
+ function settle(run) {
191
+ if (settled) return;
192
+ settled = true;
193
+ signal.removeEventListener("abort", onAbort);
194
+ run();
195
+ }
196
+ const detection = startDetection({
197
+ config: detectionConfig,
198
+ capturer,
199
+ provider,
200
+ onUpdate: (status) => onStatus?.(status),
201
+ onSuccess: (canvas, faceCoordinates) => {
202
+ onStatus?.("capturing");
203
+ (async () => {
204
+ try {
205
+ const encryptedBase64Image = await encryptSelfieImage({
206
+ canvas,
207
+ dependencies: { getWasmUtil: async () => deps.getWasmUtil() }
208
+ });
209
+ throwIfAborted(signal);
210
+ await uploadSelfie({
211
+ encryptedBase64Image,
212
+ faceCoordinates,
213
+ imageType
214
+ });
215
+ throwIfAborted(signal);
216
+ await processFace(imageType);
217
+ throwIfAborted(signal);
218
+ onStatus?.("success");
219
+ await delay(SELFIE_SUCCESS_DWELL_MS, signal);
220
+ settle(resolve);
221
+ } catch (error) {
222
+ settle(() => reject(error instanceof Error ? error : new Error(String(error))));
223
+ }
224
+ })();
225
+ }
226
+ });
227
+ function onAbort() {
228
+ detection.cleanup();
229
+ settle(() => reject(abortError()));
230
+ }
231
+ signal.addEventListener("abort", onAbort, { once: true });
232
+ if (signal.aborted) onAbort();
233
+ });
234
+ try {
235
+ await runAttempt();
236
+ return { outcome: "success" };
237
+ } catch (error) {
238
+ if (signal.aborted || !isSelfieValidationError(error)) throw error;
239
+ onStatus?.("error");
240
+ return {
241
+ outcome: "validationFailed",
242
+ failure: "selfieVerificationFailed"
243
+ };
244
+ } finally {
245
+ capturer.dispose();
246
+ provider.dispose();
247
+ }
248
+ }
249
+ async function detectId(params) {
250
+ const { step, config, stream, signal, onStatus, onOrientation } = params;
251
+ const provider = new IdCaptureProvider();
252
+ await provider.initialize({ videoSelfie: true });
253
+ if (signal.aborted) {
254
+ provider.dispose();
255
+ throw abortError();
256
+ }
257
+ const windowDimensions = getWindowDimensions();
258
+ provider.setGeometry({
259
+ ...DEFAULT_ID_CAPTURE_GEOMETRY,
260
+ windowOuterWidth: windowDimensions.outerWidth,
261
+ windowOuterHeight: windowDimensions.outerHeight,
262
+ windowInnerWidth: windowDimensions.innerWidth,
263
+ windowInnerHeight: windowDimensions.innerHeight
264
+ });
265
+ provider.setSettings({
266
+ isFixedMask: false,
267
+ isIPhone14OrHigher: false,
268
+ idType: step === "backId" ? "BackId" : "FrontId",
269
+ blurCheckEnabled: true,
270
+ glareCheckEnabled: false,
271
+ faceQualityCheckEnabled: false,
272
+ iouCheckEnabled: true,
273
+ fpsLimitEnabled: false
274
+ });
275
+ provider.setThresholds(DEFAULT_ID_CAPTURE_THRESHOLDS);
276
+ provider.setModelType("v2");
277
+ const capturer = new StreamCanvasCapture(stream);
278
+ return new Promise((resolve, reject) => {
279
+ let validating = false;
280
+ let wrongSide = false;
281
+ let settled = false;
282
+ let toreDown = false;
283
+ const session = new StreamCanvasProcessingSession({
284
+ capturer,
285
+ provider
286
+ });
287
+ function teardown() {
288
+ if (toreDown) return;
289
+ toreDown = true;
290
+ session.dispose();
291
+ capturer.dispose();
292
+ provider.dispose();
293
+ }
294
+ function settle(run) {
295
+ if (settled) return;
296
+ settled = true;
297
+ signal.removeEventListener("abort", onAbort);
298
+ teardown();
299
+ run();
300
+ }
301
+ function onAbort() {
302
+ settle(() => reject(abortError()));
303
+ }
304
+ provider.setCallbacks({
305
+ onFarAway: () => {
306
+ if (!validating && !wrongSide) onStatus?.("tooFar");
307
+ },
308
+ onMaskChange: (_show, _mask, _top, orientation) => {
309
+ onOrientation?.(orientation);
310
+ },
311
+ onBlur: () => {
312
+ if (!validating && !wrongSide) onStatus?.("blur");
313
+ },
314
+ onIdSideChange: (side) => {
315
+ wrongSide = isWrongVideoSelfieIdSide(step, side);
316
+ onStatus?.(wrongSide ? "wrongSide" : "detecting");
317
+ },
318
+ onCapturing: () => {
319
+ if (validating || wrongSide) return;
320
+ const base64Image = capturer.getLatestCanvas()?.getBase64Image();
321
+ if (!base64Image) return;
322
+ validating = true;
323
+ onStatus?.("capturing");
324
+ (async () => {
325
+ try {
326
+ const result = await validateIdFrame(step, config, base64Image);
327
+ if (result.outcome === "success") {
328
+ throwIfAborted(signal);
329
+ onStatus?.("success");
330
+ await delay(ID_SUCCESS_DWELL_MS, signal);
331
+ settle(() => resolve(result));
332
+ return;
333
+ }
334
+ onStatus?.("error");
335
+ settle(() => resolve(result));
336
+ } catch (error) {
337
+ if (signal.aborted) {
338
+ settle(() => reject(error instanceof Error ? error : new Error(String(error))));
339
+ return;
340
+ }
341
+ onStatus?.("error");
342
+ if (isIdComparisonValidationError(error)) {
343
+ settle(() => resolve({
344
+ outcome: "validationFailed",
345
+ failure: "idScanFailed"
346
+ }));
347
+ return;
348
+ }
349
+ settle(() => resolve({
350
+ outcome: "retryableFailure",
351
+ failure: "idScanFailed"
352
+ }));
353
+ }
354
+ })();
355
+ }
356
+ });
357
+ onStatus?.("detecting");
358
+ signal.addEventListener("abort", onAbort, { once: true });
359
+ if (signal.aborted) onAbort();
360
+ });
361
+ }
362
+ /** Runs the configured ID/OCR comparisons for a captured ID frame. */
363
+ async function validateIdFrame(step, config, base64Image) {
364
+ if (step === "backId") {
365
+ if (config.compareBackIdEnabled) {
366
+ const result = await compareVideoSelfieBackId(base64Image);
367
+ if (result.idSideMatched === false || result.success === false) return {
368
+ outcome: "validationFailed",
369
+ failure: "idTypeMismatch"
370
+ };
371
+ }
372
+ if (config.compareBackOcrEnabled) {
373
+ if ((await compareVideoSelfieBackOcr(base64Image)).success === false) return {
374
+ outcome: "validationFailed",
375
+ failure: "nameVerificationFailed"
376
+ };
377
+ }
378
+ return { outcome: "success" };
379
+ }
380
+ if (config.compareIdEnabled) {
381
+ const result = await compareVideoSelfieId(base64Image);
382
+ if (result.idTypeMatched === false) return {
383
+ outcome: "validationFailed",
384
+ failure: "idTypeMismatch"
385
+ };
386
+ if (typeof result.faceRecognitionConfidence === "number" && !isFaceRecognitionMatch(result.faceRecognitionConfidence)) return {
387
+ outcome: "validationFailed",
388
+ failure: "photoVerificationFailed"
389
+ };
390
+ }
391
+ if (config.compareOcrEnabled) {
392
+ if ((await compareVideoSelfieOcr(base64Image)).success === false) return {
393
+ outcome: "validationFailed",
394
+ failure: "nameVerificationFailed"
395
+ };
396
+ }
397
+ return { outcome: "success" };
398
+ }
399
+ //#endregion
400
+ //#region ../infra/src/media/CameraCompositor.ts
401
+ const MAX_COMPOSITE_LONG_EDGE = 1280;
402
+ const MAX_COMPOSITE_SHORT_EDGE = 720;
403
+ const FRAME_INTERVAL_TOLERANCE_RATIO = .9;
404
+ /**
405
+ * Composites the *active* camera into a canvas and exposes the canvas as a
406
+ * single, stable `MediaStream` to record. Switching cameras (front ↔ back)
407
+ * just repoints what the canvas draws — the output stream and its audio track
408
+ * are never replaced — so a single `MediaRecorder` produces one continuous
409
+ * video that follows the camera switches (V1 parity), instead of restarting
410
+ * per camera.
411
+ *
412
+ * The audio track from the initial stream is carried through unchanged across
413
+ * video switches, so there's no audio glitch when the camera flips.
414
+ */
415
+ var CameraCompositor = class {
416
+ constructor(options = {}) {
417
+ this.canvas = null;
418
+ this.ctx = null;
419
+ this.video = null;
420
+ this.frameRequestId = null;
421
+ this.frameRequestKind = null;
422
+ this.lastDrawTimeMs = null;
423
+ this.lastVideoTimeSeconds = null;
424
+ this.audioTrack = null;
425
+ this.videoTrack = null;
426
+ this.composite = null;
427
+ this.fps = options.fps ?? 30;
428
+ this.frameIntervalMs = this.fps > 0 ? 1e3 / this.fps : 0;
429
+ }
430
+ /**
431
+ * Begin compositing `stream` and return the composite stream to record. The
432
+ * stream's audio track (if any) is carried through unchanged across later
433
+ * `switchVideo` calls.
434
+ */
435
+ start(stream) {
436
+ this.canvas = document.createElement("canvas");
437
+ this.ctx = this.canvas.getContext("2d");
438
+ this.video = document.createElement("video");
439
+ this.video.muted = true;
440
+ this.video.playsInline = true;
441
+ this.audioTrack = stream.getAudioTracks()[0] ?? null;
442
+ this.setVideoSource(stream);
443
+ const composite = this.canvas.captureStream(this.fps);
444
+ if (this.audioTrack) composite.addTrack(this.audioTrack);
445
+ this.composite = composite;
446
+ this.startDrawLoop();
447
+ return composite;
448
+ }
449
+ /**
450
+ * Swap the camera feeding the canvas without interrupting the composite
451
+ * stream. Pass a video-only stream (audio is kept from `start`). The previous
452
+ * video track is stopped.
453
+ */
454
+ switchVideo(stream) {
455
+ if (!this.composite) return;
456
+ const previousVideoTrack = this.videoTrack;
457
+ this.setVideoSource(stream);
458
+ this.startDrawLoop();
459
+ if (previousVideoTrack) previousVideoTrack.stop();
460
+ }
461
+ /** The composite stream being recorded, if compositing is active. */
462
+ get stream() {
463
+ return this.composite ?? void 0;
464
+ }
465
+ /** Stop compositing and release the canvas, audio, and video tracks. */
466
+ stop() {
467
+ this.cancelDrawLoop();
468
+ this.videoTrack?.stop();
469
+ this.audioTrack?.stop();
470
+ this.composite?.getTracks().forEach((track) => track.stop());
471
+ if (this.video) this.video.srcObject = null;
472
+ this.canvas = null;
473
+ this.ctx = null;
474
+ this.video = null;
475
+ this.videoTrack = null;
476
+ this.audioTrack = null;
477
+ this.composite = null;
478
+ }
479
+ setVideoSource(stream) {
480
+ this.videoTrack = stream.getVideoTracks()[0] ?? null;
481
+ const video = this.video;
482
+ if (!video) return;
483
+ video.srcObject = new MediaStream(stream.getVideoTracks());
484
+ video.play().catch(() => {});
485
+ }
486
+ startDrawLoop() {
487
+ this.cancelDrawLoop();
488
+ this.lastDrawTimeMs = null;
489
+ this.lastVideoTimeSeconds = null;
490
+ const draw = (timeMs) => {
491
+ const elapsed = this.lastDrawTimeMs === null ? Infinity : timeMs - this.lastDrawTimeMs;
492
+ if ((this.frameIntervalMs === 0 || elapsed >= this.frameIntervalMs * FRAME_INTERVAL_TOLERANCE_RATIO) && this.drawCurrentFrame()) this.lastDrawTimeMs = timeMs;
493
+ this.scheduleNextFrame(draw);
494
+ };
495
+ this.scheduleNextFrame(draw);
496
+ }
497
+ scheduleNextFrame(callback) {
498
+ const video = this.video;
499
+ if (video && typeof video.requestVideoFrameCallback === "function") {
500
+ this.frameRequestKind = "video";
501
+ this.frameRequestId = video.requestVideoFrameCallback(callback);
502
+ return;
503
+ }
504
+ this.frameRequestKind = "animation";
505
+ this.frameRequestId = requestAnimationFrame(callback);
506
+ }
507
+ cancelDrawLoop() {
508
+ if (this.frameRequestId === null) return;
509
+ if (this.frameRequestKind === "video" && this.video && typeof this.video.cancelVideoFrameCallback === "function") this.video.cancelVideoFrameCallback(this.frameRequestId);
510
+ else cancelAnimationFrame(this.frameRequestId);
511
+ this.frameRequestId = null;
512
+ this.frameRequestKind = null;
513
+ }
514
+ drawCurrentFrame() {
515
+ const video = this.video;
516
+ const canvas = this.canvas;
517
+ const ctx = this.ctx;
518
+ if (!video || !canvas || !ctx || video.videoWidth <= 0) return false;
519
+ if (this.lastVideoTimeSeconds === video.currentTime) return false;
520
+ const dimensions = this.getBoundedDimensions(video.videoWidth, video.videoHeight);
521
+ if (canvas.width !== dimensions.width || canvas.height !== dimensions.height) {
522
+ canvas.width = dimensions.width;
523
+ canvas.height = dimensions.height;
524
+ }
525
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
526
+ this.lastVideoTimeSeconds = video.currentTime;
527
+ return true;
528
+ }
529
+ getBoundedDimensions(width, height) {
530
+ const longEdge = Math.max(width, height);
531
+ const shortEdge = Math.min(width, height);
532
+ const scale = Math.min(1, MAX_COMPOSITE_LONG_EDGE / longEdge, MAX_COMPOSITE_SHORT_EDGE / shortEdge);
533
+ return {
534
+ width: Math.max(1, Math.round(width * scale)),
535
+ height: Math.max(1, Math.round(height * scale))
536
+ };
537
+ }
538
+ };
539
+ //#endregion
540
+ //#region src/modules/video-selfie/videoSelfieCameraWarmer.ts
541
+ /**
542
+ * Acquire the camera for a Video Selfie step. The rear (`environment`) steps
543
+ * (frontId / backId / poa) route through {@link getBackCameraStream} so the
544
+ * main autofocus lens is selected — Samsung's default `facingMode:environment`
545
+ * camera is often a fixed-focus auxiliary lens, which blurs the ID steps and
546
+ * stalls capture (client-side blur check is on). This mirrors the ID Capture
547
+ * module. `getBackCameraStream` returns video only, so audio is acquired and
548
+ * merged separately when the step needs it. Front (`user`) steps keep the
549
+ * plain fallback path. Used both as the recording machine's default
550
+ * `acquireCamera` dependency and as this warmer's own acquire/fallback.
551
+ */
552
+ async function defaultAcquireCamera(facingMode, withAudio) {
553
+ if (facingMode !== "environment") return requestCameraWithFallback({
554
+ video: { facingMode },
555
+ audio: withAudio
556
+ });
557
+ const { stream } = await getBackCameraStream();
558
+ if (withAudio) try {
559
+ const audioStream = await requestCameraAccess({ audio: true });
560
+ for (const audioTrack of audioStream.getAudioTracks()) stream.addTrack(audioTrack);
561
+ } catch (error) {
562
+ stream.getTracks().forEach((track) => track.stop());
563
+ throw error;
564
+ }
565
+ return stream;
566
+ }
567
+ function stopStream(stream) {
568
+ stream?.getTracks().forEach((track) => track.stop());
569
+ }
570
+ /**
571
+ * Creates a {@link VideoSelfieCameraWarmer} for the given config. The first
572
+ * camera (and whether it needs audio) is derived from the resolved step order —
573
+ * the same first acquire the recording machine performs on `START`.
574
+ */
575
+ function createVideoSelfieCameraWarmer(config) {
576
+ const firstFacing = facingModeForStep(resolveVideoSelfieSteps(config)[0] ?? "selfie");
577
+ let warming = null;
578
+ let warmed = null;
579
+ let consumed = false;
580
+ return {
581
+ warm() {
582
+ if (warming) return;
583
+ warming = (async () => {
584
+ try {
585
+ if (await checkPermission() !== "granted") return null;
586
+ warmed = await defaultAcquireCamera(firstFacing, true);
587
+ return warmed;
588
+ } catch {
589
+ return null;
590
+ }
591
+ })();
592
+ },
593
+ async acquireCamera(facingMode, withAudio) {
594
+ if (!consumed && facingMode === firstFacing && withAudio) {
595
+ consumed = true;
596
+ const stream = warmed ?? (warming ? await warming : null);
597
+ warmed = null;
598
+ if (stream) return stream;
599
+ }
600
+ return defaultAcquireCamera(facingMode, withAudio);
601
+ },
602
+ dispose() {
603
+ if (consumed) return;
604
+ if (warmed) {
605
+ stopStream(warmed);
606
+ warmed = null;
607
+ } else if (warming) warming.then((stream) => {
608
+ if (!consumed) stopStream(stream);
609
+ });
610
+ }
611
+ };
612
+ }
613
+ //#endregion
614
+ export { delay as a, createDefaultStepDetector as i, defaultAcquireCamera as n, isSelfieValidationError as o, CameraCompositor as r, isWrongVideoSelfieIdSide as s, createVideoSelfieCameraWarmer as t };