Ngokungafaniyo noyilo oluqhelekileyo "lomncedisi womxhasi", usetyenziso olunatyisiweyo luphawulwa ngolu:
- Akukho mfuneko yokugcina idatabase kunye nokungena komsebenzisi kunye namagama ayimfihlo. Ulwazi lokufikelela lugcinwa kuphela ngabasebenzisi ngokwabo, kwaye ukuqinisekiswa kobunyani babo kwenzeka kwinqanaba leprotocol.
- Akukho mfuneko yokusebenzisa iseva. Ingqiqo yesicelo inokuphunyezwa kwinethiwekhi ye-blockchain, apho kunokwenzeka ukugcina inani elifunekayo ledatha.
Kukho iindawo ezi-2 ezikhuselekileyo zokugcinwa kwezitshixo zomsebenzisi - izipaji zehardware kunye nezandiso zebrawuza. Izipaji ze-Hardware ubukhulu becala zikhuseleke kakhulu, kodwa kunzima ukuzisebenzisa kwaye zikude simahla, kodwa izandiso zesiphequluli ziyindibaniselwano egqibeleleyo yokhuseleko kunye nokusebenziseka ngokulula, kwaye zinokukhululeka ngokupheleleyo kubasebenzisi bokugqibela.
Ukuthathela ingqalelo konke oku, besifuna ukwenza olona lwandiso lukhuselekileyo elenza lula uphuhliso lwezicelo ezinatyisiweyo ngokubonelela nge-API elula yokusebenza ngeentengiselwano kunye neesignesha.
Siza kukuxelela malunga nala mava angezantsi.
Inqaku liya kuba nemiyalelo yesinyathelo-nge-nyathelo malunga nendlela yokubhala isandiso sesiphequluli, kunye nemizekelo yekhowudi kunye nezikrini. Ungafumana yonke ikhowudi kuyo
Imbali emfutshane yezandiso zebhrawuza
Izandiso zebrowser bezikhona ixesha elide. Bavele kwi-Internet Explorer ngo-1999, kwiFirefox ngo-2004. Nangona kunjalo, ixesha elide kakhulu kwakungekho mgangatho omnye wokwandiswa.
Sinokuthi ivele kunye nezandiso kwinguqulo yesine yeGoogle Chrome. Ewe, kwakungekho nkcazo ngoko, kodwa yayiyi-Chrome API eyaba sisiseko sayo: ukuba yoyisile ininzi yemarike yesiphequluli kunye nevenkile eyakhelwe ngaphakathi, i-Chrome ibeka umgangatho wokongezwa kwesiphequluli.
I-Mozilla yayinomgangatho wayo, kodwa ibona ukuthandwa kwezandiso zeChrome, inkampani yagqiba ekubeni yenze i-API ehambelanayo. Ngo-2015, kwinyathelo lokuqala le-Mozilla, iqela elikhethekileyo lenziwe ngaphakathi kwi-World Wide Web Consortium (W3C) ukusebenza kwiinkcukacha zokwandiswa kwe-browser.
Izandiso ze-API ezikhoyo zeChrome zithathwe njengesiseko. Umsebenzi wenziwa ngenkxaso yeMicrosoft (uGoogle wala ukuthatha inxaxheba kuphuhliso lomgangatho), kwaye ngenxa yoko kwavela idrafti.
Ngokusemthethweni, inkcazo ixhaswa yi-Edge, iFirefox kunye ne-Opera (qaphela ukuba iChrome ayikho kolu luhlu). Kodwa eneneni, umgangatho uhambelana kakhulu neChrome, kuba ibhaliwe ngokusekwe kwizandiso zayo. Unokufunda ngakumbi malunga neWebExtensions API
Ulwakhiwo lolwandiso
Ifayile kuphela efunekayo kulwandiso yi-manifest (manifest.json). Kwakhona "indawo yokungena" ekwandiseni.
Ukubonakaliswa
Ngokobalulo, ifayile ye-manifest yifayile ye-JSON esebenzayo. Inkcazo epheleleyo yezitshixo ze-manifest ngolwazi malunga nokuba zeziphi izitshixo ezixhaswayo apho i-browser inokujongwa
Izitshixo ezingekho kwinkcazo "zinokuthi" zingahoywa (zombini iimpazamo zengxelo yeChrome kunye neFirefox, kodwa izandiso ziyaqhubeka nokusebenza).
Kwaye ndingathanda ukutsalela ingqalelo kwezinye iingongoma.
- yangasemva — into ebandakanya le mihlaba ilandelayo:
- zeempendulo — uluhlu lwemibhalo ebhaliweyo eya kusetyenziswa kumxholo ongasemva (siya kuthetha ngale nto kamva);
- iphepha - endaweni yezikripthi eziya kuphunyezwa kwiphepha elingenanto, ungakhankanya ihtml ngomxholo. Kule meko, indawo yescript iya kuhoywa, kwaye izikripthi ziya kufuneka zifakwe kwiphepha lomxholo;
- iqhubeka - iflegi yebhinari, ukuba ayichazwanga, isikhangeli "siya kubulala" inkqubo yangasemva xa sicinga ukuba ayenzi nto, kwaye siyiqalise kwakhona ukuba kuyimfuneko. Kungenjalo, iphepha liya kothulwa kuphela xa isikhangeli sivaliwe. Ayixhaswa kwiFirefox.
- umxholo_izikripthi - Uluhlu lwezinto ezikuvumela ukuba ulayishe izikripthi ezahlukeneyo kumaphepha ewebhu ahlukeneyo. Into nganye iqulethe le mihlaba ibalulekileyo ilandelayo:
- midlalo -
ipatheni url , emisela ukuba isikripthi somxholo othile siya kubandakanywa okanye hayi. - js — uludwe lwemibhalo eya kulayishwa kulo mdlalo;
- khuphela_imidlalo - akubandakanyi ebaleni
match
Ii-URL ezihambelana nalo mmandla.
- midlalo -
- iphepha_intshukumo - eneneni yinto enoxanduva lwe icon eboniswe ecaleni kwebar yedilesi kwi-browser kunye nokusebenzisana nayo. Ikwakuvumela ukuba ubonise ifestile ye popup, echazwa usebenzisa eyakho iHTML, CSS kunye neJS.
- default_popup — indlela eya kwifayile yeHTML enojongano oluzivelelayo, inokuqulatha iCSS kunye neJS.
- aneemvume - uluhlu lokulawula amalungelo awongezelelweyo. Kukho iintlobo ezi-3 zamalungelo, ezichazwe ngokubanzi
apha - web_accessible_resources - izixhobo ezongezelelekileyo ezinokucelwa kwiphepha lewebhu, umzekelo, imifanekiso, i-JS, iCSS, iifayile zeHTML.
- ngaphandle_eqhagamshelekayo — Apha ungachaza ngokucacileyo ii-ID zezinye izandiso kunye nemimandla yamaphepha ewebhu onokuqhagamshela kuwo. I-domain ingaba kwinqanaba lesibini okanye ngaphezulu. Ayisebenzi kwiFirefox.
Umxholo wophumezo
Ukongezwa kunemixholo emithathu yokusetyenziswa kwekhowudi, oko kukuthi, isicelo siqukethe iinxalenye ezintathu ezinamazinga ahlukeneyo okufikelela kwi-API yesikhangeli.
Umxholo wokwandiswa
Uninzi lwe-API luyafumaneka apha. Kulo mxholo "bahlala":
- Iphepha elingasemva — "backend" inxalenye yolwandiso. Ifayile icacisiwe kwi-manifest kusetyenziswa iqhosha elithi "imvelaphi".
- Iphepha elizivelelayo - Iphepha elivelayo elivelayo xa ucofa i icon yolwandiso. Kwi-manifesto
browser_action
->default_popup
. - Iphepha elilungiselelweyo — iphepha elongezelelweyo, “ukuphila” kwithebhu eyahlukileyo yokujonga
chrome-extension://<id_расширения>/customPage.html
.
Lo mxholo ukhona ngokuzimeleyo kwiifestile zesikhangeli kunye neethebhu. Iphepha elingasemva ikhona kwikopi enye kwaye ihlala isebenza (ngaphandle kwephepha lesiganeko, xa iskripthi esingasemva siqaliswa sisiganeko kwaye "sifa" emva kokuphunyezwa kwayo). Iphepha elizivelelayo ikhona xa ifestile ezivelelayo zivuliwe, kwaye Iphepha elilungiselelweyo — ngelixa ithebhu enayo ivuliwe. Akukho ukufikelela kwezinye iithebhu kunye nemixholo yazo kulo mxholo.
Umxholo wesikripthi somxholo
Ifayile yeskripthi somxholo iqaliswe kunye nethebhu nganye yomkhangeli zincwadi. Inokufikelela kwinxalenye ye-API yolwandiso kunye nomthi we-DOM wephepha lewebhu. Zizikripthi zomxholo ezinoxanduva lokusebenzisana nephepha. Izandiso ezisebenzisa umthi we-DOM zenza oku kwizikripthi zomxholo - umzekelo, i-ad blockers okanye abaguquleli. Kwakhona, iskripthi somxholo sinokunxibelelana nephepha ngokusemgangathweni postMessage
.
Umxholo wephepha lewebhu
Eli lelona phepha lewebhu ngokwalo. Ayinanto yakwenza nolwandiso kwaye ayinafikelelo apho, ngaphandle kwakwiimeko apho ithambeka leli phepha lingaboniswanga ngokucacileyo kwimanifest (ngaphezulu koku ngezantsi).
Ukutshintshiselana ngomyalezo
Iinxalenye ezahlukeneyo zesicelo kufuneka zitshintshiselane ngemiyalezo enye kwenye. Kukho i-API yale nto runtime.sendMessage
ukuthumela umyalezo background
и tabs.sendMessage
ukuthumela umyalezo kwiphepha (iscript somxholo, ipopup okanye iphepha lewebhu ukuba likhona externally_connectable
). Ngezantsi ngumzekelo xa ufikelela kwi-Chrome API.
// Сообщением может быть любой JSON сериализуемый объект
const msg = {a: 'foo', b: 'bar'};
// extensionId можно не указывать, если мы хотим послать сообщение 'своему' расширению (из ui или контент скрипта)
chrome.runtime.sendMessage(extensionId, msg);
// Так выглядит обработчик
chrome.runtime.onMessage.addListener((msg) => console.log(msg))
// Можно слать сообщения вкладкам зная их id
chrome.tabs.sendMessage(tabId, msg)
// Получить к вкладкам и их id можно, например, вот так
chrome.tabs.query(
{currentWindow: true, active : true},
function(tabArray){
tabArray.forEach(tab => console.log(tab.id))
}
)
Ngonxibelelwano olupheleleyo, unokwenza unxibelelwano ngokusebenzisa runtime.connect
. Ekuphenduleni siya kufumana runtime.Port
, apho, ngelixa ivuliwe, ungathumela naliphi na inani lemiyalezo. Kwicala labaxumi, umzekelo, contentscript
, kubonakala ngathi:
// Опять же 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"});
Iseva okanye ngasemva:
// Обработчик для подключения 'своих' вкладок. Контент скриптов, 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) {
...
});
Kukwakho nesiganeko onDisconnect
kunye nendlela disconnect
.
Umzobo wesicelo
Masenze ulwandiso lwebrawuza olugcina izitshixo zabucala, lubonelela ngokufikelela kulwazi lukawonke-wonke (idilesi, isitshixo sikawonke-wonke sinxibelelana nephepha kwaye sivumela usetyenziso lomntu wesithathu ukuba lucele utyikityo lwentengiselwano.
Uphuhliso lwesicelo
Isicelo sethu kufuneka sisebenze zombini kunye nomsebenzisi kwaye sinikeze iphepha nge-API yokufowuna iindlela (umzekelo, ukusayina iintengiselwano). Yenza ngento enye contentscript
ayizukusebenza, kuba inofikelelo kuphela kwi-DOM, kodwa hayi kwi-JS yephepha. Qhagamshela nge runtime.connect
asikwazi, kuba i-API iyadingeka kuzo zonke izizinda, kwaye kuphela ezikhethekileyo zingachazwa kwi-manifest. Ngenxa yoko, umzobo uya kujongeka ngolu hlobo:
Kuya kubakho esinye iskripthi - inpage
, esiya kuthi siyifake kwiphepha. Iya kuqhuba kumxholo wayo kwaye inike i-API yokusebenza kunye nolwandiso.
Ukuqala
Yonke ikhowudi yokwandiswa kwesikhangeli iyafumaneka
Masiqale nge-manifesto:
{
// Имя и описание, версия. Все это будет видно в браузере в chrome://extensions/?id=<id расширения>
"name": "Signer",
"description": "Extension demo",
"version": "0.0.1",
"manifest_version": 2,
// Скрипты, которые будут исполнятся в background, их может быть несколько
"background": {
"scripts": ["background.js"]
},
// Какой html использовать для popup
"browser_action": {
"default_title": "My Extension",
"default_popup": "popup.html"
},
// Контент скрипты.
// У нас один объект: для всех url начинающихся с http или https мы запускаем
// contenscript context со скриптом contentscript.js. Запускать сразу по получении документа для всех фреймов
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"contentscript.js"
],
"run_at": "document_start",
"all_frames": true
}
],
// Разрешен доступ к localStorage и idle api
"permissions": [
"storage",
// "unlimitedStorage",
//"clipboardWrite",
"idle"
//"activeTab",
//"webRequest",
//"notifications",
//"tabs"
],
// Здесь указываются ресурсы, к которым будет иметь доступ веб страница. Тоесть их можно будет запрашивать fetche'м или просто xhr
"web_accessible_resources": ["inpage.js"]
}
Yenza i-background.js engenanto, popup.js, inpage.js kunye nescriptscript.js. Songeza i-popup.html- kwaye isicelo sethu sinokulayishwa kuGoogle Chrome kwaye siqinisekise ukuba siyasebenza.
Ukuqinisekisa oku, unokuthatha ikhowudi
Ngoku ulwandiso lwethu lufakiwe kwaye luyasebenza. Unokusebenzisa izixhobo zomphuhlisi kwiimeko ezahlukeneyo ngolu hlobo lulandelayo:
popup ->
Ukufikelela kwikhonsoli yesikripthi somxholo kuqhutywa ngekhonsoli yephepha ngokwalo eqaliswe kulo.
Ukutshintshiselana ngomyalezo
Ke, kufuneka siseke amajelo amabini onxibelelwano: i-inpage <-> imvelaphi kunye ne-popup <-> yangasemva. Unako, ewe, ukuthumela nje imiyalezo kwizibuko kwaye wenze eyakho iprotocol, kodwa ndikhetha indlela endiyibone kwiprojekthi yomthombo ovulekileyo wemetamask.
Olu lwandiso lwesiphequluli sokusebenza kunye nenethiwekhi ye-Ethereum. Kuyo, iindawo ezahlukeneyo zesicelo zinxibelelana nge-RPC usebenzisa ithala leencwadi le-dnode. Ikuvumela ukuba uququzelele utshintshiselwano ngokukhawuleza kwaye ngokufanelekileyo ukuba unikezela ngomjelo we-nodejs njengothutho (ithetha into ephumeza ujongano olufanayo):
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)))
})
Ngoku siza kudala iklasi yesicelo. Iyakudala izinto ze-API zepopup kunye nephepha lewebhu, kwaye ibenzele i-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)
})
}
}
Apha nangezantsi, endaweni ye-Chrome yehlabathi jikelele into, sisebenzisa i-extensionApi, efikelela kuChrome kwisikhangeli sikaGoogle kunye nesikhangeli kwezinye. Oku kwenzelwa ukuhambelana kwebrowser, kodwa ngeenjongo zeli nqaku umntu unokusebenzisa ngokulula 'chrome.runtime.connect'.
Masenze umzekelo wesicelo kwiscript esingasemva:
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)
}
}
Ekubeni i-dnode isebenza kunye nemisinga, kwaye sifumana i-port, iklasi ye-adapter iyadingeka. Yenziwe kusetyenziswa ithala leencwadi elifundekayo, elisebenzisa imijelo ye-nodejs kwisikhangeli:
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()
}
}
Ngoku masenze umdibaniso kwi-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;
}
}
Emva koko senza unxibelelwano kwiskripthi somxholo:
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);
}
}
Ekubeni sifuna i-API kungekhona kwisikripthi somxholo, kodwa ngokuthe ngqo kwiphepha, senza izinto ezimbini:
- Senza imilambo emibini. Enye - ibhekisa kwiphepha, ngaphezulu komyalezo womyalezo. Kule nto sisebenzisa oku
le phakheji ukusuka kubadali bemetamask. Umlambo wesibini kukungasemva phezu kwezibuko elifunyenwe ukusukaruntime.connect
. Masizithenge. Ngoku iphepha liya kuba nomsinga ukuya ngasemva. - Faka umbhalo kwiDOM. Khuphela isikripthi (ukufikelela kuso bekuvunyelwe kwi-manifest) kwaye wenze ithegi
script
nemixholo yayo ngaphakathi:
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);
}
}
Ngoku senza into ye-api kwi-inpage kwaye siyibeke kwihlabathi jikelele:
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;
}
Silungile
I-API engenanto kunye nemvelaphi. Kwicala lephepha, sinokubiza umsebenzi we-hello ngolu hlobo:
Ukusebenza ngemisebenzi yokufowunelwa kwi-JS yanamhlanje yisimilo esibi, ke masibhale umncedisi omncinci ukwenza i-dnode ekuvumela ukuba udlulise into ye-API kwii-utils.
Izinto ze-API ngoku ziya kujongeka ngolu hlobo:
export class SignerApp {
popupApi() {
return {
hello: async () => "world"
}
}
...
}
Ukufumana into ukusuka kude ngolu hlobo:
import {cbToPromise, transformMethods} from "../../src/utils/setupDnode";
const pageApi = await new Promise(resolve => {
dnode.once('remote', remoteApi => {
// С помощью утилит меняем все callback на promise
resolve(transformMethods(cbToPromise, remoteApi))
})
});
Kwaye imisebenzi yokufowuna ibuyisela isithembiso:
Inguqulelo enemisebenzi engahambelaniyo ekhoyo
Lilonke, i-RPC kunye nendlela yokuhambisa ibonakala ibhetyebhetye: sinokusebenzisa i-multiplexing yomphunga kwaye senze ii-API ezininzi ezahlukeneyo kwimisebenzi eyahlukeneyo. Ngokomgaqo, i-dnode ingasetyenziselwa naphi na, into ephambili kukugubungela ukuthuthwa ngendlela ye-nodejs stream.
Enye indlela yifomathi ye-JSON, esebenzisa i-protocol ye-JSON RPC 2. Nangona kunjalo, isebenza kunye nothutho oluthile (TCP kunye ne-HTTP (S)), olungasebenziyo kwimeko yethu.
Ilizwe langaphakathi kunye noGcino lwendawo
Kuya kufuneka sigcine imo yangaphakathi yesicelo - ubuncinci izitshixo zokusayina. Sinokongeza ngokulula imeko kwisicelo kunye neendlela zokuyitshintsha kwi-popup API:
import {setupDnode} from "./utils/setupDnode";
export class SignerApp {
constructor(){
this.store = {
keys: [],
};
}
addKey(key){
this.store.keys.push(key)
}
removeKey(index){
this.store.keys.splice(index,1)
}
popupApi(){
return {
addKey: async (key) => this.addKey(key),
removeKey: async (index) => this.removeKey(index)
}
}
...
}
Ngasemva, siya kusonga yonke into kumsebenzi kwaye sibhale into yesicelo kwifestile ukuze sisebenze nayo kwikhonsoli:
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)
}
}
}
Makhe songeze izitshixo ezimbalwa kwikhonsoli ye-UI kwaye sibone ukuba kwenzeka ntoni na ngelizwe:
Umbuso kufuneka wenziwe ngokuqhubekayo ukuze izitshixo zingalahleki xa ziqala kwakhona.
Siza kuyigcina kwindawo yokuGcina, siyibhale ngaphezulu ngalo lonke utshintsho. Emva koko, ukufikelela kuyo kuya kuba yimfuneko kwi-UI, kwaye ndingathanda ukubhalisela utshintsho. Ngokusekwe kule nto, kuya kuba lula ukwenza ukugcinwa okubonakalayo kwaye ubhalise kwiinguqu zayo.
Siza kusebenzisa ithala leencwadi le-mobx (
Makhe songeze ukuqaliswa kwemeko yokuqala kwaye senze ivenkile ibonakale:
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)
}
...
}
"Ngaphantsi kwe-hood," i-mobx ithathe indawo yazo zonke iindawo zokugcina ngeproxy kwaye ibamba zonke iifowuni eziya kubo. Kuya kwenzeka ukuba ubhalisele le miyalezo.
Ngezantsi ndiza kuhlala ndisebenzisa igama elithi "xa utshintsha", nangona oku kungachanekanga ngokupheleleyo. I-Mobx ilandelela ukufikelela kwiindawo. Iigetters kunye neeseti zezinto zommeli eziqulunqwa lithala leencwadi ziyasetyenziswa.
Abahombi bentshukumo basebenzela iinjongo ezimbini:
- Kwimowudi engqongqo kunye neflegi yeentshukumo zokunyanzeliswa, i-mobx iyakwalela ukutshintsha urhulumente ngokuthe ngqo. Kuthathwa njengefomu elungileyo ukusebenza phantsi kweemeko ezingqongqo.
- Nokuba umsebenzi utshintsha urhulumente amaxesha amaninzi - umzekelo, sitshintsha iindawo ezininzi kwimigca emininzi yekhowudi - abakhi-mkhanyo bayaziswa kuphela xa igqibile. Oku kubaluleke ngakumbi kwi-frontend, apho uhlaziyo lwelizwe olungeyomfuneko lukhokelela ekunikezeni izinto ngokungeyomfuneko. Kwimeko yethu, akukho okokuqala okanye okwesibini kubaluleke kakhulu, kodwa siya kulandela ezona ndlela zilungileyo. Kuyinto yesiko ukunamathisela abahlobisi kuyo yonke imisebenzi etshintsha imeko yemimandla ebonwayo.
Ngasemva siya kongeza ukuqaliswa kunye nokugcina urhulumente kwindawo yoGcino lwendawo:
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)
}
}
}
Umsebenzi wokusabela unomdla apha. Ineengxoxo ezimbini:
- Umkhethi wedatha.
- Umphathi oza kubizwa ngale datha ngalo lonke ixesha itshintsha.
Ngokungafaniyo ne-reux, apho sifumana khona urhulumente ngokucacileyo njengengxabano, i-mobx ikhumbula ukuba yeyiphi into ebonakalayo esifikelela kuyo ngaphakathi komkhethi, kwaye ibiza kuphela umphathi xa etshintsha.
Kubalulekile ukuqonda ngokuthe ngqo ukuba i-mobx ithatha isigqibo malunga nokuba yeyiphi into esinokubhalisa kuyo. Ukuba ndibhale umkhethi ngekhowudi efana nale() => app.store
, ke ukusabela akusoze kubizwe, ekubeni ugcino ngokwalo alubonakali, kuphela amasimi alo.
Ukuba ndiyibhale ngolu hlobo () => app.store.keys
, emva koko akukho nto iya kwenzeka, kuba xa udibanisa / ususa izinto zoluhlu, ireferensi kuyo ayiyi kutshintsha.
I-Mobx isebenza njengomkhethi okokuqala kwaye igcina umkhondo wezinto ezinokubonwa esizifikeleleyo. Oku kwenziwa ngee-proxy getters. Ke ngoko, umsebenzi owakhelwe-ngaphakathi usetyenziswa apha toJS
. Ibuyisela into entsha kunye nazo zonke iiproxi ezitshintshwe ngamasimi oqobo. Ngethuba lokubulawa, ifunda zonke iinkalo zento - ngoko ke i-getters iqhutywe.
Kwi-popup console siya kuphinda songeze izitshixo ezininzi. Ngeli xesha nabo bangena kuGcino lwasekhaya:
Xa iphepha elingasemva lilayishwa kwakhona, ulwazi luhlala lukhona.
Yonke ikhowudi yesicelo ukuya kweli nqanaba inokujongwa
Ukugcinwa ngokukhuselekileyo kwezitshixo zabucala
Ukugcina izitshixo zangasese kwisicatshulwa esicacileyo akukhuselekanga: kukho rhoqo ithuba lokuba uya kuqhekezwa, ufumane ukufikelela kwikhompyutheni yakho, njalo njalo. Ke ngoko, kuGcino lwasekhaya siya kugcina izitshixo kwifomu efihliweyo ngegama eliyimfihlo.
Ukhuseleko olukhulu, siyakongeza imeko etshixiweyo kwisicelo, apho kungayi kubakho ukufikelela kwizitshixo konke konke. Siza kudlulisela ulwandiso ngokuzenzekelayo kwisimo esitshixiweyo ngenxa yexesha.
I-Mobx ikuvumela ukuba ugcine kuphela iseti encinci yedatha, kwaye enye ibalwa ngokuzenzekelayo ngokusekelwe kuyo. Ezi zinto zibizwa ngokuba ziipropathi zekhompyutha. Zinokuthelekiswa nemibono kuluhlu lwedatha:
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')
}
}
}
Ngoku sigcina kuphela izitshixo ezifihliweyo kunye negama lokugqitha. Yonke enye into iyabalwa. Senza ukudluliselwa kwimeko etshixiweyo ngokususa igama eligqithisiweyo kurhulumente. I-API yoluntu ngoku inendlela yokuqalisa ukugcinwa.
Ibhalelwe uguqulelo oluntsonkothileyo
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)
}
Isikhangeli sine-API engasebenziyo apho ungabhalisa kumsitho-utshintsho lwemeko. Umbuso, ngokufanelekileyo, unokuba idle
, active
и locked
. Ukungenzi nto unokuseta ixesha lokuphuma, kwaye itshixiwe imiselwe xa i-OS ngokwayo ivaliwe. Siza kutshintsha kwakhona umkhethi wokugcina kuGcino lwasekhaya:
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)
}
}
}
Ikhowudi phambi kweli nyathelo
Intengiselwano
Ke, siza kweyona nto ibalulekileyo: ukudala kunye nokusayina ukuthengiselana kwi-blockchain. Siza kusebenzisa i-WAVES blockchain kunye nethala leencwadi
Okokuqala, masifake kwisimo uluhlu lwemiyalezo ekufuneka isayinwe, emva koko songeze iindlela zokongeza umyalezo omtsha, uqinisekisa utyikityo, kunye nokwala:
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'
}
...
}
Xa sifumana umyalezo omtsha, songeza imetadata kuyo, yenza observable
kwaye wongeze kwi store.messages
.
Ukuba awenzi njalo observable
ngesandla, emva koko imobx iyakuyenza ngokwayo xa isongeza imiyalezo kuluhlu. Nangona kunjalo, iya kudala into entsha esingayi kuba nayo ireferensi, kodwa siya kuyidinga kwisinyathelo esilandelayo.
Okulandelayo, sibuyisela isithembiso esisombululayo xa isimo somyalezo sitshintsha. Ubume bujongwa ngokusabela, okuya "kuzibulala" xa isimo sitshintsha.
Indlela ikhowudi approve
и reject
ilula kakhulu: sitshintsha ngokulula isimo somyalezo, emva kokuwusayina ukuba kuyimfuneko.
Sibeka iVumela kwaye siyala kwi-UI API, uMyalezo omtsha kwiphepha le-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)
}
}
...
}
Ngoku makhe sizame ukusayina intengiselwano kunye nolwandiso:
Ngokubanzi, yonke into ilungile, yonke into eseleyo
UI
Ujongano lufuna ukufikelela kwimeko yesicelo. Kwicala le-UI siza kwenza observable
chaza kwaye wongeze umsebenzi kwi-API ozakutshintsha le meko. Masongeze observable
ukuya kwinto ye-API efunyenwe ngasemva:
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)
}
Ekugqibeleni siqala ukunikezela ujongano lwesicelo. Olu lusetyenziso olusabelayo. Into engasemva igqithiswa ngokulula kusetyenziswa iipropu. Kuya kuba kuchanekile, ngokuqinisekileyo, ukwenza inkonzo eyahlukileyo kwiindlela kunye nevenkile yelizwe, kodwa ngeenjongo zeli nqaku oku kwanele:
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')
);
}
Nge-mobx kulula kakhulu ukuqalisa ukunikezela xa idatha itshintsha. Sixhoma nje umhombisi obukeleyo kwiphakheji
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>
}
}
Amacandelo aseleyo angabonwa kwikhowudi
Ngoku kwiklasi yesicelo kufuneka wenze umkhethi karhulumente we-UI kwaye wazise i-UI xa itshintsha. Ukwenza oku, masidibanise indlela getState
и reaction
ukufowuna 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())
})
}
...
}
Xa ufumana into remote
yenziwe reaction
ukutshintsha imeko ebiza umsebenzi kwicala le UI.
Ukuchukumisa okokugqibela kukongeza umboniso wemiyalezo emitsha kwi icon yolwandiso:
function setupApp() {
...
// Reaction на выставление текста беджа.
reaction(
() => app.store.newMessages.length > 0 ? app.store.newMessages.length.toString() : '',
text => extensionApi.browserAction.setBadgeText({text}),
{fireImmediately: true}
);
...
}
Ngoko ke, isicelo silungile. Amaphepha ewebhu angacela utyikityo lwentengiselwano:
Ikhowudi iyafumaneka apha
isiphelo
Ukuba ulifundile inqaku kude kube sekupheleni, kodwa usenemibuzo, unokuyibuza kuyo
Kwaye ukuba unomdla ekujongeni ikhowudi yolwandiso lwangempela, ungayifumana le
Ikhowudi, indawo yokugcina kunye nenkcazo yomsebenzi ukusuka
umthombo: www.habr.com