Перейти к содержанию

Несколько вкладок и несколько устройств

Пользователь может открыть «Блокнот» в двух вкладках, а затем продолжить на другом устройстве. Эти ситуации похожи внешне, но имеют разные границы хранения. Вкладки одного профиля работают с общей IndexedDB, независимые профили согласуются только через сервер. Результат урока — Web Locks для одного активного отправителя и localVersion для защиты формы, которая отстала от общей базы.

API блокировки и уведомлений сверены с Web Locks MDN и BroadcastChannel MDN. Автор не запускал вкладки и не выполнял опыт на физических устройствах. Полный конечный код и условия совместимости находятся в архиве; expected состояния ниже не являются отчётом о наблюдении.

Общая база не равна общему экрану

В двух вкладках Alice имя базы одинаковое, поэтому readwrite транзакции обращаются к одному браузерному состоянию. Однако DOM формы у каждой вкладки свой. Одна может показывать localVersion три, пока другая уже сохранила четвёртую. Даже корректная IndexedDB транзакция не предотвратит прикладную потерю текста, если первая без проверки запишет свой устаревший экран.

Для этого select сохраняет version выбранной заметки. При submit expectedVersion передаётся в saveNote, которая вновь читает текущую note внутри readwrite транзакции. Если значение отличается, tx.abort отменяет запись. Интерфейс предлагает перечитать заметку и оставляет несохранённый текст формы на экране, чтобы пользователь мог скопировать полезную часть перед обновлением.

Это оптимистическая защита локальной редакции. Она не требует держать блокировку всё время набора текста и позволяет спокойно читать заметку в нескольких вкладках. Цена — возможный отказ при сохранении устаревшей формы. Такой отказ лучше скрытого перезаписывания новой работы; программа объясняет конкретную причину и предоставляет способ продолжить.

Один отправитель удерживает Web Lock

Локальный busy защищает только текущую вкладку. Чтобы две вкладки не начали одинаковые сетевые проходы, sync.run запрашивает именованную Web Lock bloknot-sync:alice. Account входит в имя: отправка Alice не должна блокировать независимую сессию Bob. Область Web Locks ограничена origin и браузерным окружением, поэтому другая физическая копия не участвует в этой блокировке.

Ниже показан существенный вызов из sync.js. Pass выполняет ранее описанную ограниченную отправку и получение страницы.

await navigator.locks.request(
  `bloknot-sync:${account}`,
  { ifAvailable: true },
  async lock => {
    if (!lock || stopped) return;
    again = await pass();
  }
);

IfAvailable означает, что занятая блокировка не ставит каждый timer в длинную очередь. Callback получает отсутствие lock и завершает попытку; следующий обычный проход сможет попробовать снова. При освобождении ресурса не накопится множество одинаковых операций от давно открытых вкладок. Блокировка автоматически удерживается до завершения асинхронного callback.

Она не превращает сеть и IndexedDB в единую транзакцию. Между запросами notes может меняться, поэтому acknowledge всё равно проверяет opId и localVersion. Если держатель закрывается после принятого сервером push, ответ может потеряться. Другая вкладка повторит сохранённую операцию, а ledger вернёт прежнее подтверждение. Таким образом, блокировка уменьшает конкуренцию, а серверный контракт сохраняет корректность результата.

Уведомление предлагает перечитать базу

После локального изменения app.js отправляет строку changed через BroadcastChannel bloknot:alice. Получившая вкладка вызывает refresh и читает notes/outbox заново. В сообщении нет всего body: истина остаётся в IndexedDB, а канал только ускоряет обновление списка. Потеря уведомления не уничтожает запись и не меняет серверный контракт.

Такой порядок особенно полезен для подписи ожидания и конфликтного блока. Другой экран узнаёт, что локальная заметка изменилась, но сам не применяет произвольный присланный объект поверх базы. Если сообщение приходит до окончания какой-то независимой операции, следующее чтение всё равно отражает завершённое состояние транзакций. Не нужно изобретать второй протокол согласования заметок внутри канала.

Открытые поля формы намеренно не обновляются скрытно при каждом refresh. Иначе текст, который человек ещё не сохранил, мог бы исчезнуть прямо во время ввода. Список и conflict блок могут измениться, а форма остаётся рабочей редакцией до select. При попытке сохранить устаревшую localVersion будет понятный отказ. Такой компромисс сохраняет введённую работу, хотя учебный интерфейс не реализует сложный diff локальных экранов.

BroadcastChannel также имеет собственные границы поддержки и области связи. Он не пересылает события на телефон или в другой браузерный профиль через интернет. Для таких участников остаются push/pull, серверная revision и ручной conflict. Использовать одинаковое имя канала на двух ноутбуках недостаточно: никакого сетевого посредника API не создаёт.

Независимые устройства имеют разные очереди

В лаборатории два профиля на одном компьютере позволяют моделировать независимые базы без публикации сервера. Оба открывают loopback адрес и учебный аккаунт Alice, но получают отдельные notes/outbox/meta. Web Locks первого профиля не запрещает отправку второго. Сервер должен корректно обработать их конкуренцию через проверку baseRevision.

Если первый профиль получил revision два, а второй офлайн сохранил правку на основе единицы, восстановление запросов второго даст conflict. Это не ошибка блокировки: участники находились в разных областях и не должны делить браузерный ресурс. Серверный контракт специально рассчитан на такую независимость. Текст local остаётся доступным для выбора.

Настоящий телефон не откроет localhost ноутбука как тот же сервер. Для опыта на разных компьютерах нужен доступный HTTPS endpoint, корректные учётные записи и новая конфигурация origin. Конечный архив это окружение не разворачивает. Поэтому курс не обещает «готовую синхронизацию устройств» лишь по факту включённых JavaScript файлов; он даёт полный контракт и локальную модель для будущего самостоятельного прохода.

Отсутствие API и закрытие вкладки

Если navigator.locks отсутствует, конечный пример отключает серверные запросы и сообщает причину. Локальное редактирование и экспорт остаются доступны. Мы не заменяем Web Locks случайным флагом localStorage: у такого флага нужны срок владения, атомарное получение и восстановление после падения. Простая запись true не предоставляет тех же свойств и может навсегда заблокировать отправку.

Для широкой совместимости можно разработать IndexedDB lease с владельцем и сроком, но это отдельный алгоритм и отдельная проверка. В данной лаборатории предсказуемый отказ ограничивает обещание. Читатель понимает, почему очередь не отправляется, и может сохранить данные внешним экспортом. Не следует изображать отсутствие поддержки как успешную синхронизацию с пустой сетью.

Закрытие вкладки освобождает Web Lock автоматически, а обработчик beforeunload закрывает соединение и канал. Последующие вкладки читают сохранённые операции. Даже если cleanup обработчик не выполняется, браузерный жизненный цикл завершит владение lock; поэтому безопасность не зависит от добровольного сброса флага в конце страницы.

После этого урока можно отдельно объяснить общую локальную базу и независимое серверное согласование. LocalVersion защищает форму, Web Locks координирует сетевой проход, BroadcastChannel ускоряет чтение, revision защищает изменения разных устройств. Следующая глава рассмотрит другой риск локальной работы: предел хранения и возможность вернуть доступные данные из внешнего экспорта.

Оглавление серии