feat: 更新WebSocket Keep-Alive客户端库以支持Bun和Node.js环境,添加统一的消息处理方法,并创建keep.ts文件以初始化KeepAlive
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
// WebSocket Keep-Alive Client Library
|
||||
import WebSocket from "ws";
|
||||
// 运行时检测:Bun 使用原生 WebSocket,Node.js 使用 ws 库
|
||||
let WebSocketModule: any;
|
||||
|
||||
if (typeof Bun !== 'undefined') {
|
||||
// Bun 环境:使用原生 WebSocket
|
||||
WebSocketModule = { WebSocket: globalThis.WebSocket };
|
||||
} else {
|
||||
// Node.js 环境:使用 ws 库
|
||||
WebSocketModule = await import('ws');
|
||||
}
|
||||
|
||||
const WebSocket = WebSocketModule.WebSocket;
|
||||
|
||||
export interface KeepAliveConfig {
|
||||
wsUrl: string;
|
||||
@@ -31,6 +42,7 @@ export class WSKeepAlive {
|
||||
private pingTimer: NodeJS.Timeout | null = null;
|
||||
private messageHandlers: Set<MessageHandler> = new Set();
|
||||
private url: URL;
|
||||
private readonly isBun: boolean;
|
||||
|
||||
constructor(config: KeepAliveConfig) {
|
||||
this.config = {
|
||||
@@ -48,6 +60,7 @@ export class WSKeepAlive {
|
||||
debug: config.debug ?? false,
|
||||
};
|
||||
this.url = new URL(this.config.wsUrl);
|
||||
this.isBun = typeof Bun !== 'undefined';
|
||||
}
|
||||
|
||||
private log(message: string) {
|
||||
@@ -107,44 +120,91 @@ export class WSKeepAlive {
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on("open", () => {
|
||||
debug && this.log("Connected!");
|
||||
this.reconnectAttempts = 0;
|
||||
this.config.onConnect();
|
||||
this.startPing();
|
||||
});
|
||||
if (this.isBun) {
|
||||
// Bun 环境:使用标准 Web API
|
||||
const ws = this.ws as any;
|
||||
ws.onopen = () => {
|
||||
debug && this.log("Connected!");
|
||||
this.reconnectAttempts = 0;
|
||||
this.config.onConnect();
|
||||
this.startPing();
|
||||
};
|
||||
|
||||
this.ws.on("message", (data: any) => {
|
||||
if (Buffer.isBuffer(data)) {
|
||||
const parsed = this.parseMessage(data);
|
||||
this.config.onMessage(parsed?.raw ?? data);
|
||||
ws.onmessage = async (event: MessageEvent) => {
|
||||
let data: Buffer | string;
|
||||
|
||||
this.messageHandlers.forEach(handler => {
|
||||
if (parsed) handler(parsed);
|
||||
});
|
||||
} else {
|
||||
this.config.onMessage(data);
|
||||
}
|
||||
});
|
||||
if (event.data instanceof Blob) {
|
||||
data = Buffer.from(await event.data.arrayBuffer());
|
||||
} else if (event.data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(event.data);
|
||||
} else if (typeof event.data === 'string') {
|
||||
data = event.data;
|
||||
} else {
|
||||
data = Buffer.from(event.data);
|
||||
}
|
||||
|
||||
this.ws.on("close", (code: number) => {
|
||||
debug && this.log(`Disconnected (code: ${code})`);
|
||||
this.stopPing();
|
||||
this.config.onDisconnect(code);
|
||||
this.handleReconnect();
|
||||
});
|
||||
this.handleMessage(data);
|
||||
};
|
||||
|
||||
this.ws.on("error", (err: Error) => {
|
||||
debug && this.log(`Error: ${err.message}`);
|
||||
this.config.onError(err);
|
||||
});
|
||||
ws.onclose = (event: CloseEvent) => {
|
||||
debug && this.log(`Disconnected (code: ${event.code})`);
|
||||
this.stopPing();
|
||||
this.config.onDisconnect(event.code);
|
||||
this.handleReconnect();
|
||||
};
|
||||
|
||||
ws.onerror = (event: Event) => {
|
||||
debug && this.log(`Error: ${event}`);
|
||||
this.config.onError(new Error("WebSocket error"));
|
||||
};
|
||||
} else {
|
||||
// Node.js (ws 库):使用 EventEmitter 模式
|
||||
const ws = this.ws as any;
|
||||
ws.on("open", () => {
|
||||
debug && this.log("Connected!");
|
||||
this.reconnectAttempts = 0;
|
||||
this.config.onConnect();
|
||||
this.startPing();
|
||||
});
|
||||
|
||||
ws.on("message", (data: any) => {
|
||||
this.handleMessage(data);
|
||||
});
|
||||
|
||||
ws.on("close", (code: number) => {
|
||||
debug && this.log(`Disconnected (code: ${code})`);
|
||||
this.stopPing();
|
||||
this.config.onDisconnect(code);
|
||||
this.handleReconnect();
|
||||
});
|
||||
|
||||
ws.on("error", (err: Error) => {
|
||||
debug && this.log(`Error: ${err.message}`);
|
||||
this.config.onError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 统一的消息处理方法
|
||||
private handleMessage(data: Buffer | string) {
|
||||
if (Buffer.isBuffer(data)) {
|
||||
const parsed = this.parseMessage(data);
|
||||
this.config.onMessage(parsed?.raw ?? data);
|
||||
|
||||
this.messageHandlers.forEach(handler => {
|
||||
if (parsed) handler(parsed);
|
||||
});
|
||||
} else {
|
||||
this.config.onMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
private startPing() {
|
||||
this.stopPing();
|
||||
this.pingTimer = setInterval(() => {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.ping();
|
||||
// 使用 JSON 格式的 ping 消息,兼容 Bun 和 Node.js
|
||||
this.ws.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
|
||||
this.log("Sent ping");
|
||||
}
|
||||
}, this.config.pingInterval);
|
||||
|
||||
Reference in New Issue
Block a user