แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒกแƒแƒ”แƒ แƒ—แƒ "แƒ™แƒšแƒ˜แƒ”แƒœแƒข-แƒกแƒ”แƒ แƒ•แƒ”แƒ แƒ˜แƒก" แƒแƒ แƒฅแƒ˜แƒขแƒ”แƒฅแƒขแƒฃแƒ แƒ˜แƒกแƒ’แƒแƒœ แƒ’แƒแƒœแƒกแƒฎแƒ•แƒแƒ•แƒ”แƒ‘แƒ˜แƒ—, แƒ“แƒ”แƒชแƒ”แƒœแƒขแƒ แƒแƒšแƒ˜แƒ–แƒ”แƒ‘แƒฃแƒšแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ”แƒ‘แƒ˜ แƒฎแƒแƒกแƒ˜แƒแƒ—แƒ“แƒ”แƒ‘แƒ:

  • แƒแƒ  แƒแƒ แƒ˜แƒก แƒกแƒแƒญแƒ˜แƒ แƒ แƒ›แƒแƒœแƒแƒชแƒ”แƒ›แƒ—แƒ แƒ‘แƒแƒ–แƒ˜แƒก แƒจแƒ”แƒœแƒแƒฎแƒ•แƒ แƒ›แƒแƒ›แƒฎแƒ›แƒแƒ แƒ”แƒ‘แƒšแƒ˜แƒก แƒจแƒ”แƒกแƒ•แƒšแƒ”แƒ‘แƒ˜แƒ—แƒ แƒ“แƒ แƒžแƒแƒ แƒแƒšแƒ”แƒ‘แƒ˜แƒ—. แƒฌแƒ•แƒ“แƒแƒ›แƒ˜แƒก แƒ˜แƒœแƒคแƒแƒ แƒ›แƒแƒชแƒ˜แƒ แƒ˜แƒœแƒแƒฎแƒ”แƒ‘แƒ แƒ”แƒฅแƒกแƒ™แƒšแƒฃแƒ–แƒ˜แƒฃแƒ แƒแƒ“ แƒ—แƒแƒ•แƒแƒ“ แƒ›แƒแƒ›แƒฎแƒ›แƒแƒ แƒ”แƒ‘แƒšแƒ”แƒ‘แƒ˜แƒก แƒ›แƒ˜แƒ”แƒ  แƒ“แƒ แƒ›แƒแƒ—แƒ˜ แƒแƒ•แƒ—แƒ”แƒœแƒขแƒฃแƒ แƒแƒ‘แƒ˜แƒก แƒ“แƒแƒ“แƒแƒกแƒขแƒฃแƒ แƒ”แƒ‘แƒ แƒฎแƒ“แƒ”แƒ‘แƒ แƒžแƒ แƒแƒขแƒแƒ™แƒแƒšแƒ˜แƒก แƒ“แƒแƒœแƒ”แƒ–แƒ”.
  • แƒแƒ  แƒแƒ แƒ˜แƒก แƒกแƒแƒญแƒ˜แƒ แƒ แƒกแƒ”แƒ แƒ•แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ. แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒšแƒแƒ’แƒ˜แƒ™แƒ แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒจแƒ”แƒกแƒ แƒฃแƒšแƒ“แƒ”แƒก แƒ‘แƒšแƒแƒ™แƒฉแƒ”แƒ˜แƒœ แƒฅแƒกแƒ”แƒšแƒจแƒ˜, แƒกแƒแƒ“แƒแƒช แƒจแƒ”แƒกแƒแƒซแƒšแƒ”แƒ‘แƒ”แƒšแƒ˜แƒ แƒกแƒแƒญแƒ˜แƒ แƒ แƒ แƒแƒแƒ“แƒ”แƒœแƒแƒ‘แƒ˜แƒก แƒ›แƒแƒœแƒแƒชแƒ”แƒ›แƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒœแƒแƒฎแƒ•แƒ.

แƒ›แƒแƒ›แƒฎแƒ›แƒแƒ แƒ”แƒ‘แƒšแƒ˜แƒก แƒ’แƒแƒกแƒแƒฆแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒแƒ แƒ˜แƒก 2 แƒจแƒ”แƒ“แƒแƒ แƒ”แƒ‘แƒ˜แƒ— แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒกแƒแƒชแƒแƒ•แƒ˜ - แƒขแƒ”แƒฅแƒœแƒ˜แƒ™แƒ˜แƒก แƒกแƒแƒคแƒฃแƒšแƒ”แƒ”แƒ‘แƒ˜ แƒ“แƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜. แƒขแƒ”แƒฅแƒœแƒ˜แƒ™แƒ˜แƒก แƒกแƒแƒคแƒฃแƒšแƒ”แƒ”แƒ‘แƒ˜ แƒซแƒ˜แƒ แƒ˜แƒ—แƒแƒ“แƒแƒ“ แƒฃแƒ™แƒ˜แƒ“แƒฃแƒ แƒ”แƒกแƒแƒ“ แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒแƒ, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ แƒ—แƒฃแƒšแƒ˜ แƒ’แƒแƒ›แƒแƒกแƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ”แƒšแƒ˜ แƒ“แƒ แƒจแƒแƒ แƒก แƒแƒ แƒ˜แƒก แƒฃแƒคแƒแƒกแƒ, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜ แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒแƒ”แƒ‘แƒ˜แƒกแƒ แƒ“แƒ แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒก แƒกแƒ˜แƒ›แƒแƒ แƒขแƒ˜แƒ•แƒ˜แƒก แƒจแƒ”แƒกแƒแƒœแƒ˜แƒจแƒœแƒแƒ•แƒ˜ แƒ™แƒแƒ›แƒ‘แƒ˜แƒœแƒแƒชแƒ˜แƒแƒ แƒ“แƒ แƒแƒกแƒ”แƒ•แƒ” แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ˜แƒงแƒแƒก แƒกแƒ แƒฃแƒšแƒ˜แƒแƒ“ แƒฃแƒคแƒแƒกแƒ แƒกแƒแƒ‘แƒแƒšแƒแƒ แƒ›แƒแƒ›แƒฎแƒ›แƒแƒ แƒ”แƒ‘แƒšแƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก.

แƒแƒ› แƒงแƒ•แƒ”แƒšแƒแƒคแƒ แƒ˜แƒก แƒ’แƒแƒ—แƒ•แƒแƒšแƒ˜แƒกแƒฌแƒ˜แƒœแƒ”แƒ‘แƒ˜แƒ—, แƒฉแƒ•แƒ”แƒœ แƒ’แƒ•แƒ˜แƒœแƒ“แƒแƒ“แƒ แƒจแƒ”แƒ’แƒ•แƒ”แƒฅแƒ›แƒœแƒ แƒงแƒ•แƒ”แƒšแƒแƒ–แƒ” แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒแƒ›แƒแƒ แƒขแƒ˜แƒ•แƒ”แƒ‘แƒก แƒ“แƒ”แƒชแƒ”แƒœแƒขแƒ แƒแƒšแƒ˜แƒ–แƒ”แƒ‘แƒฃแƒšแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ”แƒ‘แƒ˜แƒก แƒ’แƒแƒœแƒ•แƒ˜แƒ—แƒแƒ แƒ”แƒ‘แƒแƒก แƒขแƒ แƒแƒœแƒ–แƒแƒฅแƒชแƒ˜แƒ”แƒ‘แƒ—แƒแƒœ แƒ“แƒ แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒ”แƒ‘แƒ—แƒแƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ˜แƒก แƒ›แƒแƒ แƒขแƒ˜แƒ•แƒ˜ API-แƒก แƒ›แƒ˜แƒฌแƒแƒ“แƒ”แƒ‘แƒ˜แƒ—.
แƒแƒ› แƒ’แƒแƒ›แƒแƒชแƒ“แƒ˜แƒšแƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒกแƒแƒฎแƒ”แƒ‘ แƒฅแƒ•แƒ”แƒ›แƒแƒ— แƒ›แƒแƒ’แƒ˜แƒงแƒ•แƒ”แƒ‘แƒ˜แƒ—.

แƒกแƒขแƒแƒขแƒ˜แƒ แƒจแƒ”แƒ˜แƒชแƒแƒ•แƒก แƒœแƒแƒ‘แƒ˜แƒฏ-แƒœแƒแƒ‘แƒ˜แƒฏ แƒ˜แƒœแƒกแƒขแƒ แƒฃแƒฅแƒชแƒ˜แƒ”แƒ‘แƒก, แƒ—แƒฃ แƒ แƒแƒ’แƒแƒ  แƒฃแƒœแƒ“แƒ แƒ“แƒแƒฌแƒ”แƒ แƒแƒ— แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ, แƒ™แƒแƒ“แƒ˜แƒก แƒ›แƒแƒ’แƒแƒšแƒ˜แƒ—แƒ”แƒ‘แƒ˜แƒ— แƒ“แƒ แƒ”แƒ™แƒ แƒแƒœแƒ˜แƒก แƒแƒœแƒแƒ‘แƒ”แƒญแƒ“แƒ”แƒ‘แƒ˜แƒ—. แƒ—แƒฅแƒ•แƒ”แƒœ แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒ˜แƒžแƒแƒ•แƒแƒ— แƒงแƒ•แƒ”แƒšแƒ แƒ™แƒแƒ“แƒ˜ แƒกแƒแƒชแƒแƒ•แƒ”แƒ‘แƒ˜. แƒ—แƒ˜แƒ—แƒแƒ”แƒฃแƒšแƒ˜ แƒฉแƒแƒ“แƒ”แƒœแƒ แƒšแƒแƒ’แƒ˜แƒ™แƒฃแƒ แƒแƒ“ แƒจแƒ”แƒ”แƒกแƒแƒ‘แƒแƒ›แƒ”แƒ‘แƒ แƒแƒ› แƒกแƒขแƒแƒขแƒ˜แƒ˜แƒก แƒœแƒแƒฌแƒ˜แƒšแƒก.

แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒ™แƒšแƒ” แƒ˜แƒกแƒขแƒแƒ แƒ˜แƒ

แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜ แƒ“แƒ˜แƒ“แƒ˜ แƒฎแƒแƒœแƒ˜แƒ แƒแƒ แƒกแƒ”แƒ‘แƒแƒ‘แƒก. แƒ˜แƒกแƒ˜แƒœแƒ˜ แƒ’แƒแƒ›แƒแƒฉแƒœแƒ“แƒœแƒ”แƒœ Internet Explorer-แƒจแƒ˜ 1999 แƒฌแƒ”แƒšแƒก, Firefox-แƒจแƒ˜ 2004 แƒฌแƒ”แƒšแƒก. แƒ—แƒฃแƒ›แƒชแƒ, แƒซแƒแƒšแƒ˜แƒแƒœ แƒ“แƒ˜แƒ“แƒ˜ แƒฎแƒœแƒ˜แƒก แƒ’แƒแƒœแƒ›แƒแƒ•แƒšแƒแƒ‘แƒแƒจแƒ˜ แƒแƒ  แƒแƒ แƒกแƒ”แƒ‘แƒแƒ‘แƒ“แƒ แƒ”แƒ แƒ—แƒ˜ แƒกแƒขแƒแƒœแƒ“แƒแƒ แƒขแƒ˜ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก.

แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ˜แƒ—แƒฅแƒ•แƒแƒก, แƒ แƒแƒ› แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ—แƒแƒœ แƒ”แƒ แƒ—แƒแƒ“ แƒ’แƒแƒ›แƒแƒฉแƒœแƒ“แƒ Google Chrome-แƒ˜แƒก แƒ›แƒ”แƒแƒ—แƒฎแƒ” แƒ•แƒ”แƒ แƒกแƒ˜แƒแƒจแƒ˜. แƒ แƒ แƒ—แƒฅแƒ›แƒ แƒฃแƒœแƒ“แƒ, แƒ›แƒแƒจแƒ˜แƒœ แƒแƒ  แƒแƒ แƒกแƒ”แƒ‘แƒแƒ‘แƒ“แƒ แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒ, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ”แƒก แƒ˜แƒงแƒ Chrome API, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ’แƒแƒฎแƒ“แƒ แƒ›แƒ˜แƒกแƒ˜ แƒกแƒแƒคแƒฃแƒซแƒ•แƒ”แƒšแƒ˜: แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ‘แƒแƒ–แƒ แƒ˜แƒก แƒฃแƒ›แƒ”แƒขแƒ”แƒกแƒ˜ แƒœแƒแƒฌแƒ˜แƒšแƒ˜ แƒ“แƒแƒ˜แƒžแƒงแƒ แƒ แƒ“แƒ แƒฉแƒแƒจแƒ”แƒœแƒ”แƒ‘แƒฃแƒšแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒฆแƒแƒ–แƒ˜แƒ แƒฐแƒฅแƒแƒœแƒ“แƒ, Chrome-แƒ›แƒ แƒ แƒ”แƒแƒšแƒฃแƒ แƒแƒ“ แƒ“แƒแƒแƒฌแƒ”แƒกแƒ แƒกแƒขแƒแƒœแƒ“แƒแƒ แƒขแƒ˜ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก.

Mozilla-แƒก แƒฐแƒฅแƒแƒœแƒ“แƒ แƒกแƒแƒ™แƒฃแƒ—แƒแƒ แƒ˜ แƒกแƒขแƒแƒœแƒ“แƒแƒ แƒขแƒ˜, แƒ›แƒแƒ’แƒ แƒแƒ› Chrome-แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒžแƒแƒžแƒฃแƒšแƒแƒ แƒแƒ‘แƒ˜แƒก แƒ’แƒแƒ—แƒ•แƒแƒšแƒ˜แƒกแƒฌแƒ˜แƒœแƒ”แƒ‘แƒ˜แƒ—, แƒ™แƒแƒ›แƒžแƒแƒœแƒ˜แƒแƒ› แƒ’แƒแƒ“แƒแƒฌแƒงแƒ•แƒ˜แƒขแƒ แƒจแƒ”แƒ”แƒฅแƒ›แƒœแƒ แƒ—แƒแƒ•แƒกแƒ”แƒ‘แƒแƒ“แƒ˜ API. 2015 แƒฌแƒ”แƒšแƒก, Mozilla-แƒก แƒ˜แƒœแƒ˜แƒชแƒ˜แƒแƒขแƒ˜แƒ•แƒ˜แƒ—, แƒ›แƒกแƒแƒคแƒšแƒ˜แƒ แƒฅแƒกแƒ”แƒšแƒ˜แƒก แƒ™แƒแƒœแƒกแƒแƒ แƒชแƒ˜แƒฃแƒ›แƒ˜แƒก (W3C) แƒคแƒแƒ แƒ’แƒšแƒ”แƒ‘แƒจแƒ˜ แƒจแƒ”แƒ˜แƒฅแƒ›แƒœแƒ แƒกแƒžแƒ”แƒชแƒ˜แƒแƒšแƒฃแƒ แƒ˜ แƒฏแƒ’แƒฃแƒคแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ“แƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒ”แƒ‘แƒ–แƒ”.

แƒกแƒแƒคแƒฃแƒซแƒ•แƒšแƒแƒ“ แƒ˜แƒฅแƒœแƒ แƒ›แƒ˜แƒฆแƒ”แƒ‘แƒฃแƒšแƒ˜ Chrome-แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒแƒ แƒกแƒ”แƒ‘แƒฃแƒšแƒ˜ API แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜. แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ แƒ’แƒแƒœแƒฎแƒแƒ แƒชแƒ˜แƒ”แƒšแƒ“แƒ Microsoft-แƒ˜แƒก แƒ›แƒฎแƒแƒ แƒ“แƒแƒญแƒ”แƒ แƒ˜แƒ— (แƒ’แƒฃแƒ’แƒšแƒ›แƒ แƒฃแƒแƒ แƒ˜ แƒ—แƒฅแƒ•แƒ แƒกแƒขแƒแƒœแƒ“แƒแƒ แƒขแƒ˜แƒก แƒจแƒ”แƒ›แƒฃแƒจแƒแƒ•แƒ”แƒ‘แƒแƒจแƒ˜ แƒ›แƒแƒœแƒแƒฌแƒ˜แƒšแƒ”แƒแƒ‘แƒแƒ–แƒ”), แƒ แƒ˜แƒก แƒจแƒ”แƒ“แƒ”แƒ’แƒแƒ“แƒแƒช แƒ’แƒแƒ›แƒแƒฉแƒœแƒ“แƒ แƒžแƒ แƒแƒ”แƒฅแƒขแƒ˜ แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒ”แƒ‘แƒ˜.

แƒคแƒแƒ แƒ›แƒแƒšแƒฃแƒ แƒแƒ“, แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒแƒก แƒ›แƒฎแƒแƒ แƒก แƒฃแƒญแƒ”แƒ แƒก Edge, Firefox แƒ“แƒ Opera (แƒ’แƒแƒ˜แƒ—แƒ•แƒแƒšแƒ˜แƒกแƒฌแƒ˜แƒœแƒ”แƒ—, แƒ แƒแƒ› Chrome แƒแƒ  แƒแƒ แƒ˜แƒก แƒแƒ› แƒกแƒ˜แƒแƒจแƒ˜). แƒกแƒ˜แƒœแƒแƒ›แƒ“แƒ•แƒ˜แƒšแƒ”แƒจแƒ˜, แƒกแƒขแƒแƒœแƒ“แƒแƒ แƒขแƒ˜ แƒ“แƒ˜แƒ“แƒฌแƒ˜แƒšแƒแƒ“ แƒ—แƒแƒ•แƒกแƒ”แƒ‘แƒแƒ“แƒ˜แƒ Chrome-แƒ—แƒแƒœ, แƒ แƒแƒ“แƒ’แƒแƒœ แƒ˜แƒก แƒ แƒ”แƒแƒšแƒฃแƒ แƒแƒ“ แƒ˜แƒฌแƒ”แƒ แƒ”แƒ‘แƒ แƒ›แƒ˜แƒกแƒ˜ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒกแƒแƒคแƒฃแƒซแƒ•แƒ”แƒšแƒ–แƒ”. แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒ›แƒ”แƒขแƒ˜ แƒฌแƒแƒ˜แƒ™แƒ˜แƒ—แƒฎแƒแƒ— WebExtensions API-แƒ˜แƒก แƒจแƒ”แƒกแƒแƒฎแƒ”แƒ‘ แƒแƒฅ.

แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒกแƒขแƒ แƒฃแƒฅแƒขแƒฃแƒ แƒ

แƒ”แƒ แƒ—แƒแƒ“แƒ”แƒ แƒ—แƒ˜ แƒคแƒแƒ˜แƒšแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒกแƒแƒญแƒ˜แƒ แƒแƒ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก, แƒแƒ แƒ˜แƒก manifest (manifest.json). แƒ˜แƒก แƒแƒกแƒ”แƒ•แƒ” แƒแƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก "แƒจแƒ”แƒกแƒ•แƒšแƒ˜แƒก แƒฌแƒ”แƒ แƒขแƒ˜แƒšแƒ˜".

แƒ›แƒแƒœแƒ˜แƒคแƒ”แƒกแƒขแƒ˜

แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒ›แƒ˜แƒฎแƒ”แƒ“แƒ•แƒ˜แƒ—, manifest แƒคแƒแƒ˜แƒšแƒ˜ แƒแƒ แƒ˜แƒก แƒ›แƒแƒฅแƒ›แƒ”แƒ“แƒ˜ JSON แƒคแƒแƒ˜แƒšแƒ˜. แƒ›แƒแƒœแƒ˜แƒคแƒ”แƒกแƒขแƒ˜แƒก แƒ™แƒšแƒแƒ•แƒ˜แƒจแƒ”แƒ‘แƒ˜แƒก แƒกแƒ แƒฃแƒšแƒ˜ แƒแƒฆแƒฌแƒ”แƒ แƒ แƒ˜แƒœแƒคแƒแƒ แƒ›แƒแƒชแƒ˜แƒ แƒ˜แƒ›แƒ˜แƒก แƒจแƒ”แƒกแƒแƒฎแƒ”แƒ‘, แƒ—แƒฃ แƒ แƒแƒ›แƒ”แƒšแƒ˜ แƒ™แƒšแƒแƒ•แƒ˜แƒจแƒ”แƒ‘แƒ˜แƒ แƒ›แƒฎแƒแƒ แƒ“แƒแƒญแƒ”แƒ แƒ˜แƒšแƒ˜, แƒ แƒแƒ›แƒ”แƒš แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒจแƒ˜ แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒœแƒแƒฎแƒแƒ— แƒแƒฅ.

แƒ’แƒแƒกแƒแƒฆแƒ”แƒ‘แƒ”แƒ‘แƒ˜, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒแƒ  แƒแƒ แƒ˜แƒก แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒแƒชแƒ˜แƒแƒจแƒ˜ โ€žแƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒโ€œ แƒ˜แƒ’แƒœแƒแƒ แƒ˜แƒ แƒ”แƒ‘แƒฃแƒšแƒ˜ แƒ˜แƒงแƒแƒก (แƒ แƒแƒ’แƒแƒ แƒช Chrome, แƒแƒกแƒ”แƒ•แƒ” Firefox แƒแƒชแƒœแƒแƒ‘แƒ”แƒ‘แƒ”แƒœ แƒจแƒ”แƒชแƒ“แƒแƒ›แƒ”แƒ‘แƒก, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜ แƒแƒ’แƒ แƒซแƒ”แƒšแƒ”แƒ‘แƒ”แƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒแƒก).

