A’ sgrìobhadh leudachadh brabhsair tèarainte

A’ sgrìobhadh leudachadh brabhsair tèarainte

Eu-coltach ris an ailtireachd cumanta “frithealaiche teachdaiche”, tha tagraidhean dì-mheadhanaichte air an comharrachadh le:

  • Chan eil feum air stòr-dàta a stòradh le logaichean luchd-cleachdaidh agus faclan-faire. Tha fiosrachadh ruigsinneachd air a stòradh leis an luchd-cleachdaidh fhèin a-mhàin, agus tha dearbhadh air an dearbhadh a’ tachairt aig ìre a’ phròtacal.
  • Chan eil feum air frithealaiche a chleachdadh. Faodar loidsig an tagraidh a chuir gu bàs air lìonra blockchain, far a bheil e comasach an ìre de dhàta a tha a dhìth a stòradh.

Tha 2 stòradh an ìre mhath sàbhailte airson iuchraichean luchd-cleachdaidh - wallets bathar-cruaidh agus leudachadh brabhsair. Tha wallets bathar-cruaidh sa mhòr-chuid air leth tèarainte, ach duilich an cleachdadh agus fada bho bhith saor, ach tha leudachadh brobhsair na mheasgachadh foirfe de thèarainteachd agus furasta an cleachdadh, agus faodaidh iad a bhith gu tur an-asgaidh dha luchd-cleachdaidh deireannach.

Le bhith a’ toirt aire don a h-uile càil seo, bha sinn airson an leudachadh as tèarainte a dhèanamh a nì sìmplidh air leasachadh thagraidhean dì-mheadhanaichte le bhith a’ toirt seachad API sìmplidh airson obrachadh le gnothaichean agus ainmean-sgrìobhte.
Innsidh sinn dhut mun eòlas seo gu h-ìosal.

Bidh stiùireadh ceum air cheum san artaigil air mar a sgrìobhas tu leudachadh brabhsair, le eisimpleirean còd agus seallaidhean-sgrìn. Gheibh thu a h-uile còd a-steach tasgaidh. Tha gach gealladh gu loidsigeach a’ freagairt ri earrann den artaigil seo.

Eachdraidh ghoirid air leudachadh brabhsair

Tha leudachaidhean brabhsair air a bhith timcheall airson ùine mhòr. Nochd iad ann an Internet Explorer air ais ann an 1999, ann am Firefox ann an 2004. Ach, airson ùine mhòr cha robh aon ìre ann airson leudachadh.

Faodaidh sinn a ràdh gun do nochd e còmhla ri leudachadh anns a’ cheathramh dreach de Google Chrome. Gu dearbh, cha robh sònrachadh sam bith ann an uairsin, ach b ’e an Chrome API a thàinig gu bhith na bhunait: an dèidh dha a’ mhòr-chuid de mhargaidh a ’bhrobhsair a cheannsachadh agus stòr tagraidh togte a bhith aige, shuidhich Chrome an inbhe airson leudachadh brabhsair.

Bha a h-inbhe fhèin aig Mozilla, ach a’ faicinn cho mòr sa bha leudachaidhean Chrome, cho-dhùin a’ chompanaidh API co-fhreagarrach a dhèanamh. Ann an 2015, le iomairt Mozilla, chaidh buidheann sònraichte a chruthachadh taobh a-staigh Co-bhanntachd an Lìon Cruinne (W3C) gus obrachadh air mion-chomharrachadh leudachaidh thar-bhrabhsair.

Chaidh na leudachain API a th’ ann mar-thà airson Chrome a ghabhail mar bhunait. Chaidh an obair a dhèanamh le taic bho Microsoft (dhiùlt Google pàirt a ghabhail ann an leasachadh na h-ìre), agus mar thoradh air sin nochd dreachd sònrachaidhean.

Gu foirmeil, tha an sònrachadh a’ faighinn taic bho Edge, Firefox agus Opera (thoir an aire nach eil Chrome air an liosta seo). Ach gu dearbh, tha an inbhe gu ìre mhòr co-chòrdail ri Chrome, leis gu bheil e sgrìobhte gu dearbh stèidhichte air na leudachain aige. Faodaidh tu barrachd a leughadh mun WebExtensions API an seo.

Structar leudachaidh

Is e an aon fhaidhle a tha a dhìth airson an leudachaidh am manifest (manifest.json). Tha e cuideachd na “phuing inntrigidh” don leudachadh.

Didòmhnaich

A rèir an t-sònrachadh, tha am faidhle follaiseach na fhaidhle JSON dligheach. Tuairisgeul slàn air iuchraichean follaiseach le fiosrachadh mu na h-iuchraichean a tha a’ faighinn taic anns am faicear am brabhsair an seo.

Faodar iuchraichean nach eil san t-sònrachadh “a leigeil seachad” (tha an dà chuid Chrome agus Firefox ag aithris mhearachdan, ach tha na leudachaidhean fhathast ag obair).

Agus bu mhath leam aire a tharraing gu cuid de phuingean.

  1. chùl - nì anns a bheil na raointean a leanas:
    1. sgriobtaichean - sreath de sgriobtaichean a thèid a chuir gu bàs sa chùl-fhiosrachadh (bruidhnidh sinn mu dheidhinn beagan nas fhaide air adhart);
    2. duilleag - an àite sgriobtaichean a thèid a chur gu bàs ann an duilleag falamh, faodaidh tu html a shònrachadh le susbaint. Anns a 'chùis seo, thèid an raon sgriobt a leigeil seachad, agus feumar na sgriobtaichean a chuir a-steach don duilleag susbaint;
    3. buanachadh - bratach binary, mura h-eil e air a shònrachadh, bidh am brabhsair “a’ marbhadh ”am pròiseas cùl-fhiosrachaidh nuair a tha e den bheachd nach eil e a’ dèanamh dad, agus ath-thòisich e ma tha sin riatanach. Rud eile, cha tèid an duilleag a luchdachadh ach nuair a bhios am brabhsair dùinte. Chan eil taic ann am Firefox.
  2. susbaint_scripts - sreath de stuthan a leigeas leat diofar sgriobtaichean a luchdachadh gu diofar dhuilleagan lìn. Tha na raointean cudromach a leanas anns gach nì:
    1. matches - url pàtran, a cho-dhùineas an tèid sgriobt susbaint sònraichte a thoirt a-steach no nach tèid.
    2. js - liosta de sgriobtaichean a thèid a luchdachadh a-steach don gheama seo;
    3. dùnadh a-mach_maidsean - air a thoirmeasg bhon raon match URLan a fhreagras ris an raon seo.
  3. duilleag_gnìomh - gu dearbh na nì a tha cunntachail airson an ìomhaigh a tha air a thaisbeanadh ri taobh a’ bhàr seòlaidh sa bhrobhsair agus ag eadar-obrachadh leis. Leigidh e leat uinneag pop-up a thaisbeanadh, a tha air a mhìneachadh leis an HTML, CSS agus JS agad fhèin.
    1. default_popup - slighe chun fhaidhle HTML leis an eadar-aghaidh popup, faodaidh CSS agus JS a bhith ann.
  4. ceadan - sreath airson còraichean leudachaidh a riaghladh. Tha 3 seòrsaichean chòraichean ann, a tha air am mìneachadh gu mionaideach an seo
  5. web_accessible_goireasan - goireasan leudachaidh a dh’ fhaodas duilleag-lìn iarraidh, mar eisimpleir, ìomhaighean, JS, CSS, faidhlichean HTML.
  6. taobh a-muigh_connectable - an seo faodaidh tu IDan leudachaidhean eile agus raointean de dhuilleagan lìn a shònrachadh gu soilleir bhon urrainn dhut ceangal a dhèanamh. Faodaidh àrainn a bhith san dàrna ìre no nas àirde. Chan eil e ag obair ann am Firefox.

Co-theacs cur gu bàs

Tha trì co-theacsan cur an gnìomh còd aig an leudachadh, is e sin, tha trì pàirtean san tagradh le diofar ìrean ruigsinneachd air API a’ bhrobhsair.

Co-theacsa leudachaidh

Tha a’ mhòr-chuid den API ri fhaighinn an seo. Anns a’ cho-theacsa seo tha iad “beò”:

  1. Duilleag cùl-fhiosrachaidh - pàirt “backend” den leudachadh. Tha am faidhle air a shònrachadh anns an fhoillseachadh a’ cleachdadh an iuchair “cùl-raon”.
  2. Duilleag pop-up - duilleag popup a nochdas nuair a phutas tu air ìomhaigh an leudachaidh. Anns a’ mhanifesto browser_action -> default_popup.
  3. duilleag àbhaisteach - duilleag leudachaidh, “beò” ann an taba air leth den t-sealladh chrome-extension://<id_расширения>/customPage.html.

Tha an co-theacsa seo ann gu neo-eisimeileach bho uinneagan brabhsair agus tabaichean. Duilleag cùl-fhiosrachaidh ann an aon leth-bhreac agus an-còmhnaidh ag obair (ach a-mhàin duilleag an tachartais, nuair a thèid an sgriobt cùl-fhiosrachaidh a chuir air bhog le tachartas agus “bàs" às deidh a chuir gu bàs). Duilleag pop-up ann nuair a tha an uinneag pop-up fosgailte, agus duilleag àbhaisteach - fhad ‘s a tha an taba leis fosgailte. Chan eil cothrom air tabaichean eile agus an susbaint bhon cho-theacsa seo.

Co-theacsa sgriobt susbaint

Thèid am faidhle sgriobt susbaint a chuir air bhog còmhla ri gach tab brabhsair. Tha cothrom aige air pàirt de API an leudachaidh agus air craobh DOM na duilleige lìn. Is e sgriobtaichean susbaint a tha an urra ri eadar-obrachadh leis an duilleag. Bidh leudachaidhean a bhios a 'làimhseachadh craobh DOM a' dèanamh seo ann an sgriobtaichean susbaint - mar eisimpleir, luchd-bacadh sanasachd no eadar-theangairean. Cuideachd, faodaidh an sgriobt susbaint conaltradh leis an duilleag tro ìre àbhaisteach postMessage.

Co-theacs duilleag-lìn

Is e seo an fhìor dhuilleag lìn fhèin. Chan eil gnothach sam bith aige ris an leudachadh agus chan eil cothrom aige an sin, ach a-mhàin ann an cùisean far nach eil àrainn na duilleige seo air a chomharrachadh gu soilleir anns an fhoillseachadh (barrachd air seo gu h-ìosal).

Iomlaid teachdaireachd

Feumaidh diofar phàirtean den tagradh teachdaireachdan iomlaid ri chèile. Tha API ann airson seo runtime.sendMessage teachdaireachd a chuir background и tabs.sendMessage gus teachdaireachd a chuir gu duilleag (sgriobt susbaint, priob-uinneag no duilleag-lìn ma tha sin ri fhaighinn externally_connectable). Gu h-ìosal tha eisimpleir nuair a gheibh thu cothrom air 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))
    }
)

Airson làn chonaltradh, faodaidh tu ceanglaichean a chruthachadh troimhe runtime.connect. Mar fhreagairt gheibh sinn runtime.Port, agus, fhad ‘s a tha e fosgailte, faodaidh tu àireamh sam bith de theachdaireachdan a chuir. Air taobh an neach-dèiligidh, mar eisimpleir, contentscript, tha e coltach ri seo:

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

Frithealaiche no cùl-fhiosrachadh:

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

Tha tachartas ann cuideachd onDisconnect agus modh disconnect.

Diagram tagraidh

Dèanamaid leudachadh brobhsair a bhios a’ stòradh iuchraichean prìobhaideach, a’ toirt cothrom air fiosrachadh poblach (seòladh, iuchair phoblach a’ conaltradh ris an duilleag agus a’ leigeil le tagraidhean treas-phàrtaidh ainm-sgrìobhte iarraidh airson gnothaichean.

Leasachadh tagradh

Feumaidh an tagradh againn eadar-obrachadh leis an neach-cleachdaidh agus API a thoirt don duilleag gus dòighean a ghairm (mar eisimpleir, gus gnothaichean a shoidhnigeadh). Dèan le dìreach aon contentscript chan obraich e, leis nach eil cothrom aige ach air an DOM, ach chan ann gu JS na duilleige. Ceangail tro runtime.connect chan urrainn dhuinn, oir tha feum air an API air a h-uile raon, agus chan urrainnear ach feadhainn sònraichte a shònrachadh anns an fhoillseachadh. Mar thoradh air an sin, seallaidh an diagram mar seo:

A’ sgrìobhadh leudachadh brabhsair tèarainte

Bidh sgriob eile ann - inpage, a chuireas sinn a-steach don duilleag. Ruithidh e na cho-theacsa agus bheir e API seachad airson a bhith ag obair leis an leudachadh.

An toiseach

Tha a h-uile còd leudachaidh brabhsair ri fhaighinn aig GitHub. Rè an tuairisgeul bidh ceanglaichean gu geallaidhean.

Feuch an tòisich sinn leis a’ mhanifesto:

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

Cruthaich cùl-fhiosrachadh falamh.js, popup.js, inpage.js agus contentscript.js. Cuiridh sinn popup.html ris - agus faodar an tagradh againn a luchdachadh a-steach do Google Chrome mu thràth agus dèan cinnteach gu bheil e ag obair.

Gus seo a dhearbhadh, faodaidh tu an còd a ghabhail bho seo. A bharrachd air na rinn sinn, shuidhich an ceangal co-chruinneachadh a’ phròiseict a’ cleachdadh webpack. Gus tagradh a chuir ris a ’bhrobhsair, ann an chrome: // leudachain feumaidh tu an luchd gun phacaid a thaghadh agus am pasgan leis an leudachadh co-fhreagarrach - anns a’ chùis againn dist.

A’ sgrìobhadh leudachadh brabhsair tèarainte

A-nis tha an leudachadh againn air a chuir a-steach agus ag obair. Faodaidh tu na h-innealan leasaiche a ruith airson diofar cho-theacsan mar a leanas:

uinneag ->

A’ sgrìobhadh leudachadh brabhsair tèarainte

Bithear a’ faighinn cothrom air consol sgriobt susbaint tro chonsail na duilleige fhèin air a bheil e air a chuir air bhog.A’ sgrìobhadh leudachadh brabhsair tèarainte

Iomlaid teachdaireachd

Mar sin, feumaidh sinn dà sheanail conaltraidh a stèidheachadh: inpage <-> cùl-fhiosrachadh agus popup <-> cùl-fhiosrachadh. Faodaidh tu, gu dearbh, dìreach teachdaireachdan a chuir chun phort agus do phròtacal fhèin a chruthachadh, ach is fheàrr leam an dòigh-obrach a chunnaic mi sa phròiseact stòr fosgailte metamask.

Is e seo leudachadh brabhsair airson a bhith ag obair le lìonra Ethereum. Ann, bidh diofar phàirtean den tagradh a’ conaltradh tro RPC a’ cleachdadh an leabharlann dnode. Leigidh e leat iomlaid a chuir air dòigh gu math luath agus goireasach ma bheir thu seachad sruth nodejs mar chòmhdhail (a’ ciallachadh nì a chuireas an aon eadar-aghaidh an gnìomh):

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

A-nis cruthaichidh sinn clas tagraidh. Cruthaichidh e stuthan API airson an duilleag pop-up agus an duilleag-lìn, agus cruthaichidh e dnode dhaibh:

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

An seo agus gu h-ìosal, an àite an nì Chrome cruinne, bidh sinn a’ cleachdadh extensionApi, a gheibh cothrom air Chrome ann am brabhsair Google agus brobhsair ann an cuid eile. Tha seo air a dhèanamh airson co-chòrdalachd thar-bhrabhsair, ach airson adhbharan an artaigil seo dh’ fhaodadh aon dìreach ‘chrome.runtime.connect’ a chleachdadh.

Cruthaichidh sinn eisimpleir tagraidh anns an sgriobt cùl-fhiosrachaidh:

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

Leis gu bheil dnode ag obair le sruthan, agus gum faigh sinn port, tha feum air clas adapter. Tha e air a dhèanamh a’ cleachdadh an leabharlann sruth-leughaidh, a bhios a’ cur an gnìomh sruthan nodejs sa bhrobhsair:

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

A-nis cruthaichidh sinn ceangal san 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;
    }
}

An uairsin cruthaichidh sinn an ceangal anns an sgriobt susbaint:

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

Leis gu bheil feum againn air an API chan ann san sgriobt susbaint, ach gu dìreach air an duilleag, bidh sinn a’ dèanamh dà rud:

  1. Bidh sinn a 'cruthachadh dà shruth. Aon - a dh'ionnsaigh na duilleige, air mullach a 'phostMessage. Airson seo bidh sinn a 'cleachdadh seo a' phacaid seo bho luchd-cruthachaidh metamask. Tha an dàrna sruth ri cùl-raon a’ phuirt a fhuaireadh bho runtime.connect. Ceannaich sinn iad. A-nis bidh sruth aig an duilleag ris a’ chùl.
  2. Cuir a-steach an sgriobt a-steach don DOM. Luchdaich sìos an sgriobt (bha cothrom air a cheadachadh san fhollaisiche) agus cruthaich tag script leis an t-susbaint a-staigh:

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

A-nis bidh sinn a’ cruthachadh nì api ann an duilleag agus ga shuidheachadh gu cruinneil:

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

Tha sinn deiseil Call Modh Iomallach (RPC) le API air leth airson duilleag agus UI. Nuair a cheanglas sinn duilleag ùr ris a’ chùl chì sinn seo:

A’ sgrìobhadh leudachadh brabhsair tèarainte

API falamh agus tùs. Air taobh na duilleige, is urrainn dhuinn an gnìomh hello a ghairm mar seo:

A’ sgrìobhadh leudachadh brabhsair tèarainte

Is e droch mhodhan a th’ ann a bhith ag obair le gnìomhan gairm air ais ann an JS an latha an-diugh, mar sin sgrìobhamaid neach-cuideachaidh beag gus dnode a chruthachadh a leigeas leat rud API a chuir gu goireasan.

Bidh na nithean API a-nis a’ coimhead mar seo:

export class SignerApp {

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

...

}

A’ faighinn rud mar seo bho iomallach:

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

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

Agus bidh gnìomhan gairm a’ tilleadh gealladh:

A’ sgrìobhadh leudachadh brabhsair tèarainte

Tionndadh le gnìomhan asyncronach ri fhaighinn an seo.

Gu h-iomlan, tha coltas gu math sùbailte air an dòigh-obrach RPC agus sruth: is urrainn dhuinn iomadachadh smùid a chleachdadh agus grunn APIan eadar-dhealaichte a chruthachadh airson diofar ghnìomhan. Ann am prionnsapal, faodar dnode a chleachdadh an àite sam bith, is e am prìomh rud an còmhdhail a phasgadh ann an cruth sruth nodejs.

Is e roghainn eile an cruth JSON, a bhios a’ cur an gnìomh protocol JSON RPC 2. Ach, bidh e ag obair le còmhdhail sònraichte (TCP agus HTTP(S)), nach eil iomchaidh anns a’ chùis againn.

Stàit a-staigh agus Stòradh ionadail

Feumaidh sinn staid a-staigh an tagraidh a stòradh - co-dhiù na h-iuchraichean soidhnidh. Is urrainn dhuinn gu furasta stàite a chuir ris an tagradh agus dòighean airson atharrachadh anns an API popup:

import {setupDnode} from "./utils/setupDnode";

export class SignerApp {

    constructor(){
        this.store = {
            keys: [],
        };
    }

    addKey(key){
        this.store.keys.push(key)
    }

    removeKey(index){
        this.store.keys.splice(index,1)
    }

    popupApi(){
        return {
            addKey: async (key) => this.addKey(key),
            removeKey: async (index) => this.removeKey(index)
        }
    }

    ...

} 

Air a’ chùl, còmhdaichidh sinn a h-uile càil ann an gnìomh agus sgrìobhaidh sinn cuspair an tagraidh gu uinneag gus an urrainn dhuinn obrachadh leis bhon consol:

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

Nach cuir sinn beagan iuchraichean bhon chonsail UI agus faic dè thachras leis an stàit:

A’ sgrìobhadh leudachadh brabhsair tèarainte

Feumaidh an stàit a bhith seasmhach gus nach tèid na h-iuchraichean a chall nuair a thèid ath-thòiseachadh.

Bidh sinn ga stòradh ann an localStorage, ga sgrìobhadh thairis air leis a h-uile atharrachadh. Às deidh sin, bidh feum air ruigsinneachd air an UI cuideachd, agus bu mhath leam cuideachd fo-sgrìobhadh a dhèanamh airson atharrachaidhean. Stèidhichte air seo, bidh e goireasach stòradh faicsinneach a chruthachadh agus ballrachd a thoirt dha na h-atharrachaidhean aige.

Cleachdaidh sinn an leabharlann mobx (https://github.com/mobxjs/mobx). Thuit an roghainn air oir cha robh agam ri obrachadh leis, ach bha mi airson a sgrùdadh.

Nach cuir sinn toiseach tòiseachaidh air an t-suidheachadh agus an stòr a dhèanamh faicsinneach:

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

    ...

}

“Fon chochall,” tha mobx air neach-ionaid a chuir an àite a h-uile raon stòr agus a ’toirt a-steach a h-uile fios thuca. Bidh e comasach fo-sgrìobhadh dha na teachdaireachdan seo.

Gu h-ìosal cleachdaidh mi am facal “nuair a dh’ atharrachadh”, ged nach eil seo gu tur ceart. Bidh Mobx a’ cumail sùil air ruigsinneachd gu raointean. Bithear a’ cleachdadh luchd-faighinn agus seòladairean nithean progsaidh a bhios an leabharlann a’ cruthachadh.

Bidh luchd-sgeadachaidh gnìomh a 'frithealadh dà adhbhar:

  1. Ann am modh teann leis a’ bhratach èigneachaidhActions, tha mobx a’ toirmeasg an stàit atharrachadh gu dìreach. Thathas den bheachd gu bheil e na dhòigh math a bhith ag obair fo chumhachan teann.
  2. Fiù ma dh'atharraicheas gnìomh an stàit grunn thursan - mar eisimpleir, bidh sinn ag atharrachadh grunn raointean ann an grunn loidhnichean de chòd - chan fhaigh an luchd-amhairc fios ach nuair a bhios e deiseil. Tha seo gu sònraichte cudromach airson an aghaidh, far a bheil ùrachaidhean stàite neo-riatanach a’ leantainn gu toirt seachad neo-riatanach de eileamaidean. Anns a 'chùis againn, chan eil a' chiad no an dàrna fear gu sònraichte buntainneach, ach leanaidh sinn na cleachdaidhean as fheàrr. Tha e àbhaisteach luchd-sgeadachaidh a cheangal ris a h-uile gnìomh a dh’ atharraicheas staid nan raointean a chaidh fhaicinn.

Air a’ chùl cuiridh sinn tùsachadh agus sàbhaladh na stàite ann an localStorage:

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

Tha an gnìomh freagairt inntinneach an seo. Tha dà argamaid ann:

  1. Tagraiche dàta.
  2. Neach-làimhseachaidh a thèid a ghairm leis an dàta seo a h-uile uair a dh'atharraicheas e.

Eu-coltach ri redux, far am faigh sinn gu soilleir an stàit mar argamaid, tha mobx a’ cuimhneachadh dè na rudan a chì sinn a gheibh sinn taobh a-staigh an neach-taghaidh, agus chan eil e a’ gairm ach an neach-làimhseachaidh nuair a dh’ atharraicheas iad.

Tha e cudromach tuigsinn gu mionaideach mar a bhios mobx a’ co-dhùnadh dè na nithean a ghabhas coimhead ris a bheil sinn a’ fo-sgrìobhadh. Ma sgrìobh mi roghnaichear ann an còd mar seo() => app.store, an uairsin cha tèid freagairt a ghairm gu bràth, leis nach eil an stòradh fhèin ri fhaicinn, chan eil ann ach na raointean aige.

Ma sgrìobh mi e mar seo () => app.store.keys, an uairsin a-rithist cha bhiodh dad a’ tachairt, oir nuair a chuireas tu ris / thoir air falbh eileamaidean rèite, chan atharraich an iomradh air.

Bidh Mobx ag obair mar neach-taghaidh airson a’ chiad uair agus chan eil e a’ cumail ach sùil air nithean a dh’ aithnich sinn. Tha seo air a dhèanamh tro luchd-faighinn progsaidh. Mar sin, tha an gnìomh togte air a chleachdadh an seo toJS. Bidh e a’ tilleadh nì ùr leis a h-uile neach-ionaid air a chuir na àite leis na raointean tùsail. Rè a chur gu bàs, bidh e a 'leughadh a h-uile raon den nì - mar sin tha an luchd-faighinn air am piobrachadh.

Anns a’ chonsail popup cuiridh sinn grunn iuchraichean a-rithist. An turas seo thàinig iad gu crìch ann an Stòras ionadail cuideachd:

A’ sgrìobhadh leudachadh brabhsair tèarainte

Nuair a thèid an duilleag cùl-fhiosrachaidh ath-luchdachadh, bidh am fiosrachadh fhathast na àite.

Chithear a h-uile còd tagraidh suas chun na h-ìre seo an seo.

Stòradh tèarainte de iuchraichean prìobhaideach

Chan eil e sàbhailte iuchraichean prìobhaideach a stòradh ann an teacsa soilleir: tha an-còmhnaidh cothrom ann gun tèid do slaodadh, gum faigh thu cothrom air a’ choimpiutair agad, agus mar sin air adhart. Mar sin, ann an localStorage stòraidh sinn na h-iuchraichean ann an cruth crioptaichte le facal-faire.

Airson barrachd tèarainteachd, cuiridh sinn staid glaiste ris an tagradh, anns nach bi cothrom air na h-iuchraichean idir. Gluaisidh sinn an leudachadh gu fèin-ghluasadach chun staid glaiste air sgàth ùine a-muigh.

Leigidh Mobx leat dìreach seata dàta as ìsle a stòradh, agus tha an còrr air a thomhas gu fèin-ghluasadach stèidhichte air. Is iad seo na feartan ris an canar coimpiutaireachd. Faodar an coimeas ri beachdan ann an stòran-dàta:

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

A-nis cha bhith sinn a’ stòradh ach na h-iuchraichean crioptaichte agus am facal-faire. Tha a h-uile càil eile air a thomhas. Bidh sinn a’ gluasad gu stàite glaiste le bhith a’ toirt air falbh am facal-faire bhon stàit. Tha dòigh aig an API poblach a-nis airson an stòradh a thòiseachadh.

Air a sgrìobhadh airson crioptachadh goireasan a’ cleachdadh 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)
}

Tha API seòlta aig a’ bhrobhsair tron ​​​​urrainn dhut fo-sgrìobhadh gu tachartas - atharrachaidhean stàite. Stàite, a rèir sin, faodaidh e bhith idle, active и locked. Airson leisg faodaidh tu crìoch-ama a shuidheachadh, agus tha glaiste air a shuidheachadh nuair a tha an OS fhèin air a bhacadh. Atharraichidh sinn cuideachd an roghnaiche airson a shàbhaladh gu localStorage:

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

Tha an còd ron cheum seo an seo.

Gnìomhan

Mar sin, thig sinn chun rud as cudromaiche: cruthachadh agus soidhnigeadh ghnothaichean air an blockchain. Cleachdaidh sinn blockchain agus leabharlann WAVES tonnan-ghnothaichean.

An toiseach, leig dhuinn sreath de theachdaireachdan a dh’ fheumar a shoidhnigeadh a chuir ris an stàit, an uairsin cuir ris dòighean airson teachdaireachd ùr a chuir ris, dearbhadh an ainm-sgrìobhte, agus diùltadh:

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

    ...
}

Nuair a gheibh sinn teachdaireachd ùr, cuiridh sinn meata-dàta ris, dèan observable agus cuir ri store.messages.

Mura dèan thu observable le làimh, an uairsin nì mobx e fhèin nuair a chuireas tu teachdaireachdan ris an raon. Ach, cruthaichidh e rud ùr air nach bi iomradh againn, ach bidh feum againn air airson an ath cheum.

An uairsin, bidh sinn a 'tilleadh gealladh a rèiticheas nuair a dh'atharraicheas inbhe na teachdaireachd. Bithear a’ cumail sùil air an inbhe le freagairt, a bhios “ga mharbhadh fhèin” nuair a dh’ atharraicheas an inbhe.

Còd modh approve и reject gu math sìmplidh: bidh sinn dìreach ag atharrachadh inbhe na teachdaireachd, às deidh dhuinn a shoidhnigeadh ma tha sin riatanach.

Bidh sinn a’ cur Aonta agus a’ diùltadh anns an UI API, newMessage anns an duilleag 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)
        }
    }

    ...
}

A-nis feuchaidh sinn ris a’ ghnothach a shoidhnigeadh leis an leudachadh:

A’ sgrìobhadh leudachadh brabhsair tèarainte

San fharsaingeachd, tha a h-uile dad deiseil, tha a h-uile dad air fhàgail cuir UI sìmplidh ris.

UI

Feumaidh an eadar-aghaidh ruigsinneachd gu staid an tagraidh. Air taobh an UI nì sinn observable stàite agus cuir gnìomh ris an API a dh'atharraicheas an stàit seo. Cuiridh sinn observable don nì API a fhuaireadh bhon chù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)
}

Aig an deireadh bidh sinn a 'tòiseachadh a' toirt seachad eadar-aghaidh an tagraidh. Is e tagradh freagairt a tha seo. Tha an rud cùl-fhiosrachaidh dìreach air a thoirt seachad le bhith a’ cleachdadh props. Bhiodh e ceart, gu dearbh, seirbheis air leth a dhèanamh airson dòighean-obrach agus stòr airson na stàite, ach airson adhbharan an artaigil seo tha seo gu leòr:

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

Le mobx tha e gu math furasta tòiseachadh a’ toirt seachad nuair a dh’ atharraicheas dàta. Bidh sinn dìreach a’ crochadh sgeadachadh an neach-amhairc bhon phacaid mobx-freagairt air a’ cho-phàirt, agus thèid render a ghairm gu fèin-ghluasadach nuair a dh’ atharraicheas nithean faicsinneach sam bith air an toir am pàirt iomradh. Chan fheum thu mapStateToProps no ceangal mar ann an redux. Bidh a h-uile dad ag obair dìreach a-mach às a’ bhogsa:

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

Faodar na pàirtean a tha air fhàgail fhaicinn sa chòd ann am pasgan UI.

A-nis anns a’ chlas tagraidh feumaidh tu roghnaiche stàite a dhèanamh airson an UI agus fios a chuir chun UI nuair a dh’ atharraicheas e. Gus seo a dhèanamh, leig dhuinn modh-obrach a chur ris getState и reactiona' glaodhaich 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())

        })
    }

    ...
}

Nuair a gheibh thu stuth remote air a chruthachadh reaction gus an stàit atharrachadh a tha ag iarraidh an gnìomh air taobh an UI.

Is e an suathadh mu dheireadh taisbeanadh teachdaireachdan ùra a chuir air ìomhaigh an leudachaidh:

function setupApp() {
...

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

...
}

Mar sin, tha an tagradh deiseil. Faodaidh duilleagan lìn ainm-sgrìobhte iarraidh airson gnothaichean:

A’ sgrìobhadh leudachadh brabhsair tèarainte

A’ sgrìobhadh leudachadh brabhsair tèarainte

Tha an còd ri fhaighinn an seo cheangal.

co-dhùnadh

Ma tha thu air an artaigil a leughadh gu deireadh, ach gu bheil ceistean agad fhathast, faodaidh tu faighneachd dhaibh aig stòran le leudachadh. An sin gheibh thu geallaidhean airson gach ceum ainmichte.

Agus ma tha ùidh agad coimhead air a’ chòd airson an fhìor leudachadh, gheibh thu seo an seo.

Còd, stòr agus tuairisgeul obrach bho siemarell

Source: www.habr.com

Cuir beachd ann