feat: 更新WebSocket Keep-Alive客户端库以支持Bun和Node.js环境,添加统一的消息处理方法,并创建keep.ts文件以初始化KeepAlive
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { buildWithBun } from '@kevisual/code-builder'
|
||||
await buildWithBun({ naming: 'opencode', entry: 'agent/opencode.ts', dts: true });
|
||||
await buildWithBun({ naming: 'keep', entry: 'src/keep.ts', dts: true, external: ['ws'] });
|
||||
await buildWithBun({ naming: 'keep', entry: 'src/keep.ts', dts: true, target: 'node' });
|
||||
await buildWithBun({ naming: 'routes', entry: 'agent/index.ts', dts: true });
|
||||
13
keep.ts
Normal file
13
keep.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createKeepAlive } from "@kevisual/cnb/keep";
|
||||
|
||||
const config = {
|
||||
"wss": "wss://cnb-tmm-1jhgl3i0m-001.cnb.space:443/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=26ba6a08-1c57-41cc-8099-1f6e64863bf6&reconnection=false&skipWebSocketFrames=false",
|
||||
"cookie": "orange:workspace:cookie-session:cnb-tmm-1jhgl3i0m-001=93d7bc9b-9ca0-4867-963d-1928ad3038c7",
|
||||
"url": "https://cnb-tmm-1jhgl3i0m-001.cnb.space/?folder=/workspace"
|
||||
}
|
||||
|
||||
createKeepAlive({
|
||||
wsUrl: config.wss,
|
||||
cookie: config.cookie,
|
||||
debug: true,
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/cnb",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.26",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -22,9 +22,9 @@
|
||||
"@kevisual/ai": "^0.0.24",
|
||||
"@kevisual/code-builder": "^0.0.6",
|
||||
"@kevisual/dts": "^0.0.3",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/context": "^0.0.6",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@opencode-ai/plugin": "^1.2.1",
|
||||
"@opencode-ai/plugin": "^1.2.4",
|
||||
"@types/bun": "^1.3.9",
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/ws": "^8.18.1",
|
||||
|
||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -43,8 +43,8 @@ importers:
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
'@kevisual/context':
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
'@kevisual/dts':
|
||||
specifier: ^0.0.3
|
||||
version: 0.0.3(typescript@5.9.3)
|
||||
@@ -52,8 +52,8 @@ importers:
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
'@opencode-ai/plugin':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4
|
||||
'@types/bun':
|
||||
specifier: ^1.3.9
|
||||
version: 1.3.9
|
||||
@@ -90,8 +90,8 @@ packages:
|
||||
resolution: {integrity: sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw==}
|
||||
hasBin: true
|
||||
|
||||
'@kevisual/context@0.0.4':
|
||||
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
|
||||
'@kevisual/context@0.0.6':
|
||||
resolution: {integrity: sha512-w7HBOuO3JH37n6xT6W3FD7ykqHTwtyxOQzTzfEcKDCbsvGB1wVreSxFm2bvoFnnFLuxT/5QMpKlnPrwvmcTGnw==}
|
||||
|
||||
'@kevisual/dts@0.0.3':
|
||||
resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==}
|
||||
@@ -127,11 +127,11 @@ packages:
|
||||
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@opencode-ai/plugin@1.2.1':
|
||||
resolution: {integrity: sha512-PVRz3Y0l7+xi4iNxvdC32zx5wrEMfCiVQQVh3wZ7r+g6kM+8pUguKhwxTcwcOx57XMPMhmuoxuRcLMn79gtQuA==}
|
||||
'@opencode-ai/plugin@1.2.4':
|
||||
resolution: {integrity: sha512-FfRybm1Ujzkt8EQDtxZKVEA88EI8XaAu3ikViC8DYSP3lJaF++8isN3vmlSqCi+A+O2/5xd2yQ0yq3tmJ2WVhw==}
|
||||
|
||||
'@opencode-ai/sdk@1.2.1':
|
||||
resolution: {integrity: sha512-K5e15mIXTyAykBw0GX+8O28IJHlPMw1jI/m3SDu+hgUHjmg2refqLPqyuqv8hE2nRcuGi8HajhpDJjkO7H2S0A==}
|
||||
'@opencode-ai/sdk@1.2.4':
|
||||
resolution: {integrity: sha512-IPgtBpif46wTviC3HQxkjS4M/1tZSnRmD/6aEF3lL88MT+PAqKA30G+AhBlpvXBITq9EmjO4gjzM59ly2z7mYQ==}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.9':
|
||||
resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==}
|
||||
@@ -583,7 +583,7 @@ snapshots:
|
||||
|
||||
'@kevisual/code-builder@0.0.6': {}
|
||||
|
||||
'@kevisual/context@0.0.4': {}
|
||||
'@kevisual/context@0.0.6': {}
|
||||
|
||||
'@kevisual/dts@0.0.3(typescript@5.9.3)':
|
||||
dependencies:
|
||||
@@ -625,12 +625,12 @@ snapshots:
|
||||
|
||||
'@kevisual/ws@8.19.0': {}
|
||||
|
||||
'@opencode-ai/plugin@1.2.1':
|
||||
'@opencode-ai/plugin@1.2.4':
|
||||
dependencies:
|
||||
'@opencode-ai/sdk': 1.2.1
|
||||
'@opencode-ai/sdk': 1.2.4
|
||||
zod: 4.3.6
|
||||
|
||||
'@opencode-ai/sdk@1.2.1': {}
|
||||
'@opencode-ai/sdk@1.2.4': {}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.9(rollup@4.57.1)':
|
||||
dependencies:
|
||||
|
||||
@@ -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,14 +120,73 @@ export class WSKeepAlive {
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on("open", () => {
|
||||
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();
|
||||
};
|
||||
|
||||
ws.onmessage = async (event: MessageEvent) => {
|
||||
let data: Buffer | string;
|
||||
|
||||
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.handleMessage(data);
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
this.ws.on("message", (data: any) => {
|
||||
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);
|
||||
@@ -125,26 +197,14 @@ export class WSKeepAlive {
|
||||
} else {
|
||||
this.config.onMessage(data);
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on("close", (code: number) => {
|
||||
debug && this.log(`Disconnected (code: ${code})`);
|
||||
this.stopPing();
|
||||
this.config.onDisconnect(code);
|
||||
this.handleReconnect();
|
||||
});
|
||||
|
||||
this.ws.on("error", (err: Error) => {
|
||||
debug && this.log(`Error: ${err.message}`);
|
||||
this.config.onError(err);
|
||||
});
|
||||
}
|
||||
|
||||
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