Check for Updates
This commit is contained in:
parent
4805e8c4d9
commit
535ed6835f
1 changed files with 188 additions and 5 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import { registerSW } from 'virtual:pwa-register'
|
||||
import { store } from '@/store'
|
||||
|
||||
const ACTIVATION_RETRY_DELAY = 5_000
|
||||
const ACTIVATION_TIMEOUT = 30_000
|
||||
const ACTIVATION_TIMEOUT = 20_000
|
||||
|
||||
let registrationStarted = false
|
||||
let activationInProgress = false
|
||||
|
|
@ -11,10 +12,104 @@ let activateUpdateHandler: ((reloadPage?: boolean) => Promise<void>) | undefined
|
|||
let registrationInitialization: Promise<void> | undefined
|
||||
let activationRetryTimer: number | undefined
|
||||
let activationTimeoutTimer: number | undefined
|
||||
let overlayStoreUnsubscribe: (() => void) | undefined
|
||||
const watchedInstallingWorkers = new WeakSet<ServiceWorker>()
|
||||
|
||||
export type ManualUpdateResult = 'up-to-date' | 'updating' | 'unsupported'
|
||||
|
||||
type Release = {
|
||||
version: string
|
||||
changeLog?: string[]
|
||||
}
|
||||
|
||||
const getText = (value: unknown) =>
|
||||
value === undefined || value === null ? undefined : String(value)
|
||||
|
||||
function updateOverlayApiVersion(overlay: HTMLElement) {
|
||||
const apiConfig = store.getState().abpConfig.config?.extraProperties
|
||||
const apiEnvironment = getText(apiConfig?.environment)
|
||||
const apiVersion = getText(apiConfig?.version)
|
||||
|
||||
if (!apiEnvironment && !apiVersion) return false
|
||||
|
||||
const apiValue = overlay.querySelector<HTMLElement>('[data-sw-api-version]')
|
||||
if (apiValue) {
|
||||
apiValue.textContent = `${apiEnvironment ?? '-'}:${apiVersion ?? '-'}`
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function watchOverlayApiVersion(overlay: HTMLElement) {
|
||||
overlayStoreUnsubscribe?.()
|
||||
|
||||
if (updateOverlayApiVersion(overlay)) return
|
||||
|
||||
overlayStoreUnsubscribe = store.subscribe(() => {
|
||||
if (!overlay.isConnected || updateOverlayApiVersion(overlay)) {
|
||||
overlayStoreUnsubscribe?.()
|
||||
overlayStoreUnsubscribe = undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadUpdateOverlayDetails(overlay: HTMLElement) {
|
||||
const state = store.getState()
|
||||
const uiEnvironment = import.meta.env.MODE
|
||||
|
||||
let uiVersion = state.locale.currentUiVersion
|
||||
let latestRelease: Release | undefined
|
||||
|
||||
try {
|
||||
const response = await fetch(`/version.json?ts=${Date.now()}`, { cache: 'no-store' })
|
||||
if (!response.ok) throw new Error(`Version request failed with ${response.status}`)
|
||||
|
||||
const releases = ((await response.json()) as { releases?: Release[] }).releases
|
||||
latestRelease = releases?.[0]
|
||||
uiVersion = latestRelease?.version ?? uiVersion
|
||||
} catch (error) {
|
||||
console.warn('Update details could not be loaded.', error)
|
||||
}
|
||||
|
||||
// The overlay may have been removed while version.json was loading.
|
||||
if (!overlay.isConnected) return
|
||||
|
||||
const uiValue = overlay.querySelector<HTMLElement>('[data-sw-ui-version]')
|
||||
const releaseTitle = overlay.querySelector<HTMLElement>('[data-sw-release-title]')
|
||||
const releaseList = overlay.querySelector<HTMLUListElement>('[data-sw-release-list]')
|
||||
|
||||
if (uiValue) uiValue.textContent = `${uiEnvironment}:${uiVersion ?? '-'}`
|
||||
watchOverlayApiVersion(overlay)
|
||||
|
||||
if (!releaseList || !releaseTitle) return
|
||||
|
||||
const changes = latestRelease?.changeLog?.filter(Boolean) ?? []
|
||||
releaseTitle.textContent = latestRelease?.version
|
||||
? `What's new in v${latestRelease.version}`
|
||||
: "What's new"
|
||||
releaseList.replaceChildren()
|
||||
|
||||
if (changes.length === 0) {
|
||||
const item = document.createElement('li')
|
||||
item.className = 'sw-update-empty'
|
||||
item.textContent = 'Release notes are not available.'
|
||||
releaseList.appendChild(item)
|
||||
return
|
||||
}
|
||||
|
||||
changes.forEach((change) => {
|
||||
const item = document.createElement('li')
|
||||
const icon = document.createElement('span')
|
||||
const text = document.createElement('span')
|
||||
icon.className = 'sw-update-check'
|
||||
icon.setAttribute('aria-hidden', 'true')
|
||||
icon.textContent = '\u2713'
|
||||
text.textContent = change.replace(/^\s*-\s*/, '')
|
||||
item.append(icon, text)
|
||||
releaseList.appendChild(item)
|
||||
})
|
||||
}
|
||||
|
||||
function showUpdateOverlay() {
|
||||
if (document.getElementById('sw-update-overlay')) return
|
||||
|
||||
|
|
@ -36,13 +131,14 @@ function showUpdateOverlay() {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 40px 56px;
|
||||
padding: 32px 40px;
|
||||
box-shadow: 0 8px 40px rgba(0,0,0,0.25);
|
||||
text-align: center;
|
||||
max-width: 360px;
|
||||
max-width: 560px;
|
||||
max-height: min(90vh, 720px);
|
||||
width: 90%;
|
||||
}
|
||||
#sw-update-overlay .sw-update-spinner {
|
||||
|
|
@ -67,22 +163,109 @@ function showUpdateOverlay() {
|
|||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
#sw-update-overlay .sw-update-versions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
#sw-update-overlay .sw-update-separator {
|
||||
color: #9ca3af;
|
||||
margin: 0 4px;
|
||||
}
|
||||
#sw-update-overlay .sw-update-release {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 14px;
|
||||
}
|
||||
#sw-update-overlay .sw-update-release-title {
|
||||
margin: 0 0 10px;
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
#sw-update-overlay .sw-update-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
#sw-update-overlay .sw-update-list li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
#sw-update-overlay .sw-update-check {
|
||||
display: inline-flex;
|
||||
flex: 0 0 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
border-radius: 50%;
|
||||
background: #d1fae5;
|
||||
color: #059669;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
#sw-update-overlay .sw-update-empty {
|
||||
display: block;
|
||||
color: #9ca3af;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
#sw-update-overlay .sw-update-card {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
|
||||
const overlay = document.createElement('div')
|
||||
overlay.id = 'sw-update-overlay'
|
||||
overlay.setAttribute('role', 'status')
|
||||
overlay.setAttribute('aria-live', 'polite')
|
||||
overlay.innerHTML = `
|
||||
<div class="sw-update-card">
|
||||
<div class="sw-update-spinner"></div>
|
||||
<p class="sw-update-title">System Updating</p>
|
||||
<p class="sw-update-title">System Updating</p>
|
||||
<p class="sw-update-desc">Loading new version, please wait...<br/>The page will reload automatically.</p>
|
||||
<div class="sw-update-versions">
|
||||
<span><strong>UI:</strong> <span data-sw-ui-version>Loading...</span></span>
|
||||
<span class="sw-update-separator">|</span>
|
||||
<span><strong>API:</strong> <span data-sw-api-version>Loading...</span></span>
|
||||
</div>
|
||||
<div class="sw-update-release">
|
||||
<p class="sw-update-release-title" data-sw-release-title>What's new</p>
|
||||
<ul class="sw-update-list" data-sw-release-list>
|
||||
<li class="sw-update-empty">Loading release notes...</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
document.body.appendChild(overlay)
|
||||
void loadUpdateOverlayDetails(overlay)
|
||||
}
|
||||
|
||||
function hideUpdateOverlay() {
|
||||
overlayStoreUnsubscribe?.()
|
||||
overlayStoreUnsubscribe = undefined
|
||||
document.getElementById('sw-update-overlay')?.remove()
|
||||
document.getElementById('sw-update-overlay-style')?.remove()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue