This commit is contained in:
hailin 2025-06-25 15:27:50 +08:00
parent 2c1daebf74
commit f6ae45debe
4 changed files with 108 additions and 138 deletions

View File

@ -3,16 +3,16 @@ import { GlobalState } from "@/components/utility/global-state"
import { Providers } from "@/components/utility/providers"
import TranslationsProvider from "@/components/utility/translations-provider"
import initTranslations from "@/lib/i18n"
import { Database } from "@/supabase/types"
//import { Database } from "@/supabase/types"
import { getSupabaseServerClient } from "@/lib/supabase/server"
import { Metadata, Viewport } from "next"
import { Inter } from "next/font/google"
import { cookies } from "next/headers"
import { ReactNode } from "react"
import "./globals.css"
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
import Script from "next/script"
//import Script from "next/script"
import { Suspense } from "react"
import { RuntimeEnvProvider } from "@/components/utility/runtime-env-provider"
const inter = Inter({ subsets: ["latin"] })
@ -31,20 +31,20 @@ interface RootLayoutProps {
// 新增的 `getValidSupabaseUrl` 函数,带有超时机制和错误处理
async function getValidSupabaseUrl(): Promise<string> {
let url = getRuntimeEnv("SUPABASE_URL");
const timeout = Date.now() + 30000; // 设置最大等待时间为30秒
// async function getValidSupabaseUrl(): Promise<string> {
// let url = getRuntimeEnv("SUPABASE_URL");
// const timeout = Date.now() + 30000; // 设置最大等待时间为30秒
while (!url || !url.includes(":")) { // 检查是否包含合法的 IP 和端口
if (Date.now() > timeout) {
throw new Error("获取有效的 Supabase URL 超时");
}
console.log("等待有效的 Supabase URL...");
await new Promise(resolve => setTimeout(resolve, 1000)); // 每1秒检查一次
url = getRuntimeEnv("SUPABASE_URL");
}
return url;
}
// while (!url || !url.includes(":")) { // 检查是否包含合法的 IP 和端口
// if (Date.now() > timeout) {
// throw new Error("获取有效的 Supabase URL 超时");
// }
// console.log("等待有效的 Supabase URL...");
// await new Promise(resolve => setTimeout(resolve, 1000)); // 每1秒检查一次
// url = getRuntimeEnv("SUPABASE_URL");
// }
// return url;
// }
@ -113,15 +113,15 @@ export default async function RootLayout({
console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`);
}
let supabaseUrl = "";
try {
// 等待直到获取到有效的 Supabase URL
supabaseUrl = await getValidSupabaseUrl();
//console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
} catch (error) {
console.error("Supabase URL 获取失败:", error);
return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示
}
// let supabaseUrl = "";
// try {
// // 等待直到获取到有效的 Supabase URL
// supabaseUrl = await getValidSupabaseUrl();
// //console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
// } catch (error) {
// console.error("Supabase URL 获取失败:", error);
// return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示
// }
const supabase = getSupabaseServerClient()
const { data, error } = await supabase.auth.getSession();
@ -137,27 +137,29 @@ export default async function RootLayout({
<html lang="en" suppressHydrationWarning>
<head>
<Script
{/* <Script
src="/env.js"
strategy="beforeInteractive" // 确保在 React 启动之前加载
/>
/> */}
</head>
<body className={inter.className}>
<Providers attribute="class" defaultTheme="dark">
<TranslationsProvider
namespaces={i18nNamespaces}
locale={locale}
resources={resources}
>
<Toaster richColors position="top-center" duration={3000} />
<Suspense fallback={null}>
<div className="bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto">
{data.session ? <GlobalState>{children}</GlobalState> : children}
</div>
</Suspense>
</TranslationsProvider>
</Providers>
<RuntimeEnvProvider>
<Providers attribute="class" defaultTheme="dark">
<TranslationsProvider
namespaces={i18nNamespaces}
locale={locale}
resources={resources}
>
<Toaster richColors position="top-center" duration={3000} />
<Suspense fallback={null}>
<div className="bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto">
{data.session ? <GlobalState>{children}</GlobalState> : children}
</div>
</Suspense>
</TranslationsProvider>
</Providers>
</RuntimeEnvProvider>
</body>
</html>
)

View File

@ -0,0 +1,25 @@
// app/utility/runtime-env-provider.tsx
import { headers, useServerInsertedHTML } from "next/headers";
import React from "react";
export function RuntimeEnvProvider({ children }: { children: React.ReactNode }) {
/* ① 计算本次请求的 Supabase URL按你的内网场景改即可 */
const h = headers();
const proto = h.get("x-forwarded-proto") ?? "http";
const host = h.get("x-forwarded-host") ?? h.get("host"); // 内网一般就是 host
const port = process.env.SUPABASE_PORT ?? "8000";
const supabaseUrl = `${proto}://${host?.split(",")[0]}:${port}`;
/* ② 注入脚本:保证在 <head> 中且早于所有客户端 JS */
useServerInsertedHTML(() => (
<script
dangerouslySetInnerHTML={{
__html: `window.RUNTIME_ENV = { SUPABASE_URL: ${JSON.stringify(
supabaseUrl,
)} };`,
}}
/>
));
return <>{children}</>;
}

View File

@ -1,60 +0,0 @@
// lib/ipconfig.ts
// export function getRuntimeEnv(key: string): string | undefined {
// if (typeof window !== "undefined" && typeof window.RUNTIME_ENV !== "undefined") {
// return window.RUNTIME_ENV[key];
// }
// return process.env[key];
// }
let _env: Record<string, string> | null = null
// 最大重试次数
const MAX_RETRIES = 5
// 每次重试的延时(毫秒)
const RETRY_DELAY = 1000
export function getRuntimeEnv(key: string): string | undefined {
//console.log("============>>Getting Supabase API URL.")
if (typeof window !== "undefined") {
if (!_env && typeof window.RUNTIME_ENV !== "undefined") {
_env = window.RUNTIME_ENV
console.log("[browser-side] Retrieved API endpoint from window.RUNTIME_ENV:", _env)
}
// 尝试读取缓存的 _env
if (_env) {
console.log("[browser-side] Returning cached API from _env:", _env)
return _env[key]
}
console.log("[browser-side] No window.RUNTIME_ENV found in browser, _env=", _env)
let retries = 0
// 定义一个同步延时重试的函数
const tryGetEnvSync = () => {
if (typeof window.RUNTIME_ENV !== "undefined") {
_env = window.RUNTIME_ENV
console.log("[browser-side] Retrieved API endpoint from window.RUNTIME_ENV:", _env)
return _env[key] // 成功获取,返回结果
} else {
retries += 1
if (retries <= MAX_RETRIES) {
console.log(`[browser-side] Retry ${retries}/${MAX_RETRIES} - Retrying in ${RETRY_DELAY}ms...`)
// 延时并重试
setTimeout(tryGetEnvSync, RETRY_DELAY)
} else {
console.log("[browser-side] Failed to retrieve window.RUNTIME_ENV after retries.")
return undefined // 重试次数耗尽,返回 undefined
}
}
}
return tryGetEnvSync() // 调用同步重试方法
}
// 服务端始终动态读取 process.env
const val = process.env[key]
//console.log("[server-side] Falling back to process.env for key:", key, "value:", val)
return val // 服务端直接返回 process.env 的值
}

View File

@ -1,53 +1,56 @@
// import { createServerClient, type CookieOptions } from "@supabase/ssr"
// import { cookies } from "next/headers"
// import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
// import { getRuntimeEnv } from "@/lib/ipconfig"
// import { Database } from "@/supabase/types"
// export const createClient = (cookieStore: ReturnType<typeof cookies>) => {
// return createServerClient(
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
// process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
// {
// cookies: {
// get(name: string) {
// return cookieStore.get(name)?.value
// },
// set(name: string, value: string, options: CookieOptions) {
// try {
// cookieStore.set({ name, value, ...options })
// } catch (error) {
// // The `set` method was called from a Server Component.
// // This can be ignored if you have middleware refreshing
// // user sessions.
// }
// },
// remove(name: string, options: CookieOptions) {
// try {
// cookieStore.set({ name, value: "", ...options })
// } catch (error) {
// // The `delete` method was called from a Server Component.
// // This can be ignored if you have middleware refreshing
// // user sessions.
// }
// }
// export function getSupabaseServerClient() {
// const supabaseUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
// const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
// const cookieStore = cookies()
// return createServerClient<Database>(supabaseUrl, supabaseAnonKey, {
// cookies: {
// get(name: string) {
// return cookieStore.get(name)?.value
// },
// set(name: string, value: string, options: CookieOptions) {
// try {
// cookieStore.set({ name, value, ...options })
// } catch (_) {}
// },
// remove(name: string, options: CookieOptions) {
// try {
// cookieStore.set({ name, value: "", ...options })
// } catch (_) {}
// }
// }
// )
// })
// }
// lib/supabase-server.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr"
import { cookies } from "next/headers"
import { getRuntimeEnv } from "@/lib/ipconfig"
import { getRuntimeEnv } from "@/lib/get-runtime-env" // ← 改成新的工具函数
import { Database } from "@/supabase/types"
export function getSupabaseServerClient() {
const supabaseUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
/* URL RuntimeEnv RuntimeEnvProvider
退 .env localhost */
const supabaseUrl =
getRuntimeEnv("SUPABASE_URL") ??
process.env.SUPABASE_URL ??
"http://localhost:8000"
/* ② 匿名 KEY 只放 .env别暴露到 RuntimeEnv */
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
if (!supabaseAnonKey) {
throw new Error("⛔️ NEXT_PUBLIC_SUPABASE_ANON_KEY 未设置")
}
/* ③ Cookie 透传Next 13/14 App Router 写法) */
const cookieStore = cookies()
return createServerClient<Database>(supabaseUrl, supabaseAnonKey, {
@ -58,12 +61,12 @@ export function getSupabaseServerClient() {
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options })
} catch (_) {}
} catch {}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: "", ...options })
} catch (_) {}
} catch {}
}
}
})