fix: 更新依赖项版本并优化远程应用连接逻辑
This commit is contained in:
@@ -91,7 +91,6 @@ export type AssistantConfigData = {
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
token?: string;
|
||||
registry?: string; // https://kevisual.cn
|
||||
/**
|
||||
* 前端代理,比如/root/home 转到https://kevisual.cn/root/home
|
||||
|
||||
@@ -7,12 +7,13 @@ import glob from 'fast-glob';
|
||||
import type { App } from '@kevisual/router';
|
||||
import { RemoteApp } from '@/module/remote-app/remote-app.ts';
|
||||
import { logger } from '@/module/logger.ts';
|
||||
import { getEnvToken } from '@/module/http-token.ts';
|
||||
import { initApi } from '@kevisual/api/proxy'
|
||||
import { Query } from '@kevisual/query';
|
||||
import { initLightCode } from '@/module/light-code/index.ts';
|
||||
import { ModuleResolver } from './assistant-app-resolve.ts';
|
||||
import z from 'zod';
|
||||
import { assistantQuery } from '@/app.ts';
|
||||
import { useKey } from '@kevisual/context';
|
||||
export class AssistantApp extends Manager {
|
||||
config: AssistantConfig;
|
||||
pagesPath: string;
|
||||
@@ -23,8 +24,8 @@ export class AssistantApp extends Manager {
|
||||
|
||||
constructor(config: AssistantConfig, mainApp?: App) {
|
||||
config.checkMounted();
|
||||
const appsPath = config?.configPath?.appsDir || path.join(process.cwd(), 'apps');
|
||||
const pagesPath = config?.configPath?.pagesDir || path.join(process.cwd(), 'pages');
|
||||
const appsPath = config?.configPath?.appsDir
|
||||
const pagesPath = config?.configPath?.pagesDir;
|
||||
const appsConfigPath = config.configPath?.appsConfigPath;
|
||||
const configFimename = path.basename(appsConfigPath || '');
|
||||
super({
|
||||
@@ -80,6 +81,12 @@ export class AssistantApp extends Manager {
|
||||
return pagesParse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化远程应用连接,如果配置了远程应用且启用,则尝试连接远程应用服务器,并设置自动重连机制.
|
||||
* 应用暴露
|
||||
* @info 需要登录权限
|
||||
* @param opts
|
||||
*/
|
||||
async initRemoteApp(opts?: { token?: string, enabled?: boolean }) {
|
||||
const config = this.config.getConfig();
|
||||
const share = config?.share;
|
||||
@@ -90,8 +97,17 @@ export class AssistantApp extends Manager {
|
||||
this.remoteApp = null;
|
||||
this.remoteIsConnected = false;
|
||||
}
|
||||
const token = config?.token || opts?.token || getEnvToken() as string;
|
||||
const url = new URL(share.url || 'https://kevisual.cn/ws/proxy');
|
||||
let token = opts?.token;
|
||||
if (!token) {
|
||||
token = await assistantQuery.queryLogin.getToken();
|
||||
}
|
||||
let shareUrl = share?.url;
|
||||
if (!shareUrl) {
|
||||
const _url = new URL(config?.registry || 'https://kevisual.cn/');
|
||||
_url.pathname = '/ws/proxy';
|
||||
shareUrl = _url.toString();
|
||||
}
|
||||
const url = new URL(shareUrl);
|
||||
const id = config?.app?.id;
|
||||
if (token && url && id) {
|
||||
const remoteApp = new RemoteApp({
|
||||
@@ -129,11 +145,22 @@ export class AssistantApp extends Manager {
|
||||
this.remoteApp = remoteApp;
|
||||
} else {
|
||||
if (!token) {
|
||||
logger.error('Token是远程应用连接必须的参数');
|
||||
logger.error('[remote-app] cli当前未登录,无法连接远程app');
|
||||
} else if (!id) {
|
||||
logger.error('[remote-app] app id不存在,无法连接远程app');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 本地路由初始化,根据配置加载应用的模块。
|
||||
* 加载局域网或者某一个链接的远程路由, 或者代码仓库的动态加载的light-code模块(实时同步远程文件执行)
|
||||
* @info 不需要登录
|
||||
* 配置项说明:
|
||||
* - router.proxy: 数组,包含需要代理的路由信息,每项可以是以下两种类型:
|
||||
* - lightcode: 轻代码模块配置
|
||||
* @returns
|
||||
*/
|
||||
async initRouterApp() {
|
||||
const config = this.config.getConfig();
|
||||
const routerProxy = config?.router?.proxy || [];
|
||||
@@ -209,6 +236,10 @@ export class AssistantApp extends Manager {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 动态加载文件插件模块的应用模块
|
||||
* @info 不需要登录
|
||||
*/
|
||||
async initRoutes() {
|
||||
const routes = this.config.getConfig().routes || [];
|
||||
for (const route of routes) {
|
||||
@@ -222,4 +253,53 @@ export class AssistantApp extends Manager {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查本地用户登录状态,如果未登录且存在 CNB_TOKEN,则尝试使用 CNB_TOKEN 登录并更新用户信息
|
||||
*/
|
||||
async checkLocalUser() {
|
||||
const config = this.config.getConfig();
|
||||
const auth = config?.auth;
|
||||
let checkCNB = false;
|
||||
if (!auth?.username) {
|
||||
checkCNB = true;
|
||||
} else {
|
||||
let temp = await assistantQuery.queryLogin.getToken()
|
||||
if (temp) {
|
||||
const isExpired = await assistantQuery.queryLogin.checkTokenValid()
|
||||
console.log('Token 是否过期', isExpired);
|
||||
}
|
||||
logger.info('[assistant] 当前登录用户', auth.username, 'token有效性检查结果', !!temp);
|
||||
}
|
||||
const cnbToken = useKey('CNB_TOKEN');
|
||||
if (!checkCNB && cnbToken) {
|
||||
const res = await assistantQuery.query.post({
|
||||
path: 'user',
|
||||
key: 'cnb-login',
|
||||
payload: {
|
||||
data: {
|
||||
cnbToken: cnbToken,
|
||||
}
|
||||
}
|
||||
});
|
||||
if (res.code === 200) {
|
||||
logger.info('CNB登录成功,用户信息已更新');
|
||||
const resUser = await assistantQuery.queryLogin.beforeSetLoginUser(res.data)
|
||||
if (resUser.code === 200) {
|
||||
const userInfo = resUser.data;
|
||||
auth.username = userInfo.username;
|
||||
auth.share = 'protected'
|
||||
const app = config?.app || {};
|
||||
if (!app?.id) {
|
||||
app.id = 'dev-cnb'
|
||||
}
|
||||
this.config.setConfig({
|
||||
auth,
|
||||
app
|
||||
});
|
||||
} else {
|
||||
console.error('CNB登录失败,无法获取用户信息', resUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,4 +38,4 @@ export class AssistantQuery {
|
||||
getToken() {
|
||||
return this.queryLogin.getToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useKey } from '@kevisual/use-config';
|
||||
import http from 'node:http';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
export const error = (msg: string, code = 500) => {
|
||||
return JSON.stringify({ code, message: msg });
|
||||
};
|
||||
@@ -16,7 +15,12 @@ const cookie = {
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
export const getToken = async (req: http.IncomingMessage) => {
|
||||
/**
|
||||
* 从请求中获取token,优先级:Authorization header > query parameter > cookie
|
||||
* @param req
|
||||
* @returns
|
||||
*/
|
||||
export const getToken = async (req: IncomingMessage) => {
|
||||
let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || '';
|
||||
const url = new URL(req.url || '', 'http://localhost');
|
||||
if (!token) {
|
||||
@@ -31,9 +35,4 @@ export const getToken = async (req: http.IncomingMessage) => {
|
||||
}
|
||||
|
||||
return { token };
|
||||
};
|
||||
|
||||
export const getEnvToken = () => {
|
||||
const envTokne = useKey('KEVISUAL_TOKEN') || '';
|
||||
return envTokne;
|
||||
}
|
||||
};
|
||||
@@ -8,6 +8,7 @@ const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
|
||||
import { filter } from "@kevisual/js-filter";
|
||||
import { getHash, getStringHash } from '../file-hash.ts';
|
||||
import { AssistantConfig } from '@/lib.ts';
|
||||
import { assistantQuery } from '@/app.ts';
|
||||
|
||||
const codeDemo = `// 这是一个示例代码文件
|
||||
import {App} from '@kevisual/router';
|
||||
@@ -48,11 +49,11 @@ export const initLightCode = async (opts: Opts) => {
|
||||
console.log('初始化 light-code 路由');
|
||||
const config = opts.config as AssistantInit;
|
||||
const app = opts.router;
|
||||
const token = config.getConfig()?.token || '';
|
||||
const token = await assistantQuery.getToken();
|
||||
const query = config.query;
|
||||
const sync = opts.sync ?? 'remote';
|
||||
if (!config || !app) {
|
||||
console.error('initLightCode 缺少必要参数, config 或 app');
|
||||
console.error('[light-code] initLightCode 缺少必要参数, config 或 app');
|
||||
return;
|
||||
}
|
||||
const lightcodeDir = opts.rootPath;
|
||||
@@ -126,7 +127,7 @@ export const initLightCode = async (opts: Opts) => {
|
||||
}
|
||||
diffList = findGlob({ cwd: lightcodeDir });
|
||||
} else {
|
||||
console.error('light-code 同步失败', queryRes.message);
|
||||
console.error('[light-code] 同步失败', queryRes.message);
|
||||
diffList = codeFiles;
|
||||
}
|
||||
} else if (sync === 'local') {
|
||||
@@ -191,22 +192,22 @@ export const initLightCode = async (opts: Opts) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('light-code 路由执行失败', runRes.error);
|
||||
console.error('[light-code] 路由执行失败', runRes.error);
|
||||
}
|
||||
}
|
||||
console.log(`light-code 路由注册成功`, `注册${diffList.length}个路由`);
|
||||
console.log(`[light-code] 路由注册成功`, `注册${diffList.length}个路由`);
|
||||
}
|
||||
|
||||
export const clearLightCodeRoutes = (opts: Pick<Opts, 'router'>) => {
|
||||
const app = opts.router;
|
||||
if (!app) {
|
||||
console.error('clearLightCodeRoutes 缺少必要参数, app');
|
||||
console.error('[light-code] clearLightCodeRoutes 缺少必要参数, app');
|
||||
return;
|
||||
}
|
||||
const routes = app.getList();
|
||||
for (const route of routes) {
|
||||
if (route.metadata?.source === 'light-code') {
|
||||
// console.log(`删除 light-code 路由: ${route.path} ${route.id}`);
|
||||
// console.log(`[light-code] 删除 light-code 路由: ${route.path} ${route.id}`);
|
||||
app.removeById(route.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +41,14 @@ app.route({
|
||||
}
|
||||
return;
|
||||
}
|
||||
await manager.initRemoteApp({ enabled: true, token: ctx.query?.token }).then(() => {
|
||||
try {
|
||||
const res = await manager.initRemoteApp({ enabled: true, token: ctx.query?.token })
|
||||
ctx.body = {
|
||||
content: '远程app连接成功',
|
||||
}
|
||||
}).catch((err) => {
|
||||
} catch (error) {
|
||||
ctx.body = {
|
||||
content: `远程app连接失败: ${err.message}`,
|
||||
content: '远程app连接失败',
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}).addTo(app);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { app, assistantConfig } from '../../app.ts';
|
||||
import { forwardCookie } from './utils/cookie.ts';
|
||||
|
||||
app.route({
|
||||
path: 'admin',
|
||||
@@ -23,19 +24,7 @@ app.route({
|
||||
password,
|
||||
}),
|
||||
});
|
||||
|
||||
// 转发上游服务器返回的所有 set-cookie(支持多个 cookie)
|
||||
const setCookieHeaders = res.headers.getSetCookie?.() || [];
|
||||
if (setCookieHeaders.length > 0) {
|
||||
// 设置多个 cookie 到原生 http.ServerResponse
|
||||
ctx.res.setHeader('Set-Cookie', setCookieHeaders);
|
||||
} else {
|
||||
// 兼容旧版本,使用 get 方法
|
||||
const setCookieHeader = res.headers.get('set-cookie');
|
||||
if (setCookieHeader) {
|
||||
ctx.res.setHeader('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
forwardCookie(ctx, res);
|
||||
|
||||
const responseData = await res.json();
|
||||
console.debug('admin login response', { res: responseData });
|
||||
@@ -73,6 +62,9 @@ app.route({
|
||||
key: 'me'
|
||||
}).define(async (ctx) => {
|
||||
const token = ctx.query.token;
|
||||
if (!token) {
|
||||
ctx.throw(401, 'token is required');
|
||||
}
|
||||
const res = await assistantConfig.query.post({
|
||||
path: 'user',
|
||||
key: 'me',
|
||||
|
||||
14
assistant/src/routes/user/utils/cookie.ts
Normal file
14
assistant/src/routes/user/utils/cookie.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const forwardCookie = (ctx: any, res: any) => {
|
||||
// 转发上游服务器返回的所有 set-cookie(支持多个 cookie)
|
||||
const setCookieHeaders = res.headers.getSetCookie?.() || [];
|
||||
if (setCookieHeaders.length > 0) {
|
||||
// 设置多个 cookie 到原生 http.ServerResponse
|
||||
ctx.res.setHeader('Set-Cookie', setCookieHeaders);
|
||||
} else {
|
||||
// 兼容旧版本,使用 get 方法
|
||||
const setCookieHeader = res.headers.get('set-cookie');
|
||||
if (setCookieHeader) {
|
||||
ctx.res.setHeader('Set-Cookie', setCookieHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,14 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
|
||||
qwenAsr,
|
||||
]);
|
||||
const manager = useContextKey('manager', new AssistantApp(assistantConfig, app));
|
||||
setTimeout(() => {
|
||||
manager.load({ runtime: 'client' }).then(() => {
|
||||
console.log('Assistant App Loaded');
|
||||
});
|
||||
manager.initRemoteApp()
|
||||
manager.initRouterApp()
|
||||
setTimeout(async () => {
|
||||
await manager.load({ runtime: 'client' });
|
||||
console.log('Assistant App Loaded');
|
||||
await manager.checkLocalUser()
|
||||
await manager.initRemoteApp();
|
||||
await manager.initRouterApp();
|
||||
if (runtime.isServer) {
|
||||
manager.initRoutes();
|
||||
await manager.initRoutes();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const authFilter = async (req: http.IncomingMessage, res: http.ServerResponse) =
|
||||
return { code: 200, message: '允许访问根路径' };
|
||||
}
|
||||
// 放开首页
|
||||
if (pathname.startsWith('/root/home') || pathname === '/root/cli/docs/') {
|
||||
if (pathname.startsWith('/root/home') || pathname === '/root/cli-center/') {
|
||||
return { code: 200, message: '允许访问首页' };
|
||||
}
|
||||
// 放开api, 以 /api, /v1, /client, /serve 开头的请求
|
||||
@@ -86,7 +86,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
||||
let noAdmin = !auth.username;
|
||||
|
||||
const toSetting = () => {
|
||||
res.writeHead(302, { Location: `/root/cli/setting/` });
|
||||
res.writeHead(302, { Location: `/root/cli-center/` });
|
||||
res.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const main = async () => {
|
||||
const config = assistantConfig?.getConfig();
|
||||
const share = config?.share;
|
||||
if (share && share.enabled !== false) {
|
||||
const token = config?.token;
|
||||
const token = ''
|
||||
const url = new URL(share.url || 'https://kevisual.cn/ws/proxy');
|
||||
const id = config?.app?.id;
|
||||
if (!id) {
|
||||
@@ -14,7 +14,6 @@ const main = async () => {
|
||||
return;
|
||||
}
|
||||
if (!token) {
|
||||
console.error('Token is required for remote app connection.');
|
||||
return;
|
||||
}
|
||||
const remoteApp = new RemoteApp({
|
||||
|
||||
Reference in New Issue
Block a user