Реализация синхронизации данных между устройствами в браузерном расширении

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация синхронизации данных между устройствами в браузерном расширении
Средняя
~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

Реализация синхронизации данных между устройствами в браузерном расширении

Синхронизация в браузерных расширениях строится на двух механизмах: chrome.storage.sync для простых настроек и собственный бэкенд для произвольных данных. Первый вариант бесплатен, но ограничен. Второй требует авторизации и инфраструктуры.

chrome.storage.sync: ограничения и реальные возможности

Лимиты хранилища sync (Chrome):

  • Общий объём: 102 400 байт
  • Максимальный размер одного значения: 8 192 байт
  • Максимальное число ключей: 512
  • Максимальное число операций записи в минуту: 1 800 (суммарно), 120 на ключ

Firefox поддерживает browser.storage.sync с похожими ограничениями через Firefox Sync.

Для настроек расширения этого достаточно. Для пользовательского контента (закладки, заметки, история) — нет.

// Утилита для работы с sync c учётом лимита на значение
async function syncSet(key, value) {
  const serialized = JSON.stringify(value);

  if (new Blob([serialized]).size > 8000) {
    // Разбиваем на чанки по 7КБ
    const chunks = chunkString(serialized, 7000);
    const batch = {};
    batch[`${key}__count`] = chunks.length;
    chunks.forEach((chunk, i) => {
      batch[`${key}__${i}`] = chunk;
    });
    await chrome.storage.sync.set(batch);
  } else {
    await chrome.storage.sync.set({ [key]: value });
  }
}

async function syncGet(key) {
  const result = await chrome.storage.sync.get([key, `${key}__count`]);

  if (result[`${key}__count`]) {
    const count = result[`${key}__count`];
    const chunkKeys = Array.from({ length: count }, (_, i) => `${key}__${i}`);
    const chunks = await chrome.storage.sync.get(chunkKeys);
    const serialized = chunkKeys.map(k => chunks[k] ?? '').join('');
    return JSON.parse(serialized);
  }

  return result[key] ?? null;
}

function chunkString(str, size) {
  const chunks = [];
  for (let i = 0; i < str.length; i += size) {
    chunks.push(str.slice(i, i + size));
  }
  return chunks;
}

Конфликты при синхронизации

Пользователь мог изменить настройки на двух устройствах офлайн. При возврате онлайн оба устройства запишут свою версию. chrome.storage.sync не имеет механизма merge — побеждает последняя запись.

Для более предсказуемого поведения используйте версионированные объекты:

async function mergeSettings(incoming) {
  const local = await chrome.storage.sync.get('settings');
  const current = local.settings ?? { version: 0, data: defaultSettings };

  if (incoming.version <= current.version) {
    // Входящие данные старее — игнорируем
    return current;
  }

  // Простая стратегия: побеждает более новая версия
  await chrome.storage.sync.set({ settings: incoming });
  return incoming;
}

Собственный бэкенд для полноценной синхронизации

Когда нужна синхронизация произвольного объёма данных, реализуют сервер с identity + storage. Типичная схема:

  1. Пользователь авторизуется через OAuth (Google, GitHub)
  2. Расширение хранит access token в chrome.storage.local
  3. При изменениях данных — push на сервер с меткой времени
  4. При старте/фокусе браузера — pull изменений с сервера
// sync/client.js
const API_BASE = 'https://api.your-extension.com/v1';

async function getAuthToken() {
  const { authToken, tokenExpiry } = await chrome.storage.local.get(['authToken', 'tokenExpiry']);
  if (!authToken || Date.now() > tokenExpiry) {
    return null;
  }
  return authToken;
}

async function pushChanges(changes) {
  const token = await getAuthToken();
  if (!token) return;

  const { lastSyncedAt = 0 } = await chrome.storage.local.get('lastSyncedAt');

  const response = await fetch(`${API_BASE}/sync/push`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      changes,
      clientId: await getClientId(),
      timestamp: Date.now()
    })
  });

  if (!response.ok) throw new Error(`Push failed: ${response.status}`);

  const { serverTimestamp } = await response.json();
  await chrome.storage.local.set({ lastSyncedAt: serverTimestamp });
}

async function pullChanges() {
  const token = await getAuthToken();
  if (!token) return;

  const { lastSyncedAt = 0 } = await chrome.storage.local.get('lastSyncedAt');

  const response = await fetch(
    `${API_BASE}/sync/pull?since=${lastSyncedAt}&clientId=${await getClientId()}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  if (!response.ok) return;

  const { changes, serverTimestamp } = await response.json();

  if (changes.length > 0) {
    await applyRemoteChanges(changes);
    await chrome.storage.local.set({ lastSyncedAt: serverTimestamp });

    // Уведомить все открытые вкладки расширения
    const views = chrome.extension.getViews();
    views.forEach(view => {
      view.postMessage?.({ type: 'SYNC_COMPLETE', changes });
    });
  }
}

async function getClientId() {
  let { clientId } = await chrome.storage.local.get('clientId');
  if (!clientId) {
    clientId = crypto.randomUUID();
    await chrome.storage.local.set({ clientId });
  }
  return clientId;
}

Авторизация через chrome.identity

Chrome предоставляет встроенный OAuth flow через chrome.identity.getAuthToken:

async function signInWithGoogle() {
  return new Promise((resolve, reject) => {
    chrome.identity.getAuthToken({ interactive: true }, async (token) => {
      if (chrome.runtime.lastError) {
        reject(chrome.runtime.lastError);
        return;
      }

      // Получаем информацию о пользователе
      const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
        headers: { Authorization: `Bearer ${token}` }
      });
      const userInfo = await response.json();

      // Обмениваем Google token на токен своего API
      const authResponse = await fetch(`${API_BASE}/auth/google`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ googleToken: token })
      });
      const { accessToken, expiresAt } = await authResponse.json();

      await chrome.storage.local.set({
        authToken: accessToken,
        tokenExpiry: expiresAt,
        userInfo
      });

      resolve(userInfo);
    });
  });
}

Для Firefox используйте browser.identity.launchWebAuthFlow с аналогичной логикой.

Синхронизация в реальном времени

Если нужна синхронизация без задержки (например, совместное использование расширения на нескольких мониторах), используйте WebSocket или SSE в service worker:

// background/sw.js — SSE подключение
let eventSource = null;

async function startRealTimeSync() {
  const token = await getAuthToken();
  if (!token || eventSource) return;

  // SSE через fetch + ReadableStream (работает в SW, EventSource — нет в SW)
  const response = await fetch(`${API_BASE}/sync/stream`, {
    headers: { Authorization: `Bearer ${token}` }
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split('\n').filter(l => l.startsWith('data: '));

    for (const line of lines) {
      try {
        const event = JSON.parse(line.slice(6));
        await applyRemoteChanges([event]);
      } catch {}
    }
  }
}

Типичный сценарий внедрения: начинают с chrome.storage.sync для MVP, добавляют собственный бэкенд по мере роста требований к объёму и надёжности.