Files
vue-desktop/src/sdk/index.ts
2025-09-24 16:43:10 +08:00

703 lines
19 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 type {
SystemDesktopSDK,
SDKConfig,
APIResponse,
WindowSDK,
StorageSDK,
NetworkSDK,
EventSDK,
UISDK,
SystemSDK,
WindowState,
WindowEvents,
StorageEvents,
NetworkRequestConfig,
NetworkResponse,
EventMessage,
EventSubscriptionConfig,
DialogOptions,
NotificationOptions,
FilePickerOptions,
SystemInfo,
AppInfo,
PermissionStatus,
} from './types'
/**
* SDK基础类
*/
abstract class SDKBase {
protected appId: string = ''
protected initialized: boolean = false
/**
* 发送消息到系统
*/
protected sendToSystem<T = any>(type: string, data?: any): Promise<T> {
return new Promise((resolve, reject) => {
const requestId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
const handler = (event: MessageEvent) => {
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: type,
data,
appId: this.appId,
},
'*',
)
// 设置超时
setTimeout(() => {
window.removeEventListener('message', handler)
reject(new Error('系统调用超时'))
}, 10000)
})
}
/**
* 包装API响应
*/
protected wrapResponse<T>(promise: Promise<T>): Promise<APIResponse<T>> {
return promise
.then((data) => ({ success: true, data }))
.catch((error) => ({
success: false,
error: error.message || '未知错误',
code: error.code || -1,
}))
}
}
/**
* 窗体SDK实现
*/
class WindowSDKImpl extends SDKBase implements WindowSDK {
private eventListeners = new Map<keyof WindowEvents, Set<Function>>()
constructor(appId: string) {
super()
this.appId = appId
this.setupEventListeners()
}
async setTitle(title: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.setTitle', { title }))
}
async resize(width: number, height: number): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.resize', { width, height }))
}
async move(x: number, y: number): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.move', { x, y }))
}
async minimize(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.minimize'))
}
async maximize(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.maximize'))
}
async restore(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.restore'))
}
async fullscreen(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.fullscreen'))
}
async close(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('window.close'))
}
async getState(): Promise<APIResponse<WindowState>> {
return this.wrapResponse(this.sendToSystem('window.getState'))
}
async getSize(): Promise<APIResponse<{ width: number; height: number }>> {
return this.wrapResponse(this.sendToSystem('window.getSize'))
}
async getPosition(): Promise<APIResponse<{ x: number; y: number }>> {
return this.wrapResponse(this.sendToSystem('window.getPosition'))
}
on<K extends keyof WindowEvents>(event: K, callback: WindowEvents[K]): void {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set())
}
this.eventListeners.get(event)!.add(callback)
}
off<K extends keyof WindowEvents>(event: K, callback?: WindowEvents[K]): void {
if (callback) {
this.eventListeners.get(event)?.delete(callback)
} else {
this.eventListeners.delete(event)
}
}
private setupEventListeners(): void {
window.addEventListener('message', (event) => {
if (event.data?.type?.startsWith('system:window:')) {
const eventType = event.data.type.replace('system:window:', '') as keyof WindowEvents
const listeners = this.eventListeners.get(eventType)
if (listeners) {
listeners.forEach((callback) => {
try {
callback(...(event.data.args || []))
} catch (error) {
console.error('窗体事件处理错误:', error)
}
})
}
}
})
}
}
/**
* 存储SDK实现
*/
class StorageSDKImpl extends SDKBase implements StorageSDK {
private eventListeners = new Map<keyof StorageEvents, Set<Function>>()
constructor(appId: string) {
super()
this.appId = appId
this.setupEventListeners()
}
async set(key: string, value: any): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('storage.set', { key, value }))
}
async get<T = any>(key: string): Promise<APIResponse<T | null>> {
return this.wrapResponse(this.sendToSystem('storage.get', { key }))
}
async remove(key: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('storage.remove', { key }))
}
async clear(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('storage.clear'))
}
async keys(): Promise<APIResponse<string[]>> {
return this.wrapResponse(this.sendToSystem('storage.keys'))
}
async has(key: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('storage.has', { key }))
}
async getStats(): Promise<APIResponse<any>> {
return this.wrapResponse(this.sendToSystem('storage.getStats'))
}
on<K extends keyof StorageEvents>(event: K, callback: StorageEvents[K]): void {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set())
}
this.eventListeners.get(event)!.add(callback)
}
off<K extends keyof StorageEvents>(event: K, callback?: StorageEvents[K]): void {
if (callback) {
this.eventListeners.get(event)?.delete(callback)
} else {
this.eventListeners.delete(event)
}
}
private setupEventListeners(): void {
window.addEventListener('message', (event) => {
if (event.data?.type?.startsWith('system:storage:')) {
const eventType = event.data.type.replace('system:storage:', '') as keyof StorageEvents
const listeners = this.eventListeners.get(eventType)
if (listeners) {
listeners.forEach((callback) => {
try {
callback(...(event.data.args || []))
} catch (error) {
console.error('存储事件处理错误:', error)
}
})
}
}
})
}
}
/**
* 网络SDK实现
*/
class NetworkSDKImpl extends SDKBase implements NetworkSDK {
constructor(appId: string) {
super()
this.appId = appId
}
async request<T = any>(
url: string,
config?: NetworkRequestConfig,
): Promise<APIResponse<NetworkResponse<T>>> {
return this.wrapResponse(this.sendToSystem('network.request', { url, config }))
}
async get<T = any>(
url: string,
config?: Omit<NetworkRequestConfig, 'method'>,
): Promise<APIResponse<NetworkResponse<T>>> {
return this.request(url, { ...config, method: 'GET' })
}
async post<T = any>(
url: string,
data?: any,
config?: Omit<NetworkRequestConfig, 'method' | 'body'>,
): Promise<APIResponse<NetworkResponse<T>>> {
return this.request(url, { ...config, method: 'POST', body: data })
}
async put<T = any>(
url: string,
data?: any,
config?: Omit<NetworkRequestConfig, 'method' | 'body'>,
): Promise<APIResponse<NetworkResponse<T>>> {
return this.request(url, { ...config, method: 'PUT', body: data })
}
async delete<T = any>(
url: string,
config?: Omit<NetworkRequestConfig, 'method'>,
): Promise<APIResponse<NetworkResponse<T>>> {
return this.request(url, { ...config, method: 'DELETE' })
}
async upload(
url: string,
file: File | Blob,
onProgress?: (loaded: number, total: number) => void,
): Promise<APIResponse<NetworkResponse>> {
return this.wrapResponse(
this.sendToSystem('network.upload', { url, file, hasProgressCallback: !!onProgress }),
)
}
async download(
url: string,
filename?: string,
onProgress?: (loaded: number, total: number) => void,
): Promise<APIResponse<Blob>> {
return this.wrapResponse(
this.sendToSystem('network.download', { url, filename, hasProgressCallback: !!onProgress }),
)
}
async isOnline(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('network.isOnline'))
}
async getStats(): Promise<
APIResponse<{ requestCount: number; failureCount: number; averageTime: number }>
> {
return this.wrapResponse(this.sendToSystem('network.getStats'))
}
}
/**
* 事件SDK实现
*/
class EventSDKImpl extends SDKBase implements EventSDK {
private subscriptions = new Map<string, Function>()
constructor(appId: string) {
super()
this.appId = appId
this.setupEventListeners()
}
async emit<T = any>(channel: string, data: T): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('events.emit', { channel, data }))
}
async on<T = any>(
channel: string,
callback: (message: EventMessage<T>) => void,
config?: EventSubscriptionConfig,
): Promise<APIResponse<string>> {
const result = await this.wrapResponse(this.sendToSystem('events.on', { channel, config }))
if (result.success && result.data) {
this.subscriptions.set(result.data, callback)
}
return result
}
async off(subscriptionId: string): Promise<APIResponse<boolean>> {
const result = await this.wrapResponse(this.sendToSystem('events.off', { subscriptionId }))
if (result.success) {
this.subscriptions.delete(subscriptionId)
}
return result
}
async broadcast<T = any>(channel: string, data: T): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('events.broadcast', { channel, data }))
}
async sendTo<T = any>(targetAppId: string, data: T): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('events.sendTo', { targetAppId, data }))
}
async getSubscriberCount(channel: string): Promise<APIResponse<number>> {
return this.wrapResponse(this.sendToSystem('events.getSubscriberCount', { channel }))
}
async getChannels(): Promise<APIResponse<string[]>> {
return this.wrapResponse(this.sendToSystem('events.getChannels'))
}
private setupEventListeners(): void {
window.addEventListener('message', (event) => {
if (event.data?.type === 'system:event' && event.data?.subscriptionId) {
const callback = this.subscriptions.get(event.data.subscriptionId)
if (callback) {
try {
callback(event.data.message)
} catch (error) {
console.error('事件回调处理错误:', error)
}
}
}
})
}
}
/**
* UI SDK实现
*/
class UISDKImpl extends SDKBase implements UISDK {
constructor(appId: string) {
super()
this.appId = appId
}
async showDialog(
options: DialogOptions,
): Promise<APIResponse<{ buttonIndex: number; inputValue?: string }>> {
return this.wrapResponse(this.sendToSystem('ui.showDialog', options))
}
async showNotification(options: NotificationOptions): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('ui.showNotification', options))
}
async showFilePicker(options?: FilePickerOptions): Promise<APIResponse<FileList | null>> {
return this.wrapResponse(this.sendToSystem('ui.showFilePicker', options))
}
async showSaveDialog(defaultName?: string, accept?: string): Promise<APIResponse<string | null>> {
return this.wrapResponse(this.sendToSystem('ui.showSaveDialog', { defaultName, accept }))
}
async showToast(
message: string,
type?: 'info' | 'success' | 'warning' | 'error',
duration?: number,
): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('ui.showToast', { message, type, duration }))
}
async showLoading(message?: string): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('ui.showLoading', { message }))
}
async hideLoading(id: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('ui.hideLoading', { id }))
}
async showProgress(options: {
title?: string
message?: string
progress: number
}): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('ui.showProgress', options))
}
async updateProgress(
id: string,
progress: number,
message?: string,
): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('ui.updateProgress', { id, progress, message }))
}
async hideProgress(id: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('ui.hideProgress', { id }))
}
}
/**
* 系统SDK实现
*/
class SystemSDKImpl extends SDKBase implements SystemSDK {
constructor(appId: string) {
super()
this.appId = appId
}
async getSystemInfo(): Promise<APIResponse<SystemInfo>> {
return this.wrapResponse(this.sendToSystem('system.getSystemInfo'))
}
async getAppInfo(): Promise<APIResponse<AppInfo>> {
return this.wrapResponse(this.sendToSystem('system.getAppInfo'))
}
async requestPermission(
permission: string,
reason?: string,
): Promise<APIResponse<PermissionStatus>> {
return this.wrapResponse(this.sendToSystem('system.requestPermission', { permission, reason }))
}
async checkPermission(permission: string): Promise<APIResponse<PermissionStatus>> {
return this.wrapResponse(this.sendToSystem('system.checkPermission', { permission }))
}
async getClipboard(): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('system.getClipboard'))
}
async setClipboard(text: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('system.setClipboard', { text }))
}
async openExternal(url: string): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('system.openExternal', { url }))
}
async getCurrentTime(): Promise<APIResponse<Date>> {
const result = await this.wrapResponse(this.sendToSystem('system.getCurrentTime'))
if (result.success && result.data) {
result.data = new Date(result.data)
}
return result
}
async generateUUID(): Promise<APIResponse<string>> {
return this.wrapResponse(this.sendToSystem('system.generateUUID'))
}
async exit(): Promise<APIResponse<boolean>> {
return this.wrapResponse(this.sendToSystem('system.exit'))
}
}
/**
* 主SDK实现类
*/
class SystemDesktopSDKImpl implements SystemDesktopSDK {
readonly version: string = '1.0.0'
private _appId: string = ''
private _initialized: boolean = false
private _window!: WindowSDK
private _storage!: StorageSDK
private _network!: NetworkSDK
private _events!: EventSDK
private _ui!: UISDK
private _system!: SystemSDK
get appId(): string {
return this._appId
}
get initialized(): boolean {
return this._initialized
}
get window(): WindowSDK {
this.checkInitialized()
return this._window
}
get storage(): StorageSDK {
this.checkInitialized()
return this._storage
}
get network(): NetworkSDK {
this.checkInitialized()
return this._network
}
get events(): EventSDK {
this.checkInitialized()
return this._events
}
get ui(): UISDK {
this.checkInitialized()
return this._ui
}
get system(): SystemSDK {
this.checkInitialized()
return this._system
}
async init(config: SDKConfig): Promise<APIResponse<boolean>> {
try {
if (this._initialized) {
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)
// 向系统注册应用
const response = await this.sendToSystem('sdk.init', config)
if (response.success) {
this._initialized = true
console.log(`SDK已初始化应用ID: ${this._appId}`)
}
return response
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '初始化失败',
}
}
}
async destroy(): Promise<APIResponse<boolean>> {
try {
if (!this._initialized) {
return { success: false, error: 'SDK未初始化' }
}
const response = await this.sendToSystem('sdk.destroy', { appId: this._appId })
if (response.success) {
this._initialized = false
this._appId = ''
console.log('SDK已销毁')
}
return response
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '销毁失败',
}
}
}
async getStatus(): Promise<
APIResponse<{ initialized: boolean; connected: boolean; permissions: string[] }>
> {
try {
const response = await this.sendToSystem('sdk.getStatus', { appId: this._appId })
return {
success: true,
data: {
initialized: this._initialized,
connected: response.data.connected || false,
permissions: response.data.permissions || [],
},
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '获取状态失败',
}
}
}
private checkInitialized(): void {
if (!this._initialized) {
throw new Error('SDK未初始化请先调用init()方法')
}
}
private sendToSystem<T = any>(
type: string,
data?: any,
): Promise<{ success: boolean; data?: T; error?: string }> {
return new Promise((resolve, reject) => {
const requestId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
const handler = (event: MessageEvent) => {
if (event.data?.type === 'system:response' && event.data?.requestId === requestId) {
window.removeEventListener('message', handler)
resolve(event.data)
}
}
window.addEventListener('message', handler)
window.parent.postMessage(
{
type: 'sdk:call',
requestId,
method: type,
data,
appId: this._appId,
},
'*',
)
setTimeout(() => {
window.removeEventListener('message', handler)
reject(new Error('系统调用超时'))
}, 10000)
})
}
}
// 创建全局SDK实例
const SystemSDK = new SystemDesktopSDKImpl()
// 导出SDK实例
export default SystemSDK
// 在window对象上挂载SDK
if (typeof window !== 'undefined') {
window.SystemSDK = SystemSDK
}