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 { Providers } from "@/components/utility/providers"
|
||||||
import TranslationsProvider from "@/components/utility/translations-provider"
|
import TranslationsProvider from "@/components/utility/translations-provider"
|
||||||
import initTranslations from "@/lib/i18n"
|
import initTranslations from "@/lib/i18n"
|
||||||
import { Database } from "@/supabase/types"
|
//import { Database } from "@/supabase/types"
|
||||||
import { getSupabaseServerClient } from "@/lib/supabase/server"
|
import { getSupabaseServerClient } from "@/lib/supabase/server"
|
||||||
import { Metadata, Viewport } from "next"
|
import { Metadata, Viewport } from "next"
|
||||||
import { Inter } from "next/font/google"
|
import { Inter } from "next/font/google"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
import { ReactNode } from "react"
|
import { ReactNode } from "react"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import { getRuntimeEnv } from "@/lib/ipconfig" // 新增引入
|
//import Script from "next/script"
|
||||||
import Script from "next/script"
|
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
|
import { RuntimeEnvProvider } from "@/components/utility/runtime-env-provider"
|
||||||
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] })
|
const inter = Inter({ subsets: ["latin"] })
|
||||||
|
|
@ -31,20 +31,20 @@ interface RootLayoutProps {
|
||||||
|
|
||||||
|
|
||||||
// 新增的 `getValidSupabaseUrl` 函数,带有超时机制和错误处理
|
// 新增的 `getValidSupabaseUrl` 函数,带有超时机制和错误处理
|
||||||
async function getValidSupabaseUrl(): Promise<string> {
|
// async function getValidSupabaseUrl(): Promise<string> {
|
||||||
let url = getRuntimeEnv("SUPABASE_URL");
|
// let url = getRuntimeEnv("SUPABASE_URL");
|
||||||
const timeout = Date.now() + 30000; // 设置最大等待时间为30秒
|
// const timeout = Date.now() + 30000; // 设置最大等待时间为30秒
|
||||||
|
|
||||||
while (!url || !url.includes(":")) { // 检查是否包含合法的 IP 和端口
|
// while (!url || !url.includes(":")) { // 检查是否包含合法的 IP 和端口
|
||||||
if (Date.now() > timeout) {
|
// if (Date.now() > timeout) {
|
||||||
throw new Error("获取有效的 Supabase URL 超时");
|
// throw new Error("获取有效的 Supabase URL 超时");
|
||||||
}
|
// }
|
||||||
console.log("等待有效的 Supabase URL...");
|
// console.log("等待有效的 Supabase URL...");
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 每1秒检查一次
|
// await new Promise(resolve => setTimeout(resolve, 1000)); // 每1秒检查一次
|
||||||
url = getRuntimeEnv("SUPABASE_URL");
|
// url = getRuntimeEnv("SUPABASE_URL");
|
||||||
}
|
// }
|
||||||
return url;
|
// return url;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -113,15 +113,15 @@ export default async function RootLayout({
|
||||||
console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`);
|
console.log(`🍪 Cookie: ${cookie.name} = ${cookie.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let supabaseUrl = "";
|
// let supabaseUrl = "";
|
||||||
try {
|
// try {
|
||||||
// 等待直到获取到有效的 Supabase URL
|
// // 等待直到获取到有效的 Supabase URL
|
||||||
supabaseUrl = await getValidSupabaseUrl();
|
// supabaseUrl = await getValidSupabaseUrl();
|
||||||
//console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
|
// //console.log("==========>获取到有效的 Supabase URL: ", supabaseUrl);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Supabase URL 获取失败:", error);
|
// console.error("Supabase URL 获取失败:", error);
|
||||||
return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示
|
// return <div>Failed to fetch Supabase configuration, please try again later.</div>; // 出现错误时返回一个友好的提示
|
||||||
}
|
// }
|
||||||
|
|
||||||
const supabase = getSupabaseServerClient()
|
const supabase = getSupabaseServerClient()
|
||||||
const { data, error } = await supabase.auth.getSession();
|
const { data, error } = await supabase.auth.getSession();
|
||||||
|
|
@ -137,27 +137,29 @@ export default async function RootLayout({
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<Script
|
{/* <Script
|
||||||
src="/env.js"
|
src="/env.js"
|
||||||
strategy="beforeInteractive" // 确保在 React 启动之前加载
|
strategy="beforeInteractive" // 确保在 React 启动之前加载
|
||||||
/>
|
/> */}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<Providers attribute="class" defaultTheme="dark">
|
<RuntimeEnvProvider>
|
||||||
<TranslationsProvider
|
<Providers attribute="class" defaultTheme="dark">
|
||||||
namespaces={i18nNamespaces}
|
<TranslationsProvider
|
||||||
locale={locale}
|
namespaces={i18nNamespaces}
|
||||||
resources={resources}
|
locale={locale}
|
||||||
>
|
resources={resources}
|
||||||
<Toaster richColors position="top-center" duration={3000} />
|
>
|
||||||
<Suspense fallback={null}>
|
<Toaster richColors position="top-center" duration={3000} />
|
||||||
<div className="bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto">
|
<Suspense fallback={null}>
|
||||||
{data.session ? <GlobalState>{children}</GlobalState> : children}
|
<div className="bg-background text-foreground flex h-dvh flex-col items-center overflow-x-auto">
|
||||||
</div>
|
{data.session ? <GlobalState>{children}</GlobalState> : children}
|
||||||
</Suspense>
|
</div>
|
||||||
</TranslationsProvider>
|
</Suspense>
|
||||||
</Providers>
|
</TranslationsProvider>
|
||||||
|
</Providers>
|
||||||
|
</RuntimeEnvProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 { createServerClient, type CookieOptions } from "@supabase/ssr"
|
||||||
// import { cookies } from "next/headers"
|
// 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>) => {
|
// export function getSupabaseServerClient() {
|
||||||
// return createServerClient(
|
// const supabaseUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
|
||||||
// getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000",
|
// const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||||
// process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
||||||
// {
|
// const cookieStore = cookies()
|
||||||
// cookies: {
|
|
||||||
// get(name: string) {
|
// return createServerClient<Database>(supabaseUrl, supabaseAnonKey, {
|
||||||
// return cookieStore.get(name)?.value
|
// cookies: {
|
||||||
// },
|
// get(name: string) {
|
||||||
// set(name: string, value: string, options: CookieOptions) {
|
// return cookieStore.get(name)?.value
|
||||||
// try {
|
// },
|
||||||
// cookieStore.set({ name, value, ...options })
|
// set(name: string, value: string, options: CookieOptions) {
|
||||||
// } catch (error) {
|
// try {
|
||||||
// // The `set` method was called from a Server Component.
|
// cookieStore.set({ name, value, ...options })
|
||||||
// // This can be ignored if you have middleware refreshing
|
// } catch (_) {}
|
||||||
// // user sessions.
|
// },
|
||||||
// }
|
// remove(name: string, options: CookieOptions) {
|
||||||
// },
|
// try {
|
||||||
// remove(name: string, options: CookieOptions) {
|
// cookieStore.set({ name, value: "", ...options })
|
||||||
// try {
|
// } catch (_) {}
|
||||||
// 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.
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// )
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// lib/supabase-server.ts
|
||||||
|
|
||||||
|
|
||||||
import { createServerClient, type CookieOptions } from "@supabase/ssr"
|
import { createServerClient, type CookieOptions } from "@supabase/ssr"
|
||||||
import { cookies } from "next/headers"
|
import { cookies } from "next/headers"
|
||||||
import { getRuntimeEnv } from "@/lib/ipconfig"
|
import { getRuntimeEnv } from "@/lib/get-runtime-env" // ← 改成新的工具函数
|
||||||
import { Database } from "@/supabase/types"
|
import { Database } from "@/supabase/types"
|
||||||
|
|
||||||
export function getSupabaseServerClient() {
|
export function getSupabaseServerClient() {
|
||||||
const supabaseUrl = getRuntimeEnv("SUPABASE_URL") ?? "http://localhost:8000"
|
/* ① URL:优先 RuntimeEnv(由 RuntimeEnvProvider 注入),
|
||||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
然后退回到 .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()
|
const cookieStore = cookies()
|
||||||
|
|
||||||
return createServerClient<Database>(supabaseUrl, supabaseAnonKey, {
|
return createServerClient<Database>(supabaseUrl, supabaseAnonKey, {
|
||||||
|
|
@ -58,12 +61,12 @@ export function getSupabaseServerClient() {
|
||||||
set(name: string, value: string, options: CookieOptions) {
|
set(name: string, value: string, options: CookieOptions) {
|
||||||
try {
|
try {
|
||||||
cookieStore.set({ name, value, ...options })
|
cookieStore.set({ name, value, ...options })
|
||||||
} catch (_) {}
|
} catch {}
|
||||||
},
|
},
|
||||||
remove(name: string, options: CookieOptions) {
|
remove(name: string, options: CookieOptions) {
|
||||||
try {
|
try {
|
||||||
cookieStore.set({ name, value: "", ...options })
|
cookieStore.set({ name, value: "", ...options })
|
||||||
} catch (_) {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue