1208 lines
34 KiB
TypeScript
1208 lines
34 KiB
TypeScript
import { reactive } from 'vue'
|
||
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[]
|
||
messageHandler?: (event: MessageEvent) => void
|
||
}
|
||
|
||
/**
|
||
* 沙箱性能监控数据
|
||
*/
|
||
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
|
||
|
||
constructor(resourceService: ResourceService) {
|
||
this.resourceService = resourceService
|
||
this.startPerformanceMonitoring()
|
||
}
|
||
|
||
/**
|
||
* 创建沙箱实例
|
||
*/
|
||
async createSandbox(
|
||
appId: string,
|
||
windowId: string,
|
||
config: Partial<SandboxConfig> = {}
|
||
): Promise<SandboxInstance> {
|
||
const sandboxId = uuidv4()
|
||
|
||
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)
|
||
|
||
// 设置安全策略
|
||
this.applySecurity(sandbox)
|
||
|
||
// 设置性能监控
|
||
this.setupPerformanceMonitoring(sandbox)
|
||
|
||
// 更新状态
|
||
this.updateSandboxState(sandbox, SandboxState.READY)
|
||
|
||
this.sandboxes.set(sandboxId, sandbox)
|
||
|
||
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)
|
||
|
||
if (sandbox.config.type === SandboxType.IFRAME && sandbox.iframe) {
|
||
console.log(`使用iframe加载应用: ${applicationUrl}`)
|
||
|
||
// 检查iframe是否已挂载到DOM
|
||
if (!document.contains(sandbox.iframe)) {
|
||
console.warn('检测到iframe未挂载到DOM,尝试重新挂载')
|
||
await this.mountIframeToWindow(sandbox)
|
||
}
|
||
|
||
// 设置iframe源
|
||
sandbox.iframe.src = applicationUrl
|
||
|
||
// 等待加载完成
|
||
return new Promise((resolve, reject) => {
|
||
const timeoutId = setTimeout(() => {
|
||
console.error(
|
||
`应用加载超时: ${sandbox.appId}, URL: ${applicationUrl}, 超时时间: ${sandbox.config.networkTimeout}ms`
|
||
)
|
||
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
|
||
}
|
||
|
||
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)
|
||
|
||
// 移除消息事件监听器
|
||
if (sandbox.messageHandler) {
|
||
window.removeEventListener('message', sandbox.messageHandler)
|
||
sandbox.messageHandler = undefined
|
||
}
|
||
|
||
// 清理DOM元素
|
||
if (sandbox.iframe) {
|
||
sandbox.iframe.remove()
|
||
}
|
||
|
||
if (sandbox.container) {
|
||
sandbox.container.remove()
|
||
}
|
||
|
||
// 终止WebWorker
|
||
if (sandbox.worker) {
|
||
sandbox.worker.terminate()
|
||
}
|
||
|
||
// 清理性能数据
|
||
this.performanceData.delete(sandboxId)
|
||
|
||
// 从集合中移除
|
||
this.sandboxes.delete(sandboxId)
|
||
|
||
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[] {
|
||
return Array.from(this.sandboxes.values()).filter((sandbox) => sandbox.appId === appId)
|
||
}
|
||
|
||
/**
|
||
* 获取沙箱性能数据
|
||
*/
|
||
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
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
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}`
|
||
|
||
// 设置sandbox属性
|
||
const sandboxAttributes: string[] = []
|
||
|
||
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')
|
||
|
||
iframe.sandbox = sandboxAttributes.join(' ')
|
||
|
||
// 设置样式
|
||
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
|
||
|
||
// 将iframe挂载到对应的窗口内容区域
|
||
await this.mountIframeToWindow(sandbox)
|
||
}
|
||
|
||
/**
|
||
* 将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}`)
|
||
}
|
||
|
||
const contentArea = windowElement.querySelector('.window-content')
|
||
if (!contentArea) {
|
||
throw new Error(`找不到窗口内容区域: window-${sandbox.windowId}`)
|
||
}
|
||
|
||
// 替换窗口中的默认iframe
|
||
const existingIframe = contentArea.querySelector('iframe')
|
||
if (existingIframe) {
|
||
contentArea.removeChild(existingIframe)
|
||
}
|
||
|
||
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)
|
||
|
||
const worker = new Worker(workerUrl)
|
||
|
||
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}`
|
||
|
||
const shadowRoot = container.attachShadow({ mode: 'closed' })
|
||
|
||
// 添加样式隔离
|
||
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'
|
||
}
|
||
|
||
// 这些头部主要用于服务器端设置,在客户端我们通过其他方式实现
|
||
|
||
// 监听iframe的消息事件
|
||
const messageHandler = (event: MessageEvent) => {
|
||
if (event.source === sandbox.iframe!.contentWindow) {
|
||
this.handleSandboxMessage(sandbox, event.data)
|
||
}
|
||
}
|
||
window.addEventListener('message', messageHandler)
|
||
// 存储事件监听器引用,以便在销毁时移除
|
||
sandbox.messageHandler = messageHandler
|
||
|
||
// 简化安全限制,主要依赖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
|
||
|
||
case 'app:error':
|
||
sandbox.errors.push(message.error || '未知错误')
|
||
break
|
||
|
||
case 'app:resource-request':
|
||
this.handleResourceRequest(sandbox, message.data)
|
||
break
|
||
|
||
case 'app:performance':
|
||
this.recordPerformance(sandbox, message.data)
|
||
break
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理资源请求
|
||
*/
|
||
private async handleResourceRequest(sandbox: SandboxInstance, request: any): Promise<void> {
|
||
const { type, data } = request
|
||
|
||
try {
|
||
let result = null
|
||
|
||
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
|
||
|
||
case 'network':
|
||
result = await this.resourceService.makeNetworkRequest(
|
||
sandbox.appId,
|
||
data.url,
|
||
data.options
|
||
)
|
||
break
|
||
}
|
||
|
||
// 发送结果回沙箱
|
||
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'));
|
||
}
|
||
}
|
||
|
||
// 存储SDK实现
|
||
class StorageSDKImpl extends SDKBase {
|
||
constructor(appId) {
|
||
super();
|
||
this._appId = appId;
|
||
}
|
||
|
||
async set(key, value) {
|
||
return this.wrapResponse(this.sendToSystem('storage.set', { key, value }));
|
||
}
|
||
|
||
async get(key) {
|
||
return this.wrapResponse(this.sendToSystem('storage.get', { key }));
|
||
}
|
||
|
||
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 }));
|
||
|
||
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);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
async off(subscriptionId) {
|
||
return this.wrapResponse(this.sendToSystem('events.off', { subscriptionId }));
|
||
}
|
||
|
||
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);
|
||
}
|
||
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;
|
||
|
||
// 初始化各个子模块为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未初始化');
|
||
}
|
||
return this._window;
|
||
}
|
||
|
||
get storage() {
|
||
if (!this._initialized) {
|
||
console.warn('[SystemSDK] storage模块未初始化');
|
||
throw new Error('SDK未初始化');
|
||
}
|
||
return this._storage;
|
||
}
|
||
|
||
get network() {
|
||
if (!this._initialized) {
|
||
console.warn('[SystemSDK] network模块未初始化');
|
||
throw new Error('SDK未初始化');
|
||
}
|
||
return this._network;
|
||
}
|
||
|
||
get events() {
|
||
if (!this._initialized) {
|
||
console.warn('[SystemSDK] events模块未初始化');
|
||
throw new Error('SDK未初始化');
|
||
}
|
||
return this._events;
|
||
}
|
||
|
||
get ui() {
|
||
if (!this._initialized) {
|
||
console.warn('[SystemSDK] ui模块未初始化');
|
||
throw new Error('SDK未初始化');
|
||
}
|
||
return this._ui;
|
||
}
|
||
|
||
get system() {
|
||
if (!this._initialized) {
|
||
console.warn('[SystemSDK] system模块未初始化');
|
||
throw new Error('SDK未初始化');
|
||
}
|
||
return this._system;
|
||
}
|
||
|
||
async init(config) {
|
||
try {
|
||
console.log('[SystemSDK] 开始初始化SDK,配置:', config);
|
||
|
||
if (this._initialized) {
|
||
console.warn('[SystemSDK] SDK已初始化');
|
||
return { success: false, error: 'SDK已初始化' };
|
||
}
|
||
|
||
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 : '初始化失败',
|
||
};
|
||
}
|
||
}
|
||
|
||
async destroy() {
|
||
try {
|
||
if (!this._initialized) {
|
||
return { success: false, error: 'SDK未初始化' };
|
||
}
|
||
|
||
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 : '销毁失败',
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建全局SDK实例
|
||
const SystemSDK = new SystemDesktopSDKImpl();
|
||
|
||
// 在window对象上挂载SDK
|
||
window.SystemSDK = SystemSDK;
|
||
|
||
console.log('[SystemSDK] SDK已在iframe中注入并挂载到window对象');
|
||
|
||
// 通知系统应用已准备就绪
|
||
window.parent.postMessage({ type: 'app:ready' }, '*');
|
||
})();
|
||
`
|
||
|
||
iframeDoc.head.appendChild(script)
|
||
}
|
||
|
||
/**
|
||
* 设置性能监控
|
||
*/
|
||
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)
|
||
}
|
||
|
||
/**
|
||
* 检查性能阈值
|
||
*/
|
||
private checkPerformanceThresholds(sandbox: SandboxInstance, metrics: SandboxPerformance): void {}
|
||
|
||
/**
|
||
* 开始性能监控
|
||
*/
|
||
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) {
|
||
// 请求性能数据
|
||
sandbox.iframe.contentWindow.postMessage(
|
||
{
|
||
type: 'system:performance-request'
|
||
},
|
||
'*'
|
||
)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新沙箱状态
|
||
*/
|
||
private updateSandboxState(sandbox: SandboxInstance, newState: SandboxState): void {
|
||
const oldState = sandbox.state
|
||
sandbox.state = newState
|
||
|
||
// 移除事件服务消息发送
|
||
// this.eventService.sendMessage('system', 'sandbox-state-change', {
|
||
// sandboxId: sandbox.id,
|
||
// newState,
|
||
// oldState
|
||
// })
|
||
}
|
||
}
|