Реализация Background Service Worker в браузерном расширении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Background Service Worker в браузерном расширении
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Background Service Worker в браузерном расширении

В Manifest V3 фоновый скрипт превратился в service worker. Это не просто переименование — изменилась модель жизненного цикла. Service worker может быть завершён браузером в любой момент, когда нет активных задач, и это ломает паттерны, которые работали в MV2 с persistent background page.

Регистрация в манифесте

{
  "manifest_version": 3,
  "background": {
    "service_worker": "background/sw.js",
    "type": "module"
  }
}

type: "module" позволяет использовать ES-модули и import внутри service worker. Поддерживается в Chrome 93+, Firefox — с некоторыми ограничениями в MV2 (в MV3 для Firefox это тоже поддерживается с версии 101).

Жизненный цикл: что реально происходит

Браузер запускает service worker при:

  • установке/обновлении расширения
  • получении сообщения из content script или popup
  • срабатывании alarm (chrome.alarms)
  • наступлении сетевого события (если подписан)

После завершения всех обработчиков браузер может убить процесс через ~30 секунд неактивности. Следующее событие поднимет его снова — уже с чистым состоянием.

Это означает: никакого глобального состояния в памяти. Переменные не переживают перезапуск.

// ПЛОХО — состояние потеряется при перезапуске SW
let requestCount = 0;

chrome.runtime.onMessage.addListener(() => {
  requestCount++; // после перезапуска SW снова 0
});

// ХОРОШО — сохраняем в chrome.storage
chrome.runtime.onMessage.addListener(async () => {
  const { requestCount = 0 } = await chrome.storage.local.get('requestCount');
  await chrome.storage.local.set({ requestCount: requestCount + 1 });
});

Обработка событий: синхронная регистрация обязательна

Обработчики событий должны быть зарегистрированы синхронно на верхнем уровне. Если вы регистрируете их внутри async-функции или после await — браузер может не «увидеть» их при запуске SW для обработки события:

// background/sw.js

// ПРАВИЛЬНО — синхронная регистрация на верхнем уровне
chrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onMessage.addListener(onMessage);
chrome.alarms.onAlarm.addListener(onAlarm);
chrome.tabs.onUpdated.addListener(onTabUpdated);

// Реализации могут быть async
async function onInstalled(details) {
  if (details.reason === 'install') {
    await chrome.storage.sync.set({ settings: defaultSettings });
  }
  if (details.reason === 'update') {
    await migrateSettings(details.previousVersion);
  }
}

function onMessage(message, sender, sendResponse) {
  // Обязательно return true для async-ответов
  handleMessage(message, sender).then(sendResponse);
  return true;
}

Долгоживущие соединения через Port

Для задач, которые занимают больше нескольких секунд (стриминг, polling), используйте chrome.runtime.connect(). Активное соединение удерживает SW живым:

// background/sw.js
chrome.runtime.onConnect.addListener((port) => {
  if (port.name !== 'streaming-channel') return;

  const controller = new AbortController();

  port.onDisconnect.addListener(() => controller.abort());

  streamData(port.sender.tab, controller.signal, (chunk) => {
    port.postMessage({ type: 'CHUNK', data: chunk });
  }).then(() => {
    port.postMessage({ type: 'DONE' });
  }).catch((err) => {
    if (err.name !== 'AbortError') {
      port.postMessage({ type: 'ERROR', message: err.message });
    }
  });
});

Работа с chrome.alarms для периодических задач

setTimeout и setInterval ненадёжны в service worker — они не переживают перезапуск. Для периодики:

chrome.runtime.onInstalled.addListener(() => {
  chrome.alarms.create('sync-data', {
    periodInMinutes: 15,
    delayInMinutes: 1
  });
});

chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name === 'sync-data') {
    await syncWithServer();
  }
});

async function syncWithServer() {
  const { lastSync, authToken } = await chrome.storage.local.get(['lastSync', 'authToken']);
  if (!authToken) return;

  const response = await fetch('https://api.example.com/sync', {
    method: 'POST',
    headers: { Authorization: `Bearer ${authToken}` },
    body: JSON.stringify({ since: lastSync })
  });

  if (response.ok) {
    const data = await response.json();
    await chrome.storage.local.set({
      syncedData: data,
      lastSync: Date.now()
    });
    // Уведомить content scripts на открытых вкладках
    const tabs = await chrome.tabs.query({ url: 'https://*.example.com/*' });
    tabs.forEach(tab => {
      chrome.tabs.sendMessage(tab.id, { type: 'DATA_UPDATED', data });
    });
  }
}

Управление вкладками и навигация

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.status !== 'complete') return;
  if (!tab.url?.startsWith('https://target-site.com')) return;

  // Inject script только когда страница полностью загружена
  await chrome.scripting.executeScript({
    target: { tabId },
    func: initPageEnhancement,
    args: [await getConfig()]
  });
});

async function getConfig() {
  const result = await chrome.storage.sync.get('config');
  return result.config ?? {};
}

// Функция выполняется в контексте страницы (не SW)
function initPageEnhancement(config) {
  window.__EXT_CONFIG__ = config;
  document.dispatchEvent(new CustomEvent('ext:ready', { detail: config }));
}

Обработка ошибок и устойчивость

SW может быть убит в середине async-операции. Для критичных операций используйте транзакционный подход:

async function processQueue() {
  const { queue = [] } = await chrome.storage.local.get('queue');
  if (queue.length === 0) return;

  const item = queue[0];

  try {
    await processItem(item);
    // Успех — убираем из очереди
    const { queue: current = [] } = await chrome.storage.local.get('queue');
    await chrome.storage.local.set({ queue: current.slice(1) });
  } catch (err) {
    // Помечаем как неудачную попытку, но не убираем
    const { queue: current = [] } = await chrome.storage.local.get('queue');
    current[0] = { ...current[0], attempts: (current[0].attempts ?? 0) + 1, lastError: err.message };
    await chrome.storage.local.set({ queue: current });
  }
}

Взаимодействие с popup и options page

Popup и страница настроек существуют в отдельных документах, но могут общаться с SW через messaging. Для получения данных реального времени из SW в popup:

// popup/popup.js
const port = chrome.runtime.connect({ name: 'popup' });

port.onMessage.addListener((msg) => {
  if (msg.type === 'STATE_UPDATE') updateUI(msg.state);
});

port.postMessage({ type: 'GET_STATE' });

// background/sw.js
const popupPorts = new Set();

chrome.runtime.onConnect.addListener((port) => {
  if (port.name !== 'popup') return;
  popupPorts.add(port);
  port.onDisconnect.addListener(() => popupPorts.delete(port));

  port.onMessage.addListener(async (msg) => {
    if (msg.type === 'GET_STATE') {
      const state = await getAppState();
      port.postMessage({ type: 'STATE_UPDATE', state });
    }
  });
});

// Рассылка обновлений всем открытым popup
function broadcastStateUpdate(state) {
  for (const port of popupPorts) {
    try { port.postMessage({ type: 'STATE_UPDATE', state }); }
    catch {} // порт мог закрыться
  }
}

Отладка service worker

В Chrome DevTools: расширение → «Service Worker» → «Inspect». SW имеет отдельный DevTools-контекст. При отладке следите за тем, что SW не убивается между точками останова — установите флаг «Update on reload» в панели Application.

В Firefox: about:debugging#/runtime/this-firefox → «Inspect» рядом с расширением.

Логи SW не видны в консоли страницы — только в консоли самого SW. Для передачи логов в popup используйте messaging или храните в chrome.storage.local.