import React from 'react';
import type { Metadata } from "next";
import "./globals.css";
import { ThemeProvider } from "@/components/ThemeProvider";
import { AutoSync } from '@/components/AutoSync'; // <-- 引入了自动同步组件
import { SiteIconProvider } from '@/components/SiteIconProvider';
import { TVProvider } from "@/lib/contexts/TVContext";
import { TVNavigationInitializer } from "@/components/TVNavigationInitializer";
import { Analytics } from "@vercel/analytics/react";
import { ServiceWorkerRegister } from "@/components/ServiceWorkerRegister";
import { PasswordGate } from "@/components/PasswordGate";
import { siteConfig } from "@/lib/config/site-config";
import { AdKeywordsInjector } from "@/components/AdKeywordsInjector";
import { BackToTop } from "@/components/ui/BackToTop";
import { ScrollPositionManager } from "@/components/ScrollPositionManager";
import { LocaleProvider } from "@/components/LocaleProvider";
import { RuntimeFeaturesProvider } from "@/components/RuntimeFeaturesProvider";
import { VideoTogetherController } from '@/components/VideoTogetherController';
import { getRuntimeFeatures } from "@/lib/server/runtime-features";
import { resolveSiteIconSrc } from '@/lib/server/site-icon';
import fs from 'fs';
import path from 'path';
const DEFAULT_VIDEOTOGETHER_SCRIPT_URL =
'https://fastly.jsdelivr.net/gh/VideoTogether/VideoTogether@latest/release/extension.website.user.js';
// Server Component specifically for reading env/file (async for best practices)
async function AdKeywordsWrapper() {
let keywords: string[] = [];
try {
// 1. Try reading from file (Docker runtime support)
const keywordsFile = process.env.AD_KEYWORDS_FILE;
if (keywordsFile) {
// Resolve absolute path or relative to CWD
const filePath = path.isAbsolute(keywordsFile)
? keywordsFile
: path.join(process.cwd(), keywordsFile);
try {
const content = await fs.promises.readFile(filePath, 'utf-8');
keywords = content.split(/[\n,]/).map((k: string) => k.trim()).filter((k: string) => k);
console.log(`[AdFilter] Loaded ${keywords.length} keywords from file: ${filePath}`);
} catch (fileError: unknown) {
// Handle file not found (ENOENT) gracefully
if ((fileError as NodeJS.ErrnoException).code !== 'ENOENT') {
console.warn('[AdFilter] Error reading keywords file:', fileError);
}
}
}
// 2. Fallback to Env var (Runtime or Build time)
if (keywords.length === 0) {
const envKeywords = process.env.AD_KEYWORDS || process.env.NEXT_PUBLIC_AD_KEYWORDS;
if (envKeywords) {
keywords = envKeywords.split(/[\n,]/).map((k: string) => k.trim()).filter((k: string) => k);
}
}
} catch (error) {
console.warn('[AdFilter] Failed to load keywords:', error);
}
return ;
}
export async function generateMetadata(): Promise {
const siteIconSrc = await resolveSiteIconSrc();
return {
title: siteConfig.title,
description: siteConfig.description,
icons: {
icon: siteIconSrc,
},
};
}
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const siteIconSrc = await resolveSiteIconSrc();
const runtimeFeatures = getRuntimeFeatures();
const videoTogetherScriptUrl =
process.env.VIDEOTOGETHER_SCRIPT_URL?.trim() || DEFAULT_VIDEOTOGETHER_SCRIPT_URL;
const videoTogetherSettingUrl = process.env.VIDEOTOGETHER_SETTING_URL?.trim();
const videoTogetherEnvEnabled = process.env.VIDEOTOGETHER_ENABLED !== 'false';
return (
{/* PWA Manifest */}
{/* Apple PWA Support */}
{/* Theme Color (for browser address bar) */}
{/* Mobile viewport */}
{/* 加入自动同步组件,它会在后台默默工作,我们放在 ThemeProvider 内部的最前面 */}
{children}
{/* ARIA Live Region for Screen Reader Announcements */}
{/* Google Cast SDK */}
{/* Scroll Performance Optimization Script */}
);
}