Na-ede ndọtị ihe nchọgharị echedoro

Na-ede ndọtị ihe nchọgharị echedoro

N'adịghị ka ụlọ ọrụ “onye ahịa-ihe nkesa” a na-ahụkarị, ngwa ewepụrụ agbaji bụ:

  • Ọ dịghị mkpa ịchekwa nchekwa data na nbanye onye ọrụ na okwuntughe. A na-echekwa ozi nnweta naanị site n'aka ndị ọrụ n'onwe ha, na nkwenye nke izi ezi ha na-apụta n'ọkwa protocol.
  • Ọ dịghị mkpa iji ihe nkesa. Enwere ike ime mgbagha ngwa ahụ na netwọk blockchain, ebe enwere ike ịchekwa ọnụọgụ data achọrọ.

Enwere nchekwa nchekwa 2 dị oke mma maka igodo onye ọrụ - obere akpa ngwaike na mgbakwunye ihe nchọgharị. Wallet ngwaike na-enwekarị nchekwa nke ukwuu, mana ọ siri ike iji ma dị anya n'efu, mana mgbakwunye ihe nchọgharị bụ ngwakọta zuru oke nke nchekwa yana ịdị mfe iji, yana nwekwara ike nweere onwe ya kpamkpam maka ndị ọrụ njedebe.

N'iburu ihe ndị a niile n'uche, anyị chọrọ ime ndọtị kachasị dị nchebe nke na-eme ka mmepe nke ngwa ndị a na-ekewaghị ekewa dị mfe site n'inye API dị mfe maka ịrụ ọrụ na azụmahịa na mbinye aka.
Anyị ga-agwa gị gbasara ahụmahụ a n'okpuru.

Edemede a ga-enwe ntuziaka nzọụkwụ site na otu esi ede ndọtị ihe nchọgharị, yana ihe atụ koodu na nseta ihuenyo. Ị nwere ike ịchọta koodu niile na ya ebe nchekwa. Nkwekọrịta ọ bụla dabara na akụkụ nke akụkọ a.

Akụkọ dị mkpirikpi nke ndọtị ihe nchọgharị

Mgbatị ihe nchọgharị adịla ogologo oge. Ha pụtara na Internet Explorer laa azụ na 1999, na Firefox na 2004. Otú ọ dị, ruo ogologo oge ọ dịghị otu ụkpụrụ maka ndọtị.

Anyị nwere ike ịsị na ọ pụtara yana mgbakwunye na ụdị nke anọ nke Google Chrome. N'ezie, ọ nweghị nkọwapụta mgbe ahụ, mana ọ bụ Chrome API bụ ihe ndabere ya: ebe ọ meriri ọtụtụ ahịa ihe nchọgharị na inwe ụlọ ahịa ngwa arụnyere, Chrome setịpụrụ ụkpụrụ maka ndọtị ihe nchọgharị.

Mozilla nwere ọkọlọtọ nke ya, mana n'ịhụ ewu ewu nke ndọtị Chrome, ụlọ ọrụ ahụ kpebiri ime API dakọtara. Na 2015, na ntinye nke Mozilla, e mepụtara otu pụrụ iche n'ime World Wide Web Consortium (W3C) ka ha rụọ ọrụ na nkọwapụta mgbasawanye ihe nchọgharị.

Ewere ndọtị API dị adị maka Chrome ka ọ bụrụ ntọala. A rụrụ ọrụ ahụ site na nkwado nke Microsoft (Google jụrụ isonye na mmepe nke ọkọlọtọ), n'ihi ya, a na-ebipụta akwụkwọ. nkọwa.

N'ezie, Edge, Firefox na Opera na-akwado nkọwapụta (rịba ama na Chrome adịghị na ndepụta a). Mana n'ezie, ọkọlọtọ ahụ dabara na Chrome nke ukwuu, ebe ọ bụ na edere ya dabere na ndọtị ya. Ị nwere ike ịgụkwu gbasara WebExtensions API ebe a.

Ọdịdị ndọtị

Naanị faịlụ achọrọ maka ndọtị ahụ bụ ngosipụta (manifest.json). Ọ bụkwa "ebe ntinye" na mgbasawanye.

Gosiputa

Dịka nkọwapụta ahụ siri dị, faịlụ ngosipụta bụ faịlụ JSON bara uru. Nkọwa zuru ezu nke igodo ngosi nwere ozi gbasara igodo akwadoro nke enwere ike ịlele ihe nchọgharị ebe a.

Enwere ike ileghara igodo ndị na-adịghị na nkọwapụta "nwere ike" (ma Chrome na Firefox na-akọ njehie, mana ndọtị na-aga n'ihu na-arụ ọrụ).

Ọ ga-amasị m ịdọrọ uche gaa na isi ihe ụfọdụ.

  1. ndabere - ihe gụnyere mpaghara ndị a:
    1. ederede - ọtụtụ scripts nke a ga-egbu na ndabere ndabere (anyị ga-ekwu maka nke a obere oge);
    2. Page - kama script nke a ga-egbu na ibe efu, ị nwere ike dee html nwere ọdịnaya. N'okwu a, a ga-eleghara ubi edemede anya, na ọ ga-adị mkpa ka etinyere ederede n'ime ibe ọdịnaya;
    3. ịnọgide - ọkọlọtọ ọnụọgụ abụọ, ma ọ bụrụ na akọwapụtaghị ya, ihe nchọgharị ahụ "ga-egbu" usoro ndabere mgbe ọ na-eche na ọ naghị eme ihe ọ bụla, ma malitegharịa ya ma ọ bụrụ na ọ dị mkpa. Ma ọ bụghị ya, a ga-ebutu ibe ahụ naanị mgbe emechiri ihe nchọgharị ahụ. akwadoghị na Firefox.
  2. ọdịnaya_scripts - ọtụtụ ihe na-enye gị ohere ibu script dị iche iche na ibe weebụ dị iche iche. Ihe ọ bụla nwere mpaghara ndị a dị mkpa:
    1. ọkụ - ụkpụrụ url, nke na-ekpebi ma a ga-etinye otu edemede ọdịnaya ma ọ bụ na agaghị etinye ya.
    2. js - ndepụta nke scripts ga-kwajuru n'ime egwuregwu a;
    3. wepu_matches - ewepu n'ọhịa match URL ndị dabara na mpaghara a.
  3. ibe_action - bụ n'ezie ihe na-ahụ maka akara ngosi nke egosiri n'akụkụ ebe adresị ihe nchọgharị na mmekọrịta ya na ya. Ọ na-enye gị ohere igosipụta windo mmapụta, nke akọwara site na iji HTML, CSS na JS nke gị.
    1. default_popup - ụzọ faịlụ HTML nwere interface mmapụta nwere ike ịnwe CSS na JS.
  4. ikikere - nhazi maka ijikwa ikike ndọtị. Enwere ụdị ikike atọ, nke akọwapụtara n'ụzọ zuru ezu ebe a
  5. web_accessible_resources - akụrụngwa ndọtị nke ibe weebụ nwere ike ịrịọ, dịka ọmụmaatụ, onyonyo, JS, CSS, faịlụ HTML.
  6. mpụga_njikọ - ebe a ị nwere ike kọwaa n'ụzọ doro anya ID nke ndọtị ndị ọzọ na ngalaba nke ibe weebụ nke ị nwere ike jikọọ. Ngalaba nwere ike ịbụ ọkwa nke abụọ ma ọ bụ karịa. Ọ naghị arụ ọrụ na Firefox.

Ọnọdụ mmezu

Mgbatị ahụ nwere ọnọdụ mmebe koodu atọ, ya bụ, ngwa ahụ nwere akụkụ atọ nwere ọkwa dị iche iche nke ịnweta API ihe nchọgharị.

Ọnọdụ ndọtị

Ọtụtụ API dị ebe a. N'okwu a, ha "dị ndụ":

  1. Ibe ndabere - "azụ" akụkụ nke ndọtị ahụ. A na-akọwapụta faịlụ ahụ na ngosipụta site na iji igodo "ndabere".
  2. Ibe mmapụta - ibe mmapụta na-apụta mgbe ị pịrị akara ngosi ndọtị. Na manifesto browser_action -> default_popup.
  3. Ibe omenala - ibe ndọtị, “bi” na taabụ dị iche nke echiche chrome-extension://<id_расширения>/customPage.html.

Ọnọdụ a dị na-adabereghị na windo na taabụ nchọgharị. Ibe ndabere dị na otu oyiri ma na-arụ ọrụ mgbe niile (wepụ bụ ibe mmemme, mgbe ihe omume bidoro edemede ndabere wee “nwụọ” mgbe emechara ya). Ibe mmapụta dị mgbe windo mmapụta mepere emepe, yana Ibe omenala - mgbe taabụ na ya na-emeghe. Enweghị ohere ịnweta taabụ ndị ọzọ yana ọdịnaya ha site na ọnọdụ a.

Ọnọdụ edemede ọdịnaya

A na-ewepụta faịlụ ederede ọdịnaya yana taabụ nchọgharị ọ bụla. Ọ nwere ike ịnweta akụkụ nke API ndọtị yana gaa na osisi DOM nke ibe weebụ. Ọ bụ script ọdịnaya bụ maka mmekọrịta na ibe. Mgbakwunye na-emegharị osisi DOM na-eme nke a na ederede ọdịnaya - dịka ọmụmaatụ, mgbasa ozi mgbasa ozi ma ọ bụ ndị ntụgharị. Ọzọkwa, ederede ọdịnaya nwere ike ịkparịta ụka na ibe site na ọkọlọtọ postMessage.

Ọnọdụ ibe weebụ

Nke a bụ ibe weebụ n'ezie. Ọ nweghị ihe jikọrọ ya na ndọtị ahụ ma ọ nweghị ohere ebe ahụ, belụsọ na ebe egosighi ngalaba nke ibe a n'ụzọ doro anya na ngosipụta (ọzọ na nke a n'okpuru).

Mgbanwe ozi

Akụkụ dị iche iche nke ngwa ahụ ga-agbanwerịrị ozi na ibe ya. Enwere API maka nke a runtime.sendMessage izipu ozi background и tabs.sendMessage izipu ozi na ibe (ederede ọdịnaya, popup ma ọ bụ ibe weebụ ma ọ dị externally_connectable). N'okpuru bụ ọmụmaatụ mgbe ị na-enweta 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))
    }
)

