first commit
This commit is contained in:
483
web/src/services/MCPClientService.ts
Normal file
483
web/src/services/MCPClientService.ts
Normal file
@@ -0,0 +1,483 @@
|
||||
import type { MCPServerConfig, ServerCapabilities, Tool, Resource, Prompt } from '../types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SSETransport } from './SSETransport';
|
||||
|
||||
/**
|
||||
* 纯前端 MCP 客户端服务
|
||||
* 直接在浏览器中连接 MCP 服务器,无需后端中间层
|
||||
*/
|
||||
export class MCPClientService {
|
||||
private clients = new Map<string, any>();
|
||||
private listeners = new Map<string, Array<(event: string, data: any) => void>>();
|
||||
|
||||
/**
|
||||
* 添加并连接到 MCP 服务器
|
||||
*/
|
||||
async addServer(config: MCPServerConfig): Promise<ServerCapabilities> {
|
||||
try {
|
||||
console.log(`🔗 正在连接到 MCP 服务器: ${config.name} (${config.url})`);
|
||||
|
||||
let client;
|
||||
|
||||
if (config.type === 'http') {
|
||||
// HTTP 连接
|
||||
client = await this.createHttpClient(config);
|
||||
} else if (config.type === 'sse') {
|
||||
// SSE 连接
|
||||
client = await this.createSSEClient(config);
|
||||
} else {
|
||||
throw new Error(`不支持的连接类型: ${config.type}`);
|
||||
}
|
||||
|
||||
// 获取服务器能力
|
||||
const capabilities = await this.getServerCapabilities(client);
|
||||
|
||||
this.clients.set(config.id, { client, config, capabilities });
|
||||
|
||||
console.log(`✅ 成功连接到 MCP 服务器: ${config.name}`);
|
||||
console.log('服务器能力:', capabilities);
|
||||
|
||||
this.emit(config.id, 'connected', capabilities);
|
||||
|
||||
return capabilities;
|
||||
} catch (error) {
|
||||
console.error(`❌ 连接 MCP 服务器失败: ${config.name}`);
|
||||
console.error('错误详情:', error);
|
||||
|
||||
// 检查是否是 CORS 错误
|
||||
if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
|
||||
const corsError = new Error(`CORS 错误: 无法连接到 ${config.url}。请确保 MCP 服务器启用了 CORS 支持。`);
|
||||
this.emit(config.id, 'error', corsError);
|
||||
throw corsError;
|
||||
}
|
||||
|
||||
this.emit(config.id, 'error', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 HTTP 客户端
|
||||
*/
|
||||
private async createHttpClient(config: MCPServerConfig) {
|
||||
// 将 0.0.0.0 替换为 localhost(浏览器无法访问 0.0.0.0)
|
||||
let baseUrl = config.url.replace(/\/$/, '');
|
||||
baseUrl = baseUrl.replace('0.0.0.0', 'localhost').replace('127.0.0.1', 'localhost');
|
||||
|
||||
// 确保URL包含 /mcp 路径
|
||||
if (!baseUrl.includes('/mcp')) {
|
||||
baseUrl = baseUrl + '/mcp';
|
||||
}
|
||||
|
||||
console.log(`🔄 HTTP原始URL: ${config.url}`);
|
||||
console.log(`🔄 HTTP转换后URL: ${baseUrl}`);
|
||||
|
||||
// 先测试MCP端点是否可访问
|
||||
try {
|
||||
console.log(`🔍 测试MCP端点可达性: ${baseUrl}`);
|
||||
const testResponse = await fetch(baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 'test-' + Date.now(),
|
||||
method: 'ping' // 随便发一个方法测试连通性
|
||||
})
|
||||
});
|
||||
|
||||
console.log(`MCP端点响应状态: ${testResponse.status}`);
|
||||
|
||||
// 如果完全无法连接,fetch会抛出错误
|
||||
// 如果能连接但返回错误状态码,我们也认为连接有问题
|
||||
if (!testResponse.ok && testResponse.status >= 500) {
|
||||
throw new Error(`服务器错误: HTTP ${testResponse.status}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ MCP端点连接失败:`, error);
|
||||
|
||||
// 检查是否是网络错误
|
||||
if (error instanceof TypeError) {
|
||||
throw new Error(`网络连接失败: 无法访问 ${baseUrl}。请检查服务器是否运行以及网络连接。`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'http',
|
||||
baseUrl,
|
||||
async call(method: string, params: any) {
|
||||
const response = await fetch(baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: uuidv4(),
|
||||
method,
|
||||
params
|
||||
})
|
||||
});
|
||||
|
||||
console.log(`MCP 请求 (${method}):`, response.status, response.statusText);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`MCP 响应 (${method}):`, result);
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message || '请求错误');
|
||||
}
|
||||
|
||||
return result.result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SSE客户端
|
||||
*/
|
||||
private async createSSEClient(config: MCPServerConfig): Promise<any> {
|
||||
// 将 0.0.0.0 替换为 localhost(浏览器无法访问 0.0.0.0)
|
||||
let url = config.url;
|
||||
url = url.replace('0.0.0.0', 'localhost').replace('127.0.0.1', 'localhost');
|
||||
|
||||
console.log(`🔄 SSE 原始URL: ${config.url}`);
|
||||
console.log(`🔄 SSE 转换后URL: ${url}`);
|
||||
|
||||
const transport = new SSETransport(url);
|
||||
|
||||
// 连接SSE
|
||||
await transport.connect();
|
||||
|
||||
console.log(`✓ SSE 连接已建立: ${url}`);
|
||||
|
||||
return {
|
||||
type: 'sse',
|
||||
transport,
|
||||
async call(method: string, params: any) {
|
||||
try {
|
||||
const result = await transport.sendRequest(method, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`SSE 请求失败 (${method}):`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async disconnect() {
|
||||
await transport.disconnect();
|
||||
},
|
||||
get connected() {
|
||||
return transport.isConnected;
|
||||
},
|
||||
// 事件监听
|
||||
on(event: string, callback: Function) {
|
||||
transport.on(event, callback);
|
||||
},
|
||||
off(event: string, callback: Function) {
|
||||
transport.off(event, callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器能力
|
||||
*/
|
||||
private async getServerCapabilities(client: any): Promise<ServerCapabilities> {
|
||||
try {
|
||||
console.log('🔄 正在初始化MCP服务器...');
|
||||
|
||||
// 初始化请求 - 这是必须成功的
|
||||
const initResult = await client.call('initialize', {
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: {
|
||||
roots: {
|
||||
listChanged: true
|
||||
},
|
||||
sampling: {}
|
||||
},
|
||||
clientInfo: {
|
||||
name: 'MCP-Vue-Client',
|
||||
version: '1.0.0'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ MCP服务器初始化成功:', initResult);
|
||||
|
||||
// 获取工具列表
|
||||
let tools: Tool[] = [];
|
||||
try {
|
||||
const toolsResult = await client.call('tools/list', {});
|
||||
tools = toolsResult.tools || [];
|
||||
console.log(`📋 发现 ${tools.length} 个工具`);
|
||||
} catch (error) {
|
||||
console.warn('获取工具列表失败:', error);
|
||||
}
|
||||
|
||||
// 获取资源列表
|
||||
let resources: Resource[] = [];
|
||||
try {
|
||||
const resourcesResult = await client.call('resources/list', {});
|
||||
resources = resourcesResult.resources || [];
|
||||
console.log(`📁 发现 ${resources.length} 个资源`);
|
||||
} catch (error) {
|
||||
console.warn('获取资源列表失败:', error);
|
||||
}
|
||||
|
||||
// 获取提示列表
|
||||
let prompts: Prompt[] = [];
|
||||
try {
|
||||
const promptsResult = await client.call('prompts/list', {});
|
||||
prompts = promptsResult.prompts || [];
|
||||
console.log(`💡 发现 ${prompts.length} 个提示`);
|
||||
} catch (error) {
|
||||
console.warn('获取提示列表失败:', error);
|
||||
}
|
||||
|
||||
return { tools, resources, prompts };
|
||||
} catch (error) {
|
||||
console.error('❌ MCP服务器初始化失败:', error);
|
||||
// 初始化失败应该抛出错误,而不是返回空能力
|
||||
throw new Error(`MCP服务器初始化失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用工具
|
||||
*/
|
||||
async callTool(serverId: string, toolName: string, parameters: Record<string, any>): Promise<any> {
|
||||
const serverInfo = this.clients.get(serverId);
|
||||
if (!serverInfo) {
|
||||
throw new Error(`服务器 ${serverId} 未连接`);
|
||||
}
|
||||
|
||||
const { client } = serverInfo;
|
||||
|
||||
try {
|
||||
console.log(`🔧 调用工具: ${toolName}`, parameters);
|
||||
|
||||
const result = await client.call('tools/call', {
|
||||
name: toolName,
|
||||
arguments: parameters
|
||||
});
|
||||
|
||||
console.log(`✅ 工具调用成功: ${toolName}`, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 工具调用失败: ${toolName}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取资源
|
||||
*/
|
||||
async readResource(serverId: string, uri: string): Promise<any> {
|
||||
const serverInfo = this.clients.get(serverId);
|
||||
if (!serverInfo) {
|
||||
throw new Error(`服务器 ${serverId} 未连接`);
|
||||
}
|
||||
|
||||
const { client } = serverInfo;
|
||||
|
||||
try {
|
||||
console.log(`📖 读取资源: ${uri}`);
|
||||
|
||||
const result = await client.call('resources/read', { uri });
|
||||
|
||||
console.log(`✅ 资源读取成功: ${uri}`, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 资源读取失败: ${uri}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提示
|
||||
*/
|
||||
async getPrompt(serverId: string, name: string, args?: Record<string, any>): Promise<any> {
|
||||
const serverInfo = this.clients.get(serverId);
|
||||
if (!serverInfo) {
|
||||
throw new Error(`服务器 ${serverId} 未连接`);
|
||||
}
|
||||
|
||||
const { client } = serverInfo;
|
||||
|
||||
try {
|
||||
console.log(`💭 获取提示: ${name}`, args);
|
||||
|
||||
const result = await client.call('prompts/get', {
|
||||
name,
|
||||
arguments: args || {}
|
||||
});
|
||||
|
||||
console.log(`✅ 提示获取成功: ${name}`, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ 提示获取失败: ${name}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开服务器连接
|
||||
*/
|
||||
async removeServer(serverId: string): Promise<void> {
|
||||
const serverInfo = this.clients.get(serverId);
|
||||
if (serverInfo) {
|
||||
const { client } = serverInfo;
|
||||
|
||||
try {
|
||||
if (client.type === 'sse' && client.disconnect) {
|
||||
await client.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('关闭连接时出错:', error);
|
||||
}
|
||||
|
||||
this.clients.delete(serverId);
|
||||
}
|
||||
this.listeners.delete(serverId);
|
||||
console.log(`🔌 服务器 ${serverId} 已断开连接`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试服务器连接
|
||||
*/
|
||||
async testConnection(serverId: string): Promise<boolean> {
|
||||
const serverInfo = this.clients.get(serverId);
|
||||
if (!serverInfo) {
|
||||
console.log(`❌ 服务器 ${serverId} 未找到客户端实例`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const { client, config } = serverInfo;
|
||||
|
||||
try {
|
||||
if (client.type === 'sse') {
|
||||
return client.connected;
|
||||
} else if (client.type === 'http') {
|
||||
// HTTP 连接测试 - 发送真实的MCP初始化请求
|
||||
console.log(`🔍 测试HTTP MCP连接: ${client.baseUrl}`);
|
||||
|
||||
const response = await fetch(client.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 'test-' + Date.now(),
|
||||
method: 'initialize',
|
||||
params: {
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: {},
|
||||
clientInfo: { name: 'MCP-Test-Client', version: '1.0.0' }
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`❌ HTTP响应失败: ${response.status} ${response.statusText}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
console.log(`❌ MCP协议错误:`, data.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`✅ MCP连接测试成功`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(`❌ 连接测试异常:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有连接的服务器
|
||||
*/
|
||||
getConnectedServers(): string[] {
|
||||
return Array.from(this.clients.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器信息
|
||||
*/
|
||||
getServerInfo(serverId: string) {
|
||||
return this.clients.get(serverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
*/
|
||||
on(serverId: string, callback: (event: string, data: any) => void): void {
|
||||
if (!this.listeners.has(serverId)) {
|
||||
this.listeners.set(serverId, []);
|
||||
}
|
||||
this.listeners.get(serverId)!.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
*/
|
||||
off(serverId: string, callback?: (event: string, data: any) => void): void {
|
||||
if (!callback) {
|
||||
this.listeners.delete(serverId);
|
||||
} else {
|
||||
const callbacks = this.listeners.get(serverId) || [];
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index !== -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
private emit(serverId: string, event: string, data: any): void {
|
||||
const callbacks = this.listeners.get(serverId) || [];
|
||||
callbacks.forEach(callback => {
|
||||
try {
|
||||
callback(event, data);
|
||||
} catch (error) {
|
||||
console.error('事件回调执行失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有连接
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
const serverIds = Array.from(this.clients.keys());
|
||||
await Promise.all(serverIds.map(id => this.removeServer(id)));
|
||||
console.log('🧹 所有连接已清理');
|
||||
}
|
||||
}
|
||||
|
||||
// 单例导出
|
||||
export const mcpClientService = new MCPClientService();
|
||||
|
||||
// 在页面卸载时清理连接
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
mcpClientService.cleanup();
|
||||
});
|
||||
}
|
||||
247
web/src/services/SSETransport.ts
Normal file
247
web/src/services/SSETransport.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
/**
|
||||
* SSE (Server-Sent Events) 传输层实现
|
||||
* 用于MCP协议的单向数据流传输
|
||||
*/
|
||||
export class SSETransport {
|
||||
private eventSource: EventSource | null = null;
|
||||
private url: string;
|
||||
private pendingRequests = new Map<string, { resolve: Function; reject: Function; timeout: NodeJS.Timeout }>();
|
||||
private listeners = new Map<string, Function[]>();
|
||||
private connected = false;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// 首先建立SSE连接,获取sessionId
|
||||
console.log('📡 连接SSE端点:', this.url);
|
||||
|
||||
// 第一步:连接SSE获取endpoint信息
|
||||
this.eventSource = new EventSource(this.url);
|
||||
|
||||
let resolveTimeout: NodeJS.Timeout;
|
||||
|
||||
this.eventSource.addEventListener('endpoint', (event: any) => {
|
||||
const endpointData = event.data;
|
||||
console.log('✅ 收到SSE endpoint:', endpointData);
|
||||
|
||||
// 提取sessionId(格式: /message?sessionId=xxx)
|
||||
const match = endpointData.match(/sessionId=([^&]+)/);
|
||||
if (match) {
|
||||
const sessionId = match[1];
|
||||
console.log('📝 SSE sessionId:', sessionId);
|
||||
// 保存sessionId以便后续请求使用
|
||||
(this as any).sessionId = sessionId;
|
||||
}
|
||||
|
||||
this.connected = true;
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.eventSource.onopen = () => {
|
||||
console.log('📡 SSE连接已打开');
|
||||
// 设置超时,如果10秒内没有收到endpoint事件则认为失败
|
||||
resolveTimeout = setTimeout(() => {
|
||||
if (!this.connected) {
|
||||
reject(new Error('SSE连接超时:未收到endpoint'));
|
||||
}
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('📨 收到SSE消息:', data);
|
||||
this.handleMessage(data);
|
||||
} catch (error) {
|
||||
// 如果不是JSON,可能是普通文本消息
|
||||
console.log('📨 收到SSE文本消息:', event.data);
|
||||
}
|
||||
};
|
||||
|
||||
this.eventSource.onerror = (error) => {
|
||||
console.error('❌ SSE连接错误:', error);
|
||||
this.connected = false;
|
||||
this.emit('disconnected');
|
||||
|
||||
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
||||
reject(new Error('SSE连接失败'));
|
||||
}
|
||||
};
|
||||
|
||||
// 监听message事件(MCP响应)
|
||||
this.eventSource.addEventListener('message', (event: any) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('📨 收到MCP消息:', message);
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('❌ MCP消息解析失败:', error, event.data);
|
||||
}
|
||||
});
|
||||
|
||||
// 清理resolve超时
|
||||
this.eventSource.addEventListener('endpoint', () => {
|
||||
if (resolveTimeout) {
|
||||
clearTimeout(resolveTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建SSE连接失败:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async sendRequest(method: string, params?: any): Promise<any> {
|
||||
const id = uuidv4();
|
||||
const request = {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
method,
|
||||
params: params || {}
|
||||
};
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 设置超时
|
||||
const timeout = setTimeout(() => {
|
||||
this.pendingRequests.delete(id);
|
||||
reject(new Error(`SSE请求超时: ${method}`));
|
||||
}, 30000); // 30秒超时
|
||||
|
||||
this.pendingRequests.set(id, { resolve, reject, timeout });
|
||||
|
||||
try {
|
||||
console.log(`📤 发送SSE请求 (${method}):`, request);
|
||||
|
||||
// 获取sessionId
|
||||
const sessionId = (this as any).sessionId;
|
||||
if (!sessionId) {
|
||||
throw new Error('SSE sessionId未就绪');
|
||||
}
|
||||
|
||||
// 根据服务器endpoint构建URL
|
||||
// 例如: http://localhost:3200/message?sessionId=xxx
|
||||
const baseUrl = this.url.replace('/sse', '');
|
||||
const messageUrl = `${baseUrl}/message?sessionId=${sessionId}`;
|
||||
|
||||
console.log(`📤 发送到: ${messageUrl}`);
|
||||
|
||||
// SSE模式:通过HTTP POST发送请求到/message端点,响应通过SSE事件流返回
|
||||
const response = await fetch(messageUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
clearTimeout(timeout);
|
||||
this.pendingRequests.delete(id);
|
||||
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
// 对于某些简单请求,可能直接返回JSON响应
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
clearTimeout(timeout);
|
||||
this.pendingRequests.delete(id);
|
||||
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
reject(new Error(result.error.message || '请求失败'));
|
||||
} else {
|
||||
resolve(result.result);
|
||||
}
|
||||
}
|
||||
// 否则等待SSE响应
|
||||
|
||||
} catch (error) {
|
||||
const pending = this.pendingRequests.get(id);
|
||||
if (pending) {
|
||||
clearTimeout(pending.timeout);
|
||||
this.pendingRequests.delete(id);
|
||||
}
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(message: any): void {
|
||||
if (message.id && this.pendingRequests.has(message.id)) {
|
||||
const pending = this.pendingRequests.get(message.id)!;
|
||||
clearTimeout(pending.timeout);
|
||||
this.pendingRequests.delete(message.id);
|
||||
|
||||
if (message.error) {
|
||||
pending.reject(new Error(message.error.message || '请求失败'));
|
||||
} else {
|
||||
pending.resolve(message.result);
|
||||
}
|
||||
} else if (!message.id && message.method) {
|
||||
// 处理通知消息
|
||||
console.log('📢 收到通知:', message.method, message.params);
|
||||
this.emit('notification', message);
|
||||
}
|
||||
}
|
||||
|
||||
on(event: string, callback: Function): void {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, []);
|
||||
}
|
||||
this.listeners.get(event)!.push(callback);
|
||||
}
|
||||
|
||||
off(event: string, callback: Function): void {
|
||||
const callbacks = this.listeners.get(event) || [];
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private emit(event: string, data?: any): void {
|
||||
const callbacks = this.listeners.get(event) || [];
|
||||
callbacks.forEach(callback => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error('❌ SSE事件回调错误:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
console.log('🔌 断开SSE连接');
|
||||
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
}
|
||||
|
||||
this.connected = false;
|
||||
|
||||
// 清理待处理的请求
|
||||
this.pendingRequests.forEach(pending => {
|
||||
clearTimeout(pending.timeout);
|
||||
pending.reject(new Error('连接已断开'));
|
||||
});
|
||||
this.pendingRequests.clear();
|
||||
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
get isConnected(): boolean {
|
||||
return this.connected && this.eventSource?.readyState === EventSource.OPEN;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user