ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื‘ื ื™ื’ื•ื“ ืœืืจื›ื™ื˜ืงื˜ื•ืจืช "ืฉืจืช-ืœืงื•ื—" ื”ื ืคื•ืฆื”, ื™ื™ืฉื•ืžื™ื ืžื‘ื•ื–ืจื™ื ืžืื•ืคื™ื™ื ื™ื ื‘:

  • ืื™ืŸ ืฆื•ืจืš ืœืื—ืกืŸ ืžืกื“ ื ืชื•ื ื™ื ืขื ื›ื ื™ืกื•ืช ืžืฉืชืžืฉ ื•ืกื™ืกืžืื•ืช. ืคืจื˜ื™ ื”ื’ื™ืฉื” ืžืื•ื—ืกื ื™ื ืืš ื•ืจืง ืขืœ ื™ื“ื™ ื”ืžืฉืชืžืฉื™ื ืขืฆืžื, ื•ืื™ืฉื•ืจ ื”ืื•ืชื ื˜ื™ื•ืช ืฉืœื”ื ืžืชืจื—ืฉ ื‘ืจืžืช ื”ืคืจื•ื˜ื•ืงื•ืœ.
  • ืื™ืŸ ืฆื•ืจืš ืœื”ืฉืชืžืฉ ื‘ืฉืจืช. ื”ืœื•ื’ื™ืงื” ืฉืœ ื”ืืคืœื™ืงืฆื™ื” ื™ื›ื•ืœื” ืœื”ืชื‘ืฆืข ื‘ืจืฉืช ื‘ืœื•ืงืฆ'ื™ื™ืŸ, ืฉื‘ื” ื ื™ืชืŸ ืœืื—ืกืŸ ืืช ื›ืžื•ืช ื”ื ืชื•ื ื™ื ื”ื ื“ืจืฉืช.

ื™ืฉื ื 2 ืื—ืกื•ืŸ ื‘ื˜ื•ื— ื™ื—ืกื™ืช ืœืžืคืชื—ื•ืช ืžืฉืชืžืฉ - ืืจื ืงื™ ื—ื•ืžืจื” ื•ื”ืจื—ื‘ื•ืช ืœื“ืคื“ืคืŸ. ืืจื ืงื™ ื—ื•ืžืจื” ื”ื ืœืจื•ื‘ ืžืื•ื‘ื˜ื—ื™ื ื‘ืžื™ื•ื—ื“, ืืš ืงืฉื™ื ืœืฉื™ืžื•ืฉ ื•ืจื—ื•ืงื™ื ืžืœื”ื™ื•ืช ื—ื™ื ืžื™ื™ื, ืืš ื”ืจื—ื‘ื•ืช ืœื“ืคื“ืคืŸ ื”ืŸ ื”ืฉื™ืœื•ื‘ ื”ืžื•ืฉืœื ื‘ื™ืŸ ืื‘ื˜ื—ื” ื•ืงืœื•ืช ืฉื™ืžื•ืฉ, ื•ื™ื›ื•ืœื•ืช ืœื”ื™ื•ืช ื’ื ื—ื™ื ืžื™ื•ืช ืœื—ืœื•ื˜ื™ืŸ ืขื‘ื•ืจ ืžืฉืชืžืฉื™ ื”ืงืฆื”.

ื‘ื”ืชื—ืฉื‘ ื‘ื›ืœ ื–ื”, ืจืฆื™ื ื• ืœื™ืฆื•ืจ ืืช ื”ื”ืจื—ื‘ื” ื”ืžืื•ื‘ื˜ื—ืช ื‘ื™ื•ืชืจ ืฉืžืคืฉื˜ืช ืืช ื”ืคื™ืชื•ื— ืฉืœ ื™ื™ืฉื•ืžื™ื ืžื‘ื•ื–ืจื™ื ืขืœ ื™ื“ื™ ืžืชืŸ API ืคืฉื•ื˜ ืœืขื‘ื•ื“ื” ืขื ืขืกืงืื•ืช ื•ื—ืชื™ืžื•ืช.
ื ืกืคืจ ืœื›ื ืขืœ ื”ื—ื•ื•ื™ื” ื”ื–ื• ืœื”ืœืŸ.

ื”ืžืืžืจ ื™ื›ื™ืœ ื”ื•ืจืื•ืช ืฉืœื‘ ืื—ืจ ืฉืœื‘ ื›ื™ืฆื“ ืœื›ืชื•ื‘ ืชื•ืกืฃ ืœื“ืคื“ืคืŸ, ืขื ื“ื•ื’ืžืื•ืช ืงื•ื“ ื•ืฆื™ืœื•ืžื™ ืžืกืš. ืืชื” ื™ื›ื•ืœ ืœืžืฆื•ื ืืช ื›ืœ ื”ืงื•ื“ ื‘ ืžืื’ืจื™ื. ื›ืœ ื”ืชื—ื™ื™ื‘ื•ืช ืชื•ืืžืช ื‘ืื•ืคืŸ ื”ื’ื™ื•ื ื™ ืœืงื˜ืข ืฉืœ ืžืืžืจ ื–ื”.

ื”ื™ืกื˜ื•ืจื™ื” ืงืฆืจื” ืฉืœ ื”ืจื—ื‘ื•ืช ื“ืคื“ืคืŸ

ื”ืจื—ื‘ื•ืช ืœื“ืคื“ืคืŸ ืงื™ื™ืžื•ืช ื›ื‘ืจ ื”ืจื‘ื” ื–ืžืŸ. ื”ื ื”ื•ืคื™ืขื• ื‘-Internet Explorer ื‘-1999, ื‘-Firefox ื‘-2004. ืขื ื–ืืช, ื‘ืžืฉืš ื–ืžืŸ ืจื‘ ืžืื•ื“ ืœื ื”ื™ื” ืชืงืŸ ืื—ื“ ืœื”ืจื—ื‘ื•ืช.

ืื ื• ื™ื›ื•ืœื™ื ืœื•ืžืจ ืฉื”ื•ื ื”ื•ืคื™ืข ื™ื—ื“ ืขื ื”ืจื—ื‘ื•ืช ื‘ื’ืจืกื” ื”ืจื‘ื™ืขื™ืช ืฉืœ Google Chrome. ื›ืžื•ื‘ืŸ ืฉืœื ื”ื™ื” ืื– ืžืคืจื˜, ืื‘ืœ ื–ื” ื”ื™ื” ื”-API ืฉืœ Chrome ืฉื”ืคืš ืœื‘ืกื™ืก ืฉืœื•: ืœืื—ืจ ืฉื›ื‘ืฉ ืืช ืจื•ื‘ ืฉื•ืง ื”ื“ืคื“ืคื ื™ื ื•ื‘ืขืœ ื—ื ื•ืช ืืคืœื™ืงืฆื™ื•ืช ืžื•ื‘ื ื™ืช, ื›ืจื•ื ืœืžืขืฉื” ืงื‘ืข ืืช ื”ืกื˜ื ื“ืจื˜ ืœื”ืจื—ื‘ื•ืช ื“ืคื“ืคืŸ.

ืœืžื•ื–ื™ืœื” ื”ื™ื” ืกื˜ื ื“ืจื˜ ืžืฉืœื”, ืืš ื‘ืขืงื‘ื•ืช ื”ืคื•ืคื•ืœืจื™ื•ืช ืฉืœ ืชื•ืกืคื™ Chrome, ื”ื—ื‘ืจื” ื”ื—ืœื™ื˜ื” ืœื™ืฆื•ืจ API ืชื•ืื. ื‘ืฉื ืช 2015, ื‘ื™ื•ื–ืžืช ืžื•ื–ื™ืœื”, ื ื•ืฆืจื” ืงื‘ื•ืฆื” ืžื™ื•ื—ื“ืช ื‘ืชื•ืš ืงื•ื ืกื•ืจืฆื™ื•ื ื”ืื™ื ื˜ืจื ื˜ ื”ืขื•ืœืžื™ (W3C) ืœืขื‘ื•ื“ื” ืขืœ ืžืคืจื˜ื™ ืชื•ืกืคื™ื ื—ื•ืฆื™ ื“ืคื“ืคืŸ.

ื”ืจื—ื‘ื•ืช ื”-API ื”ืงื™ื™ืžื•ืช ืขื‘ื•ืจ Chrome ื ืœืงื—ื• ื›ื‘ืกื™ืก. ื”ืขื‘ื•ื“ื” ื‘ื•ืฆืขื” ื‘ืชืžื™ื›ืช ืžื™ืงืจื•ืกื•ืคื˜ (ื’ื•ื’ืœ ืกื™ืจื‘ื” ืœื”ืฉืชืชืฃ ื‘ืคื™ืชื•ื— ื”ืชืงืŸ), ื•ื›ืชื•ืฆืื” ืžื›ืš ื”ื•ืคื™ืขื” ื˜ื™ื•ื˜ื” ืžืคืจื˜ื™ื.

ืจืฉืžื™ืช, ื”ืžืคืจื˜ ื ืชืžืš ืขืœ ื™ื“ื™ Edge, Firefox ื•-Opera (ืฉื™ื ืœื‘ ืฉื›ืจื•ื ืื™ื ื• ื‘ืจืฉื™ืžื” ื–ื•). ืื‘ืœ ืœืžืขืฉื”, ื”ืชืงืŸ ืชื•ืื ื‘ืžื™ื“ื” ืจื‘ื” ืœื›ืจื•ื, ืžื›ื™ื•ื•ืŸ ืฉื”ื•ื ื ื›ืชื‘ ืœืžืขืฉื” ืขืœ ืกืžืš ื”ื”ืจื—ื‘ื•ืช ืฉืœื•. ืืชื” ื™ื›ื•ืœ ืœืงืจื•ื ืขื•ื“ ืขืœ ื”-API ืฉืœ WebExtensions ื›ืืŸ.

ืžื‘ื ื” ื”ืจื—ื‘ื”

ื”ืงื•ื‘ืฅ ื”ื™ื—ื™ื“ ืฉื ื“ืจืฉ ืขื‘ื•ืจ ื”ืกื™ื•ืžืช ื”ื•ื ื”ืžื ื™ืคืกื˜ (manifest.json). ื–ื•ื”ื™ ื’ื "ื ืงื•ื“ืช ื”ื›ื ื™ืกื”" ืœื”ืจื—ื‘ื”.

ื”ืžื ื™ืคืกื˜

ืœืคื™ ื”ืžืคืจื˜, ืงื•ื‘ืฅ ื”ืžื ื™ืคืกื˜ ื”ื•ื ืงื•ื‘ืฅ JSON ื—ื•ืงื™. ืชื™ืื•ืจ ืžืœื ืฉืœ ืžืคืชื—ื•ืช ืžื ื™ืคืกื˜ ืขื ืžื™ื“ืข ืœื’ื‘ื™ ื”ืžืคืชื—ื•ืช ื”ื ืชืžื›ื™ื ื‘ืื™ื–ื” ื“ืคื“ืคืŸ ื ื™ืชืŸ ืœื”ืฆื™ื’ ื›ืืŸ.

"ืขืฉื•ื™" ืœื”ืชืขืœื ืžืžืคืชื—ื•ืช ืฉืื™ื ื ื‘ืžืคืจื˜ (ื’ื Chrome ื•ื’ื Firefox ืžื“ื•ื•ื—ื™ื ืขืœ ืฉื’ื™ืื•ืช, ืื‘ืœ ื”ื”ืจื—ื‘ื•ืช ืžืžืฉื™ื›ื•ืช ืœืขื‘ื•ื“).

ื•ืื ื™ ืจื•ืฆื” ืœื”ืคื ื•ืช ืืช ืชืฉื•ืžืช ื”ืœื‘ ืœื›ืžื” ื ืงื•ื“ื•ืช.

  1. ืจืงืข - ืื•ื‘ื™ื™ืงื˜ ื”ื›ื•ืœืœ ืืช ื”ืฉื“ื•ืช ื”ื‘ืื™ื:
    1. ืกืงืจื™ืคื˜ื™ื - ืžืขืจืš ืฉืœ ืกืงืจื™ืคื˜ื™ื ืฉื™ื‘ื•ืฆืขื• ื‘ื”ืงืฉืจ ื”ืจืงืข (ื ื“ื‘ืจ ืขืœ ื–ื” ืงืฆืช ืžืื•ื—ืจ ื™ื•ืชืจ);
    2. ืขืžื•ื“ - ื‘ืžืงื•ื ืกืงืจื™ืคื˜ื™ื ืฉื™ื‘ื•ืฆืขื• ื‘ืขืžื•ื“ ืจื™ืง, ื ื™ืชืŸ ืœืฆื™ื™ืŸ html ืขื ืชื•ื›ืŸ. ื‘ืžืงืจื” ื–ื”, ืชืชืขืœื ืžืฉื“ื” ื”ืกืงืจื™ืคื˜, ื•ื™ื”ื™ื” ืฆื•ืจืš ืœื”ื›ื ื™ืก ืืช ื”ืกืงืจื™ืคื˜ื™ื ืœื“ืฃ ื”ืชื•ื›ืŸ;
    3. ืœื”ืชืžื™ื“ - ื“ื’ืœ ื‘ื™ื ืืจื™, ืื ืœื ืฆื•ื™ืŸ, ื”ื“ืคื“ืคืŸ "ื™ื”ืจื•ื’" ืืช ืชื”ืœื™ืš ื”ืจืงืข ื›ืืฉืจ ื”ื•ื ื™ื—ืฉื•ื‘ ืฉื”ื•ื ืœื ืขื•ืฉื” ื›ืœื•ื, ื•ื™ืชื—ื™ืœ ืื•ืชื• ืžื—ื“ืฉ ื‘ืžื™ื“ืช ื”ืฆื•ืจืš. ืื—ืจืช, ื”ืขืžื•ื“ ื™ื™ืคืจืง ืจืง ื›ืฉื”ื“ืคื“ืคืŸ ืกื’ื•ืจ. ืœื ื ืชืžืš ื‘ืคื™ื™ืจืคื•ืงืก.
  2. content_scripts - ืžืขืจืš ืฉืœ ืื•ื‘ื™ื™ืงื˜ื™ื ื”ืžืืคืฉืจ ืœืš ืœื˜ืขื•ืŸ ืกืงืจื™ืคื˜ื™ื ืฉื•ื ื™ื ืœื“ืคื™ ืื™ื ื˜ืจื ื˜ ืฉื•ื ื™ื. ื›ืœ ืื•ื‘ื™ื™ืงื˜ ืžื›ื™ืœ ืืช ื”ืฉื“ื•ืช ื”ื—ืฉื•ื‘ื™ื ื”ื‘ืื™ื:
    1. ื’ืคืจื•ืจื™ื - ื›ืชื•ื‘ืช ืืชืจ ืฉืœ ื“ืคื•ืก, ืฉืงื•ื‘ืข ืื ืกืงืจื™ืคื˜ ืชื•ื›ืŸ ืžืกื•ื™ื ื™ื™ื›ืœืœ ืื• ืœื.
    2. js - ืจืฉื™ืžื” ืฉืœ ืกืงืจื™ืคื˜ื™ื ืฉื™ื˜ืขื ื• ืœื”ืชืืžื” ื–ื•;
    3. exclude_matches - ืื™ื ื• ื›ื•ืœืœ ืžื”ืฉื˜ื— match ื›ืชื•ื‘ื•ืช ืืชืจื™ื ื”ืชื•ืืžื•ืช ืœืฉื“ื” ื–ื”.
  3. page_action - ื”ื•ื ืœืžืขืฉื” ืื•ื‘ื™ื™ืงื˜ ืฉืื—ืจืื™ ืขืœ ื”ืื™ื™ืงื•ืŸ ื”ืžื•ืฆื’ ืœืฆื“ ืฉื•ืจืช ื”ื›ืชื•ื‘ืช ื‘ื“ืคื“ืคืŸ ื•ื”ืื™ื ื˜ืจืืงืฆื™ื” ืขืžื•. ื–ื” ื’ื ืžืืคืฉืจ ืœืš ืœื”ืฆื™ื’ ื—ืœื•ืŸ ืงื•ืคืฅ, ื”ืžื•ื’ื“ืจ ื‘ืืžืฆืขื•ืช HTML, CSS ื•-JS ืžืฉืœืš.
    1. default_popup - ื ืชื™ื‘ ืœืงื•ื‘ืฅ HTML ืขื ืžืžืฉืง ืงื•ืคืฅ, ืขืฉื•ื™ ืœื”ื›ื™ืœ CSS ื•-JS.
  4. ื”ืจืฉืื•ืช - ืžืขืจืš ืœื ื™ื”ื•ืœ ื–ื›ื•ื™ื•ืช ื”ืจื—ื‘ื”. ื™ืฉื ื 3 ืกื•ื’ื™ ื–ื›ื•ื™ื•ืช, ื”ืžืชื•ืืจื™ื ื‘ืคื™ืจื•ื˜ ื›ืืŸ
  5. ืžืฉืื‘ื™ื_ื ื’ื™ืฉื™ื_ืื™ื ื˜ืจื ื˜ - ืžืฉืื‘ื™ ื”ืจื—ื‘ื” ืฉื“ืฃ ืื™ื ื˜ืจื ื˜ ื™ื›ื•ืœ ืœื‘ืงืฉ, ืœืžืฉืœ, ืชืžื•ื ื•ืช, JS, CSS, ืงื•ื‘ืฆื™ HTML.
  6. ื—ื™ืฆื•ื ื™_ื ื™ืชืŸ ืœื—ื™ื‘ื•ืจ - ื›ืืŸ ืืชื” ื™ื›ื•ืœ ืœืฆื™ื™ืŸ ื‘ืžืคื•ืจืฉ ืืช ื”ืžื–ื”ื™ื ืฉืœ ื”ืจื—ื‘ื•ืช ื•ื“ื•ืžื™ื™ื ื™ื ืื—ืจื™ื ืฉืœ ื“ืคื™ ืื™ื ื˜ืจื ื˜ ืฉืžื”ื ืืชื” ื™ื›ื•ืœ ืœื”ืชื—ื‘ืจ. ื“ื•ืžื™ื™ืŸ ื™ื›ื•ืœ ืœื”ื™ื•ืช ื‘ืจืžื” ืฉื ื™ื” ื•ืžืขืœื”. ืœื ืขื•ื‘ื“ ื‘ืคื™ื™ืจืคื•ืงืก.

