Реализация Cross-Device Copy-Paste через мобильное приложение
Скопировать текст на телефоне — вставить на ноутбуке. Звучит просто, но реализация натыкается на ограничения буфера обмена на обеих платформах и требует серверного компонента. Нативный универсальный буфер обмена (Apple Universal Clipboard) работает только в экосистеме Apple и только при включённом Handoff. Для кросс-платформенных сценариев (iOS → Android, мобильный → десктоп) нужен другой подход.
Apple Universal Clipboard: когда работает само
Если оба устройства — Apple, вошли под одним Apple ID, и включён Handoff — UIPasteboard.general на iOS автоматически синхронизируется через iCloud. Ничего реализовывать не нужно. Но это только для текста и изображений, с задержкой до 30–60 секунд, и работает только в пределах Apple-экосистемы.
Для приложения, которое должно работать независимо от платформы — строим свой механизм.
Архитектура кросс-устройственного буфера
Минимальная схема: сервер хранит буфер обмена пользователя, клиенты синхронизируются через WebSocket или polling.
// Серверная часть: простое хранилище с TTL
type ClipboardEntry = {
userId: string;
content: string;
contentType: 'text' | 'image' | 'file';
mimeType?: string;
expiresAt: number; // unix timestamp
deviceId: string; // откуда скопировали
};
TTL — обязателен. Буфер обмена не должен хранить данные вечно: чувствительная информация (пароли, токены, карточные данные), которую пользователь скопировал, должна удаляться. 30–120 минут — разумный TTL для большинства сценариев.
Нативный буфер обмена в React Native
import Clipboard from '@react-native-clipboard/clipboard';
// Копирование с синхронизацией на сервер
const copyToCloudClipboard = async (text: string) => {
// Сначала в локальный буфер — мгновенно
await Clipboard.setString(text);
// Параллельно отправляем на сервер
await api.clipboard.push({
content: text,
contentType: 'text',
deviceId: getDeviceId(),
});
};
// Вставка: сначала проверяем облачный буфер
const pasteFromCloudClipboard = async (): Promise<string> => {
const [localContent, cloudEntry] = await Promise.all([
Clipboard.getString(),
api.clipboard.getLatest(),
]);
// Выбираем более свежий
if (cloudEntry && cloudEntry.updatedAt > localTimestamp) {
return cloudEntry.content;
}
return localContent;
};
Контент не только текст
Изображения: загружаем в S3/CDN, в буфере храним только URL + метаданные. Размер изображения в буфере — до 10 МБ, ограничиваем на клиенте. Файлы: аналогично, URL для скачивания.
На iOS UIPasteboard поддерживает типы через UTType. При копировании изображения в нативное приложение нужен нативный модуль, который читает UIPasteboard.general.image и кладёт его в облачный буфер. @react-native-clipboard/clipboard в базовой комплектации не поддерживает изображения — патчим или пишем нативный модуль.
Уведомление о новом контенте в буфере
Push-уведомление при появлении нового контента в буфере — плохой UX: пользователь сам только что что-то скопировал, зачем ему пуш? Лучше: индикатор в интерфейсе при открытии приложения или тихая синхронизация через background fetch.
WebSocket-подход: при открытии приложения подписываемся на канал пользователя. Когда с другого устройства приходит новый буфер — показываем non-blocking banner «Скопировано с MacBook: "текст..."».
Безопасность: что не должно попасть в облачный буфер
Детект чувствительного контента перед отправкой на сервер:
- Regex на номера карт (Luhn-валидация): не отправлять, очистить через 30 сек.
- Regex на пароли в формате
password: xyz— не отправлять. - Очень длинные строки (>100 KB) — вероятно, не то, что нужно в буфере.
Шифрование: контент буфера шифруем ключом, производным от пользовательского пароля или device key. Сервер хранит зашифрованный blob — не может читать содержимое.
Оценка
Кросс-устройственный буфер обмена (текст + изображения) с E2E-шифрованием, WebSocket-уведомлениями и TTL: 3–5 недель.







