Qorista kordhinta biraawsarkaaga badbaadsan

Qorista kordhinta biraawsarkaaga badbaadsan

Si ka duwan qaab dhismeedka "macmiilka-server" ee caadiga ah, codsiyada baahsan waxaa lagu gartaa:

  • Looma baahna in lagu kaydiyo xog-ururin leh gelitaanka isticmaalayaasha iyo ereyada sirta ah. Macluumaadka gelitaanka waxa si gaar ah u kaydiya isticmaaleyaasha laftooda, xaqiijinta runnimadoodana waxay ku dhacdaa heerka borotokoolka.
  • Looma baahna in la isticmaalo server Codsiga macquulka ah waxaa lagu fulin karaa shabakada blockchain, halkaasoo ay suurtagal tahay in lagu kaydiyo xogta loo baahan yahay.

Waxaa jira 2 kayd oo ammaan ah oo loogu talagalay furayaasha isticmaalaha - boorsooyinka hardware iyo kordhinta browserka. Boorsooyinka qalabku inta badan waa kuwo aad ammaan u ah, laakiin ay adag tahay in la isticmaalo oo ka fog lacag la'aanta, laakiin kordhinta browserku waa isku darka ugu fiican ee amniga iyo fududaynta isticmaalka, waxayna sidoo kale si buuxda u noqon kartaa bilaash isticmaalayaasha dhamaadka.

Anaga oo waxan oo dhan xisaabta ku darnay, waxaan rabnay in aan samayno kordhinta ugu aaminka badan ee fududaysa horumarinta codsiyada la baahiyey anagoo siinaya API fudud oo lagula shaqaynayo wax kala iibsiga iyo saxeexyada.
Hoos ayaan kaaga sheegi doonaa waxa ku saabsan khibraddan.

Maqaalku wuxuu ka koobnaan doonaa tilmaamo tallaabo-tallaabo ah oo ku saabsan sida loo qoro kordhinta browserka, oo wata tusaalooyin kood ah iyo sawir-qaadis. Waxaad ka heli kartaa dhammaan koodka gudaha kayd. Mid kasta oo gasha si macquul ah wuxuu u dhigmaa qayb ka mid ah maqaalkan.

Taariikh Kooban Oo Ku Saabsan Kordhinta Browser

Kordhinta Browser-ku waxay jireen muddo dheer. Waxay ka soo muuqdeen Internet Explorer 1999kii, Firefox 2004tii. Si kastaba ha ahaatee, muddo aad u dheer ma jirin hal halbeeg oo kordhinta.

Waxaan dhihi karnaa in ay la soo baxday kordhinta nooca afaraad ee Google Chrome. Dabcan, ma jirin wax sifayn ah markaas, laakiin waxay ahayd Chrome API-ka kaas oo noqday aasaaskiisa: isagoo qabsaday inta badan suuqa biraawsarka iyo inuu haysto dukaanka codsiyada ku dhex jira, Chrome dhab ahaantii wuxuu dejiyaa heerka fidinta browserka.

Mozilla waxay lahayd halbeeg u gaar ah, laakiin markay aragtay caannimada kordhinta Chrome, shirkaddu waxay go'aansatay inay samayso API la jaan qaada. 2015, hindisaha Mozilla, koox gaar ah ayaa laga dhex abuuray World Wide Web Consortium (W3C) si ay uga shaqeeyaan qeexida fidinta browser-ka.

Kordhinta API ee hadda jirta ee Chrome ayaa loo qaatay saldhig ahaan. Shaqada waxaa lagu fuliyay taageerada Microsoft (Google wuxuu diiday inuu ka qaybqaato horumarinta heerka), natiijaduna waxay soo baxday qabyo. faahfaahinta.

Si rasmi ah, qeexitaanka waxaa taageera Edge, Firefox iyo Opera (xusuusnow in Chrome uusan ku jirin liiskan). Laakiin dhab ahaantii, halbeeggu inta badan wuu la jaan qaadayaa Chrome, maadaama ay dhab ahaantii ku qoran tahay kordhinteeda. Waxaad ka akhrisan kartaa wax badan oo ku saabsan WebExtensions API halkan.

Qaab dhismeedka fidinta

Faylka kaliya ee loo baahan yahay kordhinta waa muujinta (manifest.json). Sidoo kale waa "barta gelinta" ballaarinta.

Muuqaal

