Load layouts and optional features from staged asset caches
This commit is contained in:
@@ -74,6 +74,10 @@ const SHELL = [
|
||||
'/async-guard.js',
|
||||
'/visible-timer.js',
|
||||
'/asset-sync-core.js',
|
||||
'/lazy.js',
|
||||
'/feature-bootstrap.js',
|
||||
'/eq-parameters.js',
|
||||
'/assets.json',
|
||||
'/sw-update.js',
|
||||
'/fingerprint.js',
|
||||
'/opfs.js',
|
||||
@@ -287,7 +291,27 @@ self.addEventListener('fetch', (e) => {
|
||||
|
||||
// ---- Message: handle SKIP_WAITING from the client ----
|
||||
const playingClients = new Map(), deferredClients = new Set();
|
||||
let activeLayout = 'classic', layoutReply;
|
||||
self.addEventListener('message', (e) => {
|
||||
if (ASSET_SYNC && e.data?.type === 'LAYOUT' && e.source && typeof e.data.value === 'string') {
|
||||
activeLayout = e.data.value;
|
||||
layoutReply?.();
|
||||
e.waitUntil((async()=>{
|
||||
const cache=await caches.open(AssetSyncCore.CACHE);
|
||||
await cache.put('/__ytp_layout',Response.json({layout:activeLayout}));
|
||||
const m=await candidate(cache);
|
||||
if(m && m.activeLayout!==activeLayout) {
|
||||
await AssetSyncCore.syncAssets(m,{cache,fetchFn:fetch,activeLayout});
|
||||
m.activeLayout=activeLayout; await cache.put(candidateKey,Response.json(m));
|
||||
}
|
||||
})());
|
||||
}
|
||||
if (ASSET_SYNC && e.data?.type === 'WARM_ASSETS' && !e.data.saveData) {
|
||||
e.waitUntil((async()=>{
|
||||
const cache=await caches.open(AssetSyncCore.CACHE), m=(await AssetSyncCore.state(cache))?.current;
|
||||
if(m)await AssetSyncCore.syncAssets(m,{cache,fetchFn:fetch,groups:Object.keys(m.groups).filter(name=>m.groups[name].background)});
|
||||
})().catch(error=>console.warn('[asset-sync] idle warm interrupted',error.message)));
|
||||
}
|
||||
if (e.data && e.data.type === 'PLAYING' && e.source) {
|
||||
playingClients.set(e.source.id, !!e.data.value);
|
||||
if (!e.data.value && deferredClients.delete(e.source.id)) self.skipWaiting();
|
||||
@@ -306,7 +330,11 @@ self.addEventListener('message', (e) => {
|
||||
e.waitUntil((async () => {
|
||||
if (ASSET_SYNC) {
|
||||
let reply = { ready: false, missing: -1, version: VERSION };
|
||||
try { const cache = await caches.open(AssetSyncCore.CACHE); const m = await candidate(cache) || (await AssetSyncCore.state(cache))?.current; if(m) reply = await AssetSyncCore.status(m,cache); } catch {}
|
||||
try {
|
||||
const cache = await caches.open(AssetSyncCore.CACHE), state = await AssetSyncCore.state(cache);
|
||||
const pending = await candidate(cache), m = pending || state?.current;
|
||||
if(m) reply = await AssetSyncCore.status(m,cache,{previous:pending ? state?.current : state?.previous,activeLayout:e.data.activeLayout || (pending ? m.activeLayout : activeLayout) || 'classic'});
|
||||
} catch {}
|
||||
reply.type = 'CACHE_STATUS'; reply.assetSync = true;
|
||||
if(e.ports && e.ports[0]) e.ports[0].postMessage(reply); else if(e.source) e.source.postMessage(reply);
|
||||
return;
|
||||
@@ -446,6 +474,15 @@ async function installAssets() {
|
||||
const m = await r.json();
|
||||
if (m.buildTag !== VERSION) throw Error('Deployment changed during install');
|
||||
const cache = await caches.open(AssetSyncCore.CACHE);
|
||||
const remembered = await cache.match('/__ytp_layout');
|
||||
if(remembered)activeLayout=(await remembered.json()).layout || 'classic';
|
||||
let layoutKnown = !!remembered;
|
||||
const clients = await self.clients.matchAll({type:'window',includeUncontrolled:true});
|
||||
await new Promise(resolve=>{
|
||||
const timer=setTimeout(resolve,500); layoutReply=()=>{layoutKnown=true;clearTimeout(timer);resolve();};
|
||||
for(const client of clients)client.postMessage({type:'ASSET_LAYOUT_REQUEST'});
|
||||
});
|
||||
layoutReply=null; m.activeLayout=activeLayout;
|
||||
const freshInstall = !(await AssetSyncCore.state(cache));
|
||||
const fetchAsset = async (url,init) => {
|
||||
// Fonts/icons still use plain URLs in CSS/manifest. On first install reuse
|
||||
@@ -456,7 +493,14 @@ async function installAssets() {
|
||||
}
|
||||
return fetch(url,init);
|
||||
};
|
||||
await AssetSyncCore.syncAssets(m, { cache, fetchFn: fetchAsset });
|
||||
await AssetSyncCore.syncAssets(m, { cache, fetchFn: fetchAsset, activeLayout });
|
||||
// Older pages cannot report their saved choice. Keep every layout ready for
|
||||
// that one migration so an immediate offline reload cannot lose its theme.
|
||||
if(!layoutKnown && ((await AssetSyncCore.state(cache)) || (await caches.keys()).some(isVersionedShellCache))) {
|
||||
const layouts=new Set(Object.keys(m.groups).filter(name=>name.startsWith('layout:')));
|
||||
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]});
|
||||
}
|
||||
await cache.put(candidateKey, new Response(JSON.stringify(m)));
|
||||
}
|
||||
async function migrateLegacy(cache, m) {
|
||||
@@ -489,7 +533,7 @@ async function activateAssets() {
|
||||
if (!m) throw Error('Missing candidate manifest');
|
||||
const names = await migrateLegacy(cache,m), old = await AssetSyncCore.state(cache);
|
||||
const previousClients = (await self.clients.matchAll({type:'window'})).map(c=>c.id);
|
||||
await AssetSyncCore.commit(m,cache,{previousClients});
|
||||
await AssetSyncCore.commit(m,cache,{previousClients,activeLayout:m.activeLayout || activeLayout});
|
||||
// Legacy deletion occurs strictly after verified commit.
|
||||
await Promise.all(names.map(n => caches.delete(n)));
|
||||
for (const req of await cache.keys()) if(new URL(req.url).pathname.startsWith('/__ytp_candidate/')) await cache.delete(req);
|
||||
@@ -516,6 +560,11 @@ async function assetFetch(request, clientId) {
|
||||
const cached = await cache.match(key);
|
||||
const h = new URL(key,self.location.origin).searchParams.get('v');
|
||||
if(cached && cached.headers.get('X-Asset-Hash') === h) return cached;
|
||||
if(state.current.files[path]?.h === h) {
|
||||
const previousKey=AssetSyncCore.fallback(state.current,state.previous,path);
|
||||
const previousResponse=previousKey && await cache.match(previousKey);
|
||||
if(previousResponse && previousResponse.headers.get('X-Asset-Hash')===state.previous.files[path].h) return previousResponse;
|
||||
}
|
||||
try { const r = await fetch(key); if(r.ok && r.headers.get('X-Asset-Hash') !== h) return new Response('Asset version unavailable',{status:409}); if(r.ok && request.method !== 'HEAD') await cache.put(key,r.clone()); return r; } catch { return new Response('Offline',{status:503}); }
|
||||
}
|
||||
try { return await fetch(request); } catch { return new Response('Offline',{status:503}); }
|
||||
|
||||
Reference in New Issue
Block a user