feat: 支持工作流对话匿名分享

- 增加免登录公共接口、访客隔离、限流和匿名上传校验

- 分离 SSE 连接与运行生命周期,支持刷新恢复服务端权威状态

- 持久化分享页对话并优化时间线滚动与输入区交互
This commit is contained in:
2026-08-31 16:45:21 +08:00
parent fa174f6c16
commit 1bd9810518
46 changed files with 3742 additions and 180 deletions

View File

@@ -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);