Offline PWA works again!

This commit is contained in:
Matteo Gheza 2021-06-26 18:08:00 +02:00
parent 9f1ee8ecd6
commit 6e24a5c760
11 changed files with 126 additions and 120 deletions

View File

@ -98,7 +98,7 @@
<FilesMatch "\.(js|css|woff|woff2|ttf|eot)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
<FilesMatch "\\.(jpe?g|png|gif|swf|flv|pdf|svg)$">
<FilesMatch "\.(jpe?g|png|gif|swf|flv|pdf|svg|ico)$">
Header set Cache-Control "max-age=604800, public"
</FilesMatch>
</IfModule>

View File

@ -760,7 +760,6 @@ class crud
$this->user->log("Service removed");
}
public function edit_service($id, $date, $code, $beginning, $end, $chief, $drivers, $crew, $place, $notes, $type, $increment, $inserted_by)
{
$this->remove_service($id);
@ -936,7 +935,7 @@ function init_class($enableDebugger=true, $headers=true)
$csp_rules[] = "report-uri ".SENTRY_CSP_REPORT_URI;
}
$csp = implode("; ", $csp_rules);
if(!isset($_COOKIE["JSless"]) && (isset($_GET["JSless"]) ? !$_GET["JSless"] : true)){
if((isset($_GET["JSless"]) ? !$_GET["JSless"] : true) && !strpos($_SERVER["PHP_SELF"], "offline.php")){
header("Content-Security-Policy: $csp");
header("X-XSS-Protection: 1; mode=block");
header("X-Content-Type-Options: nosniff");

View File

@ -129,7 +129,7 @@ $(document).ajaxError(function (event, xhr, settings, error) {
});
if (getCookie("authenticated")) {
var installServiceWorker = false;
var installServiceWorker = true;
if (window.skipServiceWorkerInstallation !== undefined) { // if you want to disable SW for example via GreasyFork userscript
installServiceWorker = false;
}

View File

@ -1,106 +1,74 @@
const cacheVersion = process.env.BUNDLE_DATE;
const cacheName = "static-" + cacheVersion;
const expectedCaches = [cacheName, "tables-1"];
//Code taken from https://googlechrome.github.io/samples/service-worker/custom-offline-page/ (and edited)
const urls = ["offline.php", "manifest.webmanifest", "resources/images/favicon.ico", "resources/dist/marker-icon.png", "resources/dist/layers.png", "resources/dist/layers-2x.png", "resources/images/android-chrome-192x192.png", "resources/images/android-chrome-384x384.png", "resources/images/black_helmet.png", "resources/images/red_helmet.png", "resources/images/wheel.png", "resources/images/logo.png", "resources/images/owner.png", "resources/dist/fonts/fontawesome-webfont.woff2"];
const CACHE_NAME = 'offline';
const RESOURCES = ["offline.php", "manifest.webmanifest"];
function fetchHandler (event, contentType, notFoundMessage) {
// TODO: refactoring
self.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
for(let resource of RESOURCES){
console.log(resource);
await cache.add(new Request(resource, {cache: 'reload'}));
}
await fetch("resources/dist/assets-manifest.json")
.then((response) => response.json())
.then((manifest) => {
console.log(manifest);
const scriptsRequired = ["main.js", "src_table_engine_default_js.bundle.js"];
scriptsRequired.map((scriptName) => {
console.log(scriptName);
cache.add(new Request("resources/dist/" + manifest[scriptName]["src"], {cache: 'reload'}));
});
});
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
console.log(event);
// FROM https://googlechrome.github.io/samples/service-worker/custom-offline-page/
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
if (event.request.mode === "navigate") {
event.respondWith((async () => {
console.log("respond with");
try {
// First, try to use the navigation preload response if it's supported.
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
console.log("network response");
return networkResponse;
} catch (error) {
// catch is only triggered if an exception is thrown, which is likely
// due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
console.log("Fetch failed; returning offline page instead.", error);
const cache = await caches.open(cacheName);
if (event.request.headers.get("Accept").includes("text/html")) {
cacheFileName = "offline.php";
} else {
cacheFileName = event.request.url;
}
const cachedResponse = await cache.match(cacheFileName);
return cachedResponse;
event.respondWith((async () => {
try {
// First, try to use the navigation preload response if it's supported.
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
})());
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// catch is only triggered if an exception is thrown, which is likely
// due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
console.log('Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
let cache_element_name = event.request.url;
if (event.request.mode === "navigate") {
cache_element_name = "offline.php";
}
console.log('Cache element name:', cache_element_name);
const cachedResponse = await cache.match(cache_element_name);
return cachedResponse;
}
})());
// If our if() condition is false, then this fetch handler won't intercept the
// request. If there are any other fetch handlers registered, they will get a
// chance to call event.respondWith(). If no fetch handlers call
// event.respondWith(), the request will be handled by the browser as if there
// were no service worker involvement.
}
self.addEventListener("fetch", function (event) {
const request = event.request;
// https://stackoverflow.com/a/49719964
if (event.request.cache === "only-if-cached" && event.request.mode !== "same-origin") return;
if (request.headers.get("Accept").includes("text/html")) {
fetchHandler(event, null, "offline.php");
} else if (request.destination === "script") {
fetchHandler(event, "application/javascript", "console.error('Script " + event.request.url + " not found');");
} else if (request.destination === "image") {
fetchHandler(event, null, "resources/images/logo.png");
} else if (request.destination === "font") {
fetchHandler(event, null, null);
} else if (request.destination === "manifest" || request.url.includes("manifest")) {
fetchHandler(event, null, "manifest.webmanifest");
} else {
event.respondWith(fetch(request));
}
});
self.addEventListener("install", (event) => {
self.skipWaiting();
event.waitUntil(
caches.open(cacheName).then((cache) => {
cache.addAll(urls);
fetch("resources/dist/assets-manifest.json")
.then((response) => response.json())
.then((manifest) => {
const scriptsRequired = ["main.js", "maps.js"];
scriptsRequired.map((scriptName) => {
console.log(manifest);
console.log(scriptName);
cache.add("resources/dist/" + manifest[scriptName]["src"]);
});
});
})
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) => Promise.all(
keys.map((key) => {
if (!expectedCaches.includes(key)) {
console.log("Deleting cache " + key);
return caches.delete(key);
}
})
)).then(() => {
console.log("Service worker now ready to handle fetches!");
})
);
});
});

View File

@ -95,7 +95,8 @@ module.exports = {
integrity: true,
customize(entry) {
allowed_entries = ["main.js", "maps.js", "players.js", "games.js"]
if (entry.key.startsWith('fonts') || allowed_entries.includes(entry.key)) {
if (entry.key.startsWith('fonts') || entry.key.includes("table_engine") || allowed_entries.includes(entry.key)) {
entry.key = entry.key.split("?")[0];
return entry;
}
return false;

View File

@ -1 +1 @@
{% extends "list.html" %}
If you are using an old browser that doesn't support JS, you can't use Service Workers, so this page is useless.

View File

@ -48,7 +48,7 @@
</script>
{% endif %}
{{ script('main.js') }}
<script nonce="{{ nonce }}">$.fn.loading.defaults.message = "{{ 'Loading...'|t }}";</script>
<script{% if not is_offline_page %} nonce="{{ nonce }}"{% endif %}>$.fn.loading.defaults.message = "{{ 'Loading...'|t }}";</script>
{% endblock %}
{% if enable_debug_bar %}{{ debug_bar_head|raw }}{% endif %}
</head>

View File

@ -1,12 +1,15 @@
{% extends "base.html" %}
{% block menu %}
{% endblock %}
{% block content %}
<br>
<br>
<img alt="VVF" src="./resources/images/owner.png" width="150" style="display: block; margin-left: auto; margin-right: auto;">
<br>
<br>
<div id="list" class="table-responsive">
<br>
<img alt="VVF" src="./resources/images/owner.png" width="150"
style="display: block; margin-left: auto; margin-right: auto;">
<br>
<br>
<div style="display: none" id="list" class="table-responsive">
<table id="table" class="table table-striped table-bordered dt-responsive nowrap">
<thead>
<tr>
@ -18,17 +21,46 @@
<th>{{ 'Write'|t }}</th>
<th>{{ 'Services'|t }}</th>
<th>{{ 'Availability Minutes'|t }}</th>
<th>{{ 'Other'|t }}</th>
{# <th>{{ 'Other'|t }}</th> TODO: fix "Other" page #}
{% endif %}
</tr>
</thead>
<tbody id="table_body">
</tbody>
</table>
<script nonce="{{ nonce }}">
allertaJS.main.loadTable("list", true, 20000, true);
</script>
</div>
<br>
<br>
<br><br>
<div class="d-flex justify-content-center">
<button style="display: none; margin-left: 5%;" id="delete_cache_btn" type="button" class="btn btn-danger">{{ 'Delete the offline version of Allerta-VVF from this device.'|t }}</button>
</div>
<div class="d-flex justify-content-center">
<div style="display: none" id="cache_empty_msg" class="alert alert-danger" role="alert">
<p>{{ 'You have cleared the cache; the table will be loaded when the device is connected to the Internet.'|t }}</p>
</div>
</div>
<script>
allertaJS.main.loadTable({tablePage: "list", useCustomTableEngine: "default", callback: () => {
console.log("Callback executed");
document.querySelector("#list").style.display = 'block';
document.querySelector("#delete_cache_btn").style.display = 'block';
document.querySelector("#cache_empty_msg").style.display = 'none';
allertaJS.main.loadListListCallback();
}});
document.addEventListener("DOMContentLoaded", async () => {
if(await caches.has("tables-1")){
document.querySelector("#list").style.display = 'block';
document.querySelector("#delete_cache_btn").style.display = 'block';
} else {
document.querySelector("#cache_empty_msg").style.display = 'block';
}
});
document.querySelector("#delete_cache_btn").addEventListener("click", async () => {
await caches.delete("tables-1");
location.reload();
});
</script>
{% endblock %}

View File

@ -130,5 +130,7 @@ return [
"You are not authorized to perform this action." => "You are not authorized to perform this action.",
"Bad request." => "Bad request.",
"User not exists." => "User not exists.",
"Change" => "Change"
"Change" => "Change",
"Delete the offline version of Allerta-VVF from this device." => "Delete the offline version of Allerta-VVF from this device.",
"You have cleared the cache; the table will be loaded when the device is connected to the Internet." => "You have cleared the cache; the table will be loaded when the device is connected to the Internet."
];

View File

@ -130,5 +130,7 @@ return [
"You are not authorized to perform this action." => "Non sei autorizzato ad eseguire questa azione.",
"Bad request." => "Errore nella richiesta.",
"User not exists." => "L'utente non esiste.",
"Change" => "Cambia"
"Change" => "Cambia",
"Delete the offline version of Allerta-VVF from this device." => "Cancella la versione offline di Allerta-VVF da questo dispositivo.",
"You have cleared the cache; the table will be loaded when the device is connected to the Internet." => "Hai svuotato la cache; la tabella verrà caricata quando il dispositivo sarà connesso ad Internet."
];

View File

@ -97,15 +97,16 @@ $function_resource = new \Twig\TwigFunction(
$twig->addFunction($function_resource);
$function_script = new \Twig\TwigFunction(
'script', function ($file) {
'script', function ($context, $file) {
global $nonce, $url_software, $webpack_manifest;
$script_url = $url_software . "/resources/dist/" . $webpack_manifest[$file]["src"];
$script_integrity = $webpack_manifest[$file]["integrity"];
$script_tag = "<script src='{$script_url}' integrity='{$script_integrity}' crossorigin='anonymous' nonce='".$nonce."'";
$script_tag = "<script src='{$script_url}'";
if(!$context["is_offline_page"]) $script_tag .= " integrity='{$script_integrity}' crossorigin='anonymous' nonce='".$nonce."'";
$script_tag .= "></script>";
return $script_tag;
}, ['is_safe' => ['html']]
}, ['needs_context' => true, 'is_safe' => ['html']]
);
$twig->addFunction($function_script);
@ -157,6 +158,7 @@ function loadtemplate($templatename, $data, $requirelogin=true)
$data['user'] = $user->info();
$data['show_menu'] = !isset($_REQUEST["hide_menu"]);
$data['show_footer'] = !isset($_REQUEST["hide_footer"]);
$data['is_offline_page'] = strpos($_SERVER["PHP_SELF"], "offline.php") !== false;
if(get_option("use_custom_error_sound")) {
$data['error_sound'] = "custom-error.mp3";
} else {