proper productionize project

This commit is contained in:
2026-07-15 23:54:53 -07:00
parent b8850c87a4
commit edb6d2b5b4
46 changed files with 11346 additions and 1560 deletions
+214
View File
@@ -0,0 +1,214 @@
import { useEffect, useSyncExternalStore } from 'react'
interface InstallChoice {
outcome: 'accepted' | 'dismissed'
platform: string
}
interface BeforeInstallPromptEvent extends Event {
readonly platforms: string[]
readonly userChoice: Promise<InstallChoice>
prompt(): Promise<void>
}
interface PwaSnapshot {
online: boolean
standalone: boolean
installPrompt?: BeforeInstallPromptEvent
registration?: ServiceWorkerRegistration
waitingWorker?: ServiceWorker
}
const listeners = new Set<() => void>()
const updateBlockers = new Map<string, string>()
let snapshot: PwaSnapshot = {
online: navigator.onLine,
standalone: isStandalone(),
}
let initialized = false
let reloadForUpdate = false
function isStandalone(): boolean {
const iosNavigator = navigator as Navigator & { standalone?: boolean }
return window.matchMedia('(display-mode: standalone)').matches || iosNavigator.standalone === true
}
function emit(patch: Partial<PwaSnapshot>) {
snapshot = { ...snapshot, ...patch }
listeners.forEach(listener => listener())
}
function subscribe(listener: () => void) {
listeners.add(listener)
return () => listeners.delete(listener)
}
function getSnapshot() {
return snapshot
}
function addManifest() {
if (document.head.querySelector('link[rel="manifest"]')) return
const manifest = document.createElement('link')
manifest.rel = 'manifest'
manifest.href = '/control/manifest.webmanifest'
document.head.append(manifest)
}
function observeRegistration(registration: ServiceWorkerRegistration) {
emit({
registration,
waitingWorker: registration.waiting && navigator.serviceWorker.controller
? registration.waiting
: snapshot.waitingWorker,
})
registration.addEventListener('updatefound', () => {
const worker = registration.installing
if (!worker) return
worker.addEventListener('statechange', () => {
if (worker.state === 'installed' && navigator.serviceWorker.controller) {
emit({ waitingWorker: worker })
}
})
})
}
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()
}))
const cacheNames = await caches.keys()
await Promise.all(cacheNames
.filter(name => name.startsWith('lxc-control-shell-'))
.map(name => caches.delete(name)))
}
export function cleanupLegacyPwa() {
if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return
void removeLegacyRootWorker().catch(error => {
console.warn('旧版 PWA 清理失败', error)
})
}
async function registerWorker() {
try {
await removeLegacyRootWorker()
const registration = await navigator.serviceWorker.register(
`/control/sw.js?v=${encodeURIComponent(__PWA_BUILD_ID__)}`,
{ scope: '/control/', updateViaCache: 'none' },
)
observeRegistration(registration)
} catch (error) {
// A failed PWA registration must never stop the online control console.
console.warn('控制台 PWA 注册失败', error)
}
}
export function initializePwa() {
if (initialized || location.pathname === '/obs' || location.pathname.startsWith('/obs/')) return
initialized = true
addManifest()
const displayMode = window.matchMedia('(display-mode: standalone)')
const updateConnection = () => emit({ online: navigator.onLine })
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
if (modernListener) modernListener.call(displayMode, 'change', 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 }))
if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!reloadForUpdate) return
reloadForUpdate = false
location.reload()
})
const start = () => void registerWorker()
if (document.readyState === 'complete') start()
else window.addEventListener('load', start, { once: true })
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && navigator.onLine) {
void snapshot.registration?.update()
}
})
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/')
return inControlScope ? `/control/${name}` : `/${name}`
}
export function usePwaUpdateBlocker(key: string, reason: string, active: boolean) {
useEffect(() => {
if (active) updateBlockers.set(key, reason)
else updateBlockers.delete(key)
return () => { updateBlockers.delete(key) }
}, [active, key, reason])
}
async function requestInstall() {
const prompt = snapshot.installPrompt
if (!prompt) return
await prompt.prompt()
await prompt.userChoice
emit({ installPrompt: undefined, standalone: isStandalone() })
}
function applyUpdate() {
const worker = snapshot.waitingWorker
if (!worker) return
const reasons = [...new Set(updateBlockers.values())]
if (reasons.length > 0) {
window.alert(`暂时不能更新,请先处理以下内容:\n\n${reasons.map(reason => `• ${reason}`).join('\n')}`)
return
}
const confirmed = window.confirm('更新会刷新控制台。请先保存设置、邀请码、恢复码或刚轮换的 OBS 令牌,确定现在更新吗?')
if (!confirmed) return
reloadForUpdate = true
worker.postMessage({ type: 'SKIP_WAITING' })
}
export function PwaControls() {
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
const canInstall = Boolean(state.installPrompt) && !state.standalone
if (state.online && !state.waitingWorker && !canInstall) return null
return (
<div className="pwa-controls" aria-live="polite">
{!state.online && <span className="pwa-state offline"><i aria-hidden="true" />离线</span>}
{state.waitingWorker && (
<button type="button" className="pwa-action update" onClick={applyUpdate}>
更新可用
</button>
)}
{canInstall && (
<button type="button" className="pwa-action install" onClick={() => void requestInstall()}>
安装到设备
</button>
)}
</div>
)
}