Реализация начала задачи на одном устройстве и продолжения на другом
Пользователь начал заполнять форму на телефоне, открыл ноутбук — форма там с теми же данными, на том же шаге. Это Handoff, и у Apple он есть из коробки через NSUserActivity. Для кросс-платформенных и кросс-экосистемных сценариев строим свой.
Apple Handoff: когда не нужно ничего делать
NSUserActivity — механизм передачи контекста задачи между Apple-устройствами через iCloud. Поддерживает iOS → macOS, macOS → iOS, iOS → iPadOS.
В React Native через нативный модуль:
// iOS Native Module
let activity = NSUserActivity(activityType: "com.yourapp.editDocument")
activity.title = "Редактирование документа"
activity.userInfo = ["documentId": documentId, "scrollPosition": scrollY, "formData": formData]
activity.isEligibleForHandoff = true
activity.becomeCurrent()
На принимающей стороне macOS/iOS AppDelegate получает application(_:continue:restorationHandler:) с этим NSUserActivity. Данные userInfo — словарь, ограничен ~256 КБ.
Ограничения Handoff: только Apple-экосистема, только одна аутентифицированная учётная запись, Bluetooth + Wi-Fi обязательны. Для клиента, у которого Android-телефон и Windows-ноутбук — не работает.
Кросс-платформенный Handoff: серверная сессия
Универсальный подход: сессия задачи хранится на сервере, привязана к аккаунту, а не к устройству.
type TaskSession = {
sessionId: string;
userId: string;
taskType: 'checkout' | 'editDocument' | 'formFill' | 'mediaUpload';
state: Record<string, unknown>; // сериализованный стейт задачи
activeDeviceId: string;
updatedAt: number;
expiresAt: number;
};
Ключевые моменты:
Granular state: не сохраняем весь Redux store. Сохраняем только то, что нужно для восстановления задачи. Для формы — { step: 2, fields: { name: 'Иван', email: 'ivan@...' }, validationState: {...} }. Sensitive данные (CVV, PIN) не сохраняем никогда.
Conflict resolution: пользователь работает на двух устройствах одновременно. Последняя запись побеждает (LWW) — достаточно для большинства сценариев. Для сложных задач — vector clocks.
Обнаружение активных сессий: при открытии приложения проверяем незавершённые сессии и предлагаем продолжить.
Автосохранение стейта задачи
const useTaskHandoff = (taskType: string) => {
const [sessionId] = useState(() => generateSessionId());
const debouncedSave = useMemo(
() => debounce(async (state: Record<string, unknown>) => {
await api.handoff.saveSession({
sessionId,
taskType,
state,
deviceId: getDeviceId(),
});
}, 1000),
[sessionId, taskType]
);
// Вызываем при каждом изменении стейта формы/задачи
useEffect(() => {
debouncedSave(currentTaskState);
return () => debouncedSave.cancel();
}, [currentTaskState]);
// Восстановление при старте
const restoreSession = useCallback(async (incomingSessionId: string) => {
const session = await api.handoff.getSession(incomingSessionId);
if (session) restoreTaskState(session.state);
}, []);
return { restoreSession };
};
Debounce на 1 секунду — не сохраняем при каждом нажатии клавиши. На активном вводе это 60+ запросов в минуту, ни серверу, ни батарее не нужно.
Deep link для открытия задачи на другом устройстве
Как второе устройство узнаёт о сессии? Варианты:
-
Push-уведомление: при переходе устройства в фон отправляем silent push с
sessionId. Второе устройство показывает banner «Продолжить на этом устройстве?». -
Deep link: пользователь явно копирует ссылку
yourapp://handoff/session/abc123и открывает на другом устройстве. -
QR-код: показываем QR с
sessionId, сканируем вторым устройством. - Автоматически: при открытии приложения на втором устройстве — запрашиваем активные сессии аккаунта и предлагаем список незавершённых задач.
Вариант 4 — лучший UX, но требует чёткой политики privacy: пользователь должен видеть, что его незавершённые задачи хранятся на сервере, и иметь возможность их удалить.
Типичные задачи под Handoff
- Многостраничные формы (checkout, анкеты, заявки)
- Редактирование документов/постов с промежуточным стейтом
- Загрузка медиафайлов (начали на телефоне, продолжаем на планшете с большим экраном)
- Игровые сессии с сохранением прогресса между устройствами
Не стоит реализовывать Handoff для: одноэкранных задач, задач без чёткого «шага завершения», задач с чувствительными данными без E2E-шифрования.
Оценка
Серверная сессия задач с deep link, автосохранением и UI для восстановления: 3–5 недель. С Apple NSUserActivity + кросс-платформенным fallback: 4–6 недель.







