Maxim Salnikov
@webmaxru
How to build fast, offline-ready, user-friendly web apps
Products from the future
UI Engineer at ForgeRock
... web apps that use the latest web technologies.
... attempts to combine features offered by most modern browsers with the benefits of mobile experience
Progressive
Discoverable
Linkable
App-like
Responsive
Connectivity-independent
Re-engageable
Installable
Fresh
Safe
App
Service worker
'install'
Parsed
Installing
Activating
Redundant
'activate'
Waiting
Active
favoring or advocating progress, change, improvement, or reform
happening or developing gradually or in stages
if ('serviceWorker' in navigator) {
// Registering service worker
}
if ('SyncManager' in window) {
// Implement offline-ready network features
}
if (!('PushManager' in window)) {
// Hide UI for Web Push subscription
}
if ('actions' in Notification.prototype) {
// Consider using action buttons
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-workbox.js')
.then(...);
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw-workbox.js')
.then(...);
});
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
// Service worker registration
});
// Registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/news/2017/'});
// Registered if 'Service-Worker-Allowed' header is set to ‘/’
// NOT registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/catalog/'});
// NOT registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/'});
self.addEventListener('install', () => {
self.skipWaiting();
});
self.addEventListener('activate', () => {
self.clients.matchAll({type: 'window'}).then(tabs => {
tabs.forEach(tab => {
tab.navigate(tab.url);
});
});
});
Cache-Control: max-age=0
navigator.serviceWorker.getRegistrations()
.then((registrations) => {
for(let registration of registrations) {
registration.unregister()
}
})
const appShellFilesToCache = [
...
'./non-existing.html'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(cacheName).then((cache) => {
return cache.addAll(appShellFilesToCache)
})
)
})
Chrome | <6% of free space |
Firefox | <10% of free space |
Safari | <50MB |
IE10 | <250MB |
Edge | Dependent on volume size |
const appShellFilesToCache = [
...
'https://workboxjs.org/offline-ga.min.svg'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(cacheName).then((cache) => {
return cache.addAll(appShellFilesToCache)
})
)
})
const noCorsRequest =
new Request('https://workboxjs.org/offline-ga.svg', {
mode: 'no-cors'
});
fetch(noCorsRequest)
.then(response => cache.put(noCorsRequest, response));
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(cacheName).then((cache) => {
return cache.addAll(appShellFilesToCache)
})
)
})
const appShellFilesToCache = [
...
'./assets/redirect/redirectfrom.html'
]
app.get('/assets/redirect/redirectfrom.html', (req, res) => {
res.redirect(301, '/assets/redirect/redirectto.html')
})
// If "cleanRedirects" and this is a redirected response,
// then get a "clean" copy to add to the cache.
const newResponse = cleanRedirects && response.redirected ?
await cleanResponseCopy({response}) :
response.clone();
const env = {
// Environment polyfills
skipWaiting: Function,
caches: CacheStorage,
clients: Clients,
registration: ServiceWorkerRegistration,
addEventListener: Function,
Request: constructor Function,
Response: constructor Function,
URL: constructor Function
};
Online check in is available
MyAirline
myairline.com
Flight
DY1043
Depart
21.09 13:45
Click here to check in now
myairline.com
SW Boot
Navigation request
SW Boot
Navigation request
self.addEventListener('activate', e => {
e.waitUntil(self.registration.navigationPreload.enable());
});
self.addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
navigator.serviceWorker.ready.then((registration) => {
registration.periodicSync.register({
tag: 'get-latest-news', // default: ''
minPeriod: 12 * 60 * 60 * 1000, // default: 0
powerState: 'avoid-draining', // default: 'auto'
networkState: 'avoid-cellular' // default: 'online'
}).then((periodicSyncReg) => {
// Successfully registered
})
});
self.addEventListener('periodicsync', function(event) {
if (event.registration.tag == 'get-latest-news') {
event.waitUntil(fetchAndCacheLatestNews());
}
else {
// Unknown sync, may be old, best to unregister
event.registration.unregister();
}
});
Link: </sw-fontservice.js>; rel="serviceworker"; scope="/"
self.addEventListener('install', event => {
event.registerForeignFetch({
scopes: ['/fonts'], // or self.registration.scope
origins: ['*'] // or ['https://mycustomer.com']
});
});
self.addEventListener('foreignfetch', ...)