Or, how I learned to stop worrying and embrace the boring solution.
Sam Beckham
@samdbeckham
✅ Non-Javascript content
✅ https
✅ https redirect
✅ Fast enough on 3g
✅ <meta viewport> tag
✅ Sized for the viewport
❌ Custom splash screen
❌ Themed Address Bar
❌ Service Worker
❌ 200 When offline
❌ Prompted to install the web app
{
"name": "GitLab",
"short_name": "GitLab",
"display": "standalone",
"scope": "/",
"start_url": "/",
"theme_color": "#474D57",
"background_color": "#ffffff",
"icons": [
{
"src": "touch-icon-ipad-retina.png",
"sizes": "152x152",
"type": "image/png"
}
]
}
./manifest.json
<head>
<!-- other head stuff -->
<link rel="manifest" href="/manifest.json">
</head>
./yourpage.html
✅ Custom splash screen
✅ Themed Address Bar
❌ Service Worker
❌ 200 When offline
❌ Prompted to install the web app
Browser
Server
request
response
Browser
Server
Service
Worker
Browser
Server
Service
Worker
cat.png
cat.png
Cache
Browser
Server
Service
Worker
cat.png
cat.png
Cache
Browser
Server
Service
Worker
offline.html
Cache
Browser
Server
Service
Worker
issue
offline
Cache
issue
// Register a service worker if our
// browser allows it
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(
'/service_worker.js',
{ scope: '/'}
);
}
const CURRENT_CACHE = '...'
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(CURRENT_CACHE)
.then(cache =>
cache.add('/offline'))
)
})
./service_worker.js
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache =>
cache !== CURRENT_CACHE
? caches.delete(cache)
: Promise.resolve()
),
...
})
./service_worker.js
self.addEventListener('fetch', ({request}) => {
event.respondWith(
fetch(request).catch(() => (
caches.match('/offline')
))
)
})
./service_worker.js
const CURRENT_CACHE = '<%= Gitlab.version %>_<%= Gitlab.revision %>';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('install', event => {
event.waitUntil(caches.open(CURRENT_CACHE).then(cache => cache.add('/offline')));
});
// eslint-disable-next-line no-restricted-globals
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache =>
cache !== CURRENT_CACHE ? caches.delete(cache) : Promise.resolve(),
),
);
}),
);
});
// eslint-disable-next-line no-restricted-globals
self.addEventListener('fetch', { request } => {
event.respondWith(
fetch(request).catch(() => (
request.mode === 'navigate' ? caches.match('/offline') : null
)),
);
});
./service_worker.js
❌ Prompted to install the web app
✅ Service worker
✅ 200 when offline
✅ https
✅ http => https
✅ Custom Splash Screen
✅ Themed Address bar
✅ non-javascript content
✅ Fast enough on 3g
✅ <meta viewport> tag
✅ Sized correctly for the viewport
✅ Service worker
✅ 200 when offline
✅ https
✅ http => https
✅ Prompted to install the web app
✅ Custom Splash Screen
✅ Themed Address bar
✅ non-javascript content
✅ Fast enough on 3g
✅ <meta viewport> tag
✅ Sized correctly for the viewport
self.addEventListener('fetch', ({request}) => {
if (
request.method === 'GET' &&
request.mode === 'navigate'
) {
event.respondWith(
fetch(request).catch(() => (
caches.match('/-/offline')
))
)
}})
./service_worker.js
✅ Basic PWA Support
✅ Vulnerability Caching
Sam Beckham
@samdbeckham