From 3a2cd9b1aaeeb81b5594a700d3931884b493bbfa Mon Sep 17 00:00:00 2001 From: Heidi Date: Sat, 16 May 2026 17:01:22 +0100 Subject: [PATCH] fix --- README.md | 3 ++ example.env | 6 ++-- server.cjs | 35 ++++++++++++++++++++ vite.config.js | 86 +++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f600e04..86d603b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ npm run dev The development server runs on . +Set `comingsoon=TRUE` in `.env` to serve a temporary coming soon page instead +of the app. Omit it, or set any other value, to serve the regular site. + By default, `/api/*` and `/health` requests are proxied to `http://localhost:6000`. Override that with `VITE_API_TARGET`: diff --git a/example.env b/example.env index ab8b74d..1d2596a 100644 --- a/example.env +++ b/example.env @@ -1,15 +1,19 @@ NODE_ENV=production +comingsoon=TRUE PORT=3010 API_UPSTREAM=http://127.0.0.1:6000 PUBLIC_ORIGIN=https://example.com + UPTIME_STORAGE_DIR=~/tsswebstorage UPTIME_DATABASE_FILE=uptime.sqlite UPTIME_SAMPLE_INTERVAL_MS=1800000 UPTIME_HISTORY_LIMIT=336 + ANALYTICS_DATABASE_FILE=viewers.sqlite ANALYTICS_RETENTION_DAYS=30 ANALYTICS_ACTIVE_WINDOW_SECONDS=75 + API_CACHE_TTL_MS=15000 API_RATE_LIMIT_WINDOW_MS=60000 API_RATE_LIMIT_MAX=120 @@ -29,11 +33,9 @@ GITHUB_WEBHOOK_REPOSITORY= PM2_RESTART_TARGETS=tssbot-web DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # Set to "true" only if the Discord channel is private. Default omits the patch preview -# from Discord notifications to avoid leaking diffs (including any inline secrets). DISCORD_INCLUDE_PATCH=true # Cloudflare Turnstile. VITE_TURNSTILE_SITE_KEY is the public site key baked into the client bundle by Vite. # TURNSTILE_SECRET_KEY is the server-only secret used to call the Siteverify endpoint. -# If left blank, the gated deletion endpoint is open (no challenge enforced). Set both to enforce. VITE_TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= diff --git a/server.cjs b/server.cjs index f4d3744..12aaf8d 100644 --- a/server.cjs +++ b/server.cjs @@ -50,6 +50,7 @@ const API_CACHE_TTL_MS = Number(process.env.API_CACHE_TTL_MS || 15000) const API_RATE_LIMIT_WINDOW_MS = Number(process.env.API_RATE_LIMIT_WINDOW_MS || 60000) const API_RATE_LIMIT_MAX = Number(process.env.API_RATE_LIMIT_MAX || 120) const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_SECRET_KEY || '' +const COMING_SOON = String(process.env.comingsoon || process.env.COMINGSOON || '').toLowerCase() === 'true' const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify' const TURNSTILE_VERIFY_TIMEOUT_MS = Number(process.env.TURNSTILE_VERIFY_TIMEOUT_MS || 5000) const TURNSTILE_MAX_TOKEN_LENGTH = 2048 @@ -1691,8 +1692,42 @@ function sendHtml(req, res, data, status = 200) { }) } +function comingSoonHtml() { + return ` + + + + + + + Toothless' TSS Bot | Coming soon + + +
+
+

Toothless' TSS Bot

+

Coming soon

+

TSS analytics are getting tucked away for a little bit. Check back soon.

+
+
+ +` +} + +function sendComingSoonPage(req, res) { + send(res, 200, comingSoonHtml(), { + ...securityHeaders(req, { html: true }), + 'content-type': mimeTypes['.html'], + 'cache-control': 'no-store', + }) +} + function serveStatic(req, res) { const requestPath = decodeURIComponent(new URL(req.url, `http://localhost:${PORT}`).pathname) + if (COMING_SOON) { + return sendComingSoonPage(req, res) + } + const relativePath = requestPath === '/' ? '/index.html' : requestPath const filePath = path.normalize(path.join(DIST_DIR, relativePath)) diff --git a/vite.config.js b/vite.config.js index d4082b3..975d8e5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,5 +1,5 @@ import crypto from 'node:crypto' -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import obfuscatorPlugin from 'rollup-plugin-obfuscator' @@ -142,20 +142,76 @@ function apiGuard() { } } -export default defineConfig({ - plugins: [apiGuard(), react(), tailwindcss(), obfuscate(), sri()], - server: { - host: '0.0.0.0', - port: 3001, - proxy: { - '/api': { - target: process.env.VITE_API_TARGET ?? 'http://localhost:6000', - changeOrigin: true, - }, - '/health': { - target: process.env.VITE_API_TARGET ?? 'http://localhost:6000', - changeOrigin: true, +function comingSoonHtml() { + return ` + + + + + + + Toothless' TSS Bot | Coming soon + + +
+
+

Toothless' TSS Bot

+

Coming soon

+

TSS analytics are getting tucked away for a little bit. Check back soon.

+
+
+ +` +} + +function comingSoonDev(enabled) { + return { + name: 'coming-soon-dev', + configureServer(server) { + if (!enabled) return + + server.middlewares.use((req, res, next) => { + if (req.url?.startsWith('/api/') || req.url === '/health') { + next() + return + } + + const acceptsHtml = String(req.headers.accept || '').includes('text/html') + if (!acceptsHtml && req.url !== '/') { + next() + return + } + + res.writeHead(200, { + 'content-type': 'text/html; charset=utf-8', + 'cache-control': 'no-store', + 'x-content-type-options': 'nosniff', + }) + res.end(comingSoonHtml()) + }) + }, + } +} + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), '') + const comingSoon = String(env.comingsoon || env.COMINGSOON || '').toLowerCase() === 'true' + + return { + plugins: [comingSoonDev(comingSoon), apiGuard(), react(), tailwindcss(), obfuscate(), sri()], + server: { + host: '0.0.0.0', + port: 3001, + proxy: { + '/api': { + target: process.env.VITE_API_TARGET ?? 'http://localhost:6000', + changeOrigin: true, + }, + '/health': { + target: process.env.VITE_API_TARGET ?? 'http://localhost:6000', + changeOrigin: true, + }, }, }, - }, + } })