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.
238 lines (237 loc) • 9.19 kB
JavaScript
import { isCrossOrigin, createInterceptorContext } from './utils';
/**
* HTTP Request Manager
* Handles all HTTP requests with CORS bypass and interceptor support
*/
export 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;
}
}