diff --git a/pages/login/login.scarlet b/pages/login/login.scarlet index 36972b8f..c9c80033 100644 --- a/pages/login/login.scarlet +++ b/pages/login/login.scarlet @@ -5,7 +5,7 @@ .login-button padding 0.5rem + color white -.login-button-image - max-width 236px - max-height 44px \ No newline at end of file + :hover + color white \ No newline at end of file diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index 13f4d0a0..1bab02e4 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -99,6 +99,9 @@ export class AnimeNotifier { // Let"s start this.app.run() + + // Service worker + this.registerServiceWorker() } onContentLoaded() { @@ -123,18 +126,50 @@ export class AnimeNotifier { } onIdle() { - this.registerServiceWorker() this.pushAnalytics() } registerServiceWorker() { + if(!("serviceWorker" in navigator)) { + return + } + navigator.serviceWorker.register("service-worker", { scope: "./" + }).then(registration => { + registration.update() }) - // navigator.serviceWorker.ready.then(() => { - // console.log("Service worker registered.") - // }) + navigator.serviceWorker.onmessage = evt => { + this.onServiceWorkerMessage(evt) + } + } + + onServiceWorkerMessage(evt: ServiceWorkerMessageEvent) { + let message = JSON.parse(evt.data) + console.log(message.url, this.app.eTag, message.eTag) + + switch(message.type) { + case "content changed": + // If we don't have an etag it means it was a full page refresh. + // In this case we don't need to reload anything. + if(!this.app.eTag) { + this.app.eTag = message.eTag + return + } + + if(this.app.eTag !== message.eTag) { + if(message.url.includes("/_/")) { + // Content reload + this.reloadContent() + } else { + // Full page reload + this.reloadPage() + } + } + + break + } } pushAnalytics() { @@ -185,14 +220,39 @@ export class AnimeNotifier { } reloadContent() { + let headers = new Headers() + headers.append("X-Reload", "true") + return fetch("/_" + this.app.currentPath, { - credentials: "same-origin" + credentials: "same-origin", + headers + }) + .then(response => { + this.app.eTag = response.headers.get("ETag") + return response }) .then(response => response.text()) .then(html => Diff.innerHTML(this.app.content, html)) .then(() => this.app.emit("DOMContentLoaded")) } + reloadPage() { + let headers = new Headers() + headers.append("X-Reload", "true") + + return fetch(this.app.currentPath, { + credentials: "same-origin", + headers + }) + .then(response => { + this.app.eTag = response.headers.get("ETag") + return response + }) + .then(response => response.text()) + .then(html => Diff.innerHTML(document.body, html)) + .then(() => this.app.emit("DOMContentLoaded")) + } + loading(isLoading: boolean) { if(isLoading) { document.body.style.cursor = "progress" diff --git a/scripts/Application.ts b/scripts/Application.ts index 04c0d13d..d1d30bd5 100644 --- a/scripts/Application.ts +++ b/scripts/Application.ts @@ -13,6 +13,7 @@ export class Application { loading: HTMLElement currentPath: string originalPath: string + eTag: string lastRequest: XMLHttpRequest constructor() { @@ -45,6 +46,8 @@ export class Application { request.onerror = () => reject(new Error("You are either offline or the requested page doesn't exist.")) request.ontimeout = () => reject(new Error("The page took too much time to respond.")) request.onload = () => { + this.eTag = request.getResponseHeader("ETag") + if(request.status < 200 || request.status >= 400) reject(request.responseText) else diff --git a/sw/service-worker.ts b/sw/service-worker.ts index 0c93229d..1b44f0b5 100644 --- a/sw/service-worker.ts +++ b/sw/service-worker.ts @@ -1,26 +1,36 @@ // pack:ignore -var CACHE = "v-alpha" +var CACHE = "v-1" self.addEventListener("install", (evt: any) => { - console.log("The service worker is being installed.") - - evt.waitUntil(installCache()) + evt.waitUntil( + (self as any).skipWaiting().then(() => { + return installCache() + }) + ) }) self.addEventListener("activate", (evt: any) => { - evt.waitUntil((self as any).clients.claim()) + evt.waitUntil( + (self as any).clients.claim() + ) }) self.addEventListener("fetch", async (evt: any) => { let request = evt.request + let isAuth = request.url.includes("/auth/") || request.url.includes("/logout") - console.log("Serving:", request.url, request, request.method) + // Delete existing cache on authentication + if(isAuth) { + caches.delete(CACHE) + } // Do not use cache in some cases - if(request.method !== "GET" || request.url.includes("/auth/") || request.url.includes("chrome-extension")) { + if(request.method !== "GET" || isAuth || request.url.includes("chrome-extension")) { return evt.waitUntil(evt.respondWith(fetch(request))) } + + let servedCachedResponse = false // Start fetching the request let refresh = fetch(request).then(response => { @@ -28,15 +38,33 @@ self.addEventListener("fetch", async (evt: any) => { // Save the new version of the resource in the cache caches.open(CACHE).then(cache => { - cache.put(request, clone) + return cache.put(request, clone) + }).then(() => { + if(!servedCachedResponse) { + return + } + + let contentType = clone.headers.get("Content-Type") + + if(contentType && contentType.startsWith("text/html") && clone.headers.get("ETag") && request.headers.get("X-Reload") !== "true") { + reloadContent(clone) + } }) return response }) + // Forced reload + if(request.headers.get("X-Reload") === "true") { + return evt.waitUntil(refresh) + } + // Try to serve cache first and fall back to network response - let networkOrCache = fromCache(request).catch(error => { - console.log("Cache MISS:", request.url) + let networkOrCache = fromCache(request).then(response => { + servedCachedResponse = true + return response + }).catch(error => { + // console.log("Cache MISS:", request.url) return refresh }) @@ -57,7 +85,7 @@ function fromCache(request) { return caches.open(CACHE).then(cache => { return cache.match(request).then(matching => { if(matching) { - console.log("Cache HIT:", request.url) + // console.log("Cache HIT:", request.url) return Promise.resolve(matching) } @@ -66,8 +94,16 @@ function fromCache(request) { }) } -function updateCache(request, response) { - return caches.open(CACHE).then(cache => { - cache.put(request, response) +function reloadContent(response) { + return (self as any).clients.matchAll().then(clients => { + clients.forEach(client => { + var message = { + type: 'content changed', + url: response.url, + eTag: response.headers.get('ETag') + } + + client.postMessage(JSON.stringify(message)) + }) }) }