Интеграция Liveblocks для real-time collaboration в мобильном приложении
Liveblocks — managed-инфраструктура для совместного редактирования: WebSocket-сервер, CRDT-хранилище, presence, комментарии, нотификации — всё за облачным API. В браузере интеграция занимает день. В React Native — уже интереснее, потому что официальные React-хуки (@liveblocks/react) работают, но ряд нюансов всплывает только в продакшене.
Что даёт Liveblocks из коробки
Три ключевых примитива:
Storage — CRDT-хранилище на базе собственной реализации (не Y.js). LiveObject, LiveList, LiveMap — типизированные структуры с автоматическим merge. Изменения реплицируются между всеми клиентами в комнате (<100 мс в пределах одного региона).
Presence — эфемерный стейт пользователя: позиция курсора, выделение, статус. Не персистируется, хранится только пока пользователь онлайн. Отлично для отображения аватаров и активности в реальном времени.
Yjs-интеграция — с версии 1.x Liveblocks поддерживает Y.js через @liveblocks/yjs, что позволяет использовать Tiptap/Slate на вебе и тот же документ в мобильном приложении.
Подключение в React Native
import { createClient } from '@liveblocks/client';
import { createRoomContext } from '@liveblocks/react';
const client = createClient({
authEndpoint: '/api/liveblocks-auth', // JWT через ваш бэкенд
// или publicApiKey для прототипов
});
type Presence = {
cursor: { x: number; y: number } | null;
selectedItemId: string | null;
};
type Storage = {
items: LiveList<{ id: string; text: string; done: boolean }>;
};
export const { RoomProvider, useMyPresence, useStorage, useMutation } =
createRoomContext<Presence, Storage>(client);
RoomProvider оборачивает экран-редактор. Внутри него useStorage даёт иммутабельный снапшот CRDT-хранилища, useMutation — транзакционные мутации.
Проблема в RN: @liveblocks/client под капотом использует fetch и WebSocket — оба есть в React Native. Но EventSource (SSE, нужен для Liveblocks Notifications) отсутствует в RN без полифила. Пакет react-native-event-source или eventsource + глобальный полифил решают это:
// index.js, до всего остального
import EventSource from 'react-native-event-source';
global.EventSource = EventSource;
Presence в мобильном контексте
В браузере presence обычно — координаты мыши. В мобильном приложении это бессмысленно. Типичные кейсы:
-
Совместное редактирование списка задач: presence =
{ focusedItemId: string | null }— подсвечиваем элемент, который сейчас редактирует другой пользователь. -
Совместная доска: presence =
{ x, y, tool: 'pen' | 'eraser' }— показываем стилус другого участника. -
Документный редактор: presence =
{ selection: { anchor, focus } | null }— выделение в тексте.
useOthersConnectionIds + useOther дают реактивные данные о присутствующих. Для оптимизации: useOthersMapped позволяет выбрать только нужное поле presence и не перерендериваться при изменении несвязанных полей.
Офлайн-режим: чего у Liveblocks нет из коробки
Liveblocks не предоставляет офлайн-персистентность на клиенте. При отсутствии сети изменения теряются. Для мобильного приложения это критично.
Обходное решение — буфер мутаций в AsyncStorage:
const [pendingMutations, setPendingMutations] = useAtom(pendingMutationsAtom);
// При потере сети
NetInfo.addEventListener(state => {
if (!state.isConnected) {
// сохраняем текущий стейт локально
const snapshot = storage.toObject();
AsyncStorage.setItem('offline_snapshot', JSON.stringify(snapshot));
} else {
// при восстановлении — реплицируем pending
pendingMutations.forEach(mutation => mutation());
setPendingMutations([]);
}
});
Это не настоящий CRDT-офлайн — merge не гарантирован при одновременных изменениях на нескольких устройствах офлайн. Для полноценного офлайна на Liveblocks нужно комбинировать с Y.js через @liveblocks/yjs и кастомным локальным провайдером. Решение сложнее, но правильнее.
AppState и реконнект
iOS убивает WebSocket-соединение при переходе в фон. Liveblocks SDK обрабатывает реконнект автоматически, но room.getStatus() после восстановления проходит через цикл reconnecting → connected. Если ваш UI не реагирует на статус комнаты — пользователь будет видеть устаревший стейт.
const status = useStatus(); // 'initial' | 'connecting' | 'connected' | 'reconnecting'
if (status === 'reconnecting') {
return <ReconnectingBanner />;
}
Обрабатывайте reconnecting явно — особенно если приложение показывает совместный список, который мог измениться пока устройство было офлайн.
Тарификация и лимиты
Liveblocks — платный сервис с free-тиром (50 MAU, 1000 комнат). Для продакшена: Starter от $99/мес, Pro от $299/мес. Это managed-инфраструктура — вы не поднимаете WebSocket-серверы, но зависите от их SLA (99.9% на Pro).
Если требования включают self-hosted или data residency в конкретной юрисдикции — Liveblocks не подойдёт. В таком случае рассматривайте Y.js + Hocuspocus или PartyKit на собственной инфраструктуре.
Оценка
Интеграция Liveblocks в React Native (Storage + Presence, без офлайна): 2–4 недели. С офлайн-буфером и Y.js-интеграцией: 5–8 недель. Flutter — через @liveblocks/client в webview или custom Dart-клиент с WebSocket: 4–7 недель.







