GitHub - core-ds/bridge-to-native: Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним. · GitHub
Skip to content

About

Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.

Resources

Stars

9 stars

Watchers

6 watching

Forks

Repository files navigation

BridgeToNative

Веб-библиотека для работы в вебвью «Нашего нативного приложения».

Особенности реализации вебвью модуля нативного приложения требуют определенных действий в т.ч. на сервере. В Библиотеке реализована необходимая логика для любых веб-серверов на базе Node.js Если в вашем веб приложении нет Node.js слоя, придётся реализовать необходимую логику самостоятельно. Разобраться, что нужно от сервера можно с помощью этого документа.

  • Как использовать Библиотеку описано здесь.
  • Про особенности навигации в вебвью окружении нативного приложение можно прочитать здесь.
  • Про работу в мини-приложениях (без экземпляра BridgeToNative) можно прочитать здесь.
  • Схематичное client-server взаимодействие внутри библиотеки описано здесь.

Сокращения, используемые во всех readme и комментариях в коде

  • BBtn (BackButton) — Кнопка «Назад» в нативной части экрана, которая может работать, как «Назад» в браузере.
  • B2N (BridgeToNative) — сокращенное название данной Библиотеки.
  • NA (Native application) — нативное приложение, для WebView которого написана эта библиотека.
  • WA (Web application) — любое веб-приложение, которое открывается внутри WebView.
  • WV (WebView) — область на экране нативного приложения, которая умеет отображать веб-контент.

Используемые термины

  • Legacy — так обозначается старый способ взаимодействия веб-натив, через setPageSettings, а также подход с передачей параметров о NA через query в URL и кастомные HTTP-заголовки.
  • Modern — новый контракт взаимодействия веб-натив/натив-веб, в противовес Legacy, от которого постепенно будем отказываться.

Зачем это нужно

WV NA окружение отличается от браузерного, есть определенные сложности при работе внутри. Главная сложность — навигация в этом окружении.

Вот несколько особенностей:

  • Экран с WV содержит как UI-элементы NA, так и область для отрисовки веб-контента;
  • WA может влиять на поведение NA;
  • Поведение WV модуля NA в iOS и WV модуля NA в Android отличается во многих сценариях;
  • Пользователи используют разные версии NA (особенно в iOS), разные версии NA имеют свои особенности.

Данная Библиотека:

  • умеет собирать данные о NA, внутри которого она запущена;
  • учитывает вышеуказанные «особенности»;
  • предоставляет единый интрефейс для работы внутри NA разных версий, разных ОС.

Структура Библиотеки

Состоит из двух частей:

  • серверная часть:
    • необходима, чтобы распарсить запрос от NA и сохранить данные в cookie для клиентской части;
    • реализована на Node.js;
      • стараемся поддерживать совместимость с различными Node.js фреймворками и веб-серверами;
      • обработку запросов с WV на других ЯП/технологиях придётся делать руками 🙂 (см. логику src/server/prepare-native-app-details-for-client.ts)
  • клиентская часть:
    • предоставляет методы для работы WA внутри WV NA;
    • предоставляет чистые функции для режима без экземпляра BridgeToNative (отдельная точка входа client/primitives).

Обычно на клиентской стороне достаточно экземпляра класса BridgeToNative. Это фасад, который проксирует все публичные методы Библиотеки внутренних классов. Исключение — чистые функции из client/primitives: их же использует BridgeToNative.

Проксируемые публичные методы не описаны по месту их реализации, описание находится в классе BridgeToNative (чтобы не повторяться). А все приватные методы описаны (если есть необходимость) в месте их реализации.


Использование

Установите:

npm i @alfalab/bridge-to-native

Используйте функции Библиотеки на стороне сервера (на примере Express)

import { isWebviewEnv, prepareNativeAppDetailsForClient } from '@alfalab/bridge-to-native/server';

//...

app.get('/my-ssr-route', (req, res) => {
    // B2N проверит, что запрос пришёл из WV окружения.
    // Можно не использовать, если обработчик обслуживает запросы только из WV.
    const isWebview = isWebviewEnv(req);

    if (isWebview) {
        // B2N соберёт из запроса детали о NA и установит cookie для клиента.
        prepareNativeAppDetailsForClient(req, (headerKey, headerValue) => {
            res.set({
                headerKey: headerValue,
            });
        });
    }

    // Что-то делаем ещё и отправляем ответ клиенту...
});

Инициализируйте основной класс B2N в клиентском коде

import { BridgeToNative } from '@alfalab/bridge-to-native/client';

// Лучше использовать один экземпляр-синглтон.
window.b2n = new BridgeToNative();

// ...

Используйте методы Библиотеки в клиентском коде

// ...
window.b2n.setTitle('Заголовок для верхней панели NA');
// ...
window.b2n.openInBrowser('https://ya.ru');
// ...

Мини-приложения

С развитием NA появился ещё один режим WV — мини-приложения в нативном контейнере. BridgeToNative в этом контейнере нет.

Для этого режима есть отдельная точка входа @alfalab/bridge-to-native/client/primitives. В ней лежат кирпичики, из которых собран сам BridgeToNative: чистые функции сборки нативных URL и проверки версий NA — всё, чему не нужен экземпляр, состояние и window. Класс их же и использует, поэтому результат совпадает, а код класса в эту точку входа не попадает. Вход не ограничен мини-приложениями: подойдёт любому WA, которому нужны только эти функции. Их же экспортирует и @alfalab/bridge-to-native/client. Платформу, appId и версию NA потребитель определяет сам; навигацию (location.replace), закрытие WV и защиту от повторных вызовов — тоже.

import {
    canUseNativeFeature,
    isVersionHigherOrEqual,
    prepareNativeDeeplinkUrl,
    prepareOpenInBrowserUrl,
    prepareOpenInNewWebviewDeeplink,
    preparePdfUrl,
    type NativeAppTarget,
} from '@alfalab/bridge-to-native/client/primitives';

const target: NativeAppTarget = { platform: 'ios', appId: 'aweassist' };

prepareNativeDeeplinkUrl(target, 'alfabank:///dashboard/pfm'); // 'aweassist://pfm?fromCurrent=true'

// Флаг — результат проверки версии NA или `true`, если NA заведомо умеет открывать ссылки в браузере.
const linksInBrowser = canUseNativeFeature('ios', '13.3.0', 'linksInBrowser');

prepareOpenInBrowserUrl(target, 'https://ya.ru', linksInBrowser); // 'https://ya.ru/?openInBrowser=true'
prepareNativeDeeplinkUrl(target, prepareOpenInNewWebviewDeeplink('https://ya.ru', 'Заголовок'));
preparePdfUrl(target, 'https://example.com/file.pdf', 'pdfFile', 'Выписка');
  • prepareNativeDeeplinkUrl отрезает у диплинка один из префиксов /, alfabank:///dashboard/, alfabank:///, alfabank://, https://online.alfabank.ru/; остальные схемы и хосты не трогает. На iOS добавляет fromCurrent=true.
  • prepareOpenInBrowserUrl и prepareOpenInNewWebviewDeeplink принимают абсолютные http(s) URL и безопасные нативные диплинки (например, alfabank://webFeature?...). Бросают invalid url: … для javascript:, data:, file:, blob:, vbscript:, ftp: и URL с логином/паролем.
  • isVersionHigherOrEqual сравнивает версии формата x.y.z покомпонентно, как числа; версия в другом формате даёт false. Нужен для гейтов по версии NA за пределами известных фич.

Навигация

NA при открытии экрана c WV вместе с областью для веб-контента отображает свои UI-элементы. Один из которых — кнопка «Назад» (BBtn), которая может работать, как «Назад» в Браузере, а может закрыть VW. Чтобы синхронизировать работу BBtn с историей браузера, необходимо для навигации а рамках WA использовать ТОЛЬКО следующие методы B2N:

  • navigateClientSide — soft навигация вперед (History: pushState())
  • navigateServerSide — hard навигация вперед (Location: assign())
  • goBack — soft или hard навигация назад (History: back())
  • goBackAFewSteps — навигация на несколько шагов назад (History: go(-x)); С версии 1.4.0 подходит и для hard навигации, в т.ч. в рамках разных origin (см. описание в JSDoc метода).
  • replaceHistoryState — замена history.state и/или URL текущей записи (History: replaceState())

Каждый метод содержит JSDoc c описанием назначения и ограничений.

Пример

// Только что открылось WV, находимся на /page-1,
// Если нажать BBtn, экран с WV будет закрыт.

window.b2n.navigateClientSide('/page-2');

// Находимся на /page-2,
// Если нажать на BBtn, она сработает как «Назад» в браузере, вернув на `/page-1`.

window.b2n.navigateClientSide('/page-3');

// Находимся на /page-3,
// Если нажать на BBtn, она сработает как «Назад» в браузере, вернув на `/page-2`.

window.b2n.goBack();

// Вернулись на /page-2,
// Если нажать на BBtn, она сработает как «Назад» в браузере, вернув на `/page-1`.

window.b2n.goBack();

// Вернулись на /page-1,
// Если нажать BBtn, экран с WV будет закрыт.

Проблематика hard (server side) навигации в контексте работы WV NA

  1. Особенности WV NA заставляют WA хранить состояние синхронизации с NA. При использовании server-side навигации это состояние теряется, приходиться как-то передавать его дальше. Это на себя берёт Библиотека.

  2. Внутри NA такие переходы как правило имеют плохой UX (низкая отзывчивость). WV-браузер продолжает показывать исходную страницу пока не получит ответ на запрос целевой страницы, а NA не показывает никакой индикации. После получения ответа на HTML запрос:

    • NA в iOS и дальше не показывает никакой индикации.
    • NA в Android начинает показывать нативный спиннер с фоном (полностью перекрывает веб-контент) пока грузятся «синхронные» ресурсы из HTML.
  3. Также при переходе server-side навигацией целевое WA может быть недоступно по какой-либо причине. NA никак не обрабатывает такие ошибки.

  4. BBtn никогда не блокируется, пользователь может бесконтрольно использовать её, во время процесса навигации. До версии 1.4.0 в случае, если была использована hard навигация (navigateServerSide), этот сценарий сломает синхронизацию с NA. В версии 1.4.0 эта «особенность» учитывается, но потребители B2N должны использовать метод Библиотеки replaceHistoryState вместо прямых вызовов history.replaceState (или оберток над ним, типа history.replace из ReactRouter).

Что предлагает Библиотека

Из-за «второго и третьего пунктов» использовать server-side навигацию не рекомендуется. Переход к другому WA лучше делать через открытие нового WV (см. метод openInNewWebview). Хотя делать это тоже нужно осторожно, т.к. каждое открытое WV утилизирует ресурсы устройства.

B2N предоставляет метод navigateServerSide для server-side навигации. Но WA должно учитывать «второй пункт» и показывать пользователю какую-то индикацию перехода, блокировать кнопки (BBtn заблокировать нет возможности) и т.п. При этом «Третий пункт» не решить на стороне веба и надо иметь его ввиду.

При navigateServerSide библиотека переиспользует уже существующий query device_app_version, чтобы целевое WA могло восстановить appVersion даже если на следующем HTML-запросе не будет заголовка app-version. Исторически этот query приходит из iOS, но B2N сознательно не вводит для этого отдельный B2N-специфичный параметр и использует его как transport-поле и для Android.


Дополнительные методы взаимодействия с NA

В BridgeToNative добавлены методы getGeoConfiguration, vibrate, setSheetDragArea, hideTabbar и showTabbar: для получения конфигурации геолокации, тактильного отклика, настройки перетаскивания нативной шторы и управления таббаром. Эти методы доступны в iOS начиная с версии 17.0.0.

Фича-тоглы

Для всех методов NativeCommandsService (getGeoConfiguration, vibrate, setSheetDragArea, hideTabbar, showTabbar) должен быть включён общий фича-тогл (mainSuperAppIOS для iOS). Он включает архитектуру мини-приложений, при которой WV открываются в шторах. Для vibrate дополнительно требуется нативный фича-тогл webViewHapticsIOS.

Без соответствующих включённых ФТ функциональность не работает: NA игнорирует команды. Библиотека не имеет доступа к нативным ФТ и проверяет только платформу и версию NA. На Android или iOS ниже 17.0.0 использование команд вызовет предупреждение.

В новой версии с мини-приложениями можно столкнуться с багом при наличии перетаскиваемых элементов в WA. Шторка может перехватывать жесты, и ранее работавшее перетаскивание в WA может перестать работать. Для таких экранов ограничьте область перетаскивания только через навигационную панель:

window.b2n.setSheetDragArea('navigationBar');

Процесс релиза

Для выпуска новой версии библиотеки в npm необходимо создать GitHub Release. При этом нужно:

  1. Указать semver тег (major.minor.patch).
  2. Заполнить Release notes в формате Markdown, описав изменения в разделах: Features, Bugfixes, BREAKING CHANGES (см. пример в CHANGELOG.md). Не используйте заголовки первого уровня при описании изменений, так как они используются для основных заголовков версии.

После публикации GitHub Release автоматически запускается workflow, который:

  • Обновляет версию в package.json согласно тегу;
  • Обновляет CHANGELOG.md;
  • Создает релизный Pull Request с этими изменениями со специальным тегом release.

После мержа релизного PR запустится следующий workflow, который:

  • Опубликует версию в npm;
  • Добавит архив с актуальными ассетами в созданный релиз. Таким образом ассеты с актуальным CHANGELOG.md будут лежать в архиве release-x.y.z.zip

Бета-версии

Для выпуска beta-версии достаточно добавить в сообщение коммита строку deploy_beta. Выпущенная beta-версия будет автоматически опубликована с тегом beta. Выпущенную бета-версию можно посмотреть в разделе Actions в соответствующем workflow.

About

Утилита для удобной работы веб приложения внутри нативного приложения и коммуникации с ним.

Resources

Stars

9 stars

Watchers

6 watching

Forks

Releases

Used by

Contributors

Languages