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

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

## Обзор​

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

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

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

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

       "@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. После установки пакета переместите папки 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. Информация собирается с помощью Browser API и включает данные об устройстве, данные браузера, геолокацию (которая собирается двумя способами: доступный Browser API и данные интернет-соединения) и многое другое.

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

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

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

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

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

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

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

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

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

### Общие настройки​

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

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

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

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

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

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

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

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

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

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

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

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

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

  - Поле `angleСalculation` используется для вычисления углов поворота лица и определения вращения лица. Содержит настройку `angles`.

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

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

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

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

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

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

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

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

    import tdvc, { 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,
                }
            },
            detectorOptions: { delegate: tdvc.FaceDetectorDelegateMode.AUTO, minFaceDetectionConfidence: 0.3 },
            heathcheckImagePath: '/images/face_detector/face.jpg',
        },
    };

### Настройки заявителя​

- Поле `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` используется для указания ID конкретной камеры, которую следует использовать. Может быть полезно, если камера имеет несколько режимов работы и требуется использовать конкретный режим, или если устройство имеет несколько камер и нужно использовать конкретную.

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

  - Поле `permissionInBrowserTimeout` используется для определения времени ожидания подтверждения доступа к камере. По умолчанию 30 000 миллисекунд. По истечении установленного времени, если пользователь не подтверждает доступ к камере, будет сгенерирована ошибка «Not allowed access to camera». Если установлено 0, ограничение времени на подтверждение отсутствует.

    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` используется для определения количества действий для прохождения Motion Control. Значение по умолчанию — 3.

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

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

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

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

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

    - Поле `enabled` используется для включения или отключения описания Motion Control. Значение по умолчанию — 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,
                        },
                    },
                },
            },
        },
    }

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

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

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

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

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

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

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

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

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

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

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

Поле `language` используется для определения языка интерфейса, принимает значение типа string. Если значение не указано или для указанного значения нет локалей, будет использовано значение "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). Система определяет логику обработки событий на основе данных, предоставленных этими функциями, или на основе факта вызова callback. В конфигурации компонента определено несколько таких функций:

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". Если значение result равно 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​

    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,
                / Изменение цвета
                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';

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

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

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

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

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

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

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

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

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

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

Поле `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, если отправка логов через веб-сокет включена, но по какой-то причине соединение недоступно в момент отправки логов. По умолчанию 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],
            correlationId: '133a96cc-5ed1-4c92-1111-832dff3806eb',
            fallbackInterval: 3_000,
        },
    };

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

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

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

- Наличие хотя бы одной рабочей и доступной веб-камеры с минимальным разрешением 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