Maka nzikọrịta ozi zuru oke, ị nwere ike ịmepụta njikọ site na runtime.connect. Na nzaghachi anyị ga-enweta runtime.Port, nke, mgbe ọ na-emeghe, ị nwere ike izipu ozi ọ bụla. N'akụkụ ndị ahịa, dịka ọmụmaatụ, contentscript, ọ dị ka nke a:

// Опять же 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"});

Sava ma ọ bụ ndabere:

// Обработчик для подключения 'своих' вкладок. Контент скриптов, 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) {
    ...
});

Enwekwara mmemme onDisconnect na usoro disconnect.

Eserese ngwa

Ka anyị mee ndọtị ihe nchọgharị na-echekwa igodo nzuzo, na-enye ohere ịnweta ozi ọha (adreesị, igodo ọha na-ekwurịta okwu na ibe ahụ ma na-enye ohere ngwa ndị ọzọ ịrịọ mbinye aka maka azụmahịa.

Mmepe ngwa

Ngwa anyị ga-eso onye ọrụ na-emekọrịta ihe ma nye ibe ya API iji kpọọ ụzọ (dịka ọmụmaatụ, ịbanye azụmahịa). Jiri naanị otu mee ya contentscript agaghị arụ ọrụ, ebe ọ bụ naanị na ọ nwere ohere ịnweta DOM, mana ọ bụghị na JS nke ibe. Jikọọ site na runtime.connect anyị enweghị ike, n'ihi na API dị mkpa na ngalaba niile, na naanị ndị akọwapụtara nwere ike ịkọwapụta na ngosipụta. N'ihi ya, eserese ahụ ga-adị ka nke a:

Na-ede ndọtị ihe nchọgharị echedoro

A ga-enwe edemede ọzọ - inpage, nke anyị ga-etinye n'ime ibe. Ọ ga-agba ọsọ na gburugburu ya wee nye API maka ịrụ ọrụ na ndọtị ahụ.

Начало

Koodu ndọtị ihe nchọgharị niile dị na GitHub. N'oge nkọwa ahụ, a ga-enwe njikọ maka ime ihe.

Ka anyị malite n'ihe ngosi:

{
  // Имя и описание, версия. Все это будет видно в браузере в 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"]
}

Mepụta ndabere efu.js, popup.js, inpage.js na contentscript.js. Anyị na-agbakwunye popup.html - enwere ike ibunye ngwa anyị na Google Chrome ma hụ na ọ na-arụ ọrụ.

Iji nyochaa nke a, ị nwere ike were koodu site n'ebe a. Na mgbakwunye na ihe anyị mere, njikọ ahụ haziri mgbakọ nke ọrụ ahụ site na iji webpack. Iji tinye ngwa na ihe nchọgharị ahụ, na chrome: // extensions ịkwesịrị ịhọrọ ibu na-ebughị ibu yana folda nwere ndọtị kwekọrọ - n'ọnọdụ anyị dist.

Na-ede ndọtị ihe nchọgharị echedoro

Ugbu a ndọtị anyị arụnyere ma na-arụ ọrụ. Ị nwere ike ịme ngwa ndị nrụpụta maka ọnọdụ dị iche iche dị ka ndị a:

popup ->

Na-ede ndọtị ihe nchọgharị echedoro

A na-eme ịnweta njikwa ederede ọdịnaya site na njikwa nke ibe n'onwe ya nke etinyere ya.Na-ede ndọtị ihe nchọgharị echedoro

Mgbanwe ozi

Ya mere, anyị kwesịrị iguzobe ụzọ nkwurịta okwu abụọ: inpage <-> ndabere na popup <-> ndabere. Ị nwere ike, n'ezie, naanị iziga ozi n'ọdụ ụgbọ mmiri wee chepụta protocol nke gị, mana m na-ahọrọ ụzọ m hụrụ na metamask mepere emepe.

Nke a bụ ndọtị ihe nchọgharị maka ịrụ ọrụ na netwọk Ethereum. N'ime ya, akụkụ dị iche iche nke ngwa ahụ na-ekwurịta okwu site na RPC site na iji ọba akwụkwọ dnode. Ọ na-enye gị ohere ịhazi mgbanwe ngwa ngwa na nke ọma ma ọ bụrụ na ị nye ya iyi nodejs ka ọ bụrụ njem (nke pụtara ihe na-eme otu interface ahụ):

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)))
})

Ugbu a, anyị ga-emepụta klas ngwa. Ọ ga-emepụta ihe API maka popup na ibe weebụ, wee mepụta dnode maka ha:

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)
        })
    }
}

Ebe a na n'okpuru, kama ihe Chrome zuru ụwa ọnụ, anyị na-eji extensionApi, nke na-enweta Chrome na ihe nchọgharị Google na ihe nchọgharị na ndị ọzọ. Emere nke a maka ndakọrịta ihe nchọgharị, mana maka ebumnuche nke akụkọ a, mmadụ nwere ike iji 'chrome.runtime.connect'.

Ka anyị mepụta ihe atụ ngwa n'edemede azụ:

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)
    }
}

Ebe ọ bụ na dnode na-arụ ọrụ na iyi, ma anyị na-enweta ọdụ ụgbọ mmiri, klas nkwụnye chọrọ. Emere ya site na iji ọba akwụkwọ a na-agụ, nke na-arụ ọrụ iyi nodejs na ihe nchọgharị:

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()
    }
}

Ugbu a, ka anyị mepụta njikọ na 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;
    }
}

Mgbe ahụ, anyị na-emepụta njikọ na edemede ọdịnaya:

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);
    }
}

Ebe ọ bụ na anyị chọrọ API ọ bụghị na edemede ọdịnaya, mana ozugbo na ibe, anyị na-eme ihe abụọ:

  1. Anyị na-emepụta iyi abụọ. Otu - chere ihu ibe, n'elu ozi nzi ozi. Maka nke a anyị na-eji nke a ngwugwu a site na ndị okike metamask. iyi nke abụọ bụ n'azụ n'elu ọdụ ụgbọ mmiri enwetara runtime.connect. Ka anyị zụta ha. Ugbu a ibe ahụ ga-enwe iyi n'azụ.
  2. Tinye ederede n'ime DOM. Budata edemede ahụ (a kwere ka ịnweta ya na ngosipụta) wee mepụta mkpado script ya na ihe di n'ime ya:

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);
    }
}

Ugbu a, anyị mepụtara ihe api na inpage wee tọọ ya ka ọ bụrụ nke zuru ụwa ọnụ:

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;
}

Anyị adịla njikere Oku usoro ime ime (RPC) nwere API dị iche maka ibe na UI. Mgbe ị na-ejikọta ibe ọhụrụ na ndabere anyị nwere ike ịhụ nke a:

Na-ede ndọtị ihe nchọgharị echedoro

API efu na mmalite. N'akụkụ ibe, anyị nwere ike ịkpọ ọrụ ndewo dị ka nke a:

Na-ede ndọtị ihe nchọgharị echedoro

Ịrụ ọrụ na ọrụ ịkpọghachi azụ na JS nke oge a bụ àgwà ọjọọ, ya mere, ka anyị dee obere onye inyeaka ka ịmepụta dnode nke na-enye gị ohere ịnyefe ihe API na ihe eji eme ihe.

Ihe API ga-adị ka nke a:

export class SignerApp {

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

...

}

Inweta ihe site na ime ime dị ka nke a:

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

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

Na ọrụ ịkpọ oku na-eweghachi nkwa:

Na-ede ndọtị ihe nchọgharị echedoro

Ụdị nwere ọrụ asynchronous dị ebe a.

N'ozuzu, usoro RPC na iyi iyi dị ka ọ na-agbanwe agbanwe: anyị nwere ike iji uzuoku multiplexing ma mepụta ọtụtụ API dị iche iche maka ọrụ dị iche iche. Na ụkpụrụ, a pụrụ iji dnode mee ihe n'ebe ọ bụla, ihe bụ isi bụ iji kechie njem ahụ n'ụdị iyi nodejs.

Nhọrọ ọzọ bụ usoro JSON, nke na-emejuputa ụkpụrụ JSON RPC 2. Otú ọ dị, ọ na-arụ ọrụ na njem njem kpọmkwem (TCP na HTTP (S)), nke na-adịghị adaba n'ọnọdụ anyị.

Nchekwa steeti na ime obodo

Anyị ga-echekwa ọnọdụ dị n'ime ngwa ahụ - ọbụlagodi igodo mbinye aka. Anyị nwere ike itinye steeti n'ụzọ dị mfe na ngwa yana ụzọ maka ịgbanwe ya na API popup:

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)
        }
    }

    ...

} 

N'azụ, anyị ga-ekekọta ihe niile na ọrụ wee dee ihe ngwa ahụ na windo ka anyị nwee ike iji ya rụọ ọrụ site na njikwa:

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)
        }
    }
}

Ka anyị tinye igodo ole na ole sitere na njikwa UI wee hụ ihe na-eme steeti:

Na-ede ndọtị ihe nchọgharị echedoro

Ekwesịrị ime steeti ahụ ka ọ na-adịgide ka igodo ghara efu mgbe ịmalitegharị.

Anyị ga-echekwa ya na mpaghara Nchekwa, na-edegharị ya na mgbanwe ọ bụla. N'ikpeazụ, ịnweta ya ga-adịkwa mkpa maka UI, ọ ga-amasị m ịdenye aha na mgbanwe. Dabere na nke a, ọ ga-adaba adaba ịmepụta nchekwa a na-ahụ anya ma denye aha na mgbanwe ya.

Anyị ga-eji ọba akwụkwọ mobx (https://github.com/mobxjs/mobx). Nhọrọ ahụ dabara na ya n'ihi na agaghị m arụ ọrụ na ya, mana m chọrọ n'ezie ịmụ ya.

Ka anyị gbakwunye mmalite nke steeti mbụ wee mee ka ụlọ ahịa ahụ hụ ya:

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)
    }

    ...

}

"N'okpuru mkpuchi," mobx ejirila proxy dochie mpaghara ụlọ ahịa niile wee gbochie oku niile a na-akpọ ha. Ọ ga-ekwe omume ịdenye aha na ozi ndị a.

N'okpuru ebe a, m ga-ejikarị okwu a "mgbe m na-agbanwe", ọ bụ ezie na nke a abụghị kpamkpam. Mobx na-akwado ohere ịnweta ubi. A na-eji ndị na-emepụta ihe na-edozi ihe nnọchiteanya nke ụlọ akwụkwọ ahụ mepụtara.

Ndị na-eme ihe ndozi na-arụ ọrụ abụọ:

  1. N'ụdị siri ike yana ọkọlọtọ enforceActions, mobx machibido ịgbanwe steeti ozugbo. A na-ewere na ọ dị mma ịrụ ọrụ n'okpuru ọnọdụ siri ike.
  2. Ọbụlagodi na ọrụ na-agbanwe steeti ọtụtụ oge - dịka ọmụmaatụ, anyị na-agbanwe ọtụtụ mpaghara n'ọtụtụ ahịrị koodu - a na-agwa ndị na-ekiri ihe ngosi naanị mgbe emechara. Nke a dị mkpa karịsịa maka frontend, ebe mmelite steeti na-adịghị mkpa na-eduga na nsụgharị na-enweghị isi. N'ọnọdụ anyị, ọ bụghị nke mbụ ma ọ bụ nke abụọ dị mkpa karịsịa, mana anyị ga-agbaso omume kachasị mma. Ọ bụ omenala ijikọta ndị na-achọ mma na ọrụ niile na-agbanwe ọnọdụ nke ubi ndị a hụrụ.

N'azụ anyị ga-agbakwunye mmalite na ichekwa steeti na Nchekwa mpaghara:

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)
        }
    }
}

Ọrụ mmeghachi omume na-adọrọ mmasị ebe a. O nwere arụmụka abụọ:

  1. Nhọrọ data.
  2. Onye njikwa nke a ga-akpọ data a oge ọ bụla ọ gbanwere.

N'adịghị ka redux, ebe anyị na-enweta steeti n'ụzọ doro anya dị ka arụmụka, mobx na-echeta nke a na-ahụ anya anyị na-abanye n'ime onye na-ahọrọ, na-akpọ naanị onye njikwa mgbe ha gbanwere.

Ọ dị mkpa ịghọta kpọmkwem ka mobx si ekpebi nke ihe nlele anyị debanyere aha. Ọ bụrụ na m dere onye na-ahọrọ na koodu dị ka nke a() => app.store, mgbe ahụ, a gaghị akpọ mmeghachi omume, ebe ọ bụ na nchekwa n'onwe ya adịghị ahụ anya, ọ bụ naanị ubi ya.

Ọ bụrụ na m dee ya otú a () => app.store.keys, ọzọkwa, ọ dịghị ihe ga-eme, ebe ọ bụ na mgbe ị na-agbakwụnye / wepụ n'usoro ihe, ntụnyere ya agaghị agbanwe.

