From: Frédéric Péters Date: Tue, 21 Feb 2017 18:16:09 +0000 (+0100) Subject: add progressive web app stuff (manifest & service worker) X-Git-Url: https://git.0d.be/?p=nanofun.git;a=commitdiff_plain;h=a5fca41176db7ee2920f6b9751ddb9e312e9e93c add progressive web app stuff (manifest & service worker) --- diff --git a/README b/README index f998416..5891b17 100644 --- a/README +++ b/README @@ -26,5 +26,9 @@ A copy of the jQuery JavaScript library is included, it's Dual licensed under the MIT and GPL licenses. http://docs.jquery.com/License +Service worker support has been done using this example: + https://github.com/lyzadanger/serviceworker-example/ + MIT-licensed. + External led mode support is based on a sysex dump recorded by the Overtone project; MIT licensed; https://github.com/overtone/overtone/ diff --git a/img/launcher-icon-1x.png b/img/launcher-icon-1x.png new file mode 100644 index 0000000..ca96239 Binary files /dev/null and b/img/launcher-icon-1x.png differ diff --git a/img/launcher-icon-2x.png b/img/launcher-icon-2x.png new file mode 100644 index 0000000..a8a5eee Binary files /dev/null and b/img/launcher-icon-2x.png differ diff --git a/img/launcher-icon-4x.png b/img/launcher-icon-4x.png new file mode 100644 index 0000000..0907d8c Binary files /dev/null and b/img/launcher-icon-4x.png differ diff --git a/img/launcher-icon.svg b/img/launcher-icon.svg new file mode 100644 index 0000000..18f717e --- /dev/null +++ b/img/launcher-icon.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html index 674cd61..140c994 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + nanoFUN diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..1c2af07 --- /dev/null +++ b/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "nanoFUN", + "name": "nanoFUN MIDI sampler", + "icons": [ + { + "src": "img/launcher-icon-1x.png", + "type": "image/png", + "sizes": "48x48" + }, + { + "src": "img/launcher-icon-2x.png", + "type": "image/png", + "sizes": "96x96" + }, + { + "src": "img/launcher-icon-4x.png", + "type": "image/png", + "sizes": "192x192" + } + ], + "start_url": "index.html", + "display": "standalone", + "background_color": "#2196F3", + "theme_color": "#2196F3" +} diff --git a/nanofun.js b/nanofun.js index 627a47b..3fd313d 100644 --- a/nanofun.js +++ b/nanofun.js @@ -407,3 +407,15 @@ var nanofun = function() { } $(function() { nanofun(); }); + +if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register('service-worker.js').then(function(registration) { + // Registration was successful + console.log('ServiceWorker registration successful with scope: ', registration.scope); + }).catch(function(err) { + // registration failed :( + console.log('ServiceWorker registration failed: ', err); + }); + }); +} diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 0000000..4d25f26 --- /dev/null +++ b/service-worker.js @@ -0,0 +1,117 @@ +/* global self, caches, fetch, URL, Response */ +'use strict'; + +var config = { + version: 'v1', + staticCacheItems: [ + 'index.html', + 'jquery.min.js', + 'nanofun.js', + 'nanofun.css' + ] +}; + +function cacheName (key, opts) { + return `${opts.version}-${key}`; +} + +function addToCache (cacheKey, request, response) { + if (response.ok) { + var copy = response.clone(); + caches.open(cacheKey).then( cache => { + cache.put(request, copy); + }); + } + return response; +} + +function fetchFromCache (event) { + return caches.match(event.request).then(response => { + if (!response) { + throw Error(`${event.request.url} not found in cache`); + } + return response; + }); +} + +function offlineResponse (resourceType, opts) { + return undefined; +} + +self.addEventListener('install', event => { + function onInstall (event, opts) { + var cacheKey = cacheName('static', opts); + return caches.open(cacheKey) + .then(cache => cache.addAll(opts.staticCacheItems)); + } + + event.waitUntil( + onInstall(event, config).then( () => self.skipWaiting() ) + ); +}); + +self.addEventListener('activate', event => { + function onActivate (event, opts) { + return caches.keys() + .then(cacheKeys => { + var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0); + var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey)); + return Promise.all(deletePromises); + }); + } + + event.waitUntil( + onActivate(event, config) + .then( () => self.clients.claim() ) + ); +}); + +self.addEventListener('fetch', event => { + + function shouldHandleFetch (event, opts) { + var request = event.request; + var url = new URL(request.url); + var criteria = { + isGETRequest : request.method === 'GET', + isFromMyOrigin : url.origin === self.location.origin + }; + var failingCriteria = Object.keys(criteria) + .filter(criteriaKey => !criteria[criteriaKey]); + return !failingCriteria.length; + } + + function onFetch (event, opts) { + var request = event.request; + var acceptHeader = request.headers.get('Accept'); + var resourceType = 'static'; + var cacheKey; + + if (acceptHeader.indexOf('text/html') !== -1) { + resourceType = 'content'; + } else if (acceptHeader.indexOf('image') !== -1) { + resourceType = 'image'; + } + + cacheKey = cacheName(resourceType, opts); + + if (resourceType === 'content') { + event.respondWith( + fetch(request) + .then(response => addToCache(cacheKey, request, response)) + .catch(() => fetchFromCache(event)) + .catch(() => offlineResponse(resourceType, opts)) + ); + } else { + event.respondWith( + fetchFromCache(event) + .catch(() => fetch(request)) + .then(response => addToCache(cacheKey, request, response)) + .catch(() => offlineResponse(resourceType, opts)) + ); + } + } + if (shouldHandleFetch(event, config)) { + onFetch(event, config); + } + +});