Разработка addon firefox, или ещё один скриншотер с помощью webExtensions и addon sdk

    В этой статье мы рассмотрим разработку расширения для Firefox, с помощью addon sdk, а также разберём ключевые моменты разработки: установка sdk(jpm), инициализация проекта, тестирование, компиляция и публикация нашего расширения на addons.mozila.org, на примере всё того же скриншотера…

    image

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

    И так, с начала мы должны установить пакет jpm в нашу систему, для этого устанавливаем nodejs и npm (если нету):

    $ sudo apt-get install nodejs nodejs-legacy npm

    После установки node.js и npm устанавливаем jpm с помощью команды:

    $ sudo npm install jpm --global

    Эта команда установит нам jpm в глобальную область, его так же можно установить локально опустив ключ --global.

    Далее нам необходимо создать директорию, для будущего расширения. Давайте назовём его habrscreen:

    $ mkdir ~/habrscreen

    И инициализируем в этой директории расширение, для этого нам надо перейти в папку и выполнить команду инициализации:

    $ cd ~/habrscreen
    $ jpm init

    После выполнения данной команды, в директории создаётся скелет будущего приложения.

    habrscreen
    — index.js
    — package.json
    — test
    — test-index.js

    Рассмотрим конфигурацию нашего addon package.json:

    {
      "title": "habrahabr screenshoter",
      "name": "habrscreen",
      "version": "0.0.1",
      "description": "This add-on for make screenshot and upload to yandex disk",
      "main": "index.js",
      "author": "Roman",
      "engines": {
        "firefox": ">=38.0a1"
      },
      "license": "MIT",
      "keywords": [
        "jetpack"
      ],
      "preferences": [
        {
          "name": "hClientId",
          "title": "client id",
          "description": "client id",
          "type": "string",
          "value": "8fc231e60575439fafcdb3b9281778a3"
        },
        {
          "type": "control",
          "label": "get oAuth token",
          "name" : "getYaToken",
          "title": "Token"
        },
        {
          "description": "oauth token",
          "name": "oauthKey",
          "type": "string",
          "title": "oauth token"
        },
        {
          "description": "automaticaly copy to clipboard",
          "title": "autocopy to clipboard",
          "name":"autoCopy",
          "type":"bool",
          "value":true
        }
      ]
    }

    Что здесь может быть интересного? это тот же манифест только в формате json. Да это так тот же манифест с указанием версии, описанием и имени addons. А так же мы в нём определили настройки нашего модуля пока их будет 4:

    1. client id — id клиента yandex oauth
    2. get OAuth token — кнопка при клике переходим на яндекс и получаем токен авторизации
    3. oauth token — собственно поле для ввода токена
    4. autocopy to clipboard — флаг который автоматического копирования ссылки на скриншот в буфер обмена.

    После определения настроек нам необходимо подумать о кнопке, при нажатие на которую будет запускаться наш скриншотер. Для этого открываем index.js на редактирование и описываем в нём эту кнопку:

    var ui = require('sdk/ui');
    var {ActionButton} = require('sdk/ui/button/action');
    
    var button = ui.ActionButton({
        id: "mozilla-link",
        label: "Make screenshot",
        icon: {
            "16": "./image/camera16.png",
            "32": "./image/camera32.png",
            "64": "./image/camera64.png"
        },
        onClick: makeScreen
    });

    Код довольно прост, для начала мы импортируем, нужные нам, компоненты sdk, и создаём объект кнопки. Нам необходимо создать папку data в ней директорию image, в последнею скопировать заранее заготовленные картинки кнопки, картинка должна быть 3-х размеров 64, 32, 16… Готово, картинки загружены, настройка создана. Попробуем запустить:

    $ jpm run

    После выполнения команды запустится firefox с нашим модулем, мы увидим результат это кнопочка с права в верху, и настройка addon.

    Обработаем логику кнопки getOauth в настройках расширения, для этого необходимо обработать событие генерируемое кнопкой:

    var sp = require("sdk/simple-prefs");
    sp.on("getYaToken", function () {
        tabs.open('https://oauth.yandex.ru/authorize'
            + '?response_type=token'
            + '&client_id='
            + require('sdk/simple-prefs').prefs['hClientId']);
    });
    

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

    Далее, обработаем нажатие кнопки 'make screen' выше, мы указали что обработчиком служит функция makeScreen:

    /**
     * click to button screenshot
     * @param state
     */
    function makeScreen(state)
    {
        var date = new Date();
        var fileScreen = date.getTime().toString() + '_screen.png';
    
        var args = ["-s", "/tmp/" + fileScreen];
    
        system(
            '/usr/bin/scrot',
            args
        );
    
       uploadToYandex(fileScreen);
    }

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

    /**
     * upload screenshot to yandex
     * @param name
     */
    function uploadToYandex(name) {
        var Request = require('sdk/request').Request;
        const fileIO = require("sdk/io/file");
    
        Request({
            url: "https://cloud-api.yandex.net/v1/disk/resources/upload?path=" + name,
            headers: getHeaders(),
            onComplete: function (response) {
                var result = JSON.parse(response.text);
                if (result.method == "PUT") {
                    putRequest(result.href, '/tmp/' + name);
                    // publicate file
                    publicateFile(name);
                }
            }
        }).get();
    }

    Эта функция производит запрос с помощью класса Request к api диска. К сожалению не удалось реализовать запрос методом put, он есть в объекте но параметр content принимает либо объект, либо строку и по этому бинарные данные превращаются в строку тела запроса, что приводит к выгрузке битых файлов. Мне пришлось написать свой putRequest с помощью XMLHttpRequest, если кто знает как реализовать с помощью Request пишите в комментариях. Я буду благодарен если кто то поможет привести код к общему знаменателю.

    /**
     * put request to yandex api
     * @param url
     * @param file
     */
    function putRequest(url, file) {
        const {Cc, Ci} = require("chrome");
        // Make a stream from a file.
        var stream = Cc["@mozilla.org/network/file-input-stream;1"]
            .createInstance(Ci.nsIFileInputStream);
    
        var fileIo = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
        fileIo.initWithPath(file);
    
        stream.init(fileIo, 0x04 | 0x08, 0644, 0x04); // file is an nsIFile instance
    
        var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(Ci.nsIXMLHttpRequest);
    
        req.open('PUT', url, false);
        req.setRequestHeader('Content-Type', "application/binary");
        req.send(stream);
    }

    Здесь тоже всё просто, мы открываем файл и создаём поток, который, собственно, и передаём в тело запроса. По документации «диска» мы должны всегда отправлять «Content-Type:application/binary» так и делаем. Всё наш запрос ушол, последним запросом мы публикуем файл publicateFile:

    /**
     * publicate file on yandex disk
     * @param name
     */
    function publicateFile(name) {
        var Request = require('sdk/request').Request;
        var result;
        Request({
            url: "https://cloud-api.yandex.net/v1/disk/resources/publish?path=" + name,
            headers: getHeaders(),
            onComplete: function (responsePublic) {
                result = JSON.parse(responsePublic.text);
                if (result.method == "GET") {
                    Request({
                        url: result.href,
                        headers: getHeaders(),
                        onComplete: function (resp) {
                            result = JSON.parse(resp.text);
                            if (require('sdk/simple-prefs').prefs['autoCopy']) {
                                var clipboard = require("sdk/clipboard");
                                clipboard.set(result.public_url);
                            }
                            tabs.open(result.public_url);
                        }
                    }).get();
                }
            }
        }).put();
    }
    

    Эта функция отправляет на yandex запрос на публикацию, который возвращает ссылку, для следующего запроса, подтверждения. Второй запрос возвращает объект с информацией о файле, в котором есть ссылка на файл public_url. Если всё успешно открываем вкладку браузера со скриншотом, и в зависимости от настроек, копируем ссылку в буфер обмена…

    Когда всё готово и проверена работоспособность расширения нам необходимо собрать его в фаил xpi для этого вводим команду:

    $ jpm xpi

    Далее проходим регистрацию на addons.mozilla.org и заполняем форму для отправки расширения на модерацию здесь developer hub.

    image

    И на этом мы закончим наше приложение скриншотер разработано и опубликовано в addons.mozilla.org…

    » Как всегда, делюсь ссылкой на полный код github.com: firefox-sdk-addons
    » Вот и наша, ссылка на расширение habrahabr-screenshoter

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

    Комментарии 12

      0
      Потёрто.

      PS: Не прочитал текст под картинкой.
        0
        У вас linux?
          0
          Какая особенность у расширения не даёт установить на Win?
            0
            программа scrot которую он использует +пути к файлам.
        +1
        Просто не могу не посоветовать использовать maim :) https://github.com/naelstrof/maim
          0
          в Израиле, наверняка, писали =)

          маим — вода
          0
          Нам необходимо создать папку data, и в неё скопировать заранее заготовленные картинки кнопки

          Не data, а image
          Ну и использовать JSON ради одной только parse…
            0
            The data object is used to access data that was bundled with the add-on. This data lives in the add-on's data/ directory, immediately below the package.json file. All files in this directory will be copied into the XPI and made available through the data object.
              0
              Нам необходимо создать папку data, и в неё скопировать заранее заготовленные картинки кнопки

              при этом в коде icon: {
              "16": "./image/camera16.png",
              "32": "./image/camera32.png",
              "64": "./image/camera64.png"
              }
                0
                Оу… Сорри не сразу понял, сейчас поправлю. Спасибо.
            0
            Автор обратил внимание на интересную проблему. Под Убунтой c Firefox 49 jpm run работает — только что проверял. А вот неделю назад при попытке запустить jpm run под Windows 10 c Firefox 48 выдавалось следующее:
            WARN    Add-on @testaddon is not correctly signed.
            
            и запускался Firefox без расширения. Как я понимаю, это связано с тем, что начиная с 48й версии в обыкновенной сборке (не ночной, не Unbranded Build) Firefox нельзя устанавливать не подписанные расширения. Оказывается, это ограничение распространяется только на windows-сборки.
              0
              После выполнения команды запуститься firefox с нашим модулем


              запустится — без мягкого (что сделает)

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

              Самое читаемое