feat: add CORS support, dynamic API_BASE for Netlify frontend, and netlify.toml

This commit is contained in:
katR 2026-09-12 09:55:55 +05:30
parent 9fc9709f69
commit 55015d94d1
4 changed files with 39 additions and 4 deletions

8
netlify.toml Normal file
View file

@ -0,0 +1,8 @@
[build]
publish = "public"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"

10
public/_redirects Normal file
View file

@ -0,0 +1,10 @@
# Netlify redirects file
# To proxy API requests through Netlify (bypassing mixed content & CORS),
# replace YOUR_BACKEND_URL with your Termux backend URL (e.g., Cloudflare Tunnel / ngrok URL):
# /files https://YOUR_BACKEND_URL/files 200
# /upload https://YOUR_BACKEND_URL/upload 200
# /uploads/* https://YOUR_BACKEND_URL/uploads/:splat 200
# /delete/* https://YOUR_BACKEND_URL/delete/:splat 200
# SPA fallback rule
/* /index.html 200

View file

@ -17,6 +17,14 @@
let selectedFiles = []; let selectedFiles = [];
// API Base configuration for remote frontend (Netlify) or local hosting
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('api')) {
const customApi = urlParams.get('api').replace(/\/+$/, '');
localStorage.setItem('katdrop_api', customApi);
}
const API_BASE = window.KATDROP_API || localStorage.getItem('katdrop_api') || '';
// Toast // Toast
let toastTimeout; let toastTimeout;
function showToast(msg) { function showToast(msg) {
@ -221,7 +229,7 @@
// Fetch and display files // Fetch and display files
async function fetchFiles() { async function fetchFiles() {
try { try {
const res = await fetch('/files'); const res = await fetch(`${API_BASE}/files`);
const files = await res.json(); const files = await res.json();
fileList.innerHTML = ''; fileList.innerHTML = '';
@ -252,7 +260,7 @@
</div> </div>
</div> </div>
<div class="file-actions"> <div class="file-actions">
<a href="/uploads/${encodeURIComponent(name)}" class="action-btn action-download" download>download</a> <a href="${API_BASE}/uploads/${encodeURIComponent(name)}" class="action-btn action-download" download>download</a>
<button class="action-btn action-delete" onclick="deleteFile('${escapeHtml(name)}', this.closest('li'))">delete</button> <button class="action-btn action-delete" onclick="deleteFile('${escapeHtml(name)}', this.closest('li'))">delete</button>
</div> </div>
`; `;
@ -271,7 +279,7 @@
if (cardElement) cardElement.classList.add('deleting'); if (cardElement) cardElement.classList.add('deleting');
try { try {
const res = await fetch(`/delete/${encodeURIComponent(filename)}`, { method: 'DELETE' }); const res = await fetch(`${API_BASE}/delete/${encodeURIComponent(filename)}`, { method: 'DELETE' });
if (res.ok) { if (res.ok) {
showToast('file deleted'); showToast('file deleted');
setTimeout(() => { setTimeout(() => {
@ -343,7 +351,7 @@
showToast('upload failed'); showToast('upload failed');
}; };
xhr.open('POST', '/upload'); xhr.open('POST', `${API_BASE}/upload`);
xhr.send(formData); xhr.send(formData);
} }

View file

@ -92,6 +92,15 @@ const upload = multer({
}); });
// Middlewares // Middlewares
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.use(express.json()); app.use(express.json());
app.use(express.static(publicDir)); app.use(express.static(publicDir));