Tusia ose fa'aopoopoga fa'amautu

Tusia ose fa'aopoopoga fa'amautu

E le pei o le fausaga masani "client-server", o faʻasalalauga faʻapitoa e faʻaalia e:

  • E leai se mana'oga e teu se fa'amaumauga fa'atasi ma fa'aoga fa'aoga ma fa'aulutalatala. O fa'amatalaga avanoa e teuina fa'apitoa e tagata fa'aoga lava latou, ma o le fa'amaoniga o lo latou fa'amaoni e tupu ile tulaga fa'atulafono.
  • E le mana'omia le fa'aogaina o se server. E mafai ona faʻatinoina le faʻaogaina o le talosaga i luga o se poloka poloka, lea e mafai ai ona teuina le aofaʻi o faʻamaumauga manaʻomia.

O lo'o i ai 2 fa'amautu saogalemu mo ki fa'aoga - hardware wallets ma browser extensions. O atotupe meafaigaluega e tele lava ina malupuipuia, ae faigata ona faʻaoga ma mamao mai le leai o se totogi, ae o faʻaopoopoga o suʻesuʻega o le tuʻufaʻatasiga atoatoa lea o le saogalemu ma le faigofie o le faʻaoga, ma e mafai foi ona saoloto atoatoa mo tagata faʻaoga.

I le faʻaaogaina o nei mea uma, matou te manaʻo e faia le faʻaopoopoga sili ona malupuipuia e faʻafaigofie ai le atinaʻeina o talosaga faʻapitoa e ala i le tuʻuina atu o se API faigofie mo le galue ma fefaʻatauaiga ma saini.
O le a matou taʻuina atu ia te oe lenei aafiaga i lalo.

O le tusiga o le a aofia ai taʻiala taʻitasi i le auala e tusi ai se faʻaopoopoga o suʻesuʻega, faʻatasi ai ma faʻataʻitaʻiga faʻailoga ma faʻamalama. E mafai ona e mauaina uma le code i totonu faleteuoloa. O tautinoga taitasi e fetaui lelei ma se vaega o lenei tusiga.

Ose Tala'aga Pu'upu'u o Fa'aopoopoga Su'esu'e

Ua leva ona iai fa'aopoopoga ole su'esu'e. Na fa'aalia i le Internet Explorer i tua i le 1999, i le Firefox i le 2004. Ae ui i lea, mo se taimi umi lava e leai se tulaga e tasi mo faʻaopoopoga.

E mafai ona matou fai atu na faʻaalia faʻatasi ma faʻaopoopoga i le lona fa o Google Chrome. Ioe, e leai se faʻamatalaga i lena taimi, ae o le Chrome API na avea ma ona faʻavae: i le faʻatoʻilaloina o le tele o maketi suʻesuʻe ma le iai o se faleoloa faʻapipiʻi, na faʻatulagaina e Chrome le tulaga masani mo faʻaopoopoga o suʻesuʻega.

O Mozilla e iai lana lava tulaga, ae o le vaʻaia o le lauiloa o faʻaopoopoga Chrome, na filifili ai le kamupani e fai se API faʻafetaui. I le 2015, i le fuafuaga a Mozilla, na faia ai se vaega faʻapitoa i totonu o le World Wide Web Consortium (W3C) e galulue i luga o faʻasalalauga faʻasalalauga faʻasalalau.

O fa'aopoopoga API o lo'o iai mo Chrome na fa'avae. O le galuega na faia ma le lagolago a Microsoft (Google na musu e auai i le atinaʻeina o le tulaga masani), ma o se taunuuga na faʻaalia ai se ata faataitai. auiliiliga.

I le tulaga aloaia, o le faʻamatalaga e lagolagoina e Edge, Firefox ma Opera (ia maitau e leʻo Chrome i luga o lenei lisi). Ae o le mea moni, o le tulaga masani e tele lava ina fetaui ma Chrome, talu ai o loʻo tusia moni e faʻavae i luga o ona faʻaopoopoga. E mafai ona e faitau atili e uiga i le WebExtensions API iinei.

Fa'aopoopoina fausaga

Pau lava le faila e manaʻomia mo le faʻaopoopoga o le faʻaaliga (manifest.json). O le "mea e ulu atu ai" i le faʻalauteleina.

Faʻaalia

E tusa ai ma faʻamatalaga, o le faila faila o se faila JSON aoga. Se fa'amatalaga atoa o fa'aaliga ki fa'atasi ai ma fa'amatalaga e uiga i ki o lo'o lagolagoina e mafai ona va'aia ai su'esu'ega iinei.

Ki e le o iai i le faʻamatalaga "atonu" e le amanaiaina (o Chrome ma Firefox lipoti sese, ae o faʻaopoopoga e faʻaauau pea ona galue).

Ma ou te fia tosina atu i nisi o vaega.

  1. tua - o se mea e aofia ai vaega nei:
    1. tusitusiga - se faʻasologa o faʻamaumauga o le a faʻatinoina i le tala faʻasolopito (o le a tatou talanoa e uiga i lenei mea i se taimi mulimuli ane);
    2. itulau - nai lo tusitusiga o le a faʻatinoina i se itulau avanoa, e mafai ona e faʻamaonia html ma mea e aofia ai. I lenei tulaga, o le a le amanaʻia le fanua o tusitusiga, ma o tusitusiga e manaʻomia ona faʻaofi i totonu o le itulau o loʻo i ai;
    3. asosi - o se fuʻa binary, pe a le faʻamaonia, o le browser o le a "fasiotia" le faagasologa o tua pe a manatu e le o faia se mea, ma toe amata pe a manaʻomia. A leai, o le a na'o le la'u ese o le itulau pe a tapuni le su'esu'ega. Le lagolagoina i Firefox.
  2. content_scripts - o se faʻasologa o mea faitino e mafai ai ona e utaina tusitusiga eseese i itulau eseese. O mea ta'itasi e iai vaega taua nei:
    1. afitusi - mamanu url, lea e fuafua ai pe o le a aofia ai se tusitusiga fa'apitoa fa'apitoa pe leai.
    2. js - se lisi o tusitusiga o le a faʻapipiʻiina i lenei taʻaloga;
    3. tuuese_matches - e le aofia ai mai le fanua match URL e fetaui ma lenei fanua.
  3. itulau_action - o le mea moni o se mea e nafa ma le atigipusa o loʻo faʻaalia i tafatafa o le tuatusi pa i totonu o le browser ma fegalegaleaiga ma ia. E fa'atagaina ai fo'i oe e fa'aali se fa'amalama fa'alauiloa, lea e fa'amatalaina e fa'aaoga ai lau oe HTML, CSS ma le JS.
    1. default_popup - ala i le faila HTML ma le popup interface, atonu e iai CSS ma JS.
  4. faatagaga - se fa'asologa mo le puleaina o aia tatau fa'aopoopo. E 3 ituaiga o aia tatau, o loʻo faʻamatalaina auiliili iinei
  5. web_accessible_resources - faʻaopoopoga punaoa e mafai e se itulau web ona talosagaina, mo se faʻataʻitaʻiga, ata, JS, CSS, faila HTML.
  6. externally_connectable - iinei e mafai ona e faʻamaonia manino ID o isi faʻaopoopoga ma vaega o itulau web e mafai ona e faʻafesoʻotaʻi. O se vaega e mafai ona tulaga lua pe maualuga. E le galue i Firefox.

Si'osi'omaga o le fa'atinoga

O le faʻaopoopoga e tolu faʻasologa o faʻatinoga, o lona uiga, o le talosaga e aofia ai vaega e tolu ma tulaga eseese o avanoa i le API suʻesuʻe.

Fa'alautele tala

Ole tele ole API ole avanoa iinei. I lenei tulaga latou te "ola":

  1. Itulau tua - "backend" vaega o le faʻaopoopoga. O le faila o loʻo faʻamaonia i le faʻaaliga e faʻaaoga ai le "background" key.
  2. Popup itulau - o se itulau popup e aliali mai pe a e kiliki i luga o le faʻaopoopoga icon. I le manifesto browser_action -> default_popup.
  3. itulau masani - itulau faʻaopoopoga, "ola" i se isi laupepa o le vaaiga chrome-extension://<id_расширения>/customPage.html.

O lenei fa'amatalaga e tuto'atasi lava mai su'esu'ega windows ma tabs. Itulau tua o loʻo i ai i se kopi e tasi ma e galue i taimi uma (e ese ai o le itulau o mea na tupu, pe a faʻalauiloa le tusitusiga i tua e se mea na tupu ma "oti" pe a uma ona faʻataunuʻuina). Popup itulau e iai pe a matala le faamalama popup, ma itulau masani — a'o tatala le fa'amau. E leai se avanoa i isi laupepa ma o latou anotusi mai lenei tala.

Anotusi fa'asologa o tusitusiga

O lo'o fa'alauiloa le faila o tusitusiga fa'atasi ma ta'iala su'esu'e ta'itasi. E maua le avanoa i se vaega o le faʻaopoopoga API ma le DOM laau o le itulau web. O tusitusiga o lo'o i totonu e nafa ma fegalegaleaiga ma le itulau. O fa'aopoopoga o lo'o fa'aogaina le la'au DOM e faia lenei mea i fa'amaumauga o mea - mo se fa'ata'ita'iga, fa'asalalauga fa'asalalau po'o fa'aliliu. E le gata i lea, o le anotusi tusitusiga e mafai ona fesoʻotaʻi ma le itulau e ala i tulaga masani postMessage.

Itulau upega tafa'ilagi

O le itulau moni lava lea. E leai se mea e fai i le faʻaopoopoga ma e leai se avanoa iina, sei vagana ai tulaga e le o faʻaalia manino ai le vaega o lenei itulau i le faʻaaliga (sili atu i lalo ifo).

Fesuiaiga o feau

O vaega eseese o le talosaga e tatau ona fesuiai feʻau ma isi. E iai le API mo lenei mea runtime.sendMessage e auina atu se savali background и tabs.sendMessage e lafo ai se fe'au i se itulau (tusi tusitusiga, popup po'o le itulau web pe a maua externally_connectable). O lo'o i lalo se fa'ata'ita'iga pe a fa'aogaina le Chrome API.

// Сообщением может быть любой JSON сериализуемый объект
const msg = {a: 'foo', b: 'bar'};

// extensionId можно не указывать, если мы хотим послать сообщение 'своему' расширению (из ui или контент скрипта)
chrome.runtime.sendMessage(extensionId, msg);

// Так выглядит обработчик
chrome.runtime.onMessage.addListener((msg) => console.log(msg))

// Можно слать сообщения вкладкам зная их id
chrome.tabs.sendMessage(tabId, msg)

// Получить к вкладкам и их id можно, например, вот так
chrome.tabs.query(
    {currentWindow: true, active : true},
    function(tabArray){
      tabArray.forEach(tab => console.log(tab.id))
    }
)

Mo feso'ota'iga atoatoa, e mafai ona e faia feso'ota'iga e ala i runtime.connect. I le tali o le a tatou maua runtime.Port, lea, a'o tatala, e mafai ona e lafoina so'o se numera o fe'au. I le itu o tagata o tausia, mo se faataitaiga, contentscript, e pei o lenei:

// Опять же extensionId можно не указывать при коммуникации внутри одного расширения. Подключение можно именовать
const port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
    if (msg.question === "Who's there?")
        port.postMessage({answer: "Madame"});
    else if (msg.question === "Madame who?")
        port.postMessage({answer: "Madame... Bovary"});

Server po'o le talaaga:

// Обработчик для подключения 'своих' вкладок. Контент скриптов, popup или страниц расширения
chrome.runtime.onConnect.addListener(function(port) {
    console.assert(port.name === "knockknock");
    port.onMessage.addListener(function(msg) {
        if (msg.joke === "Knock knock")
            port.postMessage({question: "Who's there?"});
        else if (msg.answer === "Madame")
            port.postMessage({question: "Madame who?"});
        else if (msg.answer === "Madame... Bovary")
            port.postMessage({question: "I don't get it."});
    });
});

// Обработчик для подключения внешних вкладок. Других расширений или веб страниц, которым разрешен доступ в манифесте
chrome.runtime.onConnectExternal.addListener(function(port) {
    ...
});

E iai foi se mea na tupu onDisconnect ma metotia disconnect.

Fa'aoga ata

Sei o tatou faia se faʻaopoopoga o suʻesuʻega e teu ai ki faʻapitoa, maua avanoa i faʻamatalaga lautele (tuatusi, faʻamatalaga lautele e fesoʻotaʻi ma le itulau ma faʻatagaina talosaga a isi vaega e talosagaina se saini mo fefaʻatauaiga.

Fausiaina o talosaga

O la matou talosaga e tatau ona fegalegaleai uma ma le tagata faʻaoga ma tuʻuina atu le itulau i se API e valaʻau ai metotia (mo se faʻataʻitaʻiga, e sainia fefaʻatauaiga). Fai i le na'o le tasi contentscript o le a le aoga, talu ai e na o le avanoa i le DOM, ae le o le JS o le itulau. Fesootai e ala i runtime.connect e le mafai, ona o le API e manaʻomia i luga o vaega uma, ma naʻo mea faʻapitoa e mafai ona faʻamaonia i le faʻaaliga. O se taunuuga, o le ata o le a pei o lenei:

Tusia ose fa'aopoopoga fa'amautu

O le ai ai se isi tusitusiga - inpage, lea o le a matou tui i totonu o le itulau. O le a tamoʻe i lona tulaga ma tuʻuina atu se API mo le galue ma le faʻaopoopoga.

Aiga

O lo'o maua uma le code extension browser ile GitHub. I le taimi o le faʻamatalaga o le ai ai fesoʻotaʻiga i tautinoga.

Tatou amata i le manifesto:

{
  // Имя и описание, версия. Все это будет видно в браузере в chrome://extensions/?id=<id расширения>
  "name": "Signer",
  "description": "Extension demo",
  "version": "0.0.1",
  "manifest_version": 2,

  // Скрипты, которые будут исполнятся в background, их может быть несколько
  "background": {
    "scripts": ["background.js"]
  },

  // Какой html использовать для popup
  "browser_action": {
    "default_title": "My Extension",
    "default_popup": "popup.html"
  },

  // Контент скрипты.
  // У нас один объект: для всех url начинающихся с http или https мы запускаем
  // contenscript context со скриптом contentscript.js. Запускать сразу по получении документа для всех фреймов
  "content_scripts": [
    {
      "matches": [
        "http://*/*",
        "https://*/*"
      ],
      "js": [
        "contentscript.js"
      ],
      "run_at": "document_start",
      "all_frames": true
    }
  ],
  // Разрешен доступ к localStorage и idle api
  "permissions": [
    "storage",
    // "unlimitedStorage",
    //"clipboardWrite",
    "idle"
    //"activeTab",
    //"webRequest",
    //"notifications",
    //"tabs"
  ],
  // Здесь указываются ресурсы, к которым будет иметь доступ веб страница. Тоесть их можно будет запрашивать fetche'м или просто xhr
  "web_accessible_resources": ["inpage.js"]
}

Fausia background.js gaogao, popup.js, inpage.js ma contentscript.js. Matou te faaopoopo popup.html - ma o la matou talosaga e mafai ona utaina i totonu o Google Chrome ma ia mautinoa e aoga.

Ina ia faʻamaonia lenei mea, e mafai ona e ave le code mai iinei. I le faʻaopoopoga i mea na matou faia, o le fesoʻotaʻiga na faʻapipiʻiina le faʻapotopotoga o le poloketi e faʻaaoga ai webpack. Ina ia faʻaopoopo se talosaga i le suʻesuʻega, i le chrome://extensions e te manaʻomia e filifili le uta e leʻi faʻapipiʻiina ma le faila ma le faʻaopoopoga tutusa - i la matou mataupu dist.

Tusia ose fa'aopoopoga fa'amautu

O lea ua fa'apipi'i ma galue le matou fa'aopoopoga. E mafai ona e faʻatautaia meafaigaluega faʻapitoa mo tulaga eseese e pei ona taua i lalo:

popup ->

Tusia ose fa'aopoopoga fa'amautu

O le avanoa i le faʻamafanafanaga faʻamaumauga o loʻo faʻatinoina e ala i le faʻamafanafanaga o le itulau lava ia o loʻo faʻalauiloaina ai.Tusia ose fa'aopoopoga fa'amautu

Fesuiaiga o feau

O lea la, e manaʻomia ona tatou faʻatuina ni auala fesoʻotaʻiga se lua: inpage <-> background ma popup <-> background. E mafai, ioe, naʻo le auina atu o feʻau i le taulaga ma fai lau oe lava faʻasalalauga, ae ou te fiafia i le auala na ou vaʻaia i le metamask open source project.

Ole fa'aopoopoga ole su'esu'ega lea mo le galulue fa'atasi ma le feso'ota'iga Ethereum. I totonu, o vaega eseese o le talosaga e fesoʻotaʻi e ala i le RPC e faʻaaoga ai le faletusi dnode. E mafai ai e oe ona faʻatulagaina se fefaʻatauaʻiga vave ma faigofie pe afai e te tuʻuina atu i ai se vaitafe nodejs e avea o se felauaiga (o lona uiga o se mea e faʻaogaina le atinaʻe tutusa):

import Dnode from "dnode/browser";

// В этом примере условимся что клиент удаленно вызывает функции на сервере, хотя ничего нам не мешает сделать это двунаправленным

// Cервер
// API, которое мы хотим предоставить
const dnode = Dnode({
    hello: (cb) => cb(null, "world")
})
// Транспорт, поверх которого будет работать dnode. Любой nodejs стрим. В браузере есть бибилиотека 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)

// Клиент
const dnodeClient = Dnode() // Вызов без агрумента значит что мы не предоставляем API на другой стороне

// Выведет в консоль world
dnodeClient.once('remote', remote => {
    remote.hello(((err, value) => console.log(value)))
})

O lenei o le a tatou faia se vasega talosaga. O le a faia ai mea API mo le popup ma le itulau web, ma fatuina se dnode mo i latou:

import Dnode from 'dnode/browser';

export class SignerApp {

    // Возвращает объект API для ui
    popupApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // Возвращает объет API для страницы
    pageApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // Подключает popup ui
    connectPopup(connectionStream){
        const api = this.popupApi();
        const dnode = Dnode(api);

        connectionStream.pipe(dnode).pipe(connectionStream);

        dnode.on('remote', (remote) => {
            console.log(remote)
        })
    }

    // Подключает страницу
    connectPage(connectionStream, origin){
        const api = this.popupApi();
        const dnode = Dnode(api);

        connectionStream.pipe(dnode).pipe(connectionStream);

        dnode.on('remote', (remote) => {
            console.log(origin);
            console.log(remote)
        })
    }
}

O iinei ma lalo ifo, nai lo le lalolagi Chrome mea faitino, matou te faʻaogaina le extensionApi, lea e maua ai le Chrome i le Google browser ma le browser i isi. E faia lenei mea mo feso'ota'iga feso'ota'iga su'esu'e, ae mo fa'amoemoega o lenei tala e mafai ona fa'aoga le 'chrome.runtime.connect'.

Sei o tatou fatuina se faʻataʻitaʻiga faʻataʻitaʻiga i le tusitusiga pito i tua:

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";

const app = new SignerApp();

// onConnect срабатывает при подключении 'процессов' (contentscript, popup, или страница расширения)
extensionApi.runtime.onConnect.addListener(connectRemote);

function connectRemote(remotePort) {
    const processName = remotePort.name;
    const portStream = new PortStream(remotePort);
    // При установке соединения можно указывать имя, по этому имени мы и оппределяем кто к нам подлючился, контентскрипт или ui
    if (processName === 'contentscript'){
        const origin = remotePort.sender.url
        app.connectPage(portStream, origin)
    }else{
        app.connectPopup(portStream)
    }
}

Talu ai e galue le dnode i vaitafe, ma matou maua se taulaga, e manaʻomia se vasega faʻapipiʻi. E faia i le faʻaaogaina o le potutusi faitau-vaivai, lea e faʻaogaina ai nodejs vaitafe i totonu o le masini:

import {Duplex} from 'readable-stream';

export class PortStream extends Duplex{
    constructor(port){
        super({objectMode: true});
        this._port = port;
        port.onMessage.addListener(this._onMessage.bind(this));
        port.onDisconnect.addListener(this._onDisconnect.bind(this))
    }

    _onMessage(msg) {
        if (Buffer.isBuffer(msg)) {
            delete msg._isBuffer;
            const data = new Buffer(msg);
            this.push(data)
        } else {
            this.push(msg)
        }
    }

    _onDisconnect() {
        this.destroy()
    }

    _read(){}

    _write(msg, encoding, cb) {
        try {
            if (Buffer.isBuffer(msg)) {
                const data = msg.toJSON();
                data._isBuffer = true;
                this._port.postMessage(data)
            } else {
                this._port.postMessage(msg)
            }
        } catch (err) {
            return cb(new Error('PortStream - disconnected'))
        }
        cb()
    }
}

Sei o tatou faia se fesoʻotaʻiga i le UI:

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import Dnode from 'dnode/browser';

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupUi().catch(console.error);

async function setupUi(){
    // Также, как и в классе приложения создаем порт, оборачиваем в stream, делаем  dnode
    const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
    const connectionStream = new PortStream(backgroundPort);

    const dnode = Dnode();

    connectionStream.pipe(dnode).pipe(connectionStream);

    const background = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // Делаем объект API доступным из консоли
    if (DEV_MODE){
        global.background = background;
    }
}

Ona matou fatuina lea o le fesoʻotaʻiga i totonu o tusitusiga:

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import PostMessageStream from 'post-message-stream';

setupConnection();
injectScript();

function setupConnection(){
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

    pageStream.pipe(backgroundStream).pipe(pageStream);
}

function injectScript(){
    try {
        // inject in-page script
        let script = document.createElement('script');
        script.src = extensionApi.extension.getURL('inpage.js');
        const container = document.head || document.documentElement;
        container.insertBefore(script, container.children[0]);
        script.onload = () => script.remove();
    } catch (e) {
        console.error('Injection failed.', e);
    }
}

Talu ai matou te manaʻomia le API e leʻo i totonu o tusitusiga, ae tuusaʻo i luga o le itulau, matou te faia ni mea se lua:

  1. Matou te faia ni vaitafe se lua. Tasi - agai i le itulau, i luga o le postMessage. Mo lenei mea matou te faʻaaogaina lenei mea lenei afifi mai le na faia o metamask. O le vaitafe lona lua o le tua i luga o le uafu na maua mai runtime.connect. Tatou faatau. O lea la o le itulau o le ai ai se vaitafe i tua.
  2. Tu'u le tusitusiga ile DOM. La'u mai le fa'amaumauga (ua fa'atagaina i le fa'aaliga) ma fai se pine script ma ona mea i totonu:

import PostMessageStream from 'post-message-stream';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";

setupConnection();
injectScript();

function setupConnection(){
    // Стрим к бекграунду
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    // Стрим к странице
    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

    pageStream.pipe(backgroundStream).pipe(pageStream);
}

function injectScript(){
    try {
        // inject in-page script
        let script = document.createElement('script');
        script.src = extensionApi.extension.getURL('inpage.js');
        const container = document.head || document.documentElement;
        container.insertBefore(script, container.children[0]);
        script.onload = () => script.remove();
    } catch (e) {
        console.error('Injection failed.', e);
    }
}

O lenei matou te fatuina se mea api i le inpage ma seti i le lalolagi:

import PostMessageStream from 'post-message-stream';
import Dnode from 'dnode/browser';

setupInpageApi().catch(console.error);

async function setupInpageApi() {
    // Стрим к контентскрипту
    const connectionStream = new PostMessageStream({
        name: 'page',
        target: 'content',
    });

    const dnode = Dnode();

    connectionStream.pipe(dnode).pipe(connectionStream);

    // Получаем объект API
    const pageApi = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // Доступ через window
    global.SignerApp = pageApi;
}

Ua matou sauni Valaau Fa'asologa Mamao (RPC) fa'atasi ai ma API eseese mo itulau ma UI. Pe a faʻafesoʻotaʻi se itulau fou i tua e mafai ona tatou vaʻaia lenei:

Tusia ose fa'aopoopoga fa'amautu

Gaogao API ma le amataga. I le itu itulau, e mafai ona tatou taʻua le galuega faʻafeiloaʻi e pei o lenei:

Tusia ose fa'aopoopoga fa'amautu

O le galue i galuega toe fo'i i le JS fa'aonaponei o ni uiga leaga, se'i o tatou tusi se fesoasoani la'ititi e fai se dnode e mafai ai ona e pasi atu se mea API i fa'aoga.

Ole mea ole API ole a foliga nei:

export class SignerApp {

    popupApi() {
        return {
            hello: async () => "world"
        }
    }

...

}

Mauaina o se mea mai le mamao e pei o lenei:

import {cbToPromise, transformMethods} from "../../src/utils/setupDnode";

const pageApi = await new Promise(resolve => {
    dnode.once('remote', remoteApi => {
        // С помощью утилит меняем все callback на promise
        resolve(transformMethods(cbToPromise, remoteApi))
    })
});

Ma o le valaʻau galuega e toe faʻafoʻi ai se folafolaga:

Tusia ose fa'aopoopoga fa'amautu

Fa'aliliuga ma galuega asynchronous avanoa iinei.

I le aotelega, o le RPC ma le auala e foliga mai e faigofie tele: e mafai ona tatou faʻaogaina le tele o vaʻa ma fatuina ni API eseese mo galuega eseese. I le mataupu faavae, e mafai ona faʻaaogaina le dnode i soʻo se mea, o le mea autu o le afifiina o felauaiga i foliga o se vaitafe nodejs.

O se isi mea o le JSON format, lea e faʻaaogaina ai le JSON RPC 2. Ae ui i lea, e galue i felauaiga faʻapitoa (TCP ma HTTP(S)), e le talafeagai i la tatou mataupu.

Setete i totonu ma localStorage

Matou te manaʻomia le teuina o le tulaga i totonu o le talosaga - a itiiti ifo o ki saini. E faigofie lava ona matou faʻaopoopoina se setete i le talosaga ma metotia mo le suia i le popup API:

import {setupDnode} from "./utils/setupDnode";

export class SignerApp {

    constructor(){
        this.store = {
            keys: [],
        };
    }

    addKey(key){
        this.store.keys.push(key)
    }

    removeKey(index){
        this.store.keys.splice(index,1)
    }

    popupApi(){
        return {
            addKey: async (key) => this.addKey(key),
            removeKey: async (index) => this.removeKey(index)
        }
    }

    ...

} 

I tua, matou te afifi mea uma i se galuega ma tusi le mea faʻaoga i le faamalama ina ia mafai ona matou galulue faʻatasi mai le faʻamafanafanaga:

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupApp();

function setupApp() {
    const app = new SignerApp();

    if (DEV_MODE) {
        global.app = app;
    }

    extensionApi.runtime.onConnect.addListener(connectRemote);

    function connectRemote(remotePort) {
        const processName = remotePort.name;
        const portStream = new PortStream(remotePort);
        if (processName === 'contentscript') {
            const origin = remotePort.sender.url;
            app.connectPage(portStream, origin)
        } else {
            app.connectPopup(portStream)
        }
    }
}

Sei o tatou faʻaopoopoina ni nai ki mai le faʻamafanafanaga UI ma vaʻai pe o le a le mea e tupu i le setete:

Tusia ose fa'aopoopoga fa'amautu

O le setete e manaʻomia le faʻaauau pea ina ia le leiloa ki pe a toe amata.

O le a matou teuina i totonu o le localStorage, fa'asolo i suiga uma. Mulimuli ane, o le avanoa i ai o le a manaʻomia foi mo le UI, ma ou te manaʻo foi e lesitala i suiga. Faʻavae i luga o lenei mea, o le a faigofie le fatuina o se teuina mataʻituina ma faʻasoa i ana suiga.

O le a matou faʻaaogaina le faletusi mobx (https://github.com/mobxjs/mobx). Na pau le filifiliga ona sa ou le tau galue i ai, ae sa ou matuai manao lava e suesue i ai.

Sei o tatou faʻaopoopo le amataga o le tulaga muamua ma faʻaalia le faleoloa:

import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";

export class SignerApp {

    constructor(initState = {}) {
        // Внешне store так и останется тем же объектом, только теперь все его поля стали proxy, которые отслеживают доступ к ним
        this.store =  observable.object({
            keys: initState.keys || [],
        });
    }

    // Методы, которые меняют observable принято оборачивать декоратором
    @action
    addKey(key) {
        this.store.keys.push(key)
    }

    @action
    removeKey(index) {
        this.store.keys.splice(index, 1)
    }

    ...

}

“I lalo o le pulou,” ua suia uma e mobx fanua faleoloa i sui ma taofia uma valaau ia i latou. O le a mafai ona lesitala i nei savali.

Lalo o le a masani ona ou faʻaogaina le faaupuga "pe a suia", e ui lava e leʻo saʻo atoatoa. Mobx siaki avanoa i fanua. E fa'aoga ma fa'atūina mea fai sui na faia e le faletusi.

E lua fa'amoemoega e fa'atino ai galuega teuteu:

  1. I le faiga sa'o ma le fu'a enforceActions, e fa'asa e mobx le suia sa'o o le setete. E manatu e lelei le galue i lalo o tulaga sa'o.
  2. E tusa lava pe suia e se galuega le setete i le tele o taimi - mo se faʻataʻitaʻiga, matou te suia le tele o fanua i le tele o laina faʻailoga - e logoina le au matau pe a maeʻa. E taua tele lenei mea mo le pito i luma, lea e le manaʻomia ai faʻafouga a le setete e taʻitaʻia ai le le manaʻomia o elemene. I la matou tulaga, e le o le muamua poʻo le lona lua e sili ona taua, ae o le a matou mulimuli i faiga sili ona lelei. O se aga masani le faʻapipiʻiina o mea teuteu i galuega uma e suia ai le tulaga o fanua mataʻituina.

I tua o le a matou faʻaopoopoina le amataga ma le faʻasaoina o le setete i localStorage:

import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
// Вспомогательные методы. Записывают/читают объект в/из localStorage виде JSON строки по ключу 'store'
import {loadState, saveState} from "./utils/localStorage";

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupApp();

function setupApp() {
    const initState = loadState();
    const app = new SignerApp(initState);

    if (DEV_MODE) {
        global.app = app;
    }

    // Setup state persistence

    // Результат reaction присваивается переменной, чтобы подписку можно было отменить. Нам это не нужно, оставлено для примера
    const localStorageReaction = reaction(
        () => toJS(app.store), // Функция-селектор данных
        saveState // Функция, которая будет вызвана при изменении данных, которые возвращает селектор
    );

    extensionApi.runtime.onConnect.addListener(connectRemote);

    function connectRemote(remotePort) {
        const processName = remotePort.name;
        const portStream = new PortStream(remotePort);
        if (processName === 'contentscript') {
            const origin = remotePort.sender.url
            app.connectPage(portStream, origin)
        } else {
            app.connectPopup(portStream)
        }
    }
}

O le gaioiga tali e manaia iinei. E lua ona finauga:

  1. Filifili fa'amatalaga.
  2. O se tagata e faʻaaogaina o le a valaʻau ma nei faʻamatalaga i taimi uma e suia ai.

E le pei o le redux, lea tatou te maua manino ai le setete o se finauga, e manatua e mobx poʻo fea mea e mafai ona tatou maua i totonu o le tagata filifilia, ma naʻo le valaʻau i le tagata e faʻaaogaina pe a suia.

E taua le malamalama lelei pe fa'afefea ona filifili e mobx po'o fea mata'itusi tatou te lesitala i ai. Afai na ou tusia se tagata filifilia i le code e pei o lenei() => app.store, ona le taʻua lea o le tali, talu ai o le teuina lava ia e le mafai ona matauina, naʻo ona fanua.

Afai na ou tusia faapea () => app.store.keys, toe leai se mea e tupu, talu ai pe a faʻaopoopo / aveese elemene elemene, o le faʻasino i ai o le a le suia.

Mobx galue o se tagata filifilia mo le taimi muamua ma na'o le siakiina o mea e mafai ona tatou mauaina. E faia lenei mea e ala i sui sui. O le mea lea, o loʻo faʻaaogaina le galuega faʻapipiʻi iinei toJS. E toe fa'afo'i mai se mea fou ma sui uma ua suia i ulua'i fanua. I le taimi o le faʻatinoga, e faitau uma vaega o le mea - o le mea lea e faʻaosoina ai le getters.

I totonu o le popup console o le a matou toe faʻaopoopoina ni nai ki. O le taimi lea na latou iu ai foi i le localStorage:

Tusia ose fa'aopoopoga fa'amautu

A toe uta le itulau pito i tua, e tumau pea le faʻamatalaga.

E mafai ona va'aia uma le numera o talosaga e o'o mai i lenei tulaga iinei.

Saogalemu le teuina o ki patino

O le teuina o ki patino i tusitusiga manino e le saogalemu: o loʻo i ai pea le avanoa e faʻafefe ai oe, maua le avanoa i lau komepiuta, ma isi. O le mea lea, i totonu o le localStorage o le a matou teuina ki i se faʻailoga faʻailoga.

Mo le saogalemu sili atu, o le a matou faʻaopoopoina se setete loka i le talosaga, lea o le a leai se avanoa i ki uma. O le a otometi lava ona matou tu'uina atu le fa'aopoopoga i le tulaga loka ona o se taimi e malolo ai.

Mobx faʻatagaina oe e teuina naʻo se seti o faʻamaumauga, ma o le isi vaega e otometi lava ona fuafua e faʻatatau i ai. O mea ia e ta'ua o mea tau tupe. E mafai ona faʻatusatusa i latou i vaaiga i faʻamaumauga:

import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// Утилиты для безопасного шифрования строк. Используют crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";

export class SignerApp {
    constructor(initState = {}) {
        this.store = observable.object({
            // Храним пароль и зашифрованные ключи. Если пароль null - приложение locked
            password: null,
            vault: initState.vault,

            // Геттеры для вычислимых полей. Можно провести аналогию с view в бд.
            get locked(){
                return this.password == null
            },
            get keys(){
                return this.locked ?
                    undefined :
                    SignerApp._decryptVault(this.vault, this.password)
            },
            get initialized(){
                return this.vault !== undefined
            }
        })
    }
    // Инициализация пустого хранилища новым паролем
    @action
    initVault(password){
        this.store.vault = SignerApp._encryptVault([], password)
    }
    @action
    lock() {
        this.store.password = null
    }
    @action
    unlock(password) {
        this._checkPassword(password);
        this.store.password = password
    }
    @action
    addKey(key) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
    }
    @action
    removeKey(index) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault([
                ...this.store.keys.slice(0, index),
                ...this.store.keys.slice(index + 1)
            ],
            this.store.password
        )
    }

    ... // код подключения и api

    // private
    _checkPassword(password) {
        SignerApp._decryptVault(this.store.vault, password);
    }

    _checkLocked() {
        if (this.store.locked){
            throw new Error('App is locked')
        }
    }

    // Методы для шифровки/дешифровки хранилища
    static _encryptVault(obj, pass){
        const jsonString = JSON.stringify(obj)
        return encrypt(jsonString, pass)
    }

    static _decryptVault(str, pass){
        if (str === undefined){
            throw new Error('Vault not initialized')
        }
        try {
            const jsonString = decrypt(str, pass)
            return JSON.parse(jsonString)
        }catch (e) {
            throw new Error('Wrong password')
        }
    }
}

O lea ua na'o matou teuina ki fa'ailoga ma upu fa'aulu. O isi mea uma ua fuafuaina. Matou te faia le fesiitaiga i se setete loka e ala i le aveesea o le upu faataga mai le setete. O le API lautele ua i ai nei se metotia mo le amataina o le teuina.

Tusia mo fa'ailoga fa'aoga fa'aoga crypto-js:

import CryptoJS from 'crypto-js'

// Используется для осложнения подбора пароля перебором. На каждый вариант пароля злоумышленнику придется сделать 5000 хешей
function strengthenPassword(pass, rounds = 5000) {
    while (rounds-- > 0){
        pass = CryptoJS.SHA256(pass).toString()
    }
    return pass
}

export function encrypt(str, pass){
    const strongPass = strengthenPassword(pass);
    return CryptoJS.AES.encrypt(str, strongPass).toString()
}

export function decrypt(str, pass){
    const strongPass = strengthenPassword(pass)
    const decrypted = CryptoJS.AES.decrypt(str, strongPass);
    return decrypted.toString(CryptoJS.enc.Utf8)
}

O lo'o i ai i le su'esu'e se API fa'aoga e mafai ai ona e lesitala i se mea na tupu - suiga o le setete. Setete, e tusa ai, atonu idle, active и locked. Mo le le aoga e mafai ona e setiina se taimi malolo, ma loka e seti pe a poloka le OS lava ia. O le a matou suia foi le filifiliga mo le teuina i localStorage:

