Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ngokungafaniyo noyilo oluqhelekileyo "lomncedisi womxhasi", usetyenziso olunatyisiweyo luphawulwa ngolu:

  • Akukho mfuneko yokugcina idatabase kunye nokungena komsebenzisi kunye namagama ayimfihlo. Ulwazi lokufikelela lugcinwa kuphela ngabasebenzisi ngokwabo, kwaye ukuqinisekiswa kobunyani babo kwenzeka kwinqanaba leprotocol.
  • Akukho mfuneko yokusebenzisa iseva. Ingqiqo yesicelo inokuphunyezwa kwinethiwekhi ye-blockchain, apho kunokwenzeka ukugcina inani elifunekayo ledatha.

Kukho iindawo ezi-2 ezikhuselekileyo zokugcinwa kwezitshixo zomsebenzisi - izipaji zehardware kunye nezandiso zebrawuza. Izipaji ze-Hardware ubukhulu becala zikhuseleke kakhulu, kodwa kunzima ukuzisebenzisa kwaye zikude simahla, kodwa izandiso zesiphequluli ziyindibaniselwano egqibeleleyo yokhuseleko kunye nokusebenziseka ngokulula, kwaye zinokukhululeka ngokupheleleyo kubasebenzisi bokugqibela.

Ukuthathela ingqalelo konke oku, besifuna ukwenza olona lwandiso lukhuselekileyo elenza lula uphuhliso lwezicelo ezinatyisiweyo ngokubonelela nge-API elula yokusebenza ngeentengiselwano kunye neesignesha.
Siza kukuxelela malunga nala mava angezantsi.

Inqaku liya kuba nemiyalelo yesinyathelo-nge-nyathelo malunga nendlela yokubhala isandiso sesiphequluli, kunye nemizekelo yekhowudi kunye nezikrini. Ungafumana yonke ikhowudi kuyo iindawo zokugcina. Isibophelelo ngasinye siyahambelana necandelo leli nqaku.

Imbali emfutshane yezandiso zebhrawuza

Izandiso zebrowser bezikhona ixesha elide. Bavele kwi-Internet Explorer ngo-1999, kwiFirefox ngo-2004. Nangona kunjalo, ixesha elide kakhulu kwakungekho mgangatho omnye wokwandiswa.

Sinokuthi ivele kunye nezandiso kwinguqulo yesine yeGoogle Chrome. Ewe, kwakungekho nkcazo ngoko, kodwa yayiyi-Chrome API eyaba sisiseko sayo: ukuba yoyisile ininzi yemarike yesiphequluli kunye nevenkile eyakhelwe ngaphakathi, i-Chrome ibeka umgangatho wokongezwa kwesiphequluli.

I-Mozilla yayinomgangatho wayo, kodwa ibona ukuthandwa kwezandiso zeChrome, inkampani yagqiba ekubeni yenze i-API ehambelanayo. Ngo-2015, kwinyathelo lokuqala le-Mozilla, iqela elikhethekileyo lenziwe ngaphakathi kwi-World Wide Web Consortium (W3C) ukusebenza kwiinkcukacha zokwandiswa kwe-browser.

Izandiso ze-API ezikhoyo zeChrome zithathwe njengesiseko. Umsebenzi wenziwa ngenkxaso yeMicrosoft (uGoogle wala ukuthatha inxaxheba kuphuhliso lomgangatho), kwaye ngenxa yoko kwavela idrafti. Ukubalula.

Ngokusemthethweni, inkcazo ixhaswa yi-Edge, iFirefox kunye ne-Opera (qaphela ukuba iChrome ayikho kolu luhlu). Kodwa eneneni, umgangatho uhambelana kakhulu neChrome, kuba ibhaliwe ngokusekwe kwizandiso zayo. Unokufunda ngakumbi malunga neWebExtensions API apha.

Ulwakhiwo lolwandiso

Ifayile kuphela efunekayo kulwandiso yi-manifest (manifest.json). Kwakhona "indawo yokungena" ekwandiseni.

Ukubonakaliswa

Ngokobalulo, ifayile ye-manifest yifayile ye-JSON esebenzayo. Inkcazo epheleleyo yezitshixo ze-manifest ngolwazi malunga nokuba zeziphi izitshixo ezixhaswayo apho i-browser inokujongwa apha.

Izitshixo ezingekho kwinkcazo "zinokuthi" zingahoywa (zombini iimpazamo zengxelo yeChrome kunye neFirefox, kodwa izandiso ziyaqhubeka nokusebenza).

Kwaye ndingathanda ukutsalela ingqalelo kwezinye iingongoma.

  1. yangasemva — into ebandakanya le mihlaba ilandelayo:
    1. zeempendulo — uluhlu lwemibhalo ebhaliweyo eya kusetyenziswa kumxholo ongasemva (siya kuthetha ngale nto kamva);
    2. iphepha - endaweni yezikripthi eziya kuphunyezwa kwiphepha elingenanto, ungakhankanya ihtml ngomxholo. Kule meko, indawo yescript iya kuhoywa, kwaye izikripthi ziya kufuneka zifakwe kwiphepha lomxholo;
    3. iqhubeka - iflegi yebhinari, ukuba ayichazwanga, isikhangeli "siya kubulala" inkqubo yangasemva xa sicinga ukuba ayenzi nto, kwaye siyiqalise kwakhona ukuba kuyimfuneko. Kungenjalo, iphepha liya kothulwa kuphela xa isikhangeli sivaliwe. Ayixhaswa kwiFirefox.
  2. umxholo_izikripthi - Uluhlu lwezinto ezikuvumela ukuba ulayishe izikripthi ezahlukeneyo kumaphepha ewebhu ahlukeneyo. Into nganye iqulethe le mihlaba ibalulekileyo ilandelayo:
    1. midlalo - ipatheni url, emisela ukuba isikripthi somxholo othile siya kubandakanywa okanye hayi.
    2. js — uludwe lwemibhalo eya kulayishwa kulo mdlalo;
    3. khuphela_imidlalo - akubandakanyi ebaleni match Ii-URL ezihambelana nalo mmandla.
  3. iphepha_intshukumo - eneneni yinto enoxanduva lwe icon eboniswe ecaleni kwebar yedilesi kwi-browser kunye nokusebenzisana nayo. Ikwakuvumela ukuba ubonise ifestile ye popup, echazwa usebenzisa eyakho iHTML, CSS kunye neJS.
    1. default_popup — indlela eya kwifayile yeHTML enojongano oluzivelelayo, inokuqulatha iCSS kunye neJS.
  4. aneemvume - uluhlu lokulawula amalungelo awongezelelweyo. Kukho iintlobo ezi-3 zamalungelo, ezichazwe ngokubanzi apha
  5. web_accessible_resources - izixhobo ezongezelelekileyo ezinokucelwa kwiphepha lewebhu, umzekelo, imifanekiso, i-JS, iCSS, iifayile zeHTML.
  6. ngaphandle_eqhagamshelekayo — Apha ungachaza ngokucacileyo ii-ID zezinye izandiso kunye nemimandla yamaphepha ewebhu onokuqhagamshela kuwo. I-domain ingaba kwinqanaba lesibini okanye ngaphezulu. Ayisebenzi kwiFirefox.

Umxholo wophumezo

Ukongezwa kunemixholo emithathu yokusetyenziswa kwekhowudi, oko kukuthi, isicelo siqukethe iinxalenye ezintathu ezinamazinga ahlukeneyo okufikelela kwi-API yesikhangeli.

Umxholo wokwandiswa

Uninzi lwe-API luyafumaneka apha. Kulo mxholo "bahlala":

  1. Iphepha elingasemva — "backend" inxalenye yolwandiso. Ifayile icacisiwe kwi-manifest kusetyenziswa iqhosha elithi "imvelaphi".
  2. Iphepha elizivelelayo - Iphepha elivelayo elivelayo xa ucofa i icon yolwandiso. Kwi-manifesto browser_action -> default_popup.
  3. Iphepha elilungiselelweyo — iphepha elongezelelweyo, “ukuphila” kwithebhu eyahlukileyo yokujonga chrome-extension://<id_расширения>/customPage.html.

Lo mxholo ukhona ngokuzimeleyo kwiifestile zesikhangeli kunye neethebhu. Iphepha elingasemva ikhona kwikopi enye kwaye ihlala isebenza (ngaphandle kwephepha lesiganeko, xa iskripthi esingasemva siqaliswa sisiganeko kwaye "sifa" emva kokuphunyezwa kwayo). Iphepha elizivelelayo ikhona xa ifestile ezivelelayo zivuliwe, kwaye Iphepha elilungiselelweyo — ngelixa ithebhu enayo ivuliwe. Akukho ukufikelela kwezinye iithebhu kunye nemixholo yazo kulo mxholo.

Umxholo wesikripthi somxholo

Ifayile yeskripthi somxholo iqaliswe kunye nethebhu nganye yomkhangeli zincwadi. Inokufikelela kwinxalenye ye-API yolwandiso kunye nomthi we-DOM wephepha lewebhu. Zizikripthi zomxholo ezinoxanduva lokusebenzisana nephepha. Izandiso ezisebenzisa umthi we-DOM zenza oku kwizikripthi zomxholo - umzekelo, i-ad blockers okanye abaguquleli. Kwakhona, iskripthi somxholo sinokunxibelelana nephepha ngokusemgangathweni postMessage.

Umxholo wephepha lewebhu

Eli lelona phepha lewebhu ngokwalo. Ayinanto yakwenza nolwandiso kwaye ayinafikelelo apho, ngaphandle kwakwiimeko apho ithambeka leli phepha lingaboniswanga ngokucacileyo kwimanifest (ngaphezulu koku ngezantsi).

Ukutshintshiselana ngomyalezo

Iinxalenye ezahlukeneyo zesicelo kufuneka zitshintshiselane ngemiyalezo enye kwenye. Kukho i-API yale nto runtime.sendMessage ukuthumela umyalezo background и tabs.sendMessage ukuthumela umyalezo kwiphepha (iscript somxholo, ipopup okanye iphepha lewebhu ukuba likhona externally_connectable). Ngezantsi ngumzekelo xa ufikelela kwi-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))
    }
)

Ngonxibelelwano olupheleleyo, unokwenza unxibelelwano ngokusebenzisa runtime.connect. Ekuphenduleni siya kufumana runtime.Port, apho, ngelixa ivuliwe, ungathumela naliphi na inani lemiyalezo. Kwicala labaxumi, umzekelo, contentscript, kubonakala ngathi:

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

Iseva okanye ngasemva:

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

Kukwakho nesiganeko onDisconnect kunye nendlela disconnect.

Umzobo wesicelo

Masenze ulwandiso lwebrawuza olugcina izitshixo zabucala, lubonelela ngokufikelela kulwazi lukawonke-wonke (idilesi, isitshixo sikawonke-wonke sinxibelelana nephepha kwaye sivumela usetyenziso lomntu wesithathu ukuba lucele utyikityo lwentengiselwano.

Uphuhliso lwesicelo

Isicelo sethu kufuneka sisebenze zombini kunye nomsebenzisi kwaye sinikeze iphepha nge-API yokufowuna iindlela (umzekelo, ukusayina iintengiselwano). Yenza ngento enye contentscript ayizukusebenza, kuba inofikelelo kuphela kwi-DOM, kodwa hayi kwi-JS yephepha. Qhagamshela nge runtime.connect asikwazi, kuba i-API iyadingeka kuzo zonke izizinda, kwaye kuphela ezikhethekileyo zingachazwa kwi-manifest. Ngenxa yoko, umzobo uya kujongeka ngolu hlobo:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Kuya kubakho esinye iskripthi - inpage, esiya kuthi siyifake kwiphepha. Iya kuqhuba kumxholo wayo kwaye inike i-API yokusebenza kunye nolwandiso.

Ukuqala

Yonke ikhowudi yokwandiswa kwesikhangeli iyafumaneka GitHub. Ngexesha lenkcazo kuya kubakho amakhonkco okuzibophelela.

Masiqale nge-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"]
}

Yenza i-background.js engenanto, popup.js, inpage.js kunye nescriptscript.js. Songeza i-popup.html- kwaye isicelo sethu sinokulayishwa kuGoogle Chrome kwaye siqinisekise ukuba siyasebenza.

