Ukubhala isandiso sesiphequluli esivikelekile

Ukubhala isandiso sesiphequluli esivikelekile

Ngokungafani nesakhiwo esijwayelekile “seseva yeklayenti”, izinhlelo zokusebenza ezihlukaniselwe izindawo zibonakala ngokuthi:

  • Asikho isidingo sokugcina isizindalwazi esinokungena ngemvume komsebenzisi namaphasiwedi. Ulwazi lokufinyelela lugcinwa ngokukhethekile abasebenzisi ngokwabo, futhi ukuqinisekiswa kobuqiniso babo kwenzeka ezingeni lephrothokholi.
  • Asikho isidingo sokusebenzisa iseva. I-logic yesicelo ingenziwa kunethiwekhi ye-blockchain, lapho kungenzeka khona ukugcina inani elidingekayo ledatha.

Kunezindawo zokugcina eziphephile ezingu-2 uma kuqhathaniswa nezikhiye zomsebenzisi - izikhwama zehadiwe nezandiso zesiphequluli. Ama-wallet ezingxenyekazi zekhompyutha ngokuvamile avikeleke ngokwedlulele, kodwa anzima ukuwasebenzisa futhi akude nokukhululeka, kodwa izandiso zesiphequluli ziyinhlanganisela ephelele yokuphepha nokusebenziseka kalula, futhi zingaba mahhala ngokuphelele kubasebenzisi bokugcina.

Ngokucabangela konke lokhu, besifuna ukwenza isandiso esivikeleke kakhulu esenza kube lula ukuthuthukiswa kwezinhlelo zokusebenza ezihlukaniselwe izindawo ngokunikeza i-API elula yokusebenza ngemisebenzi namasiginesha.
Sizokutshela ngalokhu ngezansi.

I-athikili izoqukatha imiyalelo yesinyathelo ngesinyathelo sendlela yokubhala isandiso sesiphequluli, ngezibonelo zekhodi nezithombe-skrini. Ungathola yonke ikhodi ku izinqolobane. Ukuzinikela ngakunye ngokunengqondo kuhambisana nesigaba salesi sihloko.

Umlando omfushane Wezandiso Zesiphequluli

Izandiso zesiphequluli sezinesikhathi eside zikhona. Bavela ku-Internet Explorer emuva ngo-1999, kuFirefox ngo-2004. Kodwa-ke, isikhathi eside kakhulu kwakungekho indinganiso eyodwa yezandiso.

Singasho ukuthi ivele kanye nezandiso enguqulweni yesine ye-Google Chrome. Yiqiniso, kwakungekho ukucaciswa ngaleso sikhathi, kodwa kwaba i-Chrome API eyaba yisisekelo sayo: ngemva kokunqoba iningi lemakethe yesiphequluli nokuba nesitolo sezinhlelo zokusebenza esakhelwe ngaphakathi, i-Chrome empeleni yabeka izinga lezandiso zesiphequluli.

I-Mozilla yayinendinganiso yayo, kodwa ibona ukuthandwa kwezandiso ze-Chrome, inkampani yanquma ukwenza i-API ehambisanayo. Ngo-2015, ngokuqaliswa kwe-Mozilla, kwasungulwa iqembu elikhethekile ngaphakathi kwe-World Wide Web Consortium (W3C) ukuze lisebenze ekucacisweni kwesandiso sesiphequluli.

Izandiso ezikhona ze-API ze-Chrome zithathwe njengesisekelo. Umsebenzi wenziwa ngokusekelwa yiMicrosoft (i-Google yenqaba ukubamba iqhaza ekuthuthukisweni kwezinga), futhi ngenxa yalokho kwavela uhlaka. Ukucaciswa.

Ngokusemthethweni, imininingwane isekelwa yi-Edge, Firefox ne-Opera (qaphela ukuthi i-Chrome ayikho kulolu hlu). Kodwa empeleni, izinga lihambisana kakhulu ne-Chrome, ngoba empeleni libhaliwe ngokusekelwe ezandiso zalo. Ungafunda kabanzi mayelana ne-WebExtensions API lapha.

Isakhiwo sokunweba

Okuwukuphela kwefayela elidingekayo kusandiso yi-manifest (manifest.json). Futhi "indawo yokungena" ekwandiseni.

