Kulemba zowonjezera zotetezedwa

Kulemba zowonjezera zotetezedwa

Mosiyana ndi kamangidwe ka "kasitomala-seva", ntchito zokhazikitsidwa ndi:

  • Palibe chifukwa chosungira database yokhala ndi ma logins ogwiritsira ntchito ndi mapasiwedi. Zambiri zofikira zimasungidwa ndi ogwiritsa ntchito okha, ndipo kutsimikizira kuti ndi zoona kumachitika pamlingo wa protocol.
  • Palibe chifukwa chogwiritsa ntchito seva. Mfundo yogwiritsira ntchito ikhoza kuchitidwa pa netiweki ya blockchain, komwe ndikotheka kusunga kuchuluka kofunikira kwa data.

Pali 2 zosungirako zotetezeka zamakiyi ogwiritsa ntchito - ma wallet a hardware ndi zowonjezera za msakatuli. Ma wallet a Hardware nthawi zambiri amakhala otetezeka kwambiri, koma ovuta kugwiritsa ntchito komanso otalikirana ndi mfulu, koma zowonjezera za msakatuli ndizophatikizana bwino kwachitetezo komanso kugwiritsa ntchito mosavuta, komanso kumatha kukhala kwaulere kwa ogwiritsa ntchito.

Poganizira zonsezi, tinkafuna kuti tiwonjezere zotetezeka kwambiri zomwe zimathandizira kupanga mapulogalamu okhazikika popereka API yosavuta yogwira ntchito ndi ma siginecha.
Tikuwuzani za izi pansipa.

Nkhaniyi idzakhala ndi malangizo a pang'onopang'ono amomwe mungalembere chowonjezera cha msakatuli, ndi zitsanzo za ma code ndi zojambula. Mutha kupeza ma code onse mu nkhokwe. Kupanga kulikonse momveka kumagwirizana ndi gawo la nkhaniyi.

Mbiri Yachidule Yazowonjezera Zamsakatuli

Zowonjezeretsa msakatuli zakhala zikuchitika kwa nthawi yayitali. Adawonekera mu Internet Explorer kumbuyo mu 1999, mu Firefox mu 2004. Komabe, kwa nthawi yayitali kwambiri panalibe muyezo umodzi wowonjezera.

Titha kunena kuti zidawoneka pamodzi ndi zowonjezera mu mtundu wachinayi wa Google Chrome. Zachidziwikire, panalibe tsatanetsatane panthawiyo, koma inali Chrome API yomwe idakhala maziko ake: atagonjetsa msika wambiri wa asakatuli ndikukhala ndi sitolo yopangira mapulogalamu, Chrome idakhazikitsadi mulingo wowonjezera osatsegula.

Mozilla inali ndi muyezo wake, koma powona kutchuka kwa zowonjezera za Chrome, kampaniyo idaganiza zopanga API yogwirizana. Mu 2015, motsogozedwa ndi Mozilla, gulu lapadera lidapangidwa mkati mwa World Wide Web Consortium (W3C) kuti ligwire ntchito pazowonjezera zakusakatula.

API yowonjezerapo kale ya Chrome idatengedwa ngati maziko. Ntchitoyi idachitika mothandizidwa ndi Microsoft (Google idakana kutenga nawo gawo pakukula kwa muyezo), ndipo zotsatira zake zidawoneka. zofunika.

M'malo mwake, mawonekedwewa amathandizidwa ndi Edge, Firefox ndi Opera (zindikirani kuti Chrome siili pamndandandawu). Koma kwenikweni, muyezo umagwirizana kwambiri ndi Chrome, chifukwa umalembedwa motengera zowonjezera zake. Mutha kuwerenga zambiri za WebExtensions API apa.

Mapangidwe owonjezera

Fayilo yokhayo yomwe ikufunika pakukulitsa ndi chiwonetsero (manifest.json). Ndilonso "malo olowera" pakukulitsa.

Kuwonekera

Malinga ndi zomwe zafotokozedwera, fayilo yowonetsera ndi fayilo yovomerezeka ya JSON. Kufotokozera kwathunthu kwa makiyi owonetsera okhala ndi zambiri za makiyi omwe amathandizidwa momwe msakatuli angawonedwe apa.

Makiyi omwe sali mwatsatanetsatane "atha" kunyalanyazidwa (zolakwika zonse za Chrome ndi Firefox, koma zowonjezera zikugwirabe ntchito).

Ndipo ndikufuna kutchera khutu ku mfundo zina.

  1. maziko - chinthu chomwe chili ndi magawo otsatirawa:
    1. zolemba - mndandanda wa zolemba zomwe zidzachitike kumbuyo (tilankhula za izi posachedwa);
    2. tsamba - m'malo mwa zolembedwa zomwe zidzachitike patsamba lopanda kanthu, mutha kufotokoza html ndi zomwe zili. Pachifukwa ichi, gawo la script lidzanyalanyazidwa, ndipo zolembazo ziyenera kulowetsedwa mu tsamba lazolemba;
    3. amapitirizabe - mbendera ya binary, ngati sichinatchulidwe, msakatuli "adzapha" ndondomeko yakumbuyo pamene akuwona kuti sakuchita kalikonse, ndikuyambitsanso ngati kuli kofunikira. Apo ayi, tsambalo lidzatsitsidwa kokha pamene msakatuli watsekedwa. Osathandizidwa mu Firefox.
  2. content_scripts - mndandanda wazinthu zomwe zimakupatsani mwayi wotsitsa zolemba zosiyanasiyana pamasamba osiyanasiyana. Chilichonse chili ndi magawo ofunikira awa:
    1. machesi - url chitsanzo, zomwe zimatsimikizira ngati zolemba zinazake zidzaphatikizidwa kapena ayi.
    2. js - mndandanda wa zolemba zomwe zidzakwezedwa mumasewerawa;
    3. kupatula_machesi - osaphatikizapo kumunda match Ma URL omwe akufanana ndi gawoli.
  3. tsamba_kuchita - kwenikweni ndi chinthu chomwe chimayang'anira chithunzi chomwe chikuwonetsedwa pafupi ndi adiresi mumsakatuli ndikuchita nawo. Zimakupatsaninso mwayi wowonetsa zenera la popup, lomwe limatanthauzidwa pogwiritsa ntchito HTML, CSS ndi JS yanu.
    1. default_popup - njira yopita ku fayilo ya HTML yokhala ndi mawonekedwe a popup, ikhoza kukhala ndi CSS ndi JS.
  4. zilolezo - mndandanda wowongolera maufulu owonjezera. Pali mitundu itatu yaufulu, yomwe ikufotokozedwa mwatsatanetsatane apa
  5. web_accessible_resources - zowonjezera zomwe tsamba lawebusayiti lingapemphe, mwachitsanzo, zithunzi, JS, CSS, mafayilo a HTML.
  6. kunja_kolumikizana - apa mutha kufotokoza momveka bwino ma ID a zowonjezera ndi madambwe amasamba omwe mungalumikizane nawo. Dongosolo litha kukhala lachiwiri kapena kupitilira apo. Sichikugwira ntchito mu Firefox.

Nkhani yokonzekera

Kuwonjezako kuli ndi magawo atatu opangira ma code, ndiko kuti, kugwiritsa ntchito kumakhala ndi magawo atatu okhala ndi magawo osiyanasiyana ofikira pa msakatuli API.

Nkhani yowonjezera

Zambiri za API zilipo pano. M'nkhani ino iwo "amakhala":

  1. Tsamba lakumbuyo - "backend" gawo lazowonjezera. Fayilo imatchulidwa mu manifesto pogwiritsa ntchito kiyi ya "background".
  2. Tsamba lotulukira - tsamba loyambira lomwe limawonekera mukadina pazithunzi zowonjezera. Mu manifesto browser_action -> default_popup.
  3. Tsamba lamakonda - tsamba lokulitsa, "kukhala" mu tabu yosiyana yowonera chrome-extension://<id_расширения>/customPage.html.

Nkhaniyi ilipo popanda mawindo asakatuli ndi ma tabu. Tsamba lakumbuyo limapezeka m'kope limodzi ndipo limagwira ntchito nthawi zonse (kupatulapo tsamba la zochitika, pamene zolemba zakumbuyo zimayambitsidwa ndi chochitika ndi "kufa" pambuyo pa kuphedwa kwake). Tsamba lotulukira imakhalapo pomwe zenera la popup likutsegulidwa, ndipo Tsamba lamakonda - pomwe tabu yomwe ili nayo ndi yotseguka. Palibe mwayi wopeza ma tabo ena ndi zomwe zili munkhaniyi.

Nkhani ya script

Fayilo ya script yokhutira imayambitsidwa pamodzi ndi tsamba lililonse la msakatuli. Ili ndi mwayi wopeza gawo la API yowonjezera komanso mtengo wa DOM patsamba lawebusayiti. Ndi zolemba zomwe zili ndi udindo wolumikizana ndi tsambali. Zowonjezera zomwe zimagwiritsa ntchito mtengo wa DOM zimapanga izi muzolemba - mwachitsanzo, otsekereza ad kapena omasulira. Komanso, zolemba zomwe zili mkati zimatha kulumikizana ndi tsambalo kudzera muyeso postMessage.

Tsamba latsamba lawebusayiti

Ili ndiye tsamba lenilenilo. Zilibe chochita ndikuwonjezera ndipo zilibe mwayi wofikira pamenepo, pokhapokha ngati gawo la tsambali silinasonyezedwe mu chiwonetsero (zambiri pansipa).

Kusinthana kwa uthenga

Magawo osiyanasiyana a pulogalamuyi ayenera kusinthanitsa mauthenga wina ndi mnzake. Pali API ya izi runtime.sendMessage kutumiza meseji background и tabs.sendMessage kutumiza uthenga kutsamba (zolemba, zowonekera kapena tsamba lawebusayiti ngati likupezeka externally_connectable). Pansipa pali chitsanzo mukalowa mu 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))
    }
)

Kuti mulumikizane kwathunthu, mutha kupanga kulumikizana kudzera runtime.connect. Poyankha tidzalandira runtime.Port, kumene, pamene ili lotseguka, mukhoza kutumiza nambala iliyonse ya mauthenga. Kumbali ya kasitomala, mwachitsanzo, contentscript, zikuwoneka motere:

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

Seva kapena maziko:

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

Palinso chochitika onDisconnect ndi njira disconnect.

Chithunzi cha ntchito

Tiyeni tipange chowonjezera cha msakatuli chomwe chimasunga makiyi achinsinsi, chimapereka mwayi wodziwa zambiri za anthu (adiresi, kiyi yapagulu imalumikizana ndi tsambali ndikuloleza mapulogalamu a chipani chachitatu kuti apemphe siginecha yamalonda.

Kupititsa patsogolo ntchito

Ntchito yathu iyenera kuyanjana ndi wogwiritsa ntchito ndikupereka tsambalo ndi API yoyimbira njira (mwachitsanzo, kusaina zochitika). Chitani ndi chimodzi chokha contentscript sichigwira ntchito, chifukwa imangokhala ndi DOM, koma osati ku JS ya tsamba. Lumikizani kudzera runtime.connect sitingathe, chifukwa API ikufunika pamadera onse, ndipo zokhazokha zokhazokha zikhoza kufotokozedwa muwonetsero. Chifukwa chake, chithunzichi chikuwoneka motere:

Kulemba zowonjezera zotetezedwa

Padzakhala script ina - inpage, zomwe tidzalowetsa patsamba. Idzayenda m'mawu ake ndikupereka API yogwira ntchito ndikuwonjezera.

Kunyumba

Khodi yonse yowonjezera msakatuli ikupezeka pa GitHub. Pamafotokozedwe padzakhala maulalo ochita.

Tiyeni tiyambe ndi 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"]
}

Pangani zopanda kanthu background.js, popup.js, inpage.js ndi contentscript.js. Timawonjezera popup.html - ndipo pulogalamu yathu ikhoza kuyikidwa kale mu Google Chrome ndikuwonetsetsa kuti ikugwira ntchito.

Kuti mutsimikizire izi, mutha kutenga code kuchokera pano. Kuphatikiza pa zomwe tidachita, ulalowo udakonza msonkhano wa polojekitiyo pogwiritsa ntchito webpack. Kuti muwonjezere pulogalamu pa msakatuli, mu chrome: // zowonjezera muyenera kusankha kutsitsa osatsegula ndi chikwatu chokhala ndi cholumikizira chofananira - kwa ife dist.

Kulemba zowonjezera zotetezedwa

Tsopano zowonjezera zathu zakhazikitsidwa ndikugwira ntchito. Mutha kugwiritsa ntchito zida zamapulogalamu pazinthu zosiyanasiyana motere:

popup ->

Kulemba zowonjezera zotetezedwa

Kufikira kwa script script console kumachitika kudzera pa console ya tsamba lomwe lakhazikitsidwa.Kulemba zowonjezera zotetezedwa

Kusinthana kwa uthenga

Chifukwa chake, tiyenera kukhazikitsa njira ziwiri zoyankhulirana: inpage <-> maziko ndi popup <-> maziko. Mutha, inde, kungotumiza mauthenga ku doko ndikupanga protocol yanu, koma ndimakonda njira yomwe ndidawona mu polojekiti yotseguka ya metamask.

Izi ndizowonjezera msakatuli kuti mugwire ntchito ndi netiweki ya Ethereum. Momwemo, magawo osiyanasiyana a pulogalamuyi amalumikizana kudzera pa RPC pogwiritsa ntchito laibulale ya dnode. Zimakupatsani mwayi wosinthana mwachangu komanso mosavuta ngati mutapereka ma nodejs ngati choyendera (kutanthauza chinthu chomwe chimagwiritsa ntchito mawonekedwe omwewo):

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

Tsopano tipanga kalasi yofunsira. Idzapanga zinthu za API pazithunzi ndi tsamba lawebusayiti, ndikupanga dnode kwa iwo:

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

Apa ndi pansipa, m'malo mwa chinthu chapadziko lonse lapansi cha Chrome, timagwiritsa ntchito extensionApi, yomwe imalowa mu Chrome mu msakatuli wa Google ndi msakatuli wina. Izi zachitika kuti zigwirizane ndi msakatuli, koma cholinga cha nkhaniyi munthu atha kugwiritsa ntchito 'chrome.runtime.connect'.

Tiyeni tipange chitsanzo cha ntchito mu script yakumbuyo:

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

Popeza dnode imagwira ntchito ndi mitsinje, ndipo timalandira doko, kalasi ya adaputala ikufunika. Amapangidwa pogwiritsa ntchito laibulale yowerengeka, yomwe imagwiritsa ntchito mitsinje ya nodejs mu msakatuli:

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

Tsopano tiyeni tipange kulumikizana mu 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;
    }
}

Kenako timapanga kulumikizana mu script yokhutira:

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

Popeza tikufuna API osati muzolemba, koma mwachindunji patsamba, timachita zinthu ziwiri:

  1. Timapanga mitsinje iwiri. Imodzi - molunjika patsamba, pamwamba pa postMessage. Kwa izi timagwiritsa ntchito paketi iyi kuchokera kwa omwe amapanga metamask. Mtsinje wachiwiri ndikuyambira padoko lolandilidwa kuchokera runtime.connect. Tiyeni tigule. Tsopano tsambalo lidzakhala ndi mtsinje kumbuyo.
  2. Lowetsani script mu DOM. Tsitsani script (kulowa kwake kunaloledwa mu chiwonetsero) ndikupanga tag script ndi zomwe zili mkati mwake:

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

Tsopano tikupanga chinthu cha api mu inpage ndikuchiyika chapadziko lonse lapansi:

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

Ndife okonzeka Remote Procedure Call (RPC) yokhala ndi API yosiyana ya tsamba ndi UI. Tikalumikiza tsamba latsopano ndi maziko titha kuwona izi:

Kulemba zowonjezera zotetezedwa

Empty API ndi chiyambi. Pa tsamba, titha kuyitcha moni ntchito motere:

Kulemba zowonjezera zotetezedwa

Kugwira ntchito ndi callback ntchito mu JS yamakono ndi makhalidwe oipa, kotero tiyeni tilembe wothandizira wamng'ono kuti apange dnode yomwe imakulolani kuti mudutse chinthu cha API ku zipangizo.

Zinthu za API tsopano ziziwoneka motere:

export class SignerApp {

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

...

}

Kupeza chinthu kuchokera kutali monga chonchi:

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

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

Ndipo kuyimba ntchito kumabweretsa lonjezo:

Kulemba zowonjezera zotetezedwa

Mtundu wokhala ndi ntchito za asynchronous zilipo apa.

Ponseponse, njira ya RPC ndi mtsinje ikuwoneka yosinthika: titha kugwiritsa ntchito kuchulukitsa kwa nthunzi ndikupanga ma API angapo osiyanasiyana pantchito zosiyanasiyana. M'malo mwake, dnode ingagwiritsidwe ntchito kulikonse, chinthu chachikulu ndikukulunga zoyendera ngati mtsinje wa nodejs.

Njira ina ndi mtundu wa JSON, womwe umagwiritsa ntchito protocol ya JSON RPC 2. Komabe, imagwira ntchito ndi zoyendera zapadera (TCP ndi HTTP(S)), zomwe sizikugwira ntchito kwa ife.

Internal state and localStorage

Tidzafunika kusunga mkati mwa pulogalamuyo - osachepera makiyi osayina. Titha kuwonjezera dziko mosavuta pakugwiritsa ntchito ndi njira zosinthira mu popup API:

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

export class SignerApp {

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

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

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

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

    ...

} 

Kumbuyo, timakulunga chilichonse ndikulemba chinthucho pawindo kuti tigwiritse ntchito kuchokera ku 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)
        }
    }
}

Tiyeni tiwonjeze makiyi angapo kuchokera ku UI console ndikuwona zomwe zidachitika ndi boma:

Kulemba zowonjezera zotetezedwa

Boma liyenera kupangidwa kulimbikira kuti makiyi asatayike poyambiranso.

Tizisunga mu LocalStorage, ndikuzilemba ndikusintha kulikonse. Pambuyo pake, kuyipeza kudzafunikanso ku UI, ndipo ndikufunanso kulembetsa kusintha. Kutengera izi, zidzakhala zosavuta kupanga zosungirako zowoneka ndikulembetsa kusintha kwake.

Tidzagwiritsa ntchito laibulale ya mobx (https://github.com/mobxjs/mobx). Chosankhacho chinagwera pa icho chifukwa sindinayenera kugwira ntchito nacho, koma ndinkafunadi kuchiphunzira.

Tiyeni tiwonjeze kuyambika kwa zomwe zidayambira ndikupanga sitolo kuti iwonekere:

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

    ...

}

"Pansi pa hood," mobx yalowa m'malo onse ogulitsa ndi proxy ndikuyimba mafoni onse kwa iwo. Zidzakhala zotheka kulembetsa ku mauthengawa.

Pansipa nthawi zambiri ndimagwiritsa ntchito mawu oti "pamene ndikusintha", ngakhale izi sizolondola. Mobx amatsata mwayi wopita kuminda. Opeza ndi ma setter a zinthu zofananira zomwe laibulale imapanga zimagwiritsidwa ntchito.

Zokongoletsa zochita zimagwira ntchito ziwiri:

  1. Munjira yokhwima ndi mbendera ya enforceActions, mobx imaletsa kusintha dziko mwachindunji. Zimatengedwa kuti ndi bwino kugwira ntchito pansi pa mikhalidwe yovuta.
  2. Ngakhale ntchito ikusintha boma kangapo - mwachitsanzo, timasintha magawo angapo m'mizere ingapo yamakhodi - owonera amadziwitsidwa pokhapokha akamaliza. Izi ndizofunikira makamaka kwa kutsogolo, komwe zosintha zosafunikira za boma zimabweretsa kuperekedwa kosafunikira kwa zinthu. Kwa ife, palibe choyamba kapena chachiwiri chomwe chili chofunikira kwambiri, koma tidzatsatira njira zabwino kwambiri. Ndichizoloŵezi chophatikizira okongoletsa ku ntchito zonse zomwe zimasintha malo omwe amawonedwa.

Kumbuyo tidzawonjezera kuyambitsa ndikusunga boma mu 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)
        }
    }
}

Zomwe zimachitika ndizosangalatsa apa. Lili ndi mfundo ziwiri:

  1. Chosankha deta.
  2. Wothandizira yemwe adzayitanidwe ndi datayi nthawi iliyonse ikasintha.

Mosiyana ndi reux, pomwe timalandila boma momveka bwino ngati mkangano, mobx imakumbukira zomwe tikuwona zomwe timapeza mkati mwa chosankha, ndikungoyimbira wogwirizira akasintha.

Ndikofunikira kumvetsetsa momwe mobx imasankhira zowonera zomwe timalembetsa. Ngati ndidalemba chosankha mu code monga chonchi() => app.store, ndiye kuti zomwe zimachitika sizingatchulidwe konse, popeza kusungirako sikukuwoneka, minda yake yokha ndi yomwe.

Ngati ndinalemba motere () => app.store.keys, ndiye kachiwiri palibe chomwe chingachitike, popeza powonjezera / kuchotsa zinthu zambiri, kutchulidwa kwa izo sikudzasintha.

Mobx amakhala ngati osankha kwa nthawi yoyamba ndipo amangoyang'anira zomwe tidaziwona. Izi zimachitika kudzera pa proxy getters. Chifukwa chake, ntchito yomangidwira imagwiritsidwa ntchito pano toJS. Imabwezera chinthu chatsopano ndi ma proxies onse m'malo ndi minda yoyambirira. Panthawi ya kuphedwa, imawerenga minda yonse ya chinthucho - chifukwa chake ma getters amayambitsidwa.

Mu popup console tidzawonjezeranso makiyi angapo. Nthawi ino adakhalanso mu LocalStorage:

Kulemba zowonjezera zotetezedwa

Tsamba lakumbuyo likatsitsidwanso, chidziwitsocho chimakhalabe m'malo mwake.

Makhodi onse ogwiritsira ntchito mpaka pano akhoza kuwonedwa apa.

Kusungirako kotetezedwa kwa makiyi achinsinsi

Kusunga makiyi achinsinsi m'mawu omveka bwino sikotetezeka: nthawi zonse pamakhala mwayi woti muberedwe, mutha kugwiritsa ntchito kompyuta yanu, ndi zina zotero. Chifukwa chake, mu LocalStorage tidzasunga makiyi mu mawonekedwe obisika achinsinsi.

Kuti titetezeke kwambiri, tidzawonjezera malo otsekedwa ku pulogalamuyo, momwe sipadzakhalanso mwayi wopeza makiyi nkomwe. Tidzasamutsa zokhazo ku malo okhoma chifukwa cha nthawi yotseka.

Mobx imakupatsani mwayi wosunga zowerengeka zochepa chabe, ndipo zina zonse zimawerengedwa motengera momwemo. Izi ndi zomwe zimatchedwa computed properties. Atha kufananizidwa ndi mawonedwe mu database:

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

Tsopano timangosunga makiyi obisika ndi mawu achinsinsi. Zina zonse zimawerengedwa. Timasamutsira kumalo otsekedwa pochotsa mawu achinsinsi ku boma. API yapagulu tsopano ili ndi njira yoyambira kusungirako.

Zalembedwa kuti zisungidwe zogwiritsira ntchito 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)
}

Msakatuli ali ndi API yopanda pake yomwe mutha kulembetsa ku chochitika - kusintha kwa boma. State, motero, akhoza kukhala idle, active и locked. Popanda ntchito mutha kukhazikitsa nthawi yomaliza, ndipo zokhoma zimayikidwa pomwe OS yokha yatsekedwa. Tisinthanso chosankha kuti tisunge ku 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)
        }
    }
}

Code pamaso sitepe ili apa.

Zochitika

Chifukwa chake, tifika ku chinthu chofunikira kwambiri: kupanga ndi kusaina zochitika pa blockchain. Tidzagwiritsa ntchito WAVES blockchain ndi library mafunde-zochitika.

Choyamba, tiyeni tiwonjezere ku boma mauthenga angapo omwe akuyenera kusaina, kenaka yonjezerani njira zowonjezera uthenga watsopano, kutsimikizira siginecha, ndikukana:

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

    ...
}

Tikalandira uthenga watsopano, timawonjezera metadata kwa izo, chitani observable ndi kuwonjezera store.messages.

Ngati simutero observable pamanja, ndiye mobx izichita yokha powonjezera mauthenga pagulu. Komabe, idzapanga chinthu chatsopano chomwe sitidzakhala nacho cholozera, koma tidzachifuna pa sitepe yotsatira.

Kenako, timabwezera lonjezo lomwe limathetsa pomwe uthenga ukusintha. Mkhalidwewu umayang'aniridwa ndi zomwe zimachitika, zomwe "zidzipha" pamene chikhalidwe chikusintha.

Njira kodi approve и reject chophweka kwambiri: timangosintha momwe uthengawo uliri, titasaina kale ngati kuli kofunikira.

Timayika Kuvomereza ndi kukana mu UI API, newMessage mu tsamba 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)
        }
    }

    ...
}

Tsopano tiyeni tiyese kusaina ndi kukulitsa:

Kulemba zowonjezera zotetezedwa

Mwambiri, zonse zakonzeka, zonse zomwe zatsalira ndizo onjezani UI yosavuta.

UI

Mawonekedwe amafunikira mwayi wopita ku boma la ntchito. Kumbali ya UI tidzachita observable tchulani ndikuwonjezera ntchito ku API yomwe isintha izi. Tiyeni tiwonjeze observable kupita ku chinthu cha API cholandiridwa kuchokera kumbuyo:

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

Pamapeto pake timayamba kupereka mawonekedwe a ntchito. Ichi ndi ntchito momwe. Chinthu chakumbuyo chimangoperekedwa pogwiritsa ntchito zida. Zingakhale zolondola, ndithudi, kupanga ntchito yosiyana ya njira ndi sitolo ya boma, koma zolinga za nkhaniyi ndizokwanira:

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

Ndi mobx ndizosavuta kuyamba kupereka pomwe deta ikusintha. Timangopachika chokongoletsera chowonera kuchokera pa phukusi mobx-react pa chigawocho, ndi kupereka kudzayitanidwa kokha pamene zowoneka zilizonse zotchulidwa ndi chigawocho chikusintha. Simufunika mapStateToProps aliwonse kapena kulumikizana ngati mu redux. Chilichonse chimagwira ntchito bwino:

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

Zigawo zotsalira zikhoza kuwonedwa mu code mu chikwatu cha UI.

Tsopano m'kalasi yofunsira muyenera kupanga chosankha cha UI ndikudziwitsa UI ikasintha. Kuti tichite izi, tiyeni tiwonjezere njira getState и reactionkuyitana 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())

        })
    }

    ...
}

Polandira chinthu remote zikupangidwa reaction kusintha dziko lomwe limayitanira ntchito ku mbali ya UI.

Kukhudza komaliza ndikuwonjezera kuwonetsa kwa mauthenga atsopano pazithunzi zowonjezera:

function setupApp() {
...

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

...
}

Choncho, ntchito ndi wokonzeka. Masamba a pa intaneti atha kupempha siginecha yamalonda:

Kulemba zowonjezera zotetezedwa

Kulemba zowonjezera zotetezedwa

Khodi ikupezeka pano kugwirizana.

Pomaliza

Ngati mwawerenga nkhaniyi mpaka kumapeto, koma muli ndi mafunso, mutha kuwafunsa nkhokwe zowonjezera. Kumeneko mudzapezanso zochita pa sitepe iliyonse yosankhidwa.

Ndipo ngati mukufuna kuyang'ana pa code yowonjezerapo, mukhoza kupeza izi apa.

Khodi, nkhokwe ndi kufotokozera ntchito kuchokera siemarell

Source: www.habr.com

Kuwonjezera ndemanga