feat: 实现 RemoteApp 类以支持远程连接,添加初始化和重连逻辑;更新用户路由以支持获取用户信息

This commit is contained in:
2025-12-21 02:39:52 +08:00
parent 864766be4a
commit b3c5e7d68d
8 changed files with 300 additions and 166 deletions

View File

@@ -0,0 +1,2 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

View File

@@ -0,0 +1,20 @@
{
"name": "@kevisual/remote-app",
"version": "0.0.1",
"description": "",
"main": "remote-app.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"files": [
"remote-app.ts"
],
"publishConfig": {
"access": "public"
},
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.26.0",
"type": "module"
}

View File

@@ -0,0 +1,165 @@
import type { App } from '@kevisual/router';
import { EventEmitter } from 'eventemitter3';
type RemoteAppOptions = {
app?: App;
url?: string;
token?: string;
emitter?: EventEmitter;
id?: string;
};
export class RemoteApp {
mainApp: App;
url: string;
id: string;
emitter: EventEmitter;
isConnected: boolean;
ws: WebSocket;
remoteIsConnected: boolean;
isError: boolean = false;
constructor(opts?: RemoteAppOptions) {
this.mainApp = opts?.app;
const token = opts.token;
const url = opts.url;
const id = opts.id;
this.emitter = opts?.emitter || new EventEmitter();
const _url = new URL(url);
if (token) {
_url.searchParams.set('token', token);
}
_url.searchParams.set('id', id);
this.url = _url.toString();
this.id = id;
this.init();
}
async isConnect(): Promise<boolean> {
const that = this;
if (this.isConnected) {
return true;
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(false);
that.emitter.off('open', listenOnce);
}, 5000);
const listenOnce = () => {
clearTimeout(timeout);
that.isConnected = true;
that.remoteIsConnected = 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.id) {
throw new Error('No id provided for remote app');
}
this.isError = false;
const ws = new WebSocket(this.getWsURL(this.url));
const that = this;
ws.onopen = function () {
that.isConnected = true;
that.onOpen();
};
ws.onclose = function () {
that.isConnected = false;
that.onClose();
}
ws.onmessage = function (event) {
that.onMessage(event.data);
}
ws.onerror = function (error) {
that.onError(error);
}
this.ws = ws;
}
onOpen() {
this.emitter.emit('open', this.id);
}
onClose() {
console.log('远程应用关闭:', this.id);
this.emitter.emit('close', this.id);
this.isConnected = false;
}
onMessage(data: any) {
this.emitter.emit('message', data);
}
onError(error: any) {
console.error('远程应用错误:', this.id, error);
this.isError = true;
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) => {
try {
const data = event.toString();
const body = JSON.parse(data);
const message = body.data || {};
if (body?.code === 401) {
console.error('远程应用认证失败,请检查 token 是否正确');
this.isError = true;
return;
}
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,
},
});
} catch (error) {
console.error('处理远程代理请求出错:', error);
}
};
remoteApp.json({
id: this.id,
type: 'registryClient'
});
remoteApp.emitter.on('message', listenFn);
const closeMessage = () => {
remoteApp.emitter.off('message', listenFn);
}
remoteApp.emitter.once('close', () => {
closeMessage();
});
return () => {
closeMessage();
};
}
}