Перейти к содержанию

WebView Workflow

WebView Workflow позволяет открыть готовый интерфейс биометрической проверки внутри мобильного приложения. Backend создаёт сессию настроенного Workflow, а приложение получает session_id, формирует URL с обязательным параметром web_view=true и загружает его во встроенный WebView.

В этом режиме Biometric.Vision управляет экранами и технологиями Workflow, а мобильное приложение отвечает за разрешение доступа к камере, навигацию WebView и обработку завершения проверки. API KEY при этом остаётся на backend и не передаётся в мобильное приложение.

Архитектура интеграции

В интеграции участвуют четыре компонента:

  • backend клиентской системы создаёт сессию, возвращает приложению session_id и получает итоговый результат;
  • мобильное приложение управляет жизненным циклом WebView;
  • Biometric.Vision отображает интерфейс и выполняет технологии Workflow;
  • конечный пользователь предоставляет разрешение на камеру и проходит проверку.
sequenceDiagram
    title Встраивание Workflow в мобильное приложение

    participant APP as Мобильное приложение
    participant API as Backend клиентской системы
    participant BIO as Biometric.Vision
    participant USER as Конечный пользователь

    APP->>+API: Запрос на начало проверки
    API->>API: Создание сессии Workflow
    API-->>-APP: {session_id}

    APP->>+BIO: Открытие /flow/{session_id}?web_view=true
    BIO-->>USER: Запрос доступа к камере
    USER-->>BIO: Разрешение доступа
    BIO->>USER: Интерфейс проверки
    USER->>BIO: Прохождение технологий Workflow
    BIO-->>-APP: Переход на /finished

    APP->>APP: Перехват перехода и закрытие WebView
    APP->>API: Запрос актуального состояния проверки

1. Формирование URL

После получения session_id откройте в WebView следующий URL:

https://remote.biometric.vision/flow/<session_id>?web_view=true

Параметр web_view=true обязателен. Он включает режим встраивания, в котором браузерные переходы после завершения заменяются переходом на служебный URL:

https://remote.biometric.vision/finished

Формируйте URL через стандартный URL API платформы и передавайте только session_id, полученный для текущей попытки пользователя. Не открывайте один session_id одновременно в нескольких WebView.

2. Обязательные настройки

Настройка Требование Назначение
JavaScript Включён Интерфейс проверки работает как веб-приложение
Доступ к камере Разрешён на уровне ОС и WebView Камера используется технологиями биометрической проверки
Inline media playback Включён Видео воспроизводится внутри WebView
Media playback without user gesture Включён Поток с камеры запускается без дополнительного нажатия
Navigation delegate Настроен Приложение перехватывает переход на /finished

На iOS добавьте описание использования камеры в Info.plist. На Android объявите разрешение android.permission.CAMERA в манифесте и запросите runtime-разрешение до загрузки страницы. Разрешайте WebView только доступ к видеозахвату, который требуется для проверки.

3. Примеры интеграции

Примеры принимают готовый sessionId. Получение идентификатора от backend остаётся частью логики приложения.

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class BiometricWebView extends StatefulWidget {
  const BiometricWebView({required this.sessionId, super.key});

  final String sessionId;

  @override
  State<BiometricWebView> createState() => _BiometricWebViewState();
}

class _BiometricWebViewState extends State<BiometricWebView> {
  late final WebViewController controller;

  @override
  void initState() {
    super.initState();

    final uri = Uri.https(
      'remote.biometric.vision',
      '/flow/${widget.sessionId}',
      {'web_view': 'true', 'locale': 'ru'},
    );

    controller = WebViewController(
      onPermissionRequest: (request) {
        if (request.types.every(
          (type) => type == WebViewPermissionResourceType.camera,
        )) {
          return request.grant();
        }
        return request.deny();
      },
    )
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onNavigationRequest: (request) {
            final uri = Uri.parse(request.url);
            if (uri.host == 'remote.biometric.vision' &&
                uri.path == '/finished') {
              Navigator.of(context).pop(true);
              return NavigationDecision.prevent;
            }
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(uri);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: WebViewWidget(controller: controller));
  }
}
import UIKit
import WebKit

final class BiometricViewController: UIViewController, WKNavigationDelegate {
    let sessionId: String
    private var webView: WKWebView!

