formatting and comments
This commit is contained in:
+64
-27
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Install, offline and explicit-update lifecycle for the control-console PWA.
|
||||
*
|
||||
* Registration is restricted to `/control/`; `/obs/*` is deliberately outside
|
||||
* the scope. The worker caches only the public application shell and static
|
||||
* assets. API responses, WebSockets and secrets remain network-only. A waiting
|
||||
* worker activates only after user confirmation and after every registered
|
||||
* one-time-secret or dirty-form blocker has cleared.
|
||||
*/
|
||||
import { useEffect, useSyncExternalStore } from 'react'
|
||||
|
||||
interface InstallChoice {
|
||||
@@ -58,9 +67,10 @@ function addManifest() {
|
||||
function observeRegistration(registration: ServiceWorkerRegistration) {
|
||||
emit({
|
||||
registration,
|
||||
waitingWorker: registration.waiting && navigator.serviceWorker.controller
|
||||
? registration.waiting
|
||||
: snapshot.waitingWorker,
|
||||
waitingWorker:
|
||||
registration.waiting && navigator.serviceWorker.controller
|
||||
? registration.waiting
|
||||
: snapshot.waitingWorker,
|
||||
})
|
||||
|
||||
registration.addEventListener('updatefound', () => {
|
||||
@@ -76,18 +86,23 @@ function observeRegistration(registration: ServiceWorkerRegistration) {
|
||||
|
||||
async function removeLegacyRootWorker() {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
await Promise.all(registrations.map(async registration => {
|
||||
const scopePath = new URL(registration.scope).pathname
|
||||
const workers = [registration.installing, registration.waiting, registration.active]
|
||||
const isLegacy = scopePath === '/'
|
||||
&& workers.some(worker => worker && new URL(worker.scriptURL).pathname === '/sw.js')
|
||||
if (isLegacy) await registration.unregister()
|
||||
}))
|
||||
await Promise.all(
|
||||
registrations.map(async registration => {
|
||||
const scopePath = new URL(registration.scope).pathname
|
||||
const workers = [registration.installing, registration.waiting, registration.active]
|
||||
const isLegacy =
|
||||
scopePath === '/' &&
|
||||
workers.some(worker => worker && new URL(worker.scriptURL).pathname === '/sw.js')
|
||||
if (isLegacy) await registration.unregister()
|
||||
}),
|
||||
)
|
||||
|
||||
const cacheNames = await caches.keys()
|
||||
await Promise.all(cacheNames
|
||||
.filter(name => name.startsWith('lxc-control-shell-'))
|
||||
.map(name => caches.delete(name)))
|
||||
await Promise.all(
|
||||
cacheNames
|
||||
.filter(name => name.startsWith('lxc-control-shell-'))
|
||||
.map(name => caches.delete(name)),
|
||||
)
|
||||
}
|
||||
|
||||
export function cleanupLegacyPwa() {
|
||||
@@ -121,18 +136,25 @@ export function initializePwa() {
|
||||
const updateDisplayMode = () => emit({ standalone: isStandalone() })
|
||||
window.addEventListener('online', updateConnection)
|
||||
window.addEventListener('offline', updateConnection)
|
||||
const modernListener = (displayMode as unknown as {
|
||||
addEventListener?: (type: 'change', listener: () => void) => void
|
||||
}).addEventListener
|
||||
const modernListener = (
|
||||
displayMode as unknown as {
|
||||
addEventListener?: (type: 'change', listener: () => void) => void
|
||||
}
|
||||
).addEventListener
|
||||
if (modernListener) modernListener.call(displayMode, 'change', updateDisplayMode)
|
||||
else (displayMode as unknown as { addListener?: (listener: () => void) => void })
|
||||
.addListener?.call(displayMode, updateDisplayMode)
|
||||
else
|
||||
(displayMode as unknown as { addListener?: (listener: () => void) => void }).addListener?.call(
|
||||
displayMode,
|
||||
updateDisplayMode,
|
||||
)
|
||||
|
||||
window.addEventListener('beforeinstallprompt', event => {
|
||||
event.preventDefault()
|
||||
emit({ installPrompt: event as BeforeInstallPromptEvent })
|
||||
})
|
||||
window.addEventListener('appinstalled', () => emit({ installPrompt: undefined, standalone: true }))
|
||||
window.addEventListener('appinstalled', () =>
|
||||
emit({ installPrompt: undefined, standalone: true }),
|
||||
)
|
||||
|
||||
if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return
|
||||
|
||||
@@ -151,13 +173,17 @@ export function initializePwa() {
|
||||
void snapshot.registration?.update()
|
||||
}
|
||||
})
|
||||
window.setInterval(() => {
|
||||
if (navigator.onLine) void snapshot.registration?.update()
|
||||
}, 60 * 60 * 1000)
|
||||
window.setInterval(
|
||||
() => {
|
||||
if (navigator.onLine) void snapshot.registration?.update()
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
)
|
||||
}
|
||||
|
||||
export function authRoute(name: 'login' | 'register' | 'setup'): string {
|
||||
const inControlScope = location.pathname === '/control' || location.pathname.startsWith('/control/')
|
||||
const inControlScope =
|
||||
location.pathname === '/control' || location.pathname.startsWith('/control/')
|
||||
return inControlScope ? `/control/${name}` : `/${name}`
|
||||
}
|
||||
|
||||
@@ -165,7 +191,9 @@ export function usePwaUpdateBlocker(key: string, reason: string, active: boolean
|
||||
useEffect(() => {
|
||||
if (active) updateBlockers.set(key, reason)
|
||||
else updateBlockers.delete(key)
|
||||
return () => { updateBlockers.delete(key) }
|
||||
return () => {
|
||||
updateBlockers.delete(key)
|
||||
}
|
||||
}, [active, key, reason])
|
||||
}
|
||||
|
||||
@@ -182,10 +210,14 @@ function applyUpdate() {
|
||||
if (!worker) return
|
||||
const reasons = [...new Set(updateBlockers.values())]
|
||||
if (reasons.length > 0) {
|
||||
window.alert(`暂时不能更新,请先处理以下内容:\n\n${reasons.map(reason => `• ${reason}`).join('\n')}`)
|
||||
window.alert(
|
||||
`暂时不能更新,请先处理以下内容:\n\n${reasons.map(reason => `• ${reason}`).join('\n')}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
const confirmed = window.confirm('更新会刷新控制台。请先保存设置、邀请码、恢复码或刚轮换的 OBS 令牌,确定现在更新吗?')
|
||||
const confirmed = window.confirm(
|
||||
'更新会刷新控制台。请先保存设置、邀请码、恢复码或刚轮换的 OBS 令牌,确定现在更新吗?',
|
||||
)
|
||||
if (!confirmed) return
|
||||
reloadForUpdate = true
|
||||
worker.postMessage({ type: 'SKIP_WAITING' })
|
||||
@@ -198,7 +230,12 @@ export function PwaControls() {
|
||||
|
||||
return (
|
||||
<div className="pwa-controls" aria-live="polite">
|
||||
{!state.online && <span className="pwa-state offline"><i aria-hidden="true" />离线</span>}
|
||||
{!state.online && (
|
||||
<span className="pwa-state offline">
|
||||
<i aria-hidden="true" />
|
||||
离线
|
||||
</span>
|
||||
)}
|
||||
{state.waitingWorker && (
|
||||
<button type="button" className="pwa-action update" onClick={applyUpdate}>
|
||||
更新可用
|
||||
|
||||
Reference in New Issue
Block a user