Fix SW not ready: add registration + wait for controller

The service worker was never registered. Added registration in the
base template. Upload logic now waits for the SW to claim the page
instead of erroring, with a direct-fetch fallback if SW times out.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-10 18:14:53 +00:00
parent 0ce44383b5
commit 6c4c21591f
2 changed files with 57 additions and 9 deletions

View File

@ -161,6 +161,18 @@
{% block content %}{% endblock %}
</div>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/portal/sw.js').then(reg => {
// If the SW is installed but not yet controlling this page, reload once
if (reg.active && !navigator.serviceWorker.controller) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
// Controller is now active, no need to reload
});
}
});
}
</script>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@ -404,9 +404,10 @@ async function uploadFile(file, action) {
// Store blob in IndexedDB so service worker can access it
await dbPut('uploads', { id, blob: file, filename: file.name });
// Tell service worker to upload
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
try {
// Wait for SW controller if not ready yet (first visit)
const ctrl = await getSWController();
ctrl.postMessage({
type: 'START_UPLOAD',
id,
uploadUrl: UPLOAD_URL,
@ -414,12 +415,47 @@ async function uploadFile(file, action) {
filename: file.name,
action: action || null,
});
} else {
// Fallback: no SW controller yet, upload directly
showError(item, file.name, 'Service worker not ready — please refresh and try again');
} catch (e) {
// SW not available — fall back to direct upload from page
directUpload(item, id, file, action);
}
}
async function directUpload(item, id, file, action) {
try {
const fd = new FormData();
fd.append('file', file, file.name);
fd.append('space', SPACE);
if (action) fd.append('action', action);
const resp = await fetch(UPLOAD_URL, { method: 'POST', body: fd });
const result = await resp.json();
await dbDelete('uploads', id);
if (resp.status === 409 && result.duplicate) {
showDuplicate(item, id, result, file.name);
} else if (resp.ok && result.success) {
showSuccess(item, result);
} else {
showError(item, file.name, result.error || 'Upload failed');
}
} catch (err) {
showError(item, file.name, err.message);
}
}
function getSWController() {
return new Promise((resolve, reject) => {
if (navigator.serviceWorker.controller) {
return resolve(navigator.serviceWorker.controller);
}
// Wait for the SW to claim this page (happens after first install)
const timeout = setTimeout(() => reject(new Error('SW timeout')), 10000);
navigator.serviceWorker.addEventListener('controllerchange', () => {
clearTimeout(timeout);
resolve(navigator.serviceWorker.controller);
}, { once: true });
});
}
// --- Listen for messages from service worker ---
navigator.serviceWorker.addEventListener('message', (event) => {
@ -468,9 +504,9 @@ navigator.serviceWorker.addEventListener('message', (event) => {
});
// On page load, ask SW for any completed results from background uploads
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({ type: 'GET_RESULTS' });
}
getSWController().then(ctrl => {
ctrl.postMessage({ type: 'GET_RESULTS' });
}).catch(() => {});
function copyLink(btn, url) {
navigator.clipboard.writeText(url).then(() => {