In this article, I would like to talk about Service Workers (SW). SWs allow us to make our application ready to work offline so that it works even if we don't have an internet connection. They also allow us to use many other advanced features such as push notifications or background syncing. SW continues to run even after the browser is closed, meaning Service Workers continue to run. This is a background process. So let's register our first Service Worker.
(In this article I will implement SW related functionality in plain JS, since the code is written in plain JS we can integrate into any JS frameworks like Angular, React or Vue)
As a first step, add the sw.js file to the root of the project. In app.js, we have to check if SW is available in the navigator, that is, if SW is supported by this browser. Now that we know the SWs are available, we can execute the navigator.serviceWorker.register () method, specifying the path to the file where our SW resides in order to register it. This method actually returns a Promise. So, to get information, once that's done, we can join him.
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(event => {
console.log('Service worker registered', event);
});
}
SW, . , SW . , . SW, , , self, « SW», addEventListener (). SW , , , , Service Worker’a. , , . , Service Worker .
self.addEventListener('install', event => {
console.log('Installing [Service Worker]', event);
});
. Service Worker’a - , , . caches, API , open (), . , . event.waitUntil (). , . . then . cache.addAll () , .
self.addEventListener('install', event => {
console.log('Installing [Service Worker]', event);
event.waitUntil(
caches.open('static')
.then(cache => {
console.log('[Service Worker] Precaching App Shell');
cache.addAll([
'/',
'/index.html',
'/favicon.ico',
'/src/js/app.js',
'/src/js/chart.js',
'/src/js/materialize.js',
'/src/js/materialize.min.js',
'/src/css/materialize.css',
'/src/css/materialize.min.css',
'/src/css/style.css',
'https://fonts.googleapis.com/icon?family=Material+Icons',
'https://code.jquery.com/jquery-2.1.1.min.js',
'https://cdn.jsdelivr.net/npm/chart.js@2.8.0'
]);
}));
});
, -.
, . , . , , - . Fetch , - - , css js xhr. , fetch Service Worker’a , . , , .
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
} else {
return fetch(event.request);
}
})
);
});
event.respondWith () , . Service Worker’ , , fetch. , Service Worker, . , , . cashes.match () , . , . , , , , , , . , , , , fetch (event.request). - .
, - , « » . , , , . , . , . , , , . , .
Object.keys(pureData).forEach(key => tmp[sorter[key.toLowerCase()]] = { key, value: pureData[key] });
tmp.forEach(obj => orderedData[obj.key] = obj.value);
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: Object.entries(orderedData).map(([key, _]) => key),
datasets: [{
label: 'Users',
backgroundColor: '#26a69a',
borderColor: '#26a69a',
fill: false,
data: Object.entries(orderedData).map(([_, value]) => value),
}]
}
});
});
};
, , , .
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
} else {
return fetch(event.request)
.then(res => {
return caches.open('dynamic')
.then(cache => {
cache.put(event.request.url, res.clone());
return res;
})
});
}
})
);
});
, , , . , caches, API open (), . cache.put () , . , , - URL- , . - . , , , , . . . . xhr. , css .
. - , . ? SW . , - , , , , indexedDB. , , SW . SW, . , , . , , Service Worker’y . , . - , , API, . Service Worker’y, ready, , . , . , ( ), . , , . , « ». Service Worker’, , , , , .
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready
.then(sw => {
sw.sync.register('sync-request')
});
}
, «POST DATA» , , indexedDB . , indexedDB. , . . - . «sunday», 10 ( :)). writeData utility.js, . - , , - . .
const syncButton = document.getElementById('sync-button');
syncButton.addEventListener('click', _ => {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready
.then(sw => {
const data = {
id: new Date().getTime(),
sunday: 10
};
writeData('sync-requests', data)
.then(_ => {
sw.sync.register('sync-request')
});
});
}
});
, , - . , , .
self.addEventListener('sync', event => {
console.log('[Service Worker] Syncing');
if (event.tag === 'sync-request') {
event.waitUntil(
readAllData('sync-requests')
.then(async data => {
const requests = [];
for (const d of data) {
requests.push(fetch('https://simple-pwa-8a005.firebaseio.com/data.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
sunday: d.sunday
})
}));
}
const results = await Promise.all(requests);
results.map((response, index) => {
if (response.ok) {
deleteItemFromData('sync-requests', data[index].id);
}
})
})
);
}
});
. event.waitUntil (), , , . , indexedDB ( utility.js), , post , indexedDB, . . . , , «POST DATA» .
After pressing the "POST DATA" button, when we are offline, nothing happens, but when the connection is restored, we see that the synchronization has been completed.
And in order to confirm that the data has indeed been sent to the server, we first need to remove our fetch request from the dynamic cache and click the GET DATA button.
That's all for now. See you later. My code is available on github: https://github.com/Draakan/simplePWA