ื”ืงืฉืจ ื‘ื™ืฆื•ืข

ืœืชื•ืกืฃ ื™ืฉ ืฉืœื•ืฉื” ื”ืงืฉืจื™ื ืœื‘ื™ืฆื•ืข ืงื•ื“, ื›ืœื•ืžืจ, ื”ืืคืœื™ืงืฆื™ื” ืžื•ืจื›ื‘ืช ืžืฉืœื•ืฉื” ื—ืœืงื™ื ืขื ืจืžื•ืช ืฉื•ื ื•ืช ืฉืœ ื’ื™ืฉื” ืœ-API ืฉืœ ื”ื“ืคื“ืคืŸ.

ื”ืงืฉืจ ืฉืœ ื”ืจื—ื‘ื”

ืจื•ื‘ ื”-API ื–ืžื™ืŸ ื›ืืŸ. ื‘ื”ืงืฉืจ ื–ื” ื”ื "ื—ื™ื™ื":

  1. ืขืžื•ื“ ืจืงืข - ื—ืœืง "ื’ื‘ื™" ืฉืœ ื”ื”ืจื—ื‘ื”. ื”ืงื•ื‘ืฅ ืžืฆื•ื™ืŸ ื‘ืžื ื™ืคืกื˜ ื‘ืืžืฆืขื•ืช ืžืงืฉ "ืจืงืข".
  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 - ื•ื›ื‘ืจ ื ื™ืชืŸ ืœื˜ืขื•ืŸ ืืช ื”ืืคืœื™ืงืฆื™ื” ืฉืœื ื• ืœื’ื•ื’ืœ ื›ืจื•ื ื•ืœื•ื•ื“ื ืฉื”ื™ื ืคื•ืขืœืช.

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

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื›ืขืช ื”ืชื•ืกืฃ ืฉืœื ื• ืžื•ืชืงืŸ ื•ืขื•ื‘ื“. ืืชื” ื™ื›ื•ืœ ืœื”ืคืขื™ืœ ืืช ื›ืœื™ ื”ืžืคืชื—ื™ื ืขื‘ื•ืจ ื”ืงืฉืจื™ื ืฉื•ื ื™ื ื‘ืื•ืคืŸ ื”ื‘ื:

ืงื•ืคืฅ ->

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื”ื’ื™ืฉื” ืœืžืกื•ืฃ ืกืงืจื™ืคื˜ ื”ืชื•ื›ืŸ ืžืชื‘ืฆืขืช ื“ืจืš ื”ืžืกื•ืฃ ืฉืœ ื”ื“ืฃ ืขืฆืžื• ื‘ื• ื”ื•ื ืžื•ืคืขืœ.ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื”ื•ื“ืขื•ืช

ืื–, ืื ื—ื ื• ืฆืจื™ื›ื™ื ืœื”ืงื™ื ืฉื ื™ ืขืจื•ืฆื™ ืชืงืฉื•ืจืช: inpage <-> ืจืงืข ื•ืจืงืข ืงื•ืคืฅ <->. ืืชื” ื™ื›ื•ืœ, ื›ืžื•ื‘ืŸ, ืคืฉื•ื˜ ืœืฉืœื•ื— ื”ื•ื“ืขื•ืช ืœืคื•ืจื˜ ื•ืœื”ืžืฆื™ื ืคืจื•ื˜ื•ืงื•ืœ ืžืฉืœืš, ืื‘ืœ ืื ื™ ืžืขื“ื™ืฃ ืืช ื”ื’ื™ืฉื” ืฉืจืื™ืชื™ ื‘ืคืจื•ื™ืงื˜ ื”ืงื•ื“ ื”ืคืชื•ื— ืฉืœ metamask.

ื–ื•ื”ื™ ื”ืจื—ื‘ื” ืœื“ืคื“ืคืŸ ืœืขื‘ื•ื“ื” ืขื ืจืฉืช Ethereum. ื‘ื•, ื—ืœืงื™ื ืฉื•ื ื™ื ืฉืœ ื”ืืคืœื™ืงืฆื™ื” ืžืชืงืฉืจื™ื ื‘ืืžืฆืขื•ืช RPC ื‘ืืžืฆืขื•ืช ืกืคืจื™ื™ืช dnode. ื–ื” ืžืืคืฉืจ ืœืš ืœืืจื’ืŸ ื”ื—ืœืคื” ื“ื™ ืžื”ื™ืจื” ื•ื ื•ื—ื” ืื ืืชื” ืžืกืคืง ืœื” ื–ืจื nodejs ื›ื˜ืจื ืกืคื•ืจื˜ (ื›ืœื•ืžืจ ืื•ื‘ื™ื™ืงื˜ ืฉืžื™ื™ืฉื ืืช ืื•ืชื• ืžืžืฉืง):

import Dnode from "dnode/browser";

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

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

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

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

ื›ืขืช ื ื™ืฆื•ืจ ื›ื™ืชืช ื™ื™ืฉื•ืžื™ื. ื–ื” ื™ืฆื•ืจ ืื•ื‘ื™ื™ืงื˜ื™ API ืขื‘ื•ืจ ื”ื—ืœื•ืŸ ื”ืงื•ืคืฅ ื•ื“ืฃ ื”ืื™ื ื˜ืจื ื˜, ื•ื™ื™ืฆื•ืจ ืขื‘ื•ืจื dnode:

import Dnode from 'dnode/browser';

export class SignerApp {

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

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

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

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

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

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

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

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

ื›ืืŸ ื•ืœืžื˜ื”, ื‘ืžืงื•ื ืื•ื‘ื™ื™ืงื˜ Chrome ื”ืขื•ืœืžื™, ืื ื• ืžืฉืชืžืฉื™ื ื‘-extensionApi, ืืฉืจ ื ื™ื’ืฉืช ืœื›ืจื•ื ื‘ื“ืคื“ืคืŸ ืฉืœ ื’ื•ื’ืœ ื•ื‘ื“ืคื“ืคืŸ ื‘ืื—ืจื™ื. ื–ื” ื ืขืฉื” ืœืžืขืŸ ืชืื™ืžื•ืช ื‘ื™ืŸ ื“ืคื“ืคื ื™ื, ืื‘ืœ ืœืžื˜ืจื•ืช ืžืืžืจ ื–ื” ืืคืฉืจ ืคืฉื•ื˜ ืœื”ืฉืชืžืฉ ื‘-'chrome.runtime.connect'.

ื‘ื•ืื• ื ื™ืฆื•ืจ ืžื•ืคืข ื™ื™ืฉื•ื ื‘ืกืงืจื™ืคื˜ ื”ืจืงืข:

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

const app = new SignerApp();

// onConnect ัั€ะฐะฑะฐั‚ั‹ะฒะฐะตั‚ ะฟั€ะธ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ 'ะฟั€ะพั†ะตััะพะฒ' (contentscript, popup, ะธะปะธ ัั‚ั€ะฐะฝะธั†ะฐ ั€ะฐััˆะธั€ะตะฝะธั)
extensionApi.runtime.onConnect.addListener(connectRemote);

function connectRemote(remotePort) {
    const processName = remotePort.name;
    const portStream = new PortStream(remotePort);
    // ะŸั€ะธ ัƒัั‚ะฐะฝะพะฒะบะต ัะพะตะดะธะฝะตะฝะธั ะผะพะถะฝะพ ัƒะบะฐะทั‹ะฒะฐั‚ัŒ ะธะผั, ะฟะพ ัั‚ะพะผัƒ ะธะผะตะฝะธ ะผั‹ ะธ ะพะฟะฟั€ะตะดะตะปัะตะผ ะบั‚ะพ ะบ ะฝะฐะผ ะฟะพะดะปัŽั‡ะธะปัั, ะบะพะฝั‚ะตะฝั‚ัะบั€ะธะฟั‚ ะธะปะธ ui
    if (processName === 'contentscript'){
        const origin = remotePort.sender.url
        app.connectPage(portStream, origin)
    }else{
        app.connectPopup(portStream)
    }
}

ืžื›ื™ื•ื•ืŸ ืฉ-dnode ืขื•ื‘ื“ ืขื ื–ืจืžื™ื, ื•ืื ื• ืžืงื‘ืœื™ื ืคื•ืจื˜, ื™ืฉ ืฆื•ืจืš ื‘ืžื—ืœืงืช ืžืชืื. ื–ื” ื ืขืฉื” ื‘ืืžืฆืขื•ืช ืกืคืจื™ื™ืช ื”ื–ืจื ื”ืงืจื™ื, ื”ืžื™ื™ืฉืžืช ื–ืจืžื™ nodejs ื‘ื“ืคื“ืคืŸ:

import {Duplex} from 'readable-stream';

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

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

    _onDisconnect() {
        this.destroy()
    }

    _read(){}

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

ืขื›ืฉื™ื• ื‘ื•ืื• ื ื™ืฆื•ืจ ื—ื™ื‘ื•ืจ ื‘ืžืžืฉืง ื”ืžืฉืชืžืฉ:

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

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

setupUi().catch(console.error);

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

    const dnode = Dnode();

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

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

    // ะ”ะตะปะฐะตะผ ะพะฑัŠะตะบั‚ API ะดะพัั‚ัƒะฟะฝั‹ะผ ะธะท ะบะพะฝัะพะปะธ
    if (DEV_MODE){
        global.background = background;
    }
}

ืœืื—ืจ ืžื›ืŸ ืื ื• ื™ื•ืฆืจื™ื ืืช ื”ื—ื™ื‘ื•ืจ ื‘ืกืงืจื™ืคื˜ ื”ืชื•ื›ืŸ:

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

setupConnection();
injectScript();

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

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

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

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

ืžื›ื™ื•ื•ืŸ ืฉืื ื• ืฆืจื™ื›ื™ื ืืช ื”-API ืœื ื‘ืกืงืจื™ืคื˜ ื”ืชื•ื›ืŸ, ืืœื ื™ืฉื™ืจื•ืช ื‘ื“ืฃ, ืื ื• ืขื•ืฉื™ื ืฉื ื™ ื“ื‘ืจื™ื:

  1. ืื ื—ื ื• ื™ื•ืฆืจื™ื ืฉื ื™ ื–ืจืžื™ื. ืื—ื“ - ืœื›ื™ื•ื•ืŸ ื”ืขืžื•ื“, ืขืœ ื’ื‘ื™ ื”ืคื•ืกื˜ ื”ื•ื“ืขื”. ื‘ืฉื‘ื™ืœ ื–ื” ืื ื—ื ื• ืžืฉืชืžืฉื™ื ื‘ื–ื” ื”ื—ื‘ื™ืœื” ื”ื–ื• ืžื”ื™ื•ืฆืจื™ื ืฉืœ metamask. ื”ื–ืจื ื”ืฉื ื™ ื”ื•ื ื‘ืจืงืข ืžืขืœ ื”ื™ืฆื™ืื” ืฉื”ืชืงื‘ืœื” ืžืžื ื” runtime.connect. ื‘ื•ืื• ื ืงื ื” ืื•ืชื. ื›ืขืช ืœื“ืฃ ื™ื”ื™ื” ื–ืจื ืœืจืงืข.
  2. ื”ื›ื ืก ืืช ื”ืกืงืจื™ืคื˜ ืœ-DOM. ื”ื•ืจื“ ืืช ื”ืกืงืจื™ืคื˜ (ื”ื’ื™ืฉื” ืืœื™ื• ื”ื•ืชืจื” ื‘ืžื ื™ืคืกื˜) ื•ืฆื•ืจ ืชื’ script ืขื ืชื•ื›ื ื• ื‘ืคื ื™ื:

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

setupConnection();
injectScript();

