Service worker

This commit is contained in:
Michel 2018-07-08 10:55:43 +02:00
parent a747fb2362
commit b435128ac0
3 changed files with 177 additions and 24 deletions

View File

@ -3,6 +3,11 @@
namespace MichelSteege\ProgressiveWebApp\Controllers;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use SilverStripe\Core\ClassInfo;
use MichelSteege\ProgressiveWebApp\Interfaces\ServiceWorkerCacheProvider;
class ServiceWorkerController extends Controller {
@ -12,6 +17,11 @@ class ServiceWorkerController extends Controller {
private static $allowed_actions = [
'index'
];
/**
* @config
*/
private static $debug_mode = true;
/**
* Default controller action for the service-worker.js file
@ -22,5 +32,44 @@ class ServiceWorkerController extends Controller {
$this->getResponse()->addHeader('Content-Type', 'application/javascript; charset="utf-8"');
return $this->renderWith('ServiceWorker');
}
/**
* Base URL
* @return varchar
*/
public function BaseUrl() {
return Director::baseURL();
}
/**
* Debug mode
* @return bool
*/
public function DebugMode() {
if(Director::isDev()){
return true;
}
return $this->config()->get('debug_mode');
}
/**
* A list with file to cache in the install event
* @return ArrayList
*/
public function CacheOnInstall() {
$paths = [];
foreach(ClassInfo::implementorsOf(ServiceWorkerCacheProvider::class) as $class){
foreach($class::getServiceWorkerCachedPaths() as $path){
$paths[] = $path;
}
}
$list = new ArrayList();
foreach($paths as $path){
$list->push(new ArrayData([
'Path' => $path
]));
}
return $list;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace MichelSteege\ProgressiveWebApp\Interfaces;
interface ServiceWorkerCacheProvider {
public static function getServiceWorkerCachedPaths();
}

View File

@ -1,26 +1,121 @@
self.addEventListener('install', function(e) {
console.log('Install');
/*
e.waitUntil(
caches.open('airhorner').then(function(cache) {
return cache.addAll([
'/',
'/index.html',
'/index.html?homescreen=1',
'/?homescreen=1',
'/styles/main.css',
'/scripts/main.min.js',
'/sounds/airhorn.mp3'
]);
})
);
*/
});
<%--<script>//Ugly fix for code highlights--%>
var version = 'v1::';
var debug = <% if $DebugMode %>true<% else %>false<% end_if %>;
/**
* Console.log proxy for quick enabling/disabling
*/
function log(msg){
if(debug){
console.log(msg);
}
}
self.addEventListener('fetch', function(event) {
console.log(event.request.url);
});
/**
* Service worker installation
*/
self.addEventListener('install', function (event) {
log('Service worker: install start');
event.waitUntil(caches.open(version + 'fundamentals').then(function (cache) {
//Install all required pages/assets
return cache.addAll([
'$BaseUrl'<% if $CacheOnInstall %>,<% end_if %>
<% if $CacheOnInstall %>
<% loop $CacheOnInstall %>
'$Path'<% if not $Last %>,<% end_if %>
<% end_loop %>
<% end_if %>
]);
}).then(function () {
log('Service worker: install completed');
}).catch(function(){
log('Service worker: install failed');
}));
});
self.addEventListener('beforeinstallprompt', function(event) {
console.log('beforeinstallprompt');
});
/**
* Service worker activation
*/
self.addEventListener('activate', function (event) {
log('Service worker: activate start');
event.waitUntil(caches.keys().then(function (keys) {
//Remove old cache entries
return Promise.all(keys.filter(function (key) {
return !key.startsWith(version);
}).map(function (key) {
return caches.delete(key);
}));
}).then(function () {
log('Service worker: activate completed');
}));
});
/**
* Fetch handler
*/
self.addEventListener('fetch', function (event) {
//We are only interested in get requests
if (event.request.method !== 'GET') {
return;
}
//Parse the url
var requestURL = new URL(event.request.url);
//Test for images
if (/\.(jpg|jpeg|png|gif|webp)$/.test(requestURL.pathname)) {
log('Service worker: skip image ' + event.request.url);
//For now we skip images but change this later to maybe some caching and/or an offline fallback
return;
}
//Check for our own urls
if (requestURL.origin == location.origin) {
//All our own urls are following this route:
//-If there is cache serve from cache but also update the cache from the network
//-If there is no cache then get from the network and put in the cache
//-If both fail fallback to a generic offline message
event.respondWith(caches.match(event.request).then(function (cached) {
var networked = fetch(event.request).then(fetchedFromNetwork, unableToResolve).catch(unableToResolve);
log('Service worker: fetch event ' + (cached ? '(cached)' : '(network)') + ' - ' + event.request.url);
return cached || networked;
/**
* Fetched from network handler
*/
function fetchedFromNetwork(response) {
var cacheCopy = response.clone();
log('Service worker fetch from network - ' + event.request.url);
caches.open(version + 'pages').then(function add(cache) {
cache.put(event.request, cacheCopy);
}).then(function () {
log('Service worker: fetch response stored in cache - ' + event.request.url);
});
return response;
}
/**
* No internet and no cache handler
*/
function unableToResolve(error) {
log('Service worker: fetch request failed in both cache and network ' + error);
return new Response('<h1>Service Unavailable</h1>', {
status: 503,
statusText: 'Service Unavailable',
headers: new Headers({
'Content-Type': 'text/html'
})
});
}
}));
return;
}
//All others, nothing special here
log('Service worker: other url - ' + event.request.url);
event.respondWith(caches.match(event.request).then(function(response) {
return response || fetch(event.request);
}));
});
//<%--Ugly fix for code highlights</script>--%>