Stage legacy asset migration before activation

This commit is contained in:
Jonathan Sykes
2026-10-10 18:40:32 +08:00
parent 7591c2d14c
commit b3f0525a8e
2 changed files with 51 additions and 3 deletions

View File

@@ -305,13 +305,18 @@ self.addEventListener('fetch', (e) => {
const playingClients = new Map(), deferredClients = new Set();
let activeLayout = 'classic', layoutReply;
let completionTask;
function completeAssets(force = false) {
function completeAssets(force = false, requestedOptIns) {
if (!ASSET_SYNC) return Promise.resolve();
if (completionTask) {
if (!requestedOptIns) return completionTask;
return completionTask.finally(() => completeAssets(true, requestedOptIns));
}
completionTask ||= (async () => {
const cache = await caches.open(AssetSyncCore.CACHE);
const m = (await AssetSyncCore.state(cache))?.current;
if (!m || m.buildTag !== VERSION) return;
const job = await AssetSyncCore.complete(m, { cache, fetchFn: fetch, force,
const optIns = requestedOptIns || await AssetSyncCore.readOptIns(cache);
const job = await AssetSyncCore.complete(m, { cache, fetchFn: fetch, force, optIns,
notify: async status => {
for (const client of await self.clients.matchAll({ type: 'window' }))
client.postMessage({ type: 'OFFLINE_STATUS', ...status });
@@ -330,6 +335,16 @@ self.addEventListener('sync', e => {
if (e.tag === 'ytp-offline-complete') e.waitUntil(completeAssets());
});
self.addEventListener('message', (e) => {
if (ASSET_SYNC && e.data?.type === 'OFFLINE_OPT_INS' && Array.isArray(e.data.values)) {
e.waitUntil((async () => {
const cache = await caches.open(AssetSyncCore.CACHE);
const current = (await AssetSyncCore.state(cache))?.current || await candidate(cache);
const allowed = new Set(Object.values(current?.groups || {}).map(group => group.offlineOptIn).filter(Boolean));
const values = [...new Set(e.data.values.filter(value => typeof value === 'string' && allowed.has(value)))];
await cache.put(AssetSyncCore.OPT_INS, Response.json(values));
await completeAssets(true, values);
})());
}
if (ASSET_SYNC && e.data?.type === 'LAYOUT' && e.source && typeof e.data.value === 'string') {
activeLayout = e.data.value;
layoutReply?.();
@@ -536,6 +551,10 @@ async function installAssets() {
for(const name of layouts)for(const dependency of m.groups[name].dependencies || [])layouts.add(dependency);
await AssetSyncCore.syncAssets(m,{cache,fetchFn:fetchAsset,groups:[...layouts]});
}
// Copy and hash legacy shell entries before the candidate reaches `installed`.
// A legacy page's Refresh UI then activates quickly, before its four-second
// reload safety timer can send it back into the old shell.
m.legacyCacheNames = await migrateLegacy(cache,m);
await cache.put(candidateKey, new Response(JSON.stringify(m)));
}
async function migrateLegacy(cache, m) {
@@ -566,7 +585,11 @@ async function migrateLegacy(cache, m) {
async function activateAssets() {
const cache = await caches.open(AssetSyncCore.CACHE), m = await candidate(cache);
if (!m) throw Error('Missing candidate manifest');
const names = await migrateLegacy(cache,m), old = await AssetSyncCore.state(cache);
// Legacy migration is staged by installAssets. Keep the fallback for
// candidates created by an older worker version.
const names = m.legacyCacheNames || await migrateLegacy(cache,m);
delete m.legacyCacheNames;
const old = await AssetSyncCore.state(cache);
const previousClients = (await self.clients.matchAll({type:'window'})).map(c=>c.id);
await AssetSyncCore.commit(m,cache,{previousClients,activeLayout:m.activeLayout || activeLayout});
// Legacy deletion occurs strictly after verified commit.