fix: 统一工作流三个出口的错误反馈
- 关联 EASY-2,补齐安全错误、节点展示与执行标识 - 保留历史可读摘要并验证接口、SSE 与界面兼容
This commit is contained in:
@@ -25,7 +25,11 @@ import {
|
||||
} from '#/utils/workflow-share-context';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
import { isInactiveSseRequest } from './sseRequestLifecycle';
|
||||
import {
|
||||
isInactiveSseRequest,
|
||||
readSseRequestError,
|
||||
SseRequestError,
|
||||
} from './sseRequestLifecycle';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const ERROR_MESSAGE_DEDUP_WINDOW = 800;
|
||||
@@ -235,7 +239,7 @@ export class SseClient {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||
const error = await readSseRequestError(res);
|
||||
options?.onError?.(error);
|
||||
return;
|
||||
}
|
||||
@@ -258,7 +262,7 @@ export class SseClient {
|
||||
}
|
||||
}
|
||||
showErrorOnce(errorMessage);
|
||||
options?.onError?.(new Error(errorMessage));
|
||||
options?.onError?.(new SseRequestError(res.status, errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isInactiveSseRequest } from './sseRequestLifecycle';
|
||||
import {
|
||||
isInactiveSseRequest,
|
||||
readSseRequestError,
|
||||
SseRequestError,
|
||||
} from './sseRequestLifecycle';
|
||||
|
||||
describe('sseRequestLifecycle', () => {
|
||||
it('treats an explicit abort as an inactive request', () => {
|
||||
@@ -17,3 +21,19 @@ describe('sseRequestLifecycle', () => {
|
||||
expect(isInactiveSseRequest(controller.signal, 1, 1)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps HTTP rejection distinguishable from transport interruption', async () => {
|
||||
for (const status of [400, 403, 500]) {
|
||||
const error = await readSseRequestError(
|
||||
new Response(JSON.stringify({ message: '运行请求被拒绝' }), { status }),
|
||||
);
|
||||
expect(error).toBeInstanceOf(SseRequestError);
|
||||
expect(error.status).toBe(status);
|
||||
expect(error.message).toBe('运行请求被拒绝');
|
||||
}
|
||||
const error = await readSseRequestError(
|
||||
new Response('<html>PRIVATE_GATEWAY_BODY</html>', { status: 502 }),
|
||||
);
|
||||
expect(error.message).toContain('502');
|
||||
expect(error.message).not.toContain('PRIVATE_GATEWAY_BODY');
|
||||
});
|
||||
|
||||
@@ -8,3 +8,24 @@ export function isInactiveSseRequest(
|
||||
) {
|
||||
return signal.aborted || currentRequestId !== requestId;
|
||||
}
|
||||
/** 服务端明确拒绝请求,与已经建立的事件流断线区分。 */
|
||||
export class SseRequestError extends Error {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'SseRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
export async function readSseRequestError(response: Response) {
|
||||
let message = `请求失败(HTTP ${response.status})`;
|
||||
try {
|
||||
const body = await response.json();
|
||||
if (typeof body?.message === 'string') message = body.message;
|
||||
} catch {
|
||||
// 网关可能返回 HTML;只展示状态,不把原始正文当作错误文案。
|
||||
}
|
||||
return new SseRequestError(response.status, message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user