Перейти к содержанию
HexaTransfer
Вернуться к блогу
Технические погружения

Кеширование файлов через Service Worker для офлайн-передачи

Используйте Service Workers для офлайн-передачи файлов. Стратегии кеширования, фоновая синхронизация и паттерны прогрессивных веб-приложений.

Service Worker позволяет приложению передачи файлов продолжать работу при отсутствии сети: кешируйте HTML-оболочку и JS через Cache API, ставьте в очередь неудавшиеся загрузки с помощью Background Sync API, храните частичные чанки в IndexedDB и воспроизводите всё при возвращении соединения. Воркер работает в отдельном потоке с собственным циклом событий, перехватывает события fetch в своей области видимости и сохраняется после закрытия вкладки. Для инструментов загрузки правильный рецепт: стратегия stale-while-revalidate для оболочки приложения, очередь чанков на основе IndexedDB для текущих передач, и периодическая фоновая синхронизация с повтором каждые 15 минут до успеха.

Что Service Worker реально даёт приложениям передачи

Главный выигрыш в том, что navigator.serviceWorker переживает перезагрузки вкладок, офлайн-периоды и даже сон телефона. Когда пользователь начинает загрузку 2 ГБ через нестабильный Wi-Fi в поезде, нужно: чтобы уже отправленные чанки оставались отправленными, незавершённые повторялись при восстановлении соединения, и всё состояние было восстановимо, если браузер убьёт вкладку для освобождения памяти. Service Worker, работающий независимо от любой конкретной вкладки, — это компонент, обеспечивающий всё вышеперечисленное.

API предоставляет три строительных блока: Cache для хранения ответов по URL, IndexedDB для структурированных данных (очереди чанков, состояние сессии) и SyncManager для планирования повторов, срабатывающих при наличии соединения.

Регистрация и версионирование воркера

Регистрируйте один раз при загрузке приложения и явно обрабатывайте обновления:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then((reg) => reg.addEventListener('updatefound', () => {
      const sw = reg.installing;
      sw.addEventListener('statechange', () => {
        if (sw.state === 'installed' && navigator.serviceWorker.controller) {
          // новая версия готова, предложите пользователю обновить страницу
        }
      });
    }));
}

Версионируйте ключи кеша (transfer-v7), чтобы новый деплой инвалидировал старые активы без устаревшего JavaScript. Классический баг: index.html кешируется навсегда, пользователи никогда не получают обновления, и вы неделями отлаживаете проблемы в Telegram. Привязывайте кеши к хешам сборки и очищайте в событии activate.

Стратегии кеширования: оболочка приложения против пользовательских данных

Разные ресурсы заслуживают разных стратегий:

  • Оболочка приложения (HTML, CSS, JS, иконки): сначала кеш, затем сеть. Мгновенная загрузка, работает офлайн.
  • Метаданные API (/shares/:id): сначала сеть с кешем как запасным вариантом, TTL 60 секунд. Свежие при онлайне, использовать при офлайне.
  • Байты файлов: никогда не кешировать. Файлы часто много гигабайт, а Cache API имеет квоты по origin (обычно 60% свободного диска).
  • Шрифты из CDN: stale-while-revalidate. Быстро и с авто-обновлением.

В обработчике fetch:

self.addEventListener('fetch', (e) => {
  const url = new URL(e.request.url);
  if (url.pathname.startsWith('/assets/')) {
    e.respondWith(cacheFirst(e.request, 'shell-v7'));
  } else if (url.pathname.startsWith('/api/shares/')) {
    e.respondWith(networkFirst(e.request, 'api-v1', 60));
  }
});

Никогда не перехватывайте запросы двоичных загрузок файлов — обходите их, проверяя e.request.method === 'PUT' и возвращая раньше. Проксирование гигабайтных PUT через воркер — катастрофа для памяти.

Очередь неудавшихся загрузок с Background Sync

SyncManager — ключ к устойчивым загрузкам. Когда PUT чанка не удаётся, сохраните его в IndexedDB и зарегистрируйте синхронизацию:

// в коде страницы
const reg = await navigator.serviceWorker.ready;
await reg.sync.register('flush-uploads');
// в sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'flush-uploads') {
    event.waitUntil(flushPendingUploads());
  }
});

Браузер запускает событие sync при возвращении сети, с экспоненциальным откатом до ~24 часов. Chrome и Edge поддерживают это; Safari выпустил подмножество в 17.5 под флагом «Background Fetch» с требованием разрешения. Для Safari — откат к повтору при следующем открытии видимой страницы через visibilitychange.

Background Fetch API — отдельный инструмент специально для крупных файловых операций: показывает постоянное уведомление в интерфейсе браузера, чтобы пользователи могли отслеживать прогресс даже после закрытия вкладки. Стоит использовать для загрузок свыше 500 МБ.

