From 4936f3268affa46d9e6e0eada12cf545d7ecdd7b Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:31:40 +0530 Subject: [PATCH] cleanup --- .gitignore | 1 + server/sync-switch/.gitignore | 2 - server/sync-switch/index.js | 613 -------------------------------- server/sync-switch/package.json | 14 - 4 files changed, 1 insertion(+), 629 deletions(-) delete mode 100644 server/sync-switch/.gitignore delete mode 100644 server/sync-switch/index.js delete mode 100644 server/sync-switch/package.json diff --git a/.gitignore b/.gitignore index c7156d5f..80df2402 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ asset scripts/scrape_android_compose_animation_docs.py tools AGENTS.md +server \ No newline at end of file diff --git a/server/sync-switch/.gitignore b/server/sync-switch/.gitignore deleted file mode 100644 index 6b71ee4d..00000000 --- a/server/sync-switch/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -data/*.json -*.log diff --git a/server/sync-switch/index.js b/server/sync-switch/index.js deleted file mode 100644 index 88a7fdb2..00000000 --- a/server/sync-switch/index.js +++ /dev/null @@ -1,613 +0,0 @@ -'use strict'; - -const crypto = require('crypto'); -const fs = require('fs/promises'); -const http = require('http'); -const path = require('path'); - -const BACKEND_HOSTED = 'hosted'; -const BACKEND_NUVIO = 'nuvio'; -const BACKENDS = new Set([BACKEND_HOSTED, BACKEND_NUVIO]); -const DEFAULT_STATE_FILE = path.join(__dirname, 'data', 'state.json'); - -const env = process.env; - -const serverConfig = { - host: env.NUVIO_SWITCH_HOST || env.HOST || '0.0.0.0', - port: parsePort(env.NUVIO_SWITCH_PORT || env.PORT || '3000'), - stateFile: resolveStateFile(env.NUVIO_SWITCH_STATE_FILE || env.STATE_FILE), - manifestVersion: parsePositiveInteger(env.NUVIO_SWITCH_VERSION || '1', 'NUVIO_SWITCH_VERSION'), - defaultBackend: parseBackend(env.NUVIO_SWITCH_DEFAULT_BACKEND || 'hosted'), - forceLogoutOnChange: parseBoolean(env.NUVIO_SWITCH_FORCE_LOGOUT_ON_CHANGE, true), - adminUsername: env.NUVIO_SWITCH_ADMIN_USERNAME || env.ADMIN_USERNAME || 'admin', - adminPassword: env.NUVIO_SWITCH_ADMIN_PASSWORD || env.ADMIN_PASSWORD || '', - hosted: { - displayName: env.NUVIO_SWITCH_HOSTED_DISPLAY_NAME || 'Hosted', - supabaseUrl: - env.NUVIO_SWITCH_HOSTED_SUPABASE_URL || - env.NUVIO_SWITCH_HOSTED_URL || - 'https://dpyhjjcoabcglfmgecug.supabase.co', - anonKey: - env.NUVIO_SWITCH_HOSTED_SUPABASE_ANON_KEY || - env.NUVIO_SWITCH_HOSTED_ANON_KEY || - '', - avatarPublicBaseUrl: - env.NUVIO_SWITCH_HOSTED_AVATAR_PUBLIC_BASE_URL || - 'https://dpyhjjcoabcglfmgecug.supabase.co/storage/v1/object/public/avatars', - schemaVersion: parsePositiveInteger( - env.NUVIO_SWITCH_HOSTED_SCHEMA_VERSION || '1', - 'NUVIO_SWITCH_HOSTED_SCHEMA_VERSION' - ), - }, - nuvio: { - displayName: env.NUVIO_SWITCH_NUVIO_DISPLAY_NAME || 'Nuvio', - supabaseUrl: - env.NUVIO_SWITCH_NUVIO_SUPABASE_URL || - env.NUVIO_SWITCH_NUVIO_URL || - 'https://api.nuvio.tv', - anonKey: - env.NUVIO_SWITCH_NUVIO_SUPABASE_ANON_KEY || - env.NUVIO_SWITCH_NUVIO_ANON_KEY || - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzgxNTIxMzQ2LCJleHAiOjE5MzkyMDEzNDZ9.tmQaj682pwzehpqlgCDMnySOqiUvpgRbrE43T4VJpDI', - avatarPublicBaseUrl: - env.NUVIO_SWITCH_NUVIO_AVATAR_PUBLIC_BASE_URL || - 'https://api.nuvio.tv/storage/v1/object/public/avatars', - schemaVersion: parsePositiveInteger( - env.NUVIO_SWITCH_NUVIO_SCHEMA_VERSION || '1', - 'NUVIO_SWITCH_NUVIO_SCHEMA_VERSION' - ), - }, -}; - -const server = http.createServer((req, res) => { - handleRequest(req, res).catch((error) => { - console.error(error); - if (error.statusCode === 413) { - sendJson(res, 413, { error: 'request_body_too_large' }); - return; - } - - sendJson(res, 500, { error: 'internal_server_error' }); - }); -}); - -server.listen(serverConfig.port, serverConfig.host, () => { - console.log( - `Nuvio sync switch listening on http://${serverConfig.host}:${serverConfig.port}` - ); - console.log(`State file: ${serverConfig.stateFile}`); -}); - -async function handleRequest(req, res) { - const requestUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`); - const pathname = normalizePath(requestUrl.pathname); - - if (req.method === 'GET' && pathname === '/health') { - sendJson(res, 200, { ok: true }); - return; - } - - if (req.method === 'GET' && pathname === '/config.json') { - const state = await readState(); - sendJson(res, 200, buildManifest(state)); - return; - } - - if (req.method === 'GET' && pathname === '/admin') { - if (!requireAdmin(req, res)) return; - - const state = await readState(); - sendHtml(res, 200, renderAdminPage(state)); - return; - } - - if (req.method === 'POST' && pathname === '/admin/backend') { - if (!requireAdmin(req, res)) return; - - const body = await readRequestBody(req); - const backend = parseBackendFromRequest(req, body); - if (!backend) { - sendJson(res, 400, { - error: 'invalid_backend', - message: 'Expected activeBackend or backend to be hosted or nuvio.', - }); - return; - } - - const state = await writeBackend(backend); - if (prefersHtml(req)) { - sendRedirect(res, '/admin'); - return; - } - - sendJson(res, 200, { - ok: true, - activeBackend: state.activeBackend, - revision: state.revision, - }); - return; - } - - if (pathname === '/admin' || pathname === '/admin/backend') { - sendJson(res, 405, { error: 'method_not_allowed' }, { Allow: 'GET, POST' }); - return; - } - - sendJson(res, 404, { error: 'not_found' }); -} - -function normalizePath(pathname) { - if (pathname.length > 1 && pathname.endsWith('/')) { - return pathname.slice(0, -1); - } - - return pathname; -} - -function buildManifest(state) { - return { - version: serverConfig.manifestVersion, - activeBackend: state.activeBackend, - revision: String(state.revision), - forceLogoutOnChange: serverConfig.forceLogoutOnChange, - backends: { - hosted: publicBackendConfig(serverConfig.hosted), - nuvio: publicBackendConfig(serverConfig.nuvio), - }, - }; -} - -function publicBackendConfig(config) { - return { - displayName: config.displayName, - supabaseUrl: normalizeBaseUrl(config.supabaseUrl), - anonKey: config.anonKey, - avatarPublicBaseUrl: normalizeBaseUrl(config.avatarPublicBaseUrl), - schemaVersion: config.schemaVersion, - }; -} - -async function readState() { - try { - const rawState = await fs.readFile(serverConfig.stateFile, 'utf8'); - return normalizeState(JSON.parse(rawState)); - } catch (error) { - if (error.code === 'ENOENT') { - const initialState = { - activeBackend: serverConfig.defaultBackend, - revision: 1, - updatedAt: new Date().toISOString(), - }; - await writeState(initialState); - return initialState; - } - - if (error instanceof SyntaxError) { - throw new Error(`State file is not valid JSON: ${serverConfig.stateFile}`); - } - - throw error; - } -} - -function normalizeState(state) { - const activeBackend = parseBackend(state.activeBackend); - const revision = Number.isInteger(state.revision) && state.revision > 0 ? state.revision : 1; - - return { - activeBackend, - revision, - updatedAt: typeof state.updatedAt === 'string' ? state.updatedAt : undefined, - }; -} - -async function writeBackend(activeBackend) { - const currentState = await readState(); - const nextRevision = - currentState.activeBackend === activeBackend - ? currentState.revision - : currentState.revision + 1; - - const nextState = { - activeBackend, - revision: nextRevision, - updatedAt: new Date().toISOString(), - }; - - await writeState(nextState); - return nextState; -} - -async function writeState(state) { - const stateDir = path.dirname(serverConfig.stateFile); - const tempFile = path.join( - stateDir, - `.${path.basename(serverConfig.stateFile)}.${process.pid}.${crypto.randomUUID()}.tmp` - ); - - await fs.mkdir(stateDir, { recursive: true }); - await fs.writeFile(tempFile, `${JSON.stringify(state, null, 2)}\n`, { - encoding: 'utf8', - mode: 0o600, - }); - await fs.rename(tempFile, serverConfig.stateFile); -} - -function requireAdmin(req, res) { - if (!serverConfig.adminPassword) { - sendJson(res, 503, { - error: 'admin_password_not_configured', - message: 'Set NUVIO_SWITCH_ADMIN_PASSWORD before using admin endpoints.', - }); - return false; - } - - const authHeader = req.headers.authorization || ''; - if (!authHeader.startsWith('Basic ')) { - sendAuthRequired(res); - return false; - } - - const credentials = Buffer.from(authHeader.slice('Basic '.length), 'base64').toString('utf8'); - const separatorIndex = credentials.indexOf(':'); - const username = separatorIndex >= 0 ? credentials.slice(0, separatorIndex) : ''; - const password = separatorIndex >= 0 ? credentials.slice(separatorIndex + 1) : ''; - - if ( - timingSafeEqual(username, serverConfig.adminUsername) && - timingSafeEqual(password, serverConfig.adminPassword) - ) { - return true; - } - - sendAuthRequired(res); - return false; -} - -function timingSafeEqual(input, expected) { - const inputBuffer = Buffer.from(input); - const expectedBuffer = Buffer.from(expected); - - if (inputBuffer.length !== expectedBuffer.length) { - return false; - } - - return crypto.timingSafeEqual(inputBuffer, expectedBuffer); -} - -function sendAuthRequired(res) { - sendText(res, 401, 'Authentication required.\n', { - 'WWW-Authenticate': 'Basic realm="Nuvio Sync Switch", charset="UTF-8"', - }); -} - -async function readRequestBody(req) { - const chunks = []; - let bytesRead = 0; - const maxBytes = 16 * 1024; - - for await (const chunk of req) { - bytesRead += chunk.length; - if (bytesRead > maxBytes) { - const error = new Error('Request body too large.'); - error.statusCode = 413; - throw error; - } - - chunks.push(chunk); - } - - return Buffer.concat(chunks).toString('utf8'); -} - -function parseBackendFromRequest(req, body) { - const contentType = String(req.headers['content-type'] || '').split(';')[0].trim(); - - if (contentType === 'application/json') { - try { - const parsed = JSON.parse(body || '{}'); - return safeParseBackend(parsed.activeBackend || parsed.backend); - } catch { - return null; - } - } - - const formData = new URLSearchParams(body); - return safeParseBackend(formData.get('activeBackend') || formData.get('backend')); -} - -function parseBackend(value) { - const backend = String(value || '').trim().toLowerCase(); - if (BACKENDS.has(backend)) { - return backend; - } - - throw new Error(`Invalid backend "${value}". Expected hosted or nuvio.`); -} - -function safeParseBackend(value) { - const backend = String(value || '').trim().toLowerCase(); - return BACKENDS.has(backend) ? backend : null; -} - -function parseBoolean(value, defaultValue) { - if (value === undefined || value === '') { - return defaultValue; - } - - return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase()); -} - -function parsePort(value) { - const port = Number(value); - if (!Number.isInteger(port) || port < 1 || port > 65535) { - throw new Error(`Invalid port "${value}".`); - } - - return port; -} - -function parsePositiveInteger(value, name) { - const parsed = Number(value); - if (!Number.isInteger(parsed) || parsed < 1) { - throw new Error(`Invalid ${name} "${value}".`); - } - - return parsed; -} - -function normalizeBaseUrl(value) { - return String(value || '').trim().replace(/\/+$/, ''); -} - -function resolveStateFile(value) { - if (!value) { - return DEFAULT_STATE_FILE; - } - - if (path.isAbsolute(value)) { - return value; - } - - return path.resolve(process.cwd(), value); -} - -function prefersHtml(req) { - const accept = req.headers.accept || ''; - const contentType = req.headers['content-type'] || ''; - return contentType.includes('application/x-www-form-urlencoded') && accept.includes('text/html'); -} - -function sendJson(res, statusCode, payload, headers = {}) { - send(res, statusCode, JSON.stringify(payload, null, 2), { - 'Content-Type': 'application/json; charset=utf-8', - ...headers, - }); -} - -function sendHtml(res, statusCode, body, headers = {}) { - send(res, statusCode, body, { - 'Content-Type': 'text/html; charset=utf-8', - ...headers, - }); -} - -function sendText(res, statusCode, body, headers = {}) { - send(res, statusCode, body, { - 'Content-Type': 'text/plain; charset=utf-8', - ...headers, - }); -} - -function sendRedirect(res, location) { - res.writeHead(303, { - Location: location, - 'Cache-Control': 'no-store', - 'X-Content-Type-Options': 'nosniff', - }); - res.end(); -} - -function send(res, statusCode, body, headers = {}) { - res.writeHead(statusCode, { - 'Cache-Control': 'no-store', - 'X-Content-Type-Options': 'nosniff', - ...headers, - }); - res.end(body); -} - -function renderAdminPage(state) { - const hostedActive = state.activeBackend === BACKEND_HOSTED; - const nuvioActive = state.activeBackend === BACKEND_NUVIO; - - return ` - - - - - Nuvio Sync Switch - - - -
-

Nuvio Sync Switch

-

Select the Supabase backend exposed by /config.json.

-
-
-

Active backend

-

${escapeHtml(state.activeBackend)}

-
-
-

Revision

-

${escapeHtml(String(state.revision))}

-
-
-

Force logout

-

${serverConfig.forceLogoutOnChange ? 'true' : 'false'}

-
-
-
-
- - -
-
- - -
-
-
- - -`; -} - -function escapeHtml(value) { - return value.replace(/[&<>"']/g, (char) => { - switch (char) { - case '&': - return '&'; - case '<': - return '<'; - case '>': - return '>'; - case '"': - return '"'; - case "'": - return '''; - default: - return char; - } - }); -} diff --git a/server/sync-switch/package.json b/server/sync-switch/package.json deleted file mode 100644 index 6bde2bea..00000000 --- a/server/sync-switch/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "nuvio-sync-switch", - "version": "0.1.0", - "private": true, - "description": "Minimal VPS-hosted switch API for Nuvio Supabase backend selection.", - "main": "index.js", - "scripts": { - "start": "node index.js", - "check": "node --check index.js" - }, - "engines": { - "node": ">=18" - } -}