// Files sent to PrivaPub in pieces, read by the browser and never copied into .NET's memory: a backup can be gigabytes. // Each piece is a slice of the picked file PUT at the offset PrivaPub has received; a piece refused (409) or lost to the // network carries on from what PrivaPub says it has, so a broken upload resumes. Progress goes to listener.Progress. const attempts = 6; function pause(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function received(endpoint, token) { const response = await fetch(endpoint, { headers: { Authorization: `Bearer ${token}` } }); if (!response.ok) throw new Error(`PrivaPub answered ${response.status}`); return (await response.json()).received; } // the picked file's name and size, or null export function picked(selector) { const file = document.querySelector(selector)?.files?.[0]; return file ? { name: file.name, size: file.size } : null; } // sends the picked file: { ok, size } or { ok: false, status, error } export async function upload(selector, endpoint, token, chunkBytes, listener) { const file = document.querySelector(selector)?.files?.[0]; if (!file) return { ok: false, status: 0, error: "no file" }; let offset = 0; let failures = 0; while (offset < file.size) { const end = Math.min(offset + chunkBytes, file.size); let response; try { response = await fetch(`${endpoint}?offset=${offset}`, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/octet-stream" }, body: file.slice(offset, end) }); } catch (error) { if (++failures >= attempts) return { ok: false, status: 0, error: String(error) }; await pause(1000 * 2 ** failures); try { offset = await received(endpoint, token); } catch { // still away: try the same piece again } continue; } if (response.ok || response.status === 409) { const now = (await response.json()).received; // refused where it already stood: the piece itself is refused, and sending it again changes nothing if (!response.ok && now === offset && ++failures >= attempts) return { ok: false, status: 409, error: "the piece was refused" }; if (now !== offset) failures = 0; offset = now; await listener.invokeMethodAsync("Progress", offset, file.size); continue; } return { ok: false, status: response.status, error: await response.text() }; } return { ok: true, size: file.size }; }