Перейти к основному содержимому

# Веб-компонента

## Обзор​

Веб-компонента является ключевой частью системы BAF, пользовательского интерфейса, используемого для сбора данных о пользователе и его устройстве, выполнения биометрических проверок и отправки данных на сервер. Он интегрирован в существующую систему на стороне интерфейса и служит дополнительным уровнем биометрической верификации пользователя.

## Установка веб-компоненты​

Веб-компонента поставляется в виде архива TGZ tdvc-face-onboarding. Также требуется библиотека `tdvc`, которая поставляется отдельно в виде архива.

1. Переместите архивы в корневую папку вашего проекта и добавьте следующие строки в свой package.json в разделе зависимостей:

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

   Версия архива может отличаться. Пример окончательного файла package.json:

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

2. Вызовите команду `npm install`, которая установит библиотеку в ваш проект.

3. Для корректной работы веб-компоненты добавьте несколько файлов в каталог (обычно это public каталог), где хранятся статические ресурсы вашего проекта.

4. После установки пакета переместите папки images и networks из /node_modules/@tdvc/face-onboarding/ и frame_handler_worker.js из /node_modules/@tdvc/face-onboarding/dist в public каталог.

## Импорт и использование веб-компоненты​

1. Импортируйте библиотеку и стили.

2. Создайте интеграцию веб-компоненты на сервере.

3. Определите конфигурацию веб-компоненты.

4. Запустите проект.

#### Пример:​

    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();
    };

## Как это работает​

Поведение веб-компоненты определяется его настройками конфигурации и может изменяться соответствующим образом. Полный алгоритм выглядит следующим образом:

1. Выполняется инициализация веб-компоненты.

   Веб-компонента получает конфигурацию от интерфейса проекта, в который он встроен, и от сервера BAF через его API. Как только конфигурация получена с сервера, конфигурации объединяются в одну.

   > Если один и тот же параметр определен как в клиентской, так и в серверной конфигурациях, приоритет имеет значение из клиентской конфигурации.

   После объединения конфигураций веб-компонента инициализирует необходимые для работы сервисы, включая детектор лиц, модули видеозаписи и другие необходимые компоненты.

2. Веб-компонента определяет, существует ли пользователь в системе BAF, и проверяет его текущий статус.

   Пользователь идентифицируется с помощью уникального идентификатора, который может быть либо передан из внешней системы, либо определен непосредственно в веб-компоненте с помощью формы, в которую пользователь вводит свои данные.

   После идентификации статус пользователя проверяется в системе BAF. Если статус действителен, пользователю разрешается продолжить; в противном случае веб-компонента отображает сообщение об ошибке, указывающее на недопустимый статус, и блокирует дальнейший доступ.

   Если в конфигурации указан уникальный идентификатор пользователя, форма будет пропущена, а проверка статуса будет выполнена немедленно.

3. Веб-компонента получает доступ к веб-камере устройства пользователя.

   Веб-компонента запрашивает доступ к веб-камере пользователя. Если устройство найдено и доступ предоставлен, отображается видеопоток; в противном случае выводится сообщение об ошибке. Если доступно несколько устройств, пользователю предоставляется возможность выбрать, какое из них использовать.

4. Веб-компонента генерирует отпечаток устройства.

   Веб-компонента собирает данные об устройстве и окружающей среде для создания уникального отпечатка устройства. Этот отпечаток используется для повышения уровня безопасности при использовании BAF. Информация собирается с помощью API браузера и включает в себя данные об устройстве, данные браузера, геолокацию, доступный API браузера, данные о подключении к Интернету и многое другое.

5. Биометрическая проверка "Motion Control".

   Веб-компонента генерирует ряд команд, которые пользователь должен выполнить перед камерой. К таким командам относятся поворот головы влево, поворот головы вправо, поднятие головы, приближение к камере и отдаление от камеры.

   Во время проверки фиксируются кадры, содержащие лицо пользователя. Эти кадры затем используются для дополнительных проверок на стороне сервера, таких как определение живости лица, оценка качества изображения и т.д.

6. Биометрическая проверка лица пользователя.

   Вспомогательный этап, который активируется при выключении биометрической проверки "Motion Control".

   Веб-компонента фиксирует положение головы пользователя и извлекает кадры, содержащие лицо пользователя, которые затем используются для дополнительных проверок на стороне сервера, таких как живость лица, качество изображения и т.д.