แƒ“แƒ แƒ›แƒ˜แƒœแƒ“แƒ แƒ’แƒแƒ•แƒแƒ›แƒแƒฎแƒ•แƒ˜แƒšแƒ แƒงแƒฃแƒ แƒแƒ“แƒฆแƒ”แƒ‘แƒ แƒ แƒแƒ›แƒ“แƒ”แƒœแƒ˜แƒ›แƒ” แƒžแƒฃแƒœแƒฅแƒขแƒ–แƒ”.

  1. แƒคแƒแƒœแƒ–แƒ” - แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ›แƒแƒ˜แƒชแƒแƒ•แƒก แƒจแƒ”แƒ›แƒ“แƒ”แƒ’ แƒ•แƒ”แƒšแƒ”แƒ‘แƒก:
    1. แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜ โ€” แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒกแƒ˜แƒ•แƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒจแƒ”แƒกแƒ แƒฃแƒšแƒ“แƒ”แƒ‘แƒ แƒคแƒแƒœแƒฃแƒ  แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒจแƒ˜ (แƒแƒ›แƒแƒ–แƒ” แƒชแƒแƒขแƒ แƒ›แƒแƒ’แƒ•แƒ˜แƒแƒœแƒ”แƒ‘แƒ˜แƒ— แƒ•แƒ˜แƒกแƒแƒฃแƒ‘แƒ แƒ”แƒ‘แƒ—);
    2. แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ” โ€” แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜แƒก แƒœแƒแƒชแƒ•แƒšแƒแƒ“, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒจแƒ”แƒกแƒ แƒฃแƒšแƒ“แƒ”แƒ‘แƒ แƒชแƒแƒ แƒ˜แƒ”แƒš แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ”, แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒ›แƒ˜แƒฃแƒ—แƒ˜แƒ—แƒแƒ— html แƒจแƒ˜แƒœแƒแƒแƒ แƒกแƒ˜แƒ—. แƒแƒ› แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜ แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ˜แƒก แƒ•แƒ”แƒšแƒ˜ แƒ˜แƒ’แƒœแƒแƒ แƒ˜แƒ แƒ”แƒ‘แƒฃแƒšแƒ˜ แƒ˜แƒฅแƒœแƒ”แƒ‘แƒ แƒ“แƒ แƒกแƒแƒญแƒ˜แƒ แƒ แƒ˜แƒฅแƒœแƒ”แƒ‘แƒ แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜แƒก แƒฉแƒแƒกแƒ›แƒ แƒจแƒ˜แƒœแƒแƒแƒ แƒกแƒ˜แƒก แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ”;
    3. แƒแƒ แƒกแƒ”แƒ‘แƒแƒ‘แƒก โ€” แƒ‘แƒ˜แƒœแƒแƒ แƒฃแƒšแƒ˜ แƒ“แƒ แƒแƒจแƒ, แƒ—แƒฃ แƒแƒ  แƒแƒ แƒ˜แƒก แƒ›แƒ˜แƒ—แƒ˜แƒ—แƒ”แƒ‘แƒฃแƒšแƒ˜, แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜ โ€žแƒ›แƒแƒ™แƒšแƒแƒ•แƒกโ€œ แƒคแƒแƒœแƒฃแƒ  แƒžแƒ แƒแƒชแƒ”แƒกแƒก, แƒ แƒแƒชแƒ แƒฉแƒแƒ—แƒ•แƒšแƒ˜แƒก, แƒ แƒแƒ› แƒ˜แƒก แƒแƒ แƒแƒคแƒ”แƒ แƒก แƒแƒ™แƒ”แƒ—แƒ”แƒ‘แƒก แƒ“แƒ แƒกแƒแƒญแƒ˜แƒ แƒแƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜ แƒ’แƒแƒ“แƒแƒขแƒ•แƒ˜แƒ แƒ—แƒแƒ•แƒก แƒ›แƒแƒก. แƒฌแƒ˜แƒœแƒแƒแƒฆแƒ›แƒ“แƒ”แƒ’ แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜, แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜ แƒ’แƒแƒœแƒ˜แƒขแƒ•แƒ˜แƒ แƒ—แƒ”แƒ‘แƒ แƒ›แƒฎแƒแƒšแƒแƒ“ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ“แƒแƒฎแƒฃแƒ แƒ•แƒ˜แƒกแƒแƒก. แƒแƒ  แƒแƒ แƒ˜แƒก แƒ›แƒฎแƒแƒ แƒ“แƒแƒญแƒ”แƒ แƒ˜แƒšแƒ˜ Firefox-แƒจแƒ˜.
  2. แƒจแƒ˜แƒœแƒแƒแƒ แƒกแƒ˜_แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜ - แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒกแƒ˜แƒ•แƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒกแƒแƒจแƒฃแƒแƒšแƒ”แƒ‘แƒแƒก แƒ’แƒแƒซแƒšแƒ”แƒ•แƒ— แƒฉแƒแƒขแƒ•แƒ˜แƒ แƒ—แƒแƒ— แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜ แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒ•แƒ”แƒ‘ แƒ’แƒ•แƒ”แƒ แƒ“แƒ”แƒ‘แƒ–แƒ”. แƒ—แƒ˜แƒ—แƒแƒ”แƒฃแƒšแƒ˜ แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜ แƒจแƒ”แƒ˜แƒชแƒแƒ•แƒก แƒจแƒ”แƒ›แƒ“แƒ”แƒ’ แƒ›แƒœแƒ˜แƒจแƒ•แƒœแƒ”แƒšแƒแƒ•แƒแƒœ แƒ•แƒ”แƒšแƒ”แƒ‘แƒก:
    1. แƒ›แƒแƒขแƒฉแƒ”แƒ‘แƒ˜ - แƒœแƒ˜แƒ›แƒฃแƒจแƒ˜แƒก url, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ’แƒแƒœแƒกแƒแƒ–แƒฆแƒ•แƒ แƒแƒ•แƒก, แƒจแƒ”แƒ“แƒ˜แƒก แƒ—แƒฃ แƒแƒ แƒ แƒ™แƒแƒœแƒ™แƒ แƒ”แƒขแƒฃแƒšแƒ˜ แƒจแƒ˜แƒœแƒแƒแƒ แƒกแƒ˜แƒก แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ˜.
    2. js โ€” แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ”แƒ‘แƒ˜แƒก แƒกแƒ˜แƒ, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒฉแƒแƒ˜แƒขแƒ•แƒ˜แƒ แƒ—แƒ”แƒ‘แƒ แƒแƒ› แƒ›แƒแƒขแƒฉแƒจแƒ˜;
    3. exclude_matches - แƒ’แƒแƒ›แƒแƒ แƒ˜แƒชแƒฎแƒแƒ•แƒก แƒ›แƒแƒ”แƒ“แƒœแƒ˜แƒ“แƒแƒœ match URL-แƒ”แƒ‘แƒ˜, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒ แƒแƒ› แƒ•แƒ”แƒšแƒก.
  3. แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜_แƒ›แƒแƒฅแƒ›แƒ”แƒ“แƒ”แƒ‘แƒ - แƒ แƒ”แƒแƒšแƒฃแƒ แƒแƒ“ แƒแƒ แƒ˜แƒก แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒžแƒแƒกแƒฃแƒฎแƒ˜แƒกแƒ›แƒ’แƒ”แƒ‘แƒ”แƒšแƒ˜แƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒจแƒ˜ แƒ›แƒ˜แƒกแƒแƒ›แƒแƒ แƒ—แƒ”แƒ‘แƒ˜แƒก แƒ–แƒแƒšแƒ˜แƒก แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒ— แƒ’แƒแƒ›แƒแƒกแƒแƒฎแƒฃแƒš แƒฎแƒแƒขแƒ–แƒ” แƒ“แƒ แƒ›แƒแƒกแƒ—แƒแƒœ แƒฃแƒ แƒ—แƒ˜แƒ”แƒ แƒ—แƒฅแƒ›แƒ”แƒ“แƒ”แƒ‘แƒแƒ–แƒ”. แƒ˜แƒก แƒแƒกแƒ”แƒ•แƒ” แƒกแƒแƒจแƒฃแƒแƒšแƒ”แƒ‘แƒแƒก แƒ’แƒแƒซแƒšแƒ”แƒ•แƒ— แƒแƒฉแƒ•แƒ”แƒœแƒแƒ— แƒแƒ›แƒแƒ›แƒฎแƒขแƒแƒ แƒ˜ แƒคแƒแƒœแƒฏแƒแƒ แƒ, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ’แƒแƒœแƒ˜แƒกแƒแƒ–แƒฆแƒ•แƒ แƒ”แƒ‘แƒ แƒ—แƒฅแƒ•แƒ”แƒœแƒ˜ แƒกแƒแƒ™แƒฃแƒ—แƒแƒ แƒ˜ HTML, CSS แƒ“แƒ JS แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒ—.
    1. default_popup โ€” HTML แƒคแƒแƒ˜แƒšแƒ˜แƒก แƒ’แƒ–แƒ แƒแƒ›แƒแƒ›แƒฎแƒขแƒแƒ แƒ˜ แƒ˜แƒœแƒขแƒ”แƒ แƒคแƒ”แƒ˜แƒกแƒ˜แƒ—, แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒจแƒ”แƒ˜แƒชแƒแƒ•แƒ“แƒ”แƒก CSS แƒ“แƒ JS.
  4. แƒฃแƒคแƒšแƒ”แƒ‘แƒ”แƒ‘แƒ˜ โ€” แƒ›แƒแƒกแƒ˜แƒ•แƒ˜ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒฃแƒคแƒšแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒ แƒ—แƒ•แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก. แƒแƒ แƒกแƒ”แƒ‘แƒแƒ‘แƒก 3 แƒกแƒแƒฎแƒ˜แƒก แƒฃแƒคแƒšแƒ”แƒ‘แƒ, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒ“แƒ”แƒขแƒแƒšแƒฃแƒ แƒแƒ“ แƒแƒ แƒ˜แƒก แƒแƒฆแƒฌแƒ”แƒ แƒ˜แƒšแƒ˜ แƒแƒฅ
  5. แƒ•แƒ”แƒ‘_แƒฎแƒ”แƒšแƒ›แƒ˜แƒกแƒแƒฌแƒ•แƒ“แƒแƒ›แƒ˜_แƒ แƒ”แƒกแƒฃแƒ แƒกแƒ”แƒ‘แƒ˜ โ€” แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ แƒ”แƒกแƒฃแƒ แƒกแƒ”แƒ‘แƒ˜, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ›แƒแƒ˜แƒ—แƒฎแƒแƒ•แƒแƒก แƒ•แƒ”แƒ‘ แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ”, แƒ›แƒแƒ’แƒแƒšแƒ˜แƒ—แƒแƒ“, แƒกแƒฃแƒ แƒแƒ—แƒ”แƒ‘แƒ˜, JS, CSS, HTML แƒคแƒแƒ˜แƒšแƒ”แƒ‘แƒ˜.
  6. แƒ’แƒแƒ แƒ”_แƒ“แƒแƒ™แƒแƒ•แƒจแƒ˜แƒ แƒ”แƒ‘แƒแƒ“แƒ˜ โ€” แƒแƒฅ แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒžแƒ˜แƒ แƒ“แƒแƒžแƒ˜แƒ  แƒ›แƒ˜แƒฃแƒ—แƒ˜แƒ—แƒแƒ— แƒ•แƒ”แƒ‘ แƒ’แƒ•แƒ”แƒ แƒ“แƒ”แƒ‘แƒ˜แƒก แƒกแƒฎแƒ•แƒ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒ“แƒ แƒ“แƒแƒ›แƒ”แƒœแƒ”แƒ‘แƒ˜แƒก ID, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒ“แƒแƒœแƒแƒช แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒ“แƒแƒ™แƒแƒ•แƒจแƒ˜แƒ แƒ”แƒ‘แƒ. แƒ“แƒแƒ›แƒ”แƒœแƒ˜ แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ˜แƒงแƒแƒก แƒ›แƒ”แƒแƒ แƒ” แƒ“แƒแƒœแƒ˜แƒก แƒแƒœ แƒฃแƒคแƒ แƒ แƒ›แƒแƒฆแƒแƒšแƒ˜. Firefox-แƒจแƒ˜ แƒแƒ  แƒ›แƒฃแƒจแƒแƒแƒ‘แƒก.

แƒจแƒ”แƒกแƒ แƒฃแƒšแƒ”แƒ‘แƒ˜แƒก แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒ˜

แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒแƒก แƒแƒฅแƒ•แƒก แƒกแƒแƒ›แƒ˜ แƒ™แƒแƒ“แƒ˜แƒก แƒจแƒ”แƒกแƒ แƒฃแƒšแƒ”แƒ‘แƒ˜แƒก แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒ˜, แƒแƒœแƒฃ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ แƒจแƒ”แƒ“แƒ’แƒ”แƒ‘แƒ แƒกแƒแƒ›แƒ˜ แƒœแƒแƒฌแƒ˜แƒšแƒ˜แƒกแƒ’แƒแƒœ, แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก API-แƒ–แƒ” แƒฌแƒ•แƒ“แƒแƒ›แƒ˜แƒก แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒ“แƒแƒœแƒ˜แƒ—.

แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒ˜

API-แƒ˜แƒก แƒฃแƒ›แƒ”แƒขแƒ”แƒกแƒแƒ‘แƒ แƒแƒฅ แƒแƒ แƒ˜แƒก แƒฎแƒ”แƒšแƒ›แƒ˜แƒกแƒแƒฌแƒ•แƒ“แƒแƒ›แƒ˜. แƒแƒ› แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒจแƒ˜ แƒ˜แƒกแƒ˜แƒœแƒ˜ "แƒชแƒฎแƒแƒ•แƒ แƒแƒ‘แƒ”แƒœ":

  1. แƒคแƒแƒœแƒ˜แƒก แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜ โ€” แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก โ€žbackendโ€œ แƒœแƒแƒฌแƒ˜แƒšแƒ˜. แƒคแƒแƒ˜แƒšแƒ˜ แƒ›แƒ˜แƒ—แƒ˜แƒ—แƒ”แƒ‘แƒฃแƒšแƒ˜แƒ แƒ›แƒแƒœแƒ˜แƒคแƒ”แƒกแƒขแƒจแƒ˜ แƒฆแƒ˜แƒšแƒแƒ™แƒ˜แƒก โ€žแƒคแƒแƒœแƒ˜แƒกโ€œ แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒ—.
  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 แƒกแƒแƒญแƒ˜แƒ แƒแƒ แƒงแƒ•แƒ”แƒšแƒ แƒ“แƒแƒ›แƒ”แƒœแƒ–แƒ” แƒ“แƒ แƒ›แƒฎแƒแƒšแƒแƒ“ แƒ™แƒแƒœแƒ™แƒ แƒ”แƒขแƒฃแƒšแƒ˜ แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ˜แƒงแƒแƒก แƒ›แƒ˜แƒ—แƒ˜แƒ—แƒ”แƒ‘แƒฃแƒšแƒ˜ แƒ›แƒแƒœแƒ˜แƒคแƒ”แƒกแƒขแƒจแƒ˜. แƒจแƒ”แƒ“แƒ”แƒ’แƒแƒ“, แƒ“แƒ˜แƒแƒ’แƒ แƒแƒ›แƒ แƒแƒกแƒ” แƒ’แƒแƒ›แƒแƒ˜แƒงแƒฃแƒ แƒ”แƒ‘แƒ:

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒ˜แƒฅแƒœแƒ”แƒ‘แƒ แƒกแƒฎแƒ•แƒ แƒกแƒชแƒ”แƒœแƒแƒ แƒ˜ - inpage, แƒ แƒแƒ›แƒ”แƒšแƒกแƒแƒช แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ” แƒจแƒ”แƒ•แƒ˜แƒขแƒแƒœแƒ—. แƒ˜แƒก แƒ˜แƒ›แƒฃแƒจแƒแƒ•แƒ”แƒ‘แƒก แƒ—แƒแƒ•แƒ˜แƒก แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒจแƒ˜ แƒ“แƒ แƒฃแƒ–แƒ แƒฃแƒœแƒ•แƒ”แƒšแƒงแƒแƒคแƒก API-แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒแƒกแƒ—แƒแƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก.

แƒ“แƒแƒฌแƒงแƒ”แƒ‘แƒ

แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒงแƒ•แƒ”แƒšแƒ แƒ™แƒแƒ“แƒ˜ แƒฎแƒ”แƒšแƒ›แƒ˜แƒกแƒแƒฌแƒ•แƒ“แƒแƒ›แƒ˜แƒ แƒ›แƒ˜แƒกแƒแƒ›แƒแƒ แƒ—แƒ–แƒ” GitHub. แƒแƒฆแƒฌแƒ”แƒ แƒ˜แƒก แƒ“แƒ แƒแƒก แƒ˜แƒฅแƒœแƒ”แƒ‘แƒ แƒ‘แƒ›แƒฃแƒšแƒ”แƒ‘แƒ˜ แƒ•แƒแƒšแƒ“แƒ”แƒ‘แƒฃแƒšแƒ”แƒ‘แƒ”แƒ‘แƒ–แƒ”.

แƒ“แƒแƒ•แƒ˜แƒฌแƒงแƒแƒ— แƒ›แƒแƒœแƒ˜แƒคแƒ”แƒกแƒขแƒ˜แƒ—:

