Files
droplake/frontend/static/js/download.js
T

169 lines
6.1 KiB
JavaScript
Raw Normal View History

2026-04-02 16:05:40 +03:00
'use strict';
// ── Helpers ────────────────────────────────────────────────────────────────
function formatBytes(n) {
if (!n) return 'unknown size';
if (n < 1024) return n + ' B';
if (n < 1024 ** 2) return (n / 1024).toFixed(1) + ' KB';
if (n < 1024 ** 3) return (n / 1024 ** 2).toFixed(1) + ' MB';
return (n / 1024 ** 3).toFixed(2) + ' GB';
}
function formatExpiry(isoStr) {
return new Date(isoStr).toLocaleString();
}
function triggerDownload(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || 'download';
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 10000);
}
// ── DOM refs ───────────────────────────────────────────────────────────────
const statusWrap = document.getElementById('statusWrap');
const statusMsg = document.getElementById('statusMsg');
const infoGrid = document.getElementById('infoGrid');
const infoType = document.getElementById('infoType');
const infoSize = document.getElementById('infoSize');
const infoExpiry = document.getElementById('infoExpiry');
const infoBurn = document.getElementById('infoBurn');
const alertBox = document.getElementById('alertBox');
const pwSection = document.getElementById('pwSection');
const pwInput = document.getElementById('pwInput');
const downloadBtn = document.getElementById('downloadBtn');
const textSection = document.getElementById('textSection');
const textOutput = document.getElementById('textOutput');
const copyTextBtn = document.getElementById('copyTextBtn');
// ── Init ───────────────────────────────────────────────────────────────────
const shareId = location.pathname.split('/s/')[1];
(async function init() {
if (!shareId) { showError('Invalid share URL.'); return; }
let meta;
try {
const res = await fetch(`/api/share/${shareId}/meta`);
if (!res.ok) {
showError(res.status === 404
? 'This share does not exist or has expired.'
: 'Failed to load share info.');
return;
}
meta = await res.json();
} catch {
showError('Network error. Please check your connection.');
return;
}
if (meta.locked) {
showError(`Share locked — too many failed attempts. Try again in ${meta.lock_remaining}s.`);
return;
}
// Show info grid
statusWrap.classList.add('hidden');
infoGrid.classList.remove('hidden');
infoType.textContent = meta.share_type === 'file'
? (meta.original_filename || 'File') : 'Text snippet';
infoSize.textContent = formatBytes(meta.filesize);
infoExpiry.textContent = formatExpiry(meta.expires_at);
infoBurn.textContent = meta.burn_after_read ? 'Yes — deleted after this download' : 'No';
if (meta.has_password) pwSection.classList.remove('hidden');
downloadBtn.classList.remove('hidden');
// Attach handlers now that we have meta
downloadBtn.addEventListener('click', () => handleDownload(meta));
pwInput.addEventListener('keydown', e => { if (e.key === 'Enter') handleDownload(meta); });
})();
// ── Download ───────────────────────────────────────────────────────────────
async function handleDownload(meta) {
clearAlert();
downloadBtn.disabled = true;
downloadBtn.textContent = 'Downloading…';
try {
const res = await fetch(`/api/share/${shareId}/download`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: pwInput.value }),
});
if (res.status === 401) {
const body = await res.json().catch(() => ({}));
showAlert(body.detail || 'Incorrect password.');
pwInput.value = '';
pwInput.focus();
return;
}
if (res.status === 429) {
const body = await res.json().catch(() => ({}));
showAlert(body.detail || 'Too many attempts. Please wait.');
return;
}
if (!res.ok) {
showAlert('Download failed. The share may have expired.');
return;
}
if (meta.share_type === 'text') {
const text = await res.text();
textSection.classList.remove('hidden');
textOutput.textContent = text;
copyTextBtn.onclick = () => {
navigator.clipboard.writeText(text);
const orig = copyTextBtn.textContent;
copyTextBtn.textContent = 'Copied!';
setTimeout(() => (copyTextBtn.textContent = orig), 1500);
};
downloadBtn.textContent = 'Decrypted';
} else {
const blob = await res.blob();
triggerDownload(blob, meta.original_filename);
downloadBtn.textContent = 'Downloaded ✓';
}
if (meta.burn_after_read) {
showAlert('This share has been deleted — it can no longer be accessed.', 'info');
}
} catch (err) {
showAlert(err.message || 'An unexpected error occurred.');
} finally {
if (downloadBtn.textContent === 'Downloading…') {
downloadBtn.disabled = false;
downloadBtn.textContent = 'Decrypt & Download';
}
}
}
// ── UI helpers ─────────────────────────────────────────────────────────────
function showError(msg) {
statusWrap.classList.remove('hidden');
statusMsg.textContent = msg;
statusMsg.className = 'alert alert-error';
downloadBtn.classList.add('hidden');
}
function showAlert(msg, type = 'error') {
alertBox.className = `alert alert-${type}`;
alertBox.textContent = msg;
alertBox.classList.remove('hidden');
downloadBtn.disabled = false;
downloadBtn.textContent = 'Decrypt & Download';
}
function clearAlert() { alertBox.classList.add('hidden'); }