Files
warpbox-dev/backend/static/js/service-worker.js
Daniel Legt d11aec96e5 feat(backend): handle processing errors and add PWA routes
- Block file downloads and previews with a 424 StatusFailedDependency if file processing failed or the box has issues.
- Register routes for `/service-worker.js` and `/share-target` to support PWA features.
- Update README.md with an AI usage disclosure.
2026-06-08 11:53:37 +03:00

111 lines
3.2 KiB
JavaScript

self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (event.request.method === "POST" && url.origin === self.location.origin && url.pathname === "/share-target") {
event.respondWith(handleShareTarget(event.request));
}
});
const SHARE_CACHE = "warpbox-share-target-v1";
const SHARE_PREFIX = "/__warpbox_share_target__/";
const LATEST_KEY = SHARE_PREFIX + "latest";
async function handleShareTarget(request) {
const id = Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
try {
const formData = await request.formData();
const files = collectSharedFiles(formData);
const cache = await caches.open(SHARE_CACHE);
const metadata = {
id,
title: stringValue(formData.get("title")),
text: stringValue(formData.get("text")),
url: stringValue(formData.get("url")),
createdAt: new Date().toISOString(),
files: [],
};
await deletePreviousShare(cache);
for (let index = 0; index < files.length; index += 1) {
const file = files[index];
const key = SHARE_PREFIX + "file/" + encodeURIComponent(id) + "/" + index;
metadata.files.push({
key,
name: file.name || "shared-file",
type: file.type || "application/octet-stream",
size: file.size || 0,
lastModified: file.lastModified || Date.now(),
});
await cache.put(key, new Response(file, {
headers: {
"Content-Type": file.type || "application/octet-stream",
"Cache-Control": "no-store",
},
}));
}
await cache.put(LATEST_KEY, jsonResponse(metadata));
await cache.put(SHARE_PREFIX + "meta/" + encodeURIComponent(id), jsonResponse(metadata));
} catch (error) {
await storeShareError(id, error);
}
return Response.redirect("/?share-target=1&share-id=" + encodeURIComponent(id), 303);
}
function collectSharedFiles(formData) {
const files = [];
["files", "file", "sharex"].forEach((name) => {
formData.getAll(name).forEach((value) => {
if (value instanceof File && value.size > 0) {
files.push(value);
}
});
});
return files;
}
function stringValue(value) {
return typeof value === "string" ? value : "";
}
function jsonResponse(payload) {
return new Response(JSON.stringify(payload), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
},
});
}
async function storeShareError(id, error) {
const cache = await caches.open(SHARE_CACHE);
await cache.put(LATEST_KEY, jsonResponse({
id,
error: error && error.message ? error.message : "Shared files could not be staged.",
createdAt: new Date().toISOString(),
files: [],
}));
}
async function deletePreviousShare(cache) {
const previous = await cache.match(LATEST_KEY);
if (!previous) {
return;
}
let metadata = null;
try {
metadata = await previous.json();
} catch (error) {
metadata = null;
}
for (const file of metadata && metadata.files ? metadata.files : []) {
if (file.key) {
await cache.delete(file.key);
}
}
if (metadata && metadata.id) {
await cache.delete(SHARE_PREFIX + "meta/" + encodeURIComponent(metadata.id));
}
await cache.delete(LATEST_KEY);
}