Чому спільне редагування складне в мобільному середовищі?
Зазначимо: коли два інженери одночасно редагують один документ, мобільне середовище додає три сценарії: нестабільна мережа з втратою пакетів (до 30% при поганому сигналі), фонова синхронізація після офлайн-режиму та нативна клавіатура з composition events — затримка введення 300+ мс. Ми стикалися з цим у корпоративному редакторі для 500+ користувачів, де довелося балансувати між ACID-гарантіями та чуйністю UI. Наша команда реалізує такі рішення під ключ, використовуючи перевірені алгоритми. Досвід багаторічної роботи та понад 10 проєктів дозволяє гарантувати стабільну синхронізацію навіть при затримках мережі до 2 секунд. Отримайте консультацію — ми оцінимо ваш проєкт і запропонуємо архітектуру.
Вибір алгоритму синхронізації: OT vs CRDT
| Характеристика | OT (Operational Transform) | CRDT (Y.js, Automerge) |
|---|---|---|
| Координатор | Сервер обов'язковий | Опціонально (працює в офлайн) |
| Офлайн-режим | Буферизація з подальшим merge | Нативна підтримка ідеальна |
| Продуктивність | Низька затримка (<30ms) при стабільній мережі | Ефективний при асинхронній синхронізації (50-100ms) |
| Складність реалізації | Середня (серверна трансформація) | Низька (немає центральної логіки) |
| Використання пам'яті | ~10MB на 1000 операцій | ~5MB на 1000 операцій |
OT перевірений в Google Docs і Apache Wave — сервер координує операції, що виключає конфлікти. CRDT (Y.js — стандарт де-факто) не потребує сервера: merge працює локально. Для мобільного корпоративного редактора ми вибираємо OT, для офлайн-first блокнота — CRDT в 2 рази швидший за часом синхронізації.
Як 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'); YText — CRDT-тип з підтримкою форматування (bold, italic, headers). Інтеграція з веб-редакторами через WebView дасть швидкий MVP. Для нативного UX — кастомний TextInput з ручною синхронізацією через Y.js операції. На iOS — UITextViewDelegate, на Android — TextWatcher. Для прискорення використовуйте висоту рядка 18sp і дебонс 200мс.
Курсори та awareness
Y.js Awareness Protocol поширює ephemeral-дані (курсори, присутність) між учасниками. Не зберігається в документі:
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() на Android. Для цього використовуємо Y.js Awareness і кастомні обчислення — точність до пікселя при 60fps.
Як розв'язуються конфлікти форматування?
Y.js YText форматування працює через Delta-операції з атрибутами. Конкурентне форматування (один користувач робить bold, інший italic на перекритті) — обидва атрибути застосовуються. Для семантично несумісних операцій (H1 vs H2) Y.js вибирає за clientId, але UI має попередити або дати ручний resolve. В одному проєкті ми додали модалку вибору — користувачі розв'язують конфлікт за 2 секунди.
Персистентність та історія
Y.js документ серіалізується в Uint8Array через Y.encodeStateAsUpdate(). На мобілці зберігаємо в SQLite через react-native-sqlite-storage. При відкритті: завантажуємо збережений стан, застосовуємо через Y.applyUpdate(), потім підключаємося до WebSocket для diff. Сервер використовує y-leveldb або y-redis для persistence. Оптимізація: снепшоти кожні 100 операцій — час завантаження <500мс.
Технічні деталі реалізації
Для серверної частини достатньо мінімалістичного Node.js сервера з пакету y-websocket. В production варто додати persistence, auth та room access control.
Порівняння підходів: нативний vs WebView
| Критерій | Нативний TextInput + Y.js | WebView + Quill.js |
|---|---|---|
| Продуктивність | Висока (60fps) | Середня (30-45fps при 500 символів) |
| Складність | Висока (ручна синхронізація) | Низька (готова бібліотека) |
| Підтримка форматування | Обмежена (кастомні кнопки) | Повна (панель інструментів) |
| Час MVP | 12–16 тижнів | 8–10 тижнів |
Що входить у роботу
- Аналіз сценаріїв використання та вибір алгоритму (OT/CRDT).
- Проектування архітектури синхронізації (WebSocket/WebRTC).
- Реалізація текстового редактора з нативним рендерингом.
- Інтеграція Y.js або OT-сервера.
- Розробка відображення чужих курсорів та awareness.
- Тестування на реальних пристроях з емуляцією поганої мережі (втрата 30% пакетів).
- Документація та навчання команди.
- Підтримка після деплою (1 місяць).
Орієнтовні терміни: MVP на React Native — 8–14 тижнів, нативні iOS/Android з багатим форматуванням — 16–24 тижні. Вартість розраховується індивідуально.
Отримайте консультацію — оцінимо ваш проєкт. Замовте прототип за 2 тижні. Напишіть нам — покажемо приклади коду.