    init(sessionId: String) {
        self.sessionId = sessionId
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let configuration = WKWebViewConfiguration()
        configuration.allowsInlineMediaPlayback = true
        configuration.mediaTypesRequiringUserActionForPlayback = []

        webView = WKWebView(frame: .zero, configuration: configuration)
        webView.navigationDelegate = self
        webView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(webView)

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        var components = URLComponents()
        components.scheme = "https"
        components.host = "remote.biometric.vision"
        components.path = "/flow/\(sessionId)"
        components.queryItems = [
            URLQueryItem(name: "web_view", value: "true"),
            URLQueryItem(name: "locale", value: "ru")
        ]

        webView.load(URLRequest(url: components.url!))
    }

    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        if let url = navigationAction.request.url,
           url.host == "remote.biometric.vision",
           url.path == "/finished" {
            decisionHandler(.cancel)
            dismiss(animated: true)
            return
        }

        decisionHandler(.allow)
    }
}
import android.os.Bundle
import android.webkit.PermissionRequest
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri

class BiometricActivity : AppCompatActivity() {
    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_biometric)

        val sessionId = intent.getStringExtra("session_id") ?: run {
            finish()
            return
        }
        webView = findViewById(R.id.webView)

        webView.settings.apply {
            javaScriptEnabled = true
            mediaPlaybackRequiresUserGesture = false
        }

        webView.webChromeClient = object : WebChromeClient() {
            override fun onPermissionRequest(request: PermissionRequest) {
                val videoCapture = request.resources.filter {
                    it == PermissionRequest.RESOURCE_VIDEO_CAPTURE
                }.toTypedArray()

                if (videoCapture.isNotEmpty()) {
                    request.grant(videoCapture)
                } else {
                    request.deny()
                }
            }
        }

        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(
                view: WebView,
                request: WebResourceRequest,
            ): Boolean {
                val url = request.url
                if (request.isForMainFrame &&
                    url.host == "remote.biometric.vision" &&
                    url.path == "/finished"
                ) {
                    finish()
                    return true
                }
                return false
            }
        }

        val url = "https://remote.biometric.vision/flow/$sessionId".toUri()
            .buildUpon()
            .appendQueryParameter("web_view", "true")
            .appendQueryParameter("locale", "ru")
            .build()

        webView.loadUrl(url.toString())
    }
}

Проверяйте разрешения до открытия WebView

Примеры показывают обработку запроса со стороны WebView, но не заменяют системный запрос разрешения камеры. Если пользователь запретил доступ на уровне ОС, остановите сценарий и покажите способ открыть настройки приложения.

4. Обработка завершения

После выполнения технологий WebView переходит на https://remote.biometric.vision/finished. Перехватите только переход основного фрейма с точным сочетанием host и path, отмените навигацию и закройте WebView.

Переход на /finished означает окончание интерфейсного сценария, но не является доверенным результатом проверки. После закрытия WebView запросите у backend клиентской системы актуальный результат и обновите экран приложения.

Обработчик завершения должен быть идемпотентным: повторный callback навигации не должен повторно закрывать экран или запускать бизнес-операцию.

5. Параметры URL

Параметр Тип Обязательно Описание
web_view boolean Да Значение true включает режим WebView
locale string Нет Язык интерфейса: kz, en, ru, my, de, es, fa, fr, it, ja, kg, ko, pt
isMobile boolean Нет Принудительно задаёт мобильный тип устройства
documentType string Нет Тип документа; доступные значения зависят от конфигурации Workflow
from_session_id string Нет UUID предыдущей сессии для связанного сценария

Пример URL:

https://remote.biometric.vision/flow/<session_id>?web_view=true&locale=ru&isMobile=true

6. Типовые проблемы

Проблема Причина Решение
Камера не запускается на iOS Inline playback выключен Установить allowsInlineMediaPlayback = true
Камера не запускается на Android Не выдано системное разрешение или отклонён запрос WebView Запросить CAMERA у пользователя и обработать onPermissionRequest
Отображается пустой экран JavaScript выключен Включить выполнение JavaScript в настройках WebView
Экран не закрывается после проверки Переход на /finished не перехвачен Проверить navigation delegate и сравнение host/path

Связанные разделы