aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew@mysociety.org>2019-12-19 09:23:21 +0000
committerMatthew Somerville <matthew@mysociety.org>2020-02-14 10:38:49 +0000
commit0e80a90bebc12fe381892386f9ffd751edb38d7c (patch)
tree50f5ac13f745f6142c9b6cab726dacda2fb447fe
parent43290e5f733b4e88c6b4e5467b7e446a416a4682 (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.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>');
}