Разработка 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, и в неё скопировать заранее заготовленные картинки кнопки, картинка должна быть 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

Всем спасибо за внимание, и всего доброго.

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

  • 26 сентября 2016 в 13:52 (комментарий был изменён)

    0

    Потёрто.

    PS: Не прочитал текст под картинкой.

    • 26 сентября 2016 в 13:53

      0

      У вас linux?
      • 26 сентября 2016 в 13:56

        0

        Какая особенность у расширения не даёт установить на Win?
        • 26 сентября 2016 в 14:07

          0

          программа scrot которую он использует +пути к файлам.
  • 27 сентября 2016 в 00:47

    0

    Просто не могу не посоветовать использовать maim:) https://github.com/naelstrof/maim

© Habrahabr.ru