Marka loo eego qeexitaanka, feylka caddayntu waa fayl JSON sax ah. Sharaxa buuxda ee furayaasha bayaannada leh oo wata macluumaadka ku saabsan furayaasha la taageeray ee browserka lagu arki karo halkan.

Furayaasha aan ku jirin qeexitaanka "waa laga yaabaa" in la iska indho tiro (labadaba Chrome iyo Firefox waxay soo sheegaan khaladaadka, laakiin kordhintu waxay sii wadaa inay shaqeyso).

Oo waxaan jeclaan lahaa inaan dareenka u soo jeediyo qodobbada qaarkood.

  1. background - shay ay ku jiraan meelaha soo socda:
    1. qoraallo Qoraallo kala duwan oo lagu fulin doono macnaha guud (wax yar ka dib ayaan tan ka hadli doonaa);
    2. Page - beddelka qoraallada lagu fulin doono bog madhan, waxaad ku qeexi kartaa html oo nuxur leh. Xaaladdan oo kale, goobta qoraalka waa la iska indhatiray, qoraalladana waxay u baahan doonaan in la geliyo bogga nuxurka;
    3. Daa'imaan - Calan laba-geesood ah, haddii aan la cayimin, browser-ku wuxuu "dili doonaa" habka asalka ah marka uu tixgeliyo in uusan waxba qabaneynin, oo dib u bilaabi doona haddii loo baahdo. Haddii kale, bogga kaliya ayaa laga dajin doonaa marka browserku xiran yahay. Laguma taageero Firefox
  2. nuxurka_scripts - waxyaabo badan oo kuu oggolaanaya inaad ku shubto qoraallo kala duwan bogag shabakadeed oo kala duwan. Shay kastaa wuxuu ka kooban yahay qaybahan muhiimka ah:
    1. kulan - qaabka url, kaas oo go'aaminaya in qoraal gaar ah lagu dari doono iyo in kale.
    2. js - liiska qoraallada lagu shubi doono ciyaartan;
    3. ka saar_kulannada - ka reeban garoonka match URLs ku habboon goobtan
  3. bogga_falka - dhab ahaantii waa shay mas'uul ka ah astaanta ka muuqata ciwaanka ku xiga ciwaanka browserka iyo la dhexgalka. Waxa kale oo ay kuu ogolaanaysaa inaad soo bandhigto daaqad popup ah, kaas oo lagu qeexay adigoo isticmaalaya HTML, CSS iyo JS.
    1. default_popup - dariiqa loo maro faylka HTML ee leh interface-ka popup, waxaa ku jiri kara CSS iyo JS.
  4. rukhsadda - habayn lagu maareeyo xuquuqaha kordhinta. Waxaa jira 3 nooc oo xuquuq ah, kuwaas oo si faahfaahsan loo qeexay halkan
  5. web_accessible_sources - agabka fidinta ee bogga shabakadu codsan karo, tusaale ahaan, sawirada, JS, CSS, faylasha HTML.
  6. dibadda_isku xidhi karo - halkan waxaad si cad u qeexi kartaa aqoonsiga kordhinta kale iyo xayndaabka boggaga internetka oo aad ka xidhi karto. Domainku wuxuu noqon karaa heerka labaad ama ka sareeya. Kuma shaqeeyo Firefox

macnaha fulinta

Kordhintu waxay leedahay saddex nooc oo ah fulinta code, taas oo ah, codsigu wuxuu ka kooban yahay saddex qaybood oo leh heerar kala duwan oo gelitaanka API browser.

Macnaha kordhinta

Inta badan API-ga ayaa laga heli karaa halkan. Xaaladdan oo kale waxay "ku nool yihiin":

  1. Bogga asalka ah - qaybta "dambe" ee kordhinta. Faylka waxaa lagu qeexay muujinta iyadoo la adeegsanayo furaha "background".
  2. Bogga soo booda - bog soo baxay oo soo baxaya marka aad gujiso astaanta kordhinta. In manifesto browser_action -> default_popup.
  3. Bogga gaarka ah - bogga kordhinta, "ku nool" tab gooni ah oo aragtida ah chrome-extension://<id_расширения>/customPage.html.

Macnaha guud wuxuu u jiraa si ka madaxbanaan daaqadaha iyo tabsiyada browserka. Bogga asalka ah ku jira hal nuqul oo had iyo jeer shaqeeya (marka laga reebo waa bogga dhacdada, marka qoraalka asalka ah la bilaabo dhacdo iyo "dhimasho" ka dib marka la fuliyo). Bogga soo booda jira marka uu furmo daaqada popup, iyo Bogga gaarka ah - inta tab la furo. Ma jiro marin loo helo tabs kale iyo waxa ku jira oo laga soo qaatay macnahan.

Qoraalka qoraalka nuxurka

Faylka qoraalka qoraalka waxa la bilaabay tab browser kasta. Waxay marin u leedahay qayb ka mid ah API-ga kordhinta iyo geedka DOM ee bogga shabakadda. Waa qoraallo ka kooban kuwa mas'uul ka ah isdhexgalka bogga. Kordhinta maamulaysa geedka DOM ayaa tan ku sameeya qoraallada nuxurka ah - tusaale ahaan, xannibayaasha xayeysiiska ama tarjumayaasha. Sidoo kale, qoraalka nuxurku waxa uu kula xidhiidhi karaa bogga iyada oo loo marayo heerka caadiga ah postMessage.

Macnaha guud ee bogga shabakadda

Kani waa bogga dhabta ah laftiisa. Wax shaqo ah kuma laha kordhinta mana lahan marinka halkaas, marka laga reebo kiisaska goobta boggan aan si cad loogu muujin muujinta (wax badan oo ku saabsan tan hoose).

Isdhaafsiga farriimaha

Qaybaha kala duwan ee codsiga waa in ay is dhaafsadaan fariimaha midba midka kale. Waxaa jira API kan runtime.sendMessage in uu fariin u diro background и tabs.sendMessage si fariin loogu diro bog (qoraalka nuxurka, soo baxay ama bogga shabakada haddii la heli karo externally_connectable). Hoos waxaa ku yaal tusaale marka la gelayo 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))
    }
)

Xiriir buuxa, waxaad ku abuuri kartaa xiriiryo runtime.connect. Jawaabta waanu heli doonaa runtime.Port, kaas oo, inta ay furan tahay, waxaad u diri kartaa lambar kasta oo farriimo ah. Dhinaca macmiilka, tusaale ahaan, contentscript, waxay u egtahay sidan:

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

Adeegaha ama asalka:

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

Waxaa kaloo jirta dhacdo onDisconnect iyo habka disconnect.

jaantuska codsiga

Aan samayno kordhin browser ah oo kaydisa furayaasha gaarka ah, siiya marin u helka macluumaadka dadweynaha (cinwaanka, furaha dadweynaha wuxuu la xidhiidhaa bogga oo u oggolaanaya codsiyada qolo saddexaad inay codsadaan saxiixa macaamil ganacsi.

Horumarinta codsiga

Codsigayagu waa inuu labadaba la falgala isticmaalaha oo aanu siinaa bogga API si loogu waco hababka (tusaale, si loo saxeexo wax kala iibsiga). Ku samee hal kaliya contentscript ma shaqayn doonto, maadaama ay geli karto DOM oo keliya, laakiin ma aha JS ee bogga. Ku xidhnow via runtime.connect ma awoodno, sababtoo ah API ayaa looga baahan yahay dhammaan qaybaha, oo kaliya kuwa gaar ah ayaa lagu qeexi karaa muujinta. Natiijo ahaan, jaantusku wuxuu u ekaan doonaa sidan:

Qorista kordhinta biraawsarkaaga badbaadsan

Waxaa jiri doona qoraal kale - inpage, kaas oo aanu ku duri doono bogga. Waxay ku socon doontaa macnaha guud waxayna siin doontaa API-ka la shaqaynta kordhinta.

Начало

Dhammaan koodka fidinta browserka ayaa laga heli karaa GitHub. Inta lagu jiro sharraxaadda waxaa jiri doona xiriirin la sameeyo.

Aan ku bilowno qoraalka:

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

Abuur asalka maran.js, popup.js, inpage.js iyo contentscript.js. Waxaan ku darnaa popup.html - oo codsigeyga waxaa mar hore lagu shubi karaa Google Chrome oo hubi inuu shaqeeyo.

Si loo xaqiijiyo tan, waxaad qaadan kartaa koodka halkan. Marka lagu daro waxa aan sameynay, iskuxirku wuxuu habeeyay kulanka mashruuca iyadoo la adeegsanayo webpack. Si aad codsi ugu darto browserka, chrome://extensions waxaad u baahan tahay inaad doorato rarka aan baakadnayn iyo galka leh kordhinta u dhiganta - xaaladdeenna dist.

Qorista kordhinta biraawsarkaaga badbaadsan

Hadda kordhintayadu waa la rakibay oo shaqaynaysaa. Waxaad u maamuli kartaa qalabka horumariyaha xaaladaha kala duwan sida soo socota:

soo booda ->

Qorista kordhinta biraawsarkaaga badbaadsan

Gelitaanka konsole qoraalka ka kooban waxa lagu fuliyaa konsole ee bogga laftiisa kaas oo lagu bilaabay.Qorista kordhinta biraawsarkaaga badbaadsan

Isdhaafsiga farriimaha

Markaa, waxaan u baahanahay inaan samayno laba kanaal isgaarsiin: bogga <-> background iyo popup <-> background. Waxaad, dabcan, kaliya u diri kartaa fariimaha dekedda oo aad hindiso borotokool adiga kuu gaar ah, laakiin waxaan doorbidayaa habka aan ku arkay metamask-ka mashruuca il furan.

Tani waa kordhinta browserka ee la shaqeynta shabakada Ethereum. Gudaha, qaybaha kala duwan ee arjiga waxay ku wada xiriiraan RPC iyagoo isticmaalaya maktabadda dnode. Waxay kuu oggolaanaysaa inaad si dhakhso leh oo habboon u habayso beddelka haddii aad siiso qulqulka nodejs gaadiid ahaan (macneheedu waa shay fulinaya isla interface):

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

Hadda waxaan abuuri doonaa fasalka codsiga. Waxay u abuuri doontaa walxaha API soo-boodka iyo bogga shabakadda, waxayna u abuuri doontaa dnode iyaga:

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

Halkan iyo hoosta, halkii laga isticmaali lahaa shayga caalamiga ah ee Chrome, waxaanu isticmaalnaa extensionApi, kaas oo ka gala Chrome browserka Google iyo browserka dadka kale. Tan waxaa loo sameeyaa iswaafajinta dhexda-browser, laakiin ujeeddooyinka maqaalkan awgeed qofku wuxuu si fudud u isticmaali karaa 'chrome.runtime.connect'.

Aan ku abuurno tusaale arji qoraalka dambe:

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

Maadaama dnode uu la shaqeeyo durdurrada, oo aanu helno deked, fasalka adabtarada ayaa loo baahan yahay. Waxaa la sameeyay iyadoo la adeegsanayo maktabadda la akhrin karo, taasoo ka hirgelisa qulqulka nodejs browserka:

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

Hadda aan ku abuurno xiriir 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;
    }
}

Ka dib waxaan ku abuureynaa xiriirka qoraalka nuxurka:

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

Maadaama aan u baahnayn API-ga maahan qoraalka nuxurka, laakiin si toos ah bogga, waxaan sameynaa laba shay:

  1. Waxaan abuurnaa laba durdur. Mid ka mid ah - dhinaca bogga, oo ku yaal xagga sare ee fariinta boostada. Tan waxaan u isticmaalnaa tan xirmo this ka abuurayaasha metamask. Durdurka labaad waa xagga dambe ee dekedda laga helay runtime.connect. Aynu soo iibsano. Hadda boggu wuxuu yeelan doonaa durdur xagga dambe ah.
  2. Duri qoraalka DOM Soo deji qoraalka (helitaanka ayaa loo oggolaaday muujinta) oo samee sumad script oo ay ku jirto gudaha:

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

Hadda waxaan ku abuurnay shay api gudaha bogga oo u dhignay mid caalami ah:

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

diyaar ayaan nahay Wicida Habraaca fog (RPC) oo leh API gaar ah ee bogga iyo UI. Marka aan ku xidhno bog cusub xagga dambe waxaynu arki karnaa tan:

Qorista kordhinta biraawsarkaaga badbaadsan

API Empty iyo asalka Dhinaca bogga, waxaan ugu yeeri karnaa shaqada hello sida tan:

Qorista kordhinta biraawsarkaaga badbaadsan

La shaqaynta hawlaha dib u soo celinta ee JS casriga ah waa dhaqan xumo, markaa aynu qorno caawiye yar si aad u abuurto dnode kaas oo kuu ogolaanaya inaad u gudbiso shay API si aad u isticmaasho.

Walxaha API hadda waxay u ekaan doonaan sidan:

export class SignerApp {

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

...

}

Ka soo qaadashada shay meel fog fog sida tan:

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

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

Hawlaha wacitaankana waxay soo celiyaan ballan:

Qorista kordhinta biraawsarkaaga badbaadsan

Nooca leh hawlo isku mid ah ayaa diyaar ah halkan.

Guud ahaan, habka RPC iyo qulqulka ayaa u muuqda mid dabacsan: waxaan isticmaali karnaa isku-dhufashada uumiga waxaanan u abuuri karnaa API-yo kala duwan oo kala duwan hawlo kala duwan. Mabda 'ahaan, dnode waxaa loo isticmaali karaa meel kasta, waxa ugu muhiimsan waa in lagu duubo gaadiidka qaabka qulqulka nodejs.

Beddelku waa qaabka JSON, kaas oo fulisa borotokoolka JSON RPC 2. Si kastaba ha ahaatee, waxay la shaqeysaa gaadiid gaar ah (TCP iyo HTTP(S)), taas oo aan lagu dabaqi karin kiiskeena.

Gobolka gudaha iyo Kaydinta deegaanka

Waxaan u baahan doonaa inaan kaydino xaalada gudaha ee codsiga - ugu yaraan furayaasha saxeexa. Waxaan si fudud ugu dari karnaa xaalad codsiga iyo hababka loogu beddelo API-ka soo booda:

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

    ...

} 

Xagga dambe, wax walba waxaan ku duubi doonnaa si shaqeyn oo aan ku qori doonnaa arjiga shayga daaqadda si aan ugala shaqeyno console-ka:

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

Aan ku darno dhawr furayaal oo ka socda UI console oo aan aragno waxa ku dhacaya gobolka:

Qorista kordhinta biraawsarkaaga badbaadsan

Gobolka waxa uu u baahan yahay in si joogto ah loo sameeyo si aanay furayaashu u lumin marka dib loo bilaabayo.

Waxa aanu ku kaydin doonaa kaydinta maxaliga ah, anagoo ku dul qori doona isbedel kasta. Ka dib, gelitaankeeda waxay sidoo kale lagama maarmaan u noqon doontaa UI, waxaanan sidoo kale jeclaan lahaa inaan iska diiwaan geliyo isbeddelada. Iyada oo ku saleysan tan, waxay noqon doontaa mid ku habboon in la abuuro kayd la arki karo oo la is-beddelo.

Waxaan isticmaali doonaa maktabadda mobxhttps://github.com/mobxjs/mobx). Doorashadu waxay ku dhacday sababtoo ah maaha inaan la shaqeeyo, laakiin waxaan runtii rabay inaan barto.

Aynu ku darno bilawga gobolka bilawga oo aynu ka dhigno bakhaarka mid la arki karo:

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

    ...

}

"Hooyada hoosteeda," mobx waxay ku bedeshay dhammaan goobihii dukaamada wakiil waxayna ka joojisay dhammaan wicitaannada iyaga. Waxa suurtagal noqon doonta in aad rukunto fariimahan.

Hoos waxaan inta badan isticmaali doonaa ereyga "marka la bedelayo", inkastoo tani aysan sax ahayn. Mobx waxay la socotaa gelitaanka beeraha Soo-saareyaasha iyo dejinta walxaha wakiillada ee ay maktabaddu abuurto ayaa la adeegsadaa.

Qurxinta ficilku waxay u adeegtaa laba ujeedo:

  1. Qaab adag oo leh calanka fulintaActions, mobx waxay mamnuucday in si toos ah gobolka loo beddelo. Waxaa loo arkaa qaab wanaagsan in lagu shaqeeyo shuruudo adag.
  2. Xitaa haddii shaqadu ay beddesho gobolka dhowr jeer - tusaale ahaan, waxaan ku beddelnaa dhowr goobood oo dhowr xariiq oo kood ah - kormeerayaasha waxaa la ogeysiiyaa kaliya marka ay dhamaato. Tani waxay si gaar ah muhiim ugu tahay safka hore, halkaas oo cusboonaysiinta gobolka aan loo baahnayn ay horseeddo soo bandhigidda aan loo baahnayn ee curiyeyaasha. Xaaladeena, kan koowaad iyo kan labaad midna si gaar ah uma khuseeyo, laakiin waxaan raaci doonnaa hababka ugu wanaagsan. Waa caado in lagu dhejiyo qurxiyeyaal dhammaan hawlaha beddelaya xaaladda goobaha la arkay.

Dhabarka dambe waxa aanu ku dari doonaa bilawga iyo badbaadinta gobolka Kaydinta maxaliga ah:

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

Shaqada falcelinta waa mid xiiso leh halkan. Waxay leedahay laba doodood:

  1. Xulashada xogta.
  2. Maamule lagu wici doono xogtan mar kasta oo ay isbedesho.

Si ka duwan redux, halkaas oo aan si cad ugu helno gobolka sida dood ahaan, mobx waxay xasuusataa indha-indhaynta aan galno gudaha xulashada, oo kaliya wac maamulaha marka ay isbeddelaan.

Waa muhiim in si sax ah loo fahmo sida mobx ay u go'aamiso waxyaabaha la arki karo ee aan ku biirno. Haddii aan ku qoray cod-bixiye sidan oo kale ah() => app.store, ka dibna falcelinta weligeed loo yeedhi maayo, maadaama kaydinta lafteeda aan la arki karin, kaliya beeraheedu waa.

Hadii aan u qoray sidaan () => app.store.keys, ka dibna mar kale waxba ma dhici doonaan, tan iyo markii lagu daro / saarista curiyayaasha soo diyaariyeen, tixraaca ma beddeli doono.

Mobx waxay u shaqeysaa sidii dooriye markii ugu horeysay oo kaliya waxay ilaalisaa waxyaabaha la arki karo ee aan galnay. Tan waxaa lagu sameeyaa iyada oo loo marayo wakiil heterers. Sidaa darteed, shaqada la dhisay ayaa halkan loo isticmaalaa toJS. Waxay soo celisaa shay cusub oo leh dhammaan wakiillada lagu beddelay goobihii asalka ahaa. Inta lagu jiro fulinta, waxay akhrinaysaa dhammaan goobaha shayga - sidaas awgeed getters ayaa kiciyay.

Console-ka soo booda waxaan ku dari doonaa dhowr furayaal. Markan waxay sidoo kale ku dhamaadeen Kaydinta maxalliga ah:

Qorista kordhinta biraawsarkaaga badbaadsan

Marka bogga dambe dib loo raro, macluumaadku halkiisii ​​ayay ahaanayaan.

Dhammaan koodka codsiyada ilaa heerkan waa la eegi karaa halkan.

Kaydinta sugan ee furayaasha gaarka ah

Ku kaydinta furayaasha gaarka ah qoraal cad waa badbaado-darro: had iyo jeer waxaa jirta fursad ah in lagu jabsado, galaangal u yeesho kombayutarkaaga, iyo wixii la mid ah. Sidaa darteed, kaydinta maxaliga ah waxaan ku kaydin doonaa furayaasha qaab sir ah.

Nabadgelyada weyn, waxaan ku dari doonaa xaalad quful codsiga, kaas oo aan la heli doonin furayaasha gabi ahaanba. Waxaan si toos ah ugu wareejin doonaa kordhinta gobolka qufulan sababtoo ah waqti go'an.

Mobx wuxuu kuu ogolaanayaa inaad kaydiso xogta ugu yar, inta soo hartayna si toos ah ayaa loo xisaabiyaa iyadoo lagu saleynayo. Kuwani waa waxa loogu yeero guryaha la xisaabiyay. Waxa lala barbar dhigi karaa aragtida kaydadka xogta:

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

Hadda waxaan kaydinnaa oo keliya furayaasha iyo furaha sirta ah. Wax kasta oo kale waa la xisaabiyaa. Waxaan u wareejinaa gobol quful annagoo ka saarna erayga sirta ah ee gobolka API-ga dadweynaha hadda wuxuu leeyahay hab lagu bilaabo kaydinta.

Loo qoray si qarsoodi ah utility isticmaalaya 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)
}

Barowsarku waxa uu leeyahay API shaqo la'aan ah kaas oo aad ku biiri karto dhacdo - isbedelada gobolka. Gobolka, sidaas darteed, waxay noqon kartaa idle, active и locked. Shaqo la'aanta waxaad u dejin kartaa wakhti go'an, waxaana la xirayaa marka OS laftiisa la xannibo. Waxaan sidoo kale bedeli doonaa xulashada wax lagu kaydiyo kaydinta maxaliga ah:

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

Koodhka ka hor talaabadan waa halkan.

Wareejinta

Marka, waxaan u nimid waxa ugu muhiimsan: abuurista iyo saxiixa macaamil ganacsi ee blockchain. Waxaan isticmaali doonaa blockchain WAVES iyo maktabadda hirarka-ganacsiga.

Marka hore, aan ku darno gobolka farriimo kala duwan oo u baahan in la saxiixo, ka dibna aan ku darno habab loogu daro fariin cusub, xaqiijinta saxiixa, iyo diidmada:

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

    ...
}

Markaan helno fariin cusub, waxaan ku darnaa xogta badan, samee observable kuna darso store.messages.

Haddii aadan observable gacanta, ka dibna mobx lafteeda ayaa samayn doonta marka lagu daro fariimaha array-ga. Si kastaba ha ahaatee, waxay abuuri doontaa shay cusub oo aynaan lahayn tixraac, laakiin waxaan u baahan doonaa tallaabada xigta.

Marka xigta, waxaan soo celineynaa ballanqaad xaliya marka xaaladda fariintu ay isbedeleyso. Xaaladda waxaa lagu kormeeraa falcelin, taas oo "is dili doonta" marka xaaladdu isbedesho.

Habka code approve и reject aad u fudud: waxaan si fudud u bedelnaa heerka fariinta, ka dib markii ay saxiixday haddii loo baahdo.

Waxaan dhignay Oggolaanshaha oo diidnay UI API, Fariinta cusub ee bogga 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)
        }
    }

    ...
}

Hadda aan isku dayno inaan ku saxiixno macaamilka kordhinta:

Qorista kordhinta biraawsarkaaga badbaadsan

Guud ahaan, wax walba waa diyaar, waxa hadhay oo dhan waa ku dar UI fudud.

UI

Interface-ku wuxuu u baahan yahay marin u hel xaaladda codsiga. Dhinaca UI waanu samayn doonaa observable sheeg oo ku dar hawl qabad API-ga wax ka bedeli doona gobolkan. Aan ku daro observable ilaa shayga API ee asalka laga helay:

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

Dhammaadka waxaan bilaabeynaa sameynta interface interface. Kani waa codsi falcelin ah. Shayga asalka ah waxaa si fudud loo gudbiyaa iyadoo la isticmaalayo qalab. Waxay noqon doontaa sax, dabcan, in la sameeyo adeeg gaar ah hababka iyo dukaanka gobolka, laakiin ujeeddooyinka qodobkan tani waa ku filan:

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

Mobx aad ayay u fududahay in la bilaabo samaynta marka xogtu isbedesho. Waxaan si fudud uga soo laadlaadsannaa qurxiyaha kormeeraha xirmada mobx-ka falcelin Qaybta, iyo soo-bandhigidda si toos ah ayaa loogu yeedhi doonaa marka wax kasta oo la arki karo oo ay tixraacayso qaybtu isbeddelayso. Uma baahnid wax mapStateToProps ama uma baahnid sida redux. Wax walba waxay si toos ah uga shaqeeyaan sanduuqa:

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

Qaybaha haray waxaa lagu arki karaa koodka gudaha gal UI.

Hadda fasalka codsiga waxaad u baahan tahay inaad u samayso gobolka doorashada UI oo ogeysii UI marka uu isbeddelo. Si tan loo sameeyo, aan ku darno hab getState и reactionwacaya 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())

        })
    }

    ...
}

Marka la helo shay remote waa la abuuray reaction si loo beddelo gobolka u wac shaqada dhinaca UI.

Taabashada kama dambaysta ahi waa in lagu daro muujinta fariimaha cusub ee sumadda kordhinta:

function setupApp() {
...

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

...
}

Markaa, codsigu waa diyaar. Bogagga shabakadu waxay codsan karaan saxeex wax kala iibsiga:

Qorista kordhinta biraawsarkaaga badbaadsan

Qorista kordhinta biraawsarkaaga badbaadsan

Koodhka ayaa laga heli karaa halkan link.

gunaanad

Haddii aad akhriday maqaalka ilaa dhammaadka, laakiin aad weli hayso su'aalo, waxaad ku weydiin kartaa iyaga bakhaarrada leh kordhinta. Halkaa waxa kale oo aad ka heli doontaa ballanqaadyo tallaabo kasta oo la qoondeeyey.

Oo haddii aad xiisaynayso inaad eegto koodhka kordhinta dhabta ah, waxaad ka heli kartaa tan halkan.

Koodhka, kaydka iyo sharaxaada shaqada ee ka siemarell

Source: www.habr.com

Add a comment