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
JavaScript
'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