Nico Martin  //  nicomartin.ch

Nico Martin  //  nicomartin.ch

Nico Martin

Web- / Frontend Developer

 

       @nic_o_martin

       nicomartin.ch

 

Say Hello GmbH

       @SayHelloGmbH

       sayhello.ch

Nico Martin  //  nicomartin.ch

  • what is a progressive web app?
  • features and examples
    • installable
    • offline usage
    • push notifications
  • pwa and WordPress
  • pwa and spa
  • browser- / device-support
  • discussion

Nico Martin  //  nicomartin.ch

what is a "progressive web app"?

a website  

+

Nico Martin  //  nicomartin.ch

mobile / hybrid app

website / webapp

installable

has to be installed

can't be installed

open

progressive web app

full access to the device

no unique identifier

works offline

app store

instant updates

online only

growing number APIs

linkable

Nico Martin  //  nicomartin.ch

progressive web app

 

.../application.js

.../theme.js

.../plugin.js

serviceworker.js

Nico Martin  //  nicomartin.ch

installable

if (!'serviceWorker' in navigator){
    console.log('serviceworker is not supported');
} else if (navigator.serviceWorker.controller) {
    console.log('serviceworker already registered');
} else {
    navigator.serviceWorker.register('/serviceworker.js', {
        scope: '/'
    }).then(function (reg) {
        console.log('serviceworker registered');
    });
}

application.js

needs to be https

empty serviceworker.js in the root folder

Nico Martin  //  nicomartin.ch

<head>
    ...
    <link rel="manifest" href="/manifest.json">
</head>
{
    name: "Meine App",
    short_name: "MeineApp",
    start_url: "./",
    description: "Meine erste Progressive Web App",
    theme_color: "#1d1d1b",
    background_color: "#ffffff",
    display: "standalone",
    lang: "",
    orientation: "any",
    icons: [
        {
            src: "/path/to/app-icon-512x512.png",
            type: "image/png",
            sizes: "512x512"
        }
    ],
}

manifest.json

installable

Nico Martin  //  nicomartin.ch

installable

Nico Martin  //  nicomartin.ch

offline usage

ERR_INTERNET_DISCONNECTED

Nico Martin  //  nicomartin.ch

offline usage

application Storage

service worker

Nico Martin  //  nicomartin.ch

offline usage

offline Storage

service worker

Nico Martin  //  nicomartin.ch

offline usage

const version = '20171127';

const offlinePage = 'offline/';
const staticCachePages = [
    '/',
    '/' + offlinePage
];
const key = 'HelloTheme';
const staticCacheName = `${key}-Static-${version}`;

self.addEventListener('install', event => {

    // install offline pages
    event.waitUntil(
        caches.open(staticCacheName)
        .then(cache => {
            return cache.addAll(staticCachePages);
        })
        .then(function () {
            return self.skipWaiting();
        })
    );
});

self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys()
	    .then(keys => {
	        return Promise.all(
		    keys.map(key => {
		        if (key !== staticCacheName) {
			    return caches.delete(key);
			}
		    })
		);
	    })
	);
});

self.addEventListener('fetch', event => {

    let request = event.request;
    let url = new URL(request.url);

    // only deal with requests on the same domain.
    if (url.origin !== location.origin) {
	return;
    }

    //don't do anything if wp stuff
    if (request.url.match(/wp-admin/) || request.url.match(/preview=true/)) {
	return;
    }

    // If non-GET request, try the network first, fall back to the offline page
    if (request.method !== 'GET') {
	event.respondWith(
            fetch(request)
	        .catch(error => {
		    return caches.match(offlinePage);
		})
        );
	return;
    }

    if (request.mode === 'navigate' || request.mode === 'cors') {
        // if HTML requests, try the network first (and update cache), fall back to the cache, finally the offline page
	event.respondWith(
	    fetch(request)
	        .then(response => {
		    addToCache(request);
		    return response;
		})
                .catch(error => {
		    return caches.match(request)
		        .then(response => {
			    return response || caches.match(offlinePage);
			});
		})
	    );
    } else {
        // if non-HTML request, look in the cache first, fall back to the network
	event.respondWith(
	    caches.match(request)
	        .then(response => {
		    if (response) {
		        return response;
		    } else {
			addToCache(request);
			return fetch(request);
		    }
		})
	);
    }

    const addToCache = function (request) {
        return caches.open(staticCacheName)
	    .then(cache => {
	        return fetch(request)
		.then(response => {
		    return cache.put(request, response);
		});
	    });
    };
});

serviceworker.js

Nico Martin  //  nicomartin.ch

push notifications

Nico Martin  //  nicomartin.ch

push notifications

  • client has to agree
  • encrypted "subscription ID" has to be stored on the server
  • messaging service: firebase
     
  • server --> messaging service --> device
  • sw listens to the push
  • sw displays the notification
  • sw can interact with the notification

Nico Martin  //  nicomartin.ch

push notifications

const pushLatestPush = self.registration.scope + 'wp-content/latest_push.json';

self.addEventListener('push', event => {
    event.waitUntil(
        // pushManager
	registration.pushManager.getSubscription()
	    .then(function (subscription) {
                // get latest data
		return fetch(pushLatestPush)
		    .then(function (response) {
			return response.json()
			    .then(function (data) {
				// show Notification
				return self.registration.showNotification(data.title, {
				    body: data.body,
				    badge: data.badge,
				    icon: data.icon,
				    image: data.image
				});
			})
		    })
		})
    );
});

self.addEventListener('notificationclick', event => {
    
    const notification = event.notification;
    const action = event.action;

    if (action === 'close') {
	notification.close();
    } else {
	event.waitUntil(
	    fetch(pushLatestPush)
	        .then(function (response) {
		    return response.json()
		        .then(function (data) {
			    if ('' !== data.redirect) {
				clients.openWindow(data.redirect);
			    }
			    notification.close();
			})
		    })
		    .catch(function (err) {
			notification.close();
		    })
	);
    }
});

serviceworker.js

Nico Martin  //  nicomartin.ch

pwa and WordPress

MPA   vs.   SPA

multi page application            single page application

built on the server                    built on the client

Nico Martin  //  nicomartin.ch

pwa and WordPress

Nico Martin  //  nicomartin.ch

pwa and WordPress

Does it work?

Yes

Does have some improvements?

Yes

Does it feel like a mobile app?

No

Nico Martin  //  nicomartin.ch

pwa and spa

Nico Martin  //  nicomartin.ch

pwa and spa

app.skateparkguide.ch

api.skateparkguide.ch

WP REST API

service worker

Nico Martin  //  nicomartin.ch

pwa and spa

Nico Martin  //  nicomartin.ch

browser- / device-support

Nico Martin  //  nicomartin.ch

browser- / device-support

iOS / Safari

in Development (since August 2017)

https://webkit.org/status/#specification-service-workers

Nico Martin  //  nicomartin.ch

discussion

https://wpch.slack.com/

@nico

 

@nic_o_martin

 

 

Thank you!

WPZürich Progressive Web Apps

By Nico Martin

WPZürich Progressive Web Apps

  • 939