Added PWA and service-worker

This commit is contained in:
aunefyren 2023-12-13 12:42:07 +01:00
parent 3aee273861
commit e1265c5ae7
11 changed files with 332 additions and 0 deletions

18
main.go
View file

@ -240,5 +240,23 @@ func initRouter(config models.WrapperrConfig) *gin.Engine {
c.Data(http.StatusOK, "text/plain", TXTfile)
})
// Static endpoint for service-worker
router.GET("/service-worker.js", func(c *gin.Context) {
JSfile, err := os.ReadFile("./web/js/service-worker.js")
if err != nil {
fmt.Println("Reading service-worker threw error trying to open the file. Error: " + err.Error())
}
c.Data(http.StatusOK, "text/javascript", JSfile)
})
// Static endpoint for manifest
router.GET("/manifest.json", func(c *gin.Context) {
JSONfile, err := os.ReadFile("./web/json/manifest.json")
if err != nil {
fmt.Println("Reading manifest threw error trying to open the file. Error: " + err.Error())
}
c.Data(http.StatusOK, "text/json", JSONfile)
})
return router
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 82 KiB

42
web/assets/logos/logo.svg Normal file
View file

@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F2F2F2;}
.st1{fill:#FFBD55;}
.st2{fill:#DEDEDE;}
.st3{fill:#F2AB44;}
.st4{fill:#D3D3D3;}
.st5{fill:#FFC878;}
.st6{fill:#FFFFFF;}
.st7{fill:#D4D4D4;}
</style>
<path class="st0" d="M278.6,308.5c-96.3,0-226.9,2.4-222.3,40.4s74,42.7,56,66c-18,23.3,28,59.3,114,55.3s151.3-14.3,154-47.7 c1.7-20.9,27.9-31.6,45.3-42.9c24.7-16,30-48-12-60S278.6,308.5,278.6,308.5z"/>
<g id="BACKGROUND">
</g>
<g id="OBJECTS">
<polygon class="st1" points="401.9,359 222.8,422.5 222.8,258 401.9,194.5 "/>
<polygon class="st0" points="328.9,384.8 295.8,396.6 295.8,232.1 328.9,220.4 "/>
<polygon class="st2" points="401.9,194.5 401.9,217.7 222.7,281.3 222.7,258 "/>
<polygon class="st1" points="432,183.8 222.8,258 222.8,180.2 432,106 "/>
<polygon class="st0" points="346.7,214.1 308,227.8 308,149.9 346.7,136.2 "/>
<polygon class="st3" points="106.2,359 222.8,422.5 222.8,258 106.2,194.5 "/>
<polygon class="st4" points="153.7,384.8 175.2,396.6 175.2,232.1 153.7,220.4 "/>
<polygon class="st2" points="222.7,258 222.7,281.7 106.2,218.2 106.2,194.5 "/>
<polygon class="st3" points="86.5,183.8 222.8,258 222.8,180.2 86.5,106 "/>
<polygon class="st4" points="142.1,214.1 167.2,227.8 167.2,149.9 142.1,136.2 "/>
<polygon class="st5" points="432,106 346.7,136.2 308,149.9 222.8,180.2 167.3,149.9 142,136.2 86.5,106 175.9,78.6 175.9,78.6 210.5,68 261.3,52.5 314.7,69.2 350.5,80.4 "/>
<polygon class="st6" points="346.7,136.2 308,149.9 249.9,118.6 223.8,104.5 175.9,78.6 210.5,68 257.3,91.5 257.3,91.5 285,105.3 "/>
<polygon class="st6" points="350.5,80.4 285,105.3 249.9,118.6 167.3,149.9 142,136.2 223.8,104.5 257.3,91.5 314.7,69.2 "/>
<path class="st7" d="M246.3,110.2c0,0,8.7-79.8,47.4-69.8s0,49.7,0,49.7s6.8-31-6.8-32.4C273.2,56.4,253.1,84.6,246.3,110.2z"/>
<path class="st7" d="M323.4,117l-23.3,16.4c0,0-34.7-6.8-53.8-23.3l21.4-9.9C267.7,100.3,296.4,115.8,323.4,117z"/>
<path class="st0" d="M246.3,110.2c0,0,84.8-54.7,100.4-26.5s2.7,38.3-6.4,41.5c-9.1,3.2-40.2,8.2-40.2,8.2s14.5-31.4-1.7-39 c-3.6-1.7-7.9-1.5-11.7,0L246.3,110.2z"/>
<g>
<g>
<path class="st0" d="M169.2,117l23.3,16.4c0,0,34.7-6.8,53.8-23.3l-21.4-9.9C224.8,100.3,196.1,115.8,169.2,117z"/>
</g>
<path class="st0" d="M246.3,110.2c0,0-8.7-79.8-47.4-69.8s0,49.7,0,49.7s-6.8-31,6.8-32.4C219.4,56.4,239.4,84.6,246.3,110.2z"/>
</g>
<path class="st7" d="M246.3,110.2c0,0-84.8-54.7-100.4-26.5c-15.5,28.3-2.7,38.3,6.4,41.5c9.1,3.2,40.2,8.2,40.2,8.2 s-14.5-31.4,1.7-39c3.6-1.7,7.9-1.5,11.7,0L246.3,110.2z"/>
</g>
<g id="DESIGNED_BY_FREEPIK">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -11,7 +11,9 @@
<link rel="stylesheet" type="text/css" href="./assets/css/wrapped.css" />
<link rel="shortcut icon" href="./assets/img/favicons/favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<link rel="manifest" href="./manifest.json" />
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="./js/service-worker.js"></script>
<script src="./js/functions.js"></script>
<script src="./js/index.js"></script>
<script src="./js/get_stats.js"></script>

175
web/js/service-worker.js Normal file
View file

@ -0,0 +1,175 @@
console.log("Service-worker loaded.");
// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
const OFFLINE_VERSION = 1;
const CACHE_NAME = 'wrapperr-cache';
// Customize this with a different URL if needed.
const urlsToCache = [
'/',
'./manifest.json',
'./assets/favicons/favicon.ico'
];
self.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
for(var i = 0; i < urlsToCache.length; i++) {
await cache.add(new Request(urlsToCache[i], {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) => {
// 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 () => {
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);
const cachedResponse = await cache.match(OFFLINE_URL);
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('notificationclose', event => {
try {
const notification = event.notification;
const primaryKey = notification.data.primaryKey;
console.log('Closed notification: ' + primaryKey);
} catch(e) {
console.log("Failed to click notfication. Error: " + e)
}
});
self.addEventListener('notificationclick', event => {
try {
const notification = event.notification;
const primaryKey = notification.data.primaryKey;
const url = notification.data.url;
const action = event.action;
if (action === 'close') {
notification.close();
} else {
event.waitUntil(clients.openWindow(self.location.origin + url));
notification.close();
}
console.log('Clicked notification: ' + primaryKey);
} catch(e) {
console.log("Failed to click notfication. Error: " + e)
}
// TODO 5.3 - close all notifications when one is clicked
});
self.addEventListener('push', function(event) {
if (!(self.Notification && self.Notification.permission === "granted")) {
console.log("Notification permission not given.")
return;
}
console.log("Pushing notification.")
try {
let jsonData = event.data?.json() ?? {
category: "general",
title: "Error",
body: "An error occured"
};
console.log("JSON: " + JSON.stringify(jsonData));
let url;
let action;
if(jsonData.category == "achievement") {
url = "/achievements"
action = "Check out"
} else if(jsonData.category == "news") {
url = "/news"
action = "Read"
} else {
url = "/"
action = "Visit"
}
const options = {
body: jsonData.body,
icon: '/assets/logos/logo-384x384.png',
badge: '/assets/logos/logo-mono-96x96.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1,
url: url
},
actions: [
{action: 'explore', title: action,
icon: '/assets/check.svg'
},
{action: 'close', title: 'Close',
icon: '/assets/x.svg'
},
],
tag: 'Message'
};
event.waitUntil(
self.registration.showNotification(jsonData.title, options)
);
} catch(e) {
console.log("Failed to push notfication. Error: " + e)
}
});

64
web/json/manifest.json Normal file
View file

@ -0,0 +1,64 @@
{
"lang": "en",
"dir": "ltr",
"name": "Wrapperr",
"short_name": "Wrapperr",
"description": "nicely wrapped Plex statistics.",
"theme_color": "#F9C74F",
"background_color": "#F9844A",
"display": "standalone",
"orientation": "portrait",
"scope": "/",
"start_url": "/",
"prefer_related_applications": false,
"icons": [
{
"src": "/assets/logos/logo.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/assets/logos/logo-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/assets/logos/logo-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/assets/logos/logo-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/assets/logos/logo-1024x1024.png",
"sizes": "1024x1024",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/assets/logos/logo-mono-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any monochrome"
},
{
"src": "/assets/logos/logo-mono.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any monochrome"
}
],
"splash_pages": null,
"id": "7b46f4ad-dd2a-4446-80e7-a2c2765eedbc",
"categories": [
"games",
"lifestyle"
]
}