Mobx na-arụ ọrụ dị ka onye nhọpụta nke mbụ ma na-edobe naanị ihe a na-ahụ anya nke anyị nwetara. A na-eme nke a site na proxy getters. Ya mere, a na-eji arụ ọrụ arụnyere arụ ọrụ ebe a toJS. Ọ na-eweghachi ihe ọhụrụ ejiri proxies niile dochie ya na mpaghara izizi. Mgbe a na-egbu ya, ọ na-agụ akụkụ niile nke ihe ahụ - ya mere a na-akpalite ndị na-eme ihe.

Na njikwa mmapụta, anyị ga-agbakwunye ọtụtụ igodo ọzọ. Oge a ha banyekwara na Nchekwa obodo:

Na-ede ndọtị ihe nchọgharị echedoro

Mgbe ebugharịrị ibe ndabere, ozi a na-anọgide na ebe.

Enwere ike ịlele koodu ngwa niile ruo ebe a ebe a.

Nchekwa nke igodo nzuzo

Ịchekwa igodo nzuzo na ederede doro anya adịghị mma: enwere ohere mgbe niile na a ga-anapụ gị, nweta ohere na kọmputa gị, na ihe ndị ọzọ. Ya mere, na localStorage anyị ga-echekwa igodo na ụdị ezoro ezo.

Maka nchekwa ka ukwuu, anyị ga-agbakwunye ọnọdụ akpọchiri na ngwa ahụ, nke na-agaghị enwe ohere ịnweta igodo ma ọlị. Anyị ga-ebufe ndọtị ahụ na-akpaghị aka na steeti akpọchiri n'ihi oge nkwụsị.

Mobx na-enye gị ohere ịchekwa naanị opekempe data, yana ndị ọzọ na-agbakọ na-akpaghị aka dabere na ya. Ndị a bụ ihe a na-akpọ akụrụngwa agbakọtara. Enwere ike iji ha tụnyere echiche dị na ọdụ data:

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')
        }
    }
}

Ugbu a, anyị na-echekwa naanị igodo na paswọọdụ ezoro ezo. A na-agbakọ ihe niile ọzọ. Anyị na-ebufe na steeti akpọchiri site na iwepu paswọọdụ na steeti. API ọha ugbu a nwere usoro maka ibido nchekwa ahụ.

Edere ya maka izo ya ezo akụrụngwa na-eji 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)
}

Ihe nchọgharị ahụ nwere API na-abaghị uru nke ị nwere ike ịdenye aha na mmemme - mgbanwe steeti. Steeti, ya mere, nwere ike ịbụ idle, active и locked. Maka enweghị ọrụ ị nwere ike ịtọ oge nkwụsị, ma kpọchie ya mgbe akpọchiri OS n'onwe ya. Anyị ga-agbanwekwa onye nhọpụta maka ịchekwaa na Nchekwa mpaghara:

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)
        }
    }
}

Koodu tupu usoro a bụ ebe a.

Azụmahịa

Ya mere, anyị na-abịakwute ihe kachasị mkpa: ịmepụta na ịbanye azụmahịa na blockchain. Anyị ga-eji WAVES blockchain na ọba akwụkwọ ebili mmiri - azụmahịa.

Nke mbụ, ka anyị tinye na steeti ahụ ọtụtụ ozi achọrọ ka abịanye aka, wee tinye usoro maka ịgbakwunye ozi ọhụrụ, kwado mbinye aka, na ịjụ:

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'
    }

    ...
}

Mgbe anyị nwetara ozi ọhụrụ, anyị na-agbakwunye metadata na ya, mee observable ma tinye na store.messages.

Ọ bụrụ na ị naghị eme ya observable aka, mgbe ahụ mobx ga-eme ya n'onwe ya mgbe ị na-agbakwunye ozi na n'usoro. Otú ọ dị, ọ ga-emepụta ihe ọhụrụ nke anyị na-agaghị enwe ntụaka, ma anyị ga-achọ ya maka nzọụkwụ ọzọ.

