Большинство расширений браузера, которые я сделал, были в значительной степени ориентированы на скрипт контента (CS). Фоновый сценарий (BS) был в основном предназначен для предоставления услуг, недоступных в API CS, и когда CS нуждался в этих услугах, он запрашивал их следующим образом:

chrome.runtime.sendMessage(
  { 
    type: 'notification', 
    message: 'example notification for medium' 
  },
  (response) => {
    // RESPONSE HANDlING CODE GOES HERE (IF NEEDED)
  } 
);

Затем мне понадобится BS для обработки запроса:

chrome.runtime.onMessage.addListener((message, sender) => {
  if (message.type === 'notification') {
    // REQUEST HANDLING CODE GOES HERE
  }
}

Это не так уж плохо, но было бы некрасиво, если бы CS требовалось множество различных сервисов BS. Мне не нравилось, что CS и BS должны постоянно соглашаться с форматированием различных запросов. Вот почему я подумал, что было бы желательно вынести API фона в контент-скрипт.

Добиться этого было довольно просто. На стороне CS нам просто нужно создать такую ​​функцию:

/**
* A function that represents the Background Script's API
*
* @param apiPath {String} -- ie 'chrome.notifications.create'
* @param args {array} -- the args to be passed to the background api
*/
const backgroundAPI = (apiPath, ...args) => {
  const path = apiPath.split('.');
  return new Promise((resolve) => {
    const sendingID = Math.random();
    chrome.runtime.onMessage.addListener((response) => {
      if (response.sendingID === sendingID) {
        resolve(response.data);
      }
    });
    chrome.runtime.sendMessage({ 
      type: "background proxy", 
      sendingID,
      path,
      args,
    });
  }); // end of promise
}

Если нашему CS нужен список всех текущих вкладок, он может вызвать эту функцию следующим образом:

const tabs = await backgroundAPI('chrome.tabs.query', {});
// a '{}' as the options to chrome.tabs.query will return all tabs

Теперь вместо того, чтобы создавать обработчики для каждой отдельной службы, наша БС может использовать обобщенный обработчик для запроса «фонового прокси».

chrome.runtime.onMessage.addListener((message, sender) => {
  if (message.type === 'background proxy') {
    const { sendingID, path, args } = message;
    let apiProperty = chrome;
    if (path[0] === 'chrome') {
      path.shift();
    }
    while (path.length > 0) {
      apiProperty = apiProperty[path.shift()];
    }
    if (typeof apiProperty !== 'function') {
      chrome.tabs.sendMessage(sender.tab.id, { 
        sendingID, 
        data: apiProperty
      });
  
    } else {
      // Adding callback function to our arguments
      args.push((...callbackArgs) => {
        chrome.tabs.sendMessage(sender.tab.id, {
          sendingID,
          data: callbackArgs,
        });
      });
      apiProperty.apply(apiProperty, args); 
  
    }
  }
}

Все вышеперечисленное делает доступ к свойству или методу в API BS, которые мы указали в нашем запросе от CS. Затем он определяет, получили ли мы доступ к свойству или методу. Если мы получили доступ к свойству, он просто отправляет его обратно в CS. Если это был метод, он сначала добавляет общую функцию обратного вызова к нашим аргументам, а затем вызывает метод с нашими аргументами, используя .apply(). Общий обратный вызов включает в себя код, который отправляет свои аргументы обратно в наш CS.

Это в основном все. Для огромного количества функций это будет единственный фоновый код, который вам нужно написать. Это не полное представление API фона, потому что мы не можем добавлять прослушиватели событий (обратные вызовы не сериализуемы в формате JSON, поэтому мы не можем передать их в запросе от CS).

Если вам нужна дополнительная функциональность, которая не встроена в API BS, но все же должна выполняться в BS, например, получение некоторых данных для обхода политики CORS, вы все равно можете использовать ту же функцию backgroundAPI в сценарии содержимого и затем добавьте функцию выборки в фоновый API, расширив объект chrome.

// In Background Script
chrome.fetch = async (url, options, callback) => {
  let resp = await fetch(url, options);
  resp = await resp.json();
  callback(resp);
}
// In content script
const randomUserData = await backgroundAPI(
  'chrome.fetch',
  'https://randomuser.me/api',
  {}
);

Примеры, которые я создал, были намеренно упрощены, и вы, вероятно, захотите добавить дополнительную проверку ошибок, например, если сценарий содержимого запросил несуществующее свойство или метод в фоновом API.