@sekyro/web 0.1.0-rc.7 → 0.1.0-rc.8
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/CHANGELOG.md +87 -0
- package/LICENSE +25 -16
- package/README.md +3 -5
- package/THIRD-PARTY-NOTICES.txt +154 -0
- package/dist/background-monitor-A7FKJOAG.js +1 -0
- package/dist/capture-session-doc-YBE3UMJJ.js +1 -0
- package/dist/{capture-session-v3-Okq0EQHG.d.ts → capture-session-v3-CQK-G-qX.d.ts} +72 -3
- package/dist/{capture-session-v3-UQU_WjO2.d.cts → capture-session-v3-Dvob-uH2.d.cts} +72 -3
- package/dist/chunk-4RS2YIL4.js +1 -0
- package/dist/chunk-7V2M6Y2W.js +1 -0
- package/dist/chunk-7ZCZY3ZP.js +1 -0
- package/dist/chunk-A3FUGHAH.js +2 -0
- package/dist/chunk-BPE5NSQ2.js +1 -0
- package/dist/chunk-CJHMFPL6.js +1 -0
- package/dist/chunk-DFTCAWMP.js +1 -0
- package/dist/chunk-DK5YPBDQ.js +1 -0
- package/dist/chunk-DQWW4OCK.js +1 -0
- package/dist/chunk-FHJ7HO3M.js +1 -0
- package/dist/chunk-G6LKLN4U.js +1 -0
- package/dist/chunk-GI3UFGRQ.js +1 -0
- package/dist/{chunk-IYUOYINS.js → chunk-LQ7C44JG.js} +1 -1
- package/dist/chunk-M43ET4NJ.js +1 -0
- package/dist/chunk-MXWLCDFX.js +1 -0
- package/dist/chunk-MY6EXEL2.js +1 -0
- package/dist/chunk-O5WKNZ2C.js +1 -0
- package/dist/chunk-OKWG3PVH.js +1 -0
- package/dist/chunk-POIQGEZM.js +1 -0
- package/dist/chunk-RI3E7GBN.js +1 -0
- package/dist/chunk-RNRFHUJA.js +1 -0
- package/dist/chunk-RVHAKRAI.js +1 -0
- package/dist/chunk-SMKCBUJL.js +1 -0
- package/dist/chunk-TDLIXFDT.js +1 -0
- package/dist/chunk-XLN5OT25.js +1 -0
- package/dist/chunk-YDQBVWXG.js +1 -0
- package/dist/chunk-YPG7CB4O.js +1 -0
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +13 -3
- package/dist/client/index.d.ts +13 -3
- package/dist/client/index.js +1 -1
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +1 -1
- package/dist/core/ocr/worker.bundle.js +42 -0
- package/dist/core/ocr/worker.cjs +42 -0
- package/dist/core/ocr/worker.d.cts +84 -0
- package/dist/core/ocr/worker.d.ts +84 -0
- package/dist/core/ocr/worker.js +41 -0
- package/dist/core/v3/detect-worker.cjs +1 -1
- package/dist/core/v3/detect-worker.d.cts +1 -1
- package/dist/core/v3/detect-worker.d.ts +1 -1
- package/dist/core/v3/detect-worker.js +1 -1
- package/dist/create-document-capture-T4APHJMY.js +1 -0
- package/dist/create-portrait-capture-B57GE5FU.js +1 -0
- package/dist/create-verification-C0AVIcVW.d.ts +2787 -0
- package/dist/create-verification-DoFpgzok.d.cts +2787 -0
- package/dist/{detect-worker-CKXHlXx9.d.cts → detect-worker-DclNxkZT.d.cts} +6 -0
- package/dist/{detect-worker-CKXHlXx9.d.ts → detect-worker-DclNxkZT.d.ts} +6 -0
- package/dist/detector-scanic-YCDNLCGV.js +1 -0
- package/dist/icao-gate-D8qm1O2t.d.cts +1655 -0
- package/dist/icao-gate-D8qm1O2t.d.ts +1655 -0
- package/dist/icao-gate-PWBMKRGC.js +1 -0
- package/dist/icao-tuning-4GGVX6B4.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +52 -6
- package/dist/index.d.ts +52 -6
- package/dist/index.js +1 -1
- package/dist/internal/index.cjs +1 -1
- package/dist/internal/index.d.cts +345 -308
- package/dist/internal/index.d.ts +345 -308
- package/dist/internal/index.js +1 -1
- package/dist/keyframe-rank-YAJLYJYF.js +1 -0
- package/dist/landmarker-KB7LVXAU.js +1 -0
- package/dist/mrz-band-VYHOTDHY.js +1 -0
- package/dist/plan-runner-UBK4B4CG.js +1 -0
- package/dist/segmenter-USLNBX65.js +1 -0
- package/dist/turnstile-FATNZ4AG.js +1 -0
- package/package.json +22 -4
- package/sbom.cdx.json +8 -8
- package/dist/chunk-B45J4KC3.js +0 -1
- package/dist/chunk-BMA36W2W.js +0 -1
- package/dist/chunk-P7ECRN47.js +0 -1
- package/dist/chunk-YOB4ZTYN.js +0 -1
- package/dist/chunk-YQBK6OIM.js +0 -1
- package/dist/create-verification-C2JhOO6Y.d.cts +0 -533
- package/dist/create-verification-CGazwtAh.d.ts +0 -533
- package/dist/detector-scanic-VQFFXKTL.js +0 -1
- package/dist/hosted-client-kBeq-mnJ.d.cts +0 -602
- package/dist/hosted-client-kBeq-mnJ.d.ts +0 -602
|
@@ -0,0 +1,2787 @@
|
|
|
1
|
+
import { b as Detector } from './detect-worker-DclNxkZT.js';
|
|
2
|
+
import { i as CaptureState$1, f as CaptureSessionV3Events, g as CaptureSessionV3Options, c as CaptureErrorKind$1, p as DeviceClass, b as CaptureArtifacts, H as V3GateQuality, N as V3TelemetryEvent } from './capture-session-v3-CQK-G-qX.js';
|
|
3
|
+
import { A as AcceptedDocumentType, a5 as StepSection, i as HostedBranding, c as ContactChannel, C as CheckReason, P as IdentityMethod, E as EidAfterPass, Q as IdentitySummarySource, p as HostedIcaoPolicy, V as NationalEidOption, N as IcaoReportEntry, I as IcaoCaptureTuning, w as HostedSessionView, x as HostedStepView, L as IcaoGateSummary, M as IcaoGateUpdate, a2 as SessionFailureReason, U as MethodChoiceView, z as HostedVoiceView, ao as Point, aK as PersonMask, a8 as Box, ag as IcaoFrameObservation, aL as HostedClient, aM as AttestationInput, a6 as SessionView, b as CheckType, a0 as PortraitIcaoInput, ah as IcaoGate } from './icao-gate-D8qm1O2t.js';
|
|
4
|
+
|
|
5
|
+
declare const ISSUER_CODES: readonly string[];
|
|
6
|
+
|
|
7
|
+
/** Accepted document types per issuer (the view's `acceptedDocuments`): `byIssuer[iso2] ?? default`; `"*"` = every type. */
|
|
8
|
+
interface AcceptedDocumentsLike {
|
|
9
|
+
default: "*" | readonly AcceptedDocumentType[];
|
|
10
|
+
byIssuer?: Readonly<Record<string, readonly AcceptedDocumentType[]>>;
|
|
11
|
+
}
|
|
12
|
+
/** Picker order: identity card, passport, residence permit. */
|
|
13
|
+
declare const DOCUMENT_TYPE_ORDER: readonly AcceptedDocumentType[];
|
|
14
|
+
/** Types accepted for documents of `issuer` (ISO 3166-1 alpha-2), in picker order. */
|
|
15
|
+
declare const acceptedTypesFor: (accepted: AcceptedDocumentsLike, issuer: string) => AcceptedDocumentType[];
|
|
16
|
+
/** Issuers with at least one accepted type, sorted by code (name them with `Intl.DisplayNames`). */
|
|
17
|
+
declare const acceptedIssuers: (accepted: AcceptedDocumentsLike, codes?: readonly string[]) => string[];
|
|
18
|
+
/** `acceptedDocuments`, or — from a server that predates it — MR with the legacy `acceptedDocumentTypes`. */
|
|
19
|
+
declare const resolveAcceptedDocuments: (view: {
|
|
20
|
+
acceptedDocuments?: AcceptedDocumentsLike | undefined;
|
|
21
|
+
acceptedDocumentTypes?: readonly AcceptedDocumentType[] | undefined;
|
|
22
|
+
}) => AcceptedDocumentsLike;
|
|
23
|
+
/** Flag emoji of an alpha-2 code (regional indicator symbols); `""` otherwise. */
|
|
24
|
+
declare const flagOf: (iso2: string) => string;
|
|
25
|
+
|
|
26
|
+
declare const WEB_IDENTITY_METHODS: readonly IdentityMethod[];
|
|
27
|
+
/** Plan 4's web stop for `nfc.mode = mandatory` (view-only `failureReason`) and the skip reason in `preferred`. */
|
|
28
|
+
declare const PLATFORM_UNSUPPORTED = "platform_unsupported";
|
|
29
|
+
/** A step's section: the view's value (kept even when unknown), else the check type's. */
|
|
30
|
+
declare function sectionOf(step: Pick<HostedStepView, "checkType" | "section">): StepSection | null;
|
|
31
|
+
type DocumentSide = "recto" | "verso" | "single";
|
|
32
|
+
interface DeriveStepEnv {
|
|
33
|
+
/** A phone / tablet browser; `false` routes a `requiresMobile` biometric step to `mobile_required`. */
|
|
34
|
+
isMobile?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface StepContext {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
orgName: string;
|
|
39
|
+
returning: boolean;
|
|
40
|
+
consentRecorded: boolean;
|
|
41
|
+
expiresAt: string;
|
|
42
|
+
branding?: HostedBranding;
|
|
43
|
+
/**
|
|
44
|
+
* The sections of this session in flow order (flow model 2026-10-23), one
|
|
45
|
+
* progress segment each; a section with no steps is left out.
|
|
46
|
+
*/
|
|
47
|
+
sections: StepSection[];
|
|
48
|
+
}
|
|
49
|
+
interface StepAttempts {
|
|
50
|
+
reason?: CheckReason;
|
|
51
|
+
attemptsRemaining: number;
|
|
52
|
+
attemptsExhausted: boolean;
|
|
53
|
+
}
|
|
54
|
+
type OtpNumberSource = "prefilled" | "user_entered" | "prefilled_or_entered";
|
|
55
|
+
/** Where an email_otp address comes from (same vocabulary as the phone number). */
|
|
56
|
+
type EmailSource = OtpNumberSource;
|
|
57
|
+
/** A phone OTP delivery channel the person can choose. */
|
|
58
|
+
type OtpChannel = "whatsapp" | "sms";
|
|
59
|
+
interface PhoneOtpSnapshot extends StepAttempts {
|
|
60
|
+
/**
|
|
61
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
62
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
63
|
+
* step has none (an idle processing step, the result).
|
|
64
|
+
*/
|
|
65
|
+
section: StepSection | null;
|
|
66
|
+
kind: "phone_otp";
|
|
67
|
+
key: string;
|
|
68
|
+
context: StepContext;
|
|
69
|
+
phase: "send" | "verify";
|
|
70
|
+
numberSource: OtpNumberSource;
|
|
71
|
+
needsPhone: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The channels the person chooses from, in the server's display order
|
|
74
|
+
* (WhatsApp first). `[]` for a session created before channel choice: send
|
|
75
|
+
* without a channel (SMS). With more than one, `send` needs the person's choice.
|
|
76
|
+
*/
|
|
77
|
+
channels: OtpChannel[];
|
|
78
|
+
sentToMasked?: string;
|
|
79
|
+
channel?: "sms" | "whatsapp";
|
|
80
|
+
codeExpiresAt?: string;
|
|
81
|
+
/** ISO time from which a resend is allowed (the server's cooldown, else 30 s after this page's last send). */
|
|
82
|
+
resendAvailableAt?: string;
|
|
83
|
+
/** A `one_of` session where no contact step passed yet: the person may switch to {@link switchChannel}. */
|
|
84
|
+
canSwitch: boolean;
|
|
85
|
+
/** The other contact option (with `canSwitch`). */
|
|
86
|
+
switchChannel?: ContactChannel;
|
|
87
|
+
}
|
|
88
|
+
/** Email OTP (profile v2 §4.6.5): send a code to an address, then verify it. */
|
|
89
|
+
interface EmailOtpSnapshot extends StepAttempts {
|
|
90
|
+
/**
|
|
91
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
92
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
93
|
+
* step has none (an idle processing step, the result).
|
|
94
|
+
*/
|
|
95
|
+
section: StepSection | null;
|
|
96
|
+
kind: "email_otp";
|
|
97
|
+
key: string;
|
|
98
|
+
context: StepContext;
|
|
99
|
+
phase: "send" | "verify";
|
|
100
|
+
emailSource: EmailSource;
|
|
101
|
+
/** The person types the address (every source but `prefilled`). */
|
|
102
|
+
needsEmail: boolean;
|
|
103
|
+
/** Masked address of the last send (e.g. `a•••@e•••.com`). */
|
|
104
|
+
sentToMasked?: string;
|
|
105
|
+
codeExpiresAt?: string;
|
|
106
|
+
/** ISO time from which a resend is allowed (the server's cooldown, else 30 s after this page's last send). */
|
|
107
|
+
resendAvailableAt?: string;
|
|
108
|
+
/** See {@link PhoneOtpSnapshot.canSwitch}. */
|
|
109
|
+
canSwitch: boolean;
|
|
110
|
+
switchChannel?: ContactChannel;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Step 1 contact choice (flow model 2026-10-23): a `one_of` session before the
|
|
114
|
+
* person chose phone or email.
|
|
115
|
+
*/
|
|
116
|
+
interface ContactChoiceSnapshot {
|
|
117
|
+
/**
|
|
118
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
119
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
120
|
+
* step has none (an idle processing step, the result).
|
|
121
|
+
*/
|
|
122
|
+
section: StepSection | null;
|
|
123
|
+
kind: "contact_choice";
|
|
124
|
+
key: string;
|
|
125
|
+
context: StepContext;
|
|
126
|
+
options: ContactChannel[];
|
|
127
|
+
selected: ContactChannel | null;
|
|
128
|
+
locked: boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Step 2b summary and confirm (flow model 2026-10-23): the identity data read so
|
|
132
|
+
* far, to confirm before the live photo. Masks keep the last 4 characters. PII:
|
|
133
|
+
* never log these values or send them to analytics.
|
|
134
|
+
*/
|
|
135
|
+
interface IdentitySummarySnapshot {
|
|
136
|
+
/**
|
|
137
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
138
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
139
|
+
* step has none (an idle processing step, the result).
|
|
140
|
+
*/
|
|
141
|
+
section: StepSection | null;
|
|
142
|
+
kind: "identity_summary";
|
|
143
|
+
key: string;
|
|
144
|
+
context: StepContext;
|
|
145
|
+
/** `full` (document / chip, with the photo) or `partial` (registry from a typed NNI: short name, birth year, no photo). */
|
|
146
|
+
summaryKind: "full" | "partial" | (string & {});
|
|
147
|
+
source: IdentitySummarySource | (string & {});
|
|
148
|
+
/** The full name, or the short form "Mohamed S." on a `partial` summary. */
|
|
149
|
+
fullName?: string;
|
|
150
|
+
birthDate?: string;
|
|
151
|
+
/** `partial` summaries carry the birth year only. */
|
|
152
|
+
birthYear?: string;
|
|
153
|
+
nationality?: string;
|
|
154
|
+
documentType?: string;
|
|
155
|
+
documentNumberMasked?: string;
|
|
156
|
+
nniMasked?: string;
|
|
157
|
+
expiryDate?: string;
|
|
158
|
+
/** The portrait can be fetched (`portrait()` on the handle). */
|
|
159
|
+
portraitAvailable: boolean;
|
|
160
|
+
retakeAvailable: boolean;
|
|
161
|
+
retakeTarget?: "document" | "national_number" | (string & {});
|
|
162
|
+
}
|
|
163
|
+
interface MethodSnapshot {
|
|
164
|
+
/**
|
|
165
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
166
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
167
|
+
* step has none (an idle processing step, the result).
|
|
168
|
+
*/
|
|
169
|
+
section: StepSection | null;
|
|
170
|
+
kind: "method";
|
|
171
|
+
key: string;
|
|
172
|
+
context: StepContext;
|
|
173
|
+
options: IdentityMethod[];
|
|
174
|
+
/** The provider behind `national_eid` ("Verify with Houwiyeti"); absent when it is not offered. */
|
|
175
|
+
nationalEid?: NationalEidOption;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The national eID sign-in (SDK spec §3.2; Houwiyeti in MR). Two phases:
|
|
179
|
+
* - `identifier` — the person types their national number (the NNI in MR)
|
|
180
|
+
* unless `identifierBound`, then the SDK asks the provider for a code;
|
|
181
|
+
* - `code` — the person opens the provider's app and types the code shown.
|
|
182
|
+
* A passed eID ends the session (no document, liveness or face match). With
|
|
183
|
+
* `canUseCamera` (`eid_not_enrolled`, `eid_unavailable`, `eid_locked` on a
|
|
184
|
+
* session that offers the camera path) the person may switch to `cameraMethod`.
|
|
185
|
+
*/
|
|
186
|
+
interface EidSnapshot extends StepAttempts {
|
|
187
|
+
/**
|
|
188
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
189
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
190
|
+
* step has none (an idle processing step, the result).
|
|
191
|
+
*/
|
|
192
|
+
section: StepSection | null;
|
|
193
|
+
kind: "eid";
|
|
194
|
+
key: string;
|
|
195
|
+
context: StepContext;
|
|
196
|
+
/** `{ISO2}.{provider}`, e.g. `MR.houwiyeti`. */
|
|
197
|
+
provider: string;
|
|
198
|
+
/** Display name, e.g. "Houwiyeti" (for "Verify with Houwiyeti"). */
|
|
199
|
+
providerName: string;
|
|
200
|
+
country: string;
|
|
201
|
+
challenge: "app_code";
|
|
202
|
+
phase: "identifier" | "code";
|
|
203
|
+
identifierBound: boolean;
|
|
204
|
+
codeExpiresAt?: string;
|
|
205
|
+
resendAvailableAt?: string;
|
|
206
|
+
resendsRemaining?: number;
|
|
207
|
+
codeAttemptsRemaining?: number;
|
|
208
|
+
canUseCamera: boolean;
|
|
209
|
+
/** The camera-path method `useCamera()` selects (present with `canUseCamera`). */
|
|
210
|
+
cameraMethod?: IdentityMethod;
|
|
211
|
+
/** What follows a pass (flow model 2026-10-23): `continue` = a live photo follows ("then a quick selfie"). */
|
|
212
|
+
afterPass: EidAfterPass;
|
|
213
|
+
}
|
|
214
|
+
type RegistrySource = "bound" | "document" | "typed";
|
|
215
|
+
interface RegistrySnapshot extends StepAttempts {
|
|
216
|
+
/**
|
|
217
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
218
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
219
|
+
* step has none (an idle processing step, the result).
|
|
220
|
+
*/
|
|
221
|
+
section: StepSection | null;
|
|
222
|
+
kind: "registry";
|
|
223
|
+
key: string;
|
|
224
|
+
context: StepContext;
|
|
225
|
+
state: "pending" | "input_required";
|
|
226
|
+
source: RegistrySource;
|
|
227
|
+
recheck: boolean;
|
|
228
|
+
canUseDocument: boolean;
|
|
229
|
+
}
|
|
230
|
+
interface DocumentSnapshot extends StepAttempts {
|
|
231
|
+
/**
|
|
232
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
233
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
234
|
+
* step has none (an idle processing step, the result).
|
|
235
|
+
*/
|
|
236
|
+
section: StepSection | null;
|
|
237
|
+
kind: "document";
|
|
238
|
+
key: string;
|
|
239
|
+
context: StepContext;
|
|
240
|
+
side: "recto" | "verso";
|
|
241
|
+
acceptedSides: ("recto" | "verso")[];
|
|
242
|
+
documents: AcceptedDocumentsLike;
|
|
243
|
+
}
|
|
244
|
+
interface LivenessSnapshot extends StepAttempts {
|
|
245
|
+
/**
|
|
246
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
247
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
248
|
+
* step has none (an idle processing step, the result).
|
|
249
|
+
*/
|
|
250
|
+
section: StepSection | null;
|
|
251
|
+
kind: "liveness";
|
|
252
|
+
key: string;
|
|
253
|
+
context: StepContext;
|
|
254
|
+
mode: "liveness" | "selfie";
|
|
255
|
+
requiresMobile: boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Step 3 instructions (flow model 2026-10-23): `icao` on a session whose
|
|
258
|
+
* selfie is ICAO-checked (`icao` below) — the ICAO tips variant.
|
|
259
|
+
*/
|
|
260
|
+
instructions: "standard" | "icao";
|
|
261
|
+
/** The session's selfie ICAO policy (`icaoPolicyOf`), `null` when ICAO is off (absent on older SDKs). */
|
|
262
|
+
icao?: HostedIcaoPolicy | null;
|
|
263
|
+
/**
|
|
264
|
+
* Step 3 one capture: the capture also takes a full-resolution still. `icao`
|
|
265
|
+
* = the ICAO portrait is due from this capture (`portrait_icao` pending): the
|
|
266
|
+
* gate also requires the ICAO conditions and the still rides the liveness
|
|
267
|
+
* submission as `input.portraitIcao`. `null` on an older API.
|
|
268
|
+
*/
|
|
269
|
+
livePhoto: LivePhotoPlan | null;
|
|
270
|
+
}
|
|
271
|
+
/** How one live-photo capture is taken (flow model 2026-10-23, Step 3). */
|
|
272
|
+
interface LivePhotoPlan {
|
|
273
|
+
icao: boolean;
|
|
274
|
+
still: {
|
|
275
|
+
minLongSidePx: number;
|
|
276
|
+
jpegQuality: number;
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The ICAO portrait step of an ID-document session (SDK spec §4.9, §5.4):
|
|
281
|
+
* - `capture` — `portrait_icao` is the frontier: run the strict gate, take the
|
|
282
|
+
* portrait (a retry carries the server's `report` to coach from);
|
|
283
|
+
* - `match` — the portrait was accepted (`passed`, or `review` after the
|
|
284
|
+
* attempt cap: `review: true`, "We'll review your photo") and `face_match`
|
|
285
|
+
* is due: the SDK submits it with the portrait as the probe — no new capture.
|
|
286
|
+
*/
|
|
287
|
+
interface PortraitSnapshot extends StepAttempts {
|
|
288
|
+
/**
|
|
289
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
290
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
291
|
+
* step has none (an idle processing step, the result).
|
|
292
|
+
*/
|
|
293
|
+
section: StepSection | null;
|
|
294
|
+
kind: "portrait";
|
|
295
|
+
key: string;
|
|
296
|
+
context: StepContext;
|
|
297
|
+
phase: "capture" | "match";
|
|
298
|
+
/** The server's per-criterion report of the last attempt (`null` before one). */
|
|
299
|
+
report: IcaoReportEntry[] | null;
|
|
300
|
+
/** The portrait goes to a reviewer (attempt cap reached in decision mode `review`). */
|
|
301
|
+
review: boolean;
|
|
302
|
+
/** `capture.icao` (coaching limits) and `capture.tuningVersion` of the view. */
|
|
303
|
+
tuning: IcaoCaptureTuning | null;
|
|
304
|
+
tuningVersion: string | null;
|
|
305
|
+
/** The session's ICAO policy and profile (`icaoPolicyOf`; absent on older SDKs). */
|
|
306
|
+
icao?: HostedIcaoPolicy | null;
|
|
307
|
+
/**
|
|
308
|
+
* Step 3 (flow model 2026-10-23): the live check of this session passed and
|
|
309
|
+
* only the ICAO portrait needs another still — a portrait-only retake, no new clip.
|
|
310
|
+
*/
|
|
311
|
+
retakeOnly: boolean;
|
|
312
|
+
}
|
|
313
|
+
interface MobileRequiredSnapshot {
|
|
314
|
+
/**
|
|
315
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
316
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
317
|
+
* step has none (an idle processing step, the result).
|
|
318
|
+
*/
|
|
319
|
+
section: StepSection | null;
|
|
320
|
+
kind: "mobile_required";
|
|
321
|
+
key: string;
|
|
322
|
+
context: StepContext;
|
|
323
|
+
checkType: "liveness" | "face_match";
|
|
324
|
+
}
|
|
325
|
+
interface ProcessingSnapshot {
|
|
326
|
+
/**
|
|
327
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
328
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
329
|
+
* step has none (an idle processing step, the result).
|
|
330
|
+
*/
|
|
331
|
+
section: StepSection | null;
|
|
332
|
+
kind: "processing";
|
|
333
|
+
key: string;
|
|
334
|
+
context: StepContext;
|
|
335
|
+
stalled: boolean;
|
|
336
|
+
}
|
|
337
|
+
type UnsupportedReason = "platform_unsupported" | "update_required";
|
|
338
|
+
interface UnsupportedSnapshot {
|
|
339
|
+
/**
|
|
340
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
341
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
342
|
+
* step has none (an idle processing step, the result).
|
|
343
|
+
*/
|
|
344
|
+
section: StepSection | null;
|
|
345
|
+
kind: "unsupported";
|
|
346
|
+
key: string;
|
|
347
|
+
context: StepContext;
|
|
348
|
+
checkType: string;
|
|
349
|
+
reason: UnsupportedReason;
|
|
350
|
+
}
|
|
351
|
+
type ResultDisplay = "approved" | "review" | "rejected" | "expired" | "policy_stop" | "cooldown" | "platform_unsupported" | "attempts_exhausted";
|
|
352
|
+
interface ResultSnapshot {
|
|
353
|
+
/**
|
|
354
|
+
* Flow model 2026-10-23: the section of the flow (`contact`, `identity`,
|
|
355
|
+
* `live_photo`; an unknown value from a newer server is kept), `null` when the
|
|
356
|
+
* step has none (an idle processing step, the result).
|
|
357
|
+
*/
|
|
358
|
+
section: StepSection | null;
|
|
359
|
+
kind: "result";
|
|
360
|
+
key: string;
|
|
361
|
+
context: StepContext;
|
|
362
|
+
display: ResultDisplay;
|
|
363
|
+
result: VerificationResult;
|
|
364
|
+
failureReason?: string;
|
|
365
|
+
retryAfter?: string;
|
|
366
|
+
}
|
|
367
|
+
type StepSnapshot = PhoneOtpSnapshot | EmailOtpSnapshot | ContactChoiceSnapshot | IdentitySummarySnapshot | MethodSnapshot | EidSnapshot | RegistrySnapshot | DocumentSnapshot | LivenessSnapshot | PortraitSnapshot | MobileRequiredSnapshot | ProcessingSnapshot | UnsupportedSnapshot | ResultSnapshot;
|
|
368
|
+
/**
|
|
369
|
+
* ID mode: `face_match` is due right after an ACCEPTED portrait (passed, or
|
|
370
|
+
* review after the cap) that it has not been tried with yet — the portrait is
|
|
371
|
+
* the probe (the server picks it), so no selfie is taken. A face_match that
|
|
372
|
+
* already carries a reason was tried: the regular selfie path applies.
|
|
373
|
+
*/
|
|
374
|
+
declare function portraitMatchStep(view: HostedSessionView, frontier: HostedStepView): HostedStepView | null;
|
|
375
|
+
/** The step the view asks for right now. */
|
|
376
|
+
declare function deriveStep(view: HostedSessionView, env?: DeriveStepEnv): StepSnapshot;
|
|
377
|
+
|
|
378
|
+
type DocumentCaptureState = "idle" | "starting" | "scanning" | "captured" | "uploading" | "checking" | "done" | "error";
|
|
379
|
+
/** Aggregated coaching (copy keys `dcv2.<key>`). */
|
|
380
|
+
type DocumentFeedbackKey = "sensing" | "too_far" | "too_close" | "edge_clipped" | "wrong_shape" | "tilted" | "too_dark" | "too_bright" | "blurry" | "glare" | "hold_still" | "processing" | "side_captured" | "captured" | "focus_far";
|
|
381
|
+
type DocumentReticle = "searching" | "coaching" | "locking" | "processing" | "flip" | "done";
|
|
382
|
+
interface DocumentFeedback {
|
|
383
|
+
key: DocumentFeedbackKey;
|
|
384
|
+
reticle: DocumentReticle;
|
|
385
|
+
}
|
|
386
|
+
interface DocumentPoint {
|
|
387
|
+
x: number;
|
|
388
|
+
y: number;
|
|
389
|
+
}
|
|
390
|
+
interface DocumentQuad {
|
|
391
|
+
topLeft: DocumentPoint;
|
|
392
|
+
topRight: DocumentPoint;
|
|
393
|
+
bottomRight: DocumentPoint;
|
|
394
|
+
bottomLeft: DocumentPoint;
|
|
395
|
+
}
|
|
396
|
+
/** The card the guide-region lock measures inside the on-screen guide (ID-1 cards; absent for passport pages). */
|
|
397
|
+
interface DocumentGuideMeasure {
|
|
398
|
+
/** A card is measured in the guide on this tick. */
|
|
399
|
+
detected: boolean;
|
|
400
|
+
/** Card width / guide width (1 = fitted). */
|
|
401
|
+
fill: number;
|
|
402
|
+
/** Card centre offset in guide widths / heights (+ = right / down). */
|
|
403
|
+
offsetX: number;
|
|
404
|
+
offsetY: number;
|
|
405
|
+
}
|
|
406
|
+
/** Gate ring + overlay; quad coordinates live in `frameWidth × frameHeight` (the detect frame). */
|
|
407
|
+
interface DocumentGateUpdate {
|
|
408
|
+
progress: number;
|
|
409
|
+
quad: DocumentQuad | null;
|
|
410
|
+
frameWidth: number;
|
|
411
|
+
frameHeight: number;
|
|
412
|
+
/** The card measured in the guide (lets a UI frame the card it sees); absent when the guide lock is off. */
|
|
413
|
+
guide?: DocumentGuideMeasure;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* The on-screen guide the card is framed in: `rect` normalized (0..1) in the video element of aspect `viewAspect`
|
|
417
|
+
* (width / height) that shows the camera with `object-fit: cover`.
|
|
418
|
+
*/
|
|
419
|
+
interface DocumentGuide {
|
|
420
|
+
viewAspect: number;
|
|
421
|
+
rect: {
|
|
422
|
+
x: number;
|
|
423
|
+
y: number;
|
|
424
|
+
width: number;
|
|
425
|
+
height: number;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
type DocumentScanPhase = "framing" | "locking" | "capturing" | "flipCue" | "reaskRecto" | "reading" | "result";
|
|
429
|
+
interface DocumentScanUpdate {
|
|
430
|
+
phase: DocumentScanPhase;
|
|
431
|
+
captured: DocumentSide[];
|
|
432
|
+
expected: DocumentSide | null;
|
|
433
|
+
/** Coaching left by the last capture or read (`blurry`: the blur gate refused the photo, retake at once). */
|
|
434
|
+
notice: "same_face" | "reread" | "technical" | "blurry" | null;
|
|
435
|
+
lastShot: DocumentSide | null;
|
|
436
|
+
manualShutter: boolean;
|
|
437
|
+
torchAvailable: boolean;
|
|
438
|
+
torch: boolean;
|
|
439
|
+
}
|
|
440
|
+
interface CapturedDocument {
|
|
441
|
+
/** Upload-ready JPEG (adaptive encode applied). Live camera only — there is no file input. */
|
|
442
|
+
image: Blob;
|
|
443
|
+
/** The face this photo shows (classified on device for cards; the server confirms). */
|
|
444
|
+
side: DocumentSide;
|
|
445
|
+
documentType: AcceptedDocumentType;
|
|
446
|
+
issuer?: string;
|
|
447
|
+
width: number;
|
|
448
|
+
height: number;
|
|
449
|
+
previewDataUrl: string;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Web live MRZ (spec 2026-10-21 D5): the on-device reader's state on a face with an MRZ (passport page, card back).
|
|
453
|
+
* Never carries text or a digest. `mismatch`: a valid read whose format contradicts the picked document (the photo is
|
|
454
|
+
* still taken; the server decides). `fallback`: the reader stepped aside (today's capture continues).
|
|
455
|
+
*/
|
|
456
|
+
interface DocumentMrzUpdate {
|
|
457
|
+
state: "searching" | "reading" | "valid" | "mismatch" | "fallback";
|
|
458
|
+
format?: "TD1" | "TD2" | "TD3";
|
|
459
|
+
coach?: "moveCloser" | "turnSideways" | "glare" | "holdStill";
|
|
460
|
+
reason?: "timeout" | "slow" | "unsupported" | "download" | "integrity" | "error" | "off";
|
|
461
|
+
}
|
|
462
|
+
/** Web live MRZ options (spec D7/D9): `mode: "off"` opts out; `auto` (default) obeys the session's server tuning. */
|
|
463
|
+
interface DocumentLiveMrzOptions {
|
|
464
|
+
mode?: "auto" | "off";
|
|
465
|
+
/** Where the OCR assets are served (default the Sekyro AI assets CDN). Self-host: the `<fp12>/` directory goes under it. */
|
|
466
|
+
assetBaseUrl?: string;
|
|
467
|
+
/** Where your bundler serves `@sekyro/web/core/ocr/worker` (or the self-contained `worker.bundle.js`). Without it the reader stays off. */
|
|
468
|
+
workerUrl?: string | URL;
|
|
469
|
+
}
|
|
470
|
+
interface DocumentCaptureEvents {
|
|
471
|
+
state: DocumentCaptureState;
|
|
472
|
+
/** Web live MRZ reader state (MRZ faces only; text-free). */
|
|
473
|
+
mrz: DocumentMrzUpdate;
|
|
474
|
+
feedback: DocumentFeedback;
|
|
475
|
+
gate: DocumentGateUpdate;
|
|
476
|
+
scan: DocumentScanUpdate;
|
|
477
|
+
captured: CapturedDocument;
|
|
478
|
+
error: CaptureError;
|
|
479
|
+
}
|
|
480
|
+
interface DocumentCaptureOptions {
|
|
481
|
+
/** Host-owned `<video playsInline muted>`; the rear camera is opened. */
|
|
482
|
+
video: HTMLVideoElement;
|
|
483
|
+
documentType: AcceptedDocumentType;
|
|
484
|
+
/** Expected face; omit for a card's first face (either face). Passports are always `"single"`. */
|
|
485
|
+
side?: DocumentSide;
|
|
486
|
+
/** Display/telemetry only; capture is identical for every issuer. */
|
|
487
|
+
issuer?: string;
|
|
488
|
+
/** Default `true` (best-effort Screen Wake Lock while the camera is live). */
|
|
489
|
+
keepScreenAwake?: boolean;
|
|
490
|
+
/** `navigator.vibrate` on capture where available. Default `true`. */
|
|
491
|
+
haptics?: boolean;
|
|
492
|
+
/** Web live MRZ (on-device MRZ read that decides the shot on MRZ faces); server-controlled, additive. */
|
|
493
|
+
liveMrz?: DocumentLiveMrzOptions;
|
|
494
|
+
}
|
|
495
|
+
interface DocumentCaptureHandle {
|
|
496
|
+
readonly state: DocumentCaptureState;
|
|
497
|
+
on<K extends keyof DocumentCaptureEvents>(event: K, handler: (payload: DocumentCaptureEvents[K]) => void): () => void;
|
|
498
|
+
start(): Promise<void>;
|
|
499
|
+
/** Start over (both faces), or restart the camera after an error. */
|
|
500
|
+
retry(): void;
|
|
501
|
+
/** Ask for this face next (after the server accepted the other one). */
|
|
502
|
+
setSide(side: DocumentSide): void;
|
|
503
|
+
/** Take the photo now (meaningful while `scan.manualShutter`). */
|
|
504
|
+
shutter(): void;
|
|
505
|
+
/** Manual torch; no-op where the camera has none. Never turned on by the SDK. */
|
|
506
|
+
setTorch(on: boolean): void;
|
|
507
|
+
/**
|
|
508
|
+
* Tap-to-focus at a normalized point of the video frame (0..1, e.g. from a tap
|
|
509
|
+
* on the preview). No-op where the camera has no single-shot focus (iOS Safari).
|
|
510
|
+
*/
|
|
511
|
+
focusAt(x: number, y: number): void;
|
|
512
|
+
/**
|
|
513
|
+
* Where the on-screen guide is (call again when the view resizes); `undefined` = the default guide, centred and
|
|
514
|
+
* 86 % of the frame wide. The lock measures the card inside it, so a guide drawn elsewhere must be declared.
|
|
515
|
+
*/
|
|
516
|
+
setGuide(guide: DocumentGuide | undefined): void;
|
|
517
|
+
destroy(): void;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* - `starting` — camera and landmark model loading;
|
|
522
|
+
* - `gating` — the strict ICAO gate is coaching (`gate` events);
|
|
523
|
+
* - `captured` — the portrait frame was taken (`captured` fired);
|
|
524
|
+
* - `uploading` / `checking` / `done` — driven by the verification when the
|
|
525
|
+
* handle belongs to a plan-runner step;
|
|
526
|
+
* - `error` — camera or model failure (`error` fired).
|
|
527
|
+
*/
|
|
528
|
+
type PortraitCaptureState = "idle" | "starting" | "gating" | "captured" | "uploading" | "checking" | "done" | "error";
|
|
529
|
+
/** One gate evaluation: per-criterion state, the one coaching message, readiness. */
|
|
530
|
+
type PortraitGateUpdate = IcaoGateUpdate;
|
|
531
|
+
interface CapturedPortrait {
|
|
532
|
+
/**
|
|
533
|
+
* Upload-ready JPEG: a 3:4 portrait region of the full-resolution frame
|
|
534
|
+
* around the face (not mirrored), at least 480×640. The server cuts the
|
|
535
|
+
* normalised ICAO crop from it.
|
|
536
|
+
*/
|
|
537
|
+
image: Blob;
|
|
538
|
+
width: number;
|
|
539
|
+
height: number;
|
|
540
|
+
/** The on-device gate result and the eye-openness baseline (`gate` of the submission). */
|
|
541
|
+
gate: IcaoGateSummary;
|
|
542
|
+
/** `capture.tuningVersion` the gate ran with, when the server sent one. */
|
|
543
|
+
tuningVersion?: string;
|
|
544
|
+
}
|
|
545
|
+
interface PortraitCaptureEvents {
|
|
546
|
+
state: PortraitCaptureState;
|
|
547
|
+
gate: PortraitGateUpdate;
|
|
548
|
+
captured: CapturedPortrait;
|
|
549
|
+
error: CaptureError;
|
|
550
|
+
}
|
|
551
|
+
interface PortraitCaptureOptions {
|
|
552
|
+
/** Host-owned `<video playsInline muted>`; the front camera is opened. Mirror it with CSS. */
|
|
553
|
+
video: HTMLVideoElement;
|
|
554
|
+
/** `HostedSessionView.capture.icao`; missing fields use the bundled fallbacks. */
|
|
555
|
+
tuning?: IcaoCaptureTuning | null;
|
|
556
|
+
/** `HostedSessionView.capture.tuningVersion`, echoed on submit. */
|
|
557
|
+
tuningVersion?: string | null;
|
|
558
|
+
/**
|
|
559
|
+
* The session's ICAO policy (`icaoPolicyOf(view).policy`). `alert` coaches but
|
|
560
|
+
* never blocks: the photo is taken `capture.icao.alertGraceMs` after the face is
|
|
561
|
+
* accepted at the latest. Default `reject`.
|
|
562
|
+
*/
|
|
563
|
+
policy?: "reject" | "alert";
|
|
564
|
+
/** Self-hosted MediaPipe assets (wasm fileset + `face_landmarker.task`); default `/models-v3`. */
|
|
565
|
+
assetBaseUrl?: string;
|
|
566
|
+
/** Default `true` (best-effort Screen Wake Lock while the camera is live). */
|
|
567
|
+
keepScreenAwake?: boolean;
|
|
568
|
+
}
|
|
569
|
+
interface PortraitCaptureHandle {
|
|
570
|
+
readonly state: PortraitCaptureState;
|
|
571
|
+
on<K extends keyof PortraitCaptureEvents>(event: K, handler: (payload: PortraitCaptureEvents[K]) => void): () => void;
|
|
572
|
+
/** Open the camera, load the model and start the gate. */
|
|
573
|
+
start(): Promise<void>;
|
|
574
|
+
/** Take the photo again (gate only — the camera stays open), or restart after an error. */
|
|
575
|
+
retry(): void;
|
|
576
|
+
destroy(): void;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Gate guidance code. `'hold'` is emitted while the clip records. */
|
|
580
|
+
type GateHint = "center" | "closer" | "farther" | "light" | "still" | "hold";
|
|
581
|
+
/**
|
|
582
|
+
* Capture + verification state, as seen by the partner:
|
|
583
|
+
* - `idle → starting → gating → recording → captured` is the engine's run;
|
|
584
|
+
* - `uploading → checking → done` follows ONLY when the handle was obtained via
|
|
585
|
+
* {@link Verification.liveness} (the SDK uploads and submits after `captured`);
|
|
586
|
+
* - `error` on a camera / engine failure (see {@link CaptureError}).
|
|
587
|
+
*/
|
|
588
|
+
type CaptureState = "idle" | "starting" | "gating" | "recording" | "captured" | "uploading" | "checking" | "done" | "error";
|
|
589
|
+
/** Declared capture channel — the liveness band is mobile-calibrated. */
|
|
590
|
+
type CaptureChannel = "mobile" | "desktop";
|
|
591
|
+
/** Gate ring update: integrator progress 0..1 and the current guidance code. */
|
|
592
|
+
interface GateUpdate {
|
|
593
|
+
/** Gate integrator 0..1 — drive the ring off this directly. */
|
|
594
|
+
progress: number;
|
|
595
|
+
/** Current guidance code, `null` when no hint applies. */
|
|
596
|
+
hint: GateHint | null;
|
|
597
|
+
/**
|
|
598
|
+
* The detector currently sees a face (a non-zero engine presence factor). `false`
|
|
599
|
+
* with `hint: 'center'` means "no face in frame" rather than "off-center" —
|
|
600
|
+
* hosts use it to tell seeking from aligning, and for the no-face copy.
|
|
601
|
+
*/
|
|
602
|
+
faceDetected: boolean;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* What the engine captured. Fires BEFORE any upload. `clip` is absent only in
|
|
606
|
+
* the `degraded: 'NO_RECORDER'` case (no MediaRecorder on this browser).
|
|
607
|
+
*/
|
|
608
|
+
interface CapturedArtifacts {
|
|
609
|
+
/** Mirrored JPEG keyframe (the face_match probe). */
|
|
610
|
+
keyframe: Blob;
|
|
611
|
+
/** The 2.5 s liveness clip (MP4-first mime ladder). */
|
|
612
|
+
clip?: Blob;
|
|
613
|
+
/** Container mime the clip finalized with — may carry codec params. */
|
|
614
|
+
clipMime?: string;
|
|
615
|
+
/** Intrinsic video width at capture time. */
|
|
616
|
+
width: number;
|
|
617
|
+
/** Intrinsic video height at capture time. */
|
|
618
|
+
height: number;
|
|
619
|
+
/** Recording-start → recording-stop, when known. */
|
|
620
|
+
clipMs?: number;
|
|
621
|
+
/** `'mobile'` on phones/tablets (the calibrated channel), else `'desktop'`. */
|
|
622
|
+
captureChannel: CaptureChannel;
|
|
623
|
+
/** Set when the browser has no MediaRecorder: a keyframe-only capture. */
|
|
624
|
+
degraded?: "NO_RECORDER";
|
|
625
|
+
/** The capture engine that produced the artifacts (`'v3'` for this SDK). Always set by the SDK; optional in the type so host-built fixtures stay valid. */
|
|
626
|
+
engine?: CaptureEngineId;
|
|
627
|
+
/**
|
|
628
|
+
* Camera request (`getUserMedia`) → the preview is live, in whole
|
|
629
|
+
* milliseconds measured with `performance.now()`. Part of the face-capture
|
|
630
|
+
* timing the SDK forwards to the backend as `input.capture` on the liveness
|
|
631
|
+
* submission; absent when the phase was not observed.
|
|
632
|
+
*/
|
|
633
|
+
cameraReadyMs?: number;
|
|
634
|
+
/**
|
|
635
|
+
* Camera ready → the first face ACCEPTED by the gate (present and with
|
|
636
|
+
* size, centering and quality good enough to make progress — not merely
|
|
637
|
+
* detected), whole ms. Absent when no face was ever accepted.
|
|
638
|
+
*/
|
|
639
|
+
faceAcquireMs?: number;
|
|
640
|
+
/**
|
|
641
|
+
* Face-capture duration: FIRST accepted face → gate progress 100 % (capture
|
|
642
|
+
* complete, before the clip window and any upload), whole ms. A face lost
|
|
643
|
+
* and re-acquired keeps the first acceptance as its start.
|
|
644
|
+
*/
|
|
645
|
+
captureMs?: number;
|
|
646
|
+
}
|
|
647
|
+
/** Capture engine identifier carried on {@link CapturedArtifacts.engine}. */
|
|
648
|
+
type CaptureEngineId = "v1" | "v3";
|
|
649
|
+
/** Camera / engine failure kinds — collapsed to the public six. */
|
|
650
|
+
type CaptureErrorKind = "permission" | "notfound" | "inuse" | "insecure" | "unsupported" | "generic";
|
|
651
|
+
/** Camera / engine failure payload (`on('error')`). */
|
|
652
|
+
interface CaptureError {
|
|
653
|
+
kind: CaptureErrorKind;
|
|
654
|
+
/**
|
|
655
|
+
* A fresh attempt (`retry()`) can succeed — false for `insecure` (needs
|
|
656
|
+
* HTTPS) and `unsupported` (the browser cannot capture at all).
|
|
657
|
+
*/
|
|
658
|
+
retryable: boolean;
|
|
659
|
+
}
|
|
660
|
+
/** Event map of a {@link LivenessCaptureHandle}. */
|
|
661
|
+
interface LivenessCaptureEvents {
|
|
662
|
+
/** Every state transition. */
|
|
663
|
+
state: CaptureState;
|
|
664
|
+
/** Gate ring updates while gating/recording. */
|
|
665
|
+
gate: GateUpdate;
|
|
666
|
+
/** The capture result — fires BEFORE any upload. */
|
|
667
|
+
captured: CapturedArtifacts;
|
|
668
|
+
/** Camera / engine failure. */
|
|
669
|
+
error: CaptureError;
|
|
670
|
+
/**
|
|
671
|
+
* Clip mode only: the recording window just ended and the keyframe is already
|
|
672
|
+
* available, BEFORE the clip finalizes (iOS can lag ~1.5 s). Hosts freeze the
|
|
673
|
+
* display on `previewDataUrl` (a mirrored JPEG data URL) instead of leaving
|
|
674
|
+
* the live feed running; `captured` follows once the clip lands.
|
|
675
|
+
*/
|
|
676
|
+
frozen: {
|
|
677
|
+
previewDataUrl: string;
|
|
678
|
+
};
|
|
679
|
+
/**
|
|
680
|
+
* Device-profile hint, emitted when it changes: `'reduced'` asks the host to
|
|
681
|
+
* drop decorative animations (a LOW device class needs the frames for
|
|
682
|
+
* detection). Chrome only — never gate input.
|
|
683
|
+
*/
|
|
684
|
+
profile: {
|
|
685
|
+
animations: "full" | "reduced";
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/** Which camera to open: the selfie camera (`'user'`) or the rear one (`'environment'`). */
|
|
689
|
+
type CameraFacing = "user" | "environment";
|
|
690
|
+
/** Options of {@link LivenessCaptureHandle.start}. */
|
|
691
|
+
interface LivenessStartOptions {
|
|
692
|
+
/**
|
|
693
|
+
* Camera for this attempt and its retries; default: the current one
|
|
694
|
+
* ({@link LivenessCaptureOptions.facing}, else `'user'`). Use `switchCamera()`
|
|
695
|
+
* to change camera mid-capture.
|
|
696
|
+
*/
|
|
697
|
+
facing?: CameraFacing;
|
|
698
|
+
}
|
|
699
|
+
/** Live capture handle returned by {@link createLivenessCapture} / {@link Verification.liveness}. */
|
|
700
|
+
interface LivenessCaptureHandle {
|
|
701
|
+
/** Current {@link CaptureState}. */
|
|
702
|
+
readonly state: CaptureState;
|
|
703
|
+
/** Subscribe; returns the unsubscribe function. */
|
|
704
|
+
on<K extends keyof LivenessCaptureEvents>(event: K, handler: (payload: LivenessCaptureEvents[K]) => void): () => void;
|
|
705
|
+
/**
|
|
706
|
+
* Ask for the camera and start gating. Resolves once the attempt is running or failed.
|
|
707
|
+
* `options.facing` picks the camera for this attempt and later retries.
|
|
708
|
+
*/
|
|
709
|
+
start(options?: LivenessStartOptions): Promise<void>;
|
|
710
|
+
/** Fresh attempt: resets the engine and restarts the camera. */
|
|
711
|
+
retry(): void;
|
|
712
|
+
/** Toggle user/environment camera (gate restarts from zero). */
|
|
713
|
+
switchCamera(): void;
|
|
714
|
+
/** Stop the camera, release the detector, drop all listeners. */
|
|
715
|
+
destroy(): void;
|
|
716
|
+
}
|
|
717
|
+
/** Options of {@link createLivenessCapture} / {@link Verification.liveness}. */
|
|
718
|
+
interface LivenessCaptureOptions {
|
|
719
|
+
/** Host-owned `<video playsInline muted>` element. */
|
|
720
|
+
video: HTMLVideoElement;
|
|
721
|
+
/** Initial camera; default `'user'`. `start({ facing })` overrides it. */
|
|
722
|
+
facing?: CameraFacing;
|
|
723
|
+
/** Base URL of the detector assets (tasks-vision wasm + tflite); default `/models-v3`. */
|
|
724
|
+
assetBaseUrl?: string;
|
|
725
|
+
/**
|
|
726
|
+
* Hold a Screen Wake Lock while the camera is live (`starting` / `gating` /
|
|
727
|
+
* `recording`) so the phone does not dim or lock mid-capture; re-taken when
|
|
728
|
+
* the page becomes visible again. Best-effort: unsupported browsers or a
|
|
729
|
+
* refused request (e.g. an iframe without `allow="screen-wake-lock"`) never
|
|
730
|
+
* affect the capture. Default `true`; `false` never requests the lock.
|
|
731
|
+
*/
|
|
732
|
+
keepScreenAwake?: boolean;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* SDK-side failure reasons (status `'unavailable'`):
|
|
736
|
+
* - `no_recorder` — no MediaRecorder / unsupported clip container;
|
|
737
|
+
* - `attestation` — Turnstile failed client-side or `attestation_invalid` after one retry;
|
|
738
|
+
* - `network` — transport / API failure after the bounded replays;
|
|
739
|
+
* - `nni_unbound` — `population_registry` is the frontier but the session was
|
|
740
|
+
* minted WITHOUT a server-bound NNI (`POST /sessions` `input.nni`), so the
|
|
741
|
+
* SDK cannot submit it. Mint the session with the NNI (or complete the
|
|
742
|
+
* population step yourself) before starting the capture;
|
|
743
|
+
* - `method_required` — the session waits on the identity-method choice
|
|
744
|
+
* (profile v2 step 3; `on('method')` fired). Ask the person, call
|
|
745
|
+
* {@link Verification.selectMethod}, then capture again;
|
|
746
|
+
* - `inference_unavailable` — the model service stayed busy through the
|
|
747
|
+
* automatic retries (profile v2 step 4; `on('busy')` fired for each). No
|
|
748
|
+
* attempt was used and nothing was billed: let the person try again in a
|
|
749
|
+
* moment;
|
|
750
|
+
* - `portrait_required` — ID mode (SDK spec §4.9): liveness is done and the
|
|
751
|
+
* session continues with the ICAO portrait step — not a failure. The plan
|
|
752
|
+
* runner (`run()`) moves on by itself; a liveness-only integration hands
|
|
753
|
+
* over to {@link createPortraitCapture} (or the hosted page).
|
|
754
|
+
*
|
|
755
|
+
* A liveness step that needs a phone (`HostedStepView.requiresMobile`) on a
|
|
756
|
+
* desktop browser ends with the reason `mobile_required` (status
|
|
757
|
+
* `unavailable`): send the person to their phone. The SDK stops before any
|
|
758
|
+
* upload when the view says so; otherwise the backend answers the submit with
|
|
759
|
+
* a request-level `409 mobile_required` (no attempt used, nothing billed).
|
|
760
|
+
*/
|
|
761
|
+
type SdkFailureReason = "no_recorder" | "attestation" | "network" | "nni_unbound" | "method_required" | "inference_unavailable" | "portrait_required";
|
|
762
|
+
/** Backend reason taxonomy ∪ the SDK-side failure reasons. */
|
|
763
|
+
type ReasonCode = CheckReason | SessionFailureReason | SdkFailureReason;
|
|
764
|
+
/**
|
|
765
|
+
* - `approved` / `rejected` / `review` — the session's verdict;
|
|
766
|
+
* - `retry` — a retryable reason with attempts remaining (`autoRetry` on); the
|
|
767
|
+
* capture re-arms by itself;
|
|
768
|
+
* - `unavailable` — v2 could not run this capture (no MediaRecorder,
|
|
769
|
+
* attestation failed, network/API failure). Treat as "not completed".
|
|
770
|
+
*/
|
|
771
|
+
type VerificationStatus = "approved" | "rejected" | "review" | "retry" | "unavailable";
|
|
772
|
+
/** The verdict for this session / capture (`on('result')`). */
|
|
773
|
+
interface VerificationResult {
|
|
774
|
+
sessionId: string;
|
|
775
|
+
status: VerificationStatus;
|
|
776
|
+
/** Backend check or session-policy reason, or SDK-side reason for `unavailable`. */
|
|
777
|
+
reason?: ReasonCode;
|
|
778
|
+
/**
|
|
779
|
+
* `retry` (and a `rejected` on a still-parked step): submissions left for
|
|
780
|
+
* the parked check after this one, for the host's coaching copy.
|
|
781
|
+
*/
|
|
782
|
+
attemptsRemaining?: number;
|
|
783
|
+
/**
|
|
784
|
+
* ISO-8601 time from which this device may verify again — set on a
|
|
785
|
+
* `rejected` result with reason `device_cooldown` (profile v2 step 4).
|
|
786
|
+
*/
|
|
787
|
+
retryAfter?: string;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* `on('busy')` payload: the model service answered
|
|
791
|
+
* `503 inference_unavailable` and the SDK waits `retryAfterSeconds` before
|
|
792
|
+
* retry number `attempt` (1-based, at most 3). Show "busy, trying again" copy.
|
|
793
|
+
*/
|
|
794
|
+
interface VerificationBusy {
|
|
795
|
+
attempt: number;
|
|
796
|
+
retryAfterSeconds: number;
|
|
797
|
+
}
|
|
798
|
+
/** Consent locale picklist (mirrors the backend HostedConsentSchema). */
|
|
799
|
+
type VerificationLocale = "ar" | "fr" | "en";
|
|
800
|
+
/** Options of {@link createVerification}. */
|
|
801
|
+
interface VerificationOptions {
|
|
802
|
+
/** The session's client token (from `POST /api/v2/sessions` → `client_token`). */
|
|
803
|
+
clientToken: string;
|
|
804
|
+
/** API origin; default `https://api.sekyro.ai`. */
|
|
805
|
+
apiOrigin?: string;
|
|
806
|
+
/** Consent locale (echoed into the auditable consent record). */
|
|
807
|
+
locale: VerificationLocale;
|
|
808
|
+
/** Re-arm the capture on retryable reasons while attempts remain. Default `true`. */
|
|
809
|
+
autoRetry?: boolean;
|
|
810
|
+
/**
|
|
811
|
+
* How a `retry` verdict re-arms the capture (`autoRetry` on):
|
|
812
|
+
* - `'auto'` (default) — the camera restarts immediately;
|
|
813
|
+
* - `'manual'` — the handle settles at `done`; the host shows its coaching
|
|
814
|
+
* and calls `retry()` on the handle when the user is ready.
|
|
815
|
+
*/
|
|
816
|
+
rearm?: "auto" | "manual";
|
|
817
|
+
/** Injection seam for `fetch` (tests / non-window contexts). */
|
|
818
|
+
fetch?: typeof globalThis.fetch;
|
|
819
|
+
}
|
|
820
|
+
/** Event map of a {@link Verification}. */
|
|
821
|
+
interface VerificationEvents {
|
|
822
|
+
/** Verdict per capture (and once when the registry settles the session early). */
|
|
823
|
+
result: VerificationResult;
|
|
824
|
+
/** Every hosted-view update the SDK observes (bootstrap, consent, submits). */
|
|
825
|
+
view: HostedSessionView;
|
|
826
|
+
/**
|
|
827
|
+
* The session waits on the identity-method choice (profile v2 step 3) and
|
|
828
|
+
* the SDK stopped before submitting anything: show the options and call
|
|
829
|
+
* {@link Verification.selectMethod}.
|
|
830
|
+
*/
|
|
831
|
+
method: MethodChoiceView;
|
|
832
|
+
/**
|
|
833
|
+
* The model service is briefly unavailable and the SDK retries by itself
|
|
834
|
+
* (profile v2 step 4). A `result` follows: the verdict, or
|
|
835
|
+
* `unavailable / inference_unavailable` once the retries are spent.
|
|
836
|
+
*/
|
|
837
|
+
busy: VerificationBusy;
|
|
838
|
+
/** The plan runner's current step — only after {@link Verification.run}. */
|
|
839
|
+
step: VerificationStep;
|
|
840
|
+
}
|
|
841
|
+
/** One session's verification, from {@link createVerification}. */
|
|
842
|
+
interface Verification {
|
|
843
|
+
/** Record consent once (no-op when the session already has it). */
|
|
844
|
+
consent(): Promise<void>;
|
|
845
|
+
/**
|
|
846
|
+
* Submit `population_registry` early with an empty input when it is the
|
|
847
|
+
* pending frontier and the backend holds the NNI: the SERVER-bound one, or
|
|
848
|
+
* the one the document step read (`nniFromDocument`). Retries once on
|
|
849
|
+
* `registry_unavailable`. Never rejects — failures surface through
|
|
850
|
+
* `result`. Idempotent.
|
|
851
|
+
*/
|
|
852
|
+
startRegistry(): Promise<void>;
|
|
853
|
+
/**
|
|
854
|
+
* The identity-method choice the session waits on (profile v2 step 3),
|
|
855
|
+
* bootstrapping the hosted view when needed; `null` when there is no
|
|
856
|
+
* pending choice, the bootstrap fails, or the verification was destroyed.
|
|
857
|
+
* Never rejects.
|
|
858
|
+
*/
|
|
859
|
+
methodChoice(): Promise<MethodChoiceView | null>;
|
|
860
|
+
/**
|
|
861
|
+
* Send the person's identity method (`POST /hosted/method`) and continue
|
|
862
|
+
* from the returned view. Rejects with the typed `HostedApiError` (e.g.
|
|
863
|
+
* 409 `method_locked`).
|
|
864
|
+
*/
|
|
865
|
+
selectMethod(method: IdentityMethod): Promise<void>;
|
|
866
|
+
/**
|
|
867
|
+
* The session's pending liveness step must be captured on a phone
|
|
868
|
+
* (`HostedStepView.requiresMobile`, profile v2 step 4 automatic decisions)
|
|
869
|
+
* and this browser is not one: show "continue on your phone" instead of
|
|
870
|
+
* starting the camera. Bootstraps the hosted view when needed; `false` when
|
|
871
|
+
* the bootstrap fails or the verification was destroyed. Never rejects.
|
|
872
|
+
* Optional so custom / older `Verification` implementations stay valid —
|
|
873
|
+
* treat an absent member as "no phone requirement".
|
|
874
|
+
*/
|
|
875
|
+
mobileRequired?(): Promise<boolean>;
|
|
876
|
+
/**
|
|
877
|
+
* The session is a returning-user session (`HostedSessionView.returning`,
|
|
878
|
+
* profile v2 step 5): the person verified before and only confirms it is
|
|
879
|
+
* still them (liveness + face match against their stored photo) — show
|
|
880
|
+
* "Confirm it's you". Bootstraps the hosted view when needed; `false` when
|
|
881
|
+
* the bootstrap fails or the verification was destroyed. Never rejects.
|
|
882
|
+
* Optional so custom / older `Verification` implementations stay valid —
|
|
883
|
+
* treat an absent member as `false`.
|
|
884
|
+
*/
|
|
885
|
+
returning?(): Promise<boolean>;
|
|
886
|
+
/**
|
|
887
|
+
* Start the plan runner (M5a): bootstraps the hosted view and resolves the
|
|
888
|
+
* step it waits on; every later step arrives through `on('step')`. Idempotent.
|
|
889
|
+
* Optional so custom / older `Verification` implementations stay valid —
|
|
890
|
+
* objects from `createVerification` ≥ 0.2 always have it.
|
|
891
|
+
*/
|
|
892
|
+
run?(): Promise<VerificationStep>;
|
|
893
|
+
/** The step last delivered by the runner; `null` before {@link Verification.run}. */
|
|
894
|
+
currentStep?(): VerificationStep | null;
|
|
895
|
+
/** Start a liveness capture bound to this verification; the SDK uploads + submits on `captured`. */
|
|
896
|
+
liveness(options: LivenessCaptureOptions): LivenessCaptureHandle;
|
|
897
|
+
/**
|
|
898
|
+
* The session's Sekyro AI-hosted voice clips (`view.voice`), bootstrapping the
|
|
899
|
+
* hosted view when it has not been fetched yet. Resolves `null` when the
|
|
900
|
+
* session has no active clip set, the API predates the field, the bootstrap
|
|
901
|
+
* fails, or the verification was destroyed. Never rejects.
|
|
902
|
+
*/
|
|
903
|
+
voiceClips(): Promise<HostedVoiceView | null>;
|
|
904
|
+
/** Subscribe; returns the unsubscribe function. */
|
|
905
|
+
on<K extends keyof VerificationEvents>(event: K, handler: (payload: VerificationEvents[K]) => void): () => void;
|
|
906
|
+
/** Destroy the active capture and drop all listeners. */
|
|
907
|
+
destroy(): void;
|
|
908
|
+
}
|
|
909
|
+
/** Phone OTP (profile v2 §4.1): send, then verify the 6-digit code. */
|
|
910
|
+
interface PhoneOtpStepHandle extends PhoneOtpSnapshot {
|
|
911
|
+
/** ISO time from which a resend is allowed (the server's cooldown, else 30 s after this page's last send). */
|
|
912
|
+
resendAvailableAt?: string;
|
|
913
|
+
/**
|
|
914
|
+
* Send or resend. `phone` (E.164) is required unless `numberSource` is
|
|
915
|
+
* `prefilled`; a resend reuses the last one. `channel` is the person's
|
|
916
|
+
* choice: required when `channels` lists more than one (never picked for
|
|
917
|
+
* them), optional with one, and must be one of `channels` — else it rejects
|
|
918
|
+
* with `OtpInputError` (`channel`, `required` / `unavailable`).
|
|
919
|
+
*/
|
|
920
|
+
send(input?: {
|
|
921
|
+
phone?: string;
|
|
922
|
+
channel?: "sms" | "whatsapp";
|
|
923
|
+
}): Promise<void>;
|
|
924
|
+
/** Check the 6-digit code; anything else rejects with `InvalidInputError` before any call. */
|
|
925
|
+
verify(code: string): Promise<void>;
|
|
926
|
+
/** With `canSwitch` (a `one_of` session): confirm the other contact instead. Rejects with `RangeError` otherwise. */
|
|
927
|
+
switchTo(): Promise<void>;
|
|
928
|
+
}
|
|
929
|
+
/** Email OTP (profile v2 §4.6.5): send a code to the address, then verify it. */
|
|
930
|
+
interface EmailOtpStepHandle extends EmailOtpSnapshot {
|
|
931
|
+
/** Send or resend. `email` is required when `needsEmail` (checked locally: `OtpInputError("email", "format")`); a resend reuses the last one. */
|
|
932
|
+
send(input?: {
|
|
933
|
+
email?: string;
|
|
934
|
+
}): Promise<void>;
|
|
935
|
+
/** Check the 6-digit code; anything else rejects with `InvalidInputError` before any call. */
|
|
936
|
+
verify(code: string): Promise<void>;
|
|
937
|
+
/** See {@link PhoneOtpStepHandle.switchTo}. */
|
|
938
|
+
switchTo(): Promise<void>;
|
|
939
|
+
}
|
|
940
|
+
/** Step 1 contact choice (flow model 2026-10-23): phone or email. */
|
|
941
|
+
interface ContactChoiceStepHandle extends ContactChoiceSnapshot {
|
|
942
|
+
/** Rejects with `RangeError` for a channel not in `options`. */
|
|
943
|
+
select(channel: ContactChannel): Promise<void>;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Step 2b summary and confirm (flow model 2026-10-23). PII: show the values,
|
|
947
|
+
* never log them or pass them to analytics.
|
|
948
|
+
*/
|
|
949
|
+
interface IdentitySummaryStepHandle extends IdentitySummarySnapshot {
|
|
950
|
+
/**
|
|
951
|
+
* The portrait on record as a JPEG `Blob` (fetched with the session token;
|
|
952
|
+
* show it with `URL.createObjectURL` and revoke it when done). `null` when
|
|
953
|
+
* none is available or it cannot be loaded.
|
|
954
|
+
*/
|
|
955
|
+
portrait(): Promise<Blob | null>;
|
|
956
|
+
/** The details are right: the live photo comes next. */
|
|
957
|
+
confirm(): Promise<void>;
|
|
958
|
+
/** The details are wrong: the identity step opens again. Rejects with `RangeError` when `retakeAvailable` is false. */
|
|
959
|
+
retake(): Promise<void>;
|
|
960
|
+
}
|
|
961
|
+
/** "How do you want to verify?" (profile v2 §4.4). */
|
|
962
|
+
interface MethodStepHandle extends MethodSnapshot {
|
|
963
|
+
/** Rejects with `RangeError` for a method not in `options`. */
|
|
964
|
+
select(method: IdentityMethod): Promise<void>;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* The national eID sign-in (SDK spec §3.2; Houwiyeti in MR). Every action
|
|
968
|
+
* submits `eid_auth` and re-reads the view; a passed eID ends the session.
|
|
969
|
+
*/
|
|
970
|
+
interface EidStepHandle extends EidSnapshot {
|
|
971
|
+
/**
|
|
972
|
+
* `identifier` phase: ask the provider for a code. `identifier` (the NNI in
|
|
973
|
+
* MR, checked locally: 10 digits and the check digit — else
|
|
974
|
+
* `InvalidInputError("nni", …)`) is required unless
|
|
975
|
+
* `identifierBound`; on a bound session it is never sent.
|
|
976
|
+
*/
|
|
977
|
+
start(identifier?: string): Promise<void>;
|
|
978
|
+
/** `code` phase: check the 6-digit code from the provider's app; anything else rejects with `InvalidInputError("code", "format")`. */
|
|
979
|
+
verify(code: string): Promise<void>;
|
|
980
|
+
/** Ask for a new code (the server spaces them out — `resendAvailableAt`). Rejects with `RangeError` when `resendsRemaining` is 0. */
|
|
981
|
+
resend(): Promise<void>;
|
|
982
|
+
/** With `canUseCamera`: switch to `cameraMethod`. Rejects with `RangeError` otherwise. */
|
|
983
|
+
useCamera(): Promise<void>;
|
|
984
|
+
}
|
|
985
|
+
/** The registry step (SDK spec §5.5). */
|
|
986
|
+
interface RegistryStepHandle extends RegistrySnapshot {
|
|
987
|
+
/** Typed source only: checks the NNI locally (format, mod-97) before submitting. */
|
|
988
|
+
submitNni(nni: string): Promise<void>;
|
|
989
|
+
/** Bound or document source: submit with an empty input (the server holds the NNI). */
|
|
990
|
+
confirm(): Promise<void>;
|
|
991
|
+
/** After `nni_not_found` (`canUseDocument`): switch to the document photo. */
|
|
992
|
+
useDocument(): Promise<void>;
|
|
993
|
+
}
|
|
994
|
+
/** The document step — any issuing country (profile v2 §5.3a). */
|
|
995
|
+
interface DocumentStepHandle extends DocumentSnapshot {
|
|
996
|
+
/** The picked issuer (`null` until picked; a verso keeps the recto's pick). */
|
|
997
|
+
issuer: string | null;
|
|
998
|
+
/** The picked type (`null` until picked). */
|
|
999
|
+
documentType: AcceptedDocumentType | null;
|
|
1000
|
+
/** `"single"` for a passport, else the snapshot's side; `null` until a type is picked. */
|
|
1001
|
+
captureSide: DocumentSide | null;
|
|
1002
|
+
/** Pick (or re-pick after `document_type_not_accepted`). Throws `RangeError` when the profile does not accept `type` for `issuer`. */
|
|
1003
|
+
selectDocument(issuer: string, type: AcceptedDocumentType): void;
|
|
1004
|
+
/**
|
|
1005
|
+
* The step's camera (rear). The SDK uploads and submits `doc_ocr` for every
|
|
1006
|
+
* capture and re-arms the same stream for the other face. One handle per
|
|
1007
|
+
* step: calling again with the same video returns the same handle until it
|
|
1008
|
+
* is destroyed. Throws when no document was picked yet.
|
|
1009
|
+
*/
|
|
1010
|
+
capture(options: {
|
|
1011
|
+
video: HTMLVideoElement;
|
|
1012
|
+
keepScreenAwake?: boolean;
|
|
1013
|
+
/** Web live MRZ (on-device MRZ read on MRZ faces; obeys the session's server tuning). */
|
|
1014
|
+
liveMrz?: DocumentLiveMrzOptions;
|
|
1015
|
+
}): DocumentCaptureHandle;
|
|
1016
|
+
}
|
|
1017
|
+
/** The liveness / selfie step. */
|
|
1018
|
+
interface LivenessStepHandle extends LivenessSnapshot {
|
|
1019
|
+
/** Same as {@link Verification.liveness}: the SDK uploads + submits on `captured`. */
|
|
1020
|
+
capture(options: LivenessCaptureOptions): LivenessCaptureHandle;
|
|
1021
|
+
/**
|
|
1022
|
+
* Step 3 with `livePhoto.icao`: the ICAO coaching of the running capture
|
|
1023
|
+
* (the gate fills only while `holding`; show `portrait.coach.<update.coach>`
|
|
1024
|
+
* otherwise). Returns the unsubscribe; never fires without `livePhoto.icao`.
|
|
1025
|
+
*/
|
|
1026
|
+
onLivePhoto(handler: (gate: LivePhotoGateUpdate) => void): () => void;
|
|
1027
|
+
}
|
|
1028
|
+
/** The ICAO coaching of a one-capture live photo (`LivenessStepHandle.onLivePhoto`). */
|
|
1029
|
+
interface LivePhotoGateUpdate {
|
|
1030
|
+
/** The ICAO conditions hold: the liveness gate may fill. */
|
|
1031
|
+
holding: boolean;
|
|
1032
|
+
/** The last ICAO gate frame (`null` before the first). */
|
|
1033
|
+
update: PortraitGateUpdate | null;
|
|
1034
|
+
}
|
|
1035
|
+
/** The ICAO portrait step (SDK spec §4.9, §5.4; ID-document sessions). */
|
|
1036
|
+
interface PortraitStepHandle extends PortraitSnapshot {
|
|
1037
|
+
/**
|
|
1038
|
+
* The last submission failed on the network (nothing was decided): in the
|
|
1039
|
+
* `capture` phase offer a new take (`retry()` on the capture handle), in the
|
|
1040
|
+
* `match` phase offer {@link PortraitStepHandle.resubmit}.
|
|
1041
|
+
*/
|
|
1042
|
+
uploadFailed: boolean;
|
|
1043
|
+
/** `match` phase: submit `face_match` again after `uploadFailed` (no-op otherwise). */
|
|
1044
|
+
resubmit(): Promise<void>;
|
|
1045
|
+
/**
|
|
1046
|
+
* The step's camera (front), `capture` phase only. The SDK uploads the
|
|
1047
|
+
* portrait and submits `portrait_icao` (then `face_match` with the portrait
|
|
1048
|
+
* as the probe). Every attempt is billed, so after a server verdict the
|
|
1049
|
+
* handle settles at `done` and the host calls `retry()` when the person is
|
|
1050
|
+
* ready. One handle per step: the same video returns the same handle.
|
|
1051
|
+
*/
|
|
1052
|
+
capture(options: {
|
|
1053
|
+
video: HTMLVideoElement;
|
|
1054
|
+
keepScreenAwake?: boolean;
|
|
1055
|
+
assetBaseUrl?: string;
|
|
1056
|
+
}): PortraitCaptureHandle;
|
|
1057
|
+
}
|
|
1058
|
+
/** The biometric step needs a phone (desktop browser). */
|
|
1059
|
+
interface MobileRequiredStepHandle extends MobileRequiredSnapshot {
|
|
1060
|
+
/** Re-read the session (e.g. after the person finished on their phone). */
|
|
1061
|
+
refresh(): Promise<void>;
|
|
1062
|
+
}
|
|
1063
|
+
/** The server is still deciding. */
|
|
1064
|
+
interface ProcessingStepHandle extends ProcessingSnapshot {
|
|
1065
|
+
/** Re-read the session now (also after `stalled`). */
|
|
1066
|
+
refresh(): Promise<void>;
|
|
1067
|
+
}
|
|
1068
|
+
/** A step this SDK cannot run (`platform_unsupported` / `update_required`). */
|
|
1069
|
+
type UnsupportedStepHandle = UnsupportedSnapshot;
|
|
1070
|
+
/** The session's outcome — display only (the SDK never decides it). */
|
|
1071
|
+
type ResultStepHandle = ResultSnapshot;
|
|
1072
|
+
/** What `on('step')` delivers: the step's data plus its actions. */
|
|
1073
|
+
type VerificationStep = PhoneOtpStepHandle | EmailOtpStepHandle | ContactChoiceStepHandle | IdentitySummaryStepHandle | MethodStepHandle | EidStepHandle | RegistryStepHandle | DocumentStepHandle | LivenessStepHandle | PortraitStepHandle | MobileRequiredStepHandle | ProcessingStepHandle | UnsupportedStepHandle | ResultStepHandle;
|
|
1074
|
+
|
|
1075
|
+
/** The engine surface the handle binds — CaptureSessionV3 or a test double. */
|
|
1076
|
+
interface CaptureEngine {
|
|
1077
|
+
readonly state: CaptureState$1;
|
|
1078
|
+
on<K extends keyof CaptureSessionV3Events>(event: K, handler: (payload: CaptureSessionV3Events[K]) => void): () => void;
|
|
1079
|
+
/** `facingMode` sets the camera for this attempt and later retries. */
|
|
1080
|
+
start(options?: {
|
|
1081
|
+
facingMode?: "user" | "environment";
|
|
1082
|
+
}): Promise<void>;
|
|
1083
|
+
retry(): void;
|
|
1084
|
+
switchCamera(): void;
|
|
1085
|
+
destroy(): void;
|
|
1086
|
+
/** Device-profile chrome hint (LOW class → `'reduced'`); absent on doubles. */
|
|
1087
|
+
readonly animations?: "full" | "reduced";
|
|
1088
|
+
}
|
|
1089
|
+
/** Internal construction seams (tests; Sekyro AI's own hosted page). */
|
|
1090
|
+
interface LivenessCaptureInternals {
|
|
1091
|
+
/** Replace the engine constructor (default: `new CaptureSessionV3(options)`). */
|
|
1092
|
+
createEngine?: (options: CaptureSessionV3Options) => CaptureEngine;
|
|
1093
|
+
/** Extra engine options merged in (worker offload, warmed detector, telemetry). */
|
|
1094
|
+
engineOptions?: Partial<Pick<CaptureSessionV3Options, "createDetector" | "createWorker" | "telemetry" | "deviceClass" | "rankKeyframes" | "measureQuality">>;
|
|
1095
|
+
/** Clock (default `performance.now`). */
|
|
1096
|
+
now?: () => number;
|
|
1097
|
+
}
|
|
1098
|
+
/** fcv3.* guidance key → public gate hint code. */
|
|
1099
|
+
declare function mapGuidanceHint(key: string): GateHint | null;
|
|
1100
|
+
/** Engine state → public state (`preflight|acquiring → starting`, `positioning → gating`). */
|
|
1101
|
+
declare function mapEngineState(state: CaptureState$1): CaptureState;
|
|
1102
|
+
/** Engine error kinds → the public six (`models|cliptoolarge|recorder|overconstrained → generic`). */
|
|
1103
|
+
declare function mapErrorKind(kind: CaptureErrorKind$1): CaptureErrorKind;
|
|
1104
|
+
/** Handle + the hooks the verification layer uses to drive the flow states. */
|
|
1105
|
+
interface LivenessCaptureInternalHandle {
|
|
1106
|
+
handle: LivenessCaptureHandle;
|
|
1107
|
+
/** Push a post-capture state (`uploading | checking | done`). */
|
|
1108
|
+
setFlowState(state: Extract<CaptureState, "uploading" | "checking" | "done">): void;
|
|
1109
|
+
}
|
|
1110
|
+
declare function createLivenessCaptureInternal(options: LivenessCaptureOptions, internals?: LivenessCaptureInternals,
|
|
1111
|
+
/** Keyframe ranking switch (SDK spec §4.8a) — createVerification reads the server tuning. */
|
|
1112
|
+
rankKeyframes?: () => boolean): LivenessCaptureInternalHandle;
|
|
1113
|
+
/**
|
|
1114
|
+
* Create a standalone liveness capture (no upload — pair it with your own
|
|
1115
|
+
* submission, or use {@link Verification.liveness} for the full flow).
|
|
1116
|
+
*/
|
|
1117
|
+
declare function createLivenessCapture(options: LivenessCaptureOptions): LivenessCaptureHandle;
|
|
1118
|
+
|
|
1119
|
+
/** Preload state per asset base URL. */
|
|
1120
|
+
type LivenessAssetsStatus = "idle" | "loading" | "ready" | "failed";
|
|
1121
|
+
/** Options of {@link preloadLivenessAssets}. */
|
|
1122
|
+
interface PreloadLivenessAssetsOptions {
|
|
1123
|
+
/** Same value as the capture's `assetBaseUrl`; default `/models-v3`. */
|
|
1124
|
+
assetBaseUrl?: string;
|
|
1125
|
+
/** Defer the load to browser idle time (`requestIdleCallback`, 1.2 s timeout fallback). Default `false`. */
|
|
1126
|
+
idle?: boolean;
|
|
1127
|
+
/** Skip when the browser asks to save data (`navigator.connection.saveData`). Default `false`. */
|
|
1128
|
+
respectSaveData?: boolean;
|
|
1129
|
+
}
|
|
1130
|
+
/** Blaze init deadline for the preload — generous: a slow ~11 MB wasm download must finish. */
|
|
1131
|
+
declare const PRELOAD_BLAZE_INIT_TIMEOUT_MS = 120000;
|
|
1132
|
+
/** Outer budget reclaiming only a genuinely hung load (≥ the init deadline + slack). */
|
|
1133
|
+
declare const PRELOAD_BUDGET_MS = 130000;
|
|
1134
|
+
type Loader = (base: string) => Promise<Detector>;
|
|
1135
|
+
/** @internal Test seam: replace the detector loader (null restores the real one). */
|
|
1136
|
+
declare function setPreloadLoaderForTests(next: Loader | null): void;
|
|
1137
|
+
/** @internal Test seam: drop every entry (destroying warmed detectors). */
|
|
1138
|
+
declare function resetLivenessAssetsForTests(): void;
|
|
1139
|
+
/**
|
|
1140
|
+
* Warm the face detector for `assetBaseUrl` so the capture starts instantly.
|
|
1141
|
+
* Idempotent per base URL and never rejects: resolves the settled status.
|
|
1142
|
+
* `'idle'` means nothing was preloaded here (SSR, no camera API, Data-Saver
|
|
1143
|
+
* skip) — start normally. Calling again after `'failed'` retries the load.
|
|
1144
|
+
* With `idle: true` the load is deferred (status stays `'idle'` until it
|
|
1145
|
+
* starts); a later non-idle call — or a capture mounting meanwhile — starts it
|
|
1146
|
+
* at once, and {@link releaseLivenessAssets} cancels it.
|
|
1147
|
+
*/
|
|
1148
|
+
declare function preloadLivenessAssets(options?: PreloadLivenessAssetsOptions): Promise<LivenessAssetsStatus>;
|
|
1149
|
+
/** Current preload status for `assetBaseUrl` (default `/models-v3`). */
|
|
1150
|
+
declare function getLivenessAssetsStatus(assetBaseUrl?: string): LivenessAssetsStatus;
|
|
1151
|
+
/**
|
|
1152
|
+
* Subscribe to status changes for `assetBaseUrl`; returns the unsubscribe
|
|
1153
|
+
* function. Does NOT emit the current status on subscribe (read it with
|
|
1154
|
+
* {@link getLivenessAssetsStatus}) — the
|
|
1155
|
+
* `useSyncExternalStore(subscribe, getSnapshot)` contract, so it plugs into
|
|
1156
|
+
* that hook directly.
|
|
1157
|
+
*/
|
|
1158
|
+
declare function subscribeLivenessAssets(listener: (status: LivenessAssetsStatus) => void, assetBaseUrl?: string): () => void;
|
|
1159
|
+
/** Free the warmed detector (an SPA leaving the flow). Never call while a capture is mounted. */
|
|
1160
|
+
declare function releaseLivenessAssets(assetBaseUrl?: string): void;
|
|
1161
|
+
/**
|
|
1162
|
+
* @internal The capture engine's detector source when a preload exists for
|
|
1163
|
+
* `assetBaseUrl`: a non-owning view of the warm instance (waiting for an
|
|
1164
|
+
* in-flight load), or — once that load failed — a cold detector OWNED by the
|
|
1165
|
+
* session (default ladder + deadline). `undefined` when nothing was preloaded.
|
|
1166
|
+
* Invoking it on an idle-scheduled preload starts that load now and joins it.
|
|
1167
|
+
*/
|
|
1168
|
+
declare function preloadedDetectorFactory(assetBaseUrl?: string): (() => Promise<Detector>) | undefined;
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Physical geometry the scanner frames (ISO/IEC 7810 / ICAO 9303). `"card"` is
|
|
1172
|
+
* any identity card or residence permit of ANY issuer: ID-1 (TD1) or ID-2 (TD2)
|
|
1173
|
+
* — the issuer decides the size, so the gate accepts either aspect.
|
|
1174
|
+
*/
|
|
1175
|
+
type DocFormat = "ID-1" | "ID-2" | "ID-3" | "card";
|
|
1176
|
+
type PhysicalFormat = Exclude<DocFormat, "card">;
|
|
1177
|
+
type DocSide = "recto" | "verso" | "single";
|
|
1178
|
+
interface DocCatalogEntry {
|
|
1179
|
+
/** kyc-docs-v2 `document_type`, verbatim. */
|
|
1180
|
+
docType: "carte_identite" | "passport" | "residence_permit";
|
|
1181
|
+
format: DocFormat;
|
|
1182
|
+
/** Capture sequence the hosted doc step walks, in order. */
|
|
1183
|
+
sides: readonly DocSide[];
|
|
1184
|
+
/** MRZ geometry: td1 = 3×30 (ID-1 verso), td3 = 2×44 (passport data page). */
|
|
1185
|
+
mrz: "none" | "td1" | "td3";
|
|
1186
|
+
/** Which side carries the holder photo (drives portrait expectations). */
|
|
1187
|
+
portraitSide: DocSide;
|
|
1188
|
+
/** Physical width/height ratio of the framed page. */
|
|
1189
|
+
aspectRatio: number;
|
|
1190
|
+
/** Where the subject identifier (NNI) is parsed from, when the doc carries one. */
|
|
1191
|
+
identifierSource?: "mrz";
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Plausibility aspect bands per physical format (Task 34 gate input): ID-1
|
|
1195
|
+
* cards are 85.6×53.98mm → 1.586; ID-2 105×74mm → 1.419; an ID-3 passport DATA
|
|
1196
|
+
* PAGE frames at ≈125×88mm → 1.42 (wider tolerance — page curl and hinge
|
|
1197
|
+
* shadow skew the detected quad).
|
|
1198
|
+
*/
|
|
1199
|
+
declare const ASPECT_BANDS: Record<PhysicalFormat, {
|
|
1200
|
+
center: number;
|
|
1201
|
+
tolerance: number;
|
|
1202
|
+
}>;
|
|
1203
|
+
/** Aspect band for a format (`"card"` → the ID-1 band; use {@link aspectFits} for plausibility). */
|
|
1204
|
+
declare function aspectBand(format: DocFormat): {
|
|
1205
|
+
center: number;
|
|
1206
|
+
tolerance: number;
|
|
1207
|
+
};
|
|
1208
|
+
/** The quad aspect is plausible for the format (any of its physical bands). */
|
|
1209
|
+
declare function aspectFits(format: DocFormat, aspect: number): boolean;
|
|
1210
|
+
/** The MR docTypes facet (findings §4). Order = the hosted picker's display order. */
|
|
1211
|
+
declare const MR_DOC_CATALOG: readonly DocCatalogEntry[];
|
|
1212
|
+
/** Every document type (profile v2 §5.3a), picker order: identity card, passport, residence permit. */
|
|
1213
|
+
declare const DOC_CATALOG: readonly DocCatalogEntry[];
|
|
1214
|
+
/**
|
|
1215
|
+
* Scanner format per document type for ANY issuer (profile v2 §5.3a): identity
|
|
1216
|
+
* cards and residence permits are ID-1 or ID-2 (the issuer decides), passports
|
|
1217
|
+
* are framed by their MRZ band (ID-3). Reads DOC_CATALOG; its entries (and
|
|
1218
|
+
* MR_DOC_CATALOG, which the hosted page reads) keep `"ID-1"`.
|
|
1219
|
+
*/
|
|
1220
|
+
declare function scannerFormatFor(docType: DocCatalogEntry["docType"]): DocFormat;
|
|
1221
|
+
|
|
1222
|
+
/** Detect-frame width the session downscales to (findings §1: 25ms median).
|
|
1223
|
+
* Lives HERE (not detector-scanic.ts) so the session can import it without
|
|
1224
|
+
* statically pulling the scanic chunk into host bundles. */
|
|
1225
|
+
declare const DOC_DETECT_WIDTH = 480;
|
|
1226
|
+
interface DocQuadPoint {
|
|
1227
|
+
x: number;
|
|
1228
|
+
y: number;
|
|
1229
|
+
}
|
|
1230
|
+
/** Detected document quad, image-space pixels (scanic CornerPoints shape). */
|
|
1231
|
+
interface DocQuad {
|
|
1232
|
+
topLeft: DocQuadPoint;
|
|
1233
|
+
topRight: DocQuadPoint;
|
|
1234
|
+
bottomRight: DocQuadPoint;
|
|
1235
|
+
bottomLeft: DocQuadPoint;
|
|
1236
|
+
}
|
|
1237
|
+
interface DocGateThresholds {
|
|
1238
|
+
/** Quad area / frame area below which the doc is too far. */
|
|
1239
|
+
MIN_AREA_FRAC: number;
|
|
1240
|
+
/** A corner this close to (or past) a frame edge counts as clipped. */
|
|
1241
|
+
EDGE_CLIP_MARGIN_PX: number;
|
|
1242
|
+
/** Consecutive stable plausible+quality ticks required to fire. */
|
|
1243
|
+
STABLE_TICKS: number;
|
|
1244
|
+
/** Quad-bbox IoU with the previous tick at/above which the streak holds. */
|
|
1245
|
+
IOU_STABLE_MIN: number;
|
|
1246
|
+
/** Integrator slopes, per second — mirrors FaceGateV3's gentle drain. */
|
|
1247
|
+
RISE_PER_S: number;
|
|
1248
|
+
FALL_PER_S: number;
|
|
1249
|
+
/** Quality guidance trigger levels (same semantics as the face gate). */
|
|
1250
|
+
GUIDE_EXPOSURE: number;
|
|
1251
|
+
GUIDE_SHARPNESS: number;
|
|
1252
|
+
/** ID-3 (MRZ-band mode): band width / frame width floor. */
|
|
1253
|
+
ID3_BAND_MIN_WIDTH_FRAC: number;
|
|
1254
|
+
/** ID-3 (MRZ-band mode): band aspect (w/h) sanity bounds. */
|
|
1255
|
+
ID3_BAND_ASPECT_MIN: number;
|
|
1256
|
+
ID3_BAND_ASPECT_MAX: number;
|
|
1257
|
+
}
|
|
1258
|
+
/** Frozen defaults (plan Task 34 — do not retune without a golden update). */
|
|
1259
|
+
declare const DOC_GATE_V1_DEFAULTS: Readonly<DocGateThresholds>;
|
|
1260
|
+
interface DocGateQuality {
|
|
1261
|
+
/** frame-quality.sharpness on the quad crop. */
|
|
1262
|
+
sharpness: number;
|
|
1263
|
+
/** frame-quality.exposure on the quad crop. */
|
|
1264
|
+
exposure: number;
|
|
1265
|
+
/** Mean crop luma — picks more_light vs too_bright. */
|
|
1266
|
+
meanLuma: number;
|
|
1267
|
+
}
|
|
1268
|
+
interface DocGateTickInput {
|
|
1269
|
+
timestampMs: number;
|
|
1270
|
+
quad: DocQuad | null;
|
|
1271
|
+
quality: DocGateQuality;
|
|
1272
|
+
frameWidth: number;
|
|
1273
|
+
frameHeight: number;
|
|
1274
|
+
}
|
|
1275
|
+
/** Per-tick output — the scanner chrome contract. */
|
|
1276
|
+
interface DocGateTick {
|
|
1277
|
+
at: number;
|
|
1278
|
+
quad: DocQuad | null;
|
|
1279
|
+
/** Geometry passed: present × big enough × in frame × right shape. */
|
|
1280
|
+
plausible: boolean;
|
|
1281
|
+
/** Integrated progress 0..1 (the overlay ring). */
|
|
1282
|
+
p: number;
|
|
1283
|
+
/** dcv1.* message key (host translates; never prose). */
|
|
1284
|
+
guidanceKey: string;
|
|
1285
|
+
/** Fire signal: plausible × quality × STABLE_TICKS consecutive high-IoU ticks. */
|
|
1286
|
+
ready: boolean;
|
|
1287
|
+
/** Current stability streak length (diagnostics/telemetry). */
|
|
1288
|
+
stableTicks: number;
|
|
1289
|
+
}
|
|
1290
|
+
/** Shoelace area of the quad as a fraction of the frame area. */
|
|
1291
|
+
declare function quadAreaFrac(quad: DocQuad, frameWidth: number, frameHeight: number): number;
|
|
1292
|
+
/** IoU of the two quads' axis-aligned bounding boxes (stability metric). */
|
|
1293
|
+
declare function quadIou(a: DocQuad, b: DocQuad): number;
|
|
1294
|
+
/** Axis-aligned bounds (width/height) of a quad — the ID-3 band-mode geometry. */
|
|
1295
|
+
declare function quadBounds(quad: DocQuad): {
|
|
1296
|
+
width: number;
|
|
1297
|
+
height: number;
|
|
1298
|
+
};
|
|
1299
|
+
declare class DocGateV1 {
|
|
1300
|
+
private readonly t;
|
|
1301
|
+
private readonly format;
|
|
1302
|
+
private p;
|
|
1303
|
+
private lastTickAt;
|
|
1304
|
+
private lastQuad;
|
|
1305
|
+
private streak;
|
|
1306
|
+
constructor(format: DocFormat, overrides?: Partial<DocGateThresholds>);
|
|
1307
|
+
reset(): void;
|
|
1308
|
+
tick(input: DocGateTickInput): DocGateTick;
|
|
1309
|
+
private edgeClipped;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
type FramingStatus = "no_document" | "too_far" | "too_close" | "edge_clipped" | "wrong_shape" | "tilted" | "ok";
|
|
1313
|
+
type LightingStatus = "too_dark" | "too_bright" | "ok";
|
|
1314
|
+
type BlurStatus = "blurry" | "ok";
|
|
1315
|
+
type GlareStatus = "glare" | "ok";
|
|
1316
|
+
/** The per-frame analysis vector (every axis judged independently). */
|
|
1317
|
+
interface DocFrameAnalysis {
|
|
1318
|
+
framing: FramingStatus;
|
|
1319
|
+
lighting: LightingStatus;
|
|
1320
|
+
blur: BlurStatus;
|
|
1321
|
+
glare: GlareStatus;
|
|
1322
|
+
/** All-clear: every axis ok — the gate's integrator/stability input. */
|
|
1323
|
+
acceptable: boolean;
|
|
1324
|
+
}
|
|
1325
|
+
interface DocFrameMeasures {
|
|
1326
|
+
/** frame-quality sharpness on the doc-region crop (0..1). */
|
|
1327
|
+
sharpness: number;
|
|
1328
|
+
/** frame-quality exposure on the doc-region crop (0..1). */
|
|
1329
|
+
exposure: number;
|
|
1330
|
+
/** Mean crop luma (0..255) — picks too_dark vs too_bright. */
|
|
1331
|
+
meanLuma: number;
|
|
1332
|
+
/** Fraction of crop pixels at/above the specular ceiling (0..1). */
|
|
1333
|
+
specularFraction: number;
|
|
1334
|
+
}
|
|
1335
|
+
/** Analysis thresholds beyond the gate's — frozen defaults (round 4). */
|
|
1336
|
+
declare const DOC_ANALYSIS_DEFAULTS: Readonly<{
|
|
1337
|
+
/** Doc area fraction above which the camera is too close (capture would crop). */
|
|
1338
|
+
MAX_AREA_FRAC: 0.92;
|
|
1339
|
+
/** Opposite-side length ratio beyond which the view is too oblique. */
|
|
1340
|
+
TILT_MAX_SIDE_RATIO: 1.3;
|
|
1341
|
+
/** Specular-pixel fraction at/above which the frame reads as glare. */
|
|
1342
|
+
GLARE_MAX_SPECULAR_FRAC: 0.025;
|
|
1343
|
+
}>;
|
|
1344
|
+
/** Fraction of crop pixels with luma ≥ 252 — cheap specular-highlight measure. */
|
|
1345
|
+
declare function specularFraction(crop: ImageData): number;
|
|
1346
|
+
/** Opposite-side length ratio of the quad (1 = fronto-parallel). */
|
|
1347
|
+
declare function quadTiltRatio(quad: DocQuad): number;
|
|
1348
|
+
/**
|
|
1349
|
+
* Judge one frame. `detectionMode` mirrors the gate: ID-1 quads carry
|
|
1350
|
+
* area/aspect/tilt semantics; ID-3 MRZ bands carry width-fraction semantics
|
|
1351
|
+
* (band rects are axis-aligned, so tilt/aspect checks are band-specific).
|
|
1352
|
+
*/
|
|
1353
|
+
declare function analyzeDocFrame(input: {
|
|
1354
|
+
format: DocFormat;
|
|
1355
|
+
quad: DocQuad | null;
|
|
1356
|
+
frameWidth: number;
|
|
1357
|
+
frameHeight: number;
|
|
1358
|
+
measures: DocFrameMeasures;
|
|
1359
|
+
thresholds?: Partial<DocGateThresholds>;
|
|
1360
|
+
}): DocFrameAnalysis;
|
|
1361
|
+
|
|
1362
|
+
/** Feedback vocabulary (dcv2.* message keys on the hosted side). */
|
|
1363
|
+
type DocFeedbackKey = "sensing" | "too_far" | "too_close" | "edge_clipped" | "wrong_shape" | "tilted" | "too_dark" | "too_bright" | "blurry" | "focus_far" | "glare" | "hold_still" | "processing" | "side_captured" | "captured";
|
|
1364
|
+
/** Reticle rendering mode per feedback state (the stage maps to visuals). */
|
|
1365
|
+
type ReticleType = "searching" | "coaching" | "locking" | "processing" | "flip" | "done";
|
|
1366
|
+
interface DocFeedbackState {
|
|
1367
|
+
key: DocFeedbackKey;
|
|
1368
|
+
reticle: ReticleType;
|
|
1369
|
+
/** The state holds the UI at least this long (ms) before a switch. */
|
|
1370
|
+
minDurationMs: number;
|
|
1371
|
+
}
|
|
1372
|
+
declare const DOC_FEEDBACK_STATES: Readonly<Record<DocFeedbackKey, DocFeedbackState>>;
|
|
1373
|
+
/** Frame analysis → the feedback vote it casts (worst axis speaks). */
|
|
1374
|
+
declare function feedbackVoteOf(analysis: DocFrameAnalysis): DocFeedbackKey;
|
|
1375
|
+
/** Sliding vote window + exponential decay + per-state minimum hold. */
|
|
1376
|
+
declare class DocFeedbackAggregator {
|
|
1377
|
+
private readonly windowMs;
|
|
1378
|
+
/** Per-second decay factor — recent frames outvote stale ones. */
|
|
1379
|
+
private readonly decayPerSecond;
|
|
1380
|
+
private votes;
|
|
1381
|
+
private current;
|
|
1382
|
+
private currentSinceMs;
|
|
1383
|
+
constructor(windowMs?: number,
|
|
1384
|
+
/** Per-second decay factor — recent frames outvote stale ones. */
|
|
1385
|
+
decayPerSecond?: number);
|
|
1386
|
+
reset(): void;
|
|
1387
|
+
/** Force a terminal/choreography state (capture fired, side flip, upload). */
|
|
1388
|
+
force(key: Extract<DocFeedbackKey, "processing" | "side_captured" | "captured" | "focus_far">, nowMs: number): DocFeedbackState;
|
|
1389
|
+
/** Cast this frame's vote and return the (possibly held) winning state. */
|
|
1390
|
+
tick(vote: DocFeedbackKey, nowMs: number): DocFeedbackState;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/** A rectangle in some image's pixels. */
|
|
1394
|
+
interface PixelRect {
|
|
1395
|
+
x: number;
|
|
1396
|
+
y: number;
|
|
1397
|
+
width: number;
|
|
1398
|
+
height: number;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/** What a document still capture returns (`docRegion` in the still's pixels; null = unknown). */
|
|
1402
|
+
interface DocStill {
|
|
1403
|
+
blob: Blob;
|
|
1404
|
+
previewDataUrl: string;
|
|
1405
|
+
docRegion?: PixelRect | null;
|
|
1406
|
+
/** Which source produced it (telemetry / tests). */
|
|
1407
|
+
source?: "photo" | "video";
|
|
1408
|
+
width?: number;
|
|
1409
|
+
height?: number;
|
|
1410
|
+
/** Blur-gate sharpness of the document region (blur-gate.ts scale; -1 unknown). */
|
|
1411
|
+
sharpness?: number;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Server-tunable document camera values (`capture.document.camera`, mirrored by
|
|
1416
|
+
* identity-backend hosted-capture.ts). Coaching / capture behaviour only — the
|
|
1417
|
+
* server re-checks every upload.
|
|
1418
|
+
*/
|
|
1419
|
+
interface DocCameraTuning {
|
|
1420
|
+
/** Upgrade ask on the opened track (px; landscape terms). */
|
|
1421
|
+
width: number;
|
|
1422
|
+
height: number;
|
|
1423
|
+
/** Rank back cameras and open the main wide lens by deviceId. */
|
|
1424
|
+
lensSelection: boolean;
|
|
1425
|
+
/** Apply `focusMode: continuous` where offered. */
|
|
1426
|
+
continuousFocus: boolean;
|
|
1427
|
+
/** Settle time after a single-shot refocus before the photo (ms; 0 = no refocus). */
|
|
1428
|
+
refocusBeforePhotoMs: number;
|
|
1429
|
+
/** Zoom never exceeds this. */
|
|
1430
|
+
zoomMax: number;
|
|
1431
|
+
/** Zoom when the camera reports no focus range (iOS Safari), on a large enough track. */
|
|
1432
|
+
zoomNoFocusInfo: number;
|
|
1433
|
+
/** Short side (px) the track needs before {@link zoomNoFocusInfo} applies. */
|
|
1434
|
+
zoomMinSourcePx: number;
|
|
1435
|
+
/** Distance (m) at which the document fills the guide at 1× (main wide lens). */
|
|
1436
|
+
fillDistanceM: {
|
|
1437
|
+
id1: number;
|
|
1438
|
+
id3: number;
|
|
1439
|
+
};
|
|
1440
|
+
/** Extra margin over the minimum focus distance. */
|
|
1441
|
+
focusMarginFrac: number;
|
|
1442
|
+
/** Auto-captured still refused when its document sharpness is under this (blur-gate scale). */
|
|
1443
|
+
stillSharpnessMin: number;
|
|
1444
|
+
/** After this many refusals in a row the next still is kept (the server decides). */
|
|
1445
|
+
stillRefusalsMax: number;
|
|
1446
|
+
/** A blurry, large document (fill ≥ this of the frame) is coached "move farther". */
|
|
1447
|
+
focusFarFillFrac: number;
|
|
1448
|
+
/**
|
|
1449
|
+
* Zoomed in (> 1×) and no document seen for this long (ms): coach "move a
|
|
1450
|
+
* little farther" — a card held at the usual 1× distance overfills a 2×
|
|
1451
|
+
* frame and reads as nothing (iPhone 2026-10-22: 72/74 empty frames). 0 = off.
|
|
1452
|
+
*/
|
|
1453
|
+
noDocFarHintMs: number;
|
|
1454
|
+
/** Still nothing seen for this long (ms) while zoomed: drop the zoom to 1×. 0 = never. */
|
|
1455
|
+
zoomFallbackMs: number;
|
|
1456
|
+
}
|
|
1457
|
+
declare const DOC_CAMERA_TUNING_DEFAULTS: Readonly<DocCameraTuning>;
|
|
1458
|
+
/** Field-by-field merge of a server `capture.document.camera` block over the defaults (bad fields ignored). */
|
|
1459
|
+
declare function resolveDocCameraTuning(raw: unknown): DocCameraTuning;
|
|
1460
|
+
type LensKind = "main" | "virtual" | "ultra" | "tele" | "depth" | "front" | "unknown";
|
|
1461
|
+
/** What a camera label says about its lens (labels are English on Android Chrome / iOS Safari). */
|
|
1462
|
+
declare function lensKindOf(label: string): LensKind;
|
|
1463
|
+
/**
|
|
1464
|
+
* Back cameras best-first: main wide lenses (lowest Android index first), then
|
|
1465
|
+
* iOS virtual multi-lens devices; ultra-wide, telephoto, depth and front lenses
|
|
1466
|
+
* are dropped. Empty when nothing is identifiable (keep the browser's pick).
|
|
1467
|
+
*/
|
|
1468
|
+
declare function rankBackCameras(devices: ReadonlyArray<{
|
|
1469
|
+
deviceId: string;
|
|
1470
|
+
label: string;
|
|
1471
|
+
kind?: string;
|
|
1472
|
+
}>): string[];
|
|
1473
|
+
declare const LENS_STORAGE_KEY = "sekyro.doc.camera";
|
|
1474
|
+
/** The remembered document camera (per browser), or null (storage blocked / empty). */
|
|
1475
|
+
declare function readStoredLens(): string | null;
|
|
1476
|
+
declare function writeStoredLens(deviceId: string | null): void;
|
|
1477
|
+
interface FocusCaps {
|
|
1478
|
+
focusMode?: readonly string[];
|
|
1479
|
+
focusDistance?: {
|
|
1480
|
+
min?: number;
|
|
1481
|
+
max?: number;
|
|
1482
|
+
};
|
|
1483
|
+
zoom?: {
|
|
1484
|
+
min?: number;
|
|
1485
|
+
max?: number;
|
|
1486
|
+
step?: number;
|
|
1487
|
+
};
|
|
1488
|
+
pointsOfInterest?: unknown;
|
|
1489
|
+
}
|
|
1490
|
+
declare const supportsFocusMode: (caps: FocusCaps | null | undefined, mode: string) => boolean;
|
|
1491
|
+
/**
|
|
1492
|
+
* Single-shot refocus at a normalized point (0..1 of the video frame), when
|
|
1493
|
+
* the camera offers single-shot focus. Chrome accepts `pointsOfInterest`
|
|
1494
|
+
* without advertising it on every device, so it rides along whenever
|
|
1495
|
+
* single-shot exists.
|
|
1496
|
+
*/
|
|
1497
|
+
declare function singleShotFocusAt(caps: FocusCaps | null | undefined, x: number, y: number): MediaTrackConstraintSet | null;
|
|
1498
|
+
/** Map a tap on an `object-fit: cover` video element to normalized video-frame coordinates. */
|
|
1499
|
+
declare function coverPointToVideo(clientX: number, clientY: number, rect: {
|
|
1500
|
+
left: number;
|
|
1501
|
+
top: number;
|
|
1502
|
+
width: number;
|
|
1503
|
+
height: number;
|
|
1504
|
+
}, video: {
|
|
1505
|
+
width: number;
|
|
1506
|
+
height: number;
|
|
1507
|
+
}): {
|
|
1508
|
+
x: number;
|
|
1509
|
+
y: number;
|
|
1510
|
+
} | null;
|
|
1511
|
+
/**
|
|
1512
|
+
* The zoom that keeps the document inside the focus range, or 1 (no change).
|
|
1513
|
+
* - focus range reported (Chrome Android, `focusDistance.min` in metres): zoom
|
|
1514
|
+
* so the fill distance clears min-focus × (1 + margin);
|
|
1515
|
+
* - no focus range (iOS Safari): {@link DocCameraTuning.zoomNoFocusInfo} on a
|
|
1516
|
+
* track whose short side is ≥ zoomMinSourcePx (pixel headroom), else 1.
|
|
1517
|
+
* Always within the camera's zoom range and the tuning's zoomMax; 1 without a
|
|
1518
|
+
* zoom capability.
|
|
1519
|
+
*/
|
|
1520
|
+
declare function docZoomFor(caps: FocusCaps | null | undefined, track: {
|
|
1521
|
+
width: number;
|
|
1522
|
+
height: number;
|
|
1523
|
+
}, format: DocFormat, tuning?: DocCameraTuning): number;
|
|
1524
|
+
interface OpenDocCameraDeps {
|
|
1525
|
+
getUserMedia: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
1526
|
+
/** (navigator.mediaDevices.enumerateDevices) — absent: keep the browser's pick. */
|
|
1527
|
+
enumerateDevices?: () => Promise<ReadonlyArray<{
|
|
1528
|
+
deviceId: string;
|
|
1529
|
+
label: string;
|
|
1530
|
+
kind?: string;
|
|
1531
|
+
}>>;
|
|
1532
|
+
lensSelection: boolean;
|
|
1533
|
+
/** First ask (resolution). */
|
|
1534
|
+
base: {
|
|
1535
|
+
width: number;
|
|
1536
|
+
height: number;
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
type LensSource = "stored" | "selected" | "default";
|
|
1540
|
+
/** Focus is fine when the camera offers continuous AF, or reports no focus modes at all (Safari / Firefox). */
|
|
1541
|
+
declare function focusOk(stream: MediaStream): boolean;
|
|
1542
|
+
/** At most this many ranked lenses are tried (each costs a camera reopen). */
|
|
1543
|
+
declare const LENS_TRY_MAX = 3;
|
|
1544
|
+
/**
|
|
1545
|
+
* Open the back camera: the remembered lens first, else `facingMode:
|
|
1546
|
+
* environment` (the permission prompt; labels appear after it), then the
|
|
1547
|
+
* best-ranked main wide lens with continuous AF by deviceId. Any failure along
|
|
1548
|
+
* the way keeps — or reopens — the browser's pick; only the very first
|
|
1549
|
+
* getUserMedia error (permission, no camera) propagates.
|
|
1550
|
+
*/
|
|
1551
|
+
declare function openDocCamera(deps: OpenDocCameraDeps): Promise<{
|
|
1552
|
+
stream: MediaStream;
|
|
1553
|
+
lens: LensSource;
|
|
1554
|
+
}>;
|
|
1555
|
+
|
|
1556
|
+
/** Normalized rect (0..1) of the upright frame. */
|
|
1557
|
+
interface GuideRect {
|
|
1558
|
+
x: number;
|
|
1559
|
+
y: number;
|
|
1560
|
+
width: number;
|
|
1561
|
+
height: number;
|
|
1562
|
+
}
|
|
1563
|
+
interface GuideMetrics {
|
|
1564
|
+
/** Mean luma inside the guide (0..255). */
|
|
1565
|
+
brightness: number;
|
|
1566
|
+
/** Fraction of guide samples at/above the glare luma (native measure: guide region incl. background). */
|
|
1567
|
+
glare: number;
|
|
1568
|
+
/**
|
|
1569
|
+
* Fraction of samples at/above the glare luma INSIDE the measured card (inset
|
|
1570
|
+
* 6 %) — what the web lock gates on: a bright table or floor around the card
|
|
1571
|
+
* is not glare on the document. Equals {@link glare} when no card box is measured.
|
|
1572
|
+
*/
|
|
1573
|
+
cardGlare: number;
|
|
1574
|
+
/** Laplacian variance over the sampled guide grid (native scale). */
|
|
1575
|
+
sharpness: number;
|
|
1576
|
+
/** Detected card width / guide width (1 = fitted). */
|
|
1577
|
+
fill: number;
|
|
1578
|
+
/**
|
|
1579
|
+
* The measured box's shape over the guide's (box w/h ÷ guide w/h; 1 = the
|
|
1580
|
+
* format's aspect). A floor tile or a book in the guide reads far from 1.
|
|
1581
|
+
*/
|
|
1582
|
+
shape: number;
|
|
1583
|
+
/**
|
|
1584
|
+
* Printed content inside the measured box: the fraction of interior samples
|
|
1585
|
+
* on a strong luma step (≥ 24 levels to a 2-sample neighbour). A card's text,
|
|
1586
|
+
* photo and guilloche read ~0.08+; an empty tile, table or hand ~0.01.
|
|
1587
|
+
*/
|
|
1588
|
+
content: number;
|
|
1589
|
+
/** Card centre offset in guide widths / heights (+ = right / down). */
|
|
1590
|
+
offsetX: number;
|
|
1591
|
+
offsetY: number;
|
|
1592
|
+
/** Weakest side's edge strength over the mean gradient (≥ ~1.6 = a card is there). */
|
|
1593
|
+
edgeConfidence: number;
|
|
1594
|
+
/** The card's sides in frame pixels (for the still crop), null when unmeasured. */
|
|
1595
|
+
box: {
|
|
1596
|
+
left: number;
|
|
1597
|
+
top: number;
|
|
1598
|
+
right: number;
|
|
1599
|
+
bottom: number;
|
|
1600
|
+
} | null;
|
|
1601
|
+
}
|
|
1602
|
+
declare const EMPTY_GUIDE_METRICS: Readonly<GuideMetrics>;
|
|
1603
|
+
/**
|
|
1604
|
+
* The on-screen guide in normalized coordinates of the upright frame, grown by
|
|
1605
|
+
* `margin` of the guide on each side (GuideRegion.inFrame). `view` is the
|
|
1606
|
+
* element showing the frame with `object-fit: cover`; `guide` is in view units.
|
|
1607
|
+
*/
|
|
1608
|
+
declare function guideRegionInFrame(view: {
|
|
1609
|
+
width: number;
|
|
1610
|
+
height: number;
|
|
1611
|
+
}, guide: GuideRect, frameAspect: number, margin?: number): GuideRect;
|
|
1612
|
+
/**
|
|
1613
|
+
* Measure the card inside `region` of one RGBA frame (DocumentQuality.analyze,
|
|
1614
|
+
* rotation 0: browser frames are already upright).
|
|
1615
|
+
*/
|
|
1616
|
+
declare function analyzeGuide(frame: ImageData, region: GuideRect, opts?: {
|
|
1617
|
+
edgePeakFrac?: number;
|
|
1618
|
+
}): GuideMetrics;
|
|
1619
|
+
/** The measured card as a quad in frame pixels (axis-aligned), or null. */
|
|
1620
|
+
declare function guideQuad(m: GuideMetrics): DocQuad | null;
|
|
1621
|
+
/**
|
|
1622
|
+
* Web guide-lock thresholds — server `capture.document.guide` (identity-backend
|
|
1623
|
+
* hosted-capture.ts). Starts from the native `capture.document.scan` values
|
|
1624
|
+
* (Mattel production) with the web calibration of 2026-10-22: the guide is the
|
|
1625
|
+
* drawn card slot (no margin), so `fillMin` 0.82; glare is measured on the card
|
|
1626
|
+
* only and gated at 0.025 (the web quad gate's specular ceiling) — the native
|
|
1627
|
+
* 0.012 over the whole guide blocked a card back on a bright floor; steady
|
|
1628
|
+
* deltas scale with the 3–6 Hz tick; the edge pick prefers the guide's side;
|
|
1629
|
+
* and a card must be card-shaped and carry print (`shapeTolerance`, `contentMin`)
|
|
1630
|
+
* — the nearer-edge pick otherwise reads a floor tile in the guide as a card.
|
|
1631
|
+
*/
|
|
1632
|
+
interface GuideLockTuning {
|
|
1633
|
+
fillMin: number;
|
|
1634
|
+
fillMax: number;
|
|
1635
|
+
offsetMax: number;
|
|
1636
|
+
glareMax: number;
|
|
1637
|
+
/** Native-scale Laplacian variance floor (see {@link GuideMetrics.sharpness}). */
|
|
1638
|
+
sharpnessMin: number;
|
|
1639
|
+
edgeConfidenceMin: number;
|
|
1640
|
+
darkBrightness: number;
|
|
1641
|
+
lockMs: number;
|
|
1642
|
+
steadyMs: number;
|
|
1643
|
+
steadyFillMin: number;
|
|
1644
|
+
steadyFillMax: number;
|
|
1645
|
+
steadyFillDelta: number;
|
|
1646
|
+
steadyOffsetDelta: number;
|
|
1647
|
+
/**
|
|
1648
|
+
* Report interval the steady deltas are defined for (native: 100 ms at 10 Hz).
|
|
1649
|
+
* Web ticks at 3–6 Hz: the allowed deltas scale with the real interval
|
|
1650
|
+
* (× dt / steadyRefMs, capped at 3×). 0 = no scaling.
|
|
1651
|
+
*/
|
|
1652
|
+
steadyRefMs: number;
|
|
1653
|
+
/**
|
|
1654
|
+
* Edge pick: 0 = the band's strongest edge (native); in (0, 1) = of the peaks at
|
|
1655
|
+
* least this fraction of the strongest, the one nearest the guide's side.
|
|
1656
|
+
*/
|
|
1657
|
+
edgePeakFrac: number;
|
|
1658
|
+
/** Max relative deviation of {@link GuideMetrics.shape} from 1 (0 = no check; native has none). */
|
|
1659
|
+
shapeTolerance: number;
|
|
1660
|
+
/** Min {@link GuideMetrics.content} for a card (0 = no check; native has none). */
|
|
1661
|
+
contentMin: number;
|
|
1662
|
+
}
|
|
1663
|
+
declare const GUIDE_LOCK_DEFAULTS: Readonly<GuideLockTuning>;
|
|
1664
|
+
/** Field-by-field merge of a server `capture.document.scan` block over the defaults (bad fields ignored). */
|
|
1665
|
+
declare function resolveGuideLockTuning(raw: unknown): GuideLockTuning;
|
|
1666
|
+
/** Why the guide lock is not firing on this frame (telemetry + coaching). */
|
|
1667
|
+
type GuideBlocker = "no_card" | "too_small" | "too_big" | "off_center" | "glare" | "blurry" | "steadying" | "locking";
|
|
1668
|
+
interface GuideLockTick {
|
|
1669
|
+
/** A card is measured in the guide (edge confidence over the floor). */
|
|
1670
|
+
detected: boolean;
|
|
1671
|
+
/** Fire: good for lockMs, or steady for steadyMs. */
|
|
1672
|
+
ready: boolean;
|
|
1673
|
+
/** "good" (fill/centre/glare/sharp) or "steady" (held still, looser fill). */
|
|
1674
|
+
via: "good" | "steady" | null;
|
|
1675
|
+
blocker: GuideBlocker;
|
|
1676
|
+
}
|
|
1677
|
+
/** The capture subset of the native ScanMachine (no flip / reads / torch hints). */
|
|
1678
|
+
declare class GuideLock {
|
|
1679
|
+
private readonly t;
|
|
1680
|
+
private goodSince;
|
|
1681
|
+
private steadySince;
|
|
1682
|
+
private last;
|
|
1683
|
+
constructor(t?: GuideLockTuning);
|
|
1684
|
+
reset(): void;
|
|
1685
|
+
tick(m: GuideMetrics, at: number): GuideLockTick;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
type MrzFormat = "TD1" | "TD2" | "TD3";
|
|
1689
|
+
type LoadFailure = "unsupported" | "download" | "integrity" | "error" | "timeout";
|
|
1690
|
+
/** One band read's outcome. `lineBoxes` are [x, y, w, h] normalized to the posted bitmap (the band; the whole still
|
|
1691
|
+
* for a `still` read with a region). */
|
|
1692
|
+
interface BandResult {
|
|
1693
|
+
kind: "band";
|
|
1694
|
+
ms: number;
|
|
1695
|
+
lineBoxes: [number, number, number, number][];
|
|
1696
|
+
/** Lines the MRZ picker kept (2 or 3), 0 when none fit. */
|
|
1697
|
+
lineCount: number;
|
|
1698
|
+
format?: MrzFormat;
|
|
1699
|
+
/** ICAO document code of a parsed MRZ (P, I, ID, IR, AC…): a fixed document-type code, not personal data. */
|
|
1700
|
+
documentCode?: string;
|
|
1701
|
+
/** Every HARD check digit valid. */
|
|
1702
|
+
valid: boolean;
|
|
1703
|
+
/** sha256("sekyro-mrz-1\0" + salt + "\0" + lines.join("\n")), 64 hex, only when valid. */
|
|
1704
|
+
digest?: string;
|
|
1705
|
+
/** The picked MRZ lines' box [x, y, w, h] normalized to the posted bitmap (geometry only). */
|
|
1706
|
+
mrzBox?: [number, number, number, number];
|
|
1707
|
+
}
|
|
1708
|
+
/** The front-lock layout read (detector only, no recognizer): the text-line boxes of the card crop, [x, y, w, h]
|
|
1709
|
+
* normalized to the posted bitmap, top to bottom. Geometry only. */
|
|
1710
|
+
interface LayoutResult {
|
|
1711
|
+
kind: "layout";
|
|
1712
|
+
ms: number;
|
|
1713
|
+
lineBoxes: [number, number, number, number][];
|
|
1714
|
+
}
|
|
1715
|
+
interface ReadyInfo {
|
|
1716
|
+
warmupMs: number;
|
|
1717
|
+
downloadMs: number;
|
|
1718
|
+
cached: boolean;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
interface FrontLockTuning {
|
|
1722
|
+
/** Lines the detector must find in the card crop to call it a card face (a printed front has 6–12). */
|
|
1723
|
+
minLines: number;
|
|
1724
|
+
/** Horizontal extent of the text block / crop width: below = "move closer", above = "move back". */
|
|
1725
|
+
fillMin: number;
|
|
1726
|
+
fillMax: number;
|
|
1727
|
+
/** |text-block center − crop center| per axis, as a fraction of the crop. */
|
|
1728
|
+
offsetMax: number;
|
|
1729
|
+
/** Lumière steadiness deltas against the previous report. */
|
|
1730
|
+
steadyFillDelta: number;
|
|
1731
|
+
steadyOffsetDelta: number;
|
|
1732
|
+
/** Mean shift of matched line centers between reports (fraction of the crop), and the allowed line-count change. */
|
|
1733
|
+
layoutShiftMax: number;
|
|
1734
|
+
lineCountDelta: number;
|
|
1735
|
+
/** Lumière lockMs / steadyMs, plus a minimum number of consecutive layout reads (the sampler sets the cadence). */
|
|
1736
|
+
lockMs: number;
|
|
1737
|
+
steadyMs: number;
|
|
1738
|
+
minFrames: number;
|
|
1739
|
+
}
|
|
1740
|
+
type FrontLockHint = "closer" | "farther" | "center" | "glare" | "blurry";
|
|
1741
|
+
|
|
1742
|
+
/** The engine surface the assist needs (OcrEngineClient, once `ready`). */
|
|
1743
|
+
interface DocAssistEngine {
|
|
1744
|
+
readonly busy: boolean;
|
|
1745
|
+
bandRead(bitmap: ImageBitmap, detectorLimit?: number): Promise<BandResult>;
|
|
1746
|
+
layoutRead(bitmap: ImageBitmap, detectorLimit: number): Promise<LayoutResult>;
|
|
1747
|
+
}
|
|
1748
|
+
/** `capture.document.frontLock` (server-delivered; the FrontLockTuning fields + the sampler's). */
|
|
1749
|
+
interface TextLockTuning extends FrontLockTuning {
|
|
1750
|
+
minIntervalMs: number;
|
|
1751
|
+
maxWidthPx: number;
|
|
1752
|
+
detectorLimit: number;
|
|
1753
|
+
/** Layout reads stop after this long with a card in view and no lock (the quality locks keep going). */
|
|
1754
|
+
searchTimeoutMs: number;
|
|
1755
|
+
}
|
|
1756
|
+
/** `capture.document.liveMrz` — the sampler / gate subset (spec §9). */
|
|
1757
|
+
interface LiveMrzTuning {
|
|
1758
|
+
detectorLimit: number;
|
|
1759
|
+
bandMaxWidthPx: number;
|
|
1760
|
+
minBandWidthPx: number;
|
|
1761
|
+
bandTop: number;
|
|
1762
|
+
minIntervalMs: number;
|
|
1763
|
+
stableReads: number;
|
|
1764
|
+
profileReads: number;
|
|
1765
|
+
maxReadMs: number;
|
|
1766
|
+
searchTimeoutMs: number;
|
|
1767
|
+
}
|
|
1768
|
+
declare const TEXT_LOCK_DEFAULTS: Readonly<TextLockTuning>;
|
|
1769
|
+
declare const LIVE_MRZ_DEFAULTS: Readonly<LiveMrzTuning>;
|
|
1770
|
+
/** A rect in VIDEO pixels. */
|
|
1771
|
+
interface VideoRect {
|
|
1772
|
+
x: number;
|
|
1773
|
+
y: number;
|
|
1774
|
+
w: number;
|
|
1775
|
+
h: number;
|
|
1776
|
+
}
|
|
1777
|
+
type LiveMrzCoach = "moveCloser" | "turnSideways" | "glare" | "holdStill";
|
|
1778
|
+
/** `unsupported`: the camera can never show the band wide enough to read (e.g. a 720 px portrait stream). */
|
|
1779
|
+
type LiveMrzFallback = "timeout" | "slow" | "error" | "unsupported";
|
|
1780
|
+
type LiveMrzState = {
|
|
1781
|
+
state: "searching";
|
|
1782
|
+
} | {
|
|
1783
|
+
state: "reading";
|
|
1784
|
+
coach?: LiveMrzCoach;
|
|
1785
|
+
format?: MrzFormat;
|
|
1786
|
+
} | {
|
|
1787
|
+
state: "valid";
|
|
1788
|
+
format: MrzFormat;
|
|
1789
|
+
} | {
|
|
1790
|
+
state: "fallback";
|
|
1791
|
+
reason: LiveMrzFallback;
|
|
1792
|
+
};
|
|
1793
|
+
/** What a live-MRZ capture carries to the still confirmation and the `deviceRead` hint (text-free). */
|
|
1794
|
+
interface LiveMrzCapture {
|
|
1795
|
+
format: MrzFormat;
|
|
1796
|
+
documentCode?: string;
|
|
1797
|
+
/** The salted digest of the two agreeing reads (64 hex). */
|
|
1798
|
+
digest: string;
|
|
1799
|
+
lineCount: number;
|
|
1800
|
+
framesToValid: number;
|
|
1801
|
+
msToValid: number;
|
|
1802
|
+
}
|
|
1803
|
+
interface DocAssistTickInput {
|
|
1804
|
+
atMs: number;
|
|
1805
|
+
video: HTMLVideoElement;
|
|
1806
|
+
/** This face carries an MRZ (passport page, card back): live MRZ when enabled, else the text lock. */
|
|
1807
|
+
mrzFace: boolean;
|
|
1808
|
+
/** A card / page is in view this tick (a plausible quad, or the guide lock measuring one). */
|
|
1809
|
+
present: boolean;
|
|
1810
|
+
/** The card region (the guide slot or the card box) for the text lock. */
|
|
1811
|
+
card: VideoRect | null;
|
|
1812
|
+
/** The MRZ band for live MRZ. */
|
|
1813
|
+
band: VideoRect | null;
|
|
1814
|
+
portrait: boolean;
|
|
1815
|
+
glareOk: boolean;
|
|
1816
|
+
sharpOk: boolean;
|
|
1817
|
+
}
|
|
1818
|
+
interface DocAssistTick {
|
|
1819
|
+
/** Capture now (the OCR path fired). */
|
|
1820
|
+
ready: boolean;
|
|
1821
|
+
via?: "text_lock" | "live_mrz";
|
|
1822
|
+
/** Live MRZ decides this face: the host suspends its quality auto-capture. */
|
|
1823
|
+
suspendQuality: boolean;
|
|
1824
|
+
mode: "off" | "text_lock" | "live_mrz";
|
|
1825
|
+
mrz?: LiveMrzState;
|
|
1826
|
+
lock?: {
|
|
1827
|
+
state: "searching" | "locking" | "locked";
|
|
1828
|
+
hint?: FrontLockHint;
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
/** PII-free per-face report (the `live_mrz` capture report, spec §10 + D5b). */
|
|
1832
|
+
interface DocAssistReport {
|
|
1833
|
+
capture: "recto" | "verso" | "single";
|
|
1834
|
+
mode: "text_lock" | "live_mrz";
|
|
1835
|
+
outcome: "valid" | "locked" | "timeout" | "slow" | "error" | "unsupported" | "off";
|
|
1836
|
+
reads: number;
|
|
1837
|
+
format?: MrzFormat;
|
|
1838
|
+
framesToValid?: number;
|
|
1839
|
+
msToValid?: number;
|
|
1840
|
+
framesToLock?: number;
|
|
1841
|
+
msToLock?: number;
|
|
1842
|
+
readP50Ms?: number;
|
|
1843
|
+
readP90Ms?: number;
|
|
1844
|
+
deviceClass?: "fast" | "mid" | "slow";
|
|
1845
|
+
typeMismatch?: boolean;
|
|
1846
|
+
}
|
|
1847
|
+
interface DocAssistOptions {
|
|
1848
|
+
textLock: TextLockTuning | null;
|
|
1849
|
+
liveMrz: LiveMrzTuning | null;
|
|
1850
|
+
/** Video crop → bitmap (default createImageBitmap with resizeWidth). */
|
|
1851
|
+
crop?: (video: HTMLVideoElement, r: VideoRect, resizeWidth: number) => Promise<ImageBitmap>;
|
|
1852
|
+
}
|
|
1853
|
+
declare class DocAssist {
|
|
1854
|
+
private readonly o;
|
|
1855
|
+
private engine;
|
|
1856
|
+
private readonly crop;
|
|
1857
|
+
private gen;
|
|
1858
|
+
private inFlight;
|
|
1859
|
+
private lastReadAt;
|
|
1860
|
+
private firstSeen;
|
|
1861
|
+
private reads;
|
|
1862
|
+
private times;
|
|
1863
|
+
private mrzGate;
|
|
1864
|
+
private lockGate;
|
|
1865
|
+
private mrzState;
|
|
1866
|
+
private lockState;
|
|
1867
|
+
private fallback;
|
|
1868
|
+
private lockOff;
|
|
1869
|
+
private lockError;
|
|
1870
|
+
private fired;
|
|
1871
|
+
private valid;
|
|
1872
|
+
private faceMode;
|
|
1873
|
+
private disabled;
|
|
1874
|
+
constructor(o: DocAssistOptions);
|
|
1875
|
+
/** The engine joins when ready (the camera never waits for it). */
|
|
1876
|
+
setEngine(engine: DocAssistEngine | null): void;
|
|
1877
|
+
/** The fallback card band's top (fraction of the guide), `liveMrz.bandTop`. */
|
|
1878
|
+
get bandTop(): number;
|
|
1879
|
+
get hasEngine(): boolean;
|
|
1880
|
+
/** New face / retake / restart: every gate, clock and fallback starts over; in-flight results are dropped. */
|
|
1881
|
+
reset(): void;
|
|
1882
|
+
/**
|
|
1883
|
+
* The OCR-fired still was refused (focus gate) or failed: read again on the next frames, but keep what this face
|
|
1884
|
+
* already learnt — a fallback, the text-lock timeout, the search clock and the read profile stay.
|
|
1885
|
+
*/
|
|
1886
|
+
rearmFace(): void;
|
|
1887
|
+
/** The live-MRZ read that fired this face's capture (null for any other path). */
|
|
1888
|
+
liveCapture(): LiveMrzCapture | null;
|
|
1889
|
+
private modeFor;
|
|
1890
|
+
tick(i: DocAssistTickInput): DocAssistTick;
|
|
1891
|
+
private states;
|
|
1892
|
+
private canRead;
|
|
1893
|
+
private fall;
|
|
1894
|
+
private liveTick;
|
|
1895
|
+
private lockTick;
|
|
1896
|
+
/** Stop using the engine for the session (it died, or was disposed). */
|
|
1897
|
+
disable(): void;
|
|
1898
|
+
/** The face's PII-free report (null when no OCR mode ran on it). */
|
|
1899
|
+
report(capture: DocAssistReport["capture"], capturedVia: string | undefined, atMs: number): DocAssistReport | null;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* Pre-fetch + cache the scanic chunk (call when the scan surface MOUNTS, before
|
|
1904
|
+
* the camera is even granted) so the first detect tick pays no chunk-fetch
|
|
1905
|
+
* latency. Fire-and-forget; failures fall back to the lazy path.
|
|
1906
|
+
*/
|
|
1907
|
+
declare function warmDocDetector(): void;
|
|
1908
|
+
/** Doc detect cadence per device class — scanic @480px is ~3–8× a face tick,
|
|
1909
|
+
* so the doc loop paces slower than the face profiles (findings §1: 2–5 Hz). */
|
|
1910
|
+
declare const DOC_CADENCE_HZ: Readonly<Record<DeviceClass, number>>;
|
|
1911
|
+
interface DocChecksPayload {
|
|
1912
|
+
engine: "docv1";
|
|
1913
|
+
/** Gate integrator 0..1 — the overlay ring. */
|
|
1914
|
+
p: number;
|
|
1915
|
+
plausible: boolean;
|
|
1916
|
+
stableTicks: number;
|
|
1917
|
+
quad: DocQuad | null;
|
|
1918
|
+
/** Detect-frame size the quad coordinates live in (overlay scaling). */
|
|
1919
|
+
frameWidth: number;
|
|
1920
|
+
frameHeight: number;
|
|
1921
|
+
/** Capture-v2 per-frame status vector (framing/lighting/blur/glare). */
|
|
1922
|
+
analysis: DocFrameAnalysis;
|
|
1923
|
+
/**
|
|
1924
|
+
* The gate's fire signal on this tick (framed, steady, good quality); always
|
|
1925
|
+
* set by this engine, optional so host-built payloads stay valid. With
|
|
1926
|
+
* `autoCapture: false` the host takes the still itself ({@link CaptureSessionDoc.captureNow}).
|
|
1927
|
+
*/
|
|
1928
|
+
ready?: boolean;
|
|
1929
|
+
/** Guide-region lock on this tick — optional so host-built payloads stay valid. */
|
|
1930
|
+
guide?: {
|
|
1931
|
+
detected: boolean;
|
|
1932
|
+
blocker: GuideBlocker;
|
|
1933
|
+
fill: number;
|
|
1934
|
+
shape?: number;
|
|
1935
|
+
content?: number;
|
|
1936
|
+
offsetX?: number;
|
|
1937
|
+
offsetY?: number;
|
|
1938
|
+
};
|
|
1939
|
+
/** On-device OCR assist on this tick (text lock / live MRZ; enums only) — absent without an engine. */
|
|
1940
|
+
ocr?: Pick<DocAssistTick, "mode" | "mrz" | "lock">;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* How the still was taken: the guide-region lock (good card held `lockMs`, or a
|
|
1944
|
+
* steady card `steadyMs`), the quad gate, or the user's manual shutter.
|
|
1945
|
+
*/
|
|
1946
|
+
type DocCaptureVia = "guide_good" | "guide_steady" | "quad" | "manual" | "text_lock" | "live_mrz";
|
|
1947
|
+
/** Why the manual shutter was offered. */
|
|
1948
|
+
type DocShutterReason = "timeout" | "detected_no_lock";
|
|
1949
|
+
/**
|
|
1950
|
+
* Manual-shutter safety net (server `capture.document.scan`, the native scan
|
|
1951
|
+
* machines' block): offered after `manualShutterMs` without a capture, or once
|
|
1952
|
+
* a document has been detected (a quad, or a card in the guide) for
|
|
1953
|
+
* `detectedNoLockMs` without any lock — long enough for the guide lock's
|
|
1954
|
+
* steady path to win first, so it stays hidden on a normal scan.
|
|
1955
|
+
*/
|
|
1956
|
+
interface DocShutterTuning {
|
|
1957
|
+
manualShutterMs: number;
|
|
1958
|
+
detectedNoLockMs: number;
|
|
1959
|
+
}
|
|
1960
|
+
declare const DOC_SHUTTER_DEFAULTS: Readonly<DocShutterTuning>;
|
|
1961
|
+
/**
|
|
1962
|
+
* Resolve the shutter tuning from a server `capture.document.scan` block:
|
|
1963
|
+
* `manualShutterMs` as is; `detectedNoLockMs` = `steadyMs` + `lockMs` (a lock
|
|
1964
|
+
* gets its full chance first). Bad / missing fields keep the defaults.
|
|
1965
|
+
*/
|
|
1966
|
+
declare function resolveDocShutterTuning(scan: unknown): DocShutterTuning;
|
|
1967
|
+
/**
|
|
1968
|
+
* PII-free per-side capture report (telemetry): timings, which path took the
|
|
1969
|
+
* still, and per-tick lock-failure reasons. No pixels, no text.
|
|
1970
|
+
*/
|
|
1971
|
+
interface DocCaptureReport {
|
|
1972
|
+
side?: DocSide;
|
|
1973
|
+
format: DocFormat;
|
|
1974
|
+
deviceClass: DeviceClass;
|
|
1975
|
+
/** Delivered video size (px). */
|
|
1976
|
+
video: {
|
|
1977
|
+
width: number;
|
|
1978
|
+
height: number;
|
|
1979
|
+
};
|
|
1980
|
+
/** Zoom applied (1 = none). */
|
|
1981
|
+
zoom: number;
|
|
1982
|
+
/** ms from positioning until the zoom was dropped to 1× (nothing seen while zoomed); absent: never. */
|
|
1983
|
+
zoomFallbackAtMs?: number;
|
|
1984
|
+
/** ms from positioning to the still (absent: no still yet). */
|
|
1985
|
+
msToCapture?: number;
|
|
1986
|
+
via?: DocCaptureVia;
|
|
1987
|
+
/** ms from positioning until the manual shutter was offered (absent: never). */
|
|
1988
|
+
shutterAtMs?: number;
|
|
1989
|
+
shutterReason?: DocShutterReason;
|
|
1990
|
+
/** Detect ticks run while positioning this side. */
|
|
1991
|
+
ticks: number;
|
|
1992
|
+
/** Per tick: the gate's framing verdict ("ok" when framed; edge_clipped, too_far, no_document, …). */
|
|
1993
|
+
quadFraming: Record<string, number>;
|
|
1994
|
+
/** Per tick on framed ticks that did not lock: the quality reason (blurry, too_dark, glare, …). */
|
|
1995
|
+
qualityBlockers: Record<string, number>;
|
|
1996
|
+
/** Per tick: the guide lock's state (no_card, too_small, too_big, off_center, glare, blurry, steadying, locking). */
|
|
1997
|
+
guideBlockers: Record<string, number>;
|
|
1998
|
+
/** Focus-gate refusals of auto stills. */
|
|
1999
|
+
stillRefusals: number;
|
|
2000
|
+
/** The on-device OCR assist's per-face report (text lock / live MRZ), when it ran. */
|
|
2001
|
+
ocr?: DocAssistReport;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Artifacts: `keyframe` = the full-res still of the framed side (JPEG, cropped
|
|
2005
|
+
* to the document). `docRegion` = the document's bbox in the keyframe's pixels
|
|
2006
|
+
* (`null`: unknown, measure the whole still; absent: a host seam that did not say).
|
|
2007
|
+
*/
|
|
2008
|
+
type DocArtifacts = CaptureArtifacts & {
|
|
2009
|
+
side?: DocSide;
|
|
2010
|
+
docRegion?: PixelRect | null;
|
|
2011
|
+
/** The live-MRZ read that took this still (text-free: format, code, salted digest, counts) — the deviceRead hint's input. */
|
|
2012
|
+
liveMrz?: LiveMrzCapture;
|
|
2013
|
+
};
|
|
2014
|
+
interface CaptureSessionDocEvents {
|
|
2015
|
+
statechange: {
|
|
2016
|
+
state: CaptureState$1;
|
|
2017
|
+
};
|
|
2018
|
+
checks: DocChecksPayload;
|
|
2019
|
+
/** dcv1.* message KEY — hosts translate; never prose. Deduped. */
|
|
2020
|
+
guidance: {
|
|
2021
|
+
key: string;
|
|
2022
|
+
};
|
|
2023
|
+
/** Capture-v2 AGGREGATED feedback (time-decay voted, min-duration held) —
|
|
2024
|
+
* the stage's message + reticle channel. Deduped on state key. */
|
|
2025
|
+
feedback: DocFeedbackState;
|
|
2026
|
+
artifacts: DocArtifacts;
|
|
2027
|
+
error: {
|
|
2028
|
+
kind: CaptureErrorKind$1;
|
|
2029
|
+
retryable: boolean;
|
|
2030
|
+
};
|
|
2031
|
+
/**
|
|
2032
|
+
* Offer the manual shutter ({@link CaptureSessionDoc.captureNow}) — the safety
|
|
2033
|
+
* net when the gate does not lock. `available: false` on re-arm / capture.
|
|
2034
|
+
*/
|
|
2035
|
+
shutter: {
|
|
2036
|
+
available: boolean;
|
|
2037
|
+
reason?: DocShutterReason;
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
interface CaptureSessionDocOptions {
|
|
2041
|
+
/** Host-owned <video playsInline muted>. */
|
|
2042
|
+
video: HTMLVideoElement;
|
|
2043
|
+
format?: DocFormat;
|
|
2044
|
+
/** Side label stamped on the artifacts event (recto→verso sequencing). */
|
|
2045
|
+
side?: DocSide;
|
|
2046
|
+
thresholds?: Partial<DocGateThresholds>;
|
|
2047
|
+
deviceClass?: DeviceClass;
|
|
2048
|
+
/**
|
|
2049
|
+
* Take the still when the gate is ready (default `true` — the hosted page's
|
|
2050
|
+
* behaviour). `false`: `ready` is only reported on the checks payload and the
|
|
2051
|
+
* host calls {@link CaptureSessionDoc.captureNow} (the Lumière scan flow).
|
|
2052
|
+
*/
|
|
2053
|
+
autoCapture?: boolean;
|
|
2054
|
+
/** (navigator.mediaDevices.getUserMedia — environment camera) */
|
|
2055
|
+
getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
2056
|
+
/** (vendored scanic detectQuad) */
|
|
2057
|
+
detect?: (image: ImageData) => Promise<DocQuad | null>;
|
|
2058
|
+
/** (canvas downscale of the live video to the 480px detect frame) */
|
|
2059
|
+
grabDetectFrame?: (video: HTMLVideoElement, targetWidth: number) => ImageData | null;
|
|
2060
|
+
/** (full-res still cropped to the quad's document → JPEG blob + preview: still-capture.ts) */
|
|
2061
|
+
captureStill?: (video: HTMLVideoElement, quad: DocQuad, detectSize: {
|
|
2062
|
+
width: number;
|
|
2063
|
+
height: number;
|
|
2064
|
+
}) => Promise<DocStill>;
|
|
2065
|
+
/**
|
|
2066
|
+
* Take the ImageCapture photo when the browser has one (default `true`; never
|
|
2067
|
+
* on LOW devices or with the torch on). `false`: always the video frame.
|
|
2068
|
+
*/
|
|
2069
|
+
photoCapture?: boolean;
|
|
2070
|
+
/** Ask the opened track for up to 4K (default `true`; never on LOW devices). */
|
|
2071
|
+
upgradeResolution?: boolean;
|
|
2072
|
+
/**
|
|
2073
|
+
* Server camera tuning (`capture.document.camera`, resolved with
|
|
2074
|
+
* resolveDocCameraTuning): lens selection, focus, zoom and the still focus gate.
|
|
2075
|
+
*/
|
|
2076
|
+
camera?: DocCameraTuning;
|
|
2077
|
+
/**
|
|
2078
|
+
* Guide-region lock thresholds (resolveGuideLockTuning over `capture.document.guide`).
|
|
2079
|
+
*/
|
|
2080
|
+
guideLock?: GuideLockTuning;
|
|
2081
|
+
/**
|
|
2082
|
+
* The on-screen guide: `rect` normalized in a view of aspect `viewAspect`
|
|
2083
|
+
* (width / height) showing the video with `object-fit: cover`. Default: a
|
|
2084
|
+
* centered guide of the format's aspect spanning 86 % of the frame width.
|
|
2085
|
+
*/
|
|
2086
|
+
guide?: {
|
|
2087
|
+
viewAspect: number;
|
|
2088
|
+
rect: GuideRect;
|
|
2089
|
+
};
|
|
2090
|
+
/** Manual-shutter safety net (resolveDocShutterTuning over `capture.document.scan`). */
|
|
2091
|
+
shutter?: DocShutterTuning;
|
|
2092
|
+
/**
|
|
2093
|
+
* On-device OCR assist (web live MRZ, plan Tasks 6b + 6): the text lock on card faces and live MRZ on MRZ faces,
|
|
2094
|
+
* driven by the wasm engine worker. Absent = today's flow. The host owns the engine (`ocr.setEngine` when ready).
|
|
2095
|
+
*/
|
|
2096
|
+
ocr?: DocAssist;
|
|
2097
|
+
/** (navigator.mediaDevices.enumerateDevices) — lens selection; absent in tests unless injected. */
|
|
2098
|
+
enumerateDevices?: () => Promise<ReadonlyArray<{
|
|
2099
|
+
deviceId: string;
|
|
2100
|
+
label: string;
|
|
2101
|
+
kind?: string;
|
|
2102
|
+
}>>;
|
|
2103
|
+
/** (performance.now) */
|
|
2104
|
+
now?: () => number;
|
|
2105
|
+
/** (setTimeout/clearTimeout) — the cadence pacer. */
|
|
2106
|
+
setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
2107
|
+
clearTimer?: (id: ReturnType<typeof setTimeout>) => void;
|
|
2108
|
+
}
|
|
2109
|
+
type Handler<K extends keyof CaptureSessionDocEvents> = (payload: CaptureSessionDocEvents[K]) => void;
|
|
2110
|
+
declare class CaptureSessionDoc {
|
|
2111
|
+
private readonly opts;
|
|
2112
|
+
private readonly gate;
|
|
2113
|
+
private readonly cadenceMs;
|
|
2114
|
+
private readonly deviceClass;
|
|
2115
|
+
private torchOn;
|
|
2116
|
+
private readonly camera;
|
|
2117
|
+
/** Auto-capture stills refused in a row by the focus gate. */
|
|
2118
|
+
private stillRefusals;
|
|
2119
|
+
/** Zoom applied to the track (1 = none) — telemetry / tests. */
|
|
2120
|
+
zoom: number;
|
|
2121
|
+
private readonly shutterTuning;
|
|
2122
|
+
/** Positioning clock for this side (tick time). */
|
|
2123
|
+
private positioningSince;
|
|
2124
|
+
private detectedSince;
|
|
2125
|
+
/** Last tick a document was seen (a quad, or a card in the guide) — gates the manual shutter. */
|
|
2126
|
+
private lastSeenAt;
|
|
2127
|
+
private readonly guideLock;
|
|
2128
|
+
private readonly guideTuning;
|
|
2129
|
+
private lastGuideQuad;
|
|
2130
|
+
/** The OCR assist fired on the last tick: its crop quad (detect frame) and path, for a host-timed captureNow. */
|
|
2131
|
+
private lastOcrReady;
|
|
2132
|
+
private shutterOffered;
|
|
2133
|
+
private stats;
|
|
2134
|
+
private readonly handlers;
|
|
2135
|
+
private stream;
|
|
2136
|
+
private timer;
|
|
2137
|
+
private state;
|
|
2138
|
+
private lastGuidance;
|
|
2139
|
+
private attempt;
|
|
2140
|
+
private capturing;
|
|
2141
|
+
private readonly feedback;
|
|
2142
|
+
private lastFeedbackKey;
|
|
2143
|
+
/** Last detected quad + detect size (for a host-timed {@link captureNow}). */
|
|
2144
|
+
private lastQuad;
|
|
2145
|
+
private lastDetect;
|
|
2146
|
+
constructor(opts: CaptureSessionDocOptions);
|
|
2147
|
+
on<K extends keyof CaptureSessionDocEvents>(event: K, handler: Handler<K>): () => void;
|
|
2148
|
+
private emit;
|
|
2149
|
+
start(): Promise<void>;
|
|
2150
|
+
/** Restart the gate for another side / a retake (same stream). */
|
|
2151
|
+
rearm(side?: DocSide): void;
|
|
2152
|
+
/**
|
|
2153
|
+
* Take the full-resolution still now (the same best-frame window the gate
|
|
2154
|
+
* uses), emitting `statechange: captured` + `artifacts`. Ignored unless the
|
|
2155
|
+
* session is `positioning` and not already capturing. Without a detected
|
|
2156
|
+
* card the still is still taken (manual shutter).
|
|
2157
|
+
*/
|
|
2158
|
+
captureNow(): Promise<void>;
|
|
2159
|
+
/** PII-free report of the side being (or last) positioned — telemetry. */
|
|
2160
|
+
captureReport(): DocCaptureReport;
|
|
2161
|
+
private ocrReport;
|
|
2162
|
+
private static freshStats;
|
|
2163
|
+
/** The on-screen guide moved (orientation change) — the guide lock measures the new region. */
|
|
2164
|
+
setGuide(guide: {
|
|
2165
|
+
viewAspect: number;
|
|
2166
|
+
rect: GuideRect;
|
|
2167
|
+
} | undefined): void;
|
|
2168
|
+
/** The guide region in this frame (normalized), from the host's view geometry or the default. */
|
|
2169
|
+
private guideRegion;
|
|
2170
|
+
/** New side / retake / restart: the shutter clocks and the per-side report start over. */
|
|
2171
|
+
private resetSide;
|
|
2172
|
+
/** Withdraw the shutter (nothing card-like in view any more). */
|
|
2173
|
+
private withdrawShutter;
|
|
2174
|
+
/**
|
|
2175
|
+
* Drop a zoom > 1× back to 1× (nothing seen for zoomFallbackMs while zoomed).
|
|
2176
|
+
* Best-effort; the guide / quad gates re-measure at the new scale.
|
|
2177
|
+
*/
|
|
2178
|
+
private zoomFallback;
|
|
2179
|
+
private offerShutter;
|
|
2180
|
+
/** The live camera exposes a torch (`MediaTrackCapabilities.torch`). */
|
|
2181
|
+
torchAvailable(): boolean;
|
|
2182
|
+
/** Switch the torch (manual only; errors swallowed — unsupported cameras ignore it). */
|
|
2183
|
+
setTorch(on: boolean): Promise<void>;
|
|
2184
|
+
destroy(): void;
|
|
2185
|
+
/**
|
|
2186
|
+
* Raise the opened track towards DOC_CAMERA_UPGRADE (still-geometry.ts) on the SAME
|
|
2187
|
+
* camera (no lens re-pick). Best-effort: unsupported / refused constraints
|
|
2188
|
+
* keep the 1080p track the stream opened with.
|
|
2189
|
+
*/
|
|
2190
|
+
private upgradeResolution;
|
|
2191
|
+
private focusCaps;
|
|
2192
|
+
/** Continuous AF where offered, then the distance-strategy zoom. Each step is best-effort. */
|
|
2193
|
+
private applyFocusAndZoom;
|
|
2194
|
+
/**
|
|
2195
|
+
* Tap-to-focus at a normalized point of the video frame (0..1). Single-shot
|
|
2196
|
+
* AF there, back to continuous after ~1.5 s. No-op where the camera has no
|
|
2197
|
+
* single-shot focus (iOS Safari keeps its own AF).
|
|
2198
|
+
*/
|
|
2199
|
+
focusAt(x: number, y: number): Promise<void>;
|
|
2200
|
+
private restoreContinuous;
|
|
2201
|
+
/** Single-shot refocus on the document right before the photo (Chrome Android); no-op elsewhere. */
|
|
2202
|
+
private refocusOn;
|
|
2203
|
+
/** The live video track (null in tests / after destroy). */
|
|
2204
|
+
private videoTrack;
|
|
2205
|
+
/**
|
|
2206
|
+
* One still: the ImageCapture photo where usable (first candidate only),
|
|
2207
|
+
* else the native video frame — both cropped to the document.
|
|
2208
|
+
*/
|
|
2209
|
+
private takeStill;
|
|
2210
|
+
private detector;
|
|
2211
|
+
private setState;
|
|
2212
|
+
private scheduleTick;
|
|
2213
|
+
private tick;
|
|
2214
|
+
/**
|
|
2215
|
+
* BEST-FRAME capture window (capture-v2): the first ready tick captures a
|
|
2216
|
+
* candidate immediately, then up to CAPTURE_EXTRA_CANDIDATES more stills are
|
|
2217
|
+
* taken at CAPTURE_CANDIDATE_SPACING_MS while the user holds — each scored
|
|
2218
|
+
* by the live detect-tick sharpness at its capture moment — and the SHARPEST
|
|
2219
|
+
* wins. Beats first-acceptable-frame capture without decoding stills.
|
|
2220
|
+
*/
|
|
2221
|
+
private fire;
|
|
2222
|
+
/**
|
|
2223
|
+
* One OCR-assist tick (plan Tasks 6b + 6): the card region (the drawn guide slot) and the MRZ band (the passport band
|
|
2224
|
+
* quad, else the guide from `bandTop`) in VIDEO pixels; glare / sharpness from this tick's guide measure (cards) or
|
|
2225
|
+
* frame analysis (passports). Sets {@link lastOcrReady} when the assist fires.
|
|
2226
|
+
*/
|
|
2227
|
+
private ocrTick;
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* The passport MRZ band to read, in video pixels, from the detect-frame stroke band (mrz-band.ts): 3 % wider and at
|
|
2231
|
+
* least a fifth of its width tall, centred on it (a TD3 MRZ with margins is ≈ width / 6).
|
|
2232
|
+
*/
|
|
2233
|
+
declare function passportBandOf(b: {
|
|
2234
|
+
x: number;
|
|
2235
|
+
y: number;
|
|
2236
|
+
width: number;
|
|
2237
|
+
height: number;
|
|
2238
|
+
}, sx: number, sy: number, vw: number, vh: number): VideoRect;
|
|
2239
|
+
/** Best-frame window: 2 extra candidates, 260ms apart, ≥8% sharper to replace. */
|
|
2240
|
+
declare const CAPTURE_EXTRA_CANDIDATES = 2;
|
|
2241
|
+
declare const CAPTURE_CANDIDATE_SPACING_MS = 260;
|
|
2242
|
+
|
|
2243
|
+
interface EncodedDocument {
|
|
2244
|
+
blob: Blob;
|
|
2245
|
+
width: number;
|
|
2246
|
+
height: number;
|
|
2247
|
+
}
|
|
2248
|
+
declare function encodeDocumentImage(still: Blob): Promise<EncodedDocument>;
|
|
2249
|
+
|
|
2250
|
+
type ScanLayout = "twoSided" | "singlePage";
|
|
2251
|
+
type ScanSide = "recto" | "verso";
|
|
2252
|
+
type ScanPhase = "framing" | "locking" | "capturing" | "flipCue" | "reaskRecto" | "reading" | "result";
|
|
2253
|
+
/** `blurry`: the photo was refused before any upload (blur gate). */
|
|
2254
|
+
type ScanNotice = "same_face" | "reread" | "technical" | "blurry";
|
|
2255
|
+
interface ScanReport {
|
|
2256
|
+
/** A plausible card / passport band is in view. */
|
|
2257
|
+
present: boolean;
|
|
2258
|
+
/** The gate's fire signal (quality + stability). */
|
|
2259
|
+
ready: boolean;
|
|
2260
|
+
/** Gate progress is high: show "hold still". */
|
|
2261
|
+
locking: boolean;
|
|
2262
|
+
/** Card area / detect-frame area. */
|
|
2263
|
+
areaFrac: number;
|
|
2264
|
+
}
|
|
2265
|
+
interface ScanState {
|
|
2266
|
+
layout: ScanLayout;
|
|
2267
|
+
phase: ScanPhase;
|
|
2268
|
+
captured: {
|
|
2269
|
+
recto?: string;
|
|
2270
|
+
verso?: string;
|
|
2271
|
+
};
|
|
2272
|
+
pending: string[];
|
|
2273
|
+
lastShot?: string;
|
|
2274
|
+
current?: ScanSide;
|
|
2275
|
+
goodSince?: number;
|
|
2276
|
+
lastCaptureAt: number;
|
|
2277
|
+
lastReportAt: number;
|
|
2278
|
+
manualShutter: boolean;
|
|
2279
|
+
notice?: ScanNotice;
|
|
2280
|
+
restPhase: "framing" | "flipCue" | "reaskRecto";
|
|
2281
|
+
readSides: ScanSide[];
|
|
2282
|
+
needFlip: boolean;
|
|
2283
|
+
flipped: boolean;
|
|
2284
|
+
flipTimeoutSpent: boolean;
|
|
2285
|
+
timeoutCapture: boolean;
|
|
2286
|
+
lastShotByTimeout: boolean;
|
|
2287
|
+
awaySince?: number;
|
|
2288
|
+
/** Area of the card when the last photo was taken (flip = shrinks below FLIP_FILL_MIN of it). */
|
|
2289
|
+
captureArea?: number;
|
|
2290
|
+
lastArea?: number;
|
|
2291
|
+
}
|
|
2292
|
+
type ScanEvent = {
|
|
2293
|
+
type: "report";
|
|
2294
|
+
report: ScanReport;
|
|
2295
|
+
at: number;
|
|
2296
|
+
} | {
|
|
2297
|
+
type: "tick";
|
|
2298
|
+
at: number;
|
|
2299
|
+
} | {
|
|
2300
|
+
type: "captured";
|
|
2301
|
+
id: string;
|
|
2302
|
+
at: number;
|
|
2303
|
+
} | {
|
|
2304
|
+
type: "captureFailed";
|
|
2305
|
+
at: number;
|
|
2306
|
+
} | {
|
|
2307
|
+
type: "classified";
|
|
2308
|
+
id: string;
|
|
2309
|
+
side: ScanSide;
|
|
2310
|
+
} | {
|
|
2311
|
+
type: "duplicate";
|
|
2312
|
+
id: string;
|
|
2313
|
+
side: ScanSide;
|
|
2314
|
+
} | {
|
|
2315
|
+
type: "readOk";
|
|
2316
|
+
side: ScanSide;
|
|
2317
|
+
} | {
|
|
2318
|
+
type: "readFailed";
|
|
2319
|
+
side: ScanSide;
|
|
2320
|
+
id?: string;
|
|
2321
|
+
issue: ScanNotice;
|
|
2322
|
+
} | {
|
|
2323
|
+
type: "retake";
|
|
2324
|
+
} | {
|
|
2325
|
+
type: "clockRestart";
|
|
2326
|
+
};
|
|
2327
|
+
/** Lumière values (tuned on the Mattel reference device; retune with the cores). */
|
|
2328
|
+
declare const FLIP_GAP_MS = 250;
|
|
2329
|
+
declare const FLIP_FILL_MIN = 0.6;
|
|
2330
|
+
declare const FLIP_TIMEOUT_MS = 1800;
|
|
2331
|
+
declare const MANUAL_SHUTTER_MS = 6000;
|
|
2332
|
+
declare const NO_REPORT_MS = 2000;
|
|
2333
|
+
/**
|
|
2334
|
+
* Blur gate (Mattel 8f13198; server name `capture.document.scan.blurRefuseSharpnessMax`):
|
|
2335
|
+
* a photo measuring under this inverse-edge-width sharpness is retaken at once.
|
|
2336
|
+
* Real card photos measure ~0.42, dim/rough captures that still read 0.29 and up.
|
|
2337
|
+
*/
|
|
2338
|
+
declare const BLUR_REFUSE_SHARPNESS_MAX = 0.16;
|
|
2339
|
+
declare function initialScanState(layout: ScanLayout, at: number): ScanState;
|
|
2340
|
+
declare function nextScanState(state: ScanState, event: ScanEvent): ScanState;
|
|
2341
|
+
|
|
2342
|
+
interface DeviceReadHint {
|
|
2343
|
+
engine: string;
|
|
2344
|
+
source: "live_mrz";
|
|
2345
|
+
format: "TD1" | "TD2" | "TD3";
|
|
2346
|
+
lineCount: number;
|
|
2347
|
+
checkDigitsValid: true;
|
|
2348
|
+
stillConfirmed?: boolean;
|
|
2349
|
+
/** The MRZ band [x, y, w, h] normalized to the UPLOADED image (the still as encoded: same frame, any scale). */
|
|
2350
|
+
band: [number, number, number, number];
|
|
2351
|
+
digest: string;
|
|
2352
|
+
framesToValid: number;
|
|
2353
|
+
msToValid: number;
|
|
2354
|
+
}
|
|
2355
|
+
interface StillReader {
|
|
2356
|
+
stillRead(bitmap: ImageBitmap, region: [number, number, number, number], detectorLimit?: number): Promise<BandResult>;
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Read the still (the keyframe that is uploaded) and build the hint. `null` when the still read finds no MRZ band (the
|
|
2360
|
+
* band is the hint's only use to the server) or fails. `region` = the part of the still to read (default: the whole).
|
|
2361
|
+
*/
|
|
2362
|
+
declare function buildDeviceReadHint(reader: StillReader, still: Blob, live: LiveMrzCapture, o: {
|
|
2363
|
+
engine: string;
|
|
2364
|
+
detectorLimit?: number;
|
|
2365
|
+
decode?: (b: Blob) => Promise<ImageBitmap>;
|
|
2366
|
+
retryMs?: number;
|
|
2367
|
+
region?: [number, number, number, number];
|
|
2368
|
+
}): Promise<DeviceReadHint | null>;
|
|
2369
|
+
/** A hint promise read at POST time: the value if it already settled, else nothing (the POST never waits). */
|
|
2370
|
+
declare function settledHint(p: Promise<DeviceReadHint | null> | null): () => DeviceReadHint | null;
|
|
2371
|
+
|
|
2372
|
+
interface OcrWorkerLike {
|
|
2373
|
+
postMessage(data: unknown, transfer?: Transferable[]): void;
|
|
2374
|
+
addEventListener(type: "message" | "error" | "messageerror", listener: (event: {
|
|
2375
|
+
data?: unknown;
|
|
2376
|
+
}) => void): void;
|
|
2377
|
+
terminate(): void;
|
|
2378
|
+
}
|
|
2379
|
+
interface OcrEngineOptions {
|
|
2380
|
+
baseUrl: string;
|
|
2381
|
+
salt: string;
|
|
2382
|
+
/** `readTimeoutMs` (default 10 s): a read with no answer means the worker died; the engine is disposed. */
|
|
2383
|
+
tuning: {
|
|
2384
|
+
detectorLimit: number;
|
|
2385
|
+
maxDownloadMs: number;
|
|
2386
|
+
loadTimeoutMs: number;
|
|
2387
|
+
readTimeoutMs?: number;
|
|
2388
|
+
};
|
|
2389
|
+
/**
|
|
2390
|
+
* Where the `@sekyro/web/core/ocr/worker` dist entry is served. Required (or `createWorker`): the host resolves it
|
|
2391
|
+
* the way its bundler emits workers, as for the v3 detect worker; without either, `ready` is `unsupported`.
|
|
2392
|
+
*/
|
|
2393
|
+
workerUrl?: string | URL;
|
|
2394
|
+
/** Host- or test-built worker (e.g. `() => new Worker(new URL("@sekyro/web/core/ocr/worker", import.meta.url), { type: "module" })`). */
|
|
2395
|
+
createWorker?: () => OcrWorkerLike;
|
|
2396
|
+
/** Window to listen for `pagehide` on (default globalThis when it has addEventListener). */
|
|
2397
|
+
page?: {
|
|
2398
|
+
addEventListener(type: "pagehide", l: () => void, o?: {
|
|
2399
|
+
once: boolean;
|
|
2400
|
+
}): void;
|
|
2401
|
+
} | null;
|
|
2402
|
+
}
|
|
2403
|
+
interface OcrEngineClient {
|
|
2404
|
+
/** Resolves with the warm-up info, or a LoadFailure (never rejects). */
|
|
2405
|
+
readonly ready: Promise<ReadyInfo | LoadFailure>;
|
|
2406
|
+
bandRead(bitmap: ImageBitmap, detectorLimit?: number): Promise<BandResult>;
|
|
2407
|
+
stillRead(bitmap: ImageBitmap, region: [number, number, number, number], detectorLimit?: number): Promise<BandResult>;
|
|
2408
|
+
/** Front lock: the detector alone on a card crop (no recognizer); line boxes only. */
|
|
2409
|
+
layoutRead(bitmap: ImageBitmap, detectorLimit: number): Promise<LayoutResult>;
|
|
2410
|
+
readonly busy: boolean;
|
|
2411
|
+
dispose(): void;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/** The engine surface the flow drives (CaptureSessionDoc in production). */
|
|
2415
|
+
type DocEngineLike = Pick<CaptureSessionDoc, "on" | "start" | "rearm" | "destroy" | "captureNow" | "setTorch" | "torchAvailable"> & Partial<Pick<CaptureSessionDoc, "focusAt" | "captureReport" | "setGuide">>;
|
|
2416
|
+
/**
|
|
2417
|
+
* Web live MRZ for a session-bound capture (the step runner): the view's `capture.document` (the server-resolved
|
|
2418
|
+
* `liveMrz` / `frontLock` blocks), the digest salt (the session id), the asset base, the report sink. Absent = off.
|
|
2419
|
+
*/
|
|
2420
|
+
interface DocumentWebOcr {
|
|
2421
|
+
captureDocument: unknown;
|
|
2422
|
+
salt: string;
|
|
2423
|
+
baseUrl: string;
|
|
2424
|
+
/** Fire-and-forget `live_mrz` reports (`POST /hosted/capture-reports`). */
|
|
2425
|
+
reports?: (reports: readonly object[]) => void;
|
|
2426
|
+
/** Test seam / host-built worker. */
|
|
2427
|
+
createWorker?: () => OcrWorkerLike;
|
|
2428
|
+
}
|
|
2429
|
+
/** Seams (tests, the e2e harness, the step runner, Sekyro AI's hosted page) — `@sekyro/web/internal`. */
|
|
2430
|
+
interface DocumentCaptureInternals {
|
|
2431
|
+
createEngine?: (options: CaptureSessionDocOptions) => DocEngineLike | Promise<DocEngineLike>;
|
|
2432
|
+
engineOptions?: Partial<Omit<CaptureSessionDocOptions, "video" | "format" | "side" | "autoCapture">>;
|
|
2433
|
+
encode?: (still: Blob) => Promise<EncodedDocument>;
|
|
2434
|
+
/** Face of a card still (default: MRZ band on a 480 px copy). */
|
|
2435
|
+
classify?: (still: Blob) => Promise<ScanSide>;
|
|
2436
|
+
/**
|
|
2437
|
+
* Photo sharpness for the blur gate (default: blur-gate.ts on the card region
|
|
2438
|
+
* of the decoded still — the last gate quad, padded — at ≤ 1000 px; `-1` =
|
|
2439
|
+
* unknown, never refused). `gate` is the last `checks` geometry.
|
|
2440
|
+
*/
|
|
2441
|
+
measureSharpness?: (still: Blob, gate: LastGate | null) => Promise<number>;
|
|
2442
|
+
/** Decode a still once per shot (default `createImageBitmap`). */
|
|
2443
|
+
decode?: (still: Blob) => Promise<ImageBitmap>;
|
|
2444
|
+
/** Blur-gate threshold (server name `capture.document.scan.blurRefuseSharpnessMax`); `0` disables it. Default 0.16. */
|
|
2445
|
+
blurRefuseSharpnessMax?: number;
|
|
2446
|
+
/** The step runner settles each face from the server verdict (`settle`); standalone captures settle at once. */
|
|
2447
|
+
deferRead?: boolean;
|
|
2448
|
+
now?: () => number;
|
|
2449
|
+
/** Manual-shutter tick period (default 500 ms; 0 = no timer, tests). */
|
|
2450
|
+
tickMs?: number;
|
|
2451
|
+
/** Web live MRZ (plan Task 6): session-bound OCR (the step runner passes the view). */
|
|
2452
|
+
webOcr?: DocumentWebOcr;
|
|
2453
|
+
/**
|
|
2454
|
+
* One engine report per document face, whether or not web OCR is on: `captured` when the face is
|
|
2455
|
+
* delivered, `abandoned` when the capture is destroyed mid-face after the engine ticked. A throwing
|
|
2456
|
+
* callback never reaches the capture.
|
|
2457
|
+
*/
|
|
2458
|
+
onFaceReport?: (report: DocCaptureReport, outcome: "captured" | "abandoned") => void;
|
|
2459
|
+
}
|
|
2460
|
+
/** What {@link createDocumentCaptureInternal} returns: the public handle plus the runner's hooks. */
|
|
2461
|
+
interface DocumentCaptureController {
|
|
2462
|
+
handle: DocumentCaptureHandle;
|
|
2463
|
+
/** Upload / check progress of the current face (the step runner). */
|
|
2464
|
+
setFlowState(state: "uploading" | "checking" | "done"): void;
|
|
2465
|
+
/** Server verdict for a face (deferred reads only). */
|
|
2466
|
+
settle(side: DocumentSide, verdict: {
|
|
2467
|
+
ok: true;
|
|
2468
|
+
} | {
|
|
2469
|
+
ok: false;
|
|
2470
|
+
issue: ScanNotice;
|
|
2471
|
+
}): void;
|
|
2472
|
+
/**
|
|
2473
|
+
* The last live-MRZ capture's `deviceRead` hint if it is ready NOW (the doc_ocr POST never waits); null otherwise.
|
|
2474
|
+
* Take it once per submission so a same-key replay sends the identical body.
|
|
2475
|
+
*/
|
|
2476
|
+
takeDeviceRead(doc: CapturedDocument): DeviceReadHint | null;
|
|
2477
|
+
}
|
|
2478
|
+
/** After this many same-face drops in a row, the photo is kept (labelled as expected) and the server decides. */
|
|
2479
|
+
declare const SAME_FACE_MAX_DROPS = 2;
|
|
2480
|
+
/** The last gate geometry: the quad in detect-frame coordinates and that frame's size. */
|
|
2481
|
+
interface LastGate {
|
|
2482
|
+
quad: DocumentQuad;
|
|
2483
|
+
frameWidth: number;
|
|
2484
|
+
frameHeight: number;
|
|
2485
|
+
/** The quad is the engine's `docRegion`, already in the (cropped) still's pixels. */
|
|
2486
|
+
inStill?: boolean;
|
|
2487
|
+
}
|
|
2488
|
+
/** {@link createDocumentCapture} with seams and the step runner's hooks. */
|
|
2489
|
+
declare function createDocumentCaptureInternal(options: DocumentCaptureOptions, internals: DocumentCaptureInternals): DocumentCaptureController;
|
|
2490
|
+
|
|
2491
|
+
declare const LANDMARKER_MODEL = "face_landmarker.task";
|
|
2492
|
+
interface LandmarkedFace {
|
|
2493
|
+
/** 478 normalised (0..1) landmarks. */
|
|
2494
|
+
landmarks: Point[];
|
|
2495
|
+
blendshapes: Record<string, number> | null;
|
|
2496
|
+
/** 4×4 facial transformation matrix, column-major. */
|
|
2497
|
+
matrix: number[] | null;
|
|
2498
|
+
}
|
|
2499
|
+
interface LandmarkerLike {
|
|
2500
|
+
/** Detect on a frame; `timestampMs` must increase. Never throws (returns no face). */
|
|
2501
|
+
detect(source: CanvasImageSource, timestampMs: number): LandmarkedFace[];
|
|
2502
|
+
close(): void;
|
|
2503
|
+
}
|
|
2504
|
+
interface TasksVisionResult {
|
|
2505
|
+
faceLandmarks?: Array<Array<{
|
|
2506
|
+
x: number;
|
|
2507
|
+
y: number;
|
|
2508
|
+
}>>;
|
|
2509
|
+
faceBlendshapes?: Array<{
|
|
2510
|
+
categories: Array<{
|
|
2511
|
+
categoryName: string;
|
|
2512
|
+
score: number;
|
|
2513
|
+
}>;
|
|
2514
|
+
}>;
|
|
2515
|
+
facialTransformationMatrixes?: Array<{
|
|
2516
|
+
data: number[];
|
|
2517
|
+
}>;
|
|
2518
|
+
}
|
|
2519
|
+
/** Map a tasks-vision result to plain faces (exported for tests). */
|
|
2520
|
+
declare function facesOf(result: TasksVisionResult): LandmarkedFace[];
|
|
2521
|
+
declare function createFaceLandmarker(assetBaseUrl: string): Promise<LandmarkerLike>;
|
|
2522
|
+
|
|
2523
|
+
interface SegmenterLike {
|
|
2524
|
+
/** Person confidence of a frame; `timestampMs` must increase. Never throws (`null` = no mask). */
|
|
2525
|
+
segment(source: CanvasImageSource, timestampMs: number): PersonMask | null;
|
|
2526
|
+
close(): void;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
/** Long edge of the analysis frame the landmarker and the pixel heuristics run on. */
|
|
2530
|
+
declare const ANALYSIS_LONG_EDGE = 640;
|
|
2531
|
+
declare const PORTRAIT_JPEG_QUALITY = 0.92;
|
|
2532
|
+
/** A drawn frame: the pixels the gate measures and a source the landmarker reads. */
|
|
2533
|
+
interface GrabbedFrame {
|
|
2534
|
+
source: CanvasImageSource;
|
|
2535
|
+
width: number;
|
|
2536
|
+
height: number;
|
|
2537
|
+
/** RGBA pixels (`null` when the canvas cannot be read — heuristics are skipped). */
|
|
2538
|
+
pixels: {
|
|
2539
|
+
data: Uint8ClampedArray;
|
|
2540
|
+
width: number;
|
|
2541
|
+
height: number;
|
|
2542
|
+
} | null;
|
|
2543
|
+
}
|
|
2544
|
+
/** Construction seams (tests / Sekyro AI's hosted page). */
|
|
2545
|
+
interface PortraitCaptureInternals {
|
|
2546
|
+
createLandmarker?: (assetBaseUrl: string) => Promise<LandmarkerLike>;
|
|
2547
|
+
/** The background-uniformity check's selfie segmenter (default: MediaPipe, lazy). */
|
|
2548
|
+
createSegmenter?: (assetBaseUrl: string) => Promise<SegmenterLike>;
|
|
2549
|
+
getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
2550
|
+
/** Draw the video scaled to `longEdge` (analysis) or at full size (`null`). */
|
|
2551
|
+
grabFrame?: (video: HTMLVideoElement, longEdge: number | null) => GrabbedFrame | null;
|
|
2552
|
+
/** Cut `region` out of a full-size frame, scale to `outHeight`, encode JPEG. */
|
|
2553
|
+
encodeRegion?: (frame: GrabbedFrame, region: Box, outHeight: number, quality: number) => Promise<Blob>;
|
|
2554
|
+
now?: () => number;
|
|
2555
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
2556
|
+
clearTimer?: (id: unknown) => void;
|
|
2557
|
+
}
|
|
2558
|
+
/** Handle + the hooks the plan runner uses to drive the post-capture states. */
|
|
2559
|
+
interface PortraitCaptureController {
|
|
2560
|
+
handle: PortraitCaptureHandle;
|
|
2561
|
+
setFlowState(state: Extract<PortraitCaptureState, "uploading" | "checking" | "done">): void;
|
|
2562
|
+
}
|
|
2563
|
+
/** The observation of one landmarked frame. `scale` maps analysis px → source px. */
|
|
2564
|
+
declare function observe(faces: readonly LandmarkedFace[], frame: GrabbedFrame, sourceWidth: number, sourceHeight: number): IcaoFrameObservation;
|
|
2565
|
+
declare function createPortraitCaptureInternal(options: PortraitCaptureOptions, internals?: PortraitCaptureInternals): PortraitCaptureController;
|
|
2566
|
+
|
|
2567
|
+
/**
|
|
2568
|
+
* The client surface the flow needs — `HostedClient` or a test fake. D8: the
|
|
2569
|
+
* live-camera capture proof methods are optional (a fake / an older bridge
|
|
2570
|
+
* without them presigns without a proof).
|
|
2571
|
+
*/
|
|
2572
|
+
type HostedClientLike = Pick<HostedClient, "getSession" | "recordConsent" | "selectMethod" | "presign" | "uploadToSpaces" | "completeMedia" | "submitCheck"> & Partial<Pick<HostedClient, "presignCapture" | "armCapture">> & Partial<Pick<HostedClient, "selectContact" | "confirmIdentity" | "identityPortrait">>;
|
|
2573
|
+
/** The presign policy allowlist for `kind: 'clip'` (media.policy.ts mirror). */
|
|
2574
|
+
declare const CLIP_MIME_ALLOWLIST: readonly string[];
|
|
2575
|
+
/** Same-key transport replays AFTER the first try, and the pause between them. */
|
|
2576
|
+
declare const TRANSPORT_REPLAY_ATTEMPTS = 2;
|
|
2577
|
+
declare const TRANSPORT_REPLAY_DELAY_MS = 1000;
|
|
2578
|
+
/**
|
|
2579
|
+
* `503 inference_unavailable` (profile v2 step 4 — request-level: no state
|
|
2580
|
+
* change, no attempt used, not billed): retries after `retryAfterSeconds`
|
|
2581
|
+
* (default {@link INFERENCE_RETRY_DEFAULT_S}, capped at 30 s), at most
|
|
2582
|
+
* {@link INFERENCE_RETRY_ATTEMPTS} times, each under a FRESH key (each retry
|
|
2583
|
+
* is its own logical attempt; the backend never caches a 5xx envelope).
|
|
2584
|
+
*/
|
|
2585
|
+
declare const INFERENCE_RETRY_ATTEMPTS = 3;
|
|
2586
|
+
declare const INFERENCE_RETRY_DEFAULT_S = 10;
|
|
2587
|
+
/** A Turnstile challenge failed client-side (load / render / error-callback / timeout). */
|
|
2588
|
+
declare class AttestationError extends Error {
|
|
2589
|
+
constructor(message: string);
|
|
2590
|
+
}
|
|
2591
|
+
/** One logical submission: receives the client and ITS idempotency key. */
|
|
2592
|
+
type SubmitExecutor = (client: HostedClientLike, idempotencyKey: string) => Promise<SessionView>;
|
|
2593
|
+
interface SubmitRuntime {
|
|
2594
|
+
client: HostedClientLike;
|
|
2595
|
+
/** Force-record consent (the server just said it is missing). */
|
|
2596
|
+
ensureConsent(opts: {
|
|
2597
|
+
force: true;
|
|
2598
|
+
}): Promise<void>;
|
|
2599
|
+
sleep(ms: number): Promise<void>;
|
|
2600
|
+
uuid(): string;
|
|
2601
|
+
/** An `inference_unavailable` retry is about to wait (the `busy` event). */
|
|
2602
|
+
onBusy?(info: VerificationBusy): void;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Run one logical submission (ported use-submit semantics):
|
|
2606
|
+
* - a fresh `crypto.randomUUID()` key per logical submission;
|
|
2607
|
+
* - a transport-class failure replays the SAME executor under the SAME key
|
|
2608
|
+
* (bounded) — the backend's IdempotencyInterceptor returns the first
|
|
2609
|
+
* response instead of burning another attempt;
|
|
2610
|
+
* - ONE `consent_required` recovery: force-record consent and re-run under a
|
|
2611
|
+
* FRESH key (the 409 envelope is idempotency-cached under the old one);
|
|
2612
|
+
* - ONE attestation retry (client-side Turnstile failure or a server
|
|
2613
|
+
* `attestation_invalid`): `onAttestationInvalid` drops the cached token and
|
|
2614
|
+
* the executor re-runs under a fresh key;
|
|
2615
|
+
* - up to {@link INFERENCE_RETRY_ATTEMPTS} `inference_unavailable` retries
|
|
2616
|
+
* after `retryAfterSeconds`, each under a FRESH key and with a fresh
|
|
2617
|
+
* attestation (`onAttestationInvalid` — the Turnstile token was spent).
|
|
2618
|
+
* Throws the final error otherwise.
|
|
2619
|
+
*/
|
|
2620
|
+
declare function runSubmission(rt: SubmitRuntime, executor: SubmitExecutor, hooks?: {
|
|
2621
|
+
onAttestationInvalid?: () => void;
|
|
2622
|
+
}): Promise<SessionView>;
|
|
2623
|
+
interface LivenessFlowDeps extends SubmitRuntime {
|
|
2624
|
+
artifacts: CapturedArtifacts;
|
|
2625
|
+
/** Fresh attestation per call (Turnstile execute, or `{sandbox: true}`). */
|
|
2626
|
+
getAttestation(): Promise<AttestationInput>;
|
|
2627
|
+
/** Latest hosted view (frontier decisions). */
|
|
2628
|
+
getView(): HostedSessionView;
|
|
2629
|
+
/** Re-GET the view from the server (after a RESYNC code) and fold it in. */
|
|
2630
|
+
refreshView(): Promise<HostedSessionView>;
|
|
2631
|
+
/** Fold a submit response into the view. */
|
|
2632
|
+
applySubmit(view: SessionView, submitted: CheckType): void;
|
|
2633
|
+
/** Flow-state hook for the capture handle. */
|
|
2634
|
+
onPhase(phase: "uploading" | "checking"): void;
|
|
2635
|
+
/** The verification was destroyed (the only thing that cancels a chain). */
|
|
2636
|
+
isCancelled(): boolean;
|
|
2637
|
+
/**
|
|
2638
|
+
* The keyframe (face_match probe) was promoted — the plan runner keeps the
|
|
2639
|
+
* key for the profile v2 §4.4 wrong-number re-entry (face_match resubmitted
|
|
2640
|
+
* with the same image). Optional; observation only.
|
|
2641
|
+
*/
|
|
2642
|
+
onKeyframePromoted?(key: string): void;
|
|
2643
|
+
}
|
|
2644
|
+
type LivenessFlowOutcome = {
|
|
2645
|
+
kind: "settled";
|
|
2646
|
+
} | {
|
|
2647
|
+
kind: "resync";
|
|
2648
|
+
} | {
|
|
2649
|
+
kind: "unavailable";
|
|
2650
|
+
reason: SdkFailureReason | "mobile_required";
|
|
2651
|
+
} | {
|
|
2652
|
+
kind: "cancelled";
|
|
2653
|
+
};
|
|
2654
|
+
/**
|
|
2655
|
+
* Run the biometric submissions the CURRENT frontier allows, re-evaluating
|
|
2656
|
+
* after every submit and after every RESYNC re-GET:
|
|
2657
|
+
* - frontier `liveness` → clip upload + attestation + `liveness` (once per capture);
|
|
2658
|
+
* - frontier `face_match` → keyframe upload + `face_match` (once per capture),
|
|
2659
|
+
* allowed when liveness was NOT part of this capture (it had already
|
|
2660
|
+
* settled — any state, incl. `failed`: the selfie-fallback case, where this
|
|
2661
|
+
* capture only feeds face_match) or settled chainable from this capture
|
|
2662
|
+
* (passed / review / processing / skipped — or absent);
|
|
2663
|
+
* - anything else (terminal view, a non-biometric frontier, a check this
|
|
2664
|
+
* capture already submitted) → `settled` for the caller to map the view.
|
|
2665
|
+
* A RESYNC code re-GETs the view and re-enters (at most {@link RESYNC_REENTRY_MAX}
|
|
2666
|
+
* times); the final RESYNC surfaces as `resync` for the caller to map.
|
|
2667
|
+
*/
|
|
2668
|
+
declare function runLivenessFlow(deps: LivenessFlowDeps): Promise<LivenessFlowOutcome>;
|
|
2669
|
+
|
|
2670
|
+
/** The monitor never runs the landmarker faster than this (it shares the main thread with the liveness detector). */
|
|
2671
|
+
declare const LIVE_PHOTO_MAX_HZ = 6;
|
|
2672
|
+
/** No liveness gate tick for this long → the attempt ended: the monitor sleeps. */
|
|
2673
|
+
declare const LIVE_PHOTO_IDLE_MS = 1500;
|
|
2674
|
+
/** The still of one live-photo capture. */
|
|
2675
|
+
interface LivePhotoStill {
|
|
2676
|
+
image: Blob;
|
|
2677
|
+
width: number;
|
|
2678
|
+
height: number;
|
|
2679
|
+
/** The ICAO gate at the moment the capture fired (the `gate` block of portrait_icao). */
|
|
2680
|
+
gate: ReturnType<IcaoGate["summary"]>;
|
|
2681
|
+
}
|
|
2682
|
+
interface LivePhotoKitOptions {
|
|
2683
|
+
/** The capture's video (default: the one the liveness engine measures). */
|
|
2684
|
+
video?: HTMLVideoElement;
|
|
2685
|
+
/** `capture.icao` of the view (coaching limits), and `capture.tuningVersion`. */
|
|
2686
|
+
tuning: IcaoCaptureTuning | null;
|
|
2687
|
+
tuningVersion: string | null;
|
|
2688
|
+
/** The session's ICAO policy (`icaoPolicyOf(view).policy`): `alert` never holds the clip past `alertGraceMs`. Default `reject`. */
|
|
2689
|
+
policy?: "reject" | "alert";
|
|
2690
|
+
still: {
|
|
2691
|
+
minLongSidePx: number;
|
|
2692
|
+
jpegQuality: number;
|
|
2693
|
+
};
|
|
2694
|
+
/** Detector assets base (the landmarker sits next to the liveness wasm); default `/models-v3`. */
|
|
2695
|
+
assetBaseUrl?: string;
|
|
2696
|
+
}
|
|
2697
|
+
/** Test seams. */
|
|
2698
|
+
interface LivePhotoKitInternals {
|
|
2699
|
+
createLandmarker?: (assetBaseUrl: string) => Promise<LandmarkerLike>;
|
|
2700
|
+
/** The background-uniformity check's segmenter (default: MediaPipe selfie segmenter, lazy). */
|
|
2701
|
+
createSegmenter?: (assetBaseUrl: string) => Promise<SegmenterLike>;
|
|
2702
|
+
grabFrame?: (video: HTMLVideoElement, longEdge: number | null) => GrabbedFrame | null;
|
|
2703
|
+
/** Encode the full frame as JPEG (unmirrored). */
|
|
2704
|
+
encode?: (frame: GrabbedFrame, quality: number) => Promise<Blob>;
|
|
2705
|
+
/** The engine's default face-crop quality (canvas). */
|
|
2706
|
+
quality?: (video: HTMLVideoElement, box: {
|
|
2707
|
+
x: number;
|
|
2708
|
+
y: number;
|
|
2709
|
+
width: number;
|
|
2710
|
+
height: number;
|
|
2711
|
+
}) => V3GateQuality;
|
|
2712
|
+
now?: () => number;
|
|
2713
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
2714
|
+
clearTimer?: (id: unknown) => void;
|
|
2715
|
+
}
|
|
2716
|
+
/** ICAO coaching while the liveness gate waits (the `portrait.coach.<coach>` copy). */
|
|
2717
|
+
interface LivePhotoGate {
|
|
2718
|
+
/** The ICAO conditions hold: the liveness gate may fill. */
|
|
2719
|
+
holding: boolean;
|
|
2720
|
+
update: IcaoGateUpdate | null;
|
|
2721
|
+
}
|
|
2722
|
+
interface LivePhotoKit {
|
|
2723
|
+
/** Seams for the liveness engine (`LivenessCaptureInternals.engineOptions`). */
|
|
2724
|
+
engineOptions: {
|
|
2725
|
+
measureQuality: (video: HTMLVideoElement, box: {
|
|
2726
|
+
x: number;
|
|
2727
|
+
y: number;
|
|
2728
|
+
width: number;
|
|
2729
|
+
height: number;
|
|
2730
|
+
}) => V3GateQuality;
|
|
2731
|
+
telemetry: (event: V3TelemetryEvent) => void;
|
|
2732
|
+
};
|
|
2733
|
+
/** The client with `input.portraitIcao` added to the liveness submission. */
|
|
2734
|
+
decorate<C extends HostedClientLike>(client: C): C;
|
|
2735
|
+
/** ICAO coaching updates (while the liveness gate is live). */
|
|
2736
|
+
onGate(handler: (gate: LivePhotoGate) => void): () => void;
|
|
2737
|
+
/** The portrait_icao body of the last liveness submission (outage fallback: submit it directly). */
|
|
2738
|
+
lastPortraitInput(): PortraitIcaoInput | null;
|
|
2739
|
+
destroy(): void;
|
|
2740
|
+
}
|
|
2741
|
+
declare function createLivePhotoKit(options: LivePhotoKitOptions, internals?: LivePhotoKitInternals): LivePhotoKit;
|
|
2742
|
+
|
|
2743
|
+
/** Production API origin — the `apiOrigin` default. */
|
|
2744
|
+
declare const DEFAULT_API_ORIGIN = "https://api.sekyro.ai";
|
|
2745
|
+
/** Pause before the single `registry_unavailable` re-submit. */
|
|
2746
|
+
declare const REGISTRY_RETRY_DELAY_MS = 5000;
|
|
2747
|
+
/** Bounded poll while a biometric verdict is still `processing` after the submits. */
|
|
2748
|
+
declare const PROCESSING_POLL_DELAY_MS = 1500;
|
|
2749
|
+
declare const PROCESSING_POLL_MAX = 10;
|
|
2750
|
+
/**
|
|
2751
|
+
* Construction seams — tests and Sekyro AI's own hosted page (`@sekyro/web/internal`).
|
|
2752
|
+
* Not part of the partner contract.
|
|
2753
|
+
*/
|
|
2754
|
+
interface VerificationInternals {
|
|
2755
|
+
/** Replace the HTTP client (default: `new HostedClient({...})`). */
|
|
2756
|
+
client?: HostedClientLike;
|
|
2757
|
+
/**
|
|
2758
|
+
* Replace the Turnstile challenge (default: `executeTurnstile`, loaded on first use). The
|
|
2759
|
+
* `signal` aborts when the verification is destroyed (M1).
|
|
2760
|
+
*/
|
|
2761
|
+
getTurnstileToken?: (siteKey: string, opts: {
|
|
2762
|
+
signal: AbortSignal;
|
|
2763
|
+
}) => Promise<string>;
|
|
2764
|
+
/** Capture-engine seams forwarded to {@link createLivenessCaptureInternal}. */
|
|
2765
|
+
capture?: LivenessCaptureInternals;
|
|
2766
|
+
sleep?: (ms: number) => Promise<void>;
|
|
2767
|
+
uuid?: () => string;
|
|
2768
|
+
/** Wall clock for the plan runner (OTP resend time); default `Date.now`. */
|
|
2769
|
+
now?: () => number;
|
|
2770
|
+
/** DocumentCapture seams for the runner's document step (engine, encode, classify, blur gate). */
|
|
2771
|
+
document?: DocumentCaptureInternals;
|
|
2772
|
+
/** PortraitCapture seams for the runner's ICAO portrait step (landmarker, camera, encode). */
|
|
2773
|
+
portrait?: PortraitCaptureInternals;
|
|
2774
|
+
/** Live-photo kit seams for the runner's one-capture Step 3 (landmarker, frames, encode). */
|
|
2775
|
+
livePhoto?: LivePhotoKitInternals;
|
|
2776
|
+
}
|
|
2777
|
+
/** `crypto.randomUUID()` with a v4 fallback for hosts that lack it. */
|
|
2778
|
+
declare function randomUuid(): string;
|
|
2779
|
+
/**
|
|
2780
|
+
* Create a verification bound to one session (spec §3.1):
|
|
2781
|
+
* `consent()` → `startRegistry()` → `liveness({video})` → `on('result')`.
|
|
2782
|
+
*/
|
|
2783
|
+
declare function createVerification(options: VerificationOptions): Verification;
|
|
2784
|
+
/** {@link createVerification} with construction seams (tests / Sekyro AI-internal hosts). */
|
|
2785
|
+
declare function createVerificationInternal(options: VerificationOptions, internals: VerificationInternals): Verification;
|
|
2786
|
+
|
|
2787
|
+
export { type LivenessStartOptions as $, type AcceptedDocumentsLike as A, type DocumentReticle as B, type CameraFacing as C, type DocumentCaptureOptions as D, type DocumentScanPhase as E, type DocumentScanUpdate as F, type DocumentSide as G, type DocumentSnapshot as H, type DocumentStepHandle as I, type EidSnapshot as J, type EidStepHandle as K, type EmailOtpSnapshot as L, type EmailOtpStepHandle as M, type EmailSource as N, type GateHint as O, type PortraitCaptureOptions as P, type GateUpdate as Q, ISSUER_CODES as R, type IdentitySummarySnapshot as S, type IdentitySummaryStepHandle as T, type LivePhotoGateUpdate as U, type LivePhotoPlan as V, type LivenessAssetsStatus as W, type LivenessCaptureEvents as X, type LivenessCaptureHandle as Y, type LivenessCaptureOptions as Z, type LivenessSnapshot as _, type DocumentCaptureHandle as a, ANALYSIS_LONG_EDGE as a$, type LivenessStepHandle as a0, type MethodSnapshot as a1, type MethodStepHandle as a2, type MobileRequiredSnapshot as a3, type MobileRequiredStepHandle as a4, type OtpChannel as a5, type OtpNumberSource as a6, PLATFORM_UNSUPPORTED as a7, type PhoneOtpSnapshot as a8, type PhoneOtpStepHandle as a9, type VerificationOptions as aA, type VerificationResult as aB, type VerificationStatus as aC, type VerificationStep as aD, WEB_IDENTITY_METHODS as aE, acceptedIssuers as aF, acceptedTypesFor as aG, createLivenessCapture as aH, createVerification as aI, deriveStep as aJ, flagOf as aK, getLivenessAssetsStatus as aL, preloadLivenessAssets as aM, releaseLivenessAssets as aN, resolveAcceptedDocuments as aO, sectionOf as aP, subscribeLivenessAssets as aQ, type DocCatalogEntry as aR, type LiveMrzTuning as aS, type TextLockTuning as aT, type OcrEngineClient as aU, type DocAssistReport as aV, type ReadyInfo as aW, type LoadFailure as aX, type OcrEngineOptions as aY, type SubmitRuntime as aZ, type HostedClientLike as a_, type PortraitCaptureEvents as aa, type PortraitCaptureState as ab, type PortraitGateUpdate as ac, type PortraitSnapshot as ad, type PortraitStepHandle as ae, type PreloadLivenessAssetsOptions as af, type ProcessingSnapshot as ag, type ProcessingStepHandle as ah, type ReasonCode as ai, type RegistrySnapshot as aj, type RegistrySource as ak, type RegistryStepHandle as al, type ResultDisplay as am, type ResultSnapshot as an, type ResultStepHandle as ao, type SdkFailureReason as ap, type StepAttempts as aq, type StepContext as ar, type StepSnapshot as as, type UnsupportedReason as at, type UnsupportedSnapshot as au, type UnsupportedStepHandle as av, type Verification as aw, type VerificationBusy as ax, type VerificationEvents as ay, type VerificationLocale as az, type PortraitCaptureHandle as b, INFERENCE_RETRY_ATTEMPTS as b$, ASPECT_BANDS as b0, AttestationError as b1, BLUR_REFUSE_SHARPNESS_MAX as b2, type BlurStatus as b3, CAPTURE_CANDIDATE_SPACING_MS as b4, CAPTURE_EXTRA_CANDIDATES as b5, CLIP_MIME_ALLOWLIST as b6, type CaptureEngine as b7, CaptureSessionDoc as b8, type CaptureSessionDocEvents as b9, type DocGateThresholds as bA, type DocGateTick as bB, type DocGateTickInput as bC, DocGateV1 as bD, type DocQuad as bE, type DocQuadPoint as bF, type DocShutterReason as bG, type DocShutterTuning as bH, type DocSide as bI, type DocumentCaptureController as bJ, type DocumentCaptureInternals as bK, EMPTY_GUIDE_METRICS as bL, type EncodedDocument as bM, FLIP_FILL_MIN as bN, FLIP_GAP_MS as bO, FLIP_TIMEOUT_MS as bP, type FocusCaps as bQ, type FramingStatus as bR, GUIDE_LOCK_DEFAULTS as bS, type GlareStatus as bT, type GrabbedFrame as bU, type GuideBlocker as bV, GuideLock as bW, type GuideLockTick as bX, type GuideLockTuning as bY, type GuideMetrics as bZ, type GuideRect as b_, type CaptureSessionDocOptions as ba, DOC_ANALYSIS_DEFAULTS as bb, DOC_CADENCE_HZ as bc, DOC_CAMERA_TUNING_DEFAULTS as bd, DOC_CATALOG as be, DOC_DETECT_WIDTH as bf, DOC_FEEDBACK_STATES as bg, DOC_GATE_V1_DEFAULTS as bh, DOC_SHUTTER_DEFAULTS as bi, type DeviceReadHint as bj, type DocArtifacts as bk, DocAssist as bl, type DocAssistEngine as bm, type DocAssistTick as bn, type DocCameraTuning as bo, type DocCaptureReport as bp, type DocCaptureVia as bq, type DocChecksPayload as br, type DocEngineLike as bs, DocFeedbackAggregator as bt, type DocFeedbackKey as bu, type DocFeedbackState as bv, type DocFormat as bw, type DocFrameAnalysis as bx, type DocFrameMeasures as by, type DocGateQuality as bz, type CaptureChannel as c, facesOf as c$, INFERENCE_RETRY_DEFAULT_S as c0, LANDMARKER_MODEL as c1, LENS_STORAGE_KEY as c2, LENS_TRY_MAX as c3, LIVE_MRZ_DEFAULTS as c4, LIVE_PHOTO_IDLE_MS as c5, LIVE_PHOTO_MAX_HZ as c6, type LandmarkedFace as c7, type LandmarkerLike as c8, type LensKind as c9, SAME_FACE_MAX_DROPS as cA, type ScanEvent as cB, type ScanLayout as cC, type ScanNotice as cD, type ScanPhase as cE, type ScanReport as cF, type ScanSide as cG, type ScanState as cH, type SubmitExecutor as cI, TEXT_LOCK_DEFAULTS as cJ, TRANSPORT_REPLAY_ATTEMPTS as cK, TRANSPORT_REPLAY_DELAY_MS as cL, type VerificationInternals as cM, analyzeDocFrame as cN, analyzeGuide as cO, aspectBand as cP, aspectFits as cQ, buildDeviceReadHint as cR, coverPointToVideo as cS, createDocumentCaptureInternal as cT, createFaceLandmarker as cU, createLivePhotoKit as cV, createLivenessCaptureInternal as cW, createPortraitCaptureInternal as cX, createVerificationInternal as cY, docZoomFor as cZ, encodeDocumentImage as c_, type LensSource as ca, type LightingStatus as cb, type LiveMrzCapture as cc, type LiveMrzState as cd, type LivePhotoGate as ce, type LivePhotoKit as cf, type LivePhotoKitInternals as cg, type LivePhotoKitOptions as ch, type LivePhotoStill as ci, type LivenessCaptureInternalHandle as cj, type LivenessCaptureInternals as ck, type LivenessFlowDeps as cl, type LivenessFlowOutcome as cm, MANUAL_SHUTTER_MS as cn, MR_DOC_CATALOG as co, NO_REPORT_MS as cp, type OpenDocCameraDeps as cq, PORTRAIT_JPEG_QUALITY as cr, PRELOAD_BLAZE_INIT_TIMEOUT_MS as cs, PRELOAD_BUDGET_MS as ct, PROCESSING_POLL_DELAY_MS as cu, PROCESSING_POLL_MAX as cv, type PortraitCaptureController as cw, type PortraitCaptureInternals as cx, REGISTRY_RETRY_DELAY_MS as cy, type ReticleType as cz, type CaptureEngineId as d, feedbackVoteOf as d0, focusOk as d1, guideQuad as d2, guideRegionInFrame as d3, initialScanState as d4, lensKindOf as d5, mapEngineState as d6, mapErrorKind as d7, mapGuidanceHint as d8, nextScanState as d9, writeStoredLens as dA, observe as da, openDocCamera as db, passportBandOf as dc, portraitMatchStep as dd, preloadedDetectorFactory as de, quadAreaFrac as df, quadBounds as dg, quadIou as dh, quadTiltRatio as di, randomUuid as dj, rankBackCameras as dk, readStoredLens as dl, resetLivenessAssetsForTests as dm, resolveDocCameraTuning as dn, resolveDocShutterTuning as dp, resolveGuideLockTuning as dq, runLivenessFlow as dr, runSubmission as ds, scannerFormatFor as dt, setPreloadLoaderForTests as du, settledHint as dv, singleShotFocusAt as dw, specularFraction as dx, supportsFocusMode as dy, warmDocDetector as dz, type CaptureError as e, type CaptureErrorKind as f, type CaptureState as g, type CapturedArtifacts as h, type CapturedDocument as i, type CapturedPortrait as j, type ContactChoiceSnapshot as k, type ContactChoiceStepHandle as l, DEFAULT_API_ORIGIN as m, DOCUMENT_TYPE_ORDER as n, type DeriveStepEnv as o, type DocumentCaptureEvents as p, type DocumentCaptureState as q, type DocumentFeedback as r, type DocumentFeedbackKey as s, type DocumentGateUpdate as t, type DocumentGuide as u, type DocumentGuideMeasure as v, type DocumentLiveMrzOptions as w, type DocumentMrzUpdate as x, type DocumentPoint as y, type DocumentQuad as z };
|