feat: 添加自动重连机制和重连配置选项到 RemoteApp 类,并更新相关文档

This commit is contained in:
2026-02-05 14:09:14 +08:00
parent a46510949b
commit 8fc9605242
5 changed files with 418 additions and 42 deletions

View File

@@ -6,6 +6,16 @@ type RemoteAppOptions = {
token?: string;
emitter?: EventEmitter;
id?: string;
/** 是否启用自动重连,默认 true */
autoReconnect?: boolean;
/** 最大重连次数,默认 Infinity */
maxReconnectAttempts?: number;
/** 初始重连延迟(毫秒),默认 1000 */
reconnectDelay?: number;
/** 重连延迟最大值(毫秒),默认 30000 */
maxReconnectDelay?: number;
/** 是否启用指数退避,默认 true */
enableBackoff?: boolean;
};
/**
* 远程共享地址类似https://kevisual.cn/ws/proxy
@@ -19,6 +29,15 @@ export class RemoteApp {
ws: WebSocket;
remoteIsConnected: boolean;
isError: boolean = false;
// 重连相关属性
autoReconnect: boolean;
maxReconnectAttempts: number;
reconnectDelay: number;
maxReconnectDelay: number;
enableBackoff: boolean;
reconnectAttempts: number = 0;
reconnectTimer: NodeJS.Timeout | null = null;
isManuallyClosed: boolean = false;
constructor(opts?: RemoteAppOptions) {
this.mainApp = opts?.app;
const token = opts.token;
@@ -32,6 +51,12 @@ export class RemoteApp {
_url.searchParams.set('id', id);
this.url = _url.toString();
this.id = id;
// 初始化重连相关配置
this.autoReconnect = opts?.autoReconnect ?? true;
this.maxReconnectAttempts = opts?.maxReconnectAttempts ?? Infinity;
this.reconnectDelay = opts?.reconnectDelay ?? 1000;
this.maxReconnectDelay = opts?.maxReconnectDelay ?? 30000;
this.enableBackoff = opts?.enableBackoff ?? true;
this.init();
}
async isConnect(): Promise<boolean> {
@@ -39,6 +64,11 @@ export class RemoteApp {
if (this.isConnected) {
return true;
}
// 如果正在进行重连,等待连接成功
if (this.reconnectTimer !== null) {
console.log(`远程应用 ${this.id} 正在重连中...`);
}
// 等待连接成功(支持初次连接和重连场景)
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(false);
@@ -90,12 +120,84 @@ export class RemoteApp {
this.ws = ws;
}
onOpen() {
this.isError = false;
this.reconnectAttempts = 0;
// 清除可能存在的重连定时器
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
this.emitter.emit('open', this.id);
}
onClose() {
console.log('远程应用关闭:', this.id);
this.emitter.emit('close', this.id);
this.isConnected = false;
this.emitter.emit('close', this.id);
// 触发自动重连逻辑
if (this.autoReconnect && !this.isManuallyClosed) {
this.scheduleReconnect();
}
}
/** 计算下一次重连延迟 */
calculateReconnectDelay(): number {
if (!this.enableBackoff) {
return this.reconnectDelay;
}
// 指数退避算法delay = initialDelay * 2^(attempts - 1)
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
return Math.min(delay, this.maxReconnectDelay);
}
/** 安排重连 */
scheduleReconnect() {
// 检查是否达到最大重连次数
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error(`远程应用 ${this.id} 已达到最大重连次数 ${this.maxReconnectAttempts},停止重连`);
this.emitter.emit('maxReconnectAttemptsReached', this.id);
return;
}
// 清除可能存在的定时器
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
const delay = this.calculateReconnectDelay();
this.reconnectAttempts++;
console.log(`远程应用 ${this.id} 将在 ${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连`);
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = null;
try {
this.init();
} catch (error) {
console.error(`远程应用 ${this.id} 重连失败:`, error);
this.emitter.emit('reconnectFailed', { id: this.id, attempt: this.reconnectAttempts, error });
// 重连失败后继续尝试重连
this.scheduleReconnect();
}
}, delay);
}
/** 手动关闭连接,停止自动重连 */
disconnect() {
this.isManuallyClosed = true;
this.autoReconnect = false;
// 清除重连定时器
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
// 关闭 WebSocket
if (this.ws) {
this.ws.close();
}
}
/** 手动重连 */
reconnect() {
this.isManuallyClosed = false;
this.reconnectAttempts = 0;
// 清除可能存在的定时器
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
this.init();
}
onMessage(data: any) {
this.emitter.emit('message', data);
@@ -105,7 +207,7 @@ export class RemoteApp {
this.isError = true;
this.emitter.emit('error', error);
}
on(event: 'open' | 'close' | 'message' | 'error', listener: (data: any) => void) {
on(event: 'open' | 'close' | 'message' | 'error' | 'maxReconnectAttemptsReached' | 'reconnectFailed', listener: (data: any) => void) {
this.emitter.on(event, listener);
return () => {
this.emitter.off(event, listener);