ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಸಾಮಾನ್ಯ "ಕ್ಲೈಂಟ್-ಸರ್ವರ್" ಆರ್ಕಿಟೆಕ್ಚರ್‌ಗಿಂತ ಭಿನ್ನವಾಗಿ, ವಿಕೇಂದ್ರೀಕೃತ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಇವುಗಳಿಂದ ನಿರೂಪಿಸಲಾಗಿದೆ:

  • ಬಳಕೆದಾರರ ಲಾಗಿನ್‌ಗಳು ಮತ್ತು ಪಾಸ್‌ವರ್ಡ್‌ಗಳೊಂದಿಗೆ ಡೇಟಾಬೇಸ್ ಅನ್ನು ಸಂಗ್ರಹಿಸುವ ಅಗತ್ಯವಿಲ್ಲ. ಪ್ರವೇಶ ಮಾಹಿತಿಯನ್ನು ಬಳಕೆದಾರರಿಂದ ಪ್ರತ್ಯೇಕವಾಗಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಪ್ರೋಟೋಕಾಲ್ ಮಟ್ಟದಲ್ಲಿ ಅವರ ದೃಢೀಕರಣದ ದೃಢೀಕರಣವು ಸಂಭವಿಸುತ್ತದೆ.
  • ಸರ್ವರ್ ಅನ್ನು ಬಳಸುವ ಅಗತ್ಯವಿಲ್ಲ. ಅಪ್ಲಿಕೇಶನ್ ತರ್ಕವನ್ನು ಬ್ಲಾಕ್ಚೈನ್ ನೆಟ್ವರ್ಕ್ನಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದು, ಅಲ್ಲಿ ಅಗತ್ಯವಿರುವ ಪ್ರಮಾಣದ ಡೇಟಾವನ್ನು ಸಂಗ್ರಹಿಸಲು ಸಾಧ್ಯವಿದೆ.

ಬಳಕೆದಾರರ ಕೀಲಿಗಳಿಗಾಗಿ 2 ತುಲನಾತ್ಮಕವಾಗಿ ಸುರಕ್ಷಿತ ಸಂಗ್ರಹಣೆಗಳಿವೆ - ಹಾರ್ಡ್‌ವೇರ್ ವ್ಯಾಲೆಟ್‌ಗಳು ಮತ್ತು ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳು. ಹಾರ್ಡ್‌ವೇರ್ ವ್ಯಾಲೆಟ್‌ಗಳು ಹೆಚ್ಚಾಗಿ ಸುರಕ್ಷಿತವಾಗಿರುತ್ತವೆ, ಆದರೆ ಬಳಸಲು ಕಷ್ಟ ಮತ್ತು ಉಚಿತದಿಂದ ದೂರವಿರುತ್ತವೆ, ಆದರೆ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳು ಸುರಕ್ಷತೆ ಮತ್ತು ಬಳಕೆಯ ಸುಲಭತೆಯ ಪರಿಪೂರ್ಣ ಸಂಯೋಜನೆಯಾಗಿದೆ ಮತ್ತು ಅಂತಿಮ ಬಳಕೆದಾರರಿಗೆ ಸಹ ಸಂಪೂರ್ಣವಾಗಿ ಉಚಿತವಾಗಿರುತ್ತದೆ.

ಇವೆಲ್ಲವನ್ನೂ ಗಣನೆಗೆ ತೆಗೆದುಕೊಂಡು, ವಹಿವಾಟುಗಳು ಮತ್ತು ಸಹಿಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡಲು ಸರಳವಾದ API ಅನ್ನು ಒದಗಿಸುವ ಮೂಲಕ ವಿಕೇಂದ್ರೀಕೃತ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಅಭಿವೃದ್ಧಿಯನ್ನು ಸರಳಗೊಳಿಸುವ ಅತ್ಯಂತ ಸುರಕ್ಷಿತ ವಿಸ್ತರಣೆಯನ್ನು ಮಾಡಲು ನಾವು ಬಯಸಿದ್ದೇವೆ.
ಈ ಅನುಭವದ ಬಗ್ಗೆ ನಾವು ಕೆಳಗೆ ಹೇಳುತ್ತೇವೆ.

ಕೋಡ್ ಉದಾಹರಣೆಗಳು ಮತ್ತು ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳೊಂದಿಗೆ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಹೇಗೆ ಬರೆಯುವುದು ಎಂಬುದರ ಕುರಿತು ಹಂತ-ಹಂತದ ಸೂಚನೆಗಳನ್ನು ಲೇಖನವು ಒಳಗೊಂಡಿರುತ್ತದೆ. ನೀವು ಎಲ್ಲಾ ಕೋಡ್ ಅನ್ನು ಕಾಣಬಹುದು ಭಂಡಾರಗಳು. ಪ್ರತಿಯೊಂದು ಬದ್ಧತೆಯು ತಾರ್ಕಿಕವಾಗಿ ಈ ಲೇಖನದ ಒಂದು ವಿಭಾಗಕ್ಕೆ ಅನುರೂಪವಾಗಿದೆ.

ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳ ಸಂಕ್ಷಿಪ್ತ ಇತಿಹಾಸ

ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳು ಬಹಳ ಹಿಂದಿನಿಂದಲೂ ಇವೆ. ಅವರು 1999 ರಲ್ಲಿ ಇಂಟರ್ನೆಟ್ ಎಕ್ಸ್‌ಪ್ಲೋರರ್‌ನಲ್ಲಿ, 2004 ರಲ್ಲಿ ಫೈರ್‌ಫಾಕ್ಸ್‌ನಲ್ಲಿ ಕಾಣಿಸಿಕೊಂಡರು. ಆದಾಗ್ಯೂ, ಬಹಳ ಸಮಯದವರೆಗೆ ವಿಸ್ತರಣೆಗಳಿಗೆ ಒಂದೇ ಮಾನದಂಡವಿರಲಿಲ್ಲ.

Google Chrome ನ ನಾಲ್ಕನೇ ಆವೃತ್ತಿಯಲ್ಲಿ ವಿಸ್ತರಣೆಗಳೊಂದಿಗೆ ಇದು ಕಾಣಿಸಿಕೊಂಡಿದೆ ಎಂದು ನಾವು ಹೇಳಬಹುದು. ಸಹಜವಾಗಿ, ಆಗ ಯಾವುದೇ ನಿರ್ದಿಷ್ಟತೆ ಇರಲಿಲ್ಲ, ಆದರೆ ಕ್ರೋಮ್ API ಅದರ ಆಧಾರವಾಯಿತು: ಹೆಚ್ಚಿನ ಬ್ರೌಸರ್ ಮಾರುಕಟ್ಟೆಯನ್ನು ವಶಪಡಿಸಿಕೊಂಡ ನಂತರ ಮತ್ತು ಅಂತರ್ನಿರ್ಮಿತ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಟೋರ್ ಅನ್ನು ಹೊಂದಿರುವ ಕ್ರೋಮ್ ವಾಸ್ತವವಾಗಿ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳಿಗೆ ಮಾನದಂಡವನ್ನು ಹೊಂದಿಸಿದೆ.

ಮೊಜಿಲ್ಲಾ ತನ್ನದೇ ಆದ ಮಾನದಂಡವನ್ನು ಹೊಂದಿತ್ತು, ಆದರೆ ಕ್ರೋಮ್ ವಿಸ್ತರಣೆಗಳ ಜನಪ್ರಿಯತೆಯನ್ನು ನೋಡಿದ ಕಂಪನಿಯು ಹೊಂದಾಣಿಕೆಯ API ಅನ್ನು ಮಾಡಲು ನಿರ್ಧರಿಸಿತು. 2015 ರಲ್ಲಿ, ಮೊಜಿಲ್ಲಾದ ಉಪಕ್ರಮದಲ್ಲಿ, ವರ್ಲ್ಡ್ ವೈಡ್ ವೆಬ್ ಕನ್ಸೋರ್ಟಿಯಂ (W3C) ನಲ್ಲಿ ಕ್ರಾಸ್-ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆ ವಿಶೇಷಣಗಳಲ್ಲಿ ಕೆಲಸ ಮಾಡಲು ವಿಶೇಷ ಗುಂಪನ್ನು ರಚಿಸಲಾಯಿತು.

Chrome ಗಾಗಿ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ API ವಿಸ್ತರಣೆಗಳನ್ನು ಆಧಾರವಾಗಿ ತೆಗೆದುಕೊಳ್ಳಲಾಗಿದೆ. ಮೈಕ್ರೋಸಾಫ್ಟ್ನ ಬೆಂಬಲದೊಂದಿಗೆ ಕೆಲಸವನ್ನು ಕೈಗೊಳ್ಳಲಾಯಿತು (ಗೂಗಲ್ ಮಾನದಂಡದ ಅಭಿವೃದ್ಧಿಯಲ್ಲಿ ಭಾಗವಹಿಸಲು ನಿರಾಕರಿಸಿತು), ಮತ್ತು ಇದರ ಪರಿಣಾಮವಾಗಿ ಕರಡು ಕಾಣಿಸಿಕೊಂಡಿತು ವಿಶೇಷಣಗಳು.

ಔಪಚಾರಿಕವಾಗಿ, ನಿರ್ದಿಷ್ಟತೆಯನ್ನು ಎಡ್ಜ್, ಫೈರ್‌ಫಾಕ್ಸ್ ಮತ್ತು ಒಪೇರಾ ಬೆಂಬಲಿಸುತ್ತದೆ (ಕ್ರೋಮ್ ಈ ಪಟ್ಟಿಯಲ್ಲಿಲ್ಲ ಎಂಬುದನ್ನು ಗಮನಿಸಿ). ಆದರೆ ವಾಸ್ತವವಾಗಿ, ಸ್ಟ್ಯಾಂಡರ್ಡ್ ಹೆಚ್ಚಾಗಿ Chrome ಗೆ ಹೊಂದಿಕೊಳ್ಳುತ್ತದೆ, ಏಕೆಂದರೆ ಇದು ವಾಸ್ತವವಾಗಿ ಅದರ ವಿಸ್ತರಣೆಗಳ ಆಧಾರದ ಮೇಲೆ ಬರೆಯಲ್ಪಟ್ಟಿದೆ. ನೀವು WebExtensions API ಕುರಿತು ಇನ್ನಷ್ಟು ಓದಬಹುದು ಇಲ್ಲಿ.

ವಿಸ್ತರಣೆ ರಚನೆ

ವಿಸ್ತರಣೆಗೆ ಅಗತ್ಯವಿರುವ ಏಕೈಕ ಫೈಲ್ ಮ್ಯಾನಿಫೆಸ್ಟ್ (manifest.json). ಇದು ವಿಸ್ತರಣೆಗೆ "ಪ್ರವೇಶ ಬಿಂದು" ಕೂಡ ಆಗಿದೆ.

ಮ್ಯಾನಿಫೆಸ್ಟ್

ನಿರ್ದಿಷ್ಟತೆಯ ಪ್ರಕಾರ, ಮ್ಯಾನಿಫೆಸ್ಟ್ ಫೈಲ್ ಮಾನ್ಯವಾದ JSON ಫೈಲ್ ಆಗಿದೆ. ಯಾವ ಬ್ರೌಸರ್‌ನಲ್ಲಿ ಯಾವ ಕೀಗಳನ್ನು ಬೆಂಬಲಿಸಲಾಗುತ್ತದೆ ಎಂಬುದರ ಕುರಿತು ಮಾಹಿತಿಯೊಂದಿಗೆ ಮ್ಯಾನಿಫೆಸ್ಟ್ ಕೀಗಳ ಸಂಪೂರ್ಣ ವಿವರಣೆಯನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಇಲ್ಲಿ.

"ಮೇ" ಎಂಬ ವಿವರಣೆಯಲ್ಲಿಲ್ಲದ ಕೀಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಬಹುದು (ಕ್ರೋಮ್ ಮತ್ತು ಫೈರ್‌ಫಾಕ್ಸ್ ಎರಡೂ ದೋಷಗಳನ್ನು ವರದಿ ಮಾಡುತ್ತವೆ, ಆದರೆ ವಿಸ್ತರಣೆಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಲೇ ಇರುತ್ತವೆ).

