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. Modular TypeScript definitions for better maintainability.

1,350 lines (1,337 loc) 49 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var core = require('@capacitor/core'); var index_js = require('@modelcontextprotocol/sdk/client/index.js'); var sse_js = require('@modelcontextprotocol/sdk/client/sse.js'); const CorsBypass = core.registerPlugin('CorsBypass', { web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CorsBypassWeb()), }); /** * Check if a URL is cross-origin */ function isCrossOrigin(url) { try { const targetUrl = new URL(url); const currentUrl = new URL(window.location.href); return targetUrl.origin !== currentUrl.origin; } catch { return false; } } /** * Create interceptor context */ function createInterceptorContext() { return { startTime: Date.now(), requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, retryCount: 0, data: {}, }; } /** * Utils Manager * Provides utility functions for the web plugin */ class UtilsManager { /** * Check if a URL is cross-origin */ isCrossOrigin(url) { return isCrossOrigin(url); } /** * Create interceptor context */ createInterceptorContext() { return createInterceptorContext(); } } /** * HTTP Request Manager * Handles all HTTP requests with CORS bypass and interceptor support */ class HttpManager { constructor(proxyServerUrl) { this.proxyServerUrl = proxyServerUrl; } /** * Set custom proxy server URL */ setProxyServer(url) { this.proxyServerUrl = url; console.log(`🔧 Proxy server set to: ${url}`); } /** * Make an HTTP request with CORS bypass and interceptor support */ async request(options, interceptors) { const context = createInterceptorContext(); try { // Execute request interceptors let modifiedOptions = await this.executeRequestInterceptors(options, context, interceptors); const { url, method = 'GET', headers = {}, data, params, timeout = 30000, responseType = 'json', followRedirects = true, } = modifiedOptions; // 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 && 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; }); let httpResponse = { status: response.status, statusText: response.statusText, headers: responseHeaders, data: responseData, url: response.url, }; // Execute response interceptors httpResponse = await this.executeResponseInterceptors(httpResponse, context, interceptors); return httpResponse; } catch (error) { clearTimeout(timeoutId); // Create HTTP error const httpError = { message: error instanceof Error ? error.message : 'Unknown error', config: modifiedOptions, originalError: error, }; // Try error interceptors const interceptorResult = await this.executeErrorInterceptors(httpError, context, interceptors); if (interceptorResult) { return interceptorResult; } // 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 }, interceptors); } throw httpError; } } catch (error) { // Handle errors from interceptors or other sources if (error.config) { // Already an HttpError throw error; } // Create HTTP error const httpError = { message: error instanceof Error ? error.message : 'Unknown error', config: options, originalError: error, }; throw httpError; } } /** * Make a GET request */ async get(options, interceptors) { return this.request({ ...options, method: 'GET' }, interceptors); } /** * Make a POST request */ async post(options, interceptors) { return this.request({ ...options, method: 'POST' }, interceptors); } /** * Make a PUT request */ async put(options, interceptors) { return this.request({ ...options, method: 'PUT' }, interceptors); } /** * Make a PATCH request */ async patch(options, interceptors) { return this.request({ ...options, method: 'PATCH' }, interceptors); } /** * Make a DELETE request */ async delete(options, interceptors) { return this.request({ ...options, method: 'DELETE' }, interceptors); } /** * Execute request interceptors */ async executeRequestInterceptors(config, context, interceptors) { let modifiedConfig = { ...config }; for (const entry of interceptors) { if (!entry.enabled || !entry.interceptor.onRequest) { continue; } // Check scope if defined if (entry.options.scope) { const { urlPattern, methods } = entry.options.scope; if (urlPattern && !new RegExp(urlPattern).test(modifiedConfig.url)) { continue; } if (methods && modifiedConfig.method && !methods.includes(modifiedConfig.method)) { continue; } } try { modifiedConfig = await Promise.resolve(entry.interceptor.onRequest(modifiedConfig)); } catch (error) { console.error(`[Interceptor ${entry.id}] Request interceptor error:`, error); throw error; } } return modifiedConfig; } /** * Execute response interceptors */ async executeResponseInterceptors(response, context, interceptors) { let modifiedResponse = { ...response }; for (const entry of interceptors) { if (!entry.enabled || !entry.interceptor.onResponse) { continue; } try { modifiedResponse = await Promise.resolve(entry.interceptor.onResponse(modifiedResponse)); } catch (error) { console.error(`[Interceptor ${entry.id}] Response interceptor error:`, error); throw error; } } return modifiedResponse; } /** * Execute error interceptors */ async executeErrorInterceptors(error, context, interceptors) { for (const entry of interceptors) { if (!entry.enabled || !entry.interceptor.onError) { continue; } try { const result = await Promise.resolve(entry.interceptor.onError(error)); if (result) { // Interceptor returned a response, use it return result; } } catch (interceptorError) { console.error(`[Interceptor ${entry.id}] Error interceptor error:`, interceptorError); // Continue to next interceptor } } // No interceptor handled the error, return void return; } } /** * Stream Manager * Handles streaming HTTP requests with CORS bypass */ class StreamManager { constructor(proxyServerUrl, notifyListeners) { this.streamControllers = new Map(); this.streamCounter = 0; this.proxyServerUrl = proxyServerUrl; this.notifyListeners = notifyListeners; } /** * Make a streaming HTTP request - supports AI model streaming output */ async streamRequest(options) { const streamId = `stream_${++this.streamCounter}`; const { url, method = 'POST', headers = {}, data, params, timeout = 60000, 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; if (this.proxyServerUrl && isCrossOrigin(requestUrl)) { console.log(`🔧 Using proxy server for streaming: ${requestUrl}`); finalUrl = `${this.proxyServerUrl}/proxy/${encodeURIComponent(requestUrl)}`; } // Create AbortController for this stream const controller = new AbortController(); this.streamControllers.set(streamId, controller); // Set timeout const timeoutId = setTimeout(() => { controller.abort(); this.notifyListeners('streamStatus', { streamId, status: 'error', error: 'Request timeout', }); }, timeout); try { // Prepare fetch options const fetchOptions = { method, headers: { ...headers, 'Accept': 'text/event-stream, application/json, text/plain, */*', }, signal: controller.signal, redirect: followRedirects ? 'follow' : 'manual', }; // 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']) { fetchOptions.headers = { ...fetchOptions.headers, 'Content-Type': 'application/json', }; } } } console.log(`🌊 Starting stream request: ${streamId} to ${finalUrl}`); // Start the fetch request fetch(finalUrl, fetchOptions) .then(async (response) => { clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // Convert headers to plain object const responseHeaders = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); // Notify stream started this.notifyListeners('streamStatus', { streamId, status: 'started', statusCode: response.status, headers: responseHeaders, }); // Read the stream const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) { throw new Error('Response body is not readable'); } try { while (true) { const { done, value } = await reader.read(); if (done) { // Stream completed this.notifyListeners('streamChunk', { streamId, data: '', done: true, }); this.notifyListeners('streamStatus', { streamId, status: 'completed', }); this.streamControllers.delete(streamId); break; } // Decode and send chunk const chunk = decoder.decode(value, { stream: true }); this.notifyListeners('streamChunk', { streamId, data: chunk, done: false, }); } } catch (error) { if (error.name === 'AbortError') { this.notifyListeners('streamStatus', { streamId, status: 'cancelled', }); } else { throw error; } } }) .catch((error) => { clearTimeout(timeoutId); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.notifyListeners('streamChunk', { streamId, data: '', done: true, error: errorMessage, }); this.notifyListeners('streamStatus', { streamId, status: 'error', error: errorMessage, }); this.streamControllers.delete(streamId); }); return { streamId }; } catch (error) { clearTimeout(timeoutId); this.streamControllers.delete(streamId); throw error; } } /** * Cancel a streaming request */ async cancelStream(options) { const { streamId } = options; const controller = this.streamControllers.get(streamId); if (controller) { controller.abort(); this.streamControllers.delete(streamId); this.notifyListeners('streamStatus', { streamId, status: 'cancelled', }); } } /** * Get all active stream controllers */ getStreamControllers() { return this.streamControllers; } } /** * SSE Manager * Handles Server-Sent Events connections with CORS bypass */ class SSEManager { constructor(proxyServerUrl, notifyListeners) { this.sseConnections = new Map(); this.connectionCounter = 0; this.proxyServerUrl = proxyServerUrl; this.notifyListeners = notifyListeners; } /** * Start listening to Server-Sent Events (legacy method) */ 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 && 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 }; } /** * Stop listening to Server-Sent Events */ 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', }); } } /** * Create a Server-Sent Events connection with reconnection support */ 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 && 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', }; } /** * Close an SSE connection */ 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', }); } } /** * Get all active SSE connections */ getSSEConnections() { return this.sseConnections; } } /** * WebSocket Manager * Handles WebSocket connections with CORS bypass */ class WebSocketManager { constructor(notifyListeners) { this.wsConnections = new Map(); this.connectionCounter = 0; this.notifyListeners = notifyListeners; } /** * Create a WebSocket connection */ 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', }); }); } /** * Close a WebSocket connection */ async closeWebSocketConnection(options) { const { connectionId } = options; const connection = this.wsConnections.get(connectionId); if (connection) { connection.close(); this.wsConnections.delete(connectionId); } } /** * Send data through WebSocket */ 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'); } } /** * Get all active WebSocket connections */ getWebSocketConnections() { return this.wsConnections; } } /** * Interceptor Manager * Handles request/response interceptors with priority and scope support */ class InterceptorManager { constructor() { this.interceptors = []; this.interceptorCounter = 0; } /** * Add an interceptor to the request/response chain */ async addInterceptor(interceptor, options) { const id = `interceptor_${++this.interceptorCounter}`; const interceptorEntry = { id, interceptor, options: options || {}, enabled: options?.enabled !== false, }; this.interceptors.push(interceptorEntry); // Sort by priority (higher priority first) this.interceptors.sort((a, b) => { const priorityA = a.options.priority || 0; const priorityB = b.options.priority || 0; return priorityB - priorityA; }); const handle = { id, name: options?.name, remove: () => { this.removeInterceptor(id); }, enable: () => { const entry = this.interceptors.find(i => i.id === id); if (entry) entry.enabled = true; }, disable: () => { const entry = this.interceptors.find(i => i.id === id); if (entry) entry.enabled = false; }, isEnabled: () => { const entry = this.interceptors.find(i => i.id === id); return entry ? entry.enabled : false; }, }; return handle; } /** * Remove an interceptor by handle or ID */ async removeInterceptor(handle) { const id = typeof handle === 'string' ? handle : handle.id; const index = this.interceptors.findIndex(i => i.id === id); if (index !== -1) { this.interceptors.splice(index, 1); } } /** * Remove all interceptors */ async removeAllInterceptors() { this.interceptors = []; } /** * Get all registered interceptors */ async getInterceptors() { return this.interceptors.map(entry => ({ id: entry.id, name: entry.options.name, remove: () => this.removeInterceptor(entry.id), enable: () => { entry.enabled = true; }, disable: () => { entry.enabled = false; }, isEnabled: () => entry.enabled, })); } /** * Execute request interceptors */ async executeRequestInterceptors(config) { createInterceptorContext(); let modifiedConfig = { ...config }; for (const entry of this.interceptors) { if (!entry.enabled || !entry.interceptor.onRequest) { continue; } // Check scope if defined if (entry.options.scope) { const { urlPattern, methods } = entry.options.scope; if (urlPattern && !new RegExp(urlPattern).test(modifiedConfig.url)) { continue; } if (methods && modifiedConfig.method && !methods.includes(modifiedConfig.method)) { continue; } } try { modifiedConfig = await Promise.resolve(entry.interceptor.onRequest(modifiedConfig)); } catch (error) { console.error(`[Interceptor ${entry.id}] Request interceptor error:`, error); throw error; } } return modifiedConfig; } /** * Execute response interceptors */ async executeResponseInterceptors(response) { createInterceptorContext(); let modifiedResponse = { ...response }; for (const entry of this.interceptors) { if (!entry.enabled || !entry.interceptor.onResponse) { continue; } try { modifiedResponse = await Promise.resolve(entry.interceptor.onResponse(modifiedResponse)); } catch (error) { console.error(`[Interceptor ${entry.id}] Response interceptor error:`, error); throw error; } } return modifiedResponse; } /** * Execute error interceptors */ async executeErrorInterceptors(error) { createInterceptorContext(); for (const entry of this.interceptors) { if (!entry.enabled || !entry.interceptor.onError) { continue; } try { const result = await Promise.resolve(entry.interceptor.onError(error)); if (result) { // Interceptor returned a response, use it return result; } } catch (interceptorError) { console.error(`[Interceptor ${entry.id}] Error interceptor error:`, interceptorError); // Continue to next interceptor } } // No interceptor handled the error, return void return; } /** * Get all interceptors (internal format) */ getInterceptorsInternal() { return this.interceptors; } } class CorsBypassWeb extends core.WebPlugin { constructor() { super(); this.proxyServerUrl = null; this.globalProxyConfig = null; this.proxyRequestCount = 0; this.proxyLastSuccessTime = null; this.proxyLastError = null; // MCP specific this.mcpClients = new Map(); this.mcpTransports = new Map(); this.connectionCounter = 0; // Initialize managers this.utilsManager = new UtilsManager(); this.httpManager = new HttpManager(this.proxyServerUrl); this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this)); this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this)); this.wsManager = new WebSocketManager(this.notifyListeners.bind(this)); this.interceptorManager = new InterceptorManager(); // 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; this.httpManager.setProxyServer(url); this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this)); this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this)); 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; this.httpManager.setProxyServer(url); this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this)); this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this)); console.log(`🔧 Proxy server set to: ${url}`); } async request(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.request(options, interceptors); } async get(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.get(options, interceptors); } async post(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.post(options, interceptors); } async put(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.put(options, interceptors); } async patch(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.patch(options, interceptors); } async delete(options) { const interceptors = this.interceptorManager.getInterceptorsInternal(); return this.httpManager.delete(options, interceptors); } /** * Streaming HTTP request - supports AI model streaming output */ async streamRequest(options) { return this.streamManager.streamRequest(options); } /** * Cancel streaming request */ async cancelStream(options) { return this.streamManager.cancelStream(options); } async startSSE(options) { return this.sseManager.startSSE(options); } async stopSSE(options) { return this.sseManager.stopSSE(options); } async createSSEConnection(options) { return this.sseManager.createSSEConnection(options); } async closeSSEConnection(options) { return this.sseManager.closeSSEConnection(options); } async createWebSocketConnection(options) { return this.wsManager.createWebSocketConnection(options); } async closeWebSocketConnection(options) { return this.wsManager.closeWebSocketConnection(options); } async sendWebSocketMessage(options) { return this.wsManager.sendWebSocketMessage(options); } // ===== MCP Protocol Methods ===== async createMCPClient(options) { const connectionId = `mcp_${++this.connectionCounter}`; try { // Determine transport type and URL const transport = options.transport || 'streamablehttp'; // Get URL (support both new and legacy config) let url = options.url; if (!url && options.sseUrl) { // Backward compatibility: use sseUrl if url is not provided url = options.sseUrl; } if (!url) { throw new Error('URL is required for MCP client (provide either "url" or "sseUrl")'); } // Create transport layer let mcpTransport; if (transport === 'streamablehttp') { // Use new StreamableHTTP transport (recommended) throw new Error('StreamableHTTP transport should use mcpClientManager. Use @capacitor/cors-bypass-enhanced web managers directly.'); } else if (transport === 'sse' || options.sseUrl) { // Legacy SSE transport if (this.proxyServerUrl && this.utilsManager.isCrossOrigin(url)) { // Use proxy server const proxyUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`; mcpTransport = new sse_js.SSEClientTransport(new URL(proxyUrl)); } else { // Direct connection mcpTransport = new sse_js.SSEClientTransport(new URL(url)); } } else { throw new Error(`Unsupported transport type: ${transport}`); } // Create MCP client const client = new index_js.Client({ name: options.clientInfo.name, version: options.clientInfo.version, }, { capabilities: { roots: options.capabilities?.roots ? { listChanged: true } : undefined, sampling: options.capabilities?.sampling ? {} : undefined, } }); // Connect to server await client.connect(mcpTransport); // Store client and transport this.mcpClients.set(connectionId, client); this.mcpTransports.set(connectionId, mcpTransport); console.log(`✅ MCP client connected: ${connectionId}`); return { connectionId, status: 'connected', serverCapabilities: client.getServerCapabilities(), protocolVersion: '2025-03-26' }; } catch (error) { console.error(`❌ MCP client connection failed:`, 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) { const client = this.mcpClients.get(options.connectionId); if (!client) { throw new Error('MCP client not found'); } try { const result = await client.request({ method: options.request.method, params: options.request.params }); return result; } catch (error) { throw new Error(`Failed to send MCP sampling request: ${error}`); } } // ==================== Interceptor Management ==================== async addInterceptor(interceptor, options) { return this.interceptorManager.addInterceptor(interceptor, options); } async removeInterceptor(handle) { return this.interceptorManager.removeInterceptor(handle); } async removeAllInterceptors() { return this.interceptorManager.removeAllInterceptors(); } async getInterceptors() { return this.interceptorManager.getInterceptors(); } // ==================== Proxy Management ==================== /** * Set global proxy configuration * Note: On Web platform, proxy is handled through the CORS proxy server * The proxy config is stored and can be passed to the server for server-side proxying */ async setGlobalProxy(config) { this.globalProxyConfig = config; // If using a proxy server, we can configure it to use the specified proxy if (this.proxyServerUrl && config.enabled) { console.log(`🔧 [Web] Global proxy configured: ${config.type || 'http'}://${config.host}:${config.port}`); console.log('💡 Note: Web platform proxying requires server-side support.'); } } /** * Get current global proxy configuration */ async getGlobalProxy() { return this.globalProxyConfig; } /** * Clear global proxy configuration */ async clearGlobalProxy() { this.globalProxyConfig = null; this.proxyLastError = null; console.log('🔧 [Web] Global proxy configuration cleared'); } /** * Test proxy connection * On Web platform, this tests connectivity through the CORS proxy server */ async testProxy(config, testUrl) { const startTime = Date.now(); const url = testUrl || 'https://www.google.com'; if (!config.enabled || !config.host) { return { success: false, error: 'Proxy configuration is invalid or disabled', responseTime: 0 }; } try { // On Web, we can only test through our proxy server if (this.proxyServerUrl) { const response = await fetch(`${this.proxyServerUrl}/proxy`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, method: 'HEAD', proxy: { enabled: true, type: config.type || 'http', host: config.host, port: config.port, username: config.username, password: config.password } }) }); const responseTime = Date.now() - startTime; this.proxyRequestCount++; if (response.ok) { this.proxyLastSuccessTime = Date.now(); this.proxyLastError = null; return { success: true, responseTime, statusCode: response.status }; } else { const error = `HTTP ${response.status}`; this.proxyLastError = error; return { success: false, responseTime, statusCode: response.status, error }; } } else { // No proxy server available, test direct connection const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(url, { method: 'HEAD', mode: 'no-cors', signal: controller.signal }); clearTimeout(timeoutId); const responseTime = Date.now() - startTime; return { success: true, responseTime, statusCode: response.status || 0 }; } catch (fetchError) { clearTimeout(timeoutId); return { success: false, responseTime: Date.now() - startTime, error: fetchError.message || 'Connection failed' }; } } } catch (error) { this.proxyLastError = error.message; return { success: false, responseTime: Date.now() - startTime, error: error.message || 'Proxy test failed' }; } } /** * Get current proxy status */ async getProxyStatus() { return { active: this.globalProxyConfig?.enabled ?? false, config: this.globalProxyConfig ?? undefined, requestCount: this.proxyRequestCount, lastError: this.proxyLastError ?? undefined, lastSuccessTime: this.proxyLastSuccessTime ?? undefined }; } } var web = /*#__PURE__*/Object.freeze({ __proto__: null, CorsBypassWeb: CorsBypassWeb }); exports.CorsBypass = CorsBypass; //# sourceMappingURL=plugin.cjs.js.map