Синхронізація даних у браузерному розширенні: як об'єднати дані з різних пристроїв без втрат
Уявіть: користувач налаштував фільтри, зібрав колекцію закладок і вніс нотатки на робочому ноутбуці. Переходить на домашній ПК — і все пропало. Без синхронізації розширення втрачає сенс. Ми вирішили це завдання для 30+ проектів, включаючи розширення для нотаток із 10 000 активних користувачів, де щодня обробляється 50 000 операцій запису. Синхронізація — це не просто копіювання даних: це боротьба з конфліктами, лімітами сховища та офлайн-сценаріями.
Чому chrome.storage.sync часто не вистачає?
Вбудоване сховище Chrome (і Firefox) — хороший старт, але жорсткі обмеження швидко впираються в стелю. Згідно з документацією Chrome Extensions Storage API, ліміти такі:
| Параметр | Значення |
|---|---|
| Загальний об'єм | 102 400 байт (100 КБ) |
| Максимальний розмір одного значення | 8 192 байт |
| Максимальна кількість ключів | 512 |
| Максимальна кількість операцій запису на хвилину | 1 800 (сумарно), 120 на ключ |
Для налаштувань розширення (теми, перемикачі) цього достатньо. Але щойно з'являються нотатки, закладки, історія — користувач упреться в ліміт. Ми зустрічали проекти, де намагалися зберігати 200 КБ нотаток, розбиваючи на ключі по 8 КБ — це призводило до гальмувань і плутанини. chrome.storage.sync не вміє вирішувати конфлікти: при одночасному записі з двох пристроїв перемагає останній, дані одного губляться.
Як вирішувати конфлікти при офлайн-редагуванні?
Зауважимо: коли користувач редагує дані на двох пристроях офлайн, а потім виходить у мережу, виникає колізія. Базова стратегія Last-Write-Wins (LWW) втрачає зміни. Більш надійний підхід — версіоновані об'єкти з міткою часу:
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;
}
Для серйозних даних (колаборативні нотатки, спільні списки) використовуємо CRDT (Conflict-free Replicated Data Types) — вони гарантують узгодженість без центрального сервера. В одному з проектів ми впровадили CRDT на основі бібліотеки yjs, що дозволило синхронізувати 1 МБ даних з нульовими втратами при частоті змін 100 операцій на секунду.
Детальніше про CRDT та version vectors
CRDT — математична модель, що забезпечує злиття без конфліктів. Кожна операція має унікальний ідентифікатор (clock + peer). Для браузерних розширень популярні бібліотеки Automerge та Yjs. Version vectors — легший варіант: кожен пристрій зберігає лічильник версій, при злитті вибирається максимальна.
Чому власний бекенд надійніший за chrome.storage.sync?
Якщо обсяг даних перевищує 100 КБ або потрібна синхронізація в реальному часі — без власного сервера не обійтися. Backend дозволяє:
- зберігати необмежений об'єм (PostgreSQL, Redis);
- реалізувати WebSocket/SSE для миттєвого оновлення;
- версіонувати кожен об'єкт і відкочувати зміни;
- авторизувати користувачів через OAuth (Google, GitHub).
Кейс: розширення-органайзер з 10 000 MAU. Спершу використовували chrome.storage.sync — через місяць користувачі скаржилися на втрату нотаток і гальмування. Ми мігрували на власний backend (Node.js + Redis + PostgreSQL). Схема синхронізації: CRDT + version vectors. Підсумок: швидкість синхронізації зросла в 10 разів (з 5 секунд до 0,5 с), втрати даних припинилися. Повна інтеграція зайняла 3 тижні.
Авторизація через chrome.identity
Щоб прив'язати дані до користувача, використовуємо вбудований OAuth. Приклад для Chrome:
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();
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. Токен зберігаємо в chrome.storage.local (безпечніше, ніж sync). Оновлюємо за 5 хвилин до завершення терміну дії.
Синхронізація в реальному часі через service worker
Якщо розширення працює на кількох моніторах або в команді, потрібен real-time. Використовуємо SSE (Server-Sent Events) — він легший за WebSocket, працює через ReadableStream у service worker (EventSource недоступний).
async function startRealTimeSync() {
const token = await getAuthToken();
if (!token || eventSource) return;
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));
applyRemoteChanges([event]);
} catch {}
}
}
}
На сервері використовуємо Redis Pub/Sub — канали за userId. При зміні публікується подія, service worker отримує її та оновлює інтерфейс. Затримка — 100–300 мс.
Що входить у роботу та терміни
Аналізуємо ваше розширення, обсяг даних, кількість користувачів. Проектуємо архітектуру синхронізації: вибираємо між sync і backend, визначаємо тип авторизації. Реалізуємо клієнтський модуль (Chrome та Firefox), серверну частину (REST + WebSocket), налаштовуємо деплой (Docker, Nginx, Cloudflare). Документуємо API та стратегію merge. Навчаємо вашу команду.
Типові терміни:
- Базова синхронізація через chrome.storage.sync: від 2 днів.
- Повноцінний backend з OAuth та real-time: від 2 до 4 тижнів.
- CRDT для складних даних: від 3 тижнів.
Зв'яжіться з нами для попередньої оцінки вашого проекту. Замовте реалізацію синхронізації під ключ.
Типові помилки при впровадженні синхронізації
- Ігнорування офлайн-сценаріїв: користувач змінює дані без інтернету, а при підключенні все затирається. Рішення — використовувати version vectors замість LWW.
- Переповнення chrome.storage.sync: неконтрольоване зростання даних призводить до помилок запису. Потрібен моніторинг обсягу та своєчасний перехід на backend.
- Неправильний вибір стратегії merge: підходить одна, а обрана інша — результат непередбачуваний. Тестуємо з реальними сценаріями.
- Відсутність авторизації: дані різних користувачів перемішуються. Завжди прив'язуємо до облікового запису.
Уникнувши цих помилок, ви отримаєте стійку синхронізацію, яка працює навіть при втраті мережі. Наші рішення знижують навантаження на розробку на 30–50% за рахунок готових модулів.







