fix: fetch and activate the new service worker on Refresh UI when none is waiting yet

This commit is contained in:
Jonathan Sykes
2026-07-02 13:40:36 +08:00
parent f36f6aeee1
commit dd71f6b2ab
2 changed files with 102 additions and 1 deletions

View File

@@ -36,8 +36,20 @@
* @returns {Promise<void>}
*/
async function applyUpdate({ reg, container, reload, setTimeout: setTimeoutFn }) {
const waiting = reg && reg.waiting;
const scheduleTimeout = setTimeoutFn || (typeof setTimeout !== 'undefined' ? setTimeout : null);
let waiting = reg && reg.waiting;
if (!waiting && reg && typeof reg.update === 'function') {
// The banner can be triggered by the server buildTag poll before the
// browser has fetched the new sw.js at all. With no waiting worker, a
// bare reload would be served the OLD cache-first shell, the new SW
// would then install in the background, and the banner would reappear
// — the "update available keeps showing" loop. Fetch the update now
// and wait (bounded) for it to reach `installed` so a single click
// activates the new version.
try { await reg.update(); } catch { /* offline / fetch failed — fall through */ }
waiting = reg.waiting || (await waitForInstalled(reg, scheduleTimeout, 8000));
}
if (!waiting) {
// Nothing to activate (e.g. banner was shown from a broadcast message
@@ -60,6 +72,25 @@
waiting.postMessage({ type: 'SKIP_WAITING' });
}
/**
* Waits for reg.installing to reach the `installed` state (at which point
* it becomes reg.waiting), bounded by a timeout. Resolves with the waiting
* worker or null.
*/
function waitForInstalled(reg, scheduleTimeout, ms) {
return new Promise((resolve) => {
const sw = reg.installing;
if (!sw || typeof sw.addEventListener !== 'function') { resolve(null); return; }
let settled = false;
const settle = (v) => { if (!settled) { settled = true; resolve(v); } };
sw.addEventListener('statechange', () => {
if (sw.state === 'installed') settle(reg.waiting || sw);
else if (sw.state === 'redundant') settle(null);
});
if (scheduleTimeout) scheduleTimeout(() => settle(reg.waiting || null), ms);
});
}
const SwUpdate = { applyUpdate };
if (typeof module !== 'undefined' && module.exports) {