mirror of
https://github.com/GSManagerXZ/GameServerManager.git
synced 2026-06-06 05:19:49 +08:00
完善插件
This commit is contained in:
@@ -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(
|
||||
/<script src="\/api\/plugins\/${plugin.name}\/files\/gsm3-api\.js"><\/script>/g,
|
||||
`<script type="text/javascript" src="/api/plugins/${plugin.name}/files/gsm3-api.js"></script>`
|
||||
)
|
||||
|
||||
// 注入token设置脚本
|
||||
injectedContent = injectedContent.replace(
|
||||
'</head>',
|
||||
`<script>
|
||||
// 设置全局token变量
|
||||
window.gsm3Token = '${token}';
|
||||
console.log('全局token已设置:', '${token}');
|
||||
</script>
|
||||
</head>`
|
||||
)
|
||||
|
||||
// 在body结束前注入token设置脚本,确保在gsm3-api.js完全初始化后执行
|
||||
injectedContent = injectedContent.replace(
|
||||
'</body>',
|
||||
`<script>
|
||||
// 等待gsm3-api.js完全加载并初始化
|
||||
(function() {
|
||||
const waitForGsm3AndSetToken = () => {
|
||||
// 检查window.gsm3对象是否存在且具有initialize方法
|
||||
if (window.gsm3 && typeof window.gsm3.initialize === 'function') {
|
||||
console.log('GSM3 API对象已找到,设置token...');
|
||||
window.gsm3.token = '${token}';
|
||||
console.log('GSM3 API Token已设置:', '${token}');
|
||||
|
||||
// 如果API还未初始化,触发初始化
|
||||
if (!window.gsm3.isInitialized) {
|
||||
window.gsm3.initialize().then(() => {
|
||||
console.log('GSM3 API初始化完成');
|
||||
}).catch(error => {
|
||||
console.error('GSM3 API初始化失败:', error);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 监听DOMContentLoaded事件,确保在gsm3-api.js的DOMContentLoaded之后执行
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 延迟一点时间,确保gsm3-api.js的DOMContentLoaded先执行
|
||||
setTimeout(() => {
|
||||
if (!waitForGsm3AndSetToken()) {
|
||||
// 如果仍然失败,继续重试
|
||||
let attempts = 0;
|
||||
const checkGsm3 = () => {
|
||||
attempts++;
|
||||
if (waitForGsm3AndSetToken()) {
|
||||
console.log('Token设置成功,尝试次数:', attempts);
|
||||
} else if (attempts < 30) {
|
||||
setTimeout(checkGsm3, 200);
|
||||
} else {
|
||||
console.error('Token设置失败:超时等待gsm3对象创建');
|
||||
console.log('当前window对象包含的gsm3相关属性:', Object.keys(window).filter(key => key.includes('gsm3')));
|
||||
}
|
||||
};
|
||||
setTimeout(checkGsm3, 200);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
} else {
|
||||
// 如果DOM已经加载完成,立即尝试
|
||||
setTimeout(() => {
|
||||
if (!waitForGsm3AndSetToken()) {
|
||||
let attempts = 0;
|
||||
const checkGsm3 = () => {
|
||||
attempts++;
|
||||
if (waitForGsm3AndSetToken()) {
|
||||
console.log('Token设置成功,尝试次数:', attempts);
|
||||
} else if (attempts < 30) {
|
||||
setTimeout(checkGsm3, 200);
|
||||
} else {
|
||||
console.error('Token设置失败:超时等待gsm3对象创建');
|
||||
console.log('当前window对象包含的gsm3相关属性:', Object.keys(window).filter(key => key.includes('gsm3')));
|
||||
}
|
||||
};
|
||||
setTimeout(checkGsm3, 200);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>`
|
||||
)
|
||||
|
||||
setCurrentPluginContent(injectedContent)
|
||||
setCurrentPluginName(plugin.displayName || plugin.name)
|
||||
setShowPluginModal(true)
|
||||
|
||||
// 发送插件打开成功的通知
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: '成功',
|
||||
message: `插件 ${plugin.displayName || plugin.name} 已打开`
|
||||
})
|
||||
} else {
|
||||
addNotification({ type: 'error', title: '错误', message: '插件内容为空' })
|
||||
}
|
||||
|
||||
307
server/data/plugins/example-plugin/gsm3-api.js
Normal file
307
server/data/plugins/example-plugin/gsm3-api.js
Normal file
@@ -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() }
|
||||
}, '*')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>示例插件 - GSM3</title>
|
||||
<script src="gsm3-api.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -297,6 +298,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>🔌 API调用演示</h3>
|
||||
<p>体验插件调用主面板API的功能:</p>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="getSystemStatus()">获取系统状态</button>
|
||||
<button class="btn btn-success" onclick="getInstances()">获取实例列表</button>
|
||||
<button class="btn btn-warning" onclick="getSystemInfo()">获取系统信息</button>
|
||||
<button class="btn btn-info" onclick="getApiVersion()">获取API版本</button>
|
||||
</div>
|
||||
|
||||
<div id="apiResult" class="status-display" style="max-height: 300px; overflow-y: auto; white-space: pre-wrap; font-family: 'Courier New', monospace;">
|
||||
<strong>API调用结果:</strong> 点击上方按钮查看API响应
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>📊 系统信息</h3>
|
||||
<div id="systemInfo">
|
||||
@@ -390,10 +407,139 @@
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
|
||||
// ==================== API调用演示函数 ====================
|
||||
|
||||
async function getSystemStatus() {
|
||||
try {
|
||||
if (!window.gsm3) {
|
||||
throw new Error('GSM3 API对象未找到')
|
||||
}
|
||||
|
||||
showApiLoading('正在初始化API...')
|
||||
await window.gsm3.initialize()
|
||||
|
||||
showApiLoading('正在获取系统状态...')
|
||||
const result = await window.gsm3.getSystemStatus()
|
||||
showApiResult('系统状态', result)
|
||||
if (window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('success', '系统状态获取成功')
|
||||
}
|
||||
} catch (error) {
|
||||
showApiError('获取系统状态失败', error)
|
||||
if (window.gsm3 && window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('error', '获取系统状态失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getInstances() {
|
||||
try {
|
||||
if (!window.gsm3) {
|
||||
throw new Error('GSM3 API对象未找到')
|
||||
}
|
||||
|
||||
showApiLoading('正在初始化API...')
|
||||
await window.gsm3.initialize()
|
||||
|
||||
showApiLoading('正在获取实例列表...')
|
||||
const result = await window.gsm3.getInstances()
|
||||
showApiResult('实例列表', result)
|
||||
if (window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('success', '实例列表获取成功')
|
||||
}
|
||||
} catch (error) {
|
||||
showApiError('获取实例列表失败', error)
|
||||
if (window.gsm3 && window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('error', '获取实例列表失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getSystemInfo() {
|
||||
try {
|
||||
if (!window.gsm3) {
|
||||
throw new Error('GSM3 API对象未找到')
|
||||
}
|
||||
|
||||
showApiLoading('正在初始化API...')
|
||||
await window.gsm3.initialize()
|
||||
|
||||
showApiLoading('正在获取系统信息...')
|
||||
const result = await window.gsm3.getSystemInfo()
|
||||
showApiResult('系统信息', result)
|
||||
if (window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('success', '系统信息获取成功')
|
||||
}
|
||||
} catch (error) {
|
||||
showApiError('获取系统信息失败', error)
|
||||
if (window.gsm3 && window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('error', '获取系统信息失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getApiVersion() {
|
||||
try {
|
||||
if (!window.gsm3) {
|
||||
throw new Error('GSM3 API对象未找到')
|
||||
}
|
||||
|
||||
showApiLoading('正在初始化API...')
|
||||
await window.gsm3.initialize()
|
||||
|
||||
showApiLoading('正在获取API版本...')
|
||||
const result = await window.gsm3.getVersion()
|
||||
showApiResult('API版本', result)
|
||||
if (window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('info', 'API版本信息获取成功')
|
||||
}
|
||||
} catch (error) {
|
||||
showApiError('获取API版本失败', error)
|
||||
if (window.gsm3 && window.gsm3.showNotification) {
|
||||
window.gsm3.showNotification('error', '获取API版本失败: ' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showApiLoading(message) {
|
||||
const apiResult = document.getElementById('apiResult')
|
||||
apiResult.innerHTML = `<strong>API调用结果:</strong> <span class="loading"></span> ${message}`
|
||||
}
|
||||
|
||||
function showApiResult(title, result) {
|
||||
const apiResult = document.getElementById('apiResult')
|
||||
apiResult.innerHTML = `<strong>API调用结果 - ${title}:</strong>\n${JSON.stringify(result, null, 2)}`
|
||||
}
|
||||
|
||||
function showApiError(title, error) {
|
||||
const apiResult = document.getElementById('apiResult')
|
||||
apiResult.innerHTML = `<strong>API调用错误 - ${title}:</strong>\n${error.message || error}`
|
||||
apiResult.style.borderLeft = '4px solid #ff6b6b'
|
||||
}
|
||||
|
||||
// 控制台欢迎信息
|
||||
console.log('%c🧩 GSM3 插件系统', 'color: #667eea; font-size: 20px; font-weight: bold;')
|
||||
console.log('%c欢迎使用示例插件!', 'color: #764ba2; font-size: 14px;')
|
||||
console.log('插件开发文档: https://github.com/your-repo/gsm3-docs')
|
||||
|
||||
// 等待GSM3 API加载完成后进行健康检查
|
||||
window.addEventListener('load', async () => {
|
||||
// 等待一段时间确保gsm3-api.js完全加载
|
||||
setTimeout(async () => {
|
||||
if (window.gsm3) {
|
||||
try {
|
||||
console.log('GSM3 API对象已加载:', window.gsm3)
|
||||
const health = await window.gsm3.healthCheck()
|
||||
console.log('GSM3 API连接正常:', health)
|
||||
} catch (error) {
|
||||
console.warn('GSM3 API连接失败:', error)
|
||||
}
|
||||
} else {
|
||||
console.error('GSM3 API对象未找到,请检查gsm3-api.js是否正确加载')
|
||||
console.log('当前window对象包含的gsm3相关属性:', Object.keys(window).filter(key => key.includes('gsm3')))
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,6 +33,7 @@ import minecraftRouter from './routes/minecraft.js'
|
||||
import moreGamesRouter from './routes/moreGames.js'
|
||||
import weatherRouter from './routes/weather.js'
|
||||
import pluginsRouter, { setPluginManager } from './routes/plugins.js'
|
||||
import pluginApiRouter, { setPluginApiDependencies } from './routes/pluginApi.js'
|
||||
|
||||
// 获取当前文件目录
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
@@ -502,6 +503,10 @@ async function startServer() {
|
||||
|
||||
// 设置插件路由
|
||||
app.use('/api/plugins', pluginsRouter)
|
||||
|
||||
// 设置插件API桥接路由
|
||||
setPluginApiDependencies(instanceManager, systemManager, terminalManager, gameManager)
|
||||
app.use('/api/plugin-api', pluginApiRouter)
|
||||
|
||||
// 前端路由处理(SPA支持)
|
||||
app.get('*', (req, res) => {
|
||||
|
||||
283
server/src/routes/pluginApi.ts
Normal file
283
server/src/routes/pluginApi.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { Router, Request, Response } from 'express'
|
||||
import { authenticateToken } from '../middleware/auth.js'
|
||||
import type { InstanceManager } from '../modules/instance/InstanceManager.js'
|
||||
import type { SystemManager } from '../modules/system/SystemManager.js'
|
||||
import type { TerminalManager } from '../modules/terminal/TerminalManager.js'
|
||||
import type { GameManager } from '../modules/game/GameManager.js'
|
||||
import logger from '../utils/logger.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
// 依赖注入
|
||||
let instanceManager: InstanceManager
|
||||
let systemManager: SystemManager
|
||||
let terminalManager: TerminalManager
|
||||
let gameManager: GameManager
|
||||
|
||||
export function setPluginApiDependencies(
|
||||
instManager: InstanceManager,
|
||||
sysManager: SystemManager,
|
||||
termManager: TerminalManager,
|
||||
gmManager: GameManager
|
||||
) {
|
||||
instanceManager = instManager
|
||||
systemManager = sysManager
|
||||
terminalManager = termManager
|
||||
gameManager = gmManager
|
||||
}
|
||||
|
||||
// 插件API代理中间件
|
||||
const pluginApiProxy = (req: Request, res: Response, next: any) => {
|
||||
// 验证请求来源是否为插件
|
||||
const isPluginRequest = req.get('X-Plugin-Request') === 'true'
|
||||
|
||||
if (!isPluginRequest) {
|
||||
// 为了兼容开发环境,我们允许来自 about:srcdoc 的请求
|
||||
const referer = req.get('Referer')
|
||||
if (process.env.NODE_ENV === 'development' && referer === 'about:srcdoc') {
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '仅允许插件调用此API'
|
||||
})
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
// 应用插件API代理中间件
|
||||
router.use(pluginApiProxy)
|
||||
router.use(authenticateToken)
|
||||
|
||||
// ==================== 系统信息API ====================
|
||||
|
||||
// 获取系统状态
|
||||
router.get('/system/status', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!systemManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '系统管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const status = await systemManager.getSystemInfo()
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取系统状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取系统状态失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 获取系统信息
|
||||
router.get('/system/info', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!systemManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '系统管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const info = await systemManager.getSystemInfo()
|
||||
res.json({
|
||||
success: true,
|
||||
data: info
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取系统信息失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取系统信息失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 实例管理API ====================
|
||||
|
||||
// 获取实例列表
|
||||
router.get('/instances', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!instanceManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '实例管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const instances = instanceManager.getInstances()
|
||||
res.json({
|
||||
success: true,
|
||||
data: instances
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取实例列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取实例列表失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 获取单个实例信息
|
||||
router.get('/instances/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
if (!instanceManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '实例管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const instance = instanceManager.getInstance(id)
|
||||
if (!instance) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '实例不存在'
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: instance
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取实例信息失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取实例信息失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 获取实例状态
|
||||
router.get('/instances/:id/status', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
if (!instanceManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '实例管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const instance = instanceManager.getInstance(id)
|
||||
if (!instance) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '实例不存在'
|
||||
})
|
||||
}
|
||||
|
||||
const status = await instanceManager.getInstanceStatus(id)
|
||||
res.json({
|
||||
success: true,
|
||||
data: status
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取实例状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取实例状态失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 终端管理API ====================
|
||||
|
||||
// 获取终端会话列表
|
||||
router.get('/terminals', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!terminalManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '终端管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const terminals = terminalManager.getSessionStats()
|
||||
res.json({
|
||||
success: true,
|
||||
data: terminals
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取终端列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取终端列表失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 游戏管理API ====================
|
||||
|
||||
// 获取游戏列表
|
||||
router.get('/games', async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!gameManager) {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '游戏管理器未初始化'
|
||||
})
|
||||
}
|
||||
|
||||
const games = gameManager.getGames()
|
||||
res.json({
|
||||
success: true,
|
||||
data: games
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('插件获取游戏列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取游戏列表失败',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 通用API ====================
|
||||
|
||||
// 获取API版本信息
|
||||
router.get('/version', (req: Request, res: Response) => {
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
version: '1.0.0',
|
||||
apiVersion: 'v1',
|
||||
pluginApiVersion: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 健康检查
|
||||
router.get('/health', (req: Request, res: Response) => {
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -188,8 +188,26 @@ router.delete('/:name', authenticateToken, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取插件文件内容
|
||||
router.get('/:name/files/*', authenticateToken, async (req, res) => {
|
||||
// 获取插件文件内容(无需认证的公共资源)
|
||||
router.get('/:name/files/*', async (req, res) => {
|
||||
const { name } = req.params
|
||||
const filePath = req.params[0] || 'index.html'
|
||||
|
||||
// 对于非公共资源文件,需要认证
|
||||
const publicFiles = ['gsm3-api.js', 'index.html', 'style.css']
|
||||
const isPublicFile = publicFiles.some(file => filePath.endsWith(file))
|
||||
|
||||
if (!isPublicFile) {
|
||||
return authenticateToken(req, res, async () => {
|
||||
await handleFileRequest(req, res)
|
||||
})
|
||||
}
|
||||
|
||||
await handleFileRequest(req, res)
|
||||
})
|
||||
|
||||
// 处理文件请求的通用函数
|
||||
async function handleFileRequest(req: any, res: any) {
|
||||
try {
|
||||
const { name } = req.params
|
||||
const filePath = req.params[0] || 'index.html'
|
||||
@@ -239,8 +257,15 @@ router.get('/:name/files/*', authenticateToken, async (req, res) => {
|
||||
'.ico': 'image/x-icon'
|
||||
}
|
||||
|
||||
// 对于HTML、CSS、JS等文本文件,返回JSON格式的内容
|
||||
if (['.html', '.css', '.js', '.json'].includes(ext)) {
|
||||
// 对于JS文件,直接返回文件内容以便浏览器正确执行
|
||||
if (ext === '.js') {
|
||||
const contentType = contentTypes[ext] || 'application/javascript; charset=utf-8'
|
||||
res.setHeader('Content-Type', contentType)
|
||||
const fileContent = await fs.readFile(fullPath, 'utf-8')
|
||||
res.send(fileContent)
|
||||
}
|
||||
// 对于HTML、CSS、JSON等文本文件,返回JSON格式的内容
|
||||
else if (['.html', '.css', '.json'].includes(ext)) {
|
||||
const fileContent = await fs.readFile(fullPath, 'utf-8')
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -267,7 +292,7 @@ router.get('/:name/files/*', authenticateToken, async (req, res) => {
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新插件文件内容
|
||||
router.put('/:name/files/*', authenticateToken, async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user