Передача контекста между плагинами

Workbox предоставляет мощный набор инструментов для организации сервис-воркеров, кэширования ресурсов и управления сетевыми запросами. Один из ключевых аспектов расширяемости Workbox — возможность использования плагинов. Плагины позволяют внедрять пользовательскую логику на разных этапах обработки запросов и ответов, таких как fetchDidSucceed, cacheDidUpdate, requestWillFetch и другие. Для эффективного использования плагинов важно понимать, как можно передавать контекст между ними, сохраняя состояние и данные.


Объект context и его роль

Каждый плагин в Workbox может получать объект контекста, который предоставляет доступ к данным, общим для всего жизненного цикла запроса. Контекст позволяет:

  • Хранить промежуточные данные между разными плагинами;
  • Передавать результаты одного плагина следующему;
  • Реализовывать более сложные цепочки логики без глобальных переменных.

Пример структуры объекта контекста:

{
  request: Request,
  response: Response | undefined,
  cacheKey: string,
  timestamp: number,
  metadata: {}
}

Поле metadata обычно используется для передачи произвольных данных между плагинами. Это ключевое место, где хранится состояние конкретного запроса или кэширования.


Создание плагина с передачей контекста

Чтобы плагин мог работать с контекстом, необходимо реализовать соответствующие методы и использовать объект context:

const loggingPlugin = {
  cacheDidUpdate: async ({cacheName, request, oldResponse, newResponse, metadata}) => {
    if (!metadata) metadata = {};
    metadata.cacheUpdatedAt = Date.now();
    console.log(`Cache "${cacheName}" обновлен для URL: ${request.url}`);
  },

  fetchDidSucceed: async ({request, response, metadata}) => {
    metadata.fetchTimestamp = Date.now();
    return response;
  }
};

Здесь metadata используется для сохранения временных меток и передачи информации между этапами обработки. Если другой плагин обрабатывает тот же объект metadata, он сможет получить эти данные:

const analyticsPlugin = {
  cacheDidUpdate: async ({metadata}) => {
    if (metadata && metadata.cacheUpdatedAt) {
      console.log(`Отправка метрик: кэш обновлен в ${metadata.cacheUpdatedAt}`);
    }
  }
};

Механизм передачи контекста

Workbox гарантирует, что объект context создается для каждого запроса и передается всем плагинам, зарегистрированным для соответствующего события. Основные принципы передачи:

  1. Единый объект на запрос: Каждый запрос получает отдельный объект context. Данные одного запроса не смешиваются с данными другого.
  2. Изменения доступны последующим плагинам: Если первый плагин добавляет или изменяет поле metadata, следующий плагин в цепочке увидит эти изменения.
  3. Необходимость осторожности с асинхронностью: Если плагин выполняет асинхронные операции, важно корректно возвращать результаты, чтобы контекст был готов для следующего плагина.

Пример комплексной цепочки плагинов

const timingPlugin = {
  requestWillFetch: async ({request, metadata}) => {
    metadata.startTime = performance.now();
    return request;
  },
  fetchDidSucceed: async ({response, metadata}) => {
    metadata.endTime = performance.now();
    metadata.duration = metadata.endTime - metadata.startTime;
    return response;
  }
};

const metricsPlugin = {
  cacheDidUpdate: async ({metadata}) => {
    if (metadata && metadata.duration !== undefined) {
      console.log(`Время обработки запроса: ${metadata.duration.toFixed(2)} мс`);
    }
  }
};

workbox.routing.registerRoute(
  ({url}) => url.origin === self.location.origin,
  new workbox.strategies.NetworkFirst({
    cacheName: 'dynamic-cache',
    plugins: [timingPlugin, metricsPlugin]
  })
);

В этом примере timingPlugin фиксирует время запроса и передачи данных, а metricsPlugin использует эти данные для логирования метрик. Объект metadata является единым каналом передачи данных между плагинами.


Передача контекста через пользовательские поля

Помимо стандартного поля metadata, можно создавать свои структурированные объекты внутри context для специфических нужд:

const authPlugin = {
  requestWillFetch: async ({request, metadata}) => {
    if (!metadata.auth) metadata.auth = {};
    metadata.auth.token = await getAuthToken();
    return new Request(request, {
      headers: { ...request.headers, 'Authorization': `Bearer ${metadata.auth.token}` }
    });
  }
};

const auditPlugin = {
  fetchDidSucceed: async ({response, metadata}) => {
    if (metadata.auth?.token) {
      console.log('Запрос выполнен с токеном:', metadata.auth.token.slice(0, 8));
    }
    return response;
  }
};

Такой подход обеспечивает изоляцию логики плагинов, предотвращает конфликты имен и позволяет безопасно обмениваться данными.


Рекомендации по использованию контекста

  • Минимизировать размер данных: Контекст передается в каждом событии запроса, поэтому хранение больших объектов может негативно влиять на производительность.
  • Использовать структуры для логической сегрегации: Разделение данных на metadata.auth, metadata.timing, metadata.analytics делает код читаемым и поддерживаемым.
  • Не изменять оригинальные объекты запроса и ответа без необходимости: Контекст служит для хранения промежуточных данных, а не для изменения основного запроса или ответа напрямую.
  • Обрабатывать асинхронность корректно: Всегда возвращать промисы из методов плагина, чтобы Workbox мог корректно дождаться завершения всех операций.

Заключение по передаче контекста

Передача контекста между плагинами Workbox — мощный инструмент для построения расширяемой и гибкой логики обработки запросов и кэширования. Использование поля metadata и структурированных объектов в контексте позволяет организовать комплексные цепочки плагинов, отслеживать состояние, собирать метрики и делиться данными между разными этапами жизненного цикла запроса без глобальных переменных.