ืื ืืืื ืืืจืืืืงืืืจืช "ืฉืจืช-ืืงืื" ืื ืคืืฆื, ืืืฉืืืื ืืืืืจืื ืืืืคืืื ืื ื:
- ืืื ืฆืืจื ืืืืกื ืืกื ื ืชืื ืื ืขื ืื ืืกืืช ืืฉืชืืฉ ืืกืืกืืืืช. ืคืจืื ืืืืฉื ืืืืืกื ืื ืื ืืจืง ืขื ืืื ืืืฉืชืืฉืื ืขืฆืื, ืืืืฉืืจ ืืืืชื ืืืืช ืฉืืื ืืชืจืืฉ ืืจืืช ืืคืจืืืืงืื.
- ืืื ืฆืืจื ืืืฉืชืืฉ ืืฉืจืช. ืืืืืืงื ืฉื ืืืคืืืงืฆืื ืืืืื ืืืชืืฆืข ืืจืฉืช ืืืืงืฆ'ืืื, ืฉืื ื ืืชื ืืืืกื ืืช ืืืืช ืื ืชืื ืื ืื ืืจืฉืช.
ืืฉื ื 2 ืืืกืื ืืืื ืืืกืืช ืืืคืชืืืช ืืฉืชืืฉ - ืืจื ืงื ืืืืจื ืืืจืืืืช ืืืคืืคื. ืืจื ืงื ืืืืจื ืื ืืจืื ืืืืืืืื ืืืืืื, ืื ืงืฉืื ืืฉืืืืฉ ืืจืืืงืื ืืืืืืช ืืื ืืืื, ืื ืืจืืืืช ืืืคืืคื ืื ืืฉืืืื ืืืืฉืื ืืื ืืืืื ืืงืืืช ืฉืืืืฉ, ืืืืืืืช ืืืืืช ืื ืืื ืืืืช ืืืืืืื ืขืืืจ ืืฉืชืืฉื ืืงืฆื.
ืืืชืืฉื ืืื ืื, ืจืฆืื ื ืืืฆืืจ ืืช ืืืจืืื ืืืืืืืืช ืืืืชืจ ืฉืืคืฉืืช ืืช ืืคืืชืื ืฉื ืืืฉืืืื ืืืืืจืื ืขื ืืื ืืชื API ืคืฉืื ืืขืืืื ืขื ืขืกืงืืืช ืืืชืืืืช.
ื ืกืคืจ ืืื ืขื ืืืืืื ืืื ืืืื.
ืืืืืจ ืืืื ืืืจืืืช ืฉืื ืืืจ ืฉืื ืืืฆื ืืืชืื ืชืืกืฃ ืืืคืืคื, ืขื ืืืืืืืช ืงืื ืืฆืืืืื ืืกื. ืืชื ืืืื ืืืฆืื ืืช ืื ืืงืื ื
ืืืกืืืจืื ืงืฆืจื ืฉื ืืจืืืืช ืืคืืคื
ืืจืืืืช ืืืคืืคื ืงืืืืืช ืืืจ ืืจืื ืืื. ืื ืืืคืืขื ื-Internet Explorer ื-1999, ื-Firefox ื-2004. ืขื ืืืช, ืืืฉื ืืื ืจื ืืืื ืื ืืื ืชืงื ืืื ืืืจืืืืช.
ืื ื ืืืืืื ืืืืจ ืฉืืื ืืืคืืข ืืื ืขื ืืจืืืืช ืืืจืกื ืืจืืืขืืช ืฉื Google Chrome. ืืืืื ืฉืื ืืื ืื ืืคืจื, ืืื ืื ืืื ื-API ืฉื Chrome ืฉืืคื ืืืกืืก ืฉืื: ืืืืจ ืฉืืืฉ ืืช ืจืื ืฉืืง ืืืคืืคื ืื ืืืขื ืื ืืช ืืคืืืงืฆืืืช ืืืื ืืช, ืืจืื ืืืขืฉื ืงืืข ืืช ืืกืื ืืจื ืืืจืืืืช ืืคืืคื.
ืืืืืืื ืืื ืกืื ืืจื ืืฉืื, ืื ืืขืงืืืช ืืคืืคืืืจืืืช ืฉื ืชืืกืคื Chrome, ืืืืจื ืืืืืื ืืืฆืืจ API ืชืืื. ืืฉื ืช 2015, ืืืืืืช ืืืืืื, ื ืืฆืจื ืงืืืฆื ืืืืืืช ืืชืื ืงืื ืกืืจืฆืืื ืืืื ืืจื ื ืืขืืืื (W3C) ืืขืืืื ืขื ืืคืจืื ืชืืกืคืื ืืืฆื ืืคืืคื.
ืืจืืืืช ื-API ืืงืืืืืช ืขืืืจ Chrome ื ืืงืื ืืืกืืก. ืืขืืืื ืืืฆืขื ืืชืืืืช ืืืงืจืืกืืคื (ืืืื ืกืืจืื ืืืฉืชืชืฃ ืืคืืชืื ืืชืงื), ืืืชืืฆืื ืืื ืืืคืืขื ืืืืื
ืจืฉืืืช, ืืืคืจื ื ืชืื ืขื ืืื Edge, Firefox ื-Opera (ืฉืื ืื ืฉืืจืื ืืื ื ืืจืฉืืื ืื). ืืื ืืืขืฉื, ืืชืงื ืชืืื ืืืืื ืจืื ืืืจืื, ืืืืืื ืฉืืื ื ืืชื ืืืขืฉื ืขื ืกืื ืืืจืืืืช ืฉืื. ืืชื ืืืื ืืงืจืื ืขืื ืขื ื-API ืฉื WebExtensions
ืืื ื ืืจืืื
ืืงืืืฅ ืืืืื ืฉื ืืจืฉ ืขืืืจ ืืกืืืืช ืืื ืืื ืืคืกื (manifest.json). ืืืื ืื "ื ืงืืืช ืืื ืืกื" ืืืจืืื.
ืืื ืืคืกื
ืืคื ืืืคืจื, ืงืืืฅ ืืื ืืคืกื ืืื ืงืืืฅ JSON ืืืงื. ืชืืืืจ ืืื ืฉื ืืคืชืืืช ืื ืืคืกื ืขื ืืืืข ืืืื ืืืคืชืืืช ืื ืชืืืื ืืืืื ืืคืืคื ื ืืชื ืืืฆืื
"ืขืฉืื" ืืืชืขืื ืืืคืชืืืช ืฉืืื ื ืืืคืจื (ืื Chrome ืืื Firefox ืืืืืืื ืขื ืฉืืืืืช, ืืื ืืืจืืืืช ืืืฉืืืืช ืืขืืื).
ืืื ื ืจืืฆื ืืืคื ืืช ืืช ืชืฉืืืช ืืื ืืืื ื ืงืืืืช.
- ืจืงืข - ืืืืืืงื ืืืืื ืืช ืืฉืืืช ืืืืื:
- ืกืงืจืืคืืื - ืืขืจื ืฉื ืกืงืจืืคืืื ืฉืืืืฆืขื ืืืงืฉืจ ืืจืงืข (ื ืืืจ ืขื ืื ืงืฆืช ืืืืืจ ืืืชืจ);
- ืขืืื - ืืืงืื ืกืงืจืืคืืื ืฉืืืืฆืขื ืืขืืื ืจืืง, ื ืืชื ืืฆืืื html ืขื ืชืืื. ืืืงืจื ืื, ืชืชืขืื ืืฉืื ืืกืงืจืืคื, ืืืืื ืฆืืจื ืืืื ืืก ืืช ืืกืงืจืืคืืื ืืืฃ ืืชืืื;
- ืืืชืืื - ืืื ืืื ืืจื, ืื ืื ืฆืืื, ืืืคืืคื "ืืืจืื" ืืช ืชืืืื ืืจืงืข ืืืฉืจ ืืื ืืืฉืื ืฉืืื ืื ืขืืฉื ืืืื, ืืืชืืื ืืืชื ืืืืฉ ืืืืืช ืืฆืืจื. ืืืจืช, ืืขืืื ืืืคืจืง ืจืง ืืฉืืืคืืคื ืกืืืจ. ืื ื ืชืื ืืคืืืจืคืืงืก.
- content_scripts - ืืขืจื ืฉื ืืืืืืงืืื ืืืืคืฉืจ ืื ืืืขืื ืกืงืจืืคืืื ืฉืื ืื ืืืคื ืืื ืืจื ื ืฉืื ืื. ืื ืืืืืืงื ืืืื ืืช ืืฉืืืช ืืืฉืืืื ืืืืื:
- ืืคืจืืจืื -
ืืชืืืช ืืชืจ ืฉื ืืคืืก , ืฉืงืืืข ืื ืกืงืจืืคื ืชืืื ืืกืืื ืืืืื ืื ืื. - js - ืจืฉืืื ืฉื ืกืงืจืืคืืื ืฉืืืขื ื ืืืชืืื ืื;
- exclude_matches - ืืื ื ืืืื ืืืฉืื
match
ืืชืืืืช ืืชืจืื ืืชืืืืืช ืืฉืื ืื.
- ืืคืจืืจืื -
- page_action - ืืื ืืืขืฉื ืืืืืืงื ืฉืืืจืื ืขื ืืืืืงืื ืืืืฆื ืืฆื ืฉืืจืช ืืืชืืืช ืืืคืืคื ืืืืื ืืจืืงืฆืื ืขืื. ืื ืื ืืืคืฉืจ ืื ืืืฆืื ืืืื ืงืืคืฅ, ืืืืืืจ ืืืืฆืขืืช HTML, CSS ื-JS ืืฉืื.
- default_popup - ื ืชืื ืืงืืืฅ HTML ืขื ืืืฉืง ืงืืคืฅ, ืขืฉืื ืืืืื CSS ื-JS.
- ืืจืฉืืืช - ืืขืจื ืื ืืืื ืืืืืืช ืืจืืื. ืืฉื ื 3 ืกืืื ืืืืืืช, ืืืชืืืจืื ืืคืืจืื
ืืื - ืืฉืืืื_ื ืืืฉืื_ืืื ืืจื ื - ืืฉืืื ืืจืืื ืฉืืฃ ืืื ืืจื ื ืืืื ืืืงืฉ, ืืืฉื, ืชืืื ืืช, JS, CSS, ืงืืืฆื HTML.
- ืืืฆืื ื_ื ืืชื ืืืืืืจ - ืืื ืืชื ืืืื ืืฆืืื ืืืคืืจืฉ ืืช ืืืืืื ืฉื ืืจืืืืช ืืืืืืื ืื ืืืจืื ืฉื ืืคื ืืื ืืจื ื ืฉืืื ืืชื ืืืื ืืืชืืืจ. ืืืืืื ืืืื ืืืืืช ืืจืื ืฉื ืื ืืืขืื. ืื ืขืืื ืืคืืืจืคืืงืก.
ืืงืฉืจ ืืืฆืืข
ืืชืืกืฃ ืืฉ ืฉืืืฉื ืืงืฉืจืื ืืืืฆืืข ืงืื, ืืืืืจ, ืืืคืืืงืฆืื ืืืจืืืช ืืฉืืืฉื ืืืงืื ืขื ืจืืืช ืฉืื ืืช ืฉื ืืืฉื ื-API ืฉื ืืืคืืคื.
ืืงืฉืจ ืฉื ืืจืืื
ืจืื ื-API ืืืื ืืื. ืืืงืฉืจ ืื ืื "ืืืื":
- ืขืืื ืจืงืข - ืืืง "ืืื" ืฉื ืืืจืืื. ืืงืืืฅ ืืฆืืื ืืื ืืคืกื ืืืืฆืขืืช ืืงืฉ "ืจืงืข".
- ืืฃ ืงืืคืฅ - ืืฃ ืงืืคืฅ ืฉืืืคืืข ืืืฉืจ ืืชื ืืืืฅ ืขื ืกืื ืืืจืืื. ืืื ืืคืกื
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 ื ืืืฅ ืืื ืืืืืืื ืื, ืืจืง ืกืคืฆืืคืืื ืืืืืื ืืืืืช ืืืืืจืื ืืื ืืคืกื. ืืชืืฆืื ืืื, ืืืืืืจืื ืชืืจืื ืื:
ืืืื ืขืื ืชืกืจืื - inpage
, ืืืชื ื ืืจืืง ืืืฃ. ืืื ืืจืืฅ ืืืงืฉืจ ืฉืื ืืืกืคืง API ืืขืืืื ืขื ืืชืืกืฃ.
ืืื
ืื ืงืื ืืจืืืืช ืืืคืืคื ืืืื ื
ื ืชืืื ืขื ืืื ืืคืกื:
{
// ะะผั ะธ ะพะฟะธัะฐะฝะธะต, ะฒะตััะธั. ะัะต ััะพ ะฑัะดะตั ะฒะธะดะฝะพ ะฒ ะฑัะฐัะทะตัะต ะฒ 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 - ืืืืจ ื ืืชื ืืืขืื ืืช ืืืคืืืงืฆืื ืฉืื ื ืืืืื ืืจืื ืืืืืื ืฉืืื ืคืืขืืช.
ืืื ืืืืช ืืืช, ืืชื ืืืื ืืงืืช ืืช ืืงืื
ืืขืช ืืชืืกืฃ ืฉืื ื ืืืชืงื ืืขืืื. ืืชื ืืืื ืืืคืขืื ืืช ืืื ืืืคืชืืื ืขืืืจ ืืงืฉืจืื ืฉืื ืื ืืืืคื ืืื:
ืงืืคืฅ ->
ืืืืฉื ืืืกืืฃ ืกืงืจืืคื ืืชืืื ืืชืืฆืขืช ืืจื ืืืกืืฃ ืฉื ืืืฃ ืขืฆืื ืื ืืื ืืืคืขื.
ืืืืขืืช
ืื, ืื ืื ื ืฆืจืืืื ืืืงืื ืฉื ื ืขืจืืฆื ืชืงืฉืืจืช: inpage <-> ืจืงืข ืืจืงืข ืงืืคืฅ <->. ืืชื ืืืื, ืืืืื, ืคืฉืื ืืฉืืื ืืืืขืืช ืืคืืจื ืืืืืฆืื ืคืจืืืืงืื ืืฉืื, ืืื ืื ื ืืขืืืฃ ืืช ืืืืฉื ืฉืจืืืชื ืืคืจืืืงื ืืงืื ืืคืชืื ืฉื metamask.
ืืืื ืืจืืื ืืืคืืคื ืืขืืืื ืขื ืจืฉืช Ethereum. ืื, ืืืงืื ืฉืื ืื ืฉื ืืืคืืืงืฆืื ืืชืงืฉืจืื ืืืืฆืขืืช RPC ืืืืฆืขืืช ืกืคืจืืืช dnode. ืื ืืืคืฉืจ ืื ืืืจืื ืืืืคื ืื ืืืืจื ืื ืืื ืื ืืชื ืืกืคืง ืื ืืจื nodejs ืืืจื ืกืคืืจื (ืืืืืจ ืืืืืืงื ืฉืืืืฉื ืืช ืืืชื ืืืฉืง):
import Dnode from "dnode/browser";
// ะ ััะพะผ ะฟัะธะผะตัะต ััะปะพะฒะธะผัั ััะพ ะบะปะธะตะฝั ัะดะฐะปะตะฝะฝะพ ะฒัะทัะฒะฐะตั ััะฝะบัะธะธ ะฝะฐ ัะตัะฒะตัะต, ั
ะพัั ะฝะธัะตะณะพ ะฝะฐะผ ะฝะต ะผะตัะฐะตั ัะดะตะปะฐัั ััะพ ะดะฒัะฝะฐะฟัะฐะฒะปะตะฝะฝัะผ
// Cะตัะฒะตั
// API, ะบะพัะพัะพะต ะผั ั
ะพัะธะผ ะฟัะตะดะพััะฐะฒะธัั
const dnode = Dnode({
hello: (cb) => cb(null, "world")
})
// ะขัะฐะฝัะฟะพัั, ะฟะพะฒะตัั
ะบะพัะพัะพะณะพ ะฑัะดะตั ัะฐะฑะพัะฐัั dnode. ะัะฑะพะน nodejs ัััะธะผ. ะ ะฑัะฐัะทะตัะต ะตััั ะฑะธะฑะธะปะธะพัะตะบะฐ 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)
// ะะปะธะตะฝั
const dnodeClient = Dnode() // ะัะทะพะฒ ะฑะตะท ะฐะณััะผะตะฝัะฐ ะทะฝะฐัะธั ััะพ ะผั ะฝะต ะฟัะตะดะพััะฐะฒะปัะตะผ API ะฝะฐ ะดััะณะพะน ััะพัะพะฝะต
// ะัะฒะตะดะตั ะฒ ะบะพะฝัะพะปั world
dnodeClient.once('remote', remote => {
remote.hello(((err, value) => console.log(value)))
})
ืืขืช ื ืืฆืืจ ืืืชืช ืืืฉืืืื. ืื ืืฆืืจ ืืืืืืงืื API ืขืืืจ ืืืืื ืืงืืคืฅ ืืืฃ ืืืื ืืจื ื, ืืืืฆืืจ ืขืืืจื dnode:
import Dnode from 'dnode/browser';
export class SignerApp {
// ะะพะทะฒัะฐัะฐะตั ะพะฑัะตะบั API ะดะปั ui
popupApi(){
return {
hello: cb => cb(null, 'world')
}
}
// ะะพะทะฒัะฐัะฐะตั ะพะฑัะตั API ะดะปั ัััะฐะฝะธัั
pageApi(){
return {
hello: cb => cb(null, 'world')
}
}
// ะะพะดะบะปััะฐะตั popup ui
connectPopup(connectionStream){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(remote)
})
}
// ะะพะดะบะปััะฐะตั ัััะฐะฝะธัั
connectPage(connectionStream, origin){
const api = this.popupApi();
const dnode = Dnode(api);
connectionStream.pipe(dnode).pipe(connectionStream);
dnode.on('remote', (remote) => {
console.log(origin);
console.log(remote)
})
}
}
ืืื ืืืืื, ืืืงืื ืืืืืืงื Chrome ืืขืืืื, ืื ื ืืฉืชืืฉืื ื-extensionApi, ืืฉืจ ื ืืืฉืช ืืืจืื ืืืคืืคื ืฉื ืืืื ืืืืคืืคื ืืืืจืื. ืื ื ืขืฉื ืืืขื ืชืืืืืช ืืื ืืคืืคื ืื, ืืื ืืืืจืืช ืืืืจ ืื ืืคืฉืจ ืคืฉืื ืืืฉืชืืฉ ื-'chrome.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()
}
}
ืขืืฉืื ืืืื ื ืืฆืืจ ืืืืืจ ืืืืฉืง ืืืฉืชืืฉ:
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. ืืืจื ืืช ืืกืงืจืืคื (ืืืืฉื ืืืื ืืืชืจื ืืื ืืคืกื) ืืฆืืจ ืชื
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 ื-inpage ืืืืืืจืื ืืืชื ืืืืืื:
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 ืื:
ืขืืืื ืขื ืคืื ืงืฆืืืช ืืชืงืฉืจืืช ืืืืจืช ื-JS ืืืืจื ืืช ืืื ื ืืืืกืื ืืจืืขืื, ืื ืืื ื ืืชืื ืขืืืจ ืงืื ืืื ืืืฆืืจ dnode ืฉืืืคืฉืจ ืื ืืืขืืืจ ืืืืืืงื API ื- utils.
ืืืืืืงืื ื-API ืืืจืื ืืขืช ืื:
export class SignerApp {
popupApi() {
return {
hello: async () => "world"
}
}
...
}
ืงืืืช ืืืืืืงื ืืจืืืง ืื:
import {cbToPromise, transformMethods} from "../../src/utils/setupDnode";
const pageApi = await new Promise(resolve => {
dnode.once('remote', remoteApi => {
// ะก ะฟะพะผะพััั ััะธะปะธั ะผะตะฝัะตะผ ะฒัะต callback ะฝะฐ promise
resolve(transformMethods(cbToPromise, remoteApi))
})
});
ืืงืจืืื ืืคืื ืงืฆืืืช ืืืืืจื ืืืืื:
ืืจืกื ืขื ืคืื ืงืฆืืืช ืืกืื ืืจืื ืืืช ืืืื ืืช
ืืกื ืืื, ืืืฉืช ื-RPC ืืืืจื ื ืจืืืช ืืืืฉื ืืืื: ืื ื ืืืืืื ืืืฉืชืืฉ ืืจืืืื ืงืืืืจ ืืืืฆืืจ ืืกืคืจ ืืืฉืงื API ืฉืื ืื ืขืืืจ ืืฉืืืืช ืฉืื ืืช. ืืืืคื ืขืงืจืื ื, ื ืืชื ืืืฉืชืืฉ ื-dnode ืืื ืืงืื, ืืขืืงืจ ืืื ืืขืืืฃ ืืช ืืชืืืืจื ืืฆืืจื ืฉื ืืจื nodejs.
ืืืืคื ืืื ืคืืจืื JSON, ืืืืืฉื ืืช ืคืจืืืืงืื JSON RPC 2. ืขื ืืืช, ืืื ืขืืื ืขื ืชืขืืืจืืช ืกืคืฆืืคืืืช (TCP ื-HTTP(S)), ืื ืฉืื ืืฉืื ืืืงืจื ืฉืื ื.
ืืฆื ืคื ืืื ืืืืกืื ืืงืืื
ื ืฆืืจื ืืืืกื ืืช ืืืฆื ืืคื ืืื ืฉื ืืืคืืืงืฆืื - ืืคืืืช ืืช ืืคืชืืืช ืืืชืืื. ืื ื ืืืืืื ืื ืืงืืืช ืืืืกืืฃ ืืฆื ืืืคืืืงืฆืื ืืฉืืืืช ืืฉืื ืืื ื-API ืืืืงืคืฅ:
import {setupDnode} from "./utils/setupDnode";
export class SignerApp {
constructor(){
this.store = {
keys: [],
};
}
addKey(key){
this.store.keys.push(key)
}
removeKey(index){
this.store.keys.splice(index,1)
}
popupApi(){
return {
addKey: async (key) => this.addKey(key),
removeKey: async (index) => this.removeKey(index)
}
}
...
}
ืืจืงืข, ื ืขืืืฃ ืืื ืืคืื ืงืฆืื ืื ืืชืื ืืช ืืืืืืงื ืืืืฉืื ืืืืื ืืื ืฉื ืืื ืืขืืื ืืืชื ืืืืกืืฃ:
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
const DEV_MODE = process.env.NODE_ENV !== 'production';
setupApp();
function setupApp() {
const app = new SignerApp();
if (DEV_MODE) {
global.app = app;
}
extensionApi.runtime.onConnect.addListener(connectRemote);
function connectRemote(remotePort) {
const processName = remotePort.name;
const portStream = new PortStream(remotePort);
if (processName === 'contentscript') {
const origin = remotePort.sender.url;
app.connectPage(portStream, origin)
} else {
app.connectPopup(portStream)
}
}
}
ืืืื ื ืืกืืฃ ืืื ืืคืชืืืช ืืืกืืฃ ืืืฉืง ืืืฉืชืืฉ ืื ืจืื ืื ืงืืจื ืขื ืืืฆื:
ืืืฆื ืฆืจืื ืืืืขืฉืืช ืงืืืข ืืื ืฉืืืคืชืืืช ืื ืืืืื ืืขืช ืืคืขืื ืืืืฉ.
ื ืืืกื ืืืชื ื-localStorage, ืื ืืืืฃ ืืืชื ืืื ืฉืื ืื. ืืืืจ ืืื, ืืืฉื ืืืื ืชืืื ืืืจืืืช ืื ืขืืืจ ืืืฉืง ืืืฉืชืืฉ, ืืื ื ืื ืจืืฆื ืืืืจืฉื ืืฉืื ืืืื. ืืืชืืกืก ืขื ืื, ืืืื ื ืื ืืืฆืืจ ืืืกืื ื ืืชื ืืฆืคืืื ืืืืืจืฉื ืืฉืื ืืืื ืฉืื.
ื ืฉืชืืฉ ืืกืคืจืืืช 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 ืขืืงื ืืืจ ืืืฉื ืืฉืืืช. ื ืขืฉื ืฉืืืืฉ ื-Gutters ื-seters ืฉื ืืืืืืงืื proxy ืฉืืกืคืจืืื ืืืฆืจืช.
ืืขืฆืื ืคืขืืื ืืฉืจืชืื ืฉืชื ืืืจืืช:
- ืืืฆื ืงืคืื ื ืขื ืืื enforceActions, mobx ืืืกืจ ืขื ืฉืื ืื ืืืืื ื ืืฉืืจืืช. ืื ื ืืฉื ื ืืื ืืื ืืขืืื ืืชื ืืื ืืืืืจืื.
- ืื ืื ืคืื ืงืฆืื ืืฉื ื ืืช ืืืฆื ืืกืคืจ ืคืขืืื - ืืืฉื, ืื ื ืืฉื ืื ืืกืคืจ ืฉืืืช ืืืื ืฉืืจืืช ืงืื - ืืฆืืคืื ืืงืืืื ืืืืขื ืจืง ืืฉืืื ืืกืชืืืืช. ืื ืืฉืื ืืืืืื ืขืืืจ ื-frontend, ืฉืื ืขืืืื ื ืืฆื ืืืืชืจืื ืืืืืืื ืืขืืืื ืืืืชืจ ืฉื ืืืื ืืื. ืืืงืจื ืฉืื ื, ืื ืืจืืฉืื ืืื ืืฉื ื ืจืืืื ืื ืืืืืื, ืืื ื ืคืขื ืืคื ืืฉืืืืช ืืืืืืฆืืช. ื ืืื ืืฆืจืฃ ืืงืืจืืืจืื ืืื ืืคืื ืงืฆืืืช ืืืฉื ืืช ืืช ืืฆื ืืฉืืืช ืื ืฆืคืื.
ืืจืงืข ื ืืกืืฃ ืืชืืื ืืฉืืืจืช ืืืฆื ื-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 ืืืืื ืืืืื ืชืฆืคืืืช ืื ื ืื ืืืื. ืื ืืชืืชื ืืืจืจ ืืงืื ืืื() => app.store
, ืื ืชืืืื ืืขืืื ืื ืชืืงืจื, ืืืืืื ืฉืืืืกืื ืขืฆืื ืืื ื ื ืืชื ืืฆืคืืื, ืจืง ืืฉืืืช ืฉืื.
ืื ืืชืืชื ืืช ืื ืืื () => app.store.keys
, ืื ืฉืื ืฉืื ืืืจ ืื ืืงืจื, ืฉืื ืืขืช โโืืืกืคื/ืืกืจื ืฉื ืจืืืื ืืขืจื, ืืืชืืืืกืืช ืืืื ืื ืชืฉืชื ื.
Mobx ืคืืขืืช ืืืืจืจ ืืคืขื ืืจืืฉืื ื ืืขืืงืืช ืจืง ืืืจ ื ืงืืืืช ืฆืคืืื ืฉืืืืื ื ืืืฉื ื. ืื ื ืขืฉื ืืืืฆืขืืช Proxy getters. ืืื, ืืคืื ืงืฆืื ืืืืื ืืช ืืฉืืฉืช ืืื toJS
. ืืื ืืืืืจ ืืืืืืงื ืืืฉ ืขื ืื ืฉืจืช ื-proxy ืืืืืคืื ืืฉืืืช ืืืงืืจืืื. ืืืืื ืืืืฆืืข, ืืื ืงืืจื ืืช ืื ืืฉืืืช ืฉื ืืืืืืืงื - ืืืืื ืฉืืืงืืืื ืืืคืขืืื.
ืืืกืืฃ ืืงืืคืฅ ื ืืกืืฃ ืฉืื ืืกืคืจ ืืคืชืืืช. ืืคืขื ืื ืื ืืืืขื ื-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
. ืขืืืจ ืกืจืง ืืชื ืืืื ืืืืืืจ ืคืกืง ืืื, ืื ืขืื ืืืืืจ ืืืฉืจ ืืขืจืืช ืืืคืขืื ืขืฆืื ืืกืืื. ืื ื ืื ื ืฉื ื ืืช ืืืืจืจ ืืฉืืืจื ื-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)
}
}
}
ืืงืื ืืคื ื ืฉืื ืื ืืื
ืขืกืงืืช
ืื, ืืืขื ื ืืืืจ ืืืฉืื ืืืืชืจ: ืืฆืืจื ืืืชืืื ืขื ืขืกืงืืืช ืืืืืงืฆ'ืืื. ื ืฉืชืืฉ ืืืืืงืฆ'ืืื ืืืกืคืจืืื ืฉื WAVES
ืจืืฉืืช, ืืืื ื ืืกืืฃ ืืืืื ื ืืขืจื ืฉื ืืืืขืืช ืฉืฆืจืื ืืืชืื, ืืื ื ืืกืืฃ ืฉืืืืช ืืืืกืคืช ืืืืขื ืืืฉื, ืืืฉืืจ ืืืชืืื ืืกืืจืื:
import {action, observable, reaction} from 'mobx';
import uuid from 'uuid/v4';
import {signTx} from '@waves/waves-transactions'
import {setupDnode} from "./utils/setupDnode";
import {decrypt, encrypt} from "./utils/cryptoUtils";
export class SignerApp {
...
@action
newMessage(data, origin) {
// ะะปั ะบะฐะถะดะพะณะพ ัะพะพะฑัะตะฝะธั ัะพะทะดะฐะตะผ ะผะตัะฐะดะฐะฝะฝัะต ั id, ััะฐัััะพะผ, ะฒััะตะผะตะฝะตะผ ัะพะทะดะฐะฝะธั ะธ ัะด.
const message = observable.object({
id: uuid(), // ะะดะตะฝัะธัะธะบะฐัะพั, ะธัะฟะพะปัะทััั uuid
origin, // Origin ะฑัะดะตะผ ะฒะฟะพัะปะตะดััะฒะธะธ ะฟะพะบะฐะทัะฒะฐัั ะฒ ะธะฝัะตััะตะนัะต
data, //
status: 'new', // ะกัะฐัััะพะฒ ะฑัะดะตั ัะตัััะต: new, signed, rejected ะธ failed
timestamp: Date.now()
});
console.log(`new message: ${JSON.stringify(message, null, 2)}`);
this.store.messages.push(message);
// ะะพะทะฒัะฐัะฐะตะผ ะฟัะพะผะธั ะฒะฝัััะธ ะบะพัะพัะพะณะพ mobx ะผะพะฝะธัะพัะธั ะธะทะผะตะฝะตะฝะธั ัะพะพะฑัะตะฝะธั. ะะฐะบ ัะพะปัะบะพ ััะฐััั ะฟะพะผะตะฝัะตััั ะผั ะทะฐัะตะทะพะปะฒะธะผ ะตะณะพ
return new Promise((resolve, reject) => {
reaction(
() => message.status, //ะัะดะตะผ ะพะฑัะตัะฒะธัั ััะฐััั ัะพะพะฑัะตะฝั
(status, reaction) => { // ะฒัะพัะพะน ะฐัะณัะผะตะฝั ััะพ ัััะปะบะฐ ะฝะฐ ัะฐะผ reaction, ััะพะฑั ะตะณะพ ะผะพะถะฝะพ ะฑัะปะพ ัะฝะธััะพะถัั ะฒะฝัััะธ ะฒัะทะพะฒะฐ
switch (status) {
case 'signed':
resolve(message.data);
break;
case 'rejected':
reject(new Error('User rejected message'));
break;
case 'failed':
reject(new Error(message.err.message));
break;
default:
return
}
reaction.dispose()
}
)
})
}
@action
approve(id, keyIndex = 0) {
const message = this.store.messages.find(msg => msg.id === id);
if (message == null) throw new Error(`No msg with id:${id}`);
try {
message.data = signTx(message.data, this.store.keys[keyIndex]);
message.status = 'signed'
} catch (e) {
message.err = {
stack: e.stack,
message: e.message
};
message.status = 'failed'
throw e
}
}
@action
reject(id) {
const message = this.store.messages.find(msg => msg.id === id);
if (message == null) throw new Error(`No msg with id:${id}`);
message.status = 'rejected'
}
...
}
ืืืฉืจ ืื ื ืืงืืืื ืืืืขื ืืืฉื, ืื ื ืืืกืืคืื ืื ืืื ื ืชืื ืื, ืขืฉื observable
ืืืืืกืืฃ ื store.messages
.
ืื ืื observable
ืืืืคื ืืื ื, ืื mobx ืืขืฉื ืืืช ืืขืฆืื ืืขืช ืืืกืคืช ืืืืขืืช ืืืขืจื. ืขื ืืืช, ืื ืืฆืืจ ืืืืืืงื ืืืฉ ืฉืื ืชืืื ืื ื ืืชืืืืกืืช ืืืื, ืืื ื ืฆืืจื ืืืชื ืืฉืื ืืื.
ืืืืจ ืืื, ืื ื ืืืืืจืื ืืืืื ืฉื ืคืชืจืช ืืืฉืจ ืกืืืืก ืืืืืขื ืืฉืชื ื. ืืกืืืืก ืื ืืืจ ืขื ืืื ืชืืืื, ืืฉืจ "ืชืืจืื ืืช ืขืฆืื" ืืืฉืจ ืืืฆื ืืฉืชื ื.
ืงืื ืฉืืื approve
ะธ reject
ืคืฉืื ืืืื: ืื ื ืคืฉืื ืืฉื ืื ืืช ืกืืืืก ืืืืืขื, ืืืืจ ืืืชืืื ืขืืื ืืืืืช ืืฆืืจื.
ืฉืื ื ืืช Approve and Reject ื-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
state ืืืืืกืืฃ ืคืื ืงืฆืื ื-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>
}
}
ื ืืชื ืืจืืืช ืืช ืืจืืืืื ืื ืืชืจืื ืืงืื
ืืขืช ืืืืืงืช ืืืืฉืืืื ืขืืื ืืืฆืืจ ืืืจืจ ืืฆื ืขืืืจ ืืืฉืง ืืืฉืชืืฉ ืืืืืืืข โโืืืืฉืง ืืืฉืชืืฉ ืืืฉืจ ืืื ืืฉืชื ื. ืืฉื ืื, ืืืื ื ืืกืืฃ ืฉืืื 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