This commit is contained in:
parent
2c1daebf74
commit
f6ae45debe
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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}</>;
|
||||
}
|
||||
|
|
@ -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 的值
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue