import { IncomingMessage, ServerResponse } from 'http'; import { wsProxyManager } from './index.ts'; import { App } from '@kevisual/router'; import { logger } from '../logger.ts'; import { getLoginUser } from '@/modules/auth.ts'; import { createStudioAppListHtml } from '../html/studio-app-list/index.ts'; import { omit } from 'es-toolkit'; import { baseProxyUrl, proxyDomain } from '../domain.ts'; import { renderServerHtml } from '../html/render-server-html.ts'; type ProxyOptions = { createNotFoundPage: (msg?: string) => any; }; export const UserV1Proxy = async (req: IncomingMessage, res: ServerResponse, opts?: ProxyOptions) => { const { url, method } = req; const _url = new URL(url || '', `http://localhost`); const { pathname, searchParams } = _url; const isGet = method === 'GET'; let [user, app, userAppKey] = pathname.split('/').slice(1); if (!user || !app) { opts?.createNotFoundPage?.('应用未找到'); return false; } const data = await App.handleRequest(req, res); const loginUser = await getLoginUser(req); if (!loginUser) { opts?.createNotFoundPage?.('没有登录'); return false; } const isAdmin = loginUser.tokenUser?.username === user if (!userAppKey) { if (isAdmin) { return handleRequest(req, res, { user, app, userAppKey, isAdmin }); } else { opts?.createNotFoundPage?.('应用访问失败'); return false; } } if (!userAppKey.includes('--')) { userAppKey = user + '--' + userAppKey; } // TODO: 如果不是管理员,是否需要添加其他人可以访问的逻辑? if (!isAdmin) { opts?.createNotFoundPage?.('没有访问应用权限'); return false; } if (!userAppKey.startsWith(user + '--')) { userAppKey = user + '--' + userAppKey; } logger.debug('data', data); const client = wsProxyManager.get(userAppKey); const { ids, infoList } = wsProxyManager.getIdsInfo(user + '--'); if (!client) { if (isGet) { if (isAdmin) { // const html = createStudioAppListHtml({ user, appIds: ids, userAppKey, infoList }); // res.writeHead(200, { 'Content-Type': 'text/html' }); // res.end(html); // https://kevisual.cn/root/v1-manager/ const html = await fetch(`${baseProxyUrl}/root/v1-manager/index.html`).then(res => res.text()); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(renderServerHtml({ user, appIds: ids, userAppKey, infoList }, html)); return true; } else { opts?.createNotFoundPage?.('应用访问失败'); } } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: '应用访问失败' })); } return false; } const path = searchParams.get('path'); if (!path) { // 显示前端页面 const html = await fetch(`${baseProxyUrl}/root/router-studio/index.html`).then(res => res.text()); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); return true; } let message: any = data; if (!isAdmin) { message = omit(data, ['token', 'cookies']); } if (client.status === 'waiting') { res.writeHead(603, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: '应用没有鉴权' })); return true; } const value = await client.sendData(message, { state: { tokenUser: omit(loginUser.tokenUser, ['oauthExpand']) }, }); if (value) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value)); return true; } opts?.createNotFoundPage?.('应用未启动'); return true; }; const handleRequest = async (req: IncomingMessage, res: ServerResponse, opts?: { user?: string, app?: string, userAppKey?: string, isAdmin?: boolean }) => { const { user, userAppKey } = opts || {}; const isGet = req.method === 'GET'; // 获取所有的管理员的应用列表 const { ids, infoList } = wsProxyManager.getIdsInfo(user + '--'); if (isGet) { const html = await fetch(`${baseProxyUrl}/root/v1-manager/index.html`).then(res => res.text()); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); return; } else { const url = new URL(req.url || '', 'http://localhost'); const path = url.searchParams.get('path'); if (path) { const appId = url.searchParams.get('appId') || ''; const client = wsProxyManager.get(appId!)!; if (!client) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 404, message: '应用未找到' })); return; } if (path === 'connected') { client.sendConnected(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 200, message: '应用已连接' })); return; } if (path === 'rename') { const newId = url.searchParams.get('newId') || ''; if (!newId) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 400, message: 'newId 参数缺失' })); return; } const realNewId = user + '--' + newId; const wsMessage = wsProxyManager.get(realNewId!)!; if (wsMessage) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 400, message: 'newId 已存在' })); return; } wsProxyManager.changeId(appId, realNewId); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 200, message: '应用重命名成功' })); return; } } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 200, data: { ids, infoList } })); return; } }