ಮತ್ತು ನಾನು ಕೆಲವು ಅಂಶಗಳಿಗೆ ಗಮನ ಸೆಳೆಯಲು ಬಯಸುತ್ತೇನೆ.

  1. ಹಿನ್ನೆಲೆ - ಈ ಕೆಳಗಿನ ಕ್ಷೇತ್ರಗಳನ್ನು ಒಳಗೊಂಡಿರುವ ಒಂದು ವಸ್ತು:
    1. ಲಿಪಿಗಳು - ಹಿನ್ನೆಲೆಯ ಸಂದರ್ಭದಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳ್ಳುವ ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳ ಒಂದು ಶ್ರೇಣಿ (ನಾವು ಇದನ್ನು ಸ್ವಲ್ಪ ನಂತರ ಮಾತನಾಡುತ್ತೇವೆ);
    2. ಪುಟ - ಖಾಲಿ ಪುಟದಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳ್ಳುವ ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳ ಬದಲಿಗೆ, ನೀವು ವಿಷಯದೊಂದಿಗೆ html ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಬಹುದು. ಈ ಸಂದರ್ಭದಲ್ಲಿ, ಸ್ಕ್ರಿಪ್ಟ್ ಕ್ಷೇತ್ರವನ್ನು ನಿರ್ಲಕ್ಷಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳನ್ನು ವಿಷಯ ಪುಟಕ್ಕೆ ಸೇರಿಸಬೇಕಾಗುತ್ತದೆ;
    3. ಇರುತ್ತವೆ — ಬೈನರಿ ಫ್ಲ್ಯಾಗ್, ನಿರ್ದಿಷ್ಟಪಡಿಸದಿದ್ದರೆ, ಬ್ರೌಸರ್ ಏನನ್ನೂ ಮಾಡುತ್ತಿಲ್ಲ ಎಂದು ಪರಿಗಣಿಸಿದಾಗ ಹಿನ್ನೆಲೆ ಪ್ರಕ್ರಿಯೆಯನ್ನು "ಕೊಲ್ಲುತ್ತದೆ" ಮತ್ತು ಅಗತ್ಯವಿದ್ದರೆ ಅದನ್ನು ಮರುಪ್ರಾರಂಭಿಸುತ್ತದೆ. ಇಲ್ಲದಿದ್ದರೆ, ಬ್ರೌಸರ್ ಮುಚ್ಚಿದಾಗ ಮಾತ್ರ ಪುಟವನ್ನು ಇಳಿಸಲಾಗುತ್ತದೆ. Firefox ನಲ್ಲಿ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ.
  2. ವಿಷಯ_ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳು - ವಿವಿಧ ವೆಬ್ ಪುಟಗಳಿಗೆ ವಿವಿಧ ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುವ ವಸ್ತುಗಳ ಒಂದು ಶ್ರೇಣಿ. ಪ್ರತಿಯೊಂದು ವಸ್ತುವು ಈ ಕೆಳಗಿನ ಪ್ರಮುಖ ಕ್ಷೇತ್ರಗಳನ್ನು ಒಳಗೊಂಡಿದೆ:
    1. ಪಂದ್ಯಗಳನ್ನು - ಮಾದರಿ url, ಇದು ನಿರ್ದಿಷ್ಟ ವಿಷಯ ಸ್ಕ್ರಿಪ್ಟ್ ಅನ್ನು ಸೇರಿಸಬೇಕೆ ಅಥವಾ ಬೇಡವೇ ಎಂಬುದನ್ನು ನಿರ್ಧರಿಸುತ್ತದೆ.
    2. js - ಈ ಪಂದ್ಯದಲ್ಲಿ ಲೋಡ್ ಮಾಡಲಾಗುವ ಸ್ಕ್ರಿಪ್ಟ್‌ಗಳ ಪಟ್ಟಿ;
    3. ಹೊರತುಪಡಿಸಿ_ಹೊಂದಿಕೆಗಳು - ಕ್ಷೇತ್ರದಿಂದ ಹೊರಗಿಡುತ್ತದೆ match ಈ ಕ್ಷೇತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ URL ಗಳು.
  3. ಪುಟ_ಕ್ರಿಯೆ - ವಾಸ್ತವವಾಗಿ ಬ್ರೌಸರ್‌ನಲ್ಲಿನ ವಿಳಾಸ ಪಟ್ಟಿಯ ಪಕ್ಕದಲ್ಲಿ ಪ್ರದರ್ಶಿಸಲಾದ ಐಕಾನ್ ಮತ್ತು ಅದರೊಂದಿಗೆ ಸಂವಹನಕ್ಕೆ ಜವಾಬ್ದಾರರಾಗಿರುವ ಒಂದು ವಸ್ತುವಾಗಿದೆ. ಇದು ನಿಮ್ಮ ಸ್ವಂತ HTML, CSS ಮತ್ತು JS ಅನ್ನು ಬಳಸಿಕೊಂಡು ವ್ಯಾಖ್ಯಾನಿಸಲಾದ ಪಾಪ್ಅಪ್ ವಿಂಡೋವನ್ನು ಪ್ರದರ್ಶಿಸಲು ಸಹ ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ.
    1. ಡೀಫಾಲ್ಟ್_ಪಾಪ್ಅಪ್ — ಪಾಪ್ಅಪ್ ಇಂಟರ್ಫೇಸ್ನೊಂದಿಗೆ HTML ಫೈಲ್ಗೆ ಮಾರ್ಗವು CSS ಮತ್ತು JS ಅನ್ನು ಒಳಗೊಂಡಿರಬಹುದು.
  4. ಅನುಮತಿಗಳು - ವಿಸ್ತರಣೆ ಹಕ್ಕುಗಳನ್ನು ನಿರ್ವಹಿಸುವ ಒಂದು ಶ್ರೇಣಿ. 3 ವಿಧದ ಹಕ್ಕುಗಳಿವೆ, ಇವುಗಳನ್ನು ವಿವರವಾಗಿ ವಿವರಿಸಲಾಗಿದೆ ಇಲ್ಲಿ
  5. web_accessible_resources — ವೆಬ್ ಪುಟವು ವಿನಂತಿಸಬಹುದಾದ ವಿಸ್ತರಣೆ ಸಂಪನ್ಮೂಲಗಳು, ಉದಾಹರಣೆಗೆ, ಚಿತ್ರಗಳು, JS, CSS, HTML ಫೈಲ್‌ಗಳು.
  6. ಬಾಹ್ಯವಾಗಿ_ಸಂಪರ್ಕಿಸಬಹುದಾಗಿದೆ — ಇಲ್ಲಿ ನೀವು ಇತರ ವಿಸ್ತರಣೆಗಳ ID ಗಳನ್ನು ಮತ್ತು ನೀವು ಸಂಪರ್ಕಿಸಬಹುದಾದ ವೆಬ್ ಪುಟಗಳ ಡೊಮೇನ್‌ಗಳನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ನಿರ್ದಿಷ್ಟಪಡಿಸಬಹುದು. ಡೊಮೇನ್ ಎರಡನೇ ಹಂತ ಅಥವಾ ಹೆಚ್ಚಿನದಾಗಿರಬಹುದು. Firefox ನಲ್ಲಿ ಕೆಲಸ ಮಾಡುವುದಿಲ್ಲ.

