¿Sabes qué es un «Service Worker»?

Actualmente estamos dejando de instalar aplicaciones en nuestros dispositivos perdiendo así muchas de las funcionalidades que una App puede darnos, por ese motivo, se han creado los «Service Workers». En esta entrada de blog hablaremos de lo que es un «Service Worker», con que finalidad podemos usarlos, de su ciclo de vida y de un pequeño ejemplo en javascript plano que nos ayudará a entenderlo todo de forma práctica.

¿Qué es un «Service Worker»?

Un «Service Worker» como su nombre lo indica es un servicio o proceso que se instala en el navegador Web, que se ejecuta en segundo plano y que nos permitirá ejecutar código Javascript.

Sus características principales:

  • está guiado por eventos fetch que se producen en un dominio y una ruta
  • sigue un thread diferente al de la página web
  • gracias a que funcionan en modo offline nos permiten realizar acciones cuando nos encontramos en este estado y devolver elementos estáticos de cache cuando la red no está disponible, por poner un ejemplo.
  • nos permite interceptar los eventos de red y realizar los ajustes que deseemos
  • funciona sólo sobre https por motivos de seguridad, salvo para el caso de localhost en donde podremos experimentar con nuestros propios»Service Worker»en periodo de desarrollo

Ciclo de vida del «Service Worker»

Lo primero que hay que decir es que el ciclo de vida de un»Service Worker»(una vez instalado) es totalmente independiente al de la página Web.

Dicho esto, si nos fijamos en el ciclo de vida de un «Service Worker» veremos que tiene 3 puntos:

  1. Descarga: la descarga se produce siempre que se accede a un sitio Web con un»Service Worker»detrás. Esta descarga se realizará como mínimo cada 24h y no producirá una instalación hasta que el fichero haya cambiado.
  2. Instalación: una vez realizada la descarga se procede a la instalación del mismo. La instalación se producirá en la primera descarga o siempre que el fichero del «Service Worker» haya cambiado.
  3. Activación: la primera vez que se instala un «Service Worker», se realiza la activación del mismo de forma automática produciendo el evento de «activate». Añadir que está activación será válida para la todas aquellas páginas que cuelguen del dominio o subdominio de la ruta en la que se ha registrado el «Service Worker», salvo para la propia ruta de registro hasta que esta no se vuelva a cargar. En las actualizaciones del service worker, esta fase del ciclo de vida no se alcanzará hasta que las páginas cargadas con el viejo «Service Worker» sean cerradas. De esta forma evitaremos que unas páginas funcionen con un «Service Worker» diferente del de otras. Añadir que esto se puede alterar como veremos más adelante.

Eventos del «Service Worker»

Para que se produzca el cambio de estado en el ciclo de vida del «Service Worker»  hace falta que se produzcan los siguiente eventos. En cada uno de los eventos, nosotros como programadores, podremos realizar aquellas acciones que consideremos necesarias.

  1. Evento «install»: este evento se produce tras el registro del mismo.
  2. Evento «activate»: una vez la instalación del «Service Worker» se ha producido y se procede a su activación.
  3. Evento «fetch»: cada vez que la web realiza una petición al servidor
  4. Evento «push»: se produce cuando el servidor realiza el envío de este evento para que el «Service Worker» tome acción

Una imagen del ciclo de vida y eventos del «Service Worker»

Diagram ciclo de vida

Ejemplo práctico de lo descrito hasta ahora

En este ejemplo vamos a crear una Web que cargue una imagen inicial (cuando el «Service Worker» no está activado) u otra («Service Worker» activado) desde cache. Además crearemos una segunda versión del «Service Worker» en donde veremos el ciclo de actualización y una tercera versión en donde nos saltaremos las reglas del proceso de actualización.

Cosas que vamos a necesitar:

  • Tener node y npm instalados
  • Disponer de un navegador que soporte los «Service Worker»  como puede ser Chrome o Firefox

Creación de un servidor de ficheros estáticos

Ejecutamos el siguiente comando para instalar los paquetes connect y serve-static

npm install connect serve-static

y nos creamos nuestro propio servidor de la siguiente forma en un fichero server.js

var connect = require("connect");
var serveStatic = require("serve-static");

connect()
.use(serveStatic(__dirname))
.listen(8080, function () {

console.log("Server running on 8080...");
});

Una vez tenemos las dos cosas, abrimos una terminal y ejecutamos

node server.js

Esto hará que la carpeta en la que estamos sea servida y accesible desde el navegador en «http://localhost:8080».

Creación de nuestra Web que contendrá el «Service Worker»

Crearemos un fichero «serviceworker.html» que contendrá el siguiente código

<!DOCTYPE html>
<head>
</head>
<body>
  Una imagen aparecerá en 3 segundos:
  <script>
    const pushButton = document.getElementById('pushButton')
    navigator.serviceWorker.register('/sw1.js')
      .then(reg => {
        console.log('SW registered!', reg);
      })
      .catch(err => console.log('Boo!', err));
    
    setTimeout(() => {
      const img = new Image();
      img.src = '/dog.jpeg';
      document.body.appendChild(img);
    }, 3000);
  </script>
</body>
</html>

Como se puede ver es una página en blanco con el texto «Una imagen aparecerá en 3 segundos:» y una imagen.  Respecto al código javascript, se puede ver que se realiza el registro de un «Service Worker» contenido en el fichero «Service Worker1.js» y que muestra por consola el mensaje de éxito o fracaso. Además también incluimos  la carga de una imagen «dog.jpeg» con un retraso de 3 segundos.

Si ejecutamos esto en el navegador y abrimos el «Developers tool» de Chrome veremos que si vamos a la pestaña de «Applications» -> «Service Workers» hemos realizado la instalación con éxito de nuestro primer «Service Worker».

Service worker registrado

Implementación del «Service Worker»

Cuando registramos un «Service Worker» lo primero de todo es que recibimos el evento de instalación. Por lo tanto en el fichero «sw1.js» implementaremos lo siguiente

self.addEventListener("install", event => {
  console.log("V1 installing…");
  // cache a cat SVG
  event.waitUntil(
    caches.open("static-v1").then(cache => cache.add("/cat.jpg"))
  );
});

Escuchamos el evento de instalación y cacheamos una imagen diferente a la inicial.

El segundo paso es escuchar el evento de activación del «Service Worker»

self.addEventListener("activate", event => {
  console.log("V1 now ready to handle fetches!");
});

Por último escucharemos el evento fetch para ver cuando se realiza la petición de la imagen del perro y la sustituiremos con la imagen del gato.

self.addEventListener("fetch", event => {
  const url = new URL(event.request.url);
  // serve the cat SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == "/dog.jpeg") {
    event.respondWith(caches.match("/cat.jpg"));
  }
});

Una vez tenemos la implementación vamos a hacer lo siguiente:

  1. Cerramos las pestañas que contengan la página
  2. Vamos al «Developer tools» -> Application -> Service Workers y le damos a «unregister» para eliminarlo
  3. Volvemos a acceder al html creado
  4. Vemos como se ha registrado el «Service Worker» en el «Developer tools» pero por contra seguimos viendo el perro. Esto se debe a que el «Service Worker» no afecta a la ruta de carga la primera vez, nos hará falta recargar la página para ver la imagen del  gato
  5. Recargamos la página y ahora si, vemos la imagen del gato

Actualización del «Service Worker»

Sustituimos el contenido del fichero «sw1.js» por el siguiente

const expectedCaches = ['static-v2'];

self.addEventListener('install', event => {
  console.log('V2 installing…');
  // cache a horse SVG into a new cache, static-v2
  event.waitUntil(
    caches.open('static-v2').then(cache => cache.add('/horse.jpeg'))
  );
});

self.addEventListener('activate', event => {
  // delete any caches that aren't in expectedCaches
  // which will get rid of static-v1
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.map(key => {
        if (!expectedCaches.includes(key)) {
          return caches.delete(key);
        }
      })
    )).then(() => {
      console.log('V2 now ready to handle fetches!');
    })
  );
});

self.addEventListener('fetch', event => {

  const url = new URL(event.request.url);
  // serve the horse SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.jpeg') {
    event.respondWith(caches.match('/horse.jpeg'));
  }
});

En este caso lo que vamos a hacer es instalar el «Service Worker» que no pasará al estado de activo hasta que todas las páginas abiertas ya no sean cerradas.
Como se puede ver el código lo que se hace es crear una nueva cache y eliminar de la vieja aquellas con el mismo key. Eso si, esta vez en vez de cachear una imagen de un gato, lo hacemos con la imagen de un caballo.

Pasos a seguir:

  1. Recargar la web después de cambiar «sw1.js»
  2. Comprobar que vemos la imagen del gato y no la imagen del caballo
  3. Abrir el «developer tools» y ver que se ha instalado pero está pendiente de activarse Service worker a la esper de ser activado
  4. Cerrar todas las pestañas y volver a entrar
  5. Vemos la imagen del caballo dado que el «Service Worker» se ha activado

Actualización del «Service Worker» sin espera

En este caso vamos a cambiar la escucha del evento «install» y vamos a decirle que no espere para recargar «self.skipWaiting()»

self.addEventListener("install", event => {
  console.log("V3 Installing…");
  // don't wait
  self.skipWaiting();
  // cache a cow SVG
  event.waitUntil(caches.open("static-v3").then(cache => cache.add("cow.jpg")));
});

Como se puede ver esta vez cargamos una imagen de una vaca.

Pasos a seguir

  1. Sustituir el «SW1.js»
  2. Recargar la página
  3. Vemos la imagen de la vaca dado que le hemos dicho que no espera a ninguna otra página que esté trabajando con el viejo «Service Worker»

 

Trucos para desarrollar los «Service Workers»

El «Developer Tools» de chrome nos permite activar el «uptade on reload» que nos permitirá ejecutar siempre la última versión del «Service Worker» que estemos desarrollando independientemente de si tiene cambios o no.

Tip desarrollo

Conclusiones

En esta entrada hemos aprendido que es un «Service Worker», su ciclo de vida y cómo podemos usarlo como proxy de los eventos «fetch» a favor de nuestros intereses.
En próximas entradas os enseñaremos cómo realizar eventos push y su visualización para el usuario

 

Podéis encontrar el código de todos los ejemplos en el siguiente repositorio

Comparte esto en...

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *