# BAF Web-component

## Overview

The web-component is a key part of the BAF system, the user interface used to collect data about the user and his device, perform biometric checks and send data to the server. It is integrated into the existing system on the interface side and serves as an additional level of biometric verification of the user.

## Installing web-components

The web-component is supplied as a TGZ **tdvc-face-onboarding** archive. It also requires the `tdvc` library, which is supplied separately as an archive.

1. Move the archives to the root folder of your project and add the following lines to your **package.json** in the dependencies section:

    ```javascript
    "@tdvc/face-onboarding": "file:tdvc-face-onboarding-{version}.tgz"
    ```

    The archive version may vary. An example of the final **package file.json**:

    ```javascript
    "dependencies": {
        "@tdvc/face-onboarding": "file:tdvc-face-onboarding-1.0.0.tgz"
    }
    ```

2. Call the `npm install' command, which will install the library in your project.

3. For the web-component to work correctly, you need to add a number of files to the directory where your project's static resources are stored, usually the **public** directory. After installing the package, move the **images** and **networks** folders from **/node_modules/@tdvc/face-onboarding/** and **frame_handler_worker.js** from **/node_modules/@tdvc/face-onboarding/dist** to the **public** directory.

## Importing and using a web-component

1.  Import the library and the styles for it.
2.  [Create the integration of the web-components on the server.](https://docs.3divi.ai/baf/user_guide/dashboard_web_components)
3.  Define the configuration of the web-component.
4.  Launch the project.

#### Example of initializing a component :

```javascript
import tdvc, {
    ComponentSettingsFromClient,
    TDVAthorizationOnboarding,
    TDVRegistrationOnboarding,
} from '@tdvc/face-onboarding';

let lib: TDVRegistrationOnboarding | TDVAthorizationOnboarding;

const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
};

function run() {
	if (window.location.pathname === '/registration') {
		lib = new tdvc.Register(config);
	} else {
		lib = new tdvc.Authorization(config);
	}
}

window.onload = async () => {
	run();
};

window.onbeforeunload = () => {
	lib?.destroy();
};
```

## How it works

The behavior of the web-component is determined by its configuration settings and can change accordingly. The complete algorithm looks like this:

1. The web-component is being initialized

    The web-component receives configuration from the front-end of the project, in which it is embedded and from the BAF server via its API. Once the configuration is received from the server, the configurations are combined into one.

    > If the same parameter is defined in both the client and server configurations, the value from the client configuration takes precedence.

    After merging the configurations, the web-component initializes the necessary services for operation, including the face detector, video recording modules, and other required components.

2. The web-component determines if the user exists in the BAF system and checks their current status

    The user is identified using a unique user ID, which can either be transmitted from an external system or defined directly within the web-component via a form where the user enters their information. Once identified, the user’s status is checked in the BAF system. If the status is valid, the user is allowed to proceed; otherwise, the web-component displays an error message indicating the invalid status and blocks further access.

    If the unique user ID is provided through the configuration, the form will be skipped, and the status check will be performed immediately.

3. The web-component gets access to the webcam of the user's device

    The web-component requests access to the user’s webcam. If a device is found and access is granted, it displays the video stream; otherwise, it shows an error message. If multiple devices are available, the user is given the option to select which one to use.

4. The web-component generate device fingerprint

    The web component collects data about the device and the environment to generate a unique fingerprint of the device. This fingerprint is used to increase the level of security when using BAF. The information is collected using the Browser API and includes device data, browser data, geolocation, which is collected in two ways, available Browser API, Internet connection data, and more.

5. "Motion Control" biometric check

    The web-component generates a series of commands for the user to perform in front of the camera. These commands include: turning the head to the left, turning the head to the right, raising the head, moving closer to the camera, and moving away from the camera.

    During this check, frames containing the user’s face are captured. These frames are then used for additional server-side checks, such as facial liveness detection, image quality assessment, and others.

6. Biometric verification of the user's face

    This is an auxiliary stage that is activated when the "Motion Control" biometric check is disabled.

    The web-component captures the user’s head position and extracts frames containing the user’s face, which are then used for additional server-side checks, such as facial liveness, image quality, and others.

7. Validation of the collected data and the results of biometric checks

    The web-component generates a fingerprint of the user's device, sends all the collected data to the server and receives the user's path verification in response.

## General settings

-   The `mountElement` field is used to specify the ID of the HTML element in which the web component will be embedded. When setting the value, make sure that the element with this identifier exists in the DOM, otherwise initialization will be interrupted and an error will be generated, which can be viewed in the browser terminal.

-   The `integrationId` field is used to interact with the server in order to receive settings and determine from which integration the data comes. The value passed must be a valid UUID4 string. This value must be taken from the BAF dashboard from the integration section.

-   The `baseUrl` field is used to specify the URL address of BAF API. If you pass the value "/", then requests will be sent to the host on which the web component is deployed. Value must be a valid URL address.

#### Example of config with general settings web-component

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
};
```

## Face detector settings

-   The `networksPath` field is used to determine the path to the resources that are needed to initialize the face detector. The default value is '/networks/'.

-   The `faceModelSettings` field is used to define face detector settings. Contains settings `modelEnabled`, `timeToStartRecord` и `angleСalculation`.

    -   The `modelEnabled` field is used to turn the face detector on or off. If the detector is turned off, all processes associated with detections will be turned off. For example, if detection is turned off, the Motion Control check will be skipped, instead of dynamically determining the position of the face, static will be used.

    -   The `timeToStartRecord` field is used when the detector is off to give the user time to take the required position in the frame before starting biometric checks. Specifies the time in milliseconds.

    -   The `angleСalculation` field is used to calculate the angles of rotation of the face and determine the rotation of the face. Contains setting `angles`.

        -   The `angles` field is used to define the boundary values for the face position. Contains settings `left`, `right` и `up`.

            -   The `left` field is used to determine at what rotation angle the face of user the component should assume that the head of user is turned to the left.

            -   The `right` field is used to determine at what rotation angle the face of user the component should assume that the head of user is turned to the right.

            -   The `up` field is used to determine at what rotation angle the face of user the component should assume that the head of user is lift up.

#### Example of config with face detector settings

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    networksPath: "/other_networks_location/",
    faceModelSettings: {
        modelEnabled: true,
        timeToStartRecord: 10_000,
        angleСalculation: {
            angles: {
                left: 25,
                right: 25,
                up: 25,
            }
        }
    },
};
```

## Applicant settings

-   The `applicantId` field is used to determine applicator status without having to enter applicator data. Must contain valid UUID4 string.\

-   The `applicantFields` field determines what data will need to be entered to determine the applicant ID and his status. If 'applicantId 'is explicitly specified in the configuration, then data entry will be ignored and the status will be determined by the passed UUID. May contain field: `firstName`, `lastName`, `phone`, `email` and `referenceId`.

Each field contains `enabled` and `primary` settings. At least one of these fields must be enabled and only one field must be primary.

-   The `enabled` field is used to enable or disable the display of a field in the applicant's data entry form.

-   The `primary` field is used to determine the key field for which the applicant will be searched in the database.

#### Example applicantId setting

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    applicantId: '08ee8a87-ff47-4346-8568-ad7c3de62d35',
};
```

#### Example applicantFields setting

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';
const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    applicantFields: {
        email: {
            enabled: true,
            primary: true,
        },
        phone: {
            enabled: true,
        },
        firstName: {
            enabled: false,
        },
        lastName: {
            enabled: false,
        },
        referenceId: {
            enabled: false,
        },
    },
};
```

## Camera settings

-   The `cameraSettings` field is used to define settings of camera. Contains settings `cameraResolution`, `cameraId`, `autoSubmit` и `permissionInBrowserTimeout`.

    -   The `cameraResolution` field is used to adjust the camera resolution. The higher the resolution, the more resources will be used when processing frames. The higher the resolution, the more device resources will be used when processing frames. Takes the values "fhd" for Full HD, "hd" for HD image, and "sd" for SD image.

    -   The `cameraId` field is used to specify the ID of a specific camera to be used. It may be useful if the camera has multiple operating modes and you need to use a specific mode, or if the device has multiple cameras and you need to use a specific one.

    -   The `autoSubmit` field is used to skip camera selection if the device has more than one camera. Note that if true, the web component will use the first available camera, and the order of the cameras may change.

    -   The `permissionInBrowserTimeout` field is used to determine how long to wait for confirmation of camera access. The default is 30 000 milliseconds. When the set time expires, if the user does not confirm access to the camera, an 'Not allowed access to camera' error will be generated. If set to 0, there will be no time limit on confirmations

#### Example config with cameraSettings

```javascript
import tdvc, { ComponentSettingsFromClient } from '@tdvc/face-onboarding';
const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    cameraSettings: {
        autoSubmit: true,
        cameraResolution: tdvc.CameraResolutions.HD,
        permissionInBrowserTimeout:10_000
    },
};
```

#### Example config with cameraId setting

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';
const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    cameraSettings: {
        cameraId: '3eef5faa7a2f81c50e5fc30c2362bc4be0d208c86a8cdb0642f2194cc25492ac',
    },
};
```

## Device fingerprint settings

-   The `fingerprintWaitTime` field is used to determine the maximum waiting time when collecting device data. Specifies the time in milliseconds. Keep in mind that when waiting times decrease, the accuracy of the data may be lower.

#### Example of config with device fingerprint generation settings

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
	mountElement: 'app',
	baseUrl: '/',
	integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
    fingerprintWaitTime: 30_000,
};
```

## Motion Control Settings

-   The `motionControl` field is used to define the settings of Motion Control. Contain setting `enabled`, `attemptsCount`, `faceBorder`, `imagesHints`, `description`.

    -   The `enabled` field is used to enable or disable Motion Control. By default is true.

    -   The `attemptsCount` field is used to set the allowed number of Motion Control attempts. By default is 3.

    -   The `faceBorder` field contains settings for determining the position of the person during the check. The face frame can be calculated based on face detection (dynamic mode) or based on video stream resolution (static mode). Contains settings `allowableAccuracyError`, `faceWidthCoefficients` and `autodetected`.

        -   The `allowableAccuracyError` field is used to for calculating the size error between the detected face and the face border. Contains settings `x` and `y`. The values are in percent from 1 to 100 and the recommended ratio is 2/3. By default `x` is 20, `y` is 30,

        -   The `faceWidthCoefficients` field is used to calculate the size of the face border based on the width of the maximum camera resolution. That is, the width of the face border is calculated as the maximum camera width _ factor for resolution/100, and the height of the frame is calculated as the width of the width of the face border _ 3/2 to maintain a ratio of 2/3. Contains settings `fullHd`, `hd` and `sd` which contain the factor for resolution for a specific resolution as a percentage. By default `fullHd` is 20, `hd` is 24, `sd` is 33. Used only for static mode.

        -   The `autodetected` field is used to customize the process of determining the initial position of a face using face detection. Contain settings `enabled`, `frameCheckLimit`, `availableDeviation`, `framePadding` and `faceSize`.

            -   The `enabled` field is used to switch the face frame mode. If true, then the frame will be calculated based on face detection, otherwise based on the camera resolution. By default is true.

            -   The `frameCheckLimit` field is used to set the number of frames on which the face should be and it should be in a certain position, taking into account the error, in order to fix the initial position of the face before biometric inspections. Please note that on different devices, the verification time may take different numbers depending on the power of the device. By default is 60.

            -   The `availableDeviation` field is used to determine the allowable error that is used when calculating the dynamic face frame, with which the position of the current detected face can deviate from the estimated initial position of the face. By default is 20.

            -   The `framePadding` fields is used to determine the distance from frame boundaries at which face detection will reset the starting position determination process. It is necessary to exclude situations when the starting position is at the edge of the frame or outside the frame.. Contains settins `horizontal` and `vertical`. By default `horizontal` is 10, `vertical` is 10.

            -   The `faceSize` fields is used to determine the minimum and maximum allowable dimensions (in pixel) of the detected face. It is necessary to control the distance of the face in relation to the camera and avoid a situation where the face is too small or too large. Contains settings `min` and `max` each containing `width` and `height` settings. By default `min` is {width: 120,height: 140}, `max` is { width: 360, height: 520};

    -   The `imagesHints` field is used to configure GIF hints for Motion Control actions. Contains settings `enabled` and `resourcesPath`.

        -   The `enabled` field is used to enable or disable GIF hints for Motion Control actions. By default is false.

        -   The `resourcesPath` field is used to determine the path to the folder containing the GIF image. The folder must contain images named "left", "right", "up", "center", "farther" and "closer" and with the extension gif. The default value is "/images/motion_control_gif_hint/".

    -   The `description` field is used to to adjust the block display for the Motion Control inspection description. Contain settings `enabled` and `autoSubmit`.

        -   The `enabled` field is used to enable or disable Motion Control description. By default is true.

        -   The `autoSubmit` field is used to configure it to automatically move on after a set period of time. Contain settings `enabled` and `timer`.

            -   The `enabled` field is used to enable or disable move on after a set period of time. By default is false.

            -   The `timer` field is used to set wait timein milliseconds after which the transition will be executed, if the user does not make the transition himself. The value must be greater than 0. By default is 30 000.

#### Example config with enabled and attemptsCount settings for Motion Control

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        enabled: true,
        attemptsCount: 3,
    },
}
```

#### Example config with faceBorder setting for static face border

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        faceBorder: {
            faceWidthCoefficients: {
                fullHd: 20,
                hd: 24,
                sd: 33,
            },
            allowableAccuracyError: {
                x: 20,
                y: 30,
            },
            autodetected: {
                enabled: false,
            },
        },
    },
}
```

#### Example config with faceBorder setting for dynamic face border

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        faceBorder: {
            autodetected: {
                enabled: true,
                frameCheckLimit: 60,
                availableDeviation: 20,
                framePadding: {
                    horizontal: 10,
                    vertical: 10,
                },
                faceSize: {
                    min: {
                        width: 120,
                        height: 140,
                    },
                    max: {
                        width: 360,
                        height: 520,
                    },
                },
            },
        },
    },
}
```

#### Example config with imageHints settings

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        imagesHints: {
            enabled: true,
            resourcesPath: "/path_to_images"
        }
    },
}
```

#### Example config with description settings

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        description: {
            enabled: true,
            autoSubmit: {
                enabled: true,
                timer: 30_000,
            },
        },
    },
}
```

#### Example config with timer settings

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    motionControl: {
        description: {
            enabled: true,
            autoSubmit: {
                enabled: true,
                timer: 30_000,
            },
        },
    },
}
```

## Biometric verification of the user's face settings

The `faceBestshotSettings` field is used to define the settings of biometric verification of the user's face. Contain setting `faceBorder`.

The `faceBorder` field contains settings for determining the position of the person during the check. The face frame can be calculated based on face detection (dynamic mode) or based on video stream resolution (static mode). Contains settings `allowableAccuracyError`, `faceWidthCoefficients` and `autodetected`.

The `allowableAccuracyError` field is used to for calculating the size error between the detected face and the face border. Contains settings `x` and `y`. The values are in percent from 1 to 100 and the recommended ratio is 2/3. By default `x` is 20, `y` is 30,

The `faceWidthCoefficients` field is used to calculate the size of the face border based on the width of the maximum camera resolution. That is, the width of the face border is calculated as the maximum camera width _ factor for resolution/100, and the height of the frame is calculated as the width of the width of the face border _ 3/2 to maintain a ratio of 2/3. Contains settings `fullHd`, `hd` and `sd` which contain the factor for resolution for a specific resolution as a percentage. By default `fullHd` is 20, `hd` is 24, `sd` is 33. Used only for static mode.

The `autodetected` field is used to customize the process of determining the initial position of a face using face detection. Contain settings `enabled`, `frameCheckLimit`, `availableDeviation`, `framePadding` and `faceSize`.

The `enabled` field is used to switch the face frame mode. If true, then the frame will be calculated based on face detection, otherwise based on the camera resolution. By default is true.

The `frameCheckLimit` field is used to set the number of frames on which the face should be and it should be in a certain position, taking into account the error, in order to fix the initial position of the face before biometric inspections. Please note that on different devices, the verification time may take different numbers depending on the power of the device. By default is 60.

The `availableDeviation` field is used to determine the allowable error that is used when calculating the dynamic face frame, with which the position of the current detected face can deviate from the estimated initial position of the face. By default is 20.

The `framePadding` fields is used to determine the distance from frame boundaries at which face detection will reset the starting position determination process. It is necessary to exclude situations when the starting position is at the edge of the frame or outside the frame.. Contains settins `horizontal` and `vertical`. By default `horizontal` is 10, `vertical` is 10.

The `faceSize` fields is used to determine the minimum and maximum allowable dimensions (in pixel) of the detected face. It is necessary to control the distance of the face in relation to the camera and avoid a situation where the face is too small or too large. Contains settings `min` and `max` each containing `width` and `height` settings. By default `min` is {width: 120,height: 140}, `max` is { width: 360, height: 520};

#### Example config with faceBorder setting for static face border

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    faceBestshotSettings: {
        faceBorder: {
            faceWidthCoefficients: {
                fullHd: 20,
                hd: 24,
                sd: 33,
            },
            allowableAccuracyError: {
                x: 20,
                y: 30,
            },
            autodetected: {
                enabled: false,
            },
        },
    },
}
```

#### Example config with faceBorder setting for dynamic face border

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    faceBestshotSettings: {
        faceBorder: {
            autodetected: {
                enabled: true,
                frameCheckLimit: 60,
                availableDeviation: 20,
                framePadding: {
                    horizontal: 10,
                    vertical: 10,
                },
                faceSize: {
                    min: {
                        width: 120,
                        height: 140,
                    },
                    max: {
                        width: 360,
                        height: 520,
                    },
                },
            },
        },
    },
}
```

## Localization

By default, the web component supports two localizations: Russian (ru) and English (en), and the default interface language is English. The localization of the component can be changed via configuration at the time of initialization of the web component. To do this, specify the `language` and `locales` fields in the config.

The `language` field is used to determine the interface language, takes a **string** type value. If no value is specified or there are no locales for the specified value, then the value “en" will be used.

The `locales` field is used object for changing default UI texts. Keep in mind that the component will only use the passed object, so it must specify all the necessary locales for all the required languages. For a more accurate understanding, read the examples below.

#### Actual locale object for English localization

```javascript
const en = {
    PreparingEnvironment: 'Preparing environment',
    MessageCode: 'Message code: ',
    SomeError: 'An error occurred, please try again later',

    Mode: {
        Authorization: 'Authorization',
        Registration: 'Registration',
    },

    ErrorScreen: {
        TryAgainButton: 'Try again',
    },

    Stages: {
        Initialization: {
            IdentifyApplicantStatus: {
                FormFields: {
                    Labels: {
                        FirstName: 'First name',
                        LastName: 'Last name',
                        Phone: 'Phone',
                        Email: 'Email',
                        ReferenceId: 'ReferenceId',
                    },
                    Errors: {
                        InvalidEmail: 'Invalid email format',
                        MaxLengthField: 'Maximum field length is 150 characters',
                        IsRequired: 'This field is required',
                        WrongPhone: 'Wrong phone number',
                    },
                },
                SubmitButton: {
                    Authorization: 'Continue',
                    Registration: 'Continue',
                },
            },

            SelectCamera: {
                TextHints: {
                    CheckingWebcamOperation: 'Check the webcam for proper operation and image quality',
                },
                Preloader: {
                    RequestingAccessToCamera: 'Requesting access to the camera',
                },
                ContinueButton: 'Continue',
                BackButton: 'Back',
            },

            Description: {
                MotionControl: {
                    Heading: 'Checking Motion Control',
                    Text: 'To pass the inspection successfully, you need to perform several actions. First, you need to determine the initial position of the face. To do this, fix the face in a position that is convenient for you and so that the mask is displayed for a few seconds while the counter is filling up under the player. After determining the starting position, you need to perform a number of actions in the order generated by the system. Actions are commands: turn your head to the right/left, raise your head, approach/move away.  The action is considered completed when the frame around the face changes its color.',
                },
            },

            Errors: {
                ComponentDisabledError: 'The component is disabled',
                ApplicantIdWithApplicationFieldsError:
                    'Configuration error Only one of the two configuration fields must be passed: applicationFields or applicantId',
                NoEnabledApplicationFieldsError:
                    'Configuration error. At least one enabled field in applicationFields is required',
                NoPrimaryApplicationFieldsError:
                    'Configuration error. One primary field is required in applicationFields',
                NoPrimaryEnabledApplicationFieldsError:
                    'Configuration error. The primary field in applicationFields must be enabled',
                SeveralPrimaryEnabledApplicationFieldsError:
                    'Configuration error. Only one primary field is required in applicationFields',
                InvalidMotionControlAttemptCountError:
                    'Configuration error. The motionControl.attemptsCount field must be greater than zero',
                EnabledMotionControlWithoutFaceModelError:
                    'Configuration error. If faceModelSettings.modelEnabled is false, then motionControl.enabled must also be false',
                TimeToStartRecordLessThenOneSecondError:
                    'Configuration error. The timeToStartRecord value must be at least 1000ms',
                NoIceCandidatesError:
                    'Configuration error. At least one ICE candidate must be specified in the component settings on the server',
                IceCandidateTimeoutLessThenOneSecondError:
                    'Configuration error. The checkIceCandidateTimeout value must be at least 1000ms',
                FailedFetchOfConfigurationError: 'Failed to retrieve component settings',
                NoComponentIdError: 'Integration ID not specified in the component configuration',
                NotValidComponentIdError: 'The integration ID must be in the UUID4 format',
                NoBaseUrlError: 'Base URL of server not specified in the component configuration',
                NoExistIntegrationError:
                    'Integration does not exist on the server. Check the integration ID and the server address',
                PrepareEnvironmentForBiometricInspectionTimeoutError:
                    'The waiting time for the initialization of biometric verification services has been exceeded',
                NoRequiredConfigurationFieldsError:
                    'The settings received from the server do not contain the required data',
                InitializationProcessingVideoStremWorkerError: 'Error initializing the video frame processing service',
                InitializationFaceDetectionServiceError: 'Error initializing the face detection service',
                ApplicantNotFoundError: 'Applicant not found',
                ApplicantAlreadyExistError: 'This applicant already exists',
                ApplicantBlockedError: 'The applicant is blocked',
                ApplicantUnconfirmedError: 'The applicant is unconfirmed',
                ApplicantNotRegisterError: 'The application is not registered',
                NotSupportedMediaDevicesError: 'The browser does not support the Media Devices API',
                AbortAccessToCameraError: 'The attempt to access the camera was aborted',
                DocumentIsNotFullyActiveError: 'HTML document is not fully active',
                NotAllowedAccessToCameraError: 'Not allowed access to camera',
                TimeoutAccessToCameraError: 'The waiting time for permission to access the camera has been exceeded',
                NotFoundCameraError: 'No camera was found',
                NotReadableCameraError: 'The camera is unavailable because it is already in use by another process',
                OverconstrainedCameraError: 'No camera satisfying the constraints of the system is found',
                CameraSecurityError: 'The HTML document does not meet the minimum security requirements for camera use',
                NoVideoTrackError: 'There is no data about the video stream from the camera',
                NoCameraCapabilitiesInfoError:
                    'Information about possible camera settings does not contain the required parameters',
                MediaStreamIsUndefinedError: 'There is no video stream data',
                WebComponentError: 'An error occurred, please try again later',
                NoActiveIceCandidatesError: 'No active ICE candidates detected',
                PoorConnectionQualityError:
                    'Poor connection quality, the connection to the server does not meet the necessary requirements',
                ConnectionEstablishmentError: 'An error occurred when establishing the connection',
            },
        },

        BiomertricalChecks: {
            IdentifyFacePosition: {
                TextHints: {
                    MoveFaceOnCenter:
                        'Please position yourself so that your face is in the center of the circle on the screen',
                    IDontSeeYou: 'You are not visible',
                    FaceOutsideFrame:
                        'Please position yourself so that your face is in the center of the on the screen',
                    LookAtCamera: 'Please, turn your face to the camera',
                    LittleFace: 'Move closer to the camera',
                    BigFace: 'Move further away from the camera',
                    DontMove: "Please don't move",
                    CheckPosition: 'Checking position',
                    TimerBeforeRun: 'Before recording starts',
                },
            },

            MotionControl: {
                TextHints: {
                    AttemptFailed: 'Motion Control attempt failed',
                    SendingDataToServer: 'Sending data to the server',
                    Command: {
                        TurnLeft: 'Turn left',
                        TurnRight: 'Turn right',
                        TurnUp: 'Lift your chin up while continuing to look at the screen',
                        LookAtCenter: 'Look into the camera',
                        Closer: 'Move closer to the camera',
                        Farther: 'Move further away from the camera',
                        Normal: 'Return to the original position',
                    },
                },
            },
            Errors: {
                SlowEnternet: 'Your internet connection is slow. Image quality assessment may take long',
                InCorrectCamera: 'The selected camera is not available or does not meet the minimum requirements',
                NoCamera: 'No cameras available',
                NoPermission:
                    'Permission to access the camera is not obtained. For further work, allow access to the camera in your browser settings',
                MoreFaces: 'Many faces in the frame',
                SafariError: 'Unfortunately, your browser is temporarily not supported at the moment',
                ServerError: 'The server is temporarily unavailable',
                ServerConfigError: 'Error on the server',
                NotSupportedApiError: 'Your browser does not support the required function',
                TransportError: 'An unexpected error occurred while running the check',
                NotSupportedVideoFormatError: "This browser haven't supported needed video mime type",
                ExceedMaxMessageSizeError: 'The maximum message size has been exceeded',
                VideoStreamResolutionIsUndefinedError: 'The resolution of the video stream is not defined',
                InvalidVideoStreamResolutionValueError:
                    'The resolution of the video stream contains invalid values, the width or height of the video stream cannot be equal to 0',
                InvalidVideoPreviewResolutionValueError:
                    'The resolution of the video preview contains invalid values, the width or height of the video preview cannot be equal to 0',
                InvalidFrameDataForDetectionError: 'Incorrect frame data for face detection',
                CaptureFaceBestshotTimeoutError: 'Frame collection waiting time exceeded',
                InvalidMotionControlPatternError: 'Invalid Motion Control pattern',
                NoSupportedVideoCodecError: 'The video codec supported by the system is not detected',
                WaitResponseWorkerTimeoutError: 'The waiting time for a response from the worker has expired',
                BrowserNotSupportedWorkerApi: "This browser doesn't support Worker API",
                DeepfakeValidationError: 'Deepfake check failed',
                CameraFpsNotDefinedError: 'The frame rate of the video stream is not defined',
                TransmissionTimeoutError: 'The waiting time for a response from the server has been exceeded',
                InvalidFacesAmountOnFrameError: 'No face or too many faces found',
                InvalidMessageFormatError: 'Invalid message format',
                UndefinedLocalizedMessagesError: 'There are no localized messages for the selected language',
                InvalidVideoDataError: 'Invalid video data',
                WebComponentError: 'An error occurred, please try again later',
                NotSupportedVideoEncoderApiError: "The browser doesn't support Video Encoder API",
                120004: 'FPS too low. Please try again',
                120005: 'Error on the server. Please try again',
                120006: 'Error on the server. Please try again',
                120007: 'Error on the server. Please try again',
                120008: 'Error on the server. Please try again',
                120009: 'Please check the quality of your internet connection and try again',
                120044: 'No reference frames found',
                120052: 'Error on the server. Please try again',
                120053: 'Error on the server. Please try again',
                120054: 'Error on the server. Please try again',
                120055: 'Error while sending a message via DataChannel',
                120057: 'Error in obtaining Motion Control pattern',
                120060: 'Error on the server. Please try again',
                120061: 'No face or multiple faces detected while taking reference image. Please try again',
                180001: 'Invalid websocket message format',
                180003: 'Requesting a video recording of an unsupported type',
                180004: 'There was an error on the server while recording video',
                180005: 'Video processing time has exceeded the limit',
                180006: 'Error in operation of WebSocket connection',
                190002: 'The connection to the server was closed due to an internal error or exceeding the waiting time.',
                190003: 'The size of data sent to the server exceeds the allowed size',
                190004: 'The check failed due to facial movement',
                1100001: 'The device does not support WebGL technology',
                1100004: 'The device does not meet the requirements necessary to start the detector',
                1100005: 'No face detector in the system',
            },
        },

        ValidateFlowResult: {
            SendingDataToServer: 'Sending data to the server',
            Success: {
                Register: 'Registration completed successfully',
                Authorize: 'Authorization completed successfully',
            },
            Errors: {
                AntispoofingValidationError: 'Liveness check failed',
                RegistrationMatchingFailedError: 'An applicant with such biometrics already exists',
                AuthorizationMatchingFailedError: 'No applicant with this biometric was found',
                LowImageQualityError: 'Poor image quality',
                LivenessReflectionError: 'Liveness Reflection check failed',
                NoFacesFound: 'No face found in the image',
                FacesDontBelongApplicant: 'Face matching check failed',
                MoreFaces: 'Many faces in the frame',
                ServerError: 'The server is temporarily unavailable',
                ServerConfigError: 'Error on the server',
                MCCrossMatchError: 'Facial substitution detected',
                LRCrossMatchError: 'Facial substitution detected',
                ApplicantInBlackListError: 'The applicant is on the black list',
                ApplicantRiskError: 'During validation, the risk triggered',
                "Face profile wasn't saved": "Face profile wasn't saved",
                'Face profiles not found': 'Face profiles not found',
                'Face authorization was failed': 'Face authorization was failed',
                'A face was missing or disappeared from the frame when recording liveness reflection video':
                    'A face was missing or disappeared from the frame when recording liveness reflection video',
                'Invalid endeavor info': 'Invalid endeavor info',
                'Endeavor liveness reflection info obtain error': 'Error receiving liveness reflection information',
                'Endeavor external link not equal to applicant': 'Applicant ID consistency error',
                'Endeavor is not calculated': 'Error recording video liveness reflection',
                'Endeavor liveness reflection confidence info is null': 'Error calculating liveness reflection',
                'Endeavor liveness reflection confidence value is null': 'Error calculating liveness reflection',
                'Failed to obtain template from liveness reflection reference image':
                    'No face in the frame when recording video reflection',
                'Failed to obtain template from motion control reference image':
                    'No face in the frame when recording motion control video',
                'Endeavor id is null when required': 'Endeavor id is null when required',
                'No faces found on image': 'No faces found on image',
                'Multiple faces found on image': 'Multiple faces found on image',
                'Multiple faces or strong face movement spotted when recording liveness reflection video':
                    'Multiple faces or strong face movement spotted when recording liveness reflection video',
                'Error when perform cross matching': 'Error when perform cross matching',
                'Error on the server': 'Error on the server',
                'Liveness reflection video is not captured': 'Liveness reflection video is not captured',
                'Liveness reflection video reference template is not captured':
                    'Liveness reflection video reference template is not captured',
                'Motion control video is not captured': 'Motion control video is not captured',
                'Motion control video reference template is not captured':
                    'Motion control video reference template is not captured',
                NotSupportedApiError: 'Your browser does not support the required function',
                TransportError: 'An unexpected error occurred while running the check',
                NotSupportedVideoFormatError: "This browser haven't supported needed video mime type",
                ExceedMaxMessageSizeError: 'The maximum message size has been exceeded',
                VideoStreamResolutionIsUndefinedError: 'The resolution of the video stream is not defined',
                InvalidVideoStreamResolutionValueError:
                    'The resolution of the video stream contains invalid values, the width or height of the video stream cannot be equal to 0',
                InvalidVideoPreviewResolutionValueError:
                    'The resolution of the video preview contains invalid values, the width or height of the video preview cannot be equal to 0',
                InvalidFrameDataForDetectionError: 'Incorrect frame data for face detection',
                CaptureFaceBestshotTimeoutError: 'Frame collection waiting time exceeded',
                InvalidMotionControlPatternError: 'Invalid Motion Control pattern',
                NoSupportedVideoCodecError: 'The video codec supported by the system is not detected',
                WaitResponseWorkerTimeoutError: 'The waiting time for a response from the worker has expired',
                BrowserNotSupportedWorkerApi: "This browser doesn't support Worker API",
                DeepfakeValidationError: 'Deepfake check failed',
                CameraFpsNotDefinedError: 'The frame rate of the video stream is not defined',
                TransmissionTimeoutError: 'The waiting time for a response from the server has been exceeded',
                InvalidFacesAmountOnFrameError: 'No face or too many faces found',
                InvalidMessageFormatError: 'Invalid message format',
                UndefinedLocalizedMessagesError: 'There are no localized messages for the selected language',
                ValidationTimeHasExpiredError: 'Validation time has expired',
                ApplicantBlockedError: 'The applicant is blocked',
                WebComponentError: 'An error occurred, please try again later',
                InvalidEndeavorInfoError: 'The attempt contains invalid data',
                120004: 'FPS too low. Please try again',
                120005: 'Error on the server. Please try again',
                120006: 'Error on the server. Please try again',
                120007: 'Error on the server. Please try again',
                120008: 'Error on the server. Please try again',
                120009: 'Please check the quality of your internet connection and try again',
                120029: 'Error on the server. Please try again',
                120044: 'No reference frames found',
                120052: 'Error on the server. Please try again',
                120053: 'Error on the server. Please try again',
                120054: 'Error on the server. Please try again',
                120055: 'Error while sending a message via DataChannel',
                120057: 'Error in obtaining Motion Control pattern',
                120060: 'Error on the server. Please try again',
                120061: 'No face or multiple faces detected while taking reference image. Please try again',
                170001: 'No active ICE candidates detected',
                180001: 'Invalid websocket message format',
                180003: 'Requesting a video recording of an unsupported type',
                180004: 'There was an error on the server while recording video',
                180005: 'Video processing time has exceeded the limit',
                180006: 'Error in operation of WebSocket connection',
                190002: 'The connection to the server was closed due to an internal error or exceeding the waiting time.',
                190003: 'The size of data sent to the server exceeds the allowed size',
                1300001:
                    'Poor connection quality, the connection to the server does not meet the necessary requirements',
            },
        },
    },
};
```

#### An example of changing the English localization to TypeScript:

```javascript
import tdvc, { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

// Сopy the default locales so that when certain locales are changed, the rest remain available in their original form
const locales = structuredClone(tdvc.DefaultLocales);

// Updating the desired locales
locales.en.Mode.Authorization = 'Sign In';
locales.en.Mode.Registration = 'Sign Up';
locales.en.Stages.Initialization.IdentifyApplicantStatus.FormFields.Labels.FirstName = 'Name';

const config: ComponentSettingsFromClient = {
mountElement: 'app',
baseUrl: '/',
integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
locales,
};
```

#### An example of adding a new localization to TypeScript:

To add a new localization, prepare a JavaScript object that will contain all the same fields as the object with the English localization and define the text in the required language for each message identifier. The localization object that can be used as an example can be found in the README.md file in the delivery archive.

```javascript
import tdvc, { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

// Сopy the default locales so that when certain locales are changed, the rest remain available in their original form
const locales = structuredClone(tdvc.DefaultLocales);

// Defining an object with locales for the desired language, the structure of which must completely match the default locales.
const kkLocales = {
PreparingEnvironment: 'Ортаны дайындау',
MessageCode: 'Хабарлама коды: ',
SomeError: 'Қате орын алды, кейінірек қайта әрекет етіңіз',
Mode: {
    Authorization: 'Авторизациялау',
    Registration: 'Тіркелу',
},
// ...
};

// We add a new object to the general set of locales, the key of which is the new localization language.
locales['kk'] = kkLocales;

const config: ComponentSettingsFromClient = {
mountElement: 'app',
baseUrl: '/',
integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
language: 'kk',
locales,
};
```

## Interaction between the web-component and an external system

Interactions between the web component and external systems are handled using callback functions (callbacks). The system determines its event-processing logic based on the data provided by these functions or on the fact that the callback was triggered. Several such functions are defined in the component’s configuration:

1. `onMounted: (() => void)` — a function called when the web component is fully initialized.

2. `onError: ((message: string, code: string) => void)` — a function called if an error occurred while traversing the user path.

3. `onUpdate: (() => void)` — a function called if, after an error occurs, the user wants to try again to traverse the user's path.

4. `onIdentifyApplicantStatus: (applicant: {applicantId: string, status: number}) => void` — a function called after receiving the applicant status from the server.

5. `onBack: (() => void)` — a function called if the user clicks on the "Back" button while selecting the camera, and thereby stops going through the user's path.

6. `onMotion: ((type: 'left' | 'right' | 'up' | 'closer' | 'farther' | 'return', result: boolean | undefined) => void)` — the function called during the biometric check "Motion Control". If the result value is undefined, then the verification has just begun, if true - successfully passed, if false - failed.

7. `onGetReferenceImages: (referenceImage: string) => void`; — the function called after receiving the keyframe. referenceImage is an image in the base64 string format.

8. `onStartValidation: (() => void)` — the function called before the validation of the results begins.

9. `onValidate: ((data: any) => void)` — the function called after receiving the result of data validation from the server.

#### An example of using callbacks to TypeScript:

```javascript
import { ComponentSettingsFromClient } from '@tdvc/face-onboarding';

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    callbacks: {
        onMounted: () => {
            console.log('Component is successfully initilized');
        },
        onError: (message: string, code: string) => {
            console.log('On error callback', message, code);
        },
        onUpdate: () => {
            console.log('On update callback');
        },
        onIdentifyApplicantStatus: (applicant?: { applicantId: string; status: number }) => {
            console.log('On identify applicant status callback', applicant?.applicantId, applicant?.status);
        },
        onBack: () => {
            console.log('On Back callback');
        },
        onMotion: (
            type: 'left' | 'right' | 'up' | 'closer' | 'farther' | 'return',
            currentAttemptNumber: number,
            result?: boolean
        ) => {
            console.log('On motion callback', type, currentAttemptNumber, result);
        },
        onGetReferenceImages: (referenceImage: string) => {
            console.log('Reference image received');
            console.log(referenceImage);
        }
        onStartValidation: () => {
            console.log('On start validation callback');
        },
        onValidate: async (data) => {
            console.log('On validation callback', data);
        },
    },
}
```

## Interface styling

### The main method of styling

The styling of the web component is done through CSS. Interface elements contain identifiers and CSS classes through which you can customize their appearance. The identifiers and CSS classes used for customization can be found in the `@tdvc/face-onboarding/dist/css/style.css` file.

### Stylization through JavaScript/TypeScript

For customising complex UI elements, such as face masks based on key points, Motion Control action prompts, and face frames, which can have different shapes, styles, and display logic, a single CSS is not sufficient. Therefore, for more flexible styling, we provide the ability to override the implementation of these elements.

All of the elements below use base class as a basis for further implementation. It consists of a canvas element, methods for configuring styles and canvas resolution, and lifecycle methods such as rendering, removal from the DOM, and complete clearing via destroy.

#### Base canvas class

```typescript
export type Options = Partial<{
    strokeStyle: string;
    fillStyle: string;
    lineWidth: number;
}>;

export const DEFAULT_CANVAS_SETTINGS = {
    strokeStyle: '#000000',
    fillStyle: '#000000',
    lineWidth: 1,
};

export default abstract class Canvas {
    protected _root: HTMLCanvasElement;
    protected _context: CanvasRenderingContext2D;
    protected _options: Options;
    protected _initialOptions: Options;

    constructor(id: string, options?: Options) {
        this._root = document.createElement('canvas');
        this._root.classList.add('tdvc-canvas');
        this._root.classList.add(id);

        const context = this.root.getContext('2d');
        if (!context) {
            const elementClasses = this._root.classList
                .keys()
                .reduce((prev, cur) => (prev === '' ? prev + cur : prev + ' ' + cur), '');
            throw new WebComponentError({ message: `2D context for ${elementClasses} is null` });
        }

        this._context = context;
        this._initialOptions = options ?? DEFAULT_CANVAS_SETTINGS;
        this.setContextOption({ ...this._initialOptions });
        this.applyContextOptions();
    }

    get root() {
        return this._root as Readonly<HTMLCanvasElement>;
    }

    get options() {
        return this._options;
    }

    get initialOptions() {
        return this._initialOptions;
    }

    setContextOption(options: Options) {
        this._options = options;
    }

    applyContextOptions() {
        if (this._options.strokeStyle) this._context.strokeStyle = this._options.strokeStyle;
        if (this._options.lineWidth) this._context.lineWidth = this._options.lineWidth;
        if (this._options.fillStyle) this._context.fillStyle = this._options.fillStyle;
    }

    setResolution(width: number, height: number) {
        this._root.width = width;
        this._root.height = height;
        this.applyContextOptions();
    }

    clear() {
        const { width, height } = this._context.canvas;
        this._context.clearRect(0, 0, width, height);
    }

    removeFromDom() {
        this._root.remove();
    }

    destroy() {
        if (this._root && this._root.parentNode) this._root.remove();
        this._context = null!;
        this._root = null!;
    }
}
```

In your implementations, you can use both the base class and its derivatives.

### Face mask by keypoints

#### Base implementation

```javascript
export type FaceKeypointsMaskOptions = Options;

export const DEFAULT_FACE_KEYPOINTS_MASK_OPTIONS: FaceKeypointsMaskOptions = {
    strokeStyle: '#32EEDB',
    fillStyle: '#32EEDB',
    lineWidth: 0.5,
};

export default class FaceKeypointsMask extends Canvas {
    protected _isRendering = false;

    constructor(options?: FaceKeypointsMaskOptions) {
        super('tdvc-face-keypoints-mask', options ?? DEFAULT_FACE_KEYPOINTS_MASK_OPTIONS);
    }

    get isRendering() {
        return this._isRendering;
    }

    draw(points: Point[]) {
        if (this._isRendering || points.length !== 478) return;

        this._isRendering = true;

        this._context.beginPath();

        for (let i = 0; i < TRIANGULATION.length; i += 3) {
            const a = points[TRIANGULATION[i]];
            const b = points[TRIANGULATION[i + 1]];
            const c = points[TRIANGULATION[i + 2]];

            this._context.moveTo(a.x, a.y);
            this._context.lineTo(b.x, b.y);
            this._context.lineTo(c.x, c.y);
        }

        this._context.stroke();
        this._isRendering = false;
    }
}
```

#### Example custom implementation

```javascript
import tdvc, { ComponentSettingsFromClient, Point } from '@tdvc/face-onboarding';

// Hiding the mask so as not to waste resources on display
class NoMask extends tdvc.UiKit.FaceKeypointsMask {
    draw(points: Point[]): void {}
}

// Displaying only points instead of triangles
class OnlyPointsFaceKeypointMask extends tdvc.UiKit.FaceKeypointsMask {
    constructor() {
        super({
            ...tdvc.UiKit.DEFAULT_FACE_KEYPOINTS_MASK_OPTIONS,
            // Color change
            fillStyle: 'red',
        });
    }

    draw(points: Point[]): void {
        if (this._isRendering || points.length !== 478) return;

        this._isRendering = true;

        for (const point of points) {
            this._context.beginPath();
            this._context.arc(point.x, point.y, 1, 0, 2 * Math.PI);
            this._context.fill();
        }

        this._isRendering = false;
    }
}

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    uiKit: {
        FaceKeypointsMask: OnlyPointsFaceKeypointMask,
    },
};
```

### Face border

#### Base implementations

```javascript
export type FaceBorderOptions = Options;

export const DEFAULT_FACE_BORDER_OPTIONS: FaceBorderOptions = {
    strokeStyle: '#ffffff',
    fillStyle: 'rgba(255, 255, 255, 0.5)',
    lineWidth: 4,
};

export default abstract class FaceBorder extends Canvas {
    constructor(options?: FaceBorderOptions) {
        super('tdvc-face-position-circle', options ?? DEFAULT_FACE_BORDER_OPTIONS);
    }

    public draw(point: Point, resolution: Resolution) {
        this._drawOverlay();
        this._clearFaceArea(point, resolution);
        this._drawBorder(point, resolution);
    }

    protected _drawOverlay() {
        this._context.globalCompositeOperation = 'overlay';
        this._context.fillRect(0, 0, this._context.canvas.width, this._context.canvas.height);
    }

    protected _clearFaceArea(point: Point, resolution: Resolution) {
        this._context.globalCompositeOperation = 'destination-out';
        this._context.fillStyle = 'rgba(0,0,0,1.0)';
        this._baseFigure(point, resolution, (this._options?.lineWidth ?? 4) / 2);
        this._context.fill();
    }

    protected _drawBorder(point: Point, resolution: Resolution) {
        this._context.beginPath();
        this._context.globalCompositeOperation = 'overlay';
        this._baseFigure(point, resolution, 0);
        this._context.stroke();
    }

    protected abstract _baseFigure(point: Point, resolution: Resolution, borderWidth: number): void;
}

export class EllipseFaceBorder extends FaceBorder {
    constructor(options?: FaceBorderOptions) {
        super(options ?? DEFAULT_FACE_BORDER_OPTIONS);
    }

    protected _baseFigure(point: Point, resolution: Resolution, borderWidth: number): void {
        const { rx, ry } = this._calculateEllipseRadiuses(resolution);
        this._context.ellipse(point.x, point.y, rx + borderWidth, ry + borderWidth, 0, 0, 2 * Math.PI);
    }

    protected _calculateEllipseRadiuses(resolution: Resolution) {
        return {
            rx: resolution.width / 2,
            ry: resolution.height / 2,
        };
    }
}
```

#### Example custom implementation

```javascript
import tdvc, {
    ComponentSettingsFromClient,
    Point,
    Resolution,
} from '@tdvc/face-onboarding';

let lib: TDVRegistrationOnboarding | TDVAthorizationOnboarding;

// Changing styles for an elliptical face border
class CustomEllipseFaceBorder extends tdvc.UiKit.EllipseFaceBorder {
    constructor() {
        super({
            ...tdvc.UiKit.DEFAULT_FACE_BORDER_OPTIONS,
            fillStyle: 'rgba(0,0,0,1.0)',
            lineWidth: 1,
            strokeStyle: 'red',
        });
    }
}

// Implementation of a face border in the form of a rectangle with rounded corners
class RoundedSquareFaceBorder extends tdvc.UiKit.FaceBorder {
    constructor() {
        super({
            ...tdvc.UiKit.DEFAULT_FACE_BORDER_OPTIONS,
            fillStyle: 'rgba(0,0,0,1.0)',
        });
    }

    protected _baseFigure(point: Point, resolution: Resolution, borderWidth = 0) {
        const offset = Math.floor((resolution.width / 100) * 16);

        const { topLeftCorner, bottomLeftCorner, bottomRightCorner, topRightCorner } =
            this._calculateRectCornerCoordinates(point, resolution, borderWidth);

        this._context.moveTo(topLeftCorner.x, topLeftCorner.y + offset);

        this._context.quadraticCurveTo(topLeftCorner.x, topLeftCorner.y, topLeftCorner.x + offset, topLeftCorner.y);
        this._context.lineTo(topRightCorner.x - offset, topRightCorner.y);

        this._context.quadraticCurveTo(topRightCorner.x, topRightCorner.y, topRightCorner.x, topRightCorner.y + offset);
        this._context.lineTo(bottomRightCorner.x, bottomRightCorner.y - offset);

        this._context.quadraticCurveTo(
            bottomRightCorner.x,
            bottomRightCorner.y,
            bottomRightCorner.x - offset,
            bottomRightCorner.y
        );
        this._context.lineTo(bottomLeftCorner.x + offset, bottomRightCorner.y);

        this._context.quadraticCurveTo(
            bottomLeftCorner.x,
            bottomLeftCorner.y,
            bottomLeftCorner.x,
            bottomLeftCorner.y - offset
        );
        this._context.closePath();
    }

    protected _calculateRectCornerCoordinates(point: Point, resolution: Resolution, borderWidth = 0) {
        const topLeftCorner: Point = {
            x: point.x - resolution.width / 2 - borderWidth,
            y: point.y - resolution.height / 2 - borderWidth,
        };

        const topRightCorner: Point = {
            x: point.x + resolution.width / 2 + borderWidth,
            y: point.y - resolution.height / 2 - borderWidth,
        };

        const bottomRightCorner: Point = {
            x: point.x + resolution.width / 2 + borderWidth,
            y: point.y + resolution.height / 2 + borderWidth,
        };

        const bottomLeftCorner: Point = {
            x: point.x - resolution.width / 2 - borderWidth,
            y: point.y + resolution.height / 2 + borderWidth,
        };

        return {
            topLeftCorner,
            topRightCorner,
            bottomRightCorner,
            bottomLeftCorner,
        };
    }
}

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    uiKit: {
        FaceBorder: RoundedSquareFaceBorder,
    },
};
```

### Motion control direction hints

#### Base implementations

```javascript
export const DEFAULT_MOTION_CONTROL_DIRECTION_HINTS_OPTIONS = {
    lineWidth: 2,
    fillStyle: 'rgba(0, 0, 0, 0.5)',
    strokeStyle: 'rgba(169, 169, 169, 1)',
};

export default abstract class MotionControlDirectionHints extends Canvas {
    constructor(options?: Options) {
        super('tdv-motion-control-direction-hints', options ?? DEFAULT_MOTION_CONTROL_DIRECTION_HINTS_OPTIONS);
    }

    abstract draw(bbox: TBoundingBox, command: MotionControlPattern | 'return', progress: number): void;
}

export class ArrowsMotionControlDirectionHints extends MotionControlDirectionHints {
    protected _leftArrow: Path2D = new Path2D();
    protected _rightArrow: Path2D = new Path2D();
    protected _upArrow: Path2D = new Path2D();
    protected _downArrow: Path2D = new Path2D();

    protected _baseMargin = 4;
    protected _gap = -4;
    protected _arrowResolution: Resolution = {
        width: 16,
        height: 21,
    };
    protected _halfArrowResolution: Resolution = {
        width: this._arrowResolution.width / 2,
        height: this._arrowResolution.height / 2,
    };

    protected _successArrowFillColor = '#17ea4c';
    protected _disabledArrowStrokeColor = 'rgba(255,255,255, 1)';
    protected _disabledArrowFillColor = `rgba(255, 255, 255, 0.5)`;

    constructor(options?: Options) {
        super(options);
        this._initLeftArrowPath();
        this._initRightArrowPath();
        this._initUpArrowPath();
        this._initDownArrowPath();
    }

    draw(bbox: TBoundingBox, command: MotionControlPattern | 'return', progress = 0) {
        switch (command) {
            case 'left':
                this._drawHintForLeftAction(bbox, progress);
                break;
            case 'right':
                this._drawHintForRightAction(bbox, progress);
                break;
            case 'up':
                this._drawHintForUpAction(bbox, progress);
                break;
            case 'closer':
                this._drawHintForCloserAction(bbox, progress);
                break;
            case 'farther':
                this._drawHintForFartherAction(bbox, progress);
                break;
            default:
                break;
        }
    }

    protected _drawHintForLeftAction(bbox: TBoundingBox, progress = 0) {
        let basePoint;

        for (let i = 0; i < 4; i++) {
            basePoint = this._getPointForRightPosition(bbox, i);
            this._renderArrow(this._rightArrow, basePoint, progress, i);

            basePoint = this._getPointForLeftPosition(bbox, i);
            this._renderArrow(this._leftArrow, basePoint, 0, i, true);

            basePoint = this._getPointForUpPosition(bbox, i);
            this._renderArrow(this._upArrow, basePoint, 0, i, true);

            basePoint = this._getPointForDownPosition(bbox, i);
            this._renderArrow(this._downArrow, basePoint, 0, i, true);
        }
    }

    protected _drawHintForRightAction(bbox: TBoundingBox, progress = 0) {
        let basePoint;

        for (let i = 0; i < 4; i++) {
            basePoint = this._getPointForRightPosition(bbox, i);
            this._renderArrow(this._rightArrow, basePoint, 0, i, true);

            basePoint = this._getPointForLeftPosition(bbox, i);
            this._renderArrow(this._leftArrow, basePoint, progress, i);

            basePoint = this._getPointForUpPosition(bbox, i);
            this._renderArrow(this._upArrow, basePoint, 0, i, true);

            basePoint = this._getPointForDownPosition(bbox, i);
            this._renderArrow(this._downArrow, basePoint, 0, i, true);
        }
    }

    protected _drawHintForUpAction(bbox: TBoundingBox, progress = 0) {
        let basePoint;
        for (let i = 0; i < 4; i++) {
            basePoint = this._getPointForRightPosition(bbox, i);
            this._renderArrow(this._rightArrow, basePoint, 0, i, true);

            basePoint = this._getPointForLeftPosition(bbox, i);
            this._renderArrow(this._leftArrow, basePoint, 0, i, true);

            basePoint = this._getPointForUpPosition(bbox, i);
            this._renderArrow(this._upArrow, basePoint, progress, i);

            basePoint = this._getPointForDownPosition(bbox, i);
            this._renderArrow(this._downArrow, basePoint, 0, i, true);
        }
    }

    protected _drawHintForCloserAction(bbox: TBoundingBox, progress = 0) {
        let basePoint;
        for (let i = 0; i < 4; i++) {
            basePoint = this._getPointForRightPosition(bbox, i);
            this._renderArrow(this._leftArrow, basePoint, progress, i);

            basePoint = this._getPointForLeftPosition(bbox, i);
            this._renderArrow(this._rightArrow, basePoint, progress, i);

            basePoint = this._getPointForUpPosition(bbox, i);
            this._renderArrow(this._downArrow, basePoint, progress, i);

            basePoint = this._getPointForDownPosition(bbox, i);
            this._renderArrow(this._upArrow, basePoint, progress, i);
        }
    }

    protected _drawHintForFartherAction(bbox: TBoundingBox, progress = 0) {
        let basePoint;
        for (let i = 0; i < 4; i++) {
            basePoint = this._getPointForRightPosition(bbox, i);
            this._renderArrow(this._rightArrow, basePoint, progress, i);

            basePoint = this._getPointForLeftPosition(bbox, i);
            this._renderArrow(this._leftArrow, basePoint, progress, i);

            basePoint = this._getPointForUpPosition(bbox, i);
            this._renderArrow(this._upArrow, basePoint, progress, i);

            basePoint = this._getPointForDownPosition(bbox, i);
            this._renderArrow(this._downArrow, basePoint, progress, i);
        }
    }

    protected _getPointForRightPosition(bbox: TBoundingBox, index: number) {
        const offset = this._baseMargin + index * (this._gap + this._arrowResolution.width);
        return {
            x: bbox.xMax + offset,
            y: bbox.yMin + bbox.height / 2 - this._halfArrowResolution.height,
        };
    }

    protected _getPointForLeftPosition(bbox: TBoundingBox, index: number) {
        const offset = this._baseMargin + index * (this._gap + this._arrowResolution.width);
        return {
            x: bbox.xMin - offset - this._arrowResolution.width,
            y: bbox.yMin + bbox.height / 2 - this._halfArrowResolution.height,
        };
    }

    protected _getPointForUpPosition(bbox: TBoundingBox, index: number) {
        const offset = this._baseMargin + index * (this._gap + this._arrowResolution.width);
        return {
            x: bbox.xMin + bbox.width / 2 - this._halfArrowResolution.height,
            y: bbox.yMin - offset - this._arrowResolution.width,
        };
    }

    protected _getPointForDownPosition(bbox: TBoundingBox, index: number) {
        const offset = this._baseMargin + index * (this._gap + this._arrowResolution.width);
        return {
            x: bbox.xMin + bbox.width / 2 - this._halfArrowResolution.height,
            y: bbox.yMax + offset,
        };
    }

    protected _renderArrow(arrow: Path2D, basePoint: Point, progress: number, arrowIndex: number, isDisabled = false) {
        this._context.save();
        this._setFillColor(progress, arrowIndex, isDisabled);
        this._context.translate(basePoint.x, basePoint.y);
        this._context.fill(arrow);
        this._context.stroke(arrow);
        this._context.restore();
    }

    protected _initLeftArrowPath() {
        this._leftArrow.moveTo(this._arrowResolution.width, 0);
        this._leftArrow.lineTo(this._halfArrowResolution.width, 0);
        this._leftArrow.lineTo(0, this._halfArrowResolution.height);
        this._leftArrow.lineTo(this._halfArrowResolution.width, this._arrowResolution.height);
        this._leftArrow.lineTo(this._arrowResolution.width, this._arrowResolution.height);
        this._leftArrow.lineTo(this._halfArrowResolution.width, this._halfArrowResolution.height);
        this._leftArrow.lineTo(this._arrowResolution.width, 0);
    }

    protected _initRightArrowPath() {
        this._rightArrow.moveTo(0, 0);
        this._rightArrow.lineTo(this._halfArrowResolution.width, 0);
        this._rightArrow.lineTo(this._arrowResolution.width, this._halfArrowResolution.height);
        this._rightArrow.lineTo(this._halfArrowResolution.width, this._arrowResolution.height);
        this._rightArrow.lineTo(0, this._arrowResolution.height);
        this._rightArrow.lineTo(this._halfArrowResolution.width, this._halfArrowResolution.height);
        this._rightArrow.lineTo(0, 0);
    }

    protected _initUpArrowPath() {
        this._upArrow.moveTo(0, this._arrowResolution.width);
        this._upArrow.lineTo(0, this._halfArrowResolution.width);
        this._upArrow.lineTo(this._halfArrowResolution.height, 0);
        this._upArrow.lineTo(this._arrowResolution.height, this._halfArrowResolution.width);
        this._upArrow.lineTo(this._arrowResolution.height, this._arrowResolution.width);
        this._upArrow.lineTo(this._halfArrowResolution.height, this._halfArrowResolution.width);
        this._upArrow.lineTo(0, this._arrowResolution.width);
    }

    protected _initDownArrowPath() {
        this._downArrow.moveTo(0, 0);
        this._downArrow.lineTo(0, this._halfArrowResolution.width);
        this._downArrow.lineTo(this._halfArrowResolution.height, this._arrowResolution.width);
        this._downArrow.lineTo(this._arrowResolution.height, this._halfArrowResolution.width);
        this._downArrow.lineTo(this._arrowResolution.height, 0);
        this._downArrow.lineTo(this._halfArrowResolution.height, this._halfArrowResolution.width);
        this._downArrow.lineTo(0, 0);
    }

