const URL = «https://8014-35-223-70-178.ngrok-free.app/»; // 1 const taskChannel = new BroadcastChannel(‘task-channel’); // 2 taskChannel.onmessage = event => { // 3 persistTask(event.data.data); // 4 registration.sync.register(‘task-sync’); // 5 }; let db = null; // 6 let request = indexedDB.open(«TaskDB», 1); // 7 request.onupgradeneeded = function(event) { // 8 db = event.target.result; // 9 if (!db.objectStoreNames.contains(«tareas»)) { // 10 let taskObjectStore = db.createObjectStore(«tareas», { autoIncrement: true }); // 11 } }; solicitud.onsuccess = función(evento) { db = evento.objetivo.resultado; }; // 12 solicitud.onerror = función(evento) { console.log(«Error en db: » + evento); }; // 13 persistTask = función(tarea){ // 14 let transacción = db.transacción(«tareas», «lectura-escritura»); let tareasObjectStore = transacción.objectStore(«tareas»); let addRequest = tareasObjectStore.add(tarea); addRequest.onsuccess = función(evento){ console.log(«Tarea agregada a BD»); }; addRequest.onerror = función(evento) { console.log(“Error: “ + evento); }; } self.addEventListener(‘sync’, async function(event) { // 15 if (event.tag == ‘task-sync’) { event.waitUntil(new Promise((res, rej) => { // 16 let transaction = db.transaction(«tareas», «lectura-escritura»); let taskObjectStore = transaction.objectStore(«tareas»); let cursorRequest = taskObjectStore.openCursor(); cursorRequest.onsuccess = function(event) { // 17 let cursor = event.target.result; if (cursor) { let task = cursor.value; // 18 fetch(URL + ‘todos/add’, // a { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify({ «tarea» : tarea })}).then((serverResponse) => { console.log(«Tarea guardada en el backend.»); deleteTasks(); // b res(); // b }).catch((err) => { console.log(«ERROR: » + err); rej(); //c }) } } })) } }) async function deleteTasks() { // 19 const transaction = db.transaction(«tasks», «readwrite»); const taskObjectStore = transaction.objectStore(«tasks»); taskObjectStore.clear(); await transaction.complete; } Ahora hablemos de lo que está sucediendo en este código. Necesitamos enrutar nuestras solicitudes a través del mismo túnel seguro que creamos con ngrok, por lo que guardamos la URL aquí. Crea el canal de transmisión con el mismo nombre para que podamos escuchar los mensajes. Aquí, estamos observando los eventos de mensajes del canal de tareas. Para responder a estos eventos, hacemos dos cosas: Llamamos a persistTask() para guardar la nueva tarea en IndexedDB. Registramos un nuevo evento de sincronización. Esto es lo que invoca la capacidad especial para reintentar solicitudes de manera inteligente. El controlador de sincronización nos permite especificar una promesa de que volverá a intentar cuando la red esté disponible e implementa una estrategia de retroceso y condiciones de abandono. Una vez hecho esto, creamos una referencia para nuestro objeto de base de datos. Obtenemos una «solicitud» para el controlador en nuestra base de datos. Todo en IndexedDB se maneja de manera asincrónica. (Para una excelente descripción general de IndexedDB, recomiendo esta serie). El evento onupgradeneeded se activa si estamos accediendo a una base de datos nueva o con una versión actualizada. Dentro de onupgradeneeded, obtenemos un controlador en la base de datos en sí, con nuestro objeto db global. Si la colección de tareas no está presente, creamos la colección de tareas. Si la base de datos se creó correctamente, la guardamos en nuestro objeto db. Registramos el error si la creación de la base de datos falló. La función persistTask() llamada por el evento de difusión add-task (4). Esto simplemente pone el nuevo valor de la tarea en la colección de tareas. Nuestro evento de sincronización. Esto es llamado por el evento de difusión (5). Comprobamos que el campo event.tag sea task-sync para saber que es nuestro evento de sincronización de tareas. event.waitUntil() nos permite decirle al serviceWorker que no hemos terminado hasta que se complete la Promesa dentro de él. Debido a que estamos en un evento de sincronización, esto tiene un significado especial. En particular, si nuestra Promesa falla, el algoritmo de sincronización seguirá intentándolo. Además, recuerda que si la red no está disponible, esperará hasta que esté disponible. Definimos una nueva Promesa, y dentro de ella comenzamos abriendo una conexión a la base de datos. Dentro de la devolución de llamada onsuccess de la base de datos, obtenemos un cursor y lo usamos para tomar la tarea que guardamos. (Estamos aprovechando nuestra Promesa envolvente para lidiar con llamadas asincrónicas anidadas). Ahora tenemos una variable con el valor de nuestra tarea de difusión en ella. Con eso en mano: Emitimos una nueva solicitud de búsqueda a nuestro punto final expressJS /todos/add. Tenga en cuenta que si la solicitud tiene éxito, eliminamos la tarea de la base de datos y llamamos a res() para resolver nuestra promesa externa. Si la solicitud falla, llamamos a rej(). Esto rechazará la promesa que la contiene, lo que le permite a la API de sincronización saber que se debe volver a intentar la solicitud. El método auxiliar deleteTasks() elimina todas las tareas en la base de datos. (Este es un ejemplo simplificado que supone la creación de una tarea a la vez). Claramente, esto implica mucho, pero la recompensa es poder volver a intentar las solicitudes sin esfuerzo en segundo plano siempre que nuestra red sea irregular. Recuerde, estamos obteniendo esto en el navegador, en todo tipo de dispositivos, móviles y otros. Probar el ejemplo de PWA Si ejecuta la PWA ahora y crea una tarea pendiente, se enviará al back end y se guardará. La prueba interesante es abrir devtools (F12) y deshabilitar la red. Puede encontrar la opción «Sin conexión» en el menú «limitación» de la pestaña de red de la siguiente manera: