Как сделать расширение для браузера

Обновлено: 04.07.2024

Почему разработчикам сложно монетизировать свои проекты и как избежать установки вредоносных расширений.

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

В своей публикации Кребс рассказывает о сингапурской компании Infatica с русским основателем Владимиром Фоменко. Infatica предоставляет услуги веб-прокси необычным способом: компания договаривается с разработчиками расширений, чтобы те незаметно интегрировали код прокси-сервиса Infatica в свои проекты.

В результате через браузер пользователя расширения идёт маршрутизация трафика клиентов Infatica, взамен разработчик получает фиксированную оплату от $15 до $45 за каждую тысячу активных пользователей.

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

Как устроена экономика между расширениями и Infatica

Некоторые расширения для браузеров Apple, Google, Microsoft и Mozilla собирают сотни тысяч, а то и миллионы активных пользователей. По мере роста аудитории автор расширения может не справляться с поддержкой проекта — его обновлениями или ответами на запросы пользователей.

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

Например, так поступил разработчик расширения для тестирования сайтов ModHeader Хао Нгуен, сервисом которого пользуется более 400 тысяч человек.

Когда Нгуен понял, что тратит всё больше денег и времени на поддержку ModHeader, он попытался включить рекламу в расширении, но после большого протеста пользователей ему пришлось отказаться от этого. Более того, реклама не приносила ему много денег.

«Я потратит как минимум 10 лет на создание этой штуки, и мне не удалось её монетизировать», — признаётся Нгуен. Частично он винит Google за закрытие платных расширений — по его словам, это лишь усугубило проблему разочарованных разработчиков.

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

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

У Infatica код был более простым — они ограничились маршрутизацией запросов без получения доступа к сохраненным паролям пользователей, чтению их cookie или просмотру экрана пользователя. К тому же, сделка принесла бы Нгуену не менее $1500 в месяц.

Он согласился, но за несколько дней получил множество негативных отзывов пользователей и удалил код Infatica. К тому же расширение стали использовать для просмотра «не очень хороших мест, таких как порносайты», отмечает автор ModHeader.

Также глава Infatica владеет VPN-сервисом iNinja VPN с аудиторией 400 тысяч пользователей. Он тоже использует те же системы для маршрутизации трафика — расширение для Chrome и одноименный блокировщик рекламы, код которого содержит Infatica.

Работа Infatica похожа на HolaVPN — VPN-сервис с расширением для браузера. В 2015 году исследователи кибербезопасности обнаружили, что установивших расширение Hola использовали для перенаправления трафика других людей.

Маркетинговая команда Infatica как раз сравнивает свою бизнес-модель с моделью HolaVPN, замечает Кребс.

Скриншот из коммерческого предложения Infatica, отправленного разработчику расширения SponsorBlock

По данным Chrome-stats более 100 тысяч расширений заброшены авторами или не обновлялись более двух лет. Это существенный пласт разработчиков, которые вполне могут согласиться на продажу своего проекта и его пользовательской базы, заключает Кребс.

Сколько расширений используют код Infatica неизвестно — Кребс нашёл как минимум три десятка, у нескольких из них было более 100 тысяч пользователей. Одно из них — Video Downloader Plus, аудитория которого составляла в пике 1,4 млн активных пользователей.

Права доступа каждого расширения прописаны в его «манифесте» — описание доступно во время его установки. По данным Chrome-stats, около трети всех расширений Chrome не требуют особых разрешений, но остальные требуют полного доверия со стороны пользователя.

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

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

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

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

И всегда стоит придерживаться первого правила сетевой безопасности: «Если вы это не искали, то и не устанавливайте».

Мне иногда кажется, что реализация "расширений" это какой-то сюр, всеобщий пранк или глобальный заговор создателей браузеров.

Всем известен факт, что расширения имеют огромную популярность и невероятно полезны (Добавляют полезный разным пользователям функционал, на который нет времени \ мотивации у официальных разработчиков)

Всем известен факт, что их текущая реализация сломана и АБСОЛЮТНО небезопасна. (Большинству расширений почему-то требуется иметь доступ, чуть ли не вообще ко всему, что есть у пользователя, как в браузере, так и вне его - при том, что само расширение может представлять из себя "приколюху для дорисовывания смайлика к картинкам с котятами". И это не косяк приложений. Почему приложение для скачивания видео не может иметь доступ ТОЛЬКО к видео? Почему оно обязательно должно иметь доступ ко всем данным с сайта?

Всем известно, что их монетизация сделана ужасно. И как следствие БУДЕТ много случаев всяких мошенничеств (т.к большинство хочет вознаграждение на свой труд)

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

Хоть кто-нибудь с этим что-то делает или пытается что-либо предпринять? Нет. Есть этому какое-то логическое объяснение?

Даже в конце статьи этот мастер по кибербезопасности предлагает "читать требования расширений" и "удалять те, что имеют доступ ко всем данным", при том, что сам же выше пишет, что 70% расширений в магазине имеют доступ ко всем данным.

На хабре уже есть несколько статей о создании расширений для хрома, поделюсь своим опытом, затронув основные вещи и места, в которых у меня возникли трудности.
Что понадобится для создания расширения в двух словах:
1) Базовые знания Javascript
2) Базовые знания HTML
3) 5$

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

Итак, я начинаю создание расширения с создания папки самого расширения, в которую будем класть все создаваемые нами файлы. Назову её «losttime». Далее, я создаю файл manifest.json, выглядит он следующим образом:

manifest.json


Некоторые из строк должны быть интуитивно понятны, но что обязательно нужно знать:
— Значение manifest_version должно быть обязательно «2»;
— В content_scripts пишем, какой скрипт будет запускаться на всех страницах отдельно;
— В background пишем общий скрипт(фоновый скрипт), который запускается при запуске браузера;
— В permissions пишем адрес сайта, с которого будет браться информация.

Все, что буду использовать я, не обязательно использовать Вам, если вам это по логике просто не нужно. Подробнее о манифесте.

То самое окошко, которое Вы можете видеть по клику на иконку расширения — это страница: popup.html.


Она у меня выглядит следующим образом:


Чтобы было понятнее, описание кода вставил в самом HTML. Меню я организую просто: на картинку ставлю внутреннюю ссылку расширения.

Раз уж начал про popup.html, то расскажу сразу и о popup.js

Выглядит он у меня весьма просто:

Далее, рассмотрим фоновый скрипт background.js, где и происходит приём данных, а точнее рассмотрим саму функцию приёма данных.

background.js


Вот, собственно, и она. Разбирать подробно ничего не стану, т.к. это в принципе и не нужно. Достаточно знать наглядный пример, чтобы осуществить задуманное. Если в скрипте background.js добавить какие-либо данные в локальное хранилище( а также куки, web sql), то эти же данные можно будет использовать и в popup.js скрипте.

Вот собственно всё, что я хотел поведать о создании расширения, но я затрону еще один момент, в котором у меня возникли трудности.

На странице настроек мне необходимо было организовать перетаскивание сайтов в разные колонки.


Т.к. данные вставляются посредством InnerHtml, то данная возможность просто так не появится. Вот, что пришлось организовать:


Думаю, объяснять не нужно. Почитать подробнее можете по ссылке

Тестирование расширения

Заходим в Настройки — Инструменты — Расширения, жмем на «Загрузить распакованное расширение»

Публикация расширения
Заходим на страницу оплачиваем 5$, публикуем.
Я не останавливаюсь на моментах, с которыми у меня не возникли трудности. А трудности возникли при оплате карточкой:
— В моём случае должен быть подключен 3д пароль.
Если Вам при оплате пишет ошибку, звоните своему банку и узнавайте. Мне за минуту помогли и всё гуд.


В отличие от распространенной "клиент-серверной" архитектуры, для децентрализованных приложений характерно:

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

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

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

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

Краткая история браузерных расширений

Браузерные расширения существуют достаточно давно. В Internet Explorer они появились еще в 1999-м году, в Firefox — в 2004-м. Тем не менее, очень долго не было единого стандарта для расширений.

Можно сказать, что он появился вместе с расширениями в четвертой версии Google Chrome. Конечно, никакой спецификации тогда не было, но именно API Chrome стал ее основой: завоевав большую часть рынка браузеров и имея встроенный магазин приложений, Chrome фактически задал стандарт для браузерных расширений.

У Mozilla был свой стандарт, но, видя популярность расширений для Chrome, компания решила сделать совместимый API. В 2015 году по инициативе Mozilla в рамках World Wide Web Consortium (W3C) была создана специальная группа для работы над спецификациями кроссбраузерных расширений.

За основу был взят уже существующий API расширений для Сhrome. Работа велась при поддержке Microsoft (Google в разработке стандарта участвовать отказался), и в результате появился черновик спецификации.

Формально спецификацию поддерживают Edge, Firefox и Opera (заметьте, что в этом списке отсутствует Chrome). Но на самом деле стандарт во многом совместим и с Chrome, так как фактически написан на основе его расширений. Подробнее о WebExtensions API можно прочитать здесь.

Структура расширения

Единственный файл, который обязательно нужен для расширения — манифест (manifest.json). Он же является “точкой входа” в расширение.

Манифест

По спецификации файл манифеста является валидным JSON файлом. Полное описание ключей манифеста с информацией о том, какие ключи в поддерживается в каком браузере, можно посмотреть здесь.

Ключи, которых нет в спецификации, “могут” быть проигнорированы (и Chrome, и Firefox пишут об ошибках, но расширения продолжают работать).

