Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Progressive Web Apps – Mobile has natively come to the Web

8314890e50d87c60b2ab5fdab90a9630?s=47 Surma
April 23, 2016

Progressive Web Apps – Mobile has natively come to the Web

JSUnconf.eu 2016

8314890e50d87c60b2ab5fdab90a9630?s=128

Surma

April 23, 2016
Tweet

Transcript

  1. Proprietary + Confidential Progressive Web Apps Mobile has natively come

    to the Web Surma @DasSurma
  2. None
  3. None
  4. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <HTML> <HEAD> <TITLE>Bach's home

    page</TITLE> <STYLE type="text/css"> h1 { color: red } </STYLE> </HEAD> <BODY> <H1>Bach's home page</H1> <P>Johann Sebastian Bach was a prolific composer. </BODY> </HTML>
  5. None
  6. Ajax was born to run desktop apps on the Web

  7. None
  8. How did the Web beat native applications on the desktop?

  9. Distribution Is The Hardest Problem In Software flickr.com/photos/blakespot/

  10. None
  11. What if the Web evolved those capabilities, just as it

    did with Ajax?
  12. Got Service Workers? and HTTPS?

  13. Service Workers Are Network Progressive Enhancement A Programmable Network Proxy

    under your control.
  14. Service Workers are to Progressive Web Apps as XMLHttpRequest was

    to Ajax The foundational capability that was a tipping point for innovation
  15. What's Missing? 1. Homescreen Access 2. Push Notifications 3. Offline

  16. 1. Homescreen Access Less typing, more tapping.

  17. airhorner.com Paul Kinlan @paulkinlan

  18. 2007: iPhone launch

  19. Installable <link rel="apple-touch-icon" href="touch-icon-iphone.png"> // Later on more icon sizes....

    <link rel="apple-touch-icon" sizes="76x76" href="touch-icon-ipad.png"> <link rel="apple-touch-icon" sizes="120x120" href="touch-icon-iphone-retina.png"> <link rel="apple-touch-icon" sizes="152x152" href="touch-icon-ipad-retina.png"> // Nice splash screen ... <link rel="apple-touch-startup-image" href="/startup.png">
  20. Launch like an app (no address bar) <meta name="apple-mobile-web-app-capable" content="yes">

    <meta name="apple-mobile-web-app-status-bar-style" content="black">
  21. Offline available <html manifest="app.cache"> db = openDatabase("Todo", "1", "Todo", 5

    * 1024 * 1024); db.transaction(function(tx) { tx.executeSql("SELECT * FROM CARS)"); }); App Cache Web SQL
  22. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem We have everything we need?
  23. 2012: Early Chrome on Android

  24. Standardised Add to homescreen

  25. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Installable web apps in 2 simple steps { "name": "The Airhorner", "short_name": "Airhorner", "icons": [], "start_url": "index.html", "display": "standalone", "theme_color": "", "background_color": "" } 1. Create a manifest (json) <link rel="manifest" href="/manifest.json"> 2. Link it to your page
  26. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Flow 1. User clicks “Add to home screen” a. Manifest is downloaded 2. Confirmation prompt shown 3. You are on the homescreen
  27. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Flow 1. User clicks “Add to home screen” a. Manifest is downloaded 2. Confirmation prompt shown 3. You are on the homescreen
  28. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Flow 1. User clicks “Add to home screen” a. Manifest is downloaded 2. Confirmation prompt shown 3. You are on the homescreen
  29. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Flow { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  30. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Homescreen { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  31. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem App switcher { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  32. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Splash screen: part 1 { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  33. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem What to Launch { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html?homescreen", // stats.... "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  34. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Splash screen: part 2 { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  35. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem How to launch: standalone { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", "theme_color": "#2196F3", "background_color": "#2196F3" }
  36. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem How to launch: window { "name": "The Air Horner", "short_name": "Air Horner", "icons": [ { "src": "images/Airhorner_192.png", "type": "image/png", "sizes": "192x192" } ], "start_url": "index.html", "display": "standalone", // window "theme_color": "#2196F3", "background_color": "#2196F3" }
  37. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem Tailor user interface if (window.matchMedia('(display-mode: standalone)').matches) { console.log("Thank you for installing our app!"); } @media all and (display-mode: standalone) { body { background-color: yellow; } }
  38. It’s great, but... Native apps can prompt the user to

    install... Proprietary + Confidential
  39. Prompt to Install

  40. Prompting the user is a powerful feature Only sites the

    browser thinks the user will want to re-engage with will get the prompt
  41. Offer an offline experience Have a manifest User is engaged

    Follow Jake’s guidance. • Service Worker • HTTPS Define what to launch and how to launch it. 2 “navigations” within at least 5 minutes* * this will change
  42. Why so strict?

  43. No offline-o-saurus. When you are on the user’s Home screen,

    you should always be available
  44. Why manifest? Don’t launch into a leaf page You need

    to control the launch experience
  45. Why engaged? Don’t spam the users

  46. Can I control it?

  47. Yes onbeforeinstallprompt

  48. Suppress prompt window.addEventListener("beforeinstallprompt", (e) => e.preventDefault() );

  49. Defer to a later time var deferredEvent; window.addEventListener("beforeinstallprompt", (e) =>

    { e.preventDefault(); deferredEvent = e; // show install button } ); button.addEventListener("click", (e) => deferredEvent.prompt(); );
  50. Defer to a later time var deferredEvent; window.addEventListener("beforeinstallprompt", (e) =>

    { e.preventDefault(); deferredEvent = e; // show install button } ); button.addEventListener("click", (e) => deferredEvent.prompt(); );
  51. Understand the user’s choice var deferredEvent; window.addEventListener("beforeinstallprompt", (e) => e.prompt().then((r)

    => r.userChoice) .then((c) => { if(c.choice == "installed") { // Success } else { // The user cancelled } }) );
  52. 2. Push Notifications How can we re-engage users at the

    right time?
  53. 26% increase in average spend per visit by members arriving

    via a push notification 72% increase in time spent for users visiting via a push notification +50% repeat visits within 3 months
  54. Proprietary + Confidential

  55. Add Project ID to the Manifest.json { "short_name": "Air Horner",

    "name": "Air Horner", "start_url": "/", "display": "standalone", "icons": [{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" }], "gcm_sender_id": "123456789012345" }
  56. Add Project ID to the Manifest.json { "short_name": "Air Horner",

    "name": "Air Horner", "start_url": "/", "display": "standalone", "icons": [{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" }], "gcm_sender_id": "123456789012345" }
  57. Setup work for Firefox [This space intentionally blank]

  58. Proprietary + Confidential On the Client

  59. Checking for subscriptions if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(function(reg)

    { reg.pushManager.getSubscription() .then(function(sub) { console.log('Subscription Info', sub); }); }); } else { console.log('Subscription Info', 'Not Supported'); }
  60. Checking for subscriptions if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(function(reg)

    { reg.pushManager.getSubscription() .then(function(sub) { console.log('Subscription Info', sub); }); }); } else { console.log('Subscription Info', 'Not Supported'); }
  61. Checking for subscriptions if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(function(reg)

    { reg.pushManager.getSubscription() .then(function(sub) { console.log('Subscription Info', sub); }); }); } else { console.log('Subscription Info', 'Not Supported'); }
  62. Requesting permission and subscribing function subscribe() { navigator.serviceWorker.getRegistration().then(function(reg) { reg.pushManager.subscribe({userVisibleOnly:

    true}) .then(function(sub) { console.log('Update Server with End Point', sub); }).catch(function(error) { console.log('Unable to subscribe user', error); }); }); }
  63. Requesting permission and subscribing function subscribe() { navigator.serviceWorker.getRegistration().then(function(reg) { reg.pushManager.subscribe({userVisibleOnly:

    true}) .then(function(sub) { console.log('Update Server with End Point', sub); }).catch(function(error) { console.log('Unable to subscribe user', error); }); }); }
  64. Requesting permission and subscribing function subscribe() { navigator.serviceWorker.getRegistration().then(function(reg) { reg.pushManager.subscribe({userVisibleOnly:

    true}) .then(function(sub) { console.log('Update Server with End Point', sub); }).catch(function(error) { console.log('Unable to subscribe user', error); }); }); }
  65. Requesting permission and subscribing function subscribe() { navigator.serviceWorker.getRegistration().then(function(reg) { reg.pushManager.subscribe({userVisibleOnly:

    true}) .then(function(sub) { console.log('Update Server with End Point', sub); }).catch(function(error) { console.log('Unable to subscribe user', error); }); }); }
  66. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem or check out Propel https://github.com/GoogleChrome/propel
  67. Proprietary + Confidential Handling the message

  68. Handle incoming messages self.addEventListener('push', function(event) { event.waitUntil( function() { self.registration.showNotification('Title',

    { body: 'I\'m the message body', icon: 'images/icons.png', tag: 'tag', }); }); });
  69. Handle incoming messages self.addEventListener('push', function(event) { event.waitUntil( function() { self.registration.showNotification('Title',

    { body: 'I\'m the message body', icon: 'images/icons.png', tag: 'tag', }); }); });
  70. 3. Offline That Works™ It isn't an app if it

    doesn't start when you tap.
  71. None
  72. example.com GET /app.html HTTP/1.1 HOST example.com ... HTTP/1.1 200 OK

    Date: Thu, 19 Feb 2015 05:21:56 GMT ...
  73. example.com // sw.js onfetch = function(e) { if(e.request.url == "app.html")

    { e.respondWith( caches.match(e.request) ); } if(e.request.url == "content.json") { // go to the network for updates, // meanwhile, use cached content fetch(...).then(function(r) { r.asJSON().then(function(json) { e.client.postMessage(json); }); }); } }; GET /app.html HTTP/1.1 HOST example.com ... GET /content.json HTTP/1.1 HOST example.com ... GET /content.json HTTP/1.1 HOST example.com ... HTTP/1.1 200 OK Date: Thu, 19 Feb 2015... ...
  74. Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis

    non erat sem or check out sw-toolbox & sw-precache https://github.com/GoogleChrome/sw-toolbox https://github.com/GoogleChrome/sw-precache
  75. Confidential & Proprietary • Reliable: Fast loading, offline and on

    flaky networks • Fast: Smooth animation, scrolling and nav • Engaging and integrated ◦ On the home screen, no URL bar, icons, splash ◦ Re-engaging with push notifications • Consistent experience across browsers (still in progress, though) The Progressive Web App Experience
  76. Kill Your Dinosaur!

  77. Proprietary + Confidential Fin Surma @DasSurma