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("tasks")) { // 10
let tasksObjectStore = db.createObjectStore("tasks", { autoIncrement: true }); // 11
}
};
request.onsuccess = function(event) { db = event.target.result; }; // 12
request.onerror = function(event) { console.log("Error in db: " + event); }; // 13
persistTask = function(task){ // 14
let transaction = db.transaction("tasks", "readwrite");
let tasksObjectStore = transaction.objectStore("tasks");
let addRequest = tasksObjectStore.add(task);
addRequest.onsuccess = function(event){ console.log("Task added to DB"); };
addRequest.onerror = function(event) { console.log(“Error: “ + event); };
}
self.addEventListener('sync', async function(event) { // 15
if (event.tag == 'task-sync') {
event.waitUntil(new Promise((res, rej) => { // 16
let transaction = db.transaction("tasks", "readwrite");
let tasksObjectStore = transaction.objectStore("tasks");
let cursorRequest = tasksObjectStore.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({ "task" : task })
}).then((serverResponse) => {
console.log("Task saved to backend.");
deleteTasks(); // b
res(); // b
}).catch((err) => {
console.log("ERROR: " + err);
rej(); //c
})
}
}
}))
}
})
async function deleteTasks() { // 19
const transaction = db.transaction("tasks", "readwrite");
const tasksObjectStore = transaction.objectStore("tasks");
tasksObjectStore.clear();
await transaction.complete;
}
Agora vamos falar sobre o que está acontecendo neste código.
- Precisamos rotear nossas solicitações pelo mesmo túnel seguro que criamos com
ngrok
então salvamos a URL aqui. - Crie o canal de transmissão com o mesmo nome para que possamos ouvir as mensagens.
- Aqui, estamos observando
task-channel
eventos de mensagem. Ao responder a esses eventos, fazemos duas coisas: - Chamar
persistTask()
para salvar a nova tarefa emIndexedDB
. - Registre um novo
sync
event. É isso que invoca a capacidade especial de tentar novamente solicitações de forma inteligente. O manipulador de sincronização nos permite especificar uma promessa de que ele tentará novamente quando a rede estiver disponível e implementa uma estratégia de recuo e condições de desistência. - Feito isso, criamos uma referência para nosso objeto de banco de dados.
- Obtenha uma “solicitação” para o identificador em nosso banco de dados. Tudo em
IndexedDB
é tratado de forma assíncrona. (Para uma excelente visão geral deIndexedDB
recomendo esta série.) - O
onupgradeneeded
O evento é disparado se estivermos acessando um banco de dados novo ou com versão atualizada. - Dentro
onupgradeneeded
obtemos um controle sobre o próprio banco de dados, com nosso globaldb
objeto. - Se a coleção de tarefas não estiver presente, criamos a coleção de tarefas.
- Se o banco de dados foi criado com sucesso, nós o salvamos em nosso
db
objeto. - Registre o erro se a criação do banco de dados falhar.
- O
persistTask()
função chamada pelo evento de transmissão add-task (4). Isso simplesmente coloca o novo valor da tarefa na coleção de tarefas. - Nosso evento de sincronização. Isso é chamado pelo evento de transmissão (5). Verificamos o
event.tag
campo sendotask-sync
então sabemos que é nosso evento de sincronização de tarefas. event.waitUntil()
nos permite contar oserviceWorker
que não terminamos até quePromise
dentro dele completa. Como estamos em um evento de sincronização, isso tem um significado especial. Em particular, se nossoPromise
falhar, o algoritmo de sincronização continuará tentando. Além disso, lembre-se de que se a rede não estiver disponível, ele esperará até que fique disponível.- Nós definimos um novo
Promise
e dentro dele começamos abrindo uma conexão com o banco de dados.
- Nós definimos um novo
- Dentro do banco de dados
onsuccess
retorno de chamada, obtemos um cursor e o usamos para pegar a tarefa que salvamos. (Estamos aproveitando nosso wrapperPromise
para lidar com chamadas assíncronas aninhadas.) - Agora temos uma variável com o valor da nossa tarefa de transmissão nela. Com isso em mãos:
- Nós emitimos um novo
fetch
solicitação para nossoexpressJS /todos/add
ponto final. - Observe que se a solicitação for bem-sucedida, excluímos a tarefa do banco de dados e chamamos
res()
para resolver nossa promessa externa. - Se a solicitação falhar, ligamos
rej()
. Isso rejeitará a promessa contida, informando à API de sincronização que a solicitação deve ser repetida.
- Nós emitimos um novo
- O
deleteTasks()
O método auxiliar exclui todas as tarefas do banco de dados. (Este é um exemplo simplificado que assume umatasks
criação de cada vez.)
Claramente, há muito nisso, mas a recompensa é poder repetir solicitações sem esforço em segundo plano sempre que nossa rede estiver irregular. Lembre-se, estamos obtendo isso no navegador, em todos os tipos de dispositivos, móveis e outros.
Testando o exemplo PWA
Se você executar o PWA agora e criar uma tarefa, ela será enviada para o back-end e salva. O teste interessante é abrir o devtools (F12) e desabilitar a rede. Você pode encontrar a opção “Offline” no menu “throttling” da aba de rede assim: