181 lines
6.2 KiB
JavaScript
181 lines
6.2 KiB
JavaScript
/**
|
||
* websocket-client模块 - 与ComfyUI实例的WebSocket通信
|
||
*
|
||
* 设计说明:
|
||
* - clientId 使用实例 ID(固定不变),实现连接复用
|
||
* - 同一实例的所有任务共享同一个 WebSocket 连接
|
||
* - 通过 prompt_id 区分不同任务的消息
|
||
*/
|
||
|
||
import WebSocket from 'ws';
|
||
import logger from '../logger/index.js';
|
||
import EventEmitter from 'events';
|
||
import comfyUIMonitor from '../comfyui-monitor/index.js';
|
||
|
||
class WebSocketClient extends EventEmitter {
|
||
constructor() {
|
||
super();
|
||
this.connections = new Map();
|
||
}
|
||
|
||
connect(instanceId, wsUrl) {
|
||
return new Promise((resolve, reject) => {
|
||
const existingConn = this.connections.get(instanceId);
|
||
|
||
if (existingConn && existingConn.ws && existingConn.ws.readyState === WebSocket.OPEN) {
|
||
logger.info(`[WebSocketClient] 实例 ${instanceId} 已有连接,直接复用`);
|
||
resolve(existingConn.ws);
|
||
return;
|
||
}
|
||
|
||
if (existingConn && existingConn.ws) {
|
||
logger.info(`[WebSocketClient] 关闭旧连接,重新连接`);
|
||
existingConn.ws.close();
|
||
this.connections.delete(instanceId);
|
||
}
|
||
|
||
logger.info(`正在连接到实例 ${instanceId}: ${wsUrl}`);
|
||
logger.info(`[WebSocketClient] 连接详情: instanceId=${instanceId}, wsUrl=${wsUrl}`);
|
||
|
||
const ws = new WebSocket(wsUrl);
|
||
|
||
ws.on('open', () => {
|
||
logger.info(`成功连接到实例 ${instanceId}`);
|
||
this.connections.set(instanceId, { ws, wsUrl });
|
||
|
||
const stateChange = comfyUIMonitor.setInstanceState(instanceId, 'connected');
|
||
if (stateChange) {
|
||
const config = { wsUrl };
|
||
comfyUIMonitor.logConnectionStateChange(
|
||
instanceId,
|
||
stateChange.oldState,
|
||
'connected',
|
||
'WebSocket连接成功',
|
||
config
|
||
);
|
||
}
|
||
|
||
resolve(ws);
|
||
});
|
||
|
||
ws.on('message', (data) => {
|
||
try {
|
||
const message = JSON.parse(data.toString());
|
||
this.handleMessage(instanceId, message);
|
||
} catch (error) {
|
||
logger.error(`解析消息失败 (${instanceId}):`, error);
|
||
}
|
||
});
|
||
|
||
ws.on('error', (error) => {
|
||
logger.error(`WebSocket连接错误 (${instanceId}):`, error);
|
||
|
||
const config = { wsUrl };
|
||
comfyUIMonitor.logConnectionError(instanceId, error, config);
|
||
|
||
reject(error);
|
||
});
|
||
|
||
ws.on('close', (code, reason) => {
|
||
logger.warn(`与实例 ${instanceId} 的连接已关闭`);
|
||
|
||
const stateChange = comfyUIMonitor.setInstanceState(instanceId, 'disconnected');
|
||
if (stateChange) {
|
||
const disconnectReason = reason ? reason.toString() : `关闭代码: ${code}`;
|
||
const config = { wsUrl, closeCode: code };
|
||
comfyUIMonitor.logConnectionStateChange(
|
||
instanceId,
|
||
stateChange.oldState,
|
||
'disconnected',
|
||
disconnectReason,
|
||
config
|
||
);
|
||
}
|
||
|
||
this.connections.delete(instanceId);
|
||
this.emit('disconnected', { instanceId });
|
||
});
|
||
});
|
||
}
|
||
|
||
handleMessage(instanceId, message) {
|
||
if (message.type !== 'progress_state' && message.type !== 'progress') {
|
||
logger.info(`[WebSocketClient] 收到消息 from ${instanceId}: type=${message.type}, data=${JSON.stringify(message.data || {}).substring(0, 200)}`);
|
||
}
|
||
this.emit('message', { instanceId, message });
|
||
|
||
switch (message.type) {
|
||
case 'status':
|
||
logger.info(`[WebSocketClient] status 消息: ${JSON.stringify(message.data)}`);
|
||
this.emit('status', { instanceId, status: message.data });
|
||
break;
|
||
case 'progress':
|
||
break;
|
||
case 'execution_start':
|
||
logger.info(`[WebSocketClient] execution_start 消息: prompt_id=${message.data?.prompt_id}`);
|
||
this.emit('execution_start', { instanceId, promptId: message.data.prompt_id });
|
||
break;
|
||
case 'execution_cached':
|
||
logger.info(`[WebSocketClient] execution_cached 消息: ${JSON.stringify(message.data)}`);
|
||
this.emit('execution_cached', { instanceId, data: message.data });
|
||
break;
|
||
case 'executing':
|
||
logger.info(`[WebSocketClient] executing 消息: node=${message.data?.node}, prompt_id=${message.data?.prompt_id}`);
|
||
this.emit('executing', { instanceId, data: message.data });
|
||
break;
|
||
case 'executed':
|
||
logger.info(`[WebSocketClient] executed 消息: prompt_id=${message.data?.prompt_id}`);
|
||
this.emit('executed', { instanceId, data: message.data });
|
||
break;
|
||
case 'execution_success':
|
||
logger.info(`[WebSocketClient] execution_success 消息: prompt_id=${message.data?.prompt_id}`);
|
||
this.emit('execution_success', { instanceId, data: message.data });
|
||
break;
|
||
case 'execution_error':
|
||
logger.error(`[WebSocketClient] execution_error 消息: ${JSON.stringify(message.data)}`);
|
||
this.emit('execution_error', { instanceId, data: message.data });
|
||
break;
|
||
case 'progress_state':
|
||
break;
|
||
default:
|
||
logger.info(`[WebSocketClient] 未处理的消息类型: ${message.type}`);
|
||
}
|
||
}
|
||
|
||
send(instanceId, message) {
|
||
const conn = this.connections.get(instanceId);
|
||
const ws = conn?.ws;
|
||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||
logger.error(`[WebSocketClient] 实例 ${instanceId} 未连接,无法发送消息`);
|
||
throw new Error(`实例 ${instanceId} 未连接`);
|
||
}
|
||
const messageStr = JSON.stringify(message);
|
||
logger.info(`[WebSocketClient] 发送消息到实例 ${instanceId}: ${messageStr.substring(0, 500)}${messageStr.length > 500 ? '...' : ''}`);
|
||
ws.send(messageStr);
|
||
}
|
||
|
||
disconnect(instanceId) {
|
||
const conn = this.connections.get(instanceId);
|
||
if (conn && conn.ws) {
|
||
conn.ws.close();
|
||
this.connections.delete(instanceId);
|
||
}
|
||
}
|
||
|
||
disconnectAll() {
|
||
for (const [instanceId, conn] of this.connections) {
|
||
if (conn.ws) {
|
||
conn.ws.close();
|
||
}
|
||
}
|
||
this.connections.clear();
|
||
}
|
||
|
||
isConnected(instanceId) {
|
||
const conn = this.connections.get(instanceId);
|
||
return conn?.ws && conn.ws.readyState === WebSocket.OPEN;
|
||
}
|
||
}
|
||
|
||
export default new WebSocketClient();
|