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 lines 92.7 kB
{"version":3,"file":"plugin.mjs","sources":["esm/index.js","esm/web/utils.js","esm/web/http.js","esm/web/stream.js","esm/web/sse.js","esm/web/websocket.js","esm/web/interceptor.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst CorsBypass = registerPlugin('CorsBypass', {\n web: () => import('./web').then(m => new m.CorsBypassWeb()),\n});\nexport * from './definitions';\nexport { CorsBypass };\n","/**\n * Check if a URL is cross-origin\n */\nexport function isCrossOrigin(url) {\n try {\n const targetUrl = new URL(url);\n const currentUrl = new URL(window.location.href);\n return targetUrl.origin !== currentUrl.origin;\n }\n catch {\n return false;\n }\n}\n/**\n * Create interceptor context\n */\nexport function createInterceptorContext() {\n return {\n startTime: Date.now(),\n requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,\n retryCount: 0,\n data: {},\n };\n}\n/**\n * Utils Manager\n * Provides utility functions for the web plugin\n */\nexport class UtilsManager {\n /**\n * Check if a URL is cross-origin\n */\n isCrossOrigin(url) {\n return isCrossOrigin(url);\n }\n /**\n * Create interceptor context\n */\n createInterceptorContext() {\n return createInterceptorContext();\n }\n}\n","import { isCrossOrigin, createInterceptorContext } from './utils';\n/**\n * HTTP Request Manager\n * Handles all HTTP requests with CORS bypass and interceptor support\n */\nexport class HttpManager {\n constructor(proxyServerUrl) {\n this.proxyServerUrl = proxyServerUrl;\n }\n /**\n * Set custom proxy server URL\n */\n setProxyServer(url) {\n this.proxyServerUrl = url;\n console.log(`🔧 Proxy server set to: ${url}`);\n }\n /**\n * Make an HTTP request with CORS bypass and interceptor support\n */\n async request(options, interceptors) {\n const context = createInterceptorContext();\n try {\n // Execute request interceptors\n let modifiedOptions = await this.executeRequestInterceptors(options, context, interceptors);\n const { url, method = 'GET', headers = {}, data, params, timeout = 30000, responseType = 'json', followRedirects = true, } = modifiedOptions;\n // Build URL with query parameters\n let requestUrl = url;\n if (params) {\n const urlParams = new URLSearchParams(params);\n requestUrl += (url.includes('?') ? '&' : '?') + urlParams.toString();\n }\n // Use proxy server if available and URL is cross-origin\n let finalUrl = requestUrl;\n let fetchOptions = {\n method,\n headers,\n redirect: followRedirects ? 'follow' : 'manual',\n };\n if (this.proxyServerUrl && isCrossOrigin(requestUrl)) {\n console.log(`🔧 Using proxy server for: ${requestUrl}`);\n finalUrl = `${this.proxyServerUrl}/proxy/${encodeURIComponent(requestUrl)}`;\n }\n // Create AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n fetchOptions.signal = controller.signal;\n try {\n // Add body for methods that support it\n if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {\n if (typeof data === 'string') {\n fetchOptions.body = data;\n }\n else {\n fetchOptions.body = JSON.stringify(data);\n if (!headers['Content-Type']) {\n headers['Content-Type'] = 'application/json';\n }\n }\n }\n const response = await fetch(finalUrl, fetchOptions);\n clearTimeout(timeoutId);\n // Parse response based on responseType\n let responseData;\n switch (responseType) {\n case 'text':\n responseData = await response.text();\n break;\n case 'blob':\n responseData = await response.blob();\n break;\n case 'arraybuffer':\n responseData = await response.arrayBuffer();\n break;\n case 'json':\n default:\n try {\n responseData = await response.json();\n }\n catch {\n responseData = await response.text();\n }\n break;\n }\n // Convert Headers to plain object\n const responseHeaders = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n let httpResponse = {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n data: responseData,\n url: response.url,\n };\n // Execute response interceptors\n httpResponse = await this.executeResponseInterceptors(httpResponse, context, interceptors);\n return httpResponse;\n }\n catch (error) {\n clearTimeout(timeoutId);\n // Create HTTP error\n const httpError = {\n message: error instanceof Error ? error.message : 'Unknown error',\n config: modifiedOptions,\n originalError: error,\n };\n // Try error interceptors\n const interceptorResult = await this.executeErrorInterceptors(httpError, context, interceptors);\n if (interceptorResult) {\n return interceptorResult;\n }\n // If proxy failed and we have a proxy server, try direct request as fallback\n if (this.proxyServerUrl && finalUrl.includes(this.proxyServerUrl)) {\n console.warn(`⚠️ Proxy request failed, trying direct request: ${error}`);\n return this.request({ ...options, url: requestUrl }, interceptors);\n }\n throw httpError;\n }\n }\n catch (error) {\n // Handle errors from interceptors or other sources\n if (error.config) {\n // Already an HttpError\n throw error;\n }\n // Create HTTP error\n const httpError = {\n message: error instanceof Error ? error.message : 'Unknown error',\n config: options,\n originalError: error,\n };\n throw httpError;\n }\n }\n /**\n * Make a GET request\n */\n async get(options, interceptors) {\n return this.request({ ...options, method: 'GET' }, interceptors);\n }\n /**\n * Make a POST request\n */\n async post(options, interceptors) {\n return this.request({ ...options, method: 'POST' }, interceptors);\n }\n /**\n * Make a PUT request\n */\n async put(options, interceptors) {\n return this.request({ ...options, method: 'PUT' }, interceptors);\n }\n /**\n * Make a PATCH request\n */\n async patch(options, interceptors) {\n return this.request({ ...options, method: 'PATCH' }, interceptors);\n }\n /**\n * Make a DELETE request\n */\n async delete(options, interceptors) {\n return this.request({ ...options, method: 'DELETE' }, interceptors);\n }\n /**\n * Execute request interceptors\n */\n async executeRequestInterceptors(config, context, interceptors) {\n let modifiedConfig = { ...config };\n for (const entry of interceptors) {\n if (!entry.enabled || !entry.interceptor.onRequest) {\n continue;\n }\n // Check scope if defined\n if (entry.options.scope) {\n const { urlPattern, methods } = entry.options.scope;\n if (urlPattern && !new RegExp(urlPattern).test(modifiedConfig.url)) {\n continue;\n }\n if (methods && modifiedConfig.method && !methods.includes(modifiedConfig.method)) {\n continue;\n }\n }\n try {\n modifiedConfig = await Promise.resolve(entry.interceptor.onRequest(modifiedConfig));\n }\n catch (error) {\n console.error(`[Interceptor ${entry.id}] Request interceptor error:`, error);\n throw error;\n }\n }\n return modifiedConfig;\n }\n /**\n * Execute response interceptors\n */\n async executeResponseInterceptors(response, context, interceptors) {\n let modifiedResponse = { ...response };\n for (const entry of interceptors) {\n if (!entry.enabled || !entry.interceptor.onResponse) {\n continue;\n }\n try {\n modifiedResponse = await Promise.resolve(entry.interceptor.onResponse(modifiedResponse));\n }\n catch (error) {\n console.error(`[Interceptor ${entry.id}] Response interceptor error:`, error);\n throw error;\n }\n }\n return modifiedResponse;\n }\n /**\n * Execute error interceptors\n */\n async executeErrorInterceptors(error, context, interceptors) {\n for (const entry of interceptors) {\n if (!entry.enabled || !entry.interceptor.onError) {\n continue;\n }\n try {\n const result = await Promise.resolve(entry.interceptor.onError(error));\n if (result) {\n // Interceptor returned a response, use it\n return result;\n }\n }\n catch (interceptorError) {\n console.error(`[Interceptor ${entry.id}] Error interceptor error:`, interceptorError);\n // Continue to next interceptor\n }\n }\n // No interceptor handled the error, return void\n return;\n }\n}\n","import { isCrossOrigin } from './utils';\n/**\n * Stream Manager\n * Handles streaming HTTP requests with CORS bypass\n */\nexport class StreamManager {\n constructor(proxyServerUrl, notifyListeners) {\n this.streamControllers = new Map();\n this.streamCounter = 0;\n this.proxyServerUrl = proxyServerUrl;\n this.notifyListeners = notifyListeners;\n }\n /**\n * Make a streaming HTTP request - supports AI model streaming output\n */\n async streamRequest(options) {\n const streamId = `stream_${++this.streamCounter}`;\n const { url, method = 'POST', headers = {}, data, params, timeout = 60000, followRedirects = true, } = options;\n // Build URL with query parameters\n let requestUrl = url;\n if (params) {\n const urlParams = new URLSearchParams(params);\n requestUrl += (url.includes('?') ? '&' : '?') + urlParams.toString();\n }\n // Use proxy server if available and URL is cross-origin\n let finalUrl = requestUrl;\n if (this.proxyServerUrl && isCrossOrigin(requestUrl)) {\n console.log(`🔧 Using proxy server for streaming: ${requestUrl}`);\n finalUrl = `${this.proxyServerUrl}/proxy/${encodeURIComponent(requestUrl)}`;\n }\n // Create AbortController for this stream\n const controller = new AbortController();\n this.streamControllers.set(streamId, controller);\n // Set timeout\n const timeoutId = setTimeout(() => {\n controller.abort();\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'error',\n error: 'Request timeout',\n });\n }, timeout);\n try {\n // Prepare fetch options\n const fetchOptions = {\n method,\n headers: {\n ...headers,\n 'Accept': 'text/event-stream, application/json, text/plain, */*',\n },\n signal: controller.signal,\n redirect: followRedirects ? 'follow' : 'manual',\n };\n // Add body for methods that support it\n if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {\n if (typeof data === 'string') {\n fetchOptions.body = data;\n }\n else {\n fetchOptions.body = JSON.stringify(data);\n if (!headers['Content-Type']) {\n fetchOptions.headers = {\n ...fetchOptions.headers,\n 'Content-Type': 'application/json',\n };\n }\n }\n }\n console.log(`🌊 Starting stream request: ${streamId} to ${finalUrl}`);\n // Start the fetch request\n fetch(finalUrl, fetchOptions)\n .then(async (response) => {\n clearTimeout(timeoutId);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n // Convert headers to plain object\n const responseHeaders = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n // Notify stream started\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'started',\n statusCode: response.status,\n headers: responseHeaders,\n });\n // Read the stream\n const reader = response.body?.getReader();\n const decoder = new TextDecoder();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n // Stream completed\n this.notifyListeners('streamChunk', {\n streamId,\n data: '',\n done: true,\n });\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'completed',\n });\n this.streamControllers.delete(streamId);\n break;\n }\n // Decode and send chunk\n const chunk = decoder.decode(value, { stream: true });\n this.notifyListeners('streamChunk', {\n streamId,\n data: chunk,\n done: false,\n });\n }\n }\n catch (error) {\n if (error.name === 'AbortError') {\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'cancelled',\n });\n }\n else {\n throw error;\n }\n }\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n this.notifyListeners('streamChunk', {\n streamId,\n data: '',\n done: true,\n error: errorMessage,\n });\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'error',\n error: errorMessage,\n });\n this.streamControllers.delete(streamId);\n });\n return { streamId };\n }\n catch (error) {\n clearTimeout(timeoutId);\n this.streamControllers.delete(streamId);\n throw error;\n }\n }\n /**\n * Cancel a streaming request\n */\n async cancelStream(options) {\n const { streamId } = options;\n const controller = this.streamControllers.get(streamId);\n if (controller) {\n controller.abort();\n this.streamControllers.delete(streamId);\n this.notifyListeners('streamStatus', {\n streamId,\n status: 'cancelled',\n });\n }\n }\n /**\n * Get all active stream controllers\n */\n getStreamControllers() {\n return this.streamControllers;\n }\n}\n","import { isCrossOrigin } from './utils';\n/**\n * SSE Manager\n * Handles Server-Sent Events connections with CORS bypass\n */\nexport class SSEManager {\n constructor(proxyServerUrl, notifyListeners) {\n this.sseConnections = new Map();\n this.connectionCounter = 0;\n this.proxyServerUrl = proxyServerUrl;\n this.notifyListeners = notifyListeners;\n }\n /**\n * Start listening to Server-Sent Events (legacy method)\n */\n async startSSE(options) {\n const connectionId = `sse_${++this.connectionCounter}`;\n const { url, headers = {}, withCredentials = false, reconnectTimeout = 3000 } = options;\n // Use proxy server for SSE if available and cross-origin\n let sseUrl = url;\n if (this.proxyServerUrl && isCrossOrigin(url)) {\n console.log(`🔧 Using SSE proxy for: ${url}`);\n sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;\n }\n const eventSource = new EventSource(sseUrl);\n this.sseConnections.set(connectionId, eventSource);\n eventSource.onopen = () => {\n this.notifyListeners('sseOpen', {\n connectionId,\n status: 'connected',\n });\n };\n eventSource.onmessage = (event) => {\n this.notifyListeners('sseMessage', {\n connectionId,\n type: 'message',\n data: event.data,\n id: event.lastEventId,\n });\n };\n eventSource.onerror = () => {\n this.notifyListeners('sseError', {\n connectionId,\n error: 'Connection error',\n });\n };\n return { connectionId };\n }\n /**\n * Stop listening to Server-Sent Events\n */\n async stopSSE(options) {\n const { connectionId } = options;\n const connection = this.sseConnections.get(connectionId);\n if (connection) {\n connection.close();\n this.sseConnections.delete(connectionId);\n this.notifyListeners('sseClose', {\n connectionId,\n status: 'disconnected',\n });\n }\n }\n /**\n * Create a Server-Sent Events connection with reconnection support\n */\n async createSSEConnection(options) {\n const connectionId = `sse_${++this.connectionCounter}`;\n const { url, headers = {}, reconnect = {} } = options;\n const { enabled: reconnectEnabled = true, initialDelay = 1000, maxDelay = 30000, maxAttempts = 10, } = reconnect;\n let retryCount = 0;\n let retryDelay = initialDelay;\n const createConnection = () => {\n // Use proxy server for SSE if available and cross-origin\n let sseUrl = url;\n if (this.proxyServerUrl && isCrossOrigin(url)) {\n console.log(`🔧 Using SSE proxy for: ${url}`);\n sseUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;\n }\n const eventSource = new EventSource(sseUrl);\n this.sseConnections.set(connectionId, eventSource);\n eventSource.onopen = () => {\n retryCount = 0;\n retryDelay = initialDelay;\n this.notifyListeners('sseConnectionChange', {\n connectionId,\n status: 'connected',\n });\n };\n eventSource.onmessage = (event) => {\n this.notifyListeners('sseMessage', {\n connectionId,\n type: 'message',\n data: event.data,\n id: event.lastEventId,\n });\n };\n eventSource.onerror = () => {\n this.notifyListeners('sseConnectionChange', {\n connectionId,\n status: 'error',\n error: 'Connection error',\n });\n if (reconnectEnabled && retryCount < maxAttempts) {\n setTimeout(() => {\n retryCount++;\n retryDelay = Math.min(retryDelay * 2, maxDelay);\n eventSource.close();\n createConnection();\n }, retryDelay);\n }\n else {\n this.sseConnections.delete(connectionId);\n }\n };\n // Add custom event listeners\n eventSource.addEventListener('error', (event) => {\n this.notifyListeners('sseMessage', {\n connectionId,\n type: 'error',\n data: 'Connection error',\n });\n });\n };\n this.notifyListeners('sseConnectionChange', {\n connectionId,\n status: 'connecting',\n });\n createConnection();\n return {\n connectionId,\n status: 'connecting',\n };\n }\n /**\n * Close an SSE connection\n */\n async closeSSEConnection(options) {\n const { connectionId } = options;\n const connection = this.sseConnections.get(connectionId);\n if (connection) {\n connection.close();\n this.sseConnections.delete(connectionId);\n this.notifyListeners('sseConnectionChange', {\n connectionId,\n status: 'disconnected',\n });\n }\n }\n /**\n * Get all active SSE connections\n */\n getSSEConnections() {\n return this.sseConnections;\n }\n}\n","/**\n * WebSocket Manager\n * Handles WebSocket connections with CORS bypass\n */\nexport class WebSocketManager {\n constructor(notifyListeners) {\n this.wsConnections = new Map();\n this.connectionCounter = 0;\n this.notifyListeners = notifyListeners;\n }\n /**\n * Create a WebSocket connection\n */\n async createWebSocketConnection(options) {\n const connectionId = `ws_${++this.connectionCounter}`;\n const { url, protocols, headers, timeout = 10000 } = options;\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url, protocols);\n this.wsConnections.set(connectionId, ws);\n const timeoutId = setTimeout(() => {\n ws.close();\n this.wsConnections.delete(connectionId);\n reject(new Error('WebSocket connection timeout'));\n }, timeout);\n ws.onopen = () => {\n clearTimeout(timeoutId);\n this.notifyListeners('webSocketConnectionChange', {\n connectionId,\n status: 'connected',\n });\n resolve({\n connectionId,\n status: 'connected',\n });\n };\n ws.onmessage = (event) => {\n this.notifyListeners('webSocketMessage', {\n connectionId,\n data: event.data,\n type: typeof event.data === 'string' ? 'text' : 'binary',\n });\n };\n ws.onerror = () => {\n clearTimeout(timeoutId);\n this.notifyListeners('webSocketConnectionChange', {\n connectionId,\n status: 'error',\n error: 'WebSocket connection error',\n });\n };\n ws.onclose = () => {\n this.wsConnections.delete(connectionId);\n this.notifyListeners('webSocketConnectionChange', {\n connectionId,\n status: 'disconnected',\n });\n };\n this.notifyListeners('webSocketConnectionChange', {\n connectionId,\n status: 'connecting',\n });\n });\n }\n /**\n * Close a WebSocket connection\n */\n async closeWebSocketConnection(options) {\n const { connectionId } = options;\n const connection = this.wsConnections.get(connectionId);\n if (connection) {\n connection.close();\n this.wsConnections.delete(connectionId);\n }\n }\n /**\n * Send data through WebSocket\n */\n async sendWebSocketMessage(options) {\n const { connectionId, message } = options;\n const connection = this.wsConnections.get(connectionId);\n if (connection && connection.readyState === WebSocket.OPEN) {\n connection.send(message);\n }\n else {\n throw new Error('WebSocket connection not found or not open');\n }\n }\n /**\n * Get all active WebSocket connections\n */\n getWebSocketConnections() {\n return this.wsConnections;\n }\n}\n","import { createInterceptorContext } from './utils';\n/**\n * Interceptor Manager\n * Handles request/response interceptors with priority and scope support\n */\nexport class InterceptorManager {\n constructor() {\n this.interceptors = [];\n this.interceptorCounter = 0;\n }\n /**\n * Add an interceptor to the request/response chain\n */\n async addInterceptor(interceptor, options) {\n const id = `interceptor_${++this.interceptorCounter}`;\n const interceptorEntry = {\n id,\n interceptor,\n options: options || {},\n enabled: options?.enabled !== false,\n };\n this.interceptors.push(interceptorEntry);\n // Sort by priority (higher priority first)\n this.interceptors.sort((a, b) => {\n const priorityA = a.options.priority || 0;\n const priorityB = b.options.priority || 0;\n return priorityB - priorityA;\n });\n const handle = {\n id,\n name: options?.name,\n remove: () => {\n this.removeInterceptor(id);\n },\n enable: () => {\n const entry = this.interceptors.find(i => i.id === id);\n if (entry)\n entry.enabled = true;\n },\n disable: () => {\n const entry = this.interceptors.find(i => i.id === id);\n if (entry)\n entry.enabled = false;\n },\n isEnabled: () => {\n const entry = this.interceptors.find(i => i.id === id);\n return entry ? entry.enabled : false;\n },\n };\n return handle;\n }\n /**\n * Remove an interceptor by handle or ID\n */\n async removeInterceptor(handle) {\n const id = typeof handle === 'string' ? handle : handle.id;\n const index = this.interceptors.findIndex(i => i.id === id);\n if (index !== -1) {\n this.interceptors.splice(index, 1);\n }\n }\n /**\n * Remove all interceptors\n */\n async removeAllInterceptors() {\n this.interceptors = [];\n }\n /**\n * Get all registered interceptors\n */\n async getInterceptors() {\n return this.interceptors.map(entry => ({\n id: entry.id,\n name: entry.options.name,\n remove: () => this.removeInterceptor(entry.id),\n enable: () => {\n entry.enabled = true;\n },\n disable: () => {\n entry.enabled = false;\n },\n isEnabled: () => entry.enabled,\n }));\n }\n /**\n * Execute request interceptors\n */\n async executeRequestInterceptors(config) {\n const context = createInterceptorContext();\n let modifiedConfig = { ...config };\n for (const entry of this.interceptors) {\n if (!entry.enabled || !entry.interceptor.onRequest) {\n continue;\n }\n // Check scope if defined\n if (entry.options.scope) {\n const { urlPattern, methods } = entry.options.scope;\n if (urlPattern && !new RegExp(urlPattern).test(modifiedConfig.url)) {\n continue;\n }\n if (methods && modifiedConfig.method && !methods.includes(modifiedConfig.method)) {\n continue;\n }\n }\n try {\n modifiedConfig = await Promise.resolve(entry.interceptor.onRequest(modifiedConfig));\n }\n catch (error) {\n console.error(`[Interceptor ${entry.id}] Request interceptor error:`, error);\n throw error;\n }\n }\n return modifiedConfig;\n }\n /**\n * Execute response interceptors\n */\n async executeResponseInterceptors(response) {\n const context = createInterceptorContext();\n let modifiedResponse = { ...response };\n for (const entry of this.interceptors) {\n if (!entry.enabled || !entry.interceptor.onResponse) {\n continue;\n }\n try {\n modifiedResponse = await Promise.resolve(entry.interceptor.onResponse(modifiedResponse));\n }\n catch (error) {\n console.error(`[Interceptor ${entry.id}] Response interceptor error:`, error);\n throw error;\n }\n }\n return modifiedResponse;\n }\n /**\n * Execute error interceptors\n */\n async executeErrorInterceptors(error) {\n const context = createInterceptorContext();\n for (const entry of this.interceptors) {\n if (!entry.enabled || !entry.interceptor.onError) {\n continue;\n }\n try {\n const result = await Promise.resolve(entry.interceptor.onError(error));\n if (result) {\n // Interceptor returned a response, use it\n return result;\n }\n }\n catch (interceptorError) {\n console.error(`[Interceptor ${entry.id}] Error interceptor error:`, interceptorError);\n // Continue to next interceptor\n }\n }\n // No interceptor handled the error, return void\n return;\n }\n /**\n * Get all interceptors (internal format)\n */\n getInterceptorsInternal() {\n return this.interceptors;\n }\n}\n","import { WebPlugin } from '@capacitor/core';\n// MCP SDK imports (ESM)\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';\n// Import modular managers\nimport { UtilsManager } from './web/utils';\nimport { HttpManager } from './web/http';\nimport { StreamManager } from './web/stream';\nimport { SSEManager } from './web/sse';\nimport { WebSocketManager } from './web/websocket';\nimport { InterceptorManager } from './web/interceptor';\nexport class CorsBypassWeb extends WebPlugin {\n constructor() {\n super();\n this.proxyServerUrl = null;\n this.globalProxyConfig = null;\n this.proxyRequestCount = 0;\n this.proxyLastSuccessTime = null;\n this.proxyLastError = null;\n // MCP specific\n this.mcpClients = new Map();\n this.mcpTransports = new Map();\n this.connectionCounter = 0;\n // Initialize managers\n this.utilsManager = new UtilsManager();\n this.httpManager = new HttpManager(this.proxyServerUrl);\n this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n this.wsManager = new WebSocketManager(this.notifyListeners.bind(this));\n this.interceptorManager = new InterceptorManager();\n // Try to detect if a proxy server is available\n this.detectProxyServer();\n }\n async detectProxyServer() {\n const possibleUrls = [\n 'http://localhost:3002',\n 'http://127.0.0.1:3002',\n 'http://localhost:3001',\n 'http://127.0.0.1:3001',\n 'http://localhost:8080',\n 'http://127.0.0.1:8080'\n ];\n for (const url of possibleUrls) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 1000);\n const response = await fetch(`${url}/health`, {\n method: 'GET',\n signal: controller.signal\n });\n clearTimeout(timeoutId);\n if (response.ok) {\n this.proxyServerUrl = url;\n this.httpManager.setProxyServer(url);\n this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n console.log(`🔧 CORS Proxy server detected at: ${url}`);\n break;\n }\n }\n catch (error) {\n // Ignore errors, continue checking\n }\n }\n if (!this.proxyServerUrl) {\n console.warn('⚠️ No CORS proxy server detected. Some requests may fail due to CORS.');\n console.log('💡 To enable full functionality, run: node web-proxy-server.js');\n }\n }\n /**\n * Set custom proxy server URL\n */\n setProxyServer(url) {\n this.proxyServerUrl = url;\n this.httpManager.setProxyServer(url);\n this.streamManager = new StreamManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n this.sseManager = new SSEManager(this.proxyServerUrl, this.notifyListeners.bind(this));\n console.log(`🔧 Proxy server set to: ${url}`);\n }\n async request(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.request(options, interceptors);\n }\n async get(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.get(options, interceptors);\n }\n async post(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.post(options, interceptors);\n }\n async put(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.put(options, interceptors);\n }\n async patch(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.patch(options, interceptors);\n }\n async delete(options) {\n const interceptors = this.interceptorManager.getInterceptorsInternal();\n return this.httpManager.delete(options, interceptors);\n }\n /**\n * Streaming HTTP request - supports AI model streaming output\n */\n async streamRequest(options) {\n return this.streamManager.streamRequest(options);\n }\n /**\n * Cancel streaming request\n */\n async cancelStream(options) {\n return this.streamManager.cancelStream(options);\n }\n async startSSE(options) {\n return this.sseManager.startSSE(options);\n }\n async stopSSE(options) {\n return this.sseManager.stopSSE(options);\n }\n async createSSEConnection(options) {\n return this.sseManager.createSSEConnection(options);\n }\n async closeSSEConnection(options) {\n return this.sseManager.closeSSEConnection(options);\n }\n async createWebSocketConnection(options) {\n return this.wsManager.createWebSocketConnection(options);\n }\n async closeWebSocketConnection(options) {\n return this.wsManager.closeWebSocketConnection(options);\n }\n async sendWebSocketMessage(options) {\n return this.wsManager.sendWebSocketMessage(options);\n }\n // ===== MCP Protocol Methods =====\n async createMCPClient(options) {\n const connectionId = `mcp_${++this.connectionCounter}`;\n try {\n // Determine transport type and URL\n const transport = options.transport || 'streamablehttp';\n // Get URL (support both new and legacy config)\n let url = options.url;\n if (!url && options.sseUrl) {\n // Backward compatibility: use sseUrl if url is not provided\n url = options.sseUrl;\n }\n if (!url) {\n throw new Error('URL is required for MCP client (provide either \"url\" or \"sseUrl\")');\n }\n // Create transport layer\n let mcpTransport;\n if (transport === 'streamablehttp') {\n // Use new StreamableHTTP transport (recommended)\n throw new Error('StreamableHTTP transport should use mcpClientManager. Use @capacitor/cors-bypass-enhanced web managers directly.');\n }\n else if (transport === 'sse' || options.sseUrl) {\n // Legacy SSE transport\n if (this.proxyServerUrl && this.utilsManager.isCrossOrigin(url)) {\n // Use proxy server\n const proxyUrl = `${this.proxyServerUrl}/sse-proxy/${encodeURIComponent(url)}`;\n mcpTransport = new SSEClientTransport(new URL(proxyUrl));\n }\n else {\n // Direct connection\n mcpTransport = new SSEClientTransport(new URL(url));\n }\n }\n else {\n throw new Error(`Unsupported transport type: ${transport}`);\n }\n // Create MCP client\n const client = new Client({\n name: options.clientInfo.name,\n version: options.clientInfo.version,\n }, {\n capabilities: {\n roots: options.capabilities?.roots ? { listChanged: true } : undefined,\n sampling: options.capabilities?.sampling ? {} : undefined,\n }\n });\n // Connect to server\n await client.connect(mcpTransport);\n // Store client and transport\n this.mcpClients.set(connectionId, client);\n this.mcpTransports.set(connectionId, mcpTransport);\n console.log(`✅ MCP client connected: ${connectionId}`);\n return {\n connectionId,\n status: 'connected',\n serverCapabilities: client.getServerCapabilities(),\n protocolVersion: '2025-03-26'\n };\n }\n catch (error) {\n console.error(`❌ MCP client connection failed:`, error);\n throw new Error(`Failed to create MCP client: ${error}`);\n }\n }\n async listMCPResources(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.listResources(options.cursor ? { cursor: options.cursor } : {});\n return {\n resources: result.resources || [],\n nextCursor: result.nextCursor\n };\n }\n catch (error) {\n throw new Error(`Failed to list MCP resources: ${error}`);\n }\n }\n async readMCPResource(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.readResource({ uri: options.uri });\n return {\n uri: options.uri,\n mimeType: result.contents?.[0]?.mimeType || 'text/plain',\n text: result.contents?.[0]?.text || '',\n blob: result.contents?.[0]?.data\n };\n }\n catch (error) {\n throw new Error(`Failed to read MCP resource: ${error}`);\n }\n }\n async listMCPTools(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.listTools(options.cursor ? { cursor: options.cursor } : {});\n return {\n tools: result.tools || [],\n nextCursor: result.nextCursor\n };\n }\n catch (error) {\n throw new Error(`Failed to list MCP tools: ${error}`);\n }\n }\n async callMCPTool(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.callTool({\n name: options.name,\n arguments: options.arguments || {}\n });\n return {\n content: result.content || [],\n isError: result.isError || false\n };\n }\n catch (error) {\n throw new Error(`Failed to call MCP tool: ${error}`);\n }\n }\n async listMCPPrompts(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.listPrompts(options.cursor ? { cursor: options.cursor } : {});\n return {\n prompts: result.prompts || [],\n nextCursor: result.nextCursor\n };\n }\n catch (error) {\n throw new Error(`Failed to list MCP prompts: ${error}`);\n }\n }\n async getMCPPrompt(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.getPrompt({\n name: options.name,\n arguments: options.arguments || {}\n });\n return {\n description: result.description,\n messages: result.messages || []\n };\n }\n catch (error) {\n throw new Error(`Failed to get MCP prompt: ${error}`);\n }\n }\n async sendMCPSampling(options) {\n const client = this.mcpClients.get(options.connectionId);\n if (!client) {\n throw new Error('MCP client not found');\n }\n try {\n const result = await client.request({\n method: options.request.method,\n params: options.request.params\n });\n return result;\n }\n catch (error) {\n throw new Error(`Failed to send MCP sampling request: ${error}`);\n }\n }\n // ==================== Interceptor Management ====================\n async addInterceptor(interceptor, options) {\n return this.interceptorManager.addInterceptor(interceptor, options);\n }\n async removeInterceptor(handle) {\n return this.interceptorManager.removeInterceptor(handle);\n }\n async removeAllInterceptors() {\n return this.interceptorManager.removeAllInterceptors();\n }\n async getInterceptors() {\n return this.interceptorManager.getInterceptors();\n }\n // ==================== Proxy Management ====================\n /**\n * Set global proxy configuration\n * Note: On Web platform, proxy is handled through the CORS proxy server\n * The proxy config is stored and can be passed to the server for server-side proxying\n */\n async setGlobalProxy(config) {\n this.globalProxyConfig = config;\n // If using a proxy server, we can configure it to use the specified proxy\n if (this.proxyServerUrl && config.enabled) {\n console.log(`🔧 [Web] Global proxy configured: ${config.type || 'http'}://${config.host}:${config.port}`);\n console.log('💡 Note: Web platform proxying requires server-side support.');\n }\n }\n /**\n * Get current global proxy configuration\n */\n async getGlobalProxy() {\n return this.globalProxyConfig;\n }\n /**\n * Clear global proxy configuration\n */\n async clearGlobalProxy() {\n this.globalProxyConfig = null;\n this.proxyLastError = null;\n console.log('🔧 [Web] Global proxy configuration cleared');\n }\n /**\n * Test proxy connection\n * On Web platform, this tests connectivity through the CORS proxy server\n */\n async testProxy(config, testUrl) {\n const startTime = Date.now();\n const url = testUrl || 'https://www.google.com';\n if (!config.enabled || !config.host) {\n return {\n success: false,\n error: 'Proxy configuration is invalid or disabled',\n responseTime: 0\n };\n }\n try {\n // On Web, we can only test through our proxy server\n if (this.proxyServerUrl) {\n const response = await fetch(`${this.proxyServerUrl}/proxy`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n url,\n method: 'HEAD',\n proxy: {\n enabled: true,\n type: config.type || 'http',\n host: config.host,\n port: config.port,\n username: config.username,\n password: config.password\n }\n })\n });\n const responseTime = Date.now() - startTime;\n this.proxyRequestCount++;\n if (response.ok) {\n this.proxyLastSuccessTime = Date.now();\n this.proxyLastError = null;\n return {\n success: true,\n responseTime,\n statusCode: response.status\n };\n }\n else {\n const error = `HTTP ${response.status}`;\n this.proxyLastError = error;\n return {\n success: false,\n responseTime,\n statusCode: response.status,\n error\n };\n }\n }\n else {\n // No proxy server available, test direct connection\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000);\n try {\n const response = await fetch(url, {\n method: 'HEAD',\n mode: 'no-cors',\n signal: controller.signal\n });\n clearTimeout(timeoutId);\n const responseTime = Date.now() - startTime;\n return {\n success: true,\n responseTime,\n statusCode: response.status || 0\n };\n }\n catch (fetchError) {\n clearTimeout(timeoutId);\n