Простая связь между страницей и расширением браузера


Введение

Это не будет отличным шаблоном для публичного расширения браузера. Однако есть много случаев, когда это будет достаточно хорошим способом. Например, какой-нибудь внутренний отладчик, или что-то похожее на redux dev tools.

Требования

В manifest.json:

{
  "action": {
    "default_popup": "index.html"
  },
  "permissions": ["tabs", "activeTab", "scripting"],
  "manifest_version": 3
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Подготовить

Мы должны иметь некоторое всплывающее окно для отображения полученных данных и разрешения на внедрение скрипта в активную вкладку.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="index.css" rel="stylesheet">
    <title>Internal Dev Tools</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="index.js"></script>
  </body>
</html>
Вход в полноэкранный режим Выход из полноэкранного режима
// popups index.js
chrome.tabs.query(
  { currentWindow: true, active: true },
  ([tab]) => {
    chrome.scripting.executeScript(
      {
        target: { tabId: tab.id },
        args: [42],
        func: function (arg) {
          // executed in the page
          return arg
        },
      },
      ([{ result }]) => {
        // executed in the extension
        console.log('received data: ', result); // 42
      }
    );
  }
);
Вход в полноэкранный режим Выйти из полноэкранного режима

В этом скрипте расширение выполняет func на вкладке активной страницы с одним аргументом «42». Возврат от него перехватывается в обратном вызове второго аргумента.

Передача данных со страницы

Теперь нам нужно передать данные со страницы:

// page
const stateReport = generateDataToSendIntoExtension();
const message = { type: 'DEV_TOOLS_REPORT_MESSAGE_TYPE', payload: stateReport };
window.postMessage(message);
Войти в полноэкранный режим Выйти из полноэкранного режима

Самый простой способ поддерживать его в актуальном состоянии — обернуть его с помощью setInterval:

// page
setInterval(() => {
  // postMessage
}, 1_000)
Войти в полноэкранный режим Выйти из полноэкранного режима

Получение данных в расширении

Чтобы получить данные из window.postMessage(, мы должны использовать window.addEventListener('message', callback);. К сожалению, обратный вызов будет вызван асинхронно, поэтому простой возврат из предыдущей части не сработает, нам придется обернуть его в Promise. Также страница вызывает window.postMessage( каждые 1 секунду, поэтому в расширении мы должны использовать setInterval.

// popups index.js
const intervalTimeout = setInterval(() => {
  chrome.tabs.query(
    { currentWindow: true, active: true },
    ([tab]: [{ id: number }]) => {
      // @ts-expect-error
      chrome.scripting.executeScript(
        {
          target: { tabId: tab.id },
          args: ['DEV_TOOLS_REPORT_MESSAGE_TYPE'],
          func: function (messageType) {
            return new Promise((resolve) => {
              const callback = ({ data }) => {
                window.removeEventListener("message", callback);
                if (data?.type === messageType) {
                  resolve(data.payload);
                }
              };
              window.addEventListener("message", callback);
            });
          },
        },
        ([{ result }]) => {
          setState(result);
        }
      );
    }
  );
}, 1_000);
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку мы используем setInterval, мы должны помнить об удалении слушателя после вызова его обратного вызова. Также, чтобы избежать конфликтов с сообщениями другого окна, мы должны добавить оператор if, чтобы отфильтровать другие сообщения. И последнее — отображение данных со страницы в popup.html расширения. В моем случае popup содержит простое приложение react, поэтому единственное, что нужно сделать, это вызвать setState с данными со страницы.

Резюме

Конечно, это не производственное решение. Слишком много разрешений, слабая реактивность и потенциальные утечки данных. Однако для внутренних целей (например, в качестве инструментов разработчика) это работает.

Оцените статью
Procodings.ru
Добавить комментарий