Навиштани васеъшавии браузери бехатар

Навиштани васеъшавии браузери бехатар

Баръакси меъмории маъмули "мизоҷ-сервер", барномаҳои ғайримарказӣ бо инҳо тавсиф мешаванд:

  • Нигоҳ доштани пойгоҳи додаҳо бо логинҳо ва паролҳои корбар лозим нест. Маълумоти дастрасӣ танҳо аз ҷониби худи корбарон нигоҳ дошта мешавад ва тасдиқи ҳаққонияти онҳо дар сатҳи протокол сурат мегирад.
  • Истифодаи сервер лозим нест. Мантиқи барномаро метавон дар шабакаи blockchain иҷро кард, ки дар он миқдори зарурии маълумотро нигоҳ доштан мумкин аст.

2 анбори нисбатан бехатар барои калидҳои корбар мавҷуданд - ҳамёнҳои сахтафзор ва васеъшавии браузер. Ҳамёнҳои сахтафзор асосан бениҳоят бехатаранд, аммо истифодаашон мушкил ва аз озод дур аст, аммо васеъшавии браузер маҷмӯи комили амният ва осонии истифода мебошанд ва инчунин метавонанд барои корбарони ниҳоӣ комилан ройгон бошанд.

Бо дарназардошти ҳамаи ин, мо мехостем, ки тамдиди бехатартаринеро созем, ки таҳияи замимаҳои ғайримарказиро тавассути пешниҳоди API оддӣ барои кор бо транзаксияҳо ва имзоҳо осонтар мекунад.
Мо ба шумо дар бораи ин таҷриба дар поён нақл мекунем.

Дар мақола дастурҳои зина ба зина дар бораи чӣ гуна навиштани тамдиди браузер бо мисолҳои код ва скриншотҳо оварда мешаванд. Шумо метавонед ҳамаи кодҳоро дар анборҳо. Ҳар як иҷро мантиқӣ ба як қисми ин мақола мувофиқат мекунад.

Таърихи мухтасари васеъшавии браузер

Васеъ кардани браузер муддати тӯлонӣ вуҷуд дорад. Онҳо дар Internet Explorer дар соли 1999, дар Firefox дар соли 2004 пайдо шуданд. Бо вуҷуди ин, дар муддати хеле тӯлонӣ стандарти ягонаи васеъкунӣ вуҷуд надошт.

Мо гуфта метавонем, ки он дар баробари васеъшавӣ дар версияи чоруми Google Chrome пайдо шудааст. Албатта, он вақт ягон мушаххасот вуҷуд надошт, аммо ин Chrome API буд, ки асоси он гардид: бо забт кардани аксари бозори браузер ва дорои мағозаи дарунсохт, Chrome воқеан стандартро барои васеъшавии браузер муқаррар кард.

Mozilla стандарти худро дошт, аммо бо дидани маъруфияти васеъшавии Chrome, ширкат тасмим гирифт, ки API-и мувофиқ созад. Дар соли 2015, бо ташаббуси Mozilla, дар доираи Консорсиуми умумиҷаҳонии веб (W3C) як гурӯҳи махсус таъсис дода шуд, ки дар мушаххасоти васеъкунии кросс-браузер кор мекунад.

Васеъкуниҳои мавҷудаи API барои Chrome ҳамчун асос гирифта шуданд. Кор бо дастгирии Microsoft анҷом дода шуд (Google аз иштирок дар таҳияи стандарт даст кашид) ва дар натиҷа лоиҳа пайдо шуд. хусусиятҳои.

Ба таври расмӣ, мушаххасот аз ҷониби Edge, Firefox ва Opera дастгирӣ карда мешавад (дар хотир доред, ки Chrome дар ин рӯйхат нест). Аммо дар асл, стандарт асосан бо Chrome мувофиқ аст, зеро он воқеан дар асоси васеъшавии он навишта шудааст. Шумо метавонед дар бораи API WebExtensions бештар хонед дар ин ҷо.

