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

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

## Обзор​

Веб-компонент является ключевой частью системы 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',
    };

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

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

- Поле `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 { ComponentSettingsFromClient, CameraResolutions } from '@tdvc/face-onboarding';
    const config: ComponentSettingsFromClient = {
        mountElement: 'app',
        baseUrl: '/',
        integrationId: '225c74bb-4eb1-4c81-9199-832dff3806eb',
        cameraSettings: {
            autoSubmit: true,
            cameraResolution: CameraResolutions.HD,
            permissionInBrowserTimeout:10_000
        },
    };

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

    import { ComponentSettingsFromClient, CameraResolutions } 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` используется для определения настроек управления движением. Содержит параметры `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};

  - Поле `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,
                        },
                    },
                },
            },
        },
    }

#### Пример конфигурации с настройками 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',
                    },
                    ContinueButton: 'Continue',
                    BackButton: 'Back',
                },

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

                Errors: {
                    ApplicantIdWithApplicationFieldsError:
                        'Configuration error Only one of the two configuration fields must be passed: applicationFields or applicantId',
                    NoEnabledApplicationFieldsError:
                        'Configuration error. At least one enabled field in applicationFields is required',
                    NoPrimaryApplicationFieldsError:
                        'Configuration error. One primary field is required in applicationFields',
                    NoPrimaryEnabledApplicationFieldsError:
                        'Configuration error. The primary field in applicationFields must be enabled',
                    SeveralPrimaryEnabledApplicationFieldsError:
                        'Configuration error. Only one primary field is required in applicationFields',
                    InvalidMotionControlAttemptCountError:
                        'Configuration error. The motionControl.attemptsCount field must be greater than zero',
                    EnabledMotionControlWithoutFaceModelError:
                        'Configuration error. If faceModelSettings.modelEnabled is false, then motionControl.enabled must also be false',
                    TimeToStartRecordLessThenOneSecondError:
                        'Configuration error. The timeToStartRecord value must be at least 1000ms',
                    NoIceCandidatesError:
                        'Configuration error. At least one ICE candidate must be specified in the component settings on the server',
                    IceCandidateTimeoutLessThenOneSecondError:
                        'Configuration error. The checkIceCandidateTimeout value must be at least 1000ms',
                    FailedFetchOfConfigurationError: 'Failed to retrieve component settings',
                    NoComponentIdError: 'Integration ID not specified in the component configuration',
                    NoBaseUrlError: 'Base URL of server not specified in the component configuration',
                    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',
                    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',
                },
            },

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

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

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

#### Пример изменения английской локализации на 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.2,
    };

    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 = 8;
        protected _gap = -4;
        protected _arrowResolution: Resolution = {
            width: 22,
            height: 32,
        };
        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,
        },
    };

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

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

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

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