biometry-sdk 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/biometry-sdk.cjs.js +1423 -0
- package/dist/biometry-sdk.esm.js +1419 -0
- package/dist/biometry-sdk.umd.js +1429 -0
- package/dist/components/biometry-onboarding.d.ts +26 -1
- package/dist/components/process-video.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/package.json +16 -6
- package/dist/components/biometry-onboarding.js +0 -233
- package/dist/components/process-video.js +0 -452
- package/dist/index.js +0 -2
- package/dist/sdk.js +0 -179
- package/dist/sdk.test.js +0 -255
- package/dist/types.js +0 -14
|
@@ -0,0 +1,1423 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class BiometrySDK {
|
|
4
|
+
constructor(apiKey) {
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
throw new Error('API Key is required to initialize the SDK.');
|
|
7
|
+
}
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
}
|
|
10
|
+
async request(path, method, body, headers) {
|
|
11
|
+
const defaultHeaders = {
|
|
12
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
13
|
+
};
|
|
14
|
+
const requestHeaders = Object.assign(Object.assign({}, defaultHeaders), headers);
|
|
15
|
+
if (body && !(body instanceof FormData)) {
|
|
16
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
17
|
+
body = JSON.stringify(body);
|
|
18
|
+
}
|
|
19
|
+
const response = await fetch(`${BiometrySDK.BASE_URL}${path}`, {
|
|
20
|
+
method,
|
|
21
|
+
headers: requestHeaders,
|
|
22
|
+
body,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const errorData = await response.json().catch(() => ({}));
|
|
26
|
+
const errorMessage = (errorData === null || errorData === undefined ? undefined : errorData.error) || (errorData === null || errorData === undefined ? undefined : errorData.message) || 'Unknown error occurred';
|
|
27
|
+
throw new Error(`Error ${response.status}: ${errorMessage}`);
|
|
28
|
+
}
|
|
29
|
+
return await response.json();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Submits consent for a user.
|
|
33
|
+
*
|
|
34
|
+
* @param {boolean} isConsentGiven - Indicates whether the user has given consent.
|
|
35
|
+
* @param {string} userFullName - The full name of the user giving consent.
|
|
36
|
+
* @returns {Promise<ConsentResponse>} A promise resolving to the consent response.
|
|
37
|
+
* @throws {Error} - If the user's full name is not provided or if the request fails.
|
|
38
|
+
*/
|
|
39
|
+
async giveConsent(isConsentGiven, userFullName) {
|
|
40
|
+
if (!userFullName) {
|
|
41
|
+
throw new Error('User Full Name is required to give consent.');
|
|
42
|
+
}
|
|
43
|
+
const body = {
|
|
44
|
+
is_consent_given: isConsentGiven,
|
|
45
|
+
user_fullname: userFullName,
|
|
46
|
+
};
|
|
47
|
+
const response = await this.request('/consent', 'POST', body);
|
|
48
|
+
return {
|
|
49
|
+
is_consent_given: response.is_consent_given,
|
|
50
|
+
user_fullname: response.user_fullname,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Onboards a user's voice for biometric authentication.
|
|
55
|
+
*
|
|
56
|
+
* @param {File} audio - The audio file containing the user's voice.
|
|
57
|
+
* @param {string} userFullName - The full name of the user being onboarded.
|
|
58
|
+
* @param {string} uniqueId - A unique identifier for the onboarding process.
|
|
59
|
+
* @param {string} phrase - The phrase spoken in the audio file.
|
|
60
|
+
* @param {string} [requestUserProvidedId] - An optional user-provided ID to link transactions within a unified group.
|
|
61
|
+
* @returns {Promise<VoiceOnboardingResponse>} - A promise resolving to the voice onboarding response.
|
|
62
|
+
* @throws {Error} - If required parameters are missing or the request fails.
|
|
63
|
+
*/
|
|
64
|
+
async onboardVoice(audio, userFullName, uniqueId, phrase, requestUserProvidedId) {
|
|
65
|
+
if (!userFullName)
|
|
66
|
+
throw new Error('User fullname is required.');
|
|
67
|
+
if (!uniqueId)
|
|
68
|
+
throw new Error('Unique ID is required.');
|
|
69
|
+
if (!phrase)
|
|
70
|
+
throw new Error('Phrase is required.');
|
|
71
|
+
if (!audio)
|
|
72
|
+
throw new Error('Audio file is required.');
|
|
73
|
+
const formData = new FormData();
|
|
74
|
+
formData.append('unique_id', uniqueId);
|
|
75
|
+
formData.append('phrase', phrase);
|
|
76
|
+
formData.append('voice', audio);
|
|
77
|
+
const headers = {
|
|
78
|
+
'X-User-Fullname': userFullName,
|
|
79
|
+
};
|
|
80
|
+
if (requestUserProvidedId) {
|
|
81
|
+
headers['X-Request-User-Provided-ID'] = requestUserProvidedId;
|
|
82
|
+
}
|
|
83
|
+
return this.request('/api-gateway/onboard/voice', 'POST', formData, headers);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Onboards a user's face for biometric authentication.
|
|
87
|
+
*
|
|
88
|
+
* @param {File} face - Image file that contains user's face.
|
|
89
|
+
* @param {string} userFullName - The full name of the user being onboarded.
|
|
90
|
+
* @param {string} isDocument - Indicates whether the image is a document.
|
|
91
|
+
* @param {string} [requestUserProvidedId] - An optional user-provided ID to link transactions within a unified group.
|
|
92
|
+
* @returns {Promise<FaceOnboardingResponse>} - A promise resolving to the voice onboarding response.
|
|
93
|
+
* @throws {Error} - If required parameters are missing or the request fails.
|
|
94
|
+
*/
|
|
95
|
+
async onboardFace(face, userFullName, isDocument, requestUserProvidedId) {
|
|
96
|
+
if (!userFullName)
|
|
97
|
+
throw new Error('User fullname is required.');
|
|
98
|
+
if (!face)
|
|
99
|
+
throw new Error('Face image is required.');
|
|
100
|
+
const formData = new FormData();
|
|
101
|
+
formData.append('face', face);
|
|
102
|
+
if (isDocument) {
|
|
103
|
+
formData.append('is_document', 'true');
|
|
104
|
+
}
|
|
105
|
+
const headers = {
|
|
106
|
+
'X-User-Fullname': userFullName,
|
|
107
|
+
};
|
|
108
|
+
if (requestUserProvidedId) {
|
|
109
|
+
headers['X-Request-User-Provided-ID'] = requestUserProvidedId;
|
|
110
|
+
}
|
|
111
|
+
return this.request('/api-gateway/onboard/face', 'POST', formData, headers);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Matches a user's face from video against a reference image.
|
|
115
|
+
*
|
|
116
|
+
* @param {File} image - Reference image file that contains user's face.
|
|
117
|
+
* @param {string} video - Video file that contains user's face.
|
|
118
|
+
* @param {string} userFullName - Pass the full name of end-user to process Voice and Face recognition services.
|
|
119
|
+
* @param {string} processVideoRequestId - ID from the response header of /process-video endpoint.
|
|
120
|
+
* @param {boolean} usePrefilledVideo - Pass true to use the video from the process-video endpoint.
|
|
121
|
+
* @param {string} [requestUserProvidedId] - An optional user-provided ID to link transactions within a unified group.
|
|
122
|
+
* @returns {Promise<FaceMatchResponse>} - A promise resolving to the voice onboarding response.
|
|
123
|
+
* @throws {Error} - If required parameters are missing or the request fails.
|
|
124
|
+
*/
|
|
125
|
+
async matchFaces(image, video, userFullName, processVideoRequestId, usePrefilledVideo, requestUserProvidedId) {
|
|
126
|
+
if (!image)
|
|
127
|
+
throw new Error('Face image is required.');
|
|
128
|
+
if ((!processVideoRequestId && !usePrefilledVideo) && !video)
|
|
129
|
+
throw new Error('Video is required.');
|
|
130
|
+
const formData = new FormData();
|
|
131
|
+
if (video) {
|
|
132
|
+
formData.append('video', video);
|
|
133
|
+
}
|
|
134
|
+
formData.append('image', image);
|
|
135
|
+
const headers = {};
|
|
136
|
+
if (userFullName) {
|
|
137
|
+
headers['X-User-Fullname'] = userFullName;
|
|
138
|
+
}
|
|
139
|
+
if (processVideoRequestId) {
|
|
140
|
+
headers['X-Request-Id'] = processVideoRequestId;
|
|
141
|
+
}
|
|
142
|
+
if (processVideoRequestId && usePrefilledVideo) {
|
|
143
|
+
headers['X-Use-Prefilled-Video'] = 'true';
|
|
144
|
+
}
|
|
145
|
+
if (requestUserProvidedId) {
|
|
146
|
+
headers['X-Request-User-Provided-ID'] = requestUserProvidedId;
|
|
147
|
+
}
|
|
148
|
+
return this.request('/api-gateway/match-faces', 'POST', formData, headers);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Process the video through Biometry services to check liveness and authorize user
|
|
152
|
+
*
|
|
153
|
+
* @param {File} video - Video file that you want to process.
|
|
154
|
+
* @param {string} phrase - Set of numbers that user needs to say out loud in the video.
|
|
155
|
+
* @param {string} userFullName - Pass the full name of end-user to process Voice and Face recognition services.
|
|
156
|
+
* @param {string} requestUserProvidedId - An optional user-provided ID to link transactions within a unified group.
|
|
157
|
+
* @param {object} deviceInfo - Pass the device information in JSON format to include in transaction.
|
|
158
|
+
* @returns
|
|
159
|
+
*/
|
|
160
|
+
async processVideo(video, phrase, userFullName, requestUserProvidedId, deviceInfo) {
|
|
161
|
+
if (!video)
|
|
162
|
+
throw new Error('Video is required.');
|
|
163
|
+
if (!phrase)
|
|
164
|
+
throw new Error('Phrase is required.');
|
|
165
|
+
const formData = new FormData();
|
|
166
|
+
formData.append('phrase', phrase);
|
|
167
|
+
formData.append('video', video);
|
|
168
|
+
const headers = {};
|
|
169
|
+
if (userFullName) {
|
|
170
|
+
headers['X-User-Fullname'] = userFullName;
|
|
171
|
+
}
|
|
172
|
+
if (requestUserProvidedId) {
|
|
173
|
+
headers['X-Request-User-Provided-ID'] = requestUserProvidedId;
|
|
174
|
+
}
|
|
175
|
+
if (deviceInfo) {
|
|
176
|
+
headers['X-Device-Info'] = JSON.stringify(deviceInfo);
|
|
177
|
+
}
|
|
178
|
+
return this.request('/api-gateway/process-video', 'POST', formData, headers);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
BiometrySDK.BASE_URL = 'https://api.biometrysolutions.com';
|
|
182
|
+
|
|
183
|
+
exports.BiometryAttributes = void 0;
|
|
184
|
+
(function (BiometryAttributes) {
|
|
185
|
+
BiometryAttributes["ApiKey"] = "api-key";
|
|
186
|
+
BiometryAttributes["UserFullname"] = "user-fullname";
|
|
187
|
+
})(exports.BiometryAttributes || (exports.BiometryAttributes = {}));
|
|
188
|
+
exports.BiometryOnboardingState = void 0;
|
|
189
|
+
(function (BiometryOnboardingState) {
|
|
190
|
+
BiometryOnboardingState["Loading"] = "loading";
|
|
191
|
+
BiometryOnboardingState["Success"] = "success";
|
|
192
|
+
BiometryOnboardingState["ErrorNoFace"] = "error-no-face";
|
|
193
|
+
BiometryOnboardingState["ErrorMultipleFaces"] = "error-multiple-faces";
|
|
194
|
+
BiometryOnboardingState["ErrorNotCentered"] = "error-not-centered";
|
|
195
|
+
BiometryOnboardingState["ErrorOther"] = "error-other";
|
|
196
|
+
})(exports.BiometryOnboardingState || (exports.BiometryOnboardingState = {}));
|
|
197
|
+
|
|
198
|
+
class BiometryOnboarding extends HTMLElement {
|
|
199
|
+
constructor() {
|
|
200
|
+
super();
|
|
201
|
+
this.videoElement = null;
|
|
202
|
+
this.canvasElement = null;
|
|
203
|
+
this.captureButton = null;
|
|
204
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
205
|
+
this.sdk = null;
|
|
206
|
+
this.toggleState = this.toggleState.bind(this);
|
|
207
|
+
this.capturePhoto = this.capturePhoto.bind(this);
|
|
208
|
+
}
|
|
209
|
+
static get observedAttributes() {
|
|
210
|
+
return Object.values(exports.BiometryAttributes);
|
|
211
|
+
}
|
|
212
|
+
get apiKey() {
|
|
213
|
+
return this.getAttribute("api-key");
|
|
214
|
+
}
|
|
215
|
+
set apiKey(value) {
|
|
216
|
+
if (value) {
|
|
217
|
+
this.setAttribute("api-key", value);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.removeAttribute("api-key");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
get userFullname() {
|
|
224
|
+
return this.getAttribute("user-fullname");
|
|
225
|
+
}
|
|
226
|
+
set userFullname(value) {
|
|
227
|
+
if (value) {
|
|
228
|
+
this.setAttribute("user-fullname", value);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.removeAttribute("user-fullname");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
235
|
+
if (name === "api-key" || name === "user-fullname") {
|
|
236
|
+
this.validateAttributes();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
connectedCallback() {
|
|
240
|
+
this.validateAttributes();
|
|
241
|
+
this.init();
|
|
242
|
+
}
|
|
243
|
+
disconnectedCallback() {
|
|
244
|
+
this.cleanup();
|
|
245
|
+
}
|
|
246
|
+
validateAttributes() {
|
|
247
|
+
if (!this.apiKey) {
|
|
248
|
+
console.error("API key is required.");
|
|
249
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!this.userFullname) {
|
|
253
|
+
console.error("User fullname is required.");
|
|
254
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
init() {
|
|
259
|
+
this.shadow.innerHTML = `
|
|
260
|
+
<style>
|
|
261
|
+
.wrapper {
|
|
262
|
+
position: relative;
|
|
263
|
+
}
|
|
264
|
+
video {
|
|
265
|
+
transform: scaleX(-1); /* Flip video for preview */
|
|
266
|
+
max-width: 100%;
|
|
267
|
+
border-radius: var(--border-radius, 8px);
|
|
268
|
+
}
|
|
269
|
+
canvas {
|
|
270
|
+
display: none;
|
|
271
|
+
}
|
|
272
|
+
</style>
|
|
273
|
+
<div class="wrapper">
|
|
274
|
+
<slot name="video">
|
|
275
|
+
<video id="video" autoplay playsinline></video>
|
|
276
|
+
</slot>
|
|
277
|
+
<slot name="canvas">
|
|
278
|
+
<canvas id="canvas" style="display: none;"></canvas>
|
|
279
|
+
</slot>
|
|
280
|
+
<slot name="button">
|
|
281
|
+
<button id="button">Capture Photo</button>
|
|
282
|
+
</slot>
|
|
283
|
+
<div class="status">
|
|
284
|
+
<slot name="loading" class="loading"></slot>
|
|
285
|
+
<slot name="success" class="success"></slot>
|
|
286
|
+
<slot name="error-no-face" class="error-no-face"></slot>
|
|
287
|
+
<slot name="error-multiple-faces" class="error-multiple-faces"></slot>
|
|
288
|
+
<slot name="error-not-centered" class="error-not-centered"></slot>
|
|
289
|
+
<slot name="error-other" class="error-other"></slot>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
293
|
+
this.initializeSDK();
|
|
294
|
+
this.attachSlotListeners();
|
|
295
|
+
this.setupCamera();
|
|
296
|
+
this.toggleState("");
|
|
297
|
+
}
|
|
298
|
+
cleanup() {
|
|
299
|
+
var _a;
|
|
300
|
+
if ((_a = this.videoElement) === null || _a === undefined ? undefined : _a.srcObject) {
|
|
301
|
+
const tracks = this.videoElement.srcObject.getTracks();
|
|
302
|
+
tracks.forEach((track) => track.stop());
|
|
303
|
+
}
|
|
304
|
+
if (this.videoElement) {
|
|
305
|
+
this.videoElement.srcObject = null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
initializeSDK() {
|
|
309
|
+
if (this.apiKey) {
|
|
310
|
+
this.sdk = new BiometrySDK(this.apiKey);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
314
|
+
console.error("API key is required to initialize the SDK.");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
toggleState(state) {
|
|
318
|
+
const slots = [
|
|
319
|
+
exports.BiometryOnboardingState.Loading,
|
|
320
|
+
exports.BiometryOnboardingState.Success,
|
|
321
|
+
exports.BiometryOnboardingState.ErrorNoFace,
|
|
322
|
+
exports.BiometryOnboardingState.ErrorMultipleFaces,
|
|
323
|
+
exports.BiometryOnboardingState.ErrorNotCentered,
|
|
324
|
+
exports.BiometryOnboardingState.ErrorOther,
|
|
325
|
+
];
|
|
326
|
+
slots.forEach((slotName) => {
|
|
327
|
+
const slot = this.shadow.querySelector(`slot[name="${slotName}"]`);
|
|
328
|
+
if (slot) {
|
|
329
|
+
slot.style.display = slotName === state ? "block" : "none";
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
attachSlotListeners() {
|
|
334
|
+
const videoSlot = this.shadow.querySelector('slot[name="video"]');
|
|
335
|
+
const canvasSlot = this.shadow.querySelector('slot[name="canvas"]');
|
|
336
|
+
const buttonSlot = this.shadow.querySelector('slot[name="button"]');
|
|
337
|
+
const assignedVideoElements = videoSlot.assignedElements();
|
|
338
|
+
this.videoElement = (assignedVideoElements.length > 0 ? assignedVideoElements[0] : null) || this.shadow.querySelector("#video");
|
|
339
|
+
const assignedCanvasElements = canvasSlot.assignedElements();
|
|
340
|
+
this.canvasElement = (assignedCanvasElements.length > 0 ? assignedCanvasElements[0] : null) || this.shadow.querySelector("#canvas");
|
|
341
|
+
const assignedButtonElements = buttonSlot.assignedElements();
|
|
342
|
+
this.captureButton = (assignedButtonElements.length > 0 ? assignedButtonElements[0] : null) || this.shadow.querySelector("#button");
|
|
343
|
+
if (!this.videoElement) {
|
|
344
|
+
console.error("Video element is missing.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (!this.captureButton) {
|
|
348
|
+
console.error("Capture button is missing.");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
this.captureButton.addEventListener("click", this.capturePhoto);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
setupCamera() {
|
|
356
|
+
if (!this.videoElement) {
|
|
357
|
+
console.error("Video element is missing.");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
navigator.mediaDevices
|
|
361
|
+
.getUserMedia({ video: true })
|
|
362
|
+
.then((stream) => {
|
|
363
|
+
this.videoElement.srcObject = stream;
|
|
364
|
+
})
|
|
365
|
+
.catch((error) => {
|
|
366
|
+
console.error("Error accessing camera:", error);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async capturePhoto() {
|
|
370
|
+
try {
|
|
371
|
+
if (!this.videoElement || !this.canvasElement || !this.sdk) {
|
|
372
|
+
console.error("Essential elements or SDK are not initialized.");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const context = this.canvasElement.getContext("2d");
|
|
376
|
+
this.canvasElement.width = this.videoElement.videoWidth;
|
|
377
|
+
this.canvasElement.height = this.videoElement.videoHeight;
|
|
378
|
+
context.drawImage(this.videoElement, 0, 0, this.canvasElement.width, this.canvasElement.height);
|
|
379
|
+
this.toggleState("loading");
|
|
380
|
+
this.canvasElement.toBlob(async (blob) => {
|
|
381
|
+
try {
|
|
382
|
+
if (!blob) {
|
|
383
|
+
console.error("Failed to capture photo.");
|
|
384
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const file = new File([blob], "onboard-face.jpg", { type: "image/jpeg" });
|
|
388
|
+
try {
|
|
389
|
+
const response = await this.sdk.onboardFace(file, this.userFullname);
|
|
390
|
+
const result = response.data.onboard_result;
|
|
391
|
+
this.resultCode = result === null || result === void 0 ? void 0 : result.code;
|
|
392
|
+
this.description = (result === null || result === void 0 ? void 0 : result.description) || "Unknown error occurred.";
|
|
393
|
+
switch (this.resultCode) {
|
|
394
|
+
case 0:
|
|
395
|
+
this.toggleState(exports.BiometryOnboardingState.Success);
|
|
396
|
+
break;
|
|
397
|
+
case 1:
|
|
398
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorNoFace);
|
|
399
|
+
break;
|
|
400
|
+
case 2:
|
|
401
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorMultipleFaces);
|
|
402
|
+
break;
|
|
403
|
+
case 3:
|
|
404
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorNotCentered);
|
|
405
|
+
break;
|
|
406
|
+
default:
|
|
407
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
408
|
+
}
|
|
409
|
+
console.log("Onboarding result:", result);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error("Error onboarding face:", error);
|
|
413
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.error("Error in toBlob callback:", error);
|
|
418
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
419
|
+
}
|
|
420
|
+
}, "image/jpeg");
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
console.error("Error capturing photo:", error);
|
|
424
|
+
this.toggleState(exports.BiometryOnboardingState.ErrorOther);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
customElements.define("biometry-onboarding", BiometryOnboarding);
|
|
429
|
+
|
|
430
|
+
function getDefaultExportFromCjs (x) {
|
|
431
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
var fixWebmDuration = {exports: {}};
|
|
435
|
+
|
|
436
|
+
var hasRequiredFixWebmDuration;
|
|
437
|
+
|
|
438
|
+
function requireFixWebmDuration () {
|
|
439
|
+
if (hasRequiredFixWebmDuration) return fixWebmDuration.exports;
|
|
440
|
+
hasRequiredFixWebmDuration = 1;
|
|
441
|
+
(function (module) {
|
|
442
|
+
(function (name, definition) {
|
|
443
|
+
if (module.exports) { // CommonJS / Node.js
|
|
444
|
+
module.exports = definition();
|
|
445
|
+
} else { // Direct include
|
|
446
|
+
window.ysFixWebmDuration = definition();
|
|
447
|
+
}
|
|
448
|
+
})('fix-webm-duration', function () {
|
|
449
|
+
/*
|
|
450
|
+
* This is the list of possible WEBM file sections by their IDs.
|
|
451
|
+
* Possible types: Container, Binary, Uint, Int, String, Float, Date
|
|
452
|
+
*/
|
|
453
|
+
var sections = {
|
|
454
|
+
0xa45dfa3: { name: 'EBML', type: 'Container' },
|
|
455
|
+
0x286: { name: 'EBMLVersion', type: 'Uint' },
|
|
456
|
+
0x2f7: { name: 'EBMLReadVersion', type: 'Uint' },
|
|
457
|
+
0x2f2: { name: 'EBMLMaxIDLength', type: 'Uint' },
|
|
458
|
+
0x2f3: { name: 'EBMLMaxSizeLength', type: 'Uint' },
|
|
459
|
+
0x282: { name: 'DocType', type: 'String' },
|
|
460
|
+
0x287: { name: 'DocTypeVersion', type: 'Uint' },
|
|
461
|
+
0x285: { name: 'DocTypeReadVersion', type: 'Uint' },
|
|
462
|
+
0x6c: { name: 'Void', type: 'Binary' },
|
|
463
|
+
0x3f: { name: 'CRC-32', type: 'Binary' },
|
|
464
|
+
0xb538667: { name: 'SignatureSlot', type: 'Container' },
|
|
465
|
+
0x3e8a: { name: 'SignatureAlgo', type: 'Uint' },
|
|
466
|
+
0x3e9a: { name: 'SignatureHash', type: 'Uint' },
|
|
467
|
+
0x3ea5: { name: 'SignaturePublicKey', type: 'Binary' },
|
|
468
|
+
0x3eb5: { name: 'Signature', type: 'Binary' },
|
|
469
|
+
0x3e5b: { name: 'SignatureElements', type: 'Container' },
|
|
470
|
+
0x3e7b: { name: 'SignatureElementList', type: 'Container' },
|
|
471
|
+
0x2532: { name: 'SignedElement', type: 'Binary' },
|
|
472
|
+
0x8538067: { name: 'Segment', type: 'Container' },
|
|
473
|
+
0x14d9b74: { name: 'SeekHead', type: 'Container' },
|
|
474
|
+
0xdbb: { name: 'Seek', type: 'Container' },
|
|
475
|
+
0x13ab: { name: 'SeekID', type: 'Binary' },
|
|
476
|
+
0x13ac: { name: 'SeekPosition', type: 'Uint' },
|
|
477
|
+
0x549a966: { name: 'Info', type: 'Container' },
|
|
478
|
+
0x33a4: { name: 'SegmentUID', type: 'Binary' },
|
|
479
|
+
0x3384: { name: 'SegmentFilename', type: 'String' },
|
|
480
|
+
0x1cb923: { name: 'PrevUID', type: 'Binary' },
|
|
481
|
+
0x1c83ab: { name: 'PrevFilename', type: 'String' },
|
|
482
|
+
0x1eb923: { name: 'NextUID', type: 'Binary' },
|
|
483
|
+
0x1e83bb: { name: 'NextFilename', type: 'String' },
|
|
484
|
+
0x444: { name: 'SegmentFamily', type: 'Binary' },
|
|
485
|
+
0x2924: { name: 'ChapterTranslate', type: 'Container' },
|
|
486
|
+
0x29fc: { name: 'ChapterTranslateEditionUID', type: 'Uint' },
|
|
487
|
+
0x29bf: { name: 'ChapterTranslateCodec', type: 'Uint' },
|
|
488
|
+
0x29a5: { name: 'ChapterTranslateID', type: 'Binary' },
|
|
489
|
+
0xad7b1: { name: 'TimecodeScale', type: 'Uint' },
|
|
490
|
+
0x489: { name: 'Duration', type: 'Float' },
|
|
491
|
+
0x461: { name: 'DateUTC', type: 'Date' },
|
|
492
|
+
0x3ba9: { name: 'Title', type: 'String' },
|
|
493
|
+
0xd80: { name: 'MuxingApp', type: 'String' },
|
|
494
|
+
0x1741: { name: 'WritingApp', type: 'String' },
|
|
495
|
+
// 0xf43b675: { name: 'Cluster', type: 'Container' },
|
|
496
|
+
0x67: { name: 'Timecode', type: 'Uint' },
|
|
497
|
+
0x1854: { name: 'SilentTracks', type: 'Container' },
|
|
498
|
+
0x18d7: { name: 'SilentTrackNumber', type: 'Uint' },
|
|
499
|
+
0x27: { name: 'Position', type: 'Uint' },
|
|
500
|
+
0x2b: { name: 'PrevSize', type: 'Uint' },
|
|
501
|
+
0x23: { name: 'SimpleBlock', type: 'Binary' },
|
|
502
|
+
0x20: { name: 'BlockGroup', type: 'Container' },
|
|
503
|
+
0x21: { name: 'Block', type: 'Binary' },
|
|
504
|
+
0x22: { name: 'BlockVirtual', type: 'Binary' },
|
|
505
|
+
0x35a1: { name: 'BlockAdditions', type: 'Container' },
|
|
506
|
+
0x26: { name: 'BlockMore', type: 'Container' },
|
|
507
|
+
0x6e: { name: 'BlockAddID', type: 'Uint' },
|
|
508
|
+
0x25: { name: 'BlockAdditional', type: 'Binary' },
|
|
509
|
+
0x1b: { name: 'BlockDuration', type: 'Uint' },
|
|
510
|
+
0x7a: { name: 'ReferencePriority', type: 'Uint' },
|
|
511
|
+
0x7b: { name: 'ReferenceBlock', type: 'Int' },
|
|
512
|
+
0x7d: { name: 'ReferenceVirtual', type: 'Int' },
|
|
513
|
+
0x24: { name: 'CodecState', type: 'Binary' },
|
|
514
|
+
0x35a2: { name: 'DiscardPadding', type: 'Int' },
|
|
515
|
+
0xe: { name: 'Slices', type: 'Container' },
|
|
516
|
+
0x68: { name: 'TimeSlice', type: 'Container' },
|
|
517
|
+
0x4c: { name: 'LaceNumber', type: 'Uint' },
|
|
518
|
+
0x4d: { name: 'FrameNumber', type: 'Uint' },
|
|
519
|
+
0x4b: { name: 'BlockAdditionID', type: 'Uint' },
|
|
520
|
+
0x4e: { name: 'Delay', type: 'Uint' },
|
|
521
|
+
0x4f: { name: 'SliceDuration', type: 'Uint' },
|
|
522
|
+
0x48: { name: 'ReferenceFrame', type: 'Container' },
|
|
523
|
+
0x49: { name: 'ReferenceOffset', type: 'Uint' },
|
|
524
|
+
0x4a: { name: 'ReferenceTimeCode', type: 'Uint' },
|
|
525
|
+
0x2f: { name: 'EncryptedBlock', type: 'Binary' },
|
|
526
|
+
0x654ae6b: { name: 'Tracks', type: 'Container' },
|
|
527
|
+
0x2e: { name: 'TrackEntry', type: 'Container' },
|
|
528
|
+
0x57: { name: 'TrackNumber', type: 'Uint' },
|
|
529
|
+
0x33c5: { name: 'TrackUID', type: 'Uint' },
|
|
530
|
+
0x3: { name: 'TrackType', type: 'Uint' },
|
|
531
|
+
0x39: { name: 'FlagEnabled', type: 'Uint' },
|
|
532
|
+
0x8: { name: 'FlagDefault', type: 'Uint' },
|
|
533
|
+
0x15aa: { name: 'FlagForced', type: 'Uint' },
|
|
534
|
+
0x1c: { name: 'FlagLacing', type: 'Uint' },
|
|
535
|
+
0x2de7: { name: 'MinCache', type: 'Uint' },
|
|
536
|
+
0x2df8: { name: 'MaxCache', type: 'Uint' },
|
|
537
|
+
0x3e383: { name: 'DefaultDuration', type: 'Uint' },
|
|
538
|
+
0x34e7a: { name: 'DefaultDecodedFieldDuration', type: 'Uint' },
|
|
539
|
+
0x3314f: { name: 'TrackTimecodeScale', type: 'Float' },
|
|
540
|
+
0x137f: { name: 'TrackOffset', type: 'Int' },
|
|
541
|
+
0x15ee: { name: 'MaxBlockAdditionID', type: 'Uint' },
|
|
542
|
+
0x136e: { name: 'Name', type: 'String' },
|
|
543
|
+
0x2b59c: { name: 'Language', type: 'String' },
|
|
544
|
+
0x6: { name: 'CodecID', type: 'String' },
|
|
545
|
+
0x23a2: { name: 'CodecPrivate', type: 'Binary' },
|
|
546
|
+
0x58688: { name: 'CodecName', type: 'String' },
|
|
547
|
+
0x3446: { name: 'AttachmentLink', type: 'Uint' },
|
|
548
|
+
0x1a9697: { name: 'CodecSettings', type: 'String' },
|
|
549
|
+
0x1b4040: { name: 'CodecInfoURL', type: 'String' },
|
|
550
|
+
0x6b240: { name: 'CodecDownloadURL', type: 'String' },
|
|
551
|
+
0x2a: { name: 'CodecDecodeAll', type: 'Uint' },
|
|
552
|
+
0x2fab: { name: 'TrackOverlay', type: 'Uint' },
|
|
553
|
+
0x16aa: { name: 'CodecDelay', type: 'Uint' },
|
|
554
|
+
0x16bb: { name: 'SeekPreRoll', type: 'Uint' },
|
|
555
|
+
0x2624: { name: 'TrackTranslate', type: 'Container' },
|
|
556
|
+
0x26fc: { name: 'TrackTranslateEditionUID', type: 'Uint' },
|
|
557
|
+
0x26bf: { name: 'TrackTranslateCodec', type: 'Uint' },
|
|
558
|
+
0x26a5: { name: 'TrackTranslateTrackID', type: 'Binary' },
|
|
559
|
+
0x60: { name: 'Video', type: 'Container' },
|
|
560
|
+
0x1a: { name: 'FlagInterlaced', type: 'Uint' },
|
|
561
|
+
0x13b8: { name: 'StereoMode', type: 'Uint' },
|
|
562
|
+
0x13c0: { name: 'AlphaMode', type: 'Uint' },
|
|
563
|
+
0x13b9: { name: 'OldStereoMode', type: 'Uint' },
|
|
564
|
+
0x30: { name: 'PixelWidth', type: 'Uint' },
|
|
565
|
+
0x3a: { name: 'PixelHeight', type: 'Uint' },
|
|
566
|
+
0x14aa: { name: 'PixelCropBottom', type: 'Uint' },
|
|
567
|
+
0x14bb: { name: 'PixelCropTop', type: 'Uint' },
|
|
568
|
+
0x14cc: { name: 'PixelCropLeft', type: 'Uint' },
|
|
569
|
+
0x14dd: { name: 'PixelCropRight', type: 'Uint' },
|
|
570
|
+
0x14b0: { name: 'DisplayWidth', type: 'Uint' },
|
|
571
|
+
0x14ba: { name: 'DisplayHeight', type: 'Uint' },
|
|
572
|
+
0x14b2: { name: 'DisplayUnit', type: 'Uint' },
|
|
573
|
+
0x14b3: { name: 'AspectRatioType', type: 'Uint' },
|
|
574
|
+
0xeb524: { name: 'ColourSpace', type: 'Binary' },
|
|
575
|
+
0xfb523: { name: 'GammaValue', type: 'Float' },
|
|
576
|
+
0x383e3: { name: 'FrameRate', type: 'Float' },
|
|
577
|
+
0x61: { name: 'Audio', type: 'Container' },
|
|
578
|
+
0x35: { name: 'SamplingFrequency', type: 'Float' },
|
|
579
|
+
0x38b5: { name: 'OutputSamplingFrequency', type: 'Float' },
|
|
580
|
+
0x1f: { name: 'Channels', type: 'Uint' },
|
|
581
|
+
0x3d7b: { name: 'ChannelPositions', type: 'Binary' },
|
|
582
|
+
0x2264: { name: 'BitDepth', type: 'Uint' },
|
|
583
|
+
0x62: { name: 'TrackOperation', type: 'Container' },
|
|
584
|
+
0x63: { name: 'TrackCombinePlanes', type: 'Container' },
|
|
585
|
+
0x64: { name: 'TrackPlane', type: 'Container' },
|
|
586
|
+
0x65: { name: 'TrackPlaneUID', type: 'Uint' },
|
|
587
|
+
0x66: { name: 'TrackPlaneType', type: 'Uint' },
|
|
588
|
+
0x69: { name: 'TrackJoinBlocks', type: 'Container' },
|
|
589
|
+
0x6d: { name: 'TrackJoinUID', type: 'Uint' },
|
|
590
|
+
0x40: { name: 'TrickTrackUID', type: 'Uint' },
|
|
591
|
+
0x41: { name: 'TrickTrackSegmentUID', type: 'Binary' },
|
|
592
|
+
0x46: { name: 'TrickTrackFlag', type: 'Uint' },
|
|
593
|
+
0x47: { name: 'TrickMasterTrackUID', type: 'Uint' },
|
|
594
|
+
0x44: { name: 'TrickMasterTrackSegmentUID', type: 'Binary' },
|
|
595
|
+
0x2d80: { name: 'ContentEncodings', type: 'Container' },
|
|
596
|
+
0x2240: { name: 'ContentEncoding', type: 'Container' },
|
|
597
|
+
0x1031: { name: 'ContentEncodingOrder', type: 'Uint' },
|
|
598
|
+
0x1032: { name: 'ContentEncodingScope', type: 'Uint' },
|
|
599
|
+
0x1033: { name: 'ContentEncodingType', type: 'Uint' },
|
|
600
|
+
0x1034: { name: 'ContentCompression', type: 'Container' },
|
|
601
|
+
0x254: { name: 'ContentCompAlgo', type: 'Uint' },
|
|
602
|
+
0x255: { name: 'ContentCompSettings', type: 'Binary' },
|
|
603
|
+
0x1035: { name: 'ContentEncryption', type: 'Container' },
|
|
604
|
+
0x7e1: { name: 'ContentEncAlgo', type: 'Uint' },
|
|
605
|
+
0x7e2: { name: 'ContentEncKeyID', type: 'Binary' },
|
|
606
|
+
0x7e3: { name: 'ContentSignature', type: 'Binary' },
|
|
607
|
+
0x7e4: { name: 'ContentSigKeyID', type: 'Binary' },
|
|
608
|
+
0x7e5: { name: 'ContentSigAlgo', type: 'Uint' },
|
|
609
|
+
0x7e6: { name: 'ContentSigHashAlgo', type: 'Uint' },
|
|
610
|
+
0xc53bb6b: { name: 'Cues', type: 'Container' },
|
|
611
|
+
0x3b: { name: 'CuePoint', type: 'Container' },
|
|
612
|
+
0x33: { name: 'CueTime', type: 'Uint' },
|
|
613
|
+
0x37: { name: 'CueTrackPositions', type: 'Container' },
|
|
614
|
+
0x77: { name: 'CueTrack', type: 'Uint' },
|
|
615
|
+
0x71: { name: 'CueClusterPosition', type: 'Uint' },
|
|
616
|
+
0x70: { name: 'CueRelativePosition', type: 'Uint' },
|
|
617
|
+
0x32: { name: 'CueDuration', type: 'Uint' },
|
|
618
|
+
0x1378: { name: 'CueBlockNumber', type: 'Uint' },
|
|
619
|
+
0x6a: { name: 'CueCodecState', type: 'Uint' },
|
|
620
|
+
0x5b: { name: 'CueReference', type: 'Container' },
|
|
621
|
+
0x16: { name: 'CueRefTime', type: 'Uint' },
|
|
622
|
+
0x17: { name: 'CueRefCluster', type: 'Uint' },
|
|
623
|
+
0x135f: { name: 'CueRefNumber', type: 'Uint' },
|
|
624
|
+
0x6b: { name: 'CueRefCodecState', type: 'Uint' },
|
|
625
|
+
0x941a469: { name: 'Attachments', type: 'Container' },
|
|
626
|
+
0x21a7: { name: 'AttachedFile', type: 'Container' },
|
|
627
|
+
0x67e: { name: 'FileDescription', type: 'String' },
|
|
628
|
+
0x66e: { name: 'FileName', type: 'String' },
|
|
629
|
+
0x660: { name: 'FileMimeType', type: 'String' },
|
|
630
|
+
0x65c: { name: 'FileData', type: 'Binary' },
|
|
631
|
+
0x6ae: { name: 'FileUID', type: 'Uint' },
|
|
632
|
+
0x675: { name: 'FileReferral', type: 'Binary' },
|
|
633
|
+
0x661: { name: 'FileUsedStartTime', type: 'Uint' },
|
|
634
|
+
0x662: { name: 'FileUsedEndTime', type: 'Uint' },
|
|
635
|
+
0x43a770: { name: 'Chapters', type: 'Container' },
|
|
636
|
+
0x5b9: { name: 'EditionEntry', type: 'Container' },
|
|
637
|
+
0x5bc: { name: 'EditionUID', type: 'Uint' },
|
|
638
|
+
0x5bd: { name: 'EditionFlagHidden', type: 'Uint' },
|
|
639
|
+
0x5db: { name: 'EditionFlagDefault', type: 'Uint' },
|
|
640
|
+
0x5dd: { name: 'EditionFlagOrdered', type: 'Uint' },
|
|
641
|
+
0x36: { name: 'ChapterAtom', type: 'Container' },
|
|
642
|
+
0x33c4: { name: 'ChapterUID', type: 'Uint' },
|
|
643
|
+
0x1654: { name: 'ChapterStringUID', type: 'String' },
|
|
644
|
+
0x11: { name: 'ChapterTimeStart', type: 'Uint' },
|
|
645
|
+
0x12: { name: 'ChapterTimeEnd', type: 'Uint' },
|
|
646
|
+
0x18: { name: 'ChapterFlagHidden', type: 'Uint' },
|
|
647
|
+
0x598: { name: 'ChapterFlagEnabled', type: 'Uint' },
|
|
648
|
+
0x2e67: { name: 'ChapterSegmentUID', type: 'Binary' },
|
|
649
|
+
0x2ebc: { name: 'ChapterSegmentEditionUID', type: 'Uint' },
|
|
650
|
+
0x23c3: { name: 'ChapterPhysicalEquiv', type: 'Uint' },
|
|
651
|
+
0xf: { name: 'ChapterTrack', type: 'Container' },
|
|
652
|
+
0x9: { name: 'ChapterTrackNumber', type: 'Uint' },
|
|
653
|
+
0x0: { name: 'ChapterDisplay', type: 'Container' },
|
|
654
|
+
0x5: { name: 'ChapString', type: 'String' },
|
|
655
|
+
0x37c: { name: 'ChapLanguage', type: 'String' },
|
|
656
|
+
0x37e: { name: 'ChapCountry', type: 'String' },
|
|
657
|
+
0x2944: { name: 'ChapProcess', type: 'Container' },
|
|
658
|
+
0x2955: { name: 'ChapProcessCodecID', type: 'Uint' },
|
|
659
|
+
0x50d: { name: 'ChapProcessPrivate', type: 'Binary' },
|
|
660
|
+
0x2911: { name: 'ChapProcessCommand', type: 'Container' },
|
|
661
|
+
0x2922: { name: 'ChapProcessTime', type: 'Uint' },
|
|
662
|
+
0x2933: { name: 'ChapProcessData', type: 'Binary' },
|
|
663
|
+
0x254c367: { name: 'Tags', type: 'Container' },
|
|
664
|
+
0x3373: { name: 'Tag', type: 'Container' },
|
|
665
|
+
0x23c0: { name: 'Targets', type: 'Container' },
|
|
666
|
+
0x28ca: { name: 'TargetTypeValue', type: 'Uint' },
|
|
667
|
+
0x23ca: { name: 'TargetType', type: 'String' },
|
|
668
|
+
0x23c5: { name: 'TagTrackUID', type: 'Uint' },
|
|
669
|
+
0x23c9: { name: 'TagEditionUID', type: 'Uint' },
|
|
670
|
+
0x23c4: { name: 'TagChapterUID', type: 'Uint' },
|
|
671
|
+
0x23c6: { name: 'TagAttachmentUID', type: 'Uint' },
|
|
672
|
+
0x27c8: { name: 'SimpleTag', type: 'Container' },
|
|
673
|
+
0x5a3: { name: 'TagName', type: 'String' },
|
|
674
|
+
0x47a: { name: 'TagLanguage', type: 'String' },
|
|
675
|
+
0x484: { name: 'TagDefault', type: 'Uint' },
|
|
676
|
+
0x487: { name: 'TagString', type: 'String' },
|
|
677
|
+
0x485: { name: 'TagBinary', type: 'Binary' }
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
function doInherit(newClass, baseClass) {
|
|
681
|
+
newClass.prototype = Object.create(baseClass.prototype);
|
|
682
|
+
newClass.prototype.constructor = newClass;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function WebmBase(name, type) {
|
|
686
|
+
this.name = name || 'Unknown';
|
|
687
|
+
this.type = type || 'Unknown';
|
|
688
|
+
}
|
|
689
|
+
WebmBase.prototype.updateBySource = function() { };
|
|
690
|
+
WebmBase.prototype.setSource = function(source) {
|
|
691
|
+
this.source = source;
|
|
692
|
+
this.updateBySource();
|
|
693
|
+
};
|
|
694
|
+
WebmBase.prototype.updateByData = function() { };
|
|
695
|
+
WebmBase.prototype.setData = function(data) {
|
|
696
|
+
this.data = data;
|
|
697
|
+
this.updateByData();
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
function WebmUint(name, type) {
|
|
701
|
+
WebmBase.call(this, name, type || 'Uint');
|
|
702
|
+
}
|
|
703
|
+
doInherit(WebmUint, WebmBase);
|
|
704
|
+
function padHex(hex) {
|
|
705
|
+
return hex.length % 2 === 1 ? '0' + hex : hex;
|
|
706
|
+
}
|
|
707
|
+
WebmUint.prototype.updateBySource = function() {
|
|
708
|
+
// use hex representation of a number instead of number value
|
|
709
|
+
this.data = '';
|
|
710
|
+
for (var i = 0; i < this.source.length; i++) {
|
|
711
|
+
var hex = this.source[i].toString(16);
|
|
712
|
+
this.data += padHex(hex);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
WebmUint.prototype.updateByData = function() {
|
|
716
|
+
var length = this.data.length / 2;
|
|
717
|
+
this.source = new Uint8Array(length);
|
|
718
|
+
for (var i = 0; i < length; i++) {
|
|
719
|
+
var hex = this.data.substr(i * 2, 2);
|
|
720
|
+
this.source[i] = parseInt(hex, 16);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
WebmUint.prototype.getValue = function() {
|
|
724
|
+
return parseInt(this.data, 16);
|
|
725
|
+
};
|
|
726
|
+
WebmUint.prototype.setValue = function(value) {
|
|
727
|
+
this.setData(padHex(value.toString(16)));
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
function WebmFloat(name, type) {
|
|
731
|
+
WebmBase.call(this, name, type || 'Float');
|
|
732
|
+
}
|
|
733
|
+
doInherit(WebmFloat, WebmBase);
|
|
734
|
+
WebmFloat.prototype.getFloatArrayType = function() {
|
|
735
|
+
return this.source && this.source.length === 4 ? Float32Array : Float64Array;
|
|
736
|
+
};
|
|
737
|
+
WebmFloat.prototype.updateBySource = function() {
|
|
738
|
+
var byteArray = this.source.reverse();
|
|
739
|
+
var floatArrayType = this.getFloatArrayType();
|
|
740
|
+
var floatArray = new floatArrayType(byteArray.buffer);
|
|
741
|
+
this.data = floatArray[0];
|
|
742
|
+
};
|
|
743
|
+
WebmFloat.prototype.updateByData = function() {
|
|
744
|
+
var floatArrayType = this.getFloatArrayType();
|
|
745
|
+
var floatArray = new floatArrayType([ this.data ]);
|
|
746
|
+
var byteArray = new Uint8Array(floatArray.buffer);
|
|
747
|
+
this.source = byteArray.reverse();
|
|
748
|
+
};
|
|
749
|
+
WebmFloat.prototype.getValue = function() {
|
|
750
|
+
return this.data;
|
|
751
|
+
};
|
|
752
|
+
WebmFloat.prototype.setValue = function(value) {
|
|
753
|
+
this.setData(value);
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
function WebmContainer(name, type) {
|
|
757
|
+
WebmBase.call(this, name, type || 'Container');
|
|
758
|
+
}
|
|
759
|
+
doInherit(WebmContainer, WebmBase);
|
|
760
|
+
WebmContainer.prototype.readByte = function() {
|
|
761
|
+
return this.source[this.offset++];
|
|
762
|
+
};
|
|
763
|
+
WebmContainer.prototype.readUint = function() {
|
|
764
|
+
var firstByte = this.readByte();
|
|
765
|
+
var bytes = 8 - firstByte.toString(2).length;
|
|
766
|
+
var value = firstByte - (1 << (7 - bytes));
|
|
767
|
+
for (var i = 0; i < bytes; i++) {
|
|
768
|
+
// don't use bit operators to support x86
|
|
769
|
+
value *= 256;
|
|
770
|
+
value += this.readByte();
|
|
771
|
+
}
|
|
772
|
+
return value;
|
|
773
|
+
};
|
|
774
|
+
WebmContainer.prototype.updateBySource = function() {
|
|
775
|
+
this.data = [];
|
|
776
|
+
for (this.offset = 0; this.offset < this.source.length; this.offset = end) {
|
|
777
|
+
var id = this.readUint();
|
|
778
|
+
var len = this.readUint();
|
|
779
|
+
var end = Math.min(this.offset + len, this.source.length);
|
|
780
|
+
var data = this.source.slice(this.offset, end);
|
|
781
|
+
|
|
782
|
+
var info = sections[id] || { name: 'Unknown', type: 'Unknown' };
|
|
783
|
+
var ctr = WebmBase;
|
|
784
|
+
switch (info.type) {
|
|
785
|
+
case 'Container':
|
|
786
|
+
ctr = WebmContainer;
|
|
787
|
+
break;
|
|
788
|
+
case 'Uint':
|
|
789
|
+
ctr = WebmUint;
|
|
790
|
+
break;
|
|
791
|
+
case 'Float':
|
|
792
|
+
ctr = WebmFloat;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
var section = new ctr(info.name, info.type);
|
|
796
|
+
section.setSource(data);
|
|
797
|
+
this.data.push({
|
|
798
|
+
id: id,
|
|
799
|
+
idHex: id.toString(16),
|
|
800
|
+
data: section
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
WebmContainer.prototype.writeUint = function(x, draft) {
|
|
805
|
+
for (var bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { }
|
|
806
|
+
|
|
807
|
+
if (!draft) {
|
|
808
|
+
var value = flag + x;
|
|
809
|
+
for (var i = bytes - 1; i >= 0; i--) {
|
|
810
|
+
// don't use bit operators to support x86
|
|
811
|
+
var c = value % 256;
|
|
812
|
+
this.source[this.offset + i] = c;
|
|
813
|
+
value = (value - c) / 256;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
this.offset += bytes;
|
|
818
|
+
};
|
|
819
|
+
WebmContainer.prototype.writeSections = function(draft) {
|
|
820
|
+
this.offset = 0;
|
|
821
|
+
for (var i = 0; i < this.data.length; i++) {
|
|
822
|
+
var section = this.data[i],
|
|
823
|
+
content = section.data.source,
|
|
824
|
+
contentLength = content.length;
|
|
825
|
+
this.writeUint(section.id, draft);
|
|
826
|
+
this.writeUint(contentLength, draft);
|
|
827
|
+
if (!draft) {
|
|
828
|
+
this.source.set(content, this.offset);
|
|
829
|
+
}
|
|
830
|
+
this.offset += contentLength;
|
|
831
|
+
}
|
|
832
|
+
return this.offset;
|
|
833
|
+
};
|
|
834
|
+
WebmContainer.prototype.updateByData = function() {
|
|
835
|
+
// run without accessing this.source to determine total length - need to know it to create Uint8Array
|
|
836
|
+
var length = this.writeSections('draft');
|
|
837
|
+
this.source = new Uint8Array(length);
|
|
838
|
+
// now really write data
|
|
839
|
+
this.writeSections();
|
|
840
|
+
};
|
|
841
|
+
WebmContainer.prototype.getSectionById = function(id) {
|
|
842
|
+
for (var i = 0; i < this.data.length; i++) {
|
|
843
|
+
var section = this.data[i];
|
|
844
|
+
if (section.id === id) {
|
|
845
|
+
return section.data;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return null;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
function WebmFile(source) {
|
|
852
|
+
WebmContainer.call(this, 'File', 'File');
|
|
853
|
+
this.setSource(source);
|
|
854
|
+
}
|
|
855
|
+
doInherit(WebmFile, WebmContainer);
|
|
856
|
+
WebmFile.prototype.fixDuration = function(duration, options) {
|
|
857
|
+
var logger = options && options.logger;
|
|
858
|
+
if (logger === undefined) {
|
|
859
|
+
logger = function(message) {
|
|
860
|
+
console.log(message);
|
|
861
|
+
};
|
|
862
|
+
} else if (!logger) {
|
|
863
|
+
logger = function() { };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
var segmentSection = this.getSectionById(0x8538067);
|
|
867
|
+
if (!segmentSection) {
|
|
868
|
+
logger('[fix-webm-duration] Segment section is missing');
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
var infoSection = segmentSection.getSectionById(0x549a966);
|
|
873
|
+
if (!infoSection) {
|
|
874
|
+
logger('[fix-webm-duration] Info section is missing');
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
var timeScaleSection = infoSection.getSectionById(0xad7b1);
|
|
879
|
+
if (!timeScaleSection) {
|
|
880
|
+
logger('[fix-webm-duration] TimecodeScale section is missing');
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
var durationSection = infoSection.getSectionById(0x489);
|
|
885
|
+
if (durationSection) {
|
|
886
|
+
if (durationSection.getValue() <= 0) {
|
|
887
|
+
logger(`[fix-webm-duration] Duration section is present, but the value is ${durationSection.getValue()}`);
|
|
888
|
+
durationSection.setValue(duration);
|
|
889
|
+
} else {
|
|
890
|
+
logger(`[fix-webm-duration] Duration section is present, and the value is ${durationSection.getValue()}`);
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
logger('[fix-webm-duration] Duration section is missing');
|
|
895
|
+
// append Duration section
|
|
896
|
+
durationSection = new WebmFloat('Duration', 'Float');
|
|
897
|
+
durationSection.setValue(duration);
|
|
898
|
+
infoSection.data.push({
|
|
899
|
+
id: 0x489,
|
|
900
|
+
data: durationSection
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// set default time scale to 1 millisecond (1000000 nanoseconds)
|
|
905
|
+
timeScaleSection.setValue(1000000);
|
|
906
|
+
infoSection.updateByData();
|
|
907
|
+
segmentSection.updateByData();
|
|
908
|
+
this.updateByData();
|
|
909
|
+
|
|
910
|
+
return true;
|
|
911
|
+
};
|
|
912
|
+
WebmFile.prototype.toBlob = function(mimeType) {
|
|
913
|
+
return new Blob([ this.source.buffer ], { type: mimeType || 'video/webm' });
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
function fixWebmDuration(blob, duration, callback, options) {
|
|
917
|
+
// The callback may be omitted - then the third argument is options
|
|
918
|
+
if (typeof callback === "object") {
|
|
919
|
+
options = callback;
|
|
920
|
+
callback = undefined;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (!callback) {
|
|
924
|
+
return new Promise(function(resolve) {
|
|
925
|
+
fixWebmDuration(blob, duration, resolve, options);
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
var reader = new FileReader();
|
|
931
|
+
reader.onloadend = function() {
|
|
932
|
+
try {
|
|
933
|
+
var file = new WebmFile(new Uint8Array(reader.result));
|
|
934
|
+
if (file.fixDuration(duration, options)) {
|
|
935
|
+
blob = file.toBlob(blob.type);
|
|
936
|
+
}
|
|
937
|
+
} catch (ex) {
|
|
938
|
+
// ignore
|
|
939
|
+
}
|
|
940
|
+
callback(blob);
|
|
941
|
+
};
|
|
942
|
+
reader.readAsArrayBuffer(blob);
|
|
943
|
+
} catch (ex) {
|
|
944
|
+
callback(blob);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Support AMD import default
|
|
949
|
+
fixWebmDuration.default = fixWebmDuration;
|
|
950
|
+
|
|
951
|
+
return fixWebmDuration;
|
|
952
|
+
});
|
|
953
|
+
} (fixWebmDuration));
|
|
954
|
+
return fixWebmDuration.exports;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
var fixWebmDurationExports = requireFixWebmDuration();
|
|
958
|
+
var ysFixWebmDuration = /*@__PURE__*/getDefaultExportFromCjs(fixWebmDurationExports);
|
|
959
|
+
|
|
960
|
+
class ProcessVideoComponent extends HTMLElement {
|
|
961
|
+
constructor() {
|
|
962
|
+
super();
|
|
963
|
+
this.apiKey = null;
|
|
964
|
+
this.previewStream = null;
|
|
965
|
+
this.recordedChunks = [];
|
|
966
|
+
this.mediaRecorder = null;
|
|
967
|
+
this.videoFile = null;
|
|
968
|
+
this.startTime = 0;
|
|
969
|
+
this.timerInterval = null;
|
|
970
|
+
this.recordingTimeout = null;
|
|
971
|
+
this.errorState = null;
|
|
972
|
+
this.timeLimit = 30;
|
|
973
|
+
this.phrase = this.generateDefaultPhrase();
|
|
974
|
+
// Attach shadow DOM and initialize UI
|
|
975
|
+
this.attachShadow({ mode: 'open' });
|
|
976
|
+
this.apiKey = this.getAttribute('api-key');
|
|
977
|
+
this.initializeSDK();
|
|
978
|
+
this.initializeUI();
|
|
979
|
+
}
|
|
980
|
+
initializeSDK() {
|
|
981
|
+
if (this.apiKey) {
|
|
982
|
+
this.sdk = new BiometrySDK(this.apiKey);
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
this.toggleState('error');
|
|
986
|
+
console.error('API key is required to initialize the SDK.');
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
connectedCallback() {
|
|
990
|
+
if (this.apiKey) {
|
|
991
|
+
this.initializeSDK();
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
console.error('API key is required.');
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
disconnectedCallback() {
|
|
998
|
+
this.stopRecording();
|
|
999
|
+
if (this.previewStream) {
|
|
1000
|
+
this.previewStream.getTracks().forEach(track => track.stop());
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1004
|
+
if (name === 'api-key' && newValue !== oldValue) {
|
|
1005
|
+
this.apiKey = newValue;
|
|
1006
|
+
this.initializeSDK();
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
generateDefaultPhrase() {
|
|
1010
|
+
return Math.random().toString().slice(2, 10); // 8-digit random phrase
|
|
1011
|
+
}
|
|
1012
|
+
initializeUI() {
|
|
1013
|
+
const phraseDisplay = this.phrase
|
|
1014
|
+
.split("")
|
|
1015
|
+
.map((digit) => `<span class="digit">${digit}</span>`)
|
|
1016
|
+
.join(" ");
|
|
1017
|
+
this.shadowRoot.innerHTML = `
|
|
1018
|
+
<style>
|
|
1019
|
+
:host {
|
|
1020
|
+
display: block;
|
|
1021
|
+
font-family: Arial, sans-serif;
|
|
1022
|
+
--primary-color: #007bff;
|
|
1023
|
+
--secondary-color: #6c757d;
|
|
1024
|
+
--button-bg: var(--primary-color);
|
|
1025
|
+
--button-text-color: #fff;
|
|
1026
|
+
--input-border-color: var(--secondary-color);
|
|
1027
|
+
--input-focus-border-color: var(--primary-color);
|
|
1028
|
+
--spacing: 16px;
|
|
1029
|
+
--button-padding: 10px 20px;
|
|
1030
|
+
--border-radius: 4px;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.container {
|
|
1034
|
+
display: flex;
|
|
1035
|
+
flex-direction: column;
|
|
1036
|
+
align-items: center;
|
|
1037
|
+
justify-content: center;
|
|
1038
|
+
gap: var(--spacing);
|
|
1039
|
+
padding: var(--spacing);
|
|
1040
|
+
max-width: 500px;
|
|
1041
|
+
margin: 0 auto;
|
|
1042
|
+
text-align: center;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.video-wrapper {
|
|
1046
|
+
position: relative;
|
|
1047
|
+
display: inline-block;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
#timer-overlay {
|
|
1051
|
+
position: absolute;
|
|
1052
|
+
top: 10px;
|
|
1053
|
+
left: 10px;
|
|
1054
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
1055
|
+
color: white;
|
|
1056
|
+
padding: 5px 10px;
|
|
1057
|
+
border-radius: 4px;
|
|
1058
|
+
font-family: Arial, sans-serif;
|
|
1059
|
+
font-size: 14px;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
video {
|
|
1063
|
+
max-width: 100%;
|
|
1064
|
+
border-radius: var(--border-radius);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
input[type="text"], input[type="file"] {
|
|
1068
|
+
padding: var(--button-padding);
|
|
1069
|
+
border: 1px solid var(--input-border-color);
|
|
1070
|
+
border-radius: var(--border-radius);
|
|
1071
|
+
width: 100%;
|
|
1072
|
+
max-width: 100%;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
input[type="text"]:focus, input[type="file"]:focus {
|
|
1076
|
+
outline: none;
|
|
1077
|
+
border-color: var(--input-focus-border-color);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.hidden {
|
|
1081
|
+
display: none;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.phrase-display {
|
|
1085
|
+
font-size: 24px;
|
|
1086
|
+
font-weight: bold;
|
|
1087
|
+
display: flex;
|
|
1088
|
+
gap: 8px;
|
|
1089
|
+
justify-content: center;
|
|
1090
|
+
}
|
|
1091
|
+
.digit {
|
|
1092
|
+
padding: 4px;
|
|
1093
|
+
border: 1px solid #ccc;
|
|
1094
|
+
border-radius: 4px;
|
|
1095
|
+
text-align: center;
|
|
1096
|
+
width: 24px;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
</style>
|
|
1100
|
+
<div class="container">
|
|
1101
|
+
<slot name="video">
|
|
1102
|
+
<div class="video-wrapper">
|
|
1103
|
+
<video id="video-preview" muted autoplay></video>
|
|
1104
|
+
<div id="timer-overlay" class="hidden">00:00</div>
|
|
1105
|
+
</div>
|
|
1106
|
+
</slot>
|
|
1107
|
+
<slot name="phrase-display">
|
|
1108
|
+
<div class="phrase-display">
|
|
1109
|
+
${phraseDisplay}
|
|
1110
|
+
</div>
|
|
1111
|
+
</slot>
|
|
1112
|
+
<slot name="record-button">
|
|
1113
|
+
<button id="record-button">Start Recording</button>
|
|
1114
|
+
</slot>
|
|
1115
|
+
<slot name="stop-button">
|
|
1116
|
+
<button id="stop-button" disabled>Stop Recording</button>
|
|
1117
|
+
</slot>
|
|
1118
|
+
<slot name="file-input">
|
|
1119
|
+
<input type="file" accept="video/*" id="file-input" />
|
|
1120
|
+
</slot>
|
|
1121
|
+
<slot name="submit-button">
|
|
1122
|
+
<button id="submit-button">Submit Video</button>
|
|
1123
|
+
</slot>
|
|
1124
|
+
<slot name="loading">
|
|
1125
|
+
<div class="message">Loading...</div>
|
|
1126
|
+
</slot>
|
|
1127
|
+
<slot name="error">
|
|
1128
|
+
<div class="message error">An error occurred</div>
|
|
1129
|
+
</slot>
|
|
1130
|
+
<slot name="success">
|
|
1131
|
+
<div class="message success">Video submitted successfully!</div>
|
|
1132
|
+
</slot>
|
|
1133
|
+
</div>
|
|
1134
|
+
`;
|
|
1135
|
+
this.attachSlotListeners();
|
|
1136
|
+
this.setupPreview();
|
|
1137
|
+
this.toggleState(null);
|
|
1138
|
+
}
|
|
1139
|
+
attachSlotListeners() {
|
|
1140
|
+
const videoSlot = this.shadowRoot.querySelector('slot[name="video"]');
|
|
1141
|
+
const recordButtonSlot = this.shadowRoot.querySelector('slot[name="record-button"]');
|
|
1142
|
+
const stopButtonSlot = this.shadowRoot.querySelector('slot[name="stop-button"]');
|
|
1143
|
+
const fileInputSlot = this.shadowRoot.querySelector('slot[name="file-input"]');
|
|
1144
|
+
const submitButtonSlot = this.shadowRoot.querySelector('slot[name="submit-button"]');
|
|
1145
|
+
this.videoElement = this.getSlotElement(videoSlot, '#video-preview', HTMLVideoElement);
|
|
1146
|
+
this.recordButton = this.getSlotElement(recordButtonSlot, '#record-button', HTMLButtonElement);
|
|
1147
|
+
this.stopButton = this.getSlotElement(stopButtonSlot, '#stop-button', HTMLButtonElement);
|
|
1148
|
+
this.fileInput = this.getSlotElement(fileInputSlot, '#file-input', HTMLInputElement);
|
|
1149
|
+
this.submitButton = this.getSlotElement(submitButtonSlot, '#submit-button', HTMLButtonElement);
|
|
1150
|
+
if (this.fileInput) {
|
|
1151
|
+
this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
|
|
1152
|
+
}
|
|
1153
|
+
if (this.recordButton) {
|
|
1154
|
+
this.recordButton.addEventListener("click", () => this.startRecording());
|
|
1155
|
+
}
|
|
1156
|
+
if (this.stopButton) {
|
|
1157
|
+
this.stopButton.addEventListener("click", () => this.stopRecording());
|
|
1158
|
+
}
|
|
1159
|
+
if (this.submitButton) {
|
|
1160
|
+
this.submitButton.addEventListener("click", () => this.handleSubmit());
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
getSlotElement(slot, fallbackSelector, elementType) {
|
|
1164
|
+
const assignedElements = slot.assignedElements();
|
|
1165
|
+
return (assignedElements.length > 0 ? assignedElements[0] : null) || this.shadowRoot.querySelector(fallbackSelector);
|
|
1166
|
+
}
|
|
1167
|
+
replaceSlotContent(slotName, content) {
|
|
1168
|
+
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
1169
|
+
if (slot) {
|
|
1170
|
+
if (typeof content === 'string') {
|
|
1171
|
+
slot.innerHTML = content;
|
|
1172
|
+
}
|
|
1173
|
+
else if (content instanceof HTMLElement) {
|
|
1174
|
+
slot.innerHTML = '';
|
|
1175
|
+
slot.appendChild(content);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
removeSlotListener(slotName, event, callback) {
|
|
1180
|
+
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
1181
|
+
if (slot) {
|
|
1182
|
+
const assignedNodes = slot.assignedElements();
|
|
1183
|
+
assignedNodes.forEach((node) => {
|
|
1184
|
+
node.removeEventListener(event, callback);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
toggleState(state) {
|
|
1189
|
+
const states = ['loading', 'success', 'error'];
|
|
1190
|
+
states.forEach((slotName) => {
|
|
1191
|
+
const slot = this.shadowRoot.querySelector(`slot[name="${slotName}"]`);
|
|
1192
|
+
if (slot) {
|
|
1193
|
+
slot.style.display = slotName === state ? 'block' : 'none';
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
convertPhraseToWords(phrase) {
|
|
1198
|
+
const digitWords = [
|
|
1199
|
+
"zero", "one", "two", "three", "four",
|
|
1200
|
+
"five", "six", "seven", "eight", "nine"
|
|
1201
|
+
];
|
|
1202
|
+
return phrase
|
|
1203
|
+
.split("")
|
|
1204
|
+
.map((digit) => digitWords[parseInt(digit, 10)])
|
|
1205
|
+
.join(" ");
|
|
1206
|
+
}
|
|
1207
|
+
async setupPreview() {
|
|
1208
|
+
try {
|
|
1209
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
1210
|
+
this.previewStream = stream;
|
|
1211
|
+
this.videoElement.srcObject = stream;
|
|
1212
|
+
this.videoElement.controls = false;
|
|
1213
|
+
this.videoElement.play();
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
this.toggleState('error');
|
|
1217
|
+
console.error('Error setting up video preview:', error);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
async startTimer() {
|
|
1221
|
+
const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
|
|
1222
|
+
timerOverlay.textContent = '00:00';
|
|
1223
|
+
timerOverlay.classList.remove('hidden');
|
|
1224
|
+
let seconds = 0;
|
|
1225
|
+
this.timerInterval = setInterval(() => {
|
|
1226
|
+
seconds++;
|
|
1227
|
+
const minutes = Math.floor(seconds / 60);
|
|
1228
|
+
const remainingSeconds = seconds % 60;
|
|
1229
|
+
timerOverlay.textContent = `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
|
|
1230
|
+
}, 1000);
|
|
1231
|
+
this.recordingTimeout = setTimeout(() => {
|
|
1232
|
+
this.stopRecording();
|
|
1233
|
+
}, this.timeLimit * 1000);
|
|
1234
|
+
}
|
|
1235
|
+
async stopTimer() {
|
|
1236
|
+
if (this.recordingTimeout) {
|
|
1237
|
+
clearTimeout(this.recordingTimeout);
|
|
1238
|
+
}
|
|
1239
|
+
if (this.timerInterval) {
|
|
1240
|
+
clearInterval(this.timerInterval);
|
|
1241
|
+
this.timerInterval = null;
|
|
1242
|
+
}
|
|
1243
|
+
const timerOverlay = this.shadowRoot.querySelector('#timer-overlay');
|
|
1244
|
+
timerOverlay.classList.add('hidden');
|
|
1245
|
+
}
|
|
1246
|
+
async startRecording() {
|
|
1247
|
+
if (!window.MediaRecorder) {
|
|
1248
|
+
console.error('MediaRecorder API is not supported in this browser');
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
|
|
1253
|
+
console.log('Recording already in progress.');
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (!this.previewStream) {
|
|
1257
|
+
console.log('Initializing preview stream...');
|
|
1258
|
+
this.previewStream = await navigator.mediaDevices.getUserMedia({
|
|
1259
|
+
video: true,
|
|
1260
|
+
audio: true,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
this.videoElement.muted = true;
|
|
1264
|
+
this.videoElement.srcObject = this.previewStream;
|
|
1265
|
+
this.videoElement.currentTime = 0;
|
|
1266
|
+
await this.videoElement.play();
|
|
1267
|
+
this.mediaRecorder = new MediaRecorder(this.previewStream);
|
|
1268
|
+
this.recordedChunks = [];
|
|
1269
|
+
this.mediaRecorder.ondataavailable = (event) => {
|
|
1270
|
+
if (event.data.size > 0) {
|
|
1271
|
+
this.recordedChunks.push(event.data);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
this.mediaRecorder.onstop = () => {
|
|
1275
|
+
const duration = Date.now() - this.startTime;
|
|
1276
|
+
const buggyBlob = new Blob(this.recordedChunks, { type: 'video/webm' });
|
|
1277
|
+
ysFixWebmDuration(buggyBlob, duration, { logger: false })
|
|
1278
|
+
.then((fixedBlob) => {
|
|
1279
|
+
this.onStopMediaRecorder(fixedBlob);
|
|
1280
|
+
});
|
|
1281
|
+
};
|
|
1282
|
+
this.mediaRecorder.start();
|
|
1283
|
+
this.startTimer();
|
|
1284
|
+
this.startTime = Date.now();
|
|
1285
|
+
this.recordButton.disabled = true;
|
|
1286
|
+
this.stopButton.disabled = false;
|
|
1287
|
+
this.videoElement.controls = false;
|
|
1288
|
+
}
|
|
1289
|
+
catch (error) {
|
|
1290
|
+
console.error('Error starting video recording:', error);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
stopRecording() {
|
|
1294
|
+
try {
|
|
1295
|
+
if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive') {
|
|
1296
|
+
console.log('No recording in progress.');
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
this.mediaRecorder.stop();
|
|
1300
|
+
if (this.previewStream) {
|
|
1301
|
+
this.previewStream.getTracks().forEach(track => track.stop());
|
|
1302
|
+
}
|
|
1303
|
+
this.videoElement.srcObject = null;
|
|
1304
|
+
this.videoElement.src = '';
|
|
1305
|
+
this.videoElement.controls = false;
|
|
1306
|
+
this.recordButton.disabled = false;
|
|
1307
|
+
this.stopButton.disabled = true;
|
|
1308
|
+
this.mediaRecorder = null;
|
|
1309
|
+
this.recordedChunks = [];
|
|
1310
|
+
this.previewStream = null;
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
console.error('Error stopping video recording:', error);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
onStopMediaRecorder(blob) {
|
|
1317
|
+
const videoURL = URL.createObjectURL(blob);
|
|
1318
|
+
this.videoFile = new File([blob], 'recorded_video.webm', { type: 'video/webm' });
|
|
1319
|
+
this.videoElement.src = videoURL;
|
|
1320
|
+
this.videoElement.controls = true;
|
|
1321
|
+
this.videoElement.play();
|
|
1322
|
+
this.videoElement.muted = false;
|
|
1323
|
+
this.stopTimer();
|
|
1324
|
+
}
|
|
1325
|
+
handleFileUpload(event) {
|
|
1326
|
+
var _a, _b;
|
|
1327
|
+
const file = (_a = event.target.files) === null || _a === undefined ? undefined : _a[0];
|
|
1328
|
+
if ((_b = file === null || file === undefined ? undefined : file.type) === null || _b === undefined ? undefined : _b.startsWith('video/')) {
|
|
1329
|
+
if (file.size > 100 * 1024 * 1024) { // 100MB limit
|
|
1330
|
+
this.toggleState('error');
|
|
1331
|
+
console.error('File size exceeds limit of 100MB');
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.videoFile = file;
|
|
1335
|
+
this.videoElement.src = URL.createObjectURL(file);
|
|
1336
|
+
this.videoElement.play();
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
this.toggleState('error');
|
|
1340
|
+
console.error('Please select a valid video file.');
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async handleSubmit() {
|
|
1344
|
+
if (!this.videoFile) {
|
|
1345
|
+
this.toggleState('error');
|
|
1346
|
+
console.error('No video file to submit.');
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
if (!this.apiKey || !this.userFullname) {
|
|
1350
|
+
this.toggleState('error');
|
|
1351
|
+
console.error('API key and user fullname must be provided.');
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
this.toggleState('loading');
|
|
1355
|
+
try {
|
|
1356
|
+
const phraseInWords = this.convertPhraseToWords(this.phrase);
|
|
1357
|
+
const result = await this.sdk.processVideo(this.videoFile, phraseInWords, this.userFullname);
|
|
1358
|
+
console.log('Response from processVideo:', result);
|
|
1359
|
+
this.toggleState('success');
|
|
1360
|
+
}
|
|
1361
|
+
catch (error) {
|
|
1362
|
+
this.toggleState('error');
|
|
1363
|
+
console.error('Error submitting video:', error);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
static get observedAttributes() {
|
|
1367
|
+
return ['api-key', 'user-fullname'];
|
|
1368
|
+
}
|
|
1369
|
+
get userFullname() {
|
|
1370
|
+
return this.getAttribute('user-fullname');
|
|
1371
|
+
}
|
|
1372
|
+
set userFullname(value) {
|
|
1373
|
+
if (value) {
|
|
1374
|
+
this.setAttribute('user-fullname', value);
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
this.removeAttribute('user-fullname');
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
get isRecording() {
|
|
1381
|
+
var _a;
|
|
1382
|
+
return ((_a = this.mediaRecorder) === null || _a === undefined ? undefined : _a.state) === 'recording';
|
|
1383
|
+
}
|
|
1384
|
+
get currentPhrase() {
|
|
1385
|
+
return this.phrase;
|
|
1386
|
+
}
|
|
1387
|
+
get videoDuration() {
|
|
1388
|
+
var _a;
|
|
1389
|
+
return ((_a = this.videoElement) === null || _a === undefined ? undefined : _a.duration) || null;
|
|
1390
|
+
}
|
|
1391
|
+
get currentFile() {
|
|
1392
|
+
return this.videoFile;
|
|
1393
|
+
}
|
|
1394
|
+
get currentStream() {
|
|
1395
|
+
return this.previewStream;
|
|
1396
|
+
}
|
|
1397
|
+
set sdkInstance(newSdk) {
|
|
1398
|
+
this.sdk = newSdk;
|
|
1399
|
+
}
|
|
1400
|
+
get videoElementRef() {
|
|
1401
|
+
return this.videoElement;
|
|
1402
|
+
}
|
|
1403
|
+
get fileInputRef() {
|
|
1404
|
+
return this.fileInput;
|
|
1405
|
+
}
|
|
1406
|
+
get recordingTimeLimit() {
|
|
1407
|
+
return this.timeLimit;
|
|
1408
|
+
}
|
|
1409
|
+
set recordingTimeLimit(value) {
|
|
1410
|
+
this.timeLimit = value;
|
|
1411
|
+
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
|
|
1412
|
+
if (this.recordingTimeout) {
|
|
1413
|
+
clearTimeout(this.recordingTimeout);
|
|
1414
|
+
}
|
|
1415
|
+
this.recordingTimeout = setTimeout(() => this.stopRecording(), this.timeLimit * 1000);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
customElements.define('process-video', ProcessVideoComponent);
|
|
1420
|
+
|
|
1421
|
+
exports.BiometryOnboarding = BiometryOnboarding;
|
|
1422
|
+
exports.BiometrySDK = BiometrySDK;
|
|
1423
|
+
exports.ProcessVideoComponent = ProcessVideoComponent;
|