fix(plugins): ignore obsolete configuration UI sessions

This commit is contained in:
chihao.ou
2026-09-30 19:10:51 +08:00
parent 42bc7f6cff
commit 75420462a6
2 changed files with 68 additions and 4 deletions
+8 -2
View File
@@ -380,6 +380,7 @@ const uiError = ref("");
const iframeHeight = ref(640);
const pluginFrameLoaded = ref(false);
const pendingBridgeRequests = new Map<string, number>();
let uiSessionVersion = 0;
function errorMessage(error: unknown): string {
if (typeof error === "object" && error !== null && "message" in error) {
@@ -523,6 +524,7 @@ async function testPlugin(plugin: PluginInstallation): Promise<void> {
}
async function openConfiguration(plugin: PluginInstallation): Promise<void> {
const version = ++uiSessionVersion;
configPlugin.value = plugin;
uiSession.value = null;
pluginFrameLoaded.value = false;
@@ -531,14 +533,18 @@ async function openConfiguration(plugin: PluginInstallation): Promise<void> {
uiError.value = "";
iframeHeight.value = 640;
try {
uiSession.value = await adminAPI.plugins.createUISession(plugin.id);
const session = await adminAPI.plugins.createUISession(plugin.id);
if (version !== uiSessionVersion) return;
uiSession.value = session;
} catch (error: unknown) {
if (version !== uiSessionVersion) return;
uiLoading.value = false;
uiError.value = errorMessage(error);
}
}
function closeConfiguration(): void {
uiSessionVersion++;
clearPendingBridgeRequests();
pluginFrameLoaded.value = false;
configPlugin.value = null;
@@ -723,6 +729,6 @@ onMounted(() => {
onBeforeUnmount(() => {
window.removeEventListener("message", handleBridgeMessage);
clearPendingBridgeRequests();
closeConfiguration();
});
</script>
@@ -1,7 +1,11 @@
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import PluginsView from '../PluginsView.vue'
import BaseDialog from '@/components/common/BaseDialog.vue'
import type { PluginUISession } from '@/api/admin'
enableAutoUnmount(afterEach)
const {
listPlugins,
@@ -151,6 +155,60 @@ describe('管理员插件页二次验证', () => {
expect(enablePlugin).toHaveBeenCalledWith(7, 100, false)
})
it.each([7, 8])('keeps the latest session when reopening configuration for plugin %s', async (id) => {
let resolveOld!: (session: PluginUISession) => void
createUISession.mockReturnValueOnce(new Promise<PluginUISession>((resolve) => { resolveOld = resolve }))
listPlugins.mockResolvedValue([plugin, { ...plugin, id: 8, name: 'Other Transport' }])
const wrapper = mountView()
await flushPromises()
const buttons = wrapper.findAll('button').filter(item => item.text() === 'admin.plugins.configure')
await buttons[0].trigger('click')
wrapper.getComponent(BaseDialog).vm.$emit('close')
await buttons[id === 7 ? 0 : 1].trigger('click')
await flushPromises()
const currentURL = wrapper.get('iframe').attributes('src')
resolveOld({ url: '/obsolete-session', bridge_token: 'old', ui_bridge_version: 1, expires_at: '' })
await flushPromises()
expect(wrapper.get('iframe').attributes('src')).toBe(currentURL)
})
it('ignores an old session error while the reopened configuration is loading', async () => {
let rejectOld!: (error: Error) => void
let resolveCurrent!: (session: PluginUISession) => void
createUISession
.mockReturnValueOnce(new Promise((_, reject) => { rejectOld = reject }))
.mockReturnValueOnce(new Promise<PluginUISession>((resolve) => { resolveCurrent = resolve }))
const wrapper = mountView()
await flushPromises()
const button = wrapper.findAll('button').find(item => item.text() === 'admin.plugins.configure')!
await button.trigger('click')
wrapper.getComponent(BaseDialog).vm.$emit('close')
await button.trigger('click')
rejectOld(new Error('obsolete failure'))
await flushPromises()
expect(wrapper.text()).not.toContain('obsolete failure')
expect(wrapper.text()).toContain('admin.plugins.loadingUI')
resolveCurrent({ url: '/current-session', bridge_token: 'current', ui_bridge_version: 1, expires_at: '' })
await flushPromises()
await wrapper.get('iframe').trigger('load')
expect(wrapper.get('iframe').attributes('src')).toBe('/current-session')
expect(wrapper.text()).not.toContain('admin.plugins.loadingUI')
})
it('does not restore a session after the configuration dialog closes', async () => {
let resolveSession!: (session: PluginUISession) => void
createUISession.mockReturnValueOnce(new Promise<PluginUISession>((resolve) => { resolveSession = resolve }))
const wrapper = mountView()
await flushPromises()
await wrapper.findAll('button').find(item => item.text() === 'admin.plugins.configure')!.trigger('click')
wrapper.getComponent(BaseDialog).vm.$emit('close')
resolveSession({ url: '/closed-session', bridge_token: 'old', ui_bridge_version: 1, expires_at: '' })
await flushPromises()
expect(wrapper.find('iframe').exists()).toBe(false)
})
it('上传插件通过 step-up 控制器执行', async () => {
const wrapper = mountView()
await flushPromises()