áá¬áá¬áẠ"áá±á¬ááºáááº-áá¬áá¬" áááá¯áá¬ááŸáá·áº ááá°áá²á áááá¯áá»á¯ááºááá¯ááºááŸá¯ áá»áŸá±á¬á·áá»áá¬ážáá±á¬ á¡ááá®áá±ážááŸááºážáá»á¬ážááᯠá¡á±á¬ááºáá«ááá¯á·á ááœá²ááŒá¬ážáááºááŸááºáá¬ážáá«áááºá
- á¡áá¯á¶ážááŒá¯áá° áá±á¬á·ááºá¡ááºáá»á¬ážááŸáá·áº á áá¬ážááŸááºáá»á¬ážááŒáá·áº áá±áá¬áá±á·á áºááᯠááááºážáááºážááẠáááá¯á¡ááºáá«á á¡áá¯á¶ážááŒá¯áá°áá»á¬ážááá¯ááºááá¯áẠáá®ážááá·áºááááºážáááºážáá¬ážáá±á¬ á¡áá»ááºá¡áááºáá»á¬ážááᯠááá°áá¯á¶ážá áœá²ááá¯ááºááŒá®áž áááºážááá¯á·á á á áºááŸááºááŒá±á¬ááºáž á¡áááºááŒá¯áá»ááºááẠáááá¯ááá¯áá±á¬á¡ááá·áºááœáẠááŒá áºáá±á«áºáá«áááºá
- áá¬áá¬á¡áá¯á¶ážááŒá¯áááºáááá¯á¡ááºáá«á á¡ááá®áá±ážááŸááºážáá¯áá¹áááá±áááẠááá¯á¡ááºáá±á¬áá±áá¬ááá¬áááᯠááááºážáááºážáá¬ážááá¯ááºááá·áº blockchain ááœááºáááºáá±á«áºááœáẠáá¯ááºáá±á¬ááºááá¯ááºáááºá
á¡áá¯á¶ážááŒá¯áá°áá®ážáá»á¬ážá¡ááœáẠáá¯á¶ááŒá¯á¶á áááºáá»ááá±á¬ ááá¯ááŸá±á¬ááºááŸá¯ 2 áá¯ááŸáááẠ- áá¬á·ááºáá²ááá¯ááºáá¶á¡áááºáá»á¬ážááŸáá·áº ááá±á¬ááºáᬠááá¯ážáá»á²á·ááŸá¯áá»á¬ážá áá¬á·ááºáá²ááá¯ááºáá¶á¡áááºáá»á¬ážááẠá¡ááœááºáá¯á¶ááŒá¯á¶áá±á¬áºáááºáž á¡áá¯á¶ážááŒá¯ááááºáá²ááŒá®áž á¡ááá²á·ááŒá áºáááºá ááá¯á·áá±á¬áº ááá±á¬ááºáᬠááá¯ážáá»á²á·ááŸá¯áá»á¬ážááẠáá¯á¶ááŒá¯á¶áá±ážááŸáá·áº á¡áá¯á¶ážááŒá¯áááœááºáá°ááŸá¯á ááŒá®ážááŒáá·áºá á¯á¶áá±á¬áá±á«ááºážá ááºááŸá¯ááŒá áºááŒá®áž áá¯á¶ážá áœá²áá°áá»á¬ážá¡ááœááºáááºáž áá¯á¶ážáá¡ááá²á·ááŒá áºáááºá
á€á¡áá¬á¡á¬ážáá¯á¶ážááᯠááá·áºááœááºážá
ááºážá
á¬ážááŒááºážááŒáá·áº ááœá±áá±ážááœá±áá°ááŸáá·áº áááºááŸááºáá»á¬ážááŸáá·áº áá¯ááºáá±á¬ááºáááºá¡ááœáẠááá¯ážááŸááºážáá±á¬ API ááᯠáá¶á·ááá¯ážáá±ážááŒááºážááŒáá·áº áááá¯áá»á¯ááºááá¯ááºááŸá¯áá»áŸá±á¬á·áá»áá¬ážáá±á¬ á¡ááá®áá±ážááŸááºážáá»á¬áž ááœá¶á·ááŒáá¯ážááá¯ážáááºááŸá¯ááᯠááá¯ážááŸááºážá
á±ááá·áº á¡áá¯á¶ááŒá¯á¶áá¯á¶ážáá±á¬ ááá¯ážáá»á²á·ááŸá¯ááᯠááŒá¯áá¯ááºááá¯áá«áááºá
áá®á¡ááœá±á·á¡ááŒá¯á¶ááᯠá¡á±á¬ááºááŸá¬ ááŒá±á¬ááŒáá«áááºá
áá±á¬ááºážáá«ážááœáẠáá¯ááºááá°áá¬áá»á¬ážááŸáá·áº áááºáá¬ážááŒááºáá¬ááºáá¯á¶áá»á¬ážáá«ááŸááá±á¬ ááá±á¬ááºáᬠextension áá
áºáá¯á¡á¬áž áááºááá¯á·áá±ážááááºááᯠá¡ááá·áºááá·áº áááºážááœáŸááºáá»ááºáá»á¬ážáá«ááŸááááºá áá¯ááºááœá±á¡á¬ážáá¯á¶ážááᯠááœá±á·ááá¯ááºáá«áááºá
Browser ááá¯ážáá»á²á·ááŸá¯áá»á¬ážááááá¯ááºážá¡áá»ááºáž
ááá±á¬ááºáᬠááá¯ážáá»á²á·ááŸá¯áá»á¬ážááẠá¡áá»áááºá¡áá±á¬áºááŒá¬á¡á±á¬áẠááŸááá±áá«áááºá áááºážááá¯á·ááẠ1999 áá¯ááŸá áºááœáẠInternet Explorer á 2004 áá¯ááŸá áºááœáẠFirefox ááœááºáá±á«áºáá¬áá²á·áááºá ááá¯á·áá±á¬áº á¡áá»áááºááŒá¬ááŒáá·áºá áœá¬ ááá¯ážáá»á²á·ááŸá¯áá»á¬ážá¡ááœáẠá á¶áááºááŸááºáá»ááºáá áºáá¯áá»áŸ áááŸááá²á·áá«á
Google Chrome á á áá¯áá¹áááŒá±á¬ááºáá¬ážááŸááºážááœáẠextension áá»á¬ážááŸáá·áºá¡áá° áááºážááẠáá±á«áºáá¬áááºáᯠáá»áœááºá¯ááºááá¯á·ááŒá±á¬ááá¯ááºáááºá áá¯ááºáá«áááºá ááá¯á¡áá»áááºá áááºááŸááºáá»ááºáááŸááá±á¬áºáááºážá áááºážááẠáááºážáá¡ááŒá±áá¶ááŒá áºáá¬áá±á¬ Chrome API ááŒá áºáááº- browser á á»á±ážááœááºá¡áá»á¬ážá á¯ááᯠááááºážááá¯ááºááŒá®áž built-in application store ááŸáááŒááºážááŒá±á¬áá·áº Chrome ááẠbrowser extensions áá»á¬ážá¡ááœáẠá á¶áááºááŸááºáá»ááºááᯠá¡ááŸááºááááºáááºááŸááºáá±ážáá²á·áááºá
Mozilla ááẠáááºážáááá¯ááºááá¯ááºá á¶ááŸá¯ááºážáá áºáá¯ááŸááá±á¬áºáááºáž Chrome ááá¯ážáá»á²á·ááŸá¯áá»á¬ážááá±áááºážá á¬ážááŸá¯ááá¯ááŒááºáá»áŸáẠáá¯áá¹ááá®ááẠááœá²áááºá¡áá¯á¶ážááŒá¯ááá¯ááºáá±á¬ API áá áºáá¯ááŒá¯áá¯ááºááẠáá¯á¶ážááŒááºáá²á·áááºá 2015 áá¯ááŸá áºááœáẠMozilla áá¡á ááŒá¯ááŸá¯ááœááºá cross-browser extension specifications áá»á¬ážááá¯áá¯ááºáá±á¬ááºááẠWorld Wide Web Consortium (W3C) ááœáẠá¡áá°ážá¡ááœá²á·áá áºáá¯ááᯠáááºáá®ážáá²á·áááºá
Chrome á¡ááœáẠááŸáááŒá®ážáá¬áž API ááá¯ážáá»á²á·ááŸá¯áá»á¬ážááᯠá¡ááŒá±áá¶á¡ááŒá
Ạáá°áá¬ážáááºá Microsoft á áá¶á·ááá¯ážáá°áá®ááŸá¯ááŒáá·áº á¡áá¯ááºááᯠáá¯ááºáá±á¬ááºáá²á·ááẠ(Google ááẠá
á¶ááŸá¯ááºážááœá¶á·ááŒáá¯ážááá¯ážáááºááŸá¯ááœáẠáá«áááºááẠááŒááºážááá¯áá²á·áááº) ááááºá¡ááŒá
Ạáá°ááŒááºážáá
áºáᯠááœááºáá±á«áºáá¬áá²á·áááºá
ááá¬ážáááºá¡á¬ážááŒáá·áºá áááºááŸááºáá»ááºááᯠEdgeá Firefox ááŸáá·áº Opera ááá¯á·á áá¶á·ááá¯ážáá±ážááẠ(Chrome ááẠá€á
á¬áááºážááœáẠááá«áááºááŒá±á¬ááºáž áááááŒá¯áá«)á ááá¯á·áá±á¬áº ááááºáááºážááœááºá áááºážááẠáááºážá extensions áá»á¬ážááᯠá¡ááŒá±áá¶á á¡ááŸááºááááºáá±ážáá¬ážáá¬ážáá±á¬ááŒá±á¬áá·áº á
á¶ááẠChrome ááŸáá·áº áá»á¬ážá
áœá¬áááá¬áááŒá
áºáááºá WebExtensions API á¡ááŒá±á¬ááºáž ááá¯ááá¯áááºááŸá¯ááá¯ááºáá«áááºá
ááá¯ážáá»á²á·ááœá²á·á ááºážááŸá¯
ááá¯ážáá»á²á·ááŸá¯á¡ááœáẠááá¯á¡ááºáá±á¬ áá áºáá¯áááºážáá±á¬ááá¯ááºááŸá¬ áááºáá®ážáááºá Ạ(manifest.json) ááŒá áºáááºá áá»á²á·ááœááºááŒááºážá¡ááœáẠâáááºáá±á«ááºâ áááºážááŒá áºáááºá
á á¬áááºážááœááº
áááºááŸááºáá»ááºá¡áá áááºáá®ážáááºá
áºááá¯ááºááẠááá¬ážááẠJSON ááá¯ááºááŒá
áºáááºá áááºááá±á¬ááºáá¬ááŸá¬ áááºáá±á¬á·ááœá±ááᯠáá¶á·ááá¯ážáá±ážááá¯ááºááá²ááá¯áá²á· á¡áá»ááºá¡áááºááœá±áá²á· áááºáá®ážáááºá
áºáá±á¬á·ááœá±áá²á· áá±á¬áºááŒáá»ááºá¡ááŒáá·áºá¡á
á¯á¶
áááºááŸááºáá»ááºááœááºááá«áááºááá·áºáá±á¬á·áá»á¬ážááᯠáá»á áºáá»á°ááŸá¯áá¬ážááá¯ááºááẠ(Chrome ááŸáá·áº Firefox ááŸá áºáá¯áá¯á¶ážááœáẠá¡ááŸá¬ážá¡ááœááºážáá»á¬áž ááŸááá±á¬áºáááºáž ááá¯ážáá»á²á·ááŸá¯áá»á¬áž áááºáááºáá¯ááºáá±á¬ááºáá±áá«áááº)á
ááŒá®ážáá±á¬á· ááá»áá¯á·á¡áá»ááºááœá±ááᯠá¡á¬áá¯á¶á áá¯ááºá á±áá»ááºáá«áááºá
- áá±á¬ááºáᶠ- á¡á±á¬ááºáá«á¡ááœááºáá»á¬áž áá«áááºáá±á¬ á¡áá¬ááá¹áá¯áá
áºáá¯á
- script áá»á¬áž â áá±á¬ááºáá¶á¡ááŒá±á¬ááºážá¡áá¬ááœáẠáá¯ááºáá±á¬ááºááá·áº áá¬ááºááœáŸááºážáá»á¬ážáááºážáá»ááºážááŒááºáž (á€á¡ááŒá±á¬ááºážááᯠáá±á¬ááºá¡áááºážáááºááŒá¬á០ááŒá±á¬ááŒáá«áááº)á
- á á¬áá»ááºááŸá¬ - á¡ááœááºá á¬áá»ááºááŸá¬ááœáẠáá¯ááºáá±á¬ááºááá·áº script áá»á¬ážá¡á á¬ážá áááºááẠhtml ááᯠá¡ááŒá±á¬ááºážá¡áá¬ááŒáá·áº áááºááŸááºááá¯ááºáááºá á€ááá á¹á ááœááºá áá¬ááºááœáŸááºážá¡ááœááºááᯠáá»á áºáá»á°ááŸá¯áááºááŒá áºááŒá®ážá áá¬ááºááœáŸááºážáá»á¬ážááᯠá¡ááŒá±á¬ááºážá¡áá¬á á¬áá»ááºááŸá¬ááá¯á· ááá·áºááœááºážááẠááá¯á¡ááºáááºááŒá áºáááºá
- ááŒá¶áᶠâ áááºááŸááºáá¬ážááŒááºážáááŸááá«áá ááá±á¬ááºáá¬ááẠáá áºá á¯á¶áá áºáá¬áá¯ááºáá±á¬ááºááŒááºážáááŸááá¯áá°ááá±á¬á¡áá« ááá±á¬ááºáá¬ááẠáá±á¬ááºáá¶áá¯ááºáááºážá ááºááᯠâáááºáá áºáááá·áºáááºâ ááŒá áºáᬠááá¯á¡ááºáá«á áááºážááᯠááŒááºáááºá áááºáá«á ááá¯ááºáá«áá ááá±á¬ááºáá¬ááᯠááááºááá·áºá¡áá«ááŸáᬠá á¬áá»ááºááŸá¬ááᯠááœáŸáá·áºáááºáááºááŒá áºáááºá Firefox ááœáẠááá¶á·ááá¯ážáá«á
- content_scripts â ááá°áá®áá±á¬áááºá
á¬áá»ááºááŸá¬áá»á¬ážááá¯á· ááá°áá®áá±á¬ scripts áá»á¬ážááᯠáááºááá¯ááºá
á±ááá·áº á¡áá¬ááá¹áá¯áá»á¬ážá array áá
áºáá¯á á¡áá¬ááá¹áá¯áá
áºáá¯á
á®ááœáẠá¡á±á¬ááºáá«á¡áá±ážááŒá®ážáá±á¬ á¡ááœááºáá»á¬ážáá«ááŸááááºá
- ááœá² -
áá¯á¶á ᶠurl á¡ááŒá±á¬ááºážá¡áᬠáá¬ááºááœáŸááºážáá áºáᯠáá«áááºáááºá ááá«áááºáááºááᯠáá¯á¶ážááŒááºáá±ážáá±á¬á - js â á€ááœá²á ááºááœáẠáááºáá±ážááá·áº script áá»á¬ážá á¬áááºážá
- ááœá²á
ááºáá»á¬ážááᯠáááºáá¯ááºáá«á - áááºááœááºážááŸáááºáá¯ááºáááºá
match
á€á¡ááœááºááŸáá·áº ááá¯ááºáá®áá±á¬ URL áá»á¬áž
- ááœá² -
- page_action - á¡ááŸááºááááºááœáẠááá±á¬ááºáá¬ááŸá ááááºá
á¬áá¬ážáá±ážááœáẠááŒááá¬ážááá·áº á¡áá¯ááºááœááºá¡ááœáẠáá¬áááºááŸáááŒá®áž áááºážááŸáá·áº á¡ááŒááºá¡ááŸááºá¡áá»áá¯ážááŒá¯ááá·áº á¡áá¬áá
áºáá¯ááŒá
áºáááºá áááºážááẠááá·áºááá¯ááºááá¯áẠHTMLá CSS ááŸáá·áº JS ááá¯á¡áá¯á¶ážááŒá¯á áááºááŸááºáá¬ážááá·áº áá±á«á·ááºá¡ááºáááºážááá¯ážááá¯áááºáž ááŒááááºáááºáž ááœáá·áºááŒá¯áá¬ážáááºá
- default_popup â áá±á«á·ááºá¡ááºá¡ááºáá¬áá±á·á áºáá«ááŸááá±á¬ HTML ááá¯ááºááá¯á·ááœá¬ážáá¬áááºážááŒá±á¬ááºážá CSS ááŸáá·áº JS áá«áááºááá¯ááºáááºá
- ááœááºá·ááŒá¯áá»áẠ- ááá¯ážáá»á²á·ááŸá¯á¡ááœáá·áºá¡áá±ážáá»á¬ážááᯠá
á®áá¶ááá·áºááœá²áááºá¡ááœáẠáááºážáá»ááºážáá
áºáá¯á á¡ááœáá·áºá¡áá±áž á¡áá»áá¯ážá¡á
á¬áž á áá»áá¯áž ááŸáááŒá®áž á¡áá±ážá
ááẠáá±á¬áºááŒáá¬ážáá«áááºá
áá®ááŸá¬ - web_accessible_resources â áááºá á¬áá»ááºááŸá¬áá áºáá¯á áá±á¬ááºážááá¯ááá¯ááºáá±á¬ ááá¯ážáá»á²á·á¡áááºážá¡ááŒá áºáá»á¬áž á¥ááá¬á áá¯á¶áá»á¬ážá JSá CSSá HTML ááá¯ááºáá»á¬ážá
- ááŒááºáááœááº_áá»áááºáááºááá¯ááºáá±á¬ â á€áá±áá¬ááœáẠáááºááẠáááºáá»áááºáááºááá¯ááºááá·áº áááºá á¬áá»ááºááŸá¬áá»á¬ážá á¡ááŒá¬ážáá±á¬ ááá¯ážáá»á²á·ááŸá¯áá»á¬ážááŸáá·áº ááá¯ááááºážáá»á¬ážá ID áá»á¬ážááᯠááŒááºáá¬ážá áœá¬ áááºááŸááºááá¯ááºáá«áááºá ááá¯ááááºážááẠáá¯áááá¡ááá·áº ááá¯á·ááá¯áẠááá¯á·ááẠááá¯ááá¯ááºáááºá Firefox ááœáẠá¡áá¯ááºááá¯ááºáá«á
ááœááºáá»ááºááŒááºážááá á¹á áááº
ááá¯ážáá»á²á·ááŸá¯ááœáẠáá¯ááºáá¯ááºáá±á¬ááºááŸá¯ááá¯ááºáᬠá¡ááŒá±á¬ááºážá¡áᬠáá¯á¶ážáá¯áá«ááŸááááºá ááá¯ááá¯áááºááŸá¬ á¡ááá®áá±ážááŸááºážááœáẠááá±á¬ááºáᬠAPI ááá¯á· áááºáá±á¬ááºááá¯ááºáá±á¬ á¡ááá·áºáá¯á¶ážááá·áº áá«áááºáá«áááºá
ááá¯ážáá»á²á·ááŸá¯á¡ááŒá±á¬ááºážá¡áá¬
API á¡áá»á¬ážá á¯ááᯠá€áá±áá¬ááœáẠáááá¯ááºáá«áááºá á€á¡ááŒá±á¡áá±ááœáẠáá°ááá¯á·ááẠ"á¡áááºááŸááºáááº"
- áá±á¬ááºáá¶á á¬áá»ááºááŸá¬ - ááá¯ážáá»á²á·ááŸá¯á "áá±á¬ááºááœááºááŸ" á¡ááá¯ááºážá ááá¯ááºááᯠ"áá±á¬ááºáá¶" áá±á¬á·ááŒáá·áº áááºáá®ážáááºá áºááœáẠáááºááŸááºáá¬ážáááºá
- áá±á«á·ááºá¡ááºá
á¬áá»ááºááŸá¬ â ááá¯ážáá»á²á·á¡áá¯ááºááœááºááᯠáááºááŸáááºááá¯ááºáá±á¬á¡áá« áá±á«áºáá¬ááá·áº áá±á«áºáá¬ááá·áºá
á¬áá»ááºááŸá¬á ááŒá±ááŒá¬áá»ááºáá²ááŸá¬
browser_action
->default_popup
. - á
áááºááŒáá¯ááºá
á¬áá»ááºááŸá¬ â ááŒááºááœááºážááá®ážááŒá¬ážáááºááºáá
áºáá¯ááœáẠ"áá±ááá¯ááºááŒááºáž" ááá¯ážáá»á²á·á
á¬áá»ááºááŸá¬
chrome-extension://<id_ÑаÑÑОÑеМОÑ>/customPage.html
.
á€á¡ááŒá±á¬ááºážá¡áá¬ááẠááá±á¬ááºáá¬áááºážááá¯ážáá»á¬ážááŸáá·áº áááºááºáá»á¬ážááŸáá·áº áá®ážááŒá¬ážáááºááŸááá«áááºá áá±á¬ááºáá¶á á¬áá»ááºááŸá¬ áááá¹áá°áá áºáá¯áááºážááœááºááŸáááŒá®áž á¡ááŒá²áááºážá¡áá¯ááºáá¯ááºááẠ(ááŒáœááºážáá»ááºááŸá¬ ááŒá áºáááºáá áºáá¯á០áá±á¬ááºáá¶áá¬ááºááœáŸááºážááᯠá áááºááŒá®áž áááºážáááœááºáá»ááºááŒá®ážáá±á¬áẠâáá±áá¯á¶ážâ ááá·áºá¡áá« ááŒáœááºážáá»ááºááŸá¬ ááŒá áºáááºá á¬áá»ááºááŸá¬ááŒá áºáááºá áá±á«á·ááºá¡ááºá á¬áá»ááºááŸá¬ áá±á«á·ááºá¡ááºáááºážááá¯ážááá¯ááœáá·áºááá·áºá¡áá«ááŸáá·áº áááºááŸááá±áá«áááºá á áááºááŒáá¯ááºá á¬áá»ááºááŸá¬ â áááºážááŸáá·áºá¡áá° áááºááºááᯠááœáá·áºáá±á ááºá á€á¡ááŒá±á¬ááºážá¡áá¬á០á¡ááŒá¬ážáááºáá»á¬ážááŸáá·áº áááºážááá¯á·áá¡ááŒá±á¬ááºážá¡áá¬áá»á¬ážááᯠáááºáá±á¬ááºááœáá·áºáááŸááá«á
á¡ááŒá±á¬ááºážá¡áᬠáá¬ááºááœáŸááºážá¡ááŒá±á¬ááºážá¡áá¬
á¡ááŒá±á¬ááºážá¡áᬠscript ááá¯ááºááᯠááá±á¬ááºáá¬áááºááºáá
áºáá¯á
á®ááŸáá·áºá¡áá° á
áááºáááºá áááºážááẠextension á API ááá
áºá
áááºáá
áºááá¯ááºážááŸáá·áº áááºá
á¬áá»ááºááŸá¬á DOM áá
áºáááºááá¯á· áááºáá±á¬ááºááœáá·áºááŸááááºá áááºážááẠá
á¬áá»ááºááŸá¬ááŸáá·áº á¡ááŒááºá¡ááŸáẠáááºááœááºááŸá¯á¡ááœáẠáá¬áááºááŸááá±á¬ á¡ááŒá±á¬ááºážá¡áᬠscript áá»á¬ážááŒá
áºáááºá DOM áá
áºáááºááᯠááá¯ááºááœááºááá·áº ááá¯ážáá»á²á·ááŸá¯áá»á¬ážááẠá¡ááŒá±á¬ááºážá¡áᬠscripts áá»á¬ážááœáẠáááºážááᯠáá¯ááºáá±á¬ááºááẠ- á¥ááá¬á ááŒá±á¬áºááŒá¬ááááºááá¯á·áá°áá»á¬áž ááá¯á·ááá¯áẠáá¬áá¬ááŒááºáá°áá»á¬ážá ááá¯á·á¡ááŒááºá á¡ááŒá±á¬ááºážá¡áᬠscript ááẠá
á¶ááŸáá
áºááá·áº á
á¬áá»ááºááŸá¬ááŸáá·áº áááºááœááºááá¯ááºáááºá postMessage
.
áááºá á¬áá»ááºááŸá¬ á¡ááŒá±á¬ááºážá¡áá¬
áá«á áááá·áº web page áá«á á€á á¬áá»ááºááŸá¬á ááá¯ááááºážááᯠáááºáá®ážáááºá áºááœáẠá¡ááá¡áááºážáá±á¬áºááŒáá¬ážááŒááºážáááŸááá±á¬ ááá á¹á áá»á¬ážááŸá¡áá áááºážááẠááá¯ážáá»á²á·ááŸá¯ááŸáá·áº áá¬ááŸáááá¯ááºáá«á áááºážááœáẠá¡áá¯á¶ážááŒá¯ááœáá·áºáááŸááá«á
ááááºážáááŸááºááŒááºáž
á¡ááá®áá±ážááŸááºážá ááá°áá®áá±á¬ á¡á
áááºá¡ááá¯ááºážáá»á¬áž á¡áá»ááºážáá»ááºáž áááºáá±á·áá»áºáá»á¬áž áááŸááºááá«áááºá áá®á¡ááœáẠAPI áá
áºáá¯ááŸááá«áááºá runtime.sendMessage
áááºáá±á·ááºá»ááá¯á·ááẠbackground
О tabs.sendMessage
á
á¬áá»ááºááŸá¬áá
áºáá¯ááá¯á· áááºáá±á·áá»áºáá±ážááá¯á·ááẠ(á¡ááŒá±á¬ááºážá¡áᬠscriptá áá±á«á·ááºá¡áẠááá¯á·ááá¯áẠáááºá
á¬áá»ááºááŸá¬ááŸááá»áŸáẠ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 áá
áºáá¯ááᯠáá¶á·ááá¯ážáá±ážáááºááŒá
áºáááºá
á¡á
ááá±á¬ááºáᬠááá¯ážáá»á²á·áá¯ááºá¡á¬ážáá¯á¶ážááᯠááœáẠáááá¯ááºáá«áááºá
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 ááœááºáááºáá¬ážááŒá®áž áááºážáááºá¡áá¯ááºáá¯ááºááŒá±á¬ááºážáá±áá»á¬áá«á á±á
áááºážááá¯á¡áááºááŒá¯áááºá áááºáááºáá¯ááºááá¯áá°ááá¯ááºáááºá
ááᯠáá»áœááºá¯ááºááá¯á·á extension ááᯠáááºáááºááŒá®áž á¡áá¯ááºáá¯ááºáá«áááºá ááá°áá®áá±á¬á¡ááŒá±á¬ááºážá¡áá¬áá»á¬ážá¡ááœáẠá¡á±á¬ááºáá«á¡ááá¯ááºáž developer tools ááᯠááẠrun ááá¯ááºáááº-
áá±á«á·ááºá¡ááº->
á¡ááŒá±á¬ááºážá¡áᬠscript ááœááºááá¯ážááºááá¯á· áááºáá±á¬ááºááŒááºážá¡á¬áž á áááºááá·áº á á¬áá»ááºááŸá¬á ááœááºááá¯ážááºááŸáááá·áº áá¯ááºáá±á¬ááºáááºá
ááááºážáááŸááºááŒááºáž
ááá¯á·ááŒá±á¬áá·áºá áá»áœááºá¯ááºááá¯á·ááẠáááºááœááºáá±ážáááºážááŒá±á¬ááºážááŸá áºáá¯ááŒá áºáá±á¬ inpage <-> background ááŸáá·áº popup <-> background ááᯠáááºáá±á¬ááºááẠááá¯á¡ááºáá«áááºá áááºááẠááááºáááºážááá¯á· áááºáá±á·áá»áºáá»á¬ážááá¯á·áá¯á¶ááŒáá·áº ááá·áºááá¯ááºááá¯ááºáááá¯ááá¯áá±á¬ááᯠáá®ááœááºááá¯ááºáááºá ááá¯á·áá±á¬áº metamask ááœáá·áºáááºážáááºážááŒá áºááá±á¬áá»ááºááœáẠááŒááºáá²á·áá±á¬áá»ááºážáááºááŸá¯ááᯠáá»áœááºá¯ááºááá¯ááŸá áºáááºáá«áááºá
áááºážááẠEthereum ááœááºáááºááŸáá·áº áá¯ááºáá±á¬ááºáááºá¡ááœáẠááá±á¬ááºáᬠááá¯ážáá»á²á·ááŸá¯áá áºáá¯ááŒá áºáááºá áááºážááœááºá á¡ááá®áá±ážááŸááºážáááá°áá®áá±á¬á¡á áááºá¡ááá¯ááºážáá»á¬ážááẠdnode á á¬ááŒáá·áºááá¯ááºááá¯á¡áá¯á¶ážááŒá¯á RPC ááŸáááá·áºáááºááœááºáááºá áááºáá°ááá¯á·áá±á¬ááºáá±ážá¡ááŒá Ạ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)))
})
ááá¯áá»áœááºá¯ááºááá¯á·ááẠapplication class áá áºáá¯áááºáá®ážáá«áááºá áááºážááẠáá±á«á·ááºá¡ááºááŸáá·áº áááºá á¬áá»ááºááŸá¬á¡ááœáẠ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 á¡áá¬ááá¹áá¯á¡á á¬ážá áá»áœááºá¯ááºááá¯á·ááẠGoogle áááá±á¬ááºáá¬ááŸáá·áº á¡ááŒá¬ážááá±á¬ááºáá¬áá»á¬ážááœáẠChrome ááá¯á¡áá¯á¶ážááŒá¯ááá·áº extensionApi ááᯠá¡áá¯á¶ážááŒá¯áá«áááºá áááºážááᯠááá±á¬ááºáá¬ááŒááºáá»á±á¬áº ááá¯ááºáááºáá®á á±áááºá¡ááœáẠáá¯ááºáá±á¬ááºáá±á¬áºáááºáž á€áá±á¬ááºážáá«ážá áááºááœááºáá»ááºáá»á¬ážá¡ááœáẠ'chrome.runtime.connect' ááᯠááá¯ážááá¯ážááŸááºážááŸááºáž á¡áá¯á¶ážááŒá¯ááá¯ááºáá«áááºá
áá±á¬ááºáᶠscript ááœáẠá¡ááá®áá±ážááŸááºáž á¥ááá¬áá áºáá¯ááᯠáááºáá®ážááŒáá«á áá¯á·á
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 ááẠstream áá»á¬ážááŸáá·áºá¡áá¯ááºáá¯ááºááŒá®áž port áá áºáá¯áááŸááá±á¬ááŒá±á¬áá·áº adapter class áá áºáá¯ááá¯á¡ááºáá«áááºá ááá±á¬ááºáá¬ááŸá nodejs á á®ážááŒá±á¬ááºážáá»á¬ážááᯠá¡áá±á¬ááºá¡áááºáá±á¬áºááá·áº readable-stream library ááᯠá¡áá¯á¶ážááŒá¯á ááŒá¯áá¯ááºáá¬ážáááºá
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);
}
}
áá»áœááºá¯ááºááá¯á·ááẠá¡ááŒá±á¬ááºážá¡áᬠscript ááœááºááá¯ááºáá±á¬áºáááºáž á á¬áá»ááºááŸá¬áá±á«áºááœáẠááá¯ááºááá¯áẠAPI ááᯠááá¯á¡ááºáá±á¬ááŒá±á¬áá·áºá áá»áœááºá¯ááºááá¯á·ááẠá¡áá¬ááŸá áºáá¯ááᯠáá¯ááºáá±á¬ááºáááº-
- áá»áœááºá¯ááºááá¯á·ááẠáááºážááŒá±á¬ááºážááŸá
áºáá¯ááᯠáááºáá®ážáááºá áá
áºáá¯ááŸá¬ - postMessage áááááºááœáẠá
á¬áá»ááºááŸá¬áá®ááá¯á·á áá®á¡ááœáẠáá»áœááºáá±á¬áºááá¯á· áá«ááᯠáá¯á¶ážáá«áááºá
á€á¡áá¯áẠmetamask áááºáá®ážáá°áá»á¬ážáá¶ááŸá áá¯ááá stream ááẠáááºáá¶áááŸááá±á¬ port á áá±á¬ááºáá¶ááá¯á·ááŒá áºáááºá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);
}
}
ááᯠáá»áœááºá¯ááºááá¯á·ááẠinpage ááœáẠapi object áá áºáá¯ááᯠáááºáá®ážááŒá®áž áááºážááᯠglobal á¡ááŒá ẠáááºááŸááºáááº-
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 function ááá¯áá±á«áºááá¯ááá¯ááºáááºá
áá±ááºáá® JS ááœáẠcallback áá¯ááºáá±á¬ááºáá»ááºáá»á¬ážááŒáá·áº áá¯ááºáá±á¬ááºááŒááºážááẠááá±á¬ááºážáá±á¬á¡áá°á¡áá»áá·áºááŒá áºáááºá ááá¯á·ááŒá±á¬áá·áº ááá·áºá¡á¬áž API á¡áá¬ááá¹áá¯áá»á¬ážááá¯á· áá°ážáá°ááœáá·áºááŒá¯ááá·áº dnode áá áºáá¯ááᯠáááºáá®ážááẠá¡áá±á¬ááºá¡áá¶áá±ážáá áºáᯠáá±ážáá¬ážááá¯ááºááŒáá«á áá¯á·á
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 ááŸáá·áº stream áá»ááºážáááºáá¯á¶ááẠá¡áá±á¬áºáá±áž ááá¯ááºáá»á±á¬áá®ááœá±ááŸááá¯á¶ááááº- áá»áœááºá¯ááºááá¯á·ááẠáá±ááœá±ážááœá±á· multiplexing ááᯠá¡áá¯á¶ážááŒá¯ááŒá®áž ááá°áá®áá±á¬ áá¯ááºáááºážáá±á¬ááºáá¬áá»á¬ážá¡ááœáẠááá°áá®áá±á¬ API á¡áá»á¬ážá¡ááŒá¬ážááᯠáááºáá®ážááá¯ááºáá«áááºá áá°á¡áá dnode ááẠáááºááá·áºáá±áá¬ááœááºáááᯠá¡áá¯á¶ážááŒá¯ááá¯ááºááŒá®áž á¡áááá¡áá¬ááŸá¬ áááºáá°ááá¯á·áá±á¬ááºáá±ážááᯠnodejs stream áá¯á¶á á¶ááŒáá·áº áá¯ááºááá¯ážáááºááŒá áºáááºá
á¡ááŒá¬ážááœá±ážáá»ááºá áá¬áá áºáá¯ááŸá¬ JSON RPC 2 áááá¯ááá¯áá±á¬ááᯠá¡áá±á¬ááºá¡áááºáá±á¬áºáá±ážááá·áº JSON áá±á¬áºáááºááŒá áºáááºá ááá¯á·áá±á¬áºá áááºážááẠáá»áœááºá¯ááºááá¯á·áááá á¹á áááºááœáẠááááºááá¯ááºááá·áº áá®ážááŒá¬ážáááºáá°ááá¯á·áá±á¬ááºááŸá¯áá»á¬áž (TCP ááŸáá·áº HTTP(S)) ááŸáá·áº á¡áá¯ááºáá¯ááºáá«áááºá
á¡ááœááºážááá¯ááºážááŒááºáááºááŸáá·áº localStorage
áá»áœááºá¯ááºááá¯á·ááẠá¡ááá®áá±ážááŸááºážá á¡ááœááºážááá¯ááºážá¡ááŒá±á¡áá±ááᯠááááºážáááºážáá¬ážááẠááá¯á¡ááºáá«ááẠ- á¡áááºážáá¯á¶áž áááºááŸááºáá±ážááá¯ážááŒááºážáá±á¬á·áá»á¬ážá áá»áœááºá¯ááºááá¯á·ááẠá¡ááá®áá±ážááŸááºážááœáẠá¡ááŒá±á¡áá±áá áºáá¯ááᯠá¡ááœááºááá° ááá·áºááá¯ááºááŒá®áž áá±á«á·ááºá¡áẠ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 ááẠá ááá¯ážááá¯ááºáááºáááºá¡á¬ážáá¯á¶ážááᯠproxy ááŒáá·áº á¡á á¬ážááá¯ážáá²á·ááŒá®áž áááºážááá¯á·áá¶áá±á«áºááá¯ááŸá¯áá»á¬ážá¡á¬ážáá¯á¶ážááᯠááŒá¬ážááŒááºáá±ážáááºá á€áááºáá±á·áá»áºáá»á¬ážááᯠá á¬áááºážááœááºážááẠááŒá áºááá¯ááºáá«áááºá
á¡á±á¬ááºááœáẠáá»áœááºá¯ááºááẠâááŒá±á¬ááºážáá²áá±á¬á¡áá«â áá°áá±á¬ áá±á«áá¬áááᯠáááŒá¬áá áá¯á¶ážáá±á·ááŸááá±á¬áºáááºáž áááºážááẠáá¯á¶ážáááŸááºáááºááŒááºážáááŸááá±á Mobx ááẠá¡ááœááºáá»á¬ážááá¯á· áááºáá±á¬ááºááœáá·áºááᯠááŒá±áá¬áá¶áááºá áá áºáá»á áºááá¯ááºáááºáá®ážááá·áº ááá±á¬ááºá á®á¡áá¬ááá¹áá¯áá»á¬ážá ááá°ááŒááºážááŸáá·áº áááºááŸááºááŒááºážáá»á¬ážááᯠá¡áá¯á¶ážááŒá¯áááºá
Action Decorators áá»á¬ážááẠáááºááœááºáá»áẠááŸá áºáá¯ááŒáá·áº áá±á¬ááºááœááºáááº-
- 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 á áá¯á¶ážááŒááºáááºááᯠá¡ááá¡áá»áá¬ážáááºááẠá¡áá±ážááŒá®ážáá«áááºá á¡áááºá áá»áœááºá¯ááºááẠá€áá²á·ááá¯á·áá±á¬ áá¯ááºááŒáá·áº ááœá±ážáá»ááºáá±ážáááááá¬ááᯠáá±ážáá²á·áá»áŸááºá() => app.store
á ááá¯á·áá±á¬áẠáá¯á¶á·ááŒááºááŸá¯ááᯠáááºááá·áºá¡áá«áá»áŸ áá±á«áºáááºááá¯ááºáá«á ááá¯ááŸá±á¬ááºááŸá¯ááá¯ááºááá¯ááºá áááŒááºááá¯ááºáá±á¬ááŒá±á¬áá·áº áááºážááááºáááºáá»á¬ážáá¬ááŒá
áºáááºá
áá«áá®ááá¯áá±ážááẠ() => app.store.keys
array á¡á
áááºá¡ááá¯ááºážáá»á¬ážááᯠáá±á«ááºážááá·áºááŒááºáž/áááºááŸá¬ážááá·áºá¡áá«á áááºážááᯠáááºááœáŸááºážáá»ááºááẠááŒá±á¬ááºážáá²áááºááá¯ááºáá±á¬ááŒá±á¬áá·áº áá±á¬ááºáá
áºááŒáááºááœáẠáááºááá·áºá¡áá¬áá»áŸ ááŒá
áºáá¬áááºááá¯ááºáá«á
Mobx ááẠáááá¡ááŒáááºááœá±ážáá»ááºááá·áºáááááá¬á¡ááŒá
Ạáá¯ááºáá±á¬ááºááŒá®áž áá»áœááºá¯ááºááá¯á·áááºáá±á¬ááºááŒáá·áºááŸá¯ááá¯ááºááá·áºá¡áá¬áá»á¬ážááá¯áᬠááŒá±áá¬áá¶áááºá áááºážááᯠproxy getters ááŸáááá·áºáá¯ááºáá±á¬ááºáááºá ááá¯á·ááŒá±á¬áá·áº built-in function ááᯠá€áá±áá¬ááœáẠá¡áá¯á¶ážááŒá¯áá«áááºá toJS
. áááºážááẠáá°áááºážá¡ááœááºáá»á¬ážááŒáá·áº á¡á
á¬ážááá¯ážáá¬ážáá±á¬ ááá±á¬ááºá
á®á¡á¬ážáá¯á¶ážááŒáá·áº á¡áá¬ááá¹áá¯á¡áá
áºáá
áºáá¯ááᯠááŒááºáá±ážáááºá ááœááºáá»ááºá
ááºá¡ááœááºážá áááºážááẠá¡áá¬ááá¹áá¯ááááºáááºá¡á¬ážáá¯á¶ážááᯠáááºááŒááẠ- ááá¯á·ááŒá±á¬áá·áº getters áá»á¬ážááᯠá¡á
áá»áá¯ážáááºá
áá±á«á·ááºá¡ááºááœááºááá¯ážááºááœáẠáá±á¬á·áá»á¬ážá áœá¬ááᯠáááºáá¶ááá·áºááœááºážáá«áááºá á€áá áºááŒáááºááœááºáááºáž áááºážááá¯á·ááẠ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)
}
ááá±á¬ááºáá¬ááœáẠáááºááẠááœá²áá
áºáá¯ááá¯á· á
á¬áááºážááœááºážááá¯ááºááá·áº idle API áá«ááŸáááẠ- á¡ááŒá±á¡áá±ááŒá±á¬ááºážáá²ááŸá¯áá»á¬ážá ááŒááºáááºáááºáž ááŒá
áºááá¯ááºáááºá idle
, active
О locked
. idle á¡ááœáẠáááºááẠá¡áá»áááºáá¯ááºááŒááºážááᯠáááºááŸááºááá¯ááºááŒá®áž 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
array ááœáẠáááºáá±á·áá»áºáá»á¬ážááá·áºááá·áºá¡áá« mobx ááẠáááºážááᯠááá¯ááºááá¯ááºáá¯ááºáá±á¬ááºáááá·áºáááºá ááá¯á·áá±á¬áºá áá»áœááºá¯ááºááá¯á·ááœáẠá¡ááá¯ážá¡áá¬ážáááŸááá±á¬ á¡áá¬ááá¹áá¯á¡áá
áºáá
áºáá¯ááᯠáááºáá®ážáááá·áºáááºá ááá¯á·áá±á¬áº áá±á¬ááºáá
áºááá·áºá¡ááœáẠáá»áœááºá¯ááºááá¯á· ááá¯á¡ááºáá«áááºá
ááá¯á·áá±á¬ááºá áááºáá±á·áá»áºá¡ááŒá±á¡áá± ááŒá±á¬ááºážáá²ááá·áºá¡áá« ááŒá±ááŸááºážáá±ážááá·áº ááááá áºáá¯á¡á¬áž áá»áœááºá¯ááºááá¯á· ááŒááºáá±ážáááºá á¡ááŒá±á¡áá±ááᯠáá¯á¶á·ááŒááºááŸá¯ááŒáá·áº á á±á¬áá·áºááŒáá·áºáááºá áááºážááẠá¡ááŒá±á¡áá±ááᯠááŒá±á¬ááºážáá²áá±á¬á¡áá« âáá°á·ááá¯ááºáá° áááºáááºâ ááŒá áºáááºá
áááºážáááºážáá¯áẠapprove
О reject
á¡ááœááºááá¯ážááŸááºážáá«áááº- ááá¯á¡ááºáá«á áááºááŸááºááá¯ážááŒá®ážáá±á¬áẠáááºáá±á·áá»áºá á¡ááŒá±á¡áá±ááᯠááŒá±á¬ááºážáá²ááá¯ááºáá«á
áá»áœááºá¯ááºááá¯á·ááẠá á¬áá»ááºááŸá¬ API ááœáẠUI APIá newMessage ááᯠá¡áááºááŒá¯ááŒá®áž ááŒááºážáááºáááº-
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 ááŒáá·áº áá±áá¬ááŒá±á¬ááºážáá²áá±á¬á¡áá«ááœáẠrender á
áááºááẠá¡ááœááºááœááºáá°áááºá áá»áœááºá¯ááºááá¯á·ááẠá¡áá¯ááºáá²á០á¡áá²áááºá¡ááŸáááºáá°ááᯠááá¯ážááá¯ážáááºážáááºážááœá²áá¬ážáááºá
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}
);
...
}
áá«ááá¯ááẠApplication á¡áááºááá·áºááŒá áºáá«ááŒá®á áááºá á¬áá»ááºááŸá¬áá»á¬ážááẠááœá±áá±ážááœá±áá°á¡ááœáẠáááºááŸááºáá±á¬ááºážááá¯ááá¯ááºáááº-
áá¯ááºááᯠá€áá±áá¬ááœáẠáááá¯ááºáá«áááºá
áá±á¬ááºáá»ááº
áá±á¬ááºážáá«ážááᯠá¡áá¯á¶ážáááááºááŒá®ážááŒá®ááŒá
áºáá±á¬áºáááºáž áá±ážááœááºážáá»á¬ážááŸááá±áá±ážáá«á áááºážááá¯á·ááᯠáá±ážááŒááºážááá¯ááºáá«áááºá
á¡ááŸááºáááẠextension á¡ááœáẠáá¯ááºááá¯ááŒáá·áºááŸá¯ááẠáááºá
áááºáááºá
á¬ážáá«áá áááºážááᯠáááºááœá±á·ááá¯ááºáááºá
Code, repository ááŸáá·áº job description ááá¯á·ááŸ
source: www.habr.com