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

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

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

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

View File

@@ -0,0 +1,42 @@
import { describe, expect, it, vi } from 'vitest';
import { authenticateResponseInterceptor } from './preset-interceptors';
function createInterceptor(
shouldHandleUnauthorized?: (config: any) => boolean,
) {
const doReAuthenticate = vi.fn(async () => undefined);
const interceptor = authenticateResponseInterceptor({
client: {} as any,
doReAuthenticate,
doRefreshToken: vi.fn(async () => 'new-token'),
enableRefreshToken: false,
formatToken: (token) => token,
shouldHandleUnauthorized,
});
return { doReAuthenticate, interceptor };
}
describe('authenticate response interceptor', () => {
it('leaves anonymous endpoint 401 errors to the page', async () => {
const { doReAuthenticate, interceptor } = createInterceptor(() => false);
const error = {
config: { method: 'get', url: '/api/v1/workflowShare/resolve' },
response: { status: 401 },
};
await expect(interceptor.rejected?.(error)).rejects.toBe(error);
expect(doReAuthenticate).not.toHaveBeenCalled();
});
it('keeps the existing reauthentication behavior by default', async () => {
const { doReAuthenticate, interceptor } = createInterceptor();
const error = {
config: { method: 'get', url: '/api/v1/workflow/page' },
response: { status: 401 },
};
await expect(interceptor.rejected?.(error)).rejects.toBe(error);
expect(doReAuthenticate).toHaveBeenCalledOnce();
});
});

View File

@@ -59,12 +59,14 @@ export const authenticateResponseInterceptor = ({
doRefreshToken,
enableRefreshToken,
formatToken,
shouldHandleUnauthorized,
}: {
client: RequestClient;
doReAuthenticate: () => Promise<void>;
doRefreshToken: () => Promise<string>;
enableRefreshToken: boolean;
formatToken: (token: string) => null | string;
shouldHandleUnauthorized?: (config: any) => boolean;
}): ResponseInterceptorConfig => {
return {
rejected: async (error) => {
@@ -73,6 +75,10 @@ export const authenticateResponseInterceptor = ({
if (response?.status !== 401) {
throw error;
}
// 匿名接口的 401 由页面自身处理,不能触发刷新登录态或跳转登录页。
if (shouldHandleUnauthorized?.(config) === false) {
throw error;
}
// 判断是否启用了 refreshToken 功能
// 如果没有启用或者已经是重试请求了,直接跳转到重新登录
if (!enableRefreshToken || config.__isRetryRequest) {