Files
mnote/wolai-frontend/src/components/providers/convex-provider.tsx
T
2026-01-26 00:59:15 +08:00

89 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 (
<ConvexAuthNextjsProvider client={convex}>
{children}
</ConvexAuthNextjsProvider>
);
}