Ukuqinisekisa oku, unokuthatha ikhowudi kusuka apha. Ukongeza kwinto esiyenzileyo, ikhonkco iqwalasele indibano yeprojekthi isebenzisa i-webpack. Ukongeza isicelo kwisikhangeli, kwi-chrome: // izandiso kufuneka ukhethe umthwalo ongapakishwanga kunye nefolda enesandiso esihambelanayo - kwimeko yethu dist.

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ngoku ulwandiso lwethu lufakiwe kwaye luyasebenza. Unokusebenzisa izixhobo zomphuhlisi kwiimeko ezahlukeneyo ngolu hlobo lulandelayo:

popup ->

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ukufikelela kwikhonsoli yesikripthi somxholo kuqhutywa ngekhonsoli yephepha ngokwalo eqaliswe kulo.Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ukutshintshiselana ngomyalezo

Ke, kufuneka siseke amajelo amabini onxibelelwano: i-inpage <-> imvelaphi kunye ne-popup <-> yangasemva. Unako, ewe, ukuthumela nje imiyalezo kwizibuko kwaye wenze eyakho iprotocol, kodwa ndikhetha indlela endiyibone kwiprojekthi yomthombo ovulekileyo wemetamask.

Olu lwandiso lwesiphequluli sokusebenza kunye nenethiwekhi ye-Ethereum. Kuyo, iindawo ezahlukeneyo zesicelo zinxibelelana nge-RPC usebenzisa ithala leencwadi le-dnode. Ikuvumela ukuba uququzelele utshintshiselwano ngokukhawuleza kwaye ngokufanelekileyo ukuba unikezela ngomjelo we-nodejs njengothutho (ithetha into ephumeza ujongano olufanayo):

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

Ngoku siza kudala iklasi yesicelo. Iyakudala izinto ze-API zepopup kunye nephepha lewebhu, kwaye ibenzele i-dnode:

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

Apha nangezantsi, endaweni ye-Chrome yehlabathi jikelele into, sisebenzisa i-extensionApi, efikelela kuChrome kwisikhangeli sikaGoogle kunye nesikhangeli kwezinye. Oku kwenzelwa ukuhambelana kwebrowser, kodwa ngeenjongo zeli nqaku umntu unokusebenzisa ngokulula 'chrome.runtime.connect'.

Masenze umzekelo wesicelo kwiscript esingasemva:

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

Ekubeni i-dnode isebenza kunye nemisinga, kwaye sifumana i-port, iklasi ye-adapter iyadingeka. Yenziwe kusetyenziswa ithala leencwadi elifundekayo, elisebenzisa imijelo ye-nodejs kwisikhangeli:

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

Ngoku masenze umdibaniso kwi-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;
    }
}

Emva koko senza unxibelelwano kwiskripthi somxholo:

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

Ekubeni sifuna i-API kungekhona kwisikripthi somxholo, kodwa ngokuthe ngqo kwiphepha, senza izinto ezimbini:

  1. Senza imilambo emibini. Enye - ibhekisa kwiphepha, ngaphezulu komyalezo womyalezo. Kule nto sisebenzisa oku le phakheji ukusuka kubadali bemetamask. Umlambo wesibini kukungasemva phezu kwezibuko elifunyenwe ukusuka runtime.connect. Masizithenge. Ngoku iphepha liya kuba nomsinga ukuya ngasemva.
  2. Faka umbhalo kwiDOM. Khuphela isikripthi (ukufikelela kuso bekuvunyelwe kwi-manifest) kwaye wenze ithegi script nemixholo yayo ngaphakathi:

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

Ngoku senza into ye-api kwi-inpage kwaye siyibeke kwihlabathi jikelele:

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

Silungile Umnxeba weNkqubo ekude (RPC) ene-API eyahlukileyo yephepha kunye ne-UI. Xa uqhagamshela iphepha elitsha ukuya ngasemva sinokubona oku:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

I-API engenanto kunye nemvelaphi. Kwicala lephepha, sinokubiza umsebenzi we-hello ngolu hlobo:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ukusebenza ngemisebenzi yokufowunelwa kwi-JS yanamhlanje yisimilo esibi, ke masibhale umncedisi omncinci ukwenza i-dnode ekuvumela ukuba udlulise into ye-API kwii-utils.

Izinto ze-API ngoku ziya kujongeka ngolu hlobo:

