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.
482 lines (415 loc) • 12.2 kB
text/typescript
/**
* Cache Interceptor
* 缓存拦截器实现
*/
import type {
Interceptor,
HttpRequestOptions,
HttpResponse,
HttpError,
} from '../definitions';
import type {
CacheOptions,
CacheStrategy,
CacheEntry,
CacheInterceptorOptions,
OfflineOptions,
} from '../types/cache';
import { createCacheManager, type CacheManager } from './cache';
/**
* 缓存拦截器类
*/
export class CacheInterceptor implements Interceptor {
private cacheManager: CacheManager;
private options: CacheInterceptorOptions;
private offlineOptions?: OfflineOptions;
constructor(options?: CacheInterceptorOptions, offlineOptions?: OfflineOptions) {
this.options = {
strategy: 'network-first',
maxAge: 5 * 60 * 1000, // 5分钟
enabled: true,
compress: false,
storage: 'memory',
...options,
};
this.offlineOptions = offlineOptions;
this.cacheManager = createCacheManager(this.options);
}
/**
* 请求拦截器
*/
async onRequest(config: HttpRequestOptions): Promise<HttpRequestOptions> {
// 如果缓存未启用,直接返回
if (!this.options.enabled) {
return config;
}
// 检查是否应该排除此请求
if (this.shouldExclude(config)) {
return config;
}
// 检查是否离线
const isOffline = await this.checkOffline();
if (isOffline && this.offlineOptions?.enabled) {
return this.handleOfflineRequest(config);
}
// 根据策略处理缓存
const strategy = this.options.strategy || 'network-first';
const cacheKey = this.generateCacheKey(config);
switch (strategy) {
case 'cache-first':
case 'cache-only':
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
// 缓存命中,调用钩子
if (this.options.onCacheHit) {
await this.options.onCacheHit(cached);
}
// 返回缓存的响应(通过抛出特殊错误来中断请求)
throw {
__cacheHit: true,
response: this.entryToResponse(cached),
};
}
// cache-only 策略下,如果没有缓存则失败
if (strategy === 'cache-only') {
throw new Error('Cache miss in cache-only mode');
}
break;
case 'stale-while-revalidate':
const staleCache = await this.cacheManager.get(cacheKey);
if (staleCache) {
// 返回缓存,但在后台更新
setTimeout(() => {
this.backgroundRevalidate(config, cacheKey);
}, 0);
throw {
__cacheHit: true,
response: this.entryToResponse(staleCache),
};
}
break;
}
// 缓存未命中,调用钩子
if (this.options.onCacheMiss) {
await this.options.onCacheMiss(cacheKey);
}
return config;
}
/**
* 响应拦截器
*/
async onResponse(response: HttpResponse): Promise<HttpResponse> {
// 如果缓存未启用,直接返回
if (!this.options.enabled) {
return response;
}
// 检查是否应该排除此请求
const config = (response as any).config || {};
if (this.shouldExclude(config)) {
return response;
}
// 只缓存成功的响应
if (response.status >= 200 && response.status < 300) {
const cacheKey = this.generateCacheKey(config);
// 调用缓存前钩子
let dataToCache = response.data;
if (this.options.beforeCache) {
dataToCache = await this.options.beforeCache(response);
}
// 创建缓存条目
const entry: CacheEntry = {
key: cacheKey,
data: dataToCache,
headers: response.headers,
status: response.status,
timestamp: Date.now(),
expiresAt: Date.now() + (this.options.maxAge || 300000),
size: this.calculateSize(dataToCache),
compressed: this.options.compress,
metadata: {
url: config.url,
method: config.method || 'GET',
etag: response.headers['etag'],
lastModified: response.headers['last-modified'],
},
};
// 如果启用压缩,压缩数据
if (this.options.compress) {
entry.data = await this.compressData(entry.data);
}
// 保存到缓存
await this.cacheManager.set(cacheKey, entry);
// 调用缓存后钩子
if (this.options.afterCache) {
await this.options.afterCache(entry);
}
}
return response;
}
/**
* 错误拦截器
*/
async onError(error: HttpError): Promise<void | HttpResponse> {
// 检查是否是缓存命中的特殊错误
if ((error as any).__cacheHit) {
return (error as any).response;
}
// 如果缓存未启用,直接抛出错误
if (!this.options.enabled) {
throw error;
}
// 网络错误时,尝试使用缓存
if (!error.status || error.status >= 500) {
const cacheKey = this.generateCacheKey(error.config);
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
console.log('[Cache] Using cached response due to network error');
return this.entryToResponse(cached);
}
}
// 离线模式处理
const isOffline = await this.checkOffline();
if (isOffline && this.offlineOptions?.enabled) {
return this.handleOfflineError(error);
}
throw error;
}
/**
* 生成缓存键
*/
private generateCacheKey(config: HttpRequestOptions): string {
if (this.options.keyGenerator) {
return this.options.keyGenerator(config.url, config.method || 'GET', config.params);
}
const method = config.method || 'GET';
let url = config.url;
// 如果排除查询参数
if (this.options.exclude?.query) {
url = url.split('?')[0];
}
// 包含参数
const params = config.params ? JSON.stringify(config.params) : '';
return `${method}:${url}:${params}`;
}
/**
* 检查是否应该排除此请求
*/
private shouldExclude(config: HttpRequestOptions): boolean {
const exclude = this.options.exclude;
if (!exclude) return false;
// 检查HTTP方法
if (exclude.methods && config.method) {
if (exclude.methods.includes(config.method)) {
return true;
}
}
// 检查URL路径
if (exclude.paths) {
for (const pattern of exclude.paths) {
if (new RegExp(pattern).test(config.url)) {
return true;
}
}
}
return false;
}
/**
* 将缓存条目转换为响应
*/
private entryToResponse(entry: CacheEntry): HttpResponse {
let data = entry.data;
// 如果数据被压缩,解压
if (entry.compressed) {
data = this.decompressData(data);
}
return {
data,
status: entry.status,
statusText: 'OK',
headers: entry.headers,
url: entry.metadata?.url || '',
};
}
/**
* 计算数据大小
*/
private calculateSize(data: any): number {
try {
return new Blob([JSON.stringify(data)]).size;
} catch {
return JSON.stringify(data).length;
}
}
/**
* 压缩数据
*/
private async compressData(data: any): Promise<any> {
// 简单的压缩实现(实际项目中可以使用 pako 等库)
try {
const jsonString = JSON.stringify(data);
// 这里可以集成 CompressionStream API 或其他压缩库
return jsonString;
} catch (error) {
console.error('[Cache] Compression error:', error);
return data;
}
}
/**
* 解压数据
*/
private decompressData(data: any): any {
// 简单的解压实现
try {
if (typeof data === 'string') {
return JSON.parse(data);
}
return data;
} catch (error) {
console.error('[Cache] Decompression error:', error);
return data;
}
}
/**
* 后台重新验证
*/
private async backgroundRevalidate(config: HttpRequestOptions, cacheKey: string): Promise<void> {
try {
// 这里需要实际的HTTP请求实现
// 在实际使用中,这会调用原始的请求方法
console.log('[Cache] Background revalidation for:', cacheKey);
} catch (error) {
console.error('[Cache] Background revalidation failed:', error);
}
}
/**
* 检查是否离线
*/
private async checkOffline(): Promise<boolean> {
if (this.offlineOptions?.isOffline) {
return this.offlineOptions.isOffline();
}
// 默认使用 navigator.onLine
return typeof navigator !== 'undefined' ? !navigator.onLine : false;
}
/**
* 处理离线请求
*/
private async handleOfflineRequest(config: HttpRequestOptions): Promise<HttpRequestOptions> {
const strategy = this.offlineOptions?.strategy || 'cache-only';
const cacheKey = this.generateCacheKey(config);
if (strategy === 'cache-only') {
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
throw {
__cacheHit: true,
response: this.entryToResponse(cached),
};
}
// 如果有回退响应
if (this.offlineOptions?.fallbackResponse) {
throw {
__cacheHit: true,
response: this.offlineOptions.fallbackResponse,
};
}
throw new Error('Offline and no cache available');
}
return config;
}
/**
* 处理离线错误
*/
private async handleOfflineError(error: HttpError): Promise<HttpResponse> {
const cacheKey = this.generateCacheKey(error.config);
const cached = await this.cacheManager.get(cacheKey);
if (cached) {
return this.entryToResponse(cached);
}
if (this.offlineOptions?.fallbackResponse) {
return this.offlineOptions.fallbackResponse;
}
throw error;
}
/**
* 获取缓存管理器
*/
getCacheManager(): CacheManager {
return this.cacheManager;
}
/**
* 清空缓存
*/
async clearCache(): Promise<void> {
await this.cacheManager.clear();
}
/**
* 获取缓存统计
*/
async getStats() {
return this.cacheManager.getStats();
}
/**
* 清理过期缓存
*/
async cleanup(): Promise<number> {
return this.cacheManager.cleanup();
}
}
/**
* 创建缓存拦截器的便捷函数
*/
export function createCacheInterceptor(
options?: CacheInterceptorOptions,
offlineOptions?: OfflineOptions
): CacheInterceptor {
return new CacheInterceptor(options, offlineOptions);
}
/**
* 预设的缓存策略
*/
export const CacheStrategies = {
/**
* 网络优先策略
* 优先从网络获取,失败时使用缓存
*/
networkFirst: (maxAge: number = 300000): CacheInterceptorOptions => ({
strategy: 'network-first',
maxAge,
enabled: true,
}),
/**
* 缓存优先策略
* 优先使用缓存,缓存不存在时从网络获取
*/
cacheFirst: (maxAge: number = 300000): CacheInterceptorOptions => ({
strategy: 'cache-first',
maxAge,
enabled: true,
}),
/**
* 仅网络策略
* 总是从网络获取,不使用缓存
*/
networkOnly: (): CacheInterceptorOptions => ({
strategy: 'network-only',
enabled: false,
}),
/**
* 仅缓存策略
* 只使用缓存,不发起网络请求
*/
cacheOnly: (maxAge: number = 300000): CacheInterceptorOptions => ({
strategy: 'cache-only',
maxAge,
enabled: true,
}),
/**
* 陈旧重新验证策略
* 立即返回缓存,同时在后台更新
*/
staleWhileRevalidate: (maxAge: number = 300000): CacheInterceptorOptions => ({
strategy: 'stale-while-revalidate',
maxAge,
enabled: true,
}),
};