αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž˜αž·αž“αžŠαžΌαž…αžŸαŸ’αžαžΆαž”αžαŸ’αž™αž€αž˜αŸ’αž˜ "αž˜αŸ‰αžΆαžŸαŸŠαžΈαž“αž—αŸ’αž‰αŸ€αžœ" αž‘αžΌαž‘αŸ… αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžœαž·αž˜αž‡αŸ’αžˆαž€αžΆαžšαžαŸ’αžšαžΌαžœαž”αžΆαž“αž€αŸ†αžŽαžαŸ‹αž›αž€αŸ’αžαžŽαŸˆαžŠαŸ„αž™αŸ–

  • αž˜αž·αž“αž…αžΆαŸ†αž”αžΆαž…αŸ‹αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αž˜αžΌαž›αžŠαŸ’αž‹αžΆαž“αž‘αž·αž“αŸ’αž“αž“αŸαž™αž‡αžΆαž˜αž½αž™αž“αžΉαž„αž€αžΆαžšαž…αžΌαž›αžšαž”αžŸαŸ‹αž’αŸ’αž“αž€αž”αŸ’αžšαžΎ αž“αž·αž„αž–αžΆαž€αŸ’αž™αžŸαž˜αŸ’αž„αžΆαžαŸ‹αž‘αŸαŸ” αž–αŸαžαŸŒαž˜αžΆαž“αž“αŸƒαž€αžΆαžšαž…αžΌαž›αž”αŸ’αžšαžΎαžαŸ’αžšαžΌαžœαž”αžΆαž“αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αž‘αžΆαŸ†αž„αžŸαŸ’αžšαž»αž„αžŠαŸ„αž™αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž•αŸ’αž‘αžΆαž›αŸ‹ αž αžΎαž™αž€αžΆαžšαž”αž‰αŸ’αž‡αžΆαž€αŸ‹αž’αŸ†αž–αžΈαž—αžΆαž–αžαŸ’αžšαžΉαž˜αžαŸ’αžšαžΌαžœαžšαž”αžŸαŸ‹αž–αž½αž€αž‚αŸαž€αžΎαžαž‘αžΎαž„αž“αŸ…αž€αž˜αŸ’αžšαž·αžαž–αž·αž’αžΈαž€αžΆαžšαŸ”
  • αž˜αž·αž“αž…αžΆαŸ†αž”αžΆαž…αŸ‹αž”αŸ’αžšαžΎαž˜αŸ‰αžΆαžŸαŸŠαžΈαž“αž˜αŸαž‘αŸαŸ” αžαž€αŸ’αž€αžœαž·αž‡αŸ’αž‡αžΆαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž’αžΆαž…αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž“αŸ…αž›αžΎαž”αžŽαŸ’αžαžΆαž‰ blockchain αžŠαŸ‚αž›αž‡αžΆαž€αž“αŸ’αž›αŸ‚αž„αžŠαŸ‚αž›αžœαžΆαž’αžΆαž…αž’αŸ’αžœαžΎαž‘αŸ…αž”αžΆαž“αžŠαžΎαž˜αŸ’αž”αžΈαžšαž€αŸ’αžŸαžΆαž‘αž»αž€αž…αŸ†αž“αž½αž“αž‘αž·αž“αŸ’αž“αž“αŸαž™αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž€αžΆαžšαŸ”

αž˜αžΆαž“αž€αž“αŸ’αž›αŸ‚αž„αž•αŸ’αž‘αž»αž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–αž…αŸ†αž“αž½αž“ 2 αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αžŸαŸ„αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹ - αž€αžΆαž”αžΌαž”αž•αŸ’αž“αŸ‚αž€αžšαžΉαž„ αž“αž·αž„αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αŸ” Hardware wallets αž—αžΆαž‚αž…αŸ’αžšαžΎαž“αž˜αžΆαž“αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–αžαŸ’αž–αžŸαŸ‹ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž–αž·αž”αžΆαž€αž”αŸ’αžšαžΎ αž“αž·αž„αž“αŸ…αž†αŸ’αž„αžΆαž™αž–αžΈαž₯αžαž‚αž·αžαžαŸ’αž›αŸƒ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž‚αžΊαž‡αžΆαž€αžΆαžšαžšαž½αž˜αž”αž‰αŸ’αž…αžΌαž›αž‚αŸ’αž“αžΆαžŠαŸαž›αŸ’αž’αž₯αžαžαŸ’αž…αŸ„αŸ‡αž“αŸƒαžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž– αž“αž·αž„αž—αžΆαž–αž„αžΆαž™αžŸαŸ’αžšαž½αž›αž“αŸƒαž€αžΆαžšαž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹ αž αžΎαž™αž€αŸαž’αžΆαž…αž₯αžαž‚αž·αžαžαŸ’αž›αŸƒαž‘αžΆαŸ†αž„αžŸαŸ’αžšαž»αž„αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž…αž»αž„αž€αŸ’αžšαŸ„αž™αž•αž„αžŠαŸ‚αžšαŸ”

αžŠαŸ„αž™αž‚αž·αžαž–αžΈαž…αŸ†αžŽαž»αž…αž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž“αŸαŸ‡ αž™αžΎαž„αž…αž„αŸ‹αž”αž„αŸ’αž€αžΎαžαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžŠαŸ‚αž›αž˜αžΆαž“αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–αž”αŸ†αž•αž»αž αžŠαŸ‚αž›αž‡αž½αž™αžŸαž˜αŸ’αžšαž½αž›αžŠαž›αŸ‹αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžœαž·αž˜αž‡αŸ’αžˆαž€αžΆαžš αžŠαŸ„αž™αž•αŸ’αžαž›αŸ‹ API αžŸαžΆαž˜αž‰αŸ’αž‰αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžš αž“αž·αž„αž αžαŸ’αžαž›αŸαžαžΆαŸ”
αž™αžΎαž„αž“αžΉαž„αž”αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αž“αž€αž’αŸ†αž–αžΈαž”αž‘αž–αž·αžŸαŸ„αž’αž“αŸαž“αŸαŸ‡αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αŸ”

αž’αžαŸ’αžαž”αž‘αž“αŸαŸ‡αž“αžΉαž„αž˜αžΆαž“αž€αžΆαžšαžŽαŸ‚αž“αžΆαŸ†αž‡αžΆαž‡αŸ†αž αžΆαž“ αŸ— αž’αŸ†αž–αžΈαžšαž”αŸ€αž”αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŠαŸ„αž™αž˜αžΆαž“αž§αž‘αžΆαž αžšαžŽαŸαž€αžΌαžŠ αž“αž·αž„αžšαžΌαž”αžαžαž’αŸαž€αŸ’αžšαž„αŸ‹αŸ” αž’αŸ’αž“αž€αž’αžΆαž…αžŸαŸ’αžœαŸ‚αž„αžšαž€αž›αŸαžαž€αžΌαžŠαž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž“αŸ…αž€αŸ’αž“αž»αž„ αžƒαŸ’αž›αžΆαŸ†αž„. αžšαžΆαž›αŸ‹αž€αžΆαžšαž”αŸ’αžšαž–αŸ’αžšαžΉαžαŸ’αžαžαžΆαž˜αž‘αžΌαž‡αžΈαžαž›αžαŸ’αžšαžΌαžœαž‚αŸ’αž“αžΆαž‘αŸ…αž“αžΉαž„αž•αŸ’αž“αŸ‚αž€αž˜αž½αž™αž“αŸƒαž’αžαŸ’αžαž”αž‘αž“αŸαŸ‡αŸ”

αž”αŸ’αžšαžœαžαŸ’αžαž·αžŸαž„αŸ’αžαŸαž”αž“αŸƒαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€

αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž˜αžΆαž“αžαžΆαŸ†αž„αž–αžΈαž™αžΌαžšαž™αžΆαžšαžŽαžΆαžŸαŸ‹αž˜αž€αž αžΎαž™αŸ” αž–αž½αž€αž‚αŸαž”αžΆαž“αž”αž„αŸ’αž αžΆαž‰αžαŸ’αž›αž½αž“αž“αŸ…αž€αŸ’αž“αž»αž„ Internet Explorer αžαŸ’αžšαž‘αž”αŸ‹αž˜αž€αžœαž·αž‰αž€αŸ’αž“αž»αž„αž†αŸ’αž“αžΆαŸ† 1999 αž“αŸ…αž€αŸ’αž“αž»αž„ Firefox αž€αŸ’αž“αž»αž„αž†αŸ’αž“αžΆαŸ† 2004 αŸ” αž‘αŸ„αŸ‡αž™αŸ‰αžΆαž„αžŽαžΆαž€αŸαžŠαŸ„αž™ αž’αžŸαŸ‹αžšαž™αŸˆαž–αŸαž›αž‡αžΆαž™αžΌαžšαž˜αž€αž αžΎαž™αž˜αž·αž“αž˜αžΆαž“αžŸαŸ’αžαž„αŸ‹αžŠαžΆαžšαžαŸ‚αž˜αž½αž™αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž‘αŸαŸ”

αž™αžΎαž„αž’αžΆαž…αž“αž·αž™αžΆαž™αž”αžΆαž“αžαžΆαžœαžΆαž”αžΆαž“αž”αž„αŸ’αž αžΆαž‰αžαŸ’αž›αž½αž“αž‡αžΆαž˜αž½αž™αž“αžΉαž„αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž“αŸ…αž€αŸ’αž“αž»αž„αž€αŸ†αžŽαŸ‚αž‘αžΈ XNUMX αžšαž”αžŸαŸ‹ Google Chrome αŸ” αž‡αžΆαž€αžΆαžšαž–αž·αžαžŽαžΆαžŸαŸ‹ αž˜αž·αž“αž˜αžΆαž“αž€αžΆαžšαž”αž‰αŸ’αž‡αžΆαž€αŸ‹αžŽαžΆαž˜αž½αž™αž“αŸ„αŸ‡αž‘αŸ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžœαžΆαž‚αžΊαž‡αžΆ Chrome API αžŠαŸ‚αž›αž”αžΆαž“αž€αŸ’αž›αžΆαž™αž‡αžΆαž˜αžΌαž›αžŠαŸ’αž‹αžΆαž“αžšαž”αžŸαŸ‹αžœαžΆαŸ– αžŠαŸ„αž™αž”αžΆαž“αžŠαžŽαŸ’αžαžΎαž˜αž™αž€αž‘αžΈαž•αŸ’αžŸαžΆαžšαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαž—αžΆαž‚αž…αŸ’αžšαžΎαž“ αž“αž·αž„αž˜αžΆαž“αž αžΆαž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžŠαŸ‚αž›αž—αŸ’αž‡αžΆαž”αŸ‹αž˜αž€αž‡αžΆαž˜αž½αž™αž“αŸ„αŸ‡ Chrome αž–αž·αžαž‡αžΆαž€αŸ†αžŽαžαŸ‹αžŸαŸ’αžαž„αŸ‹αžŠαžΆαžšαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αŸ”

Mozilla αž˜αžΆαž“αžŸαŸ’αžαž„αŸ‹αžŠαžΆαžšαž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžŠαŸ„αž™αž˜αžΎαž›αžƒαžΎαž‰αž–αžΈαž—αžΆαž–αž–αŸαž‰αž“αž·αž™αž˜αž“αŸƒαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžšαž”αžŸαŸ‹ Chrome αž€αŸ’αžšαž»αž˜αž αŸŠαž»αž“αž”αžΆαž“αžŸαž˜αŸ’αžšαŸαž…αž…αž·αžαŸ’αžαž”αž„αŸ’αž€αžΎαž API αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž‚αŸ’αž“αžΆαŸ” αž“αŸ…αž€αŸ’αž“αž»αž„αž†αŸ’αž“αžΆαŸ† 2015 αžαžΆαž˜αž‚αŸ†αž“αž·αžαž•αŸ’αžαž½αž…αž•αŸ’αžαžΎαž˜αžšαž”αžŸαŸ‹ Mozilla αž€αŸ’αžšαž»αž˜αž–αž·αžŸαŸαžŸαž˜αž½αž™αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αž„αŸ’αž€αžΎαžαž‘αžΎαž„αž“αŸ…αž€αŸ’αž“αž»αž„ World Wide Web Consortium (W3C) αžŠαžΎαž˜αŸ’αž”αžΈαž’αŸ’αžœαžΎαž€αžΆαžšαž›αžΎαž›αž€αŸ’αžαžŽαŸˆαž”αž…αŸ’αž…αŸαž€αž‘αŸαžŸαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž†αŸ’αž›αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αŸ”

αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜ API αžŠαŸ‚αž›αž˜αžΆαž“αžŸαŸ’αžšαžΆαž”αŸ‹αžŸαž˜αŸ’αžšαžΆαž”αŸ‹ Chrome αžαŸ’αžšαžΌαžœαž”αžΆαž“αž™αž€αž‡αžΆαž˜αžΌαž›αžŠαŸ’αž‹αžΆαž“αŸ” αž€αžΆαžšαž„αžΆαžšαž“αŸαŸ‡αžαŸ’αžšαžΌαžœαž”αžΆαž“αž’αž“αž»αžœαžαŸ’αžαžŠαŸ„αž™αž˜αžΆαž“αž€αžΆαžšαž‚αžΆαŸ†αž‘αŸ’αžšαž–αžΈαž€αŸ’αžšαž»αž˜αž αŸŠαž»αž“ Microsoft (Google αž”αžΆαž“αž”αžŠαž·αžŸαŸαž’αž˜αž·αž“αž…αžΌαž›αžšαž½αž˜αž€αŸ’αž“αž»αž„αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαžŸαŸ’αžαž„αŸ‹αžŠαžΆαžš) αž αžΎαž™αž‡αžΆαž›αž‘αŸ’αž’αž•αž›αžŸαŸαž…αž€αŸ’αžαžΈαž–αŸ’αžšαžΆαž„αž…αŸ’αž”αžΆαž”αŸ‹αž˜αž½αž™αž”αžΆαž“αž›αŸαž…αž…αŸαž‰αž˜αž€αŸ” αž›αž€αŸ’αžαžŽαŸˆαž”αž…αŸ’αž…αŸαž€αž‘αŸαžŸ.

αž‡αžΆαž•αŸ’αž›αžΌαžœαž€αžΆαžš αž€αžΆαžšαž”αž‰αŸ’αž‡αžΆαž€αŸ‹αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αžΆαŸ†αž‘αŸ’αžšαžŠαŸ„αž™ Edge, Firefox αž“αž·αž„ Opera (αž…αŸ†αžŽαžΆαŸ†αžαžΆ Chrome αž˜αž·αž“αž˜αžΆαž“αž“αŸ…αž€αŸ’αž“αž»αž„αž”αž‰αŸ’αž‡αžΈαž“αŸαŸ‡αž‘αŸ)αŸ” αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžαžΆαž˜αž–αž·αž αžŸαŸ’αžŠαž„αŸ‹αžŠαžΆαžšαž‚αžΊαžαŸ’αžšαžΌαžœαž‚αŸ’αž“αžΆαž‡αžΆαž˜αž½αž™ Chrome αž–αŸ’αžšαŸ„αŸ‡αžœαžΆαž–αž·αžαž‡αžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αžŸαžšαžŸαŸαžšαžŠαŸ„αž™αž•αŸ’αž’αŸ‚αž€αž›αžΎαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžšαž”αžŸαŸ‹αžœαžΆαŸ” αž’αŸ’αž“αž€αž’αžΆαž…αž’αžΆαž“αž”αž“αŸ’αžαŸ‚αž˜αž’αŸ†αž–αžΈ WebExtensions API αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αžšαž…αž“αžΆαžŸαž˜αŸ’αž–αŸαž“αŸ’αž’αž”αž“αŸ’αžαŸ‚αž˜

αž―αž€αžŸαžΆαžšαžαŸ‚αž˜αž½αž™αž‚αžαŸ‹αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‘αžΆαž˜αž‘αžΆαžšαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž‚αžΊ manifest (manifest.json) αŸ” αžœαžΆαž€αŸαž‡αžΆ "αž…αŸ†αžŽαž»αž…αž…αžΌαž›" αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž€αžΆαžšαž–αž„αŸ’αžšαžΈαž€αŸ”

αžŸαž˜αŸ’αžŠαŸ‚αž„αŸ”

αž™αŸ„αž„αžαžΆαž˜αž€αžΆαžšαž”αž‰αŸ’αž‡αžΆαž€αŸ‹ αž―αž€αžŸαžΆαžš manifest αž‚αžΊαž‡αžΆαž―αž€αžŸαžΆαžš JSON αžαŸ’αžšαžΉαž˜αžαŸ’αžšαžΌαžœαŸ” αž€αžΆαžšαž–αž·αž–αžŽαŸŒαž“αžΆαž–αŸαž‰αž›αŸαž‰αž“αŸƒαžŸαŸ„ manifest αž‡αžΆαž˜αž½αž™αž“αžΉαž„αž–αŸαžαŸŒαž˜αžΆαž“αž’αŸ†αž–αžΈαžŸαŸ„αžŽαžΆαž˜αž½αž™αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αžΆαŸ†αž‘αŸ’αžš αžŠαŸ‚αž›αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž’αžΆαž…αž˜αžΎαž›αž”αžΆαž“αŸ” αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αž‚αŸ’αžšαžΆαž”αŸ‹αž…αž»αž…αžŠαŸ‚αž›αž˜αž·αž“αž˜αžΆαž“αž“αŸ…αž€αŸ’αž“αž»αž„αž€αžΆαžšαž”αž‰αŸ’αž‡αžΆαž€αŸ‹ "αž’αžΆαž…" αž˜αž·αž“αž’αžΎαž–αžΎ (αž‘αžΆαŸ†αž„αž€αŸ†αž αž»αžŸαžšαž”αžΆαž™αž€αžΆαžšαžŽαŸαžšαž”αžŸαŸ‹ Chrome αž“αž·αž„ Firefox αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž“αŸ…αžαŸ‚αž”αž“αŸ’αžαžŠαŸ†αžŽαžΎαžšαž€αžΆαžš)αŸ”

αž αžΎαž™αžαŸ’αž‰αž»αŸ†αž…αž„αŸ‹αž‚αžΌαžšαž™αž€αž…αž·αžαŸ’αžαž‘αž»αž€αžŠαžΆαž€αŸ‹αž›αžΎαž…αŸ†αžŽαž»αž…αž˜αž½αž™αž…αŸ†αž“αž½αž“αŸ”

  1. αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ - αžœαžαŸ’αžαž»αžŠαŸ‚αž›αžšαž½αž˜αž”αž‰αŸ’αž…αžΌαž›αžœαžΆαž›αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αŸ–
    1. αžŸαŸ’αž‚αŸ’αžšαžΈαž” - αž’αžΆαžšαŸαž“αŸƒαžŸαŸ’αž‚αŸ’αžšαžΈαž”αžŠαŸ‚αž›αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αŸ’αž“αž»αž„αž”αžšαž·αž”αž‘αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ (αž™αžΎαž„αž“αžΉαž„αž“αž·αž™αžΆαž™αž’αŸ†αž–αžΈαžšαžΏαž„αž“αŸαŸ‡αž”αž“αŸ’αžαž·αž…αž€αŸ’αžšαŸ„αž™αž˜αž€);
    2. αž‘αŸ†αž–αŸαžš - αž‡αŸ†αž“αž½αžŸαž±αŸ’αž™αžŸαŸ’αž‚αŸ’αžšαžΈαž”αžŠαŸ‚αž›αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αŸ’αž“αž»αž„αž‘αŸ†αž–αŸαžšαž‘αž‘αŸ αž’αŸ’αž“αž€αž’αžΆαž…αž”αž‰αŸ’αž‡αžΆαž€αŸ‹ html αž‡αžΆαž˜αž½αž™αž˜αžΆαžαž·αž€αžΆαŸ” αž€αŸ’αž“αž»αž„αž€αžšαžŽαžΈαž“αŸαŸ‡ αžœαžΆαž›αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž˜αž·αž“αž’αžΎαž–αžΎ αž αžΎαž™αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αž‰αŸ’αž…αžΌαž›αž‘αŸ…αž€αŸ’αž“αž»αž„αž‘αŸ†αž–αŸαžšαžαŸ’αž›αžΉαž˜αžŸαžΆαžšαŸ”
    3. តស៊ូ β€” αž‘αž„αŸ‹αž‚αŸ„αž›αž–αžΈαžš αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž˜αž·αž“αž”αžΆαž“αž”αž‰αŸ’αž‡αžΆαž€αŸ‹ αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž“αžΉαž„ "αžŸαž˜αŸ’αž›αžΆαž”αŸ‹" αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αžœαžΆαž–αž·αž…αžΆαžšαžŽαžΆαžαžΆαžœαžΆαž˜αž·αž“αž€αŸ†αž–αž»αž„αž’αŸ’αžœαžΎαž’αŸ’αžœαžΈ αž αžΎαž™αž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜αžœαžΆαž‘αžΎαž„αžœαž·αž‰αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž…αžΆαŸ†αž”αžΆαž…αŸ‹αŸ” αž”αžΎαž˜αž·αž“αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž‘αŸ αž‘αŸ†αž–αŸαžšαž“αžΉαž„αž›αŸ‚αž„αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αž·αž‘αŸ” αž˜αž·αž“αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αžΆαŸ†αž‘αŸ’αžšαž“αŸ…αž€αŸ’αž“αž»αž„ Firefox αž‘αŸαŸ”
  2. content_scripts - αž’αžΆαžšαŸαž“αŸƒαžœαžαŸ’αžαž»αžŠαŸ‚αž›αž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž’αŸ’αž“αž€αž•αŸ’αž‘αž»αž€αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž•αŸ’αžŸαŸαž„αŸ—αž‚αŸ’αž“αžΆαž‘αŸ…αž€αžΆαž“αŸ‹αž‚αŸαž αž‘αŸ†αž–αŸαžšαž•αŸ’αžŸαŸαž„αŸ—αž‚αŸ’αž“αžΆαŸ” αžœαžαŸ’αžαž»αž“αžΈαž˜αž½αž™αŸ—αž˜αžΆαž“αžœαžΆαž›αžŸαŸ†αžαžΆαž“αŸ‹αŸ—αžŠαžΌαž…αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αŸˆ
    1. αž€αžΆαžšαž”αŸ’αžšαž€αž½αž - αž‚αŸ†αžšαžΌ urlαžŠαŸ‚αž›αž€αŸ†αžŽαžαŸ‹αžαžΆαžαžΎαžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆαž‡αžΆαž€αŸ‹αž›αžΆαž€αŸ‹αž˜αž½αž™αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αžšαž½αž˜αž”αž‰αŸ’αž…αžΌαž›αž¬αž’αžαŸ‹αŸ”
    2. js - αž”αž‰αŸ’αž‡αžΈαž“αŸƒαžŸαŸ’αž‚αŸ’αžšαžΈαž”αžŠαŸ‚αž›αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž•αŸ’αž‘αž»αž€αž‘αŸ…αž€αŸ’αž“αž»αž„αž€αžΆαžšαž”αŸ’αžšαž€αž½αžαž“αŸαŸ‡;
    3. exclude_match - αž˜αž·αž“αžšαžΆαž”αŸ‹αž”αž‰αŸ’αž…αžΌαž›αž–αžΈαžœαžΆαž› match URL αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž“αžΉαž„αžœαžΆαž›αž“αŸαŸ‡αŸ”
  3. page_action - αžαžΆαž˜αž–αž·αžαž‚αžΊαž‡αžΆαžœαžαŸ’αžαž»αžŠαŸ‚αž›αž‘αž‘αž½αž›αžαž»αžŸαžαŸ’αžšαžΌαžœαž…αŸ†αž–αŸ„αŸ‡αžšαžΌαž”αžαŸ†αžŽαžΆαž„αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αž„αŸ’αž αžΆαž‰αž“αŸ…αž‡αžΆαž”αŸ‹αž“αžΉαž„αžšαž”αžΆαžšαž’αžΆαžŸαž™αžŠαŸ’αž‹αžΆαž“αž“αŸ…αž€αŸ’αž“αž»αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž“αž·αž„αž’αž“αŸ’αžαžšαž€αž˜αŸ’αž˜αž‡αžΆαž˜αž½αž™αžœαžΆαŸ” αžœαžΆαž€αŸαž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž’αŸ’αž“αž€αž”αž„αŸ’αž αžΆαž‰αž”αž„αŸ’αž’αž½αž…αž›αŸαž…αž‘αžΎαž„αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž€αŸ†αžŽαžαŸ‹αžŠαŸ„αž™αž”αŸ’αžšαžΎ HTML, CSS αž“αž·αž„ JS αž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“αžšαž”αžŸαŸ‹αž’αŸ’αž“αž€αŸ”
    1. default_popup - αž•αŸ’αž›αžΌαžœαž‘αŸ…αž€αžΆαž“αŸ‹αž―αž€αžŸαžΆαžš HTML αžŠαŸ‚αž›αž˜αžΆαž“αž…αŸ†αžŽαž»αž…αž”αŸ’αžšαž‘αžΆαž€αŸ‹αž›αŸαž…αž‘αžΎαž„ αž’αžΆαž…αž˜αžΆαž“ CSS αž“αž·αž„ JS αŸ”
  4. αžŸαž·αž‘αŸ’αž’αž· - αž’αžΆαžšαŸαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‚αŸ’αžšαž”αŸ‹αž‚αŸ’αžšαž„αžŸαž·αž‘αŸ’αž’αž·αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ” αžŸαž·αž‘αŸ’αž’αž·αž˜αžΆαž“ ៣ αž”αŸ’αžšαž—αŸαž‘ αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž–αž·αž–αžŽαŸŒαž“αžΆαž™αŸ‰αžΆαž„αž›αž˜αŸ’αž’αž·αž αž“αŸ…αž‘αžΈαž“αŸαŸ‡
  5. web_accessible_resources - αž’αž“αž’αžΆαž“αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžŠαŸ‚αž›αž‚αŸαž αž‘αŸ†αž–αŸαžšαž’αžΆαž…αžŸαŸ’αž“αžΎαžŸαž»αŸ† αž§αž‘αžΆαž αžšαžŽαŸ αžšαžΌαž”αž—αžΆαž– JS, CSS, αž―αž€αžŸαžΆαžš HTML αŸ”
  6. externally_connectable β€” αž“αŸ…αž‘αžΈαž“αŸαŸ‡αž’αŸ’αž“αž€αž’αžΆαž…αž”αž‰αŸ’αž‡αžΆαž€αŸ‹αž™αŸ‰αžΆαž„αž…αŸ’αž”αžΆαžŸαŸ‹αž“αžΌαžœαž›αŸαžαžŸαž˜αŸ’αž‚αžΆαž›αŸ‹αž“αŸƒαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜ αž“αž·αž„αžŠαŸ‚αž“αž“αŸƒαž‚αŸαž αž‘αŸ†αž–αŸαžšαžŠαŸ‚αž›αž’αŸ’αž“αž€αž’αžΆαž…αž—αŸ’αž‡αžΆαž”αŸ‹αž”αžΆαž“αŸ” αžŠαŸ‚αž“αž’αžΆαž…αž‡αžΆαž€αž˜αŸ’αžšαž·αžαž‘αžΈαž–αžΈαžš αž¬αžαŸ’αž–αžŸαŸ‹αž‡αžΆαž„αž“αŸαŸ‡αŸ” αž˜αž·αž“αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž“αŸ…αž€αŸ’αž“αž»αž„ Firefox αž‘αŸαŸ”

αž”αžšαž·αž”αž‘αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·

αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž˜αžΆαž“αž”αžšαž·αž”αž‘αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΌαžŠαž…αŸ†αž“αž½αž“αž”αžΈ αž–αŸ„αž›αž‚αžΊαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž˜αžΆαž“αž”αžΈαž•αŸ’αž“αŸ‚αž€αžŠαŸ‚αž›αž˜αžΆαž“αž€αž˜αŸ’αžšαž·αžαž•αŸ’αžŸαŸαž„αž‚αŸ’αž“αžΆαž“αŸƒαž€αžΆαžšαž…αžΌαž›αž”αŸ’αžšαžΎ API αžšαž”αžŸαŸ‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαŸ”

αž”αžšαž·αž”αž‘αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜

API αž—αžΆαž‚αž…αŸ’αžšαžΎαž“αž˜αžΆαž“αž“αŸ…αž‘αžΈαž“αŸαŸ‡αŸ” αž“αŸ…αž€αŸ’αž“αž»αž„αž”αžšαž·αž”αž‘αž“αŸαŸ‡αž–αž½αž€αž‚αŸ "αžšαžŸαŸ‹αž“αŸ…":

  1. αž‘αŸ†αž–αŸαžšαž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ - αž•αŸ’αž“αŸ‚αž€ "αžαžΆαž„αž€αŸ’αžšαŸ„αž™" αž“αŸƒαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ” αž―αž€αžŸαžΆαžšαžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αž‰αŸ’αž‡αžΆαž€αŸ‹αž“αŸ…αž€αŸ’αž“αž»αž„ manifest αžŠαŸ„αž™αž”αŸ’αžšαžΎαž‚αŸ’αžšαžΆαž”αŸ‹αž…αž»αž… "αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™" αŸ”
  2. αž‘αŸ†αž–αŸαžšαž›αŸαž…αž‘αžΎαž„ β€” αž‘αŸ†αž–αŸαžšαž›αŸαž…αž‘αžΎαž„αžŠαŸ‚αž›αž›αŸαž…αž‘αžΎαž„αž“αŸ…αž–αŸαž›αž’αŸ’αž“αž€αž…αž»αž…αž›αžΎαžšαžΌαž”αžαŸ†αžŽαžΆαž„αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ” αž“αŸ…αž€αŸ’αž“αž»αž„αžŸαŸαž…αž€αŸ’αžαžΈαžαŸ’αž›αŸ‚αž„αž€αžΆαžšαžŽαŸ browser_action -> default_popup.
  3. αž‘αŸ†αž–αŸαžšαž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“ - αž‘αŸ†αž–αŸαžšαž”αž“αŸ’αžαŸ‚αž˜ "αž€αžΆαžšαžšαžŸαŸ‹αž“αŸ…" αž“αŸ…αž€αŸ’αž“αž»αž„αž•αŸ’αž‘αžΆαŸ†αž„αžŠαžΆαž…αŸ‹αžŠαŸ„αž™αž‘αŸ‚αž€αž“αŸƒαž‘αž·αžŠαŸ’αž‹αž—αžΆαž– chrome-extension://<id_Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ>/customPage.html.

αž”αžšαž·αž”αž‘αž“αŸαŸ‡αž˜αžΆαž“αžŠαŸ„αž™αž―αž€αžšαžΆαž‡αŸ’αž™αž–αžΈαž”αž„αŸ’αž’αž½αž… αž“αž·αž„αž•αŸ’αž‘αžΆαŸ†αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαŸ” αž‘αŸ†αž–αŸαžšαž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ αž˜αžΆαž“αž“αŸ…αž€αŸ’αž“αž»αž„αž…αŸ’αž”αžΆαž”αŸ‹αž…αž˜αŸ’αž›αž„αžαŸ‚αž˜αž½αž™ αž αžΎαž™αžαŸ‚αž„αžαŸ‚αžŠαŸ†αžŽαžΎαžšαž€αžΆαžš (αž€αžšαžŽαžΈαž›αžΎαž€αž›αŸ‚αž„αž‚αžΊαž‘αŸ†αž–αŸαžšαž–αŸ’αžšαžΉαžαŸ’αžαž·αž€αžΆαžšαžŽαŸ αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αžΎαž€αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαžŠαŸ„αž™αž–αŸ’αžšαžΉαžαŸ’αžαž·αž€αžΆαžšαžŽαŸαž˜αž½αž™ αž αžΎαž™ "αžŸαŸ’αž›αžΆαž”αŸ‹" αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž–αžΈαžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαžšαž”αžŸαŸ‹αžœαžΆ)αŸ” αž‘αŸ†αž–αŸαžšαž›αŸαž…αž‘αžΎαž„ αž˜αžΆαž“αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αž”αž„αŸ’αž’αž½αž…αž›αŸαž…αž‘αžΎαž„αž”αžΎαž€ αž“αž·αž„ αž‘αŸ†αž–αŸαžšαž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“ - αžαžŽαŸˆαž–αŸαž›αžŠαŸ‚αž›αž•αŸ’αž‘αžΆαŸ†αž„αž‡αžΆαž˜αž½αž™αžœαžΆαž”αžΎαž€αŸ” αž˜αž·αž“αž˜αžΆαž“αž€αžΆαžšαž…αžΌαž›αž‘αŸ…αž€αžΆαž“αŸ‹αž•αŸ’αž‘αžΆαŸ†αž„αž•αŸ’αžŸαŸαž„αž‘αŸ€αž αž“αž·αž„αž˜αžΆαžαž·αž€αžΆαžšαž”αžŸαŸ‹αž–αž½αž€αž‚αŸαž–αžΈαž”αžšαž·αž”αž‘αž“αŸαŸ‡αž‘αŸαŸ”

αž”αžšαž·αž”αž‘αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆ

αž―αž€αžŸαžΆαžšαžŸαŸ’αž‚αŸ’αžšαžΈαž”αžαŸ’αž›αžΉαž˜αžŸαžΆαžšαžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αžΎαž€αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž•αŸ’αž‘αžΆαŸ†αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαž“αžΈαž˜αž½αž™αŸ—αŸ” αžœαžΆαž˜αžΆαž“αžŸαž·αž‘αŸ’αž’αž·αž…αžΌαž›αž”αŸ’αžšαžΎαž•αŸ’αž“αŸ‚αž€αž“αŸƒ API αžšαž”αžŸαŸ‹αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜ αž“αž·αž„αž‘αŸ…αž€αžΆαž“αŸ‹αž˜αŸ‚αž€αž’αžΆαž„ DOM αž“αŸƒαž‚αŸαž αž‘αŸ†αž–αŸαžšαŸ” αžœαžΆαž‚αžΊαž‡αžΆαžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆαžŠαŸ‚αž›αž‘αž‘αž½αž›αžαž»αžŸαžαŸ’αžšαžΌαžœαž…αŸ†αž–αŸ„αŸ‡αž’αž“αŸ’αžαžšαž€αž˜αŸ’αž˜αž‡αžΆαž˜αž½αž™αž‘αŸ†αž–αŸαžšαŸ” αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžŠαŸ‚αž›αžšαŸ€αž”αž…αŸ†αž˜αŸ‚αž€αž’αžΆαž„ DOM αž’αŸ’αžœαžΎαžŠαžΌαž…αž“αŸαŸ‡αž“αŸ…αž€αŸ’αž“αž»αž„αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆ - αž§αž‘αžΆαž αžšαžŽαŸ αž’αŸ’αž“αž€αž‘αž”αŸ‹αžŸαŸ’αž€αžΆαžαŸ‹αž€αžΆαžšαž•αŸ’αžŸαžΆαž™αž–αžΆαžŽαž·αž‡αŸ’αž‡αž€αž˜αŸ’αž˜ αž¬αž’αŸ’αž“αž€αž”αž€αž”αŸ’αžšαŸ‚αŸ” αž•αž„αžŠαŸ‚αžš αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆαž’αžΆαž…αž‘αžΆαž€αŸ‹αž‘αž„αž‡αžΆαž˜αž½αž™αž‘αŸ†αž–αŸαžšαžαžΆαž˜αžšαž™αŸˆαžŸαŸ’αžαž„αŸ‹αžŠαžΆαžš postMessage.

αž”αžšαž·αž”αž‘αž“αŸƒαž‚αŸαž αž‘αŸ†αž–αŸαžš

αž“αŸαŸ‡αž‚αžΊαž‡αžΆαž‚αŸαž αž‘αŸ†αž–αŸαžšαž–αž·αžαŸ” αžœαžΆαž˜αž·αž“αž˜αžΆαž“αž’αŸ’αžœαžΈαžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž’αŸ’αžœαžΎαž‡αžΆαž˜αž½αž™αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜ αž“αž·αž„αž˜αž·αž“αž˜αžΆαž“αžŸαž·αž‘αŸ’αž’αž·αž…αžΌαž›αž”αŸ’αžšαžΎαž“αŸ…αž‘αžΈαž“αŸ„αŸ‡αž‘αŸ αž›αžΎαž€αž›αŸ‚αž„αžαŸ‚αž€αžšαžŽαžΈαžŠαŸ‚αž›αžŠαŸ‚αž“αž“αŸƒαž‘αŸ†αž–αŸαžšαž“αŸαŸ‡αž˜αž·αž“αžαŸ’αžšαžΌαžœαž”αžΆαž“αž…αž„αŸ’αž’αž»αž›αž”αž„αŸ’αž αžΆαž‰αž™αŸ‰αžΆαž„αž…αŸ’αž”αžΆαžŸαŸ‹αž“αŸ…αž€αŸ’αž“αž»αž„αž€αžΆαžšαž”αž„αŸ’αž αžΆαž‰ (αž”αž“αŸ’αžαŸ‚αž˜αž›αžΎαžœαžΆαžαžΆαž„αž€αŸ’αžšαŸ„αž˜)αŸ”

αž€αžΆαžšαž•αŸ’αž‰αžΎαžŸαžΆαžš

αž•αŸ’αž“αŸ‚αž€αž•αŸ’αžŸαŸαž„αŸ—αž“αŸƒαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžαŸ’αžšαžΌαžœαžαŸ‚αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαžΆαžšαž‚αŸ’αž“αžΆαž‘αŸ…αžœαž·αž‰αž‘αŸ…αž˜αž€αŸ” αž˜αžΆαž“ API αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αžšαžΏαž„αž“αŸαŸ‡ runtime.sendMessage αžŠαžΎαž˜αŸ’αž”αžΈαž•αŸ’αž‰αžΎαžŸαžΆαžš background ΠΈ tabs.sendMessage αžŠαžΎαž˜αŸ’αž”αžΈαž•αŸ’αž‰αžΎαžŸαžΆαžšαž‘αŸ…αž€αžΆαž“αŸ‹αž‘αŸ†αž–αŸαžšαž˜αž½αž™ (αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆ αž›αŸαž…αž‘αžΎαž„ αž¬αž‘αŸ†αž–αŸαžšαž”αžŽαŸ’αžαžΆαž‰αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž˜αžΆαž“ externally_connectable) αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αž“αŸαŸ‡αž‡αžΆαž§αž‘αžΆαž αžšαžŽαŸαž“αŸ…αž–αŸαž›αž…αžΌαž›αž”αŸ’αžšαžΎ Chrome API αŸ”

// Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠ΅ΠΌ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ любой JSON сСриализуСмый ΠΎΠ±ΡŠΠ΅ΠΊΡ‚
const msg = {a: 'foo', b: 'bar'};

// extensionId ΠΌΠΎΠΆΠ½ΠΎ Π½Π΅ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ, Ссли ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΏΠΎΡΠ»Π°Ρ‚ΡŒ сообщСниС 'своСму' Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡŽ (ΠΈΠ· ui ΠΈΠ»ΠΈ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚ скрипта)
chrome.runtime.sendMessage(extensionId, msg);

// Π’Π°ΠΊ выглядит ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ
chrome.runtime.onMessage.addListener((msg) => console.log(msg))

// МоТно ΡΠ»Π°Ρ‚ΡŒ сообщСния Π²ΠΊΠ»Π°Π΄ΠΊΠ°ΠΌ зная ΠΈΡ… id
chrome.tabs.sendMessage(tabId, msg)

// ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΊ Π²ΠΊΠ»Π°Π΄ΠΊΠ°ΠΌ ΠΈ ΠΈΡ… id ΠΌΠΎΠΆΠ½ΠΎ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, Π²ΠΎΡ‚ Ρ‚Π°ΠΊ
chrome.tabs.query(
    {currentWindow: true, active : true},
    function(tabArray){
      tabArray.forEach(tab => console.log(tab.id))
    }
)

αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž€αžΆαžšαž‘αŸ†αž“αžΆαž€αŸ‹αž‘αŸ†αž“αž„αž–αŸαž‰αž›αŸαž‰ αž’αŸ’αž“αž€αž’αžΆαž…αž”αž„αŸ’αž€αžΎαžαž€αžΆαžšαžαž—αŸ’αž‡αžΆαž”αŸ‹αžαžΆαž˜αžšαž™αŸˆ runtime.connect. αž‡αžΆαž€αžΆαžšαž†αŸ’αž›αžΎαž™αžαž”αž™αžΎαž„αž“αžΉαž„αž‘αž‘αž½αž›αž”αžΆαž“ runtime.PortαžŠαŸ‚αž› αžαžŽαŸˆαž–αŸαž›αžŠαŸ‚αž›αžœαžΆαž”αžΎαž€ αž’αŸ’αž“αž€αž’αžΆαž…αž•αŸ’αž‰αžΎαžŸαžΆαžšαž…αŸ†αž“αž½αž“αžŽαžΆαž˜αž½αž™αŸ” αž‡αžΆαž§αž‘αžΆαž αžšαžŽαŸ αžαžΆαž„αž’αžαž·αžαž·αž‡αž“ contentscript, αžœαžΆαž˜αžΎαž›αž‘αŸ…αžŠαžΌαž…αž“αŸαŸ‡:

// ΠžΠΏΡΡ‚ΡŒ ΠΆΠ΅ extensionId ΠΌΠΎΠΆΠ½ΠΎ Π½Π΅ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΏΡ€ΠΈ ΠΊΠΎΠΌΠΌΡƒΠ½ΠΈΠΊΠ°Ρ†ΠΈΠΈ Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ. ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Ρ‚ΡŒ
const port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
    if (msg.question === "Who's there?")
        port.postMessage({answer: "Madame"});
    else if (msg.question === "Madame who?")
        port.postMessage({answer: "Madame... Bovary"});

αž˜αŸ‰αžΆαžŸαŸŠαžΈαž“αž˜αŸ αž¬αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αŸ–

// ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ 'своих' Π²ΠΊΠ»Π°Π΄ΠΎΠΊ. ΠšΠΎΠ½Ρ‚Π΅Π½Ρ‚ скриптов, popup ΠΈΠ»ΠΈ страниц Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ
chrome.runtime.onConnect.addListener(function(port) {
    console.assert(port.name === "knockknock");
    port.onMessage.addListener(function(msg) {
        if (msg.joke === "Knock knock")
            port.postMessage({question: "Who's there?"});
        else if (msg.answer === "Madame")
            port.postMessage({question: "Madame who?"});
        else if (msg.answer === "Madame... Bovary")
            port.postMessage({question: "I don't get it."});
    });
});

// ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ для ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ Π²Π½Π΅ΡˆΠ½ΠΈΡ… Π²ΠΊΠ»Π°Π΄ΠΎΠΊ. Π”Ρ€ΡƒΠ³ΠΈΡ… Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠΉ ΠΈΠ»ΠΈ Π²Π΅Π± страниц, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ доступ Π² манифСстС
chrome.runtime.onConnectExternal.addListener(function(port) {
    ...
});

αž€αŸαž˜αžΆαž“αž–αŸ’αžšαžΉαžαŸ’αžαž·αž€αžΆαžšαžŽαŸαž˜αž½αž™αŸ” onDisconnect αž“αž·αž„αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αž disconnect.

αžŠαŸ’αž™αžΆαž€αŸ’αžšαžΆαž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈ

αž…αžΌαžšαž”αž„αŸ’αž€αžΎαžαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž“αŸƒαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαžŠαŸ‚αž›αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ„αž―αž€αž‡αž“ αž•αŸ’αžαž›αŸ‹αžŸαž·αž‘αŸ’αž’αž·αž…αžΌαž›αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž–αŸαžαŸŒαž˜αžΆαž“αžŸαžΆαž’αžΆαžšαžŽαŸˆ (αž’αžΆαžŸαž™αžŠαŸ’αž‹αžΆαž“ αžŸαŸ„αžŸαžΆαž’αžΆαžšαžŽαŸˆαž‘αŸ†αž“αžΆαž€αŸ‹αž‘αŸ†αž“αž„αž‡αžΆαž˜αž½αž™αž‘αŸ†αž–αŸαžš αž“αž·αž„αž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž—αžΆαž‚αžΈαž‘αžΈαž”αžΈαžŸαŸ’αž“αžΎαžŸαž»αŸ†αž αžαŸ’αžαž›αŸαžαžΆαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžšαŸ”

αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈ

αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž”αžŸαŸ‹αž™αžΎαž„αžαŸ’αžšαžΌαžœαžαŸ‚αž˜αžΆαž“αž’αž“αŸ’αžαžšαž€αž˜αŸ’αž˜αž‡αžΆαž˜αž½αž™αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹ αž“αž·αž„αž•αŸ’αžαž›αŸ‹αž‘αŸ†αž–αŸαžšαž‡αžΆαž˜αž½αž™ API αžŠαžΎαž˜αŸ’αž”αžΈαž αŸ…αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžαŸ’αžš (αž§αž‘αžΆαž αžšαžŽαŸ αžŠαžΎαž˜αŸ’αž”αžΈαž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆαž›αžΎαž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžš)αŸ” αž’αŸ’αžœαžΎαžŠαŸ„αž™αž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αž˜αž½αž™αŸ” contentscript αž“αžΉαž„αž˜αž·αž“αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž‘αŸ αž–αŸ’αžšαŸ„αŸ‡αžœαžΆαž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αž…αžΌαž›αž‘αŸ…αž€αžΆαž“αŸ‹ DOM αž”αŸ‰αž»αžŽαŸ’αžŽαŸ„αŸ‡ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž˜αž·αž“αž˜αŸ‚αž“αž‘αŸ…αž€αžΆαž“αŸ‹ JS αž“αŸƒαž‘αŸ†αž–αŸαžšαž“αŸ„αŸ‡αž‘αŸαŸ” αž—αŸ’αž‡αžΆαž”αŸ‹αžαžΆαž˜αžšαž™αŸˆ runtime.connect αž™αžΎαž„αž˜αž·αž“αž’αžΆαž…αž‘αŸ αž–αŸ’αžšαŸ„αŸ‡ API αžαŸ’αžšαžΌαžœαž€αžΆαžšαž“αŸ…αž›αžΎαžŠαŸ‚αž“αž‘αžΆαŸ†αž„αž’αžŸαŸ‹ αž αžΎαž™αž˜αžΆαž“αžαŸ‚αž…αŸ†αžŽαž»αž…αž‡αžΆαž€αŸ‹αž›αžΆαž€αŸ‹αž˜αž½αž™αž”αŸ‰αž»αžŽαŸ’αžŽαŸ„αŸ‡αžŠαŸ‚αž›αž’αžΆαž…αž”αž‰αŸ’αž‡αžΆαž€αŸ‹αž”αžΆαž“αž“αŸ…αž€αŸ’αž“αž»αž„ manifest αŸ” αž‡αžΆαž›αž‘αŸ’αž’αž•αž›αžŠαŸ’αž™αžΆαž€αŸ’αžšαžΆαž˜αž“αžΉαž„αž˜αžΎαž›αž‘αŸ…αžŠαžΌαž…αž“αŸαŸ‡αŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αžœαžΆαž“αžΉαž„αž˜αžΆαž“αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αž½αž™αž‘αŸ€αž - inpageαžŠαŸ‚αž›αž™αžΎαž„αž“αžΉαž„αž…αžΆαž€αŸ‹αž…αžΌαž›αž‘αŸ…αž€αŸ’αž“αž»αž„αž‘αŸ†αž–αŸαžšαŸ” αžœαžΆαž“αžΉαž„αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž€αŸ’αž“αž»αž„αž”αžšαž·αž”αž‘αžšαž”αžŸαŸ‹αžœαžΆ αž αžΎαž™αž•αŸ’αžαž›αŸ‹ API αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ”

Начало

αž€αžΌαžŠαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž’αžΆαž…αžšαž€αž”αžΆαž“αž“αŸ… GitHub. αž€αŸ’αž“αž»αž„αž’αŸ†αž‘αž»αž„αž–αŸαž›αž–αž·αž–αžŽαŸŒαž“αžΆαž“αžΉαž„αž˜αžΆαž“αžαŸ†αžŽαž—αŸ’αž‡αžΆαž”αŸ‹αž‘αŸ…αž€αžΆαžšαž”αŸ’αžαŸαž‡αŸ’αž‰αžΆαž…αž·αžαŸ’αžαŸ”

αžαŸ„αŸ‡αž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜αž‡αžΆαž˜αž½αž™ 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 αžšαž½αž…αž αžΎαž™ αž αžΎαž™αžαŸ’αžšαžΌαžœαž”αŸ’αžšαžΆαž€αžŠαžαžΆαžœαžΆαžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαŸ”

αžŠαžΎαž˜αŸ’αž”αžΈαž•αŸ’αž‘αŸ€αž„αž•αŸ’αž‘αžΆαžαŸ‹αž“αŸαŸ‡ αž’αŸ’αž“αž€αž’αžΆαž…αž™αž€αž›αŸαžαž€αžΌαžŠ αž–αžΈαž‘αžΈαž“αŸαŸ‡. αž”αž“αŸ’αžαŸ‚αž˜αž–αžΈαž›αžΎαž’αŸ’αžœαžΈαžŠαŸ‚αž›αž™αžΎαž„αž”αžΆαž“αž’αŸ’αžœαžΎ αžαŸ†αžŽαž—αŸ’αž‡αžΆαž”αŸ‹αž”αžΆαž“αž€αŸ†αžŽαžαŸ‹αžšαž…αž“αžΆαžŸαž˜αŸ’αž–αŸαž“αŸ’αž’αž€αžΆαžšαž‡αž½αž”αž”αŸ’αžšαž‡αž»αŸ†αž‚αŸ’αž“αžΆαž“αŸƒαž‚αž˜αŸ’αžšαŸ„αž„αžŠαŸ„αž™αž”αŸ’αžšαžΎαž€αž‰αŸ’αž…αž”αŸ‹αž”αžŽαŸ’αžŠαžΆαž‰αŸ” αžŠαžΎαž˜αŸ’αž”αžΈαž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‘αŸ…αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αž αž“αŸ…αž€αŸ’αž“αž»αž„ chrome://extensions αž’αŸ’αž“αž€αžαŸ’αžšαžΌαžœαž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸ load unpacked αž“αž·αž„ folder αž‡αžΆαž˜αž½αž™αž“αžΉαž„ extension αžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž‚αŸ’αž“αžΆ - in our case dist.

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž₯αž‘αžΌαžœαž“αŸαŸ‡αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžšαž”αžŸαŸ‹αž™αžΎαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αžŠαŸ†αž‘αžΎαž„ αž“αž·αž„αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαŸ” αž’αŸ’αž“αž€αž’αžΆαž…αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž§αž”αž€αžšαžŽαŸαž’αŸ’αž“αž€αž’αž—αž·αžœαžŒαŸ’αžαž“αŸαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž”αžšαž·αž”αž‘αž•αŸ’αžŸαŸαž„αŸ—αž‚αŸ’αž“αžΆαžŠαžΌαž…αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αŸ–

αž›αŸαž…αž‘αžΎαž„ ->

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž€αžΆαžšαž…αžΌαž›αž‘αŸ…αž€αžΆαž“αŸ‹αž€αž»αž„αžŸαžΌαž›αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αž’αž“αž»αžœαžαŸ’αžαžαžΆαž˜αžšαž™αŸˆαž€αž»αž„αžŸαžΌαž›αž“αŸƒαž‘αŸ†αž–αŸαžšαžŠαŸ‚αž›αžœαžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αžΎαž€αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαŸ”αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž€αžΆαžšαž•αŸ’αž‰αžΎαžŸαžΆαžš

αžŠαžΌαž…αŸ’αž“αŸαŸ‡ αž™αžΎαž„αžαŸ’αžšαžΌαžœαž”αž„αŸ’αž€αžΎαžαž”αžŽαŸ’αžαžΆαž‰αž‘αŸ†αž“αžΆαž€αŸ‹αž‘αŸ†αž“αž„αž–αžΈαžšαŸ– inpage <-> background αž“αž·αž„ popup <-> backgroundαŸ” αž‡αžΆαž€αžΆαžšαž–αž·αžαžŽαžΆαžŸαŸ‹ αž’αŸ’αž“αž€αž’αžΆαž…αž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αž•αŸ’αž‰αžΎαžŸαžΆαžšαž‘αŸ…αž€αžΆαž“αŸ‹αž…αŸ’αžšαž€ αž“αž·αž„αž”αž„αŸ’αž€αžΎαžαž–αž·αž’αžΈαž€αžΆαžšαž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“αžšαž”αžŸαŸ‹αž’αŸ’αž“αž€ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžαŸ’αž‰αž»αŸ†αž…αžΌαž›αž…αž·αžαŸ’αžαžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αžαžŠαŸ‚αž›αžαŸ’αž‰αž»αŸ†αž”αžΆαž“αžƒαžΎαž‰αž“αŸ…αž€αŸ’αž“αž»αž„αž‚αž˜αŸ’αžšαŸ„αž„αž”αŸ’αžšαž—αž–αž”αžΎαž€αž…αŸ†αž  metamask αŸ”

αž“αŸαŸ‡αž‚αžΊαž‡αžΆαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž”αžŽαŸ’αžαžΆαž‰ Ethereum αŸ” αž“αŸ…αž€αŸ’αž“αž»αž„αžœαžΆ αž•αŸ’αž“αŸ‚αž€αž•αŸ’αžŸαŸαž„αŸ—αž“αŸƒαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‘αŸ†αž“αžΆαž€αŸ‹αž‘αŸ†αž“αž„αžαžΆαž˜αžšαž™αŸˆ RPC αžŠαŸ„αž™αž”αŸ’αžšαžΎαž”αžŽαŸ’αžŽαžΆαž›αŸαž™ dnode αŸ” αžœαžΆαž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž’αŸ’αž“αž€αžšαŸ€αž”αž…αŸ†αž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαž™αŸ‰αžΆαž„αžšαž αŸαžŸ αž“αž·αž„αž„αžΆαž™αžŸαŸ’αžšαž½αž› αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž’αŸ’αž“αž€αž•αŸ’αžαž›αŸ‹αžœαžΆαž‡αžΆαž˜αž½αž™ nodejs stream αž‡αžΆαž€αžΆαžšαžŠαžΉαž€αž‡αž‰αŸ’αž‡αžΌαž“ (αž˜αžΆαž“αž“αŸαž™αžαžΆαžœαžαŸ’αžαž»αžŠαŸ‚αž›αž’αž“αž»αžœαžαŸ’αžαž…αŸ†αžŽαž»αž…αž”αŸ’αžšαž‘αžΆαž€αŸ‹αžŠαžΌαž…αž‚αŸ’αž“αžΆ)αŸ–

import Dnode from "dnode/browser";

// Π’ этом ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ условимся Ρ‡Ρ‚ΠΎ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΡƒΠ΄Π°Π»Π΅Π½Π½ΠΎ Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π½Π° сСрвСрС, хотя Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π°ΠΌ Π½Π΅ ΠΌΠ΅ΡˆΠ°Π΅Ρ‚ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ это Π΄Π²ΡƒΠ½Π°ΠΏΡ€Π°Π²Π»Π΅Π½Π½Ρ‹ΠΌ

// CΠ΅Ρ€Π²Π΅Ρ€
// API, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ
const dnode = Dnode({
    hello: (cb) => cb(null, "world")
})
// Вранспорт, ΠΏΠΎΠ²Π΅Ρ€Ρ… ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ dnode. Π›ΡŽΠ±ΠΎΠΉ nodejs стрим. Π’ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ Π΅ΡΡ‚ΡŒ Π±ΠΈΠ±ΠΈΠ»ΠΈΠΎΡ‚Π΅ΠΊΠ° 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)

// ΠšΠ»ΠΈΠ΅Π½Ρ‚
const dnodeClient = Dnode() // Π’Ρ‹Π·ΠΎΠ² Π±Π΅Π· Π°Π³Ρ€ΡƒΠΌΠ΅Π½Ρ‚Π° Π·Π½Π°Ρ‡ΠΈΡ‚ Ρ‡Ρ‚ΠΎ ΠΌΡ‹ Π½Π΅ прСдоставляСм API Π½Π° Π΄Ρ€ΡƒΠ³ΠΎΠΉ сторонС

// Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ Π² консоль world
dnodeClient.once('remote', remote => {
    remote.hello(((err, value) => console.log(value)))
})

αž₯αž‘αžΌαžœαž“αŸαŸ‡αž™αžΎαž„αž“αžΉαž„αž”αž„αŸ’αž€αžΎαžαžαŸ’αž“αžΆαž€αŸ‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαŸ” αžœαžΆαž“αžΉαž„αž”αž„αŸ’αž€αžΎαžαžœαžαŸ’αžαž» API αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‘αŸ†αž–αŸαžšαž›αŸαž…αž‘αžΎαž„ αž“αž·αž„αž‚αŸαž αž‘αŸ†αž–αŸαžš αž αžΎαž™αž”αž„αŸ’αž€αžΎαž dnode αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž–αž½αž€αžœαžΆαŸ–

import Dnode from 'dnode/browser';

export class SignerApp {

    // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ API для ui
    popupApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΎΠ±ΡŠΠ΅Ρ‚ API для страницы
    pageApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ popup ui
    connectPopup(connectionStream){
        const api = this.popupApi();
        const dnode = Dnode(api);

        connectionStream.pipe(dnode).pipe(connectionStream);

        dnode.on('remote', (remote) => {
            console.log(remote)
        })
    }

    // ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ страницу
    connectPage(connectionStream, origin){
        const api = this.popupApi();
        const dnode = Dnode(api);

        connectionStream.pipe(dnode).pipe(connectionStream);

        dnode.on('remote', (remote) => {
            console.log(origin);
            console.log(remote)
        })
    }
}

αž“αŸ…αž‘αžΈαž“αŸαŸ‡ αž“αž·αž„αžαžΆαž„αž€αŸ’αžšαŸ„αž˜αž‡αŸ†αž“αž½αžŸαž±αŸ’αž™αžœαžαŸ’αžαž» Chrome αžŸαž€αž› αž™αžΎαž„αž”αŸ’αžšαžΎ extensionApi αžŠαŸ‚αž›αž…αžΌαž›αž”αŸ’αžšαžΎ Chrome αž“αŸ…αž€αŸ’αž“αž»αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαžšαž”αžŸαŸ‹ Google αž“αž·αž„αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αž•αŸ’αžŸαŸαž„αž‘αŸ€αžαŸ” αž“αŸαŸ‡αžαŸ’αžšαžΌαžœαž”αžΆαž“αž’αŸ’αžœαžΎαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž—αžΆαž–αž†αž”αž‚αŸ’αž“αžΆαžšαž”αžŸαŸ‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αž αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‚αŸ„αž›αž”αŸ†αžŽαž„αž“αŸƒαž’αžαŸ’αžαž”αž‘αž“αŸαŸ‡ αž’αŸ’αž“αž€αž’αžΆαž…αž”αŸ’αžšαžΎ 'chrome.runtime.connect' αžŠαŸ„αž™αžŸαžΆαž˜αž‰αŸ’αž‰αŸ”

αžαŸ„αŸ‡β€‹αž”αž„αŸ’αž€αžΎαžβ€‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈβ€‹αž€αŸ’αž“αž»αž„β€‹αžŸαŸ’αž‚αŸ’αžšαžΈαž”β€‹αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αŸ–

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";

const app = new SignerApp();

// onConnect срабатываСт ΠΏΡ€ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ 'процСссов' (contentscript, popup, ΠΈΠ»ΠΈ страница Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ)
extensionApi.runtime.onConnect.addListener(connectRemote);

function connectRemote(remotePort) {
    const processName = remotePort.name;
    const portStream = new PortStream(remotePort);
    // ΠŸΡ€ΠΈ установкС соСдинСния ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ имя, ΠΏΠΎ этому ΠΈΠΌΠ΅Π½ΠΈ ΠΌΡ‹ ΠΈ оппрСдСляСм ΠΊΡ‚ΠΎ ΠΊ Π½Π°ΠΌ ΠΏΠΎΠ΄Π»ΡŽΡ‡ΠΈΠ»ΡΡ, контСнтскрипт ΠΈΠ»ΠΈ ui
    if (processName === 'contentscript'){
        const origin = remotePort.sender.url
        app.connectPage(portStream, origin)
    }else{
        app.connectPopup(portStream)
    }
}

αžŠαŸ„αž™αžŸαžΆαžš dnode αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž‡αžΆαž˜αž½αž™αžŸαŸ’αž‘αŸ’αžšαžΈαž˜ αž αžΎαž™αž™αžΎαž„αž‘αž‘αž½αž›αž”αžΆαž“αž…αŸ’αžšαž€αž˜αž½αž™ αžαŸ’αž“αžΆαž€αŸ‹αž’αžΆαžŠαžΆαž”αŸ‹αž’αŸαžšαž‚αžΊαž…αžΆαŸ†αž”αžΆαž…αŸ‹αŸ” αžœαžΆβ€‹αžαŸ’αžšαžΌαžœβ€‹αž”αžΆαž“β€‹αž”αž„αŸ’αž€αžΎαžβ€‹αž‘αžΎαž„β€‹αžŠαŸ„αž™β€‹αž”αŸ’αžšαžΎβ€‹αž”αžŽαŸ’αžŽαžΆαž›αŸαž™β€‹αžŸαŸ’αž‘αŸ’αžšαžΈαž˜β€‹αžŠαŸ‚αž›β€‹αž’αžΆαž…β€‹αž’αžΆαž“β€‹αž”αžΆαž“ αžŠαŸ‚αž›β€‹αž’αž“αž»αžœαžαŸ’αžβ€‹αž€αžΆαžšβ€‹αžŸαŸ’αž‘αŸ’αžšαžΈαž˜β€‹ nodejs αž€αŸ’αž“αž»αž„β€‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈβ€‹αžšαž»αž€αžšαž€αŸ–

import {Duplex} from 'readable-stream';

export class PortStream extends Duplex{
    constructor(port){
        super({objectMode: true});
        this._port = port;
        port.onMessage.addListener(this._onMessage.bind(this));
        port.onDisconnect.addListener(this._onDisconnect.bind(this))
    }

    _onMessage(msg) {
        if (Buffer.isBuffer(msg)) {
            delete msg._isBuffer;
            const data = new Buffer(msg);
            this.push(data)
        } else {
            this.push(msg)
        }
    }

    _onDisconnect() {
        this.destroy()
    }

    _read(){}

    _write(msg, encoding, cb) {
        try {
            if (Buffer.isBuffer(msg)) {
                const data = msg.toJSON();
                data._isBuffer = true;
                this._port.postMessage(data)
            } else {
                this._port.postMessage(msg)
            }
        } catch (err) {
            return cb(new Error('PortStream - disconnected'))
        }
        cb()
    }
}

αž₯αž‘αžΌαžœαž“αŸαŸ‡ αž…αžΌαžšαž™αžΎαž„αž”αž„αŸ’αž€αžΎαžαž€αžΆαžšαžαž—αŸ’αž‡αžΆαž”αŸ‹αž“αŸ…αž€αŸ’αž“αž»αž„ UIαŸ–

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import Dnode from 'dnode/browser';

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupUi().catch(console.error);

async function setupUi(){
    // Π’Π°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ Π² классС прилоТСния создаСм ΠΏΠΎΡ€Ρ‚, ΠΎΠ±ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Π΅ΠΌ Π² stream, Π΄Π΅Π»Π°Π΅ΠΌ  dnode
    const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
    const connectionStream = new PortStream(backgroundPort);

    const dnode = Dnode();

    connectionStream.pipe(dnode).pipe(connectionStream);

    const background = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // Π”Π΅Π»Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ API доступным ΠΈΠ· консоли
    if (DEV_MODE){
        global.background = background;
    }
}

αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€αž™αžΎαž„αž”αž„αŸ’αž€αžΎαžαž€αžΆαžšαžαž—αŸ’αž‡αžΆαž”αŸ‹αž“αŸ…αž€αŸ’αž“αž»αž„αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆαŸ–

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import PostMessageStream from 'post-message-stream';

setupConnection();
injectScript();

function setupConnection(){
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

    pageStream.pipe(backgroundStream).pipe(pageStream);
}

function injectScript(){
    try {
        // inject in-page script
        let script = document.createElement('script');
        script.src = extensionApi.extension.getURL('inpage.js');
        const container = document.head || document.documentElement;
        container.insertBefore(script, container.children[0]);
        script.onload = () => script.remove();
    } catch (e) {
        console.error('Injection failed.', e);
    }
}

αžŠαŸ„αž™αžŸαžΆαžšαž™αžΎαž„αžαŸ’αžšαžΌαžœαž€αžΆαžš API αž˜αž·αž“αž“αŸ…αž€αŸ’αž“αž»αž„αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž˜αžΆαžαž·αž€αžΆ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αžŠαŸ„αž™αž•αŸ’αž‘αžΆαž›αŸ‹αž“αŸ…αž›αžΎαž‘αŸ†αž–αŸαžš αž™αžΎαž„αž’αŸ’αžœαžΎαžšαžΏαž„αž–αžΈαžšαž™αŸ‰αžΆαž„αŸ–

  1. αž™αžΎαž„αž”αž„αŸ’αž€αžΎαžαžŸαŸ’αž‘αŸ’αžšαžΈαž˜αž–αžΈαžšαŸ” αž˜αž½αž™ - αž†αŸ’αž–αŸ„αŸ‡αž‘αŸ…αž‘αŸ†αž–αŸαžšαž“αŸ…αž›αžΎαž€αŸ†αž–αžΌαž›αž“αŸƒαžŸαžΆαžšαž”αž„αŸ’αž αŸ„αŸ‡αŸ” αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αžšαžΏαž„αž“αŸαŸ‡αž™αžΎαž„αž”αŸ’αžšαžΎαžœαžΆαŸ” αž€αž‰αŸ’αž…αž”αŸ‹αž“αŸαŸ‡αŸ” αž–αžΈαž’αŸ’αž“αž€αž”αž„αŸ’αž€αžΎαž metamask αŸ” αžŸαŸ’αž‘αŸ’αžšαžΈαž˜αž‘αžΈαž–αžΈαžšαž‚αžΊαžŠαžΎαž˜αŸ’αž”αžΈαž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αž“αŸ…αž›αžΎαž…αŸ’αžšαž€αžŠαŸ‚αž›αž‘αž‘αž½αž›αž”αžΆαž“αž–αžΈ runtime.connect. αžαŸ„αŸ‡αž‘αž·αž‰αž–αž½αž€αž‚αŸαŸ” αž₯αž‘αžΌαžœαž“αŸαŸ‡ αž‘αŸ†αž–αŸαžšαž“αžΉαž„αž˜αžΆαž“αžŸαŸ’αž‘αŸ’αžšαžΈαž˜αž‘αŸ…αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αŸ”
  2. αž”αž‰αŸ’αž…αžΌαž›αžŸαŸ’αž‚αŸ’αžšαžΈαž”αž‘αŸ…αž€αŸ’αž“αž»αž„ DOM αŸ” αž‘αžΆαž‰αž™αž€αžŸαŸ’αž‚αŸ’αžšαžΈαž” (αž€αžΆαžšαž…αžΌαž›αž”αŸ’αžšαžΎαžœαžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αž’αž“αž»αž‰αŸ’αž‰αžΆαžαž“αŸ…αž€αŸ’αž“αž»αž„ manifest) αž αžΎαž™αž”αž„αŸ’αž€αžΎαžαžŸαŸ’αž›αžΆαž€αž˜αž½αž™αŸ” script αž‡αžΆαž˜αž½αž™αž“αžΉαž„αžαŸ’αž›αžΉαž˜αžŸαžΆαžšαžšαž”αžŸαŸ‹αžœαžΆαž“αŸ…αžαžΆαž„αž€αŸ’αž“αž»αž„αŸ–

import PostMessageStream from 'post-message-stream';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";

setupConnection();
injectScript();

function setupConnection(){
    // Π‘Ρ‚Ρ€ΠΈΠΌ ΠΊ Π±Π΅ΠΊΠ³Ρ€Π°ΡƒΠ½Π΄Ρƒ
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    // Π‘Ρ‚Ρ€ΠΈΠΌ ΠΊ страницС
    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

    pageStream.pipe(backgroundStream).pipe(pageStream);
}

function injectScript(){
    try {
        // inject in-page script
        let script = document.createElement('script');
        script.src = extensionApi.extension.getURL('inpage.js');
        const container = document.head || document.documentElement;
        container.insertBefore(script, container.children[0]);
        script.onload = () => script.remove();
    } catch (e) {
        console.error('Injection failed.', e);
    }
}

αž₯αž‘αžΌαžœαž“αŸαŸ‡αž™αžΎαž„αž”αž„αŸ’αž€αžΎαžαžœαžαŸ’αžαž» api αž“αŸ…αž€αŸ’αž“αž»αž„αž‘αŸ†αž–αŸαžš αž αžΎαž™αž€αŸ†αžŽαžαŸ‹αžœαžΆαž‘αŸ…αž‡αžΆαžŸαž€αž›αŸ–

import PostMessageStream from 'post-message-stream';
import Dnode from 'dnode/browser';

setupInpageApi().catch(console.error);

async function setupInpageApi() {
    // Π‘Ρ‚Ρ€ΠΈΠΌ ΠΊ контСнтскрипту
    const connectionStream = new PostMessageStream({
        name: 'page',
        target: 'content',
    });

    const dnode = Dnode();

    connectionStream.pipe(dnode).pipe(connectionStream);

    // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ API
    const pageApi = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // Доступ Ρ‡Π΅Ρ€Π΅Π· window
    global.SignerApp = pageApi;
}

αž™αžΎαž„αžšαž½αž…αžšαžΆαž›αŸ‹αž αžΎαž™αŸ” αž€αžΆαžšαž αŸ…αžαžΆαž˜αž“αžΈαžαž·αžœαž·αž’αžΈαž–αžΈαž…αž˜αŸ’αž„αžΆαž™ (RPC) αž‡αžΆαž˜αž½αž™αž“αžΉαž„ API αžŠαžΆαž…αŸ‹αžŠαŸ„αž™αž‘αŸ‚αž€αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‘αŸ†αž–αŸαžš αž“αž·αž„ UI. αž“αŸ…αž–αŸαž›αž—αŸ’αž‡αžΆαž”αŸ‹αž‘αŸ†αž–αŸαžšαžαŸ’αž˜αžΈαž‘αŸ…αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ αž™αžΎαž„αž’αžΆαž…αžƒαžΎαž‰αžœαžΆαŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

API αž‘αž‘αŸ αž“αž·αž„αž”αŸ’αžšαž—αž–αžŠαžΎαž˜αŸ” αž“αŸ…αž•αŸ’αž“αŸ‚αž€αžαžΆαž„αž‘αŸ†αž–αŸαžš αž™αžΎαž„αž’αžΆαž…αž αŸ…αž˜αž»αžαž„αžΆαžš hello αžŠαžΌαž…αž“αŸαŸ‡αŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž€αžΆαžšαž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž˜αž»αžαž„αžΆαžš callback αž“αŸ…αž€αŸ’αž“αž»αž„ JS αž‘αŸ†αž“αžΎαž”αž‚αžΊαž‡αžΆαž’αžΆαž€αž”αŸ’αž”αž€αž·αžšαž·αž™αžΆαž˜αž·αž“αž›αŸ’αž’ αžŠαžΌαž…αŸ’αž“αŸαŸ‡αžŸαžΌαž˜αžŸαžšαžŸαŸαžšαž‡αŸ†αž“αž½αž™αžαžΌαž…αž˜αž½αž™αžŠαžΎαž˜αŸ’αž”αžΈαž”αž„αŸ’αž€αžΎαž dnode αžŠαŸ‚αž›αž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž’αŸ’αž“αž€αž”αž‰αŸ’αž‡αžΌαž“αžœαžαŸ’αžαž» API αž‘αŸ…αž§αž”αž€αžšαžŽαŸαž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αŸ”

αž₯αž‘αžΌαžœαž“αŸαŸ‡αžœαžαŸ’αžαž» API αž“αžΉαž„αž˜αžΎαž›αž‘αŸ…αžŠαžΌαž…αž“αŸαŸ‡αŸ–

export class SignerApp {

    popupApi() {
        return {
            hello: async () => "world"
        }
    }

...

}

αž‘αž‘αž½αž›αž”αžΆαž“αžœαžαŸ’αžαž»αž–αžΈαž…αž˜αŸ’αž„αžΆαž™αžŠαžΌαž…αž“αŸαŸ‡αŸ–

import {cbToPromise, transformMethods} from "../../src/utils/setupDnode";

const pageApi = await new Promise(resolve => {
    dnode.once('remote', remoteApi => {
        // Π‘ ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚ мСняСм всС callback Π½Π° promise
        resolve(transformMethods(cbToPromise, remoteApi))
    })
});

αž αžΎαž™αž˜αž»αžαž„αžΆαžšαž αŸ…αžαŸ’αžšαž›αž”αŸ‹αž˜αž€αžœαž·αž‰αž“αžΌαžœαž€αžΆαžšαžŸαž“αŸ’αž™αžΆαŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž€αŸ†αžŽαŸ‚αžŠαŸ‚αž›αž˜αžΆαž“αž˜αž»αžαž„αžΆαžšαž’αžŸαž˜αž€αžΆαž›αž’αžΆαž…αž”αŸ’αžšαžΎαž”αžΆαž“ αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αžŸαžšαž»αž”αž˜αž€ αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αž RPC αž“αž·αž„αžŸαŸ’αž‘αŸ’αžšαžΈαž˜αž αžΆαž€αŸ‹αžŠαžΌαž…αž‡αžΆαž’αžΆαž…αž”αžαŸ‹αž”αŸ‚αž“αž”αžΆαž“αŸ– αž™αžΎαž„αž’αžΆαž…αž”αŸ’αžšαžΎαž€αžΆαžš multiplexing αž…αŸ†αž αžΆαž™ αž“αž·αž„αž”αž„αŸ’αž€αžΎαž APIs αž•αŸ’αžŸαŸαž„αŸ—αž‚αŸ’αž“αžΆαž‡αžΆαž…αŸ’αžšαžΎαž“αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž€αž·αž…αŸ’αž…αž€αžΆαžšαž•αŸ’αžŸαŸαž„αŸ—αž‚αŸ’αž“αžΆαŸ” αž‡αžΆαž‚αŸ„αž›αž€αžΆαžšαžŽαŸ dnode αž’αžΆαž…αž”αŸ’αžšαžΎαž”αžΆαž“αž‚αŸ’αžšαž”αŸ‹αž‘αžΈαž€αž“αŸ’αž›αŸ‚αž„ αžšαžΏαž„αžŸαŸ†αžαžΆαž“αŸ‹αž‚αžΊαž€αžΆαžšαžšαž»αŸ†αž€αžΆαžšαžŠαžΉαž€αž‡αž‰αŸ’αž‡αžΌαž“αž€αŸ’αž“αž»αž„αž‘αž˜αŸ’αžšαž„αŸ‹αž‡αžΆ nodejs streamαŸ”

αž‡αž˜αŸ’αžšαžΎαžŸαž˜αž½αž™αž‚αžΊαž‘αž˜αŸ’αžšαž„αŸ‹ JSON αžŠαŸ‚αž›αž’αž“αž»αžœαžαŸ’αžαž–αž·αž’αžΈαž€αžΆαžš JSON RPC 2αŸ” αž‘αŸ„αŸ‡αž™αŸ‰αžΆαž„αžŽαžΆαž€αŸαžŠαŸ„αž™ αžœαžΆαžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž‡αžΆαž˜αž½αž™αž€αžΆαžšαžŠαžΉαž€αž‡αž‰αŸ’αž‡αžΌαž“αž‡αžΆαž€αŸ‹αž›αžΆαž€αŸ‹ (TCP αž“αž·αž„ HTTP(S)) αžŠαŸ‚αž›αž˜αž·αž“αž’αžΆαž…αž’αž“αž»αžœαžαŸ’αžαž”αžΆαž“αž€αŸ’αž“αž»αž„αž€αžšαžŽαžΈαžšαž”αžŸαŸ‹αž™αžΎαž„αŸ”

αžŸαŸ’αžαžΆαž“αž—αžΆαž–αž•αŸ’αž‘αŸƒαž€αŸ’αž“αž»αž„ αž“αž·αž„αž€αž“αŸ’αž›αŸ‚αž„αž•αŸ’αž‘αž»αž€

αž™αžΎαž„αž“αžΉαž„αžαŸ’αžšαžΌαžœαž€αžΆαžšαžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ’αžαžΆαž“αž—αžΆαž–αžαžΆαž„αž€αŸ’αž“αž»αž„αž“αŸƒαž€αž˜αŸ’αž˜αžœαž·αž’αžΈ - αž™αŸ‰αžΆαž„αž αŸ„αž…αž‚αŸ’αžšαžΆαž”αŸ‹αž…αž»αž…αž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆαŸ” αž™αžΎαž„β€‹αž’αžΆαž…β€‹αž”αž“αŸ’αžαŸ‚αž˜β€‹αžŸαŸ’αžαžΆαž“αž—αžΆαž–β€‹αž™αŸ‰αžΆαž„β€‹αž„αžΆαž™β€‹αžŸαŸ’αžšαž½αž›β€‹αž‘αŸ…β€‹αž€αŸ’αž“αž»αž„β€‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈ αž“αž·αž„β€‹αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžαŸ’αžšβ€‹αžŸαž˜αŸ’αžšαžΆαž”αŸ‹β€‹αž€αžΆαžšβ€‹αž•αŸ’αž›αžΆαžŸαŸ‹β€‹αž”αŸ’αžαžΌαžšβ€‹αžœαžΆβ€‹αž“αŸ…β€‹αž€αŸ’αž“αž»αž„ API αž›αŸαž…β€‹αž‘αžΎαž„αŸ–

import {setupDnode} from "./utils/setupDnode";

export class SignerApp {

    constructor(){
        this.store = {
            keys: [],
        };
    }

    addKey(key){
        this.store.keys.push(key)
    }

    removeKey(index){
        this.store.keys.splice(index,1)
    }

    popupApi(){
        return {
            addKey: async (key) => this.addKey(key),
            removeKey: async (index) => this.removeKey(index)
        }
    }

    ...

} 

αž“αŸ…αž€αŸ’αž“αž»αž„αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ αž™αžΎαž„αž“αžΉαž„αžšαž»αŸ†αž’αŸ’αžœαžΈαž‚αŸ’αžšαž”αŸ‹αž™αŸ‰αžΆαž„αž“αŸ…αž€αŸ’αž“αž»αž„αž˜αž»αžαž„αžΆαžšαž˜αž½αž™ αž αžΎαž™αžŸαžšαžŸαŸαžšαžœαžαŸ’αžαž»αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‘αŸ…αž”αž„αŸ’αž’αž½αž… αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž™αžΎαž„αž’αžΆαž…αž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αžœαžΆαž–αžΈαž€αž»αž„αžŸαžΌαž›αŸ–

import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";

const DEV_MODE = process.env.NODE_ENV !== 'production';

setupApp();

function setupApp() {
    const app = new SignerApp();

    if (DEV_MODE) {
        global.app = app;
    }

    extensionApi.runtime.onConnect.addListener(connectRemote);

    function connectRemote(remotePort) {
        const processName = remotePort.name;
        const portStream = new PortStream(remotePort);
        if (processName === 'contentscript') {
            const origin = remotePort.sender.url;
            app.connectPage(portStream, origin)
        } else {
            app.connectPopup(portStream)
        }
    }
}

αžαŸ„αŸ‡αž”αž“αŸ’αžαŸ‚αž˜αžŸαŸ„αž˜αž½αž™αž…αŸ†αž“αž½αž“αž–αžΈαž€αž»αž„αžŸαžΌαž› UI αž αžΎαž™αž˜αžΎαž›αžαžΆαž˜αžΆαž“αž’αŸ’αžœαžΈαž€αžΎαžαž‘αžΎαž„αž‡αžΆαž˜αž½αž™αžŸαŸ’αžαžΆαž“αž—αžΆαž–αŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αžšαžŠαŸ’αž‹αžαŸ’αžšαžΌαžœαžαŸ‚αž’αŸ’αžœαžΎαž±αŸ’αž™αž‡αžΆαž”αŸ‹αž‡αžΆαž“αž·αž…αŸ’αž… αžŠαžΎαž˜αŸ’αž”αžΈαž€αž»αŸ†αž±αŸ’αž™αžŸαŸ„αžšαž”αžΆαžαŸ‹αž“αŸ…αž–αŸαž›αž…αžΆαž”αŸ‹αž•αŸ’αžŠαžΎαž˜αž‘αžΎαž„αžœαž·αž‰αŸ”

αž™αžΎαž„αž“αžΉαž„αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžœαžΆαž“αŸ…αž€αŸ’αž“αž»αž„ localStorage αžŠαŸ„αž™αžŸαžšαžŸαŸαžšαž‡αžΆαž“αŸ‹αž›αžΎαžœαžΆαž‡αžΆαž˜αž½αž™αž“αžΉαž„αžšαžΆαž›αŸ‹αž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ” αž€αŸ’αžšαŸ„αž™αž˜αž€ αž€αžΆαžšαž…αžΌαž›αž”αŸ’αžšαžΎαžœαžΆαž€αŸαž“αžΉαž„αž…αžΆαŸ†αž”αžΆαž…αŸ‹αžŸαž˜αŸ’αžšαžΆαž”αŸ‹ UI αž αžΎαž™αžαŸ’αž‰αž»αŸ†αž€αŸαž…αž„αŸ‹αž‡αžΆαžœαž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαž•αž„αžŠαŸ‚αžšαŸ” αžŠαŸ„αž™αž•αŸ’αž’αŸ‚αž€αž›αžΎαž“αŸαŸ‡ αžœαžΆαž“αžΉαž„αž˜αžΆαž“αž—αžΆαž–αž„αžΆαž™αžŸαŸ’αžšαž½αž›αž€αŸ’αž“αž»αž„αž€αžΆαžšαž”αž„αŸ’αž€αžΎαžαž€αž“αŸ’αž›αŸ‚αž„αž•αŸ’αž‘αž»αž€αžŠαŸ‚αž›αž’αžΆαž…αžŸαž„αŸ’αž€αŸαžαž”αžΆαž“ αž“αž·αž„αž‡αžΆαžœαž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžšαž”αžŸαŸ‹αžœαžΆαŸ”

αž™αžΎαž„αž“αžΉαž„αž”αŸ’αžšαžΎαž”αžŽαŸ’αžŽαžΆαž›αŸαž™ mobx (https://github.com/mobxjs/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 αžαžΆαž˜αžŠαžΆαž“αž€αžΆαžšαž…αžΌαž›αž‘αŸ…αž€αžΆαž“αŸ‹αžœαžΆαž›αŸ” αž’αŸ’αž“αž€αž‘αž‘αž½αž› αž“αž·αž„αž€αŸ†αžŽαžαŸ‹αžœαžαŸ’αžαž»αž”αŸ’αžšαžΌαž€αžŸαŸŠαžΈαžŠαŸ‚αž›αž”αžŽαŸ’αžŽαžΆαž›αŸαž™αž”αž„αŸ’αž€αžΎαžαžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ’αžšαžΎαŸ”

αž’αŸ’αž“αž€αžαž»αž”αžαŸ‚αž„αžŸαž€αž˜αŸ’αž˜αž—αžΆαž–αž”αž˜αŸ’αžšαžΎαž‚αŸ„αž›αž”αŸ†αžŽαž„αž–αžΈαžšαŸ–

  1. αž“αŸ…αž€αŸ’αž“αž»αž„αžšαž”αŸ€αž”αžŠαŸαžαžΉαž„αžšαžΉαž„αž‡αžΆαž˜αž½αž™αž“αžΉαž„αž‘αž„αŸ‹ enforceActions mobx αž αžΆαž˜αžƒαžΆαžαŸ‹αž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžšαžŠαŸ’αž‹αžŠαŸ„αž™αž•αŸ’αž‘αžΆαž›αŸ‹αŸ” αžœαžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αž…αžΆαžαŸ‹αž‘αž»αž€αžαžΆαž‡αžΆαž€αžΆαžšαž’αž“αž»αžœαžαŸ’αžαž›αŸ’αž’αžŠαžΎαž˜αŸ’αž”αžΈαž’αŸ’αžœαžΎαž€αžΆαžšαž€αŸ’αžšαŸ„αž˜αž›αž€αŸ’αžαžαžŽαŸ’αžŒαžŠαŸαžαžΉαž„αžšαŸ‰αžΉαž„αŸ”
  2. αž‘αŸ„αŸ‡αž”αžΈαž‡αžΆαž˜αž»αžαž„αžΆαžšαž˜αž½αž™αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαŸ’αžαžΆαž“αž—αžΆαž–αž…αŸ’αžšαžΎαž“αžŠαž„αž€αŸαžŠαŸ„αž™ - αž§αž‘αžΆαž αžšαžŽαŸ αž™αžΎαž„αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžœαžΆαž›αž‡αžΆαž…αŸ’αžšαžΎαž“αž“αŸ…αž€αŸ’αž“αž»αž„αž‡αž½αžšαž€αžΌαžŠαž‡αžΆαž…αŸ’αžšαžΎαž“ - αž’αŸ’αž“αž€αžŸαž„αŸ’αž€αŸαžαž€αžΆαžšαžŽαŸαžαŸ’αžšαžΌαžœαž”αžΆαž“αž‡αžΌαž“αžŠαŸ†αžŽαžΉαž„αžαŸ‚αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αžœαžΆαž”αž‰αŸ’αž…αž”αŸ‹αŸ” αž“αŸαŸ‡αž˜αžΆαž“αžŸαžΆαžšαŸˆαžŸαŸ†αžαžΆαž“αŸ‹αž‡αžΆαž–αž·αžŸαŸαžŸαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž•αŸ’αž“αŸ‚αž€αžαžΆαž„αž˜αž»αž αžŠαŸ‚αž›αž€αžΆαžšαž’αžΆαž”αŸ‹αžŠαŸαžαžšαžŠαŸ’αž‹αžŠαŸ‚αž›αž˜αž·αž“αž…αžΆαŸ†αž”αžΆαž…αŸ‹αž“αžΆαŸ†αž‘αŸ…αžŠαž›αŸ‹αž€αžΆαžšαž”αž„αŸ’αž αžΆαž‰αž’αžΆαžαž»αžŠαŸ‚αž›αž˜αž·αž“αž…αžΆαŸ†αž”αžΆαž…αŸ‹αŸ” αž€αŸ’αž“αž»αž„αž€αžšαžŽαžΈαžšαž”αžŸαŸ‹αž™αžΎαž„ αž‘αžΆαŸ†αž„αž‘αžΈαž˜αž½αž™ αž¬αž‘αžΈαž–αžΈαžšαž˜αž·αž“αž–αžΆαž€αŸ‹αž–αŸαž“αŸ’αž’αž‡αžΆαž–αž·αžŸαŸαžŸαž“αŸ„αŸ‡αž‘αŸ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž™αžΎαž„αž“αžΉαž„αž’αž“αž»αžœαžαŸ’αžαžαžΆαž˜αž€αžΆαžšαž’αž“αž»αžœαžαŸ’αžαž›αŸ’αž’αž”αŸ†αž•αž»αžαŸ” αžœαžΆαž‡αžΆαž‘αž˜αŸ’αž›αžΆαž”αŸ‹αž€αŸ’αž“αž»αž„αž€αžΆαžšαž—αŸ’αž‡αžΆαž”αŸ‹αž’αŸ’αž“αž€αžαž»αž”αžαŸ‚αž„αž‘αŸ…αž“αžΉαž„αž˜αž»αžαž„αžΆαžšαž‘αžΆαŸ†αž„αž’αžŸαŸ‹αžŠαŸ‚αž›αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαŸ’αžαžΆαž“αž—αžΆαž–αž“αŸƒαžœαžΆαž›αžŠαŸ‚αž›αž”αžΆαž“αžŸαž„αŸ’αž€αŸαžαŸ”

αž“αŸ…αž€αŸ’αž“αž»αž„αž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™ αž™αžΎαž„αž“αžΉαž„αž”αž“αŸ’αžαŸ‚αž˜αž€αžΆαžšαž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜ αž“αž·αž„αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ’αžαžΆαž“αž—αžΆαž–αž“αŸ…αž€αŸ’αž“αž»αž„ 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)
        }
    }
}

