Files
vue-desktop/src/services/ApplicationLifecycleManager.ts
2025-10-10 10:28:36 +08:00

512 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { reactive } from 'vue'
import type { WindowService } from './WindowService'
import type { ResourceService } from './ResourceService'
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 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 sandboxEngine: ApplicationSandboxEngine
constructor(
windowService: WindowService,
resourceService: ResourceService,
sandboxEngine: ApplicationSandboxEngine
) {
this.windowService = windowService
this.resourceService = resourceService
this.sandboxEngine = sandboxEngine
}
/**
* 启动应用
*/
async startApp(appId: string, options: AppStartOptions = {}): Promise<string> {
let app = this.installedApps.get(appId)
console.log('-----------------------------')
// 如果应用未安装,检查是否为外置应用
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 = true
// 创建窗体(如果不是后台应用)
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
}
// 对于外置应用,需要创建沙箱;内置应用跳过沙箱
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
}
}
/**
* 获取应用信息
*/
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
}
}
/**
* 检查权限
*/
private async checkPermissions(permissions: string[]): Promise<void> {
for (const permission of permissions) {
// 这里可以添加权限检查逻辑
// 目前简单允许所有权限
console.log(`检查权限: ${permission}`)
}
}
/**
* 为外置应用加载代码到沙箱
*/
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 handleAppError(appId: string, error: Error): void {
const app = this.installedApps.get(appId)
if (!app) return
app.errorCount++
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
// })
}
}