From 85f2be55a62d0cf05bac57fda6a3ee8eb38bfe91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9C=B1?= <10714957+xiao-zhu245@user.noreply.gitee.com> Date: Sat, 12 Jul 2025 09:50:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/PluginsPage.tsx | 129 +++++++- .../data/plugins/example-plugin/gsm3-api.js | 307 ++++++++++++++++++ server/data/plugins/example-plugin/index.html | 146 +++++++++ server/src/index.ts | 5 + server/src/routes/pluginApi.ts | 283 ++++++++++++++++ server/src/routes/plugins.ts | 35 +- 6 files changed, 899 insertions(+), 6 deletions(-) create mode 100644 server/data/plugins/example-plugin/gsm3-api.js create mode 100644 server/src/routes/pluginApi.ts diff --git a/client/src/pages/PluginsPage.tsx b/client/src/pages/PluginsPage.tsx index 7bb9f7a..11af702 100644 --- a/client/src/pages/PluginsPage.tsx +++ b/client/src/pages/PluginsPage.tsx @@ -62,6 +62,25 @@ const PluginsPage: React.FC = () => { const { addNotification } = useNotificationStore() const apiClient = new ApiClient() + // 监听来自插件的消息 + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (event.data && event.data.type === 'gsm3-notification') { + const { type, message } = event.data.data + addNotification({ + type: type as 'info' | 'success' | 'warning' | 'error', + title: '插件消息', + message + }) + } else if (event.data && event.data.type === 'gsm3-plugin-loaded') { + console.log('插件加载完成:', event.data.data) + } + } + + window.addEventListener('message', handleMessage) + return () => window.removeEventListener('message', handleMessage) + }, [addNotification]) + const categories = [ '工具', '游戏', @@ -215,9 +234,117 @@ const PluginsPage: React.FC = () => { } if (content && content.trim()) { - setCurrentPluginContent(content) + // 修复gsm3-api.js的引用路径并注入token + const token = apiClient.getToken() + let injectedContent = content + + // 替换相对路径的gsm3-api.js引用为正确的API路径 + injectedContent = injectedContent.replace( + /src="gsm3-api\.js"/g, + `src="/api/plugins/${plugin.name}/files/gsm3-api.js"` + ) + + // 确保gsm3-api.js脚本标签有正确的type属性 + injectedContent = injectedContent.replace( + /` + ) + + // 注入token设置脚本 + injectedContent = injectedContent.replace( + '', + ` + ` + ) + + // 在body结束前注入token设置脚本,确保在gsm3-api.js完全初始化后执行 + injectedContent = injectedContent.replace( + '', + ` + ` + ) + + setCurrentPluginContent(injectedContent) setCurrentPluginName(plugin.displayName || plugin.name) setShowPluginModal(true) + + // 发送插件打开成功的通知 + addNotification({ + type: 'success', + title: '成功', + message: `插件 ${plugin.displayName || plugin.name} 已打开` + }) } else { addNotification({ type: 'error', title: '错误', message: '插件内容为空' }) } diff --git a/server/data/plugins/example-plugin/gsm3-api.js b/server/data/plugins/example-plugin/gsm3-api.js new file mode 100644 index 0000000..acedef3 --- /dev/null +++ b/server/data/plugins/example-plugin/gsm3-api.js @@ -0,0 +1,307 @@ +/** + * GSM3 插件API客户端 + * 提供插件与主面板通信的接口 + */ +class GSM3API { + constructor() { + this.baseURL = '/api/plugin-api' + this.token = null + this.initializeToken() + } + + /** + * 初始化token获取机制 + */ + initializeToken() { + try { + // 检查是否已经通过脚本注入设置了全局token + if (window.gsm3Token) { + this.token = window.gsm3Token + console.log('Token已从全局变量获取:', this.token) + return + } + + // 检查是否已经通过脚本注入设置了token + if (window.gsm3 && window.gsm3.token) { + this.token = window.gsm3.token + console.log('Token已从注入脚本获取') + return + } + + // 尝试从父窗口获取token + if (window.parent && window.parent !== window) { + window.parent.postMessage({ type: 'gsm3-get-token' }, '*') + } + + console.log('正在初始化token...') + } catch (error) { + console.warn('Token初始化失败:', error) + } + + // 监听token更新 + window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'gsm3-token-update') { + this.token = event.data.token + console.log('通过消息更新Token:', this.token) + } + }) + + // 定期检查全局token变量 + const checkGlobalToken = () => { + if (!this.token && window.gsm3Token) { + this.token = window.gsm3Token + console.log('从全局变量延迟获取Token:', this.token) + } + } + + // 每100ms检查一次,最多检查50次(5秒) + let checkCount = 0 + const tokenChecker = setInterval(() => { + checkGlobalToken() + checkCount++ + if (this.token || checkCount >= 50) { + clearInterval(tokenChecker) + } + }, 100) + } + + /** + * 发送HTTP请求 + */ + async request(endpoint, options = {}) { + const url = `${this.baseURL}${endpoint}` + const config = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-Plugin-Request': 'true', // 添加插件标识 + ...(this.token && { 'Authorization': `Bearer ${this.token}` }) + }, + ...options + } + + if (config.body && typeof config.body === 'object') { + config.body = JSON.stringify(config.body) + } + + try { + const response = await fetch(url, config) + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `HTTP ${response.status}`) + } + + return data + } catch (error) { + console.error('API请求失败:', error) + throw error + } + } + + // ==================== 系统信息API ==================== + + /** + * 获取系统状态 + */ + async getSystemStatus() { + return await this.request('/system/status') + } + + /** + * 获取系统信息 + */ + async getSystemInfo() { + return await this.request('/system/info') + } + + // ==================== 实例管理API ==================== + + /** + * 获取所有实例列表 + */ + async getInstances() { + return await this.request('/instances') + } + + /** + * 获取单个实例信息 + * @param {string} instanceId 实例ID + */ + async getInstance(instanceId) { + return await this.request(`/instances/${instanceId}`) + } + + /** + * 获取实例状态 + * @param {string} instanceId 实例ID + */ + async getInstanceStatus(instanceId) { + return await this.request(`/instances/${instanceId}/status`) + } + + // ==================== 终端管理API ==================== + + /** + * 获取终端会话列表 + */ + async getTerminals() { + return await this.request('/terminals') + } + + // ==================== 游戏管理API ==================== + + /** + * 获取游戏列表 + */ + async getGames() { + return await this.request('/games') + } + + // ==================== 通用API ==================== + + /** + * 获取API版本信息 + */ + async getVersion() { + return await this.request('/version') + } + + /** + * 健康检查 + */ + async healthCheck() { + return await this.request('/health') + } + + // ==================== 工具方法 ==================== + + /** + * 显示通知消息(如果主面板支持) + * @param {string} type 消息类型: 'info', 'success', 'warning', 'error' + * @param {string} message 消息内容 + */ + showNotification(type, message) { + try { + // 尝试向父窗口发送通知消息 + if (window.parent && window.parent !== window) { + window.parent.postMessage({ + type: 'gsm3-notification', + data: { type, message } + }, '*') + } else { + // 如果无法发送到父窗口,使用浏览器原生通知 + console.log(`[${type.toUpperCase()}] ${message}`) + } + } catch (error) { + console.warn('发送通知失败:', error) + } + } + + /** + * 格式化字节大小 + * @param {number} bytes 字节数 + * @param {number} decimals 小数位数 + */ + formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] + } + + /** + * 格式化时间戳 + * @param {string|number|Date} timestamp 时间戳 + */ + formatTime(timestamp) { + const date = new Date(timestamp) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) + } + + /** + * 格式化运行时间 + * @param {number} seconds 秒数 + */ + formatUptime(seconds) { + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = Math.floor(seconds % 60) + + const parts = [] + if (days > 0) parts.push(`${days}天`) + if (hours > 0) parts.push(`${hours}小时`) + if (minutes > 0) parts.push(`${minutes}分钟`) + if (secs > 0 || parts.length === 0) parts.push(`${secs}秒`) + + return parts.join(' ') + } +} + +// 创建全局实例 +window.gsm3 = new GSM3API() + +// 添加初始化状态标记 +window.gsm3.isInitialized = false +window.gsm3.initPromise = null + +// 初始化API的Promise +window.gsm3.initialize = function() { + if (this.initPromise) { + return this.initPromise + } + + this.initPromise = new Promise((resolve) => { + const checkReady = () => { + if (this.token) { + this.isInitialized = true + console.log('GSM3 API初始化完成,Token:', this.token) + resolve(true) + } else { + setTimeout(checkReady, 100) + } + } + checkReady() + }) + + return this.initPromise +} + +// 监听来自父窗口的消息 +window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'gsm3-token-update') { + window.gsm3.token = event.data.token + console.log('Token已更新:', event.data.token) + } +}) + +// 插件加载完成后的初始化 +document.addEventListener('DOMContentLoaded', () => { + console.log('GSM3 插件API已加载') + + // 启动初始化过程 + window.gsm3.initialize().then(() => { + console.log('GSM3 API准备就绪') + + // 向父窗口发送插件加载完成的消息 + if (window.parent && window.parent !== window) { + window.parent.postMessage({ + type: 'gsm3-plugin-loaded', + data: { timestamp: new Date().toISOString() } + }, '*') + } + }) +}) \ No newline at end of file diff --git a/server/data/plugins/example-plugin/index.html b/server/data/plugins/example-plugin/index.html index 3208552..ee9b27e 100644 --- a/server/data/plugins/example-plugin/index.html +++ b/server/data/plugins/example-plugin/index.html @@ -4,6 +4,7 @@ 示例插件 - GSM3 +