2026-01-18 05:13:53 +08:00
|
|
|
|
"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 = () => {
|
2026-01-20 07:24:12 +08:00
|
|
|
|
const normalize = (url: string) => url.replace(/\/+$/, "");
|
|
|
|
|
|
const isLocalHost = (host: string) => host === "localhost" || host === "127.0.0.1";
|
|
|
|
|
|
const convexProxyPath = "/convex";
|
|
|
|
|
|
|
2026-01-18 05:13:53 +08:00
|
|
|
|
// 默认使用环境变量
|
2026-01-20 07:24:12 +08:00
|
|
|
|
const envUrlRaw = (process.env.NEXT_PUBLIC_CONVEX_URL ?? "").trim();
|
2026-01-18 05:13:53 +08:00
|
|
|
|
if (typeof window === "undefined") {
|
2026-01-20 07:24:12 +08:00
|
|
|
|
return normalize(envUrlRaw || "http://127.0.0.1:3210");
|
2026-01-18 05:13:53 +08:00
|
|
|
|
}
|
2026-01-20 07:24:12 +08:00
|
|
|
|
|
|
|
|
|
|
const browserProtocol = window.location.protocol;
|
|
|
|
|
|
|
|
|
|
|
|
// 浏览器侧:优先尊重 NEXT_PUBLIC_CONVEX_URL(例如生产环境的 https://xxx.convex.cloud)。
|
|
|
|
|
|
// 仅在 envUrl 是 localhost/127.0.0.1 时,才把 host 改为当前 hostname,避免通过域名访问前端时出现不一致。
|
|
|
|
|
|
if (envUrlRaw) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const url = new URL(envUrlRaw);
|
|
|
|
|
|
const browserHost = window.location.hostname;
|
|
|
|
|
|
|
|
|
|
|
|
// 当通过 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) && !isLocalHost(browserHost)) {
|
|
|
|
|
|
// 非 https 访问时才尝试“借用当前 host:3210”;https 场景已提前走同源反代。
|
|
|
|
|
|
url.hostname = browserHost;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalize(url.toString());
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// envUrl 不是合法 URL 时,按原值兜底
|
|
|
|
|
|
return normalize(envUrlRaw);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 未配置 env:使用当前主机名,端口固定 3210,并跟随当前页面协议(http/https)。
|
2026-01-18 05:13:53 +08:00
|
|
|
|
const hostname = window.location.hostname;
|
2026-01-20 07:24:12 +08:00
|
|
|
|
const protocol = "http:";
|
|
|
|
|
|
return normalize(`${protocol}//${hostname}:3210`);
|
2026-01-18 05:13:53 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|