עריכת מסמכים שיתופית בזמן אמת באפליקציות מובייל: מדריך למפתח
למה עריכה שיתופית מאתגרת בסביבות מובייל
הערה: כאשר שני מהנדסים עורכים את אותו מסמך בו-זמנית, סביבות מובייל מציגות שלושה תרחישים: רשתות לא יציבות עם אובדן מנות (עד 30% באות חלש), סנכרון ברקע לאחר מצב לא מקוון, ומקלדת מקורית עם אירועי קומפוזיציה – השהיית קלט של 300+ אלפיות שנייה. נתקלנו בזה בעורך ארגוני ל-500+ משתמשים, שם נאלצנו לאזן בין ערבויות ACID לבין היענות ממשק המשתמש. הצוות שלנו מיישם פתרונות כאלה במפתח מלא באמצעות אלגוריתמים מוכחים. עם ניסיון של 5+ שנים ו-10+ פרויקטים, אנו מבטיחים סנכרון יציב גם עם עיכובי רשת של עד 2 שניות.
בחירת אלגוריתם סנכרון: OT לעומת CRDT
| מאפיין | OT (טרנספורמציה אופרטיבית) | CRDT (Y.js, Automerge) |
|---|---|---|
| מתאם | נדרש שרת | אופציונלי (עובד לא מקוון) |
| מצב לא מקוון | אגירה עם מיזוג לאחר מכן | תמיכה מקורית אידיאלית |
| ביצועים | השהיה נמוכה (<30ms) עם רשת יציבה | יעיל לסנכרון אסינכרוני (50-100ms) |
| מורכבות יישום | בינונית (טרנספורמציה בצד השרת) | נמוכה (ללא לוגיקה מרכזית) |
| שימוש בזיכרון | ~10MB לכל 1000 פעולות | ~5MB לכל 1000 פעולות |
OT מוכח ב-Google Docs וב-Apache Wave – השרת מתאם פעולות, ומונע קונפליקטים. CRDT (Y.js – תקן דה-פקטו) אינו דורש שרת: מיזוג עובד מקומית. לעורך ארגוני במובייל אנו בוחרים ב-OT; לעריכה לא מקוונת-ראשונה, CRDT מהיר פי 2 בזמן סנכרון. ראו ויקיפדיה: טרנספורמציה אופרטיבית ותיעוד Yjs לפרטים.
כיצד Y.js פותר בעיות התנגשות ב-React Native
yjs הוא JavaScript טהור, עובד ב-React Native ללא שינויים. הגדרה טיפוסית:
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://your-server.com/sync', 'doc-room-id', ydoc);
const ytext = ydoc.getText('document');
import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; const ydoc = new Y.Doc(); const provider = new WebsocketProvider('wss://your-server.com/sync', 'doc-room-id', ydoc); const ytext = ydoc.getText('document'); הוא סוג CRDT עם תמיכה בעיצוב (מודגש, נטוי, כותרות). אינטגרציה עם עורכי אינטרנט דרך WebView נותנת MVP מהיר. ל-UX מקורי, יש להשתמש ב-TextInput מותאם עם סנכרון ידני דרך פעולות Y.js. ב-iOS: YText, באנדרואיד: UITextViewDelegate. לביצועים, השתמשו בגובה שורה 18sp ובדיבונס של 200ms.
כיצד ליישם Y.js ב-React Native
כדי ליישם Y.js בעורך שיתופי ב-React Native, בצעו את הצעדים הבאים:
- התקינו תלויות:
TextWatcher. - צרו Y.Doc והתחברו לספק WebSocket עם מזהה חדר.
- קבלו סוג Y.Text לתוכן המסמך.
- קשרו את Y.Text ל-TextInput מקורי באמצעות גשר מותאם ל-iOS/אנדרואיד.
- יישמו מודעות לסמן באמצעות פרוטוקול Awareness.
- שמרו את מצב המסמך ב-SQLite בשינויים באמצעות
provider.awareness.setLocalState({ user: { name: 'Иван', color: '#3B82F6' }, cursor: { anchor: 45, focus: 45 } }); provider.awareness.on('change', () => { const states = Array.from(provider.awareness.getStates().values()); // обновляем позиции курсоров других пользователей });. - התחברו מחדש וסנכרנו בעת חזרת האפליקציה.
סמנים ומודעות
פרוטוקול ה-Awareness של Y.js מפיץ נתונים זמניים (סמנים, נוכחות) בין המשתתפים. אינו נשמר במסמך:
provider.awareness.setLocalState({ user: { name: 'Иван', color: '#3B82F6' }, cursor: { anchor: 45, focus: 45 } });
provider.awareness.on('change', () => {
const states = Array.from(provider.awareness.getStates().values());
// обновляем позиции курсоров других пользователей
});הצגת סמנים של אחרים ב-TextInput מקורי אינה טריוויאלית. יש צורך להמיר מיקום תווים לקואורדינטות פיקסלים באמצעות UITextView.caretRect(for:) ב-iOS ו-Layout.getDesiredWidth() באנדרואיד. לשם כך, אנו משתמשים ב-Y.js Awareness ובחישובים מותאמים – דיוק פיקסלים ב-60fps.
כיצד נפתרים קונפליקטי עיצוב?
עיצוב YText של Y.js פועל דרך פעולות Delta עם תכונות. עיצוב במקביל (משתמש אחד עושה מודגש, אחר נטוי על חפיפה) – שתי התכונות חלות. לפעולות שאינן תואמות סמנטית (H1 לעומת H2), Y.js בוחר לפי clientId, אך הממשק צריך להזהיר או לאפשר פתרון ידני. בפרויקט אחד, הוספנו דיאלוג מודאלי – משתמשים פותרים קונפליקט תוך 2 שניות.
שמירה והיסטוריה
מסמך Y.js מסריאליזציה ל-Uint8Array דרך Y.encodeStateAsUpdate(). במובייל, שמרו ב-SQLite באמצעות react-native-sqlite-storage. בעת פתיחה: טעינת מצב שמור, החלה דרך Y.applyUpdate(), ואז חיבור ל-WebSocket להפרשים. השרת משתמש ב-y-leveldb או y-redis לשמירה. אופטימיזציה: תמונות מצב כל 100 פעולות – זמן טעינה <500ms.
פרטים טכניים ליישום
לצד השרת, שרת Node.js מינימליסטי מחבילת y-websocket מספיק. בייצור, הוסיפו שמירה, אימות, ובקרת גישה לחדרים.
השוואה: מקורי לעומת WebView
| קריטריון | TextInput מקורי + Y.js | WebView + Quill.js |
|---|---|---|
| ביצועים | גבוהים (60fps) – פי 3 טוב יותר מ-WebView | בינוניים (30-45fps ב-500 תווים) |
| מורכבות | גבוהה (סנכרון ידני) | נמוכה (ספרייה מוכנה) |
| תמיכה בעיצוב | מוגבלת (כפתורים מותאמים) | מלאה (סרגל כלים) |
| זמן MVP | 12–16 שבועות | 8–10 שבועות |
לסנכרון באפליקציות מובייל, TextInput מקורי עם Y.js מציע ביצועים פי 3 טובים יותר בהשוואה ל-WebView, חיוניים להצגת סמן חלקה.
מה כלול בעבודה
- ניתוח: הערכת תרחישים ובחירת אלגוריתם (OT/CRDT).
- ארכיטקטורה: תכנון סנכרון באמצעות WebSocket.
- יישום: עורך טקסט עם רינדור מקורי ושרת Y.js או OT.
- סמנים: פיתוח מודעות והצגת סמנים.
- בדיקות: בדיקות על מכשירים אמיתיים עם אמולציית רשת חלשה (30% אובדן מנות).
- תוצרים:
- תיעוד: מדריכי משתמש, הפניות API, ודיאגרמות ארכיטקטורה.
- גישה: מאגר קוד מקור ואישורי שרת.
- הדרכה: סדנאות צוות על תחזוקה והרחבה.
- תמיכה: חודש אחד לאחר השקה כולל תיקוני באגים.
עלויות MVP טיפוסיות נעות בין $20,000 ל-$40,000, עם חיסכון פוטנציאלי של עד 30% בהשוואה לפיתוח מותאם מלא. עלויות הפרויקט מתחילות ב-$20,000 ל-MVP, ואנו מציעים הנחה של 10% להתחייבויות מוקדמות.
קבלו ייעוץ – נעריך את הפרויקט שלכם. כתבו לנו – נראה דוגמאות קוד.







