mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
fix: 兼容插件自由 API 响应
This commit is contained in:
@@ -154,6 +154,19 @@ describe('MoviePilot API client', () => {
|
|||||||
expect(notifier.error).toHaveBeenCalledWith('Plugin rejected request')
|
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 () => {
|
it('Blob 成功响应绕过 envelope 解包', async () => {
|
||||||
const blob = new Blob(['moviepilot'], { type: 'application/octet-stream' })
|
const blob = new Blob(['moviepilot'], { type: 'application/octet-stream' })
|
||||||
const { api } = createApiClients({ adapter: resolveWith(blob), notifier })
|
const { api } = createApiClients({ adapter: resolveWith(blob), notifier })
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('API application wiring', () => {
|
|||||||
vi.useRealTimers()
|
vi.useRealTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('向 window 暴露插件 envelope 客户端,而内部默认导出数据客户端', async () => {
|
it('向 window 暴露插件最终 payload 客户端,而内部默认导出数据客户端', async () => {
|
||||||
const module = await import('@/api')
|
const module = await import('@/api')
|
||||||
|
|
||||||
expect(window.MoviePilotAPI).toBe(module.pluginApi)
|
expect(window.MoviePilotAPI).toBe(module.pluginApi)
|
||||||
|
|||||||
+8
-4
@@ -68,6 +68,9 @@ export interface DataApiClient extends Omit<
|
|||||||
): Promise<R>
|
): Promise<R>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 插件与联邦组件接收 endpoint 最终 payload,方法签名不暴露 AxiosResponse 外壳。 */
|
||||||
|
export type PluginApiClient = DataApiClient
|
||||||
|
|
||||||
/** 请求层使用的反馈出口,由应用入口接到全局 Toast。 */
|
/** 请求层使用的反馈出口,由应用入口接到全局 Toast。 */
|
||||||
export interface ApiFeedbackNotifier {
|
export interface ApiFeedbackNotifier {
|
||||||
error(message: string): void
|
error(message: string): void
|
||||||
@@ -83,7 +86,7 @@ export interface ApiClientHooks {
|
|||||||
reportConnectionFailure?(reason: 'network-error' | 'timeout' | 'server-unreachable'): void
|
reportConnectionFailure?(reason: 'network-error' | 'timeout' | 'server-unreachable'): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建内部数据客户端与插件原始协议客户端时所需的配置。 */
|
/** 创建内部数据客户端与插件最终 payload 客户端时所需的配置。 */
|
||||||
export interface CreateApiClientsOptions extends CreateAxiosDefaults {
|
export interface CreateApiClientsOptions extends CreateAxiosDefaults {
|
||||||
hooks?: ApiClientHooks
|
hooks?: ApiClientHooks
|
||||||
notifier?: ApiFeedbackNotifier
|
notifier?: ApiFeedbackNotifier
|
||||||
@@ -181,10 +184,10 @@ export function resolveConnectionFailureReason(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建普通数据客户端和保持插件 ABI 的原始 envelope 客户端。 */
|
/** 创建普通数据客户端和保持插件自由响应 ABI 的最终 payload 客户端。 */
|
||||||
export function createApiClients(options: CreateApiClientsOptions = {}): {
|
export function createApiClients(options: CreateApiClientsOptions = {}): {
|
||||||
api: DataApiClient
|
api: DataApiClient
|
||||||
pluginApi: AxiosInstance
|
pluginApi: PluginApiClient
|
||||||
} {
|
} {
|
||||||
const { hooks, notifier, resolveFallbackMessage, setupInstance, ...axiosConfig } = options
|
const { hooks, notifier, resolveFallbackMessage, setupInstance, ...axiosConfig } = options
|
||||||
const api = axios.create(axiosConfig)
|
const api = axios.create(axiosConfig)
|
||||||
@@ -202,7 +205,7 @@ export function createApiClients(options: CreateApiClientsOptions = {}): {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
api: api as DataApiClient,
|
api: api as DataApiClient,
|
||||||
pluginApi,
|
pluginApi: pluginApi as PluginApiClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +229,7 @@ function installResponseInterceptors(
|
|||||||
|
|
||||||
const payload: unknown = response.data
|
const payload: unknown = response.data
|
||||||
if (!isApiResponse(payload)) {
|
if (!isApiResponse(payload)) {
|
||||||
|
// 联邦插件自行声明 API 响应;非宿主 envelope 必须原样交给插件调用方。
|
||||||
if (responseMode === 'envelope') return payload
|
if (responseMode === 'envelope') return payload
|
||||||
const error = new ApiRequestError(resolveFallback('invalid-envelope', resolveFallbackMessage), {
|
const error = new ApiRequestError(resolveFallback('invalid-envelope', resolveFallbackMessage), {
|
||||||
code: AxiosError.ERR_BAD_RESPONSE,
|
code: AxiosError.ERR_BAD_RESPONSE,
|
||||||
|
|||||||
+4
-3
@@ -14,6 +14,7 @@ import {
|
|||||||
type ApiFeedbackMode,
|
type ApiFeedbackMode,
|
||||||
type ApiFallbackMessageKey,
|
type ApiFallbackMessageKey,
|
||||||
type DataApiClient,
|
type DataApiClient,
|
||||||
|
type PluginApiClient,
|
||||||
} from './client'
|
} from './client'
|
||||||
|
|
||||||
/** 带连接探测和反馈策略的 MoviePilot 请求配置。 */
|
/** 带连接探测和反馈策略的 MoviePilot 请求配置。 */
|
||||||
@@ -70,7 +71,7 @@ const { api, pluginApi } = createApiClients({
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
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
|
if (typeof window !== 'undefined') window.MoviePilotAPI = pluginApi
|
||||||
|
|
||||||
export { ApiRequestError, getApiBusinessErrorMessage, isApiBusinessFailure, isApiResponse, pluginApi }
|
export { ApiRequestError, getApiBusinessErrorMessage, isApiBusinessFailure, isApiResponse, pluginApi }
|
||||||
export type { ApiFeedbackMode, DataApiClient }
|
export type { ApiFeedbackMode, DataApiClient, PluginApiClient }
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|||||||
Reference in New Issue
Block a user