Ukuboniswa

Ngokuvumelana nencazelo, ifayela le-manifest liyifayela elivumelekile le-JSON. Incazelo egcwele yokhiye be-manifest enolwazi mayelana nokuthi yiziphi izinkinobho ezisekelwa lapho isiphequluli singabukwa khona lapha.

Okhiye abangekho esimisweni esithi "bangase" banganakwa (womabili amaphutha abika i-Chrome neFirefox, kodwa izandiso ziyaqhubeka nokusebenza).

Futhi ngingathanda ukudonsela ukunaka kwamanye amaphuzu.

  1. Isizinda - into ehlanganisa lezi zindawo ezilandelayo:
    1. izikripthi — uxhaxha lwemibhalo ezosetshenziswa kungqikithi yangemuva (sizokhuluma ngalokhu kamuva);
    2. Page — esikhundleni semibhalo ezosetshenziswa ekhasini elingenalutho, ungacacisa i-html enokuqukethwe. Kulesi simo, inkambu yombhalo izozitshwa futhi imibhalo izodinga ukufakwa ekhasini lokuqukethwe;
    3. iqhubeka — ifulegi eliwumbambimbili, uma lingashiwongo, isiphequluli “sizobulala” inqubo yangemuva uma sicabanga ukuthi asenzi lutho, futhi siyiqale kabusha uma kunesidingo. Uma kungenjalo, ikhasi lizolayishwa kuphela uma isiphequluli sivaliwe. Ayisekelwe kuFirefox.
  2. content_scripts — izinto eziningi ezikuvumela ukuthi ulayishe imibhalo ehlukahlukene emakhasini ewebhu ahlukene. Into ngayinye iqukethe izinkambu ezibalulekile ezilandelayo:
    1. ukufana - i-url yephethini, enquma ukuthi umbhalo othile wokuqukethwe uzofakwa noma cha.
    2. js - uhlu lwemibhalo ezolayishwa kulo mdlalo;
    3. ngaphandle_okufanayo - akubandakanyi enkundleni match Ama-URL afana nale nkambu.
  3. ikhasi_isenzo - empeleni into enesibopho sesithonjana esiboniswa eduze kwebha yekheli kusiphequluli nokusebenzisana nayo. Iphinde ikuvumela ukuthi ubonise iwindi le-popup, elichazwa usebenzisa i-HTML, CSS ne-JS yakho.
    1. okuzenzakalelayo_okuzenzakalelayo — indlela eya efayeleni le-HTML ene-popup interface, ingase iqukathe i-CSS ne-JS.
  4. izimvume - uhlu lokuphatha amalungelo esandiso. Kunezinhlobo ezi-3 zamalungelo, ezichazwe kabanzi lapha
  5. web_accessible_resources — izinsiza ezinwetshiwe ikhasi lewebhu elingazicela, isibonelo, izithombe, i-JS, i-CSS, amafayela e-HTML.
  6. ngaphandle_kuyaxhumeka — lapha ungacacisa ngokusobala ama-ID ezinye izandiso nezizinda zamakhasi ewebhu ongaxhuma kuzo. Isizinda singaba izinga lesibili noma ngaphezulu. Ayisebenzi kuFirefox.

Umongo wokwenza

Isandiso sinezimo ezintathu zokwenza amakhodi, okungukuthi, uhlelo lokusebenza luqukethe izingxenye ezintathu ezinamazinga ahlukene okufinyelela ku-API yesiphequluli.

Isandiso umongo

Iningi lama-API liyatholakala lapha. Kulo mongo "bahlala":

  1. Ikhasi elingemuva — “buyisela emuva” ingxenye yesandiso. Ifayela licaciswe ku-manifest kusetshenziswa ukhiye "ongemuva".
  2. Ikhasi elizivelelayo — ikhasi le-popup elivelayo uma uchofoza isithonjana sesandiso. Ku-manifesto browser_action -> default_popup.
  3. Ikhasi langokwezifiso — ikhasi lesandiso, “ukuphila” kuthebhu ehlukile yokubuka chrome-extension://<id_расширения>/customPage.html.