А я бы хотел обратить внимание на некоторые моменты.

  1. background — объект, который включает в себя следующие поля:
    1. scripts — массив скриптов, которые будут выполнены в background-контексте (поговорим об этом чуть позже);
    2. page — вместо скриптов, которые будут выполнятся в пустой странице, можно задать html с контентом. В этом случае поле script будет проигнорировано, а скрипты нужно будет вставить в страницу с контентом;
    3. persistent — бинарный флаг, eсли не указан, то браузер будет "убивать" background-процесс, когда посчитает, что он ничего не делает, и перезапускать при необходимости. В противном случае страница будет выгружена только при закрытии браузера. Не поддерживается в Firefox.
    1. matches — паттерн url, по которому определяется, будет включаться конкретный content script или нет.
    2. js — список скриптов которые будут загружены в данный матч;
    3. exclude_matches — исключает из поля match URL, которые удовлетворяют этому полю.
    1. default_popup — путь до HTML файла с popup-интерфейсом, может содержать CSS и JS.

    Контекст выполнения

    У расширения есть три контекста исполнения кода, то есть, приложение состоит из трех частей с разным уровнем доступа к API браузера.

    Extension context

    Здесь доступна большая часть API. В этом контексте "живут":

    1. Background page — “backend” часть расширения. Файл указывается в манифесте по ключу “background”.
    2. Popup page — popup страница, которая появляется при нажатии на иконку расширения. В манифесте browser_action -> default_popup .
    3. Custom page — страница расширения, "живущая" в отдельной вкладке вида chrome-extension://<id_расширения>/customPage.html .

    Этот контекст существует независимо от окон и вкладок браузера. Background page существует в единственном экземпляре и работает всегда (исключение — event page, когда background-скрипт запускается по событию и "умирает" после его выполнения). Popup page существует, когда открыто окно popup, а Custom page — пока открыта вкладка с ней. Доступа к другим вкладкам и их содержимому из этого контекста нет.

    Content script context

    Файл контент-скрипта запускается вместе с каждой вкладкой браузера. У него есть доступ к части API расширения и к DOM-дереву веб-страницы. Именно контент-скрипты отвечают за взаимодействие со страницей. Расширения, манипулирующие DOM-деревом, делают это в контент-скриптах – например, блокировщики рекламы или переводчики. Также контент-скрипт может общаться со страницей через стандартный postMessage .

    Web page context

    Это собственно сама веб-страница. К расширению она не имеет никакого отношения и доступа туда не имеет, кроме случаев, когда в манифесте явно не указан домен этой страницы (об этом — ниже).

    Сервер или background:

    Также есть событие onDisconnect и метод disconnect .

    Схема приложения

    Давайте сделаем браузерное расширение, которое хранит приватные ключи, предоставляет доступ к публичной информации (адрес, публичный ключ общается со страницей и позволяет сторонним приложениям запросить подпись транзакций.

    Разработка приложения

    Наше приложение должно как взаимодействовать с пользователем, так и предоставлять странице API для вызова методов (например, для подписи транзакций). Обойтись одним лишь contentscript не получится, так как у него есть доступ только к DOM, но не к JS страницы. Подключаться через runtime.connect мы не можем, потому что API нужен на всех доменах, а в манифесте можно указывать только конкретные. В итоге схема будет выглядеть так:


    Будет еще один скрипт — inpage , который мы будем инжектить в страницу. Он будет выполняться в ее контексте и предоставлять API для работы с расширением.

    Начало

    Весь код браузерного расширения доступен на GitHub. В процессе описания будут ссылки на коммиты.

    Начнем с манифеста:

    Создаем пустые background.js, popup.js, inpage.js и contentscript.js. Добавляем popup.html — и наше приложение уже можно загрузить в Google Chrome и убедиться, что оно работает.

    Чтобы убедиться в этом, можно взять код отсюда. Кроме того, что мы сделали, по ссылке настроена сборка проекта с помощью webpack. Чтобы добавить приложение в браузер, в chrome://extensions нужно выбрать load unpacked и папку с соответствующим расширением — в нашем случае dist.


    Теперь наше расширение установлено и работает. Запуститьинструменты для разработчиков для разных контекстов можно следующим образом:



    Доступ к консоли контент-скрипта осуществляется через консоль самой страницы, на которой он запущен.

    Это браузерное расширение для работы с сетью Ethereum. В нем разные части приложения общаются через RPC при помощи библиотеки dnode. Она позволяет достаточно быстро и удобно организовать обмен, если в качестве транспорта ей предоставить nodejs stream (имеется в виду объект, реализующий тот же интерфейс):

    Теперь мы создадим класс приложения. Оно будет создавать объекты API для popup и веб-страницы, а также создавать dnode для них:

    Создадим инстанс приложения в background скрипте:

    Так как dnode работает со стримами, а мы получаем порт, то необходим класс-адаптер. Он сделан при помощи библиотеки readable-stream, которая реализует nodejs-стримы в браузере:

    Теперь создаем подключение в UI:

    Затем мы создаем подключение в content script:

    Так как API нам нужен не в контент-скрипте, а непосредственно на странице, мы делаем две вещи:

    1. Создаем два стрима. Один — в сторону страницы, поверх postMessage. Для этого мы используем вот этот пакет от создателей metamask. Второй стрим — к background поверх порта, полученного от runtime.connect . Пайпим их. Теперь у страницы будет стрим до бэкграунда.
    2. Инжектим скрипт в DOM. Выкачиваем скрипт (доступ к нему был разрешен в манифесте) и создаем тег script с его содержимым внутри:

    Теперь создаем объект api в inpage и заводим его global:

    У нас готов Remote Procedure Call (RPC) с отдельным API для страницы и UI. При подключении новой страницы к background мы можем это увидеть:


    Пустой API и origin. На стороне страницы мы можем вызвать функцию hello вот так:


    Работать с callback-функциями в современном JS — моветон, поэтому напишем небольшой хелпер для создания dnode, который позволяет передавать в объект API в utils.

    Объекты API теперь будут выглядеть вот так:

    Получение объекта от remote следующим образом:

    А вызов функций возвращает промис:


    Версия с асинхронными функциями доступна здесь.

    В целом, подход с RPC и стримами кажется достаточно гибким: мы можем использовать steam multiplexing и создавать несколько разных API для разных задач. В принципе, dnode можно использовать где угодно, главное — обернуть транспорт в виде nodejs стрима.

    Внутренний стейт и localStorage

    Нам понадобится хранить внутренний стейт приложения — как минимум, ключи для подписи. Мы можем достаточно легко добавить стейт приложению и методы для его изменения в popup API:

    В background обернем все в функцию и запишем объект приложения в window, чтобы можно было с ним работать из консоли:

    Добавим из консоли UI несколько ключей и посмотрим, что получилось со стейтом:


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

    Хранить будем в localStorage, перезаписывая при каждом изменении. Впоследствии доступ к нему также будет необходим для UI, и хочется также подписываться на изменения. Исходя из этого удобно будет сделать наблюдаемое хранилище (observable storage) и подписываться на его изменения.

    Добавим инициализацию начального стейта и сделаем store observable:

    "Под капотом" mobx заменил все поля store на proxy и перехватывает все обращения к ним. На эти обращения можно будет подписываться.

    Далее я буду часто использовать термин “при изменении”, хотя это не совсем корректно. Mobx отслеживает именно доступ к полям. Используются геттеры и сеттеры прокси-объектов, которые создает библиотека.

    Декораторы action служат двум целям:

    1. В строгом режиме с флагом enforceActions mobx запрещает менять стейт напрямую. Хорошим тоном считается работа именно в строгом режиме.
    2. Даже если функция меняет стейт несколько раз – например, мы меняем несколько полей в несколько строк кода, — обсерверы оповещаются только по ее завершении. Это особенно важно для фронтенда, где лишние обновления стейта приводят к ненужному рендеру элементов. В нашем случае ни первое, ни второе особо не актуально, однако мы будем следовать лучшим практикам. Декораторы принято вешать на все функции, которые меняют стейт наблюдаемых полей.

    В background добавим инициализацию и сохранение стейта в localStorage:

    Интересна здесь функция reaction. У нее два аргумента:

    1. Селектор данных.
    2. Обработчик, который будет вызван с этими данными каждый раз, когда они изменяются.

    В отличие от redux, где мы явно получаем стейт в качестве аргумента, mobx запоминает к каким именно observable мы обращаемся внутри селектора, и только при их изменении вызывает обработчик.

    Важно понимать, как именно mobx решает, на какие observable мы подписываемся. Если бы в коде я написал селектор вот так () => app.store , то reaction не будет вызван никогда, так как сам по себе хранилище не является наблюдаемым, таковыми являются только его поля.

    Если бы я написал вот так () => app.store.keys , то опять ничего не произошло бы, так как при добавлении/удалении элементов массива ссылка на него меняться не будет.

    Mobx в первый раз выполняет функцию селектора и следит только за теми observable, к которым мы получали доступ. Сделано это через геттеры прокси. Поэтому здесь использована встроенная функция toJS . Она возвращает новый объект, в котором все прокси заменены на оригинальные поля. В процессе выполнения она читает все поля объекта – следовательно, срабатывают геттеры.

    В консоли popup снова добавим несколько ключей. На этот раз они попали еще и в localStorage:


    При перезагрузке background-страницы информация остается на месте.

    Весь код приложения до этого момента можно посмотреть здесь.

    Безопасное хранение приватных ключей

    Хранить приватные ключи в открытом виде небезопасно: всегда есть вероятность того, что вас взломают, получат доступ к вашему компьютеру и так далее. Поэтому в localStorage мы будем хранить ключи в зашифрованном паролем виде.

    Для большей безопасности добавим приложению стейт locked, в котором доступа к ключам не будет совсем. Мы будем автоматически переводить расширение в стейт locked по таймауту.

    Mobx позволяет хранить только минимальный набор данных, а остальное автоматически рассчитывать на их основе. Это — так называемые computed properties. Их можно сравнить с view в базах данных:

    Теперь мы храним только шифрованные ключи и пароль. Все остальное вычисляется. Перевод в стейт locked мы делаем с помощью удаления пароля из стейта. В публичном API появился метод для инициализации хранилища.

    У браузера есть idle API, через который можно подписаться на событие — изменения стейта. Стейт, соответственно, может быть idle , active и locked . Для idle можно настроить таймаут, а locked устанавливается, когда блокируется сама ОС. Также мы поменяем селектор для сохранения в localStorage:

    Код до этого шага находится здесь.

    Транзакции

    Итак, мы подошли к самому главному: созданию и подписи транзакций в блокчейне. Мы будем использовать блокчейн WAVES и библиотеку waves-transactions.

    Если не сделать observable вручную, то mobx сделает это сам при добавлении в массив messages. Однако он создаст новый объект, на который у нас не будет ссылки, а она понадобится для следующего шага.

    Approve и reject мы выносим в API UI, newMessage — в API страницы:

    Теперь попробуем подписать транзакцию расширением:


    В целом все готово, осталось добавить простой UI.

    Интерфейсу нужен доступ к стейту приложения. На стороне UI мы сделаем observable стейт и добавим в API функцию, которая будет этот стейт менять. Добавим observable в объект API, полученный от background:

    В конце мы запускаем рендер интерфейса приложения. Это react-приложение. Background-объект просто передается при помощи props. Правильно, конечно, сделать отдельный сервис для методов и store для стейта, но в рамках данной статьи этого достаточно:

    С помощью mobx очень просто запускать рендер при изменении данных. Мы просто вешаем декоратор observer из пакета mobx-react на компонент, и рендер будет автоматически вызываться при изменении любых observable, на которые ссылается компонент. Не нужно никаких mapStateToProps или connect, как в redux. Все работает сразу "из коробки":

    Остальные компоненты можно посмотреть в коде в папке UI.

    Теперь в классе приложения необходимо сделать селектор стейта для UI и при его изменении оповещать UI. Для этого добавим метод getState и reaction , вызывающий remote.updateState :

    При получении объекта remote создается reaction на изменение стейта, который вызывает функцию на стороне UI.

    Итак, приложение готово. Веб-страницы могут запрашивать подпись транзакций:



    Код доступен по этой ссылке.

    Заключение

    Если вы дочитали статью до конца, но у вас остались вопросы, вы можете задать их в репозитории с расширением. Там же вы найдете коммиты под каждый обозначенный шаг.

    А если вам интересно посмотреть код настоящего расширения, то вы сможете найти это здесь.

    Написать расширение для google chrome несложно. Но при написании первого раширения могут возникнуть (и возникают) вопросы. Большинство мануалов по написанию первого расширения расчитаны на использования манифеста первой версии, поддержка которого в скором будущем прекратится.

    Введение

    • Должен иметь поле для добавления события (дата, время, событие)
    • Должен отображать все задачи на текущий день, отсортированные по времени
    • Все прошедшие события должен отображать зачеркнутыми
    • Должен иметь поле для ввода времени, за сколько надо показывать уведомление, а так же чекбокс разрешающий и запрещающий показывать уведомления
    • За указанное время до события должен отображать уведомление о приближающемся событии

    Манифест

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

    manifest.json

    • Версия манифеста должна быть целочисленной, то есть должна писаться как 2, а не “2”.
    • Версия расширения должна быть строковой, но содержать только числа и точки, то есть “1.0” — хорошо, а 1.0 и “0.9 beta” — плохо.

    С обязательными полями – все, перейдем к созданию всплывающего окна расширения. Для того, чтобы по нажатию на пиктограмму, открывалось окно, необходимо добавить в манифест поле “browser_action”

    manifest.json


    Теперь создадим всплывающее окно. Это обычная html страница, которая может быть любого размера и цвета, никаких фокусов. Назовем файл “popup.html”. Создать этот файл мало – его надо указать в манифесте. Так мы и сделали: «default_popup»: «popup.html».

    Добавление расширения в браузер

    Теперь пришло время проверить работоспособность нашего расширния. Для этого загрузим расширение в браузер. Открываем в хроме меню расширений. Ставим птицу на “Developer mode”.

    После этого появятся три кнопки. Нажимаем “Load unpacked extension. ”. Выбираем папку с файлами расширения. После этого появится наше расширение. Если все правильно, то по нажатию на иконку – повится окно:

    Подключение скриптов

    manifest.json

    Теперь подключим эти скрипты в popup.html

    Storage

    manifest.json

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


    Выглядеть должно так:

    Итак, при нажатии на кнопку “+” у нас должно добавляться событие. Вначале файла объявим глобальную переменную storage – объект для работы с storage, а так же глобальный массив tasks для хранения событий.

    Функция валидации проверяет, что дата записана в формате d.m.yyyy, а время в формате hh:mm, а так же, что в описании события не меньше трех символов.

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

    Функция getTodayTasks() возвращает из общего списка только события с сегодняшней датой.

    Функция sortTasks() сортирует события по возрастанию времени.

    Уведомления

    Пришло время настроить отображение уведомлений на экране. Добавим во всплывающее окно специальный чекбокс. Если этот чекбокс будет отмечен – уведомлениея будут показываться, если не будет отмечен – не будут. Так же добавим текстовый инпут. Цифра в этом инпуте будет показывать, за какое время до событя будет показываться уведомление. То есть если у нас событие назначено на 19:00, в этом текстовом инпуте будет 5, значит в 18:55 появится уведомление. Добавим в popup.html код с этими инпутами


    Теперь давайте разберемся с тем, как это будет работать. При нажатии на чекбокс, будет проверяться его атрибут checked, значение атрибута будет записываться в cookie “show_notifications”. Перейдем к текстовому инпуту. По изменению его значения, новое значение будет валидироваться, если оно целочисленное и не больше 120, записываем новое значение в cookie “when_to_notify”.

    manifest.json

    Теперь можно приступать к скрипту. Заходим в popup.js. Для начала установим первоначальные значения в инпутах. По-умолчанию чекбокс не отмечен, то есть уведомления не показываются, а время равно 0. При клике на чекбокс, будет меняться значение cookie “show_notifications”. При изменении значения в тектовом поле, будет меняться значение cookie “when_to_notify”.

    Разберемся с функцией setCheckbox(). Она получает cookie “show_notifications” и проверяет, если полученное значение равно “true”(текстовое, да), то параметр checked у чекбокса true, иначе false.

    Перейдем к самим уведомлениям. Для этого откроем доступ к уведомлениям и подключим фоновый background.js. Нужно подключить именно фоновый файл, так как если уведомления вызывать из popup.js, то уведомления будут появляться только если открыто всплывающее окно.

    manifest.json

    Последняя строчка дает доступ к удаленному файлу. Дело в том, что картинка, которая отображается в уведомлении обязательно должна быть доступна расширению удаленно. В данном случае файл локальный, но доступ открывать все равно надо. Теперь возьмемся за background.js. Объявим переменную storage и пустой массив tasks. Далее раз в минуту скрипт будет получать список сегодняшних событий и получать из них список задач, которые должны произойти через указанное время. После этого для каждой такой задачи будет показано уведомление.

    background.js

    Функции getTodayTasks() и getCookie() взяты из popup.js. Так что начнем разбор с функции getNextTask(). Функция сравнивает текущее время и время события, если оно равно тому значению, которое хранится в cookie “when_to_notify”, то в массив next дописывается строка из времени события и его описания. После проверки всех событий возвращет массив next.

    background.js

    Функция show() показывает уведомление с заданным текстом.

    background.js


    Результатом работы этого скрипта будет такое вот уведомление:

    Послесловие

    Как и обещано, в конце статьи у нас есть готовое расширение-органайзер для Google Chrome.
    Теперь добавим расширение в Chrome Web Store. Загружать надо расширение, запакованное в .zip-архив. Для начала заходим в Web Store. Для этого заходим в хроме на вкладку “Приложения” и нажимаем кнопку “Web Strore”

    Теперь заходим в меню. Для этого нажимаем шестиренку и открываем “Developer dashboard”

    Нажимаем большую кнопку “Add new item”. После этого надо будет выбрать zip-архив с расширением нажать “upload”

    Далее надо заполнить небольшую форму с описанием расширения. Теперь есть выбор либо сохранить расширение в черновил либо опубликовать. Просто так опубликовать его не получится так как регистрация в Web Store стоит 5 $. Плату за регистрацию надо платить один раз для аккаунта, а не для каждого расширения. После оплаты появляется позможность опубликовать расширение, но и тут надо быть готовым к тому, что его будут валидировать несколько дней.

    Читайте также: