В поисках новых площадок для деятельности, я обратил внимание на "виджеты" хрома, которыми пользуюсь давно, но никогда не придавал им значения. Тут же захотелось попробовать что-нибудь реализовать.
Идеей стало создание таск-менеджера для разработчика (и не только), который был бы всегда под рукой. В нем должны были отобразиться актуальные тикеты trello и jira, реквесты в gitlab и т.п. Это те вещи, которые я обычно искал, набирая ключевые слова в адресную строку браузера, типа "jira PM-20".

Расширение для хрома
Я точно знал, что расширения в браузерах — это обычные html-странички, подпитанные js-скриптами. Но при этом не имел представления, как их создавать. Первое, что выдал мне гугл по этому вопросу — статью на medium, а не официальную документацию. Это было даже лучше, т.к. это была обзорная статья простейшего виджета от идеи до публикации.
Статья дала мне представление о том, что разработка "виджета" — дело не слишком хитрое. Я тут же приступил к созданию "hello world!".

Оказалось, что нужно создать mainfest.json в корне проекта. Он описывает расширение: название, описание, автора, иконки, разрешения и т.п. Первую версию я сделал без изучения документации.

Первый manifest.json
Расширения позволяют запускать js-скрипты в фоне, которые делают что-то даже когда пользователь ими не пользуется. Этот функционал я пощупал очень поверхностно, просто, чтобы понять, как он может работать. Он просто менял заголовок в <h1> "виджета".
Интерес для меня представляла сама страничка html, которая показывается, при нажатии на иконку в браузере, она называется popup.html в моем манифесте.
Эту страничку, кстати, можно открыть в браузере как и любой сайт, только в качестве "протокола" будет chrome-extension, например chrome-extension://id-виджета-в-webstore/popup.html. Таким образом, вы можете просмотреть исходники любого расширения, что у вас установлено.
Работает она точно так же, как и любой веб-сайт, за исключением пары возможностей, например: переходы по ссылкам работают только с target="_blank". Есть и технические ограничения, управляемые разработчиком, например Content Security Policyили permissions к функционалу браузера, которые запрашиваются у пользователя.
После создания папки с manifest.json и popup.html внутри, можно уже запустить её как виджет. На служебной страничке хрома chrome://extensions есть кнопка Загрузить распакованное расширение. С помощью нее выбираем папку

и расширение тут же отображается в списке “виджетов” рядом с адресной строкой.
С этого момента расширение уже можно тестировать: изменять popup.html, и видеть изменения, переоткрыв “виджет” нажатием на его иконку.
Начнем с того, что возможность публикации первых 20 расширений стоит 5$. Происходит она в dashboard вебстора, для этого потребуется залить zip-архив содержимого папки расширения и поработать над рекламными текстами и картинками.

Подготовка к публикации расширения
Первая часть описания расширения будет отображаться из свойства description манифеста расширения, остальное дописывается в Detailed description в управлении расширением на webstore.
Расширение имеет гибкие настройки публикации: можно выбрать регионы мира для публикации, а также видимость расширения.

Нужно помнить, что если вы выбираете для публикации только РФ, то в остальных странах расширение не появится. Я наткнулся на это находясь в Таиланде: не мог понять почему, спустя 2 дня, расширение не ищется в сторе даже по прямому названию.
Я описал необходимые шаги для публикации простейшего расширения, чтобы процесс был понятен. Теперь хочу осветить некоторые детали для написания более сложного “виджета“.

Суть моего расширения в использовании чужих API для получения информации. Для этого мне нужен как минимум localStorage, чтобы запоминать токены авторизации.
Я воспользовался "permissions": ["storage"] в своем манифесте.
В большинстве случаев разработчики API предлагают OAuth2 протокол для авторизации.
Если вы не знакомы с этим протоколом: он предлагает безопасный процесс аутентификации и авторизации приложения от лица пользователя без доступа к логину/паролю со стороны этого приложения.
Протокол описывает несколько способов для авторизации. В идеале нужно использовать Authorization Code Flow, которое предполагает наличие бэкенда у приложения; API редиректит пользователя с Auth-кодом на приложение, а приложение на бэкенде обменивает его на токен.
Есть также упрощенный Implicit Flow, позволяющий авторизовываться без бэкенда: API после авторизации редиректит пользователя на приложение с токеном в URL.
Как же пользоваться этой “редиректной” авторизацией в расширении хрома? Заводить веб-сайт? Оказывается, что не обязательно.
Изначально я взялся интегрироваться с Gitlalb и Trello. С Gitlab все оказалось “просто”: отправляешь пользователя в админку к его Gitlab, чтобы он сделал токен и передал его тебе. С таким подходом долго возиться не пришлось, я просто сделал поле для ввода токена и описал как его получить.
Trello же предоставил OAuth2, я сразу же заметил, что в нем есть implicit flow, но немного странный: токен отображается на их странице /approve в таком виде

Не став углубляться, я так же сделал поле для ввода токена в своем расширении и описал пользователю, как это сделать.
Как обычно, хорошее решение приходит не сразу. Я на него наткнулся, когда начал интеграцию с Jira, которая имеет только Authorization Code Flow.
Как-то случайно я наткнулся на chrome.identity: функционал браузера, уже реализовавший все “костыли” для авторизации. Этот функционал требует включения identityв разрешениях манифеста. Я дополнил свой манифест: "permissions": ["storage", "identity"].
Как я уже и говорил: у каждого расширения есть URL типа chrome-extension://<id>/. Такой адрес ни для чего не годится, но chrome.identity предоставляет настоящий URL https://<app-id>.chromiumapp.org/*, который можно передать в API с OAuth2 в качестве redirectUrl. API, после авторизации, отправит пользователя на него с дополнительными параметрами, будь то authCode или token, а хром их подхватит и передаст в ваш js-callback расширения.
Для этого нужно воспользоваться chrome.identity.launchWebAuthFlow(), которое открывает страницу авторизации API в новом окне:
Сразу же скажу: это окно выглядит не совсем как окно основного браузера, что у меня вызвало бы вопросы, будь я обычным пользователем, т.к. оно похоже на какой-то фишинговый блок, а не окно. Может быть это только мое восприятие, может быть так только в моей операционке.

Окно авторизации chrome.identity.launchWebAuthFlow
Когда появляется идея реализовать какой-либо функционал, рекомендую гуглить chrome API, т.к. в нем уже достаточно много подобных фич, которые облегчат вам работу. Например полный manifest.json впечатляет:
Предварительно разбираться со всеми возможностями может оказаться слишком расточительным, т.к. их огромное количество.
К тому же только js-сторона расширения сама по себе может увеличиться до больших размеров.

Моя текущая схема
Немного стоит упомянуть продвижение своего расширения. Особенностью “виджетов” является то, что они направлены на пользователей десктопов, которые в последние годы успели стать меньшинством.
Я попробовал контекстную рекламу и рекламу в социальных сетях. Получил небольшой опыт и нулевую конверсию.
Мой опыт пока ограничился одной площадкой, не могу говорить за остальные, но думаю, там то же самое.
Оказывается, что в контекстной рекламе яндекса нельзя настраивать таргетинг по устройствам и браузерам. Так что если ваше расширение только для хрома, показывать рекламу придется и людям, сидящим на других браузерах, переходы которых принесут только лишние убытки.
Я не хотел делать отдельный сайт для своего расширения, поэтому рекламировал его страницу в webstore. Минус этого подхода в том, что ты можешь доверять только статистике рекламного кабинета и не можешь увидеть как пользователи ведут себя на рекламируемой странице.
Они как раз и относятся к категории сайтов, на которых десктоп это умирающее меньшинство.
Вконтакте предоставляет таргетинг на мобильную и полную версию своего сайта. Но эта галочка скрыта в самом конце настроек, лично я ее не заметил до того, как слил бюджет и увидел в статистике печальный охват

На самом деле в тот день мобильные просмотры были сильно больше
Я считаю, что расширения это мощный инструмент для людей, работающих в интернете, т.к. в браузере мы проводим много времени, которое иногда хочется оптимизировать. Например виджет гугл-переводчика, переводящий текст при выделении, хороший пример оптимизации. Решение проблемы огромного количества открытых вкладок “на попозже” я тоже решил с помощью виджета.
Написание расширений дает опыт в изучении подводной части айсберга “Chrome” и написании “фронтенда” (особенно если вы бэкенд-разработчик). Расширения можно писать на том же React JS с которого можно перекинуться на написание приложений под мобильные устройства. Процесс написания и того и того очень похожий.
Идеей стало создание таск-менеджера для разработчика (и не только), который был бы всегда под рукой. В нем должны были отобразиться актуальные тикеты trello и jira, реквесты в gitlab и т.п. Это те вещи, которые я обычно искал, набирая ключевые слова в адресную строку браузера, типа "jira PM-20".