export class SignerApp {

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

...

}

Ukufumana into ukusuka kude ngolu hlobo:

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

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

Kwaye imisebenzi yokufowuna ibuyisela isithembiso:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Inguqulelo enemisebenzi engahambelaniyo ekhoyo apha.

Lilonke, i-RPC kunye nendlela yokuhambisa ibonakala ibhetyebhetye: sinokusebenzisa i-multiplexing yomphunga kwaye senze ii-API ezininzi ezahlukeneyo kwimisebenzi eyahlukeneyo. Ngokomgaqo, i-dnode ingasetyenziselwa naphi na, into ephambili kukugubungela ukuthuthwa ngendlela ye-nodejs stream.

Enye indlela yifomathi ye-JSON, esebenzisa i-protocol ye-JSON RPC 2. Nangona kunjalo, isebenza kunye nothutho oluthile (TCP kunye ne-HTTP (S)), olungasebenziyo kwimeko yethu.

Ilizwe langaphakathi kunye noGcino lwendawo

Kuya kufuneka sigcine imo yangaphakathi yesicelo - ubuncinci izitshixo zokusayina. Sinokongeza ngokulula imeko kwisicelo kunye neendlela zokuyitshintsha kwi-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)
        }
    }

    ...

} 

Ngasemva, siya kusonga yonke into kumsebenzi kwaye sibhale into yesicelo kwifestile ukuze sisebenze nayo kwikhonsoli:

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

Makhe songeze izitshixo ezimbalwa kwikhonsoli ye-UI kwaye sibone ukuba kwenzeka ntoni na ngelizwe:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Umbuso kufuneka wenziwe ngokuqhubekayo ukuze izitshixo zingalahleki xa ziqala kwakhona.

Siza kuyigcina kwindawo yokuGcina, siyibhale ngaphezulu ngalo lonke utshintsho. Emva koko, ukufikelela kuyo kuya kuba yimfuneko kwi-UI, kwaye ndingathanda ukubhalisela utshintsho. Ngokusekwe kule nto, kuya kuba lula ukwenza ukugcinwa okubonakalayo kwaye ubhalise kwiinguqu zayo.

Siza kusebenzisa ithala leencwadi le-mobx (https://github.com/mobxjs/mobx). Ukhetho lwawela kuyo ngenxa yokuba andizange ndisebenze nayo, kodwa ndandifuna ngokwenene ukuyifunda.

Makhe songeze ukuqaliswa kwemeko yokuqala kwaye senze ivenkile ibonakale:

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

    ...

}

"Ngaphantsi kwe-hood," i-mobx ithathe indawo yazo zonke iindawo zokugcina ngeproxy kwaye ibamba zonke iifowuni eziya kubo. Kuya kwenzeka ukuba ubhalisele le miyalezo.

Ngezantsi ndiza kuhlala ndisebenzisa igama elithi "xa utshintsha", nangona oku kungachanekanga ngokupheleleyo. I-Mobx ilandelela ukufikelela kwiindawo. Iigetters kunye neeseti zezinto zommeli eziqulunqwa lithala leencwadi ziyasetyenziswa.

Abahombi bentshukumo basebenzela iinjongo ezimbini:

  1. Kwimowudi engqongqo kunye neflegi yeentshukumo zokunyanzeliswa, i-mobx iyakwalela ukutshintsha urhulumente ngokuthe ngqo. Kuthathwa njengefomu elungileyo ukusebenza phantsi kweemeko ezingqongqo.
  2. Nokuba umsebenzi utshintsha urhulumente amaxesha amaninzi - umzekelo, sitshintsha iindawo ezininzi kwimigca emininzi yekhowudi - abakhi-mkhanyo bayaziswa kuphela xa igqibile. Oku kubaluleke ngakumbi kwi-frontend, apho uhlaziyo lwelizwe olungeyomfuneko lukhokelela ekunikezeni izinto ngokungeyomfuneko. Kwimeko yethu, akukho okokuqala okanye okwesibini kubaluleke kakhulu, kodwa siya kulandela ezona ndlela zilungileyo. Kuyinto yesiko ukunamathisela abahlobisi kuyo yonke imisebenzi etshintsha imeko yemimandla ebonwayo.