function setupConnection(){
    // ะกั‚ั€ะธะผ ะบ ะฑะตะบะณั€ะฐัƒะฝะดัƒ
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    // ะกั‚ั€ะธะผ ะบ ัั‚ั€ะฐะฝะธั†ะต
    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

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

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

ื›ืขืช ืื ื• ื™ื•ืฆืจื™ื ืื•ื‘ื™ื™ืงื˜ API ื‘-inpage ื•ืžื’ื“ื™ืจื™ื ืื•ืชื• ืœื’ืœื•ื‘ืœ:

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

setupInpageApi().catch(console.error);

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

    const dnode = Dnode();

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

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

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

ืื ื—ื ื• ืžื•ื›ื ื™ื Call Procedure Remote (RPC) ืขื API ื ืคืจื“ ืขื‘ื•ืจ ื“ืฃ ื•ืžืžืฉืง ืžืฉืชืžืฉ. ื›ืืฉืจ ืžื—ื‘ืจื™ื ื“ืฃ ื—ื“ืฉ ืœืจืงืข ืื ื• ื™ื›ื•ืœื™ื ืœืจืื•ืช ื–ืืช:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ืžืžืฉืง API ื•ืžืงื•ืจ ืจื™ืงื™ื. ื‘ืฆื“ ื”ื“ืฃ, ืื ื• ื™ื›ื•ืœื™ื ืœืงืจื•ื ืœืคื•ื ืงืฆื™ื™ืช hello ื›ืš:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ืขื‘ื•ื“ื” ืขื ืคื•ื ืงืฆื™ื•ืช ื”ืชืงืฉืจื•ืช ื—ื•ื–ืจืช ื‘-JS ืžื•ื“ืจื ื™ืช ื”ื™ื ื ื™ืžื•ืกื™ื ื’ืจื•ืขื™ื, ืื– ื‘ื•ื ื ื›ืชื•ื‘ ืขื•ื–ืจ ืงื˜ืŸ ื›ื“ื™ ืœื™ืฆื•ืจ dnode ืฉืžืืคืฉืจ ืœืš ืœื”ืขื‘ื™ืจ ืื•ื‘ื™ื™ืงื˜ API ืœ- utils.

ืื•ื‘ื™ื™ืงื˜ื™ ื”-API ื™ื™ืจืื• ื›ืขืช ื›ืš:

export class SignerApp {

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

...

}

ืงื‘ืœืช ืื•ื‘ื™ื™ืงื˜ ืžืจื—ื•ืง ื›ืš:

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

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

ื•ืงืจื™ืื” ืœืคื•ื ืงืฆื™ื•ืช ืžื—ื–ื™ืจื” ื”ื‘ื˜ื—ื”:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื’ืจืกื” ืขื ืคื•ื ืงืฆื™ื•ืช ืืกื™ื ื›ืจื•ื ื™ื•ืช ื–ืžื™ื ื•ืช ื›ืืŸ.

ื‘ืกืš ื”ื›ืœ, ื’ื™ืฉืช ื”-RPC ื•ื”ื–ืจื ื ืจืื™ืช ื’ืžื™ืฉื” ืœืžื“ื™: ืื ื• ื™ื›ื•ืœื™ื ืœื”ืฉืชืžืฉ ื‘ืจื™ื‘ื•ื™ ืงื™ื˜ื•ืจ ื•ืœื™ืฆื•ืจ ืžืกืคืจ ืžืžืฉืงื™ API ืฉื•ื ื™ื ืขื‘ื•ืจ ืžืฉื™ืžื•ืช ืฉื•ื ื•ืช. ื‘ืื•ืคืŸ ืขืงืจื•ื ื™, ื ื™ืชืŸ ืœื”ืฉืชืžืฉ ื‘-dnode ื‘ื›ืœ ืžืงื•ื, ื”ืขื™ืงืจ ื”ื•ื ืœืขื˜ื•ืฃ ืืช ื”ืชื—ื‘ื•ืจื” ื‘ืฆื•ืจื” ืฉืœ ื–ืจื nodejs.

ื—ืœื•ืคื” ื”ื™ื ืคื•ืจืžื˜ JSON, ื”ืžื™ื™ืฉื ืืช ืคืจื•ื˜ื•ืงื•ืœ JSON RPC 2. ืขื ื–ืืช, ื”ื•ื ืขื•ื‘ื“ ืขื ืชืขื‘ื•ืจื•ืช ืกืคืฆื™ืคื™ื•ืช (TCP ื•-HTTP(S)), ืžื” ืฉืœื ื™ืฉื™ื ื‘ืžืงืจื” ืฉืœื ื•.

ืžืฆื‘ ืคื ื™ืžื™ ื•ืื—ืกื•ืŸ ืžืงื•ืžื™

ื ืฆื˜ืจืš ืœืื—ืกืŸ ืืช ื”ืžืฆื‘ ื”ืคื ื™ืžื™ ืฉืœ ื”ืืคืœื™ืงืฆื™ื” - ืœืคื—ื•ืช ืืช ืžืคืชื—ื•ืช ื”ื—ืชื™ืžื”. ืื ื• ื™ื›ื•ืœื™ื ื“ื™ ื‘ืงืœื•ืช ืœื”ื•ืกื™ืฃ ืžืฆื‘ ืœืืคืœื™ืงืฆื™ื” ื•ืฉื™ื˜ื•ืช ืœืฉื™ื ื•ื™ื• ื‘-API ื”ืžื•ืงืคืฅ:

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

export class SignerApp {

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

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

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

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

    ...

} 

ื‘ืจืงืข, ื ืขื˜ื•ืฃ ื”ื›ืœ ื‘ืคื•ื ืงืฆื™ื” ื•ื ื›ืชื•ื‘ ืืช ืื•ื‘ื™ื™ืงื˜ ื”ื™ื™ืฉื•ื ืœื—ืœื•ืŸ ื›ื“ื™ ืฉื ื•ื›ืœ ืœืขื‘ื•ื“ ืื™ืชื• ืžื”ืžืกื•ืฃ:

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

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

setupApp();

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

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

    extensionApi.runtime.onConnect.addListener(connectRemote);

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

ื‘ื•ืื• ื ื•ืกื™ืฃ ื›ืžื” ืžืคืชื—ื•ืช ืžืžืกื•ืฃ ืžืžืฉืง ื”ืžืฉืชืžืฉ ื•ื ืจืื” ืžื” ืงื•ืจื” ืขื ื”ืžืฆื‘:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื”ืžืฆื‘ ืฆืจื™ืš ืœื”ื™ืขืฉื•ืช ืงื‘ื•ืข ื›ื“ื™ ืฉื”ืžืคืชื—ื•ืช ืœื ื™ืื‘ื“ื• ื‘ืขืช ื”ืคืขืœื” ืžื—ื“ืฉ.

ื ืื—ืกืŸ ืื•ืชื• ื‘-localStorage, ื•ื ื—ืœื™ืฃ ืื•ืชื• ื‘ื›ืœ ืฉื™ื ื•ื™. ืœืื—ืจ ืžื›ืŸ, ื’ื™ืฉื” ืืœื™ื• ืชื”ื™ื” ื”ื›ืจื—ื™ืช ื’ื ืขื‘ื•ืจ ืžืžืฉืง ื”ืžืฉืชืžืฉ, ื•ืื ื™ ื’ื ืจื•ืฆื” ืœื”ื™ืจืฉื ืœืฉื™ื ื•ื™ื™ื. ื‘ื”ืชื‘ืกืก ืขืœ ื–ื”, ื™ื”ื™ื” ื ื•ื— ืœื™ืฆื•ืจ ืื—ืกื•ืŸ ื ื™ืชืŸ ืœืฆืคื™ื™ื” ื•ืœื”ื™ืจืฉื ืœืฉื™ื ื•ื™ื™ื ืฉืœื•.

ื ืฉืชืžืฉ ื‘ืกืคืจื™ื™ืช mobx (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 ืขื•ืงื‘ ืื—ืจ ื’ื™ืฉื” ืœืฉื“ื•ืช. ื ืขืฉื” ืฉื™ืžื•ืฉ ื‘-Gutters ื•-seters ืฉืœ ืื•ื‘ื™ื™ืงื˜ื™ proxy ืฉื”ืกืคืจื™ื™ื” ื™ื•ืฆืจืช.

ืžืขืฆื‘ื™ ืคืขื•ืœื” ืžืฉืจืชื™ื ืฉืชื™ ืžื˜ืจื•ืช:

  1. ื‘ืžืฆื‘ ืงืคื“ื ื™ ืขื ื“ื’ืœ enforceActions, mobx ืื•ืกืจ ืขืœ ืฉื™ื ื•ื™ ื”ืžื“ื™ื ื” ื™ืฉื™ืจื•ืช. ื–ื” ื ื—ืฉื‘ ื ื•ื”ื’ ื˜ื•ื‘ ืœืขื‘ื•ื“ ื‘ืชื ืื™ื ืžื—ืžื™ืจื™ื.
  2. ื’ื ืื ืคื•ื ืงืฆื™ื” ืžืฉื ื” ืืช ื”ืžืฆื‘ ืžืกืคืจ ืคืขืžื™ื - ืœืžืฉืœ, ืื ื• ืžืฉื ื™ื ืžืกืคืจ ืฉื“ื•ืช ื‘ื›ืžื” ืฉื•ืจื•ืช ืงื•ื“ - ื”ืฆื•ืคื™ื ืžืงื‘ืœื™ื ื”ื•ื“ืขื” ืจืง ื›ืฉื”ื™ื ืžืกืชื™ื™ืžืช. ื–ื” ื—ืฉื•ื‘ ื‘ืžื™ื•ื—ื“ ืขื‘ื•ืจ ื”-frontend, ืฉื‘ื• ืขื“ื›ื•ื ื™ ืžืฆื‘ ืžื™ื•ืชืจื™ื ืžื•ื‘ื™ืœื™ื ืœืขื™ื‘ื•ื“ ืžื™ื•ืชืจ ืฉืœ ืืœืžื ื˜ื™ื. ื‘ืžืงืจื” ืฉืœื ื•, ืœื ื”ืจืืฉื•ืŸ ื•ืœื ื”ืฉื ื™ ืจืœื•ื•ื ื˜ื™ ื‘ืžื™ื•ื—ื“, ืื‘ืœ ื ืคืขืœ ืœืคื™ ื”ืฉื™ื˜ื•ืช ื”ืžื•ืžืœืฆื•ืช. ื ื”ื•ื’ ืœืฆืจืฃ ื“ืงื•ืจื˜ื•ืจื™ื ืœื›ืœ ื”ืคื•ื ืงืฆื™ื•ืช ื”ืžืฉื ื•ืช ืืช ืžืฆื‘ ื”ืฉื“ื•ืช ื”ื ืฆืคื™ื.

ื‘ืจืงืข ื ื•ืกื™ืฃ ืืชื—ื•ืœ ื•ืฉืžื™ืจืช ื”ืžืฆื‘ ื‘-localStorage:

import {reaction, toJS} from 'mobx';
import {extensionApi} from "./utils/extensionApi";
import {PortStream} from "./utils/PortStream";
import {SignerApp} from "./SignerApp";
// ะ’ัะฟะพะผะพะณะฐั‚ะตะปัŒะฝั‹ะต ะผะตั‚ะพะดั‹. ะ—ะฐะฟะธัั‹ะฒะฐัŽั‚/ั‡ะธั‚ะฐัŽั‚ ะพะฑัŠะตะบั‚ ะฒ/ะธะท localStorage ะฒะธะดะต JSON ัั‚ั€ะพะบะธ ะฟะพ ะบะปัŽั‡ัƒ 'store'
import {loadState, saveState} from "./utils/localStorage";

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

setupApp();

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

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

    // Setup state persistence

    // ะ ะตะทัƒะปัŒั‚ะฐั‚ reaction ะฟั€ะธัะฒะฐะธะฒะฐะตั‚ัั ะฟะตั€ะตะผะตะฝะฝะพะน, ั‡ั‚ะพะฑั‹ ะฟะพะดะฟะธัะบัƒ ะผะพะถะฝะพ ะฑั‹ะปะพ ะพั‚ะผะตะฝะธั‚ัŒ. ะะฐะผ ัั‚ะพ ะฝะต ะฝัƒะถะฝะพ, ะพัั‚ะฐะฒะปะตะฝะพ ะดะปั ะฟั€ะธะผะตั€ะฐ
    const localStorageReaction = reaction(
        () => toJS(app.store), // ะคัƒะฝะบั†ะธั-ัะตะปะตะบั‚ะพั€ ะดะฐะฝะฝั‹ั…
        saveState // ะคัƒะฝะบั†ะธั, ะบะพั‚ะพั€ะฐั ะฑัƒะดะตั‚ ะฒั‹ะทะฒะฐะฝะฐ ะฟั€ะธ ะธะทะผะตะฝะตะฝะธะธ ะดะฐะฝะฝั‹ั…, ะบะพั‚ะพั€ั‹ะต ะฒะพะทะฒั€ะฐั‰ะฐะตั‚ ัะตะปะตะบั‚ะพั€
    );

    extensionApi.runtime.onConnect.addListener(connectRemote);

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

ืคื•ื ืงืฆื™ื™ืช ื”ืชื’ื•ื‘ื” ืžืขื ื™ื™ื ืช ื›ืืŸ. ื™ืฉ ืœื• ืฉื ื™ ื˜ื™ืขื•ื ื™ื:

  1. ื‘ื•ืจืจ ื ืชื•ื ื™ื.
  2. ืžื˜ืคืœ ืฉื™ื™ืงืจื ืขื ื”ื ืชื•ื ื™ื ื”ืืœื” ื‘ื›ืœ ืคืขื ืฉื”ื•ื ืžืฉืชื ื”.

ื‘ื ื™ื’ื•ื“ ืœ-redux, ืฉื‘ื• ืื ื• ืžืงื‘ืœื™ื ื‘ืื•ืคืŸ ืžืคื•ืจืฉ ืืช ื”ืžืฆื‘ ื›ืืจื’ื•ืžื ื˜, mobx ื–ื•ื›ืจ ืœืื™ืœื• ืชืฆืคื™ื•ืช ืื ื• ื ื™ื’ืฉื™ื ื‘ืชื•ืš ื”ื‘ื•ืจืจ, ื•ืงื•ืจื ืœืžื˜ืคืœ ืจืง ื›ืฉื”ื ืžืฉืชื ื™ื.

ื—ืฉื•ื‘ ืœื”ื‘ื™ืŸ ื‘ื“ื™ื•ืง ื›ื™ืฆื“ mobx ืžื—ืœื™ื˜ ืœืื™ืœื• ืชืฆืคื™ื•ืช ืื ื• ืžื ื•ื™ื™ื. ืื ื›ืชื‘ืชื™ ื‘ื•ืจืจ ื‘ืงื•ื“ ื›ื–ื”() => app.store, ืื– ืชื’ื•ื‘ื” ืœืขื•ืœื ืœื ืชื™ืงืจื, ืžื›ื™ื•ื•ืŸ ืฉื”ืื—ืกื•ืŸ ืขืฆืžื• ืื™ื ื• ื ื™ืชืŸ ืœืฆืคื™ื™ื”, ืจืง ื”ืฉื“ื•ืช ืฉืœื•.

ืื ื›ืชื‘ืชื™ ืืช ื–ื” ื›ื›ื” () => app.store.keys, ืื– ืฉื•ื‘ ืฉื•ื ื“ื‘ืจ ืœื ื™ืงืจื”, ืฉื›ืŸ ื‘ืขืช โ€‹โ€‹ื”ื•ืกืคื”/ื”ืกืจื” ืฉืœ ืจื›ื™ื‘ื™ ืžืขืจืš, ื”ื”ืชื™ื™ื—ืกื•ืช ืืœื™ื• ืœื ืชืฉืชื ื”.

Mobx ืคื•ืขืœืช ื›ื‘ื•ืจืจ ื‘ืคืขื ื”ืจืืฉื•ื ื” ื•ืขื•ืงื‘ืช ืจืง ืื—ืจ ื ืงื•ื“ื•ืช ืฆืคื™ื™ื” ืฉืืœื™ื”ื ื ื™ื’ืฉื ื•. ื–ื” ื ืขืฉื” ื‘ืืžืฆืขื•ืช Proxy getters. ืœื›ืŸ, ื”ืคื•ื ืงืฆื™ื” ื”ืžื•ื‘ื ื™ืช ืžืฉืžืฉืช ื›ืืŸ toJS. ื”ื•ื ืžื—ื–ื™ืจ ืื•ื‘ื™ื™ืงื˜ ื—ื“ืฉ ืขื ื›ืœ ืฉืจืช ื”-proxy ืžื•ื—ืœืคื™ื ื‘ืฉื“ื•ืช ื”ืžืงื•ืจื™ื™ื. ื‘ืžื”ืœืš ื”ื‘ื™ืฆื•ืข, ื”ื•ื ืงื•ืจื ืืช ื›ืœ ื”ืฉื“ื•ืช ืฉืœ ื”ืื•ื‘ื™ื™ืงื˜ - ื•ืžื›ืืŸ ืฉื”ืžืงื‘ืœื™ื ืžื•ืคืขืœื™ื.

ื‘ืžืกื•ืฃ ื”ืงื•ืคืฅ ื ื•ืกื™ืฃ ืฉื•ื‘ ืžืกืคืจ ืžืคืชื—ื•ืช. ื”ืคืขื ื”ื ื’ื ื”ื’ื™ืขื• ืœ-localStorage:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื›ืืฉืจ ื“ืฃ ื”ืจืงืข ื ื˜ืขืŸ ืžื—ื“ืฉ, ื”ืžื™ื“ืข ื ืฉืืจ ื‘ืžืงื•ืžื•.

ื ื™ืชืŸ ืœืจืื•ืช ืืช ื›ืœ ืงื•ื“ ื”ืืคืœื™ืงืฆื™ื” ืขื“ ืœื ืงื•ื“ื” ื–ื• ื›ืืŸ.

ืื—ืกื•ืŸ ืžืื•ื‘ื˜ื— ืฉืœ ืžืคืชื—ื•ืช ืคืจื˜ื™ื™ื

ืื—ืกื•ืŸ ืžืคืชื—ื•ืช ืคืจื˜ื™ื™ื ื‘ื˜ืงืกื˜ ื‘ืจื•ืจ ืื™ื ื• ื‘ื˜ื•ื—: ืชืžื™ื“ ื™ืฉ ืกื™ื›ื•ื™ ืฉื™ืคืจืฆื• ืœืš, ืชืงื‘ืœ ื’ื™ืฉื” ืœืžื—ืฉื‘ ืฉืœืš ื•ื›ื•'. ืœื›ืŸ, ื‘-localStorage ื ืื—ืกืŸ ืืช ื”ืžืคืชื—ื•ืช ื‘ืฆื•ืจื” ืžื•ืฆืคื ืช ื‘ืกื™ืกืžื”.

ืœื™ืชืจ ืื‘ื˜ื—ื”, ื ื•ืกื™ืฃ ืœืืคืœื™ืงืฆื™ื” ืžืฆื‘ ื ืขื™ืœื”, ื‘ื• ืœื ืชื”ื™ื” ื’ื™ืฉื” ืœืžืคืชื—ื•ืช ื›ืœืœ. ื ืขื‘ื™ืจ ืื•ื˜ื•ืžื˜ื™ืช ืืช ื”ืชื•ืกืฃ ืœืžืฆื‘ ื ืขื•ืœ ืขืงื‘ ืคืกืง ื–ืžืŸ.

Mobx ืžืืคืฉืจ ืœืš ืœืื—ืกืŸ ืจืง ืกื˜ ืžื™ื ื™ืžืœื™ ืฉืœ ื ืชื•ื ื™ื, ื•ื”ืฉืืจ ืžื—ื•ืฉื‘ ืื•ื˜ื•ืžื˜ื™ืช ืขืœ ืกืžืš ื–ื”. ืืœื• ื”ื ืžื” ืฉื ืงืจื ืžืืคื™ื™ื ื™ื ืžื—ื•ืฉื‘ื™ื. ื ื™ืชืŸ ืœื”ืฉื•ื•ืช ืื•ืชื ืœืชืฆื•ื’ื•ืช ื‘ืžืื’ืจื™ ืžื™ื“ืข:

import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// ะฃั‚ะธะปะธั‚ั‹ ะดะปั ะฑะตะทะพะฟะฐัะฝะพะณะพ ัˆะธั„ั€ะพะฒะฐะฝะธั ัั‚ั€ะพะบ. ะ˜ัะฟะพะปัŒะทัƒัŽั‚ crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";

export class SignerApp {
    constructor(initState = {}) {
        this.store = observable.object({
            // ะฅั€ะฐะฝะธะผ ะฟะฐั€ะพะปัŒ ะธ ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝั‹ะต ะบะปัŽั‡ะธ. ะ•ัะปะธ ะฟะฐั€ะพะปัŒ null - ะฟั€ะธะปะพะถะตะฝะธะต locked
            password: null,
            vault: initState.vault,

            // ะ“ะตั‚ั‚ะตั€ั‹ ะดะปั ะฒั‹ั‡ะธัะปะธะผั‹ั… ะฟะพะปะตะน. ะœะพะถะฝะพ ะฟั€ะพะฒะตัั‚ะธ ะฐะฝะฐะปะพะณะธัŽ ั view ะฒ ะฑะด.
            get locked(){
                return this.password == null
            },
            get keys(){
                return this.locked ?
                    undefined :
                    SignerApp._decryptVault(this.vault, this.password)
            },
            get initialized(){
                return this.vault !== undefined
            }
        })
    }
    // ะ˜ะฝะธั†ะธะฐะปะธะทะฐั†ะธั ะฟัƒัั‚ะพะณะพ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะฝะพะฒั‹ะผ ะฟะฐั€ะพะปะตะผ
    @action
    initVault(password){
        this.store.vault = SignerApp._encryptVault([], password)
    }
    @action
    lock() {
        this.store.password = null
    }
    @action
    unlock(password) {
        this._checkPassword(password);
        this.store.password = password
    }
    @action
    addKey(key) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
    }
    @action
    removeKey(index) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault([
                ...this.store.keys.slice(0, index),
                ...this.store.keys.slice(index + 1)
            ],
            this.store.password
        )
    }

    ... // ะบะพะด ะฟะพะดะบะปัŽั‡ะตะฝะธั ะธ api

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

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

    // ะœะตั‚ะพะดั‹ ะดะปั ัˆะธั„ั€ะพะฒะบะธ/ะดะตัˆะธั„ั€ะพะฒะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ
    static _encryptVault(obj, pass){
        const jsonString = JSON.stringify(obj)
        return encrypt(jsonString, pass)
    }

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

ื›ืขืช ืื ื• ืžืื—ืกื ื™ื ืจืง ืืช ื”ืžืคืชื—ื•ืช ื•ื”ืกื™ืกืžื” ื”ืžื•ืฆืคื ื™ื. ื›ืœ ื”ืฉืืจ ืžื—ื•ืฉื‘. ืื ื• ืžื‘ืฆืขื™ื ืืช ื”ื”ืขื‘ืจื” ืœืžืฆื‘ ื ืขื•ืœ ืขืœ ื™ื“ื™ ื”ืกืจืช ื”ืกื™ืกืžื” ืžื”ืžืฆื‘. ืœ-API ื”ืฆื™ื‘ื•ืจื™ ื™ืฉ ื›ืขืช ืฉื™ื˜ื” ืœืืชื—ื•ืœ ื”ืื—ืกื•ืŸ.

ื ื›ืชื‘ ืœื”ืฆืคื ื” ื›ืœื™ ืขื–ืจ ื”ืžืฉืชืžืฉื™ื ื‘-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. ืขื‘ื•ืจ ืกืจืง ืืชื” ื™ื›ื•ืœ ืœื”ื’ื“ื™ืจ ืคืกืง ื–ืžืŸ, ื•ื ืขื•ืœ ืžื•ื’ื“ืจ ื›ืืฉืจ ืžืขืจื›ืช ื”ื”ืคืขืœื” ืขืฆืžื” ื—ืกื•ืžื”. ืื ื• ื’ื ื ืฉื ื” ืืช ื”ื‘ื•ืจืจ ืœืฉืžื™ืจื” ื‘-localStorage:

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

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

setupApp();

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

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

    // ะขะตะฟะตั€ัŒ ะผั‹ ัะฒะฝะพ ัƒะทั‹ะฒะฐะตะผ ะฟะพะปะต, ะบะพั‚ะพั€ะพะผัƒ ะฑัƒะดะตั‚ ะฟั€ะพะธัั…ะพะดะธั‚ัŒ ะดะพัั‚ัƒะฟ, reaction ะพั‚ั€ะฐะฑะพั‚ะฐะตั‚ ะฝะพั€ะผะฐะปัŒะฝะพ
    reaction(
        () => ({
            vault: app.store.vault
        }),
        saveState
    );

    // ะขะฐะนะผะฐัƒั‚ ะฑะตะทะดะตะนัั‚ะฒะธั, ะบะพะณะดะฐ ัั€ะฐะฑะพั‚ะฐะตั‚ ัะพะฑั‹ั‚ะธะต
    extensionApi.idle.setDetectionInterval(IDLE_INTERVAL);
    // ะ•ัะปะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะทะฐะปะพั‡ะธะป ัะบั€ะฐะฝ ะธะปะธ ะฑะตะทะดะตะนัั‚ะฒะพะฒะฐะป ะฒ ั‚ะตั‡ะตะฝะธะต ัƒะบะฐะทะฐะฝะฝะพะณะพ ะธะฝั‚ะตั€ะฒะฐะปะฐ ะปะพั‡ะธะผ ะฟั€ะธะปะพะถะตะฝะธะต
    extensionApi.idle.onStateChanged.addListener(state => {
        if (['locked', 'idle'].indexOf(state) > -1) {
            app.lock()
        }
    });

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

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

ื”ืงื•ื“ ืœืคื ื™ ืฉืœื‘ ื–ื” ื”ื•ื ื›ืืŸ.

ืขืกืงื•ืช

ืื–, ื”ื’ืขื ื• ืœื“ื‘ืจ ื”ื—ืฉื•ื‘ ื‘ื™ื•ืชืจ: ื™ืฆื™ืจื” ื•ื—ืชื™ืžื” ืขืœ ืขืกืงืื•ืช ื‘ื‘ืœื•ืงืฆ'ื™ื™ืŸ. ื ืฉืชืžืฉ ื‘ื‘ืœื•ืงืฆ'ื™ื™ืŸ ื•ื‘ืกืคืจื™ื™ื” ืฉืœ WAVES ื’ืœื™ื-ืขืกืงืื•ืช.

ืจืืฉื™ืช, ื‘ื•ืื• ื ื•ืกื™ืฃ ืœืžื“ื™ื ื” ืžืขืจืš ืฉืœ ื”ื•ื“ืขื•ืช ืฉืฆืจื™ืš ืœื—ืชื•ื, ื•ืื– ื ื•ืกื™ืฃ ืฉื™ื˜ื•ืช ืœื”ื•ืกืคืช ื”ื•ื“ืขื” ื—ื“ืฉื”, ืื™ืฉื•ืจ ื”ื—ืชื™ืžื” ื•ืกื™ืจื•ื‘:

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

export class SignerApp {

    ...

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

        this.store.messages.push(message);

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

    ...
}

ื›ืืฉืจ ืื ื• ืžืงื‘ืœื™ื ื”ื•ื“ืขื” ื—ื“ืฉื”, ืื ื• ืžื•ืกื™ืคื™ื ืœื” ืžื˜ื ื ืชื•ื ื™ื, ืขืฉื” observable ื•ืœื”ื•ืกื™ืฃ ืœ store.messages.

ืื ืœื observable ื‘ืื•ืคืŸ ื™ื“ื ื™, ืื– mobx ื™ืขืฉื” ื–ืืช ื‘ืขืฆืžื• ื‘ืขืช ื”ื•ืกืคืช ื”ื•ื“ืขื•ืช ืœืžืขืจืš. ืขื ื–ืืช, ื–ื” ื™ืฆื•ืจ ืื•ื‘ื™ื™ืงื˜ ื—ื“ืฉ ืฉืœื ืชื”ื™ื” ืœื ื• ื”ืชื™ื™ื—ืกื•ืช ืืœื™ื•, ืื‘ืœ ื ืฆื˜ืจืš ืื•ืชื• ืœืฉืœื‘ ื”ื‘ื.

ืœืื—ืจ ืžื›ืŸ, ืื ื• ืžื—ื–ื™ืจื™ื ื”ื‘ื˜ื—ื” ืฉื ืคืชืจืช ื›ืืฉืจ ืกื˜ื˜ื•ืก ื”ื”ื•ื“ืขื” ืžืฉืชื ื”. ื”ืกื˜ื˜ื•ืก ืžื ื•ื˜ืจ ืขืœ ื™ื“ื™ ืชื’ื•ื‘ื”, ืืฉืจ "ืชื”ืจื•ื’ ืืช ืขืฆืžื”" ื›ืืฉืจ ื”ืžืฆื‘ ื™ืฉืชื ื”.

ืงื•ื“ ืฉื™ื˜ื” approve ะธ reject ืคืฉื•ื˜ ืžืื•ื“: ืื ื• ืคืฉื•ื˜ ืžืฉื ื™ื ืืช ืกื˜ื˜ื•ืก ื”ื”ื•ื“ืขื”, ืœืื—ืจ ื”ื—ืชื™ืžื” ืขืœื™ื” ื‘ืžื™ื“ืช ื”ืฆื•ืจืš.

ืฉืžื ื• ืืช Approve and Reject ื‘-UI API, newMessage ื‘-API ืฉืœ ื”ื“ืฃ:

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

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

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

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

    ...
}

ื›ืขืช ื ื ืกื” ืœื—ืชื•ื ืขืœ ื”ืขืกืงื” ืขื ื”ืชื•ืกืฃ:

ื›ืชื™ื‘ืช ืกื™ื•ืžืช ื“ืคื“ืคืŸ ืžืื•ื‘ื˜ื—ืช

ื‘ืื•ืคืŸ ื›ืœืœื™, ื”ื›ืœ ืžื•ื›ืŸ, ื›ืœ ืžื” ืฉื ืฉืืจ ื–ื” ื”ื•ืกืฃ ืžืžืฉืง ืžืฉืชืžืฉ ืคืฉื•ื˜.

UI

ื”ืžืžืฉืง ื–ืงื•ืง ืœื’ื™ืฉื” ืœืžืฆื‘ ื”ื™ื™ืฉื•ื. ื‘ืฆื“ UI ื ืขืฉื” observable state ื•ืœื”ื•ืกื™ืฃ ืคื•ื ืงืฆื™ื” ืœ-API ืฉืชืฉื ื” ืžืฆื‘ ื–ื”. ื‘ื•ืื• ื ื•ืกื™ืฃ observable ืœืื•ื‘ื™ื™ืงื˜ ื”-API ืฉื”ืชืงื‘ืœ ืžื”ืจืงืข:

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

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

