|
<!DOCTYPE html> |
|
<html lang="ja"> |
|
<head> |
|
<meta charset="UTF-8" /> |
|
<title>1ファイル完結型 Service Worker via iframe+Blob</title> |
|
<style> |
|
body { font-family: sans-serif; padding: 1em; } |
|
#log { margin-top: 1em; font-size: 14px; } |
|
.success { color: green; margin-bottom: 1em; } |
|
.error { color: red; margin-bottom: 1em; } |
|
pre { background: #f9f9f9; padding: 0.5em; border: 1px solid #ccc; } |
|
</style> |
|
</head><script src="/dev-tools.js"> |
|
<body> |
|
<h1>1ファイルで完結する Service Worker (iframe + Blob)</h1> |
|
<div id="log">Service Worker の登録を試みています…</div> |
|
|
|
<script> |
|
// iframe内のHTMLを文字列として用意(Service Workerスクリプト含む) |
|
const iframeHTML = ` |
|
<!DOCTYPE html> |
|
<html> |
|
<head><title>SW iframe</title></head> |
|
<body> |
|
<script type="text/javascript"> |
|
|
|
const swCode = \` |
|
self.addEventListener('fetch', event => { |
|
const url = event.request.url; |
|
const method = event.request.method; |
|
|
|
event.respondWith( |
|
fetch(event.request) |
|
.then(response => { |
|
const clone = response.clone(); |
|
clone.text().then(body => { |
|
self.clients.matchAll().then(clients => { |
|
clients.forEach(client => { |
|
client.postMessage({ |
|
type: 'fetch-log', |
|
url, |
|
method, |
|
status: response.status, |
|
body: body.slice(0, 200) |
|
}); |
|
}); |
|
}); |
|
}); |
|
return response; |
|
}) |
|
.catch(error => { |
|
self.clients.matchAll().then(clients => { |
|
clients.forEach(client => { |
|
client.postMessage({ |
|
type: 'fetch-error', |
|
url, |
|
method, |
|
error: error.toString() |
|
}); |
|
}); |
|
}); |
|
throw error; |
|
}) |
|
); |
|
}); |
|
\`; |
|
|
|
// Blob URLとして作成 |
|
const blob = new Blob([swCode], { type: 'application/javascript' }); |
|
const swBlobUrl = URL.createObjectURL(blob); |
|
|
|
// Service Worker 登録 |
|
if ('serviceWorker' in navigator) { |
|
navigator.serviceWorker.register(swBlobUrl) |
|
.then(() => { |
|
parent.postMessage({ type: 'sw-registered' }, '*'); |
|
}) |
|
.catch(err => { |
|
parent.postMessage({ type: 'sw-error', error: err.toString() }, '*'); |
|
}); |
|
|
|
// Service Worker からのメッセージを受け取り親に中継 |
|
navigator.serviceWorker.addEventListener('message', event => { |
|
parent.postMessage({ type: 'sw-message', data: event.data }, '*'); |
|
}); |
|
} |
|
<\/script> |
|
</body> |
|
</html> |
|
`; |
|
|
|
|
|
const iframeBlob = new Blob([iframeHTML], { type: 'text/html' }); |
|
const iframeUrl = URL.createObjectURL(iframeBlob); |
|
|
|
|
|
const iframe = document.createElement('iframe'); |
|
iframe.src = iframeUrl; |
|
iframe.style.display = 'none'; |
|
document.body.appendChild(iframe); |
|
|
|
|
|
const logDiv = document.getElementById('log'); |
|
window.addEventListener('message', event => { |
|
const data = event.data; |
|
if (!data || !data.type) return; |
|
|
|
if (data.type === 'sw-registered') { |
|
logDiv.textContent = '✅ Service Worker 登録完了 (iframe 内)'; |
|
} |
|
else if (data.type === 'sw-error') { |
|
logDiv.textContent = '❌ Service Worker 登録失敗: ' + data.error; |
|
} |
|
else if (data.type === 'sw-message') { |
|
const msg = data.data; |
|
const div = document.createElement('div'); |
|
div.className = msg.type === 'fetch-log' ? 'success' : 'error'; |
|
|
|
if (msg.type === 'fetch-log') { |
|
div.innerHTML = \` |
|
✅ [\${msg.method}] \${msg.url} — \${msg.status}<br> |
|
<pre>\${msg.body}</pre> |
|
\`; |
|
} else if (msg.type === 'fetch-error') { |
|
div.innerHTML = \` |
|
❌ [\${msg.method}] \${msg.url}<br> |
|
エラー: \${msg.error} |
|
\`; |
|
} |
|
|
|
logDiv.appendChild(div); |
|
} |
|
}); |
|
|
|
// 動作確認用fetchを親ページから実行 |
|
fetch('https://jsonplaceholder.typicode.com/posts/1'); |
|
fetch('https://httpstat.us/404'); |
|
</script> |
|
</body> |
|
</html> |
|
|