Ngasemva siya kongeza ukuqaliswa kunye nokugcina urhulumente kwindawo yoGcino lwendawo:

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

Umsebenzi wokusabela unomdla apha. Ineengxoxo ezimbini:

  1. Umkhethi wedatha.
  2. Umphathi oza kubizwa ngale datha ngalo lonke ixesha itshintsha.

Ngokungafaniyo ne-reux, apho sifumana khona urhulumente ngokucacileyo njengengxabano, i-mobx ikhumbula ukuba yeyiphi into ebonakalayo esifikelela kuyo ngaphakathi komkhethi, kwaye ibiza kuphela umphathi xa etshintsha.

Kubalulekile ukuqonda ngokuthe ngqo ukuba i-mobx ithatha isigqibo malunga nokuba yeyiphi into esinokubhalisa kuyo. Ukuba ndibhale umkhethi ngekhowudi efana nale() => app.store, ke ukusabela akusoze kubizwe, ekubeni ugcino ngokwalo alubonakali, kuphela amasimi alo.

Ukuba ndiyibhale ngolu hlobo () => app.store.keys, emva koko akukho nto iya kwenzeka, kuba xa udibanisa / ususa izinto zoluhlu, ireferensi kuyo ayiyi kutshintsha.

I-Mobx isebenza njengomkhethi okokuqala kwaye igcina umkhondo wezinto ezinokubonwa esizifikeleleyo. Oku kwenziwa ngee-proxy getters. Ke ngoko, umsebenzi owakhelwe-ngaphakathi usetyenziswa apha toJS. Ibuyisela into entsha kunye nazo zonke iiproxi ezitshintshwe ngamasimi oqobo. Ngethuba lokubulawa, ifunda zonke iinkalo zento - ngoko ke i-getters iqhutywe.

Kwi-popup console siya kuphinda songeze izitshixo ezininzi. Ngeli xesha nabo bangena kuGcino lwasekhaya:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Xa iphepha elingasemva lilayishwa kwakhona, ulwazi luhlala lukhona.

Yonke ikhowudi yesicelo ukuya kweli nqanaba inokujongwa apha.

Ukugcinwa ngokukhuselekileyo kwezitshixo zabucala

Ukugcina izitshixo zangasese kwisicatshulwa esicacileyo akukhuselekanga: kukho rhoqo ithuba lokuba uya kuqhekezwa, ufumane ukufikelela kwikhompyutheni yakho, njalo njalo. Ke ngoko, kuGcino lwasekhaya siya kugcina izitshixo kwifomu efihliweyo ngegama eliyimfihlo.

Ukhuseleko olukhulu, siyakongeza imeko etshixiweyo kwisicelo, apho kungayi kubakho ukufikelela kwizitshixo konke konke. Siza kudlulisela ulwandiso ngokuzenzekelayo kwisimo esitshixiweyo ngenxa yexesha.

I-Mobx ikuvumela ukuba ugcine kuphela iseti encinci yedatha, kwaye enye ibalwa ngokuzenzekelayo ngokusekelwe kuyo. Ezi zinto zibizwa ngokuba ziipropathi zekhompyutha. Zinokuthelekiswa nemibono kuluhlu lwedatha:

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

Ngoku sigcina kuphela izitshixo ezifihliweyo kunye negama lokugqitha. Yonke enye into iyabalwa. Senza ukudluliselwa kwimeko etshixiweyo ngokususa igama eligqithisiweyo kurhulumente. I-API yoluntu ngoku inendlela yokuqalisa ukugcinwa.

Ibhalelwe uguqulelo oluntsonkothileyo izinto eziluncedo zisebenzisa i-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)
}

Isikhangeli sine-API engasebenziyo apho ungabhalisa kumsitho-utshintsho lwemeko. Umbuso, ngokufanelekileyo, unokuba idle, active и locked. Ukungenzi nto unokuseta ixesha lokuphuma, kwaye itshixiwe imiselwe xa i-OS ngokwayo ivaliwe. Siza kutshintsha kwakhona umkhethi wokugcina kuGcino lwasekhaya:

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

Ikhowudi phambi kweli nyathelo apha.

Intengiselwano