{
  // ะ˜ะผั ะธ ะพะฟะธัะฐะฝะธะต, ะฒะตั€ัะธั. ะ’ัะต ัั‚ะพ ะฑัƒะดะตั‚ ะฒะธะดะฝะพ ะฒ ะฑั€ะฐัƒะทะตั€ะต ะฒ 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-แƒจแƒ˜ แƒ“แƒ แƒ“แƒแƒ•แƒ แƒฌแƒ›แƒฃแƒœแƒ“แƒ”แƒ—, แƒ แƒแƒ› แƒ˜แƒก แƒ›แƒฃแƒจแƒแƒแƒ‘แƒก.

แƒแƒ›แƒ˜แƒก แƒ“แƒแƒกแƒแƒ“แƒแƒกแƒขแƒฃแƒ แƒ”แƒ‘แƒšแƒแƒ“, แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒแƒ˜แƒฆแƒแƒ— แƒ™แƒแƒ“แƒ˜ แƒแƒฅแƒ”แƒ“แƒแƒœ แƒ’แƒแƒ›แƒแƒ›แƒ“แƒ˜แƒœแƒแƒ แƒ”,. แƒ’แƒแƒ แƒ“แƒ แƒ˜แƒ›แƒ˜แƒกแƒ, แƒ แƒแƒช แƒฉแƒ•แƒ”แƒœ แƒ’แƒแƒ•แƒแƒ™แƒ”แƒ—แƒ”แƒ—, แƒ‘แƒ›แƒฃแƒšแƒ›แƒ แƒ›แƒแƒแƒฎแƒ“แƒ˜แƒœแƒ แƒžแƒ แƒแƒ”แƒฅแƒขแƒ˜แƒก แƒจแƒ”แƒ™แƒ แƒ”แƒ‘แƒ˜แƒก แƒ™แƒแƒœแƒคแƒ˜แƒ’แƒฃแƒ แƒแƒชแƒ˜แƒ webpack-แƒ˜แƒก แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒ—. แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒจแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒ“แƒแƒกแƒแƒ›แƒแƒขแƒ”แƒ‘แƒšแƒแƒ“, chrome://extensions-แƒจแƒ˜ แƒฃแƒœแƒ“แƒ แƒแƒ˜แƒ แƒฉแƒ˜แƒแƒ— load unpacked แƒ“แƒ แƒกแƒแƒฅแƒแƒฆแƒแƒšแƒ“แƒ” แƒจแƒ”แƒกแƒแƒ‘แƒแƒ›แƒ˜แƒกแƒ˜ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒ— - แƒฉแƒ•แƒ”แƒœแƒก แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜ dist.

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒแƒฎแƒšแƒ แƒฉแƒ•แƒ”แƒœแƒ˜ แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ แƒ“แƒแƒ˜แƒœแƒกแƒขแƒแƒšแƒ˜แƒ แƒ”แƒ‘แƒฃแƒšแƒ˜แƒ แƒ“แƒ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒก. แƒ—แƒฅแƒ•แƒ”แƒœ แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒแƒฌแƒแƒ แƒ›แƒแƒแƒ— แƒ“แƒ”แƒ•แƒ”แƒšแƒแƒžแƒ”แƒ แƒ˜แƒก แƒ˜แƒœแƒกแƒขแƒ แƒฃแƒ›แƒ”แƒœแƒขแƒ”แƒ‘แƒ˜ แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒ™แƒแƒœแƒขแƒ”แƒฅแƒกแƒขแƒจแƒ˜ แƒจแƒ”แƒ›แƒ“แƒ”แƒ’แƒœแƒแƒ˜แƒ แƒแƒ“:

แƒแƒ›แƒแƒ›แƒฎแƒขแƒแƒ แƒ˜ แƒคแƒแƒœแƒฏแƒแƒ แƒ ->

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒ™แƒแƒœแƒขแƒ”แƒœแƒขแƒ˜แƒก แƒกแƒ™แƒ แƒ˜แƒžแƒขแƒ˜แƒก แƒ™แƒแƒœแƒกแƒแƒšแƒ–แƒ” แƒฌแƒ•แƒ“แƒแƒ›แƒ แƒฎแƒแƒ แƒชแƒ˜แƒ”แƒšแƒ“แƒ”แƒ‘แƒ แƒ—แƒแƒ•แƒแƒ“ แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒก แƒ™แƒแƒœแƒกแƒแƒšแƒ˜แƒก แƒ›แƒ”แƒจแƒ•แƒ”แƒแƒ‘แƒ˜แƒ—, แƒ แƒแƒ›แƒ”แƒšแƒ–แƒ”แƒช แƒ˜แƒก แƒ’แƒแƒจแƒ•แƒ”แƒ‘แƒฃแƒšแƒ˜แƒ.แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ

แƒแƒกแƒ” แƒ แƒแƒ›, แƒฉแƒ•แƒ”แƒœ แƒฃแƒœแƒ“แƒ แƒ“แƒแƒ•แƒแƒ›แƒงแƒแƒ แƒแƒ— แƒแƒ แƒ˜ แƒกแƒแƒ™แƒแƒ›แƒฃแƒœแƒ˜แƒ™แƒแƒชแƒ˜แƒ แƒแƒ แƒฎแƒ˜: inpage <-> background แƒ“แƒ popup <-> background. แƒ—แƒฅแƒ•แƒ”แƒœ, แƒ แƒ แƒ—แƒฅแƒ›แƒ แƒฃแƒœแƒ“แƒ, แƒจแƒ”แƒ’แƒ˜แƒซแƒšแƒ˜แƒแƒ— แƒฃแƒ‘แƒ แƒแƒšแƒแƒ“ แƒ’แƒแƒ’แƒ–แƒแƒ•แƒœแƒแƒ— แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ”แƒ‘แƒ˜ แƒžแƒแƒ แƒขแƒจแƒ˜ แƒ“แƒ แƒ’แƒแƒ›แƒแƒ˜แƒ’แƒแƒœแƒแƒ— แƒ—แƒฅแƒ•แƒ”แƒœแƒ˜ แƒกแƒแƒ™แƒฃแƒ—แƒแƒ แƒ˜ แƒžแƒ แƒแƒขแƒแƒ™แƒแƒšแƒ˜, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ›แƒ” แƒ›แƒ˜แƒ แƒฉแƒ”แƒ•แƒœแƒ˜แƒ แƒ›แƒ˜แƒ“แƒ’แƒแƒ›แƒ, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒ•แƒœแƒแƒฎแƒ” แƒ›แƒ”แƒขแƒแƒ›แƒแƒกแƒ™แƒ˜แƒก แƒฆแƒ˜แƒ แƒ™แƒแƒ“แƒ˜แƒก แƒžแƒ แƒแƒ”แƒฅแƒขแƒจแƒ˜.

แƒ”แƒก แƒแƒ แƒ˜แƒก แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ Ethereum แƒฅแƒกแƒ”แƒšแƒ—แƒแƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก. แƒ›แƒแƒกแƒจแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒœแƒแƒฌแƒ˜แƒšแƒ˜ แƒฃแƒ แƒ—แƒ˜แƒ”แƒ แƒ—แƒแƒ‘แƒก RPC-แƒ˜แƒ— dnode แƒ‘แƒ˜แƒ‘แƒšแƒ˜แƒแƒ—แƒ”แƒ™แƒ˜แƒก แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒ—. แƒ˜แƒก แƒกแƒแƒจแƒฃแƒแƒšแƒ”แƒ‘แƒแƒก แƒ’แƒแƒซแƒšแƒ”แƒ•แƒ— แƒ›แƒแƒแƒฌแƒงแƒแƒ— แƒ’แƒแƒชแƒ•แƒšแƒ แƒกแƒแƒ™แƒ›แƒแƒแƒ“ แƒกแƒฌแƒ แƒแƒคแƒแƒ“ แƒ“แƒ แƒ›แƒแƒฎแƒ”แƒ แƒฎแƒ”แƒ‘แƒฃแƒšแƒแƒ“, แƒ—แƒฃ แƒ›แƒแƒก แƒ›แƒ˜แƒแƒฌแƒ•แƒ“แƒ˜แƒ— nodejs แƒœแƒแƒ™แƒแƒ“แƒก, แƒ แƒแƒ’แƒแƒ แƒช แƒขแƒ แƒแƒœแƒกแƒžแƒแƒ แƒขแƒ˜ (แƒ˜แƒ’แƒฃแƒšแƒ˜แƒกแƒฎแƒ›แƒ”แƒ‘แƒ แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒแƒฎแƒแƒ แƒชแƒ˜แƒ”แƒšแƒ”แƒ‘แƒก แƒ˜แƒ›แƒแƒ•แƒ” แƒ˜แƒœแƒขแƒ”แƒ แƒคแƒ”แƒ˜แƒกแƒก):

import Dnode from "dnode/browser";

// ะ’ ัั‚ะพะผ ะฟั€ะธะผะตั€ะต ัƒัะปะพะฒะธะผัั ั‡ั‚ะพ ะบะปะธะตะฝั‚ ัƒะดะฐะปะตะฝะฝะพ ะฒั‹ะทั‹ะฒะฐะตั‚ ั„ัƒะฝะบั†ะธะธ ะฝะฐ ัะตั€ะฒะตั€ะต, ั…ะพั‚ั ะฝะธั‡ะตะณะพ ะฝะฐะผ ะฝะต ะผะตัˆะฐะตั‚ ัะดะตะปะฐั‚ัŒ ัั‚ะพ ะดะฒัƒะฝะฐะฟั€ะฐะฒะปะตะฝะฝั‹ะผ

// Cะตั€ะฒะตั€
// API, ะบะพั‚ะพั€ะพะต ะผั‹ ั…ะพั‚ะธะผ ะฟั€ะตะดะพัั‚ะฐะฒะธั‚ัŒ
const dnode = Dnode({
    hello: (cb) => cb(null, "world")
})
// ะขั€ะฐะฝัะฟะพั€ั‚, ะฟะพะฒะตั€ั… ะบะพั‚ะพั€ะพะณะพ ะฑัƒะดะตั‚ ั€ะฐะฑะพั‚ะฐั‚ัŒ dnode. ะ›ัŽะฑะพะน nodejs ัั‚ั€ะธะผ. ะ’ ะฑั€ะฐัƒะทะตั€ะต ะตัั‚ัŒ ะฑะธะฑะธะปะธะพั‚ะตะบะฐ 'readable-stream'
connectionStream.pipe(dnode).pipe(connectionStream)

// ะšะปะธะตะฝั‚
const dnodeClient = Dnode() // ะ’ั‹ะทะพะฒ ะฑะตะท ะฐะณั€ัƒะผะตะฝั‚ะฐ ะทะฝะฐั‡ะธั‚ ั‡ั‚ะพ ะผั‹ ะฝะต ะฟั€ะตะดะพัั‚ะฐะฒะปัะตะผ API ะฝะฐ ะดั€ัƒะณะพะน ัั‚ะพั€ะพะฝะต

// ะ’ั‹ะฒะตะดะตั‚ ะฒ ะบะพะฝัะพะปัŒ world
dnodeClient.once('remote', remote => {
    remote.hello(((err, value) => console.log(value)))
})

แƒแƒฎแƒšแƒ แƒฉแƒ•แƒ”แƒœ แƒจแƒ”แƒ•แƒฅแƒ›แƒœแƒ˜แƒ— แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒ™แƒšแƒแƒกแƒก. แƒ˜แƒก แƒจแƒ”แƒฅแƒ›แƒœแƒ˜แƒก API แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ”แƒ‘แƒก แƒแƒ›แƒแƒ›แƒฎแƒขแƒแƒ แƒ˜ แƒ“แƒ แƒ•แƒ”แƒ‘ แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒ“แƒ แƒจแƒ”แƒฅแƒ›แƒœแƒ˜แƒก dnode-แƒก แƒ›แƒแƒ—แƒ—แƒ•แƒ˜แƒก:

import Dnode from 'dnode/browser';

export class SignerApp {

    // ะ’ะพะทะฒั€ะฐั‰ะฐะตั‚ ะพะฑัŠะตะบั‚ API ะดะปั ui
    popupApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // ะ’ะพะทะฒั€ะฐั‰ะฐะตั‚ ะพะฑัŠะตั‚ API ะดะปั ัั‚ั€ะฐะฝะธั†ั‹
    pageApi(){
        return {
            hello: cb => cb(null, 'world')
        }
    }

    // ะŸะพะดะบะปัŽั‡ะฐะตั‚ popup ui
    connectPopup(connectionStream){
        const api = this.popupApi();
        const dnode = Dnode(api);

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

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

    // ะŸะพะดะบะปัŽั‡ะฐะตั‚ ัั‚ั€ะฐะฝะธั†ัƒ
    connectPage(connectionStream, origin){
        const api = this.popupApi();
        const dnode = Dnode(api);

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

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

แƒแƒฅ แƒ“แƒ แƒฅแƒ•แƒ”แƒ›แƒแƒ—, แƒ’แƒšแƒแƒ‘แƒแƒšแƒฃแƒ แƒ˜ Chrome แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜แƒก แƒœแƒแƒชแƒ•แƒšแƒแƒ“, แƒ•แƒ˜แƒงแƒ”แƒœแƒ”แƒ‘แƒ— extensionApi-แƒก, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒฌแƒ•แƒ“แƒ”แƒ‘แƒ Chrome-แƒก 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. แƒฉแƒ•แƒ”แƒœ แƒ•แƒฅแƒ›แƒœแƒ˜แƒ— แƒแƒ  แƒœแƒแƒ™แƒแƒ“แƒก. แƒ”แƒ แƒ—แƒ˜ - แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒกแƒ™แƒ”แƒœ, แƒžแƒแƒกแƒขแƒ›แƒ”แƒกแƒ˜แƒฏแƒ˜แƒก แƒ—แƒแƒ•แƒ–แƒ”. แƒแƒ›แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒ•แƒ˜แƒงแƒ”แƒœแƒ”แƒ‘แƒ— แƒแƒ›แƒแƒก แƒ”แƒก แƒžแƒแƒ™แƒ”แƒขแƒ˜ แƒ›แƒ”แƒขแƒแƒ›แƒแƒกแƒ™แƒ˜แƒก แƒจแƒ”แƒ›แƒฅแƒ›แƒœแƒ”แƒšแƒ”แƒ‘แƒ˜แƒกแƒ’แƒแƒœ. แƒ›แƒ”แƒแƒ แƒ” แƒœแƒแƒ™แƒแƒ“แƒ˜ แƒแƒ แƒ˜แƒก แƒคแƒแƒœแƒ–แƒ” แƒ›แƒ˜แƒฆแƒ”แƒ‘แƒฃแƒšแƒ˜ แƒžแƒแƒ แƒขแƒ˜ 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 แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒก inpage-แƒจแƒ˜ แƒ“แƒ แƒ•แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ— แƒ›แƒแƒก แƒ’แƒšแƒแƒ‘แƒแƒšแƒฃแƒ แƒ–แƒ”:

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

setupInpageApi().catch(console.error);

async function setupInpageApi() {
    // ะกั‚ั€ะธะผ ะบ ะบะพะฝั‚ะตะฝั‚ัะบั€ะธะฟั‚ัƒ
    const connectionStream = new PostMessageStream({
        name: 'page',
        target: 'content',
    });

    const dnode = Dnode();

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

    // ะŸะพะปัƒั‡ะฐะตะผ ะพะฑัŠะตะบั‚ API
    const pageApi = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // ะ”ะพัั‚ัƒะฟ ั‡ะตั€ะตะท window
    global.SignerApp = pageApi;
}

แฒฉแƒ•แƒ”แƒœ แƒ›แƒ–แƒแƒ“ แƒ•แƒแƒ แƒ— แƒ“แƒ˜แƒกแƒขแƒแƒœแƒชแƒ˜แƒฃแƒ แƒ˜ แƒžแƒ แƒแƒชแƒ”แƒ“แƒฃแƒ แƒ˜แƒก แƒ–แƒแƒ แƒ˜ (RPC) แƒชแƒแƒšแƒ™แƒ” API แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒกแƒ แƒ“แƒ UI-แƒกแƒ—แƒ•แƒ˜แƒก. แƒแƒฎแƒแƒšแƒ˜ แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒก แƒคแƒแƒœแƒ—แƒแƒœ แƒ“แƒแƒ™แƒแƒ•แƒจแƒ˜แƒ แƒ”แƒ‘แƒ˜แƒกแƒแƒก แƒฉแƒ•แƒ”แƒœ แƒ•แƒฎแƒ”แƒ“แƒแƒ•แƒ— แƒแƒ›แƒแƒก:

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒชแƒแƒ แƒ˜แƒ”แƒšแƒ˜ API แƒ“แƒ แƒฌแƒแƒ แƒ›แƒแƒจแƒแƒ‘แƒ. แƒ’แƒ•แƒ”แƒ แƒ“แƒ˜แƒก แƒ›แƒฎแƒแƒ แƒ”แƒก, แƒฉแƒ•แƒ”แƒœ แƒจแƒ”แƒ’แƒ•แƒ˜แƒซแƒšแƒ˜แƒ แƒ•แƒฃแƒฌแƒแƒ“แƒแƒ— hello แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒ แƒแƒกแƒ”:

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒ—แƒแƒœแƒแƒ›แƒ”แƒ“แƒ แƒแƒ•แƒ” JS-แƒจแƒ˜ แƒ’แƒแƒ›แƒแƒซแƒแƒฎแƒ”แƒ‘แƒ˜แƒก แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒ”แƒ‘แƒ—แƒแƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ แƒชแƒฃแƒ“แƒ˜ แƒ›แƒแƒœแƒ”แƒ แƒแƒ, แƒแƒ›แƒ˜แƒขแƒแƒ› แƒ›แƒแƒ“แƒ˜แƒ— แƒ“แƒแƒ•แƒฌแƒ”แƒ แƒแƒ— แƒžแƒแƒขแƒแƒ แƒ แƒ“แƒแƒ›แƒฎแƒ›แƒแƒ แƒ” dnode-แƒ˜แƒก แƒจแƒ”แƒกแƒแƒฅแƒ›แƒœแƒ”แƒšแƒแƒ“, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒกแƒแƒจแƒฃแƒแƒšแƒ”แƒ‘แƒแƒก แƒ›แƒแƒ’แƒชแƒ”แƒ›แƒ— แƒ’แƒแƒ“แƒแƒกแƒชแƒ”แƒ— API แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜ utils-แƒก.

API แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ”แƒ‘แƒ˜ แƒแƒฎแƒšแƒ แƒแƒกแƒ” แƒ’แƒแƒ›แƒแƒ˜แƒงแƒฃแƒ แƒ”แƒ‘แƒ:

export class SignerApp {

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

...

}

แƒ“แƒ˜แƒกแƒขแƒแƒœแƒชแƒ˜แƒฃแƒ แƒ˜แƒ“แƒแƒœ แƒแƒกแƒ”แƒ—แƒ˜ แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜แƒก แƒ›แƒ˜แƒฆแƒ”แƒ‘แƒ:

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

const pageApi = await new Promise(resolve => {
    dnode.once('remote', remoteApi => {
        // ะก ะฟะพะผะพั‰ัŒัŽ ัƒั‚ะธะปะธั‚ ะผะตะฝัะตะผ ะฒัะต callback ะฝะฐ promise
        resolve(transformMethods(cbToPromise, remoteApi))
    })
});

แƒ“แƒ แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒ”แƒ‘แƒ˜แƒก แƒ’แƒแƒ›แƒแƒซแƒแƒฎแƒ”แƒ‘แƒ แƒแƒ‘แƒ แƒฃแƒœแƒ”แƒ‘แƒก แƒ“แƒแƒžแƒ˜แƒ แƒ”แƒ‘แƒแƒก:

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒฎแƒ”แƒšแƒ›แƒ˜แƒกแƒแƒฌแƒ•แƒ“แƒแƒ›แƒ˜แƒ แƒ•แƒ”แƒ แƒกแƒ˜แƒ แƒแƒกแƒ˜แƒœแƒฅแƒ แƒแƒœแƒฃแƒšแƒ˜ แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒ”แƒ‘แƒ˜แƒ— แƒแƒฅ.

แƒกแƒแƒ”แƒ แƒ—แƒ แƒฏแƒแƒ›แƒจแƒ˜, RPC แƒ“แƒ แƒœแƒแƒ™แƒแƒ“แƒ˜แƒก แƒ›แƒ˜แƒ“แƒ’แƒแƒ›แƒ แƒกแƒแƒ™แƒ›แƒแƒแƒ“ แƒ›แƒแƒฅแƒœแƒ˜แƒšแƒ˜ แƒฉแƒแƒœแƒก: แƒฉแƒ•แƒ”แƒœ แƒจแƒ”แƒ’แƒ•แƒ˜แƒซแƒšแƒ˜แƒ แƒ’แƒแƒ›แƒแƒ•แƒ˜แƒงแƒ”แƒœแƒแƒ— แƒแƒ แƒ—แƒฅแƒšแƒ˜แƒก แƒ›แƒฃแƒšแƒขแƒ˜แƒžแƒšแƒ”แƒฅแƒกแƒ˜แƒ แƒ”แƒ‘แƒ แƒ“แƒ แƒจแƒ”แƒ•แƒฅแƒ›แƒœแƒแƒ— แƒ แƒแƒ›แƒ“แƒ”แƒœแƒ˜แƒ›แƒ” แƒ’แƒแƒœแƒกแƒฎแƒ•แƒแƒ•แƒ”แƒ‘แƒฃแƒšแƒ˜ API แƒกแƒฎแƒ•แƒแƒ“แƒแƒกแƒฎแƒ•แƒ แƒแƒ›แƒแƒชแƒแƒœแƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก. แƒžแƒ แƒ˜แƒœแƒชแƒ˜แƒžแƒจแƒ˜, dnode แƒจแƒ”แƒ˜แƒซแƒšแƒ”แƒ‘แƒ แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒฃแƒš แƒ˜แƒฅแƒœแƒแƒก แƒœแƒ”แƒ‘แƒ˜แƒกแƒ›แƒ˜แƒ”แƒ  แƒแƒ“แƒ’แƒ˜แƒšแƒแƒก, แƒ›แƒ—แƒแƒ•แƒแƒ แƒ˜แƒ แƒขแƒ แƒแƒœแƒกแƒžแƒแƒ แƒขแƒ˜แƒก แƒ’แƒแƒ“แƒแƒขแƒแƒœแƒ nodejs แƒœแƒแƒ™แƒแƒ“แƒ˜แƒก แƒกแƒแƒฎแƒ˜แƒ—.

แƒแƒšแƒขแƒ”แƒ แƒœแƒแƒขแƒ˜แƒ•แƒ แƒแƒ แƒ˜แƒก JSON แƒคแƒแƒ แƒ›แƒแƒขแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒแƒฎแƒแƒ แƒชแƒ˜แƒ”แƒšแƒ”แƒ‘แƒก JSON RPC 2 แƒžแƒ แƒแƒขแƒแƒ™แƒแƒšแƒก. แƒ—แƒฃแƒ›แƒชแƒ, แƒ˜แƒก แƒ›แƒฃแƒจแƒแƒแƒ‘แƒก แƒกแƒžแƒ”แƒชแƒ˜แƒคแƒ˜แƒ™แƒฃแƒ  แƒขแƒ แƒแƒœแƒกแƒžแƒแƒ แƒขแƒ”แƒ‘แƒ—แƒแƒœ (TCP แƒ“แƒ HTTP(S)), แƒ แƒแƒช แƒฉแƒ•แƒ”แƒœแƒก แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜ แƒแƒ  แƒ’แƒแƒ›แƒแƒ˜แƒงแƒ”แƒœแƒ”แƒ‘แƒ.

แƒจแƒ˜แƒ“แƒ แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒ แƒ“แƒ แƒแƒ“แƒ’แƒ˜แƒšแƒแƒ‘แƒ แƒ˜แƒ•แƒ˜ แƒจแƒ”แƒœแƒแƒฎแƒ•แƒ

แƒฉแƒ•แƒ”แƒœ แƒ“แƒแƒ’แƒ•แƒญแƒ˜แƒ แƒ“แƒ”แƒ‘แƒ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒจแƒ˜แƒ“แƒ แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒ˜แƒก แƒจแƒ”แƒœแƒแƒฎแƒ•แƒ - แƒ›แƒ˜แƒœแƒ˜แƒ›แƒฃแƒ› แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒ˜แƒก แƒ’แƒแƒกแƒแƒฆแƒ”แƒ‘แƒ”แƒ‘แƒ˜. แƒฉแƒ•แƒ”แƒœ แƒกแƒแƒ™แƒ›แƒแƒแƒ“ แƒ›แƒแƒ แƒขแƒ˜แƒ•แƒแƒ“ แƒจแƒ”แƒ’แƒ•แƒ˜แƒซแƒšแƒ˜แƒ แƒ“แƒแƒ•แƒแƒ›แƒแƒขแƒแƒ— แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒแƒก แƒ“แƒ แƒ›แƒ˜แƒกแƒ˜ แƒจแƒ”แƒชแƒ•แƒšแƒ˜แƒก แƒ›แƒ”แƒ—แƒแƒ“แƒ”แƒ‘แƒ˜ แƒแƒ›แƒแƒ›แƒฎแƒขแƒแƒ  API-แƒจแƒ˜:

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

export class SignerApp {

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

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

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

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

    ...

} 

แƒคแƒแƒœแƒ–แƒ”, แƒฉแƒ•แƒ”แƒœ แƒงแƒ•แƒ”แƒšแƒแƒคแƒ”แƒ แƒก แƒ›แƒแƒ•แƒแƒฎแƒ•แƒ”แƒ•แƒ— แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒแƒก แƒ“แƒ แƒฉแƒแƒ•แƒฌแƒ”แƒ แƒ— แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒก แƒคแƒแƒœแƒฏแƒแƒ แƒแƒจแƒ˜, แƒ แƒแƒ—แƒ แƒจแƒ”แƒ•แƒซแƒšแƒแƒ— แƒ›แƒแƒกแƒ—แƒแƒœ แƒ›แƒฃแƒจแƒแƒแƒ‘แƒ แƒ™แƒแƒœแƒกแƒแƒšแƒ˜แƒ“แƒแƒœ:

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

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

setupApp();

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

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

    extensionApi.runtime.onConnect.addListener(connectRemote);

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

แƒ›แƒแƒ“แƒ˜แƒ— แƒ“แƒแƒ•แƒแƒ›แƒแƒขแƒแƒ— แƒ แƒแƒ›แƒ“แƒ”แƒœแƒ˜แƒ›แƒ” แƒ’แƒแƒกแƒแƒฆแƒ”แƒ‘แƒ˜ 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, แƒ—แƒฃ แƒ แƒแƒ›แƒ”แƒš แƒ“แƒแƒ™แƒ•แƒ˜แƒ แƒ•แƒ”แƒ‘แƒแƒ“ แƒ’แƒแƒ›แƒแƒ•แƒ˜แƒฌแƒ”แƒ แƒแƒ—. แƒกแƒ”แƒšแƒ”แƒฅแƒขแƒแƒ แƒ˜ แƒ แƒแƒ› แƒ“แƒแƒ•แƒฌแƒ”แƒ แƒ” แƒ™แƒแƒ“แƒจแƒ˜ แƒแƒกแƒ”() => 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-แƒก แƒแƒฎแƒšแƒ แƒแƒฅแƒ•แƒก แƒ›แƒ”แƒฎแƒกแƒ˜แƒ”แƒ แƒ”แƒ‘แƒ˜แƒก แƒ˜แƒœแƒ˜แƒชแƒ˜แƒแƒšแƒ˜แƒ–แƒแƒชแƒ˜แƒ˜แƒก แƒ›แƒ”แƒ—แƒแƒ“แƒ˜.

แƒ“แƒแƒฌแƒ”แƒ แƒ˜แƒšแƒ˜แƒ แƒ“แƒแƒจแƒ˜แƒคแƒ•แƒ แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒ™แƒแƒ›แƒฃแƒœแƒแƒšแƒฃแƒ แƒ˜ แƒžแƒ แƒแƒ’แƒ แƒแƒ›แƒ”แƒ‘แƒ˜ แƒ™แƒ แƒ˜แƒžแƒขแƒ-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)
        }
    }
}

แƒ™แƒแƒ“แƒ˜ แƒแƒ› แƒœแƒแƒ‘แƒ˜แƒฏแƒ˜แƒก แƒฌแƒ˜แƒœ แƒแƒ แƒ˜แƒก แƒแƒฅ.

แƒ’แƒแƒ แƒ˜แƒ’แƒ”แƒ‘แƒ”แƒ‘แƒ˜

แƒแƒกแƒ” แƒ แƒแƒ›, แƒฉแƒ•แƒ”แƒœ แƒ›แƒ˜แƒ•แƒ“แƒ˜แƒ•แƒแƒ แƒ— แƒงแƒ•แƒ”แƒšแƒแƒ–แƒ” แƒ›แƒœแƒ˜แƒจแƒ•แƒœแƒ”แƒšแƒแƒ•แƒแƒœ แƒกแƒแƒ™แƒ˜แƒ—แƒฎแƒแƒ›แƒ“แƒ”: แƒขแƒ แƒแƒœแƒ–แƒแƒฅแƒชแƒ˜แƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒฅแƒ›แƒœแƒแƒกแƒ แƒ“แƒ แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒแƒ–แƒ” แƒ‘แƒšแƒแƒ™แƒฉแƒ”แƒ˜แƒœแƒ–แƒ”. แƒฉแƒ•แƒ”แƒœ แƒ’แƒแƒ›แƒแƒ•แƒ˜แƒงแƒ”แƒœแƒ”แƒ‘แƒ— WAVES แƒ‘แƒšแƒแƒ™แƒฉแƒ”แƒ˜แƒœแƒก แƒ“แƒ แƒ‘แƒ˜แƒ‘แƒšแƒ˜แƒแƒ—แƒ”แƒ™แƒแƒก แƒขแƒแƒšแƒฆแƒ”แƒ‘แƒ˜-แƒ’แƒแƒ แƒ˜แƒ’แƒ”แƒ‘แƒ”แƒ‘แƒ˜.

แƒฏแƒ”แƒ , แƒ›แƒแƒ“แƒ˜แƒ—, แƒกแƒแƒฎแƒ”แƒšแƒ›แƒฌแƒ˜แƒคแƒแƒก แƒ“แƒแƒ•แƒแƒ›แƒแƒขแƒแƒ— แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒกแƒ˜แƒ•แƒ˜, แƒ แƒแƒ›แƒšแƒ”แƒ‘แƒ˜แƒช แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒ˜แƒšแƒ˜แƒ, แƒจแƒ”แƒ›แƒ“แƒ”แƒ’ แƒ“แƒแƒ•แƒแƒ›แƒแƒขแƒแƒ— แƒแƒฎแƒแƒšแƒ˜ แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒ›แƒแƒขแƒ”แƒ‘แƒ˜แƒก, แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒ˜แƒก แƒ“แƒแƒ“แƒแƒกแƒขแƒฃแƒ แƒ”แƒ‘แƒ˜แƒกแƒ แƒ“แƒ แƒฃแƒแƒ แƒ˜แƒก แƒ—แƒฅแƒ›แƒ˜แƒก แƒ›แƒ”แƒ—แƒแƒ“แƒ”แƒ‘แƒ˜:

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

export class SignerApp {

    ...

    @action
    newMessage(data, origin) {
        // ะ”ะปั ะบะฐะถะดะพะณะพ ัะพะพะฑั‰ะตะฝะธั ัะพะทะดะฐะตะผ ะผะตั‚ะฐะดะฐะฝะฝั‹ะต ั id, ัั‚ะฐั‚ัƒัะพะผ, ะฒั‹ั€ะตะผะตะฝะตะผ ัะพะทะดะฐะฝะธั ะธ ั‚ะด.
        const message = observable.object({
            id: uuid(), // ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€, ะธัะฟะพะปัŒะทัƒัŽัŽ uuid
            origin, // Origin ะฑัƒะดะตะผ ะฒะฟะพัะปะตะดัั‚ะฒะธะธ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฒ ะธะฝั‚ะตั€ั„ะตะนัะต
            data, //
            status: 'new', // ะกั‚ะฐั‚ัƒัะพะฒ ะฑัƒะดะตั‚ ั‡ะตั‚ั‹ั€ะต: new, signed, rejected ะธ failed
            timestamp: Date.now()
        });
        console.log(`new message: ${JSON.stringify(message, null, 2)}`);

        this.store.messages.push(message);

        // ะ’ะพะทะฒั€ะฐั‰ะฐะตะผ ะฟั€ะพะผะธั ะฒะฝัƒั‚ั€ะธ ะบะพั‚ะพั€ะพะณะพ mobx ะผะพะฝะธั‚ะพั€ะธั‚ ะธะทะผะตะฝะตะฝะธั ัะพะพะฑั‰ะตะฝะธั. ะšะฐะบ ั‚ะพะปัŒะบะพ ัั‚ะฐั‚ัƒั ะฟะพะผะตะฝัะตั‚ัั ะผั‹ ะทะฐั€ะตะทะพะปะฒะธะผ ะตะณะพ
        return new Promise((resolve, reject) => {
            reaction(
                () => message.status, //ะ‘ัƒะดะตะผ ะพะฑัะตั€ะฒะธั‚ัŒ ัั‚ะฐั‚ัƒั ัะพะพะฑั‰ะตะฝั
                (status, reaction) => { // ะฒั‚ะพั€ะพะน ะฐั€ะณัƒะผะตะฝั‚ ัั‚ะพ ััั‹ะปะบะฐ ะฝะฐ ัะฐะผ reaction, ั‡ั‚ะพะฑั‹ ะตะณะพ ะผะพะถะฝะพ ะฑั‹ะปะพ ัƒะฝะธั‡ั‚ะพะถั‚ัŒ ะฒะฝัƒั‚ั€ะธ ะฒั‹ะทะพะฒะฐ
                    switch (status) {
                        case 'signed':
                            resolve(message.data);
                            break;
                        case 'rejected':
                            reject(new Error('User rejected message'));
                            break;
                        case 'failed':
                            reject(new Error(message.err.message));
                            break;
                        default:
                            return
                    }
                    reaction.dispose()
                }
            )
        })
    }
    @action
    approve(id, keyIndex = 0) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        try {
            message.data = signTx(message.data, this.store.keys[keyIndex]);
            message.status = 'signed'
        } catch (e) {
            message.err = {
                stack: e.stack,
                message: e.message
            };
            message.status = 'failed'
            throw e
        }
    }
    @action
    reject(id) {
        const message = this.store.messages.find(msg => msg.id === id);
        if (message == null) throw new Error(`No msg with id:${id}`);
        message.status = 'rejected'
    }

    ...
}

แƒ แƒแƒ“แƒ”แƒกแƒแƒช แƒ•แƒ˜แƒฆแƒ”แƒ‘แƒ— แƒแƒฎแƒแƒš แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒแƒก, แƒฉแƒ•แƒ”แƒœ แƒ•แƒแƒ›แƒแƒขแƒ”แƒ‘แƒ— แƒ›แƒแƒก แƒ›แƒ”แƒขแƒแƒ›แƒแƒœแƒแƒชแƒ”แƒ›แƒ”แƒ‘แƒก, แƒ’แƒแƒแƒ™แƒ”แƒ—แƒ”แƒ— observable แƒ“แƒ แƒ“แƒแƒแƒ›แƒแƒขแƒ”แƒ— store.messages.

แƒ—แƒฃ แƒแƒ แƒ observable แƒฎแƒ”แƒšแƒ˜แƒ—, แƒ›แƒแƒจแƒ˜แƒœ mobx แƒแƒ›แƒแƒก แƒ—แƒแƒ•แƒแƒ“ แƒ’แƒแƒแƒ™แƒ”แƒ—แƒ”แƒ‘แƒก แƒ›แƒแƒกแƒ˜แƒ•แƒจแƒ˜ แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒ›แƒแƒขแƒ”แƒ‘แƒ˜แƒกแƒแƒก. แƒ—แƒฃแƒ›แƒชแƒ, แƒ˜แƒก แƒจแƒ”แƒฅแƒ›แƒœแƒ˜แƒก แƒแƒฎแƒแƒš แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒก, แƒ แƒแƒ›แƒ”แƒšแƒ–แƒ”แƒช แƒแƒ  แƒ’แƒ•แƒ”แƒฅแƒœแƒ”แƒ‘แƒ แƒ›แƒ˜แƒ—แƒ˜แƒ—แƒ”แƒ‘แƒ, แƒ›แƒแƒ’แƒ แƒแƒ› แƒ“แƒแƒ’แƒ•แƒญแƒ˜แƒ แƒ“แƒ”แƒ‘แƒ แƒจแƒ”แƒ›แƒ“แƒ”แƒ’แƒ˜ แƒœแƒแƒ‘แƒ˜แƒฏแƒ˜แƒกแƒ—แƒ•แƒ˜แƒก.

แƒจแƒ”แƒ›แƒ“แƒ”แƒ’แƒ˜, แƒฉแƒ•แƒ”แƒœ แƒ•แƒแƒ‘แƒ แƒฃแƒœแƒ”แƒ‘แƒ— แƒ“แƒแƒžแƒ˜แƒ แƒ”แƒ‘แƒแƒก, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒฌแƒงแƒ“แƒ”แƒ‘แƒ แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ˜แƒก แƒกแƒขแƒแƒขแƒฃแƒกแƒ˜แƒก แƒจแƒ”แƒชแƒ•แƒšแƒ˜แƒกแƒแƒก. แƒกแƒขแƒแƒขแƒฃแƒกแƒก แƒแƒ™แƒแƒœแƒขแƒ แƒแƒšแƒ”แƒ‘แƒก แƒ แƒ”แƒแƒฅแƒชแƒ˜แƒ, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒกแƒขแƒแƒขแƒฃแƒกแƒ˜แƒก แƒจแƒ”แƒชแƒ•แƒšแƒ˜แƒกแƒแƒก โ€žแƒ˜แƒ™แƒšแƒแƒ•แƒก แƒ—แƒแƒ•แƒกโ€œ.

แƒ›แƒ”แƒ—แƒแƒ“แƒ˜แƒก แƒ™แƒแƒ“แƒ˜ approve ะธ reject แƒซแƒแƒšแƒ˜แƒแƒœ แƒ›แƒแƒ แƒขแƒ˜แƒ•แƒ˜แƒ: แƒฉแƒ•แƒ”แƒœ แƒฃแƒ‘แƒ แƒแƒšแƒแƒ“ แƒ•แƒชแƒ•แƒšแƒ˜แƒ— แƒจแƒ”แƒขแƒงแƒแƒ‘แƒ˜แƒœแƒ”แƒ‘แƒ˜แƒก แƒกแƒขแƒแƒขแƒฃแƒกแƒก, แƒกแƒแƒญแƒ˜แƒ แƒแƒ”แƒ‘แƒ˜แƒก แƒจแƒ”แƒ›แƒ—แƒฎแƒ•แƒ”แƒ•แƒแƒจแƒ˜, แƒฎแƒ”แƒšแƒ›แƒแƒฌแƒ”แƒ แƒ˜แƒก แƒจแƒ”แƒ›แƒ“แƒ”แƒ’.

แƒฉแƒ•แƒ”แƒœ แƒ•แƒแƒ—แƒแƒ•แƒกแƒ”แƒ‘แƒ— Approve-แƒก แƒ“แƒ แƒฃแƒแƒ แƒงแƒแƒคแƒแƒก UI API-แƒจแƒ˜, newMessage-แƒก แƒ’แƒ•แƒ”แƒ แƒ“แƒ–แƒ” API-แƒจแƒ˜:

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

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

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

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

    ...
}

แƒแƒฎแƒšแƒ แƒ•แƒชแƒแƒ“แƒแƒ— แƒฎแƒ”แƒšแƒ˜ แƒ›แƒแƒ•แƒแƒฌแƒ”แƒ แƒแƒ— แƒขแƒ แƒแƒœแƒ–แƒแƒฅแƒชแƒ˜แƒแƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒ—:

แƒฃแƒกแƒแƒคแƒ แƒ—แƒฎแƒ แƒ‘แƒ แƒแƒฃแƒ–แƒ”แƒ แƒ˜แƒก แƒ’แƒแƒคแƒแƒ แƒ—แƒแƒ”แƒ‘แƒ˜แƒก แƒ“แƒแƒฌแƒ”แƒ แƒ

แƒ–แƒแƒ’แƒแƒ“แƒแƒ“, แƒงแƒ•แƒ”แƒšแƒแƒคแƒ”แƒ แƒ˜ แƒ›แƒ–แƒแƒ“ แƒแƒ แƒ˜แƒก, แƒ แƒฉแƒ”แƒ‘แƒ แƒ›แƒฎแƒแƒšแƒแƒ“ แƒ“แƒแƒแƒ›แƒแƒขแƒ”แƒ— แƒ›แƒแƒ แƒขแƒ˜แƒ•แƒ˜ แƒ˜แƒœแƒขแƒ”แƒ แƒคแƒ”แƒ˜แƒกแƒ˜.

UI

แƒ˜แƒœแƒขแƒ”แƒ แƒคแƒ”แƒ˜แƒกแƒก แƒกแƒญแƒ˜แƒ แƒ“แƒ”แƒ‘แƒ แƒฌแƒ•แƒ“แƒแƒ›แƒ แƒ’แƒแƒœแƒแƒชแƒฎแƒแƒ“แƒ˜แƒก แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒแƒ–แƒ”. UI แƒ›แƒฎแƒแƒ แƒ”แƒก แƒฉแƒ•แƒ”แƒœ แƒ’แƒแƒ•แƒแƒ™แƒ”แƒ—แƒ”แƒ‘แƒ— observable แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒ แƒ“แƒ แƒ“แƒแƒแƒ›แƒแƒขแƒ”แƒ— แƒคแƒฃแƒœแƒฅแƒชแƒ˜แƒ API-แƒจแƒ˜, แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒช แƒจแƒ”แƒชแƒ•แƒšแƒ˜แƒก แƒแƒ› แƒ›แƒ“แƒ’แƒแƒ›แƒแƒ แƒ”แƒแƒ‘แƒแƒก. แƒ“แƒแƒ•แƒแƒ›แƒแƒขแƒแƒ— observable แƒคแƒแƒœแƒ˜แƒ“แƒแƒœ แƒ›แƒ˜แƒฆแƒ”แƒ‘แƒฃแƒš API แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒก:

import {observable} from 'mobx'
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {cbToPromise, setupDnode, transformMethods} from "./utils/setupDnode";
import {initApp} from "./ui/index";

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

setupUi().catch(console.error);

async function setupUi() {
    // ะŸะพะดะบะปัŽั‡ะฐะตะผัั ะบ ะฟะพั€ั‚ัƒ, ัะพะทะดะฐะตะผ ะธะท ะฝะตะณะพ ัั‚ั€ะธะผ
    const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
    const connectionStream = new PortStream(backgroundPort);

    // ะกะพะทะดะฐะตะผ ะฟัƒัั‚ะพะน observable ะดะปั ัะพัั‚ะพัะฝะธั background'a
    let backgroundState = observable.object({});
    const api = {
        //ะžั‚ะดะฐะตะผ ะฑะตะบะณั€ะฐัƒะฝะดัƒ ั„ัƒะฝะบั†ะธัŽ, ะบะพั‚ะพั€ะฐั ะฑัƒะดะตั‚ ะพะฑะฝะพะฒะปัั‚ัŒ observable
        updateState: async state => {
            Object.assign(backgroundState, state)
        }
    };

    // ะ”ะตะปะฐะตะผ RPC ะพะฑัŠะตะบั‚
    const dnode = setupDnode(connectionStream, api);
    const background = await new Promise(resolve => {
        dnode.once('remote', remoteApi => {
            resolve(transformMethods(cbToPromise, remoteApi))
        })
    });

    // ะ”ะพะฑะฐะฒะปัะตะผ ะฒ background observable ัะพ ัั‚ะตะนั‚ะพะผ
    background.state = backgroundState;

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

    // ะ—ะฐะฟัƒัะบ ะธะฝั‚ะตั€ั„ะตะนัะฐ
    await initApp(background)
}

แƒ“แƒแƒกแƒแƒกแƒ แƒฃแƒšแƒก แƒ•แƒ˜แƒฌแƒงแƒ”แƒ‘แƒ— แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ˜แƒก แƒ˜แƒœแƒขแƒ”แƒ แƒคแƒ”แƒ˜แƒกแƒ˜แƒก แƒ แƒ”แƒœแƒ“แƒ”แƒ แƒ˜แƒ แƒ”แƒ‘แƒแƒก. แƒ”แƒก แƒแƒ แƒ˜แƒก แƒ แƒ”แƒแƒฅแƒขแƒ˜แƒฃแƒšแƒ˜ แƒแƒžแƒšแƒ˜แƒ™แƒแƒชแƒ˜แƒ. แƒคแƒแƒœแƒ˜แƒก แƒแƒ‘แƒ˜แƒ”แƒฅแƒขแƒ˜ แƒฃแƒ‘แƒ แƒแƒšแƒแƒ“ แƒ’แƒแƒ“แƒแƒ”แƒชแƒ”แƒ›แƒ แƒ แƒ”แƒ™แƒ•แƒ˜แƒ–แƒ˜แƒขแƒ”แƒ‘แƒ˜แƒก แƒ’แƒแƒ›แƒแƒงแƒ”แƒœแƒ”แƒ‘แƒ˜แƒ—. แƒกแƒฌแƒแƒ แƒ˜ แƒ˜แƒฅแƒœแƒ”แƒ‘แƒแƒ“แƒ, แƒ แƒ แƒ—แƒฅแƒ›แƒ แƒฃแƒœแƒ“แƒ, แƒ›แƒ”แƒ—แƒแƒ“แƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒชแƒแƒšแƒ™แƒ” แƒกแƒ”แƒ แƒ•แƒ˜แƒกแƒ˜แƒก แƒ’แƒแƒ™แƒ”แƒ—แƒ”แƒ‘แƒ แƒ“แƒ แƒกแƒแƒฎแƒ”แƒšแƒ›แƒฌแƒ˜แƒคแƒแƒกแƒ—แƒ•แƒ˜แƒก แƒ›แƒแƒฆแƒแƒ–แƒ˜แƒ, แƒ›แƒแƒ’แƒ แƒแƒ› แƒแƒ› แƒกแƒขแƒแƒขแƒ˜แƒ˜แƒก แƒ›แƒ˜แƒ–แƒœแƒ”แƒ‘แƒ˜แƒกแƒ—แƒ•แƒ˜แƒก แƒ”แƒก แƒกแƒแƒ™แƒ›แƒแƒ แƒ˜แƒกแƒ˜แƒ:

import {render} from 'react-dom'
import App from './App'
import React from "react";

// ะ˜ะฝะธั†ะธะฐะปะธะทะธั€ัƒะตะผ ะฟั€ะธะปะพะถะตะฝะธะต ั background ะพะฑัŠะตะบั‚ะพะผ ะฒ ะบะฐั‡ะตัั‚ ะฒะต props
export async function initApp(background){
    render(
        <App background={background}/>,
        document.getElementById('app-content')
    );
}

mobx-แƒ˜แƒ— แƒซแƒแƒšแƒ˜แƒแƒœ แƒแƒ“แƒ•แƒ˜แƒšแƒ˜แƒ แƒ แƒ”แƒœแƒ“แƒ”แƒ แƒ˜แƒก แƒ“แƒแƒฌแƒงแƒ”แƒ‘แƒ, แƒ แƒแƒ“แƒ”แƒกแƒแƒช แƒ›แƒแƒœแƒแƒชแƒ”แƒ›แƒ”แƒ‘แƒ˜ แƒ˜แƒชแƒ•แƒšแƒ”แƒ‘แƒ. แƒฉแƒ•แƒ”แƒœ แƒฃแƒ‘แƒ แƒแƒšแƒแƒ“ แƒ•แƒ™แƒ˜แƒ“แƒ”แƒ‘แƒ— แƒ“แƒแƒ›แƒ™แƒ•แƒ˜แƒ แƒ•แƒ”แƒ‘แƒšแƒ˜แƒก แƒ“แƒ”แƒ™แƒแƒ แƒแƒขแƒแƒ แƒก แƒจแƒ”แƒคแƒฃแƒ—แƒ•แƒ˜แƒ“แƒแƒœ mobx-แƒ แƒ”แƒแƒฅแƒขแƒ˜ แƒ™แƒแƒ›แƒžแƒแƒœแƒ”แƒœแƒขแƒ–แƒ” แƒ“แƒ แƒ แƒ”แƒœแƒ“แƒ”แƒ แƒ˜ แƒแƒ•แƒขแƒแƒ›แƒแƒขแƒฃแƒ แƒแƒ“ แƒ’แƒแƒ›แƒแƒ˜แƒซแƒแƒฎแƒ”แƒ‘แƒ, แƒ แƒแƒ“แƒ”แƒกแƒแƒช แƒ™แƒแƒ›แƒžแƒแƒœแƒ”แƒœแƒขแƒ˜แƒก แƒ›แƒ˜แƒ”แƒ  แƒ›แƒ˜แƒ—แƒ˜แƒ—แƒ”แƒ‘แƒฃแƒš แƒ แƒแƒ›แƒ”แƒšแƒ˜แƒ›แƒ” แƒ“แƒแƒ™แƒ•แƒ˜แƒ แƒ•แƒ”แƒ‘แƒ แƒจแƒ”แƒ˜แƒชแƒ•แƒšแƒ”แƒ‘แƒ. แƒ—แƒฅแƒ•แƒ”แƒœ แƒแƒ  แƒ’แƒญแƒ˜แƒ แƒ“แƒ”แƒ‘แƒแƒ— 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

แƒแƒฎแƒแƒšแƒ˜ แƒ™แƒแƒ›แƒ”แƒœแƒขแƒแƒ แƒ˜แƒก แƒ“แƒแƒ›แƒแƒขแƒ”แƒ‘แƒ