Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ko dabi faaji “olupin-alabara” ti o wọpọ, awọn ohun elo ti a ti pin ni ijuwe nipasẹ:

  • Ko si iwulo lati tọju data data pẹlu awọn iwọle olumulo ati awọn ọrọ igbaniwọle. Alaye wiwọle ti wa ni ipamọ ni iyasọtọ nipasẹ awọn olumulo funrara wọn, ati ijẹrisi ti ododo wọn waye ni ipele ilana.
  • Ko si ye lati lo olupin kan. Imọye ohun elo le ṣee ṣe lori nẹtiwọọki blockchain, nibiti o ti ṣee ṣe lati tọju iye data ti o nilo.

Awọn ibi ipamọ ailewu 2 wa fun awọn bọtini olumulo - awọn apamọwọ hardware ati awọn amugbooro ẹrọ aṣawakiri. Awọn apamọwọ ohun elo jẹ aabo pupọ julọ, ṣugbọn o nira lati lo ati jinna si ọfẹ, ṣugbọn awọn amugbooro ẹrọ aṣawakiri jẹ apapo pipe ti aabo ati irọrun ti lilo, ati pe o tun le jẹ ọfẹ patapata fun awọn olumulo ipari.

Gbigba gbogbo eyi sinu akọọlẹ, a fẹ lati ṣe itẹsiwaju ti o ni aabo julọ ti o jẹ ki o rọrun idagbasoke awọn ohun elo isọdọtun nipa fifun API ti o rọrun fun ṣiṣẹ pẹlu awọn iṣowo ati awọn ibuwọlu.
A yoo sọ fun ọ nipa iriri yii ni isalẹ.

Nkan naa yoo ni awọn ilana igbesẹ-nipasẹ-igbesẹ lori bii o ṣe le kọ itẹsiwaju ẹrọ aṣawakiri kan, pẹlu awọn apẹẹrẹ koodu ati awọn sikirinisoti. O le wa gbogbo koodu inu awọn ibi ipamọ. Iṣe kọọkan ni ọgbọn ṣe ibamu si apakan kan ti nkan yii.

Itan kukuru ti Awọn amugbooro ẹrọ aṣawakiri

Awọn amugbooro ẹrọ aṣawakiri ti wa ni ayika fun igba pipẹ. Wọn farahan ni Internet Explorer pada ni ọdun 1999, ni Firefox ni ọdun 2004. Sibẹsibẹ, fun igba pipẹ ko si boṣewa kan fun awọn amugbooro.

A le sọ pe o han pẹlu awọn amugbooro ni ẹya kẹrin ti Google Chrome. Nitoribẹẹ, ko si sipesifikesonu lẹhinna, ṣugbọn o jẹ Chrome API ti o di ipilẹ rẹ: ti ṣẹgun pupọ julọ ọja aṣawakiri ati nini ile itaja ohun elo ti a ṣe sinu rẹ, Chrome gangan ṣeto boṣewa fun awọn amugbooro aṣawakiri.

Mozilla ni boṣewa tirẹ, ṣugbọn ni wiwo olokiki ti awọn amugbooro Chrome, ile-iṣẹ pinnu lati ṣe API ibaramu. Ni 2015, ni ipilẹṣẹ ti Mozilla, ẹgbẹ pataki kan ni a ṣẹda laarin World Wide Web Consortium (W3C) lati ṣiṣẹ lori awọn alaye itẹsiwaju lilọ kiri ayelujara.

Awọn amugbooro API ti o wa tẹlẹ fun Chrome ni a mu gẹgẹbi ipilẹ. Iṣẹ naa ni a ṣe pẹlu atilẹyin Microsoft (Google kọ lati kopa ninu idagbasoke ti boṣewa), ati bi abajade ti iwe-ipamọ kan han. ni pato.

Ni deede, sipesifikesonu jẹ atilẹyin nipasẹ Edge, Firefox ati Opera (akiyesi pe Chrome ko si lori atokọ yii). Ṣugbọn ni otitọ, boṣewa jẹ ibaramu pupọ pẹlu Chrome, nitori pe o ti kọ ni otitọ da lori awọn amugbooro rẹ. O le ka diẹ sii nipa WebExtensions API nibi.

Ilana itẹsiwaju

Faili nikan ti o nilo fun itẹsiwaju ni ifihan (manifest.json). O tun jẹ "ojuami titẹsi" si imugboroosi.

Fihan

Gẹgẹbi alaye sipesifikesonu, faili ifihan jẹ faili JSON to wulo. Apejuwe kikun ti awọn bọtini ifihan pẹlu alaye nipa iru awọn bọtini ti ṣe atilẹyin ninu eyiti ẹrọ aṣawakiri le wo nibi.

Awọn bọtini ti ko si ni sipesifikesonu “le” ni aibikita (mejeeji Chrome ati awọn aṣiṣe ijabọ Firefox, ṣugbọn awọn amugbooro naa tẹsiwaju lati ṣiṣẹ).

Ati pe Emi yoo fẹ lati fa ifojusi si awọn aaye kan.

  1. lẹhin - ohun kan ti o pẹlu awọn aaye wọnyi:
    1. awọn iwe afọwọkọ - akojọpọ awọn iwe afọwọkọ ti yoo ṣe ni ipo abẹlẹ (a yoo sọrọ nipa eyi diẹ diẹ);
    2. Page - dipo awọn iwe afọwọkọ ti yoo ṣe ni oju-iwe ti o ṣofo, o le pato html pẹlu akoonu. Ni idi eyi, aaye iwe afọwọkọ naa yoo kọju, ati awọn iwe afọwọkọ yoo nilo lati fi sii sinu oju-iwe akoonu;
    3. persist - asia alakomeji, ti ko ba ṣe pato, ẹrọ aṣawakiri yoo “pa” ilana isale nigbati o ba ka pe ko ṣe ohunkohun, ati tun bẹrẹ ti o ba jẹ dandan. Bibẹẹkọ, oju-iwe naa yoo jẹ ṣiṣi silẹ nikan nigbati ẹrọ aṣawakiri ti wa ni pipade. Ko ṣe atilẹyin ni Firefox.
  2. akoonu_scripts - akojọpọ awọn nkan ti o fun ọ laaye lati ṣajọpọ awọn iwe afọwọkọ oriṣiriṣi si awọn oju-iwe wẹẹbu oriṣiriṣi. Ohun kọọkan ni awọn aaye pataki wọnyi:
    1. -kere - url apẹrẹ, eyiti o pinnu boya iwe afọwọkọ akoonu kan yoo wa tabi rara.
    2. js - atokọ ti awọn iwe afọwọkọ ti yoo kojọpọ sinu ibaamu yii;
    3. exclude_matches - excludes lati awọn aaye match Awọn URL ti o baamu aaye yii.
  3. oju-iwe_igbese - kosi ohun ti o jẹ lodidi fun aami ti o ti wa ni han tókàn si awọn adirẹsi igi ninu awọn kiri ayelujara ati ibaraenisepo pẹlu ti o. O tun gba ọ laaye lati ṣe afihan window igarun kan, eyiti o jẹ asọye nipa lilo HTML tirẹ, CSS ati JS.
    1. default_popup - ọna si faili HTML pẹlu wiwo agbejade, le ni CSS ati JS ninu.
  4. awọn igbanilaaye - akojọpọ fun iṣakoso awọn ẹtọ itẹsiwaju. Awọn oriṣi awọn ẹtọ 3 wa, eyiti a ṣe apejuwe ni awọn alaye nibi
  5. web_accessible_resources - awọn orisun itẹsiwaju ti oju-iwe wẹẹbu le beere, fun apẹẹrẹ, awọn aworan, JS, CSS, awọn faili HTML.
  6. externally_connectable - nibi o le ṣe afihan awọn ID ti awọn amugbooro miiran ati awọn ibugbe ti awọn oju-iwe wẹẹbu lati eyiti o le sopọ. Agbegbe le jẹ ipele keji tabi ga julọ. Ko ṣiṣẹ ni Firefox.

Ilana ipaniyan

Ifaagun naa ni awọn ipo ipaniyan koodu mẹta, iyẹn ni, ohun elo naa ni awọn ẹya mẹta pẹlu awọn ipele oriṣiriṣi ti iraye si API aṣawakiri.

Atokun itẹsiwaju

Pupọ julọ API wa nibi. Ni aaye yii wọn “gbe”:

  1. Oju-iwe abẹlẹ - "backend" apakan ti awọn itẹsiwaju. Faili naa ti wa ni pato ninu iṣafihan nipa lilo bọtini “lẹhin”.
  2. Oju-iwe agbejade - oju-iwe agbejade ti yoo han nigbati o tẹ aami itẹsiwaju. Ninu iwe ifihan browser_action -> default_popup.
  3. Oju-iwe aṣa - oju-iwe itẹsiwaju, “ngbe” ni taabu ọtọtọ ti wiwo chrome-extension://<id_расширения>/customPage.html.

Ọgangan yii wa ni ominira ti awọn ferese aṣawakiri ati awọn taabu. Oju-iwe abẹlẹ wa ninu ẹda ẹyọkan ati nigbagbogbo ṣiṣẹ (iyasoto jẹ oju-iwe iṣẹlẹ, nigbati iwe afọwọkọ abẹlẹ ti ṣe ifilọlẹ nipasẹ iṣẹlẹ ati “ku” lẹhin ipaniyan rẹ). Oju-iwe agbejade wa nigbati awọn popup window wa ni sisi, ati Oju-iwe aṣa - lakoko ti taabu pẹlu rẹ ṣii. Ko si iraye si awọn taabu miiran ati awọn akoonu wọn lati inu ọrọ-ọrọ yii.

Akosile akoonu

Faili iwe afọwọkọ akoonu ti ṣe ifilọlẹ pẹlu taabu aṣawakiri kọọkan. O ni iwọle si apakan ti API itẹsiwaju ati si igi DOM ti oju-iwe wẹẹbu naa. O jẹ awọn iwe afọwọkọ akoonu ti o ni iduro fun ibaraenisepo pẹlu oju-iwe naa. Awọn amugbooro ti o ṣe afọwọyi igi DOM ṣe eyi ni awọn iwe afọwọkọ akoonu - fun apẹẹrẹ, awọn olutọpa ipolowo tabi awọn onitumọ. Paapaa, iwe afọwọkọ akoonu le ṣe ibasọrọ pẹlu oju-iwe nipasẹ boṣewa postMessage.

Itumọ oju-iwe wẹẹbu

Eyi ni oju-iwe wẹẹbu gangan funrararẹ. Ko ni nkankan lati ṣe pẹlu itẹsiwaju ati pe ko ni iwọle sibẹ, ayafi ni awọn ọran nibiti aaye ti oju-iwe yii ko ṣe afihan ni gbangba ninu ifihan (diẹ sii lori eyi ni isalẹ).

Iyipada paṣipaarọ

Awọn ẹya oriṣiriṣi ti ohun elo gbọdọ ṣe paṣipaarọ awọn ifiranṣẹ pẹlu ara wọn. API wa fun eyi runtime.sendMessage lati fi ifiranṣẹ ranṣẹ background и tabs.sendMessage lati fi ifiranṣẹ ranṣẹ si oju-iwe kan (akosile akoonu, agbejade tabi oju-iwe wẹẹbu ti o ba wa externally_connectable). Ni isalẹ jẹ apẹẹrẹ nigba wiwo 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))
    }
)

Fun ibaraẹnisọrọ ni kikun, o le ṣẹda awọn asopọ nipasẹ runtime.connect. Ni idahun a yoo gba runtime.Port, si eyiti, lakoko ti o ṣii, o le fi nọmba awọn ifiranṣẹ ranṣẹ. Ni ẹgbẹ alabara, fun apẹẹrẹ, contentscript, o dabi eleyi:

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

Olupin tabi abẹlẹ:

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

Iṣẹlẹ tun wa onDisconnect ati ọna disconnect.

Aworan ohun elo

Jẹ ki a ṣe itẹsiwaju ẹrọ aṣawakiri kan ti o tọju awọn bọtini ikọkọ, pese iraye si alaye ti gbogbo eniyan (adirẹsi, bọtini gbogbogbo n ba oju-iwe sọrọ ati gba awọn ohun elo ẹnikẹta laaye lati beere ibuwọlu fun awọn iṣowo.

Ohun elo idagbasoke

Ohun elo wa gbọdọ mejeeji ṣe ajọṣepọ pẹlu olumulo ati pese oju-iwe pẹlu API lati pe awọn ọna (fun apẹẹrẹ, lati fowo si awọn iṣowo). Ṣe pẹlu ọkan kan contentscript kii yoo ṣiṣẹ, nitori pe o ni iwọle si DOM nikan, ṣugbọn kii ṣe si JS ti oju-iwe naa. Sopọ nipasẹ runtime.connect a ko le, nitori awọn API ti wa ni ti nilo lori gbogbo awọn ibugbe, ati awọn nikan ni pato le wa ni pato ninu awọn farahan. Bi abajade, aworan naa yoo dabi eyi:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Iwe afọwọkọ miiran yoo wa - inpage, eyi ti a yoo fi sinu oju-iwe naa. Yoo ṣiṣẹ ni agbegbe rẹ ati pese API kan fun ṣiṣẹ pẹlu itẹsiwaju naa.

Начало

Gbogbo koodu itẹsiwaju ẹrọ aṣawakiri wa ni GitHub. Lakoko apejuwe awọn ọna asopọ yoo wa lati ṣe.

Jẹ ká bẹrẹ pẹlu awọn 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"]
}

Ṣẹda ṣofo background.js, popup.js, inpage.js ati contentscript.js. A ṣafikun popup.html - ati pe ohun elo wa le ti kojọpọ tẹlẹ sinu Google Chrome ati rii daju pe o ṣiṣẹ.

Lati mọ daju eyi, o le gba koodu naa lati ibi. Ni afikun si ohun ti a ṣe, ọna asopọ tunto apejọ ti iṣẹ akanṣe nipa lilo webpack. Lati ṣafikun ohun elo kan si ẹrọ aṣawakiri, ni chrome: // awọn amugbooro o nilo lati yan fifuye ti ko papọ ati folda pẹlu ifaagun ti o baamu - ninu ọran wa dist.

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Bayi itẹsiwaju wa ti fi sori ẹrọ ati ṣiṣẹ. O le ṣiṣe awọn irinṣẹ idagbasoke fun awọn ipo oriṣiriṣi bi atẹle:

agbejade ->

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Wiwọle si console iwe afọwọkọ akoonu ni a ṣe nipasẹ console ti oju-iwe funrararẹ lori eyiti o ṣe ifilọlẹ.Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Iyipada paṣipaarọ

Nitorinaa, a nilo lati ṣeto awọn ikanni ibaraẹnisọrọ meji: inpage <-> abẹlẹ ati agbejade <-> abẹlẹ. O le, nitorinaa, o kan fi awọn ifiranṣẹ ranṣẹ si ibudo naa ki o ṣẹda ilana tirẹ, ṣugbọn Mo fẹran ọna ti Mo rii ninu iṣẹ-ṣiṣe orisun ṣiṣi metamask.

Eyi jẹ itẹsiwaju aṣawakiri fun ṣiṣẹ pẹlu nẹtiwọọki Ethereum. Ninu rẹ, awọn ẹya oriṣiriṣi ti ohun elo ṣe ibasọrọ nipasẹ RPC ni lilo ile-ikawe dnode. O gba ọ laaye lati ṣeto paṣipaarọ ni iyara ati irọrun ti o ba pese pẹlu ṣiṣan nodejs bi gbigbe (itumọ ohun ti o ṣe imuse wiwo kanna):

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

Bayi a yoo ṣẹda kilasi ohun elo. Yoo ṣẹda awọn ohun API fun igarun ati oju-iwe wẹẹbu, ati ṣẹda dnode fun wọn:

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

Nibi ati ni isalẹ, dipo ohun Chrome agbaye, a lo extensionApi, eyiti o wọle si Chrome ni ẹrọ aṣawakiri Google ati aṣawakiri ninu awọn miiran. Eyi ni a ṣe fun ibaramu aṣawakiri-kiri, ṣugbọn fun awọn idi ti nkan yii ọkan le rọrun lo 'chrome.runtime.connect'.

Jẹ ki a ṣẹda apẹẹrẹ ohun elo ni iwe afọwọkọ abẹlẹ:

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

Niwọn bi dnode ti n ṣiṣẹ pẹlu awọn ṣiṣan, ati pe a gba ibudo kan, a nilo kilasi ohun ti nmu badọgba. O ṣe ni lilo ile-ikawe ṣiṣan ti o ṣee ka, eyiti o ṣe imuse awọn ṣiṣan nodejs ninu ẹrọ aṣawakiri:

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

Bayi jẹ ki a ṣẹda asopọ kan ninu 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;
    }
}

Lẹhinna a ṣẹda asopọ ni iwe afọwọkọ akoonu:

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

Niwọn igba ti a nilo API kii ṣe ninu iwe afọwọkọ akoonu, ṣugbọn taara lori oju-iwe, a ṣe awọn nkan meji:

  1. A ṣẹda awọn ṣiṣan meji. Ọkan - si ọna oju-iwe, lori oke ti ifiweranṣẹ naa. Fun eyi a lo eyi yi package lati awọn ẹlẹda ti metamask. Awọn keji san ni lati abẹlẹ lori ibudo gba lati runtime.connect. Jẹ ki a ra wọn. Bayi oju-iwe naa yoo ni ṣiṣan si abẹlẹ.
  2. Fi iwe afọwọkọ sinu DOM. Ṣe igbasilẹ iwe afọwọkọ naa (iwọle si o ti gba laaye ninu ifihan) ati ṣẹda tag kan script pẹlu awọn akoonu inu rẹ:

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

Bayi a ṣẹda ohun api ni inpage a si ṣeto si agbaye:

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

A ti ṣetan Ipe Ilana Latọna jijin (RPC) pẹlu API ọtọtọ fun oju-iwe ati UI. Nigbati o ba n so oju-iwe tuntun pọ si abẹlẹ a le rii eyi:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ofo API ati Oti. Ni ẹgbẹ oju-iwe, a le pe iṣẹ hello bi eleyi:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ṣiṣẹ pẹlu awọn iṣẹ ipe pada ni JS ode oni jẹ awọn iwa buburu, nitorinaa jẹ ki a kọ oluranlọwọ kekere kan lati ṣẹda dnode kan ti o fun ọ laaye lati kọja ohun API kan si awọn ohun elo.

Awọn ohun API yoo dabi eyi:

export class SignerApp {

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

...

}

Gbigba ohun kan lati ọna jijin bii eyi:

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

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

Ati awọn iṣẹ pipe da ileri kan pada:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ẹya pẹlu awọn iṣẹ asynchronous ti o wa nibi.

Lapapọ, ọna RPC ati ṣiṣan dabi ẹni ti o rọ: a le lo isọpọ nya si ati ṣẹda ọpọlọpọ awọn API oriṣiriṣi fun awọn iṣẹ ṣiṣe oriṣiriṣi. Ni opo, dnode le ṣee lo nibikibi, ohun akọkọ ni lati fi ipari si gbigbe ni irisi ṣiṣan nodejs.

Omiiran ni ọna kika JSON, eyiti o ṣe imuse ilana JSON RPC 2. Sibẹsibẹ, o ṣiṣẹ pẹlu awọn gbigbe kan pato (TCP ati HTTP (S)), eyiti ko wulo ninu ọran wa.

Ipinlẹ inu ati Ibi ipamọ agbegbe

A yoo nilo lati tọju ipo inu ti ohun elo naa - o kere ju awọn bọtini iforukọsilẹ. A le ni irọrun ṣafikun ipinlẹ kan si ohun elo ati awọn ọna fun yiyipada rẹ ni API agbejade:

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

    ...

} 

Ni abẹlẹ, a yoo fi ipari si ohun gbogbo ni iṣẹ kan ki o kọ ohun elo ohun elo si window ki a le ṣiṣẹ pẹlu rẹ lati console:

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

Jẹ ki a ṣafikun awọn bọtini diẹ lati console UI ki o wo kini o ṣẹlẹ pẹlu ipinlẹ naa:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ipinle nilo lati jẹ ki o tẹsiwaju ki awọn bọtini ko padanu nigbati o tun bẹrẹ.

A yoo tọju rẹ ni Ibi ipamọ agbegbe, ni atunkọ pẹlu gbogbo iyipada. Lẹhinna, iraye si yoo tun jẹ pataki fun UI, ati pe Emi yoo tun fẹ lati ṣe alabapin si awọn ayipada. Da lori eyi, yoo rọrun lati ṣẹda ibi ipamọ akiyesi ati ṣe alabapin si awọn ayipada rẹ.

A yoo lo ile-ikawe mobx (https://github.com/mobxjs/mobx). Yiyan naa ṣubu lori rẹ nitori Emi ko ni lati ṣiṣẹ pẹlu rẹ, ṣugbọn Mo fẹ gaan lati kawe rẹ.

Jẹ ki a ṣafikun ibẹrẹ ti ipo ibẹrẹ ki o jẹ ki ile itaja jẹ akiyesi:

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

    ...

}

"Labẹ Hood," mobx ti rọpo gbogbo awọn aaye itaja pẹlu aṣoju ati idilọwọ gbogbo awọn ipe si wọn. Yoo ṣee ṣe lati ṣe alabapin si awọn ifiranṣẹ wọnyi.

Ni isalẹ Emi yoo nigbagbogbo lo ọrọ naa “nigbati o yipada”, botilẹjẹpe eyi ko pe ni pipe. Mobx tọpa wiwọle si awọn aaye. Getters ati awọn oluṣeto awọn nkan aṣoju ti ile-ikawe ṣẹda ni a lo.

Awọn oluṣọṣọ iṣe ṣe awọn idi meji:

  1. Ni ipo ti o muna pẹlu asia enforceActions, mobx ṣe idiwọ iyipada ipinle taara. O jẹ iṣe ti o dara lati ṣiṣẹ labẹ awọn ipo ti o muna.
  2. Paapaa ti iṣẹ kan ba yipada ipinle ni igba pupọ - fun apẹẹrẹ, a yipada awọn aaye pupọ ni awọn ila pupọ ti koodu - awọn alafojusi ti wa ni iwifunni nikan nigbati o ba pari. Eyi ṣe pataki ni pataki fun iwaju iwaju, nibiti awọn imudojuiwọn ipinlẹ ti ko wulo ja si jigbe awọn eroja ti ko wulo. Ninu ọran wa, bẹni akọkọ tabi keji jẹ pataki pataki, ṣugbọn a yoo tẹle awọn iṣe ti o dara julọ. O jẹ aṣa lati so awọn ohun ọṣọ si gbogbo awọn iṣẹ ti o yi ipo ti awọn aaye ti a ṣe akiyesi pada.

Ni abẹlẹ a yoo ṣafikun ipilẹṣẹ ati fifipamọ ipinlẹ ni Ibi ipamọ agbegbe:

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

Awọn lenu iṣẹ jẹ awon nibi. O ni awọn ariyanjiyan meji:

  1. Aṣayan data.
  2. Olutọju ti yoo pe pẹlu data yii ni gbogbo igba ti o yipada.

Ko redux, ibi ti a kedere gba ipinle bi ohun ariyanjiyan, ranti mobx eyi ti observables a wọle si inu awọn selector, ati ki o nikan ipe olutọju nigba ti won yi.

O ṣe pataki lati ni oye gangan bi mobx ṣe pinnu iru awọn akiyesi ti a ṣe alabapin si. Ti MO ba ko oluyan ninu koodu bii eyi() => app.store, lẹhinna a ko ni pe aiṣedeede rara, nitori ibi ipamọ funrararẹ ko ṣe akiyesi, awọn aaye rẹ nikan ni o wa.

Ti mo ba ko o bi yi () => app.store.keys, lẹhinna ko si ohun ti yoo ṣẹlẹ lẹẹkansi, niwon nigba fifi / yiyọ awọn eroja orun kuro, itọkasi rẹ kii yoo yipada.

Mobx ṣe bi yiyan fun igba akọkọ ati pe o tọju abala awọn ohun akiyesi ti a wọle nikan. Eyi ni a ṣe nipasẹ awọn getters aṣoju. Nitorinaa, iṣẹ ti a ṣe sinu rẹ ni a lo nibi toJS. O da ohun titun pada pẹlu gbogbo awọn aṣoju rọpo pẹlu awọn aaye atilẹba. Lakoko ipaniyan, o ka gbogbo awọn aaye ti nkan naa - nitorinaa awọn getters ti nfa.

Ninu console agbejade a yoo tun ṣafikun ọpọlọpọ awọn bọtini. Ni akoko yii wọn tun pari ni Ibi ipamọ agbegbe:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Nigbati oju-iwe abẹlẹ ba tun gbejade, alaye naa wa ni aye.

Gbogbo koodu ohun elo titi di aaye yii ni a le wo nibi.

Ibi ipamọ aabo ti awọn bọtini ikọkọ

Titoju awọn bọtini ikọkọ ni ọrọ mimọ ko lewu: aye nigbagbogbo wa pe iwọ yoo gepa, ni iraye si kọnputa rẹ, ati bẹbẹ lọ. Nitorinaa, ni Ibi ipamọ agbegbe a yoo tọju awọn bọtini ni fọọmu ti paroko ọrọ igbaniwọle.

Fun aabo nla, a yoo ṣafikun ipo titiipa si ohun elo, ninu eyiti kii yoo ni iraye si awọn bọtini rara rara. A yoo gbe itẹsiwaju laifọwọyi si ipo titiipa nitori akoko ipari kan.

Mobx faye gba o lati fipamọ nikan kan kere ṣeto ti data, ati awọn iyokù ti wa ni laifọwọyi iṣiro da lori o. Iwọnyi jẹ awọn ohun-ini ti a pe ni iṣiro. Wọn le ṣe afiwe si awọn iwo ni awọn ibi ipamọ data:

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

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

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

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

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

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

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

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

Bayi a tọju awọn bọtini ti paroko ati ọrọ igbaniwọle nikan. Gbogbo nkan miiran ni iṣiro. A ṣe gbigbe si ipo titiipa nipa yiyọ ọrọ igbaniwọle kuro ni ipinlẹ naa. API ti gbogbo eniyan ni bayi ni ọna kan fun pilẹṣẹ ibi ipamọ naa.

Kọ fun ìsekóòdù awọn ohun elo lilo 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)
}

Ẹrọ aṣawakiri naa ni API ti ko ṣiṣẹ nipasẹ eyiti o le ṣe alabapin si iṣẹlẹ kan - awọn ayipada ipinlẹ. Ipinle, gẹgẹbi, le jẹ idle, active и locked. Fun laišišẹ o le ṣeto akoko ipari, ati titiipa ti ṣeto nigbati OS funrararẹ ti dinamọ. A yoo tun yi oluyan pada fun fifipamọ si Ibi ipamọ agbegbe:

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

Awọn koodu ṣaaju ki o to yi igbese ni nibi.

Awọn iṣowo

Nitorina, a wa si ohun pataki julọ: ṣiṣẹda ati wíwọlé awọn iṣowo lori blockchain. A yoo lo WAVES blockchain ati ile-ikawe igbi-lẹkọ.

Ni akọkọ, jẹ ki a ṣafikun si ipinlẹ ọpọlọpọ awọn ifiranṣẹ ti o nilo lati fowo si, lẹhinna ṣafikun awọn ọna fun fifi ifiranṣẹ tuntun kun, ifẹsẹmulẹ ibuwọlu, ati kiko:

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

    ...
}

Nigba ti a ba gba ifiranṣẹ titun kan, a fi metadata kun si, ṣe observable ki o si fi si store.messages.

Ti o ko ba ṣe bẹ observable pẹlu ọwọ, lẹhinna mobx yoo ṣe funrararẹ nigba fifi awọn ifiranṣẹ kun si orun. Sibẹsibẹ, yoo ṣẹda ohun titun si eyiti a ko ni ni itọkasi, ṣugbọn a yoo nilo rẹ fun igbesẹ ti n tẹle.

Nigbamii, a pada ileri ti o yanju nigbati ipo ifiranṣẹ ba yipada. Ipo naa ni abojuto nipasẹ iṣesi, eyiti yoo “pa ararẹ” nigbati ipo ba yipada.

Ilana koodu approve и reject irorun: a nìkan yi awọn ipo ti awọn ifiranṣẹ, lẹhin wíwọlé o ti o ba wulo.

A fi Ifọwọsi ati kọ sinu UI API, Ifọrọranṣẹ titun ni oju-iwe 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)
        }
    }

    ...
}

Bayi jẹ ki a gbiyanju lati fowo si idunadura pẹlu itẹsiwaju:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Ni gbogbogbo, ohun gbogbo ti šetan, gbogbo awọn ti o ku ni fi o rọrun UI.

UI

Ni wiwo nilo iraye si ipo ohun elo. Ni ẹgbẹ UI a yoo ṣe observable sọ ki o ṣafikun iṣẹ kan si API ti yoo yi ipo yii pada. Jẹ ki a fi kun observable si ohun API ti a gba lati abẹlẹ:

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

Ni ipari a bẹrẹ ṣiṣe ni wiwo ohun elo. Eyi jẹ ohun elo idahun. Ohun abẹlẹ ti kọja ni lilo awọn atilẹyin. Yoo jẹ deede, nitorinaa, lati ṣe iṣẹ lọtọ fun awọn ọna ati ile itaja fun ipinlẹ, ṣugbọn fun awọn idi ti nkan yii eyi ti to:

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

Pẹlu mobx o rọrun pupọ lati bẹrẹ ṣiṣe nigbati data ba yipada. A nìkan so ohun ọṣọ Oluwoye lati package mobx-fesi lori paati, ati ki o mu yoo wa ni a npe ni laifọwọyi nigbati eyikeyi observables tọka nipa paati ayipada. O ko nilo eyikeyi maapuStateToProps tabi sopọ bi ni redux. Ohun gbogbo ṣiṣẹ taara lati inu apoti:

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

Awọn paati ti o ku ni a le wo ni koodu naa ninu folda UI.

Ni bayi ninu kilasi ohun elo o nilo lati ṣe yiyan ipinlẹ fun UI ki o fi to UI leti nigbati o ba yipada. Lati ṣe eyi, jẹ ki a fi ọna kan kun getState и reactionpípè 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())

        })
    }

    ...
}

Nigba gbigba ohun kan remote ṣẹda reaction lati yi ipo ti o pe iṣẹ ni ẹgbẹ UI pada.

Ifọwọkan ikẹhin ni lati ṣafikun ifihan awọn ifiranṣẹ tuntun lori aami itẹsiwaju:

function setupApp() {
...

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

...
}

Nitorinaa, ohun elo naa ti ṣetan. Awọn oju-iwe wẹẹbu le beere ibuwọlu fun awọn iṣowo:

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Kikọ itẹsiwaju ẹrọ aṣawakiri to ni aabo

Awọn koodu wa nibi ọna asopọ.

ipari

Ti o ba ti ka nkan naa titi de opin, ṣugbọn tun ni awọn ibeere, o le beere lọwọ wọn ni awọn ibi ipamọ pẹlu itẹsiwaju. Nibẹ ni iwọ yoo tun rii awọn adehun fun igbesẹ kọọkan ti a yan.

Ati pe ti o ba nifẹ lati wo koodu fun itẹsiwaju gangan, o le rii eyi nibi.

Koodu, ibi ipamọ ati apejuwe iṣẹ lati siemarell

orisun: www.habr.com

Fi ọrọìwòye kun