Lo mongo utholakala ngaphandle kwamawindi esiphequluli namathebhu. Ikhasi elingemuva ikhona kukhophi eyodwa futhi isebenza njalo (okuhlukile yikhasi lomcimbi, lapho iskripthi sangemuva siqaliswa umcimbi futhi "sifa" ngemva kokukhishwa kwaso). Ikhasi elizivelelayo ikhona uma iwindi le-popup livuliwe, futhi Ikhasi langokwezifiso — ngenkathi ithebhu enayo ivuliwe. Akukho ukufinyelela kwamanye amathebhu nokuqukethwe kwawo kulo mongo.

Umongo weskripthi sokuqukethwe

Ifayela lombhalo wokuqukethwe wethulwa kanye nethebhu yesiphequluli ngasinye. Inokufinyelela engxenyeni ye-API yesandiso kanye nesihlahla se-DOM sekhasi lewebhu. Yimibhalo yokuqukethwe enesibopho sokusebenzelana nekhasi. Izandiso ezikhohlisa isihlahla se-DOM zenza lokhu emibhalweni yokuqukethwe - isibonelo, izivimbeli zezikhangiso noma abahumushi. Futhi, umbhalo wokuqukethwe ungaxhumana nekhasi ngendlela evamile postMessage.

Umongo wekhasi lewebhu

Leli yikhasi lewebhu langempela ngokwalo. Ayihlangene nesandiso futhi ayinakho ukufinyelela lapho, ngaphandle kwasezimeni lapho isizinda saleli khasi singakhonjiswanga ngokucacile ku-manifest (okunye kulokhu ngezansi).

Ukushintshaniswa kwemiyalezo

Izingxenye ezihlukene zohlelo lokusebenza kumele zishintshisane ngemiyalezo. Kukhona i-API yalokhu runtime.sendMessage ukuthumela umlayezo background и tabs.sendMessage ukuthumela umlayezo ekhasini (umbhalo wokuqukethwe, i-popup noma ikhasi lewebhu uma likhona externally_connectable). Ngezansi kunesibonelo lapho ufinyelela i-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))
    }
)

Ukuze uthole ukuxhumana okugcwele, ungakha ukuxhumana ngokusebenzisa runtime.connect. Ekuphenduleni sizothola runtime.Port, lapho, ngenkathi ivuliwe, ungathumela noma iyiphi inombolo yemiyalezo. Ohlangothini lweklayenti, isibonelo, contentscript, kubukeka kanje:

// Опять же 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 noma ingemuva:

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

Kukhona nomcimbi onDisconnect kanye nendlela disconnect.

Umdwebo wohlelo lokusebenza

Masenze isandiso sesiphequluli esigcina okhiye abayimfihlo, esihlinzeka ngokufinyelela kulwazi lwasesidlangalaleni (ikheli, ukhiye womphakathi uxhumana nekhasi futhi uvumela izinhlelo zokusebenza zezinkampani zangaphandle ukuthi zicele isiginesha mayelana nokwenziwe.

Ukuthuthukiswa kohlelo lokusebenza

Uhlelo lwethu lokusebenza kufanele lusebenzisane nomsebenzisi futhi lunikeze ikhasi nge-API ukuze ushayele izindlela (isibonelo, ukusayina imisebenzi). Yenza ngeyodwa nje contentscript ngeke isebenze, njengoba inokufinyelela ku-DOM kuphela, kodwa hhayi ku-JS yekhasi. Xhuma nge runtime.connect asikwazi, ngoba i-API iyadingeka kuzo zonke izizinda, futhi eziqondile kuphela ezingacaciswa ku-manifest. Ngenxa yalokho, umdwebo uzobukeka kanje:

Ukubhala isandiso sesiphequluli esivikelekile

Kuzoba nesinye iskripthi - inpage, esizoyijova ekhasini. Izosebenza kumongo wayo futhi inikeze i-API yokusebenza nesandiso.

Начало

Yonke ikhodi yesandiso yesiphequluli iyatholakala kokuthi GitHub. Ngesikhathi sencazelo kuzoba nezixhumanisi zokuzibophezela.

Ake siqale 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"]
}

Dala ingemuva.js elingenalutho, i-popup.js, i-inpage.js kanye ne-contentscript.js. Sengeza i-popup.html - futhi uhlelo lwethu lokusebenza lungavele lulayishwe ku-Google Chrome futhi siqiniseke ukuthi luyasebenza.