7. Проверка собранных данных и результатов биометрических проверок.

   Веб-компонента генерирует отпечаток устройства пользователя, отправляет все собранные данные на сервер и получает в ответ подтверждение пути пользователя.

## Настройки веб-компоненты​

### Основные настройки​

- Поле `mountElement` используется для указания идентификатора HTML-элемента, в который будет встроена веб-компонента. При установке значения убедитесь, что элемент с таким идентификатором существует в DOM, в противном случае инициализация будет прервана, и появится ошибка, которую можно просмотреть в терминале браузера.

- Поле `integrationId` используется для взаимодействия с сервером, чтобы получать настройки и определять, из какой интеграции поступают данные. Передаваемое значение должно быть допустимой строкой UUID4. Значение указано в дашборде BAF в разделе интеграций.

- Поле `baseUrl` используется для указания URL-адреса BAF API. Если указать `/`, запросы будут отправляться на хост, на котором развернута веб-компонента. Значение должно быть действительным URL-адресом.

#### Пример конфигурации веб-компоненты с основными настройками​

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

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

### Настройки аутентификации​

- Поле `authenticationToken` используется для передачи JWT токена, который будет использоваться сервером для аутентификации компоненты. Токен передается внутри запросов и помогает серверу определить, что интеграция была создана проверенным пользователем. Для работы в этом режиме вам необходимо настроить тип интеграции на сервере.

### Настройки детектора лиц​

- Поле `networksPath` используется для определения пути к ресурсам, которые необходимы для инициализации детектора лиц. Значение по умолчанию - /networks/.

- Поле detectorOptions используется для настройки параметров детектора лиц. Содержит параметры `delegate` и `minFaceDetectionConfidence`.

      -   Поле `delegate` используется для определения процессора, который будет применяться для обработки кадра. Допустимые значения: CPU, GPU и AUTO. По умолчанию используется AUTO. В режиме AUTO компонент сначала пытается инициализировать детектор на GPU. Если это не удается, он автоматически повторно инициализирует детектор на CPU.

      -   Поле `minFaceDetectionConfidence` используется для задания минимального порога достоверности обнаружения лица. Если достоверность обнаружения ниже указанного значения, компонент не будет обрабатывать такое обнаружение. По умолчанию используется значение 0.3.

  - Поле `heathcheckImagePath` используется для указания пути к ресурсам, необходимым для проверки работоспособности детектора лиц. По умолчанию используется путь /images/face_detector/face.jpg.

- Поле `faceModelSettings` используется для определения настроек детектора лиц. Содержит настройки `modelEnabled`, `timeToStartRecord` и `angleCalculation`.

  - Поле `modelEnabled` используется для включения или выключения детектора лиц. Если детектор выключен, все процессы, связанные с распознаванием лиц, будут отключены. Например, если функция обнаружения отключена, проверка управления движением будет пропущена, и вместо динамического определения положения лица будет использоваться статическое.

  - Поле `timeToStartRecord` используется, когда детектор выключен, чтобы дать пользователю время занять требуемое положение в кадре перед началом биометрической проверки. Указывается время в миллисекундах.

  - Поле `angleCalculation` используется для вычисления углов поворота грани и определения поворота грани. Содержит параметр `angles`.

    - Поле `angles` используется для определения граничных значений для определения направления положения лица. Содержит настройки `left`, `right` и `up`.

      - Поле `left` используется для определения того, при каком градусе поворота головы веб-компонента должна предполагать, что голова пользователя повернута влево.

      - Поле `right` используется для определения того, при каком градусе поворота головы веб-компонента должна предполагать, что голова пользователя повернута вправо.

      - Поле `up` используется для определения того, при каком градусе подъема головы веб-компонента должна предполагать, что голова пользователя поднята вверх.

#### Пример конфигурации с настройками детектора лица​

    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,
                }
            }
        },
    };

### Настройки аппликанта​

- Поле `applicantId` используется для определения статуса аппликанта без необходимости ввода данных. Должен содержать допустимую UUID4 строку.

- Поле `applicantFields` определяет, какие данные необходимо ввести для определения идентификатора аппликанта и его статуса. Если `applicantId` явно указан в конфигурации, то ввод данных будет игнорироваться, а статус будет определяться через переданный UUID. Может содержать поля firstName, lastName, phone, email и referenceId.

Каждое поле содержит параметры `enabled` и `primary` . По крайней мере одно из этих полей должно быть включено, и только одно поле должно быть основным.

- Поле `enabled` используется для включения или отключения отображения поля в форме ввода данных.

