@undefineds.co/xpod 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/cloud.json +1 -1
- package/dist/api/chatkit/ai-provider.js +4 -3
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +8 -7
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/runtime/PtyThreadRuntime.js +2 -1
- package/dist/api/chatkit/runtime/PtyThreadRuntime.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +1 -1
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.js +2 -27
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
- package/dist/api/handlers/NodeHandler.js +17 -158
- package/dist/api/handlers/NodeHandler.js.map +1 -1
- package/dist/api/service/VercelChatService.js +23 -23
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/service/platform-ai-config.d.ts +8 -0
- package/dist/api/service/platform-ai-config.js +64 -0
- package/dist/api/service/platform-ai-config.js.map +1 -0
- package/dist/cli/commands/start.js +1 -2
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/components/context.jsonld +3 -0
- package/dist/http/TracingHandler.js +2 -0
- package/dist/http/TracingHandler.js.map +1 -1
- package/dist/identity/drizzle/DrizzleIndexedStorage.d.ts +10 -1
- package/dist/identity/drizzle/DrizzleIndexedStorage.js +99 -6
- package/dist/identity/drizzle/DrizzleIndexedStorage.js.map +1 -1
- package/dist/identity/drizzle/DrizzleIndexedStorage.jsonld +36 -0
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +3 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +10 -37
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/db.js +13 -2
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.js +0 -1
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +0 -19
- package/dist/identity/drizzle/schema.sqlite.js +0 -1
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/main.js +1 -2
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.d.ts +1 -0
- package/dist/provision/ProvisionPodCreator.js +25 -2
- package/dist/provision/ProvisionPodCreator.js.map +1 -1
- package/dist/provision/ProvisionPodCreator.jsonld +4 -0
- package/dist/runtime/bootstrap.js +4 -4
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/storage/MetadataRequestContext.d.ts +13 -0
- package/dist/storage/MetadataRequestContext.js +6 -0
- package/dist/storage/MetadataRequestContext.js.map +1 -0
- package/dist/storage/NodeSqliteDrizzle.js +19 -5
- package/dist/storage/NodeSqliteDrizzle.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +1 -0
- package/dist/storage/accessors/MinioDataAccessor.js +25 -2
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +3 -1
- package/dist/storage/accessors/MixDataAccessor.js +61 -23
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +23 -0
- package/dist/supervisor/Supervisor.js +1 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/package.json +1 -1
- package/static/app/assets/index.css +1 -1
- package/static/app/assets/main.js +50 -10
- package/static/dashboard/assets/{dashboard-BfmT5Vc8.js → dashboard-CTk9cn1-.js} +8 -8
- package/static/dashboard/assets/dashboard-VQRsvXx9.css +1 -0
- package/static/dashboard/dashboard.html +2 -2
- package/static/dashboard/assets/dashboard-DkZOr7bV.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/AdminHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin API Handler\n * Provides configuration management, restart functionality, and log streaming\n */\n\nimport type { ServerResponse } from 'node:http';\nimport type { ApiServer, RouteHandler } from '../ApiServer';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport fs from 'fs';\nimport path from 'path';\nimport { createReadStream, statSync } from 'fs';\nimport { createInterface } from 'readline';\nimport { PACKAGE_ROOT } from '../../runtime';\n\nconst CONFIG_DIR = path.resolve(PACKAGE_ROOT, 'config');\n\nfunction getEnvFilePath(): string {\n const envPath = process.env.XPOD_ENV_PATH;\n if (envPath && envPath.trim()) {\n return path.resolve(envPath);\n }\n return path.resolve(process.cwd(), '.env.local');\n}\n\ninterface ConfigFile {\n name: string;\n path: string;\n exists: boolean;\n}\n\ninterface EnvConfig {\n [key: string]: string;\n}\n\n/**\n * Read .env.local file and parse it\n */\nfunction readEnvFile(filePath: string): EnvConfig {\n const config: EnvConfig = {};\n if (!fs.existsSync(filePath)) {\n return config;\n }\n const content = fs.readFileSync(filePath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Remove quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n config[key] = value;\n }\n return config;\n}\n\n/**\n * Write .env.local file\n */\nfunction writeEnvFile(filePath: string, config: EnvConfig): void {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(config)) {\n // Quote values that contain spaces or special characters\n const needsQuotes = /[\\s\"'=]/.test(value);\n const quotedValue = needsQuotes ? `\"${value}\"` : value;\n lines.push(`${key}=${quotedValue}`);\n }\n fs.writeFileSync(filePath, lines.join('\\n') + '\\n');\n}\n\n/**\n * List available config files\n */\nfunction listConfigFiles(): ConfigFile[] {\n const files: ConfigFile[] = [];\n const configFiles = ['local.json', 'cloud.json', 'main.json', 'xpod.base.json'];\n\n for (const name of configFiles) {\n const filePath = path.join(CONFIG_DIR, name);\n files.push({\n name,\n path: filePath,\n exists: fs.existsSync(filePath),\n });\n }\n return files;\n}\n\n/**\n * Send JSON response helper\n */\n\nfunction isPrivateIp(host: string): boolean {\n if (host === 'localhost' || host === '::1') return true;\n if (host.startsWith('127.')) return true;\n if (host.startsWith('10.')) return true;\n if (host.startsWith('192.168.')) return true;\n const m = host.match(/^172\\.(\\d+)\\./);\n if (m) {\n const second = Number(m[1]);\n if (second >= 16 && second <= 31) return true;\n }\n return false;\n}\n\nasync function fetchPublicIp(): Promise<string | null> {\n const endpoints = [\n 'https://api.ipify.org?format=json',\n 'https://ifconfig.me/ip',\n ];\n\n for (const url of endpoints) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) {\n continue;\n }\n const contentType = res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const json = await res.json() as { ip?: string };\n const ip = json.ip?.trim();\n if (ip) {\n return ip;\n }\n } else {\n const ip = (await res.text()).trim();\n if (ip) {\n return ip;\n }\n }\n } catch {\n // ignore and try next endpoint\n }\n }\n\n return null;\n}\n\nfunction sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(data));\n}\n\n/**\n * Parse JSON body from request\n */\nfunction parseJsonBody(req: AuthenticatedRequest): Promise<{ env?: EnvConfig }> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch (err) {\n reject(err);\n }\n });\n req.on('error', reject);\n });\n}\n\nexport function registerAdminRoutes(server: ApiServer): void {\n const logger = console;\n\n // GET /api/admin/status - Get xpod status\n const statusHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n const configs = listConfigFiles();\n\n sendJson(res, 200, {\n status: 'running',\n pid: process.pid,\n ppid: process.ppid,\n uptime: process.uptime(),\n env: {\n CSS_BASE_URL: env.CSS_BASE_URL || process.env.CSS_BASE_URL,\n CSS_EDITION: env.CSS_EDITION || process.env.CSS_EDITION,\n CSS_PORT: env.CSS_PORT || process.env.CSS_PORT,\n },\n configs,\n });\n } catch (error) {\n logger.error('[Admin] Status error:', error);\n sendJson(res, 500, { error: 'Failed to get status' });\n }\n };\n\n // GET /api/admin/config - Get current configuration\n const getConfigHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n sendJson(res, 200, {\n env,\n configFiles: listConfigFiles(),\n });\n } catch (error) {\n logger.error('[Admin] Get config error:', error);\n sendJson(res, 500, { error: 'Failed to read configuration' });\n }\n };\n\n // PUT /api/admin/config - Update configuration\n const updateConfigHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n // Parse body from raw request\n const body = await parseJsonBody(req);\n\n if (body.env) {\n // Merge with existing config\n const envFilePath = getEnvFilePath();\n const currentEnv = readEnvFile(envFilePath);\n const newEnv = { ...currentEnv, ...body.env };\n\n // Remove keys set to null or empty string\n for (const [key, value] of Object.entries(newEnv)) {\n if (value === null || value === '') {\n delete newEnv[key];\n }\n }\n\n writeEnvFile(envFilePath, newEnv);\n logger.log('[Admin] Configuration updated');\n }\n\n sendJson(res, 200, {\n success: true,\n message: 'Configuration updated. Restart required for changes to take effect.',\n });\n } catch (error) {\n logger.error('[Admin] Update config error:', error);\n sendJson(res, 500, { error: 'Failed to update configuration' });\n }\n };\n\n // POST /api/admin/restart - Trigger xpod restart\n const restartHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const ppid = process.ppid;\n\n if (!ppid) {\n sendJson(res, 500, { error: 'Cannot determine parent process' });\n return;\n }\n\n logger.log(`[Admin] Sending SIGUSR1 to parent process (pid: ${ppid})`);\n\n // Send response before triggering restart\n sendJson(res, 200, {\n success: true,\n message: 'Restart signal sent. Server will restart shortly.',\n });\n\n // Give time for response to be sent, then signal parent\n setTimeout(() => {\n try {\n process.kill(ppid, 'SIGUSR1');\n } catch (err) {\n logger.error('[Admin] Failed to send restart signal:', err);\n }\n }, 100);\n } catch (error) {\n logger.error('[Admin] Restart error:', error);\n sendJson(res, 500, { error: 'Failed to trigger restart' });\n }\n };\n\n // Log buffer for recent logs (in-memory)\n const logBuffer: Array<{\n timestamp: string;\n level: string;\n source: string;\n message: string;\n }> = [];\n const MAX_LOG_BUFFER = 1000;\n\n // Capture stdout/stderr logs\n const originalStdoutWrite = process.stdout.write.bind(process.stdout);\n const originalStderrWrite = process.stderr.write.bind(process.stderr);\n\n function addLog(level: string, source: string, message: string): void {\n const entry = {\n timestamp: new Date().toISOString(),\n level,\n source,\n message: message.trim(),\n };\n logBuffer.push(entry);\n if (logBuffer.length > MAX_LOG_BUFFER) {\n logBuffer.shift();\n }\n }\n\n // Intercept stdout\n process.stdout.write = (chunk: any, ...args: any[]): boolean => {\n const message = chunk.toString();\n addLog('info', 'xpod', message);\n return originalStdoutWrite(chunk, ...args);\n };\n\n // Intercept stderr\n process.stderr.write = (chunk: any, ...args: any[]): boolean => {\n const message = chunk.toString();\n addLog('error', 'xpod', message);\n return originalStderrWrite(chunk, ...args);\n };\n\n // GET /api/admin/logs - Get recent logs\n const getLogsHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const limit = parseInt(url.searchParams.get('limit') || '100', 10);\n const level = url.searchParams.get('level');\n const source = url.searchParams.get('source');\n\n let logs = [...logBuffer];\n\n // Apply filters\n if (level && level !== 'all') {\n logs = logs.filter(log => log.level === level);\n }\n if (source && source !== 'all') {\n logs = logs.filter(log => log.source === source);\n }\n\n // Return last N logs\n logs = logs.slice(-limit);\n\n sendJson(res, 200, { logs });\n } catch (error) {\n logger.error('[Admin] Get logs error:', error);\n sendJson(res, 500, { error: 'Failed to get logs' });\n }\n };\n\n // GET /api/admin/logs/stream - Stream logs via SSE\n const streamLogsHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial logs\n const lastIndex = logBuffer.length;\n res.write(`data: ${JSON.stringify({ type: 'init', logs: logBuffer.slice(-100) })}\\n\\n`);\n\n // Send new logs every second\n let currentIndex = lastIndex;\n const interval = setInterval(() => {\n if (logBuffer.length > currentIndex) {\n const newLogs = logBuffer.slice(currentIndex);\n res.write(`data: ${JSON.stringify({ type: 'update', logs: newLogs })}\\n\\n`);\n currentIndex = logBuffer.length;\n }\n }, 1000);\n\n // Clean up on close\n res.on('close', () => {\n clearInterval(interval);\n });\n };\n\n // GET /api/admin/logs/file - Read log file from disk\n const getLogFileHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const lines = parseInt(url.searchParams.get('lines') || '100', 10);\n\n // Try common log file locations\n const logPaths = [\n path.resolve(process.cwd(), 'logs', 'combined.log'),\n path.resolve(process.cwd(), 'logs', 'error.log'),\n path.resolve(process.cwd(), 'xpod.log'),\n path.resolve(process.cwd(), 'combined.log'),\n ];\n\n let logPath: string | null = null;\n for (const p of logPaths) {\n if (fs.existsSync(p)) {\n logPath = p;\n break;\n }\n }\n\n if (!logPath) {\n sendJson(res, 404, { error: 'No log file found' });\n return;\n }\n\n // Read last N lines\n const fileLogs: string[] = [];\n const stats = statSync(logPath);\n const stream = createReadStream(logPath, {\n start: Math.max(0, stats.size - 1024 * 100), // Last 100KB\n end: stats.size,\n });\n\n const rl = createInterface({ input: stream });\n for await (const line of rl) {\n fileLogs.push(line);\n }\n\n const lastLines = fileLogs.slice(-lines);\n sendJson(res, 200, {\n file: logPath,\n lines: lastLines,\n });\n } catch (error) {\n logger.error('[Admin] Get log file error:', error);\n sendJson(res, 500, { error: 'Failed to read log file' });\n }\n };\n\n\n // GET /api/admin/public-ip - Detect outbound public IP and compare with CSS_BASE_URL\n const ipv4Handler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n const parsedUrl = new URL(req.url ?? '/', 'http://localhost');\n const baseUrl = parsedUrl.searchParams.get('baseUrl') || env.CSS_BASE_URL || process.env.CSS_BASE_URL || '';\n\n const ip = await fetchPublicIp();\n\n if (!baseUrl) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: ip,\n baseUrl,\n detail: ip ? '未配置 Base URL,无法判断是否可直连。' : '未配置 Base URL,且无法获取公网 IP。',\n });\n return;\n }\n\n let hostname = '';\n try {\n hostname = new URL(baseUrl).hostname;\n } catch {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: ip,\n baseUrl,\n detail: 'Base URL 格式不合法,无法判断。',\n });\n return;\n }\n\n if (isPrivateIp(hostname)) {\n sendJson(res, 200, {\n status: 'fail',\n ipv4: ip,\n baseUrl,\n detail: 'Base URL 为本地/内网地址,默认不可直连。',\n });\n return;\n }\n\n // If hostname is an IP, compare directly.\n const isIpLiteral = /^\\d{1,3}(\\.\\d{1,3}){3}$/.test(hostname);\n if (isIpLiteral) {\n if (!ip) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: null,\n baseUrl,\n detail: '无法获取公网出口 IP,无法比对。',\n });\n return;\n }\n const ok = hostname === ip;\n sendJson(res, 200, {\n status: ok ? 'pass' : 'fail',\n ipv4: ip,\n baseUrl,\n detail: ok\n ? 'Base URL IP 与公网出口 IP 一致,默认可直连。'\n : 'Base URL IP 与公网出口 IP 不一致,默认不可直连。',\n });\n return;\n }\n\n // Domain name: we can only do best-effort.\n if (!ip) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: null,\n baseUrl,\n detail: '已配置域名,但无法获取公网出口 IP,无法进一步判断。',\n });\n return;\n }\n\n sendJson(res, 200, {\n status: 'pass',\n ipv4: ip,\n baseUrl,\n detail: '已配置域名,默认可直连(仍需确保端口映射/防火墙放行)。',\n });\n } catch (error) {\n logger.error('[Admin] Public IP check error:', error);\n sendJson(res, 500, { error: 'Failed to detect public ip' });\n }\n };\n\n // Register routes - public for now (TODO: add auth for production)\n server.get('/api/admin/status', statusHandler, { public: true });\n server.get('/api/admin/config', getConfigHandler, { public: true });\n server.get('/api/admin/public-ip', ipv4Handler, { public: true });\n server.put('/api/admin/config', updateConfigHandler, { public: true });\n server.post('/api/admin/restart', restartHandler, { public: true });\n server.get('/api/admin/logs', getLogsHandler, { public: true });\n server.get('/api/admin/logs/stream', streamLogsHandler, { public: true });\n server.get('/api/admin/logs/file', getLogFileHandler, { public: true });\n\n logger.log('[Admin] Admin API routes registered');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AdminHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/AdminHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin API Handler\n * Provides configuration management, restart functionality, and log streaming\n */\n\nimport type { ServerResponse } from 'node:http';\nimport type { ApiServer, RouteHandler } from '../ApiServer';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport fs from 'fs';\nimport path from 'path';\nimport { createReadStream, statSync } from 'fs';\nimport { createInterface } from 'readline';\nimport { PACKAGE_ROOT } from '../../runtime';\n\nconst CONFIG_DIR = path.resolve(PACKAGE_ROOT, 'config');\n\nfunction getEnvFilePath(): string {\n const envPath = process.env.XPOD_ENV_PATH;\n if (envPath && envPath.trim()) {\n return path.resolve(envPath);\n }\n return path.resolve(process.cwd(), '.env.local');\n}\n\ninterface ConfigFile {\n name: string;\n path: string;\n exists: boolean;\n}\n\ninterface EnvConfig {\n [key: string]: string;\n}\n\n/**\n * Read .env.local file and parse it\n */\nfunction readEnvFile(filePath: string): EnvConfig {\n const config: EnvConfig = {};\n if (!fs.existsSync(filePath)) {\n return config;\n }\n const content = fs.readFileSync(filePath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Remove quotes\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n config[key] = value;\n }\n return config;\n}\n\n/**\n * Write .env.local file\n */\nfunction writeEnvFile(filePath: string, config: EnvConfig): void {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(config)) {\n // Quote values that contain spaces or special characters\n const needsQuotes = /[\\s\"'=]/.test(value);\n const quotedValue = needsQuotes ? `\"${value}\"` : value;\n lines.push(`${key}=${quotedValue}`);\n }\n fs.writeFileSync(filePath, lines.join('\\n') + '\\n');\n}\n\n/**\n * List available config files\n */\nfunction listConfigFiles(): ConfigFile[] {\n const files: ConfigFile[] = [];\n const configFiles = ['local.json', 'cloud.json', 'main.json', 'xpod.base.json'];\n\n for (const name of configFiles) {\n const filePath = path.join(CONFIG_DIR, name);\n files.push({\n name,\n path: filePath,\n exists: fs.existsSync(filePath),\n });\n }\n return files;\n}\n\n/**\n * Send JSON response helper\n */\n\nfunction isPrivateIp(host: string): boolean {\n if (host === 'localhost' || host === '::1') return true;\n if (host.startsWith('127.')) return true;\n if (host.startsWith('10.')) return true;\n if (host.startsWith('192.168.')) return true;\n const m = host.match(/^172\\.(\\d+)\\./);\n if (m) {\n const second = Number(m[1]);\n if (second >= 16 && second <= 31) return true;\n }\n return false;\n}\n\nasync function fetchPublicIp(): Promise<string | null> {\n const endpoints = [\n 'https://api.ipify.org?format=json',\n 'https://ifconfig.me/ip',\n ];\n\n for (const url of endpoints) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) {\n continue;\n }\n const contentType = res.headers.get('content-type') || '';\n if (contentType.includes('application/json')) {\n const json = await res.json() as { ip?: string };\n const ip = json.ip?.trim();\n if (ip) {\n return ip;\n }\n } else {\n const ip = (await res.text()).trim();\n if (ip) {\n return ip;\n }\n }\n } catch {\n // ignore and try next endpoint\n }\n }\n\n return null;\n}\n\nfunction sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(data));\n}\n\n/**\n * Parse JSON body from request\n */\nfunction parseJsonBody(req: AuthenticatedRequest): Promise<{ env?: EnvConfig }> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n try {\n resolve(body ? JSON.parse(body) : {});\n } catch (err) {\n reject(err);\n }\n });\n req.on('error', reject);\n });\n}\n\nexport function registerAdminRoutes(server: ApiServer): void {\n const logger = console;\n\n // GET /api/admin/status - Get xpod status\n const statusHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n const configs = listConfigFiles();\n\n sendJson(res, 200, {\n status: 'running',\n pid: process.pid,\n ppid: process.ppid,\n uptime: process.uptime(),\n env: {\n CSS_BASE_URL: env.CSS_BASE_URL || process.env.CSS_BASE_URL,\n XPOD_EDITION: env.XPOD_EDITION || process.env.XPOD_EDITION,\n CSS_PORT: env.CSS_PORT || process.env.CSS_PORT,\n },\n configs,\n });\n } catch (error) {\n logger.error('[Admin] Status error:', error);\n sendJson(res, 500, { error: 'Failed to get status' });\n }\n };\n\n // GET /api/admin/config - Get current configuration\n const getConfigHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n sendJson(res, 200, {\n env,\n configFiles: listConfigFiles(),\n });\n } catch (error) {\n logger.error('[Admin] Get config error:', error);\n sendJson(res, 500, { error: 'Failed to read configuration' });\n }\n };\n\n // PUT /api/admin/config - Update configuration\n const updateConfigHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n // Parse body from raw request\n const body = await parseJsonBody(req);\n\n if (body.env) {\n // Merge with existing config\n const envFilePath = getEnvFilePath();\n const currentEnv = readEnvFile(envFilePath);\n const newEnv = { ...currentEnv, ...body.env };\n\n // Remove keys set to null or empty string\n for (const [key, value] of Object.entries(newEnv)) {\n if (value === null || value === '') {\n delete newEnv[key];\n }\n }\n\n writeEnvFile(envFilePath, newEnv);\n logger.log('[Admin] Configuration updated');\n }\n\n sendJson(res, 200, {\n success: true,\n message: 'Configuration updated. Restart required for changes to take effect.',\n });\n } catch (error) {\n logger.error('[Admin] Update config error:', error);\n sendJson(res, 500, { error: 'Failed to update configuration' });\n }\n };\n\n // POST /api/admin/restart - Trigger xpod restart\n const restartHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const ppid = process.ppid;\n\n if (!ppid) {\n sendJson(res, 500, { error: 'Cannot determine parent process' });\n return;\n }\n\n logger.log(`[Admin] Sending SIGUSR1 to parent process (pid: ${ppid})`);\n\n // Send response before triggering restart\n sendJson(res, 200, {\n success: true,\n message: 'Restart signal sent. Server will restart shortly.',\n });\n\n // Give time for response to be sent, then signal parent\n setTimeout(() => {\n try {\n process.kill(ppid, 'SIGUSR1');\n } catch (err) {\n logger.error('[Admin] Failed to send restart signal:', err);\n }\n }, 100);\n } catch (error) {\n logger.error('[Admin] Restart error:', error);\n sendJson(res, 500, { error: 'Failed to trigger restart' });\n }\n };\n\n // Log buffer for recent logs (in-memory)\n const logBuffer: Array<{\n timestamp: string;\n level: string;\n source: string;\n message: string;\n }> = [];\n const MAX_LOG_BUFFER = 1000;\n\n // Capture stdout/stderr logs\n const originalStdoutWrite = process.stdout.write.bind(process.stdout);\n const originalStderrWrite = process.stderr.write.bind(process.stderr);\n\n function addLog(level: string, source: string, message: string): void {\n const entry = {\n timestamp: new Date().toISOString(),\n level,\n source,\n message: message.trim(),\n };\n logBuffer.push(entry);\n if (logBuffer.length > MAX_LOG_BUFFER) {\n logBuffer.shift();\n }\n }\n\n // Intercept stdout\n process.stdout.write = (chunk: any, ...args: any[]): boolean => {\n const message = chunk.toString();\n addLog('info', 'xpod', message);\n return originalStdoutWrite(chunk, ...args);\n };\n\n // Intercept stderr\n process.stderr.write = (chunk: any, ...args: any[]): boolean => {\n const message = chunk.toString();\n addLog('error', 'xpod', message);\n return originalStderrWrite(chunk, ...args);\n };\n\n // GET /api/admin/logs - Get recent logs\n const getLogsHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const limit = parseInt(url.searchParams.get('limit') || '100', 10);\n const level = url.searchParams.get('level');\n const source = url.searchParams.get('source');\n\n let logs = [...logBuffer];\n\n // Apply filters\n if (level && level !== 'all') {\n logs = logs.filter(log => log.level === level);\n }\n if (source && source !== 'all') {\n logs = logs.filter(log => log.source === source);\n }\n\n // Return last N logs\n logs = logs.slice(-limit);\n\n sendJson(res, 200, { logs });\n } catch (error) {\n logger.error('[Admin] Get logs error:', error);\n sendJson(res, 500, { error: 'Failed to get logs' });\n }\n };\n\n // GET /api/admin/logs/stream - Stream logs via SSE\n const streamLogsHandler: RouteHandler = async (\n _req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // Send initial logs\n const lastIndex = logBuffer.length;\n res.write(`data: ${JSON.stringify({ type: 'init', logs: logBuffer.slice(-100) })}\\n\\n`);\n\n // Send new logs every second\n let currentIndex = lastIndex;\n const interval = setInterval(() => {\n if (logBuffer.length > currentIndex) {\n const newLogs = logBuffer.slice(currentIndex);\n res.write(`data: ${JSON.stringify({ type: 'update', logs: newLogs })}\\n\\n`);\n currentIndex = logBuffer.length;\n }\n }, 1000);\n\n // Clean up on close\n res.on('close', () => {\n clearInterval(interval);\n });\n };\n\n // GET /api/admin/logs/file - Read log file from disk\n const getLogFileHandler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const lines = parseInt(url.searchParams.get('lines') || '100', 10);\n\n // Try common log file locations\n const logPaths = [\n path.resolve(process.cwd(), 'logs', 'combined.log'),\n path.resolve(process.cwd(), 'logs', 'error.log'),\n path.resolve(process.cwd(), 'xpod.log'),\n path.resolve(process.cwd(), 'combined.log'),\n ];\n\n let logPath: string | null = null;\n for (const p of logPaths) {\n if (fs.existsSync(p)) {\n logPath = p;\n break;\n }\n }\n\n if (!logPath) {\n sendJson(res, 404, { error: 'No log file found' });\n return;\n }\n\n // Read last N lines\n const fileLogs: string[] = [];\n const stats = statSync(logPath);\n const stream = createReadStream(logPath, {\n start: Math.max(0, stats.size - 1024 * 100), // Last 100KB\n end: stats.size,\n });\n\n const rl = createInterface({ input: stream });\n for await (const line of rl) {\n fileLogs.push(line);\n }\n\n const lastLines = fileLogs.slice(-lines);\n sendJson(res, 200, {\n file: logPath,\n lines: lastLines,\n });\n } catch (error) {\n logger.error('[Admin] Get log file error:', error);\n sendJson(res, 500, { error: 'Failed to read log file' });\n }\n };\n\n\n // GET /api/admin/public-ip - Detect outbound public IP and compare with CSS_BASE_URL\n const ipv4Handler: RouteHandler = async (\n req: AuthenticatedRequest,\n res: ServerResponse,\n ) => {\n try {\n const envFilePath = getEnvFilePath();\n const env = readEnvFile(envFilePath);\n const parsedUrl = new URL(req.url ?? '/', 'http://localhost');\n const baseUrl = parsedUrl.searchParams.get('baseUrl') || env.CSS_BASE_URL || process.env.CSS_BASE_URL || '';\n\n const ip = await fetchPublicIp();\n\n if (!baseUrl) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: ip,\n baseUrl,\n detail: ip ? '未配置 Base URL,无法判断是否可直连。' : '未配置 Base URL,且无法获取公网 IP。',\n });\n return;\n }\n\n let hostname = '';\n try {\n hostname = new URL(baseUrl).hostname;\n } catch {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: ip,\n baseUrl,\n detail: 'Base URL 格式不合法,无法判断。',\n });\n return;\n }\n\n if (isPrivateIp(hostname)) {\n sendJson(res, 200, {\n status: 'fail',\n ipv4: ip,\n baseUrl,\n detail: 'Base URL 为本地/内网地址,默认不可直连。',\n });\n return;\n }\n\n // If hostname is an IP, compare directly.\n const isIpLiteral = /^\\d{1,3}(\\.\\d{1,3}){3}$/.test(hostname);\n if (isIpLiteral) {\n if (!ip) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: null,\n baseUrl,\n detail: '无法获取公网出口 IP,无法比对。',\n });\n return;\n }\n const ok = hostname === ip;\n sendJson(res, 200, {\n status: ok ? 'pass' : 'fail',\n ipv4: ip,\n baseUrl,\n detail: ok\n ? 'Base URL IP 与公网出口 IP 一致,默认可直连。'\n : 'Base URL IP 与公网出口 IP 不一致,默认不可直连。',\n });\n return;\n }\n\n // Domain name: we can only do best-effort.\n if (!ip) {\n sendJson(res, 200, {\n status: 'unknown',\n ipv4: null,\n baseUrl,\n detail: '已配置域名,但无法获取公网出口 IP,无法进一步判断。',\n });\n return;\n }\n\n sendJson(res, 200, {\n status: 'pass',\n ipv4: ip,\n baseUrl,\n detail: '已配置域名,默认可直连(仍需确保端口映射/防火墙放行)。',\n });\n } catch (error) {\n logger.error('[Admin] Public IP check error:', error);\n sendJson(res, 500, { error: 'Failed to detect public ip' });\n }\n };\n\n // Register routes - public for now (TODO: add auth for production)\n server.get('/api/admin/status', statusHandler, { public: true });\n server.get('/api/admin/config', getConfigHandler, { public: true });\n server.get('/api/admin/public-ip', ipv4Handler, { public: true });\n server.put('/api/admin/config', updateConfigHandler, { public: true });\n server.post('/api/admin/restart', restartHandler, { public: true });\n server.get('/api/admin/logs', getLogsHandler, { public: true });\n server.get('/api/admin/logs/stream', streamLogsHandler, { public: true });\n server.get('/api/admin/logs/file', getLogFileHandler, { public: true });\n\n logger.log('[Admin] Admin API routes registered');\n}\n"]}
|
|
@@ -34,33 +34,8 @@ function registerEdgeNodeSignalRoutes(server, options) {
|
|
|
34
34
|
nodeId = (0, AuthContext_1.getNodeId)(auth);
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!webId) {
|
|
40
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
nodeId = typeof payload.nodeId === 'string' ? payload.nodeId.trim() : '';
|
|
44
|
-
if (!nodeId) {
|
|
45
|
-
sendJson(response, 400, { error: 'nodeId is required' });
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
const owner = await repo.getNodeOwner(nodeId);
|
|
50
|
-
if (!owner) {
|
|
51
|
-
sendJson(response, 404, { error: 'Node not found' });
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (owner !== webId) {
|
|
55
|
-
sendJson(response, 403, { error: 'Access denied' });
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
logger.error(`Failed to validate node access: ${error}`);
|
|
61
|
-
sendJson(response, 500, { error: 'Failed to validate node access' });
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
37
|
+
sendJson(response, 501, { error: 'WebID/API-key based node signaling is temporarily unavailable' });
|
|
38
|
+
return;
|
|
64
39
|
}
|
|
65
40
|
const now = new Date();
|
|
66
41
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeSignalHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/EdgeNodeSignalHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { isNodeAuth, getNodeId, getWebId } from '../auth/AuthContext';\n\nexport interface EdgeNodeSignalHandlerOptions {\n repository: EdgeNodeRepository;\n dnsCoordinator?: EdgeNodeDnsCoordinator;\n healthProbeService?: EdgeNodeHealthProbeService;\n}\n\n/**\n * Handler for edge node signaling API\n *\n * POST /v1/signal - Edge node heartbeat/signal\n *\n * Requires API authentication and a nodeId in the request body.\n */\nexport function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void {\n const logger = getLoggerFor('EdgeNodeSignalHandler');\n const repo = options.repository;\n const { dnsCoordinator, healthProbeService } = options;\n\n // POST /v1/signal - authenticated via nodeToken, API key, or Solid token\n server.post('/v1/signal', async (request, response, _params) => {\n const auth = request.auth;\n if (!auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n const payload = body as Record<string, unknown>;\n\n // Resolve nodeId based on auth type\n let nodeId: string;\n\n if (isNodeAuth(auth)) {\n // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查\n nodeId = getNodeId(auth)!;\n } else {\n // WebID 认证:从 body 读 nodeId,检查 owner\n const webId = getWebId(auth);\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n nodeId = typeof payload.nodeId === 'string' ? payload.nodeId.trim() : '';\n if (!nodeId) {\n sendJson(response, 400, { error: 'nodeId is required' });\n return;\n }\n try {\n const owner = await repo.getNodeOwner(nodeId);\n if (!owner) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n if (owner !== webId) {\n sendJson(response, 403, { error: 'Access denied' });\n return;\n }\n } catch (error) {\n logger.error(`Failed to validate node access: ${error}`);\n sendJson(response, 500, { error: 'Failed to validate node access' });\n return;\n }\n }\n\n const now = new Date();\n\n try {\n // Get current metadata to merge\n const existing = await repo.getNodeMetadata(nodeId);\n let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);\n\n // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用\n const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);\n if (connectivityInfo) {\n if (connectivityInfo.subdomain && !metadata.subdomain) {\n metadata.subdomain = connectivityInfo.subdomain;\n }\n if (connectivityInfo.ipv4 && !metadata.ipv4) {\n metadata.ipv4 = connectivityInfo.ipv4;\n }\n if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {\n metadata.connectivityStatus = connectivityInfo.connectivityStatus;\n }\n }\n\n // Update heartbeat\n await repo.updateNodeHeartbeat(nodeId, metadata, now);\n\n // Update pods if provided\n if (Array.isArray(payload.pods)) {\n await repo.replaceNodePods(nodeId, payload.pods as string[]);\n }\n\n // 健康检查 → DNS 同步\n if (healthProbeService) {\n await healthProbeService.probeNode(nodeId);\n // 健康检查结果写入了 DB metadata,重新读取\n const freshMeta = await repo.getNodeMetadata(nodeId);\n if (freshMeta?.metadata) {\n const reachability = (freshMeta.metadata as Record<string, unknown>).reachability;\n if (reachability) {\n metadata.reachability = reachability;\n const status = (reachability as Record<string, unknown>).status;\n if (typeof status === 'string') {\n metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';\n }\n }\n }\n }\n\n if (dnsCoordinator) {\n await dnsCoordinator.synchronize(nodeId, metadata);\n }\n\n logger.debug(`Signal received from node ${nodeId}`);\n\n sendJson(response, 200, {\n status: 'ok',\n nodeId,\n lastSeen: now.toISOString(),\n metadata,\n });\n } catch (error) {\n logger.error(`Signal handling error for node ${nodeId}:`, error);\n if (error instanceof Error) {\n logger.error(`Stack trace: ${error.stack}`);\n }\n sendJson(response, 500, { error: 'Failed to process signal' });\n }\n });\n}\n\nfunction mergeMetadata(\n previous: Record<string, unknown>,\n payload: Record<string, unknown>,\n now: Date,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...previous };\n next.lastHeartbeatAt = now.toISOString();\n\n const copyIfPresent = (key: string) => {\n if (payload[key] !== undefined) {\n next[key] = payload[key];\n }\n };\n\n copyIfPresent('baseUrl');\n copyIfPresent('publicAddress');\n copyIfPresent('hostname');\n copyIfPresent('ipv4');\n copyIfPresent('ipv6');\n copyIfPresent('version');\n copyIfPresent('status');\n copyIfPresent('capabilities');\n copyIfPresent('metrics');\n\n return next;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeSignalHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/EdgeNodeSignalHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { isNodeAuth, getNodeId } from '../auth/AuthContext';\n\nexport interface EdgeNodeSignalHandlerOptions {\n repository: EdgeNodeRepository;\n dnsCoordinator?: EdgeNodeDnsCoordinator;\n healthProbeService?: EdgeNodeHealthProbeService;\n}\n\n/**\n * Handler for edge node signaling API\n *\n * POST /v1/signal - Edge node heartbeat/signal\n *\n * Requires API authentication and a nodeId in the request body.\n */\nexport function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void {\n const logger = getLoggerFor('EdgeNodeSignalHandler');\n const repo = options.repository;\n const { dnsCoordinator, healthProbeService } = options;\n\n // POST /v1/signal - authenticated via nodeToken, API key, or Solid token\n server.post('/v1/signal', async (request, response, _params) => {\n const auth = request.auth;\n if (!auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n const payload = body as Record<string, unknown>;\n\n // Resolve nodeId based on auth type\n let nodeId: string;\n\n if (isNodeAuth(auth)) {\n // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查\n nodeId = getNodeId(auth)!;\n } else {\n sendJson(response, 501, { error: 'WebID/API-key based node signaling is temporarily unavailable' });\n return;\n }\n\n const now = new Date();\n\n try {\n // Get current metadata to merge\n const existing = await repo.getNodeMetadata(nodeId);\n let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);\n\n // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用\n const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);\n if (connectivityInfo) {\n if (connectivityInfo.subdomain && !metadata.subdomain) {\n metadata.subdomain = connectivityInfo.subdomain;\n }\n if (connectivityInfo.ipv4 && !metadata.ipv4) {\n metadata.ipv4 = connectivityInfo.ipv4;\n }\n if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {\n metadata.connectivityStatus = connectivityInfo.connectivityStatus;\n }\n }\n\n // Update heartbeat\n await repo.updateNodeHeartbeat(nodeId, metadata, now);\n\n // Update pods if provided\n if (Array.isArray(payload.pods)) {\n await repo.replaceNodePods(nodeId, payload.pods as string[]);\n }\n\n // 健康检查 → DNS 同步\n if (healthProbeService) {\n await healthProbeService.probeNode(nodeId);\n // 健康检查结果写入了 DB metadata,重新读取\n const freshMeta = await repo.getNodeMetadata(nodeId);\n if (freshMeta?.metadata) {\n const reachability = (freshMeta.metadata as Record<string, unknown>).reachability;\n if (reachability) {\n metadata.reachability = reachability;\n const status = (reachability as Record<string, unknown>).status;\n if (typeof status === 'string') {\n metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';\n }\n }\n }\n }\n\n if (dnsCoordinator) {\n await dnsCoordinator.synchronize(nodeId, metadata);\n }\n\n logger.debug(`Signal received from node ${nodeId}`);\n\n sendJson(response, 200, {\n status: 'ok',\n nodeId,\n lastSeen: now.toISOString(),\n metadata,\n });\n } catch (error) {\n logger.error(`Signal handling error for node ${nodeId}:`, error);\n if (error instanceof Error) {\n logger.error(`Stack trace: ${error.stack}`);\n }\n sendJson(response, 500, { error: 'Failed to process signal' });\n }\n });\n}\n\nfunction mergeMetadata(\n previous: Record<string, unknown>,\n payload: Record<string, unknown>,\n now: Date,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...previous };\n next.lastHeartbeatAt = now.toISOString();\n\n const copyIfPresent = (key: string) => {\n if (payload[key] !== undefined) {\n next[key] = payload[key];\n }\n };\n\n copyIfPresent('baseUrl');\n copyIfPresent('publicAddress');\n copyIfPresent('hostname');\n copyIfPresent('ipv4');\n copyIfPresent('ipv6');\n copyIfPresent('version');\n copyIfPresent('status');\n copyIfPresent('capabilities');\n copyIfPresent('metrics');\n\n return next;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerNodeRoutes = registerNodeRoutes;
|
|
4
4
|
const global_logger_factory_1 = require("global-logger-factory");
|
|
5
|
-
const AuthContext_1 = require("../auth/AuthContext");
|
|
6
5
|
/**
|
|
7
6
|
* Handler for edge node management API
|
|
8
7
|
*
|
|
@@ -15,171 +14,31 @@ const AuthContext_1 = require("../auth/AuthContext");
|
|
|
15
14
|
*/
|
|
16
15
|
function registerNodeRoutes(server, options) {
|
|
17
16
|
const logger = (0, global_logger_factory_1.getLoggerFor)('NodeHandler');
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
17
|
+
void options;
|
|
18
|
+
const sendUnavailable = (response) => {
|
|
19
|
+
sendJson(response, 501, {
|
|
20
|
+
error: 'Node management API is temporarily unavailable',
|
|
21
|
+
});
|
|
26
22
|
};
|
|
27
23
|
// GET /v1/nodes - List user's nodes
|
|
28
|
-
server.get('/v1/nodes', async (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
const auth = request.auth;
|
|
33
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
34
|
-
if (!webId) {
|
|
35
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
// Use webId as owner identifier
|
|
40
|
-
const nodes = await repo.listNodesByAccount(webId);
|
|
41
|
-
sendJson(response, 200, {
|
|
42
|
-
nodes: nodes.map(formatNodeInfo),
|
|
43
|
-
total: nodes.length,
|
|
44
|
-
timestamp: new Date().toISOString(),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
logger.error(`Failed to list nodes: ${error}`);
|
|
49
|
-
sendJson(response, 500, { error: 'Failed to list nodes' });
|
|
50
|
-
}
|
|
24
|
+
server.get('/v1/nodes', async (_request, response, _params) => {
|
|
25
|
+
logger.info('Node management API is temporarily unavailable: GET /v1/nodes');
|
|
26
|
+
sendUnavailable(response);
|
|
51
27
|
});
|
|
52
28
|
// POST /api/nodes - Create a new node
|
|
53
|
-
server.post('/v1/nodes', async (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const auth = request.auth;
|
|
58
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
59
|
-
console.log(`[NodeHandler] POST /v1/nodes - auth: ${JSON.stringify(auth)}, webId: ${webId}`);
|
|
60
|
-
if (!webId) {
|
|
61
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const body = await readJsonBody(request);
|
|
65
|
-
const displayName = body && typeof body === 'object'
|
|
66
|
-
? body.displayName
|
|
67
|
-
: undefined;
|
|
68
|
-
try {
|
|
69
|
-
// Use webId as owner identifier
|
|
70
|
-
const result = await repo.createNode(displayName, webId);
|
|
71
|
-
logger.info(`Created node ${result.nodeId} for user ${webId}`);
|
|
72
|
-
sendJson(response, 201, {
|
|
73
|
-
success: true,
|
|
74
|
-
nodeId: result.nodeId,
|
|
75
|
-
token: result.token,
|
|
76
|
-
displayName,
|
|
77
|
-
createdAt: result.createdAt,
|
|
78
|
-
message: 'Edge node created. Save the token - it will not be shown again.',
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logger.error(`Failed to create node: ${error}`);
|
|
83
|
-
sendJson(response, 500, { error: 'Failed to create node' });
|
|
84
|
-
}
|
|
29
|
+
server.post('/v1/nodes', async (_request, response, _params) => {
|
|
30
|
+
logger.info('Node management API is temporarily unavailable: POST /v1/nodes');
|
|
31
|
+
sendUnavailable(response);
|
|
85
32
|
});
|
|
86
33
|
// GET /api/nodes/:nodeId - Get node info
|
|
87
|
-
server.get('/v1/nodes/:nodeId', async (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
const auth = request.auth;
|
|
92
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
93
|
-
const nodeId = decodeURIComponent(params.nodeId);
|
|
94
|
-
if (!webId) {
|
|
95
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
// Check ownership
|
|
100
|
-
const nodeOwner = await repo.getNodeOwner(nodeId);
|
|
101
|
-
if (nodeOwner !== webId) {
|
|
102
|
-
sendJson(response, 403, { error: 'Access denied' });
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const nodeInfo = await repo.getNodeCapabilities(nodeId);
|
|
106
|
-
if (!nodeInfo) {
|
|
107
|
-
sendJson(response, 404, { error: 'Node not found' });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
sendJson(response, 200, formatNodeInfo(nodeInfo));
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
logger.error(`Failed to get node: ${error}`);
|
|
114
|
-
sendJson(response, 500, { error: 'Failed to get node info' });
|
|
115
|
-
}
|
|
34
|
+
server.get('/v1/nodes/:nodeId', async (_request, response, _params) => {
|
|
35
|
+
logger.info('Node management API is temporarily unavailable: GET /v1/nodes/:nodeId');
|
|
36
|
+
sendUnavailable(response);
|
|
116
37
|
});
|
|
117
38
|
// DELETE /api/nodes/:nodeId - Delete a node
|
|
118
|
-
server.delete('/v1/nodes/:nodeId', async (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
const auth = request.auth;
|
|
123
|
-
const webId = (0, AuthContext_1.getWebId)(auth);
|
|
124
|
-
const nodeId = decodeURIComponent(params.nodeId);
|
|
125
|
-
if (!webId) {
|
|
126
|
-
sendJson(response, 400, { error: 'Cannot determine user' });
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
// Check ownership
|
|
131
|
-
const nodeOwner = await repo.getNodeOwner(nodeId);
|
|
132
|
-
if (nodeOwner !== webId) {
|
|
133
|
-
sendJson(response, 403, { error: 'Access denied' });
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const deleted = await repo.deleteNode(nodeId);
|
|
137
|
-
if (!deleted) {
|
|
138
|
-
sendJson(response, 404, { error: 'Node not found' });
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
logger.info(`Deleted node ${nodeId}`);
|
|
142
|
-
sendJson(response, 200, { status: 'deleted', nodeId });
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
logger.error(`Failed to delete node: ${error}`);
|
|
146
|
-
sendJson(response, 500, { error: 'Failed to delete node' });
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
function formatNodeInfo(node) {
|
|
151
|
-
return {
|
|
152
|
-
nodeId: node.nodeId,
|
|
153
|
-
displayName: node.displayName,
|
|
154
|
-
accessMode: node.accessMode,
|
|
155
|
-
connectivityStatus: node.connectivityStatus,
|
|
156
|
-
lastSeen: node.lastSeen?.toISOString?.() ?? node.lastSeen,
|
|
157
|
-
capabilities: {
|
|
158
|
-
structured: node.capabilities,
|
|
159
|
-
strings: node.stringCapabilities,
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
async function readJsonBody(request) {
|
|
164
|
-
return new Promise((resolve, reject) => {
|
|
165
|
-
let data = '';
|
|
166
|
-
request.setEncoding('utf8');
|
|
167
|
-
request.on('data', (chunk) => {
|
|
168
|
-
data += chunk;
|
|
169
|
-
});
|
|
170
|
-
request.on('end', () => {
|
|
171
|
-
if (!data) {
|
|
172
|
-
resolve(undefined);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
resolve(JSON.parse(data));
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
resolve(undefined);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
request.on('error', reject);
|
|
39
|
+
server.delete('/v1/nodes/:nodeId', async (_request, response, _params) => {
|
|
40
|
+
logger.info('Node management API is temporarily unavailable: DELETE /v1/nodes/:nodeId');
|
|
41
|
+
sendUnavailable(response);
|
|
183
42
|
});
|
|
184
43
|
}
|
|
185
44
|
function sendJson(response, status, data) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/NodeHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { getWebId, isSolidAuth } from '../auth/AuthContext';\n\nexport interface NodeHandlerOptions {\n repository: EdgeNodeRepository;\n}\n\n/**\n * Handler for edge node management API\n *\n * GET /v1/nodes - List user's nodes\n * POST /v1/nodes - Create a new node\n * GET /v1/nodes/:nodeId - Get node info\n * DELETE /v1/nodes/:nodeId - Delete a node\n *\n * All endpoints require Solid Token (user must have a Pod)\n */\nexport function registerNodeRoutes(server: ApiServer, options: NodeHandlerOptions): void {\n const logger = getLoggerFor('NodeHandler');\n const repo = options.repository;\n\n const rejectApiKey = (request: AuthenticatedRequest, response: ServerResponse): boolean => {\n const auth = request.auth;\n if (auth && isSolidAuth(auth) && auth.viaApiKey) {\n sendJson(response, 403, { error: 'API key is not allowed for this endpoint' });\n return true;\n }\n return false;\n };\n\n // GET /v1/nodes - List user's nodes\n server.get('/v1/nodes', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Use webId as owner identifier\n const nodes = await repo.listNodesByAccount(webId);\n\n sendJson(response, 200, {\n nodes: nodes.map(formatNodeInfo),\n total: nodes.length,\n timestamp: new Date().toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to list nodes: ${error}`);\n sendJson(response, 500, { error: 'Failed to list nodes' });\n }\n });\n\n // POST /api/nodes - Create a new node\n server.post('/v1/nodes', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n console.log(`[NodeHandler] POST /v1/nodes - auth: ${JSON.stringify(auth)}, webId: ${webId}`);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n const body = await readJsonBody(request);\n const displayName = body && typeof body === 'object'\n ? (body as Record<string, unknown>).displayName as string | undefined\n : undefined;\n\n try {\n // Use webId as owner identifier\n const result = await repo.createNode(displayName, webId);\n\n logger.info(`Created node ${result.nodeId} for user ${webId}`);\n\n sendJson(response, 201, {\n success: true,\n nodeId: result.nodeId,\n token: result.token,\n displayName,\n createdAt: result.createdAt,\n message: 'Edge node created. Save the token - it will not be shown again.',\n });\n } catch (error) {\n logger.error(`Failed to create node: ${error}`);\n sendJson(response, 500, { error: 'Failed to create node' });\n }\n });\n\n // GET /api/nodes/:nodeId - Get node info\n server.get('/v1/nodes/:nodeId', async (request, response, params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n const nodeId = decodeURIComponent(params.nodeId);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Check ownership\n const nodeOwner = await repo.getNodeOwner(nodeId);\n if (nodeOwner !== webId) {\n sendJson(response, 403, { error: 'Access denied' });\n return;\n }\n\n const nodeInfo = await repo.getNodeCapabilities(nodeId);\n if (!nodeInfo) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n\n sendJson(response, 200, formatNodeInfo(nodeInfo));\n } catch (error) {\n logger.error(`Failed to get node: ${error}`);\n sendJson(response, 500, { error: 'Failed to get node info' });\n }\n });\n\n // DELETE /api/nodes/:nodeId - Delete a node\n server.delete('/v1/nodes/:nodeId', async (request, response, params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n const nodeId = decodeURIComponent(params.nodeId);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Check ownership\n const nodeOwner = await repo.getNodeOwner(nodeId);\n if (nodeOwner !== webId) {\n sendJson(response, 403, { error: 'Access denied' });\n return;\n }\n\n const deleted = await repo.deleteNode(nodeId);\n if (!deleted) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n\n logger.info(`Deleted node ${nodeId}`);\n sendJson(response, 200, { status: 'deleted', nodeId });\n } catch (error) {\n logger.error(`Failed to delete node: ${error}`);\n sendJson(response, 500, { error: 'Failed to delete node' });\n }\n });\n}\n\nfunction formatNodeInfo(node: any): Record<string, unknown> {\n return {\n nodeId: node.nodeId,\n displayName: node.displayName,\n accessMode: node.accessMode,\n connectivityStatus: node.connectivityStatus,\n lastSeen: node.lastSeen?.toISOString?.() ?? node.lastSeen,\n capabilities: {\n structured: node.capabilities,\n strings: node.stringCapabilities,\n },\n };\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"NodeHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/NodeHandler.ts"],"names":[],"mappings":";;AAmBA,gDAiCC;AAnDD,iEAAqD;AAQrD;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,MAAiB,EAAE,OAA2B;IAC/E,MAAM,MAAM,GAAG,IAAA,oCAAY,EAAC,aAAa,CAAC,CAAC;IAC3C,KAAK,OAAO,CAAC;IAEb,MAAM,eAAe,GAAG,CAAC,QAAwB,EAAQ,EAAE;QACzD,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;YACtB,KAAK,EAAE,gDAAgD;SACxD,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,oCAAoC;IACpC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC5D,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;QAC7E,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC7D,MAAM,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC9E,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,yCAAyC;IACzC,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QACpE,MAAM,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC;QACrF,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QACvE,MAAM,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;QACxF,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa;IACvE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\n\nexport interface NodeHandlerOptions {\n repository: EdgeNodeRepository;\n}\n\n/**\n * Handler for edge node management API\n *\n * GET /v1/nodes - List user's nodes\n * POST /v1/nodes - Create a new node\n * GET /v1/nodes/:nodeId - Get node info\n * DELETE /v1/nodes/:nodeId - Delete a node\n *\n * All endpoints require Solid Token (user must have a Pod)\n */\nexport function registerNodeRoutes(server: ApiServer, options: NodeHandlerOptions): void {\n const logger = getLoggerFor('NodeHandler');\n void options;\n\n const sendUnavailable = (response: ServerResponse): void => {\n sendJson(response, 501, {\n error: 'Node management API is temporarily unavailable',\n });\n };\n\n // GET /v1/nodes - List user's nodes\n server.get('/v1/nodes', async (_request, response, _params) => {\n logger.info('Node management API is temporarily unavailable: GET /v1/nodes');\n sendUnavailable(response);\n });\n\n // POST /api/nodes - Create a new node\n server.post('/v1/nodes', async (_request, response, _params) => {\n logger.info('Node management API is temporarily unavailable: POST /v1/nodes');\n sendUnavailable(response);\n });\n\n // GET /api/nodes/:nodeId - Get node info\n server.get('/v1/nodes/:nodeId', async (_request, response, _params) => {\n logger.info('Node management API is temporarily unavailable: GET /v1/nodes/:nodeId');\n sendUnavailable(response);\n });\n\n // DELETE /api/nodes/:nodeId - Delete a node\n server.delete('/v1/nodes/:nodeId', async (_request, response, _params) => {\n logger.info('Node management API is temporarily unavailable: DELETE /v1/nodes/:nodeId');\n sendUnavailable(response);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
|
|
@@ -8,6 +8,7 @@ const undici_1 = require("undici");
|
|
|
8
8
|
const AuthContext_1 = require("../auth/AuthContext");
|
|
9
9
|
const types_1 = require("../../credential/schema/types");
|
|
10
10
|
const provider_registry_1 = require("./provider-registry");
|
|
11
|
+
const platform_ai_config_1 = require("./platform-ai-config");
|
|
11
12
|
// Create a proxy-aware fetch function
|
|
12
13
|
function createProxyFetch(proxyUrl) {
|
|
13
14
|
const agent = new undici_1.ProxyAgent(proxyUrl);
|
|
@@ -38,21 +39,13 @@ class VercelChatService {
|
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
getAiGatewayBaseUrl() {
|
|
41
|
-
|
|
42
|
-
return raw ? raw.replace(/\/$/, '') : null;
|
|
42
|
+
return (0, platform_ai_config_1.getAiGatewayBaseUrl)() ?? null;
|
|
43
43
|
}
|
|
44
44
|
getAiGatewayTimeoutMs() {
|
|
45
|
-
|
|
46
|
-
if (!raw) {
|
|
47
|
-
return 30_000;
|
|
48
|
-
}
|
|
49
|
-
const parsed = Number(raw);
|
|
50
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 30_000;
|
|
45
|
+
return (0, platform_ai_config_1.getPlatformTimeoutMs)();
|
|
51
46
|
}
|
|
52
47
|
getAiGatewayApiKey() {
|
|
53
|
-
|
|
54
|
-
?? process.env.AI_GATEWAY_SERVICE_API_KEY?.trim();
|
|
55
|
-
return raw || null;
|
|
48
|
+
return (0, platform_ai_config_1.getAiGatewayApiKey)() ?? null;
|
|
56
49
|
}
|
|
57
50
|
toModelId(model) {
|
|
58
51
|
return typeof model?.id === 'string' ? model.id : JSON.stringify(model);
|
|
@@ -110,7 +103,7 @@ class VercelChatService {
|
|
|
110
103
|
buildAiGatewayUrl(path) {
|
|
111
104
|
const baseUrl = this.getAiGatewayBaseUrl();
|
|
112
105
|
if (!baseUrl) {
|
|
113
|
-
throw new Error('
|
|
106
|
+
throw new Error('DEFAULT_API_BASE is not configured');
|
|
114
107
|
}
|
|
115
108
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
116
109
|
if (baseUrl.endsWith('/v1') && normalizedPath.startsWith('/v1/')) {
|
|
@@ -127,7 +120,7 @@ class VercelChatService {
|
|
|
127
120
|
async sendAiGatewayRequest(path, method, body, headers) {
|
|
128
121
|
const apiKey = this.getAiGatewayApiKey();
|
|
129
122
|
if (!apiKey) {
|
|
130
|
-
throw new Error('
|
|
123
|
+
throw new Error('DEFAULT_API_KEY is not configured');
|
|
131
124
|
}
|
|
132
125
|
const requestHeaders = new Headers(headers);
|
|
133
126
|
requestHeaders.set('Authorization', `Bearer ${apiKey}`);
|
|
@@ -142,8 +135,8 @@ class VercelChatService {
|
|
|
142
135
|
});
|
|
143
136
|
if (!response.ok) {
|
|
144
137
|
const errorText = await response.text().catch(() => '');
|
|
145
|
-
this.logger.warn(`AI
|
|
146
|
-
const error = new Error(`AI
|
|
138
|
+
this.logger.warn(`Platform AI request failed: ${response.status} ${errorText}`);
|
|
139
|
+
const error = new Error(`Platform AI error: ${response.status} ${response.statusText}`);
|
|
147
140
|
error.status = response.status;
|
|
148
141
|
error.headers = response.headers;
|
|
149
142
|
error.body = errorText;
|
|
@@ -285,10 +278,10 @@ class VercelChatService {
|
|
|
285
278
|
return { baseURL, apiKey: config.apiKey, proxy, credentialId: config.credentialId };
|
|
286
279
|
}
|
|
287
280
|
// 平台 Provider
|
|
288
|
-
const platformBase =
|
|
281
|
+
const platformBase = (0, platform_ai_config_1.getPlatformApiBaseUrl)();
|
|
289
282
|
if (platformBase) {
|
|
290
283
|
this.logger.info(`Provider config: baseURL=${platformBase}, proxy=none (source=platform)`);
|
|
291
|
-
return { baseURL: platformBase, apiKey:
|
|
284
|
+
return { baseURL: platformBase, apiKey: (0, platform_ai_config_1.getPlatformApiKey)(), proxy: undefined, credentialId: undefined };
|
|
292
285
|
}
|
|
293
286
|
this.logger.warn('No AI provider config found in Pod or DEFAULT_API_BASE');
|
|
294
287
|
return null;
|
|
@@ -530,7 +523,7 @@ class VercelChatService {
|
|
|
530
523
|
}
|
|
531
524
|
async responsesViaCompletions(body, context, providerConfig) {
|
|
532
525
|
const prompt = this.extractPromptFromResponsesBody(body);
|
|
533
|
-
const model = body?.model ||
|
|
526
|
+
const model = body?.model || (0, platform_ai_config_1.getPlatformDefaultModel)();
|
|
534
527
|
const provider = await this.getProvider(context);
|
|
535
528
|
const result = await (0, ai_1.generateText)({
|
|
536
529
|
model: provider.chat(model),
|
|
@@ -563,7 +556,7 @@ class VercelChatService {
|
|
|
563
556
|
}
|
|
564
557
|
async messagesViaCompletions(body, context, providerConfig) {
|
|
565
558
|
const prompt = this.extractPromptFromMessagesBody(body);
|
|
566
|
-
const model = body?.model ||
|
|
559
|
+
const model = body?.model || (0, platform_ai_config_1.getPlatformDefaultModel)();
|
|
567
560
|
const coreMessages = [];
|
|
568
561
|
if (body?.system) {
|
|
569
562
|
const systemText = typeof body.system === 'string'
|
|
@@ -689,11 +682,18 @@ class VercelChatService {
|
|
|
689
682
|
pushModels(aiGatewayCache.items);
|
|
690
683
|
}
|
|
691
684
|
// 平台 Provider 模型(从 DEFAULT_API_BASE 获取)
|
|
692
|
-
const platformBase =
|
|
693
|
-
const platformKey =
|
|
694
|
-
|
|
685
|
+
const platformBase = (0, platform_ai_config_1.getPlatformApiBaseUrl)();
|
|
686
|
+
const platformKey = (0, platform_ai_config_1.getPlatformApiKey)();
|
|
687
|
+
const aiGatewayBase = this.getAiGatewayBaseUrl();
|
|
688
|
+
const normalizedAiGatewayModelsUrl = aiGatewayBase
|
|
689
|
+
? this.buildAiGatewayUrl('/v1/models')
|
|
690
|
+
: undefined;
|
|
691
|
+
const normalizedPlatformModelsUrl = platformBase
|
|
692
|
+
? `${platformBase.replace(/\/$/, '')}/models`
|
|
693
|
+
: undefined;
|
|
694
|
+
if (platformBase && normalizedPlatformModelsUrl !== normalizedAiGatewayModelsUrl) {
|
|
695
695
|
try {
|
|
696
|
-
const url =
|
|
696
|
+
const url = normalizedPlatformModelsUrl;
|
|
697
697
|
const headers = { 'Content-Type': 'application/json' };
|
|
698
698
|
if (platformKey) {
|
|
699
699
|
headers['Authorization'] = `Bearer ${platformKey}`;
|