Ukuze uqinisekise lokhu, ungathatha ikhodi kusuka lapha. Ngaphezu kwalokho esikwenzile, isixhumanisi silungise ukuhlangana kwephrojekthi sisebenzisa i-webpack. Ukwengeza uhlelo esipheqululini, ku-chrome://extensions udinga ukukhetha ukulayisha okungapakishiwe kanye nefolda enesandiso esihambisanayo - esimweni sethu i-dist.

Ukubhala isandiso sesiphequluli esivikelekile

Manje isandiso sethu sesifakiwe futhi siyasebenza. Ungasebenzisa amathuluzi onjiniyela ezimo ezihlukile ngale ndlela elandelayo:

i-pop-up ->

Ukubhala isandiso sesiphequluli esivikelekile

Ukufinyelela kukhonsoli yeskripthi sokuqukethwe kwenziwa ngekhonsoli yekhasi ngokwalo eyethulwe kulo.Ukubhala isandiso sesiphequluli esivikelekile

Ukushintshaniswa kwemiyalezo

Ngakho-ke, sidinga ukusungula iziteshi ezimbili zokuxhumana: i-inpage <-> ingemuva kanye ne-popup <-> ingemuva. Yebo, ungavele uthumele imilayezo ethekwini futhi usungule eyakho iphrothokholi, kodwa ngikhetha indlela engiyibone kuphrojekthi yomthombo ovulekile we-metamask.

Lesi isandiso sesiphequluli sokusebenza nenethiwekhi ye-Ethereum. Kuyo, izingxenye ezihlukene zohlelo lokusebenza zixhumana nge-RPC zisebenzisa umtapo wezincwadi we-dnode. Ikuvumela ukuthi uhlele ukushintshanisa ngokushesha nakalula uma ukuhlinzeka ngokusakaza kwe-nodejs njengesithuthi (okusho into esebenzisa isixhumi esibonakalayo esifanayo):

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

Manje sizokwakha ikilasi lesicelo. Izodala izinto ze-API ze-popup nekhasi lewebhu, futhi izenzele i-dnode yazo:

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

Lapha nangezansi, esikhundleni sento ye-Chrome yomhlaba wonke, sisebenzisa i-extensionApi, efinyelela i-Chrome kusiphequluli se-Google kanye nesiphequluli kwezinye. Lokhu kwenzelwa ukusebenzisana kwe-cross-browser, kodwa ngezinjongo zalesi sihloko, ungase usebenzise i-'chrome.runtime.connect'.

Ake sakhe isibonelo sohlelo lokusebenza kumbhalo ongemuva:

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

Njengoba i-dnode isebenza nokusakaza, futhi sithola imbobo, ikilasi le-adaptha liyadingeka. Yenziwa kusetshenziswa umtapo wolwazi ofundekayo, esebenzisa imifudlana ye-nodejs esipheqululini:

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

Manje ake sakhe uxhumano ku-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;
    }
}

Bese sidala ukuxhumana kusikripthi sokuqukethwe:

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

Njengoba sidinga i-API hhayi kusikripthi sokuqukethwe, kodwa ngqo ekhasini, senza izinto ezimbili:

  1. Sakha imifudlana emibili. Eyodwa - ibheke ekhasini, phezu kweposiUmlayezo. Kulokhu sisebenzisa lokhu leli phakheji kusukela kubadali be-metamask. Ukusakaza kwesibili kungemuva kwembobo etholwe kuyo runtime.connect. Masizithenge. Manje ikhasi lizoba nokusakaza ngemuva.
  2. Faka umbhalo ku-DOM. Landa iskripthi (ukufinyelela kuso kuvunyelwe ku-manifest) bese udala umaka script nokuqukethwe 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);
    }
}

Manje sakha into ye-api ku-inpage futhi siyisethela kumhlaba 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;
}

Sesilungile I-Remote Procedure Call (RPC) ene-API ehlukene yekhasi ne-UI. Lapho uxhuma ikhasi elisha ngemuva singabona lokhu:

Ukubhala isandiso sesiphequluli esivikelekile

I-API engenalutho kanye nemvelaphi. Eceleni kwekhasi, singabiza umsebenzi we-hello kanje:

