Paint saved playlists before the launch sync and swap a background-downloaded update instantly
This commit is contained in:
@@ -109,11 +109,44 @@
|
||||
return { refreshed: fetched.length, caches: names.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks a worker whether it has already precached its whole shell.
|
||||
* Resolves to null on any problem (no reply, no MessageChannel, timeout) —
|
||||
* the caller then takes the download path, which is always correct.
|
||||
*
|
||||
* @param {ServiceWorker} worker
|
||||
* @param {object} [opts]
|
||||
* @param {number} [opts.timeoutMs]
|
||||
* @param {Function} [opts.setTimeout]
|
||||
* @param {Function} [opts.Channel] MessageChannel constructor (injectable)
|
||||
* @returns {Promise<{ready: boolean, version: string}|null>}
|
||||
*/
|
||||
function askCacheStatus(worker, { timeoutMs = 3000, setTimeout: setTimeoutFn, Channel } = {}) {
|
||||
const Ctor = Channel || (typeof MessageChannel !== 'undefined' ? MessageChannel : null);
|
||||
const schedule = setTimeoutFn || (typeof setTimeout !== 'undefined' ? setTimeout : null);
|
||||
if (!worker || !Ctor) return Promise.resolve(null);
|
||||
return new Promise((resolve) => {
|
||||
let done = false;
|
||||
const finish = (v) => { if (!done) { done = true; resolve(v); } };
|
||||
try {
|
||||
const ch = new Ctor();
|
||||
ch.port1.onmessage = (ev) => finish(ev && ev.data ? ev.data : null);
|
||||
worker.postMessage({ type: 'CACHE_STATUS' }, [ch.port2]);
|
||||
} catch { finish(null); return; }
|
||||
if (schedule) schedule(() => finish(null), timeoutMs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an update: refresh the shell in place (throws on failure — the
|
||||
* caller reports it and nothing reloads), then activate a waiting worker if
|
||||
* there is one, then reload exactly once.
|
||||
*
|
||||
* Fast path: a waiting worker precached the entire new shell while it
|
||||
* installed, so when it confirms that, the download is skipped and the swap
|
||||
* is immediate. If it cannot confirm, the all-or-nothing download runs as
|
||||
* before — the slow path is still what guarantees correctness.
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {ServiceWorkerRegistration|null} opts.reg
|
||||
* @param {ServiceWorkerContainer} opts.container
|
||||
@@ -122,12 +155,24 @@
|
||||
* @param {Function} [opts.setTimeout] injectable for tests
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function applyUpdate({ reg, container, reload, refreshShell, setTimeout: setTimeoutFn }) {
|
||||
async function applyUpdate({ reg, container, reload, refreshShell, askStatus, onPhase, setTimeout: setTimeoutFn }) {
|
||||
const scheduleTimeout = setTimeoutFn || (typeof setTimeout !== 'undefined' ? setTimeout : null);
|
||||
|
||||
if (refreshShell) await refreshShell();
|
||||
|
||||
const waiting = reg && reg.waiting;
|
||||
|
||||
// Did the waiting worker already download this build in the background?
|
||||
// Opt-in: without an askStatus the download path runs exactly as before.
|
||||
// (It must never be implicit — a probe that waits on a reply would hang
|
||||
// forever if the caller injected a setTimeout that never fires.)
|
||||
let precached = false;
|
||||
if (waiting && askStatus) {
|
||||
try {
|
||||
const status = await askStatus(waiting);
|
||||
precached = !!(status && status.ready);
|
||||
} catch { precached = false; }
|
||||
}
|
||||
if (onPhase) onPhase(precached ? 'ready' : 'downloading');
|
||||
if (!precached && refreshShell) await refreshShell();
|
||||
|
||||
if (!waiting) {
|
||||
reload();
|
||||
return;
|
||||
@@ -146,7 +191,7 @@
|
||||
waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
|
||||
const SwUpdate = { applyUpdate, refreshShellInPlace, BUST_PARAM };
|
||||
const SwUpdate = { applyUpdate, refreshShellInPlace, askCacheStatus, BUST_PARAM };
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = SwUpdate;
|
||||
|
||||
Reference in New Issue
Block a user