# sw.js を作る:各ファイルの中身のSHA-256を埋め込み、インストール時と保存時に照合する import hashlib, json, sys, os V=sys.argv[1]; D=sys.argv[2] files={'./index.html':'index.html','./style.css?v='+V:'style.css','./style-v11.css?v='+V:'style-v11.css','./core.js?v='+V:'core.js','./app.js?v='+V:'app.js','./manifest.webmanifest':'manifest.webmanifest','./icon.svg':'icon.svg'} H={k:hashlib.sha256(open(os.path.join(D,f),'rb').read()).hexdigest() for k,f in files.items()} sw=f"""const VERSION='{V}'; const CACHE='dochiga-otoku-v'+VERSION; const V='?v='+VERSION; // この版の各ファイルの中身(SHA-256)。インストール時に全部照合し、1つでも違えば失敗にする const HASH={json.dumps(H,indent=0,ensure_ascii=False)}; const ASSETS=Object.keys(HASH); const hex=b=>[...new Uint8Array(b)].map(x=>x.toString(16).padStart(2,'0')).join(''); const sha=res=>res.clone().arrayBuffer().then(b=>crypto.subtle.digest('SHA-256',b)).then(hex); // インストール:全ファイルを取得→中身を照合→全部一致したときだけ保存して有効化。失敗したら作りかけのキャッシュを消す self.addEventListener('install',e=>{{e.waitUntil(Promise.all(ASSETS.map(u=>fetch(new Request(u,{{cache:'reload'}})).then(res=>{{if(!res.ok)throw new Error('fetch '+u);return sha(res).then(h=>{{if(h!==HASH[u])throw new Error('hash '+u);return [u,res];}});}}))).then(list=>caches.open(CACHE).then(c=>{{const top=list.find(x=>x[0]==='./index.html')[1].clone();return Promise.all(list.map(([u,res])=>c.put(u,res)).concat(c.put('./',top)));}})).then(()=>self.skipWaiting(),err=>caches.delete(CACHE).then(()=>{{throw err;}})));}}); self.addEventListener('activate',e=>{{e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k.startsWith('dochiga-otoku-')&&k!==CACHE).map(k=>caches.delete(k)))).then(()=>self.clients.claim()));}}); const isPage=r=>r.mode==='navigate'||r.destination==='document'; const saved=()=>caches.match('./index.html',{{cacheName:CACHE}}); self.addEventListener('fetch',e=>{{ const r=e.request;const u=new URL(r.url); if(r.method!=='GET'||u.origin!==location.origin)return; if(isPage(r)){{ // 画面(HTML)はネット優先。キャッシュには書き込まない(インストール時に照合済みのものだけを使う)。エラー応答・通信失敗のときは照合済みの画面を返す e.respondWith(fetch(r,{{cache:'no-cache'}}).then(res=>res.ok?res:saved().then(m=>m||res),()=>saved().then(m=>m||Response.error()))); return; }} // JS・CSSなどは、この版のキャッシュ(照合済み)から完全一致で探し、なければネットから返すだけ。実行中にキャッシュへは書き込まない e.respondWith(caches.match(r,{{cacheName:CACHE}}).then(m=>m||fetch(r))); }}); """ open(os.path.join(D,'sw.js'),'w').write(sw) print(json.dumps(H,indent=1))