סנכרון הרחבות דפדפן: איחוד מכשירים ללא אובדן נתונים
תארו לעצמכם: משתמש מגדיר מסננים, אוסף סימניות ומוסיף הערות במחשב הנייד בעבודה. ואז עובר למחשב הביתי — והכל נעלם. ללא סנכרון, ההרחבה מאבדת את מטרתה. פתרנו זאת עבור למעלה מ-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 באמצעות ספריית 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; } , מה שאיפשר סנכרון של 1 מגה-בייט של נתונים עם אפס אובדן בתדירות שינוי של 100 פעולות בשנייה.
עוד על CRDT ווקטורי גרסאות
CRDT הוא מודל מתמטי המבטיח מיזוג ללא קונפליקטים. לכל פעולה יש מזהה ייחודי (שעון + עמית). להרחבות דפדפן, ספריות פופולריות הן Automerge ו-Yjs. וקטורי גרסאות הם אפשרות קלה יותר: כל מכשיר מאחסן מונה גרסאות; במיזוג, נבחר המקסימום.
למה backend מותאם אישית אמין יותר מ-chrome.storage.sync
אם נפח הנתונים עולה על 100 קילו-בייט או שנדרש סנכרון בזמן אמת, שרת מותאם אישית הוא חיוני. backend מאפשר:
- אחסון בלתי מוגבל (PostgreSQL, Redis);
- עדכונים בזמן אמת באמצעות WebSocket/SSE;
- גרסאות לכל אובייקט וחזרה לשינויים;
- אימות משתמשים באמצעות OAuth (Google, GitHub).
מקרה: הרחבת מארגן עם 10,000 משתמשים פעילים חודשיים. בתחילה השתמשה ב-yjs — לאחר חודש, משתמשים התלוננו על הערות שאבדו ואיטיות. העברנו ל-backend מותאם אישית (Node.js + Redis + PostgreSQL). סכמת סנכרון: CRDT + וקטורי גרסאות. תוצאה: מהירות הסנכרון גדלה פי 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, השתמשו ב-chrome.storage.sync. אחסנו את הטוקן ב-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); }); }); } (בטוח יותר מ-sync). רעננו 5 דקות לפני פקיעת התוקף.
סנכרון בזמן אמת באמצעות service worker
אם ההרחבה פועלת על מספר מסכים או בצוות, נדרש זמן אמת. אנו משתמשים ב-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 ואת אסטרטגיית המיזוג. אנו מאמנים את הצוות שלכם.
לוחות זמנים אופייניים:
- סנכרון בסיסי דרך chrome.storage.sync: מיומיים.
- backend מלא עם OAuth וזמן אמת: משבועיים עד 4 שבועות.
- CRDT לנתונים מורכבים: מ-3 שבועות.
צרו קשר להערכה ראשונית של הפרויקט שלכם. הזמינו יישום סנכרון סוהר.
טעויות נפוצות ביישום סנכרון
- התעלמות מתרחישי מצב לא מקוון: משתמש משנה נתונים ללא אינטרנט, ואז הכל נדרס באינטרנט. פתרון: השתמשו בוקטורי גרסאות במקום LWW.
- מילוי יתר של chrome.storage.sync: גידול נתונים בלתי מבוקר המוביל לשגיאות כתיבה. עקבו אחר הנפח ועברו ל-backend בזמן.
- בחירת אסטרטגיית מיזוג שגויה: גישה אחת מתאימה, נבחרת אחרת — תוצאות בלתי צפויות. בדקו עם תרחישים אמיתיים.
- חוסר אימות: נתונים של משתמשים שונים מתערבבים. תמיד קשרו נתונים לחשבון.
הימנעות מטעויות אלו מניבה סנכרון חזק שעובד גם במהלך אובדן רשת. הפתרונות שלנו מפחיתים את עומס הפיתוח ב-30–50% באמצעות מודולים מוכנים.







