"feat: 新增远程应用测试脚本,升级 pm2 依赖"

This commit is contained in:
2025-05-22 13:19:57 +08:00
parent 1c2aa26dd0
commit 1e340ec2b3
8 changed files with 415 additions and 259 deletions

View File

@@ -77,6 +77,14 @@ export type AssistantConfigData = {
path?: string;
port?: number;
};
share?: {
url: string;
enabled?: boolean; // 是否启用远程应用
name: string;
};
watch?: {
enabled?: boolean;
};
/**
* 首页
*/

View File

@@ -0,0 +1,138 @@
import type { AssistantConfig } from '@/module/assistant/index.ts';
import { WebSocket } from 'ws';
import type { App } from '@kevisual/router';
import { EventEmitter } from 'eventemitter3';
import { logger } from '@/module/logger.ts';
type RemoteAppOptions = {
app?: App;
assistantConfig?: AssistantConfig;
emitter?: EventEmitter;
};
export class RemoteApp {
mainApp: App;
assistantConfig: AssistantConfig;
url: string;
name: string;
enabled: boolean;
emitter: EventEmitter;
isConnected: boolean;
ws: WebSocket;
constructor(opts?: RemoteAppOptions) {
this.mainApp = opts?.app;
this.assistantConfig = opts?.assistantConfig;
const share = this.assistantConfig?.getConfig()?.share;
this.emitter = opts?.emitter || new EventEmitter();
if (share) {
const { url, name, enabled } = share;
this.url = url;
this.name = name;
this.enabled = enabled ?? false;
if (this.enabled) {
this.init();
}
}
}
async isConnect(): Promise<boolean> {
const that = this;
if (this.isConnected) {
return true;
}
if (!this.enabled) {
return false;
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(false);
that.emitter.off('open', listenOnce);
}, 5000);
const listenOnce = () => {
clearTimeout(timeout);
that.isConnected = true;
resolve(true);
};
that.emitter.once('open', listenOnce);
});
}
getWsURL(url: string) {
const { protocol } = new URL(url);
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
const wsURL = url.toString().replace(protocol, wsProtocol);
return wsURL;
}
async init() {
if (!this.url) {
throw new Error('No url provided for remote app');
}
if (!this.name) {
throw new Error('No name provided for remote app');
}
console.log('Connecting to remote app:', this.name, this.url, this.getWsURL(this.url));
const ws = new WebSocket(this.getWsURL(this.url), {
rejectUnauthorized: true,
});
const that = this;
ws.on('open', that.onOpen.bind(that));
ws.on('close', that.onClose.bind(that));
ws.on('message', that.onMessage.bind(that));
ws.on('error', that.onError.bind(that));
this.ws = ws;
}
onOpen() {
this.emitter.emit('open', this.name);
}
onClose() {
this.emitter.emit('close', this.name);
}
onMessage(data: any) {
this.emitter.emit('message', data);
}
onError(error: any) {
console.error('Error in remote app:', this.name, error);
this.emitter.emit('error', error);
}
on(event: 'open' | 'close' | 'message' | 'error', listener: (data: any) => void) {
this.emitter.on(event, listener);
return () => {
this.emitter.off(event, listener);
};
}
sendData(data: any) {}
json(data: any) {
this.ws.send(JSON.stringify(data));
}
listenProxy() {
const remoteApp = this;
const app = this.mainApp;
const listenFn = async (event: any) => {
const data = event.toString();
logger.debug('Received message:', data);
const body = JSON.parse(data);
const message = body.data || {};
if (body?.type !== 'proxy') return;
if (!body.id) {
remoteApp.json({
id: body.id,
data: {
code: 400,
message: 'id is required',
},
});
return;
}
const res = await app.call(message);
remoteApp.json({
id: body.id,
data: {
code: res.code,
data: res.body,
message: res.message,
},
});
};
remoteApp.emitter.on('message', listenFn);
return () => {
remoteApp.emitter.off('message', listenFn);
};
}
}