1057 lines
29 KiB
TypeScript
1057 lines
29 KiB
TypeScript
import { reactive } from 'vue'
|
||
import type { WindowService } from './WindowService'
|
||
import type { ResourceService } from './ResourceService'
|
||
import type { EventCommunicationService } from './EventCommunicationService'
|
||
import type { ApplicationSandboxEngine } from './ApplicationSandboxEngine'
|
||
import { v4 as uuidv4 } from 'uuid'
|
||
import { externalAppDiscovery } from './ExternalAppDiscovery'
|
||
|
||
/**
|
||
* 应用状态枚举
|
||
*/
|
||
export enum AppLifecycleState {
|
||
INSTALLING = 'installing',
|
||
INSTALLED = 'installed',
|
||
STARTING = 'starting',
|
||
RUNNING = 'running',
|
||
SUSPENDED = 'suspended',
|
||
STOPPING = 'stopping',
|
||
STOPPED = 'stopped',
|
||
UNINSTALLING = 'uninstalling',
|
||
ERROR = 'error',
|
||
CRASHED = 'crashed',
|
||
AVAILABLE = 'available', // 外置应用可用但未注册状态
|
||
}
|
||
|
||
/**
|
||
* 应用清单文件接口
|
||
*/
|
||
export interface AppManifest {
|
||
id: string
|
||
name: string
|
||
version: string
|
||
description: string
|
||
author: string
|
||
homepage?: string
|
||
icon: string
|
||
entryPoint: string // 入口文件路径
|
||
permissions: string[]
|
||
minSystemVersion?: string
|
||
dependencies?: Record<string, string>
|
||
window?: {
|
||
width: number
|
||
height: number
|
||
minWidth?: number
|
||
minHeight?: number
|
||
maxWidth?: number
|
||
maxHeight?: number
|
||
resizable?: boolean
|
||
center?: boolean
|
||
}
|
||
background?: {
|
||
persistent?: boolean
|
||
scripts?: string[]
|
||
}
|
||
contentSecurity?: {
|
||
policy?: string
|
||
allowedDomains?: string[]
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 应用实例接口
|
||
*/
|
||
export interface AppInstance {
|
||
id: string
|
||
manifest: AppManifest
|
||
state: AppLifecycleState
|
||
windowId?: string
|
||
sandboxId?: string
|
||
processId: string
|
||
installedAt: Date
|
||
startedAt?: Date
|
||
lastActiveAt?: Date
|
||
stoppedAt?: Date
|
||
errorCount: number
|
||
crashCount: number
|
||
memoryUsage: number
|
||
cpuUsage: number
|
||
version: string
|
||
autoStart: boolean
|
||
persistent: boolean
|
||
}
|
||
|
||
/**
|
||
* 应用安装包接口
|
||
*/
|
||
export interface AppPackage {
|
||
manifest: AppManifest
|
||
files: Map<string, Blob | string> // 文件路径到内容的映射
|
||
signature?: string // 数字签名
|
||
checksum: string // 校验和
|
||
}
|
||
|
||
/**
|
||
* 应用启动选项
|
||
*/
|
||
export interface AppStartOptions {
|
||
windowConfig?: {
|
||
x?: number
|
||
y?: number
|
||
width?: number
|
||
height?: number
|
||
state?: 'normal' | 'minimized' | 'maximized'
|
||
}
|
||
args?: Record<string, any>
|
||
background?: boolean
|
||
}
|
||
|
||
/**
|
||
* 应用生命周期事件
|
||
*/
|
||
export interface AppLifecycleEvents {
|
||
onInstalled: (appId: string, manifest: AppManifest) => void
|
||
onUninstalled: (appId: string) => void
|
||
onStarted: (appId: string, processId: string) => void
|
||
onStopped: (appId: string, processId: string) => void
|
||
onSuspended: (appId: string, processId: string) => void
|
||
onResumed: (appId: string, processId: string) => void
|
||
onError: (appId: string, error: Error) => void
|
||
onCrashed: (appId: string, reason: string) => void
|
||
onStateChanged: (appId: string, newState: AppLifecycleState, oldState: AppLifecycleState) => void
|
||
}
|
||
|
||
/**
|
||
* 应用生命周期管理器
|
||
*/
|
||
export class ApplicationLifecycleManager {
|
||
private installedApps = reactive(new Map<string, AppInstance>())
|
||
private runningProcesses = reactive(new Map<string, AppInstance>())
|
||
private appFiles = new Map<string, Map<string, Blob | string>>() // 应用文件存储
|
||
|
||
private windowService: WindowService
|
||
private resourceService: ResourceService
|
||
private eventService: EventCommunicationService
|
||
private sandboxEngine: ApplicationSandboxEngine
|
||
|
||
constructor(
|
||
windowService: WindowService,
|
||
resourceService: ResourceService,
|
||
eventService: EventCommunicationService,
|
||
sandboxEngine: ApplicationSandboxEngine,
|
||
) {
|
||
this.windowService = windowService
|
||
this.resourceService = resourceService
|
||
this.eventService = eventService
|
||
this.sandboxEngine = sandboxEngine
|
||
|
||
this.setupEventListeners()
|
||
this.loadInstalledApps()
|
||
// 外部应用发现已由 SystemServiceIntegration 统一管理,无需重复初始化
|
||
}
|
||
|
||
/**
|
||
* 安装应用
|
||
*/
|
||
async installApp(appPackage: AppPackage): Promise<string> {
|
||
const { manifest, files, checksum } = appPackage
|
||
const appId = manifest.id
|
||
|
||
try {
|
||
// 检查应用是否已安装
|
||
if (this.installedApps.has(appId)) {
|
||
throw new Error(`应用 ${appId} 已安装`)
|
||
}
|
||
|
||
// 验证清单文件
|
||
this.validateManifest(manifest)
|
||
|
||
// 验证校验和
|
||
if (!this.verifyChecksum(files, checksum)) {
|
||
throw new Error('应用包校验失败')
|
||
}
|
||
|
||
// 检查权限
|
||
await this.checkPermissions(manifest.permissions)
|
||
|
||
const now = new Date()
|
||
const appInstance: AppInstance = {
|
||
id: appId,
|
||
manifest,
|
||
state: AppLifecycleState.INSTALLING,
|
||
processId: '',
|
||
installedAt: now,
|
||
errorCount: 0,
|
||
crashCount: 0,
|
||
memoryUsage: 0,
|
||
cpuUsage: 0,
|
||
version: manifest.version,
|
||
autoStart: false,
|
||
persistent: manifest.background?.persistent || false,
|
||
}
|
||
|
||
// 更新状态
|
||
this.updateAppState(appInstance, AppLifecycleState.INSTALLING)
|
||
this.installedApps.set(appId, appInstance)
|
||
|
||
// 存储应用文件
|
||
this.appFiles.set(appId, files)
|
||
|
||
// 保存到本地存储
|
||
await this.saveAppToStorage(appInstance)
|
||
|
||
// 更新状态为已安装
|
||
this.updateAppState(appInstance, AppLifecycleState.INSTALLED)
|
||
|
||
this.eventService.sendMessage('system', 'app-lifecycle', {
|
||
type: 'installed',
|
||
appId,
|
||
manifest,
|
||
})
|
||
|
||
console.log(`应用 ${manifest.name} (${appId}) 安装成功`)
|
||
return appId
|
||
} catch (error) {
|
||
// 清理安装失败的应用
|
||
this.installedApps.delete(appId)
|
||
this.appFiles.delete(appId)
|
||
|
||
console.error('应用安装失败:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 卸载应用
|
||
*/
|
||
async uninstallApp(appId: string): Promise<boolean> {
|
||
const app = this.installedApps.get(appId)
|
||
if (!app) {
|
||
throw new Error(`应用 ${appId} 未安装`)
|
||
}
|
||
|
||
try {
|
||
// 如果应用正在运行,先停止
|
||
if (app.state === AppLifecycleState.RUNNING || app.state === AppLifecycleState.SUSPENDED) {
|
||
await this.stopApp(appId)
|
||
}
|
||
|
||
this.updateAppState(app, AppLifecycleState.UNINSTALLING)
|
||
|
||
// 清理应用数据
|
||
await this.resourceService.clearStorage(appId)
|
||
await this.resourceService.revokeAllPermissions(appId)
|
||
|
||
// 删除应用文件
|
||
this.appFiles.delete(appId)
|
||
|
||
// 从存储中删除
|
||
await this.removeAppFromStorage(appId)
|
||
|
||
// 从已安装列表中移除
|
||
this.installedApps.delete(appId)
|
||
|
||
this.eventService.sendMessage('system', 'app-lifecycle', {
|
||
type: 'uninstalled',
|
||
appId,
|
||
})
|
||
|
||
console.log(`应用 ${appId} 卸载成功`)
|
||
return true
|
||
} catch (error) {
|
||
console.error('应用卸载失败:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动应用
|
||
*/
|
||
async startApp(appId: string, options: AppStartOptions = {}): Promise<string> {
|
||
let app = this.installedApps.get(appId)
|
||
|
||
// 如果应用未安装,检查是否为外置应用
|
||
let isExternalApp = false
|
||
if (!app) {
|
||
const externalApp = externalAppDiscovery.getApp(appId)
|
||
if (externalApp) {
|
||
console.log(`[LifecycleManager] 发现外置应用 ${appId}`)
|
||
isExternalApp = true
|
||
|
||
// 为外部应用创建临时实例
|
||
const now = new Date()
|
||
app = {
|
||
id: externalApp.manifest.id,
|
||
manifest: externalApp.manifest,
|
||
state: AppLifecycleState.INSTALLED,
|
||
processId: '',
|
||
installedAt: now,
|
||
errorCount: 0,
|
||
crashCount: 0,
|
||
memoryUsage: 0,
|
||
cpuUsage: 0,
|
||
version: externalApp.manifest.version,
|
||
autoStart: false,
|
||
persistent: false,
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!app) {
|
||
throw new Error(`应用 ${appId} 未安装且未发现`)
|
||
}
|
||
|
||
if (app.state === AppLifecycleState.RUNNING) {
|
||
throw new Error(`应用 ${appId} 已在运行`)
|
||
}
|
||
|
||
try {
|
||
const processId = uuidv4()
|
||
app.processId = processId
|
||
|
||
this.updateAppState(app, AppLifecycleState.STARTING)
|
||
|
||
// 检查是否为内置应用
|
||
let isBuiltInApp = false
|
||
|
||
try {
|
||
const { AppRegistry } = await import('../apps/AppRegistry')
|
||
const appRegistry = AppRegistry.getInstance()
|
||
isBuiltInApp = appRegistry.hasApp(appId)
|
||
} catch (error) {
|
||
console.warn('无法导入 AppRegistry')
|
||
}
|
||
|
||
// 检查是否为外置应用(仅当不是内置应用时)
|
||
// 修复:移除重复的变量声明,使用已声明的isExternalApp变量
|
||
if (!isBuiltInApp && !isExternalApp) {
|
||
isExternalApp = externalAppDiscovery.hasApp(appId)
|
||
}
|
||
|
||
// 创建窗体(如果不是后台应用)
|
||
let windowId: string | undefined
|
||
if (!options.background && !app.manifest.background?.scripts) {
|
||
const windowConfig = {
|
||
title: app.manifest.name,
|
||
width: options.windowConfig?.width || app.manifest.window?.width || 800,
|
||
height: options.windowConfig?.height || app.manifest.window?.height || 600,
|
||
x: options.windowConfig?.x,
|
||
y: options.windowConfig?.y,
|
||
resizable: app.manifest.window?.resizable !== false,
|
||
minWidth: app.manifest.window?.minWidth,
|
||
minHeight: app.manifest.window?.minHeight,
|
||
maxWidth: app.manifest.window?.maxWidth,
|
||
maxHeight: app.manifest.window?.maxHeight,
|
||
}
|
||
|
||
const windowInstance = await this.windowService.createWindow(appId, windowConfig)
|
||
windowId = windowInstance.id
|
||
app.windowId = windowId
|
||
|
||
// 对于内置应用,需要在窗口内容区域挂载 AppRenderer 组件
|
||
if (isBuiltInApp) {
|
||
await this.mountBuiltInApp(appId, windowInstance)
|
||
}
|
||
|
||
// 对于内置应用,不需要等待窗口DOM元素渲染
|
||
if (!isBuiltInApp) {
|
||
// 稍等片刻确保窗口DOM元素已经渲染完成
|
||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||
}
|
||
}
|
||
|
||
// 对于外置应用,需要创建沙箱;内置应用跳过沙箱
|
||
if (isExternalApp && !isBuiltInApp) {
|
||
// 为外置应用创建沙箱
|
||
const sandboxConfig = {
|
||
securityLevel: 2, // HIGH
|
||
allowScripts: true,
|
||
allowSameOrigin: false, // 安全考虑:不允许同源访问以防止沙箱逃逸
|
||
allowForms: true,
|
||
networkTimeout: 15000, // 增加超时时间到15秒
|
||
csp:
|
||
app.manifest.contentSecurity?.policy ||
|
||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';",
|
||
}
|
||
|
||
const sandbox = await this.sandboxEngine.createSandbox(
|
||
appId,
|
||
windowId || 'background',
|
||
sandboxConfig,
|
||
)
|
||
app.sandboxId = sandbox.id
|
||
|
||
// 为外置应用加载代码
|
||
await this.loadExternalAppInSandbox(app, sandbox.id)
|
||
} else if (isBuiltInApp) {
|
||
console.log(`[LifecycleManager] 内置应用 ${appId} 跳过沙箱创建和代码加载`)
|
||
} else {
|
||
console.warn(`[LifecycleManager] 未知应用类型: ${appId}`)
|
||
}
|
||
|
||
// 更新状态
|
||
const now = new Date()
|
||
app.startedAt = now
|
||
app.lastActiveAt = now
|
||
this.updateAppState(app, AppLifecycleState.RUNNING)
|
||
|
||
// 添加到运行进程列表
|
||
this.runningProcesses.set(processId, app)
|
||
|
||
// 注意:状态变更消息由updateAppState方法自动发送,不需要手动发送
|
||
|
||
console.log(`应用 ${app.manifest.name} (${appId}) 启动成功,进程ID: ${processId}`)
|
||
return processId
|
||
} catch (error) {
|
||
this.updateAppState(app, AppLifecycleState.ERROR)
|
||
app.errorCount++
|
||
|
||
console.error('应用启动失败:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 停止应用
|
||
*/
|
||
async stopApp(appId: string): Promise<boolean> {
|
||
// 首先从已安装应用中查找
|
||
let app = this.installedApps.get(appId)
|
||
|
||
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
|
||
if (!app) {
|
||
for (const runningApp of this.runningProcesses.values()) {
|
||
if (runningApp.id === appId) {
|
||
app = runningApp
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!app) {
|
||
throw new Error(`应用 ${appId} 未安装或未运行`)
|
||
}
|
||
|
||
if (app.state !== AppLifecycleState.RUNNING && app.state !== AppLifecycleState.SUSPENDED) {
|
||
return true
|
||
}
|
||
|
||
try {
|
||
this.updateAppState(app, AppLifecycleState.STOPPING)
|
||
|
||
// 销毁沙箱
|
||
if (app.sandboxId) {
|
||
this.sandboxEngine.destroySandbox(app.sandboxId)
|
||
app.sandboxId = undefined
|
||
}
|
||
|
||
// 关闭窗体(如果还存在)
|
||
if (app.windowId) {
|
||
const window = this.windowService.getWindow(app.windowId)
|
||
if (window) {
|
||
await this.windowService.destroyWindow(app.windowId)
|
||
}
|
||
app.windowId = undefined
|
||
}
|
||
|
||
// 更新状态
|
||
app.stoppedAt = new Date()
|
||
this.updateAppState(app, AppLifecycleState.STOPPED)
|
||
|
||
// 从运行进程列表中移除
|
||
if (app.processId) {
|
||
this.runningProcesses.delete(app.processId)
|
||
app.processId = ''
|
||
}
|
||
|
||
// 注意:状态变更消息由updateAppState方法自动发送,不需要手动发送
|
||
|
||
console.log(`应用 ${appId} 停止成功`)
|
||
return true
|
||
} catch (error) {
|
||
console.error('应用停止失败:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 暂停应用
|
||
*/
|
||
async suspendApp(appId: string): Promise<boolean> {
|
||
// 首先从已安装应用中查找
|
||
let app = this.installedApps.get(appId)
|
||
|
||
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
|
||
if (!app) {
|
||
for (const runningApp of this.runningProcesses.values()) {
|
||
if (runningApp.id === appId) {
|
||
app = runningApp
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!app || app.state !== AppLifecycleState.RUNNING) {
|
||
return false
|
||
}
|
||
|
||
try {
|
||
// 暂停沙箱
|
||
if (app.sandboxId) {
|
||
this.sandboxEngine.suspendSandbox(app.sandboxId)
|
||
}
|
||
|
||
// 最小化窗体
|
||
if (app.windowId) {
|
||
this.windowService.minimizeWindow(app.windowId)
|
||
}
|
||
|
||
this.updateAppState(app, AppLifecycleState.SUSPENDED)
|
||
|
||
// 注意:状态变更消息由updateAppState方法自动发送,不需要手动发送
|
||
|
||
console.log(`应用 ${appId} 已暂停`)
|
||
return true
|
||
} catch (error) {
|
||
console.error('应用暂停失败:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 恢复应用
|
||
*/
|
||
async resumeApp(appId: string): Promise<boolean> {
|
||
// 首先从已安装应用中查找
|
||
let app = this.installedApps.get(appId)
|
||
|
||
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
|
||
if (!app) {
|
||
for (const runningApp of this.runningProcesses.values()) {
|
||
if (runningApp.id === appId) {
|
||
app = runningApp
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!app || app.state !== AppLifecycleState.SUSPENDED) {
|
||
return false
|
||
}
|
||
|
||
try {
|
||
// 恢复沙箱
|
||
if (app.sandboxId) {
|
||
this.sandboxEngine.resumeSandbox(app.sandboxId)
|
||
}
|
||
|
||
// 恢复窗体
|
||
if (app.windowId) {
|
||
this.windowService.restoreWindow(app.windowId)
|
||
}
|
||
|
||
app.lastActiveAt = new Date()
|
||
this.updateAppState(app, AppLifecycleState.RUNNING)
|
||
|
||
// 注意:状态变更消息由updateAppState方法自动发送,不需要手动发送
|
||
|
||
console.log(`应用 ${appId} 已恢复`)
|
||
return true
|
||
} catch (error) {
|
||
console.error('应用恢复失败:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重启应用
|
||
*/
|
||
async restartApp(appId: string, options?: AppStartOptions): Promise<string> {
|
||
await this.stopApp(appId)
|
||
// 等待一小段时间确保完全停止
|
||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||
return this.startApp(appId, options)
|
||
}
|
||
|
||
/**
|
||
* 获取应用信息
|
||
*/
|
||
getApp(appId: string): AppInstance | undefined {
|
||
return this.installedApps.get(appId)
|
||
}
|
||
|
||
/**
|
||
* 获取所有已安装应用
|
||
*/
|
||
getAllApps(): AppInstance[] {
|
||
return Array.from(this.installedApps.values())
|
||
}
|
||
|
||
/**
|
||
* 获取正在运行的应用
|
||
*/
|
||
getRunningApps(): AppInstance[] {
|
||
return Array.from(this.runningProcesses.values())
|
||
}
|
||
|
||
/**
|
||
* 检查应用是否正在运行
|
||
*/
|
||
isAppRunning(appId: string): boolean {
|
||
// 首先从已安装应用中查找
|
||
let app = this.installedApps.get(appId)
|
||
|
||
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
|
||
if (!app) {
|
||
for (const runningApp of this.runningProcesses.values()) {
|
||
if (runningApp.id === appId) {
|
||
app = runningApp
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
return app?.state === AppLifecycleState.RUNNING || app?.state === AppLifecycleState.SUSPENDED
|
||
}
|
||
|
||
/**
|
||
* 获取应用统计信息
|
||
*/
|
||
getAppStats(appId: string) {
|
||
const app = this.installedApps.get(appId)
|
||
if (!app) return null
|
||
|
||
return {
|
||
state: app.state,
|
||
errorCount: app.errorCount,
|
||
crashCount: app.crashCount,
|
||
memoryUsage: app.memoryUsage,
|
||
cpuUsage: app.cpuUsage,
|
||
startedAt: app.startedAt,
|
||
lastActiveAt: app.lastActiveAt,
|
||
uptime: app.startedAt ? Date.now() - app.startedAt.getTime() : 0,
|
||
}
|
||
}
|
||
|
||
// 私有方法
|
||
|
||
/**
|
||
* 为内置应用挂载 AppRenderer 组件
|
||
*/
|
||
private async mountBuiltInApp(appId: string, windowInstance: any): Promise<void> {
|
||
try {
|
||
// 动态导入 Vue 和 AppRenderer
|
||
const { createApp } = await import('vue')
|
||
const AppRenderer = (await import('../ui/components/AppRenderer.vue')).default
|
||
|
||
console.log(`[LifecycleManager] 为内置应用 ${appId} 创建 AppRenderer 组件`)
|
||
|
||
const app = createApp({
|
||
components: { AppRenderer },
|
||
template: `<AppRenderer :app-id="'${appId}'" :window-id="'${windowInstance.id}'"/>`,
|
||
})
|
||
|
||
// 提供系统服务(使用当前实例所在的系统服务)
|
||
app.provide('systemService', {
|
||
getWindowService: () => this.windowService,
|
||
getResourceService: () => this.resourceService,
|
||
getEventService: () => this.eventService,
|
||
getSandboxEngine: () => this.sandboxEngine,
|
||
getLifecycleManager: () => this,
|
||
})
|
||
|
||
// 挂载到窗口内容区域
|
||
const contentArea = windowInstance.element?.querySelector('.window-content')
|
||
if (contentArea) {
|
||
app.mount(contentArea)
|
||
console.log(`[LifecycleManager] AppRenderer 组件已挂载到窗口 ${windowInstance.id}`)
|
||
} else {
|
||
throw new Error('未找到窗口内容区域')
|
||
}
|
||
} catch (error) {
|
||
console.error(`内置应用 ${appId} 挂载失败:`, error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证应用清单文件
|
||
*/
|
||
private validateManifest(manifest: AppManifest): void {
|
||
const required = ['id', 'name', 'version', 'entryPoint']
|
||
|
||
for (const field of required) {
|
||
if (!manifest[field as keyof AppManifest]) {
|
||
throw new Error(`清单文件缺少必需字段: ${field}`)
|
||
}
|
||
}
|
||
|
||
// 验证版本格式
|
||
if (!/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
||
throw new Error('版本号格式不正确,应为 x.y.z 格式')
|
||
}
|
||
|
||
// 验证应用ID格式
|
||
if (!/^[a-zA-Z0-9._-]+$/.test(manifest.id)) {
|
||
throw new Error('应用ID格式不正确')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证校验和
|
||
*/
|
||
private verifyChecksum(files: Map<string, Blob | string>, expectedChecksum: string): boolean {
|
||
// 简化实现,实际应用中应使用更强的哈希算法
|
||
let content = ''
|
||
for (const [path, data] of files.entries()) {
|
||
content += path + (typeof data === 'string' ? data : data.size)
|
||
}
|
||
|
||
const actualChecksum = this.safeBase64Encode(content).slice(0, 32)
|
||
return actualChecksum === expectedChecksum
|
||
}
|
||
|
||
/**
|
||
* 安全的 Base64 编码,支持 Unicode 字符
|
||
*/
|
||
private safeBase64Encode(str: string): string {
|
||
try {
|
||
// 使用 encodeURIComponent + atob 来处理 Unicode 字符
|
||
return btoa(
|
||
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) {
|
||
return String.fromCharCode(parseInt(p1, 16))
|
||
}),
|
||
)
|
||
} catch (error) {
|
||
// 如果还是失败,使用简单的哈希算法
|
||
let hash = 0
|
||
for (let i = 0; i < str.length; i++) {
|
||
const char = str.charCodeAt(i)
|
||
hash = (hash << 5) - hash + char
|
||
hash = hash & hash // 转换为32位整数
|
||
}
|
||
return Math.abs(hash).toString(36)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查权限
|
||
*/
|
||
private async checkPermissions(permissions: string[]): Promise<void> {
|
||
for (const permission of permissions) {
|
||
// 这里可以添加权限检查逻辑
|
||
// 目前简单允许所有权限
|
||
console.log(`检查权限: ${permission}`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 在沙箱中加载应用
|
||
*/
|
||
private async loadAppInSandbox(app: AppInstance, sandboxId: string): Promise<void> {
|
||
const appFiles = this.appFiles.get(app.id)
|
||
if (!appFiles) {
|
||
throw new Error('应用文件不存在')
|
||
}
|
||
|
||
const entryFile = appFiles.get(app.manifest.entryPoint)
|
||
if (!entryFile) {
|
||
throw new Error('入口文件不存在')
|
||
}
|
||
|
||
// 创建应用URL
|
||
const entryUrl = this.createAppUrl(app.id, app.manifest.entryPoint, entryFile)
|
||
|
||
// 在沙箱中加载应用
|
||
await this.sandboxEngine.loadApplication(sandboxId, entryUrl)
|
||
}
|
||
|
||
/**
|
||
* 创建应用文件URL
|
||
*/
|
||
private createAppUrl(appId: string, filePath: string, content: Blob | string): string {
|
||
let blob: Blob
|
||
|
||
if (typeof content === 'string') {
|
||
// 确保使用UTF-8编码创建Blob
|
||
const contentType = this.getContentType(filePath)
|
||
const utf8ContentType = contentType.includes('charset')
|
||
? contentType
|
||
: `${contentType}; charset=utf-8`
|
||
blob = new Blob([content], { type: utf8ContentType })
|
||
} else {
|
||
blob = content
|
||
}
|
||
|
||
return URL.createObjectURL(blob)
|
||
}
|
||
|
||
/**
|
||
* 获取文件内容类型
|
||
*/
|
||
private getContentType(filePath: string): string {
|
||
const ext = filePath.split('.').pop()?.toLowerCase()
|
||
|
||
switch (ext) {
|
||
case 'html':
|
||
return 'text/html; charset=utf-8'
|
||
case 'js':
|
||
return 'application/javascript; charset=utf-8'
|
||
case 'css':
|
||
return 'text/css; charset=utf-8'
|
||
case 'json':
|
||
return 'application/json; charset=utf-8'
|
||
case 'png':
|
||
return 'image/png'
|
||
case 'jpg':
|
||
case 'jpeg':
|
||
return 'image/jpeg'
|
||
case 'svg':
|
||
return 'image/svg+xml; charset=utf-8'
|
||
default:
|
||
return 'text/plain; charset=utf-8'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存应用到存储
|
||
*/
|
||
private async saveAppToStorage(app: AppInstance): Promise<void> {
|
||
const appData = {
|
||
id: app.id,
|
||
manifest: app.manifest,
|
||
installedAt: app.installedAt.toISOString(),
|
||
version: app.version,
|
||
autoStart: app.autoStart,
|
||
persistent: app.persistent,
|
||
}
|
||
|
||
await this.resourceService.setStorage('system', `apps.${app.id}`, appData)
|
||
}
|
||
|
||
/**
|
||
* 从存储中删除应用
|
||
*/
|
||
private async removeAppFromStorage(appId: string): Promise<void> {
|
||
await this.resourceService.removeStorage('system', `apps.${appId}`)
|
||
}
|
||
|
||
/**
|
||
* 初始化外置应用发现(已由 SystemServiceIntegration 统一管理)
|
||
* 这个方法保留用于未来可能的扩展,但不再重复启动发现服务
|
||
*/
|
||
private async initializeExternalAppDiscovery(): Promise<void> {
|
||
try {
|
||
console.log('[LifecycleManager] 外置应用发现服务已由系统服务集成统一管理')
|
||
|
||
// 可以在这里添加应用生命周期管理器特有的外置应用监听逻辑
|
||
// 例如监听外置应用的注册/卸载事件
|
||
} catch (error) {
|
||
console.error('[LifecycleManager] 初始化外置应用监听失败:', error)
|
||
}
|
||
}
|
||
|
||
// 已移除:外部应用不再需要注册到 installedApps 中
|
||
// 外部应用在启动时会创建临时实例
|
||
|
||
/**
|
||
* 为外置应用加载代码到沙箱
|
||
*/
|
||
private async loadExternalAppInSandbox(app: AppInstance, sandboxId: string): Promise<void> {
|
||
try {
|
||
const externalApp = externalAppDiscovery.getApp(app.id)
|
||
if (!externalApp) {
|
||
throw new Error('外置应用信息未找到')
|
||
}
|
||
|
||
// 直接使用外置应用的入口URL
|
||
const entryUrl = externalApp.entryPath
|
||
|
||
console.log(`[LifecycleManager] 加载外置应用: ${app.id} from ${entryUrl}`)
|
||
|
||
// 在沙箱中加载应用
|
||
await this.sandboxEngine.loadApplication(sandboxId, entryUrl)
|
||
} catch (error) {
|
||
console.error(`[LifecycleManager] 加载外置应用到沙箱失败:`, error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有可用应用(包括内置和外置)
|
||
*/
|
||
getAllAvailableApps(): (AppInstance & { isExternal?: boolean })[] {
|
||
const apps: (AppInstance & { isExternal?: boolean })[] = []
|
||
|
||
// 添加已安装的应用
|
||
for (const app of this.installedApps.values()) {
|
||
apps.push(app)
|
||
}
|
||
|
||
// 添加未注册的外置应用
|
||
for (const externalApp of externalAppDiscovery.getDiscoveredApps()) {
|
||
if (!this.installedApps.has(externalApp.id)) {
|
||
const appInstance: AppInstance & { isExternal: boolean } = {
|
||
id: externalApp.manifest.id,
|
||
manifest: externalApp.manifest,
|
||
state: AppLifecycleState.AVAILABLE,
|
||
processId: '',
|
||
installedAt: externalApp.lastScanned,
|
||
errorCount: 0,
|
||
crashCount: 0,
|
||
memoryUsage: 0,
|
||
cpuUsage: 0,
|
||
version: externalApp.manifest.version,
|
||
autoStart: false,
|
||
persistent: false,
|
||
isExternal: true,
|
||
}
|
||
apps.push(appInstance)
|
||
}
|
||
}
|
||
|
||
return apps
|
||
}
|
||
|
||
/**
|
||
* 刷新外置应用列表
|
||
*/
|
||
async refreshExternalApps(): Promise<void> {
|
||
try {
|
||
await externalAppDiscovery.refresh()
|
||
console.log('[LifecycleManager] 外置应用列表已刷新')
|
||
} catch (error) {
|
||
console.error('[LifecycleManager] 刷新外置应用列表失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载已安装的应用
|
||
*/
|
||
private async loadInstalledApps(): Promise<void> {
|
||
try {
|
||
// 从存储中加载应用列表
|
||
const appsData = (await this.resourceService.getStorage('system', 'apps')) || {}
|
||
|
||
for (const [appId, appData] of Object.entries(appsData as Record<string, any>)) {
|
||
if (appData && typeof appData === 'object') {
|
||
const app: AppInstance = {
|
||
id: appId,
|
||
manifest: appData.manifest,
|
||
state: AppLifecycleState.STOPPED,
|
||
processId: '',
|
||
installedAt: new Date(appData.installedAt),
|
||
errorCount: 0,
|
||
crashCount: 0,
|
||
memoryUsage: 0,
|
||
cpuUsage: 0,
|
||
version: appData.version,
|
||
autoStart: appData.autoStart || false,
|
||
persistent: appData.persistent || false,
|
||
}
|
||
|
||
this.updateAppState(app, AppLifecycleState.INSTALLED)
|
||
this.installedApps.set(appId, app)
|
||
}
|
||
}
|
||
|
||
console.log(`已加载 ${this.installedApps.size} 个已安装应用`)
|
||
} catch (error) {
|
||
console.error('加载已安装应用失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置事件监听器
|
||
*/
|
||
private setupEventListeners(): void {
|
||
// 监听沙箱状态变化
|
||
this.eventService.subscribe('system', 'sandbox-state-change', (message) => {
|
||
const { sandboxId, newState } = message.payload
|
||
|
||
// 查找对应的应用
|
||
for (const app of this.installedApps.values()) {
|
||
if (app.sandboxId === sandboxId) {
|
||
if (newState === 'error') {
|
||
this.handleAppError(app.id, new Error('沙箱错误'))
|
||
} else if (newState === 'destroyed') {
|
||
this.handleAppCrash(app.id, '沙箱被销毁')
|
||
}
|
||
break
|
||
}
|
||
}
|
||
})
|
||
|
||
// 监听性能告警
|
||
this.eventService.subscribe('system', 'performance-alert', (message) => {
|
||
const { sandboxId, type, usage, limit } = message.payload
|
||
|
||
for (const app of this.installedApps.values()) {
|
||
if (app.sandboxId === sandboxId) {
|
||
if (type === 'memory') {
|
||
app.memoryUsage = usage
|
||
} else if (type === 'cpu') {
|
||
app.cpuUsage = usage
|
||
}
|
||
|
||
console.warn(`应用 ${app.id} ${type} 使用量 ${usage} 超过限制 ${limit}`)
|
||
break
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 处理应用错误
|
||
*/
|
||
private handleAppError(appId: string, error: Error): void {
|
||
const app = this.installedApps.get(appId)
|
||
if (!app) return
|
||
|
||
app.errorCount++
|
||
|
||
this.eventService.sendMessage('system', 'app-lifecycle', {
|
||
type: 'error',
|
||
appId,
|
||
error: error.message,
|
||
})
|
||
|
||
console.error(`应用 ${appId} 发生错误:`, error)
|
||
}
|
||
|
||
/**
|
||
* 处理应用崩溃
|
||
*/
|
||
private handleAppCrash(appId: string, reason: string): void {
|
||
const app = this.installedApps.get(appId)
|
||
if (!app) return
|
||
|
||
app.crashCount++
|
||
this.updateAppState(app, AppLifecycleState.CRASHED)
|
||
|
||
// 清理资源
|
||
if (app.processId) {
|
||
this.runningProcesses.delete(app.processId)
|
||
}
|
||
|
||
// 注意:状态变更消息由updateAppState方法自动发送,不需要手动发送
|
||
|
||
console.error(`应用 ${appId} 崩溃: ${reason}`)
|
||
}
|
||
|
||
/**
|
||
* 更新应用状态
|
||
*/
|
||
private updateAppState(app: AppInstance, newState: AppLifecycleState): void {
|
||
const oldState = app.state
|
||
app.state = newState
|
||
|
||
this.eventService.sendMessage('system', 'app-lifecycle', {
|
||
type: 'stateChanged',
|
||
appId: app.id,
|
||
newState,
|
||
oldState,
|
||
})
|
||
}
|
||
}
|