- Поле `primary` используется для определения ключевого поля, по которому будет выполняться поиск аппликанта в базе данных.

#### Пример конфигурации с настройкой applicantId​

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

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

#### Пример конфигурации с настройкой applicantFields​

    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,
            },
        },
    };

### Настройки камеры​

- Поле `cameraSettings` используется для определения настроек камеры. Содержит настройки `cameraResolution`, `cameraId`, `autoSubmit` и `permissionInBrowserTimeout`.

  - Поле `cameraResolution` используется для настройки разрешения камеры. Чем выше разрешение, тем больше ресурсов устройства будет использовано при обработке кадров. Принимает значения `fhd` для Full HD, `hd` для HD-изображения и `sd` для SD-изображения.

  - Поле `cameraId` используется для указания идентификатора конкретной камеры, которая будет использоваться. Это может быть полезно, если камера имеет несколько режимов работы, и вам нужно использовать определенный режим, или если устройство имеет несколько камер, и вам нужно использовать конкретную камеру.

  - Поле `autoSubmit` используется для пропуска выбора камеры, если устройство содержит более одной камеры. Обратите внимание, что при значении true веб-компонента будет использовать первую доступную камеру, а порядок камер в системе может измениться.

  - Поле `permissionInBrowserTimeout` используется для определения продолжительности ожидания подтверждения доступа к камере. Значение по умолчанию - 30 000 миллисекунд. По истечении установленного времени, если пользователь не подтвердит доступ к камере, будет сгенерирована ошибка `Не разрешен доступ к камере`. Если установлено значение 0, время подтверждения не ограничено.

#### Пример конфигурации с cameraSettings​

    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
        },
    };

#### Пример конфигурации с настройкой cameraId​

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

### Настройки отпечатка устройства​

- Поле `fingerprintWaitTime` используется для определения максимального времени ожидания при сборе данных устройства. Время в миллисекундах. Имейте в виду, что при уменьшении времени ожидания точность данных может быть ниже.

#### Пример конфигурации с настройками отпечатка устройства​

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

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

### Настройки Motion Control​

- Поле `motionControl` используется для определения настроек Motion Control. Содержит параметры `enabled`, `attemptsCount`, `faceBorder`, `imagesHints`, `description`.

  - Поле `enabled` используется для включения или отключения проверки Motion Control. По умолчанию имеет значение `true`.

  - Поле `attemptsCount` используется для установки допустимого количества попыток Motion Control. По умолчанию используется значение 3.

  - Поле `faceBorder` содержит настройки для определения положения лица во время проверки. Кадр лица может быть вычислен на основе обнаруженного лица (динамический режим) или на основе разрешения видеопотока (статический режим). Содержит параметры `allowableAccuracyError`, `faceWidthCoefficients` и `autodetected`.

    - Поле `allowableAccuracyError` используется для вычисления погрешности размера между обнаруженным лицом и рамкой лица. Содержит параметры `x` и `y`. Значения указаны в процентах от 1 до 100, а рекомендуемое соотношение - 2/3. По умолчанию `x` равно 20, `y` равно 30,

    - Поле `faceWidthCoefficients` используется для расчета размера рамки лица на основе ширины максимального разрешения камеры. Ширина рамки лица = ширина разрешения камеры ÷ 100 × соответствующий разрешению камеры коэффициент, а высота кадра = ширина рамки лица × на 3/2 (для поддержания соотношения 2/3). Содержит настройки `fullHd`, `hd` и `sd`, которые содержат коэффициент разрешения для конкретного разрешения в процентах. По умолчанию `fullHd` равно 20, `hd` равно 24, `sd` равно 33. Применяется только в статическом режиме.

    - Поле `autodetected` используется для настройки процесса определения исходного положения лица с помощью детекции лица. Содержит параметры `enabled`, `frameCheckLimit`, `availableDeviation`, `framePadding` и `faceSize`.

      - Поле `enabled` используется для переключения режима рамки лица. Если значение равно true, то кадр будет рассчитываться на основе детекции лица, в противном случае - на основе разрешения камеры. По умолчанию имеет значение true.

      - Поле `frameCheckLimit` используется для установки количества кадров, на которых должно находиться лицо и оно должно находиться в определенном положении с учетом погрешности, чтобы зафиксировать исходное положение лица перед биометрическими осмотрами. Обратите внимание, что на разных устройствах время верификации может принимать разные цифры в зависимости от мощности устройства. По умолчанию 60.

      - Поле `availableDeviation` используется для определения допустимой погрешности при вычислении динамического кадра лица, с которой положение текущего обнаруженного лица может отклоняться от оцененного начального положения лица. По умолчанию используется значение 20 пикселей.

      - Поле `framePadding` используется для определения расстояния от границ кадра, при котором обнаружение лица будет сбрасывать процесс определения начального положения. Необходимо чтобы исключить ситуации, когда стартовая позиция находится у края кадра или за его пределами. Содержит параметры `horizontal` и `vertical`. По умолчанию `horizontal` равен 10, `vertical` равен 10.

      - Поле `faceSize` используется для определения минимально и максимально допустимых размеров (в пикселях) обнаруженного лица. Необходимо, чтобы контролировать расстояние лица по отношению к камере и избегать ситуации, когда лицо слишком маленькое или слишком большое. Содержит параметры `min` и `max`, каждый из которых содержит параметры `width` и `height`. По умолчанию `min` равен {width: 120, height: 140}, `max` равен {width: 360, height: 520};

  - Поле `patternSettings` используется для определения настроек действий Motion Control. Содержит настройки `enableSaveFrames`, `autoGeneration`, `autoGenerationPatternActionsList`, `autoGenerationPatternLength` и `specifiedPatternList`.

    - Поле `enableSaveFrames` используется для включения или выключения режима сохранения кадров лиц при финальной оценке действия. По умолчанию используется false.

    - Поле `autoGeneration` используется для включения или выключения режима генерации случайных действий Motion Control. По умолчанию true.

    - Поле `autoGenerationPatternActionsList`используется для определения массива действий, которые будут использоваться при автоматической генерации. По умолчанию [MotionControlActions.LEFT, MotionControlActions.RIGHT].

    - Поле `autoGenerationPatternLength` используется для определения количества действий для передачи управления движением. По умолчанию 3.

    - Поле `specifiedPatternList` используется для определения наборов действий, которые будут использоваться при отключении режима автоматической генерации действий. По умолчанию используется [[MotionControlActions.LEFT, MotionControlActions.RIGHT],[MotionControlActions.RIGHT, MotionControlActions.LEFT]].

  - Поле `imagesHints` используется для настройки GIF подсказок для действий Motion Control. Содержит параметры `enabled` и `resourcesPath`.

    - Поле `enabled` используется для включения или отключения GIF подсказок. По умолчанию имеет значение false.

    - Поле `resourcesPath` используется для определения пути к папке, содержащей GIF-изображение. Папка должна содержать изображения с именами `left`, `right`, `up`, `center`, `farther` и `closer` с расширением gif. Значение по умолчанию /images/motion_control_gif_hint/.

  - Поле `description` используется для настройки отображения описания проверки Motion Control. Содержит параметры `enabled` и `autoSubmit`.

    - Поле `enabled` используется для включения или отключения описания управления движением. По умолчанию имеет значение true.

    - Поле `autoSubmit` используется для настройки автоматического перемещения по истечении заданного периода времени. Содержит параметры `enabled` и `timer`.

      - Поле `enabled` используется для включения или отключения перехода по истечении заданного периода времени. По умолчанию имеет значение false.

      - Поле `timer` используется для установки времени ожидания в миллисекундах, по истечении которого будет выполнен переход, если пользователь сам не выполняет переход. Значение должно быть больше 0. По умолчанию - 30 000.

#### Пример конфигурации с параметрами enabled и attemptsCount для Motion Control​

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

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

#### Пример конфигурации с параметром faceBorder для статической рамки лица​

    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,
                },
            },
        },
    }

#### Пример конфигурации с параметром faceBorder для динамической рамки лица​

    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 save patterns frames settings

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

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

#### Пример конфигурации с автоматической генерацией действий Motion Control​

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

    const config: ComponentSettingsFromClient = {
        mountElement: 'app',
        baseUrl: '/',
        integrationId: '78fc6242-a812-4583-b508-078939cc747a',
        motionControl: {
            patternSettings: {
                autoGeneration: true,
                autoGenerationPatternLength: 3,
                autoGenerationPatternActionsList: [
                    tdvc.MotionControlActions.LEFT,
                    tdvc.MotionControlActions.RIGHT,
                    tdvc.MotionControlActions.UP,
                    tdvc.MotionControlActions.FARTHER,
                    tdvc.MotionControlActions.CLOSER,
                ],
            },
        },
    }

#### Пример конфигурации без автоматической генерации действий Motion Control​

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

    const config: ComponentSettingsFromClient = {
        mountElement: 'app',
        baseUrl: '/',
        integrationId: '78fc6242-a812-4583-b508-078939cc747a',
        motionControl: {
            patternSettings: {
                autoGeneration: false,
                specifiedPatternList: [
                    [tdvc.MotionControlActions.LEFT, tdvc.MotionControlActions.RIGHT],
                    [tdvc.MotionControlActions.LEFT, tdvc.MotionControlActions.LEFT],
                    [tdvc.MotionControlActions.UP],
                ],
            },
        },
    }

#### Пример конфигурации с настройками imageHints​

    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"
            }
        },
    }

#### Пример конфигурации с настройками description​

    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,
                },
            },
        },
    }

## Настройки биометрической верификации лица пользователя​

- Поле `faceBestshotSettings` используется для определения настроек биометрической верификации лица пользователя. Содержит параметр faceBorder.

- Поле `faceBorder` содержит настройки для определения положения лица во время проверки. Кадр лица может быть вычислен на основе обнаруженного лица (динамический режим) или на основе разрешения видеопотока (статический режим). Содержит параметры `allowableAccuracyError`, `faceWidthCoefficients` и `autodetected`.

  - Поле `allowableAccuracyError` используется для вычисления погрешности размера между обнаруженным лицом и рамкой лица. Содержит параметры `x` и `y`. Значения указаны в процентах от 1 до 100, а рекомендуемое соотношение - 2/3. По умолчанию `x` равно 20, `y` равно 30,

  - Поле `faceWidthCoefficients` используется для расчета размера рамки лица на основе ширины максимального разрешения камеры. Ширина рамки лица рассчитывается как ширина разрешения камеры ÷ 100 × соответствующий разрешению камеры коэффициент, а высота кадра рассчитывается как ширина рамки лица × 3/2 для соблюдения соотношения 2/3. Содержит настройки `fullHd`, `hd` и `sd`, которые содержат коэффициент разрешения для конкретного разрешения в процентах. По умолчанию `fullHd` равно 20, `hd` равно 24, `sd` равно 33. Используется только для статического режима.

  - Поле `autodetected` используется для настройки процесса определения исходного положения лица с помощью детекцию лица. Содержит параметры `enabled`, `frameCheckLimit`, `availableDeviation`, `framePadding` и `faceSize`.

    - Поле `enabled` используется для переключения режима рамки лица. Если значение равно true, то кадр будет рассчитываться на основе детекции лица, в противном случае - на основе разрешения камеры. По умолчанию имеет значение true.

    - Поле `frameCheckLimit` используется для установки количества кадров, на которых должно находиться лицо и оно должно находиться в определенном положении с учетом погрешности, чтобы зафиксировать исходное положение лица перед биометрическими осмотрами. Обратите внимание, что на разных устройствах время верификации может принимать разные цифры в зависимости от мощности устройства. По умолчанию 60.

    - Поле `availableDeviation` используется для определения допустимой погрешности, которая используется при вычислении динамического кадра лица, с которой положение текущего обнаруженного лица может отклоняться от оцененного начального положения лица. По умолчанию используется значение 20 пикселей.

    - Поле `framePadding` используется для определения расстояния от границ кадра, при котором обнаружение лица будет сбрасывать процесс определения начального положения. Необходимо, чтобы исключить ситуации, когда стартовая позиция находится у края кадра или за его пределами. Содержит параметры `horizontal` и `vertical`. По умолчанию `horizontal` равен 10, `vertical` равен 10.

    - Поле `faceSize` используется для определения минимально и максимально допустимых размеров (в пикселях) обнаруженного лица. Необходимо, чтобы контролировать расстояние лица по отношению к камере и избегать ситуации, когда лицо слишком маленькое или слишком большое. Содержит параметры `min` и `max`, каждый из которых содержит параметры `width` и `height`. По умолчанию `min` равен {width: 120, height: 140}, `max` равен {width: 360, height: 520};

#### Пример конфигурации с параметром faceBorder для статической рамки лица​

    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,
                },
            },
        },
    }

#### Пример конфигурации с параметром faceBorder для динамической рамки лица​

    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,
                        },
                    },
                },
            },
        },
    }

## Локализация​

По умолчанию веб-компонента поддерживает две локализации: русскую (ru) и английскую (en), а языком интерфейса по умолчанию является английский. Локализация компонента может быть изменена посредством конфигурирования во время инициализации веб-компоненты. Для этого укажите в конфиге поля `language` и `locales`.

Поле `language` используется для определения языка интерфейса. Если значение не указано или для указанного значения нет языковых настроек, будет использоваться значение `en`.

Поле `locales` используется для изменения текстов пользовательского интерфейса по умолчанию. При определении пользовательских языковых настроек помните, что компонент будет использовать только переданный объект, поэтому он должен указать все необходимые языковые настройки для всех необходимых языков. Для более точного понимания прочитайте примеры ниже.

#### Актуальный объект локалей для английской локализации​

    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.',
                    },
                    ContinueButton: 'Continue',
                    BackButton: 'Back',
                },

                Errors: {
                    ComponentDisabledError: 'The component is disabled',
                    ApplicantIdWithApplicantFieldsError:
                        'Configuration error Only one of the two configuration fields must be passed: applicantFields or applicantId',
                    NoEnabledApplicantFieldsError:
                        'Configuration error. At least one enabled field in applicantFields is required',
                    NoPrimaryApplicantFieldsError: 'Configuration error. One primary field is required in applicantFields',
                    NoPrimaryEnabledApplicantFieldsError:
                        'Configuration error. The primary field in applicantFields must be enabled',
                    SeveralPrimaryEnabledApplicantFieldsError:
                        'Configuration error. Only one primary field is required in applicantFields',
                    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',
                    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',
                    ConnectionEstablishmentError: 'An error occurred when establishing the connection',
                    VersionCompatibilityError: 'Incompatible versions of components and servers',
                    InactiveVideoTrackError: 'The video track of the camera is inactive',
                    EndedVideoTrackError: 'The video track of the camera is has been ended',
                    DisabledVideoTrackError: 'The video track of the camera is disabled',
                    NoWebGLSupportError: 'WebGL is unavailable in the current browser configuration',
                    FaceDetectorRunningError: 'The device does not meet the requirements necessary to start the detector',
                    NoFaceDetectorError: 'No face detector in the system',
                    NotSupportedDelegationModeError: 'Unsupported detector delegate mode',
                    NoTestFaceDetectorImageError: 'No test image was found to test the operability of the face detector',
                    UnsupportFaceDetectorError:
                        'The face detector cannot be started because the device does not support any of the possible operating modes',
                    NotSupportedWebglApiError: 'WebGL API is unavailable in the current browser configuration',
                    HardwareAccelerationUnavailableError:
                        'Hardware Acceleration is disabled or not supported by the browser',
                },
            },

            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",
                    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",
                    LivenessTransportConnectionTimeoutError:
                        'The connection to the server was closed due to an internal error or exceeding the waiting time',
                    InactiveVideoTrackError: 'The video track from the camera is inactive',
                    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',
                    190003: 'The size of data sent to the server exceeds the allowed size',
                    NoWebGLSupportError: 'The device does not support WebGL technology',
                    FaceDetectorRunningError: 'The device does not meet the requirements necessary to start the detector',
                    NoFaceDetectorError: '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',
                    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',
                    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',
                    'Invalid endeavor info': 'Invalid endeavor info',
                    'Endeavor external link not equal to applicant': 'Applicant ID consistency error',
                    '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',
                    'Error on the server': 'Error on the server',
                    '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",
                    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',
                    LivenessTransportConnectionTimeoutError:
                        'The connection to the server was closed due to an internal error or exceeding the waiting time',
                    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',
                    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',
                    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',
                },
            },
        },
    };

#### Пример изменения английской локализации на TypeScript:​

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

    / Копируем локали по умолчанию, чтобы при изменении определенных локалей остальные оставались доступными в их первоначальном виде.
    const locales = structuredClone(tdvc.DefaultLocales);

    / Обновление нужных локалей
    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,
    };

#### Пример добавления новой локализации в TypeScript:​

Чтобы добавить новую локализацию, подготовьте объект JavaScript, который будет содержать все те же поля, что и объект с английской локализацией, и определите текст на требуемом языке для каждого идентификатора сообщения. Объект локализации, который можно использовать в качестве примера, можно найти в файле README.md в архиве поставки.

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

    / Копируем локали по умолчанию, чтобы при изменении определенных локалей остальные оставались доступными в их первоначальном виде.
    const locales = structuredClone(tdvc.DefaultLocales);

    / Определение объекта с локалями для нужного языка, структура которого должна полностью соответствовать локалям по умолчанию.
    const kkLocales = {
    PreparingEnvironment: 'Ортаны дайындау',
    MessageCode: 'Хабарлама коды: ',
    SomeError: 'Қате орын алды, кейінірек қайта әрекет етіңіз',
    Mode: {
        Authorization: 'Авторизациялау',
        Registration: 'Тіркелу',
    },
    / ...
    };

    / Добавляем в общий набор локалей новый объект, ключом к которому является новый язык локализации.
    locales['kk'] = kkLocales;

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

