Files
vue-desktop/src/services/ApplicationLifecycleManager.ts

588 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-25 15:31:11 +08:00
import { reactive } from 'vue'
2025-09-24 16:43:10 +08:00
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'
2025-09-25 15:31:11 +08:00
import { externalAppDiscovery } from './ExternalAppDiscovery'
2025-09-24 16:43:10 +08:00
/**
*
*/
export enum AppLifecycleState {
INSTALLING = 'installing',
INSTALLED = 'installed',
STARTING = 'starting',
RUNNING = 'running',
SUSPENDED = 'suspended',
STOPPING = 'stopping',
STOPPED = 'stopped',
UNINSTALLING = 'uninstalling',
ERROR = 'error',
CRASHED = 'crashed',
2025-10-10 10:08:05 +08:00
AVAILABLE = 'available' // 外置应用可用但未注册状态
2025-09-24 16:43:10 +08:00
}
/**
*
*/
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 eventService: EventCommunicationService
private sandboxEngine: ApplicationSandboxEngine
constructor(
windowService: WindowService,
resourceService: ResourceService,
eventService: EventCommunicationService,
2025-10-10 10:08:05 +08:00
sandboxEngine: ApplicationSandboxEngine
2025-09-24 16:43:10 +08:00
) {
this.windowService = windowService
this.resourceService = resourceService
this.eventService = eventService
this.sandboxEngine = sandboxEngine
this.setupEventListeners()
}
/**
*
*/
async startApp(appId: string, options: AppStartOptions = {}): Promise<string> {
let app = this.installedApps.get(appId)
2025-10-10 10:08:05 +08:00
console.log('-----------------------------')
2025-09-24 16:43:10 +08:00
// 如果应用未安装,检查是否为外置应用
2025-09-25 15:31:11 +08:00
let isExternalApp = false
2025-09-24 16:43:10 +08:00
if (!app) {
const externalApp = externalAppDiscovery.getApp(appId)
if (externalApp) {
2025-09-25 15:31:11 +08:00
console.log(`[LifecycleManager] 发现外置应用 ${appId}`)
isExternalApp = true
2025-10-10 10:08:05 +08:00
2025-09-25 15:31:11 +08:00
// 为外部应用创建临时实例
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,
2025-10-10 10:08:05 +08:00
persistent: false
2025-09-25 15:31:11 +08:00
}
2025-09-24 16:43:10 +08:00
}
}
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)
// 检查是否为内置应用
2025-10-10 10:08:05 +08:00
let isBuiltInApp = true
2025-09-24 16:43:10 +08:00
// 创建窗体(如果不是后台应用)
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,
2025-10-10 10:08:05 +08:00
maxHeight: app.manifest.window?.maxHeight
2025-09-24 16:43:10 +08:00
}
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 ||
2025-10-10 10:08:05 +08:00
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';"
2025-09-24 16:43:10 +08:00
}
const sandbox = await this.sandboxEngine.createSandbox(
appId,
windowId || 'background',
2025-10-10 10:08:05 +08:00
sandboxConfig
2025-09-24 16:43:10 +08:00
)
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> {
2025-09-25 15:31:11 +08:00
// 首先从已安装应用中查找
let app = this.installedApps.get(appId)
2025-10-10 10:08:05 +08:00
2025-09-25 15:31:11 +08:00
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
2025-09-24 16:43:10 +08:00
if (!app) {
2025-09-25 15:31:11 +08:00
for (const runningApp of this.runningProcesses.values()) {
if (runningApp.id === appId) {
app = runningApp
break
}
}
}
2025-10-10 10:08:05 +08:00
2025-09-25 15:31:11 +08:00
if (!app) {
throw new Error(`应用 ${appId} 未安装或未运行`)
2025-09-24 16:43:10 +08:00
}
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 {
2025-09-25 15:31:11 +08:00
// 首先从已安装应用中查找
let app = this.installedApps.get(appId)
2025-10-10 10:08:05 +08:00
2025-09-25 15:31:11 +08:00
// 如果未找到,从运行进程列表中查找(可能是外部应用的临时实例)
if (!app) {
for (const runningApp of this.runningProcesses.values()) {
if (runningApp.id === appId) {
app = runningApp
break
}
}
}
2025-10-10 10:08:05 +08:00
2025-09-24 16:43:10 +08:00
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,
2025-10-10 10:08:05 +08:00
uptime: app.startedAt ? Date.now() - app.startedAt.getTime() : 0
2025-09-24 16:43:10 +08:00
}
}
// 私有方法
/**
* 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 组件`)
2025-10-10 10:08:05 +08:00
console.log('----------------------------------')
2025-09-24 16:43:10 +08:00
const app = createApp({
components: { AppRenderer },
2025-10-10 10:08:05 +08:00
template: `<AppRenderer :app-id="'${appId}'" :window-id="'${windowInstance.id}'"/>`
2025-09-24 16:43:10 +08:00
})
// 提供系统服务(使用当前实例所在的系统服务)
app.provide('systemService', {
getWindowService: () => this.windowService,
getResourceService: () => this.resourceService,
getEventService: () => this.eventService,
getSandboxEngine: () => this.sandboxEngine,
2025-10-10 10:08:05 +08:00
getLifecycleManager: () => this
2025-09-24 16:43:10 +08:00
})
// 挂载到窗口内容区域
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 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,
2025-10-10 10:08:05 +08:00
isExternal: true
2025-09-24 16:43:10 +08:00
}
apps.push(appInstance)
}
}
return apps
}
/**
*
*/
async refreshExternalApps(): Promise<void> {
try {
await externalAppDiscovery.refresh()
console.log('[LifecycleManager] 外置应用列表已刷新')
} catch (error) {
console.error('[LifecycleManager] 刷新外置应用列表失败:', 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
}
}
})
}
/**
*
*/
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,
2025-10-10 10:08:05 +08:00
error: error.message
2025-09-24 16:43:10 +08:00
})
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,
2025-10-10 10:08:05 +08:00
oldState
2025-09-24 16:43:10 +08:00
})
}
}