159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
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;
|
|
}
|
|
}
|