Na-esote, anyị na-eweghachite nkwa na-edozi mgbe ọnọdụ ozi gbanwere. A na-enyocha ọkwa site na mmeghachi omume, nke "ga-egbu onwe ya" mgbe ọnọdụ gbanwere.

Koodu usoro approve и reject dị nnọọ mfe: anyị na-agbanwe nnọọ ọnọdụ nke ozi, mgbe bịanyere aka na ya ma ọ bụrụ na ọ dị mkpa.

Anyị na-etinye nkwado ma jụ na UI API, ozi ọhụrụ na ibe 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)
        }
    }

    ...
}

Ugbu a, ka anyị gbalịa iji ndọtị ahụ banye azụmahịa ahụ:

Na-ede ndọtị ihe nchọgharị echedoro

N'ozuzu, ihe niile dị njikere, ihe niile fọdụrụ bụ tinye mfe UI.

UI

Ihe interface ahụ chọrọ ịnweta steeti ngwa. N'akụkụ UI anyị ga-eme observable kwupụta ma tinye ọrụ na API nke ga-agbanwe steeti a. Ka anyị tinye observable gaa na ihe API enwetara site na ndabere:

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)
}

Na njedebe anyị na-amalite na-enye interface ngwa. Nke a bụ ngwa nzaghachi. A na-agafe ihe dị n'azụ naanị site na iji ihe nkwado. Ọ ga-abụ ihe ziri ezi, n'ezie, ịme ọrụ dị iche iche maka ụzọ na ụlọ ahịa maka steeti, mana maka ebumnuche nke isiokwu a zuru oke:

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')
    );
}

Site na mobx ọ dị mfe ịmalite ịtụgharị mgbe data gbanwere. Naanị anyị na-atụkwasị onye na-ahụ ihe ịchọ mma na ngwugwu mobx-emeghachi omume na akụrụngwa, a ga-akpọkwa ihe na-akpaghị aka mgbe ihe ọ bụla a na-ahụ anya nke akụkụ ahụ gbanwere. Ịchọghị mapStateToProps ma ọ bụ jikọọ dị ka redux. Ihe niile na-arụ ọrụ ozugbo na igbe:

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>
    }
}

Enwere ike ịhụ ihe ndị fọdụrụ na koodu na UI nchekwa.

Ugbu a na klaasị ngwa ị ga-emerịrị onye na-ahọpụta steeti maka UI wee gwa UI mgbe ọ gbanwere. Iji mee nke a, ka anyị tinye usoro getState и reactionna-akpọ 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())

        })
    }

    ...
}

Mgbe ị na-anata ihe remote a na-eke reaction ịgbanwe steeti na-akpọ ọrụ n'akụkụ UI.

Mmetụ ikpeazụ bụ ịgbakwunye ngosipụta nke ozi ọhụrụ na akara ngosi ndọtị:

function setupApp() {
...

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

...
}

Yabụ, ngwa ahụ adịla njikere. Ibe weebụ nwere ike ịrịọ mbinye aka maka azụmahịa:

Na-ede ndọtị ihe nchọgharị echedoro

Na-ede ndọtị ihe nchọgharị echedoro

Koodu dị ebe a njikọ.

nkwubi

Ọ bụrụ na ị gụọla akụkọ ahụ ruo ọgwụgwụ, mana ka nwere ajụjụ, ị nwere ike ịjụ ha na repositories na ndọtị. N'ebe ahụ, ị ​​ga-ahụkwa akwụkwọ nkwado maka nzọụkwụ ọ bụla ahọpụtara.

Ma ọ bụrụ na ị nwere mmasị na-elele koodu maka n'ezie ndọtị, ị nwere ike ịhụ nke a ebe a.

Koodu, ebe nchekwa na nkọwa ọrụ si siemarell

isi: www.habr.com

Tinye a comment