Mosiyana ndi kamangidwe ka "kasitomala-seva", ntchito zokhazikitsidwa ndi:
Palibe chifukwa chosungira database yokhala ndi ma logins ogwiritsira ntchito ndi mapasiwedi. Zambiri zofikira zimasungidwa ndi ogwiritsa ntchito okha, ndipo kutsimikizira kuti ndi zoona kumachitika pamlingo wa protocol.
Palibe chifukwa chogwiritsa ntchito seva. Mfundo yogwiritsira ntchito ikhoza kuchitidwa pa netiweki ya blockchain, komwe ndikotheka kusunga kuchuluka kofunikira kwa data.
Pali 2 zosungirako zotetezeka zamakiyi ogwiritsa ntchito - ma wallet a hardware ndi zowonjezera za msakatuli. Ma wallet a Hardware nthawi zambiri amakhala otetezeka kwambiri, koma ovuta kugwiritsa ntchito komanso otalikirana ndi mfulu, koma zowonjezera za msakatuli ndizophatikizana bwino kwachitetezo komanso kugwiritsa ntchito mosavuta, komanso kumatha kukhala kwaulere kwa ogwiritsa ntchito.
Poganizira zonsezi, tinkafuna kuti tiwonjezere zotetezeka kwambiri zomwe zimathandizira kupanga mapulogalamu okhazikika popereka API yosavuta yogwira ntchito ndi ma siginecha.
Tikuwuzani za izi pansipa.
Nkhaniyi idzakhala ndi malangizo a pang'onopang'ono amomwe mungalembere chowonjezera cha msakatuli, ndi zitsanzo za ma code ndi zojambula. Mutha kupeza ma code onse mu nkhokwe. Kupanga kulikonse momveka kumagwirizana ndi gawo la nkhaniyi.
Mbiri Yachidule Yazowonjezera Zamsakatuli
Zowonjezeretsa msakatuli zakhala zikuchitika kwa nthawi yayitali. Adawonekera mu Internet Explorer kumbuyo mu 1999, mu Firefox mu 2004. Komabe, kwa nthawi yayitali kwambiri panalibe muyezo umodzi wowonjezera.
Titha kunena kuti zidawoneka pamodzi ndi zowonjezera mu mtundu wachinayi wa Google Chrome. Zachidziwikire, panalibe tsatanetsatane panthawiyo, koma inali Chrome API yomwe idakhala maziko ake: atagonjetsa msika wambiri wa asakatuli ndikukhala ndi sitolo yopangira mapulogalamu, Chrome idakhazikitsadi mulingo wowonjezera osatsegula.
Mozilla inali ndi muyezo wake, koma powona kutchuka kwa zowonjezera za Chrome, kampaniyo idaganiza zopanga API yogwirizana. Mu 2015, motsogozedwa ndi Mozilla, gulu lapadera lidapangidwa mkati mwa World Wide Web Consortium (W3C) kuti ligwire ntchito pazowonjezera zakusakatula.
API yowonjezerapo kale ya Chrome idatengedwa ngati maziko. Ntchitoyi idachitika mothandizidwa ndi Microsoft (Google idakana kutenga nawo gawo pakukula kwa muyezo), ndipo zotsatira zake zidawoneka. zofunika.
M'malo mwake, mawonekedwewa amathandizidwa ndi Edge, Firefox ndi Opera (zindikirani kuti Chrome siili pamndandandawu). Koma kwenikweni, muyezo umagwirizana kwambiri ndi Chrome, chifukwa umalembedwa motengera zowonjezera zake. Mutha kuwerenga zambiri za WebExtensions API apa.
Malinga ndi zomwe zafotokozedwera, fayilo yowonetsera ndi fayilo yovomerezeka ya JSON. Kufotokozera kwathunthu kwa makiyi owonetsera okhala ndi zambiri za makiyi omwe amathandizidwa momwe msakatuli angawonedwe apa.
Makiyi omwe sali mwatsatanetsatane "atha" kunyalanyazidwa (zolakwika zonse za Chrome ndi Firefox, koma zowonjezera zikugwirabe ntchito).
Ndipo ndikufuna kutchera khutu ku mfundo zina.
maziko - chinthu chomwe chili ndi magawo otsatirawa:
zolemba - mndandanda wa zolemba zomwe zidzachitike kumbuyo (tilankhula za izi posachedwa);
tsamba - m'malo mwa zolembedwa zomwe zidzachitike patsamba lopanda kanthu, mutha kufotokoza html ndi zomwe zili. Pachifukwa ichi, gawo la script lidzanyalanyazidwa, ndipo zolembazo ziyenera kulowetsedwa mu tsamba lazolemba;
kunja_kolumikizana - apa mutha kufotokoza momveka bwino ma ID a zowonjezera ndi madambwe amasamba omwe mungalumikizane nawo. Dongosolo litha kukhala lachiwiri kapena kupitilira apo. Sichikugwira ntchito mu Firefox.
Nkhani yokonzekera
Kuwonjezako kuli ndi magawo atatu opangira ma code, ndiko kuti, kugwiritsa ntchito kumakhala ndi magawo atatu okhala ndi magawo osiyanasiyana ofikira pa msakatuli API.
Nkhani yowonjezera
Zambiri za API zilipo pano. M'nkhani ino iwo "amakhala":
Tsamba lakumbuyo - "backend" gawo lazowonjezera. Fayilo imatchulidwa mu manifesto pogwiritsa ntchito kiyi ya "background".
Nkhaniyi ilipo popanda mawindo asakatuli ndi ma tabu. Tsamba lakumbuyo limapezeka m'kope limodzi ndipo limagwira ntchito nthawi zonse (kupatulapo tsamba la zochitika, pamene zolemba zakumbuyo zimayambitsidwa ndi chochitika ndi "kufa" pambuyo pa kuphedwa kwake). Tsamba lotulukira imakhalapo pomwe zenera la popup likutsegulidwa, ndipo Tsamba lamakonda - pomwe tabu yomwe ili nayo ndi yotseguka. Palibe mwayi wopeza ma tabo ena ndi zomwe zili munkhaniyi.
Nkhani ya script
Fayilo ya script yokhutira imayambitsidwa pamodzi ndi tsamba lililonse la msakatuli. Ili ndi mwayi wopeza gawo la API yowonjezera komanso mtengo wa DOM patsamba lawebusayiti. Ndi zolemba zomwe zili ndi udindo wolumikizana ndi tsambali. Zowonjezera zomwe zimagwiritsa ntchito mtengo wa DOM zimapanga izi muzolemba - mwachitsanzo, otsekereza ad kapena omasulira. Komanso, zolemba zomwe zili mkati zimatha kulumikizana ndi tsambalo kudzera muyeso postMessage.
Tsamba latsamba lawebusayiti
Ili ndiye tsamba lenilenilo. Zilibe chochita ndikuwonjezera ndipo zilibe mwayi wofikira pamenepo, pokhapokha ngati gawo la tsambali silinasonyezedwe mu chiwonetsero (zambiri pansipa).
Kusinthana kwa uthenga
Magawo osiyanasiyana a pulogalamuyi ayenera kusinthanitsa mauthenga wina ndi mnzake. Pali API ya izi runtime.sendMessage kutumiza meseji background и tabs.sendMessage kutumiza uthenga kutsamba (zolemba, zowonekera kapena tsamba lawebusayiti ngati likupezeka externally_connectable). Pansipa pali chitsanzo mukalowa mu Chrome API.
// Сообщением может быть любой JSON сериализуемый объект
const msg = {a: 'foo', b: 'bar'};
// extensionId можно не указывать, если мы хотим послать сообщение 'своему' расширению (из ui или контент скрипта)
chrome.runtime.sendMessage(extensionId, msg);
// Так выглядит обработчик
chrome.runtime.onMessage.addListener((msg) => console.log(msg))
// Можно слать сообщения вкладкам зная их id
chrome.tabs.sendMessage(tabId, msg)
// Получить к вкладкам и их id можно, например, вот так
chrome.tabs.query(
{currentWindow: true, active : true},
function(tabArray){
tabArray.forEach(tab => console.log(tab.id))
}
)
Kuti mulumikizane kwathunthu, mutha kupanga kulumikizana kudzera runtime.connect. Poyankha tidzalandira runtime.Port, kumene, pamene ili lotseguka, mukhoza kutumiza nambala iliyonse ya mauthenga. Kumbali ya kasitomala, mwachitsanzo, contentscript, zikuwoneka motere:
// Опять же extensionId можно не указывать при коммуникации внутри одного расширения. Подключение можно именовать
const port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question === "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question === "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
Seva kapena maziko:
// Обработчик для подключения 'своих' вкладок. Контент скриптов, popup или страниц расширения
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name === "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke === "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer === "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer === "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
// Обработчик для подключения внешних вкладок. Других расширений или веб страниц, которым разрешен доступ в манифесте
chrome.runtime.onConnectExternal.addListener(function(port) {
...
});
Palinso chochitika onDisconnect ndi njira disconnect.
Chithunzi cha ntchito
Tiyeni tipange chowonjezera cha msakatuli chomwe chimasunga makiyi achinsinsi, chimapereka mwayi wodziwa zambiri za anthu (adiresi, kiyi yapagulu imalumikizana ndi tsambali ndikuloleza mapulogalamu a chipani chachitatu kuti apemphe siginecha yamalonda.
Kupititsa patsogolo ntchito
Ntchito yathu iyenera kuyanjana ndi wogwiritsa ntchito ndikupereka tsambalo ndi API yoyimbira njira (mwachitsanzo, kusaina zochitika). Chitani ndi chimodzi chokha contentscript sichigwira ntchito, chifukwa imangokhala ndi DOM, koma osati ku JS ya tsamba. Lumikizani kudzera runtime.connect sitingathe, chifukwa API ikufunika pamadera onse, ndipo zokhazokha zokhazokha zikhoza kufotokozedwa muwonetsero. Chifukwa chake, chithunzichi chikuwoneka motere:
Padzakhala script ina - inpage, zomwe tidzalowetsa patsamba. Idzayenda m'mawu ake ndikupereka API yogwira ntchito ndikuwonjezera.
{
// Имя и описание, версия. Все это будет видно в браузере в chrome://extensions/?id=<id расширения>
"name": "Signer",
"description": "Extension demo",
"version": "0.0.1",
"manifest_version": 2,
// Скрипты, которые будут исполнятся в background, их может быть несколько
"background": {
"scripts": ["background.js"]
},
// Какой html использовать для popup
"browser_action": {
"default_title": "My Extension",
"default_popup": "popup.html"
},
// Контент скрипты.
// У нас один объект: для всех url начинающихся с http или https мы запускаем
// contenscript context со скриптом contentscript.js. Запускать сразу по получении документа для всех фреймов
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"contentscript.js"
],
"run_at": "document_start",
"all_frames": true
}
],
// Разрешен доступ к localStorage и idle api
"permissions": [
"storage",
// "unlimitedStorage",
//"clipboardWrite",
"idle"
//"activeTab",
//"webRequest",
//"notifications",
//"tabs"
],
// Здесь указываются ресурсы, к которым будет иметь доступ веб страница. Тоесть их можно будет запрашивать fetche'м или просто xhr
"web_accessible_resources": ["inpage.js"]
}
Pangani zopanda kanthu background.js, popup.js, inpage.js ndi contentscript.js. Timawonjezera popup.html - ndipo pulogalamu yathu ikhoza kuyikidwa kale mu Google Chrome ndikuwonetsetsa kuti ikugwira ntchito.
Kuti mutsimikizire izi, mutha kutenga code kuchokera pano. Kuphatikiza pa zomwe tidachita, ulalowo udakonza msonkhano wa polojekitiyo pogwiritsa ntchito webpack. Kuti muwonjezere pulogalamu pa msakatuli, mu chrome: // zowonjezera muyenera kusankha kutsitsa osatsegula ndi chikwatu chokhala ndi cholumikizira chofananira - kwa ife dist.
Kufikira kwa script script console kumachitika kudzera pa console ya tsamba lomwe lakhazikitsidwa.
Kusinthana kwa uthenga
Chifukwa chake, tiyenera kukhazikitsa njira ziwiri zoyankhulirana: inpage <-> maziko ndi popup <-> maziko. Mutha, inde, kungotumiza mauthenga ku doko ndikupanga protocol yanu, koma ndimakonda njira yomwe ndidawona mu polojekiti yotseguka ya metamask.
Izi ndizowonjezera msakatuli kuti mugwire ntchito ndi netiweki ya Ethereum. Momwemo, magawo osiyanasiyana a pulogalamuyi amalumikizana kudzera pa RPC pogwiritsa ntchito laibulale ya dnode. Zimakupatsani mwayi wosinthana mwachangu komanso mosavuta ngati mutapereka ma nodejs ngati choyendera (kutanthauza chinthu chomwe chimagwiritsa ntchito mawonekedwe omwewo):
import Dnode from "dnode/browser";
// В этом примере условимся что клиент удаленно вызывает функции на сервере, хотя ничего нам не мешает сделать это двунаправленным
// Cервер
// API, которое мы хотим предоставить
const dnode = Dnode({
hello: (cb) => cb(null, "world")
})
// Транспорт, поверх которого будет работать dnode. Любой nodejs стрим. В браузере есть бибилиотека 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)
// Клиент
const dnodeClient = Dnode() // Вызов без агрумента значит что мы не предоставляем API на другой стороне
// Выведет в консоль world
dnodeClient.once('remote', remote => {
remote.hello(((err, value) => console.log(value)))
})
Tsopano tipanga kalasi yofunsira. Idzapanga zinthu za API pazithunzi ndi tsamba lawebusayiti, ndikupanga dnode kwa iwo:
import Dnode from 'dnode/browser';
export class SignerApp {
// Возвращает объект API для ui
popupApi(){
return {
hello: cb => cb(null, 'world')
}
}
// Возвращает объет API для страницы
pageApi(){
return {
hello: cb => cb(null, 'world')
}
}
// Подключает popup ui
connectPopup(connectionStream){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(remote)
})
}
// Подключает страницу
connectPage(connectionStream, origin){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(origin);
console.log(remote)
})
}
}
Apa ndi pansipa, m'malo mwa chinthu chapadziko lonse lapansi cha Chrome, timagwiritsa ntchito extensionApi, yomwe imalowa mu Chrome mu msakatuli wa Google ndi msakatuli wina. Izi zachitika kuti zigwirizane ndi msakatuli, koma cholinga cha nkhaniyi munthu atha kugwiritsa ntchito 'chrome.runtime.connect'.
Tiyeni tipange chitsanzo cha ntchito mu script yakumbuyo:
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
const app = new SignerApp();
// onConnect срабатывает при подключении 'процессов' (contentscript, popup, или страница расширения)
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
// При установке соединения можно указывать имя, по этому имени мы и оппределяем кто к нам подлючился, контентскрипт или ui
if (processName === 'contentscript'){
const origin = remotePort.sender.url
app.connectPage(portStream, origin)
}else{
app.connectPopup(portStream)
}
}
Popeza dnode imagwira ntchito ndi mitsinje, ndipo timalandira doko, kalasi ya adaputala ikufunika. Amapangidwa pogwiritsa ntchito laibulale yowerengeka, yomwe imagwiritsa ntchito mitsinje ya nodejs mu msakatuli:
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import Dnode from 'dnode/browser';
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupUi().catch(console.error);
async function setupUi(){
// Также, как и в классе приложения создаем порт, оборачиваем в stream, делаем dnode
const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
const connectionStream = new PortStream(backgroundPort);
const dnode = Dnode();
connectionStream.pipe(dnode).pipe(connectionStream);
const background = await new Promise(resolve => {
dnode.once('remote', api => {
resolve(api)
})
});
// Делаем объект API доступным из консоли
if (DEV_MODE){
global.background = background;
}
}
Kenako timapanga kulumikizana mu script yokhutira:
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import PostMessageStream from 'post-message-stream';
setupConnection();
injectScript();
function setupConnection(){
const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
const backgroundStream = new PortStream(backgroundPort);
const pageStream = new PostMessageStream({
name: 'content',
target: 'page',
});
pageStream.pipe(backgroundStream).pipe(pageStream);
}
function injectScript(){
try {
// inject in-page script
let script = document.createElement('script');
script.src = extensionApi.extension.getURL('inpage.js');
const container = document.head || document.documentElement;
container.insertBefore(script, container.children[0]);
script.onload = () => script.remove();
} catch (e) {
console.error('Injection failed.', e);
}
}
Popeza tikufuna API osati muzolemba, koma mwachindunji patsamba, timachita zinthu ziwiri:
Timapanga mitsinje iwiri. Imodzi - molunjika patsamba, pamwamba pa postMessage. Kwa izi timagwiritsa ntchito paketi iyi kuchokera kwa omwe amapanga metamask. Mtsinje wachiwiri ndikuyambira padoko lolandilidwa kuchokera runtime.connect. Tiyeni tigule. Tsopano tsambalo lidzakhala ndi mtsinje kumbuyo.
Lowetsani script mu DOM. Tsitsani script (kulowa kwake kunaloledwa mu chiwonetsero) ndikupanga tag script ndi zomwe zili mkati mwake:
import PostMessageStream from 'post-message-stream';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
setupConnection();
injectScript();
function setupConnection(){
// Стрим к бекграунду
const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
const backgroundStream = new PortStream(backgroundPort);
// Стрим к странице
const pageStream = new PostMessageStream({
name: 'content',
target: 'page',
});
pageStream.pipe(backgroundStream).pipe(pageStream);
}
function injectScript(){
try {
// inject in-page script
let script = document.createElement('script');
script.src = extensionApi.extension.getURL('inpage.js');
const container = document.head || document.documentElement;
container.insertBefore(script, container.children[0]);
script.onload = () => script.remove();
} catch (e) {
console.error('Injection failed.', e);
}
}
Tsopano tikupanga chinthu cha api mu inpage ndikuchiyika chapadziko lonse lapansi:
import PostMessageStream from 'post-message-stream';
import Dnode from 'dnode/browser';
setupInpageApi().catch(console.error);
async function setupInpageApi() {
// Стрим к контентскрипту
const connectionStream = new PostMessageStream({
name: 'page',
target: 'content',
});
const dnode = Dnode();
connectionStream.pipe(dnode).pipe(connectionStream);
// Получаем объект API
const pageApi = await new Promise(resolve => {
dnode.once('remote', api => {
resolve(api)
})
});
// Доступ через window
global.SignerApp = pageApi;
}
Empty API ndi chiyambi. Pa tsamba, titha kuyitcha moni ntchito motere:
Kugwira ntchito ndi callback ntchito mu JS yamakono ndi makhalidwe oipa, kotero tiyeni tilembe wothandizira wamng'ono kuti apange dnode yomwe imakulolani kuti mudutse chinthu cha API ku zipangizo.
import {cbToPromise, transformMethods} from "../../src/utils/setupDnode";
const pageApi = await new Promise(resolve => {
dnode.once('remote', remoteApi => {
// С помощью утилит меняем все callback на promise
resolve(transformMethods(cbToPromise, remoteApi))
})
});
Ndipo kuyimba ntchito kumabweretsa lonjezo:
Mtundu wokhala ndi ntchito za asynchronous zilipo apa.
Ponseponse, njira ya RPC ndi mtsinje ikuwoneka yosinthika: titha kugwiritsa ntchito kuchulukitsa kwa nthunzi ndikupanga ma API angapo osiyanasiyana pantchito zosiyanasiyana. M'malo mwake, dnode ingagwiritsidwe ntchito kulikonse, chinthu chachikulu ndikukulunga zoyendera ngati mtsinje wa nodejs.
Njira ina ndi mtundu wa JSON, womwe umagwiritsa ntchito protocol ya JSON RPC 2. Komabe, imagwira ntchito ndi zoyendera zapadera (TCP ndi HTTP(S)), zomwe sizikugwira ntchito kwa ife.
Internal state and localStorage
Tidzafunika kusunga mkati mwa pulogalamuyo - osachepera makiyi osayina. Titha kuwonjezera dziko mosavuta pakugwiritsa ntchito ndi njira zosinthira mu popup API:
Tidzagwiritsa ntchito laibulale ya mobx (https://github.com/mobxjs/mobx). Chosankhacho chinagwera pa icho chifukwa sindinayenera kugwira ntchito nacho, koma ndinkafunadi kuchiphunzira.
Tiyeni tiwonjeze kuyambika kwa zomwe zidayambira ndikupanga sitolo kuti iwonekere:
import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
export class SignerApp {
constructor(initState = {}) {
// Внешне store так и останется тем же объектом, только теперь все его поля стали proxy, которые отслеживают доступ к ним
this.store = observable.object({
keys: initState.keys || [],
});
}
// Методы, которые меняют observable принято оборачивать декоратором
@action
addKey(key) {
this.store.keys.push(key)
}
@action
removeKey(index) {
this.store.keys.splice(index, 1)
}
...
}
"Pansi pa hood," mobx yalowa m'malo onse ogulitsa ndi proxy ndikuyimba mafoni onse kwa iwo. Zidzakhala zotheka kulembetsa ku mauthengawa.
Pansipa nthawi zambiri ndimagwiritsa ntchito mawu oti "pamene ndikusintha", ngakhale izi sizolondola. Mobx amatsata mwayi wopita kuminda. Opeza ndi ma setter a zinthu zofananira zomwe laibulale imapanga zimagwiritsidwa ntchito.
Zokongoletsa zochita zimagwira ntchito ziwiri:
Munjira yokhwima ndi mbendera ya enforceActions, mobx imaletsa kusintha dziko mwachindunji. Zimatengedwa kuti ndi bwino kugwira ntchito pansi pa mikhalidwe yovuta.
Ngakhale ntchito ikusintha boma kangapo - mwachitsanzo, timasintha magawo angapo m'mizere ingapo yamakhodi - owonera amadziwitsidwa pokhapokha akamaliza. Izi ndizofunikira makamaka kwa kutsogolo, komwe zosintha zosafunikira za boma zimabweretsa kuperekedwa kosafunikira kwa zinthu. Kwa ife, palibe choyamba kapena chachiwiri chomwe chili chofunikira kwambiri, koma tidzatsatira njira zabwino kwambiri. Ndichizoloŵezi chophatikizira okongoletsa ku ntchito zonse zomwe zimasintha malo omwe amawonedwa.
Kumbuyo tidzawonjezera kuyambitsa ndikusunga boma mu LocalStorage:
import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
// Вспомогательные методы. Записывают/читают объект в/из localStorage виде JSON строки по ключу 'store'
import {loadState, saveState} from "./utils/localStorage";
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupApp();
function setupApp() {
const initState = loadState();
const app = new SignerApp(initState);
if (DEV_MODE) {
global.app = app;
}
// Setup state persistence
// Результат reaction присваивается переменной, чтобы подписку можно было отменить. Нам это не нужно, оставлено для примера
const localStorageReaction = reaction(
() => toJS(app.store), // Функция-селектор данных
saveState // Функция, которая будет вызвана при изменении данных, которые возвращает селектор
);
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
if (processName === 'contentscript') {
const origin = remotePort.sender.url
app.connectPage(portStream, origin)
} else {
app.connectPopup(portStream)
}
}
}
Zomwe zimachitika ndizosangalatsa apa. Lili ndi mfundo ziwiri:
Chosankha deta.
Wothandizira yemwe adzayitanidwe ndi datayi nthawi iliyonse ikasintha.
Kuti titetezeke kwambiri, tidzawonjezera malo otsekedwa ku pulogalamuyo, momwe sipadzakhalanso mwayi wopeza makiyi nkomwe. Tidzasamutsa zokhazo ku malo okhoma chifukwa cha nthawi yotseka.
Mobx imakupatsani mwayi wosunga zowerengeka zochepa chabe, ndipo zina zonse zimawerengedwa motengera momwemo. Izi ndi zomwe zimatchedwa computed properties. Atha kufananizidwa ndi mawonedwe mu database:
import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// Утилиты для безопасного шифрования строк. Используют crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";
export class SignerApp {
constructor(initState = {}) {
this.store = observable.object({
// Храним пароль и зашифрованные ключи. Если пароль null - приложение locked
password: null,
vault: initState.vault,
// Геттеры для вычислимых полей. Можно провести аналогию с view в бд.
get locked(){
return this.password == null
},
get keys(){
return this.locked ?
undefined :
SignerApp._decryptVault(this.vault, this.password)
},
get initialized(){
return this.vault !== undefined
}
})
}
// Инициализация пустого хранилища новым паролем
@action
initVault(password){
this.store.vault = SignerApp._encryptVault([], password)
}
@action
lock() {
this.store.password = null
}
@action
unlock(password) {
this._checkPassword(password);
this.store.password = password
}
@action
addKey(key) {
this._checkLocked();
this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
}
@action
removeKey(index) {
this._checkLocked();
this.store.vault = SignerApp._encryptVault([
...this.store.keys.slice(0, index),
...this.store.keys.slice(index + 1)
],
this.store.password
)
}
... // код подключения и api
// private
_checkPassword(password) {
SignerApp._decryptVault(this.store.vault, password);
}
_checkLocked() {
if (this.store.locked){
throw new Error('App is locked')
}
}
// Методы для шифровки/дешифровки хранилища
static _encryptVault(obj, pass){
const jsonString = JSON.stringify(obj)
return encrypt(jsonString, pass)
}
static _decryptVault(str, pass){
if (str === undefined){
throw new Error('Vault not initialized')
}
try {
const jsonString = decrypt(str, pass)
return JSON.parse(jsonString)
}catch (e) {
throw new Error('Wrong password')
}
}
}
Tsopano timangosunga makiyi obisika ndi mawu achinsinsi. Zina zonse zimawerengedwa. Timasamutsira kumalo otsekedwa pochotsa mawu achinsinsi ku boma. API yapagulu tsopano ili ndi njira yoyambira kusungirako.
import CryptoJS from 'crypto-js'
// Используется для осложнения подбора пароля перебором. На каждый вариант пароля злоумышленнику придется сделать 5000 хешей
function strengthenPassword(pass, rounds = 5000) {
while (rounds-- > 0){
pass = CryptoJS.SHA256(pass).toString()
}
return pass
}
export function encrypt(str, pass){
const strongPass = strengthenPassword(pass);
return CryptoJS.AES.encrypt(str, strongPass).toString()
}
export function decrypt(str, pass){
const strongPass = strengthenPassword(pass)
const decrypted = CryptoJS.AES.decrypt(str, strongPass);
return decrypted.toString(CryptoJS.enc.Utf8)
}
Msakatuli ali ndi API yopanda pake yomwe mutha kulembetsa ku chochitika - kusintha kwa boma. State, motero, akhoza kukhala idle, active и locked. Popanda ntchito mutha kukhazikitsa nthawi yomaliza, ndipo zokhoma zimayikidwa pomwe OS yokha yatsekedwa. Tisinthanso chosankha kuti tisunge ku LocalStorage:
import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
import {loadState, saveState} from "./utils/localStorage";
const DEV_MODE = process.env.NODE_ENV !== 'production';
const IDLE_INTERVAL = 30;
setupApp();
function setupApp() {
const initState = loadState();
const app = new SignerApp(initState);
if (DEV_MODE) {
global.app = app;
}
// Теперь мы явно узываем поле, которому будет происходить доступ, reaction отработает нормально
reaction(
() => ({
vault: app.store.vault
}),
saveState
);
// Таймаут бездействия, когда сработает событие
extensionApi.idle.setDetectionInterval(IDLE_INTERVAL);
// Если пользователь залочил экран или бездействовал в течение указанного интервала лочим приложение
extensionApi.idle.onStateChanged.addListener(state => {
if (['locked', 'idle'].indexOf(state) > -1) {
app.lock()
}
});
// Connect to other contexts
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
if (processName === 'contentscript') {
const origin = remotePort.sender.url
app.connectPage(portStream, origin)
} else {
app.connectPopup(portStream)
}
}
}
Chifukwa chake, tifika ku chinthu chofunikira kwambiri: kupanga ndi kusaina zochitika pa blockchain. Tidzagwiritsa ntchito WAVES blockchain ndi library mafunde-zochitika.
Mawonekedwe amafunikira mwayi wopita ku boma la ntchito. Kumbali ya UI tidzachita observable tchulani ndikuwonjezera ntchito ku API yomwe isintha izi. Tiyeni tiwonjeze observable kupita ku chinthu cha API cholandiridwa kuchokera kumbuyo:
import {observable} from 'mobx'
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {cbToPromise, setupDnode, transformMethods} from "./utils/setupDnode";
import {initApp} from "./ui/index";
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupUi().catch(console.error);
async function setupUi() {
// Подключаемся к порту, создаем из него стрим
const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
const connectionStream = new PortStream(backgroundPort);
// Создаем пустой observable для состояния background'a
let backgroundState = observable.object({});
const api = {
//Отдаем бекграунду функцию, которая будет обновлять observable
updateState: async state => {
Object.assign(backgroundState, state)
}
};
// Делаем RPC объект
const dnode = setupDnode(connectionStream, api);
const background = await new Promise(resolve => {
dnode.once('remote', remoteApi => {
resolve(transformMethods(cbToPromise, remoteApi))
})
});
// Добавляем в background observable со стейтом
background.state = backgroundState;
if (DEV_MODE) {
global.background = background;
}
// Запуск интерфейса
await initApp(background)
}
Pamapeto pake timayamba kupereka mawonekedwe a ntchito. Ichi ndi ntchito momwe. Chinthu chakumbuyo chimangoperekedwa pogwiritsa ntchito zida. Zingakhale zolondola, ndithudi, kupanga ntchito yosiyana ya njira ndi sitolo ya boma, koma zolinga za nkhaniyi ndizokwanira:
import {render} from 'react-dom'
import App from './App'
import React from "react";
// Инициализируем приложение с background объектом в качест ве props
export async function initApp(background){
render(
<App background={background}/>,
document.getElementById('app-content')
);
}
Ndi mobx ndizosavuta kuyamba kupereka pomwe deta ikusintha. Timangopachika chokongoletsera chowonera kuchokera pa phukusi mobx-react pa chigawocho, ndi kupereka kudzayitanidwa kokha pamene zowoneka zilizonse zotchulidwa ndi chigawocho chikusintha. Simufunika mapStateToProps aliwonse kapena kulumikizana ngati mu redux. Chilichonse chimagwira ntchito bwino:
import React, {Component, Fragment} from 'react'
import {observer} from "mobx-react";
import Init from './components/Initialize'
import Keys from './components/Keys'
import Sign from './components/Sign'
import Unlock from './components/Unlock'
@observer // У Компонета с этим декоратом будет автоматически вызван метод render, если будут изменены observable на которые он ссылается
export default class App extends Component {
// Правильно конечно вынести логику рендера страниц в роутинг и не использовать вложенные тернарные операторы,
// и привязывать observable и методы background непосредственно к тем компонентам, которые их используют
render() {
const {keys, messages, initialized, locked} = this.props.background.state;
const {lock, unlock, addKey, removeKey, initVault, deleteVault, approve, reject} = this.props.background;
return <Fragment>
{!initialized
?
<Init onInit={initVault}/>
:
locked
?
<Unlock onUnlock={unlock}/>
:
messages.length > 0
?
<Sign keys={keys} message={messages[messages.length - 1]} onApprove={approve} onReject={reject}/>
:
<Keys keys={keys} onAdd={addKey} onRemove={removeKey}/>
}
<div>
{!locked && <button onClick={() => lock()}>Lock App</button>}
{initialized && <button onClick={() => deleteVault()}>Delete all keys and init</button>}
</div>
</Fragment>
}
}
Ngati mwawerenga nkhaniyi mpaka kumapeto, koma muli ndi mafunso, mutha kuwafunsa nkhokwe zowonjezera. Kumeneko mudzapezanso zochita pa sitepe iliyonse yosankhidwa.