Управління повідомленнями розширення Chrome за допомогою RxJS

pic

Комунікація між різними контекстами розширення Chrome зазвичай досягається двома способами: chrome.storage.onChanged і chrome.runtime.onMessage. Існують крайні випадки, як інжектовані iframe контент, які повинні покладатися на postMessage, але зазвичай все зводиться до цих двох слухачів.

Для побудови надійного інтерфейсу в бічній панелі і, в меншій мірі, в спливаючому вікні, швидко стає важко керувати порядком подій і агрегувати дані з різних джерел у різний час, оскільки складність зростає.

RxJS на допомогу

Ось тут і проявляється вся сила RxJS. Тому має сенс включити його в базу коду, незалежно від того, який фреймворк ви використовуєте чи не використовуєте. Ви навіть можете поділитися однією копією його між контекстами, але це вже виходить за межі цього посібника.

Ми будемо зосереджені на використанні RxJS в бічній панелі і інтеграції його в кодову базу ReactJS, але він чудово працює і без фреймворка.

Використовуємо потужні інструменти

Щоб інтегрувати RxJS у ваш існуючий проєкт на ReactJS, достатньо просто встановити його через npm:

npm install rxjs

Альтернативно, завантажте останній бандл rxjs з unpkg і вставте його в папку веб-ресурсів (або в будь-яке місце вашої папки з розширенням, просто потрібно вказати шлях до файлу).

https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js

У такому випадку вам слід посилатися на бандл у тегу script у вашому HTML файлі:

Те саме можна застосувати до sync та session сховища, просто трубопровід потрібно розділити при фільтрації за areaName.

Контекст повідомлень Chrome

Для повідомлень під час виконання нам потрібно просто слухати onMessage і розділити спостережуваний потік на основі вашої власної конвенції коду, оскільки потрібно якось розрізняти джерело повідомлення, щоб уникнути ехо.

Для мого коду наступний простий тип є достатнім:

type RuntimeMessage = { source: string, action: string, payload: T };

Це можна обгорнути в кастомний тип ChromeMessage:

type ChromeMessage = [  
 RuntimeMessage,  
 chrome.runtime.MessageSender,(response?: unknown) => void  
];

Тепер ми можемо розділяти повідомлення на основі джерела:

export const createChromeMessageState = () => {  
 const message$ = fromEventPattern>(  
 handler => chrome.runtime.onMessage.addListener(handler),  
 handler => chrome.runtime.onMessage.removeListener(handler)  
 ).pipe(share());  

 const background$ = message$.pipe(  
 filter(([message]) => message.source === 'background'),  
 map(([message]) => ({ action: message.action, payload: message.payload }))  
 );  

 const broadcast = (message: RuntimeMessage): Observable => {  
 return from(chrome.runtime.sendMessage(message));  
 };  

 const exchange = (message: RuntimeMessage): Observable => {  
 return fromEventPattern(  
 handler => chrome.runtime.sendMessage(message, handler)  
 ).pipe(timeout(3000), take(1));  
 };  

 const sendToBackground = (action: string, payload?: T) => {  
 return broadcast({  
 source: 'side-panel',  
 action,  
 payload  
 });  
 };  

 return {  
 message$,  
 background$,  
 broadcast,  
 exchange,  
 sendToBackground  
 }  
}

Для відправки повідомлення є два варіанти: ви або очікуєте відповідь на повідомлення, або просто хочете відправити і забути. Це мета функцій exchange і broadcast відповідно.

Я додав оператор timeout, щоб переконатися, що спостережуваний потік обміну не застрягне в очікуванні відповіді.

Використання контекстів у React

Щоб використовувати ці два контексти в React, нам потрібно встановити їх як значення в провайдері контексту. Щоб уникнути перезавантаження спостережуваних потоків, ми використовуємо хук useRef під час створення цих контекстів. Повний контекст повинен виглядати ось так:

import { createContext, useRef } from "react";  
import { createChromeStorageState } from "./createChromeStorageState";  

export type ChromeStorageState = ReturnType;  

export const ChromeStorageContext = createContext({} as ChromeStorageState);  

export function useChromeStorageState() {  
 const stateRef = useRef();  
 if (stateRef.current === undefined) {  
 stateRef.current = createChromeStorageState();  
 }  

 const state = stateRef.current;  
 return state;  
}

Зробіть те ж саме для ChromeMessageState, після чого ви зможете додати обидва провайдери у ваш компонент App:

function App() {  
 const chromeStorageState = useChromeStorageState();  
 const chromeMessageState = useChromeMessageState();  

 return (  


 {/* ваш вміст...
*/}  


 )  
}

І майже все готово!

Використання спостережуваних потоків (Observables) у компонентах

Ви можете легко створити стан React із спостережуваних потоків за допомогою наступного корисного хука, написаного dnlytras у його блозі https://dnlytras.com/blog/rxjs-react:

import { Observable } from 'rxjs';  
import { useState, useEffect } from 'react';  

type ObservableWrapper = {  
 observable: Observable;  
 initialValue: T;  
};  

export function withInitialValue(  
 observable: Observable,  
 initialValue: T  
) {  
 return Object.freeze({ observable, initialValue });  
}  

export function useObservable({  
 observable,  
 initialValue,  
}: ObservableWrapper) {  
 const [state, setState] = useState(() => initialValue);  

 useEffect(() => {  
 const sub = observable.subscribe(setState);  
 return () => sub.unsubscribe();  
 }, [observable]);  

 return state;  
}

Блог також надає багато цікавих деталей щодо реалізації RxJS в React, це варте прочитання.

Повний приклад використання

Швидкий приклад, щоб почати, де я відправляю повідомлення на фонового робітника з дією generate, він потім згенерує випадкове число та збереже його в chrome.storage.local з ключем rand:

import { useContext, useMemo } from "react";  
import { ChromeMessageContext } from "./contexts/ChromeMessageContext";  
import { ChromeStorageContext } from "./contexts/ChromeStorageContext";  
import { filter, map } from "rxjs";  
import { useObservable } from "./hooks/useObservable";  

export const RandomNumber = () => {  
 const { sendToBackground } = useContext(ChromeMessageContext);  
 const { local$ } = useContext(ChromeStorageContext);  

 const rando$ = useMemo(() => local$.pipe(  
 filter(local => 'rand' in local),  
 map(local => local['rand'] as number)  
 ), []);  

 const rand = useObservable({  
 observable: rando$,  
 initialValue: null  
 });  

 return (  

 sendToBackground('generate').subscribe()}>    { rand ? `Number is ${ rand }` : 'Start Generating' }        
    )   } ```  І все, дані, що надходять з усіх куточків розширення, більше не будуть змушувати вас не спати вночі. Все це легко керується завдяки поєднанню потужних операторів rxjs, просто не забувайте використовувати хуки _useRef_ і _useMemo_ для обгортання нових спостережуваних потоків, розширених з існуючих, або ви побачите, що спостережувані потоки будуть перезавантажуватись.



Перекладено з: [Managing Chrome Extension Messaging with RxJS](https://medium.com/@mayzyo/managing-chrome-extension-messaging-with-rxjs-d6d911901669)

Leave a Reply

Your email address will not be published. Required fields are marked *