import { reactive, ref } from 'vue' import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder' /** * 资源类型枚举 */ export enum ResourceType { LOCAL_STORAGE = 'localStorage', NETWORK = 'network', FILE_SYSTEM = 'fileSystem', NOTIFICATION = 'notification', CLIPBOARD = 'clipboard', MEDIA = 'media', GEOLOCATION = 'geolocation', } /** * 权限级别枚举 */ export enum PermissionLevel { DENIED = 'denied', GRANTED = 'granted', PROMPT = 'prompt', } /** * 权限请求结果 */ export interface PermissionRequest { id: string appId: string resourceType: ResourceType description: string requestedAt: Date status: PermissionLevel approvedAt?: Date deniedAt?: Date expiresAt?: Date } /** * 资源访问配置 */ export interface ResourceAccessConfig { maxStorageSize: number // 本地存储最大容量(MB) allowedDomains: string[] // 允许访问的网络域名 maxNetworkRequests: number // 每分钟最大网络请求数 allowFileAccess: boolean // 是否允许文件系统访问 allowNotifications: boolean // 是否允许通知 allowClipboard: boolean // 是否允许剪贴板访问 allowMedia: boolean // 是否允许摄像头麦克风 allowGeolocation: boolean // 是否允许地理位置 } /** * 网络请求记录 */ export interface NetworkRequest { id: string appId: string url: string method: string timestamp: Date status?: number responseSize?: number } /** * 存储使用情况 */ export interface StorageUsage { appId: string usedSpace: number // 已使用空间(MB) maxSpace: number // 最大空间(MB) lastAccessed: Date } /** * 资源事件接口 */ export interface ResourceEvents extends IEventMap { onPermissionRequest: (request: PermissionRequest) => void onPermissionGranted: (appId: string, resourceType: ResourceType) => void onPermissionDenied: (appId: string, resourceType: ResourceType) => void onResourceQuotaExceeded: (appId: string, resourceType: ResourceType) => void onNetworkRequest: (request: NetworkRequest) => void onStorageChange: (appId: string, usage: StorageUsage) => void } /** * 资源管理服务类 */ export class ResourceService { private permissions = reactive(new Map>()) private networkRequests = reactive(new Map()) private storageUsage = reactive(new Map()) private defaultConfig: ResourceAccessConfig private eventBus: IEventBuilder constructor(eventBus: IEventBuilder) { this.eventBus = eventBus // 默认资源访问配置 this.defaultConfig = { maxStorageSize: 10, // 10MB allowedDomains: [], maxNetworkRequests: 60, // 每分钟60次 allowFileAccess: false, allowNotifications: false, allowClipboard: false, allowMedia: false, allowGeolocation: false, } this.initializeStorageMonitoring() } /** * 请求资源权限 */ async requestPermission( appId: string, resourceType: ResourceType, description: string, ): Promise { const requestId = `${appId}-${resourceType}-${Date.now()}` const request: PermissionRequest = { id: requestId, appId, resourceType, description, requestedAt: new Date(), status: PermissionLevel.PROMPT, } // 检查是否已有权限 const existingPermission = this.getPermission(appId, resourceType) if (existingPermission) { if (existingPermission.status === PermissionLevel.GRANTED) { // 检查权限是否过期 if (!existingPermission.expiresAt || existingPermission.expiresAt > new Date()) { return PermissionLevel.GRANTED } } else if (existingPermission.status === PermissionLevel.DENIED) { return PermissionLevel.DENIED } } // 触发权限请求事件,UI层处理用户确认 this.eventBus.notifyEvent('onPermissionRequest', request) // 根据资源类型的默认策略处理 return this.handlePermissionRequest(request) } /** * 授予权限 */ grantPermission(appId: string, resourceType: ResourceType, expiresIn?: number): boolean { try { const request = this.getPermission(appId, resourceType) if (!request) return false request.status = PermissionLevel.GRANTED request.approvedAt = new Date() if (expiresIn) { request.expiresAt = new Date(Date.now() + expiresIn) } this.setPermission(appId, resourceType, request) this.eventBus.notifyEvent('onPermissionGranted', appId, resourceType) return true } catch (error) { console.error('授予权限失败:', error) return false } } /** * 拒绝权限 */ denyPermission(appId: string, resourceType: ResourceType): boolean { try { const request = this.getPermission(appId, resourceType) if (!request) return false request.status = PermissionLevel.DENIED request.deniedAt = new Date() this.setPermission(appId, resourceType, request) this.eventBus.notifyEvent('onPermissionDenied', appId, resourceType) return true } catch (error) { console.error('拒绝权限失败:', error) return false } } /** * 检查应用是否有指定资源权限 */ hasPermission(appId: string, resourceType: ResourceType): boolean { const permission = this.getPermission(appId, resourceType) if (!permission || permission.status !== PermissionLevel.GRANTED) { return false } // 检查权限是否过期 if (permission.expiresAt && permission.expiresAt <= new Date()) { permission.status = PermissionLevel.DENIED this.setPermission(appId, resourceType, permission) return false } return true } /** * 本地存储操作 */ async setStorage(appId: string, key: string, value: any): Promise { if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) { const permission = await this.requestPermission( appId, ResourceType.LOCAL_STORAGE, '应用需要访问本地存储来保存数据', ) if (permission !== PermissionLevel.GRANTED) { return false } } try { const storageKey = `app-${appId}-${key}` const serializedValue = JSON.stringify(value) // 检查存储配额 const usage = this.getStorageUsage(appId) const valueSize = new Blob([serializedValue]).size / (1024 * 1024) // MB if (usage.usedSpace + valueSize > usage.maxSpace) { this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.LOCAL_STORAGE) return false } localStorage.setItem(storageKey, serializedValue) // 更新存储使用情况 this.updateStorageUsage(appId) return true } catch (error) { console.error('存储数据失败:', error) return false } } /** * 获取本地存储数据 */ async getStorage(appId: string, key: string): Promise { if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) { return null } try { const storageKey = `app-${appId}-${key}` const value = localStorage.getItem(storageKey) if (value === null) { return null } // 更新最后访问时间 this.updateStorageUsage(appId) return JSON.parse(value) } catch (error) { console.error('获取存储数据失败:', error) return null } } /** * 删除本地存储数据 */ async removeStorage(appId: string, key: string): Promise { if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) { return false } try { const storageKey = `app-${appId}-${key}` localStorage.removeItem(storageKey) // 更新存储使用情况 this.updateStorageUsage(appId) return true } catch (error) { console.error('删除存储数据失败:', error) return false } } /** * 清空应用存储 */ async clearStorage(appId: string): Promise { if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) { return false } try { const prefix = `app-${appId}-` const keysToRemove: string[] = [] for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i) if (key && key.startsWith(prefix)) { keysToRemove.push(key) } } keysToRemove.forEach((key) => localStorage.removeItem(key)) // 重置存储使用情况 this.resetStorageUsage(appId) return true } catch (error) { console.error('清空存储失败:', error) return false } } /** * 网络请求 */ async makeNetworkRequest( appId: string, url: string, options: RequestInit = {}, ): Promise { if (!this.hasPermission(appId, ResourceType.NETWORK)) { const permission = await this.requestPermission( appId, ResourceType.NETWORK, `应用需要访问网络来请求数据: ${url}`, ) if (permission !== PermissionLevel.GRANTED) { return null } } // 检查域名白名单 try { const urlObj = new URL(url) const config = this.getAppResourceConfig(appId) if ( config.allowedDomains.length > 0 && !config.allowedDomains.some((domain) => urlObj.hostname.endsWith(domain)) ) { console.warn(`域名 ${urlObj.hostname} 不在白名单中`) return null } // 检查请求频率限制 if (!this.checkNetworkRateLimit(appId)) { this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.NETWORK) return null } // 记录网络请求 const requestRecord: NetworkRequest = { id: `${appId}-${Date.now()}`, appId, url, method: options.method || 'GET', timestamp: new Date(), } const response = await fetch(url, options) // 更新请求记录 requestRecord.status = response.status requestRecord.responseSize = parseInt(response.headers.get('content-length') || '0') this.recordNetworkRequest(requestRecord) this.eventBus.notifyEvent('onNetworkRequest', requestRecord) return response } catch (error) { console.error('网络请求失败:', error) return null } } /** * 显示通知 */ async showNotification( appId: string, title: string, options?: NotificationOptions, ): Promise { if (!this.hasPermission(appId, ResourceType.NOTIFICATION)) { const permission = await this.requestPermission( appId, ResourceType.NOTIFICATION, '应用需要显示通知来提醒您重要信息', ) if (permission !== PermissionLevel.GRANTED) { return false } } try { if ('Notification' in window) { // 请求浏览器通知权限 if (Notification.permission === 'default') { await Notification.requestPermission() } if (Notification.permission === 'granted') { new Notification(`[${appId}] ${title}`, options) return true } } return false } catch (error) { console.error('显示通知失败:', error) return false } } /** * 访问剪贴板 */ async getClipboard(appId: string): Promise { if (!this.hasPermission(appId, ResourceType.CLIPBOARD)) { const permission = await this.requestPermission( appId, ResourceType.CLIPBOARD, '应用需要访问剪贴板来读取您复制的内容', ) if (permission !== PermissionLevel.GRANTED) { return null } } try { if (navigator.clipboard && navigator.clipboard.readText) { return await navigator.clipboard.readText() } return null } catch (error) { console.error('读取剪贴板失败:', error) return null } } /** * 写入剪贴板 */ async setClipboard(appId: string, text: string): Promise { if (!this.hasPermission(appId, ResourceType.CLIPBOARD)) { const permission = await this.requestPermission( appId, ResourceType.CLIPBOARD, '应用需要访问剪贴板来复制内容', ) if (permission !== PermissionLevel.GRANTED) { return false } } try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text) return true } return false } catch (error) { console.error('写入剪贴板失败:', error) return false } } /** * 获取应用权限列表 */ getAppPermissions(appId: string): PermissionRequest[] { const appPermissions = this.permissions.get(appId) return appPermissions ? Array.from(appPermissions.values()) : [] } /** * 获取所有网络请求记录 */ getNetworkRequests(appId: string): NetworkRequest[] { return this.networkRequests.get(appId) || [] } /** * 获取存储使用情况 */ getStorageUsage(appId: string): StorageUsage { let usage = this.storageUsage.get(appId) if (!usage) { usage = { appId, usedSpace: 0, maxSpace: this.defaultConfig.maxStorageSize, lastAccessed: new Date(), } this.storageUsage.set(appId, usage) } return usage } /** * 获取应用资源配置 */ getAppResourceConfig(appId: string): ResourceAccessConfig { // 这里可以从数据库或配置文件加载应用特定配置 // 目前返回默认配置 return { ...this.defaultConfig } } /** * 撤销应用所有权限 */ revokeAllPermissions(appId: string): boolean { try { this.permissions.delete(appId) this.networkRequests.delete(appId) this.clearStorage(appId) return true } catch (error) { console.error('撤销权限失败:', error) return false } } // 私有方法 /** * 处理权限请求 */ private async handlePermissionRequest(request: PermissionRequest): Promise { // 对于本地存储,默认授权 if (request.resourceType === ResourceType.LOCAL_STORAGE) { this.grantPermission(request.appId, request.resourceType) return PermissionLevel.GRANTED } // 其他资源需要用户确认,这里模拟用户同意 // 实际实现中,这里应该显示权限确认对话框 return new Promise((resolve) => { setTimeout(() => { // 模拟用户操作 const userResponse = Math.random() > 0.3 // 70%的概率同意 if (userResponse) { this.grantPermission(request.appId, request.resourceType, 24 * 60 * 60 * 1000) // 24小时有效 resolve(PermissionLevel.GRANTED) } else { this.denyPermission(request.appId, request.resourceType) resolve(PermissionLevel.DENIED) } }, 1000) }) } /** * 获取权限记录 */ private getPermission(appId: string, resourceType: ResourceType): PermissionRequest | undefined { const appPermissions = this.permissions.get(appId) return appPermissions?.get(resourceType) } /** * 设置权限记录 */ private setPermission( appId: string, resourceType: ResourceType, request: PermissionRequest, ): void { if (!this.permissions.has(appId)) { this.permissions.set(appId, new Map()) } this.permissions.get(appId)!.set(resourceType, request) } /** * 检查网络请求频率限制 */ private checkNetworkRateLimit(appId: string): boolean { const requests = this.networkRequests.get(appId) || [] const now = new Date() const oneMinuteAgo = new Date(now.getTime() - 60 * 1000) const recentRequests = requests.filter((req) => req.timestamp > oneMinuteAgo) const config = this.getAppResourceConfig(appId) return recentRequests.length < config.maxNetworkRequests } /** * 记录网络请求 */ private recordNetworkRequest(request: NetworkRequest): void { if (!this.networkRequests.has(request.appId)) { this.networkRequests.set(request.appId, []) } const requests = this.networkRequests.get(request.appId)! requests.push(request) // 保留最近1000条记录 if (requests.length > 1000) { requests.splice(0, requests.length - 1000) } } /** * 更新存储使用情况 */ private updateStorageUsage(appId: string): void { const usage = this.getStorageUsage(appId) usage.lastAccessed = new Date() // 计算实际使用空间 let usedSpace = 0 const prefix = `app-${appId}-` for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i) if (key && key.startsWith(prefix)) { const value = localStorage.getItem(key) if (value) { usedSpace += new Blob([value]).size } } } usage.usedSpace = usedSpace / (1024 * 1024) // 转换为MB this.eventBus.notifyEvent('onStorageChange', appId, usage) } /** * 重置存储使用情况 */ private resetStorageUsage(appId: string): void { const usage = this.getStorageUsage(appId) usage.usedSpace = 0 usage.lastAccessed = new Date() this.eventBus.notifyEvent('onStorageChange', appId, usage) } /** * 初始化存储监控 */ private initializeStorageMonitoring(): void { // 监听存储变化事件 window.addEventListener('storage', (e) => { if (e.key && e.key.startsWith('app-')) { const parts = e.key.split('-') if (parts.length >= 2) { const appId = parts[1] this.updateStorageUsage(appId) } } }) } }