aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--perllib/FixMyStreet/App/Controller/Offline.pm9
-rw-r--r--t/app/controller/offline.t12
-rw-r--r--templates/web/base/common_header_tags.html8
-rw-r--r--templates/web/base/offline/fallback.html12
-rw-r--r--templates/web/base/offline/service_worker.html62
-rw-r--r--web/cobrands/fixmystreet/offline.js7
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>');
}