Расширение для хрома
Я точно знал, что расширения в браузерах — это обычные html-странички, подпитанные js-скриптами. Но при этом не имел представления, как их создавать. Первое, что выдал мне гугл по этому вопросу — статью на medium, а не официальную документацию. Это было даже лучше, т.к. это была обзорная статья простейшего виджета от идеи до публикации.
Статья дала мне представление о том, что разработка "виджета" — дело не слишком хитрое. Я тут же приступил к созданию "hello world!".
Структура

Оказалось, что нужно создать mainfest.json в корне проекта. Он описывает расширение: название, описание, автора, иконки, разрешения и т.п. Первую версию я сделал без изучения документации.

Первый manifest.json
Расширения позволяют запускать js-скрипты в фоне, которые делают что-то даже когда пользователь ими не пользуется. Этот функционал я пощупал очень поверхностно, просто, чтобы понять, как он может работать. Он просто менял заголовок в <h1> "виджета".
Интерес для меня представляла сама страничка html, которая показывается, при нажатии на иконку в браузере, она называется popup.html в моем манифесте.
Эту страничку, кстати, можно открыть в браузере как и любой сайт, только в качестве "протокола" будет chrome-extension, например chrome-extension://id-виджета-в-webstore/popup.html. Таким образом, вы можете просмотреть исходники любого расширения, что у вас установлено.
Работает она точно так же, как и любой веб-сайт, за исключением пары возможностей, например: переходы по ссылкам работают только с target="_blank". Есть и технические ограничения, управляемые разработчиком, например Content Security Policyили permissions к функционалу браузера, которые запрашиваются у пользователя.
Запустить расширение
После создания папки с manifest.json и popup.html внутри, можно уже запустить её как виджет. На служебной страничке хрома chrome://extensions есть кнопка Загрузить распакованное расширение. С помощью нее выбираем папку

и расширение тут же отображается в списке “виджетов” рядом с адресной строкой.
С этого момента расширение уже можно тестировать: изменять popup.html, и видеть изменения, переоткрыв “виджет” нажатием на его иконку.
Публикация в webstore
Начнем с того, что возможность публикации первых 20 расширений стоит 5$. Происходит она в dashboard вебстора, для этого потребуется залить zip-архив содержимого папки расширения и поработать над рекламными текстами и картинками.

Подготовка к публикации расширения
Первая часть описания расширения будет отображаться из свойства description манифеста расширения, остальное дописывается в Detailed description в управлении расширением на webstore.
Расширение имеет гибкие настройки публикации: можно выбрать регионы мира для публикации, а также видимость расширения.

Нужно помнить, что если вы выбираете для публикации только РФ, то в остальных странах расширение не появится. Я наткнулся на это находясь в Таиланде: не мог понять почему, спустя 2 дня, расширение не ищется в сторе даже по прямому названию.
Продвинутые опции
Я описал необходимые шаги для публикации простейшего расширения, чтобы процесс был понятен. Теперь хочу осветить некоторые детали для написания более сложного “виджета“.

Суть моего расширения в использовании чужих API для получения информации. Для этого мне нужен как минимум localStorage, чтобы запоминать токены авторизации.
Я воспользовался "permissions": ["storage"] в своем манифесте.
OAuth2
В большинстве случаев разработчики API предлагают OAuth2 протокол для авторизации.
Если вы не знакомы с этим протоколом: он предлагает безопасный процесс аутентификации и авторизации приложения от лица пользователя без доступа к логину/паролю со стороны этого приложения.
Протокол описывает несколько способов для авторизации. В идеале нужно использовать Authorization Code Flow, которое предполагает наличие бэкенда у приложения; API редиректит пользователя с Auth-кодом на приложение, а приложение на бэкенде обменивает его на токен.
Есть также упрощенный Implicit Flow, позволяющий авторизовываться без бэкенда: API после авторизации редиректит пользователя на приложение с токеном в URL.
Как же пользоваться этой “редиректной” авторизацией в расширении хрома? Заводить веб-сайт? Оказывается, что не обязательно.
Мои костыли
Изначально я взялся интегрироваться с Gitlalb и Trello. С Gitlab все оказалось “просто”: отправляешь пользователя в админку к его Gitlab, чтобы он сделал токен и передал его тебе. С таким подходом долго возиться не пришлось, я просто сделал поле для ввода токена и описал как его получить.
Trello же предоставил OAuth2, я сразу же заметил, что в нем есть implicit flow, но немного странный: токен отображается на их странице /approve в таком виде

Не став углубляться, я так же сделал поле для ввода токена в своем расширении и описал пользователю, как это сделать.
Правильный способ
Как обычно, хорошее решение приходит не сразу. Я на него наткнулся, когда начал интеграцию с Jira, которая имеет только Authorization Code Flow.
Как-то случайно я наткнулся на chrome.identity: функционал браузера, уже реализовавший все “костыли” для авторизации. Этот функционал требует включения identityв разрешениях манифеста. Я дополнил свой манифест: "permissions": ["storage", "identity"].
Как я уже и говорил: у каждого расширения есть URL типа chrome-extension://<id>/. Такой адрес ни для чего не годится, но chrome.identity предоставляет настоящий URL https://<app-id>.chromiumapp.org/*, который можно передать в API с OAuth2 в качестве redirectUrl. API, после авторизации, отправит пользователя на него с дополнительными параметрами, будь то authCode или token, а хром их подхватит и передаст в ваш js-callback расширения.
Для этого нужно воспользоваться chrome.identity.launchWebAuthFlow(), которое открывает страницу авторизации API в новом окне:
chrome.identity.launchWebAuthFlow(
{
'url': JiraApi.url(),
'interactive': true
},
jira.callback()
);
Сразу же скажу: это окно выглядит не совсем как окно основного браузера, что у меня вызвало бы вопросы, будь я обычным пользователем, т.к. оно похоже на какой-то фишинговый блок, а не окно. Может быть это только мое восприятие, может быть так только в моей операционке.

Окно авторизации chrome.identity.launchWebAuthFlow
Другие опции
Когда появляется идея реализовать какой-либо функционал, рекомендую гуглить chrome API, т.к. в нем уже достаточно много подобных фич, которые облегчат вам работу. Например полный manifest.json впечатляет:
{
// Required
"app": {
"background": {
// Optional
"scripts": ["background.js"]
}
},
"manifest_version": 2,
"name": "My App",
"version": "versionString",
// Recommended
"default_locale": "en",
"description": "A plain text description",
"icons": {...},
// Optional
"action_handlers": ["new_note"],
"author": ...,
"automation": ...,
"bluetooth": {
"uuids": ["1105", "1006"]
},
"commands": {...},
"current_locale": ...,
"event_rules": [{...}],
"externally_connectable": {
"matches": ["*://*.example.com/*"]
},
"file_handlers": {...},
"file_system_provider_capabilities": {
"configurable": true,
"multiple_mounts": true,
"source": "network"
},
"import": [{"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
"key": "publicKey",
"kiosk": {
"always_update": ...,
"required_platform_version": ...
},
"kiosk_enabled": true,
"kiosk_only": true,
"kiosk_secondary_apps": ...,
"minimum_chrome_version": "versionString",
"nacl_modules": [...],
"oauth2": ...,
"offline_enabled": true,
"optional_permissions": ["tabs"],
"permissions": ["tabs"],
"platforms": ...,
"requirements": {...},
"sandbox": [...],
"short_name": "Short Name",
"signature": ...,
"sockets": {
"tcp": {
"connect": "*"
},
"udp": {
"send": "*"
}
},
"storage": {
"managed_schema": "schema.json"
},
"system_indicator": ...,
"update_url": "http://path/to/updateInfo.xml",
"url_handlers": {...},
"usb_printers": {
"filters": [...]
},
"version_name": "aString",
"webview": {...}
}
Предварительно разбираться со всеми возможностями может оказаться слишком расточительным, т.к. их огромное количество.
К тому же только js-сторона расширения сама по себе может увеличиться до больших размеров.

Моя текущая схема
Продвижение
Немного стоит упомянуть продвижение своего расширения. Особенностью “виджетов” является то, что они направлены на пользователей десктопов, которые в последние годы успели стать меньшинством.
Я попробовал контекстную рекламу и рекламу в социальных сетях. Получил небольшой опыт и нулевую конверсию.
Контекстная реклама
Мой опыт пока ограничился одной площадкой, не могу говорить за остальные, но думаю, там то же самое.
Оказывается, что в контекстной рекламе яндекса нельзя настраивать таргетинг по устройствам и браузерам. Так что если ваше расширение только для хрома, показывать рекламу придется и людям, сидящим на других браузерах, переходы которых принесут только лишние убытки.
Я не хотел делать отдельный сайт для своего расширения, поэтому рекламировал его страницу в webstore. Минус этого подхода в том, что ты можешь доверять только статистике рекламного кабинета и не можешь увидеть как пользователи ведут себя на рекламируемой странице.
Социальные сети
Они как раз и относятся к категории сайтов, на которых десктоп это умирающее меньшинство.
Вконтакте предоставляет таргетинг на мобильную и полную версию своего сайта. Но эта галочка скрыта в самом конце настроек, лично я ее не заметил до того, как слил бюджет и увидел в статистике печальный охват

На самом деле в тот день мобильные просмотры были сильно больше
Мысли напоследок
Я считаю, что расширения это мощный инструмент для людей, работающих в интернете, т.к. в браузере мы проводим много времени, которое иногда хочется оптимизировать. Например виджет гугл-переводчика, переводящий текст при выделении, хороший пример оптимизации. Решение проблемы огромного количества открытых вкладок “на попозже” я тоже решил с помощью виджета.
Написание расширений дает опыт в изучении подводной части айсберга “Chrome” и написании “фронтенда” (особенно если вы бэкенд-разработчик). Расширения можно писать на том же React JS с которого можно перекинуться на написание приложений под мобильные устройства. Процесс написания и того и того очень похожий.
Комментарии (8)
Vadem
03.12.2018 14:22Спасибо за статью.
Добавлю пару рекомендаций:
— Используйте сразу полифил для WebExtensions API от Mozilla.
Это не сильно усложнит разработку, зато сильно облегчит жизнь при переносе расширения на другие браузеры.
— Сразу настройте Hot Reload для расширения. Это сильно облегчает жизнь.
Я делал это с помощью Rollup, но с помощью Webpack тоже должно легко настраиваться.borisd_ru Автор
04.12.2018 05:10Спасибо за советы.
Я бэкенд-разработчик, поэтому пока что хожу по граблям js, писал свой код параллельно с перечитыванием книжки по javascript. Так что у меня пока что без пакетных менеджеров, но хочу поиграть с webpack. Про hot reload не задумывался до этого, штука и впрямь полезная.
altrus
Пару лет назад публиковал свое расширение в Chrome Web Store, чуть не поседел. Версий 30, наверно, запостил, пытаясь понять, что от меня хотят.
А в Mozille такие приятные люди оказались, оперативно посмотрели код, опубликовали, дали пару добрых советов.
Как оно сейчас?
По продвижению, кстати, могу сказать, что если дать расширению хорошее название, правильное описание и годный функционал, оно будет продвигать себя само (через поиск).
mechatroner
Забавно, у меня был прямо противоположный опыт: В Chrome Web Store удалось опубликовать сразу и без проблем а в Firefox приложение как минимум месяц висело на проверке, причем размер очереди проверки и ETA если и были указаны, то крайне приблизительно.
foxyrus
У меня конечно не 30, но 4 версии пришлось выпустить (причем проверяли вручную неделю). В конце концов пришлось добавить ссылку на Privacy Policy, кстати рекомендую автору его добавить.
altrus
У меня, кажется, ему какие-то паттерны в коде не нравились.
Но первичный выпуск расширения — это еще ладно. А вот когда понадобилось срочно выпустить обновление, а Google предложил поиграть в ту же игру (вкупе с их немой службой поддержки), вот это взбесило.
После этого сама идея разработки расширений для Хрома была отброшена.
borisd_ru Автор
Я пока что пробовал только в хроме. В планах было портирование на сафари и мозиллу, но нулевая конверсия поубавила желание.