Ke, siza kweyona nto ibalulekileyo: ukudala kunye nokusayina ukuthengiselana kwi-blockchain. Siza kusebenzisa i-WAVES blockchain kunye nethala leencwadi amaza-intengiselwano.

Okokuqala, masifake kwisimo uluhlu lwemiyalezo ekufuneka isayinwe, emva koko songeze iindlela zokongeza umyalezo omtsha, uqinisekisa utyikityo, kunye nokwala:

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

    ...
}

Xa sifumana umyalezo omtsha, songeza imetadata kuyo, yenza observable kwaye wongeze kwi store.messages.

Ukuba awenzi njalo observable ngesandla, emva koko imobx iyakuyenza ngokwayo xa isongeza imiyalezo kuluhlu. Nangona kunjalo, iya kudala into entsha esingayi kuba nayo ireferensi, kodwa siya kuyidinga kwisinyathelo esilandelayo.

Okulandelayo, sibuyisela isithembiso esisombululayo xa isimo somyalezo sitshintsha. Ubume bujongwa ngokusabela, okuya "kuzibulala" xa isimo sitshintsha.

Indlela ikhowudi approve и reject ilula kakhulu: sitshintsha ngokulula isimo somyalezo, emva kokuwusayina ukuba kuyimfuneko.

Sibeka iVumela kwaye siyala kwi-UI API, uMyalezo omtsha kwiphepha le-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)
        }
    }

    ...
}

Ngoku makhe sizame ukusayina intengiselwano kunye nolwandiso:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ngokubanzi, yonke into ilungile, yonke into eseleyo yongeza i-UI elula.

UI

Ujongano lufuna ukufikelela kwimeko yesicelo. Kwicala le-UI siza kwenza observable chaza kwaye wongeze umsebenzi kwi-API ozakutshintsha le meko. Masongeze observable ukuya kwinto ye-API efunyenwe ngasemva:

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

Ekugqibeleni siqala ukunikezela ujongano lwesicelo. Olu lusetyenziso olusabelayo. Into engasemva igqithiswa ngokulula kusetyenziswa iipropu. Kuya kuba kuchanekile, ngokuqinisekileyo, ukwenza inkonzo eyahlukileyo kwiindlela kunye nevenkile yelizwe, kodwa ngeenjongo zeli nqaku oku kwanele:

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

Nge-mobx kulula kakhulu ukuqalisa ukunikezela xa idatha itshintsha. Sixhoma nje umhombisi obukeleyo kwiphakheji mobx-react kwicandelo, kwaye unikezelo luyakubizwa ngokuzenzekelayo xa nakuphi na okuqaphelekayo kubhekiswa lutshintsho lwecandelo. Awufuni nayiphi na imaphuStateToProps okanye uqhagamshele njengakwi-redux. Yonke into isebenza ngaphandle kwebhokisi:

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

Amacandelo aseleyo angabonwa kwikhowudi kwifolda ye-UI.

Ngoku kwiklasi yesicelo kufuneka wenze umkhethi karhulumente we-UI kwaye wazise i-UI xa itshintsha. Ukwenza oku, masidibanise indlela getState и reactionukufowuna 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())

        })
    }

    ...
}

Xa ufumana into remote yenziwe reaction ukutshintsha imeko ebiza umsebenzi kwicala le UI.

Ukuchukumisa okokugqibela kukongeza umboniso wemiyalezo emitsha kwi icon yolwandiso:

function setupApp() {
...

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

...
}

Ngoko ke, isicelo silungile. Amaphepha ewebhu angacela utyikityo lwentengiselwano:

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ukubhala ulwandiso lwesikhangeli esikhuselekileyo

Ikhowudi iyafumaneka apha unxibelelwano.

isiphelo

Ukuba ulifundile inqaku kude kube sekupheleni, kodwa usenemibuzo, unokuyibuza kuyo zokugcina kunye nolwandiso. Apho uya kufumana ukuzinikela kwinyathelo ngalinye elikhethiweyo.

Kwaye ukuba unomdla ekujongeni ikhowudi yolwandiso lwangempela, ungayifumana le apha.

Ikhowudi, indawo yokugcina kunye nenkcazo yomsebenzi ukusuka siemarell

umthombo: www.habr.com

Yongeza izimvo