## Взаимодействие между веб-компонентой и внешней системой​

Взаимодействие между веб-компонентой и внешними системами осуществляется с помощью функций обратного вызова (callbacks). Система определяет логику обработки событий на основе данных, предоставляемых этими функциями, или на основании того факта, что был инициирован обратный вызов.

1. `onMounted: (() => void)` — функция, вызываемая при полной инициализации веб-компоненты.

2. `onError: ((message: string, code: string) => void)` — функция вызывается, если произошла ошибка при прохождении пользовательского пути.

3. `onUpdate: (() => void)` — функция, вызываемая, если после возникновения ошибки пользователь хочет повторить попытку.

4. `onIdentifyApplicantStatus: (applicant: {applicantId: string, status: number}) => void` — функция, вызываемая после получения статуса аппилканта от сервера.

5. `onBack: (() => void)` — функция, вызываемая, если пользователь нажимает кнопку `Назад`, и тем самым перестает проходить путь пользователя.

6. `onMotion: ((type: 'left' | 'right' | 'up' | 'closer' | 'farther' | 'return', result: boolean | undefined) => void)` — функция, вызываемая при биометрической проверке `Motion Control`. Если значение результата - undefined, то проверка только что началась, если true - успешно пройдена, если false - не пройдена.

7. `onGetReferenceImages: (referenceImage: string) => void`; — функция, вызываемая после получения ключевого кадра. referenceImage - изображение в строковом формате base64.

8. `onStartValidation: (() => void)` — функция, вызываемая до начала проверки результатов.

9. `onValidate: ((data: any) => void)` — функция, вызываемая после получения результата проверки данных от сервера.

#### Пример использования обратных вызовов в TypeScript:​

    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);
            },
        },
    }

## Стилизация интерфейса​

### Основной метод стилизации​

Для определения стиля веб-компоненты используется CSS. Элементы интерфейса содержат идентификаторы и классы CSS, с помощью которых можно настроить их внешний вид. Идентификаторы и классы CSS, используемые для настройки, можно найти в файле `@tdvc/face-onboarding/dist/css/style.css`.

### Стилизация через JavaScript/TypeScript​

Для настройки сложных элементов пользовательского интерфейса, таких как маска лица на основе ключевых точек, подсказки действий для Motion Control и рамка лица, которые могут иметь разные формы, стили и логику отображения, одного CSS недостаточно. Поэтому для более гибкого стиля мы предоставляем возможность переопределить реализацию этих элементов.

Все нижеприведенные элементы используют базовый класс в качестве основы для дальнейшей реализации. Он состоит из элемента canvas, методов настройки стилей и разрешения canvas, а также методов жизненного цикла, таких как рендеринг, удаление из DOM и полная очистка через destroy.

#### Основной класс​

    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!;
        }
    }

В реализациях можно использовать как базовый класс, так и его производные.

### Маска лица по ключевым точкам​

#### Базовая реализация​

    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;
        }
    }

#### Пример пользовательской реализации​

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

    / Скрытие маски, чтобы не тратить ресурсы на дисплей
    class NoMask extends tdvc.UiKit.FaceKeypointsMask {
        draw(points: Point[]): void {}
    }

    / Отображение только точек вместо треугольников
    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,
        },
    };

### Рамка лица​

#### Базовая реализация​

    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,
            };
        }
    }

#### Пример пользовательской реализации​

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

    let lib: TDVRegistrationOnboarding | TDVAthorizationOnboarding;

    / Изменение стилей для эллиптической рамки лица
    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',
            });
        }
    }

    / Реализация границы лица в виде прямоугольника со скругленными углами
    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​

#### Базовая реализация​

    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();
        }
    }

