fix: add missing fetch timeouts across server API layer

Prevent potential hangs from fetch calls without AbortSignal:
- heicode-auth.ts: token exchange (15s)
- heicode-tasks.ts: forwardJson upstream calls (60s)
- createDirectConnectSession.ts: session creation (15s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 18:44:41 +08:00
co-authored by Claude Opus 4.6
parent bbe5c8866b
commit 8adce51961
3 changed files with 6 additions and 0 deletions
+3
View File
@@ -162,6 +162,7 @@ export async function handleHeicodeAuthApi(
const upstream = await fetch(`${mcpBase}/api/user/heicode/balance`, { const upstream = await fetch(`${mcpBase}/api/user/heicode/balance`, {
method: 'GET', method: 'GET',
headers: { 'Authorization': `Bearer ${active.mcpAuth.accessToken}` }, headers: { 'Authorization': `Bearer ${active.mcpAuth.accessToken}` },
signal: AbortSignal.timeout(15_000),
}) })
// Happy path — mcp-server knows the user. // Happy path — mcp-server knows the user.
if (upstream.ok) { if (upstream.ok) {
@@ -179,6 +180,7 @@ export async function handleHeicodeAuthApi(
const self = await fetch(`${heicodeBase}/api/user/self`, { const self = await fetch(`${heicodeBase}/api/user/self`, {
method: 'GET', method: 'GET',
headers: { 'Authorization': `Bearer ${active.apiKey}` }, headers: { 'Authorization': `Bearer ${active.apiKey}` },
signal: AbortSignal.timeout(15_000),
}) })
if (self.ok) { if (self.ok) {
const json = await self.json() as { const json = await self.json() as {
@@ -836,6 +838,7 @@ async function exchangeCodeForToken(input: {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(), body: body.toString(),
signal: AbortSignal.timeout(15_000),
}) })
if (!res.ok) { if (!res.ok) {
throw new Error(`token 交换失败: ${res.status} ${res.statusText}`) throw new Error(`token 交换失败: ${res.status} ${res.statusText}`)
+2
View File
@@ -60,6 +60,7 @@ async function forwardJson(
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}), ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
}, },
...(body !== undefined ? { body: JSON.stringify(body) } : {}), ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
signal: AbortSignal.timeout(60_000),
} }
let upstream: Response let upstream: Response
try { try {
@@ -96,6 +97,7 @@ async function forwardSse(path: string): Promise<Response> {
Accept: 'text/event-stream', Accept: 'text/event-stream',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
}, },
signal: AbortSignal.timeout(600_000),
}) })
if (!upstream.ok || !upstream.body) { if (!upstream.ok || !upstream.body) {
const text = await upstream.text().catch(() => '') const text = await upstream.text().catch(() => '')
@@ -55,6 +55,7 @@ export async function createDirectConnectSession({
dangerously_skip_permissions: true, dangerously_skip_permissions: true,
}), }),
}), }),
signal: AbortSignal.timeout(15_000),
}) })
} catch (err) { } catch (err) {
throw new DirectConnectError( throw new DirectConnectError(