diff options
author | Matthew Somerville <matthew@mysociety.org> | 2019-12-19 09:23:21 +0000 |
---|---|---|
committer | Matthew Somerville <matthew@mysociety.org> | 2020-02-14 10:38:49 +0000 |
commit | 0e80a90bebc12fe381892386f9ffd751edb38d7c (patch) | |
tree | 50f5ac13f745f6142c9b6cab726dacda2fb447fe | |
parent | 43290e5f733b4e88c6b4e5467b7e446a416a4682 (diff) |
Initial service worker.
This basic service worker behaves identically to the existing appcache -
some static scripts and CSS are cached, any HTML offline instead returns
a static HTML page that knows how to show data on stored problems out of
localStorage (stored there when /my/planned was visited online). Inspect
form submissions will be captured and can be synced back when online.
Once feature parity is established, we will then remove appcache, switch
from using localStorage to the cache API, and hopefully move all offline
support into the service worker.
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Offline.pm | 9 | ||||
-rw-r--r-- | t/app/controller/offline.t | 12 | ||||
-rw-r--r-- | templates/web/base/common_header_tags.html | 8 | ||||
-rw-r--r-- | templates/web/base/offline/fallback.html | 12 | ||||
-rw-r--r-- | templates/web/base/offline/service_worker.html | 62 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/offline.js | 7 |
7 files changed, 108 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c710e1e03..38cafc2ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ - Display map inline with duplicate suggestions on mobile. #2668 - Improved try again process on mobile. #2863 - Improve messaging/display of private reports. - - Add a web manifest. #2220 + - Add a web manifest and service worker. #2220 - Admin improvements: - Add new roles system, to group permissions and apply to users. #2483 - Contact form emails now include user admin links. diff --git a/perllib/FixMyStreet/App/Controller/Offline.pm b/perllib/FixMyStreet/App/Controller/Offline.pm index e65642899..d8b9180dd 100644 --- a/perllib/FixMyStreet/App/Controller/Offline.pm +++ b/perllib/FixMyStreet/App/Controller/Offline.pm @@ -20,6 +20,15 @@ Offline pages Catalyst Controller - service worker and appcache. =cut +sub service_worker : Path("/service-worker.js") { + my ($self, $c) = @_; + $c->res->content_type('application/javascript'); +} + +sub fallback : Local { + my ($self, $c) = @_; +} + sub manifest: Path("/.well-known/manifest.webmanifest") { my ($self, $c) = @_; $c->res->content_type('application/manifest+json'); diff --git a/t/app/controller/offline.t b/t/app/controller/offline.t index 505e605a0..d2a5009ec 100644 --- a/t/app/controller/offline.t +++ b/t/app/controller/offline.t @@ -33,4 +33,16 @@ FixMyStreet::override_config { }; }; +subtest 'service worker' => sub { + $mech->get_ok('/service-worker.js'); + $mech->content_contains('translation_strings'); + $mech->content_contains('offline/fallback'); +}; + +subtest 'offline fallback page' => sub { + $mech->get_ok('/offline/fallback'); + $mech->content_contains('Offline'); + $mech->content_contains('offline_list'); +}; + done_testing();
\ No newline at end of file diff --git a/templates/web/base/common_header_tags.html b/templates/web/base/common_header_tags.html index d54d97297..279f561df 100644 --- a/templates/web/base/common_header_tags.html +++ b/templates/web/base/common_header_tags.html @@ -16,6 +16,14 @@ (function(a){a=a.documentElement;a.className=a.className.replace(/\bno-js\b/,"js");var b=-1<a.className.indexOf("ie8");b=Modernizr.mq("(min-width: 48em)")||b?"desktop":"mobile";"IntersectionObserver"in window&&(a.className+=" lazyload");"mobile"==b&&(a.className+=' mobile[% " map-fullscreen only-map map-reporting" IF page == "around" %]')})(document); </script> +<script nonce="[% csp_nonce %]"> +if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register('/service-worker.js'); + }); +} +</script> + [% IF robots %] <meta name="robots" content="[% robots %]"> [% ELSIF c.config.STAGING_SITE %] diff --git a/templates/web/base/offline/fallback.html b/templates/web/base/offline/fallback.html new file mode 100644 index 000000000..b8e1ee9b9 --- /dev/null +++ b/templates/web/base/offline/fallback.html @@ -0,0 +1,12 @@ +[% SET bodyclass = "fullwidthpage offlinepage" ~%] +[% INCLUDE 'header.html' %] + +<h1>[% loc('Offline') %]</h1> + +<p>[% loc('Sorry, we don’t have a good enough connection to fetch that page.') %]</p> + +<ul class="item-list item-list--reports" id="offline_list"></ul> + +<div id="offline_clear"></div> + +[% INCLUDE 'footer.html' %] diff --git a/templates/web/base/offline/service_worker.html b/templates/web/base/offline/service_worker.html new file mode 100644 index 000000000..1005e959b --- /dev/null +++ b/templates/web/base/offline/service_worker.html @@ -0,0 +1,62 @@ +[% +SET bodyclass = "offlinepage"; # For selection of scripts +PROCESS 'common_scripts.html'; +SET offline_html = version('../templates/web/base/offline/fallback.html', '/offline/fallback'); +SET scripts_seen = {}; + +~%] + +const requiredOffline = [ + "[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/base.css') %]", + "[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/layout.css') %]", + "[% version('/vendor/OpenLayers/theme/default/style.css') %]", + "[% version('/vendor/fancybox/jquery.fancybox-1.3.4.css') %]", + [% + FOR script IN scripts; + NEXT IF scripts_seen.${script}; + scripts_seen.${script} = 1; + ~%] + "[%- script %]", + [% END %] + "[% offline_html %]" +]; + +const staticCache = 'static'; + +addEventListener('install', function(evt) { + evt.waitUntil(precache()); +}); + +async function precache() { + const cache = await caches.open(staticCache); + return cache.addAll(requiredOffline); +} + +addEventListener('fetch', fetchEvent => { + const request = fetchEvent.request; + const url = new URL(request.url); + + if (request.method !== "GET" || url.origin !== location.origin) { + return; + } + + fetchEvent.respondWith(async function() { + if (request.mode === 'navigate') { + const fetchPromise = fetch(request); + + try { + return await fetchPromise; + } + catch { + let cached = await caches.match("[% offline_html %]"); + return cached || offlineResponse(); + } + } else { + const responseFromCache = await caches.match(request); + return responseFromCache || fetch(request); + } + }()); +}); + +var offlineResponse = () => + new Response('Service Unavailable', { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'text/html' }});
\ No newline at end of file diff --git a/web/cobrands/fixmystreet/offline.js b/web/cobrands/fixmystreet/offline.js index 4d7c471aa..648375afd 100644 --- a/web/cobrands/fixmystreet/offline.js +++ b/web/cobrands/fixmystreet/offline.js @@ -411,9 +411,10 @@ if ($('#offline_list').length) { } fixmystreet.offlineBanner.make(true); } else { - // Put the appcache manifest in a page in an iframe so that HTML pages - // aren't cached (thanks to Jake Archibald for documenting this!) - if (window.applicationCache && window.localStorage) { + // If we're using appcache, not a service worker, put the appcache manifest + // in a page in an iframe so that HTML pages aren't cached + // (thanks to Jake Archibald for documenting this!) + if (!('serviceWorker' in navigator) && window.applicationCache && window.localStorage) { $(document.body).prepend('<iframe src="/offline/appcache" style="position:absolute;top:-999em;visibility:hidden"></iframe>'); } |