#### Пример пользовательской реализации​

    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,
        },
    };

    ### Экран ошибки

    #### Базовая реализация

    ```javascript
    export default class ErrorScreenLayout {
        public readonly root: HTMLDivElement;
        public readonly errorMessage: HTMLParagraphElement;
        protected id = 'tdvc-error';

        constructor() {
            this.root = this._createRootElement();
            this.errorMessage = this._createErrorMessage();
            this.root.append(this.errorMessage);
        }

        protected _createRootElement() {
            const element = document.createElement('div');
            element.id = this.id;
            element.classList.add(this.id);
            return element;
        }

        protected _createErrorMessage() {
            const element = document.createElement('p');
            const id = `${this.id}__error-message`;
            element.classList.add(id);
            return element;
        }

        public setErrorMessage(text?: string) {
            if (text === this.errorMessage.textContent) return;
            this.errorMessage.textContent = text ?? '';
        }

        destroy() {
            this.errorMessage.remove();
            this.root.remove();
        }
    }

#### Пример пользовательской реализации​

    export class CustomErrorScreenLayout extends tdvc.UiKit.ErrorScreenLayout {
        private _codeMessage: HTMLParagraphElement;
        private _image: HTMLImageElement;

        constructor() {
            super();

            this._createImage();
            this._createCodeMessage();
        }

        private _createCodeMessage() {
            this._codeMessage = document.createElement('p');
            this._codeMessage.classList.add(`${this.id}__error-code`);
            this.root.append(this._codeMessage);
        }

        private _createImage() {
            this._image = document.createElement('img');
            this._image.classList.add(`${this.id}__error-image`);
            this._image.src = '/images/warning.png';
            this.root.append(this._image);
        }

        setErrorMessage(text?: string): void {
            if (!text) return;

            const [message, code] = text?.split('. ');

            this.errorMessage.textContent = message;
            this._codeMessage.textContent = code;
        }
    }

## Логгирование​

Поле `loggingSettings` поле используется для определения параметров логгирования. Содержит настройки `enabled`, `level`, `output`.

- Поле `enabled` используется для включения/отключения логгирования. По умолчанию true.

- Поле `level` поле используется для определения минимального уровня логгирования. Веб-компонента будет регистрировать все сообщения установленного уровня и выше. Уровень определяется в следующем порядке: 'debug', 'warning', 'info', 'error', critical'. По умолчанию "debug".

- Поле `output` используется для определения места вывода логов. Допустимые значения: "browser", 'server_via_websocket'. Можно указать несколько мест вывода. По умолчанию ['server_via_websocket'].

- Поле `correlationId` используется для задания идентификатора, который позволяет однозначно сопоставлять запросы и ответы в рамках одной сессии. По умолчанию undefined.

- Поле `fallbackInterval` используется для задания интервала отправки логов по HTTP/HTTPS, если включена отправка логов через WebSocket, но по какой-либо причине соединение недоступно в момент отправки. По умолчанию 3_000. Если значение равно 0, логи будут отправлены один раз по завершении попытки или при возникновении ошибки.

#### Пример конфигурации с настройками логгирования​

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

    const config: ComponentSettingsFromClient = {
        mountElement: 'app',
        baseUrl: '/',
        integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
        loggingSettings: {
            enabled: true,
            level: tdvc.LogLevel.DEBUG,
            output: [tdvc.LoggingOutput.BROWSER],
        },
    };

## Рекомендации к устройству​

Веб-компонента выполняет множество ресурсоемких операций, таких как обработка видеопотока с камеры, поиск лиц и анализ положения лица на кадре, визуализация маски и многое другое. Сочетание этих операций накладывает ограничения на технические характеристики устройства.

Для корректной работы устройство должно обладать следующими характеристиками:

- Наличие хотя бы одной рабочей и доступной веб-камеры с минимальным разрешением 1280x720 и минимальным FPS 25
- Уровень процессора MediaTek Dimensity 700 или выше
- Для смартфона требуется IOS 16/Android 10 и выше

Примеры устройств, которые мы используем для тестирования:

- 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гб)

## Поддержка браузеров​

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

## Таблица совместимости версий​

Server BAF | @tdvc/face-onboarding
1.18.* | 1.18.*
1.15.*-1.18.* | 1.17.*
1.15.*-1.16.* | 1.16.*
1.15.* | 1.15.*
1.14.* | 1.14.*
1.13.* | 1.13.*
1.12.* | 1.12.*
1.10.* | 1.10.*-1.11.*
1.9.* | 1.9.*
1.8.* | 1.8.*
1.7.0 | 1.7.0
1.5.0-1.7.0 | 1.6.0
1.5.0 | 1.5.0
1.3.0-1.4.0 | 1.4.0-1.4.1
1.3.0-1.3.1 | 1.3.1
1.3.0-1.3.1 | 1.3.1
1.2.0 | 1.2.0-1.3.0
1.1.0 | 1.0.0-1.1.2
1.0.0 | 1.0.0