From 900efcba7c461fbbc74357503f297d7178d713ef Mon Sep 17 00:00:00 2001 From: TylerMaloney Date: Mon, 24 Jul 2023 20:17:33 -0700 Subject: [PATCH 01/19] added sw-custom.js --- src/frontend-pwa/public/sw-custom.js | 105 +++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/frontend-pwa/public/sw-custom.js diff --git a/src/frontend-pwa/public/sw-custom.js b/src/frontend-pwa/public/sw-custom.js new file mode 100644 index 0000000..b6f6bf5 --- /dev/null +++ b/src/frontend-pwa/public/sw-custom.js @@ -0,0 +1,105 @@ +/** + * @summary Supplemental service-worker file. Appends functionality to + * the service-worker generated by the VitePWA "generateSW" + * strategy. + * + * @author Tyler Maloney + */ + +/** + * @summary Handles splitting of workbox precache into "mapTiles" cache consisting of + * map tiles, and the "site" cache which contains everything else from precache. + * + * Deletes the original file from precache after it has been moved to ensure + * cache size remains below 50mb limit (iOS Safari limitation). + * After the files have been moved to the separate caches, the empty precache + * is deleted. + * + * @param 'activate' is the event which this listener will listen for. + * This event fires as soon as the service-worker is finished precaching assets + * and is functional. + * @type {event} + * @author Tyler Maloney + */ +self.addEventListener('activate', (event) => { + event.waitUntil( + self.clients.claim().then(() => { + return caches.open('mapTiles').then((mapTilesCache) => { + return caches.open('site').then((siteCache) => { + return caches.keys().then((cacheNames) => { + const precacheNames = cacheNames.filter((cacheName) => cacheName.startsWith('workbox-precache-v2')); + return Promise.all( + precacheNames.map((precacheName) => { + return caches.open(precacheName).then((precache) => { + return precache.keys().then((cacheKeys) => { + const mapTilesUrls = cacheKeys.filter((cacheKey) => cacheKey.url.includes('mapTiles')); + const siteUrls = cacheKeys.filter((cacheKey) => !cacheKey.url.includes('mapTiles')); + + const mapTilesPromises = mapTilesUrls.map((url) => { + return precache.match(url).then((response) => { + if (response) { + return mapTilesCache.put(url, response.clone()).then(() => { + return precache.delete(url); + }); + } + }); + }); + + const sitePromises = siteUrls.map((url) => { + return precache.match(url).then((response) => { + if (response) { + return siteCache.put(url, response.clone()).then(() => { + return precache.delete(url); + }); + } + }); + }); + + return Promise.all([...mapTilesPromises, ...sitePromises]); + }); + }); + }) + ); + }); + }); + }).then(() => { + return caches.keys().then((cacheNames) => { + const precacheNamesToDelete = cacheNames.filter((cacheName) => cacheName.startsWith('workbox-precache-v2')); + return Promise.all( + precacheNamesToDelete.map((cacheName) => { + return caches.delete(cacheName); + }) + ); + }); + }).then(() => { + }).catch((error) => { + console.error('Error deleting caches:', error); + }); + }) + ); +}); + +/** +* @summary Deletes the "mapTiles" cache, through an event linked to the +* "Delete Data" button in the "Delete Mapping Data" +* accordion tab. +* +* @param 'message' is the event which this listener will listen for. +* The message event is used in a page controlled by a +* service worker to receive messages from the service worker. +* @type {event} +* @author Tyler Maloney +*/ +self.addEventListener('message', (event) => { +if (event.data && event.data.action === 'clearCache') { + caches.keys().then((cacheNames) => { + const cachesToDelete = cacheNames.filter((cacheName) => cacheName === 'mapTiles'); + return Promise.all(cachesToDelete.map((cacheName) => caches.delete(cacheName))); + }).then(() => { + event.source.postMessage({ action: 'clearCache', success: true }); + }).catch((error) => { + console.error('Cache clear error:', error); + event.source.postMessage({ action: 'clearCache', error }); + }); +} +}); From 586d401120b1d53cbfa55307e6f174dbbc08d6e1 Mon Sep 17 00:00:00 2001 From: TylerMaloney Date: Mon, 24 Jul 2023 20:23:35 -0700 Subject: [PATCH 02/19] added content --- src/frontend-pwa/src/content/content.ts | 16 +++++ .../src/views/Settings/Settings.tsx | 71 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/frontend-pwa/src/content/content.ts b/src/frontend-pwa/src/content/content.ts index d54c369..c03200f 100644 --- a/src/frontend-pwa/src/content/content.ts +++ b/src/frontend-pwa/src/content/content.ts @@ -68,6 +68,22 @@ export const SettingsContent: ContentMap = { eng: 'Pull in new location data from the server', fr: 'Récupérer de nouvelles données de localisation à partir du serveur', }, + clearCacheAccordionTitle: { + eng: 'Delete Map Cache', + fr: 'Supprimer le cache', + }, + clearCache: { + eng: 'Delete Data', + fr: 'Suprimmer les données', + }, + clearCacheToolTip: { + eng: 'Deletes the cached map data.', + fr: 'Supprime les données de site mises en cache.', + }, + clearCacheWarningText: { + eng: 'WARNING: Deleting this will prevent offline map functionality.', + fr: 'AVERTISSEMENT : la suppression de ce paramètre empêchera la fonctionnalité de carte hors ligne.', + }, languages: { eng: [ 'English', diff --git a/src/frontend-pwa/src/views/Settings/Settings.tsx b/src/frontend-pwa/src/views/Settings/Settings.tsx index 0c057ed..1bd9846 100644 --- a/src/frontend-pwa/src/views/Settings/Settings.tsx +++ b/src/frontend-pwa/src/views/Settings/Settings.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /* eslint-disable react/jsx-indent */ /* eslint-disable max-len */ /** @@ -164,8 +165,15 @@ export default function Settings() { }; /** - * @summary Pulls in new app data if user hit the refresh button - * @author Dallas Richmond + * @summary Pulls in new app data if user hits the refresh button. + * Clears the browser cache, then forces the page + * to unregister the service-worker and reload the + * window. This forces an updated service-worker + * to initialize and download, triggering all assets + * to be downloaded anew. + * + * + * @author Dallas Richmond, Tyler Maloney */ const handleRefresh = () => { setAppData(onlineCheck); @@ -183,6 +191,43 @@ export default function Settings() { }; sendAnalytics(analytics); } + if ('serviceWorker' in navigator) { + const clearCachesPromise = Promise.all([ + caches.delete('mapTiles'), + caches.delete('site'), + ]); + clearCachesPromise.then(() => { + navigator.serviceWorker.getRegistration().then((registration) => { + if (registration) { + registration.unregister().then((isUnregistered) => { + if (isUnregistered) { + window.location.reload(); + } else { + console.error('Service worker unregistration failed.'); + } + }).catch((error) => { + console.error('Service worker unregistration failed:', error); + }); + } else { + window.location.reload(); + } + }).catch((error) => { + console.error('Error getting service worker registration:', error); + }); + }).catch((error) => { + console.error('Error clearing caches:', error); + }); + } + }; + + /** + * @summary Directs the service worker to clear all mapTile data from browser cache. + * @author Tyler Maloney + */ + const handleClearCache = () => { + if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ action: 'clearCache' }); + } }; return ( @@ -287,6 +332,28 @@ export default function Settings() { text={SettingsContent.changeLog[lang]} /> + +

+ {SettingsContent.clearCacheWarningText[lang]} +

+