UNPKG

@deskthing/cli

Version:
114 lines (101 loc) 3.79 kB
import { createServer } from "http"; import { existsSync, readFileSync, statSync } from "fs"; import { extname, join, normalize } from "path"; import { fileURLToPath } from "url"; import { DeskThingConfig } from "../../config/deskthing.config"; import { Logger } from "../services/logger"; import { CallbackService } from "./callbackService"; export class DevClient { async start(): Promise<void> { const __dirname = fileURLToPath(new URL(".", import.meta.url)); const staticPath = join(__dirname, "./template"); Logger.debug(`Static files will be served from: ${staticPath}`); const mimeTypes = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".json": "application/json", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".eot": "application/vnd.ms-fontobject", ".otf": "font/otf", }; const server = createServer((req, res) => { // Handle only GET requests if (req.method !== "GET") { res.statusCode = 405; res.end("Method Not Allowed"); return; } // Get URL path and handle "/" default route let urlPath = req.url || "/"; if (urlPath === "/") { urlPath = "/index.html"; } // Handle callback request if (urlPath.startsWith("/callback")) { CallbackService.handleCallback(req, res); return; } // Handle config request if (urlPath === "/config") { try { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(DeskThingConfig.development.client)); return; } catch (err) { Logger.error("Error serving config:", err); res.statusCode = 500; res.end("Internal Server Error"); return; } } // Security: Prevent directory traversal attacks const safePath = normalize(urlPath).replace(/^(\.\.(\/|\\|$))+/, ""); const filePath = join(staticPath, safePath); Logger.debug(`Serving ${urlPath}`); // Check if file exists if (existsSync(filePath) && statSync(filePath).isFile()) { try { // Determine content type based on file extension const ext = extname(filePath).toLowerCase(); const contentType = mimeTypes[ext] || "application/octet-stream"; const fileContent = readFileSync(filePath); res.writeHead(200, { "Content-Type": contentType }); res.end(fileContent); } catch (err) { Logger.error(`Error serving ${filePath}:`, err); res.statusCode = 500; res.end("Internal Server Error"); } } else { // Try serving index.html as fallback for SPA routing const indexPath = join(staticPath, "index.html"); if (existsSync(indexPath)) { const indexContent = readFileSync(indexPath); res.writeHead(200, { "Content-Type": "text/html" }); res.end(indexContent); } else { res.statusCode = 404; res.end("Not Found"); } } }); const clientPort = DeskThingConfig.development.client.clientPort; server.listen(clientPort, () => { Logger.info( `\x1b[36m🚀 Development Server is running at http://localhost:${clientPort}\x1b[0m` ); Logger.info( `\x1b[33m🔄 Callback Server is running at http://localhost:${clientPort}/callback \x1b[0m` ); }); } }