Considere este archivo de muestra index.html.

<!DOCTYPE html>
<html><head><title>test page</title>
<script>navigator.serviceWorker.register('sw.js');</script>
</head>
<body>
<p>test page</p>
</body>
</html>

Usando este Service Worker, diseñado para cargar desde el caché, luego recurrir a la red si es necesario.

cacheFirst = (request) => {
    var mycache;
    return caches.open('mycache')
        .then(cache => {
            mycache = cache;
            cache.match(request);
        })
        .then(match => match || fetch(request, {credentials: 'include'}))
        .then(response => {
            mycache.put(request, response.clone());
            return response;
        })
}

addEventListener('fetch', event => event.respondWith(cacheFirst(event.request)));

Esto falla mucho en Chrome 62. La actualización del HTML no se carga en el navegador, con el error "No se puede acceder a este sitio"; Tengo que cambiar la actualización para salir de este estado roto. En la consola, dice:

Tipo de error no capturado (en promesa): no se pudo ejecutar 'fetch' en 'ServiceWorkerGlobalScope': no se puede construir una solicitud con una solicitud cuyo modo sea 'navegar' y una RequestInit no vacía.

"construir una solicitud" ?! No estoy construyendo una solicitud. Estoy usando la solicitud del evento, sin modificaciones. ¿Qué estoy haciendo mal aquí?

7
Dan Fabulich 27 oct. 2017 a las 22:56

3 respuestas

La mejor respuesta

Según investigaciones posteriores, resulta que estoy construyendo una solicitud cuando fetch(request, {credentials: 'include'}).

Cada vez que pasa un objeto de opciones a fetch, ese objeto es RequestInit, y crea un nuevo objeto Request cuando lo hace. Y, aparentemente, no puede pedirle a fetch() que cree un nuevo Request en modo navigate y un RequestInit no vacío por alguna razón.

En mi caso, la navegación del evento Request ya permitió las credenciales, por lo que la solución es convertir fetch(request, {credentials: 'include'}) en fetch(request).

Me engañaron pensando que necesitaba {credentials: 'include'} debido a este Google artículo de documentación.

Cuando usa fetch, por defecto, las solicitudes no contendrán credenciales como cookies. Si desea credenciales, llame a:

fetch(url, {
  credentials: 'include'
})

Eso solo es cierto si pasa a buscar una URL, como lo hacen en el ejemplo de código. Si tiene un objeto Request a mano, como lo hacemos normalmente en un trabajador de servicio, el Request sabe si quiere usar credenciales o no, por lo que fetch(request) usará credenciales normalmente.

13
Dan Fabulich 27 oct. 2017 a las 23:30

Problema

Me encontré con este problema al intentar anular fetch para todo tipo de activos diferentes. El modo navigate se configuró para el Request inicial que obtiene el archivo index.html (u otro html); y quería que se le aplicaran las mismas reglas de almacenamiento en caché que a otros activos estáticos.

Estas son las dos cosas que quería lograr:

  1. Al recuperar activos estáticos, a veces quiero poder anular url, lo que significa que quiero algo como: fetch(new Request(newUrl))
  2. Al mismo tiempo, quiero que se vayan a buscar tal como pretendía el remitente; lo que significa que quiero establecer el segundo argumento de fetch (es decir, el objeto RequestInit mencionado en el mensaje de error) en el originalRequest en sí, así: fetch(new Request(newUrl), originalRequest)

Sin embargo, la segunda parte no es posible para solicitudes en modo navigate (es decir, el archivo inicial html); al mismo tiempo, no es necesario, como explicaron otros, ya que mantendrá sus cookies, credenciales, etc.

Solución

Aquí está mi solución: un fetch versátil que ...

  1. puede anular la URL
  2. puede anular el objeto de configuración RequestInit
  3. funciona tanto con navigate como con cualquier otra solicitud
function fetchOverride(originalRequest, newUrl) {
  const fetchArgs = [new Request(newUrl)];
  if (request.mode !== 'navigate') {
    // customize the request only if NOT in navigate mode
    //    (since in "navigate" that is not allowed)
    fetchArgs.push(request);
  }

  return fetch(...fetchArgs);
}

0
Domi 12 dic. 2019 a las 08:54

https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker

var networkDataReceived = false;
// fetch fresh data
var networkUpdate = fetch('/data.json').then(function(response) {
  return response.json();
}).then(function(data) {
  networkDataReceived = true;
  updatePage(data);
});

// fetch cached data
caches.match('mycache').then(function(response) {
  if (!response) throw Error("No data");
  return response.json();
}).then(function(data) {
  // don't overwrite newer network data
  if (!networkDataReceived) {
    updatePage(data);
  }
}).catch(function() {
  // we didn't get cached data, the network is our last hope:
  return networkUpdate;
}).catch(showErrorMessage).then(console.log('error');

El mejor ejemplo de lo que está intentando hacer, aunque debe actualizar su código en consecuencia. El ejemplo web se toma de Caché y luego de red.

for the service worker:
self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mycache').then(function(cache) {
      return fetch(event.request).then(function(response) {
        cache.put(event.request, response.clone());
        return response;
      });
    })
  );
});
2
Hunter 27 oct. 2017 a las 20:23