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.

363 lines (321 loc) 9.95 kB
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; } }