Ukubhala isandiso sesiphequluli esivikelekile

Ukusebenza ngemisebenzi yokushayela emuva ku-JS yesimanje kuwukuziphatha okubi, ngakho-ke masibhale umsizi omncane ukuze sakhe i-dnode ekuvumela ukuthi udlulise into ye-API ezintweni ezisetshenziswayo.

Izinto ze-API manje zizobukeka kanje:

export class SignerApp {

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

...

}

Ukuthola into kusilawuli kude kanje:

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

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

Futhi imisebenzi yokubiza ibuyisela isithembiso:

Ukubhala isandiso sesiphequluli esivikelekile

Inguqulo enemisebenzi engavumelaniyo etholakalayo lapha.

Sekukonke, i-RPC nendlela yokusakaza ibonakala ivumelana nezimo: singasebenzisa i-steam multiplexing futhi sidale ama-API amaningana emisebenzi eyahlukene. Eqinisweni, i-dnode ingasetshenziswa noma kuphi, into eyinhloko ukugoqa ukuthutha ngendlela yomfudlana we-nodejs.

Enye ifomethi ye-JSON, esebenzisa iphrothokholi ye-JSON RPC 2. Nokho, isebenza ngezinto ezithile zokuthutha (i-TCP ne-HTTP(S)), ezingasebenzi kithi.

Isimo sangaphakathi neSitoreji sasendaweni

Kuzodingeka sigcine isimo sangaphakathi sohlelo lokusebenza - okungenani okhiye bokusayina. Singangeza kalula isimo kuhlelo lokusebenza nezindlela zokusishintsha ku-API ye-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)
        }
    }

    ...

} 

Ngasemuva, sizogoqa yonke into ngomsebenzi bese sibhala into yohlelo lokusebenza efasiteleni ukuze sisebenze nayo kusuka kukhonsoli:

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

Ake sengeze okhiye abambalwa ku-UI console futhi sibone ukuthi kwenzekani ngesimo:

Ukubhala isandiso sesiphequluli esivikelekile

Umbuso udinga ukwenziwa ukuphikelela ukuze okhiye bangalahleki lapho uqala kabusha.

Sizoyigcina ku-LocalStore, siyikhiphe ngalo lonke ushintsho. Ngokulandelayo, ukufinyelela kuyo kuyodingeka futhi ku-UI, futhi ngingathanda ukubhalisela izinguquko. Ngokusekelwe kulokhu, kuzoba lula ukudala isitoreji esibonakalayo futhi ubhalisele izinguquko zayo.

Sizosebenzisa umtapo wezincwadi we-mobx (https://github.com/mobxjs/mobx). Ukukhetha kwawela phezu kwayo ngoba kwakungadingeki ngisebenze nayo, kodwa ngangifuna ngempela ukuyifundela.

Ake sengeze ukuqaliswa kwesimo sokuqala futhi senze isitolo sibonakale:

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

    ...

}

“Ngaphansi kwe-hood,” i-mobx imiselele zonke izinkambu zesitolo ngommeleli futhi ibamba zonke izingcingo eziya kuzo. Kuzokwazi ukuthi ubhalisele le milayezo.

Ngezansi ngizovame ukusebenzisa igama elithi “lapho ushintsha”, nakuba lokhu kungalungile ngokuphelele. I-Mobx ilandelela ukufinyelela ezinkambu. Ama-Getter kanye namasethi ezinto ze-proxy ezidalwa umtapo wolwazi ziyasetshenziswa.

Abahlobisi besenzo bafeza izinhloso ezimbili:

  1. Kumodi eqinile enefulegi le-enforceActions, i-mobx iyakwenqabela ukushintsha isimo ngokuqondile. Kuthathwa njengomkhuba omuhle ukusebenza ngaphansi kwezimo eziqinile.
  2. Ngisho noma umsebenzi ushintsha isimo izikhathi eziningana - isibonelo, sishintsha izinkambu eziningana emigqeni eminingana yekhodi - izibukeli ziyaziswa kuphela uma ziqeda. Lokhu kubaluleke kakhulu ku-frontend, lapho izibuyekezo zesimo ezingadingekile ziholela ekunikezeni okungadingekile kwezinto. Esimweni sethu, akuyona eyokuqala noma eyesibili ebaluleke kakhulu, kodwa sizolandela izinqubo ezihamba phambili. Kuyisiko ukunamathisela abahlobisi kuyo yonke imisebenzi eshintsha isimo sezinkambu ezibukiwe.

