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:
Параметр web_view=true обязателен. Он включает режим встраивания, в котором браузерные переходы после завершения заменяются переходом на служебный URL:
Формируйте 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:
6. Типовые проблемы¶
| Проблема | Причина | Решение |
|---|---|---|
| Камера не запускается на iOS | Inline playback выключен | Установить allowsInlineMediaPlayback = true |
| Камера не запускается на Android | Не выдано системное разрешение или отклонён запрос WebView | Запросить CAMERA у пользователя и обработать onPermissionRequest |
| Отображается пустой экран | JavaScript выключен | Включить выполнение JavaScript в настройках WebView |
| Экран не закрывается после проверки | Переход на /finished не перехвачен |
Проверить navigation delegate и сравнение host/path |