mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-03 22:21:13 +00:00
Expose plugin pages at /plugin/:id/page/:path
Add a public route to serve plugin extension pages without auth and update related pieces accordingly. Backend: register GET /plugin/:pluginId/page/:pagePath to locate the plugin router, validate page and HTML file existence, and send the file (returns appropriate 4xx/5xx errors). Frontend: switch iframe and new-window URLs to the new unauthenticated route (remove webui_token usage). Builtin plugin: clarify page registration comment and add a log line for the extension page URL. Minor formatting whitespace tweaks in plugin manager type annotations.
This commit is contained in:
parent
d9297c1e10
commit
a5769b6a62
@ -216,7 +216,7 @@ export class OB11PluginMangerAdapter extends IOB11NetworkAdapter<PluginConfig> i
|
||||
this.pluginRouters.set(entry.id, routerRegistry);
|
||||
|
||||
// 创建获取其他插件导出的方法
|
||||
const getPluginExports = <T = any>(pluginId: string): T | undefined => {
|
||||
const getPluginExports = <T = any> (pluginId: string): T | undefined => {
|
||||
const targetEntry = this.plugins.get(pluginId);
|
||||
if (!targetEntry || !targetEntry.loaded || targetEntry.runtime.status !== 'loaded') {
|
||||
return undefined;
|
||||
|
||||
@ -195,7 +195,7 @@ export class OB11PluginManager extends IOB11NetworkAdapter<PluginConfig> impleme
|
||||
}
|
||||
|
||||
// 创建获取其他插件导出的方法
|
||||
const getPluginExports = <T = any>(pluginId: string): T | undefined => {
|
||||
const getPluginExports = <T = any> (pluginId: string): T | undefined => {
|
||||
const targetEntry = this.plugins.get(pluginId);
|
||||
if (!targetEntry || !targetEntry.loaded || targetEntry.runtime.status !== 'loaded') {
|
||||
return undefined;
|
||||
|
||||
@ -160,7 +160,7 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 注册扩展页面
|
||||
// 注册扩展页面(无需鉴权,可通过 /plugin/{pluginId}/page/dashboard 访问)
|
||||
ctx.router.page({
|
||||
path: 'dashboard',
|
||||
title: '插件仪表盘',
|
||||
@ -171,6 +171,7 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
||||
|
||||
logger.info('WebUI 路由已注册:');
|
||||
logger.info(' - API 路由: /api/Plugin/ext/' + ctx.pluginName + '/');
|
||||
logger.info(' - 扩展页面: /plugin/' + ctx.pluginName + '/page/dashboard');
|
||||
logger.info(' - 静态资源: /plugin/' + ctx.pluginName + '/files/static/');
|
||||
logger.info(' - 内存资源: /plugin/' + ctx.pluginName + '/mem/dynamic/');
|
||||
};
|
||||
|
||||
@ -332,6 +332,42 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra
|
||||
return res.status(404).json({ code: -1, message: 'Memory file not found' });
|
||||
});
|
||||
|
||||
// 插件页面路由(不需要鉴权)
|
||||
// 路径格式: /plugin/:pluginId/page/:pagePath
|
||||
app.get('/plugin/:pluginId/page/:pagePath', (req, res) => {
|
||||
const { pluginId, pagePath } = req.params;
|
||||
if (!pluginId) return res.status(400).json({ code: -1, message: 'Plugin ID is required' });
|
||||
|
||||
const ob11 = WebUiDataRuntime.getOneBotContext() as NapCatOneBot11Adapter | null;
|
||||
if (!ob11) return res.status(503).json({ code: -1, message: 'OneBot context not available' });
|
||||
|
||||
const pluginManager = ob11.networkManager.findSomeAdapter('plugin_manager') as OB11PluginMangerAdapter | undefined;
|
||||
if (!pluginManager) return res.status(503).json({ code: -1, message: 'Plugin manager not available' });
|
||||
|
||||
const routerRegistry = pluginManager.getPluginRouter(pluginId);
|
||||
if (!routerRegistry || !routerRegistry.hasPages()) {
|
||||
return res.status(404).json({ code: -1, message: `Plugin '${pluginId}' has no registered pages` });
|
||||
}
|
||||
|
||||
const pages = routerRegistry.getPages();
|
||||
const page = pages.find(p => p.path === '/' + pagePath || p.path === pagePath);
|
||||
if (!page) {
|
||||
return res.status(404).json({ code: -1, message: `Page '${pagePath}' not found in plugin '${pluginId}'` });
|
||||
}
|
||||
|
||||
const pluginPath = routerRegistry.getPluginPath();
|
||||
if (!pluginPath) {
|
||||
return res.status(500).json({ code: -1, message: 'Plugin path not available' });
|
||||
}
|
||||
|
||||
const htmlFilePath = join(pluginPath, page.htmlFile);
|
||||
if (!existsSync(htmlFilePath)) {
|
||||
return res.status(404).json({ code: -1, message: `HTML file not found: ${page.htmlFile}` });
|
||||
}
|
||||
|
||||
return res.sendFile(htmlFilePath);
|
||||
});
|
||||
|
||||
// 插件文件系统静态资源路由(不需要鉴权)
|
||||
// 路径格式: /plugin/:pluginId/files/*
|
||||
app.use('/plugin/:pluginId/files', (req, res, next) => {
|
||||
|
||||
@ -63,13 +63,12 @@ export default function ExtensionPage () {
|
||||
}, [extensionPages]);
|
||||
|
||||
// 获取当前选中页面的 iframe URL
|
||||
// 新路由格式不需要鉴权: /plugin/:pluginId/page/:pagePath
|
||||
const currentPageUrl = useMemo(() => {
|
||||
if (!selectedTab) return '';
|
||||
const [pluginId, ...pathParts] = selectedTab.split(':');
|
||||
const path = pathParts.join(':').replace(/^\//, '');
|
||||
// 获取认证 token
|
||||
const token = localStorage.getItem('token') || '';
|
||||
return `/api/Plugin/page/${pluginId}/${path}?webui_token=${token}`;
|
||||
return `/plugin/${pluginId}/page/${path}`;
|
||||
}, [selectedTab]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -86,11 +85,10 @@ export default function ExtensionPage () {
|
||||
setIframeLoading(false);
|
||||
};
|
||||
|
||||
// 在新窗口打开页面
|
||||
// 在新窗口打开页面(新路由不需要鉴权)
|
||||
const openInNewWindow = (pluginId: string, path: string) => {
|
||||
const cleanPath = path.replace(/^\//, '');
|
||||
const token = localStorage.getItem('token') || '';
|
||||
const url = `/api/Plugin/page/${pluginId}/${cleanPath}?webui_token=${token}`;
|
||||
const url = `/plugin/${pluginId}/page/${cleanPath}`;
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user