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.
363 lines (321 loc) • 9.95 kB
text/typescript
import type {
HttpRequestOptions,
HttpResponse,
HttpError,
InterceptorContext,
} from '../definitions';
import { isCrossOrigin, createInterceptorContext } from './utils';
/**
* HTTP Request Manager
* Handles all HTTP requests with CORS bypass and interceptor support
*/
export class HttpManager {
private proxyServerUrl: string | null;
constructor(proxyServerUrl: string | null) {
this.proxyServerUrl = proxyServerUrl;
}
/**
* Set custom proxy server URL
*/
setProxyServer(url: string): void {
this.proxyServerUrl = url;
console.log(`🔧 Proxy server set to: ${url}`);
}
/**
* Make an HTTP request with CORS bypass and interceptor support
*/
async request(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
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: RequestInit = {
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: any;
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: { [key: string]: string } = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
let httpResponse: 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: 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 as any).config) {
// Already an HttpError
throw error;
}
// Create HTTP error
const httpError: HttpError = {
message: error instanceof Error ? error.message : 'Unknown error',
config: options,
originalError: error,
};
throw httpError;
}
}
/**
* Make a GET request
*/
async get(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
return this.request({ ...options, method: 'GET' }, interceptors);
}
/**
* Make a POST request
*/
async post(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
return this.request({ ...options, method: 'POST' }, interceptors);
}
/**
* Make a PUT request
*/
async put(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
return this.request({ ...options, method: 'PUT' }, interceptors);
}
/**
* Make a PATCH request
*/
async patch(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
return this.request({ ...options, method: 'PATCH' }, interceptors);
}
/**
* Make a DELETE request
*/
async delete(
options: HttpRequestOptions,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
return this.request({ ...options, method: 'DELETE' }, interceptors);
}
/**
* Execute request interceptors
*/
private async executeRequestInterceptors(
config: HttpRequestOptions,
context: InterceptorContext,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpRequestOptions> {
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
*/
private async executeResponseInterceptors(
response: HttpResponse,
context: InterceptorContext,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse> {
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
*/
private async executeErrorInterceptors(
error: HttpError,
context: InterceptorContext,
interceptors: Array<{
id: string;
interceptor: any;
options: any;
enabled: boolean;
}>
): Promise<HttpResponse | void> {
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;
}
}