diff --git a/files/en-us/web/progressive_web_apps/app_structure/index.html b/files/en-us/web/progressive_web_apps/app_structure/index.html index a8e917f83a7e36d..4a51cd68b7b429b 100644 --- a/files/en-us/web/progressive_web_apps/app_structure/index.html +++ b/files/en-us/web/progressive_web_apps/app_structure/index.html @@ -120,61 +120,61 @@
The app.js file does a few things we will look into closely in the next articles. First of all it generates the content based on this template:
-var template = "<article>\n\ - <img src='data/img/SLUG.jpg' alt='NAME'>\n\ - <h3>#POS. NAME</h3>\n\ - <ul>\n\ - <li><span>Author:</span> <strong>AUTHOR</strong></li>\n\ - <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li>\n\ - <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\ - <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\ - <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li>\n\ - </ul>\n\ -</article>"; -var content = ''; -for(var i=0; i<games.length; i++) { - var entry = template.replace(/POS/g,(i+1)) - .replace(/SLUG/g,games[i].slug) - .replace(/NAME/g,games[i].name) - .replace(/AUTHOR/g,games[i].author) - .replace(/TWITTER/g,games[i].twitter) - .replace(/WEBSITE/g,games[i].website) - .replace(/GITHUB/g,games[i].github); - entry = entry.replace('<a href=\'http:///\'></a>','-'); - content += entry; -}; +const template = `<article> + <img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'> + <h3>#POS. NAME</h3> + <ul> + <li><span>Author:</span> <strong>AUTHOR</strong></li> + <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li> + <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li> + <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li> + <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li> + </ul> +</article>`; +let content = ''; +for (let i = 0; i < games.length; i++) { + let entry = template.replace(/POS/g, (i + 1)) + .replace(/SLUG/g, games[i].slug) + .replace(/NAME/g, games[i].name) + .replace(/AUTHOR/g, games[i].author) + .replace(/TWITTER/g, games[i].twitter) + .replace(/WEBSITE/g, games[i].website) + .replace(/GITHUB/g, games[i].github); + entry = entry.replace('<a href=\'http:///\'></a>', '-'); + content += entry; +} document.getElementById('content').innerHTML = content;Next, it registers a service worker:
if('serviceWorker' in navigator) { - navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js'); + navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js'); };The next code block requests permission for notifications when a button is clicked:
-var button = document.getElementById("notifications"); -button.addEventListener('click', function(e) { - Notification.requestPermission().then(function(result) { - if(result === 'granted') { - randomNotification(); - } - }); +const button = document.getElementById('notifications'); +button.addEventListener('click', () => { + Notification.requestPermission().then((result) => { + if (result === 'granted') { + randomNotification(); + } + }); });The last block creates notifications that display a randomly-selected item from the games list:
function randomNotification() { - var randomItem = Math.floor(Math.random()*games.length); - var notifTitle = games[randomItem].name; - var notifBody = 'Created by '+games[randomItem].author+'.'; - var notifImg = 'data/img/'+games[randomItem].slug+'.jpg'; - var options = { - body: notifBody, - icon: notifImg - } - var notif = new Notification(notifTitle, options); - setTimeout(randomNotification, 30000); + const randomItem = Math.floor(Math.random() * games.length); + const notifTitle = games[randomItem].name; + const notifBody = `Created by ${games[randomItem].author}.`; + const notifImg = `data/img/${games[randomItem].slug}.jpg`; + const options = { + body: notifBody, + icon: notifImg, + }; + new Notification(notifTitle, options); + setTimeout(randomNotification, 30000); }The service worker
@@ -185,8 +185,8 @@The service worker
Next, it creates a list of all the files to be cached, both from the app shell and the content:
-var cacheName = 'js13kPWA-v1'; -var appShellFiles = [ +const cacheName = 'js13kPWA-v1'; +const appShellFiles = [ '/pwa-examples/js13kpwa/', '/pwa-examples/js13kpwa/index.html', '/pwa-examples/js13kpwa/app.js', @@ -204,41 +204,38 @@+const contentToCache = appShellFiles.concat(gamesImages);The service worker
'/pwa-examples/js13kpwa/icons/icon-168.png', '/pwa-examples/js13kpwa/icons/icon-192.png', '/pwa-examples/js13kpwa/icons/icon-256.png', - '/pwa-examples/js13kpwa/icons/icon-512.png' + '/pwa-examples/js13kpwa/icons/icon-512.png', ]; -var gamesImages = []; -for(var i=0; i<games.length; i++) { - gamesImages.push('data/img/'+games[i].slug+'.jpg'); +const gamesImages = []; +for (let i = 0; i < games.length; i++) { + gamesImages.push(`data/img/${games[i].slug}.jpg`); } -var contentToCache = appShellFiles.concat(gamesImages);The next block installs the service worker, which then actually caches all the files contained in the above list:
-self.addEventListener('install', function(e) { +self.addEventListener('install', (e) => { console.log('[Service Worker] Install'); - e.waitUntil( - caches.open(cacheName).then(function(cache) { - console.log('[Service Worker] Caching all: app shell and content'); - return cache.addAll(contentToCache); - }) - ); + e.waitUntil((async () => { + const cache = await caches.open(cacheName); + console.log('[Service Worker] Caching all: app shell and content'); + await cache.addAll(contentToCache); + })()); });Last of all, the service worker fetches content from the cache if it is available there, providing offline functionality:
-self.addEventListener('fetch', function(e) { - e.respondWith( - caches.match(e.request).then(function(r) { - console.log('[Service Worker] Fetching resource: '+e.request.url); - return r || fetch(e.request).then(function(response) { - return caches.open(cacheName).then(function(cache) { - console.log('[Service Worker] Caching new resource: '+e.request.url); - cache.put(e.request, response.clone()); - return response; - }); - }); - }) - ); +self.addEventListener('fetch', (e) => { + e.respondWith((async () => { + const r = await caches.match(e.request); + console.log(`[Service Worker] Fetching resource: ${e.request.url}`); + if (r) { return r; } + const response = await fetch(e.request); + const cache = await caches.open(cacheName); + console.log(`[Service Worker] Caching new resource: ${e.request.url}`); + cache.put(e.request, response.clone()); + return response; + })()); });The JavaScript data
@@ -246,31 +243,31 @@The JavaScript data
The games data is present in the data folder in a form of a JavaScript object (games.js):
var games = [ - { - slug: 'lost-in-cyberspace', - name: 'Lost in Cyberspace', - author: 'Zosia and Bartek', - twitter: 'bartaz', - website: '', - github: 'github.com/bartaz/lost-in-cyberspace' - }, - { - slug: 'vernissage', - name: 'Vernissage', - author: 'Platane', - twitter: 'platane_', - website: 'github.com/Platane', - github: 'github.com/Platane/js13k-2017' - }, -// ... - { - slug: 'emma-3d', - name: 'Emma-3D', - author: 'Prateek Roushan', - twitter: '', - website: '', - github: 'github.com/coderprateek/Emma-3D' - } + { + slug: 'lost-in-cyberspace', + name: 'Lost in Cyberspace', + author: 'Zosia and Bartek', + twitter: 'bartaz', + website: '', + github: 'github.com/bartaz/lost-in-cyberspace' + }, + { + slug: 'vernissage', + name: 'Vernissage', + author: 'Platane', + twitter: 'platane_', + website: 'github.com/Platane', + github: 'github.com/Platane/js13k-2017' + }, + // ... + { + slug: 'emma-3d', + name: 'Emma-3D', + author: 'Prateek Roushan', + twitter: '', + website: '', + github: 'github.com/coderprateek/Emma-3D' + } ];Every entry has its own image in the data/img folder. This is our content, loaded into the content section with JavaScript.
diff --git a/files/en-us/web/progressive_web_apps/offline_service_workers/index.html b/files/en-us/web/progressive_web_apps/offline_service_workers/index.html index 514c69780e89b10..6bfab1d2984f516 100644 --- a/files/en-us/web/progressive_web_apps/offline_service_workers/index.html +++ b/files/en-us/web/progressive_web_apps/offline_service_workers/index.html @@ -44,7 +44,7 @@Registering the Service Worker
NOTE : We're using the es6 arrow functions syntax in the Service Worker Implementation
if('serviceWorker' in navigator) { - navigator.serviceWorker.register('./pwa-examples/js13kpwa/sw.js'); + navigator.serviceWorker.register('./pwa-examples/js13kpwa/sw.js'); };If the service worker API is supported in the browser, it is registered against the site using the {{domxref("ServiceWorkerContainer.register()")}} method. Its contents reside in the sw.js file, and can be executed after the registration is successful. It's the only piece of Service Worker code that sits inside the app.js file; everything else that is Service Worker-specific is written in the sw.js file itself.
@@ -58,15 +58,15 @@Installation
The API allows us to add event listeners for key events we are interested in — the first one is the
install
event:self.addEventListener('install', (e) => { - console.log('[Service Worker] Install'); + console.log('[Service Worker] Install'); });In the
install
listener, we can initialize the cache and add files to it for offline use. Our js13kPWA app does exactly that.First, a variable for storing the cache name is created, and the app shell files are listed in one array.
-var cacheName = 'js13kPWA-v1'; -var appShellFiles = [ +const cacheName = 'js13kPWA-v1'; +const appShellFiles = [ '/pwa-examples/js13kpwa/', '/pwa-examples/js13kpwa/index.html', '/pwa-examples/js13kpwa/app.js', @@ -89,22 +89,21 @@Installation
Next, the links to images to be loaded along with the content from the data/games.js file are generated in the second array. After that, both arrays are merged using the {{jsxref("Array.prototype.concat()")}} function.
-var gamesImages = []; -for(var i=0; i<games.length; i++) { - gamesImages.push('data/img/'+games[i].slug+'.jpg'); +const gamesImages = []; +for (let i = 0; i < games.length; i++) { + gamesImages.push(`data/img/${games[i].slug}.jpg`); } -var contentToCache = appShellFiles.concat(gamesImages);+const contentToCache = appShellFiles.concat(gamesImages);Then we can manage the
install
event itself:self.addEventListener('install', (e) => { console.log('[Service Worker] Install'); - e.waitUntil( - caches.open(cacheName).then((cache) => { - console.log('[Service Worker] Caching all: app shell and content'); - return cache.addAll(contentToCache); - }) - ); + e.waitUntil((async () => { + const cache = await caches.open(cacheName); + console.log('[Service Worker] Caching all: app shell and content'); + await cache.addAll(contentToCache); + })()); });There are two things that need an explanation here: what {{domxref("ExtendableEvent.waitUntil")}} does, and what the {{domxref("Cache","caches")}} object is.
@@ -126,7 +125,7 @@Responding to fetches
We also have a
fetch
event at our disposal, which fires every time an HTTP request is fired off from our app. This is very useful, as it allows us to intercept requests and respond to them with custom responses. Here is a simple usage example:self.addEventListener('fetch', (e) => { - console.log('[Service Worker] Fetched resource '+e.request.url); + console.log(`[Service Worker] Fetched resource ${e.request.url}`); });The response can be anything we want: the requested file, its cached copy, or a piece of JavaScript code that will do something specific — the possibilities are endless.
@@ -134,18 +133,16 @@Responding to fetches
In our example app, we serve content from the cache instead of the network as long as the resource is actually in the cache. We do this whether the app is online or offline. If the file is not in the cache, the app adds it there first before then serving it:
self.addEventListener('fetch', (e) => { - e.respondWith( - caches.match(e.request).then((r) => { - console.log('[Service Worker] Fetching resource: '+e.request.url); - return r || fetch(e.request).then((response) => { - return caches.open(cacheName).then((cache) => { - console.log('[Service Worker] Caching new resource: '+e.request.url); - cache.put(e.request, response.clone()); - return response; - }); - }); - }) - ); + e.respondWith((async () => { + const r = await caches.match(e.request); + console.log(`[Service Worker] Fetching resource: ${e.request.url}`); + if (r) { return r; } + const response = await fetch(e.request); + const cache = await caches.open(cacheName); + console.log(`[Service Worker] Caching new resource: ${e.request.url}`); + cache.put(e.request, response.clone()); + return response; + })()); });Here, we respond to the fetch event with a function that tries to find the resource in the cache and return the response if it's there. If not, we use another fetch request to fetch it from the network, then store the response in the cache so it will be available there next time it is requested.
@@ -167,11 +164,10 @@Updates
// ... self.addEventListener('install', (e) => { - e.waitUntil( - caches.open('js13kPWA-v2').then((cache) => { - return cache.addAll(contentToCache); - }) - ); + e.waitUntil((async () => { + const cache = await caches.open(cacheName); + await cache.addAll(contentToCache); + })()); });A new service worker is installed in the background, and the previous one (v1) works correctly up until there are no pages using it — the new Service Worker is then activated and takes over management of the page from the old one.
@@ -181,15 +177,13 @@Clearing the cache
Remember the
activate
event we skipped? It can be used to clear out the old cache we don't need anymore:self.addEventListener('activate', (e) => { - e.waitUntil( - caches.keys().then((keyList) => { - return Promise.all(keyList.map((key) => { - if(key !== cacheName) { - return caches.delete(key); - } - })); - }) - ); + e.waitUntil((async () => { + const keyList = await caches.keys(); + await Promise.all(keyList.map((key) => { + if (key === cacheName) { return; } + await caches.delete(key); + })) + })()); });This ensures we have only the files we need in the cache, so we don't leave any garbage behind; the available cache space in the browser is limited, so it is a good idea to clean up after ourselves.
diff --git a/files/en-us/web/progressive_web_apps/re-engageable_notifications_push/index.html b/files/en-us/web/progressive_web_apps/re-engageable_notifications_push/index.html index 04efea27c9c4330..5a2d36f34bef1cb 100644 --- a/files/en-us/web/progressive_web_apps/re-engageable_notifications_push/index.html +++ b/files/en-us/web/progressive_web_apps/re-engageable_notifications_push/index.html @@ -27,14 +27,14 @@Request permission
To show a notification, we have to request permission to do so first. Instead of showing the notification immediately though, best practice dictates that we should show the popup when the user requests it by clicking on a button:
-var button = document.getElementById("notifications"); -button.addEventListener('click', function(e) { - Notification.requestPermission().then(function(result) { - if(result === 'granted') { - randomNotification(); - } - }); -});+const button = document.getElementById('notifications'); +button.addEventListener('click', () => { + Notification.requestPermission().then((result) => { + if (result === 'granted') { + randomNotification(); + } + }); +})This shows a popup using the operating system’s own notifications service:
@@ -49,16 +49,16 @@Create a notification
The example app creates a notification out of the available data — a game is picked at random, and the chosen one feeds the notification with the content: it sets the game's name as the title, mentioning the author in the body, and showing the image as an icon:
function randomNotification() { - var randomItem = Math.floor(Math.random()*games.length); - var notifTitle = games[randomItem].name; - var notifBody = 'Created by '+games[randomItem].author+'.'; - var notifImg = 'data/img/'+games[randomItem].slug+'.jpg'; - var options = { - body: notifBody, - icon: notifImg - } - var notif = new Notification(notifTitle, options); - setTimeout(randomNotification, 30000); + const randomItem = Math.floor(Math.random() * games.length); + const notifTitle = games[randomItem].name; + const notifBody = `Created by ${games[randomItem].author}.`; + const notifImg = `data/img/${games[randomItem].slug}.jpg`; + const options = { + body: notifBody, + icon: notifImg, + }; + new Notification(notifTitle, options); + setTimeout(randomNotification, 30000); }A new random notification is created every 30 seconds until it becomes too annoying and is disabled by the user. (For a real app, the notifications should be much less frequent, and more useful.) The advantage of the Notifications API is that it uses the operating system's notification functionality. This means that notifications can be displayed to the user even when they are not looking at the web app, and the notifications look similar to ones displayed by native apps.
@@ -71,7 +71,7 @@Push
As mentioned before, to be able to receive push messages, you have to have a service worker, the basics of which are already explained in the Making PWAs work offline with Service workers article. Inside the service worker, a push service subscription mechanism is created.
-registration.pushManager.getSubscription() .then( /* ... */ );+registration.pushManager.getSubscription().then( /* ... */ );Once the user is subscribed, they can receive push notifications from the server.
@@ -79,7 +79,7 @@Push
To receive push messages, we can listen to the {{event("push")}} event in the Service Worker file:
-self.addEventListener('push', function(e) { /* ... */ });+self.addEventListener('push', (e) => { /* ... */ });The data can be retrieved and then shown as a notification to the user immediately. This, for example, can be used to remind the user about something, or let them know about new content being available in the app.
@@ -102,22 +102,22 @@index.js
The
index.js
file starts by registering the service worker:navigator.serviceWorker.register('service-worker.js') -.then(function(registration) { - return registration.pushManager.getSubscription() - .then(async function(subscription) { - // registration part - }); -}) -.then(function(subscription) { + .then((registration) => { + return registration.pushManager.getSubscription() + .then(async (subscription) => { + // registration part + }); + }) + .then((subscription) => { // subscription part -});+ });It is a little bit more complicated than the service worker we saw in the js13kPWA demo. In this particular case, after registering, we use the registration object to subscribe, and then use the resulting subscription object to complete the whole process.
In the registration part, the code looks like this:
if(subscription) { - return subscription; + return subscription; }If the user has already subscribed, we then return the subscription object and move to the subscription part. If not, we initialize a new subscription:
@@ -131,41 +131,41 @@index.js
The app can now use the {{domxref("PushManager")}} to subscribe the new user. There are two options passed to the {{domxref("PushManager.subscribe()")}} method — the first is
userVisibleOnly: true
, which means all the notifications sent to the user will be visible to them, and the second one is theapplicationServerKey
, which contains our successfully acquired and converted VAPID key.return registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: convertedVapidKey + userVisibleOnly: true, + applicationServerKey: convertedVapidKey });Now let's move to the subscription part — the app first sends the subscription details as JSON to the server using Fetch.
fetch('./register', { - method: 'post', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify({ - subscription: subscription - }), + method: 'post', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify({ + subscription: subscription + }), });Then the {{domxref("onclick","GlobalEventHandlers.onclick")}} function on the Subscribe button is defined:
document.getElementById('doIt').onclick = function() { - const payload = document.getElementById('notification-payload').value; - const delay = document.getElementById('notification-delay').value; - const ttl = document.getElementById('notification-ttl').value; - - fetch('./sendNotification', { - method: 'post', - headers: { - 'Content-type': 'application/json' - }, - body: JSON.stringify({ - subscription: subscription, - payload: payload, - delay: delay, - ttl: ttl, - }), - }); + const payload = document.getElementById('notification-payload').value; + const delay = document.getElementById('notification-delay').value; + const ttl = document.getElementById('notification-ttl').value; + + fetch('./sendNotification', { + method: 'post', + headers: { + 'Content-type': 'application/json' + }, + body: JSON.stringify({ + subscription: subscription, + payload: payload, + delay: delay, + ttl: ttl, + }), + }); };When the button is clicked,
@@ -231,12 +231,12 @@fetch
asks the server to send the notification with the given parameters:payload
is the text that to be shown in the notification,delay
defines a delay in seconds until the notification will be shown, andttl
is the time-to-live setting that keeps the notification available on the server for a specified amount of time, also defined in seconds.service-worker.js
The last file we will look at is the service worker:
self.addEventListener('push', function(event) { - const payload = event.data ? event.data.text() : 'no payload'; - event.waitUntil( - self.registration.showNotification('ServiceWorker Cookbook', { - body: payload, - }) - ); + const payload = event.data ? event.data.text() : 'no payload'; + event.waitUntil( + self.registration.showNotification('ServiceWorker Cookbook', { + body: payload, + }) + ); });All it does is add a listener for the {{event("push")}} event, create the payload variable consisting of the text taken from the data (or create a string to use if data is empty), and then wait until the notification is shown to the user.
diff --git a/files/en-us/web/progressive_web_apps/structural_overview/index.html b/files/en-us/web/progressive_web_apps/structural_overview/index.html index 16659d467a8b2df..031bfd41e875c4f 100644 --- a/files/en-us/web/progressive_web_apps/structural_overview/index.html +++ b/files/en-us/web/progressive_web_apps/structural_overview/index.html @@ -151,68 +151,62 @@app.js: The main app JavaScript
The first thing it does is to generate the app's displayed content using the following template:
-var template = "<article>\n\ - <img src='data/img/SLUG.jpg' alt='NAME'>\n\ - <h3>#POS. NAME</h3>\n\ - <ul>\n\ - <li><span>Author:</span> <strong>AUTHOR</strong></li>\n\ - <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'> - @TWITTER</a></li>\n\ - <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\ - <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\ - <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'> - js13kgames.com/entries/SLUG</a></li>\n\ - </ul>\n\ -</article>"; -var content = ''; -for(var i=0; i<games.length; i++) { - var entry = template.replace(/POS/g,(i+1)) - .replace(/SLUG/g,games[i].slug) - .replace(/NAME/g,games[i].name) - .replace(/AUTHOR/g,games[i].author) - .replace(/TWITTER/g,games[i].twitter) - .replace(/WEBSITE/g,games[i].website) - .replace(/GITHUB/g,games[i].github); - entry = entry.replace('<a href=\'http:///\'></a>','-'); - content += entry; -}; -document.getElementById('content').innerHTML = content; -+const template = `<article> + <img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'> + <h3>#POS. NAME</h3> + <ul> + <li><span>Author:</span> <strong>AUTHOR</strong></li> + <li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li> + <li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li> + <li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li> + <li><span>More:</span> <a href='http://js13kgames.com/entries/SLUG'>js13kgames.com/entries/SLUG</a></li> + </ul> +</article>`; +let content = ''; +for (let i = 0; i < games.length; i++) { + let entry = template.replace(/POS/g, (i + 1)) + .replace(/SLUG/g, games[i].slug) + .replace(/NAME/g, games[i].name) + .replace(/AUTHOR/g, games[i].author) + .replace(/TWITTER/g, games[i].twitter) + .replace(/WEBSITE/g, games[i].website) + .replace(/GITHUB/g, games[i].github); + entry = entry.replace('<a href=\'http:///\'></a>', '-'); + content += entry; +} +document.getElementById('content').innerHTML = content;Then it registers a service worker:
if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/pwa-examples/js13kpwa/sw.js"); -} -+}After that, the app adds a handler for clicks on a button whose ID is
-notifications
; this handler requests permission to send notifications to the user, then generates and sends a random notification.var button = document.getElementById("notifications"); -button.addEventListener('click', function(e) { - Notification.requestPermission().then(function(result) { - if (result === 'granted') { - randomNotification(); - } - }); -}); -+const button = document.getElementById('notifications'); +button.addEventListener('click', () => { + Notification.requestPermission().then((result) => { + if (result === 'granted') { + randomNotification(); + } + }); +});The
randomNotification()
function follows, rounding out the last of the code in the file:function randomNotification() { - var randomItem = Math.floor(Math.random()*games.length); - var notifTitle = games[randomItem].name; - var notifBody = 'Created by '+games[randomItem].author+'.'; - var notifImg = 'data/img/'+games[randomItem].slug+'.jpg'; - var options = { - body: notifBody, - icon: notifImg - } - var notif = new Notification(notifTitle, options); - setTimeout(randomNotification, 30000); -} -+ const randomItem = Math.floor(Math.random() * games.length); + const notifTitle = games[randomItem].name; + const notifBody = `Created by ${games[randomItem].author}.`; + const notifImg = `data/img/${games[randomItem].slug}.jpg`; + const options = { + body: notifBody, + icon: notifImg, + }; + new Notification(notifTitle, options); + setTimeout(randomNotification, 30000); +}The service worker
@@ -222,102 +216,95 @@The service worker
Then it creates a list of all the files that the service worker needs to cache. This list includes both app shell and content files:
-var cacheName = 'js13kPWA-v1'; -var appShellFiles = [ - '/pwa-examples/js13kpwa/', - '/pwa-examples/js13kpwa/index.html', - '/pwa-examples/js13kpwa/app.js', - '/pwa-examples/js13kpwa/style.css', - '/pwa-examples/js13kpwa/fonts/graduate.eot', - '/pwa-examples/js13kpwa/fonts/graduate.ttf', - '/pwa-examples/js13kpwa/fonts/graduate.woff', - '/pwa-examples/js13kpwa/favicon.ico', - '/pwa-examples/js13kpwa/img/js13kgames.png', - '/pwa-examples/js13kpwa/img/bg.png', - '/pwa-examples/js13kpwa/icons/icon-32.png', - '/pwa-examples/js13kpwa/icons/icon-64.png', - '/pwa-examples/js13kpwa/icons/icon-96.png', - '/pwa-examples/js13kpwa/icons/icon-128.png', - '/pwa-examples/js13kpwa/icons/icon-168.png', - '/pwa-examples/js13kpwa/icons/icon-192.png', - '/pwa-examples/js13kpwa/icons/icon-256.png', - '/pwa-examples/js13kpwa/icons/icon-512.png' +const cacheName = 'js13kPWA-v1'; +const appShellFiles = [ + '/pwa-examples/js13kpwa/', + '/pwa-examples/js13kpwa/index.html', + '/pwa-examples/js13kpwa/app.js', + '/pwa-examples/js13kpwa/style.css', + '/pwa-examples/js13kpwa/fonts/graduate.eot', + '/pwa-examples/js13kpwa/fonts/graduate.ttf', + '/pwa-examples/js13kpwa/fonts/graduate.woff', + '/pwa-examples/js13kpwa/favicon.ico', + '/pwa-examples/js13kpwa/img/js13kgames.png', + '/pwa-examples/js13kpwa/img/bg.png', + '/pwa-examples/js13kpwa/icons/icon-32.png', + '/pwa-examples/js13kpwa/icons/icon-64.png', + '/pwa-examples/js13kpwa/icons/icon-96.png', + '/pwa-examples/js13kpwa/icons/icon-128.png', + '/pwa-examples/js13kpwa/icons/icon-168.png', + '/pwa-examples/js13kpwa/icons/icon-192.png', + '/pwa-examples/js13kpwa/icons/icon-256.png', + '/pwa-examples/js13kpwa/icons/icon-512.png', ]; -var gamesImages = []; -for(var i=0; i<games.length; i++) { - gamesImages.push('data/img/'+games[i].slug+'.jpg'); +const gamesImages = []; +for (let i = 0; i < games.length; i++) { + gamesImages.push(`data/img/${games[i].slug}.jpg`); } -var contentToCache = appShellFiles.concat(gamesImages); -+const contentToCache = appShellFiles.concat(gamesImages);With the file list prepared, it's time to install the service worker itself. The service worker will actually handle caching all the listed files.
-self.addEventListener('install', function(e) { - console.log('[Service Worker] Install'); - e.waitUntil( - caches.open(cacheName).then(function(cache) { - console.log('[Service Worker] Caching all: app shell and content'); - return cache.addAll(contentToCache); - }) - ); -}); -+self.addEventListener('install', (e) => { + console.log('[Service Worker] Install'); + e.waitUntil((async () => { + const cache = await caches.open(cacheName); + console.log('[Service Worker] Caching all: app shell and content'); + await cache.addAll(contentToCache); + })()); +});With that done, we implement the service worker's fetch event handler; its job is to return the contents of the specified file, either from the cache or by loading it over the network (caching it upon doing so):
-self.addEventListener('fetch', function(e) { - e.respondWith( - caches.match(e.request).then(function(r) { - console.log('[Service Worker] Fetching resource: '+e.request.url); - return r || fetch(e.request).then(function(response) { - return caches.open(cacheName).then(function(cache) { - console.log('[Service Worker] Caching new resource: '+e.request.url); - cache.put(e.request, response.clone()); - return response; - }); - }); - }) - ); -}); -+self.addEventListener('fetch', (e) => { + e.respondWith((async () => { + const r = await caches.match(e.request); + console.log(`[Service Worker] Fetching resource: ${e.request.url}`); + if (r) { return r; } + const response = await fetch(e.request); + const cache = await caches.open(cacheName); + console.log(`[Service Worker] Caching new resource: ${e.request.url}`); + cache.put(e.request, response.clone()); + return response; + })()); +});Auxiliary JavaScript file: games.js
The games data for this app example is provided in a JavaScript source file called
games.js
. Other apps might use JSON or other formats for this data.var games = [ - { - slug: 'lost-in-cyberspace', - name: 'Lost in Cyberspace', - author: 'Zosia and Bartek', - twitter: 'bartaz', - website: '', - github: 'github.com/bartaz/lost-in-cyberspace' - }, - { - slug: 'vernissage', - name: 'Vernissage', - author: 'Platane', - twitter: 'platane_', - website: 'github.com/Platane', - github: 'github.com/Platane/js13k-2017' - }, -// ... - { - slug: 'emma-3d', - name: 'Emma-3D', - author: 'Prateek Roushan', - twitter: '', - website: '', - github: 'github.com/coderprateek/Emma-3D' - } -]; -+ { + slug: 'lost-in-cyberspace', + name: 'Lost in Cyberspace', + author: 'Zosia and Bartek', + twitter: 'bartaz', + website: '', + github: 'github.com/bartaz/lost-in-cyberspace' + }, + { + slug: 'vernissage', + name: 'Vernissage', + author: 'Platane', + twitter: 'platane_', + website: 'github.com/Platane', + github: 'github.com/Platane/js13k-2017' + }, + // ... + { + slug: 'emma-3d', + name: 'Emma-3D', + author: 'Prateek Roushan', + twitter: '', + website: '', + github: 'github.com/coderprateek/Emma-3D' + } +];Each entry in the array
games
describes a specific game, and has a corresponding image file in thedata/img/
directory. This is the content we'll load into thecontent
section of the page using JavaScript code.See also