From 5ee84ca308917c83381967e58f59cfc7fc94f941 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 9 Dec 2025 17:22:41 -0600 Subject: [PATCH] SW refinements --- src/webpage/localuser.ts | 13 ++- src/webpage/service.ts | 86 +++++++++++++++++-- src/webpage/utils/serviceType.ts | 28 +++++++ src/webpage/utils/utils.ts | 138 +++++++++++++++++++++++-------- translations/en.json | 3 + 5 files changed, 223 insertions(+), 45 deletions(-) create mode 100644 src/webpage/utils/serviceType.ts diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index eb5528b..50cf377 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -2471,8 +2471,17 @@ class Localuser { sw.onchange = (e) => { SW.setMode(["false", "offlineOnly", "true"][e] as "false" | "offlineOnly" | "true"); }; - update.addButtonInput("", I18n.localuser.CheckUpdate(), () => { - SW.checkUpdate(); + update.addButtonInput("", I18n.localuser.CheckUpdate(), async () => { + const update = await SW.checkUpdates(); + const text = update ? I18n.localuser.updatesYay() : I18n.localuser.noUpdates(); + const d = new Dialog(""); + d.options.addTitle(text); + if (update) { + d.options.addButtonInput("", I18n.localuser.refreshPage(), () => { + window.location.reload(); + }); + } + d.show(); }); update.addButtonInput("", I18n.localuser.clearCache(), () => { SW.forceClear(); diff --git a/src/webpage/service.ts b/src/webpage/service.ts index a124144..474d53f 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -1,3 +1,5 @@ +import {messageFrom, messageTo} from "./utils/serviceType"; + function deleteoldcache() { caches.delete("cache"); console.log("this ran :P"); @@ -19,32 +21,54 @@ self.addEventListener("activate", async () => { console.log("Service Worker activated"); checkCache(); }); - +async function tryToClose() { + const portArr = [...ports]; + if (portArr.length) { + for (let i = 1; i < portArr.length; i++) { + portArr[i].postMessage({code: "closing"}); + } + portArr[0].postMessage({code: "close"}); + } else { + throw new Error("No Fermi clients connected?"); + } +} +function sendAll(message: messageFrom) { + for (const port of ports) { + port.postMessage(message); + } +} async function checkCache() { if (checkedrecently) { - return; + return false; } const promise = await caches.match("/getupdates"); if (promise) { lastcache = await promise.text(); } console.log(lastcache); - fetch("/getupdates").then(async (data) => { + return fetch("/getupdates").then(async (data) => { setTimeout( (_: any) => { checkedrecently = false; }, 1000 * 60 * 30, ); - if (!data.ok) return; + if (!data.ok) return false; const text = await data.clone().text(); console.log(text, lastcache); if (lastcache !== text) { deleteoldcache(); putInCache("/getupdates", data); - self.close(); + tryToClose(); + checkedrecently = true; + sendAll({ + code: "updates", + updates: true, + }); + return true; } checkedrecently = true; + return false; }); } var checkedrecently = false; @@ -133,17 +157,56 @@ self.addEventListener("fetch", (e) => { console.error(e); } }); +const ports = new Set(); +function listenToPort(port: MessagePort) { + function sendMessage(message: messageFrom) { + port.postMessage(message); + } + port.onmessage = async (e) => { + const data = e.data as messageTo; + switch (data.code) { + case "ping": { + sendMessage({ + code: "pong", + count: ports.size, + }); + break; + } + case "close": { + ports.delete(port); + break; + } + case "replace": { + //@ts-ignore-error Just the type or wrong or something + self.skipWaiting(); + break; + } + case "CheckUpdate": { + checkedrecently = false; + if (!(await checkCache())) { + sendMessage({ + code: "updates", + updates: false, + }); + } + break; + } + } + }; + port.addEventListener("close", () => { + ports.delete(port); + }); +} +console.log("heya"); +// self.addEventListener("message", (message) => { const data = message.data; switch (data.code) { case "setMode": enabled = data.data; break; - case "CheckUpdate": - checkedrecently = false; - checkCache(); - break; + case "ForceClear": deleteoldcache(); break; @@ -152,5 +215,10 @@ self.addEventListener("message", (message) => { console.error("Hey!"); port.postMessage({code: "isValid", res: toPathNoDefault(data.url)}); break; + case "port": { + const port = data.port as MessagePort; + ports.add(port); + listenToPort(port); + } } }); diff --git a/src/webpage/utils/serviceType.ts b/src/webpage/utils/serviceType.ts new file mode 100644 index 0000000..5a43474 --- /dev/null +++ b/src/webpage/utils/serviceType.ts @@ -0,0 +1,28 @@ +export type messageTo = + | { + code: "ping"; + } + | { + code: "close"; + } + | { + code: "replace"; + } + | { + code: "CheckUpdate"; + }; +export type messageFrom = + | { + code: "pong"; + count: number; + } + | { + code: "close"; + } + | { + code: "closing"; + } + | { + code: "updates"; + updates: boolean; + }; diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index ac1a63c..798fb31 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -2,6 +2,7 @@ import {I18n} from "../i18n.js"; import {MarkDown} from "../markdown.js"; import {Dialog} from "../settings.js"; import {fix} from "./cssMagic.js"; +import {messageFrom, messageTo} from "./serviceType.js"; fix(); let instances: | { @@ -833,23 +834,120 @@ export {checkInstance}; export class SW { static worker: undefined | ServiceWorker; + static port?: MessagePort; + static init() { + SW.setMode( + (localStorage.getItem("SWMode") as "false" | "offlineOnly" | "true" | undefined) || "true", + ); + const port = new MessageChannel(); + SW.worker?.postMessage( + { + code: "port", + port: port.port2, + }, + [port.port2], + ); + this.port = port.port1; + this.port.onmessage = (e) => { + this.handleMessage(e.data); + }; + window.addEventListener("beforeunload", () => { + this.postMessage({code: "close"}); + port.port1.close(); + }); + this.postMessage({code: "ping"}); + } + static postMessage(message: messageTo) { + this.port?.postMessage(message); + } + private static updateWatchers = new Set<(updates: boolean) => void>(); + static watchForUpdates(func: (updates: boolean) => void) { + this.updateWatchers.add(func); + } + static stopWatchForUpdates(func: (updates: boolean) => void) { + this.updateWatchers.delete(func); + } + static async handleMessage(message: messageFrom) { + switch (message.code) { + case "pong": { + console.log(message); + break; + } + case "close": { + for (const thing of await navigator.serviceWorker.getRegistrations()) { + await thing.unregister(); + } + await this.start(); + this.postMessage({code: "replace"}); + break; + } + case "closing": { + await this.start(); + break; + } + case "updates": { + for (const thing of this.updateWatchers) { + thing(message.updates); + } + } + } + } + static async checkUpdates(): Promise { + return new Promise((res) => { + const func = (update: boolean) => { + this.stopWatchForUpdates(func); + res(update); + }; + this.watchForUpdates(func); + this.postMessage({code: "CheckUpdate"}); + }); + } + static async start() { + if (!("serviceWorker" in navigator)) return; + return new Promise((res) => { + navigator.serviceWorker + .register("/service.js", { + scope: "/", + }) + .then((registration) => { + let serviceWorker: ServiceWorker | undefined; + if (registration.installing) { + serviceWorker = registration.installing; + console.log("installing"); + } else if (registration.waiting) { + serviceWorker = registration.waiting; + console.log("waiting"); + } else if (registration.active) { + serviceWorker = registration.active; + console.log("active"); + } + SW.worker = serviceWorker; + SW.init(); + + if (serviceWorker) { + console.log(serviceWorker.state); + serviceWorker.addEventListener("statechange", (_) => { + console.log(serviceWorker.state); + }); + res(); + } + }); + }); + } static setMode(mode: "false" | "offlineOnly" | "true") { localStorage.setItem("SWMode", mode); if (this.worker) { this.worker.postMessage({data: mode, code: "setMode"}); } } - static checkUpdate() { - if (this.worker) { - this.worker.postMessage({code: "CheckUpdate"}); - } - } + static forceClear() { if (this.worker) { this.worker.postMessage({code: "ForceClear"}); } } } +SW.start(); let installPrompt: Event | undefined = undefined; window.addEventListener("beforeinstallprompt", (event) => { event.preventDefault(); @@ -858,35 +956,7 @@ window.addEventListener("beforeinstallprompt", (event) => { export function installPGet() { return installPrompt; } -if ("serviceWorker" in navigator) { - navigator.serviceWorker - .register("/service.js", { - scope: "/", - }) - .then((registration) => { - let serviceWorker: ServiceWorker | undefined; - if (registration.installing) { - serviceWorker = registration.installing; - console.log("installing"); - } else if (registration.waiting) { - serviceWorker = registration.waiting; - console.log("waiting"); - } else if (registration.active) { - serviceWorker = registration.active; - console.log("active"); - } - SW.worker = serviceWorker; - SW.setMode( - (localStorage.getItem("SWMode") as "false" | "offlineOnly" | "true" | undefined) || "true", - ); - if (serviceWorker) { - console.log(serviceWorker.state); - serviceWorker.addEventListener("statechange", (_) => { - console.log(serviceWorker.state); - }); - } - }); -} + export function getInstances() { return instances; } diff --git a/translations/en.json b/translations/en.json index defdcb8..bee6566 100644 --- a/translations/en.json +++ b/translations/en.json @@ -485,6 +485,9 @@ "keyname":"Key name:" }, "localuser": { + "noUpdates":"No updates found", + "updatesYay":"Updates have been found!", + "refreshPage":"Refresh to apply", "trusted":"Trusted Domains", "trustedDesc":"These domains when you click on links from them will ***not*** prompt you for permission to open like other links, only give this to URLs you trust", "trace": "Traces",