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.
640 lines (553 loc) • 17.7 kB
text/typescript
/**
* Cache Manager Implementation
* 缓存管理器实现
*/
import type {
CacheEntry,
CacheStats,
CacheOptions,
CacheEvictionPolicy,
CacheEvictionOptions,
} from '../types/cache';
import type { CacheManager } from '../types/cache';
// Re-export CacheManager for external use
export type { CacheManager } from '../types/cache';
/**
* Memory Cache Manager
* 内存缓存管理器
*/
export class MemoryCacheManager implements CacheManager {
private cache: Map<string, CacheEntry> = new Map();
private accessCount: Map<string, number> = new Map();
private accessTime: Map<string, number> = new Map();
private stats = {
hits: 0,
misses: 0,
};
private maxSize: number;
private evictionPolicy: CacheEvictionPolicy;
private cleanupInterval?: NodeJS.Timeout;
constructor(
maxSize: number = 50 * 1024 * 1024, // 50MB
evictionOptions?: CacheEvictionOptions
) {
this.maxSize = maxSize;
this.evictionPolicy = evictionOptions?.policy || 'lru';
// 启动自动清理
if (evictionOptions?.interval) {
this.startAutoCleanup(evictionOptions.interval);
}
}
async get(key: string): Promise<CacheEntry | null> {
const entry = this.cache.get(key);
if (!entry) {
this.stats.misses++;
return null;
}
// 检查是否过期
if (Date.now() > entry.expiresAt) {
await this.delete(key);
this.stats.misses++;
return null;
}
// 更新访问统计
this.accessCount.set(key, (this.accessCount.get(key) || 0) + 1);
this.accessTime.set(key, Date.now());
this.stats.hits++;
return entry;
}
async set(key: string, entry: CacheEntry): Promise<void> {
// 检查是否需要清理空间
const currentSize = await this.getSize();
if (currentSize + entry.size > this.maxSize) {
await this.evict(entry.size);
}
this.cache.set(key, entry);
this.accessCount.set(key, 1);
this.accessTime.set(key, Date.now());
}
async delete(key: string): Promise<boolean> {
this.accessCount.delete(key);
this.accessTime.delete(key);
return this.cache.delete(key);
}
async clear(): Promise<void> {
this.cache.clear();
this.accessCount.clear();
this.accessTime.clear();
this.stats.hits = 0;
this.stats.misses = 0;
}
async has(key: string): Promise<boolean> {
const entry = this.cache.get(key);
if (!entry) return false;
// 检查是否过期
if (Date.now() > entry.expiresAt) {
await this.delete(key);
return false;
}
return true;
}
async keys(): Promise<string[]> {
return Array.from(this.cache.keys());
}
async getStats(): Promise<CacheStats> {
const entries = Array.from(this.cache.values());
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
const timestamps = entries.map(e => e.timestamp);
return {
totalEntries: this.cache.size,
totalSize,
hits: this.stats.hits,
misses: this.stats.misses,
hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0,
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined,
};
}
async cleanup(): Promise<number> {
const now = Date.now();
let cleaned = 0;
for (const [key, entry] of this.cache.entries()) {
if (now > entry.expiresAt) {
await this.delete(key);
cleaned++;
}
}
return cleaned;
}
async getSize(): Promise<number> {
let totalSize = 0;
for (const entry of this.cache.values()) {
totalSize += entry.size;
}
return totalSize;
}
/**
* 清理缓存以腾出空间
*/
private async evict(requiredSpace: number): Promise<void> {
const currentSize = await this.getSize();
const targetSize = this.maxSize * 0.7; // 清理到70%
let freedSpace = 0;
const entries = Array.from(this.cache.entries());
const sortedEntries = this.sortEntriesForEviction(entries);
for (const [key] of sortedEntries) {
if (currentSize - freedSpace <= targetSize && freedSpace >= requiredSpace) {
break;
}
const entry = this.cache.get(key);
if (entry) {
freedSpace += entry.size;
await this.delete(key);
}
}
}
/**
* 根据清理策略排序条目
*/
private sortEntriesForEviction(
entries: Array<[string, CacheEntry]>
): Array<[string, CacheEntry]> {
switch (this.evictionPolicy) {
case 'lru': // 最近最少使用
return entries.sort((a, b) => {
const timeA = this.accessTime.get(a[0]) || 0;
const timeB = this.accessTime.get(b[0]) || 0;
return timeA - timeB;
});
case 'lfu': // 最不经常使用
return entries.sort((a, b) => {
const countA = this.accessCount.get(a[0]) || 0;
const countB = this.accessCount.get(b[0]) || 0;
return countA - countB;
});
case 'fifo': // 先进先出
return entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
case 'ttl': // 基于过期时间
return entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
default:
return entries;
}
}
/**
* 启动自动清理
*/
private startAutoCleanup(interval: number): void {
this.cleanupInterval = setInterval(() => {
this.cleanup().catch(console.error);
}, interval);
}
/**
* 停止自动清理
*/
stopAutoCleanup(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = undefined;
}
}
/**
* 销毁缓存管理器
*/
destroy(): void {
this.stopAutoCleanup();
this.clear();
}
}
/**
* LocalStorage Cache Manager
* LocalStorage 缓存管理器
*/
export class LocalStorageCacheManager implements CacheManager {
private prefix: string;
private stats = {
hits: 0,
misses: 0,
};
constructor(prefix: string = 'cache_') {
this.prefix = prefix;
}
async get(key: string): Promise<CacheEntry | null> {
try {
const data = localStorage.getItem(this.prefix + key);
if (!data) {
this.stats.misses++;
return null;
}
const entry: CacheEntry = JSON.parse(data);
// 检查是否过期
if (Date.now() > entry.expiresAt) {
await this.delete(key);
this.stats.misses++;
return null;
}
this.stats.hits++;
return entry;
} catch (error) {
console.error('[Cache] Error reading from localStorage:', error);
this.stats.misses++;
return null;
}
}
async set(key: string, entry: CacheEntry): Promise<void> {
try {
localStorage.setItem(this.prefix + key, JSON.stringify(entry));
} catch (error) {
console.error('[Cache] Error writing to localStorage:', error);
// 如果存储失败(可能是空间不足),尝试清理
await this.cleanup();
try {
localStorage.setItem(this.prefix + key, JSON.stringify(entry));
} catch (retryError) {
console.error('[Cache] Failed to write after cleanup:', retryError);
}
}
}
async delete(key: string): Promise<boolean> {
try {
localStorage.removeItem(this.prefix + key);
return true;
} catch (error) {
console.error('[Cache] Error deleting from localStorage:', error);
return false;
}
}
async clear(): Promise<void> {
const keys = await this.keys();
for (const key of keys) {
await this.delete(key);
}
this.stats.hits = 0;
this.stats.misses = 0;
}
async has(key: string): Promise<boolean> {
const entry = await this.get(key);
return entry !== null;
}
async keys(): Promise<string[]> {
const keys: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(this.prefix)) {
keys.push(key.substring(this.prefix.length));
}
}
return keys;
}
async getStats(): Promise<CacheStats> {
const keys = await this.keys();
let totalSize = 0;
const timestamps: number[] = [];
for (const key of keys) {
const entry = await this.get(key);
if (entry) {
totalSize += entry.size;
timestamps.push(entry.timestamp);
}
}
return {
totalEntries: keys.length,
totalSize,
hits: this.stats.hits,
misses: this.stats.misses,
hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0,
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined,
};
}
async cleanup(): Promise<number> {
const keys = await this.keys();
const now = Date.now();
let cleaned = 0;
for (const key of keys) {
const entry = await this.get(key);
if (entry && now > entry.expiresAt) {
await this.delete(key);
cleaned++;
}
}
return cleaned;
}
async getSize(): Promise<number> {
const keys = await this.keys();
let totalSize = 0;
for (const key of keys) {
const entry = await this.get(key);
if (entry) {
totalSize += entry.size;
}
}
return totalSize;
}
}
/**
* IndexedDB Cache Manager
* IndexedDB 缓存管理器(用于大数据量)
*/
export class IndexedDBCacheManager implements CacheManager {
private dbName: string;
private storeName: string;
private db: IDBDatabase | null = null;
private stats = {
hits: 0,
misses: 0,
};
constructor(dbName: string = 'CacheDB', storeName: string = 'cache') {
this.dbName = dbName;
this.storeName = storeName;
}
private async getDB(): Promise<IDBDatabase> {
if (this.db) return this.db;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(request.result);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
const store = db.createObjectStore(this.storeName, { keyPath: 'key' });
store.createIndex('expiresAt', 'expiresAt', { unique: false });
store.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
async get(key: string): Promise<CacheEntry | null> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
return new Promise((resolve, reject) => {
request.onsuccess = () => {
const entry = request.result as CacheEntry | undefined;
if (!entry) {
this.stats.misses++;
resolve(null);
return;
}
// 检查是否过期
if (Date.now() > entry.expiresAt) {
this.delete(key);
this.stats.misses++;
resolve(null);
return;
}
this.stats.hits++;
resolve(entry);
};
request.onerror = () => {
this.stats.misses++;
reject(request.error);
};
});
} catch (error) {
console.error('[Cache] Error reading from IndexedDB:', error);
this.stats.misses++;
return null;
}
}
async set(key: string, entry: CacheEntry): Promise<void> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(entry);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error writing to IndexedDB:', error);
throw error;
}
}
async delete(key: string): Promise<boolean> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(key);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(true);
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error deleting from IndexedDB:', error);
return false;
}
}
async clear(): Promise<void> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.clear();
return new Promise((resolve, reject) => {
request.onsuccess = () => {
this.stats.hits = 0;
this.stats.misses = 0;
resolve();
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error clearing IndexedDB:', error);
throw error;
}
}
async has(key: string): Promise<boolean> {
const entry = await this.get(key);
return entry !== null;
}
async keys(): Promise<string[]> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAllKeys();
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result as string[]);
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error getting keys from IndexedDB:', error);
return [];
}
}
async getStats(): Promise<CacheStats> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
return new Promise((resolve, reject) => {
request.onsuccess = () => {
const entries = request.result as CacheEntry[];
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
const timestamps = entries.map(e => e.timestamp);
resolve({
totalEntries: entries.length,
totalSize,
hits: this.stats.hits,
misses: this.stats.misses,
hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0,
oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined,
});
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error getting stats from IndexedDB:', error);
return {
totalEntries: 0,
totalSize: 0,
hits: this.stats.hits,
misses: this.stats.misses,
hitRate: 0,
};
}
}
async cleanup(): Promise<number> {
try {
const db = await this.getDB();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const index = store.index('expiresAt');
const now = Date.now();
const range = IDBKeyRange.upperBound(now);
const request = index.openCursor(range);
let cleaned = 0;
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result as IDBCursorWithValue;
if (cursor) {
cursor.delete();
cleaned++;
cursor.continue();
} else {
resolve(cleaned);
}
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error('[Cache] Error cleaning up IndexedDB:', error);
return 0;
}
}
async getSize(): Promise<number> {
const stats = await this.getStats();
return stats.totalSize;
}
/**
* 关闭数据库连接
*/
close(): void {
if (this.db) {
this.db.close();
this.db = null;
}
}
}
/**
* 创建缓存管理器工厂函数
*/
export function createCacheManager(options?: CacheOptions): CacheManager {
const storage = options?.storage || 'memory';
const maxSize = options?.maxSize || 50 * 1024 * 1024;
switch (storage) {
case 'localStorage':
return new LocalStorageCacheManager();
case 'indexedDB':
return new IndexedDBCacheManager();
case 'memory':
default:
return new MemoryCacheManager(maxSize);
}
}