    protected _setFillColor(progress: number, currentIndex: number, isDisabled: boolean) {
        const options = { ...this._initialOptions };

        if (!isDisabled && Math.floor(progress / 25) >= currentIndex + 1) {
            options.fillStyle = this._successArrowFillColor;
        }

        if (isDisabled) {
            options.fillStyle = this._disabledArrowFillColor;
            options.strokeStyle = this._disabledArrowStrokeColor;
        }

        this.setContextOption(options);
        this.applyContextOptions();
    }

    destroy(): void {
        this._leftArrow = undefined!;
        this._rightArrow = undefined!;
        this._upArrow = undefined!;
        this._downArrow = undefined!;
        super.destroy();
    }
}
```

#### Example custom implementation

```javascript
import tdvc, {BoundingBox, ComponentSettingsFromClient, MotionControlPattern } from '@tdvc/face-onboarding';

let lib: TDVRegistrationOnboarding | TDVAthorizationOnboarding;

class MotionControlDirectionHintsViaFilledFrame extends tdvc.UiKit.MotionControlDirectionHints {
    draw(bbox: BoundingBox, command: MotionControlPattern | 'return', progress = 0) {
        this._context.save();
        this._setStyleByProgress(progress);
        this._draw(bbox);
        this._context.restore();
    }

    private _draw(bbox: BoundingBox) {
        const resolution = {
            width: bbox.xMax - bbox.xMin + this._context.lineWidth / 2,
            height: bbox.yMax - bbox.yMin + this._context.lineWidth / 2,
        };

        const offset = Math.floor((resolution.width / 100) * 16);

        this._context.beginPath();
        this._context.moveTo(bbox.xMin, bbox.yMin + offset);

        this._context.quadraticCurveTo(bbox.xMin, bbox.yMin, bbox.xMin + offset, bbox.yMin);
        this._context.lineTo(bbox.xMax - offset, bbox.yMin);

        this._context.quadraticCurveTo(bbox.xMax, bbox.yMin, bbox.xMax, bbox.yMin + offset);
        this._context.lineTo(bbox.xMax, bbox.yMax - offset);

        this._context.quadraticCurveTo(bbox.xMax, bbox.yMax, bbox.xMax - offset, bbox.yMax);
        this._context.lineTo(bbox.xMin + offset, bbox.yMax);

        this._context.quadraticCurveTo(bbox.xMin, bbox.yMax, bbox.xMin, bbox.yMax - offset);
        this._context.closePath();

        this._context.closePath();
        this._context.stroke();
    }

    private _setStyleByProgress(progress = 0) {
        this._context.lineWidth = 4;
        this._context.strokeStyle = this._getStrokeColor(progress);
    }