Сохтори васеъшавӣ

Ягона файле, ки барои васеъкунӣ талаб карда мешавад, манифест (manifest.json). Он инчунин "нуқтаи вуруд" ба густариш аст.

Нишондиҳанда

Тибқи мушаххасот, файли манифест файли дурусти JSON мебошад. Тавсифи пурраи калидҳои манифест бо маълумот дар бораи он, ки кадом калидҳоро дар кадом браузер дидан мумкин аст дар ин ҷо.

Калидҳое, ки дар мушаххасот "метавонад" нестанд, сарфи назар карда шаванд (ҳам Chrome ва ҳам Firefox дар бораи хатогиҳо гузориш медиҳанд, аммо васеъшавӣ кор карданро идома медиҳанд).

Ва ман мехостам ба баъзе нуктахо диккат дихам.

  1. замина — объекте, ки майдонҳои зеринро дар бар мегирад:
    1. сутунҳо — маҷмӯи скриптҳо, ки дар заминаи замина иҷро мешаванд (дар ин бора каме дертар сӯҳбат хоҳем кард);
    2. саҳифа - ба ҷои скриптҳое, ки дар саҳифаи холӣ иҷро мешаванд, шумо метавонед html-ро бо мундариҷа муайян кунед. Дар ин ҳолат, майдони скрипт нодида гирифта мешавад ва скриптҳо бояд ба саҳифаи мундариҷа ворид карда шаванд;
    3. устувор будан — парчами дуӣ, агар нишон дода нашуда бошад, браузер раванди пасзаминаро вақте ҳисоб мекунад, ки ҳеҷ кор намекунад ва агар лозим бошад, онро аз нав оғоз мекунад. Дар акси ҳол, саҳифа танҳо ҳангоми баста шудани браузер холӣ карда мешавад. Дар Firefox дастгирӣ намешавад.
  2. content_scripts — маҷмӯи объектҳое, ки ба шумо имкон медиҳанд, ки скриптҳои гуногунро ба саҳифаҳои интернетии гуногун бор кунед. Ҳар як объект дорои майдонҳои муҳими зерин аст:
    1. гӯгирд - URL-и намуна, ки муайян мекунад, ки скрипти мундариҷаи мушаххас дохил карда мешавад ё не.
    2. js — рӯйхати скриптҳое, ки ба ин бозӣ бор карда мешаванд;
    3. истиснои_матолиб - аз майдон хориҷ мекунад match URL-ҳое, ки ба ин майдон мувофиқанд.
  3. саҳифа_амал - воқеан объектест, ки барои нишонае, ки дар паҳлӯи сатри суроғаҳо дар браузер нишон дода мешавад ва ҳамкорӣ бо он масъул аст. Он инчунин ба шумо имкон медиҳад, ки равзанаи поп-апро намоиш диҳед, ки он бо истифода аз HTML, CSS ва JS-и худ муайян карда мешавад.
    1. default_pup — роҳ ба файли HTML бо интерфейси поп-ап, метавонад дорои CSS ва JS бошад.
  4. иҷозатномаҳоро — массив барои идоракунии ҳуқуқҳои васеъкунӣ. 3 намуди ҳуқуқ вуҷуд дорад, ки ба таври муфассал тавсиф шудаанд дар ин ҷо
  5. web_accessible_sources — захираҳои васеъшавӣ, ки саҳифаи веб метавонад дархост кунад, масалан, тасвирҳо, JS, CSS, файлҳои HTML.
  6. берунӣ_пайвастшаванда — дар ин ҷо шумо метавонед идентификаторҳои дигар васеъкуниҳо ва доменҳои веб-саҳифаҳоеро, ки шумо метавонед аз онҳо пайваст шавед, ба таври возеҳ муайян кунед. Домен метавонад дараҷаи дуюм ё баландтар бошад. Дар Firefox кор намекунад.

Контексти иҷро

Васеъ се контексти иҷрои код дорад, яъне барнома аз се қисм бо сатҳҳои гуногуни дастрасӣ ба API браузер иборат аст.

Контексти васеъшавӣ

Аксари API дар ин ҷо дастрасанд. Дар ин замина онҳо "зиндагӣ" мекунанд:

  1. Саҳифаи замина - қисми "пушти"-и васеъшавӣ. Файл дар манифест бо истифода аз калиди "замина" муайян карда мешавад.
  2. Саҳифаи поп-ап — саҳифаи поп-ап, ки ҳангоми пахш кардани тасвири васеъшавӣ пайдо мешавад. Дар манифест browser_action -> default_popup.
  3. Саҳифаи фармоишӣ — саҳифаи васеъ, «зиндагӣ» дар ҷадвали алоҳидаи намуди chrome-extension://<id_расширения>/customPage.html.

Ин контекст новобаста аз равзанаҳои браузер ва ҷадвалҳо вуҷуд дорад. Саҳифаи замина дар як нусха мавҷуд аст ва ҳамеша кор мекунад (истисно саҳифаи рӯйдодҳост, вақте ки скрипти замина аз ҷониби ҳодиса оғоз мешавад ва пас аз иҷрои он "мемирад"). Саҳифаи поп-ап вуҷуд дорад, вақте ки равзанаи поп-ап кушода аст, ва Саҳифаи фармоишӣ — дар ҳоле ки ҷадвал бо он кушода аст. Аз ин контекст дастрасӣ ба ҷадвалҳои дигар ва мундариҷаи онҳо вуҷуд надорад.

Контексти скрипти мундариҷа

Файли скрипти мундариҷа дар якҷоягӣ бо ҳар як ҷадвали браузер оғоз карда мешавад. Он ба як қисми API-и васеъшавӣ ва дарахти DOM-и саҳифаи веб дастрасӣ дорад. Ин скриптҳои мундариҷа мебошанд, ки барои ҳамкорӣ бо саҳифа масъуланд. Васеъкуниҳо, ки дарахти DOM-ро идора мекунанд, инро дар скриптҳои мундариҷа иҷро мекунанд - масалан, блокаторҳои таблиғ ё тарҷумонҳо. Инчунин, скрипти мундариҷа метавонад бо саҳифа тавассути стандарт муошират кунад postMessage.

Контексти саҳифаи веб

Ин худи саҳифаи воқеии веб аст. Он ба васеъшавӣ ҳеҷ иртиботе надорад ва ба он ҷо дастрасӣ надорад, ба истиснои ҳолатҳое, ки домени ин саҳифа дар манифест ба таври возеҳ нишон дода нашудааст (бештар дар ин бора дар зер).

Паёмнависӣ

Қисмҳои гуногуни барнома бояд бо ҳамдигар паём мубодила кунанд. Барои ин API мавҷуд аст runtime.sendMessage фиристодани паём background и tabs.sendMessage барои фиристодани паём ба саҳифа (скрипти мундариҷа, поп-ап ё саҳифаи веб, агар дастрас бошад externally_connectable). Дар зер як мисол ҳангоми дастрасӣ ба API Chrome оварда шудааст.

// Сообщением может быть любой 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))
    }
)

Барои муоширати пурра, шумо метавонед пайвастҳоро тавассути runtime.connect. Дар ҷавоб мо мегирем runtime.Port, ки дар ҳоле ки он кушода аст, шумо метавонед шумораи дилхоҳи паёмҳоро фиристед. Аз ҷониби муштарӣ, масалан, contentscript, чунин ба назар мерасад:

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

Сервер ё замина:

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

Инчунин як чорабинӣ вуҷуд дорад onDisconnect ва усул disconnect.

Диаграммаи татбиқ

Биёед васеъшавии браузерро созем, ки калидҳои хусусиро нигоҳ медорад, дастрасӣ ба иттилооти умумиро таъмин мекунад (суроға, калиди ҷамъиятӣ бо саҳифа муошират мекунад ва ба замимаҳои тарафи сеюм имкон медиҳад, ки имзои транзаксияҳоро дархост кунанд.

Таҳияи барнома

Замимаи мо бояд ҳам бо корбар муошират кунад ва ҳам саҳифаро бо API барои даъват кардани усулҳо таъмин кунад (масалан, барои имзои транзаксия). Танҳо бо як чиз кор кунед contentscript кор намекунад, зеро он танҳо ба DOM дастрасӣ дорад, аммо на ба JS саҳифа. Пайвастшавӣ тавассути runtime.connect мо наметавонем, зеро API дар ҳама доменҳо лозим аст ва дар манифест танҳо доменҳои мушаххасро метавон нишон дод. Дар натиҷа, диаграмма чунин хоҳад буд:

Навиштани васеъшавии браузери бехатар

Скрипти дигар хоҳад буд - inpage, ки мо онро ба саҳифа ворид мекунем. Он дар контексти худ кор мекунад ва API-ро барои кор бо тамдид таъмин мекунад.

Оғоз

Ҳама рамзи васеъшавии браузер дар ин ҷо дастрас аст GitHub. Дар давоми тавсиф пайвандҳо ба ӯҳдадориҳо хоҳанд буд.

Биёед бо манифест оғоз кунем:

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

background.js, popup.js, inpage.js ва contentscript.js-и холӣ эҷод кунед. Мо popup.html -ро илова мекунем - ва замимаи мо аллакай метавонад ба Google Chrome бор карда шавад ва боварӣ ҳосил кунед, ки он кор мекунад.

Барои тасдиқи ин, шумо метавонед рамзро гиред аз ин ҷо. Илова бар он коре, ки мо анҷом додем, истиноди конфигуратсияи лоиҳаро бо истифода аз webpack танзим кард. Барои илова кардани замима ба браузер, дар chrome://extensions шумо бояд load unpacked ва ҷузвдони дорои тамдиди мувофиқро интихоб кунед - дар ҳолати мо dist.

Навиштани васеъшавии браузери бехатар

Ҳоло васеъшавии мо насб ва кор мекунад. Шумо метавонед асбобҳои таҳиякунандаро барои контекстҳои гуногун ба таври зерин иҷро кунед:

пайдошуда ->

Навиштани васеъшавии браузери бехатар

Дастрасӣ ба консоли скрипти мундариҷа тавассути консоли худи саҳифа, ки дар он кушода шудааст, сурат мегирад.Навиштани васеъшавии браузери бехатар

Паёмнависӣ

Ҳамин тавр, мо бояд ду канали алоқаро таъсис диҳем: дар саҳифаи <-> замина ва поп-ап <-> замина. Шумо метавонед, албатта, танҳо ба порт паём фиристед ва протоколи шахсии худро ихтироъ кунед, аммо ман равишеро, ки дар лоиҳаи кушодаи метамаск дидам, бартарӣ медиҳам.

Ин васеъшавии браузер барои кор бо шабакаи Ethereum мебошад. Дар он қисмҳои гуногуни барнома тавассути RPC бо истифода аз китобхонаи dnode муошират мекунанд. Он ба шумо имкон медиҳад, ки мубодиларо зуд ва ба осонӣ ташкил кунед, агар шумо онро бо ҷараёни nodejs ҳамчун нақлиёт таъмин кунед (маънои объекте, ки ҳамон интерфейсро амалӣ мекунад):

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

Акнун мо синфи барнома эҷод мекунем. Он барои поп-ап ва саҳифаи веб объектҳои API эҷод мекунад ва барои онҳо dnode эҷод мекунад:

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

Дар ин ҷо ва дар поён, ба ҷои объекти глобалии Chrome, мо extensionApi-ро истифода мебарем, ки ба Chrome дар браузери Google ва дар дигар браузерҳо дастрасӣ пайдо мекунад. Ин барои мутобиқати байни браузерҳо анҷом дода мешавад, аммо барои мақсадҳои ин мақола, шумо метавонед танҳо 'chrome.runtime.connect' -ро истифода баред.

Биёед як мисоли барномаро дар скрипти замина эҷод кунем:

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

Азбаски dnode бо ҷараёнҳо кор мекунад ва мо порт мегирем, синфи адаптер лозим аст. Он бо истифода аз китобхонаи readable-stream сохта шудааст, ки ҷараёнҳои nodejs-ро дар браузер амалӣ мекунад:

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

Акнун биёед пайвастро дар 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;
    }
}

Сипас, мо пайвастро дар скрипти мундариҷа эҷод мекунем:

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

Азбаски мо ба API на дар скрипти мундариҷа, балки мустақиман дар саҳифа ниёз дорем, мо ду чизро иҷро мекунем:

  1. Мо ду ҷараёнро эҷод мекунем. Яке - ба сӯи саҳифа, дар болои postMessage. Барои ин мо инро истифода мебарем ин баста аз созандагони метамаск. Ҷараёни дуюм аст, ки ба замина болои бандари гирифта аз runtime.connect. Биёед онҳоро бихарем. Акнун саҳифа ҷараён ба замина хоҳад дошт.
  2. Скриптро ба DOM ворид кунед. Скриптро зеркашӣ кунед (дастрасӣ ба он дар манифест иҷозат дода шудааст) ва тег эҷод кунед script бо мазмуни даруни он:

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

Ҳоло мо дар саҳифаи дохилӣ объекти api эҷод мекунем ва онро ба глобалӣ муқаррар мекунем:

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

Мо тайёрем Даъвати протседураҳои дурдаст (RPC) бо API алоҳида барои саҳифа ва UI. Ҳангоми пайваст кардани саҳифаи нав ба замина мо инро мебинем:

Навиштани васеъшавии браузери бехатар

API-и холӣ ва пайдоиш. Дар паҳлӯи саҳифа мо метавонем функсияи hello-ро чунин даъват кунем:

Навиштани васеъшавии браузери бехатар

Кор бо функсияҳои бозпас занг дар JS муосир одоби бад аст, аз ин рӯ биёед як ёвари хурдеро барои сохтани dnode нависем, ки ба шумо имкон медиҳад объекти API-ро ба utils интиқол диҳед.

Объектҳои API ҳоло чунин хоҳанд буд:

export class SignerApp {

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

...

}

Гирифтани объект аз дурдаст чунин:

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

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

Ва даъват кардани функсияҳо ваъда медиҳад:

Навиштани васеъшавии браузери бехатар

Версия бо функсияҳои асинхронӣ дастрас аст дар ин ҷо.

Дар маҷмӯъ, равиши RPC ва ҷараён хеле чандир ба назар мерасад: мо метавонем мултиплекси буғро истифода барем ва барои вазифаҳои гуногун якчанд API-ҳои гуногун эҷод кунем. Аслан, dnode-ро дар ҳама ҷо истифода бурдан мумкин аст, чизи асосӣ ин аст, ки интиқол дар шакли ҷараёни nodejs печонида шавад.

Алтернатива формати JSON мебошад, ки протоколи JSON RPC 2-ро амалӣ мекунад.Аммо он бо интиқолҳои мушаххас кор мекунад (TCP ва HTTP(S)), ки дар ҳолати мо татбиқ намешавад.

Захираи дохилӣ ва маҳаллӣ

Мо бояд ҳолати дохилии барномаро нигоҳ дорем - ҳадди аққал калидҳои имзо. Мо метавонем ба осонӣ ҳолатро ба барнома ва усулҳои тағир додани он дар 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)
        }
    }

    ...

} 

Дар замина, мо ҳама чизро дар функсия мепӯшем ва объекти барномаро ба тиреза менависем, то ки мо бо он аз консол кор кунем:

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

Биёед аз консоли UI чанд калидро илова кунем ва бубинем, ки бо ҳолат чӣ мешавад:

Навиштани васеъшавии браузери бехатар

Ҳолатро устувор кардан лозим аст, то калидҳо ҳангоми аз нав оғоз кардан гум нашаванд.

Мо онро дар localStorage нигоҳ медорем ва бо ҳар як тағирот онро аз нав сабт мекунем. Минбаъд, дастрасӣ ба он барои UI низ зарур хоҳад буд ва ман низ мехоҳам ба тағйирот обуна шавам. Дар асоси ин, сохтани анбори мушоҳидашаванда ва обуна ба тағйироти он қулай хоҳад буд.

Мо китобхонаи mobx (https://github.com/mobxjs/mobx). Интихоб ба он афтод, зеро ман маҷбур набудам, ки бо он кор кунам, аммо ман дар ҳақиқат мехостам онро омӯзам.

Биёед оғози ҳолати ибтидоиро илова кунем ва мағозаро мушоҳидашаванда гардонем:

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

    ...

}

"Дар зери кулоҳ" mobx ҳама майдонҳои мағозаро бо прокси иваз кард ва ҳама зангҳоро ба онҳо бозмедорад. Ба ин хабархо обуна шудан мумкин аст.

Дар зер ман аксар вақт истилоҳи "ҳангоми тағир" -ро истифода хоҳам кард, гарчанде ки ин комилан дуруст нест. Mobx дастрасӣ ба майдонҳоро пайгирӣ мекунад. Гирандагон ва танзимкунандагони объектҳои прокси, ки китобхона эҷод мекунад, истифода мешаванд.

Декораторҳои амалиёт ду ҳадафро иҷро мекунанд:

  1. Дар реҷаи қатъӣ бо парчами enforceActions, mobx тағир додани ҳолати мустақимро манъ мекунад. Дар шароити сахт кор карданро тачрибаи хуб хисоб мекунанд.
  2. Ҳатто агар функсия вазъиятро якчанд маротиба тағир диҳад - масалан, мо якчанд майдонҳоро дар якчанд сатри код иваз мекунем - нозирон танҳо ҳангоми анҷом додани он огоҳ карда мешаванд. Ин махсусан барои фронтент муҳим аст, ки дар он навсозиҳои нолозими давлатӣ ба намоиши нолозими элементҳо оварда мерасонанд. Дар шароити мо на якум ва на дуюм махсусан дахл надорад, вале мо ба тачрибаи пешкадам пайравй мекунем. Ба хамаи вазифахое, ки холати майдонхои мушохидашударо тагйир медиханд, ороишгаронро часпондан одат шудааст.

Дар замина мо оғозсозӣ ва захира кардани ҳолатро дар 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)
        }
    }
}

Функсияи реаксия дар ин ҷо ҷолиб аст. Он ду далел дорад:

  1. Интихобкунандаи маълумот.
  2. Коркунандае, ки ҳар дафъае, ки он тағир меёбад, бо ин маълумот даъват карда мешавад.

Баръакси redux, ки мо ба таври возеҳ ҳолатро ҳамчун аргумент қабул мекунем, mobx ба ёд меорад, ки мо кадоме аз мушоҳидашавандаҳоро дар дохили селектор дастрас мекунем ва танҳо ҳангоми тағир додани онҳо коркардкунандаро даъват мекунад.

Муҳим аст, ки дақиқ фаҳмед, ки чӣ гуна mobx қарор мекунад, ки мо ба кадом мушоҳидаҳои мушоҳидашаванда обуна мешавем. Агар ман як селекторро дар код чунин навиштам() => app.store, он гоҳ реаксия ҳеҷ гоҳ даъват карда намешавад, зеро худи анбор мушоҳидашаванда нест, танҳо майдонҳои он ҳастанд.

Агар ман хамин тавр нависам () => app.store.keys, пас боз чизе рӯй намедиҳад, зеро ҳангоми илова кардан/нест кардани элементҳои массив истинод ба он тағир намеёбад.

Mobx бори аввал ҳамчун селектор амал мекунад ва танҳо чизҳои мушоҳидашавандаеро, ки мо дастрас кардаем, пайгирӣ мекунад. Ин тавассути дастраскунандагони прокси анҷом дода мешавад. Аз ин рӯ, дар ин ҷо функсияи дарунсохт истифода мешавад toJS. Он объекти навро бо ҳамаи проксиҳои бо майдонҳои аслӣ ивазшуда бармегардонад. Ҳангоми иҷро, он тамоми майдонҳои объектро мехонад - аз ин рӯ гетерҳо ба кор андохта мешаванд.

Дар консоли поп-ап мо боз якчанд калидҳоро илова мекунем. Ин дафъа онҳо инчунин дар localStorage анҷом ёфтанд:

Навиштани васеъшавии браузери бехатар

Вақте ки саҳифаи замина дубора бор карда мешавад, маълумот дар ҷои худ мемонад.

Ҳама рамзи барномаро то ин лаҳза дидан мумкин аст дар ин ҷо.

Нигоҳдории бехатари калидҳои хусусӣ

Нигоҳ доштани калидҳои хусусӣ дар матни равшан хатарнок аст: ҳамеша эҳтимолияти ҳакерӣ, дастрасӣ ба компютери шумо ва ғайра вуҷуд дорад. Аз ин рӯ, дар localStorage мо калидҳоро дар шакли рамзгузоришуда нигоҳ медорем.

Барои бехатарии бештар, мо ба барнома ҳолати қулфшуда илова мекунем, ки дар он ба калидҳо умуман дастрасӣ надоранд. Мо ба таври худкор тамдидро ба ҳолати қуфл бо сабаби ба охир расидани вақт интиқол медиҳем.

Mobx ба шумо имкон медиҳад, ки танҳо маҷмӯи ҳадди ақали маълумотро нигоҳ доред ва боқимонда дар асоси он ба таври худкор ҳисоб карда мешавад. Инҳо хосиятҳои ба ном ҳисобшуда мебошанд. Онҳоро бо намоишҳо дар пойгоҳи додаҳо муқоиса кардан мумкин аст:

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

Ҳоло мо танҳо калидҳо ва паролро нигоҳ медорем. Ҳама чизи дигар ҳисоб карда мешавад. Мо интиқолро ба ҳолати қулфшуда тавассути хориҷ кардани парол аз давлат анҷом медиҳем. Ҳоло API-и оммавӣ усули оғоз кардани нигаҳдорӣ дорад.

Барои рамзгузорӣ навишта шудааст коммуналӣ бо истифода аз 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)
}

Браузер дорои API-и ғайрифаъол аст, ки тавассути он шумо метавонед ба рӯйдод обуна шавед - тағироти ҳолати. Давлат, мутаносибан, метавонад бошад idle, active и locked. Барои бекорӣ, шумо метавонед вақти фарогириро муқаррар кунед ва қуфл ҳангоми баста шудани худи ОС муқаррар карда мешавад. Мо инчунин селекторро барои захира кардан ба 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)
        }
    }
}

Рамзи пеш аз ин қадам аст дар ин ҷо.

Амалиётҳо

Ҳамин тавр, мо ба чизи муҳимтарин мерасем: эҷод ва имзо кардани транзаксияҳо дар blockchain. Мо блокчейн ва китобхонаи WAVES-ро истифода хоҳем бурд мавҷҳои амалиёти.

Аввалан, биёед ба давлат як қатор паёмҳоеро илова кунем, ки бояд имзо карда шаванд, пас усулҳои илова кардани паёми нав, тасдиқи имзо ва радкуниро илова кунед:

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

    ...
}

Вақте ки мо паёми нав мегирем, мо ба он метамаълумот илова мекунем, иҷро кунед observable ва илова кунед store.messages.

Агар шумо не observable ба таври дастӣ, пас mobx ин корро худаш ҳангоми илова кардани паёмҳо ба массив анҷом медиҳад. Бо вуҷуди ин, он объекти наверо эҷод мекунад, ки мо ба он истинод надорем, аммо барои қадами оянда ба мо лозим меояд.

Баъдан, мо ваъдаеро бармегардонем, ки ҳангоми тағир додани ҳолати паём ҳал мешавад. Вазъ аз ҷониби реаксия назорат карда мешавад, ки ҳангоми тағир додани вазъ "худро мекушад".

Рамзи усул approve и reject хеле оддӣ: мо танҳо пас аз имзои он, агар лозим бошад, ҳолати паёмро тағир медиҳем.

Мо Тасдиқ ва радро дар API API, newMessage -ро дар саҳифаи 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)
        }
    }

    ...
}

Акнун биёед кӯшиш кунем, ки транзаксияро бо тамдид имзо кунем:

Навиштани васеъшавии браузери бехатар

Умуман, ҳама чиз омода аст, ҳама чиз боқӣ мемонад UI оддӣ илова кунед.

UI

Интерфейс ба ҳолати барнома дастрасӣ дорад. Дар тарафи UI мо кор хоҳем кард observable давлат ва ба API функсияе илова кунед, ки ин ҳолатро тағир медиҳад. илова кунем observable ба объекти API аз замина гирифта шудааст:

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

Дар охир мо ба намоиш додани интерфейси барнома шурӯъ мекунем. Ин як барномаи реаксия аст. Объекти замина танҳо бо истифода аз реквизитҳо интиқол дода мешавад. Албатта, дуруст мебуд, ки хидмати алоҳида барои усулҳо ва мағоза барои давлат, аммо барои мақсадҳои ин мақола ин кофӣ аст:

import {render} from 'react-dom'
import App from './App'
import React from "react";

// Инициализируем приложение с background объектом в качест ве props
export async function initApp(background){
    render(
        <App background={background}/>,
        document.getElementById('app-content')
    );
}

Бо mobx оғоз кардани намоиш ҳангоми тағир додани маълумот хеле осон аст. Мо танҳо ороишгари нозирро аз баста овезон мекунем mobx-реаксия дар ҷузъ, ва render ба таври худкор даъват карда мешавад, вақте ки ҳама гуна мушоҳидашавандае, ки бо тағир додани ҷузъ истинод шудааст. Ба шумо ягон mapStateToProps лозим нест ё ба монанди redux пайваст шавед. Ҳама чиз аз қуттӣ кор мекунад:

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

Қисмҳои боқимондаро дар код дидан мумкин аст дар папкаи UI.

Ҳоло дар синфи барнома ба шумо лозим аст, ки интихобкунандаи ҳолати UI созед ва ҳангоми тағир додани он UI-ро огоҳ кунед. Барои ин, биёед як усулро илова кунем getState и reactionзанг задан 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())

        })
    }

    ...
}

Хангоми кабули объект remote таъсис дода шудааст reaction барои тағир додани ҳолате, ки функсияро дар тарафи UI даъват мекунад.

Ласми ниҳоӣ ин илова кардани намоиши паёмҳои нав дар тасвири васеъшавӣ аст:

function setupApp() {
...

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

...
}

Ҳамин тавр, ариза омода аст. Веб-саҳифаҳо метавонанд барои транзаксия имзо дархост кунанд:

Навиштани васеъшавии браузери бехатар

Навиштани васеъшавии браузери бехатар

Рамз дар ин ҷо дастрас аст пайванд.

хулоса

Агар шумо мақоларо то ба охир хонда бошед, аммо ба ҳар ҳол саволҳо дошта бошед, метавонед аз онҳо пурсед анборҳо бо васеъшавӣ. Дар он ҷо шумо инчунин ӯҳдадориҳоро барои ҳар як қадами таъиншуда хоҳед ёфт.

Ва агар шумо ба коди васеъшавии воқеӣ таваҷҷӯҳ дошта бошед, шумо метавонед инро пайдо кунед дар ин ҷо.

Рамз, анбор ва тавсифи кор аз сиемарелл

Манбаъ: will.com

Илова Эзоҳ