Ngemuva sizokwengeza ukuqaliswa futhi silondoloze isimo ku-LocalStore:

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 uyathakazelisa lapha. Inezimpikiswano ezimbili:

  1. Isikhethi sedatha.
  2. Isibambi esizobizwa ngale datha njalo uma ishintsha.

Ngokungafani ne-reux, lapho sithola khona ngokusobala isimo njengengxabano, i-mobx ikhumbula ukuthi yiziphi izinto ezibonakalayo esifinyelela kuzo ngaphakathi kwesikhethi, futhi ibiza isibambi kuphela uma sishintsha.

Kubalulekile ukuqonda kahle ukuthi i-mobx inquma kanjani ukuthi yiziphi izinto ezibonakalayo esizibhalisa kukho. Uma ngibhale isikhethi ngekhodi kanje() => app.store, khona-ke ukusabela akusoze kwabizwa, njengoba isitoreji ngokwaso singabonakali, izinkambu zaso kuphela.

Uma ngibhale kanje () => app.store.keys, bese kungenzeki lutho futhi, njengoba lapho wengeza/ususa izakhi zamalungu afanayo, ireferensi yakho ngeke ishintshe.

I-Mobx isebenza njengesikhethi okokuqala ngqa futhi ilandelela kuphela okuqaphelekayo esikufinyelele. Lokhu kwenziwa ngama-proxy getters. Ngakho-ke, umsebenzi owakhelwe ngaphakathi usetshenziswa lapha toJS. Ibuyisela into entsha enawo wonke ama-proxies afakwe izinkambu zangempela. Ngesikhathi sokubulawa, ifunda zonke izinkambu zento - yingakho ama-getter aqala.

Kukhonsoli ye-popup sizophinda sengeze okhiye abambalwa. Kulokhu baphinde bangena ku-LocalStore:

Ukubhala isandiso sesiphequluli esivikelekile

Uma ikhasi elingemuva lilayishwa kabusha, ulwazi luhlala lukhona.

Yonke ikhodi yohlelo lokusebenza kuze kube manje ingabukwa lapha.

Isitoreji esivikelekile sokhiye abayimfihlo

Ukugcina okhiye abayimfihlo embhalweni ocacile akuphephile: kuhlale kunethuba lokuthi uzogetshengwa, uthole ukufinyelela kukhompyutha yakho, nokunye. Ngakho-ke, ku-LocalStore sizogcina okhiye kufomu elibethelwe ngephasiwedi.

Ukuze uthole ukuvikeleka okukhulu, sizongeza isimo esikhiyiwe kuhlelo lokusebenza, lapho okungeke kube khona ukufinyelela kokhiye nhlobo. Sizodlulisela ngokuzenzakalelayo isandiso esimweni esikhiyiwe ngenxa yokuvala isikhathi.

I-Mobx ikuvumela ukuthi ugcine kuphela isethi encane yedatha, futhi enye ibalwa ngokuzenzakalelayo ngokusekelwe kuyo. Lezi yizinto ezibizwa nge-computed properties. Angaqhathaniswa nokubukwa kusizindalwazi:

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

Manje sigcina kuphela okhiye ababethelwe kanye nephasiwedi. Konke okunye kubalwa. Senza ukudlulisela esimweni esikhiyiwe ngokususa iphasiwedi kuhulumeni. I-API yomphakathi manje isinendlela yokuqalisa isitoreji.

Ibhalelwe ukubethela izinsiza 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)
}

Isiphequluli sine-API engenzi lutho ongabhalisa ngayo emcimbini - izinguquko zesimo. State, ngokufanele, kungaba idle, active и locked. Ngokungenzi lutho ungasetha isikhathi sokuvala, futhi ukukhiya kusethwa lapho i-OS ngokwayo ivinjiwe. Sizophinda siguqule isikhethi sokulondoloza kokuthi Isitoreji sasendaweni:

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

