mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
refactor: merge download.js into download.html
This commit is contained in:
@@ -83,7 +83,6 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
<script src="download.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
@@ -94,5 +93,186 @@
|
||||
<p id="status-text"></p>
|
||||
<div id="file-list"></div>
|
||||
<div id="single-file"></div>
|
||||
|
||||
<script>
|
||||
// IMPORTANT: This script works in Internet Explorer 8!
|
||||
|
||||
var BASE_URL = '/api/localsend/v2';
|
||||
|
||||
var i18n = {};
|
||||
var sessionId = sessionStorage.getItem('sessionId');
|
||||
var queryParams = location.search.slice(1).split('&');
|
||||
var queryPin = null;
|
||||
|
||||
// Parse query parameters manually for IE
|
||||
for (var i = 0; i < queryParams.length; i++) {
|
||||
var pair = queryParams[i].split('=');
|
||||
if (pair[0] === 'pin') {
|
||||
queryPin = decodeURIComponent(pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function firstRequestFiles() {
|
||||
document.getElementById('status-text').innerText = i18n.waiting;
|
||||
var initialUrl = BASE_URL + '/prepare-download';
|
||||
|
||||
if (sessionId) {
|
||||
initialUrl += '?sessionId=' + encodeURIComponent(sessionId);
|
||||
if (queryPin) {
|
||||
initialUrl += '&pin=' + encodeURIComponent(queryPin);
|
||||
}
|
||||
} else if (queryPin) {
|
||||
initialUrl += '?pin=' + encodeURIComponent(queryPin);
|
||||
}
|
||||
|
||||
makeRequest(initialUrl, 'POST', function (response) {
|
||||
if (response.status === 401) {
|
||||
pinRequestFiles(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
document.getElementById('status-text').innerText = i18n.rejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
document.getElementById('status-text').innerText = 'Error: ' + response.status;
|
||||
return;
|
||||
}
|
||||
|
||||
handleSuccess(response);
|
||||
});
|
||||
}
|
||||
|
||||
function pinRequestFiles(firstAttempt) {
|
||||
var pin = prompt(i18n.enterPin + (firstAttempt ? '' : '\n' + i18n.invalidPin));
|
||||
if (!pin) {
|
||||
document.getElementById('status-text').innerText = i18n.invalidPin;
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest(BASE_URL + '/prepare-download?pin=' + encodeURIComponent(pin), 'POST', function (response) {
|
||||
if (response.status === 401) {
|
||||
pinRequestFiles(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
document.getElementById('status-text').innerText = i18n.rejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
document.getElementById('status-text').innerText = 'Error: ' + response.status;
|
||||
return;
|
||||
}
|
||||
|
||||
handleSuccess(response);
|
||||
});
|
||||
}
|
||||
|
||||
function makeRequest(url, method, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
callback(xhr);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function fetchI18n(then) {
|
||||
makeRequest('/i18n.json', 'GET', function (response) {
|
||||
if (response.status === 200) {
|
||||
i18n = JSON.parse(response.responseText);
|
||||
then();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
fetchI18n(firstRequestFiles);
|
||||
}
|
||||
|
||||
function handleSuccess(response) {
|
||||
var data = JSON.parse(response.responseText);
|
||||
var files = data.files;
|
||||
sessionId = data.sessionId;
|
||||
sessionStorage.setItem('sessionId', sessionId);
|
||||
|
||||
document.getElementById('status-text').innerText = i18n.files + ' (' + getKeys(data.files).length + ')';
|
||||
|
||||
// Handling file display
|
||||
handleFilesDisplay(files, sessionId);
|
||||
}
|
||||
|
||||
function handleFilesDisplay(files, sessionId) {
|
||||
var html = '';
|
||||
var fileKeys = getKeys(files);
|
||||
for (var i = 0; i < fileKeys.length; i++) {
|
||||
var file = files[fileKeys[i]];
|
||||
html += '<a class="file-item" href="' + BASE_URL + '/download?sessionId=' + encodeURIComponent(sessionId) + '&fileId=' + encodeURIComponent(fileKeys[i]) + '">' +
|
||||
'<div class="file-index-cell">' + (i + 1) + '</div>' +
|
||||
'<div class="file-name-cell">' + escapeHtml(file.fileName) + '</div>' +
|
||||
'<div class="file-size-cell">' + formatBytes(file.size) + '</div>' +
|
||||
'</a>';
|
||||
}
|
||||
|
||||
if (fileKeys.length === 1) {
|
||||
document.getElementById('single-file').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('file-list').innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (text === null || text === undefined) {
|
||||
return '';
|
||||
}
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return String(text).replace(/[&<>"']/g, function (m) { return map[m]; });
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + ' B';
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return (bytes / 1024).toFixed(1) + ' KB';
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
} else {
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys(obj) {
|
||||
var keys = [];
|
||||
for (var key in obj) {
|
||||
keys.push(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
// IMPORTANT: This script works in Internet Explorer 8!
|
||||
|
||||
var BASE_URL = '/api/localsend/v2';
|
||||
|
||||
var i18n = {};
|
||||
var sessionId = sessionStorage.getItem('sessionId');
|
||||
var queryParams = location.search.slice(1).split('&');
|
||||
var queryPin = null;
|
||||
|
||||
// Parse query parameters manually for IE
|
||||
for (var i = 0; i < queryParams.length; i++) {
|
||||
var pair = queryParams[i].split('=');
|
||||
if (pair[0] === 'pin') {
|
||||
queryPin = decodeURIComponent(pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function firstRequestFiles() {
|
||||
document.getElementById('status-text').innerText = i18n.waiting;
|
||||
var initialUrl = BASE_URL + '/prepare-download';
|
||||
|
||||
if (sessionId) {
|
||||
initialUrl += '?sessionId=' + encodeURIComponent(sessionId);
|
||||
if (queryPin) {
|
||||
initialUrl += '&pin=' + encodeURIComponent(queryPin);
|
||||
}
|
||||
} else if (queryPin) {
|
||||
initialUrl += '?pin=' + encodeURIComponent(queryPin);
|
||||
}
|
||||
|
||||
makeRequest(initialUrl, 'POST', function (response) {
|
||||
if (response.status === 401) {
|
||||
pinRequestFiles(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
document.getElementById('status-text').innerText = i18n.rejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
document.getElementById('status-text').innerText = 'Error: ' + response.status;
|
||||
return;
|
||||
}
|
||||
|
||||
handleSuccess(response);
|
||||
});
|
||||
}
|
||||
|
||||
function pinRequestFiles(firstAttempt) {
|
||||
var pin = prompt(i18n.enterPin + (firstAttempt ? '' : '\n' + i18n.invalidPin));
|
||||
if (!pin) {
|
||||
document.getElementById('status-text').innerText = i18n.invalidPin;
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest(BASE_URL + '/prepare-download?pin=' + encodeURIComponent(pin), 'POST', function (response) {
|
||||
if (response.status === 401) {
|
||||
pinRequestFiles(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
document.getElementById('status-text').innerText = i18n.rejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
document.getElementById('status-text').innerText = 'Error: ' + response.status;
|
||||
return;
|
||||
}
|
||||
|
||||
handleSuccess(response);
|
||||
});
|
||||
}
|
||||
|
||||
function makeRequest(url, method, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
callback(xhr);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function fetchI18n(then) {
|
||||
makeRequest('/i18n.json', 'GET', function (response) {
|
||||
if (response.status === 200) {
|
||||
i18n = JSON.parse(response.responseText);
|
||||
then();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
fetchI18n(firstRequestFiles);
|
||||
}
|
||||
|
||||
function handleSuccess(response) {
|
||||
var data = JSON.parse(response.responseText);
|
||||
var files = data.files;
|
||||
sessionId = data.sessionId;
|
||||
sessionStorage.setItem('sessionId', sessionId);
|
||||
|
||||
document.getElementById('status-text').innerText = i18n.files + ' (' + getKeys(data.files).length + ')';
|
||||
|
||||
// Handling file display
|
||||
handleFilesDisplay(files, sessionId);
|
||||
}
|
||||
|
||||
function handleFilesDisplay(files, sessionId) {
|
||||
var html= '';
|
||||
var fileKeys = getKeys(files);
|
||||
for (var i = 0; i < fileKeys.length; i++) {
|
||||
var file = files[fileKeys[i]];
|
||||
html += '<a class="file-item" href="' + BASE_URL + '/download?sessionId=' + encodeURIComponent(sessionId) + '&fileId=' + encodeURIComponent(fileKeys[i]) + '">' +
|
||||
'<div class="file-index-cell">' + (i + 1) + '</div>' +
|
||||
'<div class="file-name-cell">' + escapeHtml(file.fileName) + '</div>' +
|
||||
'<div class="file-size-cell">' + formatBytes(file.size) + '</div>' +
|
||||
'</a>';
|
||||
}
|
||||
|
||||
if (fileKeys.length === 1) {
|
||||
document.getElementById('single-file').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('file-list').innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (text === null || text === undefined) {
|
||||
return '';
|
||||
}
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + ' B';
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return (bytes / 1024).toFixed(1) + ' KB';
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
} else {
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys(obj) {
|
||||
var keys = [];
|
||||
for (var key in obj) {
|
||||
keys.push(key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -483,7 +483,6 @@ async fn handle_request_inner(mut req: Request<Incoming>) -> Result<Response<Box
|
||||
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&Method::GET, "/") => Ok(web::index(&state)),
|
||||
(&Method::GET, "/download.js") => Ok(web::download_js(&state)),
|
||||
(&Method::GET, "/i18n.json") => web::i18n(&state),
|
||||
(&Method::POST, "/api/localsend/v2/prepare-download") => {
|
||||
web::prepare_download(req, state, client_info).await
|
||||
|
||||
@@ -68,7 +68,6 @@ pub enum WebSendEvent {
|
||||
}
|
||||
|
||||
const DOWNLOAD_HTML: &str = include_str!("../../../assets/web/download.html");
|
||||
const DOWNLOAD_JS: &str = include_str!("../../../assets/web/download.js");
|
||||
const UPLOAD_HTML: &str = include_str!("../../../assets/web/upload.html");
|
||||
const ERROR_403_HTML: &str = include_str!("../../../assets/web/error-403.html");
|
||||
|
||||
@@ -199,17 +198,6 @@ pub(crate) fn index(state: &AppState) -> Response<BoxedBody> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn download_js(state: &AppState) -> Response<BoxedBody> {
|
||||
match &state.web {
|
||||
Some(_) => html_response(
|
||||
StatusCode::OK,
|
||||
DOWNLOAD_JS,
|
||||
"text/javascript; charset=utf-8",
|
||||
),
|
||||
None => error_403_page(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn i18n(state: &AppState) -> Result<Response<BoxedBody>, AppError> {
|
||||
let Some(i18n) = &state.web_i18n else {
|
||||
return Err(AppError::Message(
|
||||
|
||||
@@ -271,18 +271,9 @@ async fn test_web_page() {
|
||||
|
||||
let response = client.get(&base_url).send().await.unwrap();
|
||||
assert_eq!(response.status().as_u16(), 200);
|
||||
assert!(response.text().await.unwrap().contains("LocalSend"));
|
||||
|
||||
let response = client
|
||||
.get(format!("{base_url}/download.js"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status().as_u16(), 200);
|
||||
assert_eq!(
|
||||
response.headers()["content-type"],
|
||||
"text/javascript; charset=utf-8"
|
||||
);
|
||||
let body = response.text().await.unwrap();
|
||||
assert!(body.contains("LocalSend"));
|
||||
assert!(body.contains("prepare-download"));
|
||||
|
||||
let response = client
|
||||
.get(format!("{base_url}/i18n.json"))
|
||||
@@ -372,6 +363,8 @@ async fn test_upload_page() {
|
||||
let body = response.text().await.unwrap();
|
||||
assert!(body.contains("LocalSend"));
|
||||
assert!(body.contains("prepare-upload"));
|
||||
// The download page is not served without web send.
|
||||
assert!(!body.contains("prepare-download"));
|
||||
|
||||
let response = client
|
||||
.get(format!("{base_url}/i18n.json"))
|
||||
@@ -382,14 +375,6 @@ async fn test_upload_page() {
|
||||
let i18n = response.json::<HashMap<String, String>>().await.unwrap();
|
||||
assert!(i18n.contains_key("busy"));
|
||||
assert!(i18n.contains_key("uploadRejected"));
|
||||
|
||||
// The download page assets stay disabled without web send.
|
||||
let response = client
|
||||
.get(format!("{base_url}/download.js"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status().as_u16(), 403);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user