fix: 兼容插件自由 API 响应

This commit is contained in:
jxxghp
2026-08-18 00:29:14 +08:00
parent 0518817dd0
commit b8b8fabe15
4 changed files with 26 additions and 8 deletions
+13
View File
@@ -154,6 +154,19 @@ describe('MoviePilot API client', () => {
expect(notifier.error).toHaveBeenCalledWith('Plugin rejected request')
})
it.each([
['object', { plugin: 'demo', count: 2 }],
['array', [{ id: 1 }, { id: 2 }]],
['scalar', 'ready'],
['null', null],
])('插件客户端原样交付非 envelope payload%s', async (_label, payload) => {
const { pluginApi } = createApiClients({ adapter: resolveWith(payload), notifier })
await expect(pluginApi.get('/plugin/demo')).resolves.toEqual(payload)
expect(notifier.error).not.toHaveBeenCalled()
expect(notifier.success).not.toHaveBeenCalled()
})
it('Blob 成功响应绕过 envelope 解包', async () => {
const blob = new Blob(['moviepilot'], { type: 'application/octet-stream' })
const { api } = createApiClients({ adapter: resolveWith(blob), notifier })
+1 -1
View File
@@ -74,7 +74,7 @@ describe('API application wiring', () => {
vi.useRealTimers()
})
it('向 window 暴露插件 envelope 客户端,而内部默认导出数据客户端', async () => {
it('向 window 暴露插件最终 payload 客户端,而内部默认导出数据客户端', async () => {
const module = await import('@/api')
expect(window.MoviePilotAPI).toBe(module.pluginApi)
+8 -4
View File
@@ -68,6 +68,9 @@ export interface DataApiClient extends Omit<
): Promise<R>
}
/** 插件与联邦组件接收 endpoint 最终 payload,方法签名不暴露 AxiosResponse 外壳。 */
export type PluginApiClient = DataApiClient
/** 请求层使用的反馈出口,由应用入口接到全局 Toast。 */
export interface ApiFeedbackNotifier {
error(message: string): void
@@ -83,7 +86,7 @@ export interface ApiClientHooks {
reportConnectionFailure?(reason: 'network-error' | 'timeout' | 'server-unreachable'): void
}
/** 创建内部数据客户端与插件原始协议客户端时所需的配置。 */
/** 创建内部数据客户端与插件最终 payload 客户端时所需的配置。 */
export interface CreateApiClientsOptions extends CreateAxiosDefaults {
hooks?: ApiClientHooks
notifier?: ApiFeedbackNotifier
@@ -181,10 +184,10 @@ export function resolveConnectionFailureReason(
return null
}
/** 创建普通数据客户端和保持插件 ABI 的原始 envelope 客户端。 */
/** 创建普通数据客户端和保持插件自由响应 ABI 的最终 payload 客户端。 */
export function createApiClients(options: CreateApiClientsOptions = {}): {
api: DataApiClient
pluginApi: AxiosInstance
pluginApi: PluginApiClient
} {
const { hooks, notifier, resolveFallbackMessage, setupInstance, ...axiosConfig } = options
const api = axios.create(axiosConfig)
@@ -202,7 +205,7 @@ export function createApiClients(options: CreateApiClientsOptions = {}): {
return {
api: api as DataApiClient,
pluginApi,
pluginApi: pluginApi as PluginApiClient,
}
}
@@ -226,6 +229,7 @@ function installResponseInterceptors(
const payload: unknown = response.data
if (!isApiResponse(payload)) {
// 联邦插件自行声明 API 响应;非宿主 envelope 必须原样交给插件调用方。
if (responseMode === 'envelope') return payload
const error = new ApiRequestError(resolveFallback('invalid-envelope', resolveFallbackMessage), {
code: AxiosError.ERR_BAD_RESPONSE,
+4 -3
View File
@@ -14,6 +14,7 @@ import {
type ApiFeedbackMode,
type ApiFallbackMessageKey,
type DataApiClient,
type PluginApiClient,
} from './client'
/** 带连接探测和反馈策略的 MoviePilot 请求配置。 */
@@ -70,7 +71,7 @@ const { api, pluginApi } = createApiClients({
declare global {
interface Window {
MoviePilotAPI: AxiosInstance
MoviePilotAPI: PluginApiClient
}
}
@@ -88,10 +89,10 @@ function initializeClient(instance: AxiosInstance | DataApiClient) {
})
}
// 插件远程组件依赖完整 envelope ABI,内部页面默认使用已解包的数据客户端。
// 插件远程组件接收 endpoint 的最终 payload,内部页面默认使用严格 envelope 解包客户端。
if (typeof window !== 'undefined') window.MoviePilotAPI = pluginApi
export { ApiRequestError, getApiBusinessErrorMessage, isApiBusinessFailure, isApiResponse, pluginApi }
export type { ApiFeedbackMode, DataApiClient }
export type { ApiFeedbackMode, DataApiClient, PluginApiClient }
export default api