import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
import {loadState, saveState} from "./utils/localStorage";

const DEV_MODE = process.env.NODE_ENV !== 'production';
const IDLE_INTERVAL = 30;

setupApp();

function setupApp() {
    const initState = loadState();
    const app = new SignerApp(initState);

    if (DEV_MODE) {
        global.app = app;
    }

    // Теперь мы явно узываем поле, которому будет происходить доступ, reaction отработает нормально
    reaction(
        () => ({
            vault: app.store.vault
        }),
        saveState
    );

    // Таймаут бездействия, когда сработает событие
    extensionApi.idle.setDetectionInterval(IDLE_INTERVAL);
    // Если пользователь залочил экран или бездействовал в течение указанного интервала лочим приложение
    extensionApi.idle.onStateChanged.addListener(state => {
        if (['locked', 'idle'].indexOf(state) > -1) {
            app.lock()
        }
    });

    // Connect to other contexts
    extensionApi.runtime.onConnect.addListener(connectRemote);

    function connectRemote(remotePort) {
        const processName = remotePort.name;
        const portStream = new PortStream(remotePort);
        if (processName === 'contentscript') {
            const origin = remotePort.sender.url
            app.connectPage(portStream, origin)
        } else {
            app.connectPopup(portStream)
        }
    }
}

