feat: better way to update PWA site

This commit is contained in:
Ou 2024-11-24 23:37:23 +08:00
parent 9fb5f6eb83
commit 3f4bdde790
7 changed files with 66 additions and 41 deletions

View File

@ -3,7 +3,7 @@ import { ourongxing, react } from "@ourongxing/eslint-config"
export default ourongxing({ export default ourongxing({
type: "app", type: "app",
// 貌似不能 ./ 开头, // 貌似不能 ./ 开头,
ignores: ["src/routeTree.gen.ts", "imports.app.d.ts"], ignores: ["src/routeTree.gen.ts", "imports.app.d.ts", "public/"],
}).append(react({ }).append(react({
files: ["src/**"], files: ["src/**"],
})) }))

23
public/sw.js Normal file
View File

@ -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)
}),
)
})
})
})

View File

@ -4,6 +4,7 @@ import { VitePWA } from "vite-plugin-pwa"
const pwaOption: Partial<VitePWAOptions> = { const pwaOption: Partial<VitePWAOptions> = {
includeAssets: ["icon.svg", "apple-touch-icon.png"], includeAssets: ["icon.svg", "apple-touch-icon.png"],
filename: "swx.js",
manifest: { manifest: {
name: "NewsNow", name: "NewsNow",
short_name: "NewsNow", short_name: "NewsNow",
@ -42,7 +43,6 @@ const pwaOption: Partial<VitePWAOptions> = {
type: "module", type: "module",
navigateFallback: "index.html", navigateFallback: "index.html",
}, },
} }
export default function pwa() { export default function pwa() {

5
server/api/latest.ts Normal file
View File

@ -0,0 +1,5 @@
export default defineEventHandler(async () => {
return {
v: Version,
}
})

View File

@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
if (!url.pathname.startsWith("/api")) return if (!url.pathname.startsWith("/api")) return
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) { if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
event.context.disabledLogin = true 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" }) throw createError({ statusCode: 506, message: "Server not configured, disable login" })
} else { } else {
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) { if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {

View File

@ -20,7 +20,7 @@ export function Toast() {
width: WIDTH, width: WIDTH,
left: center, 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 => <Item key={k.id} info={k} />) toastItems.map(k => <Item key={k.id} info={k} />)

View File

@ -1,45 +1,42 @@
import { useEffect } from "react"
import { useRegisterSW } from "virtual:pwa-register/react" import { useRegisterSW } from "virtual:pwa-register/react"
import { useMount } from "react-use"
import { useToast } from "./useToast" import { useToast } from "./useToast"
const intervalMS = 60 * 60 * 1000
export function usePWA() { 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 toaster = useToast()
const { updateServiceWorker } = useRegisterSW()
useEffect(() => { useMount(async () => {
if (needRefresh) { const update = () => {
toaster("网站有更新,点击更新", { updateServiceWorker().then(() => localStorage.setItem("updated", "1"))
action: {
label: "更新",
onClick: () => updateServiceWorker(true),
},
onDismiss: () => updateServiceWorker(true),
})
} }
}, [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,
})
}
}
})
} }