Files
decePubClient/wwwroot/js/transfer.js
T

65 lines
2.4 KiB
JavaScript
Raw Normal View History

// 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 };
}