Files
frontend/src/services/core/datasources/CacheDataSource.ts
lan 2e6912dddf
Some checks failed
Frontend CI / ota-android (push) Successful in 1m27s
Frontend CI / build-and-push-web (push) Failing after 1m28s
Frontend CI / ota-ios (push) Successful in 2m58s
Frontend CI / build-android-apk (push) Successful in 51m46s
feat(call): implement CallKeep integration and enhance group call support
Integrate `expo-callkit-telecom` to support native system call handling (answering, ending, and muting via system UI). This includes a new `callKeepService` and a `CallKeepBootstrap` component to manage the lifecycle of system call events.

Additionally, improves the calling experience by:
- Adding support for group calls with participant tracking and UI indicators.
- Enhancing LiveKit integration by replacing polling with event-driven video track synchronization.
- Updating `WebSocketService` to prevent disconnection when the app enters the background during an active call.
- Adding new WebSocket message types for participant join/leave events and group invites.
- Refining call UI components (`CallScreen`, `FloatingCallWindow`, `IncomingCallModal`) for better visual feedback and safe area handling.

Refactor LiveKit service to use event-driven updates for local and remote tracks, improving performance and reliability.
2026-06-03 01:56:02 +08:00

200 lines
5.5 KiB
TypeScript

/**
* 缓存数据源实现
* 基于内存和 AsyncStorage 的混合缓存实现
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { ICacheDataSource, DataSourceError } from './interfaces';
interface CacheEntry<T> {
value: T;
expiry: number | null; // null 表示永不过期
}
export class CacheDataSource implements ICacheDataSource {
private memoryCache: Map<string, CacheEntry<any>> = new Map();
private maxSize: number;
private defaultTtl: number; // 默认过期时间(毫秒)
private persistPrefix: string;
constructor(config?: {
maxSize?: number;
defaultTtl?: number; // 默认5分钟
persistPrefix?: string;
}) {
this.maxSize = config?.maxSize || 100;
this.defaultTtl = config?.defaultTtl || 5 * 60 * 1000;
this.persistPrefix = config?.persistPrefix || 'cache_';
}
/**
* 检查 key 是否过期
*/
private isExpired(entry: CacheEntry<any>): boolean {
if (entry.expiry === null) return false;
return Date.now() > entry.expiry;
}
/**
* 清理过期缓存
*/
private cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.memoryCache.entries()) {
if (entry.expiry !== null && now > entry.expiry) {
this.memoryCache.delete(key);
}
}
// 如果仍然超过最大大小,删除最旧的条目
while (this.memoryCache.size > this.maxSize) {
const firstKey = this.memoryCache.keys().next().value;
if (firstKey !== undefined) {
this.memoryCache.delete(firstKey);
}
}
}
/**
* 生成持久化存储 key
*/
private getPersistKey(key: string): string {
return `${this.persistPrefix}${key}`;
}
// ==================== ICacheDataSource 实现 ====================
async get<T>(key: string): Promise<T | null> {
try {
// 先检查内存缓存
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry) {
if (!this.isExpired(memoryEntry)) {
return memoryEntry.value as T;
}
// 过期了,从内存中删除
this.memoryCache.delete(key);
}
// 尝试从持久化存储读取
const persistKey = this.getPersistKey(key);
const stored = await AsyncStorage.getItem(persistKey);
if (stored) {
const entry: CacheEntry<T> = JSON.parse(stored);
if (!this.isExpired(entry)) {
// 重新加载到内存缓存
this.memoryCache.set(key, entry);
this.cleanup();
return entry.value;
}
// 过期了,删除
await AsyncStorage.removeItem(persistKey);
}
return null;
} catch (error) {
console.warn(`[CacheDataSource] Failed to get ${key}:`, error);
return null;
}
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
try {
const expiry = ttl === undefined
? (this.defaultTtl > 0 ? Date.now() + this.defaultTtl : null)
: (ttl > 0 ? Date.now() + ttl : null);
const entry: CacheEntry<T> = { value, expiry };
// 存入内存
this.memoryCache.set(key, entry);
this.cleanup();
// 持久化存储(重要数据)
if (ttl === undefined || ttl > 0) {
const persistKey = this.getPersistKey(key);
await AsyncStorage.setItem(persistKey, JSON.stringify(entry));
}
} catch (error) {
throw new DataSourceError(
`Failed to set cache ${key}`,
'CACHE_SET_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async delete(key: string): Promise<void> {
try {
// 从内存删除
this.memoryCache.delete(key);
// 从持久化存储删除
const persistKey = this.getPersistKey(key);
await AsyncStorage.removeItem(persistKey);
} catch (error) {
throw new DataSourceError(
`Failed to delete cache ${key}`,
'CACHE_DELETE_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async clear(): Promise<void> {
try {
// 清空内存
this.memoryCache.clear();
// 清空持久化存储中的所有缓存项
const keys = await AsyncStorage.getAllKeys();
const cacheKeys = keys.filter(k => k.startsWith(this.persistPrefix));
if (cacheKeys.length > 0) {
await AsyncStorage.removeMany(cacheKeys);
}
} catch (error) {
throw new DataSourceError(
'Failed to clear cache',
'CACHE_CLEAR_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async has(key: string): Promise<boolean> {
try {
// 检查内存
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry && !this.isExpired(memoryEntry)) {
return true;
}
// 检查持久化存储
const persistKey = this.getPersistKey(key);
const stored = await AsyncStorage.getItem(persistKey);
if (stored) {
const entry: CacheEntry<any> = JSON.parse(stored);
return !this.isExpired(entry);
}
return false;
} catch (error) {
return false;
}
}
async getMultiple<T>(keys: string[]): Promise<(T | null)[]> {
return Promise.all(keys.map(key => this.get<T>(key)));
}
async setMultiple<T>(entries: { key: string; value: T; ttl?: number }[]): Promise<void> {
await Promise.all(entries.map(entry => this.set(entry.key, entry.value, entry.ttl)));
}
}
// 导出单例实例
export const cacheDataSource = new CacheDataSource();