Предлагаю вашему вниманию перевод статьи Automatic Firefox Extension Updates.

Статья старенькая, но вся информация актуальна и по сей день.


Разработчики расширений Firefox конечно знают, что при распространении расширений через официальный store вы бесплатно получаете возможность автоматически обновлять свои расширения. Но что делать, если мы хотим сами хостить свое расширение на своем сайте? Как нам самим реализовать поддержку автоматических обновлений?

Подписывание расширений


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

  • Предоставлять обновления по защищенному (https) соединению
  • Подписывать расширение электронной подписью

Первый вариант мы рассматривать не будем — в большинстве случаев это требует дополнительных затрат (надо купить SSL сертификат и иметь статичный IP (сам не понял, к чему это, но из песни слов не выкинешь [прим. перев.])) Цифровая подпись напротив — бесплатна, легка в использовании и быстро реализуема. Ну давайте уже научимся ею пользоваться!

Создаем пару приватный/публичный ключ


Первый шаг в подписывании нашего расширения заключается в создании пары приватного/публичного ключа. Mozilla предоставляет утилиту McCoy для этих целей. Не самая простая утилита в мире, так что ниже расписано как её готовить:

  • идем на сайт McCoy и качаем подходящий пакет (есть версии для  Windows, Linux и Mac OS X)
  • распаковываем пакет в удобную нам директорию
  • запускаем приложение. При первом запуске оно предложит создать master password для защиты ключей. Создание такого пароля крайне настойчиво рекомендуется! При каждом последующем запуске McCoy он будет спрашивать этот пароль.
  • Приложение запущено, выбираем в меню Keys » Create New Key. Введите вменяемое имя своему ключу и нажмите Ok. Имейте в виду, даже если вы хотите хостить несколько расширений на своем сайте — вам достаточно будет одного ключа.

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

Обновляем install.rdf


Предполагается, что вы знакомы с install.rdf, так что не будем тратить время на описание его структуры (если нет — вам сюда). Для примера я использую install.rdf из Toolbar Tutorial. Вот его исходный вариант:

<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">

    <Description about="urn:mozilla:install-manifest">

        <em:id>tuttoolbar@borngeek.com</em:id>
        <em:name>Tutorial Toolbar</em:name>
        <em:type>2</em:type>
        <em:version>1.0</em:version>

        <em:targetApplication>
            <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>4.0</em:minVersion>
                <em:maxVersion>30.*</em:maxVersion>
            </Description>
        </em:targetApplication>

        <em:creator>Jonah Bishop</em:creator>
        <em:description>An example toolbar extension.</em:description>
        <em:homepageURL>https://www.borngeek.com/firefox/</em:homepageURL>

    </Description>
</RDF>

Нам надо добавить два элемента в этот манифест: em:updateURL и em:updateKey. Элемент em:updateURL указывает на URL по которому лежит манифест обновления (update.rdf). Выглядит вот так:

<em:updateURL>http://www.example.com/update.rdf</em:updateURL>

Имейте в виду — вы не сможете разместить расширение в официальном store, если ваш манифест содержит этот элемент.

Следующий у нас em:updateKey. Он просто содержит в себе публичный ключ. Чтобы получить его, откройте McCoy, сделайте правый клик на ключе, который вы создали ранее и выберите Copy Public Key в контекстном меню. После поместите ключ между открывающим и закрывающим тегами:

<em:updateKey>
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDW8qxAeURIMLnHjb
KrjR/uqnRsiomahNArMh3KqRLDDmRGCoO21fyHyh5kdgiEL+2Q+sNP
z+j5maIG4qePXp7BVp90QMqiGLvl+z4baDOqcNvErN0l8scd8EegXc
G7Ofa5Gc5oEU/gItIVR4k9AICyW2pJhe51UPa3UKXDS0v3TwIDAQAB
</em:updateKey>

Абракадабра. К счастью, Firefox достаточно умен и позволяет использовать пробелы в em:updateKey элементе, что позволяет сделать install.rdf более читаемым (как показано выше, это уже отформатированный вариант). По умолчанию из McCoy копируется одна длинная строка.

После добавления этих двух элементов install.rdf начинает выглядет так:

<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:em="http://www.mozilla.org/2004/em-rdf#">

    <Description about="urn:mozilla:install-manifest">

        <em:id>tuttoolbar@borngeek.com</em:id>
        <em:name>Tutorial Toolbar</em:name>
        <em:type>2</em:type>
        <em:version>1.0</em:version>

        <em:targetApplication>
            <Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>4.0</em:minVersion>
                <em:maxVersion>30.*</em:maxVersion>
            </Description>
        </em:targetApplication>

        <em:creator>Jonah Bishop</em:creator>
        <em:description>An example toolbar extension.</em:description>
        <em:homepageURL>https://www.borngeek.com/firefox/</em:homepageURL>

        <em:updateURL>http://www.example.com/update.rdf</em:updateURL>

        <em:updateKey>
        MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDW8qxAeURIMLnHjb
        KrjR/uqnRsiomahNArMh3KqRLDDmRGCoO21fyHyh5kdgiEL+2Q+sNP
        z+j5maIG4qePXp7BVp90QMqiGLvl+z4baDOqcNvErN0l8scd8EegXc
        G7Ofa5Gc5oEU/gItIVR4k9AICyW2pJhe51UPa3UKXDS0v3TwIDAQAB
        </em:updateKey>

    </Description>
</RDF>

Мы завершили обновление манифеста нашего расширения! Обратите внимание — этот шаг необходимо сделать всего один раз. То есть при каждом следующем обновлении эти действия повторять не придется (если только вы не измените URL update.rdf или не поменяете ключ). Все, на этом этапе уже можно паковать расширение для дальнейшей дистрибуции (раздавать пользователям).

Создаем update.rdf


Раз уж мы завершили с самим расширением, самое время создать манифест обновления. Этот файл (update.rdf) будет жить у нас на сервере и, собственно, именно он определяет, что увидит пользователь при проверке наличия обновлений. Давайте начнем с того, что взглянем сразу на конечный вариант файла, который мы собираемся создать:

<?xml version="1.0"?>
<r:RDF xmlns:r="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns="http://www.mozilla.org/2004/em-rdf#">

<r:Description about="urn:mozilla:extension:tuttoolbar@borngeek.com">
    <updates>
    <r:Seq>
    <r:li>
    <r:Description>
        <version>1.0.2</version>
        <targetApplication>
            <r:Description>
            <id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</id>
            <minVersion>4.0</minVersion>
            <maxVersion>30.*</maxVersion>
            <updateLink>http://www.example.com/downloads/tuttoolbar_1_0_2.xpi</updateLink>
            <updateHash>
            sha256:c22ad513c1243959a6d8e6b3cfad18a2a9141306f0165da6b05b008b2042e502
            </updateHash>
            </r:Description>
        </targetApplication>
    </r:Description>
    </r:li>
    </r:Seq>
    </updates>
</r:Description>

</r:RDF>

Давайте пробежимся построчно и обсудим, что тут происходит. Во первых, мы имеем стандартный XML заголовок с последующим r:RDF элементом, который и говорит нам о том, что перед нами RDF (Resource Description Framework) файл. Нас в основном интересует элемент r:Description. Вы должны иметь по одному элементу на каждое расширение, которое описываете в update.rdf.

(Да, да, в одном update.rdf можно описывать обновления множества расширений, Extension Versioning, Update and Compatibility [прим. перевод.])

Далее, первое, что мы должны прописать — это аттрибут about. Часть urn:mozilla:extension обязательна, далее следует GUID вашего расширения. В нашем случае GUID такой: tuttoolbar@borngeek.com. Имейте в виду — если у вас не email-style GUID ( а что то вроде d4373b50-43b3-11de-8a39-0800200c9a66), то его надо заключить в фигурные скобки: {d4373b50-43b3-11de-8a39-0800200c9a66}.

После нескольких дочерних элементов (updates, r:Seq, r:li, и еще одного r:Description) остановимся на элементе version. Это версия вашего расширения, в нашем случае 1.0.2.

Далее у нас идет информация о приложении, для которого предназначено наше расширение (Firefox). У нас есть targetApplication элемент, заключенный в r:Description и содержащий крайне важный id элемент. Значение этого элемента — GUID Firefox-а.

(Сразу видно, басурмане писали. Коротко — у каждого target есть свой GUID — у Firefox-а, у Thunderbird- а, у Firefox for Android, у всех разные. Идем вот сюда, выбираем приложение, под которым работает наше расширение и копируем его GUID. Тут можно взглянуть на пример update.rdf для нескольких targets [прим. перевод.])

Далее следуют знакомые нам minVersion и maxVersion элементы, которые соответственно определяют минимальную и максимальную версии Firefox-а (или иного target-а), на работу в которых рассчитано расширение. Очень важно, чтобы эти значения совпадали с указанными в install.rdf.

Следующим у нас идет updateLink элемент. В нем указывается URL самого расширения (т.е. xpi файла). Убедитесь в том, что URL указывает на соответствующий файл, особенно если вы поддерживаете и даете скачать более старые версии расширения.

