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
ngrokentã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-channeleventos de mensagem. Ao responder a esses eventos, fazemos duas coisas: - Chamar
persistTask()para salvar a nova tarefa emIndexedDB. - Registre um novo
syncevent. É 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 deIndexedDBrecomendo esta série.) - O
onupgradeneededO evento é disparado se estivermos acessando um banco de dados novo ou com versão atualizada. - Dentro
onupgradeneededobtemos um controle sobre o próprio banco de dados, com nosso globaldbobjeto. - 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
dbobjeto. - 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.tagcampo sendotask-syncentão sabemos que é nosso evento de sincronização de tarefas. event.waitUntil()nos permite contar oserviceWorkerque não terminamos até quePromisedentro dele completa. Como estamos em um evento de sincronização, isso tem um significado especial. Em particular, se nossoPromisefalhar, 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
Promisee dentro dele começamos abrindo uma conexão com o banco de dados.
- Nós definimos um novo
- Dentro do banco de dados
onsuccessretorno de chamada, obtemos um cursor e o usamos para pegar a tarefa que salvamos. (Estamos aproveitando nosso wrapperPromisepara 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
fetchsolicitação para nossoexpressJS /todos/addponto 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 umataskscriaçã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:
