diff --git a/README.md b/README.md index 8c23131..1584684 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,42 @@ # katdrop -a minimal, smooth file transfer service. +a minimal, smooth file transfer service with 10-minute auto-deletion. + +> **Note**: This repository uses a placeholder backend URL (`https://YOUR_BACKEND_URL.trycloudflare.com`) and placeholder API key (`YOUR_API_KEY_HERE`) so no private endpoints or keys are exposed publicly. ## features - minimal dark interface with smooth interactive particle background +- real-time backend status indicator (`online` / `offline`) +- live upload byte progress and percentage - drag & drop files anywhere to upload instantly - 10-minute auto-deletion timer per file with live countdown -- sleek upload progress bar - safe path verification and collision-free file naming +- optional API key authorization (`x-katdrop-key`) - zero external CDN dependencies (works completely offline) +## configuration + +### connecting your frontend (Netlify) to your backend + +To connect your deployed frontend to your private backend (e.g. Cloudflare Tunnel or local server), pass your API URL and optional key via URL parameters once: + +``` +https://your-site.netlify.app/?api=https://your-tunnel.trycloudflare.com&key=YOUR_SECRET_KEY +``` + +KatDrop will automatically save these to `localStorage` in your browser. + +### backend API key (optional) + +To require an API key on your backend, set `KATDROP_KEY`: + +```bash +KATDROP_KEY=mysecretkey npm start +``` + +If not set, the backend runs without key verification. + ## usage ```bash @@ -25,8 +51,9 @@ Runs by default on `http://localhost:3000` (or falls back to an available port i ## endpoints +- `GET /health` - health check & uptime - `POST /upload` - upload files (`file`) -- `GET /files` - list files +- `GET /files` - list active files with remaining TTL - `GET /uploads/:filename` - download file - `DELETE /delete/:filename` - delete file diff --git a/public/_redirects b/public/_redirects index 8106436..3490ceb 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1,5 +1,7 @@ -/files https://feof-signals-fix-chevy.trycloudflare.com/files 200 -/upload https://feof-signals-fix-chevy.trycloudflare.com/upload 200 -/uploads/* https://feof-signals-fix-chevy.trycloudflare.com/uploads/:splat 200 -/delete/* https://feof-signals-fix-chevy.trycloudflare.com/delete/:splat 200 -/* /index.html 200 +# Netlify API Proxying +# Replace YOUR_BACKEND_URL with your live Cloudflare Tunnel URL: +# /files https://YOUR_BACKEND_URL.trycloudflare.com/files 200 +# /upload https://YOUR_BACKEND_URL.trycloudflare.com/upload 200 +# /uploads/* https://YOUR_BACKEND_URL.trycloudflare.com/uploads/:splat 200 +# /delete/* https://YOUR_BACKEND_URL.trycloudflare.com/delete/:splat 200 +/* /index.html 200 diff --git a/public/js/app.js b/public/js/app.js index c59990a..d6c4b4d 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -20,15 +20,29 @@ let selectedFiles = []; - // API Base configuration for remote frontend (Netlify) or local hosting - const DEFAULT_REMOTE_API = 'https://feof-signals-fix-chevy.trycloudflare.com'; + // API & Key Configuration (configurable via URL param ?api=...&key=... or localStorage) + // Public repo uses placeholder values for security + const DEFAULT_REMOTE_API = 'https://YOUR_BACKEND_URL.trycloudflare.com'; const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('api')) { const customApi = urlParams.get('api').replace(/\/+$/, ''); localStorage.setItem('katdrop_api', customApi); } + if (urlParams.has('key')) { + localStorage.setItem('katdrop_key', urlParams.get('key')); + } const isLocalOrigin = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.startsWith('192.168.'); const API_BASE = window.KATDROP_API || localStorage.getItem('katdrop_api') || (isLocalOrigin ? '' : DEFAULT_REMOTE_API); + const API_KEY = window.KATDROP_KEY || localStorage.getItem('katdrop_key') || 'YOUR_API_KEY_HERE'; + + // Authenticated fetch helper + function apiFetch(url, options = {}) { + options.headers = options.headers || {}; + if (API_KEY && API_KEY !== 'YOUR_API_KEY_HERE') { + options.headers['x-katdrop-key'] = API_KEY; + } + return fetch(url, options); + } // Toast let toastTimeout; @@ -59,7 +73,7 @@ try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 4000); - const res = await fetch(`${API_BASE}/health`, { signal: controller.signal }); + const res = await apiFetch(`${API_BASE}/health`, { signal: controller.signal }); clearTimeout(timeoutId); if (res.ok) { @@ -267,7 +281,7 @@ // Fetch and display files async function fetchFiles() { try { - const res = await fetch(`${API_BASE}/files`); + const res = await apiFetch(`${API_BASE}/files`); const files = await res.json(); fileList.innerHTML = ''; @@ -288,6 +302,7 @@ const size = typeof file === 'object' && file.sizeFormatted ? file.sizeFormatted : ''; const expiresMs = file.expiresAt ? new Date(file.expiresAt).getTime() : (Date.now() + 600000); const remainingSec = Math.max(0, Math.ceil((expiresMs - Date.now()) / 1000)); + const keyParam = (API_KEY && API_KEY !== 'YOUR_API_KEY_HERE') ? `?key=${encodeURIComponent(API_KEY)}` : ''; li.innerHTML = `