αα·αααΌα ααααΆαααααααα "αααΆαααΈαααααα" ααΌαα αααααα·ααΈαα·ααααααΆαααααΌαααΆαααααααααααααααα
- αα·αα αΆαααΆα ααααααΆαα»αααΌαααααΆααα·ααααααααΆαα½αααΉαααΆαα αΌαααααα’αααααααΎ αα·αααΆααααααααΆααααα ααααααΆαααααΆαα αΌαααααΎααααΌαααΆααααααΆαα»αααΆαααααα»ααααα’αααααααΎααααΆααααααΆαα α αΎαααΆααααααΆααα’αααΈααΆαααααΉαααααΌααααααα½αααααΎαα‘αΎααα ααααα·ααα·ααΈααΆαα
- αα·αα αΆαααΆα αααααΎαααΆαααΈαααααα αααααα·ααααΆαααααα·ααΈα’αΆα ααααΌαααΆαααααα·ααααα·αα ααΎαααααΆα blockchain αααααΆαααααααααααΆα’αΆα ααααΎαα ααΆαααΎααααΈαααααΆαα»αα ααα½ααα·αααααααααααααΌαααΆαα
ααΆααααααααααα»ααα»ααααα·ααΆαα ααα½α 2 αααααΆααααα’αααααααΎααααΆαα - ααΆααΌααααααααΉα αα·αααααααααααααααααα·ααΈαα»αααα Hardware wallets ααΆαα αααΎαααΆααα»ααααα·ααΆαααααα ααα»αααααα·ααΆαααααΎ αα·ααα ααααΆαααΈα₯ααα·ααααα ααα»ααααααααααααααααααααα·ααΈαα»αααααΊααΆααΆααα½ααααα αΌαααααΆααααα’α₯αααα αααααα»ααααα·ααΆα αα·αααΆαααΆααααα½αααααΆαααααΎααααΆαα α αΎαααα’αΆα α₯ααα·αααααααΆαααααα»ααααααΆααα’αααααααΎααααΆααα α»αααααααααααα
ααααα·αααΈα
ααα»α
ααΆααα’ααααα ααΎαα
αααααααΎαααααααααααααααααΆααα»ααααα·ααΆααααα»α ααααα½αααααα½ααααααΆαα’αα·αααααααααααα·ααΈαα·ααααααΆα αααααααα API ααΆαααααααααΆααααααΎααΆαααΆαα½αααααα·ααααα·ααΆα αα·αα αααααααΆα
ααΎαααΉαααααΆααα’αααα’αααΈαααα·ααααααααααΆααααααα
α’ααααααααααΉαααΆαααΆαααααΆαααΆααα αΆα α α’αααΈαααααααααααααααααααααααααα·ααΈαα»ααααααααΆαα§ααΆα αααααΌα αα·αααΌαααα’ααααααα α’αααα’αΆα
ααααααααααααΌαααΆααα’αααα
αααα»α
αααααααα·ααααααααααααααααααααααααα·ααΈαα»ααα
ααααααααααααααααα·ααΈαα»αααααΆαααΆααααΈααΌαααΆαααΆααααα αΎαα αα½αααααΆααααα αΆααααα½ααα αααα»α Internet Explorer αααα‘αααααα·ααααα»αααααΆα 1999 αα αααα»α Firefox αααα»αααααΆα 2004 α ααααααΆαααΆααααα α’ααααααααααΆααΌαααα αΎααα·αααΆααααααααΆααααα½ααααααΆαααααααααααααααα
ααΎαα’αΆα αα·ααΆαααΆαααΆααΆααΆααααα αΆααααα½αααΆαα½αααΉαααααααααααααα αααα»αααααααΈ XNUMX αααα Google Chrome α ααΆααΆααα·αααΆαα αα·αααΆαααΆααααααΆααααΆαα½αααααα ααα»ααααααΆααΊααΆ Chrome API αααααΆαααααΆαααΆααΌαααααΆαααααααΆα αααααΆααααααΎαααααΈααααΆααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·αααΆαα αααΎα αα·αααΆαα αΆααααααα·ααΈαααααααΆααααααΆαα½αααα Chrome αα·αααΆααααααααααααΆααααααΆααααααααααααααααααα·ααΈαα»αααα
Mozilla ααΆααααααααΆαααααΆαααααα½α ααα»αααααααααΎαααΎαααΈααΆαααααα·ααααααααααααααααααα Chrome αααα»αα αα»αααΆααααααα α α·ααααααααΎα API αααααααΌαααααΆα αα αααα»αααααΆα 2015 ααΆααααα·ααααα½α ααααΎααααα Mozilla αααα»ααα·ααααα½αααααΌαααΆααααααΎαα‘αΎααα αααα»α World Wide Web Consortium (W3C) ααΎααααΈααααΎααΆαααΎαααααααα αα αααααααααααααααααααααααααα·ααΈαα»αααα
ααααααααααα API αααααΆαααααΆαααααααΆαα Chrome ααααΌαααΆαααααΆααΌαααααΆαα ααΆαααΆααααααααΌαααΆαα’αα»αααααααααΆαααΆαααΆααααααΈαααα»αα αα»α Microsoft (Google ααΆαααα·ααααα·αα
αΌααα½ααααα»αααΆαα’αα·αααααααααααααΆα) α αΎαααΆααααααααα
ααααΈααααΆαα
αααΆαααα½αααΆαααα
α
ααααα
ααΆααααΌαααΆα ααΆααααααΆααααααΌαααΆαααΆααααααα Edge, Firefox αα·α Opera (α
αααΆαααΆ Chrome αα·αααΆααα
αααα»ααααααΈααααα)α ααα»ααααααΆααα·α αααααααΆαααΊααααΌαααααΆααΆαα½α Chrome αααααααΆαα·αααΆααααΌαααΆαααααααααααα’ααααΎαααααααααααααααααΆα α’αααα’αΆα
α’αΆαααααααα’αααΈ WebExtensions API
αα ααΆαααααααααααααα
α―αααΆααααα½αααααααααααΌαααΆαααΆαααΆααααααΆαααααααααααααααΊ manifest (manifest.json) α ααΆααααΆ "α ααα»α α αΌα" αααααΆααααΆααααααΈαα
ααααααα
αααααΆαααΆααααααΆαα α―αααΆα manifest ααΊααΆα―αααΆα JSON ααααΉαααααΌαα ααΆααα·αααααΆαααααααααα manifest ααΆαα½αααΉαααααααΆαα’αααΈααααΆαα½αααααΌαααΆαααΆαααα ααααααααα·ααΈαα»αααα’αΆα
ααΎαααΆαα
ααααΆααα α»α ααααα·αααΆααα αααα»αααΆααααααΆαα "α’αΆα " αα·αα’αΎααΎ (ααΆααααα α»ααααΆαααΆααααααα Chrome αα·α Firefox ααα»ααααααααααααααααα ααααααααααΎαααΆα)α
α αΎααααα»αα ααααΌαααα α·ααααα»αααΆααααΎα ααα»α αα½αα ααα½αα
- ααααααΆαααααα - ααααα»ααααα½ααααα
αΌαααΆαααΆααααααα
- ααααααΈα - α’αΆααααααααααΈααααααΉαααααΌαααΆαααααα·ααααα·αααα»αααα·ααααααααΆαααααα (ααΎαααΉααα·ααΆαα’αααΈααΏααααααααα·α ααααααα);
- ααααα - αααα½αα±ααααααααΈααααααΉαααααΌαααΆαααααα·ααααα·αααα»ααααααααα α’αααα’αΆα αααααΆαα html ααΆαα½αααΆαα·ααΆα αααα»αααααΈααα ααΆαααααααΈαααΉαααααΌαααΆααα·αα’αΎααΎ α αΎαααααααΈαααΉαααααΌααααα αΌααα αααα»ααααααααααΉαααΆαα
- ααααΌ β ααααααααΈα ααααα·αααΎαα·αααΆααααααΆαα αααααα·ααΈαα»αααααΉα "αααααΆαα" ααααΎαααΆαααααααΆαααααα αα ααααααααΆαα·α αΆαααΆααΆααΆαα·ααααα»αααααΎα’αααΈ α αΎαα αΆααααααΎαααΆα‘αΎααα·αααααα·αααΎα αΆαααΆα αα ααΎαα·αααΌα αααααα αααααααΉααααααααΎαααΆααα αααααααααααα·ααΈαα»αααααααΌαααΆααα·αα αα·αααααΌαααΆαααΆαααααα αααα»α Firefox ααα
- content_scripts - α’αΆααααααααα»αααα’αα»ααααΆαα±ααα’ααααααα»αααααααΈαααααααααααΆαα
ααΆααααα αααααααααααααααΆα ααααα»ααΈαα½ααααΆαααΆαααααΆαααααΌα
ααΆααααααα
- ααΆαααααα½α -
ααααΌ url ααααααααααΆααΎααααααΈαααΆαα·ααΆααΆααααΆαααα½αααΉαααααΌαααΆααα½ααααα αΌαα¬α’ααα - js - αααααΈααααααααΈααααααΉαααααΌαααΆααααα»ααα αααα»αααΆαααααα½αααα;
- exclude_match - αα·αααΆαααααα
αΌαααΈααΆα
match
URL αααααααΌαααΉαααΆααααα
- ααΆαααααα½α -
- page_action - ααΆααα·αααΊααΆααααα»αααααα½ααα»αααααΌαα
ααααααΌαααααΆααααααααΌαααΆααααα αΆααα
ααΆααααΉααααΆαα’αΆααααααΆααα
αααα»ααααααα·ααΈαα»ααααα·αα’ααααααααααΆαα½αααΆα ααΆααα’αα»ααααΆαα±ααα’ααααααα αΆααααα’α½α
ααα
α‘αΎααααααααΌαααΆαααααααααααααΎ HTML, CSS αα·α JS ααααΆαααααα½αααααα’αααα
- default_popup - ααααΌααα ααΆααα―αααΆα HTML αααααΆαα ααα»α αααααΆααααα α‘αΎα α’αΆα ααΆα CSS αα·α JS α
- αα·αααα· - α’αΆαααααααΆααααααααααααα·αααα·αααααααααααα αα·αααα·ααΆα α£ αααααα αααααααΌαααΆααα·αααααΆαααΆααααα’α·α
αα ααΈααα - web_accessible_resources - ααααΆαααααααααααααααααα αααααα’αΆα ααααΎαα»α α§ααΆα ααα ααΌαααΆα JS, CSS, α―αααΆα HTML α
- externally_connectable β αα ααΈαααα’αααα’αΆα αααααΆαααααΆαα αααΆααααΌαααααααααΆααααααααααααααα αα·ααααααααα ααααααααα’αααα’αΆα ααααΆααααΆαα αααα’αΆα ααΆααααα·αααΈααΈα α¬αααααααΆααααα αα·αααααΎαααΆααα αααα»α Firefox ααα
ααα·ααααααα·ααααα·
αααααααααααααΆαααα·ααααααα·ααααα·ααΌαα ααα½αααΈ αααααΊαααααα·ααΈααΆαααΈααααααααααΆαααααα·ααααααααααΆααααΆαα αΌαααααΎ API αααααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·αα
ααα·ααααααααααααα
API ααΆαα αααΎαααΆααα ααΈαααα αα αααα»αααα·ααααααα½ααα "ααααα ":
- αααααααααααΆαααααα - ααααα "ααΆαααααα" αααααααααααααα α―αααΆαααααΌαααΆααααααΆαααα αααα»α manifest αααααααΎααααΆααα α»α "ααααααΆαααααα" α
- αααααααα
α‘αΎα β αααααααα
α‘αΎααααααα
α‘αΎααα
αααα’αααα
α»α
ααΎααΌαααααΆααααααααααααα αα
αααα»αααα
ααααΈαααααααΆααα
browser_action
->default_popup
. - αααααααααΆαααααα½α - ααααααααααα "ααΆαααααα
" αα
αααα»αααααΆααααΆα
ααααα‘αααααα·αααααΆα
chrome-extension://<id_ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ>/customPage.html
.
ααα·αααααααΆααααα―αααΆαααααΈαααα’α½α αα·αααααΆαααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·αα αααααααααααΆαααααα ααΆααα αααα»αα αααΆααα αααααααα½α α αΎααααααααααΎαααΆα (ααααΈααΎααααααΊαααααααααΉαααα·ααΆααα αα ααααααααααααΈαααααααΆααααααααααΌαααΆαααΎαααααΎαααΆααααααααΉαααα·ααΆααααα½α α αΎα "ααααΆαα" αααααΆααααΈααααΎαααΆαααααααΆ)α αααααααα α‘αΎα ααΆααα αααααααααα’α½α ααα α‘αΎαααΎα αα·α αααααααααΆαααααα½α - αααααααααααααΆααααΆαα½αααΆααΎαα αα·αααΆαααΆαα αΌααα ααΆααααααΆαααααααααα αα·αααΆαα·ααΆαααααα½αααααΈααα·αααααααα
ααα·ααααααααΈαααΆαα·ααΆ
α―αααΆαααααααΈαααααΉαααΆαααααΌαααΆαααΎαααααΎαααΆαααΆαα½αααααΆαααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·αααΈαα½ααα ααΆααΆααα·αααα·α
αΌαααααΎααααααα API ααααααααααααααα αα·ααα
ααΆαααααααΆα DOM ααααα αααααα ααΆααΊααΆααααααΈαααΆαα·ααΆαααααα½ααα»αααααΌαα
ααααα’ααααααααααΆαα½ααααααα αααααααααααααααααα
ααααααΆα DOM ααααΎααΌα
ααααα
αααα»αααααααΈαααΆαα·ααΆ - α§ααΆα ααα α’ααααααααααΆααααΆαααααΆαααΆαα·ααααααα α¬α’αααααααααα ααααα ααααααΈαααΆαα·ααΆα’αΆα
ααΆααααααΆαα½ααααααααΆαααααααααααΆα postMessage
.
ααα·ααααααα ααααα
αααααΊααΆααα ααααααα·αα ααΆαα·αααΆαα’αααΈαααααααΌαααααΎααΆαα½αααααααααααα αα·ααα·αααΆααα·αααα·α αΌαααααΎαα ααΈααααα ααΎααααααααααΈαααααααααααααααααα·αααααΌαααΆαα ααα’α»ααααα αΆααααΆαα αααΆαααα αααα»αααΆααααα αΆα (ααααααααΎααΆααΆαααααα)α
ααΆαααααΎααΆα
ααααααααααααααααααα·ααΈααααΌαααααααΆααααααΌαααΆαααααΆαα
αα·ααα
ααα ααΆα API αααααΆααααΏαααα runtime.sendMessage
ααΎααααΈααααΎααΆα background
ΠΈ tabs.sendMessage
ααΎααααΈααααΎααΆααα
ααΆααααααααα½α (ααααααΈαααΆαα·ααΆ ααα
α‘αΎα α¬ααααααααααΆαααααα·αααΎααΆα externally_connectable
) ααΆαααααααααααΆα§ααΆα ααααα
αααα
αΌαααααΎ 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))
}
)
αααααΆααααΆαααααΆαααααααααααα α’αααα’αΆα
αααααΎαααΆααααααΆααααΆαααα 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 ααααΌαααΆααα
ααΎαααααΆααα’αα α αΎαααΆαααα
ααα»α
ααΆααααΆαααα½αααα»ααααααααα’αΆα
αααααΆααααΆααα
αααα»α manifest α ααΆααααααααααΆααααΆαααΉαααΎααα
ααΌα
αααα
ααΆααΉαααΆαααααααΈααα½αααα - inpage
αααααΎαααΉαα
αΆααα
αΌααα
αααα»ααααααα ααΆααΉαααααΎαααΆααααα»αααα·ααααααααΆ α αΎαααααα API αααααΆααααααΎααΆαααΆαα½ααααααααααααα
ΠΠ°ΡΠ°Π»ΠΎ
ααΌαααααααααααααααααα·ααΈαα»αααααΆααα’ααα’αΆα
ααααΆααα
αααα αΆααααααΎαααΆαα½α 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"]
}
αααααΎα background.js, popup.js, inpage.js αα·α contentscript.js αααα ααΎααααααα popup.html - α αΎααααααα·ααΈααααααΎαα’αΆα ααααΌαααΆααααα αΌααα αααα»α Google Chrome αα½α α αΎα α αΎαααααΌαααααΆααααΆααΆααααΎαααΆαα
ααΎααααΈαααααααααΆααααα α’αααα’αΆα
αααααααΌα
α₯α‘αΌαααααααααααααααααααααΎαααααΌαααΆαααα‘αΎα αα·αααααΎαααΆαα α’αααα’αΆα ααααΎαααΆαα§αααααα’αααα’αα·αααααααααααΆααααα·ααααααααααααΆααΌα ααΆααααααα
ααα α‘αΎα ->
ααΆαα αΌααα ααΆαααα»αααΌαααααααΈαααΆαα·ααΆααααΌαααΆαα’αα»ααααααΆαααααα»αααΌαααααααααααααΆααααΌαααΆαααΎαααααΎαααΆαα
ααΆαααααΎααΆα
ααΌα αααα ααΎαααααΌααααααΎααααααΆαααααΆααααααααΈαα inpage <-> background αα·α popup <-> backgroundα ααΆααΆααα·αααΆαα α’αααα’αΆα ααααΆααααααααΎααΆααα ααΆααα ααα αα·ααααααΎααα·ααΈααΆαααααΆαααααα½αααααα’ααα ααα»αααααααα»αα αΌαα α·ααααα·ααΈααΆαααααααααααα»αααΆαααΎααα αααα»ααααααααααααααΎαα αα metamask α
αααααΊααΆααααααααααααααααα·ααΈαα»ααααααααΆααααααΎααΆαααΆαα½ααααααΆα Ethereum α αα αααα»αααΆ ααααααααααααααααααα·ααΈααααΆααααααααΆαααα RPC αααααααΎαααααΆααα dnode α ααΆα’αα»ααααΆαα±ααα’ααααααα αααΆαααααΆααααααΌααααΆααα αα αα·αααΆααααα½α ααααα·αααΎα’ααααααααααΆααΆαα½α nodejs stream ααΆααΆαααΉααααααΌα (ααΆααααααΆααααα»αααα’αα»ααααα ααα»α αααααΆααααΌα ααααΆ)α
import Dnode from "dnode/browser";
// Π ΡΡΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΠΌΡΡ ΡΡΠΎ ΠΊΠ»ΠΈΠ΅Π½Ρ ΡΠ΄Π°Π»Π΅Π½Π½ΠΎ Π²ΡΠ·ΡΠ²Π°Π΅Ρ ΡΡΠ½ΠΊΡΠΈΠΈ Π½Π° ΡΠ΅ΡΠ²Π΅ΡΠ΅, Ρ
ΠΎΡΡ Π½ΠΈΡΠ΅Π³ΠΎ Π½Π°ΠΌ Π½Π΅ ΠΌΠ΅ΡΠ°Π΅Ρ ΡΠ΄Π΅Π»Π°ΡΡ ΡΡΠΎ Π΄Π²ΡΠ½Π°ΠΏΡΠ°Π²Π»Π΅Π½Π½ΡΠΌ
// CΠ΅ΡΠ²Π΅Ρ
// API, ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΌΡ Ρ
ΠΎΡΠΈΠΌ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²ΠΈΡΡ
const dnode = Dnode({
hello: (cb) => cb(null, "world")
})
// Π’ΡΠ°Π½ΡΠΏΠΎΡΡ, ΠΏΠΎΠ²Π΅ΡΡ
ΠΊΠΎΡΠΎΡΠΎΠ³ΠΎ Π±ΡΠ΄Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ dnode. ΠΡΠ±ΠΎΠΉ nodejs ΡΡΡΠΈΠΌ. Π Π±ΡΠ°ΡΠ·Π΅ΡΠ΅ Π΅ΡΡΡ Π±ΠΈΠ±ΠΈΠ»ΠΈΠΎΡΠ΅ΠΊΠ° 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)
// ΠΠ»ΠΈΠ΅Π½Ρ
const dnodeClient = Dnode() // ΠΡΠ·ΠΎΠ² Π±Π΅Π· Π°Π³ΡΡΠΌΠ΅Π½ΡΠ° Π·Π½Π°ΡΠΈΡ ΡΡΠΎ ΠΌΡ Π½Π΅ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅ΠΌ API Π½Π° Π΄ΡΡΠ³ΠΎΠΉ ΡΡΠΎΡΠΎΠ½Π΅
// ΠΡΠ²Π΅Π΄Π΅Ρ Π² ΠΊΠΎΠ½ΡΠΎΠ»Ρ world
dnodeClient.once('remote', remote => {
remote.hello(((err, value) => console.log(value)))
})
α₯α‘αΌααααααΎαααΉααααααΎαααααΆαααααααα·ααΈα ααΆααΉααααααΎαααααα» API αααααΆαααααααααα α‘αΎα αα·αααα ααααα α αΎααααααΎα dnode αααααΆαααα½αααΆα
import Dnode from 'dnode/browser';
export class SignerApp {
// ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΎΠ±ΡΠ΅ΠΊΡ API Π΄Π»Ρ ui
popupApi(){
return {
hello: cb => cb(null, 'world')
}
}
// ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΎΠ±ΡΠ΅Ρ API Π΄Π»Ρ ΡΡΡΠ°Π½ΠΈΡΡ
pageApi(){
return {
hello: cb => cb(null, 'world')
}
}
// ΠΠΎΠ΄ΠΊΠ»ΡΡΠ°Π΅Ρ popup ui
connectPopup(connectionStream){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(remote)
})
}
// ΠΠΎΠ΄ΠΊΠ»ΡΡΠ°Π΅Ρ ΡΡΡΠ°Π½ΠΈΡΡ
connectPage(connectionStream, origin){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(origin);
console.log(remote)
})
}
}
αα ααΈααα αα·αααΆαααααααααα½αα±ααααααα» Chrome ααα ααΎαααααΎ extensionApi αααα αΌαααααΎ Chrome αα αααα»ααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·ααααα Google αα·ααααααα·ααΈαα»αααααααααααα αααααααΌαααΆαααααΎαααααΆααααΆαααααααΆαααααααααα·ααΈαα»αααααΆαα’ααΈαααΊαα·α ααα»αααααααααΆαααααααααααα’αααααααα α’αααα’αΆα ααααΎ 'chrome.runtime.connect' αααααΆααααα
αααβαααααΎαβαααααα·ααΈβαααα»αβααααααΈαβααααααΆααααααα
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
const app = new SignerApp();
// onConnect ΡΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΠΏΡΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠΈ 'ΠΏΡΠΎΡΠ΅ΡΡΠΎΠ²' (contentscript, popup, ΠΈΠ»ΠΈ ΡΡΡΠ°Π½ΠΈΡΠ° ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ)
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
// ΠΡΠΈ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠ΅ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Π·ΡΠ²Π°ΡΡ ΠΈΠΌΡ, ΠΏΠΎ ΡΡΠΎΠΌΡ ΠΈΠΌΠ΅Π½ΠΈ ΠΌΡ ΠΈ ΠΎΠΏΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΠΌ ΠΊΡΠΎ ΠΊ Π½Π°ΠΌ ΠΏΠΎΠ΄Π»ΡΡΠΈΠ»ΡΡ, ΠΊΠΎΠ½ΡΠ΅Π½ΡΡΠΊΡΠΈΠΏΡ ΠΈΠ»ΠΈ ui
if (processName === 'contentscript'){
const origin = remotePort.sender.url
app.connectPage(portStream, origin)
}else{
app.connectPopup(portStream)
}
}
αααααΆα dnode ααααΎαααΆαααΆαα½αααααααΈα α αΎαααΎαααα½αααΆαα ααααα½α ααααΆααα’αΆααΆαααααααΊα αΆαααΆα αα ααΆβααααΌαβααΆαβαααααΎαβα‘αΎαβαααβααααΎβαααααΆαααβααααααΈαβαααβα’αΆα βα’αΆαβααΆα αααβα’αα»ααααβααΆαβααααααΈαβ nodejs αααα»αβαααααα·ααΈβαα»αααα
import {Duplex} from 'readable-stream';
export class PortStream extends Duplex{
constructor(port){
super({objectMode: true});
this._port = port;
port.onMessage.addListener(this._onMessage.bind(this));
port.onDisconnect.addListener(this._onDisconnect.bind(this))
}
_onMessage(msg) {
if (Buffer.isBuffer(msg)) {
delete msg._isBuffer;
const data = new Buffer(msg);
this.push(data)
} else {
this.push(msg)
}
}
_onDisconnect() {
this.destroy()
}
_read(){}
_write(msg, encoding, cb) {
try {
if (Buffer.isBuffer(msg)) {
const data = msg.toJSON();
data._isBuffer = true;
this._port.postMessage(data)
} else {
this._port.postMessage(msg)
}
} catch (err) {
return cb(new Error('PortStream - disconnected'))
}
cb()
}
}
α₯α‘αΌαααα α αΌαααΎααααααΎαααΆααααααΆαααα αααα»α UIα
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import Dnode from 'dnode/browser';
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupUi().catch(console.error);
async function setupUi(){
// Π’Π°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ Π² ΠΊΠ»Π°ΡΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΡΠΎΠ·Π΄Π°Π΅ΠΌ ΠΏΠΎΡΡ, ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ Π² stream, Π΄Π΅Π»Π°Π΅ΠΌ dnode
const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
const connectionStream = new PortStream(backgroundPort);
const dnode = Dnode();
connectionStream.pipe(dnode).pipe(connectionStream);
const background = await new Promise(resolve => {
dnode.once('remote', api => {
resolve(api)
})
});
// ΠΠ΅Π»Π°Π΅ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡ API Π΄ΠΎΡΡΡΠΏΠ½ΡΠΌ ΠΈΠ· ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ
if (DEV_MODE){
global.background = background;
}
}
αααααΆααααααΎααααααΎαααΆααααααΆαααα αααα»αααααααΈαααΆαα·ααΆα
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import PostMessageStream from 'post-message-stream';
setupConnection();
injectScript();
function setupConnection(){
const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
const backgroundStream = new PortStream(backgroundPort);
const pageStream = new PostMessageStream({
name: 'content',
target: 'page',
});
pageStream.pipe(backgroundStream).pipe(pageStream);
}
function injectScript(){
try {
// inject in-page script
let script = document.createElement('script');
script.src = extensionApi.extension.getURL('inpage.js');
const container = document.head || document.documentElement;
container.insertBefore(script, container.children[0]);
script.onload = () => script.remove();
} catch (e) {
console.error('Injection failed.', e);
}
}
αααααΆαααΎαααααΌαααΆα API αα·ααα αααα»αααααααΈαααΆαα·ααΆ ααα»αααααααααααΆαααα ααΎααααα ααΎαααααΎααΏαααΈααααΆαα
- ααΎααααααΎαααααααΈαααΈαα αα½α - ααααααα
ααααααα
ααΎααααΌαααααΆααααα ααα αααααΆααααΏααααααΎαααααΎααΆα
αααα αααααα ααΈα’ααααααααΎα metamask α ααααααΈαααΈααΈαααΊααΎααααΈααααααΆαααααααα ααΎα ααααααααα½αααΆαααΈruntime.connect
. ααααα·ααα½αααα α₯α‘αΌαααα αααααααΉαααΆαααααααΈααα ααααααΆααααααα - αααα
αΌαααααααΈααα
αααα»α DOM α ααΆαααααααααΈα (ααΆαα
αΌαααααΎααΆααααΌαααΆαα’αα»ααααΆααα
αααα»α manifest) α αΎααααααΎαααααΆααα½αα
script
ααΆαα½αααΉαααααΉαααΆαααααααΆαα ααΆααααα»αα
import PostMessageStream from 'post-message-stream';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
setupConnection();
injectScript();
function setupConnection(){
// Π‘ΡΡΠΈΠΌ ΠΊ Π±Π΅ΠΊΠ³ΡΠ°ΡΠ½Π΄Ρ
const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
const backgroundStream = new PortStream(backgroundPort);
// Π‘ΡΡΠΈΠΌ ΠΊ ΡΡΡΠ°Π½ΠΈΡΠ΅
const pageStream = new PostMessageStream({
name: 'content',
target: 'page',
});
pageStream.pipe(backgroundStream).pipe(pageStream);
}
function injectScript(){
try {
// inject in-page script
let script = document.createElement('script');
script.src = extensionApi.extension.getURL('inpage.js');
const container = document.head || document.documentElement;
container.insertBefore(script, container.children[0]);
script.onload = () => script.remove();
} catch (e) {
console.error('Injection failed.', e);
}
}
α₯α‘αΌααααααΎααααααΎαααααα» api αα αααα»αααααα α αΎααααααααΆαα ααΆαααα
import PostMessageStream from 'post-message-stream';
import Dnode from 'dnode/browser';
setupInpageApi().catch(console.error);
async function setupInpageApi() {
// Π‘ΡΡΠΈΠΌ ΠΊ ΠΊΠΎΠ½ΡΠ΅Π½ΡΡΠΊΡΠΈΠΏΡΡ
const connectionStream = new PostMessageStream({
name: 'page',
target: 'content',
});
const dnode = Dnode();
connectionStream.pipe(dnode).pipe(connectionStream);
// ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΎΠ±ΡΠ΅ΠΊΡ API
const pageApi = await new Promise(resolve => {
dnode.once('remote', api => {
resolve(api)
})
});
// ΠΠΎΡΡΡΠΏ ΡΠ΅ΡΠ΅Π· window
global.SignerApp = pageApi;
}
ααΎααα½α
ααΆααα αΎαα
API ααα αα·ααααααααΎαα αα αααααααΆαααααα ααΎαα’αΆα α α αα»αααΆα hello ααΌα αααα
ααΆαααααΎααΆαααΆαα½ααα»αααΆα callback αα αααα»α 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 αα·αααααααΈαα αΆααααΌα ααΆα’αΆα ααααααααΆαα ααΎαα’αΆα ααααΎααΆα multiplexing α αα αΆα αα·ααααααΎα APIs ααααααααααΆααΆα αααΎααααααΆαααα·α αα ααΆαααααααααααΆα ααΆαααααΆααα dnode α’αΆα ααααΎααΆααααααααΈαααααα ααΏαααααΆααααΊααΆααα»αααΆαααΉααααααΌααααα»αααααααααΆ nodejs streamα
αααααΎααα½αααΊαααααα JSON αααα’αα»αααααα·ααΈααΆα JSON RPC 2α ααααααΆαααΆααααα ααΆααααΎαααΆαααΆαα½αααΆαααΉααααααΌαααΆααααΆαα (TCP αα·α HTTP(S)) ααααα·αα’αΆα α’αα»ααααααΆααααα»αααααΈααααααΎαα
ααααΆαααΆααααααααα»α αα·ααααααααααα»α
ααΎαααΉαααααΌαααΆααααααΆαα»αααααΆαααΆαααΆααααα»ααααααααα·ααΈ - αααΆαα αα ααααΆααα α»α α α»αα αααααααΆα ααΎαβα’αΆα βααααααβααααΆαααΆαβαααΆαβααΆαβαααα½αβαα βαααα»αβαααααα·ααΈ αα·αβαα·ααΈααΆαααααβαααααΆααβααΆαβααααΆααβααααΌαβααΆβαα βαααα»α API ααα βα‘αΎαα
import {setupDnode} from "./utils/setupDnode";
export class SignerApp {
constructor(){
this.store = {
keys: [],
};
}
addKey(key){
this.store.keys.push(key)
}
removeKey(index){
this.store.keys.splice(index,1)
}
popupApi(){
return {
addKey: async (key) => this.addKey(key),
removeKey: async (index) => this.removeKey(index)
}
}
...
}
αα αααα»αααααααΆαααααα ααΎαααΉααα»αα’αααΈααααααααΆααα αααα»ααα»αααΆααα½α α αΎααααααααααα»αααααα·ααΈαα αααα’α½α ααΌα ααααααΎαα’αΆα ααααΎααΆαααΆαα½αααΆααΈαα»αααΌαα
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupApp();
function setupApp() {
const app = new SignerApp();
if (DEV_MODE) {
global.app = app;
}
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
if (processName === 'contentscript') {
const origin = remotePort.sender.url;
app.connectPage(portStream, origin)
} else {
app.connectPopup(portStream)
}
}
}
ααααααααααααα½αα ααα½αααΈαα»αααΌα UI α αΎαααΎαααΆααΆαα’αααΈααΎαα‘αΎαααΆαα½αααααΆαααΆαα
ααααααααΌαααααααΎα±ααααΆααααΆαα·α αα ααΎααααΈαα»αα±αααααααΆαααα αααα αΆααααααΎαα‘αΎααα·αα
ααΎαααΉααααααΆαα»αααΆαα αααα»α localStorage ααααααααααΆααααΎααΆααΆαα½αααΉαααΆααααΆαααααΆααααααΌαα ααααααα ααΆαα αΌαααααΎααΆααααΉαα αΆαααΆα ααααααΆαα UI α αΎααααα»αααα ααααΆαααΆαααααΆααααααΌααααααα αααααα’ααααΎααα ααΆααΉαααΆαααΆαααΆααααα½ααααα»αααΆααααααΎααααααααααα»ααααα’αΆα ααααααααΆα αα·αααΆαααΆαααααΆααααααΌαααααααΆα
ααΎαααΉαααααΎαααααΆααα mobx (
α αΌαααααααααΆαα αΆααααααΎαααααααΆαααΆαααααΌα αα·αααααΎα±ααα αΆαα’αΆα ααααααααΆαα
import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
export class SignerApp {
constructor(initState = {}) {
// ΠΠ½Π΅ΡΠ½Π΅ store ΡΠ°ΠΊ ΠΈ ΠΎΡΡΠ°Π½Π΅ΡΡΡ ΡΠ΅ΠΌ ΠΆΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠΌ, ΡΠΎΠ»ΡΠΊΠΎ ΡΠ΅ΠΏΠ΅ΡΡ Π²ΡΠ΅ Π΅Π³ΠΎ ΠΏΠΎΠ»Ρ ΡΡΠ°Π»ΠΈ proxy, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°ΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ Π½ΠΈΠΌ
this.store = observable.object({
keys: initState.keys || [],
});
}
// ΠΠ΅ΡΠΎΠ΄Ρ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΠ΅Π½ΡΡΡ observable ΠΏΡΠΈΠ½ΡΡΠΎ ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°ΡΡ Π΄Π΅ΠΊΠΎΡΠ°ΡΠΎΡΠΎΠΌ
@action
addKey(key) {
this.store.keys.push(key)
}
@action
removeKey(index) {
this.store.keys.splice(index, 1)
}
...
}
βαα ααααααααααΆααβ mobx ααΆααααα½ααααααααααααΆααα’αααααααααΌααααΈ α αΎαααααΆααα αΆααααΆααααΆαα α ααΌαααααααα ααΆαααα½αααα ααΆααΉαα’αΆα ααΆαααΆαααΆαααααααΆαα
ααΆααααααααα αααα»αααΉαααααΎααΆααα "αα αααααααΆααααααΌα" ααΆααΉαααΆαα αααααΈααΆααΆαα·αααααΉαααααΌαααΆαααααα»ααααααα Mobx ααΆαααΆαααΆαα αΌααα ααΆααααΆαα α’αααααα½α αα·ααααααααααα»ααααΌααααΈααααααααΆααααααααΎαααααΌαααΆαααααΎα
α’ααααα»αααααααααααΆααααααΎαααααααααΈαα
- αα αααα»αααααααααΉαααΉαααΆαα½αααΉαααα enforceActions mobx α αΆαααΆααααΆαααααΆααααααΌααααααααααααΆααα ααΆααααΌαααΆαα αΆαααα»αααΆααΆααΆαα’αα»ααααααα’ααΎααααΈααααΎααΆααααααααααααααααααΉααααΉαα
- αααααΈααΆαα»αααΆααα½αααααΆααααααΌαααααΆαααΆαα αααΎαααααααα - α§ααΆα ααα ααΎαααααΆααααααΌαααΆαααΆα αααΎααα αααα»ααα½αααΌαααΆα αααΎα - α’αααααααααααΆαααααααΌαααΆαααΌαααααΉααααα ααααααααΆαααα ααα αααααΆαααΆααααααΆααααΆαα·ααααααααΆαααααααααΆααα»α αααααΆαα’αΆαααααααααααααα·αα αΆαααΆα αααΆααα αααααΆααααα αΆαααΆαα»ααααα·αα αΆαααΆα αα αααα»αααααΈααααααΎα ααΆααααΈαα½α α¬ααΈααΈααα·αααΆαααααααααΆαα·αααααααα ααα»ααααααΎαααΉαα’αα»ααααααΆαααΆαα’αα»ααααααα’αααα»αα ααΆααΆαααααΆαααααα»αααΆαααααΆααα’ααααα»αααααα ααΉααα»αααΆαααΆααα’αααααααααΆααααααΌαααααΆαααΆαααααΆααααααΆαααααααα
αα αααα»αααααααΆαααααα ααΎαααΉαααααααααΆαα αΆααααααΎα αα·ααααααΆαα»αααααΆαααΆααα αααα»α 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)
}
}
}
αα»αααΆαααααα·ααααααΊαα½αα±ααα αΆααα’αΆαααααααα ααΈαααα ααΆααΆαα’αααα’αααΆαααΈαα
- α§αααααααααΎαααΎααα·ααααααα
- α§ααααααααααααΆααααααΉαααααΌαααΆαα α ααΆαα½ααα·αααααααααααΆααααααααααΆααααΆααααααΌαα
αα·αααΌα redux αα αααααΆαααααααααααΎαααα½αααΆαααααααΆα’αΆαα»ααααααααΆαα αααΆαα mobx α αα αΆαααΌαα’αααΈαααα’αΆα ααααααααΆααααααΎαα αΌαααααΎαα ααΆααααα»αα§αααααααααΎαααΎα α αΎαα α ααΌαααααααα α’ααααααααααΆααα αααααααα½αααααααΆααααααΌαα
ααΆααΆααΏαααααΆαααααα»αααΆααααα
αααΆααα’αααΈααααααα mobx αααααα
α
α·αααααΆααΎααΆαααααααααΆαα½ααααααΎαααΆαα ααααα·αααΎαααα»αααααα Selector αα
αααα»αααΌαααΌα
ααα() => app.store
αααααΆαααα ααααα·ααααααΉααα·αααααΌαααΆαααα α
ααΆαα α
αΆααααΆααααΈααΆααααα»ααααα½αααΆαα·αα’αΆα
ααΎαααΎαααΆα ααΆαααααΆαααααααΆααα»αααααα
ααΎαααα»αααααααααααα () => app.store.keys
αααααΆαααααααααααααααΆαα’αααΈααΉαααΎαα‘αΎααα α
αΆααααΆααααΈααααααααα/ααααΆαα»α’αΆαα ααα
ααααΈααααα
ααΆααΉααα·αααααΆααααααΌαααα
Mobx ααΎααα½ααΆα’αααααααΎαααΎαααΆααΎαααααΌα α αΎαααααΆααααααΆαααΆαααΆααααααααααααΎαααΆαα
αΌαααααΎααα»αααααα αααααααΌαααΆαααααΎααΆααααα’αααααα½αααααΌααααΈα ααΌα
αααααα»αααΆααααααααΆααααααΆαα½αααααΌαααΆαααααΎαα
ααΈααα toJS
. ααΆαααα‘ααααααα»ααααΈαα½αααΆαα½αααΉαααααΌααααΈααΆααα’αααααα½ααααααΆαααΎαα ααα‘α»ααααααααα·ααααα· ααΆα’αΆαααΆαααΆααα’ααααααααα» - ααΌα
ααααα’αααααα½αααααΌαααΆααααα
αα αααα»ααα»αααΌαααα α‘αΎα ααΎαααΉαααααααααΌαααααΆα αααΎααααααααα ααΎαααααα½αααααααΆαα αΌααα αααα»α localStorageα
αα αααααααααααααααααΆααααααααααΌαααΆααααα»αα‘αΎααα·α ααααααΆααα ααααΆααα ααΉαααααααα
ααΌααααααα·ααΈααΆααα’αααα αΌααααα
ααα»α
αααα’αΆα
ααααΌαααΆαααΎα
ααΆααααααΆαα»αααα―αααααααα»ααααα·ααΆα
ααΆααααααΆαα»αααα―αααααΆα’αααααα αααΆααααΆααααΊαα·αααΆααα»ααααα·ααΆαααα ααΆαααααααΆαα±ααΆααααα’αααααΉαααααΌαααΆααα½α α αΌα α αΌαααααΎαα»αααααΌαααααααα’αααααΆααΎαα ααΌα αααααα αααα»α localStorage ααΎαααΉααααααΆαα»αααΌααααααα»ααααααααααααΆαα’αα·αααααΈααααααΆααααααααΆααα
ααΎααααΈαα»ααααα·ααΆαααΆααααααααΆαα ααΎαααΉαααααααααααΆαααΆαα αΆαααααα αααααα·ααΈ αααααΆααΉααα·αααΆααα·αααα·α αΌαααααΎααααΆαααααααα ααΎαααΉααααααααααααααααααααααααααααααααα·αα ααααΆαααΆαα αΆαααα αααααΆαα’αααααα
Mobx α’αα»ααααΆαα±ααα’ααααααααΆαα»ααααααα»ααα·ααααααα’αααααααΆ α αΎααα αααααααΌαααΆαααααΆαααααααααααααααα·αααααα’ααααΎααΆα ααΆαααααααΊααΆα’αααΈαααααα α ααΆ αααααααααααααα·ααααΆα αα½αααα’αΆα αααααααααα ααΉααα·αααααΆααα αααα»αααΌαααααΆααα·ααααααα
import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// Π£ΡΠΈΠ»ΠΈΡΡ Π΄Π»Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠ³ΠΎ ΡΠΈΡΡΠΎΠ²Π°Π½ΠΈΡ ΡΡΡΠΎΠΊ. ΠΡΠΏΠΎΠ»ΡΠ·ΡΡΡ crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";
export class SignerApp {
constructor(initState = {}) {
this.store = observable.object({
// Π₯ΡΠ°Π½ΠΈΠΌ ΠΏΠ°ΡΠΎΠ»Ρ ΠΈ Π·Π°ΡΠΈΡΡΠΎΠ²Π°Π½Π½ΡΠ΅ ΠΊΠ»ΡΡΠΈ. ΠΡΠ»ΠΈ ΠΏΠ°ΡΠΎΠ»Ρ null - ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ locked
password: null,
vault: initState.vault,
// ΠΠ΅ΡΡΠ΅ΡΡ Π΄Π»Ρ Π²ΡΡΠΈΡΠ»ΠΈΠΌΡΡ
ΠΏΠΎΠ»Π΅ΠΉ. ΠΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΠ²Π΅ΡΡΠΈ Π°Π½Π°Π»ΠΎΠ³ΠΈΡ Ρ view Π² Π±Π΄.
get locked(){
return this.password == null
},
get keys(){
return this.locked ?
undefined :
SignerApp._decryptVault(this.vault, this.password)
},
get initialized(){
return this.vault !== undefined
}
})
}
// ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΏΡΡΡΠΎΠ³ΠΎ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ° Π½ΠΎΠ²ΡΠΌ ΠΏΠ°ΡΠΎΠ»Π΅ΠΌ
@action
initVault(password){
this.store.vault = SignerApp._encryptVault([], password)
}
@action
lock() {
this.store.password = null
}
@action
unlock(password) {
this._checkPassword(password);
this.store.password = password
}
@action
addKey(key) {
this._checkLocked();
this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
}
@action
removeKey(index) {
this._checkLocked();
this.store.vault = SignerApp._encryptVault([
...this.store.keys.slice(0, index),
...this.store.keys.slice(index + 1)
],
this.store.password
)
}
... // ΠΊΠΎΠ΄ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ ΠΈ api
// private
_checkPassword(password) {
SignerApp._decryptVault(this.store.vault, password);
}
_checkLocked() {
if (this.store.locked){
throw new Error('App is locked')
}
}
// ΠΠ΅ΡΠΎΠ΄Ρ Π΄Π»Ρ ΡΠΈΡΡΠΎΠ²ΠΊΠΈ/Π΄Π΅ΡΠΈΡΡΠΎΠ²ΠΊΠΈ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°
static _encryptVault(obj, pass){
const jsonString = JSON.stringify(obj)
return encrypt(jsonString, pass)
}
static _decryptVault(str, pass){
if (str === undefined){
throw new Error('Vault not initialized')
}
try {
const jsonString = decrypt(str, pass)
return JSON.parse(jsonString)
}catch (e) {
throw new Error('Wrong password')
}
}
}
α₯α‘αΌαααα ααΎααααααΆαα»ααααααααααΆαα’αα·αααααΈα αα·αααΆααααααααΆααααα»αααααα α’αααΈαααααααααααααΌαααΆαααααΆα ααΎαααααΎααΆαααααααα ααααα αΆαααααααααααΆααααααααΆααα ααααΈααααα α₯α‘αΌαααα API ααΆααΆαααααΆααα·ααΈααΆααααααααααΆααα αΆααααααΎαααΆααααα»αα
ααααααααααΆααααΆαα’αα·αααααΈα
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 αααα½αααΆααααΌαααΆαααΆααΆααα ααΎαβααβααΉαβααααΌαβαααααΎαβαααααΆααβαααααΆβαα»αβαα
localStorageα
import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
import {loadState, saveState} from "./utils/localStorage";
const DEV_MODE = process.env.NODE_ENV !== 'production';
const IDLE_INTERVAL = 30;
setupApp();
function setupApp() {
const initState = loadState();
const app = new SignerApp(initState);
if (DEV_MODE) {
global.app = app;
}
// Π’Π΅ΠΏΠ΅ΡΡ ΠΌΡ ΡΠ²Π½ΠΎ ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΏΠΎΠ»Π΅, ΠΊΠΎΡΠΎΡΠΎΠΌΡ Π±ΡΠ΄Π΅Ρ ΠΏΡΠΎΠΈΡΡ
ΠΎΠ΄ΠΈΡΡ Π΄ΠΎΡΡΡΠΏ, reaction ΠΎΡΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π½ΠΎΡΠΌΠ°Π»ΡΠ½ΠΎ
reaction(
() => ({
vault: app.store.vault
}),
saveState
);
// Π’Π°ΠΉΠΌΠ°ΡΡ Π±Π΅Π·Π΄Π΅ΠΉΡΡΠ²ΠΈΡ, ΠΊΠΎΠ³Π΄Π° ΡΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅
extensionApi.idle.setDetectionInterval(IDLE_INTERVAL);
// ΠΡΠ»ΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ Π·Π°Π»ΠΎΡΠΈΠ» ΡΠΊΡΠ°Π½ ΠΈΠ»ΠΈ Π±Π΅Π·Π΄Π΅ΠΉΡΡΠ²ΠΎΠ²Π°Π» Π² ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΡΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ ΠΈΠ½ΡΠ΅ΡΠ²Π°Π»Π° Π»ΠΎΡΠΈΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
extensionApi.idle.onStateChanged.addListener(state => {
if (['locked', 'idle'].indexOf(state) > -1) {
app.lock()
}
});
// Connect to other contexts
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
if (processName === 'contentscript') {
const origin = remotePort.sender.url
app.connectPage(portStream, origin)
} else {
app.connectPopup(portStream)
}
}
}
αααααΌααα»ααααααα αΆααααααΊ
ααααα·ααααα·ααΆα
ααΌα
αααα, ααΎααααααα’αααΈαααααααΆαααααα»α: ααΆααααααΎααα·αα
α»αα αααααααΆααΎααααα·ααααα·ααΆααα
ααΎ blockchain α ααΎαααΉαααααΎ WAVES blockchain αα·ααααααΆααα
ααΆααααΌα ααΌααααααααα ααααααΌαα’αΆααααααΆααααα αΆαααΆα αααααΌαα α»αα αααααααΆ αααααΆαααααααααααα·ααΈααΆααααααααααΆααααααααααΆαααααΈ αααααΆααα αααααααΆ αα·αααα·αααα
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
ααΆααααααΆααα ααΎαααααΆααααααααΆααααααΌαααααΆαααΆαααααΆα αααααΆααααΈα
α»αα αααααααΆααΎααΆααααα·αααΎα
αΆαααΆα
αα
ααΎαααΆαα Approve αα·αααα·ααααα αααα»α UI API, newMessage αα αααα»αααααα APIα
export class SignerApp {
...
popupApi() {
return {
addKey: async (key) => this.addKey(key),
removeKey: async (index) => this.removeKey(index),
lock: async () => this.lock(),
unlock: async (password) => this.unlock(password),
initVault: async (password) => this.initVault(password),
approve: async (id, keyIndex) => this.approve(id, keyIndex),
reject: async (id) => this.reject(id)
}
}
pageApi(origin) {
return {
signTransaction: async (txParams) => this.newMessage(txParams, origin)
}
}
...
}
α₯α‘αΌαααα αααααααΆααΆαα α»αα αααααααΆααΎααααα·ααααα·ααΆαααΆαα½ααααααααααααα
ααΆααΌαα
α’αααΈααααααααΆαααΊαα½α
ααΆααα αΎαα’αααΈαααααα
αααααΊ
UI
α
ααα»α
αααααΆααααααΌαααΆαα
αΌαααααΎααααΆαααΆααααααα·ααΈα αα
ααααα UI ααΎαααΉαααααΎ 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 αα
αααααΆααααΆααααααΌαα ααΎααααΈααααΎααΌα
αααααΎααααααααα·ααΈααΆααααααα½αα 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}
);
...
}
ααΌα αααααααααα·ααΈααΊαα½α ααΆααα ααα αααααα’αΆα ααααΎαα»αα αααααααΆαααααΆααααααα·ααααα·ααΆαα
αααααΌαααΆααα
ααΈααα
ααα ααααΈααααα·ααααΆα
ααααα·αααΎα’αααααΆαα’αΆαα’ααααααααα
ααα αΎα ααα»αααααα
ααααΆαα
ααααα α’αααα’αΆα
αα½ααα½ααααα
α αΎαααααα·αααΎα’αααα
αΆααα’αΆαααααααααα»αααΆαααΎαααΌααααααΆααααααααααααααα·αααααΆααααα α’αααα’αΆα
αααααααααΆααΆα
αααααΌα ααααΆαα αα·αααΆααα·αααααΆααΆαααΆαααΈ
ααααα: www.habr.com