Files
cli/assistant/src/module/livecode/index.ts
abearxiong 5d6bd4f429 Refactor client routes and add IP fetching functionality
- Moved client route definitions to separate files for better organization.
- Added new route to fetch client IP addresses, supporting both IPv4 and IPv6.
- Implemented system information retrieval in the client routes.
- Updated package dependencies to their latest versions.
- Adjusted call route to prevent overwriting existing routes.
2026-02-04 13:20:12 +08:00

142 lines
4.5 KiB
TypeScript

import { WSSManager } from './wss.ts';
import { App, Route } from '@kevisual/router'
import { WebSocketReq, ListenProcessParams } from '@kevisual/router'
import { EventEmitter } from 'eventemitter3';
import { customAlphabet } from 'nanoid';
const letter = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const customId = customAlphabet(letter, 16);
/**
* 实时注册的代码模块
*
* 别人通过 WebSocket 连接到此模块,发送路由列表和请求数据
*/
export class LiveCode {
wssManager: WSSManager;
app: App;
emitter: EventEmitter;
constructor(app: App) {
this.wssManager = new WSSManager({ heartbeatInterval: 6 * 5000 });
this.app = app;
this.emitter = new EventEmitter();
console.log('[LiveCode] 模块已初始化');
}
async conn(req: WebSocketReq) {
const { ws, emitter, id } = req;
const that = this;
// @ts-ignore
let wid = ws.data?.wid;
if (!wid) {
const _id = this.wssManager.addConnection(req, { userId: id });
// @ts-ignore
ws.data.wid = _id;
emitter.once('close--' + id, () => {
that.wssManager.closeConnection(_id);
this.deinitAppRoutes(_id);
});
console.log('[LiveCode]新的 WebSocket 连接已打开', _id);
const res = await that.init(_id);
if (res.code === 200) {
console.log('[LiveCode]初始化路由列表完成');
that.initAppRoutes(res.data?.list || [], _id);
} else {
console.error('[LiveCode]初始化路由列表失败:', res?.message);
}
return this;
}
that.onMessage(req);
return this;
}
getWss(id: string) {
return this.wssManager.getConnection(id)
}
async init(id: string): Promise<{ code: number, message?: string, data?: any }> {
return this.sendData({ message: { path: 'router', key: 'list', } }, id);
}
sendData(data: ListenProcessParams, id: string): Promise<{ code: number, message?: string, data?: any }> {
const reqId = customId()
const wss = this.getWss(id);
if (!wss) {
return Promise.resolve({ code: 500, message: '连接不存在或已关闭' });
}
const emitter = this.emitter;
const wsReq = wss.wsReq;
try {
wsReq.ws.send(JSON.stringify({
type: 'router',
id: reqId,
data: data
}));
} catch (error) {
console.error('[LiveCode]发送数据失败:', error);
return Promise.resolve({ code: 500, message: '发送数据失败' });
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve({ code: 500, message: '请求超时' });
emitter.off(reqId, listenOnce);
}, 5000);
const listenOnce = (resData: any) => {
clearTimeout(timeout);
resolve(resData);
emitter.off(reqId, listenOnce);
}
emitter.once(reqId, listenOnce);
});
}
onMessage(req: WebSocketReq) {
const { data } = req;
if (data?.id) {
// console.log('LiveCode 收到消息:', data);
this.emitter.emit(data.id, data.data);
} else {
console.warn('[LiveCode] 未知的消息格式', data);
}
}
initAppRoutes(list: Route[], wid: string) {
for (const route of list) {
const path = route.path || '';
const id = route.id || '';
if (path.startsWith('router') || path.startsWith('auth') || path.startsWith('admin-autu') || path.startsWith('call')) {
continue;
}
// console.log('注册路由:', route.path, route.description, route.metadata, route.id);
this.app.route({
path: route.id,
key: route.key,
description: route.description,
metadata: {
...route.metadata,
source: 'livecode',
liveCodeId: wid
},
middleware: ['auth'],
}).define(async (ctx) => {
const { token, cookie, ...rest } = ctx.query;
const tokenUser = ctx.state.tokernUser;
const res = await this.sendData({
message: {
id: route.id,
payload: rest,
},
context: {
state: {
tokenUser
}
}
}, wid);
// console.log('路由响应数据:', res);
ctx.forward(res)
}).addTo(this.app, {
// override: false,
// // @ts-ignore
// overwrite: false
});
}
}
deinitAppRoutes(wid: string) {
const routesToRemove = this.app.routes.filter(route => route.metadata?.liveCodeId === wid);
for (const route of routesToRemove) {
this.app.removeById(route.id);
}
}
}