Ну и в конце нас ждет updateHash элемент. Этот элемент содержит sha1, sha256, sha384, или sha512 хэш нашего расширения (то есть xpi файла). Я предпочитаю sha256, поскольку были проблемы с обратной совместимостью для sha384 и sha512 (см. bug 383390 (пофиксено все уже давно). Если вы на Линуксе — у вас уже есть все необходимое для генерации sha. Пользователи Windows могут скачать соответствующую утилиту (я использую sha256sum), например, тут: Cygwin. Для того, чтобы получить хэш, наберите что-то вроде этого в терминале:

sha256sum tuttoolbar_1_0_2.xpi

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

c22ad513c1243959a6d8e6b3cfad18a2a9141306f0165da6b05b008b2042e502 *tuttoolbar.xpi

Шестнадцатеричная строка (все что до пробела) — это то, что вам надо положить в updateHash с указанием типа шифрования. Выглядеть это должно примерно так:

<updateHash>
sha256:c22ad513c1243959a6d8e6b3cfad18a2a9141306f0165da6b05b008b2042e502
</updateHash>

Собственно все! Сохраните его как девелоперскую версию! Я лично использую название update.rdf.dev. Почему надо держать отдельную версию для девелопмента? Потому что когда вы подпишите этот манифест (а мы ведь рассматриваем вариант с подписью [прим. перев.]) его содержимое станет немного малочитаемым и малопригодным для дальнейшего редактирования. Так что лучше держать отдельно девелоперскую версию, а когда надо — подписывать её копию.

Подписываем манифест


Для того, чтобы подписать манифест, последовательно выполните следующие несложные действия:

  • скопируйте девелоперскую версию манифеста и назовите её update.rdf. Именно этот файл мы и будем подписывать
  • запустите McCoy если он еще не запущен
  • выделите в нем ключ, которым вы собираетесь подписать и выберите в меню Update » Sign Update Manifest
  • в открывшемся меню выберите update.rdf, который мы только что создали, и жмите Open

Будет выглядеть так, будто ничего не происходит, но это не так! Есть у меня такая претензия к McCoy — он ничего не говорит по завершении работы. Но если вы откроете манифест, то можете заметить, что он немного изменился. Собственно на этом работа над расширением завершается — пришло время загружать файлы на сервер.

Хостим update.rdf


Перед тем, как залить файлы, мы должны убедиться в том, что наш сервер готов хостить rdf и xpi файлы. На Apache мы можем сделать это через правила .htaccess (не знаю как это работает в IIS или TomCat, имейте в виду — это инструкция только для Apache). Я обычно кладу эти правила в .htaccess корня сайта — на тот случай, если мне захочется поперемещать xpi и rdf. Правила простые:

AddType application/x-xpinstall .xpi
AddType text/xml .rdf

(сейчас-то уже модно nginx, под него делаем так:

types {
 application/x-xpinstall      xpi;
 text/xml      xml, rdf;
}

и кладем это либо в общий /etc/nginx/mime.types, либо в конфиг нашего сервера. [прим. перевод.]
)

Это необходимо для правильной отдачи файлов сервером. Это очень важный момент! В противном случае ваше расширение не будет установлено, также не будут работать обновления. Также имейте в виду: если вы используете какую-либо CMS (типа WordPress), то лучше на всякий случай эти правила разместить в корне сайта.

После того, как все прописано, можно залить наш xpi и update.rdf по соответствующим адресам. Update.rdf должен лежать там, куда указывает install.rdf расширения (тег em:updateURL файла install.rdf расширения). Само расширение (.xpi) должно лежать там, куда указывает update.rdf (тег updateLink). Постарайтесь не запутаться.

На этом все! Хоть процедура слегка запутанна на первый раз — все достаточно просто.

Чистого кода!

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


  1. Alexufo
    11.03.2016 16:36

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


    1. susbox
      12.03.2016 07:51

      Alexufo, в статье рассматриваются подписи для хостинга на своём сайте и автоматических обновлений. Это один вид подписи. Второй, и тоже обязательный — это как раз валидация на стороне Mozilla, чтобы они имели возможность разрешать или блокировать расширения (опция xpinstall.signatures.required решает проблему, но до 46 версии Firefox, где этот ключ удалят для веток Release и Beta, но оставят для Developer). Подписывать можно либо на сайте, либо настроив скрипты — благо, есть для этого API (http://olympia.readthedocs.org/en/latest/topics/api/signing.html).

      Как разработчик расширения FireShot с 7-летним стажем, я имею много претензий к ребятам из addons.mozilla.org (начиная от того, что каждый апдейт расширения для AMO проверяется в среднем 2-3 недели и заканчивая их постоянными "революциями" в API). Но надо отдать им должное — их концепция с manual review скрипит (они постоянно ищут редакторов-добровольцев для проверки расширений), но в принципе она работает.

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


      1. Alexufo
        12.03.2016 07:59

        Мы сделали шаг в сторону внешнего приложения и общения с ним через ajax local host:5000 что стало кросс браузерным и проще в разработке. Связывали сайт и железо.


        1. susbox
          12.03.2016 08:11

          Отличный ход, его я всегда держу в уме до времён, когда совсем закрутят гайки с Native компонентами расширений. А как вы решаете задачу автозапуска внешнего приложения для нормального функционирования расширения?

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


          1. Alexufo
            12.03.2016 09:12

            Расширения нет. Ни в хроме ни в лисе. Аякс запрос на локалхост браузеры разрешают посылать и так. А там просто разбор параметров и ответ.Проблемам в том что с https нужно повозякаться с установкой самоподписного сертификата или щелкать постоянно в браузере по иконке для разрешения смешанного содержимого. У нас все равно в расширении экзеха для посылки в принтер строчки с zpl кодом и такая же для одного NFC сканера.


          1. Alexufo
            13.03.2016 03:58

            Мы даже выводили через расширение FF поток со сканера отпечатков на вебстраничку при регистрации людей во внутренней CRM. Со сканера идет поток и мы его с помощью js-ctypes подхватывали и рендерели прямо на страничке, по кнопке захватывали картинку, оценивали удачный ли отпечаток пальца или нет, сохранять ли его или нет. А на сервере был софт от SONDA, который уже отвечал за векторизацию и тому прочие алгоритмы. Да, была просадка ФПС заметная но вполне рабочая.
            Демка была рабочая но в деле не пригодилась.

            Попандос был с тем, что если бы не подсказка Владимира Паланта. автора Adblock+ на SO хрена бы что сделали. Просто невозможно найти рабочих примеров как работать в лисе вне потока интерфейса.
            Сейчас, конечно же, проще поднять мини сервер с вещанием lossless видео или секвенции на localhost-е. А в браузере подхватить через video тег. Просто когда переписали на шарпе… эт заняло ну два дня.)))) А с расширением лисы там какая то адовая вещь с этой отладкой… да, удобств поменьше. но все равно я ставлю что расширение что прогу. А мне как то это по барабану все одинаково)


  1. rshadow
    11.03.2016 17:07
    +3

    У https две эпохи:
    1. До LetsEncrypt — дорого, трудно, не всем доступно
    2. После LetsEncrypt — бесплатно, легко, доступно всем


  1. gearbox
    11.03.2016 17:57

    О, дайте две! можно то же самое но для всех популярных броузеров — chrome, opera, safari?


    1. Antelle
      11.03.2016 19:22

      Для safari вообще бы хорошо иметь возможность сделать экстеншн не оплачивая $99 в год.


    1. susbox
      12.03.2016 08:04

      Chrome отключил возможность дефолтового распространения расширения с хостингом на своём сайте года как два назад. Есть лазейка с GPO (http://dev.chromium.org/administrators/policy-list-3#ExtensionInstallSources), но не рекомендую использовать эту технику в инсталлере для массового распространения своего аддона — мой негативный опыт закончился на блокировке расширения и блокировки сети собственных сайтов, как распространяющих malware. В итоге три дня простоя и куча потраченных нервов в процессе восстановления репутации.


    1. Methos
      12.03.2016 16:46

      для хрома можно хостить на своём сайте, обновление тоже простые.


  1. developinwed
    11.03.2016 20:01

    Если в about:config отключить проверку, выставив значение xpinstall.signatures.required в false, то можно и не использовать подпись?


    1. gonzazoid
      11.03.2016 20:19

      из wiki:
      What about private add-ons used in enterprise environments?
      The ESR release will support signing starting with version 45-based releases. Signing enforcement will be enabled by default in these releases, and enforcement can be disabled using the xpinstall.signatures.required preference.

      неподписанный xpi локально ставится, а вот на счет обновлений — не проверял.


  1. Kop3t3
    11.03.2016 22:31

    А такая подпись заменит подпись Mozilla? (у них на сайте написано, что теперь нужна именно их подпись)
    И если нет, то можно как-то пропатчить лису, заменив публичный ключ Mozillы на свой, чтобы ставить свои расширения на свой браузер без отправки их на подпись? (или поменять в сорцах, но что-то не хочется пересобирать браузер)


    1. Alexufo
      12.03.2016 07:49

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


      1. Alexufo
        12.03.2016 08:00

        *Nightly


    1. susbox
      12.03.2016 07:53

      Нет не заменит — смотрите мой пост про подписи в начале ветки.