د خوندي براوزر توسیع لیکل

د خوندي براوزر توسیع لیکل

د عام "پیرودونکي-سرور" جوړښت برعکس، غیر متمرکز غوښتنلیکونه د دې لخوا مشخص شوي:

  • د کارن ننوتلو او پاسورډونو سره ډیټابیس ذخیره کولو ته اړتیا نشته. د لاسرسي معلوماتو په ځانګړي ډول د کاروونکو لخوا ساتل کیږي، او د دوی د اعتبار تصدیق د پروتوکول په کچه واقع کیږي.
  • د سرور کارولو ته اړتیا نشته. د غوښتنلیک منطق په بلاکچین شبکه کې اجرا کیدی شي ، چیرې چې دا ممکنه ده چې د اړین مقدار ډیټا ذخیره کړئ.

د کاروونکي کیلي لپاره 2 نسبتا خوندي ذخیره شتون لري - هارډویر والټونه او د براوزر توسیع. د هارډویر والټونه اکثرا خورا خوندي دي ، مګر کارول یې ستونزمن دي او له وړیا څخه لرې دي ، مګر د براوزر توسیع د امنیت او کارولو اسانه ترکیب دی ، او د پای کاروونکو لپاره هم په بشپړ ډول وړیا کیدی شي.

د دې ټولو په پام کې نیولو سره، موږ غوښتل چې ترټولو خوندي توسیع رامینځته کړو چې د معاملو او لاسلیکونو سره کار کولو لپاره د ساده API چمتو کولو له لارې د غیر متمرکز غوښتنلیکونو پراختیا ساده کوي.
موږ به تاسو ته لاندې د دې تجربې په اړه ووایو.

مقاله به د کوډ مثالونو او سکرین شاټونو سره د براوزر توسیع لیکلو څرنګوالي په اړه ګام په ګام لارښوونې ولري. تاسو کولی شئ ټول کوډونه ومومئ ذخیره. هره ژمنه په منطقي ډول د دې مقالې یوې برخې سره مطابقت لري.

د براوزر توسیعونو لنډ تاریخ

د براوزر توسیعونه د اوږدې مودې راهیسې شتون لري. دوی په انټرنیټ اکسپلورر کې په 1999 کې ښکاره شول، په 2004 کې په فایرفوکس کې. په هرصورت، د اوږدې مودې لپاره د غزولو لپاره یو واحد معیار نه و.

موږ کولی شو ووایو چې دا د ګوګل کروم په څلورم نسخه کې د توسیعونو سره څرګند شو. البته، بیا هیڅ مشخصات شتون نه درلود، مګر دا د کروم API و چې د هغې اساس شو: د براوزر ډیری بازار فتح کول او د جوړ شوي غوښتنلیک پلورنځي درلودل، کروم په حقیقت کې د براوزر توسیع لپاره معیار ټاکلی.

موزیلا خپل معیار درلود، مګر د کروم توسیعونو شهرت ته په کتلو سره، شرکت پریکړه وکړه چې یو مناسب API جوړ کړي. په 2015 کې، د موزیلا په نوښت، د نړیوال پراخ ویب کنسورشیم (W3C) دننه یوه ځانګړې ډله جوړه شوه ترڅو د کراس براوزر توسیع مشخصاتو باندې کار وکړي.

د کروم لپاره موجوده API توسیعونه د اساس په توګه اخیستل شوي. کار د مایکروسافټ په ملاتړ ترسره شو (ګوګل د معیاري پراختیا کې برخه اخیستو څخه انکار وکړ) ، او په پایله کې یوه مسوده راڅرګنده شوه مشخصات.

په رسمي ډول، مشخصات د ایج، فایرفوکس او اوپیرا لخوا ملاتړ کیږي (یادونه وکړئ چې کروم پدې لیست کې ندي). مګر په حقیقت کې ، معیار په پراخه کچه د کروم سره مطابقت لري ، ځکه چې دا واقعیا د دې توسیع پراساس لیکل شوی. تاسو کولی شئ د WebExtensions API په اړه نور ولولئ دلته.

د تمدید جوړښت

یوازینی فایل چې د توسیع لپاره اړین دی manifest (manifest.json) دی. دا د پراختیا لپاره "د ننوتلو نقطه" هم ده.

څرګند

د ځانګړتیاوو له مخې، منشور فایل یو معتبر JSON فایل دی. د ښکاره کیلي بشپړ توضیحات د معلوماتو سره چې کوم کلیدونه ملاتړ کیږي په کوم براوزر کې لیدل کیدی شي دلته.

هغه کلیدونه چې په مشخصاتو کې ندي "ممکن" له پامه غورځول شي (دواړه د کروم او فایرفوکس راپور تېروتنې، مګر توسیع کار ته دوام ورکوي).

او غواړم څو ټکو ته متوجه شم.

  1. شاليد - یو څیز چې لاندې برخې پکې شاملې دي:
    1. سکرېپټونه - د سکریپټونو لړۍ چې د شالید شرایطو کې به اجرا شي (موږ به لږ وروسته پدې اړه وغږیږو)؛
    2. مخ - د سکریپټونو پرځای چې په خالي پاڼه کې به اجرا شي، تاسو کولی شئ د منځپانګې سره html مشخص کړئ. په دې حالت کې، د سکریپټ ساحه به له پامه غورځول شي، او سکریپټونه به د منځپانګې پاڼې ته داخل شي؛
    3. دوام - یو بائنری بیرغ، که مشخص شوی نه وي، براوزر به د شالید پروسه "وژني" کله چې دا فکر کوي چې دا هیڅ نه کوي، او د اړتیا په صورت کې یې بیا پیل کړئ. که نه نو، پاڼه به یوازې هغه وخت پورته شي کله چې براوزر بند وي. په فایرفوکس کې نه ملاتړ کیږي.
  2. منځپانګې_سکریپټونه - د شیانو لړۍ چې تاسو ته اجازه درکوي مختلف ویب پاڼو ته مختلف سکریپټونه پورته کړئ. هر څیز لاندې مهمې برخې لري:
    1. لوبې - نمونه url، کوم چې ټاکي چې ایا د ځانګړي مینځپانګې سکریپټ به پکې شامل وي یا نه.
    2. js - د سکریپټونو لیست چې پدې لوبه کې به پورته شي؛
    3. exclude_maches - له ساحې څخه ایستل کیږي match URLs چې د دې ساحې سره سمون لري.
  3. پاڼه_عمل - په حقیقت کې یو څیز دی چې د آیکون لپاره مسؤل دی چې په براوزر کې د ادرس بار سره نږدې ښودل شوی او ورسره تعامل دی. دا تاسو ته اجازه درکوي چې د پاپ اپ کړکۍ ښکاره کړئ، کوم چې ستاسو د خپل HTML، CSS او JS په کارولو سره تعریف شوی.
    1. default_popup - د پاپ اپ انٹرفیس سره د HTML فایل ته لاره، کیدای شي CSS او JS ولري.
  4. پرېښلې - د تمدید حقونو اداره کولو لپاره یو لړ. حقوق درې ډوله دي، چې په تفصیل سره بیان شوي دي دلته
  5. ویب_د لاسرسي وړ_سرچینې - د تمدید سرچینې چې ویب پاڼه یې غوښتنه کولی شي، د بیلګې په توګه، انځورونه، JS، CSS، HTML فایلونه.
  6. بهرنۍ_د نښلولو وړ - دلته تاسو کولی شئ په ښکاره ډول د نورو توسیعونو IDs او د ویب پا pagesو ډومینونه مشخص کړئ چې تاسو ورسره وصل شئ. یو ډومین کیدای شي دویمه کچه یا لوړه وي. په فایرفوکس کې کار نه کوي.

د اجرا کولو شرایط

تمدید د کوډ اجرا کولو درې شرایط لري ، دا دی ، غوښتنلیک د براوزر API ته د لاسرسي مختلف کچې سره درې برخې لري.

د تمدید شرایط

ډیری API دلته شتون لري. په دې شرایطو کې دوی "ژوند کوي":

  1. پس منظر پاڼه - د تمدید "بیک اینڈ" برخه. فایل د "شالید" کیلي په کارولو سره په منشور کې مشخص شوی.
  2. پاپ اپ پاڼه - یو پاپ اپ پاڼه چې ښکاره کیږي کله چې تاسو د توسیع آیکون باندې کلیک وکړئ. په منشور کې browser_action -> default_popup.
  3. دودیز پاڼه - د تمدید پاڼه، د لید په جلا ټب کې "ژوندی". chrome-extension://<id_расширения>/customPage.html.

دا شرایط په خپلواک ډول د براوزر وینډوز او ټبونو څخه شتون لري. پس منظر پاڼه په یوه کاپي کې شتون لري او تل کار کوي (استثنا د پیښې پا pageه ده ، کله چې د شالید سکریپټ د پیښې لخوا پیل کیږي او د هغې له اجرا کولو وروسته "مړ" کیږي). پاپ اپ پاڼه شتون لري کله چې د پاپ اپ کړکۍ پرانيستې وي، او دودیز پاڼه - پداسې حال کې چې د دې سره ټب خلاص وي. د دې شرایطو څخه نورو ټبونو او د دوی مینځپانګو ته لاسرسی نشته.

د منځپانګې سکریپټ شرایط

د منځپانګې سکریپټ فایل د هر براوزر ټب سره پیل شوی. دا د توسیع API برخې او د ویب پاڼې DOM ونې ته لاسرسی لري. دا د منځپانګې سکریپټونه دي چې د پاڼې سره د متقابل عمل مسولیت لري. تمدیدونه چې د DOM ونې اداره کوي دا د مینځپانګې سکریپټونو کې کوي - د مثال په توګه ، د اعلاناتو بلاکونکي یا ژباړونکي. همدارنګه، د منځپانګې سکریپټ کولی شي د معیار له لارې د پاڼې سره اړیکه ونیسي postMessage.

د ویب پاڼې شرایط

دا پخپله اصلي ویب پاڼه ده. دا د تمدید سره هیڅ تړاو نلري او هلته لاسرسی نلري، پرته له هغه حالتونو کې چې د دې پاڼې ډومین په ښکاره توګه په منشور کې نه دی ښودل شوی (لاندې په دې اړه نور).

د پیغام تبادله

د غوښتنلیک مختلفې برخې باید یو بل سره پیغامونه تبادله کړي. د دې لپاره یو API شتون لري runtime.sendMessage پیغام لیږلو لپاره background и tabs.sendMessage یوې پاڼې ته د پیغام لیږلو لپاره (د منځپانګې سکریپټ، پاپ اپ یا ویب پاڼه که شتون ولري externally_connectable). لاندې یو مثال دی کله چې کروم 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))
    }
)

د بشپړ ارتباط لپاره، تاسو کولی شئ له لارې اړیکې جوړې کړئ 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 اضافه کوو - او زموږ غوښتنلیک دمخه په ګوګل کروم کې بار کیدی شي او ډاډ ترلاسه کړئ چې دا کار کوي.

د دې تصدیق کولو لپاره، تاسو کولی شئ کوډ واخلئ له دې ځایه. د هغه څه سربیره چې موږ یې وکړل، لینک د ویبپیک په کارولو سره د پروژې مجلس تنظیم کړ. په براوزر کې د اپلیکیشن اضافه کولو لپاره ، په کروم: // ایکسینشن کې تاسو اړتیا لرئ د load unpacked او د اړوند توسیع سره فولډر غوره کړئ - زموږ په قضیه کې dist.

د خوندي براوزر توسیع لیکل

اوس زموږ توسیع نصب او کار کوي. تاسو کولی شئ د مختلف شرایطو لپاره د پراختیا کونکي وسیلې په لاندې ډول پرمخ وړئ:

پاپ اپ ->

د خوندي براوزر توسیع لیکل

د مینځپانګې سکریپټ کنسول ته لاسرسی پخپله د پا pageې کنسول له لارې ترسره کیږي چیرې چې دا پیل شوی.د خوندي براوزر توسیع لیکل

د پیغام تبادله

نو، موږ اړتیا لرو دوه مخابراتي چینلونه رامینځته کړو: inpage <-> شالید او پاپ اپ <-> شالید. تاسو کولی شئ، البته، یوازې بندر ته پیغامونه واستوئ او خپل پروتوکول اختراع کړئ، مګر زه هغه طریقه غوره کوم چې ما د میټاماسک خلاصې سرچینې پروژې کې ولیدل.

دا د Ethereum شبکې سره کار کولو لپاره د براوزر توسیع دی. په دې کې، د غوښتنلیک مختلفې برخې د dnode کتابتون په کارولو سره د RPC له لارې اړیکه نیسي. دا تاسو ته اجازه درکوي چې تبادله په ګړندۍ او اسانۍ سره تنظیم کړئ که تاسو دا د ټرانسپورټ په توګه د نوډج جریان چمتو کړئ (د هغه شی معنی چې ورته انٹرفیس پلي کوي):

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

اوس موږ به د غوښتنلیک ټولګی جوړ کړو. دا به د پاپ اپ او ویب پا pageې لپاره 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)
        })
    }
}

دلته او لاندې، د نړیوال کروم اعتراض پرځای، موږ extensionApi کاروو، کوم چې په نورو کې د ګوګل براوزر او براوزر کې کروم ته لاسرسی لري. دا د کراس براوزر مطابقت لپاره ترسره کیږي، مګر د دې مقالې موخو لپاره یو څوک کولی شي په ساده ډول '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 د جریانونو سره کار کوي، او موږ یو بندر ترلاسه کوو، د اډاپټر ټولګي ته اړتیا ده. دا د لوستلو وړ سټریم کتابتون په کارولو سره رامینځته شوی ، کوم چې په براوزر کې د نوډج جریان پلي کوي:

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. موږ دوه جریانونه جوړوو. یو - د پاڼې په لور، د پوسټ پیغام په سر کې. د دې لپاره موږ دا کاروو دا بسته د میټاماسک جوړونکو څخه. دوهم جریان د بندر څخه ترلاسه شوي شالید ته دی 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);
    }
}

اوس موږ په inpage کې د 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) د پاڼې او UI لپاره جلا API سره. کله چې یو نوی مخ له شالید سره وصل کړئ موږ کولی شو دا وګورو:

د خوندي براوزر توسیع لیکل

خالي API او اصلي. د پاڼې په اړخ کې، موږ کولی شو د هیلو فنکشن په لاندې ډول ووایو:

د خوندي براوزر توسیع لیکل

په عصري JS کې د کال بیک فنکشن سره کار کول بد اخلاق دي، نو راځئ چې د dnode رامینځته کولو لپاره یو کوچنی مرستندویه ولیکئ چې تاسو ته اجازه درکوي د API څیز کارولو ته انتقال کړئ.

د 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 او جریان طریقه خورا انعطاف وړ ښکاري: موږ کولی شو د بھاپ ملټي پلیکسینګ وکاروو او د مختلف کارونو لپاره ډیری مختلف APIs رامینځته کړو. په اصولو کې، dnode هرچیرې کارول کیدی شي، اصلي شی دا دی چې ټرانسپورټ د نوډز جریان په بڼه وپلټئ.

یو بدیل د 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 کنسول څخه یو څو کیلي اضافه کړو او وګورو چې د دولت سره څه پیښیږي:

د خوندي براوزر توسیع لیکل

دولت باید دوامداره شي ترڅو د بیا پیل کولو پرمهال کیلي له لاسه ورنکړي.

موږ به دا په محلي ذخیره کې ذخیره کړو، د هر بدلون سره به یې له سره لیکو. په تعقیب ، دې ته لاسرسی به د UI لپاره هم اړین وي ، او زه غواړم بدلونونو ته هم ګډون وکړم. د دې پراساس ، دا به اسانه وي چې د لید وړ ذخیره رامینځته کړئ او د هغې بدلونونو کې ګډون وکړئ.

موږ به د موبکس کتابتون وکاروو (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 ساحو ته لاسرسی تعقیبوي. د پراکسي شیانو ترلاسه کونکي او تنظیم کونکي چې کتابتون رامینځته کوي کارول کیږي.

د عمل سینګار کونکي دوه موخې لري:

  1. د پلي کولو بیرغ سره په سخت حالت کې ، mobx په مستقیم ډول د دولت بدلول منع کوي. د سختو شرایطو لاندې کار کول ښه عمل ګڼل کیږي.
  2. حتی که یو فعالیت څو ځله حالت بدل کړي - د مثال په توګه، موږ د کوډ په څو کرښو کې ډیری ساحې بدلوو - څارونکو ته یوازې هغه وخت خبر ورکول کیږي کله چې بشپړ شي. دا په ځانګړې توګه د فرنټ اینډ لپاره مهم دی، چیرې چې غیر ضروري حالت تازه کول د عناصرو غیر ضروري رینډینګ لامل کیږي. زموږ په قضیه کې، نه لومړی او نه دویمه په ځانګړې توګه اړونده ده، مګر موږ به غوره عملونه تعقیب کړو. دا دود دی چې ډیکورټرونه په ټولو دندو کې وصل کړئ چې د لیدل شوي ساحو حالت بدلوي.

په شالید کې به موږ په محلي ذخیره کې د دولت ابتدا او خوندي کول اضافه کړو:

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. دا د اصلي ساحو سره ځای پرځای شوي ټولو پراکسيونو سره یو نوی څیز بیرته راګرځوي. د اجرا کولو په جریان کې، دا د اعتراض ټولې ساحې لوستل کیږي - له دې امله ګیټرز محرک کیږي.

په پاپ اپ کنسول کې به موږ بیا څو کیلي اضافه کړو. دا ځل دوی په محلي ذخیره کې هم پای ته ورسیدل:

د خوندي براوزر توسیع لیکل

کله چې د شالید پاڼه بیا پورته شي، معلومات په خپل ځای کې پاتې کیږي.

تر دې وخت پورې د غوښتنلیک ټول کوډ لیدل کیدی شي دلته.

د شخصي کیلي خوندي ذخیره کول

په روښانه متن کې د شخصي کیلي ذخیره کول غیر خوندي دي: تل یو چانس شتون لري چې تاسو به هیک شوي وي، خپل کمپیوټر ته لاسرسی ومومئ، او داسې نور. له همدې امله، په محلي ذخیره کې به موږ کلیدونه د پټنوم کوډ شوي بڼه کې ذخیره کړو.

د ډیر امنیت لپاره، موږ به په اپلیکیشن کې یو تړل شوی حالت اضافه کړو، په کوم کې چې کیلي ته هیڅ لاس رسی شتون نلري. موږ به د وخت پای ته رسیدو له امله په اوتومات ډول تمدید بند حالت ته انتقال کړو.

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. د غیر فعال لپاره تاسو کولی شئ د وخت پای تنظیم کړئ، او لاک شوی تنظیم شوی کله چې OS پخپله بلاک شوی وي. موږ به محلي ذخیره ته د خوندي کولو لپاره انتخاب کونکی هم بدل کړو:

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

د دې ګام مخکې کوډ دی دلته.

معاملې

نو، موږ خورا مهم شی ته راځو: په بلاکچین کې د معاملو رامینځته کول او لاسلیک کول. موږ به د 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 خورا ساده: موږ په ساده ډول د اړتیا په صورت کې د لاسلیک کولو وروسته د پیغام حالت بدلوو.

موږ په UI API کې تصویب او رد کوو، د پاڼې 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- غبرګون په اجزاوو کې، او رینډر به په اوتومات ډول ویل کیږي کله چې د اجزاو د بدلون لخوا حواله شوي کوم مشاهده کیږي. تاسو هیڅ نقشې سټیټ ټوپروپس ته اړتیا نلرئ یا د ریډکس په څیر وصل شئ. هرڅه د بکس څخه سم کار کوي:

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

...
}

نو، غوښتنلیک چمتو دی. ویب پاڼې کولی شي د معاملو لپاره د لاسلیک غوښتنه وکړي:

د خوندي براوزر توسیع لیکل

د خوندي براوزر توسیع لیکل

کوډ دلته شتون لري مخونه.

پایلې

که تاسو مقاله تر پایه لوستلې وي، مګر بیا هم پوښتنې لرئ، تاسو کولی شئ له دوی څخه وپوښتئ د تمدید سره ذخیره. هلته به تاسو د هر ټاکل شوي مرحلې لپاره ژمنې هم ومومئ.

او که تاسو د ریښتیني توسیع لپاره د کوډ په لټه کې علاقه لرئ ، تاسو کولی شئ دا ومومئ دلته.

کوډ، ذخیره او د دندې توضیحات siemarell

سرچینه: www.habr.com

Add a comment