refactor: merge download.js into download.html
CI / format (push) Has been cancelled
CI / test (push) Has been cancelled
CI / packaging (push) Has been cancelled

This commit is contained in:
Tien Do Nam
2026-08-03 14:41:40 +02:00
parent 39e593100b
commit 8a91809811
5 changed files with 186 additions and 212 deletions
+181 -1
View File
@@ -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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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>
-178
View File
@@ -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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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();
-1
View File
@@ -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
-12
View File
@@ -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(
+5 -20
View File
@@ -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]