补齐runtime story到STDB的兼容桥

This commit is contained in:
2026-04-20 11:22:37 +00:00
parent 00edcfe121
commit 9d27284a64
14 changed files with 478 additions and 9 deletions

View File

@@ -10,10 +10,16 @@ vi.mock('./apiClient', async () => {
return {
...actual,
requestJson: requestJsonMock,
getStoredAccessToken: vi.fn(() => ''),
getStoredSpacetimeToken: vi.fn(() => ''),
};
});
import { AnimationState } from '../types';
import {
getStoredAccessToken,
getStoredSpacetimeToken,
} from './apiClient';
import {
buildStoryMomentFromRuntimeOptions,
getRuntimeClientVersion,
@@ -32,6 +38,8 @@ describe('runtimeStoryService', () => {
beforeEach(() => {
requestJsonMock.mockReset();
resetRuntimeStoryTransport();
vi.mocked(getStoredAccessToken).mockReturnValue('');
vi.mocked(getStoredSpacetimeToken).mockReturnValue('');
});
function createMockRuntimeResponse(overrides: Record<string, unknown> = {}) {
@@ -110,7 +118,33 @@ describe('runtimeStoryService', () => {
}),
}),
'执行运行时动作失败',
expect.any(Object),
expect.objectContaining({
skipAuth: true,
skipRefresh: true,
}),
);
});
it('prefers spacetime token auth headers for runtime story compatibility bridge', async () => {
vi.mocked(getStoredAccessToken).mockReturnValue('legacy-http-token');
vi.mocked(getStoredSpacetimeToken).mockReturnValue('stdb-token');
requestJsonMock.mockResolvedValue(createMockRuntimeResponse());
await getRuntimeStoryState('runtime-main');
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/runtime/story/state/runtime-main',
expect.objectContaining({
headers: expect.objectContaining({
Authorization: 'Bearer stdb-token',
'x-genarrative-runtime-story-auth': 'spacetime-token',
}),
}),
'读取运行时故事状态失败',
expect.objectContaining({
skipAuth: true,
skipRefresh: true,
}),
);
});

View File

@@ -16,7 +16,12 @@ import type {
} from '../persistence/runtimeSnapshotTypes';
import type { GameState, StoryMoment, StoryOption } from '../types';
import { AnimationState } from '../types';
import { type ApiRetryOptions, requestJson } from './apiClient';
import {
getStoredAccessToken,
getStoredSpacetimeToken,
type ApiRetryOptions,
requestJson,
} from './apiClient';
const RUNTIME_STORY_API_BASE = '/api/runtime/story';
const DEFAULT_SESSION_ID = 'runtime-main';
@@ -33,6 +38,7 @@ const TASK5_RUNTIME_FUNCTION_ID_SET = new Set<string>(
const SERVER_RUNTIME_FUNCTION_ID_SET = new Set<string>([
...SERVER_RUNTIME_FUNCTION_IDS,
]);
const RUNTIME_STORY_STDB_AUTH_HEADER = 'x-genarrative-runtime-story-auth';
export type RuntimeStoryServiceOptions = {
signal?: AbortSignal;
@@ -64,6 +70,29 @@ export type RuntimeStoryTransport = {
) => Promise<RuntimeStoryResponse>;
};
function withRuntimeStoryAuthHeaders(headers?: HeadersInit) {
const nextHeaders =
headers instanceof Headers
? Object.fromEntries(headers.entries())
: Array.isArray(headers)
? Object.fromEntries(headers)
: { ...(headers ?? {}) };
const httpAccessToken = getStoredAccessToken();
const spacetimeToken = getStoredSpacetimeToken();
const bearerToken = spacetimeToken || httpAccessToken;
if (bearerToken) {
nextHeaders.Authorization = `Bearer ${bearerToken}`;
}
if (spacetimeToken) {
nextHeaders[RUNTIME_STORY_STDB_AUTH_HEADER] = 'spacetime-token';
} else if (httpAccessToken) {
nextHeaders[RUNTIME_STORY_STDB_AUTH_HEADER] = 'http-access-token';
}
return nextHeaders;
}
function requestRuntimeStoryJson<T>(
path: string,
init: RequestInit,
@@ -75,9 +104,14 @@ function requestRuntimeStoryJson<T>(
{
...init,
signal: options.signal,
headers: withRuntimeStoryAuthHeaders(init.headers),
},
fallbackMessage,
{ retry: options.retry ?? RUNTIME_STORY_RETRY },
{
retry: options.retry ?? RUNTIME_STORY_RETRY,
skipAuth: true,
skipRefresh: true,
},
);
}