Buckets:
| import express, {} from 'express'; | |
| import cors from 'cors'; | |
| import path from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; | |
| import { settingsService } from '../shared/settings.js'; | |
| import { logger } from './utils/logger.js'; | |
| import { formatMetricsForAPI } from '../shared/transport-metrics.js'; | |
| import { ALL_BUILTIN_TOOL_IDS } from '@llmindset/hf-mcp'; | |
| import { CORS_ALLOWED_ORIGINS, CORS_EXPOSED_HEADERS } from '../shared/constants.js'; | |
| import { apiMetrics } from './utils/api-metrics.js'; | |
| import { gradioMetrics } from './utils/gradio-metrics.js'; | |
| import { formatCacheMetricsForAPI } from './utils/gradio-cache.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| export class WebServer { | |
| app; | |
| server = null; | |
| transportInfo = { | |
| transport: 'unknown', | |
| defaultHfTokenSet: false, | |
| externalApiMode: false, | |
| stdioClient: null, | |
| }; | |
| localSharedToolStates = new Map(); | |
| transport; | |
| apiClient; | |
| constructor() { | |
| this.app = express(); | |
| this.setupMiddleware(); | |
| } | |
| setupMiddleware() { | |
| this.app.disable('x-powered-by'); | |
| this.app.set('trust proxy', true); | |
| this.app.use(express.json({ limit: '1mb' })); | |
| this.app.use((_, res, next) => { | |
| res.setHeader('X-Content-Type-Options', 'nosniff'); | |
| res.setHeader('Referrer-Policy', 'no-referrer'); | |
| if (process.env.HSTS === 'true') { | |
| res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); | |
| } | |
| next(); | |
| }); | |
| const envOrigins = (process.env.CORS_ALLOWED_ORIGINS || '') | |
| .split(',') | |
| .map((s) => s.trim()) | |
| .filter(Boolean); | |
| const normalize = (s) => s.replace(/\/+$/, ''); | |
| const envOriginsNorm = envOrigins.map(normalize); | |
| const allowedOrigins = (envOriginsNorm.length > 0 ? envOriginsNorm : CORS_ALLOWED_ORIGINS).map(normalize); | |
| let originSetting; | |
| if (allowedOrigins.length === 1 && allowedOrigins[0] === '*') { | |
| originSetting = '*'; | |
| } | |
| else if (allowedOrigins.some((o) => o.includes('*'))) { | |
| const exact = new Set(allowedOrigins.filter((o) => !o.includes('*'))); | |
| const patterns = allowedOrigins.filter((o) => o.includes('*')); | |
| originSetting = (requestOrigin, cb) => { | |
| if (!requestOrigin) | |
| return cb(null, true); | |
| const reqOrigin = normalize(requestOrigin); | |
| if (exact.has(reqOrigin)) | |
| return cb(null, true); | |
| try { | |
| const u = new URL(requestOrigin); | |
| for (const p of patterns) { | |
| let scheme; | |
| let hostPattern = p; | |
| if (p.startsWith('http://') || p.startsWith('https://')) { | |
| scheme = p.split('://', 1)[0]; | |
| hostPattern = p.slice((scheme + '://').length); | |
| } | |
| if (!hostPattern.startsWith('*.')) | |
| continue; | |
| const suffix = hostPattern.slice(2); | |
| const host = u.hostname; | |
| if (scheme && u.protocol !== scheme + ':') | |
| continue; | |
| if (host.endsWith('.' + suffix) && host !== suffix) { | |
| return cb(null, true); | |
| } | |
| } | |
| return cb(null, false); | |
| } | |
| catch { | |
| return cb(null, false); | |
| } | |
| }; | |
| } | |
| else { | |
| originSetting = allowedOrigins; | |
| } | |
| const corsOptions = { | |
| origin: originSetting, | |
| exposedHeaders: CORS_EXPOSED_HEADERS, | |
| }; | |
| this.app.use(cors(corsOptions)); | |
| this.app.options('*', cors(corsOptions)); | |
| } | |
| getApp() { | |
| return this.app; | |
| } | |
| setTransportInfo(info) { | |
| this.transportInfo = info; | |
| } | |
| setClientInfo(clientInfo) { | |
| this.transportInfo.stdioClient = clientInfo; | |
| } | |
| initializeToolStates() { | |
| const currentSettings = settingsService.getSettings(); | |
| for (const toolId of ALL_BUILTIN_TOOL_IDS) { | |
| const isEnabled = currentSettings.builtInTools.includes(toolId); | |
| this.localSharedToolStates.set(toolId, isEnabled); | |
| } | |
| } | |
| setTransport(transport) { | |
| this.transport = transport; | |
| } | |
| setApiClient(apiClient) { | |
| this.apiClient = apiClient; | |
| } | |
| getTransportInfo() { | |
| return this.transportInfo; | |
| } | |
| async start(port) { | |
| if (this.server) { | |
| throw new Error('Server is already running'); | |
| } | |
| return new Promise((resolve, reject) => { | |
| this.server = this.app | |
| .listen(port, () => { | |
| this.transportInfo.port = port; | |
| resolve(); | |
| }) | |
| .on('error', reject); | |
| }); | |
| } | |
| async stop() { | |
| if (!this.server) { | |
| return; | |
| } | |
| return new Promise((resolve, reject) => { | |
| this.server?.close((err) => { | |
| if (err) { | |
| reject(err); | |
| } | |
| else { | |
| this.server = null; | |
| resolve(); | |
| } | |
| }); | |
| }); | |
| } | |
| async setupStaticFiles(isDevelopment) { | |
| if (isDevelopment) { | |
| try { | |
| const { createServer: createViteServer } = await import('vite'); | |
| const rootDir = path.resolve(__dirname, '..', '..', '..', 'app', 'src', 'web'); | |
| const vite = await createViteServer({ | |
| configFile: path.resolve(__dirname, '..', '..', '..', 'app', 'vite.config.ts'), | |
| server: { | |
| middlewareMode: true, | |
| hmr: true, | |
| }, | |
| appType: 'spa', | |
| root: rootDir, | |
| }); | |
| this.app.use(vite.middlewares); | |
| logger.info('Using Vite middleware in development mode with HMR enabled'); | |
| logger.info({ rootDir }, 'Vite root directory'); | |
| } | |
| catch (err) { | |
| logger.error({ err }, 'Error setting up Vite middleware'); | |
| throw err; | |
| } | |
| } | |
| else { | |
| const staticPath = path.join(__dirname, '..', 'web'); | |
| this.app.use(express.static(staticPath)); | |
| this.app.get('*', (req, res) => { | |
| if (!req.path.startsWith('/api/')) { | |
| res.sendFile(path.join(staticPath, 'index.html')); | |
| } | |
| }); | |
| } | |
| } | |
| setupApiRoutes() { | |
| this.app.get('/api/transport', (_req, res) => { | |
| res.json(this.transportInfo); | |
| }); | |
| this.app.get('/api/sessions', (_req, res) => { | |
| if (!this.transport) { | |
| res.json([]); | |
| return; | |
| } | |
| const sessions = this.transport.getSessions(); | |
| if (this.transportInfo.transport === 'stdio' && sessions.length > 0) { | |
| const stdioSession = sessions[0]; | |
| if (stdioSession?.clientInfo && !this.transportInfo.stdioClient) { | |
| this.transportInfo.stdioClient = { | |
| name: stdioSession.clientInfo.name, | |
| version: stdioSession.clientInfo.version, | |
| }; | |
| } | |
| } | |
| res.json(sessions); | |
| }); | |
| this.app.get('/api/transport-metrics', (req, res) => { | |
| if (!this.transport) { | |
| res.status(503).json({ error: 'Transport not initialized' }); | |
| return; | |
| } | |
| try { | |
| const tempLogParam = req.query.templog; | |
| let tempLogStatus = undefined; | |
| if (tempLogParam && this.transportInfo.transport === 'streamableHttpJson') { | |
| const statelessTransport = this.transport; | |
| if (statelessTransport.activateTempLogging && statelessTransport.getTempLogStatus) { | |
| const requestedCount = parseInt(tempLogParam, 10); | |
| if (!isNaN(requestedCount) && requestedCount > 0) { | |
| const activated = statelessTransport.activateTempLogging(requestedCount); | |
| tempLogStatus = { | |
| activated: true, | |
| remaining: activated, | |
| maxAllowed: statelessTransport.getTempLogStatus().maxAllowed, | |
| }; | |
| } | |
| } | |
| } | |
| const metrics = this.transport.getMetrics(); | |
| const isStateless = this.transportInfo.transport === 'streamableHttpJson'; | |
| const config = this.transport.getConfiguration(); | |
| const sessions = this.transport.getSessions().map((session) => { | |
| const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); | |
| const hasRecentActivity = session.lastActivity > fiveMinutesAgo; | |
| const hasPingFailures = (session.pingFailures || 0) >= 1; | |
| let connectionStatus; | |
| if (!hasRecentActivity) { | |
| connectionStatus = 'Disconnected'; | |
| } | |
| else if (hasPingFailures) { | |
| connectionStatus = 'Distressed'; | |
| } | |
| else { | |
| connectionStatus = 'Connected'; | |
| } | |
| return { | |
| id: session.id, | |
| connectedAt: session.connectedAt.toISOString(), | |
| lastActivity: session.lastActivity.toISOString(), | |
| requestCount: session.requestCount, | |
| clientInfo: session.clientInfo, | |
| isConnected: hasRecentActivity, | |
| connectionStatus, | |
| pingFailures: session.pingFailures || 0, | |
| lastPingAttempt: session.lastPingAttempt?.toISOString(), | |
| ipAddress: session.ipAddress, | |
| }; | |
| }); | |
| const formattedMetrics = formatMetricsForAPI(metrics, this.transportInfo.transport, isStateless, sessions); | |
| if (!isStateless && config.staleCheckInterval && config.staleTimeout) { | |
| formattedMetrics.configuration = { | |
| heartbeatInterval: config.heartbeatInterval || 30000, | |
| staleCheckInterval: config.staleCheckInterval, | |
| staleTimeout: config.staleTimeout, | |
| pingEnabled: config.pingEnabled, | |
| pingInterval: config.pingInterval, | |
| pingFailureThreshold: config.pingFailureThreshold || 1, | |
| }; | |
| } | |
| if (this.transportInfo.externalApiMode) { | |
| formattedMetrics.apiMetrics = apiMetrics.getMetrics(); | |
| } | |
| formattedMetrics.gradioMetrics = gradioMetrics.getMetrics(); | |
| formattedMetrics.gradioCacheMetrics = formatCacheMetricsForAPI(); | |
| const extendedMetrics = formattedMetrics; | |
| if (tempLogStatus) { | |
| extendedMetrics.tempLogStatus = tempLogStatus; | |
| } | |
| else if (this.transportInfo.transport === 'streamableHttpJson') { | |
| const statelessTransport = this.transport; | |
| if (statelessTransport.getTempLogStatus) { | |
| const status = statelessTransport.getTempLogStatus(); | |
| if (status.enabled) { | |
| extendedMetrics.tempLogStatus = status; | |
| } | |
| } | |
| } | |
| res.json(formattedMetrics); | |
| } | |
| catch (error) { | |
| logger.error({ error }, 'Error retrieving transport metrics'); | |
| res.status(500).json({ error: 'Failed to retrieve transport metrics' }); | |
| } | |
| }); | |
| this.app.get('/api/settings', (_req, res) => { | |
| res.json(settingsService.getSettings()); | |
| }); | |
| this.app.post('/api/settings', express.json(), (req, res) => { | |
| const { builtInTools, spaceTools } = req.body; | |
| let updatedSettings = settingsService.getSettings(); | |
| if (builtInTools !== undefined) { | |
| updatedSettings = settingsService.updateBuiltInTools(builtInTools); | |
| } | |
| if (spaceTools !== undefined) { | |
| updatedSettings = settingsService.updateSpaceTools(spaceTools); | |
| } | |
| if (builtInTools !== undefined) { | |
| for (const toolId of ALL_BUILTIN_TOOL_IDS) { | |
| const shouldBeEnabled = builtInTools.includes(toolId); | |
| const currentlyEnabled = this.localSharedToolStates.get(toolId) ?? false; | |
| if (currentlyEnabled !== shouldBeEnabled) { | |
| this.localSharedToolStates.set(toolId, shouldBeEnabled); | |
| if (this.apiClient) { | |
| this.apiClient.emit('toolStateChange', toolId, shouldBeEnabled); | |
| } | |
| logger.info(`Tool ${toolId} has been ${shouldBeEnabled ? 'enabled' : 'disabled'} via API`); | |
| } | |
| } | |
| } | |
| res.json(updatedSettings); | |
| }); | |
| this.app.get('/api/gradio-endpoints', (_req, res) => { | |
| if (!this.apiClient) { | |
| res.json([]); | |
| return; | |
| } | |
| res.json(this.apiClient.getGradioEndpoints()); | |
| }); | |
| this.app.post('/api/gradio-endpoints/:index', express.json(), (req, res) => { | |
| const index = parseInt(req.params.index); | |
| const { enabled } = req.body; | |
| if (!this.apiClient) { | |
| res.status(500).json({ error: 'API client not initialized' }); | |
| return; | |
| } | |
| const endpoints = this.apiClient.getGradioEndpoints(); | |
| if (index < 0 || index >= endpoints.length) { | |
| res.status(404).json({ error: 'Endpoint not found' }); | |
| return; | |
| } | |
| this.apiClient.updateGradioEndpointState(index, enabled); | |
| const endpoint = endpoints[index]; | |
| if (endpoint) { | |
| const toolId = `gradio_${endpoint.subdomain}`; | |
| this.apiClient.emit('toolStateChange', toolId, enabled); | |
| } | |
| const updatedEndpoint = endpoints[index]; | |
| res.json(updatedEndpoint); | |
| }); | |
| this.app.put('/api/gradio-endpoints/:index', express.json(), (req, res) => { | |
| const index = parseInt(req.params.index); | |
| const { name, subdomain, id, emoji } = req.body; | |
| if (!this.apiClient) { | |
| res.status(500).json({ error: 'API client not initialized' }); | |
| return; | |
| } | |
| const endpoints = this.apiClient.getGradioEndpoints(); | |
| if (index < 0 || index >= endpoints.length) { | |
| res.status(404).json({ error: 'Endpoint not found' }); | |
| return; | |
| } | |
| if (!name || !subdomain) { | |
| res.status(400).json({ error: 'Name and subdomain are required' }); | |
| return; | |
| } | |
| const updatedEndpoint = { name, subdomain, id, emoji }; | |
| this.apiClient.updateGradioEndpoint(index, updatedEndpoint); | |
| res.json(updatedEndpoint); | |
| }); | |
| } | |
| } | |
| //# sourceMappingURL=web-server.js.map |
Xet Storage Details
- Size:
- 16.1 kB
- Xet hash:
- 9b1717bc83d9e0e97602f6c9d703af117c9436f6df960d9582be302036faa670
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.