feat: 支持工作流对话匿名分享
- 增加免登录公共接口、访客隔离、限流和匿名上传校验 - 分离 SSE 连接与运行生命周期,支持刷新恢复服务端权威状态 - 持久化分享页对话并优化时间线滚动与输入区交互
This commit is contained in:
@@ -21,12 +21,11 @@ import { events } from 'fetch-event-stream';
|
||||
import { useAuthStore } from '#/store';
|
||||
import {
|
||||
isWorkflowShareRequest,
|
||||
readWorkflowShareKey,
|
||||
withWorkflowShareHeader,
|
||||
WORKFLOW_SHARE_HEADER,
|
||||
withWorkflowShareHeaders,
|
||||
} from '#/utils/workflow-share-context';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
import { isInactiveSseRequest } from './sseRequestLifecycle';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const ERROR_MESSAGE_DEDUP_WINDOW = 800;
|
||||
@@ -101,12 +100,15 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
|
||||
config.headers['easyflow-token'] = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
const workflowShareKey = readWorkflowShareKey();
|
||||
if (
|
||||
workflowShareKey &&
|
||||
isWorkflowShareRequest(config.url, config.method)
|
||||
) {
|
||||
config.headers[WORKFLOW_SHARE_HEADER] = workflowShareKey;
|
||||
const workflowShareHeaders = withWorkflowShareHeaders(
|
||||
{},
|
||||
{
|
||||
requestMethod: config.method,
|
||||
requestUrl: config.url,
|
||||
},
|
||||
);
|
||||
for (const [name, value] of Object.entries(workflowShareHeaders)) {
|
||||
config.headers[name] = value;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
@@ -132,6 +134,8 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken ?? false,
|
||||
formatToken,
|
||||
shouldHandleUnauthorized: (config) =>
|
||||
!isWorkflowShareRequest(config?.url, config?.method),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -186,7 +190,7 @@ export function createEventStreamHeaders(
|
||||
headers[key] = value;
|
||||
});
|
||||
}
|
||||
return withWorkflowShareHeader(headers, {
|
||||
return withWorkflowShareHeaders(headers, {
|
||||
requestMethod: 'POST',
|
||||
requestUrl,
|
||||
});
|
||||
@@ -274,15 +278,25 @@ export class SseClient {
|
||||
options?.onMessage?.(event);
|
||||
}
|
||||
} catch (innerError) {
|
||||
if (
|
||||
isInactiveSseRequest(signal, this.currentRequestId, currentRequestId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
options?.onError?.(innerError);
|
||||
return;
|
||||
}
|
||||
|
||||
// 只有在还是同一个请求的情况下才调用 onFinished
|
||||
if (this.currentRequestId === currentRequestId) {
|
||||
if (
|
||||
!isInactiveSseRequest(signal, this.currentRequestId, currentRequestId)
|
||||
) {
|
||||
options?.onFinished?.();
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.currentRequestId !== currentRequestId) {
|
||||
if (
|
||||
isInactiveSseRequest(signal, this.currentRequestId, currentRequestId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
console.error('SSE错误:', error);
|
||||
|
||||
19
easyflow-ui-admin/app/src/api/sseRequestLifecycle.test.ts
Normal file
19
easyflow-ui-admin/app/src/api/sseRequestLifecycle.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isInactiveSseRequest } from './sseRequestLifecycle';
|
||||
|
||||
describe('sseRequestLifecycle', () => {
|
||||
it('treats an explicit abort as an inactive request', () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
||||
expect(isInactiveSseRequest(controller.signal, 1, 1)).toBe(true);
|
||||
});
|
||||
|
||||
it('treats a superseded request as inactive', () => {
|
||||
const controller = new AbortController();
|
||||
|
||||
expect(isInactiveSseRequest(controller.signal, 2, 1)).toBe(true);
|
||||
expect(isInactiveSseRequest(controller.signal, 1, 1)).toBe(false);
|
||||
});
|
||||
});
|
||||
10
easyflow-ui-admin/app/src/api/sseRequestLifecycle.ts
Normal file
10
easyflow-ui-admin/app/src/api/sseRequestLifecycle.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 判断 SSE 请求是否已被主动中止或被后续请求替换。
|
||||
*/
|
||||
export function isInactiveSseRequest(
|
||||
signal: AbortSignal,
|
||||
currentRequestId: number,
|
||||
requestId: number,
|
||||
) {
|
||||
return signal.aborted || currentRequestId !== requestId;
|
||||
}
|
||||
Reference in New Issue
Block a user