UNPKG

capacitor-cors-bypass-enhanced

Version:

Enhanced Capacitor plugin for CORS bypass with HTTP/2, HTTP/3, gRPC, GraphQL, file operations, and advanced networking features

531 lines (523 loc) 22.3 kB
var capacitorCorsProxy = (function (exports, core) { 'use strict'; const CorsBypass = core.registerPlugin('CorsBypass', { web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CorsBypassWeb()), }); // MCP SDK imports with type assertions const { Client } = require('@modelcontextprotocol/sdk/client/index.js'); const { SSEClientTransport } = require('@modelcontextprotocol/sdk/client/sse.js'); class CorsBypassWeb extends core.WebPlugin { constructor() { super(); this.sseConnections = new Map(); this.wsConnections = new Map(); this.mcpClients = new Map(); this.mcpTransports = new Map(); this.connectionCounter = 0; this.proxyServerUrl = null; // Try to detect if a proxy server is available this.detectProxyServer(); } async detectProxyServer() { const possibleUrls = [ 'http://localhost:3002', 'http://127.0.0.1:3002', 'http://localhost:3001', 'http://127.0.0.1:3001', 'http://localhost:8080', 'http://127.0.0.1:8080' ]; for (const url of possibleUrls) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 1000); const response = await fetch(`${url}/health`, { method: 'GET', signal: controller.signal }); clearTimeout(timeoutId); if (response.ok) { this.proxyServerUrl = url; console.log(`🔧 CORS Proxy server detected at: ${url}`); break; } } catch (error) { // Ignore errors, continue checking } } if (!this.proxyServerUrl) { console.warn('⚠️ No CORS proxy server detected. Some requests may fail due to CORS.'); console.log('💡 To enable full functionality, run: node web-proxy-server.js'); } } /** * Set custom proxy server URL */ setProxyServer(url) { this.proxyServerUrl = url; console.log(`🔧 Proxy server set to: ${url}`); } async request(options) { const { url, method = 'GET', headers = {}, data, params, timeout = 30000, responseType = 'json', followRedirects = true, } = options; // Build URL with query parameters let requestUrl = url; if (params) { const urlParams = new URLSearchParams(params); requestUrl += (url.includes('?') ? '&' : '?') + urlParams.toString(); } // Use proxy server if available and URL is cross-origin let finalUrl = requestUrl; let fetchOptions = { method, headers, redirect: followRedirects ? 'follow' : 'manual', }; if (this.proxyServerUrl && this.isCrossOrigin(requestUrl)) { console.log(`🔧 Using proxy server for: ${requestUrl}`); finalUrl = `${this.proxyServerUrl}/proxy/${encodeURIComponent(requestUrl)}`; } // Create AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); fetchOptions.signal = controller.signal; try { // Add body for methods that support it if (data && ['POST', 'PUT', 'PATCH'].includes(method)) { if (typeof data === 'string') { fetchOptions.body = data; } else { fetchOptions.body = JSON.stringify(data); if (!headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } } } const response = await fetch(finalUrl, fetchOptions); clearTimeout(timeoutId); // Parse response based on responseType let responseData; switch (responseType) { case 'text': responseData = await response.text(); break; case 'blob': responseData = await response.blob(); break; case 'arraybuffer': responseData = await response.arrayBuffer(); break; case 'json': default: try { responseData = await response.json(); } catch { responseData = await response.text(); } break; } // Convert Headers to plain object const responseHeaders = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); return { status: response.status, statusText: response.statusText, headers: responseHeaders, data: responseData, url: response.url, }; } catch (error) { clearTimeout(timeoutId); // If proxy failed and we have a proxy server, try direct request as fallback if (this.proxyServerUrl && finalUrl.includes(this.proxyServerUrl)) { console.warn(`⚠️ Proxy request failed, trying direct request: ${error}`); return this.request({ ...options, url: requestUrl }); } throw error; } } async get(options) { return this.request({ ...options, method: 'GET' }); } async post(options) { return this.request({ ...options, method: 'POST' }); } async put(options) { return this.request({ ...options, method: 'PUT' }); } async patch(options) { return this.request({ ...options, method: 'PATCH' }); } async delete(options) { return this.request({ ...options, method: 'DELETE' }); } async startSSE(options) { const connectionId = `sse_${++this.connectionCounter}`; const { url, headers = {}, withCredentials = false, reconnectTimeout = 3000 } = options; // Use proxy server for SSE if available and cross-origin let sseUrl = url; if (this.proxyServerUrl && this.isCrossOrigin(url)) { console.log(`🔧 Using SSE proxy for: ${url}`); sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`; } const eventSource = new EventSource(sseUrl); this.sseConnections.set(connectionId, eventSource); eventSource.onopen = () => { this.notifyListeners('sseOpen', { connectionId, status: 'connected', }); }; eventSource.onmessage = (event) => { this.notifyListeners('sseMessage', { connectionId, type: 'message', data: event.data, id: event.lastEventId, }); }; eventSource.onerror = () => { this.notifyListeners('sseError', { connectionId, error: 'Connection error', }); }; return { connectionId }; } async stopSSE(options) { const { connectionId } = options; const connection = this.sseConnections.get(connectionId); if (connection) { connection.close(); this.sseConnections.delete(connectionId); this.notifyListeners('sseClose', { connectionId, status: 'disconnected', }); } } isCrossOrigin(url) { try { const targetUrl = new URL(url); const currentUrl = new URL(window.location.href); return targetUrl.origin !== currentUrl.origin; } catch { return false; } } async createSSEConnection(options) { const connectionId = `sse_${++this.connectionCounter}`; const { url, headers = {}, reconnect = {} } = options; const { enabled: reconnectEnabled = true, initialDelay = 1000, maxDelay = 30000, maxAttempts = 10, } = reconnect; let retryCount = 0; let retryDelay = initialDelay; const createConnection = () => { // Use proxy server for SSE if available and cross-origin let sseUrl = url; if (this.proxyServerUrl && this.isCrossOrigin(url)) { console.log(`🔧 Using SSE proxy for: ${url}`); sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`; } const eventSource = new EventSource(sseUrl); this.sseConnections.set(connectionId, eventSource); eventSource.onopen = () => { retryCount = 0; retryDelay = initialDelay; this.notifyListeners('sseConnectionChange', { connectionId, status: 'connected', }); }; eventSource.onmessage = (event) => { this.notifyListeners('sseMessage', { connectionId, type: 'message', data: event.data, id: event.lastEventId, }); }; eventSource.onerror = () => { this.notifyListeners('sseConnectionChange', { connectionId, status: 'error', error: 'Connection error', }); if (reconnectEnabled && retryCount < maxAttempts) { setTimeout(() => { retryCount++; retryDelay = Math.min(retryDelay * 2, maxDelay); eventSource.close(); createConnection(); }, retryDelay); } else { this.sseConnections.delete(connectionId); } }; // Add custom event listeners eventSource.addEventListener('error', (event) => { this.notifyListeners('sseMessage', { connectionId, type: 'error', data: 'Connection error', }); }); }; this.notifyListeners('sseConnectionChange', { connectionId, status: 'connecting', }); createConnection(); return { connectionId, status: 'connecting', }; } async closeSSEConnection(options) { const { connectionId } = options; const connection = this.sseConnections.get(connectionId); if (connection) { connection.close(); this.sseConnections.delete(connectionId); this.notifyListeners('sseConnectionChange', { connectionId, status: 'disconnected', }); } } async createWebSocketConnection(options) { const connectionId = `ws_${++this.connectionCounter}`; const { url, protocols, headers, timeout = 10000 } = options; return new Promise((resolve, reject) => { const ws = new WebSocket(url, protocols); this.wsConnections.set(connectionId, ws); const timeoutId = setTimeout(() => { ws.close(); this.wsConnections.delete(connectionId); reject(new Error('WebSocket connection timeout')); }, timeout); ws.onopen = () => { clearTimeout(timeoutId); this.notifyListeners('webSocketConnectionChange', { connectionId, status: 'connected', }); resolve({ connectionId, status: 'connected', }); }; ws.onmessage = (event) => { this.notifyListeners('webSocketMessage', { connectionId, data: event.data, type: typeof event.data === 'string' ? 'text' : 'binary', }); }; ws.onerror = () => { clearTimeout(timeoutId); this.notifyListeners('webSocketConnectionChange', { connectionId, status: 'error', error: 'WebSocket connection error', }); }; ws.onclose = () => { this.wsConnections.delete(connectionId); this.notifyListeners('webSocketConnectionChange', { connectionId, status: 'disconnected', }); }; this.notifyListeners('webSocketConnectionChange', { connectionId, status: 'connecting', }); }); } async closeWebSocketConnection(options) { const { connectionId } = options; const connection = this.wsConnections.get(connectionId); if (connection) { connection.close(); this.wsConnections.delete(connectionId); } } async sendWebSocketMessage(options) { const { connectionId, message } = options; const connection = this.wsConnections.get(connectionId); if (connection && connection.readyState === WebSocket.OPEN) { connection.send(message); } else { throw new Error('WebSocket connection not found or not open'); } } // ===== MCP Protocol Methods ===== async createMCPClient(options) { const connectionId = `mcp_${++this.connectionCounter}`; try { // 创建SSE传输层 let transport; if (this.proxyServerUrl && this.isCrossOrigin(options.sseUrl)) { // 使用代理服务器 const proxyUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(options.sseUrl)}`; transport = new SSEClientTransport(new URL(proxyUrl)); } else { // 直接连接 transport = new SSEClientTransport(new URL(options.sseUrl)); } // 创建MCP客户端 const client = new Client({ name: options.clientInfo.name, version: options.clientInfo.version, }, { capabilities: { roots: options.capabilities?.roots ? { listChanged: true } : undefined, sampling: options.capabilities?.sampling ? {} : undefined, } }); // 连接到服务器 await client.connect(transport); // 存储客户端和传输层 this.mcpClients.set(connectionId, client); this.mcpTransports.set(connectionId, transport); console.log(`✅ MCP客户端已连接: ${connectionId}`); return { connectionId, status: 'connected', serverCapabilities: client.getServerCapabilities(), protocolVersion: '2025-03-26' }; } catch (error) { console.error(`❌ MCP客户端连接失败:`, error); throw new Error(`Failed to create MCP client: ${error}`); } } async listMCPResources(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.listResources(options.cursor ? { cursor: options.cursor } : {}); return { resources: result.resources || [], nextCursor: result.nextCursor }; } catch (error) { throw new Error(`Failed to list MCP resources: ${error}`); } } async readMCPResource(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.readResource({ uri: options.uri }); return { uri: options.uri, mimeType: result.contents?.[0]?.mimeType || 'text/plain', text: result.contents?.[0]?.text || '', blob: result.contents?.[0]?.data }; } catch (error) { throw new Error(`Failed to read MCP resource: ${error}`); } } async listMCPTools(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.listTools(options.cursor ? { cursor: options.cursor } : {}); return { tools: result.tools || [], nextCursor: result.nextCursor }; } catch (error) { throw new Error(`Failed to list MCP tools: ${error}`); } } async callMCPTool(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.callTool({ name: options.name, arguments: options.arguments || {} }); return { content: result.content || [], isError: result.isError || false }; } catch (error) { throw new Error(`Failed to call MCP tool: ${error}`); } } async listMCPPrompts(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.listPrompts(options.cursor ? { cursor: options.cursor } : {}); return { prompts: result.prompts || [], nextCursor: result.nextCursor }; } catch (error) { throw new Error(`Failed to list MCP prompts: ${error}`); } } async getMCPPrompt(options) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.getPrompt({ name: options.name, arguments: options.arguments || {} }); return { description: result.description || '', messages: result.messages || [] }; } catch (error) { throw new Error(`Failed to get MCP prompt: ${error}`); } } async sendMCPSampling(options) { // MCP SDK doesn't directly support sampling, this would need to be implemented // based on the specific server's sampling capabilities throw new Error('MCP sampling not yet implemented with SDK'); } } var web = /*#__PURE__*/Object.freeze({ __proto__: null, CorsBypassWeb: CorsBypassWeb }); exports.CorsBypass = CorsBypass; Object.defineProperty(exports, '__esModule', { value: true }); return exports; })({}, capacitorExports); //# sourceMappingURL=plugin.js.map