@incodetech/core 0.0.0-dev-20260127-938ff95 → 0.0.0-dev-20260130-5bb04b9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OpenViduLogger-Cu0BjUgP.esm.js +3 -0
- package/dist/{deepsightLoader-Ct3HSIhk.esm.js → deepsightLoader-CGdK3U_s.esm.js} +7 -8
- package/dist/{deepsightService-B8c8aZje.esm.js → deepsightService-BqN04Xvz.esm.js} +53 -5
- package/dist/{types-CRVSv38Q.d.ts → deepsightService-C-8SR9TZ.d.ts} +91 -2
- package/dist/email.d.ts +1 -1
- package/dist/email.esm.js +3 -3
- package/dist/{endpoints-BUsSVoJV.esm.js → endpoints-CmGlc9UG.esm.js} +7 -1
- package/dist/{events-B8ZkhAZo.esm.js → events-CLVaKe2J.esm.js} +1 -1
- package/dist/flow.d.ts +2 -2
- package/dist/flow.esm.js +3 -3
- package/dist/id-DE76bgjS.esm.js +2498 -0
- package/dist/id.d.ts +5 -5
- package/dist/id.esm.js +7 -6
- package/dist/{index-CJMK8K5u.d.ts → index-aadmCg5s.d.ts} +84 -62
- package/dist/index.d.ts +78 -6
- package/dist/index.esm.js +12 -46
- package/dist/{lib-CbAibJlt.esm.js → lib-Cmee0CBZ.esm.js} +1 -1
- package/dist/phone.d.ts +1 -1
- package/dist/phone.esm.js +3 -3
- package/dist/selfie.d.ts +8 -87
- package/dist/selfie.esm.js +39 -19
- package/dist/{src-XSoNGEQW.esm.js → src-BEaVRVtC.esm.js} +6 -2
- package/dist/stats.d.ts +4 -0
- package/dist/stats.esm.js +1 -1
- package/dist/{streamingEvents-J6ffKmJL.esm.js → streamingEvents-EcGvh3bl.esm.js} +13 -4
- package/package.json +1 -1
- package/dist/OpenViduLogger-DyqID_-7.esm.js +0 -3
- package/dist/getDeviceClass-DkfbtsIJ.esm.js +0 -41
- package/dist/id-GPFS1Wo_.esm.js +0 -1827
- /package/dist/{Manager-Co-PsiG9.d.ts → Manager-WTb99jnh.d.ts} +0 -0
- /package/dist/{OpenViduLogger-BLxxXoyF.esm.js → OpenViduLogger-XKcjntVs.esm.js} +0 -0
- /package/dist/{stats-DnU4uUFv.esm.js → stats-onWUN0tY.esm.js} +0 -0
- /package/dist/{types-CMR6NkxW.d.ts → types-CflhN94Q.d.ts} +0 -0
|
@@ -0,0 +1,2498 @@
|
|
|
1
|
+
import { d as addEvent, m as revokeObjectURL, n as eventModuleNames } from "./events-CLVaKe2J.esm.js";
|
|
2
|
+
import { C as createManager, D as isIOS, E as isDesktop, O as isIPhone14OrHigher, S as stopCameraStream, T as isAndroid, b as enumerateVideoDevices, c as OpenViduRecordingProvider, d as BrowserTimerProvider, f as BrowserStorageProvider, h as StreamCanvasCapture, k as isSafari, m as StreamCanvasProcessingSession, n as DEFAULT_ID_CAPTURE_THRESHOLDS, p as BrowserEnvironmentProvider, t as DEFAULT_ID_CAPTURE_MODEL_VERSION, v as IncodeCanvas, x as requestCameraAccess, y as applyTrackConstraints } from "./src-BEaVRVtC.esm.js";
|
|
3
|
+
import { i as getUserAgent, n as getDeviceInfo, r as getWindowDimensions } from "./deepsightService-BqN04Xvz.esm.js";
|
|
4
|
+
import { a as fromPromise, i as fromCallback, n as setup, o as createActor, r as assign, t as endpoints } from "./endpoints-CmGlc9UG.esm.js";
|
|
5
|
+
import { c as getDeviceClass, i as stopRecording$1, n as createRecordingSession, o as checkPermission, r as startRecording, s as requestPermission, t as streamingEvents } from "./streamingEvents-EcGvh3bl.esm.js";
|
|
6
|
+
import { n as getApi, r as getToken, t as api } from "./api-DfRLAneb.esm.js";
|
|
7
|
+
import { t as addDeviceStats } from "./stats-onWUN0tY.esm.js";
|
|
8
|
+
|
|
9
|
+
//#region ../infra/src/capabilities/ITimerCapability.ts
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/internal/session/sessionService.ts
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new onboarding session.
|
|
18
|
+
*
|
|
19
|
+
* @param apiKey - The API key from the Incode dashboard
|
|
20
|
+
* @param options - Session creation options
|
|
21
|
+
* @param signal - Optional AbortSignal for request cancellation
|
|
22
|
+
* @returns The created session with token
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const session = await createSession('your-api-key', {
|
|
27
|
+
* configurationId: 'your-flow-id',
|
|
28
|
+
* language: 'en-US',
|
|
29
|
+
* });
|
|
30
|
+
* console.log(session.token); // Use this token for subsequent API calls
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
async function createSession(apiKey, options, signal) {
|
|
34
|
+
const res = await getApi().post(endpoints.createSession, {
|
|
35
|
+
configurationId: options.configurationId,
|
|
36
|
+
externalId: options.externalId,
|
|
37
|
+
externalCustomerId: options.externalCustomerId,
|
|
38
|
+
language: options.language ?? "en-US",
|
|
39
|
+
customFields: options.customFields,
|
|
40
|
+
uuid: options.uuid ?? null,
|
|
41
|
+
interviewId: options.interviewId ?? null
|
|
42
|
+
}, {
|
|
43
|
+
headers: {
|
|
44
|
+
"x-api-key": apiKey,
|
|
45
|
+
"api-version": "1.0"
|
|
46
|
+
},
|
|
47
|
+
signal
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) throw new Error(`POST ${endpoints.createSession} failed: ${res.status} ${res.statusText}`);
|
|
50
|
+
return res.data;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/internal/featureConfig/featureConfigService.ts
|
|
55
|
+
let cachedFeatures$1 = null;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a feature is enabled in the feature config.
|
|
58
|
+
*/
|
|
59
|
+
function isFeatureEnabled(feature, features) {
|
|
60
|
+
return features?.find((f) => f.feature === feature)?.enabled ?? false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fetches feature configuration from the backend.
|
|
64
|
+
* Results are cached for the session lifetime.
|
|
65
|
+
*/
|
|
66
|
+
async function fetchFeatureConfig(signal) {
|
|
67
|
+
if (cachedFeatures$1) return cachedFeatures$1;
|
|
68
|
+
const response = await api.get(endpoints.featureConfig, { signal });
|
|
69
|
+
if (!response.ok) throw new Error(`Failed to fetch feature config: ${response.status} ${response.statusText}`);
|
|
70
|
+
cachedFeatures$1 = response.data;
|
|
71
|
+
return cachedFeatures$1;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resets the cached feature config (useful for testing).
|
|
75
|
+
*/
|
|
76
|
+
function resetFeatureConfigCache() {
|
|
77
|
+
cachedFeatures$1 = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/internal/fingerprint/fingerprintService.ts
|
|
82
|
+
const IP_PROVIDER_URL = "https://api.ipify.org?format=json";
|
|
83
|
+
const AUTOMATION_MARKERS = {
|
|
84
|
+
window: [
|
|
85
|
+
"callPhantom",
|
|
86
|
+
"_phantom",
|
|
87
|
+
"phantom",
|
|
88
|
+
"__nightmare",
|
|
89
|
+
"domAutomation",
|
|
90
|
+
"domAutomationController"
|
|
91
|
+
],
|
|
92
|
+
navigator: ["webdriver"]
|
|
93
|
+
};
|
|
94
|
+
function isBrowserSimulation(browserEnv) {
|
|
95
|
+
for (const prop of AUTOMATION_MARKERS.window) if (browserEnv.getWindowProperty(prop)) return true;
|
|
96
|
+
for (const prop of AUTOMATION_MARKERS.navigator) if (browserEnv.getNavigatorProperty(prop)) return true;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
async function fetchPublicIp(browserEnv) {
|
|
100
|
+
try {
|
|
101
|
+
return (await browserEnv.fetchJson(IP_PROVIDER_URL, 3e3))?.ip || "";
|
|
102
|
+
} catch {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function parseBrowserInfo(ua) {
|
|
107
|
+
const uaLower = ua.toLowerCase();
|
|
108
|
+
if (uaLower.includes("edg/")) {
|
|
109
|
+
const match = ua.match(/edg\/(\d+)/i);
|
|
110
|
+
return {
|
|
111
|
+
name: "Edge",
|
|
112
|
+
version: match ? match[1] : "Unknown"
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (uaLower.includes("chrome/") && !uaLower.includes("edg/")) {
|
|
116
|
+
const match = ua.match(/chrome\/(\d+)/i);
|
|
117
|
+
return {
|
|
118
|
+
name: "Chrome",
|
|
119
|
+
version: match ? match[1] : "Unknown"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (uaLower.includes("firefox/")) {
|
|
123
|
+
const match = ua.match(/firefox\/(\d+)/i);
|
|
124
|
+
return {
|
|
125
|
+
name: "Firefox",
|
|
126
|
+
version: match ? match[1] : "Unknown"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (uaLower.includes("safari/") && !uaLower.includes("chrome/")) {
|
|
130
|
+
const match = ua.match(/version\/(\d+)/i);
|
|
131
|
+
return {
|
|
132
|
+
name: "Safari",
|
|
133
|
+
version: match ? match[1] : "Unknown"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
name: "Unknown",
|
|
138
|
+
version: "Unknown"
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function parseOSInfo(ua, platform) {
|
|
142
|
+
if (/iphone|ipad|ipod/i.test(ua)) {
|
|
143
|
+
const match = ua.match(/os (\d+)[._](\d+)/i);
|
|
144
|
+
if (match) return {
|
|
145
|
+
name: "iOS",
|
|
146
|
+
version: `${match[1]}.${match[2]}`
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
name: "iOS",
|
|
150
|
+
version: "Unknown"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (/android/i.test(ua)) {
|
|
154
|
+
const match = ua.match(/android (\d+(?:\.\d+)?)/i);
|
|
155
|
+
return {
|
|
156
|
+
name: "Android",
|
|
157
|
+
version: match ? match[1] : "Unknown"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (/windows/i.test(ua)) {
|
|
161
|
+
const match = ua.match(/windows nt (\d+\.\d+)/i);
|
|
162
|
+
return {
|
|
163
|
+
name: "Windows",
|
|
164
|
+
version: match ? match[1] : "Unknown"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (/macintosh|mac os x/i.test(ua)) {
|
|
168
|
+
const match = ua.match(/mac os x (\d+)[._](\d+)/i);
|
|
169
|
+
if (match) return {
|
|
170
|
+
name: "macOS",
|
|
171
|
+
version: `${match[1]}.${match[2]}`
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
name: "macOS",
|
|
175
|
+
version: "Unknown"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (/linux/i.test(ua)) return {
|
|
179
|
+
name: "Linux",
|
|
180
|
+
version: "Unknown"
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
name: platform || "Unknown",
|
|
184
|
+
version: "Unknown"
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isWebview(ua) {
|
|
188
|
+
const uaLower = ua.toLowerCase();
|
|
189
|
+
return uaLower.includes("wv") || uaLower.includes("webview") || uaLower.includes("android") && !uaLower.includes("chrome");
|
|
190
|
+
}
|
|
191
|
+
function getDeviceModel(ua, platform) {
|
|
192
|
+
if (/iphone/i.test(ua)) {
|
|
193
|
+
const match = ua.match(/iphone\s*(\w+)/i);
|
|
194
|
+
return match ? `iPhone ${match[1]}` : "iPhone";
|
|
195
|
+
}
|
|
196
|
+
if (/ipad/i.test(ua)) return ua.match(/ipad/i) ? "iPad" : "iPad";
|
|
197
|
+
if (/android/i.test(ua)) {
|
|
198
|
+
const match = ua.match(/android.*;\s*([^)]+)\)/i);
|
|
199
|
+
return match ? match[1].trim() : "Android Device";
|
|
200
|
+
}
|
|
201
|
+
return platform || "Unknown";
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Collects device fingerprint data and submits it to the backend.
|
|
205
|
+
*/
|
|
206
|
+
async function submitDeviceFingerprint(options) {
|
|
207
|
+
const { browserEnv, sdkVersion, disableIpify = false, hostingApp, signal } = options;
|
|
208
|
+
const ua = getUserAgent();
|
|
209
|
+
const deviceInfo = getDeviceInfo();
|
|
210
|
+
const hash = browserEnv.generateCanvasFingerprint();
|
|
211
|
+
const ip = disableIpify ? "" : await fetchPublicIp(browserEnv);
|
|
212
|
+
const browserInfo = parseBrowserInfo(ua);
|
|
213
|
+
const osInfo = parseOSInfo(ua, deviceInfo.platform);
|
|
214
|
+
const deviceModel = getDeviceModel(ua, deviceInfo.platform);
|
|
215
|
+
const browserString = isWebview(ua) ? "webview" : `${browserInfo.name} ${browserInfo.version}`;
|
|
216
|
+
const fingerprintData = {
|
|
217
|
+
hash,
|
|
218
|
+
ip,
|
|
219
|
+
deviceType: "WEBAPP",
|
|
220
|
+
data: JSON.stringify({
|
|
221
|
+
visitorId: hash,
|
|
222
|
+
ip
|
|
223
|
+
}),
|
|
224
|
+
osVersion: `${osInfo.name} ${osInfo.version}`,
|
|
225
|
+
deviceModel,
|
|
226
|
+
browser: browserString,
|
|
227
|
+
sdkVersion,
|
|
228
|
+
hasLiedBrowser: isBrowserSimulation(browserEnv),
|
|
229
|
+
hostingApp
|
|
230
|
+
};
|
|
231
|
+
const response = await api.post(endpoints.deviceFingerprint, {
|
|
232
|
+
hash: fingerprintData.hash,
|
|
233
|
+
ip: fingerprintData.ip,
|
|
234
|
+
deviceType: fingerprintData.deviceType,
|
|
235
|
+
data: fingerprintData.data,
|
|
236
|
+
osVersion: fingerprintData.osVersion,
|
|
237
|
+
deviceModel: fingerprintData.deviceModel,
|
|
238
|
+
browser: fingerprintData.browser,
|
|
239
|
+
hasLiedBrowser: fingerprintData.hasLiedBrowser,
|
|
240
|
+
sdkVersion: fingerprintData.sdkVersion,
|
|
241
|
+
hostingApp: fingerprintData.hostingApp,
|
|
242
|
+
ipLocation: fingerprintData.ipLocation
|
|
243
|
+
}, { signal });
|
|
244
|
+
if (!response.ok) throw new Error(`Failed to submit device fingerprint: ${response.status} ${response.statusText}`);
|
|
245
|
+
return response.data;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/internal/session/sessionInitializer.ts
|
|
250
|
+
const SDK_VERSION = "2.0.0";
|
|
251
|
+
let sessionInitialized = false;
|
|
252
|
+
let cachedFeatures = null;
|
|
253
|
+
let cachedDisableIpify = false;
|
|
254
|
+
/**
|
|
255
|
+
* Initializes session-level services after the token is set.
|
|
256
|
+
* This should be called once after createSession() or setToken().
|
|
257
|
+
*
|
|
258
|
+
* Performs:
|
|
259
|
+
* 1. Fetches feature configuration from backend
|
|
260
|
+
* 2. Submits device fingerprint
|
|
261
|
+
*
|
|
262
|
+
* Results are cached for the session lifetime.
|
|
263
|
+
*
|
|
264
|
+
* @param options - Optional configuration
|
|
265
|
+
* @returns Session initialization result with feature config
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* // After creating a session
|
|
270
|
+
* const session = await createSession('api-key', options);
|
|
271
|
+
* setup({ apiURL, token: session.token });
|
|
272
|
+
* const { features } = await initializeSession();
|
|
273
|
+
*
|
|
274
|
+
* // Check feature flags
|
|
275
|
+
* if (isFeatureEnabled('DISABLE_IPIFY', features.features)) {
|
|
276
|
+
* // Handle disabled ipify
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
async function initializeSession(options = {}) {
|
|
281
|
+
const { hostingApp, signal } = options;
|
|
282
|
+
if (sessionInitialized && cachedFeatures) return {
|
|
283
|
+
features: cachedFeatures,
|
|
284
|
+
disableIpify: cachedDisableIpify,
|
|
285
|
+
fingerprintSuccess: true
|
|
286
|
+
};
|
|
287
|
+
let features;
|
|
288
|
+
let disableIpify = false;
|
|
289
|
+
try {
|
|
290
|
+
features = await fetchFeatureConfig(signal);
|
|
291
|
+
disableIpify = isFeatureEnabled("DISABLE_IPIFY", features.features);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.warn("Failed to fetch feature config:", error);
|
|
294
|
+
features = { sessionIdentifier: "" };
|
|
295
|
+
}
|
|
296
|
+
let fingerprintSuccess = false;
|
|
297
|
+
try {
|
|
298
|
+
await submitDeviceFingerprint({
|
|
299
|
+
browserEnv: new BrowserEnvironmentProvider(),
|
|
300
|
+
sdkVersion: SDK_VERSION,
|
|
301
|
+
disableIpify,
|
|
302
|
+
hostingApp,
|
|
303
|
+
signal
|
|
304
|
+
});
|
|
305
|
+
fingerprintSuccess = true;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.warn("Failed to submit device fingerprint:", error);
|
|
308
|
+
}
|
|
309
|
+
sessionInitialized = true;
|
|
310
|
+
cachedFeatures = features;
|
|
311
|
+
cachedDisableIpify = disableIpify;
|
|
312
|
+
return {
|
|
313
|
+
features,
|
|
314
|
+
disableIpify,
|
|
315
|
+
fingerprintSuccess
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Gets the cached feature configuration.
|
|
320
|
+
* Returns undefined if session hasn't been initialized.
|
|
321
|
+
*/
|
|
322
|
+
function getSessionFeatures() {
|
|
323
|
+
return cachedFeatures ?? void 0;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Gets the cached disableIpify flag.
|
|
327
|
+
* Returns false if session hasn't been initialized.
|
|
328
|
+
*/
|
|
329
|
+
function getDisableIpify() {
|
|
330
|
+
return cachedDisableIpify;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Checks if the session has been initialized.
|
|
334
|
+
*/
|
|
335
|
+
function isSessionInitialized() {
|
|
336
|
+
return sessionInitialized;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resets session initialization state.
|
|
340
|
+
* Useful for testing or when starting a new session.
|
|
341
|
+
*/
|
|
342
|
+
function resetSessionInit() {
|
|
343
|
+
sessionInitialized = false;
|
|
344
|
+
cachedFeatures = null;
|
|
345
|
+
cachedDisableIpify = false;
|
|
346
|
+
resetFeatureConfigCache();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/modules/id/types.ts
|
|
351
|
+
const ID_ERROR_CODES = {
|
|
352
|
+
UPLOAD_ERROR: "UPLOAD_ERROR",
|
|
353
|
+
CLASSIFICATION_FAILED: "CLASSIFICATION_FAILED",
|
|
354
|
+
LOW_SHARPNESS: "LOW_SHARPNESS",
|
|
355
|
+
GLARE_DETECTED: "GLARE_DETECTED",
|
|
356
|
+
WRONG_DOCUMENT_SIDE: "WRONG_DOCUMENT_SIDE",
|
|
357
|
+
ID_TYPE_UNACCEPTABLE: "ID_TYPE_UNACCEPTABLE",
|
|
358
|
+
READABILITY_ISSUE: "READABILITY_ISSUE",
|
|
359
|
+
RETRY_EXHAUSTED_CONTINUE_TO_BACK: "RETRY_EXHAUSTED_CONTINUE_TO_BACK",
|
|
360
|
+
RETRY_EXHAUSTED_SKIP_BACK: "RETRY_EXHAUSTED_SKIP_BACK",
|
|
361
|
+
NO_MORE_TRIES: "NO_MORE_TRIES",
|
|
362
|
+
UNEXPECTED_ERROR: "UNEXPECTED_ERROR",
|
|
363
|
+
NO_TOKEN: "NO_TOKEN",
|
|
364
|
+
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
365
|
+
USER_CANCELLED: "USER_CANCELLED",
|
|
366
|
+
SERVER: "SERVER_ERROR"
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/internal/analytics/deviceStats.ts
|
|
371
|
+
async function postDeviceStats(stats) {
|
|
372
|
+
try {
|
|
373
|
+
await api.post(endpoints.deviceStats, stats);
|
|
374
|
+
} catch {}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/modules/id/idCameraStream.ts
|
|
379
|
+
const BACK_CAMERA_KEYWORDS = [
|
|
380
|
+
"rear",
|
|
381
|
+
"back",
|
|
382
|
+
"rück",
|
|
383
|
+
"arrière",
|
|
384
|
+
"trasera",
|
|
385
|
+
"trás",
|
|
386
|
+
"traseira",
|
|
387
|
+
"posteriore",
|
|
388
|
+
"后面",
|
|
389
|
+
"後面",
|
|
390
|
+
"背面",
|
|
391
|
+
"后置",
|
|
392
|
+
"後置",
|
|
393
|
+
"背置",
|
|
394
|
+
"задней",
|
|
395
|
+
"الخلفية",
|
|
396
|
+
"후",
|
|
397
|
+
"arka",
|
|
398
|
+
"achterzijde",
|
|
399
|
+
"หลัง",
|
|
400
|
+
"baksidan",
|
|
401
|
+
"bagside",
|
|
402
|
+
"sau",
|
|
403
|
+
"bak",
|
|
404
|
+
"tylny",
|
|
405
|
+
"takakamera",
|
|
406
|
+
"belakang",
|
|
407
|
+
"אחורית",
|
|
408
|
+
"πίσω",
|
|
409
|
+
"spate",
|
|
410
|
+
"hátsó",
|
|
411
|
+
"zadní",
|
|
412
|
+
"darrere",
|
|
413
|
+
"zadná",
|
|
414
|
+
"задня",
|
|
415
|
+
"stražnja",
|
|
416
|
+
"बैक"
|
|
417
|
+
];
|
|
418
|
+
function isBackCameraLabel(label) {
|
|
419
|
+
const lowercaseLabel = label.toLowerCase();
|
|
420
|
+
return BACK_CAMERA_KEYWORDS.some((keyword) => lowercaseLabel.includes(keyword));
|
|
421
|
+
}
|
|
422
|
+
function classifyCamera(device, index, totalDevices) {
|
|
423
|
+
let cameraType;
|
|
424
|
+
if (device.label === "") cameraType = totalDevices === 1 || index + 1 <= totalDevices / 2 ? "front" : "back";
|
|
425
|
+
else cameraType = isBackCameraLabel(device.label) ? "back" : "front";
|
|
426
|
+
return {
|
|
427
|
+
deviceId: device.deviceId,
|
|
428
|
+
label: device.label,
|
|
429
|
+
cameraType
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
async function getCameras() {
|
|
433
|
+
const videoDevices = await enumerateVideoDevices();
|
|
434
|
+
const cameras = videoDevices.map((d, i) => classifyCamera(d, i, videoDevices.length));
|
|
435
|
+
if (cameras.length > 1 && !cameras.some((c) => c.cameraType === "back")) {
|
|
436
|
+
const resolutions = cameras.map((c) => {
|
|
437
|
+
const match = c.label.match(/\b([0-9]+)MP?\b/i);
|
|
438
|
+
return match ? parseInt(match[1], 10) : NaN;
|
|
439
|
+
});
|
|
440
|
+
let backCameraIndex = cameras.length - 1;
|
|
441
|
+
if (!resolutions.some(isNaN)) backCameraIndex = resolutions.lastIndexOf(Math.max(...resolutions));
|
|
442
|
+
cameras[backCameraIndex].cameraType = "back";
|
|
443
|
+
}
|
|
444
|
+
return cameras;
|
|
445
|
+
}
|
|
446
|
+
function selectMainCameraFromStream(track, cameras) {
|
|
447
|
+
const settings = track.getSettings();
|
|
448
|
+
const activeCamera = cameras.find((c) => c.deviceId === settings.deviceId || c.label !== "" && c.label === track.label);
|
|
449
|
+
if (!activeCamera) return void 0;
|
|
450
|
+
if ((settings.facingMode === "environment" || isBackCameraLabel(track.label)) && cameras.length > 1) {
|
|
451
|
+
cameras.forEach((camera) => {
|
|
452
|
+
if (camera.deviceId === activeCamera.deviceId) camera.cameraType = "back";
|
|
453
|
+
else if (!isBackCameraLabel(camera.label)) camera.cameraType = "front";
|
|
454
|
+
});
|
|
455
|
+
return cameras.filter((c) => c.cameraType === "back").sort((a, b) => a.label.localeCompare(b.label))[0];
|
|
456
|
+
}
|
|
457
|
+
if (cameras.length === 1) return activeCamera;
|
|
458
|
+
}
|
|
459
|
+
function getAndroidVideoConstraints(level) {
|
|
460
|
+
const base = {
|
|
461
|
+
resizeMode: "none",
|
|
462
|
+
facingMode: "environment"
|
|
463
|
+
};
|
|
464
|
+
switch (level) {
|
|
465
|
+
case 0: return {
|
|
466
|
+
...base,
|
|
467
|
+
height: { ideal: 720 },
|
|
468
|
+
aspectRatio: { ideal: 19.5 / 9 }
|
|
469
|
+
};
|
|
470
|
+
case 1: return {
|
|
471
|
+
...base,
|
|
472
|
+
width: {
|
|
473
|
+
min: 3200,
|
|
474
|
+
ideal: 3840,
|
|
475
|
+
max: 4096
|
|
476
|
+
},
|
|
477
|
+
height: {
|
|
478
|
+
min: 1800,
|
|
479
|
+
ideal: 2160,
|
|
480
|
+
max: 2400
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
case 2: return {
|
|
484
|
+
...base,
|
|
485
|
+
width: {
|
|
486
|
+
min: 1400,
|
|
487
|
+
ideal: 1920,
|
|
488
|
+
max: 2160
|
|
489
|
+
},
|
|
490
|
+
height: {
|
|
491
|
+
min: 900,
|
|
492
|
+
ideal: 1080,
|
|
493
|
+
max: 1440
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
case 3: return {
|
|
497
|
+
...base,
|
|
498
|
+
width: {
|
|
499
|
+
min: 640,
|
|
500
|
+
ideal: 640,
|
|
501
|
+
max: 800
|
|
502
|
+
},
|
|
503
|
+
height: {
|
|
504
|
+
min: 480,
|
|
505
|
+
ideal: 480,
|
|
506
|
+
max: 600
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
case 4: return {
|
|
510
|
+
...base,
|
|
511
|
+
width: {
|
|
512
|
+
min: 640,
|
|
513
|
+
ideal: 800,
|
|
514
|
+
max: 960
|
|
515
|
+
},
|
|
516
|
+
height: {
|
|
517
|
+
min: 480,
|
|
518
|
+
ideal: 480,
|
|
519
|
+
max: 480
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
default: return {};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function getAndroidBackCameraStream(fallbackLevel = 0) {
|
|
526
|
+
if (fallbackLevel > 4) throw new Error("Failed to get camera after all fallback attempts");
|
|
527
|
+
try {
|
|
528
|
+
const initialStream = await requestCameraAccess({ video: getAndroidVideoConstraints(fallbackLevel) });
|
|
529
|
+
const track = initialStream.getVideoTracks()[0];
|
|
530
|
+
const mainCamera = selectMainCameraFromStream(track, await getCameras());
|
|
531
|
+
stopCameraStream(initialStream);
|
|
532
|
+
if (!mainCamera) throw new Error("Could not identify main camera");
|
|
533
|
+
let idealWidth = 1280;
|
|
534
|
+
let idealHeight = 720;
|
|
535
|
+
if (fallbackLevel > 1) {
|
|
536
|
+
const constraints = getAndroidVideoConstraints(fallbackLevel);
|
|
537
|
+
const width = constraints.width;
|
|
538
|
+
const height = constraints.height;
|
|
539
|
+
idealWidth = width?.ideal ?? 1280;
|
|
540
|
+
idealHeight = height?.ideal ?? 720;
|
|
541
|
+
}
|
|
542
|
+
return await requestCameraAccess({ video: {
|
|
543
|
+
deviceId: { exact: mainCamera.deviceId },
|
|
544
|
+
width: { ideal: idealWidth },
|
|
545
|
+
height: { ideal: idealHeight }
|
|
546
|
+
} });
|
|
547
|
+
} catch (error) {
|
|
548
|
+
const errorName = error instanceof Error ? error.name : "UnknownError";
|
|
549
|
+
const nextLevel = Math.min(fallbackLevel + 1, 4);
|
|
550
|
+
if (errorName === "NotReadableError") {
|
|
551
|
+
await sleep(300);
|
|
552
|
+
return getAndroidBackCameraStream(nextLevel);
|
|
553
|
+
}
|
|
554
|
+
if (errorName === "AbortError") {
|
|
555
|
+
await sleep(300);
|
|
556
|
+
return getAndroidBackCameraStream(fallbackLevel);
|
|
557
|
+
}
|
|
558
|
+
return getAndroidBackCameraStream(nextLevel);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function applyIOSFocusHack(stream) {
|
|
562
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
563
|
+
try {
|
|
564
|
+
await applyTrackConstraints(videoTrack, { advanced: [{ focusDistance: 1 }] });
|
|
565
|
+
} catch {}
|
|
566
|
+
}
|
|
567
|
+
function getIOSConstraints() {
|
|
568
|
+
return {
|
|
569
|
+
audio: false,
|
|
570
|
+
video: {
|
|
571
|
+
resizeMode: "none",
|
|
572
|
+
facingMode: "environment",
|
|
573
|
+
height: { ideal: isIPhone14OrHigher() ? 1080 : 720 },
|
|
574
|
+
aspectRatio: { ideal: 19.5 / 9 }
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
async function getIOSCameraStream() {
|
|
579
|
+
const stream = await requestCameraAccess(getIOSConstraints());
|
|
580
|
+
await applyIOSFocusHack(stream);
|
|
581
|
+
return stream;
|
|
582
|
+
}
|
|
583
|
+
function getDesktopConstraints(options) {
|
|
584
|
+
const { deviceId } = options;
|
|
585
|
+
return {
|
|
586
|
+
audio: isSafari(),
|
|
587
|
+
video: {
|
|
588
|
+
facingMode: "user",
|
|
589
|
+
deviceId: deviceId ? { exact: deviceId } : void 0,
|
|
590
|
+
height: { ideal: 1080 },
|
|
591
|
+
width: { ideal: 1920 }
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
async function getDesktopCameraStream(options) {
|
|
596
|
+
return requestCameraAccess(getDesktopConstraints(options));
|
|
597
|
+
}
|
|
598
|
+
async function getIdCameraStream(deviceId) {
|
|
599
|
+
if (isIOS()) return getIOSCameraStream();
|
|
600
|
+
if (isAndroid()) return getAndroidBackCameraStream(0);
|
|
601
|
+
if (isDesktop()) return getDesktopCameraStream({ deviceId });
|
|
602
|
+
return getDesktopCameraStream({ deviceId });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/modules/id/idCaptureServices.ts
|
|
607
|
+
/**
|
|
608
|
+
* Camera constraints used for PRC warmup before ID capture.
|
|
609
|
+
* Uses environment-facing camera on mobile devices (for back camera ID capture)
|
|
610
|
+
* and user-facing camera on desktop.
|
|
611
|
+
*/
|
|
612
|
+
function getPrcCameraConstraints() {
|
|
613
|
+
if (isDesktop()) return { video: {
|
|
614
|
+
facingMode: "user",
|
|
615
|
+
height: { ideal: 480 },
|
|
616
|
+
width: { ideal: 640 }
|
|
617
|
+
} };
|
|
618
|
+
return { video: {
|
|
619
|
+
facingMode: "environment",
|
|
620
|
+
height: { ideal: 720 }
|
|
621
|
+
} };
|
|
622
|
+
}
|
|
623
|
+
const SHARPNESS_THRESHOLD = 10;
|
|
624
|
+
const GLARE_THRESHOLD = 10;
|
|
625
|
+
const DEFAULT_ID_CAPTURE_THRESHOLDS$1 = {
|
|
626
|
+
blurThreshold: .2,
|
|
627
|
+
blurChangeThreshold: .2,
|
|
628
|
+
glareThreshold: .3,
|
|
629
|
+
clsThreshold: .98,
|
|
630
|
+
sideThreshold: .8,
|
|
631
|
+
iouThreshold: .8,
|
|
632
|
+
framesAggregationInterval: 3e3,
|
|
633
|
+
minFaceIdQualityScore: .62
|
|
634
|
+
};
|
|
635
|
+
const DEFAULT_ID_CAPTURE_SETTINGS = {
|
|
636
|
+
isFixedMask: false,
|
|
637
|
+
isIPhone14OrHigher: false,
|
|
638
|
+
idType: "",
|
|
639
|
+
blurCheckEnabled: false,
|
|
640
|
+
glareCheckEnabled: false,
|
|
641
|
+
faceQualityCheckEnabled: true,
|
|
642
|
+
iouCheckEnabled: true
|
|
643
|
+
};
|
|
644
|
+
async function initializeIdCapture(params) {
|
|
645
|
+
const { provider, config, deepsightService } = params;
|
|
646
|
+
await provider.initialize({});
|
|
647
|
+
provider.setThresholds({
|
|
648
|
+
...DEFAULT_ID_CAPTURE_THRESHOLDS$1,
|
|
649
|
+
...config.thresholds,
|
|
650
|
+
idDetectedTimeout: config.thresholds?.idDetectedTimeout ?? config.deviceIdleTimeout * 1e3,
|
|
651
|
+
autocaptureTimeout: config.thresholds?.autocaptureTimeout ?? config.autoCaptureTimeout * 1e3
|
|
652
|
+
});
|
|
653
|
+
if (config.settings) provider.setSettings({
|
|
654
|
+
...DEFAULT_ID_CAPTURE_SETTINGS,
|
|
655
|
+
...config.settings
|
|
656
|
+
});
|
|
657
|
+
if (deepsightService) try {
|
|
658
|
+
await deepsightService.performPrcCheck({ constraints: getPrcCameraConstraints() });
|
|
659
|
+
} catch (error) {
|
|
660
|
+
console.warn("PRC check failed during ID capture initialization:", error);
|
|
661
|
+
}
|
|
662
|
+
const stream = await getIdCameraStream();
|
|
663
|
+
if (deepsightService) {
|
|
664
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
665
|
+
if (videoTrack) deepsightService.metadata.updateCameraInfo(videoTrack);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
stream,
|
|
669
|
+
provider
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function stopStream(stream) {
|
|
673
|
+
for (const track of stream.getTracks()) track.stop();
|
|
674
|
+
}
|
|
675
|
+
function validateUploadResponse(response, sessionState) {
|
|
676
|
+
if (response.failReason === "ID_TYPE_UNACCEPTABLE") return {
|
|
677
|
+
error: true,
|
|
678
|
+
message: "ID type is not acceptable",
|
|
679
|
+
messageDescription: "Please use a valid ID type",
|
|
680
|
+
errorKey: ID_ERROR_CODES.ID_TYPE_UNACCEPTABLE
|
|
681
|
+
};
|
|
682
|
+
if (response.failReason === "WRONG_DOCUMENT_SIDE") return {
|
|
683
|
+
error: true,
|
|
684
|
+
message: "Wrong side of document",
|
|
685
|
+
messageDescription: response.side === "back" ? "Please show the back side of your ID" : "Please show the front side of your ID",
|
|
686
|
+
errorKey: ID_ERROR_CODES.WRONG_DOCUMENT_SIDE
|
|
687
|
+
};
|
|
688
|
+
if (!response.classification) return {
|
|
689
|
+
error: true,
|
|
690
|
+
message: "ID classification failed",
|
|
691
|
+
errorKey: ID_ERROR_CODES.CLASSIFICATION_FAILED
|
|
692
|
+
};
|
|
693
|
+
const sharpnessThreshold = getDeviceClass() === "desktop" ? -1 : SHARPNESS_THRESHOLD;
|
|
694
|
+
if (response.sharpness !== void 0 && sharpnessThreshold >= 0 && response.sharpness < sharpnessThreshold) return {
|
|
695
|
+
error: true,
|
|
696
|
+
message: "Image is not sharp enough",
|
|
697
|
+
messageDescription: "Please ensure the image is clear and well-focused",
|
|
698
|
+
errorKey: ID_ERROR_CODES.LOW_SHARPNESS
|
|
699
|
+
};
|
|
700
|
+
if (response.glare !== void 0 && response.glare < GLARE_THRESHOLD && !sessionState?.skipGlareFront && !sessionState?.skipGlareBack) return {
|
|
701
|
+
error: true,
|
|
702
|
+
message: "Glare detected on ID",
|
|
703
|
+
messageDescription: "Please avoid bright reflections on your ID",
|
|
704
|
+
errorKey: ID_ERROR_CODES.GLARE_DETECTED
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async function getExtraImages(params) {
|
|
708
|
+
const extraImages = params.ageAssurance ? [params.type === "back" ? "croppedBackID" : "croppedFrontID"] : [];
|
|
709
|
+
try {
|
|
710
|
+
const res = await api.post(endpoints.getImages, { images: ["croppedIDFace", ...extraImages] }, { signal: params.signal });
|
|
711
|
+
if (!res.ok) throw new Error(`Failed to get extra images: ${res.status}`);
|
|
712
|
+
return res.data ?? {
|
|
713
|
+
croppedIDFace: "",
|
|
714
|
+
croppedFrontID: "",
|
|
715
|
+
croppedBackID: ""
|
|
716
|
+
};
|
|
717
|
+
} catch {
|
|
718
|
+
return {
|
|
719
|
+
croppedIDFace: "",
|
|
720
|
+
croppedFrontID: "",
|
|
721
|
+
croppedBackID: ""
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const getRealQualityValue = (value) => (1 - value) * 100;
|
|
726
|
+
async function uploadIdImage(params) {
|
|
727
|
+
const { type, image, onProgress, signal, metadata, ageAssurance, glare, sharpness, shouldSkipGlareBack, analyticsProvider, imageData } = params;
|
|
728
|
+
addEvent({
|
|
729
|
+
code: "captureAttemptFinished",
|
|
730
|
+
module: eventModuleNames.id,
|
|
731
|
+
payload: { logs: [] }
|
|
732
|
+
});
|
|
733
|
+
let finalMetadata = metadata;
|
|
734
|
+
if (analyticsProvider && imageData) try {
|
|
735
|
+
await analyticsProvider.analyzeFrame(imageData);
|
|
736
|
+
analyticsProvider.update();
|
|
737
|
+
const analysisStatus = analyticsProvider.getAnalysisStatus();
|
|
738
|
+
const motionStatus = analyticsProvider.getMotionStatus();
|
|
739
|
+
await postDeviceStats({
|
|
740
|
+
frontIdStatsAnalysisStatus: type === "front" ? analysisStatus : void 0,
|
|
741
|
+
backIdStatsAnalysisStatus: type === "back" ? analysisStatus : void 0,
|
|
742
|
+
motionStatus
|
|
743
|
+
});
|
|
744
|
+
finalMetadata = analyticsProvider.getMetadata();
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.warn("[IdCapture] Analytics failed:", error);
|
|
747
|
+
}
|
|
748
|
+
const endpoint = type === "front" ? endpoints.frontId : endpoints.backId;
|
|
749
|
+
const body = {
|
|
750
|
+
base64Image: image,
|
|
751
|
+
metadata: finalMetadata
|
|
752
|
+
};
|
|
753
|
+
const queryParams = { imageType: "id" };
|
|
754
|
+
if (shouldSkipGlareBack && type === "back") queryParams.glare = 0;
|
|
755
|
+
else if (glare !== void 0) queryParams.glare = getRealQualityValue(glare);
|
|
756
|
+
if (sharpness !== void 0) queryParams.sharpness = getRealQualityValue(sharpness);
|
|
757
|
+
try {
|
|
758
|
+
const res = await api.post(endpoint, body, {
|
|
759
|
+
signal,
|
|
760
|
+
query: queryParams,
|
|
761
|
+
onUploadProgress: onProgress
|
|
762
|
+
});
|
|
763
|
+
if (!res.ok) throw new Error(`POST ${endpoint} failed: ${res.status} ${res.statusText}`);
|
|
764
|
+
const response = res.data;
|
|
765
|
+
const extraImages = await getExtraImages({
|
|
766
|
+
type,
|
|
767
|
+
ageAssurance,
|
|
768
|
+
signal
|
|
769
|
+
});
|
|
770
|
+
const fullResponse = {
|
|
771
|
+
...response,
|
|
772
|
+
originalImage: image,
|
|
773
|
+
frontIdImage: type === "front" ? image : void 0,
|
|
774
|
+
backIdImage: extraImages.croppedBackID,
|
|
775
|
+
...extraImages
|
|
776
|
+
};
|
|
777
|
+
onProgress?.(100);
|
|
778
|
+
return fullResponse;
|
|
779
|
+
} catch (error) {
|
|
780
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
781
|
+
throw new Error(`${ID_ERROR_CODES.UPLOAD_ERROR}: ${errorMessage}`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function buildStandardResolution(width, height) {
|
|
785
|
+
return height > width ? {
|
|
786
|
+
width: 1080,
|
|
787
|
+
height: 1920
|
|
788
|
+
} : {
|
|
789
|
+
width: 1920,
|
|
790
|
+
height: 1080
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function buildResolutionFromStream(stream) {
|
|
794
|
+
const track = stream.getVideoTracks()[0];
|
|
795
|
+
if (!track) return "1080x1920";
|
|
796
|
+
const settings = track.getSettings();
|
|
797
|
+
const width = settings.width;
|
|
798
|
+
const height = settings.height;
|
|
799
|
+
if (typeof width === "number" && typeof height === "number") {
|
|
800
|
+
const standard = buildStandardResolution(width, height);
|
|
801
|
+
return `${standard.width}x${standard.height}`;
|
|
802
|
+
}
|
|
803
|
+
return "1080x1920";
|
|
804
|
+
}
|
|
805
|
+
async function startRecordingSession(params) {
|
|
806
|
+
if (params.config.enableIdRecording !== true) return;
|
|
807
|
+
if (params.existing) return params.existing;
|
|
808
|
+
const provider = params.config.recording?.capability ?? new OpenViduRecordingProvider();
|
|
809
|
+
const clonedStream = params.stream.clone();
|
|
810
|
+
const hasAudio = clonedStream.getAudioTracks().length > 0;
|
|
811
|
+
const resolution = buildResolutionFromStream(clonedStream);
|
|
812
|
+
const session = await createRecordingSession(params.type);
|
|
813
|
+
const connection = await provider.connect({
|
|
814
|
+
sessionToken: session.token,
|
|
815
|
+
stream: clonedStream,
|
|
816
|
+
events: {
|
|
817
|
+
onSessionConnected: (sessionId) => {
|
|
818
|
+
addEvent({
|
|
819
|
+
code: streamingEvents.strSessionDidConnect,
|
|
820
|
+
module: eventModuleNames.id,
|
|
821
|
+
payload: {
|
|
822
|
+
message: "Recording session connected",
|
|
823
|
+
sessionId
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
},
|
|
827
|
+
onSessionDisconnected: (sessionId) => {
|
|
828
|
+
addEvent({
|
|
829
|
+
code: streamingEvents.strSessionDidDisconnect,
|
|
830
|
+
module: eventModuleNames.id,
|
|
831
|
+
payload: {
|
|
832
|
+
message: "Recording session disconnected",
|
|
833
|
+
sessionId
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
},
|
|
837
|
+
onSessionException: (event) => {
|
|
838
|
+
addEvent({
|
|
839
|
+
code: streamingEvents.strSessionDidFailWithError,
|
|
840
|
+
module: eventModuleNames.id,
|
|
841
|
+
payload: {
|
|
842
|
+
message: "Recording session failed due to an error",
|
|
843
|
+
eventName: event.name,
|
|
844
|
+
type: "OpenViduException",
|
|
845
|
+
errorMessage: event.message,
|
|
846
|
+
sessionId: event.sessionId
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
},
|
|
850
|
+
onPublisherCreated: (p) => {
|
|
851
|
+
addEvent({
|
|
852
|
+
code: streamingEvents.strStreamPublisherCreated,
|
|
853
|
+
module: eventModuleNames.id,
|
|
854
|
+
payload: {
|
|
855
|
+
message: "Recording publisher created",
|
|
856
|
+
sessionId: p.sessionId,
|
|
857
|
+
streamId: p.streamId
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
},
|
|
861
|
+
onPublisherError: (p) => {
|
|
862
|
+
addEvent({
|
|
863
|
+
code: streamingEvents.strStreamPublisherDidFailWithError,
|
|
864
|
+
module: eventModuleNames.id,
|
|
865
|
+
payload: {
|
|
866
|
+
message: "Recording publisher failed due to an error",
|
|
867
|
+
sessionId: p.sessionId,
|
|
868
|
+
streamId: p.streamId,
|
|
869
|
+
error: { message: p.message ?? "Unknown error" }
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
await startRecording({
|
|
876
|
+
videoRecordingId: session.videoRecordingId,
|
|
877
|
+
type: params.type,
|
|
878
|
+
resolution,
|
|
879
|
+
hasAudio
|
|
880
|
+
});
|
|
881
|
+
addEvent({
|
|
882
|
+
code: streamingEvents.strStreamVideoCaptureStart,
|
|
883
|
+
module: eventModuleNames.id,
|
|
884
|
+
payload: {
|
|
885
|
+
message: "Recording capture started",
|
|
886
|
+
resolution,
|
|
887
|
+
videoRecordingId: session.videoRecordingId,
|
|
888
|
+
sessionId: session.sessionId,
|
|
889
|
+
streamId: connection.publisher.getStreamId()
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
return {
|
|
893
|
+
token: session.token,
|
|
894
|
+
sessionId: session.sessionId,
|
|
895
|
+
videoRecordingId: session.videoRecordingId,
|
|
896
|
+
connection,
|
|
897
|
+
resolution,
|
|
898
|
+
hasAudio
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function stopRecording(session) {
|
|
902
|
+
(async () => {
|
|
903
|
+
try {
|
|
904
|
+
addEvent({
|
|
905
|
+
code: streamingEvents.strStreamVideoCaptureStop,
|
|
906
|
+
module: eventModuleNames.id,
|
|
907
|
+
payload: {
|
|
908
|
+
message: "Recording capture stopped",
|
|
909
|
+
videoRecordingId: session.videoRecordingId,
|
|
910
|
+
sessionId: session.sessionId,
|
|
911
|
+
streamId: session.connection.publisher.getStreamId()
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
await stopRecording$1(session.videoRecordingId);
|
|
915
|
+
addEvent({
|
|
916
|
+
code: streamingEvents.strStreamPublisherDestroyed,
|
|
917
|
+
module: eventModuleNames.id,
|
|
918
|
+
payload: {
|
|
919
|
+
message: "Recording publisher destroyed",
|
|
920
|
+
sessionId: session.sessionId,
|
|
921
|
+
streamId: session.connection.publisher.getStreamId()
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
} finally {
|
|
925
|
+
await session.connection.disconnect();
|
|
926
|
+
addEvent({
|
|
927
|
+
code: streamingEvents.strSessionDidDisconnect,
|
|
928
|
+
module: eventModuleNames.id,
|
|
929
|
+
payload: {
|
|
930
|
+
message: "Recording session disconnected",
|
|
931
|
+
sessionId: session.sessionId
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
})();
|
|
936
|
+
}
|
|
937
|
+
async function processId(isSecondId = false, queueName = "", signal) {
|
|
938
|
+
const endpoint = isSecondId ? endpoints.processSecondId : endpoints.processId;
|
|
939
|
+
const url = queueName ? `${endpoint}?queueName=${queueName}` : endpoint;
|
|
940
|
+
return { isDocumentExpired: (await api.post(url, {}, { signal })).data?.isDocumentExpired ?? false };
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/modules/id/idCaptureActors.ts
|
|
945
|
+
const checkPermissionActor = fromPromise(async () => {
|
|
946
|
+
return checkPermission();
|
|
947
|
+
});
|
|
948
|
+
const requestPermissionActor = fromPromise(async () => {
|
|
949
|
+
return requestPermission();
|
|
950
|
+
});
|
|
951
|
+
const initializeCameraActor = fromPromise(async ({ input }) => {
|
|
952
|
+
return initializeIdCapture(input);
|
|
953
|
+
});
|
|
954
|
+
const runDetectionActor = fromCallback(({ input, sendBack }) => {
|
|
955
|
+
if (!input.frameCapturer || !input.provider) {
|
|
956
|
+
sendBack({
|
|
957
|
+
type: "DETECTION_UPDATE",
|
|
958
|
+
status: "error"
|
|
959
|
+
});
|
|
960
|
+
return () => {};
|
|
961
|
+
}
|
|
962
|
+
const provider = input.provider;
|
|
963
|
+
let currentCanvas = null;
|
|
964
|
+
let bestCanvas = null;
|
|
965
|
+
let storedQualityElements = {};
|
|
966
|
+
let isCapturing = false;
|
|
967
|
+
const timer = BrowserTimerProvider.getInstance();
|
|
968
|
+
let notificationTimeout = null;
|
|
969
|
+
let currentNotificationStatus = null;
|
|
970
|
+
const NOTIFICATION_DURATION_MS = 500;
|
|
971
|
+
const clearNotificationTimeout = () => {
|
|
972
|
+
if (notificationTimeout) {
|
|
973
|
+
timer.clearTimeout(notificationTimeout);
|
|
974
|
+
notificationTimeout = null;
|
|
975
|
+
currentNotificationStatus = null;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
const sendNotification = (status) => {
|
|
979
|
+
if (notificationTimeout && currentNotificationStatus !== status) return;
|
|
980
|
+
if (notificationTimeout && currentNotificationStatus === status) timer.clearTimeout(notificationTimeout);
|
|
981
|
+
currentNotificationStatus = status;
|
|
982
|
+
sendBack({
|
|
983
|
+
type: "DETECTION_UPDATE",
|
|
984
|
+
status
|
|
985
|
+
});
|
|
986
|
+
notificationTimeout = timer.setTimeout(() => {
|
|
987
|
+
notificationTimeout = null;
|
|
988
|
+
currentNotificationStatus = null;
|
|
989
|
+
sendBack({
|
|
990
|
+
type: "DETECTION_UPDATE",
|
|
991
|
+
status: "detecting"
|
|
992
|
+
});
|
|
993
|
+
}, NOTIFICATION_DURATION_MS);
|
|
994
|
+
};
|
|
995
|
+
const canvas = input.frameCapturer.getLatestCanvas();
|
|
996
|
+
const windowDimensions = getWindowDimensions(canvas?.width() ?? 1280, canvas?.height() ?? 720);
|
|
997
|
+
const DEFAULT_GEOMETRY = {
|
|
998
|
+
areaDown: 25e3,
|
|
999
|
+
areaUp: 55e3,
|
|
1000
|
+
areaIOSPassportUp: 3e4,
|
|
1001
|
+
areaIOSPassportDown: 2e4,
|
|
1002
|
+
widthIOSUp: 160,
|
|
1003
|
+
widthIOSDown: 85,
|
|
1004
|
+
widthDown: 110,
|
|
1005
|
+
widthUp: 205
|
|
1006
|
+
};
|
|
1007
|
+
if (input.config.geometry) provider.setGeometry({
|
|
1008
|
+
...input.config.geometry,
|
|
1009
|
+
windowOuterWidth: windowDimensions.outerWidth,
|
|
1010
|
+
windowOuterHeight: windowDimensions.outerHeight,
|
|
1011
|
+
windowInnerWidth: windowDimensions.innerWidth,
|
|
1012
|
+
windowInnerHeight: windowDimensions.innerHeight
|
|
1013
|
+
});
|
|
1014
|
+
else provider.setGeometry({
|
|
1015
|
+
...DEFAULT_GEOMETRY,
|
|
1016
|
+
windowOuterWidth: windowDimensions.outerWidth,
|
|
1017
|
+
windowOuterHeight: windowDimensions.outerHeight,
|
|
1018
|
+
windowInnerWidth: windowDimensions.innerWidth,
|
|
1019
|
+
windowInnerHeight: windowDimensions.innerHeight
|
|
1020
|
+
});
|
|
1021
|
+
const idType = input.currentMode === "back" ? "BackId" : input.currentMode === "passport" || input.currentMode === "front" && input.config.onlyFront ? "Passport" : "FrontId";
|
|
1022
|
+
provider.setSettings({
|
|
1023
|
+
isFixedMask: input.config.settings?.isFixedMask ?? false,
|
|
1024
|
+
isIPhone14OrHigher: input.config.settings?.isIPhone14OrHigher ?? false,
|
|
1025
|
+
idType,
|
|
1026
|
+
blurCheckEnabled: input.config.settings?.blurCheckEnabled ?? false,
|
|
1027
|
+
glareCheckEnabled: input.config.settings?.glareCheckEnabled ?? false,
|
|
1028
|
+
faceQualityCheckEnabled: input.config.settings?.faceQualityCheckEnabled ?? true,
|
|
1029
|
+
iouCheckEnabled: input.config.settings?.iouCheckEnabled ?? true
|
|
1030
|
+
});
|
|
1031
|
+
const thresholds = {
|
|
1032
|
+
...DEFAULT_ID_CAPTURE_THRESHOLDS,
|
|
1033
|
+
...input.config.thresholds,
|
|
1034
|
+
idDetectedTimeout: (input.config.deviceIdleTimeout ?? 10) * 1e3,
|
|
1035
|
+
autocaptureTimeout: (input.config.autoCaptureTimeout ?? 5) * 1e3
|
|
1036
|
+
};
|
|
1037
|
+
provider.setThresholds(thresholds);
|
|
1038
|
+
const modelType = input.config.modelVersion ?? DEFAULT_ID_CAPTURE_MODEL_VERSION;
|
|
1039
|
+
provider.setModelType(modelType);
|
|
1040
|
+
provider.setCallbacks({
|
|
1041
|
+
onFarAway: () => {
|
|
1042
|
+
if (!isCapturing) sendNotification("farAway");
|
|
1043
|
+
},
|
|
1044
|
+
onDetectionStarted: () => {},
|
|
1045
|
+
onMaskChange: (_show, _mask, _top, orientation) => {
|
|
1046
|
+
sendBack({
|
|
1047
|
+
type: "ORIENTATION_CHANGE",
|
|
1048
|
+
orientation
|
|
1049
|
+
});
|
|
1050
|
+
},
|
|
1051
|
+
onBlur: () => {
|
|
1052
|
+
if (!isCapturing) sendNotification("blur");
|
|
1053
|
+
},
|
|
1054
|
+
onGlare: () => {
|
|
1055
|
+
if (!isCapturing) sendNotification("glare");
|
|
1056
|
+
},
|
|
1057
|
+
onIdNotDetected: () => {
|
|
1058
|
+
if (!isCapturing) sendBack({
|
|
1059
|
+
type: "DETECTION_UPDATE",
|
|
1060
|
+
status: "idNotDetected"
|
|
1061
|
+
});
|
|
1062
|
+
},
|
|
1063
|
+
onSwitchToManualCapture: () => {
|
|
1064
|
+
isCapturing = false;
|
|
1065
|
+
sendBack({
|
|
1066
|
+
type: "DETECTION_UPDATE",
|
|
1067
|
+
status: "manualCapture"
|
|
1068
|
+
});
|
|
1069
|
+
},
|
|
1070
|
+
onCapturing: () => {
|
|
1071
|
+
clearNotificationTimeout();
|
|
1072
|
+
isCapturing = true;
|
|
1073
|
+
sendBack({
|
|
1074
|
+
type: "DETECTION_UPDATE",
|
|
1075
|
+
status: "capturing"
|
|
1076
|
+
});
|
|
1077
|
+
},
|
|
1078
|
+
onBestFrame: (blur, glare, orientation) => {
|
|
1079
|
+
if (currentCanvas) {
|
|
1080
|
+
bestCanvas = currentCanvas.clone();
|
|
1081
|
+
storedQualityElements = {
|
|
1082
|
+
glare,
|
|
1083
|
+
sharpness: blur
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (orientation === "horizontal" || orientation === "vertical") sendBack({
|
|
1087
|
+
type: "ORIENTATION_CHANGE",
|
|
1088
|
+
orientation
|
|
1089
|
+
});
|
|
1090
|
+
},
|
|
1091
|
+
onCapture: () => {
|
|
1092
|
+
isCapturing = false;
|
|
1093
|
+
if (bestCanvas) sendBack({
|
|
1094
|
+
type: "DETECTION_SUCCESS",
|
|
1095
|
+
canvas: bestCanvas,
|
|
1096
|
+
qualityElements: storedQualityElements
|
|
1097
|
+
});
|
|
1098
|
+
else if (currentCanvas) sendBack({
|
|
1099
|
+
type: "DETECTION_SUCCESS",
|
|
1100
|
+
canvas: currentCanvas.clone(),
|
|
1101
|
+
qualityElements: {}
|
|
1102
|
+
});
|
|
1103
|
+
},
|
|
1104
|
+
onIdTypeChange: (idType$1) => {
|
|
1105
|
+
sendBack({
|
|
1106
|
+
type: "ID_TYPE_CHANGE",
|
|
1107
|
+
idType: idType$1
|
|
1108
|
+
});
|
|
1109
|
+
},
|
|
1110
|
+
onIdSideChange: (side) => {
|
|
1111
|
+
if (isCapturing) return;
|
|
1112
|
+
sendBack({
|
|
1113
|
+
type: "ID_SIDE_CHANGE",
|
|
1114
|
+
side
|
|
1115
|
+
});
|
|
1116
|
+
},
|
|
1117
|
+
onCapturingCounterValueChange: (value) => {
|
|
1118
|
+
sendBack({
|
|
1119
|
+
type: "COUNTER_VALUE_CHANGE",
|
|
1120
|
+
value
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
sendBack({
|
|
1125
|
+
type: "DETECTION_UPDATE",
|
|
1126
|
+
status: "detecting"
|
|
1127
|
+
});
|
|
1128
|
+
const session = new StreamCanvasProcessingSession({
|
|
1129
|
+
capturer: input.frameCapturer,
|
|
1130
|
+
provider: {
|
|
1131
|
+
processFrame: async (frame) => {
|
|
1132
|
+
currentCanvas = IncodeCanvas.fromImageData(frame);
|
|
1133
|
+
sendBack({
|
|
1134
|
+
type: "DETECTION_FRAME",
|
|
1135
|
+
frame
|
|
1136
|
+
});
|
|
1137
|
+
await provider.processFrame(frame);
|
|
1138
|
+
},
|
|
1139
|
+
reset: () => {
|
|
1140
|
+
if (notificationTimeout) {
|
|
1141
|
+
timer.clearTimeout(notificationTimeout);
|
|
1142
|
+
notificationTimeout = null;
|
|
1143
|
+
currentNotificationStatus = null;
|
|
1144
|
+
}
|
|
1145
|
+
currentCanvas = null;
|
|
1146
|
+
bestCanvas = null;
|
|
1147
|
+
storedQualityElements = {};
|
|
1148
|
+
isCapturing = false;
|
|
1149
|
+
provider.reset();
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
onFrame: (frame) => sendBack({
|
|
1153
|
+
type: "DETECTION_FRAME",
|
|
1154
|
+
frame
|
|
1155
|
+
})
|
|
1156
|
+
});
|
|
1157
|
+
sendBack({
|
|
1158
|
+
type: "DETECTION_RESET_READY",
|
|
1159
|
+
reset: () => {
|
|
1160
|
+
provider.reset();
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
return () => {
|
|
1164
|
+
clearNotificationTimeout();
|
|
1165
|
+
session?.dispose();
|
|
1166
|
+
};
|
|
1167
|
+
});
|
|
1168
|
+
const uploadIdImageActor = fromPromise(async ({ input, signal }) => {
|
|
1169
|
+
const image = input.canvas.getBase64Image();
|
|
1170
|
+
if (!image) throw new Error(ID_ERROR_CODES.UPLOAD_ERROR);
|
|
1171
|
+
let metadata;
|
|
1172
|
+
if (input.deepsightService) try {
|
|
1173
|
+
const sessionToken = getToken();
|
|
1174
|
+
const frameSource = input.type === "back" ? "BACK_ID" : "FRONT_ID";
|
|
1175
|
+
await input.deepsightService.performVirtualCameraCheck(sessionToken, frameSource);
|
|
1176
|
+
const imageData$1 = input.canvas.getImageData();
|
|
1177
|
+
if (imageData$1) await input.deepsightService.analyzeFrame(imageData$1);
|
|
1178
|
+
const analysisStatus = input.deepsightService.getAnalysisStatus();
|
|
1179
|
+
const motionStatus = input.deepsightService.getMotionStatus();
|
|
1180
|
+
if (input.type === "front") await addDeviceStats({
|
|
1181
|
+
frontIdStatsAnalysisStatus: analysisStatus,
|
|
1182
|
+
backIdStatsAnalysisStatus: "",
|
|
1183
|
+
selfieStatsAnalysisStatus: "",
|
|
1184
|
+
motionStatus
|
|
1185
|
+
});
|
|
1186
|
+
else await addDeviceStats({
|
|
1187
|
+
frontIdStatsAnalysisStatus: "",
|
|
1188
|
+
backIdStatsAnalysisStatus: analysisStatus,
|
|
1189
|
+
selfieStatsAnalysisStatus: "",
|
|
1190
|
+
motionStatus
|
|
1191
|
+
});
|
|
1192
|
+
metadata = input.deepsightService.getMetadata();
|
|
1193
|
+
} catch {}
|
|
1194
|
+
const imageData = input.canvas?.getImageData() ?? void 0;
|
|
1195
|
+
return uploadIdImage({
|
|
1196
|
+
image,
|
|
1197
|
+
type: input.type,
|
|
1198
|
+
sendBase64: true,
|
|
1199
|
+
glare: input.qualityElements?.glare,
|
|
1200
|
+
sharpness: input.qualityElements?.sharpness,
|
|
1201
|
+
ageAssurance: false,
|
|
1202
|
+
signal,
|
|
1203
|
+
onProgress: input.onProgress,
|
|
1204
|
+
metadata,
|
|
1205
|
+
analyticsProvider: input.analyticsProvider,
|
|
1206
|
+
imageData
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
const processIdActor = fromPromise(async ({ input, signal }) => {
|
|
1210
|
+
return processId(input.isSecondId, "", signal);
|
|
1211
|
+
});
|
|
1212
|
+
const startRecordingActor = fromPromise(async ({ input }) => {
|
|
1213
|
+
if (!input.stream) return;
|
|
1214
|
+
const type = input.currentMode === "back" ? "backId" : "frontId";
|
|
1215
|
+
return startRecordingSession({
|
|
1216
|
+
config: input.config,
|
|
1217
|
+
stream: input.stream,
|
|
1218
|
+
existing: input.existing,
|
|
1219
|
+
type
|
|
1220
|
+
});
|
|
1221
|
+
});
|
|
1222
|
+
const checkMotionSensorActor = fromCallback(({ input, sendBack }) => {
|
|
1223
|
+
if (!input.motionProvider) {
|
|
1224
|
+
sendBack({
|
|
1225
|
+
type: "MOTION_STATUS",
|
|
1226
|
+
status: "UNCLEAR"
|
|
1227
|
+
});
|
|
1228
|
+
return () => {};
|
|
1229
|
+
}
|
|
1230
|
+
const timer = BrowserTimerProvider.getInstance();
|
|
1231
|
+
const interval = timer.setInterval(() => {
|
|
1232
|
+
sendBack({
|
|
1233
|
+
type: "MOTION_STATUS",
|
|
1234
|
+
status: input.motionProvider.check()
|
|
1235
|
+
});
|
|
1236
|
+
}, 500);
|
|
1237
|
+
return () => timer.clearInterval(interval);
|
|
1238
|
+
});
|
|
1239
|
+
const initializeDeepsightSessionActor = fromPromise(async ({ input }) => {
|
|
1240
|
+
if (!input.dependencies) return;
|
|
1241
|
+
const { loadDeepsightSession } = await import("./deepsightLoader-CGdK3U_s.esm.js");
|
|
1242
|
+
return loadDeepsightSession({
|
|
1243
|
+
ds: input.ds,
|
|
1244
|
+
storage: input.dependencies.storage,
|
|
1245
|
+
disableIpify: input.disableIpify
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
const checkVirtualCameraActor = fromPromise(async ({ input }) => {
|
|
1249
|
+
if (!input.deepsightService || !input.stream) return false;
|
|
1250
|
+
const videoTrack = input.stream.getVideoTracks()[0];
|
|
1251
|
+
if (!videoTrack) return false;
|
|
1252
|
+
return input.deepsightService.checkVirtualCamera(videoTrack);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/modules/id/idCaptureActions.ts
|
|
1257
|
+
function performStopMediaStream(context) {
|
|
1258
|
+
context.frameCapturer?.dispose();
|
|
1259
|
+
if (context.stream) stopStream(context.stream);
|
|
1260
|
+
}
|
|
1261
|
+
function performDisposeProvider(context) {
|
|
1262
|
+
context.provider?.dispose?.();
|
|
1263
|
+
}
|
|
1264
|
+
function performCleanupDeepsight(context) {
|
|
1265
|
+
context.deepsightService?.cleanup();
|
|
1266
|
+
}
|
|
1267
|
+
function performResetForBackCapture(context) {
|
|
1268
|
+
context.frameCapturer?.dispose();
|
|
1269
|
+
if (context.stream) stopStream(context.stream);
|
|
1270
|
+
context.provider?.reset();
|
|
1271
|
+
}
|
|
1272
|
+
function performResetDetection(context) {
|
|
1273
|
+
context.resetDetection?.();
|
|
1274
|
+
}
|
|
1275
|
+
function performTrackTutorialId() {
|
|
1276
|
+
addEvent({
|
|
1277
|
+
code: "tutorialVideoStarted",
|
|
1278
|
+
module: eventModuleNames.id,
|
|
1279
|
+
payload: { tutorialFrontID: true }
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
function performTrackContinue() {}
|
|
1283
|
+
function performStopMediaRecording(context) {
|
|
1284
|
+
if (context.recordingSession) stopRecording(context.recordingSession);
|
|
1285
|
+
}
|
|
1286
|
+
function getStreamFromEvent(event) {
|
|
1287
|
+
if ("output" in event) return event.output.stream;
|
|
1288
|
+
}
|
|
1289
|
+
function getProviderFromEvent(event) {
|
|
1290
|
+
if ("output" in event) return event.output.provider;
|
|
1291
|
+
}
|
|
1292
|
+
function getFrameCapturerFromEvent(event) {
|
|
1293
|
+
if ("output" in event) {
|
|
1294
|
+
const output = event.output;
|
|
1295
|
+
if (output.stream) return new StreamCanvasCapture(output.stream);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function getResetContextValues(context) {
|
|
1299
|
+
context.provider?.reset();
|
|
1300
|
+
const currentMode = context.config.onlyBack ? "back" : !context.config.enableId && context.config.enablePassport ? "passport" : "front";
|
|
1301
|
+
return {
|
|
1302
|
+
stream: void 0,
|
|
1303
|
+
provider: context.provider,
|
|
1304
|
+
frameCapturer: void 0,
|
|
1305
|
+
error: void 0,
|
|
1306
|
+
detectionStatus: "idle",
|
|
1307
|
+
counterValue: 0,
|
|
1308
|
+
orientation: void 0,
|
|
1309
|
+
capturedImages: {},
|
|
1310
|
+
uploadResponse: void 0,
|
|
1311
|
+
recordingSession: void 0,
|
|
1312
|
+
attemptsRemaining: context.config.captureAttempts,
|
|
1313
|
+
uploadError: void 0,
|
|
1314
|
+
permissionResult: void 0,
|
|
1315
|
+
resetDetection: void 0,
|
|
1316
|
+
idType: void 0,
|
|
1317
|
+
qualityElements: void 0,
|
|
1318
|
+
previewImageUrl: void 0,
|
|
1319
|
+
uploadProgress: void 0,
|
|
1320
|
+
currentMode,
|
|
1321
|
+
selectedDocumentType: void 0,
|
|
1322
|
+
manualCaptureTriggered: false
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function getClearUploadFailureValues(context) {
|
|
1326
|
+
if (context.previewImageUrl) revokeObjectURL(context.previewImageUrl);
|
|
1327
|
+
return {
|
|
1328
|
+
uploadError: void 0,
|
|
1329
|
+
detectionStatus: "idle",
|
|
1330
|
+
previewImageUrl: void 0,
|
|
1331
|
+
uploadProgress: void 0
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
function getUploadValidationError(context) {
|
|
1335
|
+
if (!context.uploadResponse) return ID_ERROR_CODES.SERVER;
|
|
1336
|
+
return validateUploadResponse(context.uploadResponse, {
|
|
1337
|
+
skipGlareFront: context.uploadResponse.skipGlareFront,
|
|
1338
|
+
skipGlareBack: context.uploadResponse.skipGlareBack
|
|
1339
|
+
})?.errorKey ?? ID_ERROR_CODES.SERVER;
|
|
1340
|
+
}
|
|
1341
|
+
function storeCapturedCanvasInProviderLogic(context, event) {
|
|
1342
|
+
let canvas = null;
|
|
1343
|
+
if ("canvas" in event && event.canvas) canvas = event.canvas;
|
|
1344
|
+
else canvas = context.frameCapturer?.getLatestCanvas() ?? null;
|
|
1345
|
+
if (!canvas || !context.provider) return;
|
|
1346
|
+
const canvasWidth = canvas.width();
|
|
1347
|
+
const canvasHeight = canvas.height();
|
|
1348
|
+
if (!canvasWidth || !canvasHeight) return;
|
|
1349
|
+
const originalCanvas = canvas;
|
|
1350
|
+
let frameRect;
|
|
1351
|
+
const { innerWidth: viewportWidth, innerHeight: viewportHeight } = getWindowDimensions();
|
|
1352
|
+
if (context.detectionArea) {
|
|
1353
|
+
const scaleX = viewportWidth / canvasWidth;
|
|
1354
|
+
const scaleY = viewportHeight / canvasHeight;
|
|
1355
|
+
const scale = Math.max(scaleX, scaleY);
|
|
1356
|
+
const displayedWidth = canvasWidth * scale;
|
|
1357
|
+
const displayedHeight = canvasHeight * scale;
|
|
1358
|
+
const offsetX = (viewportWidth - displayedWidth) / 2;
|
|
1359
|
+
const offsetY = (viewportHeight - displayedHeight) / 2;
|
|
1360
|
+
frameRect = {
|
|
1361
|
+
x: (context.detectionArea.x - offsetX) / scale,
|
|
1362
|
+
y: (context.detectionArea.y - offsetY) / scale,
|
|
1363
|
+
w: context.detectionArea.width / scale,
|
|
1364
|
+
h: context.detectionArea.height / scale
|
|
1365
|
+
};
|
|
1366
|
+
} else if (context.frameRect) {
|
|
1367
|
+
const scaleX = viewportWidth / canvasWidth;
|
|
1368
|
+
const scaleY = viewportHeight / canvasHeight;
|
|
1369
|
+
const scale = Math.max(scaleX, scaleY);
|
|
1370
|
+
const displayedWidth = canvasWidth * scale;
|
|
1371
|
+
const displayedHeight = canvasHeight * scale;
|
|
1372
|
+
const offsetX = (viewportWidth - displayedWidth) / 2;
|
|
1373
|
+
const offsetY = (viewportHeight - displayedHeight) / 2;
|
|
1374
|
+
frameRect = {
|
|
1375
|
+
x: (context.frameRect.x - offsetX) / scale,
|
|
1376
|
+
y: (context.frameRect.y - offsetY) / scale,
|
|
1377
|
+
w: context.frameRect.w / scale,
|
|
1378
|
+
h: context.frameRect.h / scale
|
|
1379
|
+
};
|
|
1380
|
+
} else {
|
|
1381
|
+
const quadValue = (context.provider.getLastProcessResult?.())?.quad;
|
|
1382
|
+
const hasQuad = !!quadValue;
|
|
1383
|
+
const quadSize = quadValue?.size ? quadValue.size() : quadValue?.length ?? 0;
|
|
1384
|
+
if (hasQuad && quadSize >= 4 && quadValue.get) {
|
|
1385
|
+
const p0 = quadValue.get(0);
|
|
1386
|
+
const p1 = quadValue.get(1);
|
|
1387
|
+
const p2 = quadValue.get(2);
|
|
1388
|
+
const p3 = quadValue.get(3);
|
|
1389
|
+
const minX = Math.min(p0.x, p1.x, p2.x, p3.x);
|
|
1390
|
+
const maxX = Math.max(p0.x, p1.x, p2.x, p3.x);
|
|
1391
|
+
const minY = Math.min(p0.y, p1.y, p2.y, p3.y);
|
|
1392
|
+
const maxY = Math.max(p0.y, p1.y, p2.y, p3.y);
|
|
1393
|
+
frameRect = {
|
|
1394
|
+
x: minX,
|
|
1395
|
+
y: minY,
|
|
1396
|
+
w: maxX - minX,
|
|
1397
|
+
h: maxY - minY
|
|
1398
|
+
};
|
|
1399
|
+
} else {
|
|
1400
|
+
const frameViewportWidth = Math.min(387, viewportWidth * .9);
|
|
1401
|
+
const frameViewportHeight = frameViewportWidth / (35 / 22);
|
|
1402
|
+
const frameViewportX = (viewportWidth - frameViewportWidth) / 2;
|
|
1403
|
+
const frameViewportY = (viewportHeight - frameViewportHeight) / 2;
|
|
1404
|
+
frameRect = {
|
|
1405
|
+
x: canvasWidth * frameViewportX / viewportWidth,
|
|
1406
|
+
y: canvasHeight * frameViewportY / viewportHeight,
|
|
1407
|
+
w: canvasWidth * frameViewportWidth / viewportWidth,
|
|
1408
|
+
h: canvasHeight * frameViewportHeight / viewportHeight
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
const transformedCanvas = context.provider.transformPerspective(originalCanvas, frameRect);
|
|
1413
|
+
context.provider.setCapturedCanvases(originalCanvas, transformedCanvas);
|
|
1414
|
+
}
|
|
1415
|
+
function getStoreCapturedImageValues(context, event) {
|
|
1416
|
+
if (!context.currentMode) return {
|
|
1417
|
+
capturedImages: context.capturedImages,
|
|
1418
|
+
previewImageUrl: context.previewImageUrl
|
|
1419
|
+
};
|
|
1420
|
+
const transformedCanvas = context.provider?.getCapturedCanvas();
|
|
1421
|
+
const transformedImage = transformedCanvas?.getBase64Image(1, true);
|
|
1422
|
+
let fallbackImage = "";
|
|
1423
|
+
if ("output" in event) fallbackImage = event.output.originalImage ?? "";
|
|
1424
|
+
const imageData = { imageBase64: transformedImage ?? fallbackImage };
|
|
1425
|
+
let capturedImages = context.capturedImages;
|
|
1426
|
+
if (context.currentMode === "front" || context.currentMode === "passport") capturedImages = {
|
|
1427
|
+
...context.capturedImages,
|
|
1428
|
+
front: imageData
|
|
1429
|
+
};
|
|
1430
|
+
else capturedImages = {
|
|
1431
|
+
...context.capturedImages,
|
|
1432
|
+
back: imageData
|
|
1433
|
+
};
|
|
1434
|
+
let previewImageUrl = context.previewImageUrl;
|
|
1435
|
+
if (transformedCanvas) {
|
|
1436
|
+
transformedCanvas.updateBlob();
|
|
1437
|
+
const blobData = transformedCanvas.getBlobData();
|
|
1438
|
+
if (blobData?.url) previewImageUrl = blobData.url;
|
|
1439
|
+
} else if ("canvas" in event && event.canvas) {
|
|
1440
|
+
const canvas = event.canvas;
|
|
1441
|
+
canvas.updateBlob();
|
|
1442
|
+
const blobData = canvas.getBlobData();
|
|
1443
|
+
if (blobData?.url) previewImageUrl = blobData.url;
|
|
1444
|
+
}
|
|
1445
|
+
return {
|
|
1446
|
+
capturedImages,
|
|
1447
|
+
previewImageUrl
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function getDetectionStatusFromUpdate(context, event) {
|
|
1451
|
+
if (event.type === "DETECTION_UPDATE") {
|
|
1452
|
+
const newStatus = event.status;
|
|
1453
|
+
const currentStatus = context.detectionStatus;
|
|
1454
|
+
if ((newStatus === "blur" || newStatus === "glare") && (currentStatus === "wrongSide" || currentStatus === "farAway")) return currentStatus;
|
|
1455
|
+
if ((newStatus === "wrongSide" || newStatus === "farAway") && (currentStatus === "blur" || currentStatus === "glare")) return newStatus;
|
|
1456
|
+
return newStatus;
|
|
1457
|
+
}
|
|
1458
|
+
return "idle";
|
|
1459
|
+
}
|
|
1460
|
+
function getDetectionStatusFromSideChange(context, side) {
|
|
1461
|
+
const detectedSide = side?.toLowerCase() || "";
|
|
1462
|
+
const currentMode = context.currentMode;
|
|
1463
|
+
if (detectedSide === "wrong") return "wrongSide";
|
|
1464
|
+
const isBackDetected = detectedSide.includes("back") && !detectedSide.includes("front");
|
|
1465
|
+
const isFrontDetected = detectedSide.includes("front") && !detectedSide.includes("back");
|
|
1466
|
+
if (currentMode === "front" && isBackDetected || currentMode === "back" && isFrontDetected) return "wrongSide";
|
|
1467
|
+
if (currentMode === "front" && isFrontDetected || currentMode === "back" && isBackDetected || currentMode === "passport") return "detecting";
|
|
1468
|
+
return "detecting";
|
|
1469
|
+
}
|
|
1470
|
+
function getCurrentModeFromEvent(context, event) {
|
|
1471
|
+
if ("documentType" in event) {
|
|
1472
|
+
if (event.documentType === "passport") return "passport";
|
|
1473
|
+
return "front";
|
|
1474
|
+
}
|
|
1475
|
+
if (event.type === "FRONT_COMPLETE") return "back";
|
|
1476
|
+
return context.currentMode;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/modules/id/idCaptureGuards.ts
|
|
1481
|
+
const hasShowTutorialGuard = ({ context }) => context.config.showTutorial;
|
|
1482
|
+
const hasShowDocumentChooserGuard = ({ context }) => context.config.showDocumentChooserScreen ?? false;
|
|
1483
|
+
const isPermissionGrantedGuard = ({ event }) => {
|
|
1484
|
+
if ("output" in event) return event.output === "granted";
|
|
1485
|
+
return false;
|
|
1486
|
+
};
|
|
1487
|
+
const isPermissionDeniedErrorGuard = ({ event }) => {
|
|
1488
|
+
if ("error" in event) {
|
|
1489
|
+
const error = event.error;
|
|
1490
|
+
return error?.name === "NotAllowedError" || error?.name === "PermissionDeniedError";
|
|
1491
|
+
}
|
|
1492
|
+
return false;
|
|
1493
|
+
};
|
|
1494
|
+
const hasStreamGuard = ({ context }) => context.stream !== void 0;
|
|
1495
|
+
const hasAttemptsRemainingGuard = ({ context }) => context.attemptsRemaining > 0;
|
|
1496
|
+
const hasCapturedImageGuard = ({ context }) => {
|
|
1497
|
+
return context.provider?.getCapturedCanvas() !== null;
|
|
1498
|
+
};
|
|
1499
|
+
const hasUploadValidationErrorGuard = ({ context }) => {
|
|
1500
|
+
if (!context.uploadResponse) return false;
|
|
1501
|
+
return validateUploadResponse(context.uploadResponse, {
|
|
1502
|
+
skipGlareFront: context.uploadResponse.skipGlareFront,
|
|
1503
|
+
skipGlareBack: context.uploadResponse.skipGlareBack
|
|
1504
|
+
}) !== void 0;
|
|
1505
|
+
};
|
|
1506
|
+
const isFrontModeGuard = ({ context }) => context.currentMode === "front" || context.currentMode === "passport";
|
|
1507
|
+
const isOnlyFrontGuard = ({ context }) => context.config.onlyFront;
|
|
1508
|
+
const shouldContinueToBackGuard = ({ context }) => {
|
|
1509
|
+
if (context.currentMode === "passport") return false;
|
|
1510
|
+
if (context.currentMode !== "front") return false;
|
|
1511
|
+
if (context.config.onlyFront) return false;
|
|
1512
|
+
if (context.config.onlyBack) return false;
|
|
1513
|
+
if (!context.config.enableId && context.config.enablePassport) return false;
|
|
1514
|
+
if (context.uploadResponse?.skipBackIdCapture) return false;
|
|
1515
|
+
return true;
|
|
1516
|
+
};
|
|
1517
|
+
const isDeepsightEnabledGuard = ({ context }) => context.dependencies !== void 0;
|
|
1518
|
+
const isDeepsightReadyGuard = ({ context }) => context.deepsightService !== void 0;
|
|
1519
|
+
const needsDeepsightInitGuard = ({ context }) => context.dependencies !== void 0 && context.deepsightService === void 0 && !context.deepsightInitAttempted;
|
|
1520
|
+
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/modules/id/idCaptureStateMachine.ts
|
|
1523
|
+
function getIdErrorCodeFromUnknown(error) {
|
|
1524
|
+
if (error instanceof Error) {
|
|
1525
|
+
const message = error.message;
|
|
1526
|
+
return Object.values(ID_ERROR_CODES).find((value) => message.includes(value));
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
const _idCaptureMachine = setup({
|
|
1530
|
+
types: {
|
|
1531
|
+
context: {},
|
|
1532
|
+
events: {},
|
|
1533
|
+
input: {}
|
|
1534
|
+
},
|
|
1535
|
+
actors: {
|
|
1536
|
+
checkPermission: checkPermissionActor,
|
|
1537
|
+
requestPermission: requestPermissionActor,
|
|
1538
|
+
initializeCamera: initializeCameraActor,
|
|
1539
|
+
runDetection: runDetectionActor,
|
|
1540
|
+
uploadIdImage: uploadIdImageActor,
|
|
1541
|
+
processId: processIdActor,
|
|
1542
|
+
startRecording: startRecordingActor,
|
|
1543
|
+
checkMotionSensor: checkMotionSensorActor,
|
|
1544
|
+
initializeDeepsightSession: initializeDeepsightSessionActor,
|
|
1545
|
+
checkVirtualCamera: checkVirtualCameraActor
|
|
1546
|
+
},
|
|
1547
|
+
actions: {
|
|
1548
|
+
stopMediaStream: assign(({ context }) => {
|
|
1549
|
+
performStopMediaStream(context);
|
|
1550
|
+
return {
|
|
1551
|
+
stream: void 0,
|
|
1552
|
+
frameCapturer: void 0
|
|
1553
|
+
};
|
|
1554
|
+
}),
|
|
1555
|
+
disposeProvider: ({ context }) => {
|
|
1556
|
+
performDisposeProvider(context);
|
|
1557
|
+
},
|
|
1558
|
+
cleanupDeepsight: ({ context }) => {
|
|
1559
|
+
performCleanupDeepsight(context);
|
|
1560
|
+
},
|
|
1561
|
+
resetForBackCapture: assign(({ context }) => {
|
|
1562
|
+
performResetForBackCapture(context);
|
|
1563
|
+
return {
|
|
1564
|
+
stream: void 0,
|
|
1565
|
+
frameCapturer: void 0,
|
|
1566
|
+
detectionStatus: "idle",
|
|
1567
|
+
counterValue: 0,
|
|
1568
|
+
orientation: void 0,
|
|
1569
|
+
resetDetection: void 0,
|
|
1570
|
+
idType: void 0,
|
|
1571
|
+
qualityElements: void 0,
|
|
1572
|
+
debugFrame: void 0,
|
|
1573
|
+
frameRect: void 0,
|
|
1574
|
+
previewImageUrl: void 0,
|
|
1575
|
+
uploadProgress: void 0,
|
|
1576
|
+
manualCaptureTriggered: false
|
|
1577
|
+
};
|
|
1578
|
+
}),
|
|
1579
|
+
prepareForBackCapture: assign(({ context }) => {
|
|
1580
|
+
context.provider?.reset();
|
|
1581
|
+
return {
|
|
1582
|
+
detectionStatus: "idle",
|
|
1583
|
+
counterValue: 0,
|
|
1584
|
+
orientation: void 0,
|
|
1585
|
+
resetDetection: void 0,
|
|
1586
|
+
idType: void 0,
|
|
1587
|
+
qualityElements: void 0,
|
|
1588
|
+
debugFrame: void 0,
|
|
1589
|
+
frameRect: void 0,
|
|
1590
|
+
previewImageUrl: void 0,
|
|
1591
|
+
uploadProgress: void 0,
|
|
1592
|
+
manualCaptureTriggered: false
|
|
1593
|
+
};
|
|
1594
|
+
}),
|
|
1595
|
+
setStreamAndCapturer: assign({
|
|
1596
|
+
stream: ({ event }) => getStreamFromEvent(event),
|
|
1597
|
+
provider: ({ event }) => getProviderFromEvent(event),
|
|
1598
|
+
frameCapturer: ({ event }) => getFrameCapturerFromEvent(event)
|
|
1599
|
+
}),
|
|
1600
|
+
trackTutorialId: () => {
|
|
1601
|
+
performTrackTutorialId();
|
|
1602
|
+
},
|
|
1603
|
+
trackContinue: () => {
|
|
1604
|
+
/* @__PURE__ */ performTrackContinue();
|
|
1605
|
+
},
|
|
1606
|
+
resetContext: assign(({ context }) => getResetContextValues(context)),
|
|
1607
|
+
resetDetection: ({ context }) => {
|
|
1608
|
+
performResetDetection(context);
|
|
1609
|
+
},
|
|
1610
|
+
captureImage: assign({ qualityElements: ({ event }) => {
|
|
1611
|
+
if ("qualityElements" in event) return event.qualityElements;
|
|
1612
|
+
} }),
|
|
1613
|
+
storeCapturedCanvasInProvider: ({ context, event }) => {
|
|
1614
|
+
storeCapturedCanvasInProviderLogic(context, event);
|
|
1615
|
+
},
|
|
1616
|
+
captureLatestFrame: ({ context }) => {
|
|
1617
|
+
context.frameCapturer?.getLatestCanvas();
|
|
1618
|
+
},
|
|
1619
|
+
clearUploadFailure: assign(({ context }) => getClearUploadFailureValues(context)),
|
|
1620
|
+
decrementAttemptsRemaining: assign(({ context }) => ({ attemptsRemaining: context.attemptsRemaining - 1 })),
|
|
1621
|
+
setUploadErrorFromUploadValidation: assign({ uploadError: ({ context }) => getUploadValidationError(context) }),
|
|
1622
|
+
stopMediaRecording: ({ context }) => {
|
|
1623
|
+
performStopMediaRecording(context);
|
|
1624
|
+
},
|
|
1625
|
+
clearRecordingSession: assign({ recordingSession: () => void 0 }),
|
|
1626
|
+
setSelectedDocument: assign({ selectedDocumentType: ({ event }) => {
|
|
1627
|
+
if ("documentType" in event) return event.documentType;
|
|
1628
|
+
} }),
|
|
1629
|
+
setCurrentMode: assign({ currentMode: ({ event, context }) => getCurrentModeFromEvent(context, event) }),
|
|
1630
|
+
storeCapturedImage: assign(({ context, event }) => getStoreCapturedImageValues(context, event)),
|
|
1631
|
+
setDetectionStatus: assign({ detectionStatus: ({ event, context }) => getDetectionStatusFromUpdate(context, event) }),
|
|
1632
|
+
setCounterValue: assign({ counterValue: ({ event }) => {
|
|
1633
|
+
if ("value" in event) return event.value;
|
|
1634
|
+
return 0;
|
|
1635
|
+
} }),
|
|
1636
|
+
setIdType: assign({ idType: ({ event }) => {
|
|
1637
|
+
if ("idType" in event) return event.idType;
|
|
1638
|
+
} }),
|
|
1639
|
+
setOrientation: assign({ orientation: ({ event }) => {
|
|
1640
|
+
if ("orientation" in event) return event.orientation;
|
|
1641
|
+
} }),
|
|
1642
|
+
setFrameRect: assign({ frameRect: ({ event }) => {
|
|
1643
|
+
if ("frameRect" in event) return event.frameRect;
|
|
1644
|
+
} }),
|
|
1645
|
+
setDetectionArea: assign({ detectionArea: ({ event }) => {
|
|
1646
|
+
if ("detectionArea" in event) return event.detectionArea;
|
|
1647
|
+
} }),
|
|
1648
|
+
setMotionStatus: assign({ motionStatus: ({ event }) => {
|
|
1649
|
+
if ("status" in event && event.type === "MOTION_STATUS") return event.status;
|
|
1650
|
+
} })
|
|
1651
|
+
},
|
|
1652
|
+
guards: {
|
|
1653
|
+
hasShowTutorial: hasShowTutorialGuard,
|
|
1654
|
+
hasShowDocumentChooser: hasShowDocumentChooserGuard,
|
|
1655
|
+
isPermissionGranted: isPermissionGrantedGuard,
|
|
1656
|
+
isPermissionDeniedError: isPermissionDeniedErrorGuard,
|
|
1657
|
+
hasStream: hasStreamGuard,
|
|
1658
|
+
hasAttemptsRemaining: hasAttemptsRemainingGuard,
|
|
1659
|
+
hasCapturedImage: hasCapturedImageGuard,
|
|
1660
|
+
hasUploadValidationError: hasUploadValidationErrorGuard,
|
|
1661
|
+
isFrontMode: isFrontModeGuard,
|
|
1662
|
+
isOnlyFront: isOnlyFrontGuard,
|
|
1663
|
+
shouldContinueToBack: shouldContinueToBackGuard,
|
|
1664
|
+
isDeepsightEnabled: isDeepsightEnabledGuard,
|
|
1665
|
+
isDeepsightReady: isDeepsightReadyGuard,
|
|
1666
|
+
needsDeepsightInit: needsDeepsightInitGuard
|
|
1667
|
+
}
|
|
1668
|
+
}).createMachine({
|
|
1669
|
+
id: "idCapture",
|
|
1670
|
+
initial: "idle",
|
|
1671
|
+
context: ({ input }) => {
|
|
1672
|
+
const currentMode = input.config.onlyBack ? "back" : !input.config.enableId && input.config.enablePassport ? "passport" : "front";
|
|
1673
|
+
return {
|
|
1674
|
+
config: input.config,
|
|
1675
|
+
currentMode,
|
|
1676
|
+
selectedDocumentType: void 0,
|
|
1677
|
+
stream: void 0,
|
|
1678
|
+
provider: input.provider,
|
|
1679
|
+
frameCapturer: void 0,
|
|
1680
|
+
error: void 0,
|
|
1681
|
+
detectionStatus: "idle",
|
|
1682
|
+
counterValue: 0,
|
|
1683
|
+
orientation: void 0,
|
|
1684
|
+
capturedImages: {},
|
|
1685
|
+
uploadResponse: void 0,
|
|
1686
|
+
recordingSession: void 0,
|
|
1687
|
+
attemptsRemaining: input.config.captureAttempts,
|
|
1688
|
+
uploadError: void 0,
|
|
1689
|
+
permissionResult: void 0,
|
|
1690
|
+
resetDetection: void 0,
|
|
1691
|
+
idType: void 0,
|
|
1692
|
+
qualityElements: void 0,
|
|
1693
|
+
debugFrame: void 0,
|
|
1694
|
+
frameRect: void 0,
|
|
1695
|
+
detectionArea: void 0,
|
|
1696
|
+
previewImageUrl: void 0,
|
|
1697
|
+
uploadProgress: void 0,
|
|
1698
|
+
motionStatus: void 0,
|
|
1699
|
+
manualCaptureTriggered: false,
|
|
1700
|
+
deepsightService: void 0,
|
|
1701
|
+
analyticsProvider: input.analyticsProvider,
|
|
1702
|
+
dependencies: input.dependencies,
|
|
1703
|
+
disableIpify: getDisableIpify(),
|
|
1704
|
+
deepsightInitAttempted: false
|
|
1705
|
+
};
|
|
1706
|
+
},
|
|
1707
|
+
on: {
|
|
1708
|
+
QUIT: { target: "#idCapture.closed" },
|
|
1709
|
+
UPDATE_DETECTION_AREA: { actions: "setDetectionArea" }
|
|
1710
|
+
},
|
|
1711
|
+
states: {
|
|
1712
|
+
idle: { on: { LOAD: [
|
|
1713
|
+
{
|
|
1714
|
+
target: "chooser",
|
|
1715
|
+
guard: "hasShowDocumentChooser"
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
target: "tutorial",
|
|
1719
|
+
guard: "hasShowTutorial"
|
|
1720
|
+
},
|
|
1721
|
+
{ target: "loading" }
|
|
1722
|
+
] } },
|
|
1723
|
+
chooser: { on: { SELECT_DOCUMENT: [{
|
|
1724
|
+
target: "tutorial",
|
|
1725
|
+
guard: "hasShowTutorial",
|
|
1726
|
+
actions: ["setSelectedDocument", "setCurrentMode"]
|
|
1727
|
+
}, {
|
|
1728
|
+
target: "loading",
|
|
1729
|
+
actions: ["setSelectedDocument", "setCurrentMode"]
|
|
1730
|
+
}] } },
|
|
1731
|
+
loading: {
|
|
1732
|
+
type: "parallel",
|
|
1733
|
+
states: {
|
|
1734
|
+
permissionCheck: {
|
|
1735
|
+
initial: "checking",
|
|
1736
|
+
states: {
|
|
1737
|
+
checking: { invoke: {
|
|
1738
|
+
id: "checkPermissionLoading",
|
|
1739
|
+
src: "checkPermission",
|
|
1740
|
+
onDone: {
|
|
1741
|
+
target: "done",
|
|
1742
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1743
|
+
},
|
|
1744
|
+
onError: {
|
|
1745
|
+
target: "done",
|
|
1746
|
+
actions: assign({ permissionResult: () => "prompt" })
|
|
1747
|
+
}
|
|
1748
|
+
} },
|
|
1749
|
+
done: { type: "final" }
|
|
1750
|
+
}
|
|
1751
|
+
},
|
|
1752
|
+
deepsightInit: {
|
|
1753
|
+
initial: "initializing",
|
|
1754
|
+
states: {
|
|
1755
|
+
initializing: { invoke: {
|
|
1756
|
+
id: "initDeepsightLoading",
|
|
1757
|
+
src: "initializeDeepsightSession",
|
|
1758
|
+
input: ({ context }) => ({
|
|
1759
|
+
ds: context.config.ds,
|
|
1760
|
+
dependencies: context.dependencies,
|
|
1761
|
+
disableIpify: context.disableIpify
|
|
1762
|
+
}),
|
|
1763
|
+
onDone: {
|
|
1764
|
+
target: "done",
|
|
1765
|
+
actions: assign({
|
|
1766
|
+
deepsightService: ({ event }) => event.output,
|
|
1767
|
+
deepsightInitAttempted: () => true
|
|
1768
|
+
})
|
|
1769
|
+
},
|
|
1770
|
+
onError: {
|
|
1771
|
+
target: "done",
|
|
1772
|
+
actions: [assign({ deepsightInitAttempted: () => true }), () => console.warn("Deepsight initialization failed")]
|
|
1773
|
+
}
|
|
1774
|
+
} },
|
|
1775
|
+
done: { type: "final" }
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
onDone: [{
|
|
1780
|
+
target: "capture",
|
|
1781
|
+
guard: "isPermissionGranted"
|
|
1782
|
+
}, { target: "permissions" }]
|
|
1783
|
+
},
|
|
1784
|
+
tutorial: {
|
|
1785
|
+
initial: "checkingPermission",
|
|
1786
|
+
entry: "trackTutorialId",
|
|
1787
|
+
states: {
|
|
1788
|
+
checkingPermission: {
|
|
1789
|
+
invoke: {
|
|
1790
|
+
id: "checkPermissionTutorial",
|
|
1791
|
+
src: "checkPermission",
|
|
1792
|
+
onDone: [{
|
|
1793
|
+
target: "initializingCamera",
|
|
1794
|
+
guard: "isPermissionGranted",
|
|
1795
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1796
|
+
}, {
|
|
1797
|
+
target: "ready",
|
|
1798
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1799
|
+
}]
|
|
1800
|
+
},
|
|
1801
|
+
on: { NEXT_STEP: {
|
|
1802
|
+
target: "initializingCamera",
|
|
1803
|
+
actions: "trackContinue"
|
|
1804
|
+
} }
|
|
1805
|
+
},
|
|
1806
|
+
initializingCamera: {
|
|
1807
|
+
type: "parallel",
|
|
1808
|
+
states: {
|
|
1809
|
+
cameraInit: {
|
|
1810
|
+
initial: "initializingDeepsight",
|
|
1811
|
+
states: {
|
|
1812
|
+
initializingDeepsight: { invoke: {
|
|
1813
|
+
id: "tutorialInitDeepsight",
|
|
1814
|
+
src: "initializeDeepsightSession",
|
|
1815
|
+
input: ({ context }) => ({
|
|
1816
|
+
ds: context.config.ds,
|
|
1817
|
+
dependencies: context.dependencies,
|
|
1818
|
+
disableIpify: context.disableIpify
|
|
1819
|
+
}),
|
|
1820
|
+
onDone: {
|
|
1821
|
+
target: "initializingStream",
|
|
1822
|
+
actions: assign({
|
|
1823
|
+
deepsightService: ({ event }) => event.output,
|
|
1824
|
+
deepsightInitAttempted: () => true
|
|
1825
|
+
})
|
|
1826
|
+
},
|
|
1827
|
+
onError: {
|
|
1828
|
+
target: "initializingStream",
|
|
1829
|
+
actions: [assign({ deepsightInitAttempted: () => true }), () => console.warn("Deepsight initialization failed in tutorial")]
|
|
1830
|
+
}
|
|
1831
|
+
} },
|
|
1832
|
+
initializingStream: { invoke: {
|
|
1833
|
+
id: "tutorialInitCamera",
|
|
1834
|
+
src: "initializeCamera",
|
|
1835
|
+
input: ({ context }) => {
|
|
1836
|
+
if (!context.provider) throw new Error("Provider is required");
|
|
1837
|
+
return {
|
|
1838
|
+
provider: context.provider,
|
|
1839
|
+
config: context.config,
|
|
1840
|
+
deepsightService: context.deepsightService
|
|
1841
|
+
};
|
|
1842
|
+
},
|
|
1843
|
+
onDone: {
|
|
1844
|
+
target: "ready",
|
|
1845
|
+
actions: "setStreamAndCapturer"
|
|
1846
|
+
},
|
|
1847
|
+
onError: [{
|
|
1848
|
+
target: "#idCapture.tutorial.ready",
|
|
1849
|
+
guard: "isPermissionDeniedError",
|
|
1850
|
+
actions: assign({ permissionResult: () => "denied" })
|
|
1851
|
+
}, {
|
|
1852
|
+
target: "#idCapture.tutorial.ready",
|
|
1853
|
+
actions: assign({ error: ({ event }) => String(event.error) })
|
|
1854
|
+
}]
|
|
1855
|
+
} },
|
|
1856
|
+
ready: { type: "final" }
|
|
1857
|
+
}
|
|
1858
|
+
},
|
|
1859
|
+
userIntent: {
|
|
1860
|
+
initial: "booting",
|
|
1861
|
+
states: {
|
|
1862
|
+
booting: {
|
|
1863
|
+
always: [{
|
|
1864
|
+
target: "clicked",
|
|
1865
|
+
guard: "hasStream"
|
|
1866
|
+
}],
|
|
1867
|
+
on: { NEXT_STEP: {
|
|
1868
|
+
target: "clicked",
|
|
1869
|
+
actions: "trackContinue"
|
|
1870
|
+
} }
|
|
1871
|
+
},
|
|
1872
|
+
clicked: { type: "final" }
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
onDone: [{
|
|
1877
|
+
target: "#tutorialCameraReady",
|
|
1878
|
+
guard: "hasStream"
|
|
1879
|
+
}, { target: "#idCapture.capture" }]
|
|
1880
|
+
},
|
|
1881
|
+
cameraReady: {
|
|
1882
|
+
id: "tutorialCameraReady",
|
|
1883
|
+
on: { NEXT_STEP: {
|
|
1884
|
+
target: "#idCapture.capture",
|
|
1885
|
+
actions: "trackContinue"
|
|
1886
|
+
} }
|
|
1887
|
+
},
|
|
1888
|
+
ready: { on: { NEXT_STEP: {
|
|
1889
|
+
target: "waitingForPermission",
|
|
1890
|
+
actions: "trackContinue"
|
|
1891
|
+
} } },
|
|
1892
|
+
waitingForPermission: { invoke: {
|
|
1893
|
+
id: "checkPermissionWaiting",
|
|
1894
|
+
src: "checkPermission",
|
|
1895
|
+
onDone: [{
|
|
1896
|
+
target: "#idCapture.capture",
|
|
1897
|
+
guard: "isPermissionGranted",
|
|
1898
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1899
|
+
}, {
|
|
1900
|
+
target: "#idCapture.permissions",
|
|
1901
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1902
|
+
}]
|
|
1903
|
+
} }
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
permissions: {
|
|
1907
|
+
initial: "idle",
|
|
1908
|
+
states: {
|
|
1909
|
+
idle: {
|
|
1910
|
+
invoke: {
|
|
1911
|
+
id: "checkPermissionIdle",
|
|
1912
|
+
src: "checkPermission",
|
|
1913
|
+
onDone: [
|
|
1914
|
+
{
|
|
1915
|
+
target: "#idCapture.capture",
|
|
1916
|
+
guard: "isPermissionGranted",
|
|
1917
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
target: "denied",
|
|
1921
|
+
guard: ({ event }) => event.output === "denied",
|
|
1922
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
target: "waitingForUser",
|
|
1926
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1927
|
+
}
|
|
1928
|
+
],
|
|
1929
|
+
onError: {
|
|
1930
|
+
target: "waitingForUser",
|
|
1931
|
+
actions: assign({ permissionResult: () => "prompt" })
|
|
1932
|
+
}
|
|
1933
|
+
},
|
|
1934
|
+
on: {
|
|
1935
|
+
REQUEST_PERMISSION: "requesting",
|
|
1936
|
+
GO_TO_LEARN_MORE: "learnMore"
|
|
1937
|
+
}
|
|
1938
|
+
},
|
|
1939
|
+
waitingForUser: { on: {
|
|
1940
|
+
REQUEST_PERMISSION: "requesting",
|
|
1941
|
+
GO_TO_LEARN_MORE: "learnMore"
|
|
1942
|
+
} },
|
|
1943
|
+
learnMore: { on: {
|
|
1944
|
+
BACK: "idle",
|
|
1945
|
+
REQUEST_PERMISSION: "requesting"
|
|
1946
|
+
} },
|
|
1947
|
+
requesting: { invoke: {
|
|
1948
|
+
id: "requestPermission",
|
|
1949
|
+
src: "requestPermission",
|
|
1950
|
+
onDone: [
|
|
1951
|
+
{
|
|
1952
|
+
target: "#idCapture.capture",
|
|
1953
|
+
guard: "isPermissionGranted",
|
|
1954
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
target: "denied",
|
|
1958
|
+
guard: ({ event }) => event.output === "denied",
|
|
1959
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1960
|
+
},
|
|
1961
|
+
{
|
|
1962
|
+
target: "idle",
|
|
1963
|
+
actions: assign({ permissionResult: ({ event }) => event.output })
|
|
1964
|
+
}
|
|
1965
|
+
],
|
|
1966
|
+
onError: { target: "denied" }
|
|
1967
|
+
} },
|
|
1968
|
+
denied: { entry: assign({ permissionResult: () => "refresh" }) }
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
capture: {
|
|
1972
|
+
initial: "checkingStream",
|
|
1973
|
+
exit: ["stopMediaRecording", "clearRecordingSession"],
|
|
1974
|
+
on: { SET_FRAME_RECT: { actions: "setFrameRect" } },
|
|
1975
|
+
states: {
|
|
1976
|
+
checkingStream: { always: [
|
|
1977
|
+
{
|
|
1978
|
+
target: "initializingDeepsight",
|
|
1979
|
+
guard: "needsDeepsightInit"
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
target: "detecting",
|
|
1983
|
+
guard: "hasStream"
|
|
1984
|
+
},
|
|
1985
|
+
{ target: "initializing" }
|
|
1986
|
+
] },
|
|
1987
|
+
initializingDeepsight: { invoke: {
|
|
1988
|
+
id: "initDeepsightCapture",
|
|
1989
|
+
src: "initializeDeepsightSession",
|
|
1990
|
+
input: ({ context }) => ({
|
|
1991
|
+
ds: context.config.ds,
|
|
1992
|
+
dependencies: context.dependencies,
|
|
1993
|
+
disableIpify: context.disableIpify
|
|
1994
|
+
}),
|
|
1995
|
+
onDone: {
|
|
1996
|
+
target: "checkingStream",
|
|
1997
|
+
actions: assign({
|
|
1998
|
+
deepsightService: ({ event }) => event.output,
|
|
1999
|
+
deepsightInitAttempted: () => true
|
|
2000
|
+
})
|
|
2001
|
+
},
|
|
2002
|
+
onError: {
|
|
2003
|
+
target: "checkingStream",
|
|
2004
|
+
actions: [assign({ deepsightInitAttempted: () => true }), () => console.warn("Deepsight initialization failed in capture")]
|
|
2005
|
+
}
|
|
2006
|
+
} },
|
|
2007
|
+
initializing: { invoke: {
|
|
2008
|
+
id: "initializeCamera",
|
|
2009
|
+
src: "initializeCamera",
|
|
2010
|
+
input: ({ context }) => {
|
|
2011
|
+
if (!context.provider) throw new Error("Provider is required");
|
|
2012
|
+
return {
|
|
2013
|
+
provider: context.provider,
|
|
2014
|
+
config: context.config,
|
|
2015
|
+
deepsightService: context.deepsightService
|
|
2016
|
+
};
|
|
2017
|
+
},
|
|
2018
|
+
onDone: {
|
|
2019
|
+
target: "detecting",
|
|
2020
|
+
actions: "setStreamAndCapturer"
|
|
2021
|
+
},
|
|
2022
|
+
onError: [{
|
|
2023
|
+
target: "#idCapture.permissions",
|
|
2024
|
+
guard: "isPermissionDeniedError",
|
|
2025
|
+
actions: assign({ permissionResult: () => "denied" })
|
|
2026
|
+
}, {
|
|
2027
|
+
target: "#idCapture.error",
|
|
2028
|
+
actions: assign({ error: ({ event }) => String(event.error) })
|
|
2029
|
+
}]
|
|
2030
|
+
} },
|
|
2031
|
+
detecting: {
|
|
2032
|
+
always: [{
|
|
2033
|
+
target: "manualCaptureWaiting",
|
|
2034
|
+
guard: ({ context }) => context.manualCaptureTriggered,
|
|
2035
|
+
actions: assign({ detectionStatus: () => "manualCapture" })
|
|
2036
|
+
}],
|
|
2037
|
+
entry: [assign({ detectionStatus: () => "detecting" })],
|
|
2038
|
+
invoke: [{
|
|
2039
|
+
id: "startRecording",
|
|
2040
|
+
src: "startRecording",
|
|
2041
|
+
input: ({ context }) => ({
|
|
2042
|
+
config: context.config,
|
|
2043
|
+
stream: context.stream,
|
|
2044
|
+
existing: context.recordingSession,
|
|
2045
|
+
currentMode: context.currentMode
|
|
2046
|
+
}),
|
|
2047
|
+
onDone: { actions: assign({ recordingSession: ({ context, event }) => {
|
|
2048
|
+
return event.output ?? context.recordingSession;
|
|
2049
|
+
} }) },
|
|
2050
|
+
onError: { actions: () => void 0 }
|
|
2051
|
+
}, {
|
|
2052
|
+
id: "runDetection",
|
|
2053
|
+
src: "runDetection",
|
|
2054
|
+
input: ({ context }) => ({
|
|
2055
|
+
frameCapturer: context.frameCapturer,
|
|
2056
|
+
provider: context.provider,
|
|
2057
|
+
config: context.config,
|
|
2058
|
+
currentMode: context.currentMode,
|
|
2059
|
+
detectionArea: context.detectionArea ?? context.config.detectionArea
|
|
2060
|
+
})
|
|
2061
|
+
}],
|
|
2062
|
+
on: {
|
|
2063
|
+
DETECTION_UPDATE: { actions: "setDetectionStatus" },
|
|
2064
|
+
DETECTION_FRAME: { actions: assign({ debugFrame: ({ event }) => event.frame }) },
|
|
2065
|
+
DETECTION_RESET_READY: { actions: assign({ resetDetection: ({ event }) => event.reset }) },
|
|
2066
|
+
DETECTION_SUCCESS: {
|
|
2067
|
+
target: "capturing",
|
|
2068
|
+
actions: assign({ qualityElements: ({ event }) => event.qualityElements })
|
|
2069
|
+
},
|
|
2070
|
+
MANUAL_CAPTURE: { target: "capturingManual" },
|
|
2071
|
+
SWITCH_TO_MANUAL_CAPTURE: {
|
|
2072
|
+
target: "manualCaptureWaiting",
|
|
2073
|
+
actions: assign({
|
|
2074
|
+
detectionStatus: () => "manualCapture",
|
|
2075
|
+
manualCaptureTriggered: () => true
|
|
2076
|
+
})
|
|
2077
|
+
},
|
|
2078
|
+
COUNTER_VALUE_CHANGE: { actions: "setCounterValue" },
|
|
2079
|
+
ID_TYPE_CHANGE: { actions: "setIdType" },
|
|
2080
|
+
ID_SIDE_CHANGE: { actions: assign({ detectionStatus: ({ event, context }) => getDetectionStatusFromSideChange(context, event.side) }) },
|
|
2081
|
+
ORIENTATION_CHANGE: { actions: "setOrientation" }
|
|
2082
|
+
}
|
|
2083
|
+
},
|
|
2084
|
+
manualCaptureWaiting: { on: { MANUAL_CAPTURE: { target: "capturingManual" } } },
|
|
2085
|
+
capturing: {
|
|
2086
|
+
entry: [
|
|
2087
|
+
"captureImage",
|
|
2088
|
+
"storeCapturedCanvasInProvider",
|
|
2089
|
+
"storeCapturedImage"
|
|
2090
|
+
],
|
|
2091
|
+
always: [{
|
|
2092
|
+
target: "uploading",
|
|
2093
|
+
guard: "hasCapturedImage"
|
|
2094
|
+
}, {
|
|
2095
|
+
target: "uploadError",
|
|
2096
|
+
actions: assign(({ context }) => ({
|
|
2097
|
+
uploadError: ID_ERROR_CODES.UPLOAD_ERROR,
|
|
2098
|
+
attemptsRemaining: context.attemptsRemaining - 1
|
|
2099
|
+
}))
|
|
2100
|
+
}]
|
|
2101
|
+
},
|
|
2102
|
+
capturingManual: {
|
|
2103
|
+
entry: [
|
|
2104
|
+
"captureLatestFrame",
|
|
2105
|
+
"storeCapturedCanvasInProvider",
|
|
2106
|
+
"storeCapturedImage"
|
|
2107
|
+
],
|
|
2108
|
+
always: [{
|
|
2109
|
+
target: "uploading",
|
|
2110
|
+
guard: "hasCapturedImage"
|
|
2111
|
+
}, {
|
|
2112
|
+
target: "uploadError",
|
|
2113
|
+
actions: assign(({ context }) => ({
|
|
2114
|
+
uploadError: ID_ERROR_CODES.UPLOAD_ERROR,
|
|
2115
|
+
attemptsRemaining: context.attemptsRemaining - 1
|
|
2116
|
+
}))
|
|
2117
|
+
}]
|
|
2118
|
+
},
|
|
2119
|
+
uploading: {
|
|
2120
|
+
entry: assign({ uploadProgress: () => 0 }),
|
|
2121
|
+
invoke: {
|
|
2122
|
+
id: "uploadIdImage",
|
|
2123
|
+
src: "uploadIdImage",
|
|
2124
|
+
input: ({ context, self }) => {
|
|
2125
|
+
const canvas = context.provider?.getOriginalCapturedCanvas();
|
|
2126
|
+
if (!canvas) throw new Error(ID_ERROR_CODES.UPLOAD_ERROR);
|
|
2127
|
+
return {
|
|
2128
|
+
canvas,
|
|
2129
|
+
type: context.currentMode === "back" ? "back" : "front",
|
|
2130
|
+
qualityElements: context.qualityElements,
|
|
2131
|
+
onProgress: (progress) => {
|
|
2132
|
+
self.send({
|
|
2133
|
+
type: "UPLOAD_PROGRESS",
|
|
2134
|
+
progress
|
|
2135
|
+
});
|
|
2136
|
+
},
|
|
2137
|
+
deepsightService: context.deepsightService,
|
|
2138
|
+
stream: context.stream
|
|
2139
|
+
};
|
|
2140
|
+
},
|
|
2141
|
+
onDone: {
|
|
2142
|
+
target: "validatingUpload",
|
|
2143
|
+
actions: [assign({
|
|
2144
|
+
uploadResponse: ({ event }) => event.output,
|
|
2145
|
+
uploadProgress: () => 100
|
|
2146
|
+
}), "storeCapturedImage"]
|
|
2147
|
+
},
|
|
2148
|
+
onError: {
|
|
2149
|
+
target: "uploadError",
|
|
2150
|
+
actions: assign(({ context, event }) => ({
|
|
2151
|
+
uploadError: getIdErrorCodeFromUnknown(event.error) ?? ID_ERROR_CODES.UPLOAD_ERROR,
|
|
2152
|
+
attemptsRemaining: context.attemptsRemaining - 1
|
|
2153
|
+
}))
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
on: { UPLOAD_PROGRESS: { actions: assign({ uploadProgress: ({ event }) => event.progress }) } }
|
|
2157
|
+
},
|
|
2158
|
+
validatingUpload: { always: [{
|
|
2159
|
+
target: "uploadError",
|
|
2160
|
+
guard: "hasUploadValidationError",
|
|
2161
|
+
actions: ["setUploadErrorFromUploadValidation", "decrementAttemptsRemaining"]
|
|
2162
|
+
}, { target: "success" }] },
|
|
2163
|
+
uploadError: { on: { CONTINUE_FROM_ERROR: [
|
|
2164
|
+
{
|
|
2165
|
+
target: "detecting",
|
|
2166
|
+
guard: "hasAttemptsRemaining",
|
|
2167
|
+
actions: [
|
|
2168
|
+
"resetDetection",
|
|
2169
|
+
"clearUploadFailure",
|
|
2170
|
+
assign({ manualCaptureTriggered: () => false })
|
|
2171
|
+
]
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
target: "#idCapture.frontFinished",
|
|
2175
|
+
guard: "shouldContinueToBack"
|
|
2176
|
+
},
|
|
2177
|
+
{ target: "#idCapture.finished" }
|
|
2178
|
+
] } },
|
|
2179
|
+
success: { on: { NEXT_STEP: [{
|
|
2180
|
+
target: "#idCapture.frontFinished",
|
|
2181
|
+
guard: "shouldContinueToBack"
|
|
2182
|
+
}, { target: "#idCapture.processing" }] } }
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
frontFinished: {
|
|
2186
|
+
entry: ["stopMediaRecording", "resetForBackCapture"],
|
|
2187
|
+
type: "parallel",
|
|
2188
|
+
states: {
|
|
2189
|
+
cameraInit: {
|
|
2190
|
+
initial: "checking",
|
|
2191
|
+
states: {
|
|
2192
|
+
checking: { always: [{
|
|
2193
|
+
target: "ready",
|
|
2194
|
+
guard: "hasStream"
|
|
2195
|
+
}, { target: "initializingStream" }] },
|
|
2196
|
+
initializingStream: { invoke: {
|
|
2197
|
+
id: "frontFinishedInitCamera",
|
|
2198
|
+
src: "initializeCamera",
|
|
2199
|
+
input: ({ context }) => {
|
|
2200
|
+
if (!context.provider) throw new Error("Provider is required");
|
|
2201
|
+
return {
|
|
2202
|
+
provider: context.provider,
|
|
2203
|
+
config: context.config,
|
|
2204
|
+
deepsightService: context.deepsightService
|
|
2205
|
+
};
|
|
2206
|
+
},
|
|
2207
|
+
onDone: {
|
|
2208
|
+
target: "ready",
|
|
2209
|
+
actions: "setStreamAndCapturer"
|
|
2210
|
+
},
|
|
2211
|
+
onError: {
|
|
2212
|
+
target: "ready",
|
|
2213
|
+
actions: () => {
|
|
2214
|
+
console.warn("Camera initialization failed during flip transition");
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
} },
|
|
2218
|
+
ready: { type: "final" }
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2221
|
+
userIntent: {
|
|
2222
|
+
initial: "waiting",
|
|
2223
|
+
states: {
|
|
2224
|
+
waiting: { on: { CONTINUE_TO_BACK: {
|
|
2225
|
+
target: "clicked",
|
|
2226
|
+
actions: assign({ currentMode: () => "back" })
|
|
2227
|
+
} } },
|
|
2228
|
+
clicked: { type: "final" }
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
},
|
|
2232
|
+
onDone: [{
|
|
2233
|
+
target: "#idCapture.capture.detecting",
|
|
2234
|
+
guard: "hasStream"
|
|
2235
|
+
}, { target: "#idCapture.capture" }]
|
|
2236
|
+
},
|
|
2237
|
+
processing: {
|
|
2238
|
+
entry: "stopMediaStream",
|
|
2239
|
+
invoke: {
|
|
2240
|
+
id: "processId",
|
|
2241
|
+
src: "processId",
|
|
2242
|
+
input: ({ context }) => ({ isSecondId: context.config.isSecondId ?? false }),
|
|
2243
|
+
onDone: [{
|
|
2244
|
+
target: "expired",
|
|
2245
|
+
guard: ({ event }) => event.output.isDocumentExpired
|
|
2246
|
+
}, { target: "finished" }],
|
|
2247
|
+
onError: { target: "finished" }
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
expired: { on: { RETRY_CAPTURE: [
|
|
2251
|
+
{
|
|
2252
|
+
target: "chooser",
|
|
2253
|
+
guard: "hasShowDocumentChooser",
|
|
2254
|
+
actions: ["resetContext"]
|
|
2255
|
+
},
|
|
2256
|
+
{
|
|
2257
|
+
target: "tutorial",
|
|
2258
|
+
guard: "hasShowTutorial",
|
|
2259
|
+
actions: "resetContext"
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
target: "loading",
|
|
2263
|
+
actions: "resetContext"
|
|
2264
|
+
}
|
|
2265
|
+
] } },
|
|
2266
|
+
finished: {
|
|
2267
|
+
entry: [
|
|
2268
|
+
"stopMediaRecording",
|
|
2269
|
+
"stopMediaStream",
|
|
2270
|
+
"disposeProvider",
|
|
2271
|
+
"cleanupDeepsight"
|
|
2272
|
+
],
|
|
2273
|
+
type: "final"
|
|
2274
|
+
},
|
|
2275
|
+
closed: {
|
|
2276
|
+
entry: [
|
|
2277
|
+
"stopMediaStream",
|
|
2278
|
+
"disposeProvider",
|
|
2279
|
+
"cleanupDeepsight"
|
|
2280
|
+
],
|
|
2281
|
+
type: "final"
|
|
2282
|
+
},
|
|
2283
|
+
error: {
|
|
2284
|
+
entry: [
|
|
2285
|
+
"stopMediaStream",
|
|
2286
|
+
"disposeProvider",
|
|
2287
|
+
"cleanupDeepsight"
|
|
2288
|
+
],
|
|
2289
|
+
on: { RESET: {
|
|
2290
|
+
target: "idle",
|
|
2291
|
+
actions: "resetContext"
|
|
2292
|
+
} }
|
|
2293
|
+
},
|
|
2294
|
+
manualIdUpload: { on: { QUIT: { target: "closed" } } },
|
|
2295
|
+
digitalIdUpload: { on: { QUIT: { target: "closed" } } }
|
|
2296
|
+
}
|
|
2297
|
+
});
|
|
2298
|
+
const idCaptureMachine = _idCaptureMachine;
|
|
2299
|
+
|
|
2300
|
+
//#endregion
|
|
2301
|
+
//#region src/modules/id/idCaptureActor.ts
|
|
2302
|
+
function createIdCaptureActor(options) {
|
|
2303
|
+
const dependencies = options.dependencies ?? { storage: new BrowserStorageProvider() };
|
|
2304
|
+
return createActor(idCaptureMachine, { input: {
|
|
2305
|
+
config: options.config,
|
|
2306
|
+
provider: options.provider,
|
|
2307
|
+
dependencies
|
|
2308
|
+
} }).start();
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
//#endregion
|
|
2312
|
+
//#region src/modules/id/idCaptureManager.ts
|
|
2313
|
+
function getPermissionStatus(snapshot) {
|
|
2314
|
+
if (!snapshot.matches("permissions")) return;
|
|
2315
|
+
if (snapshot.matches({ permissions: "idle" })) return "idle";
|
|
2316
|
+
if (snapshot.matches({ permissions: "waitingForUser" })) return "idle";
|
|
2317
|
+
if (snapshot.matches({ permissions: "learnMore" })) return "learnMore";
|
|
2318
|
+
if (snapshot.matches({ permissions: "requesting" })) return "requesting";
|
|
2319
|
+
if (snapshot.matches({ permissions: "denied" })) return "denied";
|
|
2320
|
+
}
|
|
2321
|
+
function getCaptureStatus(snapshot) {
|
|
2322
|
+
const matches = {
|
|
2323
|
+
initializing: snapshot.matches({ capture: "initializing" }),
|
|
2324
|
+
detecting: snapshot.matches({ capture: "detecting" }),
|
|
2325
|
+
manualCaptureWaiting: snapshot.matches({ capture: "manualCaptureWaiting" }),
|
|
2326
|
+
capturing: snapshot.matches({ capture: "capturing" }),
|
|
2327
|
+
capturingManual: snapshot.matches({ capture: "capturingManual" }),
|
|
2328
|
+
uploading: snapshot.matches({ capture: "uploading" }),
|
|
2329
|
+
uploadError: snapshot.matches({ capture: "uploadError" }),
|
|
2330
|
+
success: snapshot.matches({ capture: "success" })
|
|
2331
|
+
};
|
|
2332
|
+
if (matches.initializing) return "initializing";
|
|
2333
|
+
if (matches.detecting || matches.manualCaptureWaiting) return "detecting";
|
|
2334
|
+
if (matches.capturing || matches.capturingManual) return "capturing";
|
|
2335
|
+
if (matches.uploading) return "uploading";
|
|
2336
|
+
if (matches.uploadError) return "uploadError";
|
|
2337
|
+
if (matches.success) return "success";
|
|
2338
|
+
}
|
|
2339
|
+
function getErrorMessage(errorCode) {
|
|
2340
|
+
if (!errorCode) return void 0;
|
|
2341
|
+
return {
|
|
2342
|
+
UPLOAD_ERROR: "Upload failed",
|
|
2343
|
+
CLASSIFICATION_FAILED: "ID classification failed",
|
|
2344
|
+
LOW_SHARPNESS: "Image is not sharp enough",
|
|
2345
|
+
GLARE_DETECTED: "Glare detected on ID",
|
|
2346
|
+
WRONG_DOCUMENT_SIDE: "Wrong side of document",
|
|
2347
|
+
ID_TYPE_UNACCEPTABLE: "ID type is not acceptable",
|
|
2348
|
+
READABILITY_ISSUE: "ID readability issue",
|
|
2349
|
+
RETRY_EXHAUSTED_CONTINUE_TO_BACK: "Retry exhausted",
|
|
2350
|
+
RETRY_EXHAUSTED_SKIP_BACK: "Retry exhausted",
|
|
2351
|
+
NO_MORE_TRIES: "No more tries remaining",
|
|
2352
|
+
UNEXPECTED_ERROR: "An unexpected error occurred",
|
|
2353
|
+
NO_TOKEN: "No token available",
|
|
2354
|
+
PERMISSION_DENIED: "Permission denied",
|
|
2355
|
+
USER_CANCELLED: "User cancelled",
|
|
2356
|
+
SERVER_ERROR: "Server error"
|
|
2357
|
+
}[errorCode];
|
|
2358
|
+
}
|
|
2359
|
+
function getErrorDescription(errorCode, uploadResponse) {
|
|
2360
|
+
if (!errorCode) return void 0;
|
|
2361
|
+
return {
|
|
2362
|
+
UPLOAD_ERROR: "Please try again",
|
|
2363
|
+
CLASSIFICATION_FAILED: "Please ensure your ID is clearly visible",
|
|
2364
|
+
LOW_SHARPNESS: "Please ensure the image is clear and well-focused",
|
|
2365
|
+
GLARE_DETECTED: "Please avoid bright reflections on your ID",
|
|
2366
|
+
WRONG_DOCUMENT_SIDE: uploadResponse?.side === "back" ? "Please show the back side of your ID" : "Please show the front side of your ID",
|
|
2367
|
+
ID_TYPE_UNACCEPTABLE: "Please use a valid ID type",
|
|
2368
|
+
READABILITY_ISSUE: "Please ensure all text is clearly visible",
|
|
2369
|
+
RETRY_EXHAUSTED_CONTINUE_TO_BACK: "Continuing to back side capture",
|
|
2370
|
+
RETRY_EXHAUSTED_SKIP_BACK: "Skipping back side capture",
|
|
2371
|
+
NO_MORE_TRIES: "Maximum attempts reached",
|
|
2372
|
+
UNEXPECTED_ERROR: "Please try again later",
|
|
2373
|
+
NO_TOKEN: "Session expired",
|
|
2374
|
+
PERMISSION_DENIED: "Camera permission is required",
|
|
2375
|
+
USER_CANCELLED: "Capture was cancelled",
|
|
2376
|
+
SERVER_ERROR: "Please try again later"
|
|
2377
|
+
}[errorCode];
|
|
2378
|
+
}
|
|
2379
|
+
function mapState(snapshot) {
|
|
2380
|
+
const { context } = snapshot;
|
|
2381
|
+
if (snapshot.matches("idle")) return { status: "idle" };
|
|
2382
|
+
if (snapshot.matches("chooser")) return { status: "chooser" };
|
|
2383
|
+
if (snapshot.matches("loading")) return { status: "loading" };
|
|
2384
|
+
if (snapshot.matches("tutorial")) return {
|
|
2385
|
+
status: "tutorial",
|
|
2386
|
+
selectedDocumentType: context.selectedDocumentType
|
|
2387
|
+
};
|
|
2388
|
+
if (snapshot.matches("closed")) return { status: "closed" };
|
|
2389
|
+
if (snapshot.matches("permissions")) {
|
|
2390
|
+
const permissionStatus = getPermissionStatus(snapshot);
|
|
2391
|
+
if (permissionStatus === void 0) return {
|
|
2392
|
+
status: "permissions",
|
|
2393
|
+
permissionStatus: "idle"
|
|
2394
|
+
};
|
|
2395
|
+
return {
|
|
2396
|
+
status: "permissions",
|
|
2397
|
+
permissionStatus
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
if (snapshot.matches("capture")) {
|
|
2401
|
+
const captureStatus = getCaptureStatus(snapshot);
|
|
2402
|
+
const needsBackCapture = context.currentMode === "front" && !context.config.onlyFront && !context.config.onlyBack;
|
|
2403
|
+
return {
|
|
2404
|
+
status: "capture",
|
|
2405
|
+
captureStatus: captureStatus ?? "initializing",
|
|
2406
|
+
stream: context.stream,
|
|
2407
|
+
detectionStatus: context.detectionStatus,
|
|
2408
|
+
debugFrame: void 0,
|
|
2409
|
+
attemptsRemaining: context.attemptsRemaining,
|
|
2410
|
+
uploadError: context.uploadError,
|
|
2411
|
+
currentMode: context.currentMode,
|
|
2412
|
+
counterValue: context.counterValue,
|
|
2413
|
+
orientation: context.orientation,
|
|
2414
|
+
idType: context.idType,
|
|
2415
|
+
previewImageUrl: context.previewImageUrl,
|
|
2416
|
+
uploadProgress: context.uploadProgress ?? 0,
|
|
2417
|
+
uploadErrorMessage: context.uploadError ? getErrorMessage(context.uploadError) : void 0,
|
|
2418
|
+
uploadErrorDescription: context.uploadError ? getErrorDescription(context.uploadError, context.uploadResponse) : void 0,
|
|
2419
|
+
needsBackCapture,
|
|
2420
|
+
showCaptureButtonInAuto: context.config.showCaptureButtonInAuto ?? false,
|
|
2421
|
+
canRetry: context.attemptsRemaining > 0
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
if (snapshot.matches("frontFinished")) return { status: "frontFinished" };
|
|
2425
|
+
if (snapshot.matches("processing")) return { status: "processing" };
|
|
2426
|
+
if (snapshot.matches("expired")) return { status: "expired" };
|
|
2427
|
+
if (snapshot.matches("finished")) return { status: "finished" };
|
|
2428
|
+
if (snapshot.matches("error")) return {
|
|
2429
|
+
status: "error",
|
|
2430
|
+
error: context.error ?? "Unknown error"
|
|
2431
|
+
};
|
|
2432
|
+
return { status: "idle" };
|
|
2433
|
+
}
|
|
2434
|
+
function createApi({ actor }) {
|
|
2435
|
+
return {
|
|
2436
|
+
load() {
|
|
2437
|
+
actor.send({ type: "LOAD" });
|
|
2438
|
+
},
|
|
2439
|
+
selectDocument(documentType) {
|
|
2440
|
+
actor.send({
|
|
2441
|
+
type: "SELECT_DOCUMENT",
|
|
2442
|
+
documentType
|
|
2443
|
+
});
|
|
2444
|
+
},
|
|
2445
|
+
nextStep() {
|
|
2446
|
+
actor.send({ type: "NEXT_STEP" });
|
|
2447
|
+
},
|
|
2448
|
+
requestPermission() {
|
|
2449
|
+
actor.send({ type: "REQUEST_PERMISSION" });
|
|
2450
|
+
},
|
|
2451
|
+
goToLearnMore() {
|
|
2452
|
+
actor.send({ type: "GO_TO_LEARN_MORE" });
|
|
2453
|
+
},
|
|
2454
|
+
back() {
|
|
2455
|
+
actor.send({ type: "BACK" });
|
|
2456
|
+
},
|
|
2457
|
+
close() {
|
|
2458
|
+
actor.send({ type: "QUIT" });
|
|
2459
|
+
},
|
|
2460
|
+
reset() {
|
|
2461
|
+
actor.send({ type: "RESET" });
|
|
2462
|
+
},
|
|
2463
|
+
retryCapture() {
|
|
2464
|
+
actor.send({ type: "RETRY_CAPTURE" });
|
|
2465
|
+
},
|
|
2466
|
+
continueFromError() {
|
|
2467
|
+
actor.send({ type: "CONTINUE_FROM_ERROR" });
|
|
2468
|
+
},
|
|
2469
|
+
capture() {
|
|
2470
|
+
actor.send({ type: "MANUAL_CAPTURE" });
|
|
2471
|
+
},
|
|
2472
|
+
switchToManualCapture() {
|
|
2473
|
+
actor.send({ type: "SWITCH_TO_MANUAL_CAPTURE" });
|
|
2474
|
+
},
|
|
2475
|
+
continueToBack() {
|
|
2476
|
+
actor.send({ type: "CONTINUE_TO_BACK" });
|
|
2477
|
+
},
|
|
2478
|
+
skipBack() {
|
|
2479
|
+
actor.send({ type: "SKIP_BACK" });
|
|
2480
|
+
},
|
|
2481
|
+
updateDetectionArea(detectionArea) {
|
|
2482
|
+
actor.send({
|
|
2483
|
+
type: "UPDATE_DETECTION_AREA",
|
|
2484
|
+
detectionArea
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
function createIdCaptureManager(options) {
|
|
2490
|
+
return createManager({
|
|
2491
|
+
actor: createIdCaptureActor(options),
|
|
2492
|
+
mapState,
|
|
2493
|
+
createApi
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
//#endregion
|
|
2498
|
+
export { createSession as _, processId as a, stopStream as c, ID_ERROR_CODES as d, getDisableIpify as f, resetSessionInit as g, isSessionInitialized as h, initializeIdCapture as i, uploadIdImage as l, initializeSession as m, createIdCaptureActor as n, startRecordingSession as o, getSessionFeatures as p, idCaptureMachine as r, stopRecording as s, createIdCaptureManager as t, validateUploadResponse as u };
|