setupUi().catch(console.error);

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

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

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

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

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

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

ื‘ืกื•ืฃ ืื ื—ื ื• ืžืชื—ื™ืœื™ื ืœืขื‘ื“ ืืช ืžืžืฉืง ื”ืืคืœื™ืงืฆื™ื”. ื–ื•ื”ื™ ืืคืœื™ืงืฆื™ื™ืช ืชื’ื•ื‘ื”. ืื•ื‘ื™ื™ืงื˜ ื”ืจืงืข ืžื•ืขื‘ืจ ืคืฉื•ื˜ ื‘ืืžืฆืขื•ืช ืื‘ื™ื–ืจื™ื. ื ื›ื•ืŸ ื™ื”ื™ื” ื›ืžื•ื‘ืŸ ืœืขืฉื•ืช ืฉื™ืจื•ืช ื ืคืจื“ ืœืฉื™ื˜ื•ืช ื•ื—ื ื•ืช ืœืžื“ื™ื ื”, ืื‘ืœ ืœืฆื•ืจื›ื™ ืžืืžืจ ื–ื” ืžืกืคื™ืง:

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

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

ืขื mobx ืงืœ ืžืื•ื“ ืœื”ืชื—ื™ืœ ืœืขื‘ื“ ื›ืืฉืจ ื”ื ืชื•ื ื™ื ืžืฉืชื ื™ื. ืื ื—ื ื• ืคืฉื•ื˜ ืชื•ืœื™ื ืืช ืžืขืฆื‘ ื”ืฆื•ืคื” ืžื”ื—ื‘ื™ืœื” mobx-react ืขืœ ื”ืจื›ื™ื‘, ื•ื”ืจื™ื ื“ื•ืจ ื™ื™ืงืจื ืื•ื˜ื•ืžื˜ื™ืช ื›ืืฉืจ ื›ืœ ื”ื ืชื•ื ื™ื ื”ื ื™ืชื ื™ื ืœืฆืคื™ื™ื” ืฉืืœื™ื”ื ื”ืจื›ื™ื‘ ื™ืฉืชื ื• ื™ืฉืชื ื•. ืืชื” ืœื ืฆืจื™ืš ืฉื•ื 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>
    }
}

ื ื™ืชืŸ ืœืจืื•ืช ืืช ื”ืจื›ื™ื‘ื™ื ื”ื ื•ืชืจื™ื ื‘ืงื•ื“ ื‘ืชื™ืงื™ื™ืช ืžืžืฉืง ื”ืžืฉืชืžืฉ.

ื›ืขืช ื‘ืžื—ืœืงืช ื”ื™ื™ืฉื•ืžื™ื ืขืœื™ืš ืœื™ืฆื•ืจ ื‘ื•ืจืจ ืžืฆื‘ ืขื‘ื•ืจ ืžืžืฉืง ื”ืžืฉืชืžืฉ ื•ืœื”ื•ื“ื™ืข โ€‹โ€‹ืœืžืžืฉืง ื”ืžืฉืชืžืฉ ื›ืืฉืจ ื”ื•ื ืžืฉืชื ื”. ืœืฉื ื›ืš, ื‘ื•ืื• ื ื•ืกื™ืฃ ืฉื™ื˜ื” 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

ื”ื•ืกืคืช ืชื’ื•ื‘ื”