Coder Social home page Coder Social logo

pwa-workshop's Introduction

Offline-first progressive web apps workshop

Devfest Ukraine 2016

Main goal of this workshop is to create a web application using progressive web apps technologies and methods.

Step 0 - Set up

Github repository

You can download this workshop from the following github repository:

git clone https://github.com/jskvara/pwa-workshop

Web server

While you're free to use your own web server, this workshop is designed to work well with the Chrome Web Server. If you don't have that app installed yet, you can install it from the Chrome Web Store.

After installing the Web Server for Chrome app, click on the Apps shortcut on the bookmarks bar.

Apps

In the ensuing window, click on the Web Server icon:

Web server icon

Click Choose Folder and select app. While you're in the Web Server, also select "Automatically show index.html".

Web server for chrome

Open a new browser tab and navigate to localhost:8887. You should see the content of the app/index.html file.

Step 1 - Application Shell

In this step we add an Application Shell to our website.

The app's shell, is the minimal HTML, CSS, and JavaScript that is required to power the user interface of a progressive web app and is one of the the components that ensures reliably good performance. Its first load should be extremely quick, then immediately be cached. This means that the shell does not need to be loaded every time, but instead just gets the necessary content.

We add a basic html content for our app:

<body>

<header class="header">
    <h1>Progressive web apps workshop</h1>
</header>

<div class="loader">
    <svg viewBox="0 0 32 32" width="32" height="32">
        <circle class="spinner" cx="16" cy="16" r="14" fill="none"></circle>
    </svg>
</div>

And we inline css styles just for the application shell into <head> element.

    ...
    <title>Progressive web apps workshop</title>
    <style type="text/css">
        body {
            padding: 0;
            margin: 0;
        }

        .header {
            background: #3f51b5;
            color: #fff;
            font-size: 16px;
            padding: 6px 16px;
            width: 100%;
            z-index: 1000;
        }

        .loader {
            left: 50%;
            top: 50%;
            position: fixed;
            -webkit-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
        }

        @keyframes line {
            0% {
                stroke-dasharray: 2, 85.964;
                -webkit-transform: rotate(0);
                transform: rotate(0);
            }
            50% {
                stroke-dasharray: 65.973, 21.9911;
                stroke-dashoffset: 0;
            }
            100% {
                stroke-dasharray: 2, 85.964;
                stroke-dashoffset: -65.973;
                -webkit-transform: rotate(90deg);
                transform: rotate(90deg);
            }
        }

        @keyframes rotate {
            from {
                -webkit-transform: rotate(0);
                transform: rotate(0);
            }
            to {
                -webkit-transform: rotate(450deg);
                transform: rotate(450deg);
            }
        }

        .loader .spinner {
            box-sizing: border-box;
            stroke: #673ab7;
            stroke-width: 3px;
            -webkit-transform-origin: 50%;
            transform-origin: 50%;
            -webkit-animation: line 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite, rotate 1.6s linear infinite;
            animation: line 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite, rotate 1.6s linear infinite;
        }
    </style>
</head>
...

It's not always necessary to put css into your head, you can use service workers to cache your css files.

You can also embed basic data into your HTML file or combine it with different ways of caching.

More resources:

Step 2 - Manifest

In this step we're going to add manifest file to our website and we show different configuration options.

Create a new JSON called manifest.json in the same folder as your index.html file with the following content:

{
  "short_name": "PWA workshop",
  "name": "Progressive web apps workshop",
  "icons": [
    {
      "src":"images/icon.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "background_color": "#3f51b5",
  "theme_color": "#3f51b5",
  "display": "standalone",
  "orientation": "portrait"
}

Important options:

  • background_color (only when application is loading)
  • description
  • start_url (URL that loads when a user launches the application from a device. ) - you can use: "./?utm_source=web_app_manifest"
  • display: standalone | fullscreen (games) | browser
  • orientation: portrait | landscape
  • icons
{
  "icons": [
      {
        "src": "icon/lowres.webp",
        "sizes": "48x48",
        "type": "image/webp"
      },{
        "src": "icon/lowres",
        "sizes": "48x48"
      },{
        "src": "icon/hd_hi.ico",
        "sizes": "72x72 96x96 128x128 256x256"
      },{
        "src": "icon/hd_hi.svg",
        "sizes": "257x257"
      }]
 }
  • related applications
 "related_applications": [{
    "platform": "web",
    "url": "..."
  }, {
    "platform": "play",
    "url": "..."
  }]

For more information, you can read the following content:

Step 3 - Service worker

In this step we add a service worker to our application

Update file js/main.js and add the following code:

if ('serviceWorker' in navigator) {
    console.log('Service Worker is supported');
    navigator.serviceWorker.register('sw.js').then(function() {
        console.log(navigator.serviceWorker);
        return navigator.serviceWorker.ready;
    }).then(function(reg) {
        console.log('Service Worker is ready', reg);
    }).catch(function(error) {
        console.log('Service Worker error', error);
    });
}

Now we've registered a service worker, now we can define the functionality for it. we have to update sw.js file with the following content:

console.log('Started', self);

self.addEventListener('install', function(event) {
    self.skipWaiting();
    console.log('Installed', event);
});

self.addEventListener('activate', function(event) {
    console.log('Activated', event);
});

Now you can open the Developer console in your browser and switch to Application tab. You should see our registered service worker in Service workers section.

Step 4 - Cache Application Data

In this step we updated the service worker to cache all data.

Update the sw.js file with the following content:

var cacheName = 'pwa-ws';
var filesToCache = [
    '/',
    '/index.html',
    '/index.html?homescreen=1', // you have to add exact url with query parameters
    '/js/main.js'
];

self.addEventListener('install', function(e) {
    console.log('[ServiceWorker] Install');
    e.waitUntil(
        caches.open(cacheName).then(function(cache) {
            console.log('[ServiceWorker] Caching app shell');
            return cache.addAll(filesToCache);
        })
    );
});

Service worker can intercept every request that the page makes. So we can define, what to do with the request, either return a cached version or request the network.

self.addEventListener('fetch', function(event) {
    console.log(event.request.url);
    event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event.request);
        })
    );
});

You can also Indexed DB for JSON and simple data.

Step 5 - Push notifications

  1. Make a project on the Google Developer Console

From the Google Developers Console create a new project:

Select APIs for the project

From the API Manager menu, select Overview:

Create

From the Google APIs list, select Google Cloud Messaging.

GCM

Get credentials

From the APIs & auth menu, select Credentials, click the Add credentials dropdown button, and select API key:

Credentials

Click the Browser Key button:

Browser Key

Leave the HTTP referrers field blank and click the Create button:

Create

Get the API key — you'll need this later:

Api Key

From the IAM and Admin Settings page, get the Project number — you'll need this later:

Project Number

Congratulations!