ಮರಣದಂಡನೆ ಸಂದರ್ಭ

ವಿಸ್ತರಣೆಯು ಮೂರು ಕೋಡ್ ಎಕ್ಸಿಕ್ಯೂಶನ್ ಸಂದರ್ಭಗಳನ್ನು ಹೊಂದಿದೆ, ಅಂದರೆ, ಬ್ರೌಸರ್ 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 ಅನ್ನು ಸೇರಿಸುತ್ತೇವೆ - ಮತ್ತು ನಮ್ಮ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಈಗಾಗಲೇ Google Chrome ಗೆ ಲೋಡ್ ಮಾಡಬಹುದು ಮತ್ತು ಅದು ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ.

ಇದನ್ನು ಪರಿಶೀಲಿಸಲು, ನೀವು ಕೋಡ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಇಲ್ಲಿಂದ. ನಾವು ಏನು ಮಾಡಿದ್ದೇವೆ ಎಂಬುದರ ಜೊತೆಗೆ, ವೆಬ್‌ಪ್ಯಾಕ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಯೋಜನೆಯ ಜೋಡಣೆಯನ್ನು ಲಿಂಕ್ ಕಾನ್ಫಿಗರ್ ಮಾಡಿದೆ. ಬ್ರೌಸರ್‌ಗೆ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸೇರಿಸಲು, chrome://extensions ನಲ್ಲಿ ನೀವು ಅನ್ಪ್ಯಾಕ್ ಮಾಡಲಾದ ಲೋಡ್ ಅನ್ನು ಮತ್ತು ಅನುಗುಣವಾದ ವಿಸ್ತರಣೆಯೊಂದಿಗೆ ಫೋಲ್ಡರ್ ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಬೇಕಾಗುತ್ತದೆ - ನಮ್ಮ ಸಂದರ್ಭದಲ್ಲಿ dist.

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಈಗ ನಮ್ಮ ವಿಸ್ತರಣೆಯನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿದೆ ಮತ್ತು ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿದೆ. ನೀವು ಈ ಕೆಳಗಿನಂತೆ ವಿವಿಧ ಸಂದರ್ಭಗಳಿಗಾಗಿ ಡೆವಲಪರ್ ಪರಿಕರಗಳನ್ನು ಚಲಾಯಿಸಬಹುದು:

ಪಾಪ್ಅಪ್ ->

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಕಂಟೆಂಟ್ ಸ್ಕ್ರಿಪ್ಟ್ ಕನ್ಸೋಲ್‌ಗೆ ಪ್ರವೇಶವನ್ನು ಪುಟದ ಕನ್ಸೋಲ್ ಮೂಲಕ ಅದನ್ನು ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತದೆ.ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಸಂದೇಶ ವಿನಿಮಯ

ಆದ್ದರಿಂದ, ನಾವು ಎರಡು ಸಂವಹನ ಚಾನಲ್ಗಳನ್ನು ಸ್ಥಾಪಿಸಬೇಕಾಗಿದೆ: inpage <-> ಹಿನ್ನೆಲೆ ಮತ್ತು ಪಾಪ್ಅಪ್ <-> ಹಿನ್ನೆಲೆ. ನೀವು ಸಹಜವಾಗಿ, ಪೋರ್ಟ್‌ಗೆ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಸ್ವಂತ ಪ್ರೋಟೋಕಾಲ್ ಅನ್ನು ಆವಿಷ್ಕರಿಸಬಹುದು, ಆದರೆ ಮೆಟಾಮಾಸ್ಕ್ ಓಪನ್ ಸೋರ್ಸ್ ಪ್ರಾಜೆಕ್ಟ್‌ನಲ್ಲಿ ನಾನು ನೋಡಿದ ವಿಧಾನವನ್ನು ನಾನು ಬಯಸುತ್ತೇನೆ.

ಇದು Ethereum ನೆಟ್ವರ್ಕ್ನೊಂದಿಗೆ ಕೆಲಸ ಮಾಡಲು ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯಾಗಿದೆ. ಅದರಲ್ಲಿ, ಅಪ್ಲಿಕೇಶನ್‌ನ ವಿವಿಧ ಭಾಗಗಳು dnode ಲೈಬ್ರರಿಯನ್ನು ಬಳಸಿಕೊಂಡು RPC ಮೂಲಕ ಸಂವಹನ ನಡೆಸುತ್ತವೆ. ನೀವು ಅದನ್ನು ಸಾರಿಗೆಯಾಗಿ 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.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 ಸ್ಟ್ರೀಮ್‌ಗಳೊಂದಿಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುವುದರಿಂದ ಮತ್ತು ನಾವು ಪೋರ್ಟ್ ಅನ್ನು ಸ್ವೀಕರಿಸುತ್ತೇವೆ, ಅಡಾಪ್ಟರ್ ವರ್ಗದ ಅಗತ್ಯವಿದೆ. ಇದನ್ನು ಓದಬಲ್ಲ-ಸ್ಟ್ರೀಮ್ ಲೈಬ್ರರಿಯನ್ನು ಬಳಸಿ ತಯಾರಿಸಲಾಗುತ್ತದೆ, ಇದು ಬ್ರೌಸರ್‌ನಲ್ಲಿ ನೋಡೆಜ್ ಸ್ಟ್ರೀಮ್‌ಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುತ್ತದೆ:

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 ಗೆ ಸ್ಕ್ರಿಪ್ಟ್ ಅನ್ನು ಇಂಜೆಕ್ಟ್ ಮಾಡಿ. ಸ್ಕ್ರಿಪ್ಟ್ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ (ಮ್ಯಾನಿಫೆಸ್ಟ್‌ನಲ್ಲಿ ಅದರ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಲಾಗಿದೆ) ಮತ್ತು ಟ್ಯಾಗ್ ರಚಿಸಿ script ಅದರ ಒಳಗಿನ ವಿಷಯಗಳೊಂದಿಗೆ:

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

setupConnection();
injectScript();

function setupConnection(){
    // Стрим к бекграунду
    const backgroundPort = extensionApi.runtime.connect({name: 'contentscript'});
    const backgroundStream = new PortStream(backgroundPort);

    // Стрим к странице
    const pageStream = new PostMessageStream({
        name: 'content',
        target: 'page',
    });

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

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

ಈಗ ನಾವು ಇನ್‌ಪೇಜ್‌ನಲ್ಲಿ api ವಸ್ತುವನ್ನು ರಚಿಸುತ್ತೇವೆ ಮತ್ತು ಅದನ್ನು ಜಾಗತಿಕವಾಗಿ ಹೊಂದಿಸುತ್ತೇವೆ:

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

setupInpageApi().catch(console.error);

async function setupInpageApi() {
    // Стрим к контентскрипту
    const connectionStream = new PostMessageStream({
        name: 'page',
        target: 'content',
    });

    const dnode = Dnode();

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

    // Получаем объект API
    const pageApi = await new Promise(resolve => {
        dnode.once('remote', api => {
            resolve(api)
        })
    });

    // Доступ через window
    global.SignerApp = pageApi;
}

ನಾವು ಸಿದ್ಧರಿದ್ದೇವೆ ಪುಟ ಮತ್ತು UI ಗಾಗಿ ಪ್ರತ್ಯೇಕ API ಜೊತೆಗೆ ರಿಮೋಟ್ ಪ್ರೊಸೀಜರ್ ಕರೆ (RPC).. ಹೊಸ ಪುಟವನ್ನು ಹಿನ್ನೆಲೆಗೆ ಸಂಪರ್ಕಿಸುವಾಗ ನಾವು ಇದನ್ನು ನೋಡಬಹುದು:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಖಾಲಿ API ಮತ್ತು ಮೂಲ. ಪುಟದ ಬದಿಯಲ್ಲಿ, ನಾವು ಹಲೋ ಕಾರ್ಯವನ್ನು ಈ ರೀತಿ ಕರೆಯಬಹುದು:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಆಧುನಿಕ JS ನಲ್ಲಿ ಕಾಲ್‌ಬ್ಯಾಕ್ ಕಾರ್ಯಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವುದು ಕೆಟ್ಟ ನಡವಳಿಕೆಯಾಗಿದೆ, ಆದ್ದರಿಂದ ನೀವು API ವಸ್ತುವನ್ನು ಯುಟಿಲ್ಸ್‌ಗೆ ರವಾನಿಸಲು ಅನುಮತಿಸುವ dnode ಅನ್ನು ರಚಿಸಲು ಸಣ್ಣ ಸಹಾಯಕವನ್ನು ಬರೆಯೋಣ.

API ಆಬ್ಜೆಕ್ಟ್‌ಗಳು ಈಗ ಈ ರೀತಿ ಕಾಣುತ್ತವೆ:

export class SignerApp {

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

...

}

ಈ ರೀತಿಯ ರಿಮೋಟ್‌ನಿಂದ ವಸ್ತುವನ್ನು ಪಡೆಯುವುದು:

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

const pageApi = await new Promise(resolve => {
    dnode.once('remote', remoteApi => {
        // С помощью утилит меняем все callback на promise
        resolve(transformMethods(cbToPromise, remoteApi))
    })
});

ಮತ್ತು ಕರೆ ಕಾರ್ಯಗಳು ಭರವಸೆಯನ್ನು ಹಿಂದಿರುಗಿಸುತ್ತದೆ:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಅಸಮಕಾಲಿಕ ಕಾರ್ಯಗಳೊಂದಿಗೆ ಆವೃತ್ತಿ ಲಭ್ಯವಿದೆ ಇಲ್ಲಿ.

ಒಟ್ಟಾರೆಯಾಗಿ, RPC ಮತ್ತು ಸ್ಟ್ರೀಮ್ ವಿಧಾನವು ಸಾಕಷ್ಟು ಹೊಂದಿಕೊಳ್ಳುವಂತಿದೆ: ನಾವು ಸ್ಟೀಮ್ ಮಲ್ಟಿಪ್ಲೆಕ್ಸಿಂಗ್ ಅನ್ನು ಬಳಸಬಹುದು ಮತ್ತು ವಿಭಿನ್ನ ಕಾರ್ಯಗಳಿಗಾಗಿ ಹಲವಾರು ವಿಭಿನ್ನ 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 ಕನ್ಸೋಲ್‌ನಿಂದ ಕೆಲವು ಕೀಗಳನ್ನು ಸೇರಿಸೋಣ ಮತ್ತು ರಾಜ್ಯದಲ್ಲಿ ಏನಾಗುತ್ತದೆ ಎಂದು ನೋಡೋಣ:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಮರುಪ್ರಾರಂಭಿಸುವಾಗ ಕೀಗಳು ಕಳೆದುಹೋಗದಂತೆ ಸ್ಥಿತಿಯನ್ನು ನಿರಂತರವಾಗಿ ಮಾಡಬೇಕಾಗಿದೆ.

ನಾವು ಅದನ್ನು ಸ್ಥಳೀಯ ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಂಗ್ರಹಿಸುತ್ತೇವೆ, ಪ್ರತಿ ಬದಲಾವಣೆಯೊಂದಿಗೆ ಅದನ್ನು ತಿದ್ದಿ ಬರೆಯುತ್ತೇವೆ. ತರುವಾಯ, ಅದರ ಪ್ರವೇಶವು 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. ಜಾರಿ ಕ್ರಿಯೆಗಳ ಧ್ವಜದೊಂದಿಗೆ ಕಟ್ಟುನಿಟ್ಟಾದ ಮೋಡ್‌ನಲ್ಲಿ, ರಾಜ್ಯವನ್ನು ನೇರವಾಗಿ ಬದಲಾಯಿಸುವುದನ್ನು mobx ನಿಷೇಧಿಸುತ್ತದೆ. ಕಠಿಣ ಪರಿಸ್ಥಿತಿಗಳಲ್ಲಿ ಕೆಲಸ ಮಾಡುವುದು ಉತ್ತಮ ಅಭ್ಯಾಸವೆಂದು ಪರಿಗಣಿಸಲಾಗಿದೆ.
  2. ಕಾರ್ಯವು ಹಲವಾರು ಬಾರಿ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸಿದರೂ ಸಹ - ಉದಾಹರಣೆಗೆ, ನಾವು ಹಲವಾರು ಕ್ಷೇತ್ರಗಳನ್ನು ಕೋಡ್‌ನ ಹಲವಾರು ಸಾಲುಗಳಲ್ಲಿ ಬದಲಾಯಿಸುತ್ತೇವೆ - ಅದು ಪೂರ್ಣಗೊಂಡಾಗ ಮಾತ್ರ ವೀಕ್ಷಕರಿಗೆ ಸೂಚಿಸಲಾಗುತ್ತದೆ. ಮುಂಭಾಗಕ್ಕೆ ಇದು ವಿಶೇಷವಾಗಿ ಮುಖ್ಯವಾಗಿದೆ, ಅಲ್ಲಿ ಅನಗತ್ಯ ಸ್ಥಿತಿ ನವೀಕರಣಗಳು ಅಂಶಗಳ ಅನಗತ್ಯ ರೆಂಡರಿಂಗ್ಗೆ ಕಾರಣವಾಗುತ್ತವೆ. ನಮ್ಮ ಸಂದರ್ಭದಲ್ಲಿ, ಮೊದಲ ಅಥವಾ ಎರಡನೆಯದು ನಿರ್ದಿಷ್ಟವಾಗಿ ಪ್ರಸ್ತುತವಲ್ಲ, ಆದರೆ ನಾವು ಉತ್ತಮ ಅಭ್ಯಾಸಗಳನ್ನು ಅನುಸರಿಸುತ್ತೇವೆ. ಗಮನಿಸಿದ ಕ್ಷೇತ್ರಗಳ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸುವ ಎಲ್ಲಾ ಕಾರ್ಯಗಳಿಗೆ ಅಲಂಕಾರಕಾರರನ್ನು ಲಗತ್ತಿಸುವುದು ವಾಡಿಕೆ.

ಹಿನ್ನೆಲೆಯಲ್ಲಿ ನಾವು ಪ್ರಾರಂಭವನ್ನು ಸೇರಿಸುತ್ತೇವೆ ಮತ್ತು ಸ್ಥಳೀಯ ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸ್ಥಿತಿಯನ್ನು ಉಳಿಸುತ್ತೇವೆ:

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. ಇದು ಮೂಲ ಕ್ಷೇತ್ರಗಳೊಂದಿಗೆ ಬದಲಾಯಿಸಲಾದ ಎಲ್ಲಾ ಪ್ರಾಕ್ಸಿಗಳೊಂದಿಗೆ ಹೊಸ ವಸ್ತುವನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ. ಮರಣದಂಡನೆಯ ಸಮಯದಲ್ಲಿ, ಇದು ವಸ್ತುವಿನ ಎಲ್ಲಾ ಕ್ಷೇತ್ರಗಳನ್ನು ಓದುತ್ತದೆ - ಆದ್ದರಿಂದ ಪಡೆಯುವವರು ಪ್ರಚೋದಿಸಲ್ಪಡುತ್ತಾರೆ.

ಪಾಪ್ಅಪ್ ಕನ್ಸೋಲ್ನಲ್ಲಿ ನಾವು ಮತ್ತೆ ಹಲವಾರು ಕೀಗಳನ್ನು ಸೇರಿಸುತ್ತೇವೆ. ಈ ಬಾರಿ ಅವರು ಸ್ಥಳೀಯ ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಕೊನೆಗೊಂಡರು:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಹಿನ್ನೆಲೆ ಪುಟವನ್ನು ಮರುಲೋಡ್ ಮಾಡಿದಾಗ, ಮಾಹಿತಿಯು ಸ್ಥಳದಲ್ಲಿ ಉಳಿಯುತ್ತದೆ.

ಈ ಹಂತದವರೆಗಿನ ಎಲ್ಲಾ ಅಪ್ಲಿಕೇಶನ್ ಕೋಡ್ ಅನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಇಲ್ಲಿ.

ಖಾಸಗಿ ಕೀಲಿಗಳ ಸುರಕ್ಷಿತ ಸಂಗ್ರಹಣೆ

ಸ್ಪಷ್ಟ ಪಠ್ಯದಲ್ಲಿ ಖಾಸಗಿ ಕೀಲಿಗಳನ್ನು ಸಂಗ್ರಹಿಸುವುದು ಅಸುರಕ್ಷಿತವಾಗಿದೆ: ನಿಮ್ಮನ್ನು ಹ್ಯಾಕ್ ಮಾಡುವ, ನಿಮ್ಮ ಕಂಪ್ಯೂಟರ್‌ಗೆ ಪ್ರವೇಶವನ್ನು ಪಡೆದುಕೊಳ್ಳುವ ಮತ್ತು ಮುಂತಾದವುಗಳಿಗೆ ಯಾವಾಗಲೂ ಅವಕಾಶವಿರುತ್ತದೆ. ಆದ್ದರಿಂದ, ಲೋಕಲ್ ಸ್ಟೋರೇಜ್‌ನಲ್ಲಿ ನಾವು ಕೀಲಿಗಳನ್ನು ಪಾಸ್‌ವರ್ಡ್-ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ರೂಪದಲ್ಲಿ ಸಂಗ್ರಹಿಸುತ್ತೇವೆ.

ಹೆಚ್ಚಿನ ಭದ್ರತೆಗಾಗಿ, ನಾವು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಲಾಕ್ ಮಾಡಲಾದ ಸ್ಥಿತಿಯನ್ನು ಸೇರಿಸುತ್ತೇವೆ, ಇದರಲ್ಲಿ ಕೀಗಳಿಗೆ ಯಾವುದೇ ಪ್ರವೇಶವಿರುವುದಿಲ್ಲ. ಅವಧಿ ಮೀರಿದ ಕಾರಣ ನಾವು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ವಿಸ್ತರಣೆಯನ್ನು ಲಾಕ್ ಆಗಿರುವ ಸ್ಥಿತಿಗೆ ವರ್ಗಾಯಿಸುತ್ತೇವೆ.

Mobx ನಿಮಗೆ ಕನಿಷ್ಟ ಡೇಟಾವನ್ನು ಮಾತ್ರ ಸಂಗ್ರಹಿಸಲು ಅನುಮತಿಸುತ್ತದೆ, ಮತ್ತು ಉಳಿದವುಗಳನ್ನು ಅದರ ಆಧಾರದ ಮೇಲೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಲೆಕ್ಕಹಾಕಲಾಗುತ್ತದೆ. ಇವು ಕಂಪ್ಯೂಟೆಡ್ ಗುಣಲಕ್ಷಣಗಳು ಎಂದು ಕರೆಯಲ್ಪಡುತ್ತವೆ. ಅವುಗಳನ್ನು ಡೇಟಾಬೇಸ್‌ಗಳಲ್ಲಿನ ವೀಕ್ಷಣೆಗಳಿಗೆ ಹೋಲಿಸಬಹುದು:

import {observable, action} from 'mobx';
import {setupDnode} from "./utils/setupDnode";
// Утилиты для безопасного шифрования строк. Используют crypto-js
import {encrypt, decrypt} from "./utils/cryptoUtils";

export class SignerApp {
    constructor(initState = {}) {
        this.store = observable.object({
            // Храним пароль и зашифрованные ключи. Если пароль null - приложение locked
            password: null,
            vault: initState.vault,

            // Геттеры для вычислимых полей. Можно провести аналогию с view в бд.
            get locked(){
                return this.password == null
            },
            get keys(){
                return this.locked ?
                    undefined :
                    SignerApp._decryptVault(this.vault, this.password)
            },
            get initialized(){
                return this.vault !== undefined
            }
        })
    }
    // Инициализация пустого хранилища новым паролем
    @action
    initVault(password){
        this.store.vault = SignerApp._encryptVault([], password)
    }
    @action
    lock() {
        this.store.password = null
    }
    @action
    unlock(password) {
        this._checkPassword(password);
        this.store.password = password
    }
    @action
    addKey(key) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault(this.store.keys.concat(key), this.store.password)
    }
    @action
    removeKey(index) {
        this._checkLocked();
        this.store.vault = SignerApp._encryptVault([
                ...this.store.keys.slice(0, index),
                ...this.store.keys.slice(index + 1)
            ],
            this.store.password
        )
    }

    ... // код подключения и api

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

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

    // Методы для шифровки/дешифровки хранилища
    static _encryptVault(obj, pass){
        const jsonString = JSON.stringify(obj)
        return encrypt(jsonString, pass)
    }

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

ಈಗ ನಾವು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಕೀಗಳು ಮತ್ತು ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮಾತ್ರ ಸಂಗ್ರಹಿಸುತ್ತೇವೆ. ಉಳಿದಂತೆ ಲೆಕ್ಕ ಹಾಕಲಾಗಿದೆ. ರಾಜ್ಯದಿಂದ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ತೆಗೆದುಹಾಕುವ ಮೂಲಕ ನಾವು ಲಾಕ್ ಮಾಡಿದ ಸ್ಥಿತಿಗೆ ವರ್ಗಾವಣೆಯನ್ನು ಮಾಡುತ್ತೇವೆ. ಸಾರ್ವಜನಿಕ API ಈಗ ಸಂಗ್ರಹಣೆಯನ್ನು ಪ್ರಾರಂಭಿಸುವ ವಿಧಾನವನ್ನು ಹೊಂದಿದೆ.

ಗೂಢಲಿಪೀಕರಣಕ್ಕಾಗಿ ಬರೆಯಲಾಗಿದೆ ಕ್ರಿಪ್ಟೋ-ಜೆಎಸ್ ಬಳಸುವ ಉಪಯುಕ್ತತೆಗಳು:

import CryptoJS from 'crypto-js'

// Используется для осложнения подбора пароля перебором. На каждый вариант пароля злоумышленнику придется сделать 5000 хешей
function strengthenPassword(pass, rounds = 5000) {
    while (rounds-- > 0){
        pass = CryptoJS.SHA256(pass).toString()
    }
    return pass
}

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

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

ಬ್ರೌಸರ್ ಐಡಲ್ API ಅನ್ನು ಹೊಂದಿದೆ ಅದರ ಮೂಲಕ ನೀವು ಈವೆಂಟ್‌ಗೆ ಚಂದಾದಾರರಾಗಬಹುದು - ಸ್ಥಿತಿ ಬದಲಾವಣೆಗಳು. ರಾಜ್ಯ, ಪ್ರಕಾರವಾಗಿ, ಇರಬಹುದು idle, active и locked. ಐಡಲ್‌ಗಾಗಿ ನೀವು ಕಾಲಾವಧಿಯನ್ನು ಹೊಂದಿಸಬಹುದು ಮತ್ತು OS ಅನ್ನು ನಿರ್ಬಂಧಿಸಿದಾಗ ಲಾಕ್ ಅನ್ನು ಹೊಂದಿಸಲಾಗುತ್ತದೆ. ಸ್ಥಳೀಯ ಸಂಗ್ರಹಣೆಗೆ ಉಳಿಸಲು ನಾವು ಆಯ್ಕೆಯನ್ನು ಬದಲಾಯಿಸುತ್ತೇವೆ:

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)
        }
    }
}

ಈ ಹಂತದ ಹಿಂದಿನ ಕೋಡ್ ಇಲ್ಲಿ.

ವ್ಯವಹಾರಗಳು

ಆದ್ದರಿಂದ, ನಾವು ಅತ್ಯಂತ ಮುಖ್ಯವಾದ ವಿಷಯಕ್ಕೆ ಬರುತ್ತೇವೆ: ಬ್ಲಾಕ್ಚೈನ್ನಲ್ಲಿ ವಹಿವಾಟುಗಳನ್ನು ರಚಿಸುವುದು ಮತ್ತು ಸಹಿ ಮಾಡುವುದು. ನಾವು ವೇವ್ಸ್ ಬ್ಲಾಕ್‌ಚೈನ್ ಮತ್ತು ಲೈಬ್ರರಿಯನ್ನು ಬಳಸುತ್ತೇವೆ ಅಲೆಗಳು-ವ್ಯವಹಾರಗಳು.

ಮೊದಲಿಗೆ, ಸಹಿ ಮಾಡಬೇಕಾದ ಸಂದೇಶಗಳ ಒಂದು ಶ್ರೇಣಿಯನ್ನು ರಾಜ್ಯಕ್ಕೆ ಸೇರಿಸೋಣ, ನಂತರ ಹೊಸ ಸಂದೇಶವನ್ನು ಸೇರಿಸಲು, ಸಹಿಯನ್ನು ದೃಢೀಕರಿಸಲು ಮತ್ತು ನಿರಾಕರಿಸುವ ವಿಧಾನಗಳನ್ನು ಸೇರಿಸಿ:

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 ತುಂಬಾ ಸರಳವಾಗಿದೆ: ಅಗತ್ಯವಿದ್ದರೆ ಸಹಿ ಮಾಡಿದ ನಂತರ ನಾವು ಸಂದೇಶದ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸುತ್ತೇವೆ.

ನಾವು UI API ನಲ್ಲಿ ಅನುಮೋದಿಸಿ ಮತ್ತು ತಿರಸ್ಕರಿಸುತ್ತೇವೆ, ಪುಟ API ನಲ್ಲಿ ಹೊಸ ಸಂದೇಶ:

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

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

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

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

    ...
}

ಈಗ ವಿಸ್ತರಣೆಯೊಂದಿಗೆ ವಹಿವಾಟಿಗೆ ಸಹಿ ಮಾಡಲು ಪ್ರಯತ್ನಿಸೋಣ:

ಸುರಕ್ಷಿತ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಯನ್ನು ಬರೆಯುವುದು

ಸಾಮಾನ್ಯವಾಗಿ, ಎಲ್ಲವೂ ಸಿದ್ಧವಾಗಿದೆ, ಉಳಿದಿರುವುದು ಸರಳ UI ಸೇರಿಸಿ.

UI

ಇಂಟರ್ಫೇಸ್ ಅಪ್ಲಿಕೇಶನ್ ಸ್ಥಿತಿಗೆ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ. UI ಭಾಗದಲ್ಲಿ ನಾವು ಮಾಡುತ್ತೇವೆ observable ಈ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸುವ API ಗೆ ಒಂದು ಕಾರ್ಯವನ್ನು ರಾಜ್ಯ ಮತ್ತು ಸೇರಿಸಿ. ಸೇರಿಸೋಣ observable ಹಿನ್ನೆಲೆಯಿಂದ ಸ್ವೀಕರಿಸಿದ API ವಸ್ತುವಿಗೆ:

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

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

setupUi().catch(console.error);

async function setupUi() {
    // Подключаемся к порту, создаем из него стрим
    const backgroundPort = extensionApi.runtime.connect({name: 'popup'});
    const connectionStream = new PortStream(backgroundPort);

    // Создаем пустой observable для состояния background'a
    let backgroundState = observable.object({});
    const api = {
        //Отдаем бекграунду функцию, которая будет обновлять observable
        updateState: async state => {
            Object.assign(backgroundState, state)
        }
    };

    // Делаем RPC объект
    const dnode = setupDnode(connectionStream, api);
    const background = await new Promise(resolve => {
        dnode.once('remote', remoteApi => {
            resolve(transformMethods(cbToPromise, remoteApi))
        })
    });

    // Добавляем в background observable со стейтом
    background.state = backgroundState;

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

    // Запуск интерфейса
    await initApp(background)
}

ಕೊನೆಯಲ್ಲಿ ನಾವು ಅಪ್ಲಿಕೇಶನ್ ಇಂಟರ್ಫೇಸ್ ಅನ್ನು ರೆಂಡರಿಂಗ್ ಮಾಡಲು ಪ್ರಾರಂಭಿಸುತ್ತೇವೆ. ಇದು ರಿಯಾಕ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿದೆ. ಹಿನ್ನೆಲೆ ವಸ್ತುವನ್ನು ಸರಳವಾಗಿ ರಂಗಪರಿಕರಗಳನ್ನು ಬಳಸಿ ರವಾನಿಸಲಾಗುತ್ತದೆ. ವಿಧಾನಗಳಿಗಾಗಿ ಪ್ರತ್ಯೇಕ ಸೇವೆ ಮತ್ತು ರಾಜ್ಯಕ್ಕಾಗಿ ಅಂಗಡಿಯನ್ನು ಮಾಡುವುದು ಸರಿಯಾಗಿರುತ್ತದೆ, ಆದರೆ ಈ ಲೇಖನದ ಉದ್ದೇಶಗಳಿಗಾಗಿ ಇದು ಸಾಕು:

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

// Инициализируем приложение с background объектом в качест ве props
export async function initApp(background){
    render(
        <App background={background}/>,
        document.getElementById('app-content')
    );
}

mobx ನೊಂದಿಗೆ ಡೇಟಾ ಬದಲಾದಾಗ ರೆಂಡರಿಂಗ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸುವುದು ತುಂಬಾ ಸುಲಭ. ನಾವು ಪ್ಯಾಕೇಜ್ನಿಂದ ವೀಕ್ಷಕ ಡೆಕೋರೇಟರ್ ಅನ್ನು ಸರಳವಾಗಿ ಸ್ಥಗಿತಗೊಳಿಸುತ್ತೇವೆ mobx-ಪ್ರತಿಕ್ರಿಯೆ ಘಟಕದ ಮೇಲೆ, ಮತ್ತು ಕಾಂಪೊನೆಂಟ್ ಬದಲಾದ ಯಾವುದೇ ಅವಲೋಕನಗಳನ್ನು ಉಲ್ಲೇಖಿಸಿದಾಗ ರೆಂಡರ್ ಅನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕರೆಯಲಾಗುತ್ತದೆ. ನಿಮಗೆ ಯಾವುದೇ 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

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