Ikhodi ngaphambi kwalesi sinyathelo lapha.

Ukuthengiselana

Ngakho-ke, sifika entweni ebaluleke kakhulu: ukudala nokusayina ukuthengiselana ku-blockchain. Sizosebenzisa i-WAVES blockchain kanye nomtapo wolwazi amagagasi-ukuthengiselana.

Okokuqala, ake sengeze kusimo uxhaxha lwemilayezo edinga ukusayinwa, bese sengeza izindlela zokwengeza umlayezo omusha, ukuqinisekisa isiginesha, nokwenqaba:

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

    ...
}

Uma sithola umlayezo omusha, sengeza imethadatha kuwo, yenza observable futhi wengeze ku store.messages.

Uma ungakwenzi observable ngesandla, bese i-mobx izokwenza ngokwayo lapho yengeza imilayezo kuhlu. Nokho, izodala into entsha esingeke sibe nayo inkomba, kodwa sizoyidinga esinyathelweni esilandelayo.

Okulandelayo, sibuyisela isithembiso esixazululayo lapho isimo somlayezo sishintsha. Isimo sigadwa ukusabela, "okuzozibulala" lapho isimo sishintsha.

Ikhodi yendlela approve и reject ilula kakhulu: simane siguqule isimo somlayezo, ngemva kokuwusayina uma kunesidingo.

Sifaka okuthi Vuma futhi senqabe ku-UI API, umlayezo omusha ekhasini 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)
        }
    }

    ...
}

Manje ake sizame ukusayina umsebenzi ngesandiso:

Ukubhala isandiso sesiphequluli esivikelekile

Ngokuvamile, konke sekumi ngomumo, konke okusele engeza i-UI elula.

UI

I-interface idinga ukufinyelela kusimo sohlelo lokusebenza. Ngasohlangothini lwe-UI sizokwenza observable chaza futhi wengeze umsebenzi ku-API ozoshintsha lesi simo. Ake sengeze observable entweni ye-API etholwe ngemuva:

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

Ekugcineni siqala ukunikeza isixhumi esibonakalayo sohlelo lokusebenza. Lolu uhlelo lokusebenza lokusabela. Into yangemuva imane idluliswe kusetshenziswa ama-props. Kungaba okulungile, yiqiniso, ukwenza isevisi ehlukile yezindlela kanye nesitolo sombuso, kodwa ngezinjongo zalesi sihloko lokhu 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 ukuqala ukunikezela lapho idatha ishintsha. Simane silenga umhlobisi wezibukeli ephaketheni mobx-sabela engxenyeni, futhi ukunikezela kuzobizwa ngokuzenzakalelayo lapho noma yikuphi okubonwayo okubhekiselwa kushintsho lwengxenye. Awudingi i-mapStateToProps noma uxhume njenge-redux. Konke kusebenza 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>
    }
}

Izingxenye ezisele zingabonwa kukhodi kufolda ye-UI.

Manje ekilasini lesicelo udinga ukwenza isikhethi sesifunda se-UI futhi wazise i-UI uma ishintsha. Ukwenza lokhu, ake sengeze indlela getState и reactionukubiza 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())

        })
    }

    ...
}

Lapho ethola into remote idaliwe reaction ukushintsha isimo esibiza umsebenzi ohlangothini lwe-UI.

Ukuthinta okokugcina ukwengeza ukuboniswa kwemilayezo emisha kusithonjana sesandiso:

function setupApp() {
...

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

...
}

Ngakho, isicelo sesilungile. Amakhasi ewebhu angase acele isiginesha yemisebenzi:

Ukubhala isandiso sesiphequluli esivikelekile

Ukubhala isandiso sesiphequluli esivikelekile

Ikhodi iyatholakala lapha isixhumanisi.

isiphetho

Uma usifundile isihloko kwaze kwaba sekugcineni, kodwa usenemibuzo, ungababuza kuyo amakhosombe anesandiso. Lapho futhi uzothola izibophezelo zesinyathelo ngasinye esibekiwe.

Futhi uma unentshisekelo yokubheka ikhodi yesandiso sangempela, ungakuthola lokhu lapha.

Ikhodi, inqolobane kanye nencazelo yomsebenzi kusuka siemarell

Source: www.habr.com

Engeza amazwana