    private _getStrokeColor(progress: number) {
        let hue, saturation, lightness;

        hue = progress;
        saturation = 83;
        lightness = 50;

        return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    destroy(): void {
        super.destroy();
    }
}

const config: ComponentSettingsFromClient = {
    mountElement: 'app',
    baseUrl: '/',
    integrationId: '78fc6242-a812-4583-b508-078939cc747a',
    uiKit: {
        MotionControlDirectionHints: MotionControlDirectionHintsViaFilledFrame,
    },
};
```

## Device Recommendations

The web-component performs many resource-intensive operations such as processing the video stream from the camera, searching for faces and analyzing the position of the face on the frame, visualizing the mask, and more. The combination of these operations imposes limitations on the technical characteristics of the device.

For correct operation, the device must have the following characteristics:

-   Having at least one working and accessible webcam with a minimum resolution of 1280x720 and a minimum FPS of 25
-   Processor level MediaTek Dimensity 700 or higher
-   For smartphone required IOS 16 / Android 10 and higher

Examples of devices that we use for testing:

-   POCO M4 5G
-   Samsung Galaxy S9
-   Samsung Galaxy A55
-   Samsung Galaxy Tab S9
-   iPhone 11 Pro
-   Iphone 15 Plus
-   Lenovo LOQ 15IRH8
-   Macbook Air 13 (m3, 16гб)
-   Macbook Pro 14" (m4, 16гб)

## Browser support

-   Google Chrome
-   Mozila Firefox
-   Yandex Browser
-   Safari
-   Mi Browser
-   Samsung Browser

## Version compatibility table

<table>
    <tbody>
        <tr>
            <td class="center-aligned" width="400">
                <b>Server BAF</b>
            </td>
            <td class="center-aligned" width="400">
                <b>@tdvc/face-onboarding</b>
            </td>
        </tr>
         <tr>
            <td width="400">1.15.*-1.16.*</td>
            <td width="400">1.16.*</td>
        </tr>
        <tr>
            <td width="400">1.15.*</td>
            <td width="400">1.15.*</td>
        </tr>
        <tr>
            <td width="400">1.14.*</td>
            <td width="400">1.14.*</td>
        </tr>
        <tr>
            <td width="400">1.13.*</td>
            <td width="400">1.13.*</td>
        </tr>
        <tr>
            <td width="400">1.12.*</td>
            <td width="400">1.12.*</td>
        </tr>
        <tr>
            <td width="400">1.10.*</td>
            <td width="400">1.10.*-1.11.*</td>
        </tr>
        <tr>
            <td width="400">1.9.*</td>
            <td width="400">1.9.*</td>
        </tr>
        <tr>
            <td width="400">1.8.*</td>
            <td width="400">1.8.*</td>
        </tr>
         <tr>
            <td width="400">1.7.0</td>
            <td width="400">1.7.0</td>
        </tr>
        <tr>
            <td width="400">1.5.0-1.7.0</td>
            <td width="400">1.6.0</td>
        </tr>
         <tr>
            <td width="400">1.5.0</td>
            <td width="400">1.5.0</td>
        </tr>
        <tr>
            <td width="400">1.3.0-1.4.0</td>
            <td width="400">1.4.0-1.4.1</td>
        </tr>
        <tr>
            <td width="400">1.3.0-1.3.1</td>
            <td width="400">1.3.1</td>
        </tr>
        <tr>
            <td width="400">1.3.0-1.3.1</td>
            <td width="400">1.3.1</td>
        </tr>
         <tr>
            <td width="400">1.2.0</td>
            <td width="400">1.2.0-1.3.0</td>
        </tr>
        <tr>
            <td width="400">1.1.0</td>
            <td width="400">1.0.0-1.1.2</td>
        </tr>
        <tr>
            <td width="400">1.0.0</td>
            <td width="400">1.0.0</td>
        </tr>
    </tbody>
</table>

## Release notes

-   **1.16.0**

    -   Added the ability to redefine UI logic for rendering a face mask based on keypoints.
    -   Added the ability to redefine UI logic for rendering a face border
    -   Added the ability to redefine UI logic for Motion Control direction hints
    -   Added error 'The waiting time for permission to access the camera has been exceeded' instead of 'Not allowed access to camera' if access to the camera was not allowed for a certain period of time
    -   Added error handling that occurred when an invalid value was passed to the integrationId.
    -   Added error handling for receiving server settings if the integrationId of the integration was specified, which does not exist on the server, or the incorrect baseUrl of the server was specified.
    -   Added error display if integration is disabled on the server.
    -   Added error display if the Video Encoder API is not supported by the browser
    -   The overlay settings block has been removed from the motionControl and faceBestshotSettings blocks of the component configuration. The background when displaying the face frame is now configured by defining a custom version of the FaceBorder UI element.
    -   Update locales
    -   Update Readme
    -   Fixed a bug that ignored the cameraId setting if it was required to fill out a form to determine the applicator status
    -   Fixed a bug where setting an invalid integrationId caused the component to endlessly try to open a connection to send logs.
    -   Fixed a bug that prevented a WebSocket connection for video recording from opening when transmitting a value other than "/" to the baseUrl field.
    -   Fixed a bug that caused the set bitrate to not be applied when recording videos via the Media Recoder API.
    -   Fixed a bug where the applicant could not be found if a phone number was used to search for him.

-   **1.15.3**

    -   Fixed a bug that could cause the component to freeze at the end of Motion Control video recording if the best shots of the face were not collected.
    -   Fixed a bug that could cause the component to freeze at the end of Motion Control video recording on Firefox

-   **1.15.2**

    -   Added fixes from path 1.14.5

-   **1.15.1**

    -   Added fixes from path 1.14.4

-   **1.15.0**

    -   The face detector has been updated
    -   The logic of calculating the angles of rotation of the face has been changed
    -   The following settings have been removed: autoSubmit obtainReferenceFrame, focalLength, showCameraSelect, facePositionValidation

-   **1.14.5**

    -   Fixed a bug that caused the component to not work in WebKit
    -   Fixed a bug that would not download the worker file if the component was running on the URL that contained the path.
    -   Fixed a bug where instead of configuring autoSubmit, the showCameraSelect setting was used to skip the camera selection stage.

-   **1.14.4**

    -   Fixed the display of masks and face frames when adding the object-fit:cover property for the camera preview.
    -   Fixed a bug where the component ignored the face frame detection mode and, when dynamic face position detection was disabled, calculated the frame through detection face.
    -   Fixed a bug where the background overlay settings for motion control stage were taken from the settings of the taking bestshot stage

-   **1.14.3**

    -   Fixed component freezes if the message about frame processing and frame saving arrived faster than with an interval of 33 ms
    -   Fixed a bug that opened multiple websocket connections to send logs.

-   **1.14.2**

    -   Fixed transmission of keyframe candidates instead of the final keyframe in onGetReferenceImages callback

-   **1.14.1**

    -   Fixed a bug due to which, after the build, the check for restarting the attempt did not work when Motion Control failed.

-   **1.14.0**

    -   Added video recording mode via WebCodec API
    -   The logic of collecting the best facial images has been changed
    -   Unnecessary screens with results processing between stages have been removed.
    -   Removed Livenes Reflection
    -   Motion control settings extended
    -   Fixed error codes
    -   Added camera setting permissionInBrowserTimeout

-   **1.13.1**

    -   Fixed logging of the error stack instead of the message
    -   Added processing of empty frames when receiving a bestshot
    -   Added a limit on the number of attempts to establish a connection to send logs in case of receiving multiple connection errors in a row
    -   Fixed a bug that caused spam requests to open a ws connection to send logs

-   **1.13.0**

    -   Added logic for generating a random pattern for Motion Control and selecting a pattern from a list of defined patterns on the server
    -   Removed HTTP transport for livenessTransport setting
    -   Added sending logs via WebSocket
    -   Added the onGetReferenceImages callback to get a reference image of a face before requesting validation

-   **1.12.4**

    -   Fixed a bug that caused the component to freeze after passing motion control and survivability reflection checks for iOS if an error occurred when transferring chunks of video to WebSocket.

-   **1.12.3**

    -   Fixed a bug where Motion Control GIF hints would not show up when replaying Motion Control after a Motion Control failure

-   **1.12.2**

    -   Added additional log components

-   **1.12.1**

    -   The logic of processing the status of the "Unconfirmed" applicant has been changed. The status "Unconfirmed" does not block the user's flow during registration.

-   **1.12.0**

    -   Expanded configuration options for WebRTC on par with WebSocket
    -   Fixed an issue where the default value was always used as the path to the model's resources and the value set on the server or client was ignored.

-   **1.11.3**

    -   Added additional checks for the presence of an API in the browser related to the settings and capabilities of MediaStreamTrack

-   **1.11.2**

    -   Fixed a bug where applicantId was not logged if it was passed via client-side configuration
    -   Fixed a bug where the component would freeze after passing Motion Control and Liveness Reflection checks
    -   Added additional log components

-   **1.11.1**

    -   Added `beforeStart` setting in timer for Motion Control to set the minimum Motion Control passing time. If the values ​​are less than 1000 and the Motion Control test is passed quickly with 1 action pattern, incorrect video recording may occur.
    -   Fixed a bug where video chunks would sometimes be sent in the wrong order when recording in the Firefox browser.

-   **1.11.0**

    -   Added logging of component operation to the server
    -   Updated packages related to the operation of the face recognition model
    -   Fixed re-loading opencv.js if it already exists in html document
    -   Fixed a bug that caused the web-component to slow down on multiple consecutive attempts
    -   Fixed memory leak when releasing detector resources
    -   Fixed a bug because of which the onMounted callback was not called when the component initialisation was completed
    -   Added video format selection restriction for iOS, due to video rotation for webm format

-   **1.10.1**

    -   Added onIdentifyApplicantStatus callback function that will be called after determining the status of the applicant.

-   **1.10.0**

    -   Added a mode to automatically calculate the size and position of the face frame based on face detection.
    -   Added Unconfirmed status processing for the applicant
    -   Added error handling if face detector operation is not supported by the device
    -   Improved accuracy of head movement detection during inspections

-   **1.9.3**

    -   Fixed a bug where the coefficient settings did not affect the size of the face frame when taking a bestshot
    -   Added settings to stylize the face frame

-   **1.9.2**

    -   Added onStartValidation callback function that will be called before sending a validation attempt request

-   **1.9.1**

    -   Added a static method to pre-initialize the face detector from outside the component
    -   Added the ability to skip rendering the UI at the camera selection stage and move on to the next stage
    -   Added option to disable stage description display
    -   Added possibility of automatic transition from stage description by timer
    -   Fixed a bug that caused the performance of components to drop when repeatedly retrying

-   **1.9.0**

    -   Return HTTP Liveness Transport
    -   Changed logic of calculating frame size for face positioning
    -   Remove `faceSettings` field from configuration
    -   Added face frame circle configuration object for Liveness Reflection and Motion Control, in the corresponding objects (`faceBorder` in `livenessReflection` and `motionControl`).
    -   Changed the UI for Motion Control check
    -   Added additional GIF hints for Motion Control
    -   Added Motion Control overlay display settings
    -   Added time interval settings for actions within Motion Control
    -   Added an additional face position check when passing Liveness Reflection
    -   Added blocks with information about Liveness Reflection and Motion Control checks

-   **1.8.1**

    -   Added the ability to limit the time to wait for an ICE candidate response using the **_checkIceCandidateTimeout_** variable.
    -   The object for localization has been updated.
    -   Fixed a bug that caused component settings validation errors to be displayed as an error receiving settings from the server
    -   Fixed a bug that caused the component to not display an error and continue to work without switching to checks if there were no working ICE candidates.

-   **1.8.0**

    -   HTTP liveness transport replaced by WebSocket
    -   Added ability to customize the order of checks and disable Motion Control and Liveness Reflection **(WebSocket only)**
    -   Fixed a bug that causes the validation request to be executed before the server processes Liveness Reflection and Motion Control data when “autoSubmit” is enabled.
    -   Fixed a bug where face settings did not affect Motion Control checks
    -   Fixed a bug in the web-component that resulted in multiple requests to access the camera in telegram.
    -   Fixed a bug that caused the component to hang on a mobile device if access to the camera was allowed in the web session, but access to the camera was denied in the application permissions.

-   **1.7.2**

    -   Added ability to overwrite component settings received from the server through client-side configs
    -   Added error display if a request to retrieve component settings fails
    -   Fixed a bug that caused “Server Error” to be displayed instead of validation error
    -   Fixed a bug that caused the component to slow down when the “Update” button was clicked multiple times

-   **1.7.1**

    -   Fixed a bug that caused the component display to break after clicking on the “Update” button when receiving error 120061

-   **1.7.0**

    -   Added configuration of business logic components on the server
    -   The configuration has been updated:
        -   The **_fingerprintWaitTime_** field has been added.
        -   **_lrsToken_** replaced on **_videoRecorderToken_**.
        -   **_lrsDecryptKey_** replaced on **_videoRecorderDecryptionKey_**
    -   Fixed a bug that caused the Liveness check don't start when setting the value to 0 for the **_timeToStartRecord_** variable.
    -   Fixed a bug that caused a processing message to be displayed and it was not possible to continue working with the component.
    -   Fixed a bug that caused the processing window to be displayed indefinitely after passing Motion Control
    -   Updated TDVC library to 1.5.3 version.
    -   Update README.md

-   **1.6.3**

    -   The configuration has been updated: the **_livenessTransport_** field has been added to the **_blConfig_** object. This setting allows you to select the type of communication between the client and the server (WebRTC or HTTP) when passing checks on liveness.
    -   Update README.md
    -   Updated TDVC library to 1.5.2 version.

-   **1.6.2**

    -   The configuration has been updated: the **_faceModelSettings_** field has been added to the **_blConfig_** object. In these settings, you can enable the use of a face grid, as well as adjust the time before recording starts. Read more in the configuration description in this file. Checking the update configuration.
    -   Update README.md
    -   Updated TDVC library to 1.5.1 version.

-   **1.6.1**

    -   Fix README.md
    -   Added missing recognition models to the "networks" folder.

-   **1.6.0**

    -   Updated TDVC library to 1.5.0 version.
    -   Deleted **_platformToken_** from **_serverConfig_**.
    -   Updated using **_TDVC.API.getApplicantByFilter_** methods. Add **_page_** and **_pageSize_** parameters.
    -   Updated using **_TDVC.API.getLRSOffer_** methods. Delete **_any_** type annotation.
    -   Added Tensowflow models in networks directory. Fix tensorflow initial and destroy.
    -   The configuration has been updated: the **_cameraSettings_** field has been added to the **_blConfig_** object. In these settings you can enable camera selection in the web-component or pass cameraId. You can also specify the required video stream resolution. Update config validation.
    -   Fixed error with camera selector after Liveness Reflection.
    -   Added risk processing during registration and authorization.
    -   Added package version printing.
    -   Added text for LRS errors.
    -   Added **_onMounted_** callback. It's a callback-function which will be called on the component mounted
    -   Added **_Version compatibility table_** in README.md.
    -   Added **_networksPath_** parameter in **_serverConfig_**.

-   **1.5.0**

    -   Add size accuracy in faceSettings config
    -   Update custom-style.css file
    -   Add sending Motion Control result on server

-   **1.4.1**

    -   Update Motion Control config
    -   Add motion controll attempts count function
    -   Update config validation
    -   Update locales
    -   Update onMotion callback
    -   Fix camera changing
    -   Fix Keypoints canvas view
    -   Add face sizes settings

-   **1.4.0**

    -   Add dynamic face circle
    -   Add new callback - onBack
    -   Add new backendUrl pattern - if you pass the value "/", then requests will be sent to the host on which the web-component is deployed
    -   Add OpenCV script in package
    -   Add enabled WebRTC metrics: FPS and network bitrate
    -   Fix bug with form validation
    -   Add auto submit feature
    -   Add onMotion callback
    -   Add onUpdate callback
    -   Fixed logic for checking applicant status
    -   Fix bug with WebRTC stop connection
    -   Update TDVC lib to 1.4.0 version
    -   Add new errors checking
    -   Update video angles

-   **1.3.1**

    -   Added processing of Liveness Reflection check results
    -   Add new message for face capturing
    -   Performance improvement
    -   Update TDVC lib to 1.3.1

-   **1.3.0**

    -   Add applicantID in blConfig
    -   Add locales for liveness reflection calculating
    -   Fix server error message view
    -   Update TDVC lib to 1.3.0

-   **1.2.0**

    -   Add active liveness with head turns
    -   Optimize works with backend
    -   Change applicationFields and iceServers configs
    -   Add new styles and locales
    -   Fix bugs

-   **1.1.2**

    -   Update FaceMesh lib.
    -   Fix bugs with Safari browser

-   **1.1.1**

    -   Update Liveness Reflection algorithm.
    -   Fix bugs

-   **1.1.0**

    -   First release
