From b96ec6973092c79443bc4fdd51fa6207d1d573bc Mon Sep 17 00:00:00 2001 From: digua Date: Sun, 31 May 2026 15:43:36 +0800 Subject: [PATCH] fix(cli): add /_proxy/chatlab.fun reverse proxy for web mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 Web 生产模式(chatlab start)下,浏览器无法直连 chatlab.fun(CORS 限制), 导致首页底部社交链接(如小红书)及云端助手/技能预设拉取失败。 Vite dev proxy 已有对应配置,但生产 Fastify server 缺少该代理,现补全。 Co-Authored-By: Claude Sonnet 4.6 --- apps/cli/src/http/index.ts | 5 ++++ apps/cli/src/http/routes/proxy.ts | 42 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 apps/cli/src/http/routes/proxy.ts diff --git a/apps/cli/src/http/index.ts b/apps/cli/src/http/index.ts index 04b8e58f..7268ba4d 100644 --- a/apps/cli/src/http/index.ts +++ b/apps/cli/src/http/index.ts @@ -25,6 +25,7 @@ import { registerWebRoutes } from './routes/web' import { registerNlpRoutes } from './routes/nlp' import { registerAiRoutes } from './routes/ai' import { registerPreferencesRoutes } from './routes/preferences' +import { registerProxyRoutes } from './routes/proxy' import { initServerAiLogger, closeServerAiLogger } from '../ai/logger' import { initSync, cleanupSync } from '../sync' import { resolveCliPath } from '../paths' @@ -140,6 +141,10 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{ if (options?.webRoot && fs.existsSync(options.webRoot)) { setWebMode(true) + // 注册反向代理:将 /_proxy/chatlab.fun/* 转发至 https://chatlab.fun, + // 行为与 vite dev proxy 一致,解决浏览器 CORS 问题(见 vite.web.config.mts:138-144)。 + // 必须在 @fastify/static 之前注册,确保显式路由优先于静态文件/SPA fallback。 + registerProxyRoutes(server) const fastifyStatic = await import('@fastify/static') await server.register(fastifyStatic.default, { root: options.webRoot, diff --git a/apps/cli/src/http/routes/proxy.ts b/apps/cli/src/http/routes/proxy.ts new file mode 100644 index 00000000..a6eb8de6 --- /dev/null +++ b/apps/cli/src/http/routes/proxy.ts @@ -0,0 +1,42 @@ +/** + * 反向代理路由 — /_proxy/chatlab.fun/* + * + * 在 Web 生产模式(chatlab start)下,将 /_proxy/chatlab.fun/* 代理到 + * https://chatlab.fun,行为与 vite.web.config.mts 中的 dev proxy 保持一致, + * 以解决浏览器 CORS 限制。 + * + * 目标 host 硬编码为 https://chatlab.fun,不允许外部指定,避免开放代理风险。 + */ + +import type { FastifyInstance } from 'fastify' + +const PROXY_TARGET = 'https://chatlab.fun' + +export function registerProxyRoutes(server: FastifyInstance): void { + server.get<{ Params: { '*': string } }>('/_proxy/chatlab.fun/*', async (request, reply) => { + const subPath = request.params['*'] ?? '' + const queryString = new URLSearchParams( + request.query as Record, + ).toString() + const targetUrl = queryString + ? `${PROXY_TARGET}/${subPath}?${queryString}` + : `${PROXY_TARGET}/${subPath}` + + try { + const upstream = await fetch(targetUrl, { + headers: { Accept: request.headers.accept ?? '*/*' }, + signal: AbortSignal.timeout(10_000), + }) + + const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream' + const body = Buffer.from(await upstream.arrayBuffer()) + + reply.code(upstream.status).header('content-type', contentType).send(body) + } catch (err) { + reply.code(502).send({ + error: 'Proxy error', + message: err instanceof Error ? err.message : String(err), + }) + } + }) +}