O le code i luma o lenei laasaga o le iinei.

Feuaiga

O lea la, matou te oʻo mai i le mea sili ona taua: fatuina ma sainia fefaʻatauaiga i luga o le poloka poloka. O le a matou faʻaogaina le WAVES poloka poloka ma le faletusi galu-gaoioiga.

Muamua, tatou faʻaopoopo i le setete se faʻasologa o feʻau e manaʻomia ona sainia, ona faʻaopoopo lea o metotia mo le faʻaopoopoina o se feʻau fou, faʻamaonia le saini, ma le teena:

import {action, observable, reaction} from 'mobx';
import uuid from 'uuid/v4';
import {signTx} from '@waves/waves-transactions'
import {setupDnode} from "./utils/setupDnode";
import {decrypt, encrypt} from "./utils/cryptoUtils";

export class SignerApp {

    ...

    @action
    newMessage(data, origin) {
        // Для каждого сообщения создаем метаданные с id, статусом, выременем создания и тд.
        const message = observable.object({
            id: uuid(), // Идентификатор, используюю uuid
            origin, // Origin будем впоследствии показывать в интерфейсе
            data, //
            status: 'new', // Статусов будет четыре: new, signed, rejected и failed
            timestamp: Date.now()
        });
        console.log(`new message: ${JSON.stringify(message, null, 2)}`);

        this.store.messages.push(message);

        // Возвращаем промис внутри которого mobx мониторит изменения сообщения. Как только статус поменяется мы зарезолвим его
        return new Promise((resolve, reject) => {
            reaction(
                () => message.status, //Будем обсервить статус сообщеня
                (status, reaction) => { // второй аргумент это ссылка на сам reaction, чтобы его можно было уничтожть внутри вызова
                    switch (status) {
                        case 'signed':
                            resolve(message.data);
                            break;
                        case 'rejected':
                            reject(new Error('User rejected message'));
                            break;
                        case 'failed':
                            reject(new Error(message.err.message));
                            break;
                        default:
                            return
                    }
                    reaction.dispose()
                }
            )
        })
    }
    @action
    approve(id, keyIndex = 0) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        try {
            message.data = signTx(message.data, this.store.keys[keyIndex]);
            message.status = 'signed'
        } catch (e) {
            message.err = {
                stack: e.stack,
                message: e.message
            };
            message.status = 'failed'
            throw e
        }
    }
    @action
    reject(id) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        message.status = 'rejected'
    }

    ...
}

A matou mauaina se feʻau fou, matou te faʻaopoopo metadata i ai, fai observable ma faaopoopo i store.messages.

Afai e te le faia observable ma le lima, ona faia lea e le mobx lava ia pe a faʻaopoopo feʻau i le laina. Ae ui i lea, o le a fatuina ai se mea fou e le maua ai se matou faʻamatalaga, ae matou te manaʻomia mo le isi laasaga.

O le isi, matou te toe faʻafoʻi atu se folafolaga e faʻamalieina pe a suia le tulaga o le feʻau. O le tulaga e mataʻituina e ala i le tali atu, lea o le a "fasioti" pe a suia le tulaga.

Metotia code approve и reject faigofie tele: matou te suia le tulaga o le feʻau, pe a uma ona sainia pe a manaʻomia.

Matou te tuʻuina le Approve ma teena i le UI API, newMessage i le itulau API:

export class SignerApp {
    ...
    popupApi() {
        return {
            addKey: async (key) => this.addKey(key),
            removeKey: async (index) => this.removeKey(index),

            lock: async () => this.lock(),
            unlock: async (password) => this.unlock(password),
            initVault: async (password) => this.initVault(password),

            approve: async (id, keyIndex) => this.approve(id, keyIndex),
            reject: async (id) => this.reject(id)
        }
    }

    pageApi(origin) {
        return {
            signTransaction: async (txParams) => this.newMessage(txParams, origin)
        }
    }

    ...
}

Sei o tatou taumafai e sainia le fefaʻatauaiga ma le faʻaopoopoga:

Tusia ose fa'aopoopoga fa'amautu

I se tulaga lautele, ua saunia mea uma, o mea uma e totoe fa'aopoopo UI faigofie.

UI

E mana'omia e le atina'e le avanoa ile tulaga ole talosaga. I le itu UI o le a matou faia observable setete ma faʻaopoopo se galuega i le API o le a suia ai lenei setete. Tatou faaopoopo observable i le mea API maua mai tua:

import {observable} from 'mobx'
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {cbToPromise, setupDnode, transformMethods} from "./utils/setupDnode";
import {initApp} from "./ui/index";

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupUi().catch(console.error);

async function setupUi() {
    // Подключаемся к порту, создаем из него стрим
    const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
    const connectionStream = new PortStream(backgroundPort);

    // Создаем пустой observable для состояния background'a
    let backgroundState = observable.object({});
    const api = {
        //Отдаем бекграунду функцию, которая будет обновлять observable
        updateState: async state => {
            Object.assign(backgroundState, state)
        }
    };

    // Делаем RPC объект
    const dnode = setupDnode(connectionStream, api);
    const background = await new Promise(resolve => {
        dnode.once('remote', remoteApi => {
            resolve(transformMethods(cbToPromise, remoteApi))
        })
    });

    // Добавляем в background observable со стейтом
    background.state = backgroundState;

    if (DEV_MODE) {
        global.background = background;
    }

    // Запуск интерфейса
    await initApp(background)
}

I le fa'ai'uga tatou amata tu'uina atu le fa'aoga fa'aoga. O se fa'aoga tali atu lea. O le mea i tua e na'o le pasi e fa'aaoga ai mea fa'apipi'i. O le a saʻo, ioe, le faia o se auaunaga ese mo metotia ma se faleoloa mo le setete, ae mo faamoemoega o lenei tusiga ua lava lea:

import {render} from 'react-dom'
import App from './App'
import React from "react";

// Инициализируем приложение с background объектом в качест ве props
export async function initApp(background){
    render(
        <App background={background}/>,
        document.getElementById('app-content')
    );
}

Faatasi ai ma le mobx e matua faigofie lava ona amata faʻaalia pe a suia faʻamatalaga. Matou te tautau na o le tagata teuteu teuteu mai le afifi mobx-react i luga o le vaega, ma o le a otometi lava ona valaʻau pe a soʻo se mea e vaʻaia e faʻasino e le vaega e suia. E te le manaʻomia se mapStateToProps pe faʻafesoʻotaʻi pei o le redux. E aoga mea uma mai le pusa:

import React, {Component, Fragment} from 'react'
import {observer} from "mobx-react";
import Init from './components/Initialize'
import Keys from './components/Keys'
import Sign from './components/Sign'
import Unlock from './components/Unlock'

@observer // У Компонета с этим декоратом будет автоматически вызван метод render, если будут изменены observable на которые он ссылается
export default class App extends Component {

    // Правильно конечно вынести логику рендера страниц в роутинг и не использовать вложенные тернарные операторы,
    // и привязывать observable и методы background непосредственно к тем компонентам, которые их используют
    render() {
        const {keys, messages, initialized, locked} = this.props.background.state;
        const {lock, unlock, addKey, removeKey, initVault, deleteVault, approve, reject} = this.props.background;

        return <Fragment>
            {!initialized
                ?
                <Init onInit={initVault}/>
                :
                locked
                    ?
                    <Unlock onUnlock={unlock}/>
                    :
                    messages.length > 0
                        ?
                        <Sign keys={keys} message={messages[messages.length - 1]} onApprove={approve} onReject={reject}/>
                        :
                        <Keys keys={keys} onAdd={addKey} onRemove={removeKey}/>
            }
            <div>
                {!locked && <button onClick={() => lock()}>Lock App</button>}
                {initialized && <button onClick={() => deleteVault()}>Delete all keys and init</button>}
            </div>
        </Fragment>
    }
}

O vaega o totoe e mafai ona vaʻaia i le code i le faila UI.

I le taimi nei i le vasega talosaga e te manaʻomia le faia o se setete filifilia mo le UI ma logo le UI pe a suia. Ina ia faia lenei mea, seʻi o tatou faʻaopoopoina se metotia getState и reactionvalaau remote.updateState:

import {action, observable, reaction} from 'mobx';
import uuid from 'uuid/v4';
import {signTx} from '@waves/waves-transactions'
import {setupDnode} from "./utils/setupDnode";
import {decrypt, encrypt} from "./utils/cryptoUtils";

export class SignerApp {

    ...

    // public
    getState() {
        return {
            keys: this.store.keys,
            messages: this.store.newMessages,
            initialized: this.store.initialized,
            locked: this.store.locked
        }
    }

    ...

    //
    connectPopup(connectionStream) {
        const api = this.popupApi();
        const dnode = setupDnode(connectionStream, api);

        dnode.once('remote', (remote) => {
            // Создаем reaction на изменения стейта, который сделает вызовет удаленну процедуру и обновит стейт в ui процессе
            const updateStateReaction = reaction(
                () => this.getState(),
                (state) => remote.updateState(state),
                // Третьим аргументом можно передавать параметры. fireImmediatly значит что reaction выполниться первый раз сразу.
                // Это необходимо, чтобы получить начальное состояние. Delay позволяет установить debounce
                {fireImmediately: true, delay: 500}
            );
            // Удалим подписку при отключении клиента
            dnode.once('end', () => updateStateReaction.dispose())

        })
    }

    ...
}

Pe a maua se mea faitino remote faia reaction e sui le setete e taʻua le galuega i le itu UI.

O le pa'i mulimuli o le fa'aopoopoina lea o le fa'aaliga o fe'au fou i luga o le fa'aopoopoga icon:

function setupApp() {
...

    // Reaction на выставление текста беджа.
    reaction(
        () => app.store.newMessages.length > 0 ? app.store.newMessages.length.toString() : '',
        text => extensionApi.browserAction.setBadgeText({text}),
        {fireImmediately: true}
    );

...
}

O lea, ua saunia le talosaga. O itulau i luga ole laiga e mafai ona talosagaina se saini mo fefaʻatauaiga:

Tusia ose fa'aopoopoga fa'amautu

Tusia ose fa'aopoopoga fa'amautu

O lo'o maua le code iinei fesoʻotaʻiga.

iʻuga

Afai na e faitauina le tusiga e oʻo i le faaiuga, ae o loʻo i ai pea ni fesili, e mafai ona e fesili i ai fale teu oloa ma faʻaopoopoga. O iina e te maua ai fo'i ta'utinoga mo laasaga ta'itasi.

Ma afai e te fiafia e vaʻai i le code mo le faʻaopoopoga moni, e mafai ona e mauaina lenei iinei.

Code, fale teu oloa ma fa'amatalaga galuega mai siemarell

puna: www.habr.com

Faaopoopo i ai se faamatalaga