From 3f4bdde7906b9eb02ac84c33450d23dfc7e495d6 Mon Sep 17 00:00:00 2001 From: Ou Date: Sun, 24 Nov 2024 23:37:23 +0800 Subject: [PATCH] feat: better way to update PWA site --- eslint.config.mjs | 2 +- public/sw.js | 23 +++++++++++ pwa.config.ts | 2 +- server/api/latest.ts | 5 +++ server/middleware/auth.ts | 2 +- src/components/common/toast.tsx | 2 +- src/hooks/usePWA.ts | 71 ++++++++++++++++----------------- 7 files changed, 66 insertions(+), 41 deletions(-) create mode 100644 public/sw.js create mode 100644 server/api/latest.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 5081bc2..b2110db 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,7 +3,7 @@ import { ourongxing, react } from "@ourongxing/eslint-config" export default ourongxing({ type: "app", // 貌似不能 ./ 开头, - ignores: ["src/routeTree.gen.ts", "imports.app.d.ts"], + ignores: ["src/routeTree.gen.ts", "imports.app.d.ts", "public/"], }).append(react({ files: ["src/**"], })) diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..cfc8c17 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,23 @@ +self.addEventListener("install", (e) => { + self.skipWaiting() +}) +self.addEventListener("activate", (e) => { + self.registration + .unregister() + .then(() => self.clients.matchAll()) + .then((clients) => { + clients.forEach((client) => { + if (client instanceof WindowClient) client.navigate(client.url) + }) + return Promise.resolve() + }) + .then(() => { + self.caches.keys().then((cacheNames) => { + Promise.all( + cacheNames.map((cacheName) => { + return self.caches.delete(cacheName) + }), + ) + }) + }) +}) diff --git a/pwa.config.ts b/pwa.config.ts index 21089aa..5adf1dc 100644 --- a/pwa.config.ts +++ b/pwa.config.ts @@ -4,6 +4,7 @@ import { VitePWA } from "vite-plugin-pwa" const pwaOption: Partial = { includeAssets: ["icon.svg", "apple-touch-icon.png"], + filename: "swx.js", manifest: { name: "NewsNow", short_name: "NewsNow", @@ -42,7 +43,6 @@ const pwaOption: Partial = { type: "module", navigateFallback: "index.html", }, - } export default function pwa() { diff --git a/server/api/latest.ts b/server/api/latest.ts new file mode 100644 index 0000000..8b2de30 --- /dev/null +++ b/server/api/latest.ts @@ -0,0 +1,5 @@ +export default defineEventHandler(async () => { + return { + v: Version, + } +}) diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 10e4c00..68de973 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { if (!url.pathname.startsWith("/api")) return if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) { event.context.disabledLogin = true - if (["/api/s", "/api/proxy"].every(p => !url.pathname.startsWith(p))) + if (["/api/s", "/api/proxy", "/api/latest"].every(p => !url.pathname.startsWith(p))) throw createError({ statusCode: 506, message: "Server not configured, disable login" }) } else { if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) { diff --git a/src/components/common/toast.tsx b/src/components/common/toast.tsx index ec86374..f378f6a 100644 --- a/src/components/common/toast.tsx +++ b/src/components/common/toast.tsx @@ -20,7 +20,7 @@ export function Toast() { width: WIDTH, left: center, }} - className="absolute top-0 z-99 flex flex-col gap-2" + className="absolute top-4 z-99 flex flex-col gap-2" > { toastItems.map(k => ) diff --git a/src/hooks/usePWA.ts b/src/hooks/usePWA.ts index 7a7752b..947264e 100644 --- a/src/hooks/usePWA.ts +++ b/src/hooks/usePWA.ts @@ -1,45 +1,42 @@ -import { useEffect } from "react" import { useRegisterSW } from "virtual:pwa-register/react" +import { useMount } from "react-use" import { useToast } from "./useToast" -const intervalMS = 60 * 60 * 1000 export function usePWA() { - const { - needRefresh: [needRefresh, setNeedRefresh], - updateServiceWorker, - } - = useRegisterSW({ - onRegisteredSW(swUrl, r) { - if (r) { - setInterval(async () => { - if (r.installing || !navigator) return - - if ("connection" in navigator && !navigator.onLine) return - - const resp = await fetch(swUrl, { - cache: "no-store", - headers: { - "cache": "no-store", - "cache-control": "no-cache", - }, - }) - - if (resp?.status === 200) await r.update() - }, intervalMS) - } - }, - }) const toaster = useToast() + const { updateServiceWorker } = useRegisterSW() - useEffect(() => { - if (needRefresh) { - toaster("网站有更新,点击更新", { - action: { - label: "更新", - onClick: () => updateServiceWorker(true), - }, - onDismiss: () => updateServiceWorker(true), - }) + useMount(async () => { + const update = () => { + updateServiceWorker().then(() => localStorage.setItem("updated", "1")) } - }, [needRefresh, updateServiceWorker, setNeedRefresh, toaster]) + await delay(1000) + if (localStorage.getItem("updated")) { + localStorage.removeItem("updated") + toaster("更新成功,赶快体验吧", { + action: { + label: "查看更新", + onClick: () => { + window.open(`${Homepage}/releases/tag/v${Version}`) + }, + }, + }) + } else { + if (!navigator) return + + if ("connection" in navigator && !navigator.onLine) return + + const resp = await myFetch("/latest") + + if (resp.v && resp.v !== Version) { + toaster("有更新,5 秒后自动更新", { + action: { + label: "立刻更新", + onClick: update, + }, + onDismiss: update, + }) + } + } + }) }