αž˜αž»αžαž„αžΆαžšαž”αŸ’αžšαžαž·αž€αž˜αŸ’αž˜αž‚αžΊαž‚αž½αžšαž±αŸ’αž™αž…αžΆαž”αŸ‹αž’αžΆαžšαž˜αŸ’αž˜αžŽαŸαž“αŸ…αž‘αžΈαž“αŸαŸ‡αŸ” αžœαžΆαž˜αžΆαž“αž’αŸ†αžŽαŸ‡αž’αŸ†αžŽαžΆαž„αž–αžΈαžšαŸ–

  1. αž§αž”αž€αžšαžŽαŸαž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸαž‘αž·αž“αŸ’αž“αž“αŸαž™αŸ”
  2. αž§αž”αž€αžšαžŽαŸαžŠαŸ„αŸ‡αžŸαŸ’αžšαžΆαž™αžŠαŸ‚αž›αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž αŸ…αž‡αžΆαž˜αž½αž™αž‘αž·αž“αŸ’αž“αž“αŸαž™αž“αŸαŸ‡αžšαžΆαž›αŸ‹αž–αŸαž›αžŠαŸ‚αž›αžœαžΆαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ”

αž˜αž·αž“αžŠαžΌαž… redux αž‘αŸ αžŠαŸ‚αž›αž‡αžΆαž€αž“αŸ’αž›αŸ‚αž„αžŠαŸ‚αž›αž™αžΎαž„αž‘αž‘αž½αž›αž”αžΆαž“αžšαžŠαŸ’αž‹αž‡αžΆαž’αžΆαž‚αž»αž™αž˜αŸ‰αž„αŸ‹αž™αŸ‰αžΆαž„αž…αŸ’αž”αžΆαžŸαŸ‹ mobx αž…αž„αž…αžΆαŸ†αž“αžΌαžœαž’αŸ’αžœαžΈαžŠαŸ‚αž›αž’αžΆαž…αžŸαž„αŸ’αž€αŸαžαž”αžΆαž“αžŠαŸ‚αž›αž™αžΎαž„αž…αžΌαž›αž”αŸ’αžšαžΎαž“αŸ…αžαžΆαž„αž€αŸ’αž“αž»αž„αž§αž”αž€αžšαžŽαŸαž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸ αž αžΎαž™αž αŸ…αž‘αžΌαžšαžŸαŸαž–αŸ’αž‘αž‘αŸ…αž’αŸ’αž“αž€αžŠαŸ„αŸ‡αžŸαŸ’αžšαžΆαž™αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αž–αž½αž€αž‚αŸαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ”

αžœαžΆαž‡αžΆαžšαžΏαž„αžŸαŸ†αžαžΆαž“αŸ‹αž€αŸ’αž“αž»αž„αž€αžΆαžšαž™αž›αŸ‹αž…αŸ’αž”αžΆαžŸαŸ‹αž’αŸ†αž–αžΈαžšαž”αŸ€αž”αžŠαŸ‚αž› mobx αžŸαž˜αŸ’αžšαŸαž…αž…αž·αžαŸ’αžαžαžΆαžαžΎαž€αžΆαžšαžŸαž„αŸ’αž€αŸαžαžŽαžΆαž˜αž½αž™αžŠαŸ‚αž›αž™αžΎαž„αž‡αžΆαžœαŸ” αž”αŸ’αžšαžŸαž·αž“αž”αžΎαžαŸ’αž‰αž»αŸ†αžŸαžšαžŸαŸαžš Selector αž“αŸ…αž€αŸ’αž“αž»αž„αž€αžΌαžŠαžŠαžΌαž…αž“αŸαŸ‡() => app.storeαž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€ αž”αŸ’αžšαžαž·αž€αž˜αŸ’αž˜αž“αžΉαž„αž˜αž·αž“αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αŸαž αŸ…αžαžΆαž‘αŸ αž…αžΆαž”αŸ‹αžαžΆαŸ†αž„αž–αžΈαž€αžΆαžšαž•αŸ’αž‘αž»αž€αžαŸ’αž›αž½αž“αžœαžΆαž˜αž·αž“αž’αžΆαž…αž˜αžΎαž›αžƒαžΎαž‰αž”αžΆαž“ αž˜αžΆαž“αžαŸ‚αžœαžΆαž›αžšαž”αžŸαŸ‹αžœαžΆαž”αŸ‰αž»αžŽαŸ’αžŽαŸ„αŸ‡αŸ”

αž”αžΎαžαŸ’αž‰αž»αŸ†αžŸαžšαžŸαŸαžšαž”αŸ‚αž”αž“αŸαŸ‡ () => app.store.keysαž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€αž˜αŸ’αžαž„αž‘αŸ€αžαž‚αŸ’αž˜αžΆαž“αž’αŸ’αžœαžΈαž“αžΉαž„αž€αžΎαžαž‘αžΎαž„αž‘αŸ αž…αžΆαž”αŸ‹αžαžΆαŸ†αž„αž–αžΈαž–αŸαž›αž”αž“αŸ’αžαŸ‚αž˜/αž™αž€αž’αžΆαžαž»αž’αžΆαžšαŸ αžŸαŸαž…αž€αŸ’αžαžΈαž™αŸ„αž„αž‘αŸ…αžœαžΆαž“αžΉαž„αž˜αž·αž“αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαž‘αŸαŸ”

Mobx αžŠαžΎαžšαžαž½αž‡αžΆαž’αŸ’αž“αž€αž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸαž‡αžΆαž›αžΎαž€αžŠαŸ†αž”αžΌαž„ αž αžΎαž™αž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αžαžΆαž˜αžŠαžΆαž“αž€αžΆαžšαžŸαž„αŸ’αž€αŸαžαžŠαŸ‚αž›αž™αžΎαž„αž”αžΆαž“αž…αžΌαž›αž”αŸ’αžšαžΎαž”αŸ‰αž»αžŽαŸ’αžŽαŸ„αŸ‡αŸ” αž“αŸαŸ‡αžαŸ’αžšαžΌαžœαž”αžΆαž“αž’αŸ’αžœαžΎαžαžΆαž˜αžšαž™αŸˆαž’αŸ’αž“αž€αž‘αž‘αž½αž›αž”αŸ’αžšαžΌαž€αžŸαŸŠαžΈαŸ” αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž˜αž»αžαž„αžΆαžšαžŠαŸ‚αž›αž—αŸ’αž‡αžΆαž”αŸ‹αž˜αž€αž‡αžΆαž˜αž½αž™αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ’αžšαžΎαž“αŸ…αž‘αžΈαž“αŸαŸ‡ toJS. αžœαžΆαžαŸ’αžšαž‘αž”αŸ‹αžœαžαŸ’αžαž»αžαŸ’αž˜αžΈαž˜αž½αž™αž‡αžΆαž˜αž½αž™αž“αžΉαž„αž”αŸ’αžšαžΌαž€αžŸαŸŠαžΈαž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž‡αŸ†αž“αž½αžŸαžŠαŸ„αž™αžœαžΆαž›αžŠαžΎαž˜αŸ” αž€αŸ†αž‘αž»αž„αž–αŸαž›αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž· αžœαžΆαž’αžΆαž“αžœαžΆαž›αž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž“αŸƒαžœαžαŸ’αžαž» - αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž’αŸ’αž“αž€αž‘αž‘αž½αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž€αŸαŸ‡αŸ”

αž“αŸ…αž€αŸ’αž“αž»αž„αž€αž»αž„αžŸαžΌαž›αž›αŸαž…αž‘αžΎαž„ αž™αžΎαž„αž“αžΉαž„αž”αž“αŸ’αžαŸ‚αž˜αž€αžΌαž“αžŸαŸ„αž‡αžΆαž…αŸ’αžšαžΎαž“αž˜αŸ’αžαž„αž‘αŸ€αžαŸ” αž›αžΎαž€αž“αŸαŸ‡αž–αž½αž€αž‚αŸαž€αŸαž”αžΆαž“αž…αžΌαž›αž‘αŸ…αž€αŸ’αž“αž»αž„ localStorageαŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αž‘αŸ†αž–αŸαžšαž•αŸ’αž‘αŸƒαžαžΆαž„αž€αŸ’αžšαŸ„αž™αžαŸ’αžšαžΌαžœαž”αžΆαž“αž•αŸ’αž‘αž»αž€αž‘αžΎαž„αžœαž·αž‰ αž–αŸαžαŸŒαž˜αžΆαž“αž“αŸ…αžαŸ‚αž˜αžΆαž“αž“αŸ…αž“αžΉαž„αž€αž“αŸ’αž›αŸ‚αž„αŸ”

αž€αžΌαžŠαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‘αžΆαŸ†αž„αž’αžŸαŸ‹αžšαž αžΌαžαžŠαž›αŸ‹αž…αŸ†αžŽαž»αž…αž“αŸαŸ‡αž’αžΆαž…αžαŸ’αžšαžΌαžœαž”αžΆαž“αž˜αžΎαž› αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αž€αžΆαžšαžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ„αž―αž€αž‡αž“αžŠαŸ„αž™αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž€αžΆαžšαžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ„αž―αž€αž‡αž“αž‡αžΆαž’αžαŸ’αžαž”αž‘αž…αŸ’αž”αžΆαžŸαŸ‹αž›αžΆαžŸαŸ‹αž‚αžΊαž˜αž·αž“αž˜αžΆαž“αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–αž‘αŸαŸ– αžœαžΆαžαŸ‚αž„αžαŸ‚αž˜αžΆαž“αž±αž€αžΆαžŸαžŠαŸ‚αž›αž’αŸ’αž“αž€αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž›αž½αž…αž…αžΌαž› αž…αžΌαž›αž”αŸ’αžšαžΎαž€αž»αŸ†αž–αŸ’αž™αžΌαž‘αŸαžšαžšαž”αžŸαŸ‹αž’αŸ’αž“αž€αž‡αžΆαžŠαžΎαž˜αŸ” αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž“αŸ…αž€αŸ’αž“αž»αž„ localStorage αž™αžΎαž„αž“αžΉαž„αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αž€αžΌαž“αžŸαŸ„αž€αŸ’αž“αž»αž„αž‘αž˜αŸ’αžšαž„αŸ‹αžŠαŸ‚αž›αž”αžΆαž“αž’αŸŠαž·αž“αž‚αŸ’αžšαžΈαž”αžŠαŸ„αž™αž–αžΆαž€αŸ’αž™αžŸαž˜αŸ’αž„αžΆαžαŸ‹αŸ”