You've now created a Google Cloud Messaging project.

  1. Add gcm_sender_id to manifest.json file:
  ...
  "name": "Progressive web apps workshop",
  "gcm_sender_id": "1026906795551",
  "icons": [
  ...
  1. Subscribe to push notifications

Update main.js with the following code:

    ...
    }).then(function(reg) {
        console.log('Service Worker is ready', reg);
        reg.pushManager.subscribe({userVisibleOnly: true}).then(function(sub) {
            console.log('endpoint:', sub.endpoint);
        });
    }).catch(function(error) {
    ...

To send a push notification message you need to create a following HTTP request:

curl --header "Authorization: key=<PUBLIC_API_KEY>" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"<SUBSCRIPTION_ID>\"]}"`

Where PUBLIC_API_KEY is the key you've generated on GCM site, which looks like: AIzaSyAMzp4LO9CiODdPEpfe7eQtdKHlB3foxcs And SUBSCRIPTION_ID is he last part of the subscription endpoint URL, and looks like this: APA91bHMaA-R0eZrPisZCGfwwd7z1EzL7P7Q7cyocVkxBU3nXWed1cQYCYvFglMHIJ40kn-jZENQ62UFgg5QnEcqwB5dFZ-AmNZjATO8QObGp0p1S6Rq2tcCuUibjnyaS0UF1gIM1mPeM25MdZdNVLG3dM6ZSfxV8itpihroEN5ANj9A26RU2Uw

Note: be careful when copying the URL from console, part of it might be replaced by ...

curl --header "Authorization: key=AIzaSyAc2e8MeZHA5NfhPANea01wnyeQD7uVY0c" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"APA91bE9DAy6_p9bZ9I58rixOv-ya6PsNMi9Nh5VfV4lpXGw1wS6kxrkQbowwBu17ryjGO0ExDlp-S-mCiwKc5HmVNbyVfylhgwITXBYsmSszpK0LpCxr9Cc3RgxqZD7614SqDokwsc3vIEXkaT8OPIM-mnGMRYG1-hsarEU4coJWNjdFP16gWs\"]}"

We use fetch API to send a notification:

Update index.html with the following code to add a new button:

<div class="content">
    <button id="send">Send notification</button><br />
</div>

And update main.js file:

var sub;

...

    }).then(function(reg) {
        serviceWorkerRegistration = reg;
        subscribeButton.disabled = false;
        console.log('Service Worker is ready', reg);
    }).catch(function(error) {

...

sendButton.addEventListener('click', function() {
    if (isSubscribed && sub.endpoint.startsWith('https://android.googleapis.com/gcm/send')) {
        var endpointParts = sub.endpoint.split('/');
        var registrationId = endpointParts[endpointParts.length - 1];
        fetch('https://android.googleapis.com/gcm/send', {
            method: 'POST',
            headers: new Headers({
                'Authorization': 'key=AIzaSyAMzp4LO9CiODdPEpfe7eQtdKHlB3foxcs',
                'Content-Type': 'application/json'
            }),
            body: JSON.stringify({
                registration_ids: [registrationId]
            })
        }).then(function(response) {
            console.log(response);
        }).catch(function(err) {
            console.error(error);
        });
    } else {
        console.error('You need to subscribe');
    }
});
  1. Show notification
...

self.addEventListener('push', function(event) {
    console.log('Push message', event);

    var title = 'Push message';

    event.waitUntil(
        self.registration.showNotification(title, {
            body: 'The Message',
            icon: 'images/icon.png',
            tag: 'my-tag'
        }));
});
  1. Open notification
...

self.addEventListener('notificationclick', function(event) {
    console.log('Notification click: tag', event.notification.tag);
    event.notification.close();
    var url = 'https://www.youtube.com/watch?v=DfMnJAzOFng';
    // Check if there's already a tab open with this URL.
    // If yes: focus on the tab.
    // If no: open a tab with the URL.
    event.waitUntil(
        clients.matchAll({
            type: 'window'
        })
            .then(function(windowClients) {
                console.log('WindowClients', windowClients);
                for (var i = 0; i < windowClients.length; i++) {
                    var client = windowClients[i];
                    console.log('WindowClient', client);
                    if (client.url === url && 'focus' in client) {
                        return client.focus();
                    }
                }
                if (clients.openWindow) {
                    return clients.openWindow(url);
                }
            })
    );
});
  1. Subscribe and unsubscribe
var serviceWorkerRegistration;
var sub;
var isSubscribed = false;
var subscribeButton = document.querySelector('button');

if ('serviceWorker' in navigator) {
    console.log('Service Worker is supported');
    navigator.serviceWorker.register('sw.js').then(function() {
        console.log(navigator.serviceWorker);
        return navigator.serviceWorker.ready;
    }).then(function(reg) {
        serviceWorkerRegistration = reg;
        subscribeButton.disabled = false;
        console.log('Service Worker is ready', reg);
    }).catch(function(error) {
        console.log('Service Worker error', error);
    });
}

subscribeButton.addEventListener('click', function() {
    if (isSubscribed) {
        unsubscribe();
    } else {
        subscribe();
    }
});

function subscribe() {
    serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true}).
    then(function(pushSubscription){
        sub = pushSubscription;
        console.log('Subscribed! Endpoint:', sub.endpoint);
        subscribeButton.textContent = 'Unsubscribe';
        isSubscribed = true;
    });
}

function unsubscribe() {
    sub.unsubscribe().then(function(event) {
        subscribeButton.textContent = 'Subscribe';
        console.log('Unsubscribed!', event);
        isSubscribed = false;
    }).catch(function(error) {
        console.log('Error unsubscribing', error);
        subscribeButton.textContent = 'Subscribe';
    });
}

More info:

Step 6 - Deploy to firebase

We're going to show data from API fetched via AJAX

If you’re new to Firebase, you’ll need to sign in using your Google account and install some tools first.

  1. Sign in to Firebase with your Google account at https://firebase.google.com/
  2. Install the Firebase tools via npm: npm install -g firebase-tools

Once your account has been created and you’ve signed in, you’re ready to deploy!

  1. Create a new app at https://console.firebase.google.com/
  2. If you haven’t recently signed in to the Firebase tools, update your credentials: firebase login
  3. Initialize your app, and provide the directory where your completed app lives: firebase init
  4. Finally, deploy the app to Firebase: firebase deploy
  5. Celebrate. You’re done! Your app will be deployed to the domain: https://YOUR-FIREBASE-APP.firebaseapp.com

Further reading: Firebase Hosting Guide

pwa-workshop's People

Contributors

jskvara avatar taras-ua avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.