Offline first with progressive web apps [ Part 2 / 3 ]

Improving our offline screen

01: How the offline screen currently looks
02: How the new offline screen will look
03: Service worker states
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Video Voter</title>
<link href="css/bootstrap.min.css" rel="stylesheet" />
<link href="css/cover.css" rel="stylesheet" />
</head>
<body class="text-center full-screen-background-image">
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
<header class="masthead mb-auto"></header>
<main role="main" class="inner cover">
<h1 class="cover-heading">Youtube video voter.</h1>
<p class="lead">There seems to be a problem with your connection.</p>
</main>
<footer class="mastfoot mt-auto">
<div class="inner">
<p>Developed by Wout Schoovaerts.</p>
</div>
</footer>
</div>
</body>
</html>
04: What we need to load in
const CACHE_NAME = 'vid-voter';
const URLS_TO_CACHE = [
"/offline.html",
"/css/bootstrap.min.css",
"/css/cover.css",
"/image/cover.jpg"
];
self.addEventListener("install", function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(URLS_TO_CACHE);
})
);
});
self.addEventListener("fetch", function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request).then(function(response) {
if (response) {
return response;
} else if (event.request.headers.get("accept").includes("text/html")) {
return caches.match("/offline.html");
}
});
})
);
});
05: Cached assets

Going offline

What will we use?

Coding time!

const CACHE_NAME = 'vid-voter-v3';
const URLS_CACHE_ONLY = [
"/css/bootstrap.min.css",
"/js/bootstrap.min.js",
"/js/jquery-3.3.1.min.js",
"/image/cover.jpg",
"/image/did-logo.png",
// font-awesome
"/css/all.min.css",
"/webfonts/fa-brands-400.eot",
"/webfonts/fa-brands-400.svg",
"/webfonts/fa-brands-400.ttf",
"/webfonts/fa-brands-400.woff",
"/webfonts/fa-brands-400.woff2",
"/webfonts/fa-regular-400.eot",
"/webfonts/fa-regular-400.svg",
"/webfonts/fa-regular-400.ttf",
"/webfonts/fa-regular-400.woff",
"/webfonts/fa-regular-400.woff2",
"/webfonts/fa-solid-900.eot",
"/webfonts/fa-solid-900.svg",
"/webfonts/fa-solid-900.ttf",
"/webfonts/fa-solid-900.woff",
"/webfonts/fa-solid-900.woff2",
];
const URLS_OVER_NETWORK_WITH_CACHE_FALLBACK = [
"/index.html",
"/voter.html",
"/css/app.css",
"/css/cover.css",
"/js/app.js",
"/js/voter/voter.js",
"http://localhost:3000/videos"
];
self.addEventListener("install", function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(URLS_CACHE_ONLY.concat(URLS_OVER_NETWORK_WITH_CACHE_FALLBACK));
}).catch((err) => {
console.error(err);
return new Promise((resolve, reject) => {
reject('ERROR: ' + err);
});
})
);
});
self.addEventListener("fetch", function (event) {
const requestURL = new URL(event.request.url);
if (requestURL.pathname === '/') {
event.respondWith(getByNetworkFallingBackByCache("/index.html"));
} else if (URLS_OVER_NETWORK_WITH_CACHE_FALLBACK.includes(requestURL.href) || URLS_OVER_NETWORK_WITH_CACHE_FALLBACK.includes(requestURL.pathname)) {
event.respondWith(getByNetworkFallingBackByCache(event.request));
} else if (URLS_CACHE_ONLY.includes(requestURL.href) || URLS_CACHE_ONLY.includes(requestURL.pathname)) {
event.respondWith(getByCacheOnly(event.request));
}
});
self.addEventListener("activate", function (event) {
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (CACHE_NAME !== cacheName && cacheName.startsWith("vid-voter")) {
return caches.delete(cacheName);
}
})
);
})
);
});
/**
* 1. We fetch the request over the network
* 2. If successful we add the new response to the cache
* 3. If failed we return the result from the cache
*
*
@param request
*
@param showAlert
*
@returns Promise
*/
const getByNetworkFallingBackByCache = (request, showAlert = false) => {
return caches.open(CACHE_NAME).then((cache) => {
return fetch(request).then((networkResponse) => {
cache.put(request, networkResponse.clone());
return networkResponse;
}).catch(() => {
if (showAlert) {
alert('You are in offline mode. The data may be outdated.')
}
return caches.match(request);
});
});
};
/**
* Get from cache
*
*
@param request
*
@returns Promise
*/
const getByCacheOnly = (request) => {
return caches.open(CACHE_NAME).then((cache) => {
return cache.match(request).then((response) => {
return response;
});
});
};

What have we done?

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store