Привет, Хабр!

Меня зовут Евгений, я Full Stack JS разработчик, текущий стек Node.js + React + React Native. В разработке я более 10 лет. В мобильной разработке пробовал разные инструменты от Cordova до React Native. Получив опыт работы с Cardova, я понял, что мне хотелось бы создавать нативные интерфейсы, на мой взгляд WebView не должно быть всем приложением. Но это не значит, что его не надо использовать вовсе.

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


Источник: srishta.com

Также немного расскажу о том, как вы можете использовать WebView и как его могут использовать против вас злоумышленники. Примеры в статье будут показаны с использованием фреймворка React Native, но те же идеи можно реализовать и без него.

Немного про стартапы


Начну с принципиальных отличий в запуске стартапов у нас и на Западе, расскажу, как здесь может помочь WebView, дам рабочие примеры взаимодействия веб и нативных элементов, а также советы по технике безопасности при взаимодействии со сторонними приложениями.

Как правило, чтобы стартап стал успешным, ему нужно быстро запуститься. Потеряешь время – и конкуренты тебя обойдут. Это понимают и у нас, и на Западе. Но российский подход к запуску, как правило, гораздо основательнее — в плохом смысле этого слова.

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

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

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

Предлагаю подробнее рассмотреть тот этап, когда уже есть мобильная версия сайта и нужно разрабатывать приложение для мобильных устройств. Итак, мы сделали сайт, а значит занимались разработкой серверного API, интерфейса и бизнес-логики. Два из трех компонентов –
— интерфейс и логика — присутствуют и в мобильном приложении. Согласитесь, не хочется писать их заново.

Объединяем лучшее от нативных и веб-приложений


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


* для уменьшения тавтологии веб-приложениями я назову мобильные приложения, основная часть логики и интерфейса которых реализована на стороне браузера

Объединить все достоинства нативных приложений и веба позволяют гибридные приложения, которые создают с помощью компонента WebView. Конечно, найдутся дотошные разработчики, которые категорически против WebView в любых его проявлениях. Они аргументируют это тем, что приложение должно сразу быть полностью нативным, чтобы можно было использовать все возможности мобильного устройства, а также обеспечить комфортную производительность пользовательского интерфейса. Но во многих случаях, когда возможностей мобильной версии сайта вполне достаточно, можно сократить время первого запуска, сделав гибридное приложение, и заменять его на нативное постепенно.


Гибридные приложения — это не всегда что-то плохое и не расширяемое. Они могут быть удобными и производительными. При грамотном использовании такой подход помогает получить достаточное время на разработку качественного приложения, а не выпускать нативное приложение на скорую руку.

Есть несколько ситуаций, в которых целесообразно использовать гибридные приложения. Они хороши в качестве временной заглушки для быстрого старта — когда у нас готова мобильная версия сайта, а мобильное приложение нужно было «вчера». Такое приложение можно создать за несколько часов, запустить в продакшн. Пользователи получат возможность работать с мобильным приложением, а вы — возможность работать над более полноценной версией в менее жестких временных рамках (если это нужно).

Вот пример. Недавно коллегам срочно понадобилось мобильное приложение. В веб-версии у него было восемь пунктов меню, и мы их отобразили через WebView. А потом по одному пункту заменяли. Так получилось выпустить приложение не через месяц-три, а буквально за несколько дней. После постепенно переводили его на натив.

Гибридное решение не всегда временное. Его возможности позволяют переиспользовать в приложении кодовую базу, созданную ранее для веб-версии. К примеру специфичные анимации уже созданные на Canvas. Также WebView удобен, когда используется какой-то сторонний сервис. Еще один вариант – когда у вас есть сложный интерфейс, который проще подключить через WebView.

Как использовать WebView


Возьмем популярный сценарий. Мы хотим использовать мобильную версию сайта и нативное меню. Мы создаем нативное приложение с меню, но вместо контента подключаем мобильную версию сайта через WebView (пока что без каких либо изменений).


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

export default (props) => (
  <Layout {...props} name="Example1">
    <WebView source={{uri: '<a href="https://provincehotels.ru/">https://provincehotels.ru/</a>'}} style={{flex:1}} />
  </Layout>
);

Следующий пример – о том, как нативная часть может взаимодействовать с вебом.


Робот нарисован на Canvas, это часть веб-сайта. А переключатель к нему построен на нативном UI. На разных телефонах он будет выглядеть по-разному. Мы можем управлять движениями робота при помощи переключателя. Можно и наоборот – какими-то элементами веб-интерфейса влиять на приложение. В React Native для этого предусмотрено специальный API для взаимодействия между вебом и нативной частью.

Ниже код для использования этой анимации. Layout — все пространство. Picker — нативная часть, которая может выбирать из dropdown варианты состояния робота. WebView — контейнер для отображения веба, внутри которого отрисовывается робот.

const states = [ 'Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing' ];
const uri = 'https://artexoid.ru/181212/example2/';

export default class Example2 extends React.Component {
  myWebView = React.<i>createRef</i>();
  
  state = {
    robot: states[0]
  };
  
  render() {
    return (
      <Layout { ...this.props } name="Example2">
        <Picker
          mode="dropdown"
          selectedValue={this.state.robot}
          onValueChange={(item) => {
            this.setState({robot: item});
            this.myWebView.current.postMessage(item);
          }}
        >
          {states.map((item => <Picker.Item key={item} label={item} value={item} /> ))}
        </Picker>
        <WebView ref={this.myWebView} source={{uri}} style={{flex:1}} />
      </Layout>
    );
  }
}

Подобные кейсы возникают часто. Например, мы сделали приложение для тестирования и аттестации стоматологов. Для каждого варианта ответа в тесте внутри вопросов рисовалась анимация, реализованная посредствам Canvas на вебе. Задача состояла в том, чтобы создать мобильное приложение, с этим тестированием. Использовав WebView, мы смогли отображать анимации из веба, тогда как остальной интерфейс мы построили нативно. Анимация отлично работала даже на старых смартфонах.

Как делаются инъекции


До 2013 года браузер Opera использовал собственный движок Presto, но потом его перевели на движок Blink от Google. Многих пользователей это очень расстроило. Свет на причины этого перехода проливает видео «Зачем опере вебкит». Главные виновники — большие корпорации типа Google или Facebook, которые не тестировали код своих продуктов в Opera и запрещали отображение страниц в этом браузере, ссылаясь на то, что он не достаточно популярен у пользователей.

Например, заходишь на Gmail через Opera и видишь: «Ошибка JavaScript». Пишешь в саппорт, получаешь ответ: «Opera у нас не поддерживается, мы не будем писать под нее код». Сначала компания Opera нанимала разработчиков, чтобы писать инъекции – специальный код, который встраивался в Gmail и позволял ему работать в Opera. Но постепенно таких сайтов, как Gmail, становилось все больше. Opera сдалась и сменила движок.

Так о чем это я? Ах да самое время поговорить об инъекциях:


На гифке – пример инъекции, которая изменяет поведение сайтов. Допустим, у нас есть чужой сайт, и мы делаем инъекцию стилей – скрываем правое меню и слайдер, выезжающий справа. Это – инъекция стилей. Логика работы сайта не меняется, только отображение.

const addStyles=`
  const newCSS = 'div[class*=svgHamburger],div[class*=drawerPanel] { display: none !important; }'
        head = document.head || document.getElementsByTagName('head')[0],
        style = document.createElement('style');
  style.type = 'text/css';
  style.appendChild(document.createTextNode(newCSS));

  head.appendChild(style);
`;
const uri = 'https://provincehotels.ru/';
export default (props) => (
  <Layout { ...props } name="Example1">
    <WebView source={{uri }} style={{flex:1}} injectedJavaScript={addStyles} javaScriptEnabled={true} />
  </Layout>
);

Код, написанный зеленым, — инъекция. Она скрывает элементы, на них нельзя нажать, с ними нельзя взаимодействовать. С виду получается полностью нативное приложение, без веб-элементов управления.

Следующая инъекция интереснее. Допустим, у нас есть мобильное приложение, а в нем — встроенный мобильный браузер.


Человек переходит по ссылке, и мы запросто подставляем ему страничку Фейсбука, в которой нужно ввести логин и пароль. Если человек его вводит – приложение его перехватывает. Вот код:

const addScripts=`
  document
    .querySelector('input[type=password]')
    .addEventListener("change", (event) => alert(event.target.value));
`;

const uri = 'https://www.facebook.com/';

export default (props) => (
  <Layout { ...props } name="Example4">
    <WebView source={{uri }} style={{flex:1}} injectedJavaScript={addScripts} javaScriptEnabled={true} />
  </Layout>
);

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

Минутка паранойи: браузеры, встроенные в приложения


Как известно, во многих приложениях есть встроенные браузеры (WebView) — например, ВКонтакте, Telegram, Gmail, WhatsApp и так далее. Крупным компаниям мы можем доверять, но WebView используется и огромным количеством приложений с малым количеством звезд и сомнительными авторами — к примеру QR-ридерами, файловыми менеджерами, оболочками для камер и т.п… Устанавливаешь приложение, читаешь через него код, нажимаешь на ссылку, вводишь конфиденциальные данные — и у приложения, как показано в предыдущем примере, появляется доступ к ним. А потом уже не отследишь, куда эти данные утекают. Поэтому для открытия ссылок пользуйтесь только браузерами, которым доверяете.

Есть сайты, которые запрашивают логин и пароль каждый раз. А есть такие, которые делают это редко — раз в месяц, раз в год. Как ни странно, второй вариант безопаснее с точки зрения утечки данных через WebView. Например, ты заходишь на сайт с какого-то левого браузера. Сайт требует логин и пароль, и тебе не кажется это странным – он всегда так делает. А в случае, когда авторизация требуется редко, это заставит насторожиться.

Интересно, что двухфакторная авторизация от такой атаки не защищает – только от кражи пароля. Дело в том, что после подтверждения тебе в ответ возвращается токен, который, в свою очередь, двухфакторной авторизации уже не имеет, и его легко перехватить. То есть если ты ввел логин и код с СМС один раз, то браузер получает токен, который можно использовать многократно. С этим подтвержденным токеном он может делать что хочет, в течение времени, пока токен остается актуальным. В общем, не стоит слишком доверять встроенным браузерам.

Познакомиться с примерами из этого поста можно через демо-приложения. На ОС Android нужно скачать Expo Project — инструмент для работы с JavaScript и React Native. После установки Expo останется только считать QR-код:



С устройствами под iOS сложнее: компания Apple запретила распространять приложения таким образом. Так что любопытствующим придется собрать приложение из исходников на GitHub. Спасибо за внимание!

Комментарии (9)


  1. keydon2
    25.02.2019 08:36

    Есть 2 версии
    1) веб
    -зависит от интернета
    -неотзывчивость, долго грузится
    -меньший функционал
    +не требует установки
    +просто пишется (на самом деле нет)
    +не занимает постоянно дисковое пространство
    2) нативная
    +не зависит от интернета
    +более отзывчивая
    +больший функционал
    +отзывчивость
    -тяжело пишется
    -требует установки
    -занимает дисковое пространство
    Давайте сделаем лучше!
    Гибрид
    -зависит от интернета
    -периодическая неотзывчивость, долго грузится
    -требует установки
    -по-прежнему тяжело пишется
    -занимает дисковое пространство
    +функционал как у нативной версии
    image


    1. MeGaPk
      25.02.2019 11:08

      это приложения которые заслужило человечество… </сарказм>


    1. Artexoid
      25.02.2019 20:06
      +1

      Гибрид.

      • Не обязательно зависит от интернета. А вообще это зависит от специфики контента… Приложение ютуб к примеру нативное, но без интернета им тоже сложновато пользоваться :-)
      • Что грузится если интерфейс нативный а бандл локальный, и даже то, что мы отображаем через webview это можно положить локально. А значит ничего не грузится :-). Вообще там шел разговор о кейсах в которых не важно гибрид у тебя или нет...
      • Любые приложения требуют установки.
      • Тут говорится о том, что у тебя уже есть веб версия и кодовая база которую ты можешь использовать.
      • А что есть приложения которые не занимают дисковое пространство?


    1. BATMAH69 Автор
      25.02.2019 20:50
      +1

      Основная идея статьи в том, что есть задачи, лучшим решением которых является гибрид, а не в том, что гибрид лучшее решение любой задачи.

      Спасибо за комментарий, надо будет лучше акцентировать это в статье.

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

      Соответственно плюсы и минусы стоит оценивать в разрезе конкретных примеров, а не в вакууме.


    1. rudinandrey
      26.02.2019 08:17

      ну например приложение погоды, ему все равно нужен Интернет. Тебе все равно хочется получить вот прям актуальные данные по погоде.
      Т.е. понятно что есть приложения типа калькуляторов, записных книжек, читалок, и многого такого другого которым Интернет не нужен. И даже такие приложения можно делать на той же Cordova, почему им обязательно нужен Интернет?
      Но 80% сейчас это приложения для работы с сервисами в Интернете ИМХО.


  1. inoyakaigor
    25.02.2019 14:06

    Крупным компаниям мы можем доверять

    Нет.


  1. Arfeo
    25.02.2019 22:06
    +1

    Как по мне, всё тлен, и гибриды, и нативы. Потому что много, кто умеет делать красиво, но мало кто оптимизирует. Вот и выходит, как на той фотке из известного мема, где чувак в костюме весь при параде, а сзади в зеркале — голая задница. Как можно на серьёзных щах, простите, вести дискуссии о «долго грузится» относительно веба, когда нативное, казалось бы приложение какого-нибудь мажорного банка, например, весит 100 мегабайт и грузится больше 10-15 секунд (с учетом загрузки и вывода информации на главные виджеты)? 100 мегабайт, Карл!!! Что оно в себе несет? Полчаса видео в формате mpeg4, новый альбом Егора Крида в формате AAC?? Да там просто таблички и графики разноцветные… Всё тлен, всё тлен.


  1. smanioso
    27.02.2019 17:08

    Swift. Сложно начинать.
    1. Запустил Xcode.
    2. Выбрал типовой проект.
    3. Запустил на своем iPhone.
    Очень сложно.


    1. BATMAH69 Автор
      27.02.2019 17:10

      Под словосочитанием «Сложно начинать» имеется ввиду получение минимальной рабочей версии своего продукта, а не типового.