Manoratra fanitarana navigateur azo antoka

Manoratra fanitarana navigateur azo antoka

Mifanohitra amin'ny maritrano "client-server" mahazatra, ny fampiharana tsy miankina dia miavaka amin'ny:

  • Tsy ilaina ny mitahiry tahiry miaraka amin'ny fidirana sy tenimiafina mpampiasa. Ny fampahalalam-baovao fidirana dia tehirizin'ny mpampiasa ihany, ary ny fanamafisana ny maha-azo itokiana azy dia mitranga amin'ny ambaratonga protocol.
  • Tsy mila mampiasa mpizara. Ny lojika fampiharana dia azo tanterahina amin'ny tambajotra blockchain, izay ahafahana mitahiry ny habetsaky ny angona ilaina.

Misy fitehirizana 2 azo antoka ho an'ny fanalahidin'ny mpampiasa - poketra hardware sy fanitarana navigateur. Ny poketra hardware dia tena azo antoka, saingy sarotra ampiasaina ary lavitra ny maimaim-poana, fa ny fanitarana ny navigateur dia fampifangaroana tonga lafatra amin'ny fiarovana sy ny fanamorana ny fampiasana, ary mety ho maimaim-poana ihany koa ho an'ny mpampiasa farany.

Raha raisina izany rehetra izany, dia naniry ny hanao ny fanitarana azo antoka indrindra izahay izay manamora ny fampivoarana ny rindranasa itsinjaram-pahefana amin'ny fanomezana API tsotra ho an'ny fiaraha-miasa amin'ny fifampiraharahana sy ny sonia.
Holazainay eto ambany ity traikefa ity.

Ny lahatsoratra dia ahitana torolalana tsikelikely momba ny fomba fanoratana fanitarana navigateur, miaraka amin'ny ohatra kaody sy pikantsary. Azonao atao ny mahita ny code rehetra ao repository. Ny fanoloran-tena tsirairay dia mifanandrify amin'ny fizarana iray amin'ity lahatsoratra ity.

Tantara fohy momba ny fanitarana navigateur

Efa nisy hatramin'ny ela ny fanitarana navigateur. Niseho tao amin'ny Internet Explorer izy ireo tamin'ny 1999, tao amin'ny Firefox tamin'ny 2004. Na izany aza, nandritra ny fotoana ela dia tsy nisy fenitra tokana ho an'ny fanitarana.

Azontsika lazaina fa niseho niaraka tamin'ny fanitarana tamin'ny dikan-teny fahefatra amin'ny Google Chrome izy io. Mazava ho azy fa tsy nisy fanoritsoritana tamin'izany, fa ny Chrome API no lasa fototra: rehefa nandresy ny ankamaroan'ny tsenan'ny navigateur ary manana fivarotana fampiharana namboarina, dia nametraka ny fenitra ho an'ny fanitarana navigateur i Chrome.

Nanana ny fenitra manokana i Mozilla, saingy nahita ny lazan'ny fanitarana Chrome ny orinasa dia nanapa-kevitra ny hanao API mifanentana. Tamin'ny taona 2015, noho ny fandraisana an-tanana an'i Mozilla, dia nisy vondrona manokana noforonina tao anatin'ny World Wide Web Consortium (W3C) mba hiasa amin'ny fanondroana fanitarana cross-browser.

Ny fanitarana API efa misy ho an'ny Chrome dia noraisina ho fototra. Ny asa dia natao niaraka tamin'ny fanohanan'ny Microsoft (Google nandà tsy handray anjara amin'ny fampandrosoana ny fenitra), ary vokatr'izany dia nisy drafitra niseho. fepetra arahana.

Amin'ny fomba ofisialy, ny fanondroana dia tohanan'ny Edge, Firefox ary Opera (mariho fa tsy ao anatin'ity lisitra ity ny Chrome). Saingy raha ny marina, ny fenitra dia mifanaraka amin'ny Chrome, satria izy io dia nosoratana mifototra amin'ny fanitarana azy. Afaka mamaky bebe kokoa momba ny WebExtensions API ianao eto.

Firafitry ny fanitarana

Ny hany rakitra ilaina amin'ny fanitarana dia ny manifest (manifest.json). Izy io koa no "toerana fidirana" amin'ny fanitarana.

Manifesto

Araka ny fanondroana dia rakitra JSON manankery ny rakitra manifest. Famaritana feno momba ny lakile mibaribary miaraka amin'ny fampahafantarana momba ny fanalahidy izay tohana amin'ny navigateur azo jerena eto.

Ny fanalahidy izay tsy ao amin'ny famaritana "mety" tsy hiraharaha (na Chrome sy Firefox dia mitatitra ny fahadisoana, fa ny fanitarana dia mitohy miasa).

Ary tiako ny hisarika ny saina amin'ny teboka sasany.

  1. lafika - zavatra ahitana ireto sehatra manaraka ireto:
    1. soratra — andiana sora-baventy izay hotanterahina ao amin'ny contexte background (hiresaka momba izany isika aoriana kely);
    2. pejy - raha tokony ho script izay hotanterahina amin'ny pejy tsy misy na inona na inona, dia azonao atao ny mamaritra html misy atiny. Amin'ity tranga ity, tsy hiraharaha ny saha script, ary ny script dia mila ampidirina ao amin'ny pejy votoaty;
    3. maharitra - saina mimari-droa, raha tsy voatondro, ny navigateur dia "hamono" ny fizotry ny background rehefa heveriny fa tsy manao na inona na inona izy, ary hamerina izany raha ilaina. Raha tsy izany, rehefa mihidy ny navigateur dia tsy ho voavaha ihany ny pejy. Tsy tohana amin'ny Firefox.
  2. content_scripts - zavatra maromaro mamela anao hampiditra sora-baventy amin'ny pejin-tranonkala samihafa. Ny zavatra tsirairay dia misy ireto saha manan-danja manaraka ireto:
    1. lalao - url modely, izay mamaritra raha hampiditra sora-baventy manokana na tsia.
    2. js - lisitr'ireo script izay ho entina ao anatin'ity lalao ity;
    3. exclude_matches - tsy tafiditra ao anatin'ny saha match URL mifanaraka amin'ity saha ity.
  3. page_action - raha ny marina dia zavatra iray izay tompon'andraikitra amin'ny kisary izay aseho eo akaikin'ny bara adiresy ao amin'ny navigateur sy ny fifandraisana aminy. Izy io koa dia ahafahanao mampiseho varavarankely mipoitra, izay voafaritra amin'ny alàlan'ny HTML, CSS ary JS anao manokana.
    1. default_popup — lalana mankany amin'ny rakitra HTML miaraka amin'ny interface popup, mety misy CSS sy JS.
  4. alalana - laharan'ny fitantanana ny zon'ny fanitarana. Misy karazana zo 3, izay voafaritra amin'ny antsipiriany eto
  5. web_accessible_resources - loharano fanitarana izay azon'ny pejin-tranonkala mangataka, ohatra, sary, JS, CSS, rakitra HTML.
  6. externally_connectable — eto ianao dia afaka mamaritra mazava tsara ny ID an'ny fanitarana hafa sy ny sahan'ny pejin-tranonkala izay ahafahanao mifandray. Ny sehatra iray dia mety ho ambaratonga faharoa na ambony. Tsy mandeha amin'ny Firefox.

contexte execution

Ny fanitarana dia manana sehatra famonoana kaody telo, izany hoe, ny fampiharana dia misy ampahany telo miaraka amin'ny ambaratonga samihafa amin'ny fidirana amin'ny API navigateur.

contexte fanitarana

Ny ankamaroan'ny API dia hita eto. Amin'ity toe-javatra ity dia "miaina" izy ireo:

  1. Pejy ambadika — “backend” ampahany amin'ny fanitarana. Ny rakitra dia voafaritra ao amin'ny manifest amin'ny fampiasana ny famaha "background".
  2. Pejy popup — pejy popup izay miseho rehefa manindry ny kisary fanitarana ianao. Ao amin'ny manifesto browser_action -> default_popup.
  3. Pejy manokana — pejy fanitarana, “miaina” amin'ny tabilao misaraka amin'ny fijery chrome-extension://<id_расширения>/customPage.html.

Ity contexte ity dia tsy misy afa-tsy amin'ny fikandrana sy tabilao navigateur. Pejy ambadika dia misy ao anaty dika tokana ary miasa foana (ankoatra ny pejin'ny hetsika, rehefa atomboka amin'ny hetsika iray ny script ambadika ary "maty" aorian'ny famonoana azy). Pejy popup misy rehefa misokatra ny varavarankely popup, ary Pejy manokana — raha mbola misokatra ny tabilao misy azy. Tsy misy fidirana amin'ny tabilao hafa sy ny atiny avy amin'ity contexte ity.

contexte script votoaty

Ny rakitra script votoaty dia alefa miaraka amin'ny tabilao navigateur tsirairay. Afaka miditra amin'ny ampahany amin'ny API fanitarana sy ny hazo DOM amin'ny pejin-tranonkala. Izy io dia sora-baventy izay tompon'andraikitra amin'ny fifandraisana amin'ny pejy. Ny fanitarana manodinkodina ny hazo DOM dia manao izany amin'ny sora-baventy - ohatra, mpanakana doka na mpandika teny. Ary koa, ny script votoaty dia afaka mifandray amin'ny pejy amin'ny alàlan'ny fenitra postMessage.

contexte pejin-tranonkala

Ity no pejy web tena izy. Tsy misy ifandraisany amin'ny fanitarana izany ary tsy manana fidirana ao, afa-tsy amin'ny tranga izay tsy voatondro mazava tsara ny sehatra amin'ity pejy ity ao amin'ny fanehoana (bebe kokoa momba ity eto ambany ity).

Fifanakalozana hafatra

Ny ampahany samihafa amin'ny fampiharana dia tsy maintsy mifanakalo hafatra. Misy API amin'izany runtime.sendMessage handefa hafatra background и tabs.sendMessage handefa hafatra amin'ny pejy iray (script votoaty, popup na pejy web raha misy externally_connectable). Ity ambany ity ny ohatra iray rehefa miditra amin'ny 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))
    }
)

Ho an'ny fifandraisana feno dia azonao atao ny mamorona fifandraisana amin'ny alàlan'ny runtime.connect. Ho setrin'izany dia hahazo izahay runtime.Port, izay, raha mbola misokatra izy, dia afaka mandefa hafatra maromaro. Amin'ny lafiny mpanjifa, ohatra, contentscript, toa izao:

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

Server na ambadika:

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

Misy ihany koa ny hetsika onDisconnect ary fomba disconnect.

Kisary fampiharana

Andao hanao fanitarana navigateur izay mitahiry fanalahidin'ny tena manokana, manome fidirana amin'ny fampahalalana ho an'ny daholobe (adiresy, fanalahidin'ny daholobe mifandray amin'ny pejy ary mamela ny fampiharana an'ny antoko fahatelo hangataka sonia amin'ny fifampiraharahana.

Fampivoarana fampiharana

Ny fampiharana anay dia tsy maintsy mifandray amin'ny mpampiasa ary manome ny pejy miaraka amin'ny API hiantsoana fomba (ohatra, hanasonia fifampiraharahana). Ataovy amin'ny iray ihany contentscript dia tsy mandeha, satria manana fidirana amin'ny DOM ihany izy, fa tsy amin'ny JS amin'ny pejy. Mifandraisa amin'ny runtime.connect tsy afaka, satria ny API dia ilaina amin'ny sehatra rehetra, ary izay voafaritra ihany no azo faritana ao amin'ny manifest. Vokatr'izany dia ho toy izao ny kisary:

Manoratra fanitarana navigateur azo antoka

Hisy script iray hafa - inpage, izay atsipintsika ao amin'ny pejy. Hihazakazaka amin'ny teny manodidina azy izy ary hanome API ho an'ny fiasana amin'ny fanitarana.

Ny fiandohana

Ny kaody fanitarana navigateur rehetra dia misy ao amin'ny GitHub. Mandritra ny famaritana dia hisy rohy mankany amin'ny fanolorana.

Aleo atomboka amin'ny 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"]
}

Mamorona background.js, popup.js, inpage.js ary contentscript.js. Manampy popup.html izahay - ary efa azo ampidirina ao amin'ny Google Chrome ny fampiharana anay ary azo antoka fa miasa izany.

Mba hanamarinana izany dia azonao atao ny maka ny kaody avy eto. Ho fanampin'ny zavatra nataonay, ny rohy dia nanamboatra ny fivorian'ny tetikasa tamin'ny alàlan'ny webpack. Raha te hampiditra fampiharana amin'ny navigateur, ao amin'ny chrome: // extensions dia mila misafidy entana tsy misy entana ianao ary ny lahatahiry misy ny fanitarana mifanaraka amin'izany - raha ny dist.

Manoratra fanitarana navigateur azo antoka

Ankehitriny dia napetraka sy miasa ny fanitaranay. Azonao atao ny mampandeha ny fitaovana developer amin'ny toe-javatra samihafa toy izao manaraka izao:

popup ->

Manoratra fanitarana navigateur azo antoka

Ny fidirana amin'ny console script votoaty dia atao amin'ny alàlan'ny console amin'ny pejy izay nanombohana azy.Manoratra fanitarana navigateur azo antoka

Fifanakalozana hafatra

Noho izany, mila mametraka fantsona fifandraisana roa isika: ao amin'ny pejy <-> background ary popup <-> background. Azonao atao, mazava ho azy, mandefa hafatra any amin'ny seranana fotsiny ary mamorona ny protocol anao manokana, fa aleoko ny fomba fijery hitako tao amin'ny tetikasa open source metamask.

Ity dia fanitarana navigateur miasa amin'ny tambajotra Ethereum. Ao anatin'izany, ny ampahany samihafa amin'ny fampiharana dia mifandray amin'ny alàlan'ny RPC mampiasa ny tranomboky dnode. Mamela anao handamina fifanakalozam-bola haingana sy mora izany raha manome azy amin'ny renirano nodejs ho toy ny fitaterana (midika hoe zavatra iray izay mametraka ny interface mitovy):

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

Ankehitriny dia hamorona kilasy fampiharana isika. Izy io dia hamorona zavatra API ho an'ny popup sy pejy web, ary hamorona dnode ho azy ireo:

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

Eto sy etsy ambany, fa tsy ilay zavatra Chrome maneran-tany, dia mampiasa extensionApi izahay, izay miditra amin'ny Chrome amin'ny navigateur Google sy navigateur amin'ny hafa. Natao izany mba hifanaraka amin'ny navigateur, fa ho an'ny tanjon'ity lahatsoratra ity dia azo ampiasaina fotsiny ny 'chrome.runtime.connect'.

Andao hamorona ohatra fampiharana amin'ny script background:

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

Satria ny dnode dia miasa amin'ny streams, ary mahazo seranan-tsambo izahay, dia ilaina ny kilasy adaptatera. Izy io dia natao amin'ny alàlan'ny tranomboky azo vakiana, izay mametraka ny renirano nodejs amin'ny navigateur:

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

Andeha isika hamorona fifandraisana ao amin'ny 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;
    }
}

Avy eo dia mamorona ny fifandraisana amin'ny script votoaty izahay:

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

Koa satria mila ny API tsy ao amin'ny script votoaty, fa mivantana amin'ny pejy, dia manao zavatra roa izahay:

  1. Mamorona renirano roa izahay. Iray - mankany amin'ny pejy, eo an-tampon'ny hafatra hafatra. Ho an'ity dia mampiasa ity izahay ity fonosana ity avy amin'ny mpamorona ny metamask. Ny stream faharoa dia ny fiafarany amin'ny seranan-tsambo azo avy runtime.connect. Andeha isika hividy azy ireo. Amin'izao fotoana izao dia hisy stream mankany ambadika ny pejy.
  2. Ampidiro ao amin'ny DOM ny script. Ampidino ny script (avela hiditra ao amin'ny manifest) ary mamorona marika script miaraka amin'ny ao anatiny:

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

Ankehitriny dia mamorona zavatra api ao amin'ny inpage isika ary mametraka izany amin'ny global:

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

Vonona izahay Antso amin'ny fomba lavitra (RPC) misy API misaraka ho an'ny pejy sy UI. Rehefa mampifandray pejy vaovao amin'ny lafika dia hitantsika izao:

Manoratra fanitarana navigateur azo antoka

Foana ny API sy ny fiaviany. Eo amin'ny ilany pejy dia afaka miantso ny fiasa hello toy izao isika:

Manoratra fanitarana navigateur azo antoka

Ny fiaraha-miasa amin'ny asa antso an-tariby ao amin'ny JS maoderina dia fomba ratsy, koa andao hanoratra mpanampy kely hamorona dnode ahafahanao mandefa zavatra API amin'ny utils.

Ny zavatra API dia ho toy izao:

export class SignerApp {

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

...

}

Mahazo zavatra avy lavitra toy izao:

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

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

Ary ny fiantsoana asa dia mamerina fampanantenana:

Manoratra fanitarana navigateur azo antoka

Dikan-teny misy fiasa asynchronous misy eto.

Amin'ny ankapobeny, ny fomba RPC sy ny stream dia toa mora azo: afaka mampiasa steam multiplexing isika ary mamorona API samihafa ho an'ny asa samihafa. Amin'ny ankapobeny, ny dnode dia azo ampiasaina na aiza na aiza, ny zava-dehibe indrindra dia ny mametaka ny fitaterana amin'ny endrika stream nodejs.

Ny hafa dia ny format JSON, izay mampihatra ny protocol JSON RPC 2. Na izany aza, miasa miaraka amin'ny fitaterana manokana (TCP sy HTTP(S)), izay tsy azo ampiharina amin'ny tranga misy antsika.

Fanjakana anatiny sy fitehirizana eo an-toerana

Mila mitahiry ny toetry ny atiny amin'ny fampiharana isika - farafaharatsiny ny fanalahidin'ny sonia. Azontsika atao mora foana ny manampy fanjakana amin'ny fampiharana sy ny fomba fanovana azy ao amin'ny 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)
        }
    }

    ...

} 

Ao ambadika, hofonosinay ny zava-drehetra ary soraty amin'ny varavarankely ny zavatra fampiharana mba ahafahantsika miasa miaraka amin'ny 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)
        }
    }
}

Andao hanampy fanalahidy vitsivitsy avy amin'ny console UI ary hijery izay mitranga amin'ny fanjakana:

Manoratra fanitarana navigateur azo antoka

Mila atao maharitra ny fanjakana mba tsy ho very ny lakile rehefa manomboka indray.

Hotehirizinay ao amin'ny localStorage izany, hosoloina amin'ny fanovana rehetra. Aorian'izay dia ilaina ihany koa ny fidirana amin'izany ho an'ny UI, ary te-hisoratra anarana amin'ny fanovana ihany koa aho. Mifototra amin'izany dia ho mora ny mamorona fitahirizana azo jerena ary misoratra anarana amin'ny fanovana azy.

Hampiasa ny tranomboky mobx izahay (https://github.com/mobxjs/mobx). Ny safidy dia nianjera tamin'izany satria tsy voatery niasa tamin'izany aho, fa tena te hianatra azy io aho.

Andao ampio ny fanombohana ny fanjakana voalohany ary ataovy azo jerena ny fivarotana:

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

    ...

}

"Eo ambanin'ny saron-tava," nosoloin'ny mobx ny saha fivarotana rehetra amin'ny proxy ary manakana ny antso rehetra amin'izy ireo. Azo atao ny misoratra anarana amin'ireo hafatra ireo.

Eto ambany aho dia matetika mampiasa ny teny hoe "rehefa miova", na dia tsy marina tanteraka aza izany. Mobx dia manara-maso ny fidirana amin'ny saha. Mpahazo sy mpanorina zavatra proxy izay noforonin'ny tranomboky no ampiasaina.

Ny mpanao haingon-trano dia manana tanjona roa:

  1. Amin'ny fomba hentitra miaraka amin'ny saina enforceActions, ny mobx dia mandrara ny fanovana mivantana ny fanjakana. Heverina ho fanao tsara ny miasa amin'ny fepetra hentitra.
  2. Na dia manova ny fanjakana imbetsaka aza ny fiasa iray - ohatra, manova sehatra maromaro amin'ny andalana maromaro misy kaody isika - rehefa vita izany dia tsy ampahafantarina ny mpanara-maso. Zava-dehibe indrindra izany ho an'ny frontend, izay mahatonga ny fanavaozam-panjakana tsy ilaina amin'ny famoahana singa tsy ilaina. Amin'ny tranga misy antsika, na ny voalohany na ny faharoa dia tsy misy dikany manokana, fa hanaraka ny fanao tsara indrindra isika. Fanaon'ny mpanao haingo amin'ny asa rehetra izay manova ny toetry ny saha voamarika.

Ao ambadika dia hanampy fanombohana sy hamonjy ny fanjakana ao amin'ny 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)
        }
    }
}

Mahaliana ny fiasan'ny fanehoan-kevitra eto. Misy hevitra roa izy io:

  1. Mpifidy data.
  2. Mpitantana izay hantsoina miaraka amin'ity data ity isaky ny miova izy.

Tsy sahala amin'ny redux, izay andraisantsika mazava ny fanjakana ho tohan-kevitra, tsaroan'i mobx hoe iza no azo jerena ao anatin'ny mpifidy, ary miantso ny mpitantana rehefa miova izy ireo.

Zava-dehibe ny mahatakatra tsara ny fomba fanapahan-kevitry ny mobx hoe iza amin'ireo azo jerena no abontsinay. Raha nanoratra mpifidy amin'ny kaody toy izao aho() => app.store, dia tsy hantsoina mihitsy ny fanehoan-kevitra, satria tsy hita maso ny fitahirizana, fa ny sahany ihany no hita.

Raha toa izao no nanoratako azy () => app.store.keys, dia tsy hisy na inona na inona hitranga intsony, satria rehefa manampy / manala ireo singa array dia tsy hiova ny fanondroana azy.

Sambany i Mobx no miasa toy ny mpifidy ary manara-maso ireo zavatra azo jerena ihany. Izany dia atao amin'ny alalan'ny proxy getters. Noho izany, ny asa naorina-in no ampiasaina eto toJS. Izy io dia mamerina zavatra vaovao miaraka amin'ny proxy rehetra nosoloina ny saha voalohany. Mandritra ny famonoana dia mamaky ny sahan'ny zavatra rehetra izy - noho izany dia mipoitra ny getter.

Ao amin'ny console pop-up dia hampiditra fanalahidy maromaro indray isika. Tamin'ity indray mitoraka ity dia nifarana tao amin'ny localStorage ihany koa izy ireo:

Manoratra fanitarana navigateur azo antoka

Rehefa averina alefa ny pejy ambadika dia mijanona eo amin'ny toerany ny fampahalalana.

Ny kaody fampiharana rehetra hatramin'izao dia azo jerena eto.

Fitehirizana azo antoka ny fanalahidy manokana

Ny fitehirizana fanalahidy manokana amin'ny lahatsoratra mazava dia tsy azo antoka: misy foana ny mety ho voasokajy, mahazo fidirana amin'ny solosainao, sns. Noho izany, ao amin'ny localStorage dia hitahiry ny lakile amin'ny endrika encryption ny tenimiafina.

Ho an'ny fiarovana bebe kokoa dia hampiditra fanjakana mihidy amin'ny fampiharana izahay, izay tsy hisy fidirana amin'ny fanalahidy mihitsy. Hafindra ho azy ho any amin'ny fanjakana mihidy ny fanitarana noho ny fahatapahan'ny fotoana.

Mobx dia ahafahanao mitahiry angon-drakitra faran'izay kely fotsiny, ary ny ambiny dia kajy mandeha ho azy mifototra amin'izany. Ireo no atao hoe fananana kajy. Izy ireo dia azo ampitahaina amin'ny fomba fijery amin'ny angon-drakitra:

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

Amin'izao fotoana izao, ny fanalahidy sy ny tenimiafina voatahiry fotsiny. Ny sisa rehetra dia kajy. Manao ny famindrana ho any amin'ny fanjakana mihidy izahay amin'ny fanesorana ny tenimiafina amin'ny fanjakana. Ny API ho an'ny daholobe izao dia manana fomba hanombohana ny fitahirizana.

Nosoratana ho an'ny encryption fitaovana mampiasa 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)
}

Ny navigateur dia manana API tsy miasa izay ahafahanao misoratra anarana amin'ny hetsika iray - fanovana fanjakana. Fanjakana, araka izany, dia mety ho idle, active и locked. Ho an'ny tsy miasa dia afaka mametraka fe-potoana ianao, ary voahidy rehefa voasakana ny OS. Hanova ny mpifidy hotehirizina ao amin'ny localStorage ihany koa izahay:

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

Ny kaody alohan'ity dingana ity dia eto.

varotra

Noho izany, tonga amin'ny zava-dehibe indrindra isika: mamorona sy manao sonia ny fifanakalozana amin'ny blockchain. Hampiasa ny blockchain sy ny tranomboky WAVES izahay onja-transactions.

Voalohany, ndao ampidiro ao amin'ny fanjakana ny andiana hafatra mila sonia, avy eo ampio fomba hanampiana hafatra vaovao, fanamafisana ny sonia, ary fandavana:

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

    ...
}

Rehefa mahazo hafatra vaovao izahay dia ampiana metadata izany, ataovy observable ary ampio store.messages.

Raha tsy manao izany ianao observable amin'ny tanana, dia hanao izany ny mobx rehefa manampy hafatra amin'ny array. Na izany aza, hamorona zavatra vaovao izay tsy hanana reference isika, fa mila izany amin'ny dingana manaraka.

Manaraka izany dia mamerina fampanantenana izay mamaha rehefa miova ny satan'ny hafatra. Ny sata dia manara-maso ny fanehoan-kevitra, izay "hamono tena" rehefa miova ny sata.

Kaody fomba approve и reject tena tsotra: manova fotsiny ny satan'ny hafatra, rehefa avy nanao sonia izany raha ilaina.

Napetrakay ao amin'ny UI API ny Approve sy reject, newMessage ao amin'ny page 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)
        }
    }

    ...
}

Andeha isika hanasonia ny fifanarahana amin'ny fanitarana:

Manoratra fanitarana navigateur azo antoka

Amin'ny ankapobeny, efa vonona ny zava-drehetra, izay sisa tavela ampio UI tsotra.

UI

Mila fidirana amin'ny toetry ny fampiharana ny interface. Amin'ny lafiny UI no hataontsika observable fanjakana ary ampio fiasa amin'ny API izay hanova io fanjakana io. Andeha ampio observable mankany amin'ny zavatra API azo avy any aoriana:

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

Amin'ny farany dia manomboka manome ny interface interface isika. Ity dia fampiharana react. Ampitaina amin'ny fampiasana props fotsiny ny zavatra ambadika. Mazava ho azy fa marina ny manao serivisy manokana ho an'ny fomba sy fivarotana ho an'ny fanjakana, fa ho an'ny tanjon'ity lahatsoratra ity dia ampy izany:

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

Miaraka amin'ny mobx dia tena mora ny manomboka mamadika rehefa miova ny angona. Ahantona fotsiny eo amin'ny fonosana ny haingo mpandinika mobx-react eo amin'ny singa, ary ny render dia antsoina ho azy rehefa misy fiovana azo jerena amin'ny singa. Tsy mila mapStateToProps ianao na mifandray toy ny amin'ny redux. Miasa avy hatrany ny zava-drehetra:

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

Ny singa sisa tavela dia azo jerena ao amin'ny code ao amin'ny lahatahiry UI.

Ankehitriny ao amin'ny kilasin'ny fampiharana dia mila manao mpifidy fanjakana ho an'ny UI ianao ary mampandre ny UI rehefa miova izany. Mba hanaovana izany, ndao hanampy fomba getState и reactionfiantsoana 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())

        })
    }

    ...
}

Rehefa mandray zavatra remote namorona reaction hanova ny fanjakana izay miantso ny asa eo amin'ny lafiny UI.

Ny fikasihana farany dia ny manampy ny fampisehoana hafatra vaovao amin'ny kisary fanitarana:

function setupApp() {
...

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

...
}

Noho izany, vonona ny fampiharana. Ny pejy web dia mety hangataka sonia ho an'ny fifampiraharahana:

Manoratra fanitarana navigateur azo antoka

Manoratra fanitarana navigateur azo antoka

Hita eto ny kaody rohy.

famaranana

Raha namaky ny lahatsoratra hatramin'ny farany ianao, nefa mbola manana fanontaniana, dia azonao atao ny manontany azy ireo amin'ny repository misy fanitarana. Any ianao dia hahita fanoloran-tena ho an'ny dingana voatondro tsirairay.

Ary raha liana amin'ny fijerena ny kaody ho an'ny fanitarana tena izy ianao dia afaka mahita izany eto.

Code, repository ary famaritana asa avy amin'ny siemarell

Source: www.habr.com

Add a comment