2025-09-25 15:31:11 +08:00
|
|
|
|
import { reactive } from 'vue'
|
2025-09-24 16:43:10 +08:00
|
|
|
|
import type { ResourceService } from './ResourceService'
|
|
|
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱状态枚举
|
|
|
|
|
|
*/
|
|
|
|
|
|
export enum SandboxState {
|
|
|
|
|
|
INITIALIZING = 'initializing',
|
|
|
|
|
|
READY = 'ready',
|
|
|
|
|
|
RUNNING = 'running',
|
|
|
|
|
|
SUSPENDED = 'suspended',
|
|
|
|
|
|
ERROR = 'error',
|
|
|
|
|
|
DESTROYED = 'destroyed'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱类型枚举
|
|
|
|
|
|
*/
|
|
|
|
|
|
export enum SandboxType {
|
|
|
|
|
|
IFRAME = 'iframe',
|
|
|
|
|
|
WEBWORKER = 'webworker',
|
|
|
|
|
|
SHADOW_DOM = 'shadowdom'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱安全级别
|
|
|
|
|
|
*/
|
|
|
|
|
|
export enum SecurityLevel {
|
|
|
|
|
|
LOW = 0,
|
|
|
|
|
|
MEDIUM = 1,
|
|
|
|
|
|
HIGH = 2,
|
|
|
|
|
|
STRICT = 3
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱配置接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface SandboxConfig {
|
|
|
|
|
|
type: SandboxType
|
|
|
|
|
|
securityLevel: SecurityLevel
|
|
|
|
|
|
allowScripts: boolean
|
|
|
|
|
|
allowSameOrigin: boolean
|
|
|
|
|
|
allowForms: boolean
|
|
|
|
|
|
allowPopups: boolean
|
|
|
|
|
|
allowModals: boolean
|
|
|
|
|
|
allowPointerLock: boolean
|
|
|
|
|
|
allowPresentation: boolean
|
|
|
|
|
|
allowTopNavigation: boolean
|
|
|
|
|
|
maxMemoryUsage: number // MB
|
|
|
|
|
|
maxCpuUsage: number // 百分比
|
|
|
|
|
|
networkTimeout: number // 网络请求超时时间(ms)
|
|
|
|
|
|
csp: string // 内容安全策略
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱实例接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface SandboxInstance {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
appId: string
|
|
|
|
|
|
windowId: string
|
|
|
|
|
|
config: SandboxConfig
|
|
|
|
|
|
state: SandboxState
|
|
|
|
|
|
container: HTMLElement | null
|
|
|
|
|
|
iframe?: HTMLIFrameElement
|
|
|
|
|
|
worker?: Worker
|
|
|
|
|
|
shadowRoot?: ShadowRoot
|
|
|
|
|
|
createdAt: Date
|
|
|
|
|
|
lastActiveAt: Date
|
|
|
|
|
|
memoryUsage: number
|
|
|
|
|
|
cpuUsage: number
|
|
|
|
|
|
networkRequests: number
|
|
|
|
|
|
errors: string[]
|
2025-09-25 15:31:11 +08:00
|
|
|
|
messageHandler?: (event: MessageEvent) => void
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱性能监控数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface SandboxPerformance {
|
|
|
|
|
|
sandboxId: string
|
|
|
|
|
|
timestamp: Date
|
|
|
|
|
|
memoryUsage: number
|
|
|
|
|
|
cpuUsage: number
|
|
|
|
|
|
networkRequests: number
|
|
|
|
|
|
renderTime: number
|
|
|
|
|
|
jsExecutionTime: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沙箱事件接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface SandboxEvents {
|
|
|
|
|
|
onStateChange: (sandboxId: string, newState: SandboxState, oldState: SandboxState) => void
|
|
|
|
|
|
onError: (sandboxId: string, error: Error) => void
|
|
|
|
|
|
onPerformanceAlert: (sandboxId: string, metrics: SandboxPerformance) => void
|
|
|
|
|
|
onResourceLimit: (sandboxId: string, resourceType: string, usage: number, limit: number) => void
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 应用沙箱引擎类
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ApplicationSandboxEngine {
|
|
|
|
|
|
private sandboxes = reactive(new Map<string, SandboxInstance>())
|
|
|
|
|
|
private performanceData = reactive(new Map<string, SandboxPerformance[]>())
|
|
|
|
|
|
private monitoringInterval: number | null = null
|
|
|
|
|
|
private resourceService: ResourceService
|
|
|
|
|
|
|
2025-10-11 12:10:35 +08:00
|
|
|
|
constructor(resourceService: ResourceService) {
|
2025-09-24 16:43:10 +08:00
|
|
|
|
this.resourceService = resourceService
|
|
|
|
|
|
this.startPerformanceMonitoring()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建沙箱实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
async createSandbox(
|
|
|
|
|
|
appId: string,
|
|
|
|
|
|
windowId: string,
|
|
|
|
|
|
config: Partial<SandboxConfig> = {}
|
|
|
|
|
|
): Promise<SandboxInstance> {
|
|
|
|
|
|
const sandboxId = uuidv4()
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
const defaultConfig: SandboxConfig = {
|
|
|
|
|
|
type: SandboxType.IFRAME,
|
|
|
|
|
|
securityLevel: SecurityLevel.HIGH,
|
|
|
|
|
|
allowScripts: true,
|
|
|
|
|
|
allowSameOrigin: false,
|
|
|
|
|
|
allowForms: true,
|
|
|
|
|
|
allowPopups: false,
|
|
|
|
|
|
allowModals: false,
|
|
|
|
|
|
allowPointerLock: false,
|
|
|
|
|
|
allowPresentation: false,
|
|
|
|
|
|
allowTopNavigation: false,
|
|
|
|
|
|
maxMemoryUsage: 100, // 100MB
|
|
|
|
|
|
maxCpuUsage: 30, // 30%
|
|
|
|
|
|
networkTimeout: 10000, // 10秒
|
|
|
|
|
|
csp: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const finalConfig = { ...defaultConfig, ...config }
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
|
|
|
|
|
|
const sandbox: SandboxInstance = {
|
|
|
|
|
|
id: sandboxId,
|
|
|
|
|
|
appId,
|
|
|
|
|
|
windowId,
|
|
|
|
|
|
config: finalConfig,
|
|
|
|
|
|
state: SandboxState.INITIALIZING,
|
|
|
|
|
|
container: null,
|
|
|
|
|
|
createdAt: now,
|
|
|
|
|
|
lastActiveAt: now,
|
|
|
|
|
|
memoryUsage: 0,
|
|
|
|
|
|
cpuUsage: 0,
|
|
|
|
|
|
networkRequests: 0,
|
|
|
|
|
|
errors: []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 根据类型创建沙箱容器
|
|
|
|
|
|
await this.createSandboxContainer(sandbox)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 设置安全策略
|
|
|
|
|
|
this.applySecurity(sandbox)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 设置性能监控
|
|
|
|
|
|
this.setupPerformanceMonitoring(sandbox)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 更新状态
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.READY)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
this.sandboxes.set(sandboxId, sandbox)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
console.log(`沙箱 ${sandboxId} 创建成功`)
|
|
|
|
|
|
return sandbox
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.ERROR)
|
|
|
|
|
|
sandbox.errors.push(error instanceof Error ? error.message : String(error))
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载应用到沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
async loadApplication(sandboxId: string, applicationUrl: string): Promise<boolean> {
|
|
|
|
|
|
const sandbox = this.sandboxes.get(sandboxId)
|
|
|
|
|
|
if (!sandbox) {
|
|
|
|
|
|
throw new Error(`沙箱 ${sandboxId} 不存在`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log(`开始加载应用: ${sandbox.appId}, 沙箱ID: ${sandboxId}`)
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.RUNNING)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
if (sandbox.config.type === SandboxType.IFRAME && sandbox.iframe) {
|
|
|
|
|
|
console.log(`使用iframe加载应用: ${applicationUrl}`)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 检查iframe是否已挂载到DOM
|
|
|
|
|
|
if (!document.contains(sandbox.iframe)) {
|
|
|
|
|
|
console.warn('检测到iframe未挂载到DOM,尝试重新挂载')
|
|
|
|
|
|
await this.mountIframeToWindow(sandbox)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 设置iframe源
|
|
|
|
|
|
sandbox.iframe.src = applicationUrl
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 等待加载完成
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const timeoutId = setTimeout(() => {
|
2025-10-10 10:08:05 +08:00
|
|
|
|
console.error(
|
|
|
|
|
|
`应用加载超时: ${sandbox.appId}, URL: ${applicationUrl}, 超时时间: ${sandbox.config.networkTimeout}ms`
|
|
|
|
|
|
)
|
2025-09-24 16:43:10 +08:00
|
|
|
|
reject(new Error('应用加载超时'))
|
|
|
|
|
|
}, sandbox.config.networkTimeout)
|
|
|
|
|
|
|
|
|
|
|
|
sandbox.iframe!.onload = () => {
|
|
|
|
|
|
console.log(`应用加载成功: ${sandbox.appId}`)
|
|
|
|
|
|
clearTimeout(timeoutId)
|
|
|
|
|
|
this.injectSDK(sandbox)
|
|
|
|
|
|
resolve(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sandbox.iframe!.onerror = (error) => {
|
|
|
|
|
|
console.error(`应用加载失败: ${sandbox.appId}`, error)
|
|
|
|
|
|
clearTimeout(timeoutId)
|
|
|
|
|
|
reject(new Error('应用加载失败'))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (sandbox.config.type === SandboxType.WEBWORKER && sandbox.worker) {
|
|
|
|
|
|
// WebWorker方式加载
|
|
|
|
|
|
sandbox.worker.postMessage({
|
|
|
|
|
|
type: 'load',
|
|
|
|
|
|
url: applicationUrl
|
|
|
|
|
|
})
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
return false
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`加载应用失败: ${sandbox.appId}`, error)
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.ERROR)
|
|
|
|
|
|
sandbox.errors.push(error instanceof Error ? error.message : String(error))
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 销毁沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
destroySandbox(sandboxId: string): boolean {
|
|
|
|
|
|
const sandbox = this.sandboxes.get(sandboxId)
|
|
|
|
|
|
if (!sandbox) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.DESTROYED)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
// 移除消息事件监听器
|
|
|
|
|
|
if (sandbox.messageHandler) {
|
2025-10-10 10:08:05 +08:00
|
|
|
|
window.removeEventListener('message', sandbox.messageHandler)
|
|
|
|
|
|
sandbox.messageHandler = undefined
|
2025-09-25 15:31:11 +08:00
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 清理DOM元素
|
|
|
|
|
|
if (sandbox.iframe) {
|
|
|
|
|
|
sandbox.iframe.remove()
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
if (sandbox.container) {
|
|
|
|
|
|
sandbox.container.remove()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 终止WebWorker
|
|
|
|
|
|
if (sandbox.worker) {
|
|
|
|
|
|
sandbox.worker.terminate()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理性能数据
|
|
|
|
|
|
this.performanceData.delete(sandboxId)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 从集合中移除
|
|
|
|
|
|
this.sandboxes.delete(sandboxId)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
console.log(`沙箱 ${sandboxId} 已销毁`)
|
|
|
|
|
|
return true
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('销毁沙箱失败:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取沙箱实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
getSandbox(sandboxId: string): SandboxInstance | undefined {
|
|
|
|
|
|
return this.sandboxes.get(sandboxId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取应用的所有沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
getAppSandboxes(appId: string): SandboxInstance[] {
|
2025-10-10 10:08:05 +08:00
|
|
|
|
return Array.from(this.sandboxes.values()).filter((sandbox) => sandbox.appId === appId)
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取沙箱性能数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
getPerformanceData(sandboxId: string): SandboxPerformance[] {
|
|
|
|
|
|
return this.performanceData.get(sandboxId) || []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送消息到沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
sendMessage(sandboxId: string, message: any): boolean {
|
|
|
|
|
|
const sandbox = this.sandboxes.get(sandboxId)
|
|
|
|
|
|
if (!sandbox || sandbox.state !== SandboxState.RUNNING) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (sandbox.iframe) {
|
|
|
|
|
|
const iframeWindow = sandbox.iframe.contentWindow
|
|
|
|
|
|
if (iframeWindow) {
|
|
|
|
|
|
iframeWindow.postMessage(message, '*')
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (sandbox.worker) {
|
|
|
|
|
|
sandbox.worker.postMessage(message)
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
return false
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('发送消息到沙箱失败:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清理所有沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
cleanup(): void {
|
|
|
|
|
|
for (const sandboxId of this.sandboxes.keys()) {
|
|
|
|
|
|
this.destroySandbox(sandboxId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 销毁引擎
|
|
|
|
|
|
*/
|
|
|
|
|
|
destroy(): void {
|
|
|
|
|
|
if (this.monitoringInterval) {
|
|
|
|
|
|
clearInterval(this.monitoringInterval)
|
|
|
|
|
|
this.monitoringInterval = null
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
this.cleanup()
|
|
|
|
|
|
console.log('沙箱引擎已销毁')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 私有方法
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建沙箱容器
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async createSandboxContainer(sandbox: SandboxInstance): Promise<void> {
|
|
|
|
|
|
switch (sandbox.config.type) {
|
|
|
|
|
|
case SandboxType.IFRAME:
|
|
|
|
|
|
await this.createIframeSandbox(sandbox)
|
|
|
|
|
|
break
|
|
|
|
|
|
case SandboxType.WEBWORKER:
|
|
|
|
|
|
await this.createWorkerSandbox(sandbox)
|
|
|
|
|
|
break
|
|
|
|
|
|
case SandboxType.SHADOW_DOM:
|
|
|
|
|
|
await this.createShadowDOMSandbox(sandbox)
|
|
|
|
|
|
break
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new Error(`不支持的沙箱类型: ${sandbox.config.type}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建iframe沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async createIframeSandbox(sandbox: SandboxInstance): Promise<void> {
|
|
|
|
|
|
const iframe = document.createElement('iframe')
|
|
|
|
|
|
iframe.id = `sandbox-${sandbox.id}`
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 设置sandbox属性
|
|
|
|
|
|
const sandboxAttributes: string[] = []
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
if (sandbox.config.allowScripts) sandboxAttributes.push('allow-scripts')
|
|
|
|
|
|
if (sandbox.config.allowSameOrigin) sandboxAttributes.push('allow-same-origin')
|
|
|
|
|
|
if (sandbox.config.allowForms) sandboxAttributes.push('allow-forms')
|
|
|
|
|
|
if (sandbox.config.allowPopups) sandboxAttributes.push('allow-popups')
|
|
|
|
|
|
if (sandbox.config.allowModals) sandboxAttributes.push('allow-modals')
|
|
|
|
|
|
if (sandbox.config.allowPointerLock) sandboxAttributes.push('allow-pointer-lock')
|
|
|
|
|
|
if (sandbox.config.allowPresentation) sandboxAttributes.push('allow-presentation')
|
|
|
|
|
|
if (sandbox.config.allowTopNavigation) sandboxAttributes.push('allow-top-navigation')
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
iframe.sandbox = sandboxAttributes.join(' ')
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 设置样式
|
|
|
|
|
|
iframe.style.cssText = `
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
// 设置CSP - 不使用iframe的csp属性,而是在内容中设置
|
|
|
|
|
|
// if (sandbox.config.csp) {
|
|
|
|
|
|
// iframe.setAttribute('csp', sandbox.config.csp)
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
sandbox.iframe = iframe
|
|
|
|
|
|
sandbox.container = iframe
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 将iframe挂载到对应的窗口内容区域
|
|
|
|
|
|
await this.mountIframeToWindow(sandbox)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将iframe挂载到窗口内容区域
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async mountIframeToWindow(sandbox: SandboxInstance): Promise<void> {
|
|
|
|
|
|
const windowElement = document.getElementById(`window-${sandbox.windowId}`)
|
|
|
|
|
|
if (!windowElement) {
|
|
|
|
|
|
// 如果是后台应用,创建隐藏容器
|
|
|
|
|
|
if (sandbox.windowId === 'background') {
|
|
|
|
|
|
const hiddenContainer = document.createElement('div')
|
|
|
|
|
|
hiddenContainer.id = 'background-apps-container'
|
|
|
|
|
|
hiddenContainer.style.cssText = `
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: -9999px;
|
|
|
|
|
|
left: -9999px;
|
|
|
|
|
|
width: 1px;
|
|
|
|
|
|
height: 1px;
|
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
|
`
|
|
|
|
|
|
document.body.appendChild(hiddenContainer)
|
|
|
|
|
|
hiddenContainer.appendChild(sandbox.iframe!)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
throw new Error(`找不到窗口元素: window-${sandbox.windowId}`)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
const contentArea = windowElement.querySelector('.window-content')
|
|
|
|
|
|
if (!contentArea) {
|
|
|
|
|
|
throw new Error(`找不到窗口内容区域: window-${sandbox.windowId}`)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 替换窗口中的默认iframe
|
|
|
|
|
|
const existingIframe = contentArea.querySelector('iframe')
|
|
|
|
|
|
if (existingIframe) {
|
|
|
|
|
|
contentArea.removeChild(existingIframe)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
contentArea.appendChild(sandbox.iframe!)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建WebWorker沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async createWorkerSandbox(sandbox: SandboxInstance): Promise<void> {
|
|
|
|
|
|
// 创建Worker脚本
|
|
|
|
|
|
const workerScript = `
|
|
|
|
|
|
let appCode = null;
|
|
|
|
|
|
|
|
|
|
|
|
self.onmessage = function(e) {
|
|
|
|
|
|
const { type, data } = e.data;
|
|
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 'load':
|
|
|
|
|
|
fetch(data.url)
|
|
|
|
|
|
.then(response => response.text())
|
|
|
|
|
|
.then(code => {
|
|
|
|
|
|
appCode = code;
|
|
|
|
|
|
self.postMessage({ type: 'loaded' });
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
self.postMessage({ type: 'error', error: error.message });
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'execute':
|
|
|
|
|
|
if (appCode) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
eval(appCode);
|
|
|
|
|
|
self.postMessage({ type: 'executed' });
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
self.postMessage({ type: 'error', error: error.message });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
const blob = new Blob([workerScript], { type: 'application/javascript' })
|
|
|
|
|
|
const workerUrl = URL.createObjectURL(blob)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
const worker = new Worker(workerUrl)
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
worker.onmessage = (e) => {
|
|
|
|
|
|
const { type, error } = e.data
|
|
|
|
|
|
if (type === 'error') {
|
|
|
|
|
|
sandbox.errors.push(error)
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.ERROR)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
worker.onerror = (error) => {
|
|
|
|
|
|
sandbox.errors.push(error.message)
|
|
|
|
|
|
this.updateSandboxState(sandbox, SandboxState.ERROR)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sandbox.worker = worker
|
|
|
|
|
|
URL.revokeObjectURL(workerUrl)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建Shadow DOM沙箱
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async createShadowDOMSandbox(sandbox: SandboxInstance): Promise<void> {
|
|
|
|
|
|
const container = document.createElement('div')
|
|
|
|
|
|
container.id = `sandbox-${sandbox.id}`
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
const shadowRoot = container.attachShadow({ mode: 'closed' })
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 添加样式隔离
|
|
|
|
|
|
const style = document.createElement('style')
|
|
|
|
|
|
style.textContent = `
|
|
|
|
|
|
:host {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
`
|
|
|
|
|
|
shadowRoot.appendChild(style)
|
|
|
|
|
|
|
|
|
|
|
|
sandbox.container = container
|
|
|
|
|
|
sandbox.shadowRoot = shadowRoot
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 应用安全策略
|
|
|
|
|
|
*/
|
|
|
|
|
|
private applySecurity(sandbox: SandboxInstance): void {
|
|
|
|
|
|
if (sandbox.iframe) {
|
|
|
|
|
|
// 设置安全头
|
|
|
|
|
|
const securityHeaders = {
|
|
|
|
|
|
'X-Frame-Options': 'DENY',
|
|
|
|
|
|
'X-Content-Type-Options': 'nosniff',
|
|
|
|
|
|
'X-XSS-Protection': '1; mode=block',
|
|
|
|
|
|
'Referrer-Policy': 'no-referrer'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 这些头部主要用于服务器端设置,在客户端我们通过其他方式实现
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 监听iframe的消息事件
|
2025-09-25 15:31:11 +08:00
|
|
|
|
const messageHandler = (event: MessageEvent) => {
|
2025-09-24 16:43:10 +08:00
|
|
|
|
if (event.source === sandbox.iframe!.contentWindow) {
|
|
|
|
|
|
this.handleSandboxMessage(sandbox, event.data)
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener('message', messageHandler)
|
2025-09-25 15:31:11 +08:00
|
|
|
|
// 存储事件监听器引用,以便在销毁时移除
|
2025-10-10 10:08:05 +08:00
|
|
|
|
sandbox.messageHandler = messageHandler
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
|
|
|
|
|
// 简化安全限制,主要依赖iframe的sandbox属性
|
|
|
|
|
|
sandbox.iframe.addEventListener('load', () => {
|
|
|
|
|
|
const iframeDoc = sandbox.iframe!.contentDocument
|
|
|
|
|
|
if (iframeDoc) {
|
|
|
|
|
|
// 添加基本的安全提示脚本
|
|
|
|
|
|
const script = iframeDoc.createElement('script')
|
|
|
|
|
|
script.textContent = `
|
|
|
|
|
|
// 标记应用在沙箱中运行
|
|
|
|
|
|
window.__SANDBOXED__ = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用一些显而易见的危险API
|
|
|
|
|
|
if (typeof window.open !== 'undefined') {
|
|
|
|
|
|
window.open = function() {
|
|
|
|
|
|
console.warn('沙箱应用不允许打开新窗口');
|
|
|
|
|
|
return null;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('应用已在安全沙箱中加载');
|
|
|
|
|
|
`
|
|
|
|
|
|
iframeDoc.head.appendChild(script)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理沙箱消息
|
|
|
|
|
|
*/
|
|
|
|
|
|
private handleSandboxMessage(sandbox: SandboxInstance, message: any): void {
|
|
|
|
|
|
if (typeof message !== 'object' || !message.type) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (message.type) {
|
|
|
|
|
|
case 'app:ready':
|
|
|
|
|
|
sandbox.lastActiveAt = new Date()
|
|
|
|
|
|
break
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
case 'app:error':
|
|
|
|
|
|
sandbox.errors.push(message.error || '未知错误')
|
|
|
|
|
|
break
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
case 'app:resource-request':
|
|
|
|
|
|
this.handleResourceRequest(sandbox, message.data)
|
|
|
|
|
|
break
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
case 'app:performance':
|
|
|
|
|
|
this.recordPerformance(sandbox, message.data)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理资源请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private async handleResourceRequest(sandbox: SandboxInstance, request: any): Promise<void> {
|
|
|
|
|
|
const { type, data } = request
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
try {
|
|
|
|
|
|
let result = null
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 'storage':
|
|
|
|
|
|
if (data.action === 'get') {
|
|
|
|
|
|
result = await this.resourceService.getStorage(sandbox.appId, data.key)
|
|
|
|
|
|
} else if (data.action === 'set') {
|
|
|
|
|
|
result = await this.resourceService.setStorage(sandbox.appId, data.key, data.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
case 'network':
|
2025-10-10 10:08:05 +08:00
|
|
|
|
result = await this.resourceService.makeNetworkRequest(
|
|
|
|
|
|
sandbox.appId,
|
|
|
|
|
|
data.url,
|
|
|
|
|
|
data.options
|
|
|
|
|
|
)
|
2025-09-24 16:43:10 +08:00
|
|
|
|
break
|
|
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-09-24 16:43:10 +08:00
|
|
|
|
// 发送结果回沙箱
|
|
|
|
|
|
this.sendMessage(sandbox.id, {
|
|
|
|
|
|
type: 'system:resource-response',
|
|
|
|
|
|
requestId: request.id,
|
|
|
|
|
|
result
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.sendMessage(sandbox.id, {
|
|
|
|
|
|
type: 'system:resource-error',
|
|
|
|
|
|
requestId: request.id,
|
|
|
|
|
|
error: error instanceof Error ? error.message : String(error)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注入SDK
|
|
|
|
|
|
*/
|
|
|
|
|
|
private injectSDK(sandbox: SandboxInstance): void {
|
|
|
|
|
|
if (!sandbox.iframe) return
|
|
|
|
|
|
|
|
|
|
|
|
const iframeDoc = sandbox.iframe.contentDocument
|
|
|
|
|
|
if (!iframeDoc) return
|
|
|
|
|
|
|
|
|
|
|
|
// 确保正确的字符编码
|
|
|
|
|
|
const existingCharset = iframeDoc.querySelector('meta[charset]')
|
|
|
|
|
|
if (!existingCharset) {
|
|
|
|
|
|
const charsetMeta = iframeDoc.createElement('meta')
|
|
|
|
|
|
charsetMeta.setAttribute('charset', 'UTF-8')
|
|
|
|
|
|
iframeDoc.head.insertBefore(charsetMeta, iframeDoc.head.firstChild)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加正确的CSP meta标签
|
|
|
|
|
|
const existingCSP = iframeDoc.querySelector('meta[http-equiv="Content-Security-Policy"]')
|
|
|
|
|
|
if (!existingCSP && sandbox.config.csp) {
|
|
|
|
|
|
const cspMeta = iframeDoc.createElement('meta')
|
|
|
|
|
|
cspMeta.setAttribute('http-equiv', 'Content-Security-Policy')
|
|
|
|
|
|
cspMeta.setAttribute('content', sandbox.config.csp)
|
|
|
|
|
|
iframeDoc.head.appendChild(cspMeta)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const script = iframeDoc.createElement('script')
|
|
|
|
|
|
script.textContent = `
|
|
|
|
|
|
// 系统SDK注入 - 完整版本
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
console.log('[SystemSDK] 开始注入SDK到iframe');
|
|
|
|
|
|
|
|
|
|
|
|
// SDK基础类
|
|
|
|
|
|
class SDKBase {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this._appId = '';
|
|
|
|
|
|
this._initialized = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get appId() {
|
|
|
|
|
|
return this._appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get initialized() {
|
|
|
|
|
|
return this._initialized;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息到系统
|
|
|
|
|
|
sendToSystem(method, data) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const requestId = Date.now().toString() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
|
|
|
|
|
|
|
|
|
const handler = (event) => {
|
|
|
|
|
|
if (event.data?.type === 'system:response' && event.data?.requestId === requestId) {
|
|
|
|
|
|
window.removeEventListener('message', handler);
|
|
|
|
|
|
if (event.data.success) {
|
|
|
|
|
|
resolve(event.data.data);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error(event.data.error || '系统调用失败'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('message', handler);
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息到父窗口(系统)
|
|
|
|
|
|
window.parent.postMessage({
|
|
|
|
|
|
type: 'sdk:call',
|
|
|
|
|
|
requestId,
|
|
|
|
|
|
method,
|
|
|
|
|
|
data,
|
|
|
|
|
|
appId: this._appId,
|
|
|
|
|
|
}, '*');
|
|
|
|
|
|
|
|
|
|
|
|
// 设置超时
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
window.removeEventListener('message', handler);
|
|
|
|
|
|
reject(new Error('系统调用超时'));
|
|
|
|
|
|
}, 10000);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 包装API响应
|
|
|
|
|
|
wrapResponse(promise) {
|
|
|
|
|
|
return promise
|
|
|
|
|
|
.then((data) => ({ success: true, data }))
|
|
|
|
|
|
.catch((error) => ({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: error.message || '未知错误',
|
|
|
|
|
|
code: error.code || -1,
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 窗体SDK实现
|
|
|
|
|
|
class WindowSDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async setTitle(title) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.setTitle', { title }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async resize(width, height) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.resize', { width, height }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async move(x, y) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.move', { x, y }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async minimize() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.minimize'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async maximize() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.maximize'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async restore() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.restore'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async close() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.close'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getState() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.getState'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getSize() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('window.getSize'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
|
|
|
|
|
|
// 存储SDK实现
|
|
|
|
|
|
class StorageSDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async set(key, value) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.set', { key, value }));
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async get(key) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.get', { key }));
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async remove(key) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.remove', { key }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async clear() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.clear'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async keys() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.keys'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async has(key) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.has', { key }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getStats() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('storage.getStats'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 网络SDK实现
|
|
|
|
|
|
class NetworkSDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async request(url, config) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('network.request', { url, config }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async get(url, config) {
|
|
|
|
|
|
return this.request(url, { ...config, method: 'GET' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async post(url, data, config) {
|
|
|
|
|
|
return this.request(url, { ...config, method: 'POST', body: data });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async put(url, data, config) {
|
|
|
|
|
|
return this.request(url, { ...config, method: 'PUT', body: data });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async delete(url, config) {
|
|
|
|
|
|
return this.request(url, { ...config, method: 'DELETE' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 事件SDK实现
|
|
|
|
|
|
class EventSDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async emit(channel, data) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('events.emit', { channel, data }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async on(channel, callback, config) {
|
|
|
|
|
|
const result = await this.wrapResponse(this.sendToSystem('events.on', { channel, config }));
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
if (result.success && result.data) {
|
|
|
|
|
|
// 注册事件监听器
|
|
|
|
|
|
window.addEventListener('message', (event) => {
|
|
|
|
|
|
if (event.data?.type === 'system:event' && event.data?.subscriptionId === result.data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
callback(event.data.message);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('事件回调处理错误:', error);
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return result;
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async off(subscriptionId) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('events.off', { subscriptionId }));
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async broadcast(channel, data) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('events.broadcast', { channel, data }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async sendTo(targetAppId, data) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('events.sendTo', { targetAppId, data }));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UI SDK实现
|
|
|
|
|
|
class UISDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async showDialog(options) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('ui.showDialog', options));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async showNotification(options) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('ui.showNotification', options));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async showToast(message, type, duration) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('ui.showToast', { message, type, duration }));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 系统SDK实现
|
|
|
|
|
|
class SystemSDKImpl extends SDKBase {
|
|
|
|
|
|
constructor(appId) {
|
|
|
|
|
|
super();
|
|
|
|
|
|
this._appId = appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getSystemInfo() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('system.getSystemInfo'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAppInfo() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('system.getAppInfo'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getClipboard() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('system.getClipboard'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async setClipboard(text) {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('system.setClipboard', { text }));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getCurrentTime() {
|
|
|
|
|
|
const result = await this.wrapResponse(this.sendToSystem('system.getCurrentTime'));
|
|
|
|
|
|
if (result.success && result.data) {
|
|
|
|
|
|
result.data = new Date(result.data);
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async generateUUID() {
|
|
|
|
|
|
return this.wrapResponse(this.sendToSystem('system.generateUUID'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 主SDK实现类
|
|
|
|
|
|
class SystemDesktopSDKImpl {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.version = '1.0.0';
|
|
|
|
|
|
this._appId = '';
|
|
|
|
|
|
this._initialized = false;
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
// 初始化各个子模块为null
|
|
|
|
|
|
this._window = null;
|
|
|
|
|
|
this._storage = null;
|
|
|
|
|
|
this._network = null;
|
|
|
|
|
|
this._events = null;
|
|
|
|
|
|
this._ui = null;
|
|
|
|
|
|
this._system = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get appId() {
|
|
|
|
|
|
return this._appId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get initialized() {
|
|
|
|
|
|
return this._initialized;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get window() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] window模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._window;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get storage() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] storage模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._storage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get network() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] network模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._network;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get events() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] events模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._events;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get ui() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] ui模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._ui;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get system() {
|
|
|
|
|
|
if (!this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] system模块未初始化');
|
|
|
|
|
|
throw new Error('SDK未初始化');
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return this._system;
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
async init(config) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('[SystemSDK] 开始初始化SDK,配置:', config);
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
2025-09-25 15:31:11 +08:00
|
|
|
|
if (this._initialized) {
|
|
|
|
|
|
console.warn('[SystemSDK] SDK已初始化');
|
|
|
|
|
|
return { success: false, error: 'SDK已初始化' };
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
|
|
|
|
|
|
this._appId = config.appId;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化各个子模块
|
|
|
|
|
|
this._window = new WindowSDKImpl(this._appId);
|
|
|
|
|
|
this._storage = new StorageSDKImpl(this._appId);
|
|
|
|
|
|
this._network = new NetworkSDKImpl(this._appId);
|
|
|
|
|
|
this._events = new EventSDKImpl(this._appId);
|
|
|
|
|
|
this._ui = new UISDKImpl(this._appId);
|
|
|
|
|
|
this._system = new SystemSDKImpl(this._appId);
|
|
|
|
|
|
|
|
|
|
|
|
this._initialized = true;
|
|
|
|
|
|
console.log('[SystemSDK] SDK初始化完成,应用ID:', this._appId);
|
|
|
|
|
|
|
|
|
|
|
|
// 通知父窗口SDK已初始化
|
|
|
|
|
|
window.parent.postMessage({
|
|
|
|
|
|
type: 'sdk:initialized',
|
|
|
|
|
|
appId: this._appId
|
|
|
|
|
|
}, '*');
|
|
|
|
|
|
|
|
|
|
|
|
return { success: true, data: true };
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[SystemSDK] 初始化失败:', error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: error instanceof Error ? error.message : '初始化失败',
|
|
|
|
|
|
};
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async destroy() {
|
|
|
|
|
|
try {
|
2025-09-24 16:43:10 +08:00
|
|
|
|
if (!this._initialized) {
|
2025-09-25 15:31:11 +08:00
|
|
|
|
return { success: false, error: 'SDK未初始化' };
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
|
|
|
|
|
|
this._initialized = false;
|
|
|
|
|
|
this._appId = '';
|
|
|
|
|
|
console.log('[SystemSDK] SDK已销毁');
|
|
|
|
|
|
|
|
|
|
|
|
return { success: true, data: true };
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[SystemSDK] 销毁失败:', error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: error instanceof Error ? error.message : '销毁失败',
|
|
|
|
|
|
};
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-25 15:31:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建全局SDK实例
|
|
|
|
|
|
const SystemSDK = new SystemDesktopSDKImpl();
|
|
|
|
|
|
|
|
|
|
|
|
// 在window对象上挂载SDK
|
|
|
|
|
|
window.SystemSDK = SystemSDK;
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[SystemSDK] SDK已在iframe中注入并挂载到window对象');
|
|
|
|
|
|
|
|
|
|
|
|
// 通知系统应用已准备就绪
|
|
|
|
|
|
window.parent.postMessage({ type: 'app:ready' }, '*');
|
|
|
|
|
|
})();
|
2025-10-10 10:08:05 +08:00
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
iframeDoc.head.appendChild(script)
|
|
|
|
|
|
}
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置性能监控
|
|
|
|
|
|
*/
|
|
|
|
|
|
private setupPerformanceMonitoring(sandbox: SandboxInstance): void {
|
|
|
|
|
|
if (!this.performanceData.has(sandbox.id)) {
|
|
|
|
|
|
this.performanceData.set(sandbox.id, [])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 记录性能数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
private recordPerformance(sandbox: SandboxInstance, data: any): void {
|
|
|
|
|
|
const performanceRecord: SandboxPerformance = {
|
|
|
|
|
|
sandboxId: sandbox.id,
|
|
|
|
|
|
timestamp: new Date(),
|
|
|
|
|
|
memoryUsage: data.memoryUsage || 0,
|
|
|
|
|
|
cpuUsage: data.cpuUsage || 0,
|
|
|
|
|
|
networkRequests: data.networkRequests || 0,
|
|
|
|
|
|
renderTime: data.renderTime || 0,
|
|
|
|
|
|
jsExecutionTime: data.jsExecutionTime || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const records = this.performanceData.get(sandbox.id)!
|
|
|
|
|
|
records.push(performanceRecord)
|
|
|
|
|
|
|
|
|
|
|
|
// 保留最近1000条记录
|
|
|
|
|
|
if (records.length > 1000) {
|
|
|
|
|
|
records.splice(0, records.length - 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新沙箱性能指标
|
|
|
|
|
|
sandbox.memoryUsage = performanceRecord.memoryUsage
|
|
|
|
|
|
sandbox.cpuUsage = performanceRecord.cpuUsage
|
|
|
|
|
|
sandbox.networkRequests = performanceRecord.networkRequests
|
|
|
|
|
|
|
|
|
|
|
|
// 检查性能阈值
|
|
|
|
|
|
this.checkPerformanceThresholds(sandbox, performanceRecord)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查性能阈值
|
|
|
|
|
|
*/
|
2025-10-10 10:28:36 +08:00
|
|
|
|
private checkPerformanceThresholds(sandbox: SandboxInstance, metrics: SandboxPerformance): void {}
|
2025-09-24 16:43:10 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 开始性能监控
|
|
|
|
|
|
*/
|
|
|
|
|
|
private startPerformanceMonitoring(): void {
|
|
|
|
|
|
this.monitoringInterval = setInterval(() => {
|
|
|
|
|
|
for (const sandbox of this.sandboxes.values()) {
|
|
|
|
|
|
if (sandbox.state === SandboxState.RUNNING) {
|
|
|
|
|
|
this.collectPerformanceMetrics(sandbox)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 5000) // 每5秒收集一次性能数据
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 收集性能指标
|
|
|
|
|
|
*/
|
|
|
|
|
|
private collectPerformanceMetrics(sandbox: SandboxInstance): void {
|
|
|
|
|
|
if (sandbox.iframe && sandbox.iframe.contentWindow) {
|
|
|
|
|
|
// 请求性能数据
|
2025-10-10 10:08:05 +08:00
|
|
|
|
sandbox.iframe.contentWindow.postMessage(
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'system:performance-request'
|
|
|
|
|
|
},
|
|
|
|
|
|
'*'
|
|
|
|
|
|
)
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新沙箱状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
private updateSandboxState(sandbox: SandboxInstance, newState: SandboxState): void {
|
|
|
|
|
|
const oldState = sandbox.state
|
|
|
|
|
|
sandbox.state = newState
|
2025-10-10 10:08:05 +08:00
|
|
|
|
|
2025-10-10 10:28:36 +08:00
|
|
|
|
// 移除事件服务消息发送
|
|
|
|
|
|
// this.eventService.sendMessage('system', 'sandbox-state-change', {
|
|
|
|
|
|
// sandboxId: sandbox.id,
|
|
|
|
|
|
// newState,
|
|
|
|
|
|
// oldState
|
|
|
|
|
|
// })
|
2025-09-24 16:43:10 +08:00
|
|
|
|
}
|
2025-10-10 10:08:05 +08:00
|
|
|
|
}
|