diff options
-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>'); } |