Хранение частичных загрузок в IndexedDB

IndexedDB — ваше надёжное рабочее пространство. Откройте небольшую базу данных один раз, затем сохраняйте метаданные сессии и смещения чанков:

const db = await openDB('transfers', 1, {
  upgrade(db) {
    db.createObjectStore('sessions', { keyPath: 'id' });
    db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
  }
});
await db.put('sessions', {
  id, fileName, fileSize, fileFingerprint, createdAt: Date.now(),
  completedIndexes: [], partUrls
});

Не храните сырые байты чанков — они приходят из дескриптора File, который IndexedDB может сохранить как структурированную клонированную ссылку, остающуюся валидной между перезагрузками. Хранение дескриптора избегает дублирования 2 ГБ байт в базе данных.

У хранилища origin есть лимиты: примерно 60% свободного диска на desktop Chrome, 1 ГБ на origin в iOS Safari до срабатывания давления вытеснения. Запросите navigator.storage.persist() для получения «постоянного» бакета, который браузеры избегают вытеснять автоматически.

Обработка переходов офлайн/онлайн

Слушайте события online и offline как на странице, так и в service worker:

// страница
window.addEventListener('online', () => {
  ui.showBanner('Соединение восстановлено — загрузки возобновляются');
  navigator.serviceWorker.controller?.postMessage({ type: 'resume' });
});
window.addEventListener('offline', () => {
  ui.showBanner('Офлайн — загрузки приостановлены');
});

navigator.onLine ненадёжен на корпоративных captive portal — сообщает true при наличии локального сетевого соединения без интернета. Для достоверного обнаружения выполняйте небольшой fetch('/ping', { cache: 'no-store' }) с таймаутом 3 секунды.

Превращение в полноценный PWA

Поставляйте manifest.json с display: standalone, набором иконок и start_url: /. Добавьте ссылки apple-touch-icon для iOS. Объявляйте обработчики файлов, чтобы ОС могла связать приложение с конкретными расширениями:

{
  "name": "Hex Transfer",
  "file_handlers": [{
    "action": "/share-target",
    "accept": { "application/*": [".pdf", ".zip", ".docx"] }
  }]
}

В сочетании с Web Share Target это позволяет пользователям делиться файлами прямо из системного диалога «Поделиться» в приложение. На Chrome Android и desktop Chromium PWA может зарегистрироваться как обработчик по умолчанию для объявленных типов файлов — превращая браузерную страницу в приложение, ведущее себя как нативный инструмент загрузки.

Тестирование офлайн-сценариев

Три сценария для ручного тестирования, поскольку автоматизированные офлайн-тесты ненадёжны:

  1. Начните загрузку 500 МБ на быстром Wi-Fi, переключитесь в режим полёта на 30%, подождите 30 секунд, включите Wi-Fi обратно. Загрузка должна возобновиться с точки остановки без действий пользователя.
  2. Начните загрузку, закройте вкладку на 60%, подождите 2 минуты, откройте снова. Предложите возобновить сессию.
  3. Начните загрузку на мобильном, заблокируйте экран на 5 минут. Фоновая синхронизация должна сработать при разблокировке и завершить передачу.

Флажок «Offline» в Chrome DevTools и Application > Service Workers > Update on reload незаменимы. Профили «Throttling» на панели Network позволяют симулировать Fast 3G и Slow 3G, чтобы увидеть поведение UI при ошибках.

Веб-приложение HexaTransfer использует Service Worker для кеширования оболочки и IndexedDB для состояния текущих сессий, так что перезагрузки и краткие офлайн-периоды не теряют прогресс загрузки. Попробуйте на hexatransfer.com — бесплатно, без регистрации, до 10 ГБ.

Подводные камни, о которых стоит знать

Service Workers имеют небольшой набор ловушек, подстерегающих новичков: работают только по HTTPS (кроме localhost), квоты кеша сильно варьируются между браузерами, iOS Safari ненадёжно пробуждает воркеры для Background Sync, DevTools могут агрессивно кешировать устаревшие воркеры (во время разработки всегда нажимайте «Bypass for network»), а importScripts выполняется синхронно при установке, поэтому никогда не загружайте там медленные сторонние скрипты. Напишите небольшой интеграционный тест, проверяющий активацию воркера, захват клиентов и обслуживание офлайн-страницы — этот один тест ловит 80% регрессий в продакшене.

Безопасная отправка больших файлов со сквозным шифрованием

Передавайте файлы до 10 ГБ бесплатно со сквозным шифрованием. Регистрация не требуется. Ваши файлы шифруются в браузере перед загрузкой — никто другой не может их прочитать.

Отправить файл