@axlotl-lab/navigrator
Version:
A powerful local domain manager for development environments. Navigrator helps you manage local domains and SSL certificates with a simple web interface.
448 lines (447 loc) • 20.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebServer = void 0;
const express_1 = __importDefault(require("express"));
const open_1 = __importDefault(require("open"));
const path = __importStar(require("path"));
const proxy_service_1 = require("./proxy-service");
class WebServer {
constructor(hostsManager, certManager, config) {
this.server = null;
this.app = (0, express_1.default)();
this.hostsManager = hostsManager;
this.certManager = certManager;
this.proxyService = new proxy_service_1.ProxyService();
this.config = {
port: config.port || 10191,
};
this.setupMiddleware();
this.setupRoutes();
}
/**
* Configure middleware for Express
*/
setupMiddleware() {
this.app.use(express_1.default.json());
this.app.use(express_1.default.urlencoded({ extended: true }));
// Serve static files
const staticDir = path.join(__dirname, '..', '..', 'public');
this.app.use(express_1.default.static(staticDir));
}
/**
* Configure routes for the API
*/
setupRoutes() {
// Original routes...
this.app.get('/api/hosts', (_, res) => __awaiter(this, void 0, void 0, function* () {
try {
const hosts = yield this.hostsManager.readLocalHosts();
res.json({ success: true, hosts });
}
catch (error) {
console.error('Error fetching hosts:', error);
res.status(500).json({ success: false, error: 'Failed to fetch hosts' });
}
}));
this.app.post('/api/hosts', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain, ip = '127.0.0.1' } = req.body;
if (!domain) {
res.status(400).json({ success: false, error: 'Domain is required' });
return;
}
const success = yield this.hostsManager.addHost(domain, ip);
if (success) {
res.json({ success: true, message: `Host ${domain} added successfully` });
}
else {
res.status(500).json({ success: false, error: 'Failed to add host' });
}
}
catch (error) {
console.error('Error adding host:', error);
res.status(500).json({ success: false, error: 'Failed to add host' });
}
}));
this.app.post('/api/hosts/:domain/adopt', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const { ip = '127.0.0.1' } = req.body;
const success = yield this.hostsManager.adoptHost(domain, ip);
if (success) {
res.json({ success: true, message: `Host ${domain} adopted successfully` });
}
else {
res.status(404).json({ success: false, error: 'Host not found or already adopted' });
}
}
catch (error) {
console.error('Error adopting host:', error);
res.status(500).json({ success: false, error: 'Failed to adopt host' });
}
}));
this.app.post('/api/hosts/import-all', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const result = yield this.hostsManager.importAllLocalHosts();
if (result.success) {
res.json({
success: true,
message: `${result.count} hosts imported successfully`
});
}
else {
res.status(404).json({ success: false, error: 'No hosts found to import' });
}
}
catch (error) {
console.error('Error importing hosts:', error);
res.status(500).json({ success: false, error: 'Failed to import hosts' });
}
}));
this.app.delete('/api/hosts/:domain', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const { ip = '127.0.0.1' } = req.query;
const hostSuccess = yield this.hostsManager.removeHost(domain, ip);
// Also remove the associated certificate
let certSuccess = false;
try {
certSuccess = yield this.certManager.deleteCertificate(domain);
}
catch (certError) {
console.error(`Error removing certificate for ${domain}:`, certError);
// Don't fail the main operation if deleting the certificate fails
}
if (hostSuccess) {
const message = certSuccess
? `Host ${domain} and its certificate removed successfully`
: `Host ${domain} removed successfully`;
res.json({ success: true, message, certificateRemoved: certSuccess });
}
else {
res.status(404).json({ success: false, error: 'Host not found or not created by this application' });
}
}
catch (error) {
console.error('Error removing host:', error);
res.status(500).json({ success: false, error: 'Failed to remove host' });
}
}));
this.app.patch('/api/hosts/:domain/toggle', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const { disabled, ip = '127.0.0.1' } = req.body;
if (disabled === undefined) {
res.status(400).json({ success: false, error: 'Disabled state is required' });
return;
}
const success = yield this.hostsManager.toggleHostState(domain, disabled, ip);
if (success) {
const state = disabled ? 'disabled' : 'enabled';
res.json({ success: true, message: `Host ${domain} ${state} successfully` });
}
else {
res.status(404).json({ success: false, error: 'Host not found or not created by this application' });
}
}
catch (error) {
console.error('Error toggling host:', error);
res.status(500).json({ success: false, error: 'Failed to toggle host state' });
}
}));
this.app.get('/api/certificates', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const certificates = yield this.certManager.listCertificates();
res.json({ success: true, certificates });
}
catch (error) {
console.error('Error fetching certificates:', error);
res.status(500).json({ success: false, error: 'Failed to fetch certificates' });
}
}));
this.app.get('/api/certificates/:domain', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const certInfo = yield this.certManager.verifyCertificate(domain);
if (certInfo) {
res.json({ success: true, certificate: certInfo });
}
else {
res.status(404).json({ success: false, error: 'Certificate not found' });
}
}
catch (error) {
console.error('Error verifying certificate:', error);
res.status(500).json({ success: false, error: 'Failed to verify certificate' });
}
}));
this.app.post('/api/certificates', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.body;
if (!domain) {
res.status(400).json({ success: false, error: 'Domain is required' });
return;
}
const certInfo = yield this.certManager.createCertificate(domain);
res.json({ success: true, certificate: certInfo });
}
catch (error) {
console.error('Error creating certificate:', error);
res.status(500).json({ success: false, error: 'Failed to create certificate' });
}
}));
this.app.delete('/api/certificates/:domain', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const success = yield this.certManager.deleteCertificate(domain);
if (success) {
res.json({ success: true, message: `Certificate for ${domain} removed successfully` });
}
else {
res.status(404).json({ success: false, error: 'Certificate not found' });
}
}
catch (error) {
console.error('Error removing certificate:', error);
res.status(500).json({ success: false, error: 'Failed to remove certificate' });
}
}));
this.app.get('/api/status/:domain', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
const hosts = yield this.hostsManager.readLocalHosts();
const hostEntry = hosts.find(host => host.domain === domain);
const hostExists = !!hostEntry;
const certInfo = yield this.certManager.verifyCertificate(domain);
const certValid = (certInfo === null || certInfo === void 0 ? void 0 : certInfo.isValid) || false;
const status = {
domain,
hostConfigured: hostExists,
certificateValid: certValid,
isValid: hostExists && certValid,
isDisabled: (hostEntry === null || hostEntry === void 0 ? void 0 : hostEntry.isDisabled) || false
};
res.json({ success: true, status });
}
catch (error) {
console.error('Error checking domain status:', error);
res.status(500).json({ success: false, error: 'Failed to check domain status' });
}
}));
// New proxy-related routes
this.app.get('/api/proxies', (req, res) => {
try {
const proxies = this.proxyService.getProxies();
res.json({ success: true, proxies });
}
catch (error) {
console.error('Error fetching proxies:', error);
res.status(500).json({ success: false, error: 'Failed to fetch proxies' });
}
});
this.app.post('/api/proxies', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain, target, port } = req.body;
if (!domain || !target) {
res.status(400).json({ success: false, error: 'Domain and target are required' });
return;
}
// Verify domain exists and has a valid certificate
const hosts = yield this.hostsManager.readLocalHosts();
const hostEntry = hosts.find(host => host.domain === domain);
if (!hostEntry) {
res.status(404).json({ success: false, error: 'Domain not found in hosts file' });
return;
}
const certInfo = yield this.certManager.verifyCertificate(domain);
if (!certInfo || !certInfo.isValid) {
res.status(400).json({ success: false, error: 'Domain does not have a valid certificate' });
return;
}
const proxyConfig = {
domain,
target,
isRunning: false,
port: port || 443
};
const config = this.proxyService.addProxy(proxyConfig);
res.json({
success: true,
message: `Proxy configuration for ${domain} added successfully`,
proxy: config
});
}
catch (error) {
console.error('Error adding proxy:', error);
res.status(500).json({ success: false, error: 'Failed to add proxy' });
}
}));
this.app.delete('/api/proxies/:domain', (req, res) => {
try {
const { domain } = req.params;
const success = this.proxyService.removeProxy(domain);
if (success) {
res.json({ success: true, message: `Proxy for ${domain} removed successfully` });
}
else {
res.status(404).json({ success: false, error: 'Proxy not found' });
}
}
catch (error) {
console.error('Error removing proxy:', error);
res.status(500).json({ success: false, error: 'Failed to remove proxy' });
}
});
this.app.post('/api/proxies/:domain/start', (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const { domain } = req.params;
// Get certificate paths
const certInfo = yield this.certManager.verifyCertificate(domain);
if (!certInfo || !certInfo.isValid || !certInfo.certFilePath || !certInfo.keyFilePath) {
res.status(400).json({ success: false, error: 'Domain does not have a valid certificate' });
return;
}
const success = this.proxyService.startProxy(domain, certInfo.certFilePath, certInfo.keyFilePath);
if (success) {
res.json({ success: true, message: `Proxy for ${domain} started successfully` });
}
else {
res.status(500).json({ success: false, error: 'Failed to start proxy' });
}
}
catch (error) {
console.error('Error starting proxy:', error);
res.status(500).json({ success: false, error: 'Failed to start proxy' });
}
}));
this.app.post('/api/proxies/:domain/stop', (req, res) => {
try {
const { domain } = req.params;
const success = this.proxyService.stopProxy(domain);
if (success) {
res.json({ success: true, message: `Proxy for ${domain} stopped successfully` });
}
else {
res.status(404).json({ success: false, error: 'Proxy not found or not running' });
}
}
catch (error) {
console.error('Error stopping proxy:', error);
res.status(500).json({ success: false, error: 'Failed to stop proxy' });
}
});
this.app.patch('/api/proxies/:domain', (req, res) => {
try {
const { domain } = req.params;
const { target, port } = req.body;
if (!target && !port) {
res.status(400).json({ success: false, error: 'No update parameters provided' });
return;
}
const success = this.proxyService.updateProxy(domain, { target, port });
if (success) {
res.json({ success: true, message: `Proxy for ${domain} updated successfully` });
}
else {
res.status(404).json({ success: false, error: 'Proxy not found' });
}
}
catch (error) {
console.error('Error updating proxy:', error);
res.status(500).json({ success: false, error: 'Failed to update proxy' });
}
});
// Route to serve the user interface
this.app.get('*', (_, res) => {
res.sendFile(path.join(__dirname, '..', '..', 'public', 'index.html'));
});
}
/**
* Start the web server
*/
start() {
return __awaiter(this, void 0, void 0, function* () {
this.server = this.app.listen(this.config.port, () => {
console.log(`Server listening on http://localhost:${this.config.port}`);
});
try {
yield (0, open_1.default)(`http://localhost:${this.config.port}`);
}
catch (error) {
console.warn('Could not open browser automatically:', error);
}
});
}
/**
* Stop the web server
*/
stop() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (this.server) {
// Stop all proxies first
this.proxyService.stopAllProxies();
this.server.close(err => {
if (err) {
console.error('Error stopping HTTP server:', err);
reject(err);
}
else {
console.log('HTTP server stopped');
resolve();
}
});
}
else {
resolve();
}
});
});
}
}
exports.WebServer = WebServer;