αžŠαžΎαž˜αŸ’αž”αžΈαžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–αž€αžΆαž“αŸ‹αžαŸ‚αžαŸ’αž›αžΆαŸ†αž„ αž™αžΎαž„αž“αžΉαž„αž”αž“αŸ’αžαŸ‚αž˜αžŸαŸ’αžαžΆαž“αž—αžΆαž–αž…αžΆαž€αŸ‹αžŸαŸ„αž‘αŸ…αž€αž˜αŸ’αž˜αžœαž·αž’αžΈ αžŠαŸ‚αž›αžœαžΆαž“αžΉαž„αž˜αž·αž“αž˜αžΆαž“αžŸαž·αž‘αŸ’αž’αž·αž…αžΌαž›αž”αŸ’αžšαžΎαžŸαŸ„αž‘αžΆαž›αŸ‹αžαŸ‚αžŸαŸ„αŸ‡αŸ” αž™αžΎαž„αž“αžΉαž„αž•αŸ’αž‘αŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αžŠαŸ„αž™αžŸαŸ’αžœαŸαž™αž”αŸ’αžšαžœαžαŸ’αžαž·αž‘αŸ…αžŸαŸ’αžαžΆαž“αž—αžΆαž–αž…αžΆαž€αŸ‹αžŸαŸ„ αžŠαŸ„αž™αžŸαžΆαžšαž’αžŸαŸ‹αž–αŸαž›αŸ”

Mobx αž’αž“αž»αž‰αŸ’αž‰αžΆαžαž±αŸ’αž™αž’αŸ’αž“αž€αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžαŸ‚αžŸαŸ†αžŽαž»αŸ†αž‘αž·αž“αŸ’αž“αž“αŸαž™αž’αž”αŸ’αž”αž”αžšαž˜αžΆ αž αžΎαž™αž“αŸ…αžŸαž›αŸ‹αžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αžŽαž“αžΆαžŠαŸ„αž™αžŸαŸ’αžœαŸαž™αž”αŸ’αžšαžœαžαŸ’αžαž·αžŠαŸ„αž™αž•αŸ’αž’αŸ‚αž€αž›αžΎαžœαžΆαŸ” αž‘αžΆαŸ†αž„αž“αŸαŸ‡αž‚αžΊαž‡αžΆαž’αŸ’αžœαžΈαžŠαŸ‚αž›αž‚αŸαž αŸ…αžαžΆ αž›αž€αŸ’αžαžŽαŸˆαžŸαž˜αŸ’αž”αžαŸ’αžαž·αž‚αžŽαž“αžΆαŸ” αž–αž½αž€αž‚αŸαž’αžΆαž…αž”αŸ’αžšαŸ€αž”αž’αŸ€αž”αž‘αŸ…αž“αžΉαž„αž‘αž·αžŠαŸ’αž‹αž—αžΆαž–αž“αŸ…αž€αŸ’αž“αž»αž„αž˜αžΌαž›αžŠαŸ’αž‹αžΆαž“αž‘αž·αž“αŸ’αž“αž“αŸαž™αŸ–

import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// Π£Ρ‚ΠΈΠ»ΠΈΡ‚Ρ‹ для бСзопасного ΡˆΠΈΡ„Ρ€ΠΎΠ²Π°Π½ΠΈΡ строк. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";

export class SignerApp {
    constructor(initState = {}) {
        this.store = observable.object({
            // Π₯Ρ€Π°Π½ΠΈΠΌ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ ΠΈ Π·Π°ΡˆΠΈΡ„Ρ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ ΠΊΠ»ΡŽΡ‡ΠΈ. Если ΠΏΠ°Ρ€ΠΎΠ»ΡŒ null - ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ locked
            password: null,
            vault: initState.vault,

            // Π“Π΅Ρ‚Ρ‚Π΅Ρ€Ρ‹ для вычислимых ΠΏΠΎΠ»Π΅ΠΉ. МоТно провСсти аналогию с view Π² Π±Π΄.
            get locked(){
                return this.password == null
            },
            get keys(){
                return this.locked ?
                    undefined :
                    SignerApp._decryptVault(this.vault, this.password)
            },
            get initialized(){
                return this.vault !== undefined
            }
        })
    }
    // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ пустого Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° Π½ΠΎΠ²Ρ‹ΠΌ ΠΏΠ°Ρ€ΠΎΠ»Π΅ΠΌ
    @action
    initVault(password){
        this.store.vault = SignerApp._encryptVault([], password)
    }
    @action
    lock() {
        this.store.password = null
    }
    @action
    unlock(password) {
        this._checkPassword(password);
        this.store.password = password
    }
    @action
    addKey(key) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
    }
    @action
    removeKey(index) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault([
                ...this.store.keys.slice(0, index),
                ...this.store.keys.slice(index + 1)
            ],
            this.store.password
        )
    }

    ... // ΠΊΠΎΠ΄ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΈ api

    // private
    _checkPassword(password) {
        SignerApp._decryptVault(this.store.vault, password);
    }

    _checkLocked() {
        if (this.store.locked){
            throw new Error('App is locked')
        }
    }

    // ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ для ΡˆΠΈΡ„Ρ€ΠΎΠ²ΠΊΠΈ/Π΄Π΅ΡˆΠΈΡ„Ρ€ΠΎΠ²ΠΊΠΈ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
    static _encryptVault(obj, pass){
        const jsonString = JSON.stringify(obj)
        return encrypt(jsonString, pass)
    }

    static _decryptVault(str, pass){
        if (str === undefined){
            throw new Error('Vault not initialized')
        }
        try {
            const jsonString = decrypt(str, pass)
            return JSON.parse(jsonString)
        }catch (e) {
            throw new Error('Wrong password')
        }
    }
}

αž₯αž‘αžΌαžœαž“αŸαŸ‡ αž™αžΎαž„αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžαŸ‚αžŸαŸ„αžŠαŸ‚αž›αž”αžΆαž“αž’αŸŠαž·αž“αž‚αŸ’αžšαžΈαž” αž“αž·αž„αž–αžΆαž€αŸ’αž™αžŸαž˜αŸ’αž„αžΆαžαŸ‹αž”αŸ‰αž»αžŽαŸ’αžŽαŸ„αŸ‡αŸ” αž’αŸ’αžœαžΈαŸ—αž•αŸ’αžŸαŸαž„αž‘αŸ€αžαžαŸ’αžšαžΌαžœαž”αžΆαž“αž‚αžŽαž“αžΆαŸ” αž™αžΎαž„αž’αŸ’αžœαžΎαž€αžΆαžšαž•αŸ’αž‘αŸαžšαž‘αŸ…αžšαžŠαŸ’αž‹αž…αžΆαž€αŸ‹αžŸαŸ„αžŠαŸ„αž™αžŠαž€αž–αžΆαž€αŸ’αž™αžŸαž˜αŸ’αž„αžΆαžαŸ‹αž…αŸαž‰αž–αžΈαžšαžŠαŸ’αž‹αŸ” αž₯αž‘αžΌαžœαž“αŸαŸ‡ API αžŸαžΆαž’αžΆαžšαžŽαŸˆαž˜αžΆαž“αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αžαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜αž€αžΆαžšαž•αŸ’αž‘αž»αž€αŸ”

αžŸαžšαžŸαŸαžšαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž€αžΆαžšαž’αŸŠαž·αž“αž‚αŸ’αžšαžΈαž” αž§αž”αž€αžšαžŽαŸαž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αžŠαŸ„αž™αž”αŸ’αžšαžΎ crypto-js:

import CryptoJS from 'crypto-js'

// Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для ослоТнСния ΠΏΠΎΠ΄Π±ΠΎΡ€Π° пароля ΠΏΠ΅Ρ€Π΅Π±ΠΎΡ€ΠΎΠΌ. На ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ пароля Π·Π»ΠΎΡƒΠΌΡ‹ΡˆΠ»Π΅Π½Π½ΠΈΠΊΡƒ придСтся ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ 5000 Ρ…Π΅ΡˆΠ΅ΠΉ
function strengthenPassword(pass, rounds = 5000) {
    while (rounds-- > 0){
        pass = CryptoJS.SHA256(pass).toString()
    }
    return pass
}

export function encrypt(str, pass){
    const strongPass = strengthenPassword(pass);
    return CryptoJS.AES.encrypt(str, strongPass).toString()
}

export function decrypt(str, pass){
    const strongPass = strengthenPassword(pass)
    const decrypted = CryptoJS.AES.decrypt(str, strongPass);
    return decrypted.toString(CryptoJS.enc.Utf8)
}

αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αžαž˜αžΆαž“ API αž‘αŸ†αž“αŸαžš αžŠαŸ‚αž›αž’αŸ’αž“αž€αž’αžΆαž…αž‡αžΆαžœαž–αŸ’αžšαžΉαžαŸ’αžαž·αž€αžΆαžšαžŽαŸαž˜αž½αž™ - αž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαŸ’αžαžΆαž“αž—αžΆαž–αŸ” αžšαžŠαŸ’αž‹, αž™αŸ„αž„αž‘αŸ…αžαžΆαž˜, αž’αžΆαž…αž‡αžΆ idle, active ΠΈ locked. αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž€αžΆαžšαž‘αŸ†αž“αŸαžš αž’αŸ’αž“αž€αž’αžΆαž…αž€αŸ†αžŽαžαŸ‹αž–αŸαž›αž’αžŸαŸ‹αž–αŸαž› αž αžΎαž™αž€αžΆαžšαž…αžΆαž€αŸ‹αžŸαŸ„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž€αŸ†αžŽαžαŸ‹αž“αŸ…αž–αŸαž›αžŠαŸ‚αž› OS αžαŸ’αž›αž½αž“αžœαžΆαžαŸ’αžšαžΌαžœαž”αžΆαž“αžšαžΆαžšαžΆαŸ†αž„αŸ” αž™αžΎαž„β€‹αž€αŸβ€‹αž“αžΉαž„β€‹αž”αŸ’αžαžΌαžšβ€‹αž‡αž˜αŸ’αžšαžΎαžŸβ€‹αžŸαž˜αŸ’αžšαžΆαž”αŸ‹β€‹αžšαž€αŸ’αžŸαžΆβ€‹αž‘αž»αž€β€‹αž‘αŸ… localStorageαŸ–

import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
import {loadState, saveState} from "./utils/localStorage";

const DEV_MODE = process.env.NODE_ENV !== 'production';
const IDLE_INTERVAL = 30;

setupApp();

function setupApp() {
    const initState = loadState();
    const app = new SignerApp(initState);

    if (DEV_MODE) {
        global.app = app;
    }

    // Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΌΡ‹ явно ΡƒΠ·Ρ‹Π²Π°Π΅ΠΌ ΠΏΠΎΠ»Π΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌΡƒ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠΈΡΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ доступ, reaction ΠΎΡ‚Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π½ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ
    reaction(
        () => ({
            vault: app.store.vault
        }),
        saveState
    );

    // Π’Π°ΠΉΠΌΠ°ΡƒΡ‚ бСздСйствия, ΠΊΠΎΠ³Π΄Π° сработаСт событиС
    extensionApi.idle.setDetectionInterval(IDLE_INTERVAL);
    // Если ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π·Π°Π»ΠΎΡ‡ΠΈΠ» экран ΠΈΠ»ΠΈ бСздСйствовал Π² Ρ‚Π΅Ρ‡Π΅Π½ΠΈΠ΅ ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ ΠΈΠ½Ρ‚Π΅Ρ€Π²Π°Π»Π° Π»ΠΎΡ‡ΠΈΠΌ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
    extensionApi.idle.onStateChanged.addListener(state => {
        if (['locked', 'idle'].indexOf(state) > -1) {
            app.lock()
        }
    });

    // Connect to other contexts
    extensionApi.runtime.onConnect.addListener(connectRemote);

    function connectRemote(remotePort) {
        const processName = remotePort.name;
        const portStream = new PortStream(remotePort);
        if (processName === 'contentscript') {
            const origin = remotePort.sender.url
            app.connectPage(portStream, origin)
        } else {
            app.connectPopup(portStream)
        }
    }
}

αž›αŸαžαž€αžΌαžŠαž˜αž»αž“αž–αŸαž›αž‡αŸ†αž αžΆαž“αž“αŸαŸ‡αž‚αžΊ αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžš

αžŠαžΌαž…αŸ’αž“αŸαŸ‡, αž™αžΎαž„αž˜αž€αžŠαž›αŸ‹αž’αŸ’αžœαžΈαžŠαŸ‚αž›αžŸαŸ†αžαžΆαž“αŸ‹αž”αŸ†αž•αž»αž: αž€αžΆαžšαž”αž„αŸ’αž€αžΎαžαž“αž·αž„αž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆαž›αžΎαž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžšαž“αŸ…αž›αžΎ blockchain αŸ” αž™αžΎαž„αž“αžΉαž„αž”αŸ’αžšαžΎ WAVES blockchain αž“αž·αž„αž”αžŽαŸ’αžŽαžΆαž›αŸαž™ αžšαž›αž€ - αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžš.

αž‡αžΆαžŠαŸ†αž”αžΌαž„ αžŸαžΌαž˜αž”αž“αŸ’αžαŸ‚αž˜αž‘αŸ…αžšαžŠαŸ’αž‹αž“αžΌαžœαž’αžΆαžšαŸαž“αŸƒαžŸαžΆαžšαžŠαŸ‚αž›αž…αžΆαŸ†αž”αžΆαž…αŸ‹αžαŸ’αžšαžΌαžœαž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆ αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€αž”αž“αŸ’αžαŸ‚αž˜αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αžαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž”αž“αŸ’αžαŸ‚αž˜αžŸαžΆαžšαžαŸ’αž˜αžΈ αž”αž‰αŸ’αž‡αžΆαž€αŸ‹αž αžαŸ’αžαž›αŸαžαžΆ αž“αž·αž„αž”αžŠαž·αžŸαŸαž’αŸ–

import {action, observable, reaction} from 'mobx';
import uuid from 'uuid/v4';
import {signTx} from '@waves/waves-transactions'
import {setupDnode} from "./utils/setupDnode";
import {decrypt, encrypt} from "./utils/cryptoUtils";

export class SignerApp {

    ...

    @action
    newMessage(data, origin) {
        // Для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ сообщСния создаСм ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ с id, статусом, Π²Ρ‹Ρ€Π΅ΠΌΠ΅Π½Π΅ΠΌ создания ΠΈ Ρ‚Π΄.
        const message = observable.object({
            id: uuid(), // Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡŽ uuid
            origin, // Origin Π±ΡƒΠ΄Π΅ΠΌ впослСдствии ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ Π² интСрфСйсС
            data, //
            status: 'new', // Бтатусов Π±ΡƒΠ΄Π΅Ρ‚ Ρ‡Π΅Ρ‚Ρ‹Ρ€Π΅: new, signed, rejected ΠΈ failed
            timestamp: Date.now()
        });
        console.log(`new message: ${JSON.stringify(message, null, 2)}`);

        this.store.messages.push(message);

        // Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ промис Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ mobx ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΡ‚ измСнСния сообщСния. Как Ρ‚ΠΎΠ»ΡŒΠΊΠΎ статус помСняСтся ΠΌΡ‹ Π·Π°Ρ€Π΅Π·ΠΎΠ»Π²ΠΈΠΌ Π΅Π³ΠΎ
        return new Promise((resolve, reject) => {
            reaction(
                () => message.status, //Π‘ΡƒΠ΄Π΅ΠΌ ΠΎΠ±ΡΠ΅Ρ€Π²ΠΈΡ‚ΡŒ статус сообщСня
                (status, reaction) => { // Π²Ρ‚ΠΎΡ€ΠΎΠΉ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ это ссылка Π½Π° сам reaction, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ ΡƒΠ½ΠΈΡ‡Ρ‚ΠΎΠΆΡ‚ΡŒ Π²Π½ΡƒΡ‚Ρ€ΠΈ Π²Ρ‹Π·ΠΎΠ²Π°
                    switch (status) {
                        case 'signed':
                            resolve(message.data);
                            break;
                        case 'rejected':
                            reject(new Error('User rejected message'));
                            break;
                        case 'failed':
                            reject(new Error(message.err.message));
                            break;
                        default:
                            return
                    }
                    reaction.dispose()
                }
            )
        })
    }
    @action
    approve(id, keyIndex = 0) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        try {
            message.data = signTx(message.data, this.store.keys[keyIndex]);
            message.status = 'signed'
        } catch (e) {
            message.err = {
                stack: e.stack,
                message: e.message
            };
            message.status = 'failed'
            throw e
        }
    }
    @action
    reject(id) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        message.status = 'rejected'
    }

    ...
}

αž“αŸ…αž–αŸαž›αž™αžΎαž„αž‘αž‘αž½αž›αž”αžΆαž“αžŸαžΆαžšαžαŸ’αž˜αžΈ αž™αžΎαž„αž”αž“αŸ’αžαŸ‚αž˜αž‘αž·αž“αŸ’αž“αž“αŸαž™αž˜αŸαžαžΆαž‘αŸ…αžœαžΆ αž’αŸ’αžœαžΎ observable αž“αž·αž„αž”αž“αŸ’αžαŸ‚αž˜αž‘αŸ… store.messages.

αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž’αŸ’αž“αž€αž˜αž·αž“αž’αŸ’αžœαžΎ observable αžŠαŸ„αž™αžŠαŸƒ αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€ mobx αž“αžΉαž„αž’αŸ’αžœαžΎαžœαžΆαžŠαŸ„αž™αžαŸ’αž›αž½αž“αž―αž„ αž“αŸ…αž–αŸαž›αž”αž“αŸ’αžαŸ‚αž˜αžŸαžΆαžšαž‘αŸ…αž’αžΆαžšαŸαŸ” αž‘αŸ„αŸ‡αž™αŸ‰αžΆαž„αžŽαžΆαž€αŸαžŠαŸ„αž™ αžœαžΆαž“αžΉαž„αž”αž„αŸ’αž€αžΎαžαžœαžαŸ’αžαž»αžαŸ’αž˜αžΈαž˜αž½αž™αžŠαŸ‚αž›αž™αžΎαž„αž“αžΉαž„αž˜αž·αž“αž˜αžΆαž“αž―αž€αžŸαžΆαžšαž™αŸ„αž„ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž™αžΎαž„αž“αžΉαž„αžαŸ’αžšαžΌαžœαž€αžΆαžšαžœαžΆαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‡αŸ†αž αžΆαž“αž”αž“αŸ’αž‘αžΆαž”αŸ‹αŸ”

αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž˜αž€ αž™αžΎαž„αžαŸ’αžšαž‘αž”αŸ‹αž€αžΆαžšαžŸαž“αŸ’αž™αžΆαžŠαŸ‚αž›αžŠαŸ„αŸ‡αžŸαŸ’αžšαžΆαž™αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αžŸαŸ’αžαžΆαž“αž—αžΆαž–αžŸαžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ” αžŸαŸ’αžαžΆαž“αž—αžΆαž–αžαŸ’αžšαžΌαžœαž”αžΆαž“αžαŸ’αžšαž½αžαž–αž·αž“αž·αžαŸ’αž™αžŠαŸ„αž™αž”αŸ’αžšαžαž·αž€αž˜αŸ’αž˜αžŠαŸ‚αž›αž“αžΉαž„ "αžŸαž˜αŸ’αž›αžΆαž”αŸ‹αžαŸ’αž›αž½αž“" αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αžŸαŸ’αžαžΆαž“αž—αžΆαž–αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ”

αž›αŸαžαž€αžΌαžŠαžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αž approve ΠΈ reject αžŸαžΆαž˜αž‰αŸ’αž‰αžŽαžΆαžŸαŸ‹αŸ– αž™αžΎαž„αž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαŸ’αžαžΆαž“αž—αžΆαž–αž“αŸƒαžŸαžΆαžš αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž–αžΈαž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆαž›αžΎαžœαžΆαž”αŸ’αžšαžŸαž·αž“αž”αžΎαž…αžΆαŸ†αž”αžΆαž…αŸ‹αŸ”

αž™αžΎαž„αžŠαžΆαž€αŸ‹ Approve αž“αž·αž„αž”αžŠαž·αžŸαŸαž’αž“αŸ…αž€αŸ’αž“αž»αž„ UI API, newMessage αž“αŸ…αž€αŸ’αž“αž»αž„αž‘αŸ†αž–αŸαžš APIαŸ–

export class SignerApp {
    ...
    popupApi() {
        return {
            addKey: async (key) => this.addKey(key),
            removeKey: async (index) => this.removeKey(index),

            lock: async () => this.lock(),
            unlock: async (password) => this.unlock(password),
            initVault: async (password) => this.initVault(password),

            approve: async (id, keyIndex) => this.approve(id, keyIndex),
            reject: async (id) => this.reject(id)
        }
    }

    pageApi(origin) {
        return {
            signTransaction: async (txParams) => this.newMessage(txParams, origin)
        }
    }

    ...
}

αž₯αž‘αžΌαžœαž“αŸαŸ‡ αžαŸ„αŸ‡αž–αŸ’αž™αžΆαž™αžΆαž˜αž…αž»αŸ‡αž αžαŸ’αžαž›αŸαžαžΆαž›αžΎαž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžšαž‡αžΆαž˜αž½αž™αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž‡αžΆαž‘αžΌαž‘αŸ…αž’αŸ’αžœαžΈαž‚αŸ’αžšαž”αŸ‹αž™αŸ‰αžΆαž„αž‚αžΊαžšαž½αž…αžšαžΆαž›αŸ‹αž αžΎαž™αž’αŸ’αžœαžΈαŸ—αžŠαŸ‚αž›αž“αŸ…αžŸαž›αŸ‹αž‚αžΊ αž”αž“αŸ’αžαŸ‚αž˜ UI αžŸαžΆαž˜αž‰αŸ’αž‰.

UI

αž…αŸ†αžŽαž»αž…αž”αŸ’αžšαž‘αžΆαž€αŸ‹αžαŸ’αžšαžΌαžœαž€αžΆαžšαž…αžΌαž›αž”αŸ’αžšαžΎαžŸαŸ’αžαžΆαž“αž—αžΆαž–αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαŸ” αž“αŸ…αž•αŸ’αž“αŸ‚αž€ 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 αžœαžΆαž„αžΆαž™αžŸαŸ’αžšαž½αž›αžŽαžΆαžŸαŸ‹αž€αŸ’αž“αž»αž„αž€αžΆαžšαž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜αž”αž„αŸ’αž αžΆαž‰αž“αŸ…αž–αŸαž›αž‘αž·αž“αŸ’αž“αž“αŸαž™αž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ” αž™αžΎαž„αž‚αŸ’αžšαžΆαž“αŸ‹αžαŸ‚αž–αŸ’αž™αž½αžšαž’αŸ’αž“αž€αžαž»αž”αžαŸ‚αž„αž’αŸ’αž“αž€αžŸαž„αŸ’αž€αŸαžαž–αžΈαž€αž‰αŸ’αž…αž”αŸ‹ mobx-αž”αŸ’αžšαžαž·αž€αž˜αŸ’αž˜ αž“αŸ…αž›αžΎαžŸαž˜αžΆαžŸαž—αžΆαž‚ αž αžΎαž™αž€αžΆαžš render αž“αžΉαž„αžαŸ’αžšαžΌαžœαž”αžΆαž“αž αŸ…αžŠαŸ„αž™αžŸαŸ’αžœαŸαž™αž”αŸ’αžšαžœαžαŸ’αžαž· αž“αŸ…αž–αŸαž›αžŠαŸ‚αž›αž€αžΆαžšαžŸαž„αŸ’αž€αŸαžαžŽαžΆαž˜αž½αž™αžŠαŸ‚αž›αž™αŸ„αž„αžŠαŸ„αž™αž€αžΆαžšαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαž˜αžΆαžŸαž—αžΆαž‚αŸ” αž’αŸ’αž“αž€αž˜αž·αž“αžαŸ’αžšαžΌαžœαž€αžΆαžš mapStateToProps αž¬αž—αŸ’αž‡αžΆαž”αŸ‹αžŠαžΌαž…αž“αŸ…αž€αŸ’αž“αž»αž„ redux αž‘αŸαŸ” αž’αŸ’αžœαžΈαž‚αŸ’αžšαž”αŸ‹αž™αŸ‰αžΆαž„αžŠαŸ†αžŽαžΎαžšαž€αžΆαžšαž—αŸ’αž›αžΆαž˜αŸ—αž…αŸαž‰αž–αžΈαž”αŸ’αžšαž’αž”αŸ‹αŸ–

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 αž αžΎαž™αž‡αžΌαž“αžŠαŸ†αžŽαžΉαž„αžŠαž›αŸ‹ UI αž“αŸ…αž–αŸαž›αžœαžΆαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαŸ” αžŠαžΎαž˜αŸ’αž”αžΈαž’αŸ’αžœαžΎαžŠαžΌαž…αž“αŸαŸ‡αž™αžΎαž„αž”αž“αŸ’αžαŸ‚αž˜αžœαž·αž’αžΈαžŸαžΆαžŸαŸ’αžšαŸ’αžαž˜αž½αž™αŸ” getState ΠΈ reactionαž€αžΆαžšαž αŸ…αž‘αžΌαžšαžŸαŸαž–αŸ’αž‘ remote.updateState:

import {action, observable, reaction} from 'mobx';
import uuid from 'uuid/v4';
import {signTx} from '@waves/waves-transactions'
import {setupDnode} from "./utils/setupDnode";
import {decrypt, encrypt} from "./utils/cryptoUtils";

export class SignerApp {

    ...

    // public
    getState() {
        return {
            keys: this.store.keys,
            messages: this.store.newMessages,
            initialized: this.store.initialized,
            locked: this.store.locked
        }
    }

    ...

    //
    connectPopup(connectionStream) {
        const api = this.popupApi();
        const dnode = setupDnode(connectionStream, api);

        dnode.once('remote', (remote) => {
            // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ reaction Π½Π° измСнСния стСйта, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ сдСлаСт Π²Ρ‹Π·ΠΎΠ²Π΅Ρ‚ ΡƒΠ΄Π°Π»Π΅Π½Π½Ρƒ ΠΏΡ€ΠΎΡ†Π΅Π΄ΡƒΡ€Ρƒ ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ стСйт Π² ui процСссС
            const updateStateReaction = reaction(
                () => this.getState(),
                (state) => remote.updateState(state),
                // Π’Ρ€Π΅Ρ‚ΡŒΠΈΠΌ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠΌ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹. fireImmediatly Π·Π½Π°Ρ‡ΠΈΡ‚ Ρ‡Ρ‚ΠΎ reaction Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒΡΡ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ Ρ€Π°Π· сразу.
                // Π­Ρ‚ΠΎ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ΅ состояниС. Delay позволяСт ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ debounce
                {fireImmediately: true, delay: 500}
            );
            // Π£Π΄Π°Π»ΠΈΠΌ подписку ΠΏΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°
            dnode.once('end', () => updateStateReaction.dispose())

        })
    }

    ...
}

αž“αŸ…αž–αŸαž›αž‘αž‘αž½αž›αž”αžΆαž“αžœαžαŸ’αžαž»αž˜αž½αž™αŸ” remote αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αž„αŸ’αž€αžΎαž reaction αžŠαžΎαž˜αŸ’αž”αžΈαž•αŸ’αž›αžΆαžŸαŸ‹αž”αŸ’αžαžΌαžšαžŸαŸ’αžαžΆαž“αž—αžΆαž–αžŠαŸ‚αž›αž αŸ…αž˜αž»αžαž„αžΆαžšαž“αŸ…αž•αŸ’αž“αŸ‚αž€ UI αŸ”

αž€αžΆαžšαž”αŸ‰αŸ‡αž…αž»αž„αž€αŸ’αžšαŸ„αž™αž‚αžΊαžŠαžΎαž˜αŸ’αž”αžΈαž”αž“αŸ’αžαŸ‚αž˜αž€αžΆαžšαž”αž„αŸ’αž αžΆαž‰αžŸαžΆαžšαžαŸ’αž˜αžΈαž“αŸ…αž›αžΎαžšαžΌαž”αžαŸ†αžŽαžΆαž„αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αŸ–

function setupApp() {
...

    // Reaction Π½Π° выставлСниС тСкста Π±Π΅Π΄ΠΆΠ°.
    reaction(
        () => app.store.newMessages.length > 0 ? app.store.newMessages.length.toString() : '',
        text => extensionApi.browserAction.setBadgeText({text}),
        {fireImmediately: true}
    );

...
}

αžŠαžΌαž…αŸ’αž“αŸαŸ‡αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‚αžΊαžšαž½αž…αžšαžΆαž›αŸ‹αŸ” αž‚αŸαž αž‘αŸ†αž–αŸαžšαž’αžΆαž…αžŸαŸ’αž“αžΎαžŸαž»αŸ†αž αžαŸ’αžαž›αŸαžαžΆαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžšαŸ–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αžŸαžšαžŸαŸαžšαž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž»αž€αžšαž€αžŸαž»αžœαžαŸ’αžαž·αž—αžΆαž–

αž›αŸαžαž€αžΌαžŠαž˜αžΆαž“αž“αŸ…αž‘αžΈαž“αŸαŸ‡ αžαŸ†αžŽαž—αŸ’αž‡αžΆαž”αŸ‹.

αžŸαŸαž…αž€αŸ’αžαžΈαžŸαž“αŸ’αž“αž·αžŠαŸ’αž‹αžΆαž“

αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž’αŸ’αž“αž€αž”αžΆαž“αž’αžΆαž“αž’αžαŸ’αžαž”αž‘αžŠαž›αŸ‹αž…αž”αŸ‹αž αžΎαž™ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž“αŸ…αžαŸ‚αž˜αžΆαž“αž…αž˜αŸ’αž„αž›αŸ‹ αž’αŸ’αž“αž€αž’αžΆαž…αžŸαž½αžšαž–αž½αž€αž‚αŸαž“αŸ… αžƒαŸ’αž›αžΆαŸ†αž„αž‡αžΆαž˜αž½αž™αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜. αž“αŸ…αž‘αžΈαž“αŸ„αŸ‡αž’αŸ’αž“αž€αž€αŸαž“αžΉαž„αžšαž€αžƒαžΎαž‰αž€αžΆαžšαžŸαž“αŸ’αž™αžΆαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž‡αŸ†αž αžΆαž“αž“αžΈαž˜αž½αž™αŸ—αžŠαŸ‚αž›αž”αžΆαž“αž€αŸ†αžŽαžαŸ‹αž•αž„αžŠαŸ‚αžšαŸ”

αž αžΎαž™αž”αŸ’αžšαžŸαž·αž“αž”αžΎαž’αŸ’αž“αž€αž…αžΆαž”αŸ‹αž’αžΆαžšαž˜αŸ’αž˜αžŽαŸαž€αŸ’αž“αž»αž„αž€αžΆαžšαž˜αžΎαž›αž€αžΌαžŠαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž•αŸ’αž“αŸ‚αž€αž”αž“αŸ’αžαŸ‚αž˜αž–αž·αžαž”αŸ’αžšαžΆαž€αžŠαž“αŸ„αŸ‡ αž’αŸ’αž“αž€αž’αžΆαž…αžŸαŸ’αžœαŸ‚αž„αžšαž€αžœαžΆαž”αžΆαž“ αž“αŸ…αž‘αžΈαž“αŸαŸ‡.

αž›αŸαžαž€αžΌαžŠ αžƒαŸ’αž›αžΆαŸ†αž„ αž“αž·αž„αž€αžΆαžšαž–αž·αž–αžŽαŸŒαž“αžΆαž€αžΆαžšαž„αžΆαžšαž–αžΈ αžŸαŸŠαžΈαž˜αŸ‰αžΆαžšαŸ‰αŸαž›αŸ”

αž”αŸ’αžšαž—αž–: www.habr.com

αž”αž“αŸ’αžαŸ‚αž˜αž˜αžαž·αž™αŸ„αž”αž›αŸ‹