fix(chat): isolate pending flows and cancel stale requests

This commit is contained in:
2026-07-17 19:22:01 +08:00
parent 2fc312b5c7
commit 8bb1e21886
27 changed files with 501 additions and 92 deletions
@@ -59,6 +59,23 @@ describe("multi-character API contract", () => {
});
});
it("forwards request cancellation to the HTTP client", async () => {
const controller = new AbortController();
httpClientMock.mockResolvedValue({
success: true,
data: { messages: [], total: 0, limit: 50, offset: 0 },
});
await new ChatApi().getHistory(CHARACTER_ID, 50, 0, {
signal: controller.signal,
});
expect(httpClientMock).toHaveBeenCalledWith("/api/chat/history", {
query: { characterId: CHARACTER_ID, limit: 50, offset: 0 },
signal: controller.signal,
});
});
it("sends characterId with private and history unlocks", async () => {
const api = new ChatApi();
httpClientMock
+11 -1
View File
@@ -25,10 +25,14 @@ export class ChatApi {
/**
* 发送消息
*/
async sendMessage(body: SendMessageRequest): Promise<ChatSendResponse> {
async sendMessage(
body: SendMessageRequest,
options?: { signal?: AbortSignal },
): Promise<ChatSendResponse> {
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.chatSend, {
method: "POST",
body,
...(options?.signal ? { signal: options.signal } : {}),
});
return ChatSendResponseSchema.parse(unwrap(env) as Record<string, unknown>);
}
@@ -40,9 +44,11 @@ export class ChatApi {
characterId: string,
limit = 50,
offset = 0,
options?: { signal?: AbortSignal },
): Promise<ChatHistoryResponse> {
const env = await httpClient<ApiEnvelope<unknown>>(ApiPath.chatHistory, {
query: { characterId, limit, offset },
...(options?.signal ? { signal: options.signal } : {}),
});
return ChatHistoryResponseSchema.parse(
unwrap(env) as Record<string, unknown>,
@@ -54,12 +60,14 @@ export class ChatApi {
*/
async unlockPrivateMessage(
body: UnlockPrivateRequest,
options?: { signal?: AbortSignal },
): Promise<UnlockPrivateResponse> {
const env = await httpClient<ApiEnvelope<unknown>>(
ApiPath.chatUnlockPrivate,
{
method: "POST",
body,
...(options?.signal ? { signal: options.signal } : {}),
},
);
return UnlockPrivateResponseSchema.parse(
@@ -72,12 +80,14 @@ export class ChatApi {
*/
async unlockHistory(
body: UnlockHistoryRequest,
options?: { signal?: AbortSignal },
): Promise<UnlockHistoryResponse> {
const env = await httpClient<ApiEnvelope<unknown>>(
ApiPath.chatUnlockHistory,
{
method: "POST",
body,
...(options?.signal ? { signal: options.signal } : {}),
},
);
return UnlockHistoryResponseSchema.parse(