Synchronize verified assets incrementally and guard update activation
This commit is contained in:
@@ -64,7 +64,7 @@
|
||||
* @param {Function} [opts.setTimeout]
|
||||
* @returns {Promise<{refreshed: number, caches: number, resumed: number}>}
|
||||
*/
|
||||
async function refreshShellInPlace({ cachesApi, fetchFn, isShellCache, bust, timeoutMs, attempts, attemptTimeoutMs, concurrency, stagingKey, setTimeout: setTimeoutFn }) {
|
||||
async function refreshLegacyShell({ cachesApi, fetchFn, isShellCache, bust, timeoutMs, attempts, attemptTimeoutMs, concurrency, stagingKey, setTimeout: setTimeoutFn }) {
|
||||
const names = (await cachesApi.keys()).filter(isShellCache);
|
||||
if (!names.length) return { refreshed: 0, caches: 0, resumed: 0 }; // uncontrolled page: a reload already hits the network
|
||||
|
||||
@@ -145,6 +145,42 @@
|
||||
return { refreshed: urls.size, caches: names.length, resumed };
|
||||
}
|
||||
|
||||
async function refreshShellInPlace(opts) {
|
||||
const core = root.AssetSyncCore;
|
||||
if (core) {
|
||||
const response = await opts.fetchFn('/api/manifest', { cache: 'no-store' });
|
||||
if (!response.ok) throw new Error('Manifest unavailable');
|
||||
const m = await response.json();
|
||||
const cache = await opts.cachesApi.open(core.CACHE);
|
||||
// Only upgraded workers can route the persistent scheme. A rollback
|
||||
// worker explicitly advertises the legacy path through CACHE_STATUS.
|
||||
const reg = root.navigator && await root.navigator.serviceWorker.getRegistration();
|
||||
const worker = reg && (reg.waiting || reg.active);
|
||||
const status = await askCacheStatus(worker);
|
||||
if (status && status.assetSync) {
|
||||
const result = await core.syncAssets(m,{cache,fetchFn:opts.fetchFn});
|
||||
if (!reg.waiting) await core.commit(m,cache);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return refreshLegacyShell({ ...opts, isShellCache: n => n !== 'ytplayer-assets' && opts.isShellCache(n) });
|
||||
}
|
||||
|
||||
function playing() { return typeof Player !== 'undefined' && !!Player._wantsPlaying; }
|
||||
function reportPlaying(value = playing()) {
|
||||
const sw = root.navigator && root.navigator.serviceWorker;
|
||||
if (!sw) return;
|
||||
sw.getRegistration().then(reg => {
|
||||
for (const worker of [sw.controller, reg && reg.waiting]) if(worker) worker.postMessage({type:'PLAYING',value});
|
||||
}).catch(()=>{});
|
||||
}
|
||||
if (root.document && root.navigator && root.navigator.serviceWorker) {
|
||||
if (root.VisibleTimer) root.VisibleTimer.setIntervalWhenVisible(()=>reportPlaying(),1000);
|
||||
for (const event of ['play','pause','ended']) root.document.addEventListener(event,()=>reportPlaying(),true);
|
||||
root.document.addEventListener('visibilitychange',()=>reportPlaying());
|
||||
root.navigator.serviceWorker.addEventListener('controllerchange',()=>reportPlaying());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks a worker whether it has already precached its whole shell.
|
||||
* Resolves to null on any problem (no reply, no MessageChannel, timeout) —
|
||||
@@ -191,7 +227,14 @@
|
||||
* @param {Function} [opts.setTimeout] injectable for tests
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function applyUpdate({ reg, container, reload, refreshShell, askStatus, onPhase, setTimeout: setTimeoutFn }) {
|
||||
async function applyUpdate({ reg, container, reload, refreshShell, askStatus, onPhase, isPlaying = playing, confirmOverride = root.confirm && root.confirm.bind(root), setTimeout: setTimeoutFn }) {
|
||||
let overridden = false;
|
||||
const guard = () => {
|
||||
if (!isPlaying() || overridden) return true;
|
||||
if (confirmOverride && confirmOverride('Playing - update after this song / when paused. Update now anyway? Playback may stop.')) { overridden = true; return true; }
|
||||
return false;
|
||||
};
|
||||
if (!guard()) throw new Error('Playing - update after this song / when paused');
|
||||
const scheduleTimeout = setTimeoutFn || (typeof setTimeout !== 'undefined' ? setTimeout : null);
|
||||
const waiting = reg && reg.waiting;
|
||||
|
||||
@@ -209,6 +252,7 @@
|
||||
if (onPhase) onPhase(precached ? 'ready' : 'downloading');
|
||||
if (!precached && refreshShell) await refreshShell();
|
||||
|
||||
if (!guard()) throw new Error('Playing - update after this song / when paused');
|
||||
if (!waiting) {
|
||||
reload();
|
||||
return;
|
||||
@@ -216,7 +260,7 @@
|
||||
|
||||
let reloaded = false;
|
||||
const reloadOnce = () => {
|
||||
if (reloaded) return;
|
||||
if (reloaded || (isPlaying() && !overridden)) return;
|
||||
reloaded = true;
|
||||
reload();
|
||||
};
|
||||
@@ -224,6 +268,7 @@
|
||||
// either way; reload on controllerchange, or after a short safety net.
|
||||
container.addEventListener('controllerchange', reloadOnce, { once: true });
|
||||
if (scheduleTimeout) scheduleTimeout(reloadOnce, 4000);
|
||||
if (overridden) waiting.postMessage({type:'PLAYING',value:false});
|
||||
waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user