"use client"; import { ReactNode, useState } from "react"; import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs"; import { ConvexReactClient } from "convex/react"; interface ConvexClientProviderProps { children: ReactNode; } /** * Convex 客户端 Provider(包含 Auth) */ export function ConvexClientProvider({ children }: ConvexClientProviderProps) { const [convex] = useState(() => { // 动态获取 Convex URL,默认优先使用环境变量。 // 说明:浏览器侧优先用当前 hostname(端口固定 3210),避免通过非 localhost/127 访问前端时出现跨域/白名单不匹配。 const getConvexUrl = () => { const normalize = (url: string) => url.replace(/\/+$/, ""); const isLocalHost = (host: string) => host === "localhost" || host === "127.0.0.1"; const convexProxyPath = "/convex"; // 默认使用环境变量 const envUrlRaw = (process.env.NEXT_PUBLIC_CONVEX_URL ?? "").trim(); if (typeof window === "undefined") { return normalize(envUrlRaw || "http://127.0.0.1:3210"); } const browserProtocol = window.location.protocol; const browserHost = window.location.hostname; const shouldUseSameOriginProxy = () => { // 说明: // - 本项目的 Convex 自托管通常运行在本机 3210 端口; // - 通过 FRP/域名访问前端时,通常不会额外暴露 3210; // 因此只要不是 localhost/127.0.0.1 访问,就优先走同源 `/convex/*` 反代(由 dev/prod-server.js 处理), // 避免登录后因 Convex 连接失败而卡在“无法进入/未同步登录态”的状态。 return !isLocalHost(browserHost); }; // 浏览器侧:优先尊重 NEXT_PUBLIC_CONVEX_URL(例如生产环境的 https://xxx.convex.cloud)。 // 仅在 envUrl 是 localhost/127.0.0.1 时,才把 host 改为当前 hostname,避免通过域名访问前端时出现不一致。 if (envUrlRaw) { try { const url = new URL(envUrlRaw); // 当通过 HTTPS(如 frp/nginx)访问前端时,浏览器不允许从 https 页面发起 ws://(不安全)连接。 // 若 envUrl 仍是 http://localhost:3210,则走同源反代(由 scripts/dev-server.js 处理 Upgrade)转到本机 3210。 if (browserProtocol === "https:" && (url.protocol === "http:" || isLocalHost(url.hostname))) { return normalize(`${window.location.origin}${convexProxyPath}`); } if (isLocalHost(url.hostname) && shouldUseSameOriginProxy()) { // 说明:HTTP 场景下如果直接改成 `${browserHost}:3210`,通常会因为 3210 未暴露而连接失败。 // 因此对“域名访问”同样走同源反代。 return normalize(`${window.location.origin}${convexProxyPath}`); } return normalize(url.toString()); } catch { // envUrl 不是合法 URL 时,按原值兜底 return normalize(envUrlRaw); } } // 未配置 env:使用当前主机名,端口固定 3210,并跟随当前页面协议(http/https)。 // - https 场景下浏览器禁止 ws://,因此默认走同源反代 `/convex`(需要 server 支持 Upgrade 透传)。 // - 域名/FRP 场景下通常也不会暴露 3210,因此同样走同源反代。 if (browserProtocol === "https:" || shouldUseSameOriginProxy()) { return normalize(`${window.location.origin}${convexProxyPath}`); } const hostname = window.location.hostname; return normalize(`http://${hostname}:3210`); }; const convexUrl = getConvexUrl(); if (!convexUrl) { throw new Error("NEXT_PUBLIC_CONVEX_URL is not defined"); } return new ConvexReactClient(convexUrl); }); return ( {children} ); }