diff --git a/Dockerfile b/Dockerfile index 6aa1bfb4b..7ed7bc155 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,6 @@ RUN apk update && apk add --no-cache git ENV OPENAI_API_KEY="" ENV CODE="" -ARG DOCKER=true WORKDIR /app COPY --from=deps /app/node_modules ./node_modules diff --git a/app/api/access.ts b/app/api/access.ts deleted file mode 100644 index d3e4c9cf9..000000000 --- a/app/api/access.ts +++ /dev/null @@ -1,17 +0,0 @@ -import md5 from "spark-md5"; - -export function getAccessCodes(): Set { - const code = process.env.CODE; - - try { - const codes = (code?.split(",") ?? []) - .filter((v) => !!v) - .map((v) => md5.hash(v.trim())); - return new Set(codes); - } catch (e) { - return new Set(); - } -} - -export const ACCESS_CODES = getAccessCodes(); -export const IS_IN_DOCKER = process.env.DOCKER; diff --git a/app/components/settings.tsx b/app/components/settings.tsx index bbb28b46e..d39985966 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -26,13 +26,14 @@ import { import { Avatar } from "./chat"; import Locale, { AllLangs, changeLang, getLang } from "../locales"; -import { getCurrentVersion, getEmojiUrl } from "../utils"; +import { getEmojiUrl } from "../utils"; import Link from "next/link"; import { UPDATE_URL } from "../constant"; import { SearchService, usePromptStore } from "../store/prompt"; import { requestUsage } from "../requests"; import { ErrorBoundary } from "./error"; import { InputRange } from "./input-range"; +import { getClientSideConfig } from "../config/client"; function SettingItem(props: { title: string; @@ -88,9 +89,9 @@ export function Settings(props: { closeSettings: () => void }) { const updateStore = useUpdateStore(); const [checkingUpdate, setCheckingUpdate] = useState(false); - const currentId = getCurrentVersion(); + const currentVersion = getClientSideConfig()?.version; const remoteId = updateStore.remoteId; - const hasNewVersion = currentId !== remoteId; + const hasNewVersion = currentVersion !== remoteId; function checkUpdate(force = false) { setCheckingUpdate(true); @@ -224,7 +225,7 @@ export function Settings(props: { closeSettings: () => void }) { { + try { + const childProcess = require("child_process"); + return ( + childProcess + // .execSync("git describe --tags --abbrev=0") + .execSync("git rev-parse --short HEAD") + .toString() + .trim() + ); + } catch (e) { + console.error("[Build Config] No git or not from git repo."); + return "unknown"; + } +})(); + +export const getBuildConfig = () => { + if (typeof process === "undefined") { + throw Error( + "[Server Config] you are importing a nodejs-only module outside of nodejs", + ); + } + + return { + commitId: COMMIT_ID, + }; +}; diff --git a/app/config/client.ts b/app/config/client.ts new file mode 100644 index 000000000..0e9c50d0b --- /dev/null +++ b/app/config/client.ts @@ -0,0 +1,42 @@ +import { RUNTIME_CONFIG_DOM } from "../constant"; + +function queryMeta(key: string, defaultValue?: string): string { + let ret: string; + if (document) { + const meta = document.head.querySelector( + `meta[name='${key}']`, + ) as HTMLMetaElement; + ret = meta?.content ?? ""; + } else { + ret = defaultValue ?? ""; + } + + return ret; +} + +export function getClientSideConfig() { + if (typeof window === "undefined") { + throw Error( + "[Client Config] you are importing a browser-only module outside of browser", + ); + } + + const dom = document.getElementById(RUNTIME_CONFIG_DOM); + + if (!dom) { + throw Error("[Config] Dont get config before page loading!"); + } + + try { + const fromServerConfig = JSON.parse(dom.innerText) as DangerConfig; + const fromBuildConfig = { + version: queryMeta("version"), + }; + return { + ...fromServerConfig, + ...fromBuildConfig, + }; + } catch (e) { + console.error("[Config] failed to parse client config"); + } +} diff --git a/app/config/server.ts b/app/config/server.ts new file mode 100644 index 000000000..798177e59 --- /dev/null +++ b/app/config/server.ts @@ -0,0 +1,42 @@ +import md5 from "spark-md5"; + +declare global { + namespace NodeJS { + interface ProcessEnv { + OPENAI_API_KEY?: string; + CODE?: string; + PROXY_URL?: string; + VERCEL?: string; + } + } +} + +const ACCESS_CODES = (function getAccessCodes(): Set { + const code = process.env.CODE; + + try { + const codes = (code?.split(",") ?? []) + .filter((v) => !!v) + .map((v) => md5.hash(v.trim())); + return new Set(codes); + } catch (e) { + return new Set(); + } +})(); + +export const getServerSideConfig = () => { + if (typeof process === "undefined") { + throw Error( + "[Server Config] you are importing a nodejs-only module outside of nodejs", + ); + } + + return { + apiKey: process.env.OPENAI_API_KEY, + code: process.env.CODE, + codes: ACCESS_CODES, + needCode: ACCESS_CODES.size > 0, + proxyUrl: process.env.PROXY_URL, + isVercel: !!process.env.VERCEL, + }; +}; diff --git a/app/constant.ts b/app/constant.ts index 8a519d44e..6f08ad756 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -5,3 +5,4 @@ export const ISSUE_URL = `https://github.com/${OWNER}/${REPO}/issues`; export const UPDATE_URL = `${REPO_URL}#keep-updated`; export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`; export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`; +export const RUNTIME_CONFIG_DOM = "danger-runtime-config"; diff --git a/app/layout.tsx b/app/layout.tsx index 49a6d644d..38748ef37 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,19 +2,9 @@ import "./styles/globals.scss"; import "./styles/markdown.scss"; import "./styles/highlight.scss"; -import process from "child_process"; -import { ACCESS_CODES, IS_IN_DOCKER } from "./api/access"; +import { getBuildConfig } from "./config/build"; -let COMMIT_ID: string | undefined; -try { - COMMIT_ID = process - // .execSync("git describe --tags --abbrev=0") - .execSync("git rev-parse --short HEAD") - .toString() - .trim(); -} catch (e) { - console.error("No git or not from git repo."); -} +const buildConfig = getBuildConfig(); export const metadata = { title: "ChatGPT Next Web", @@ -26,21 +16,6 @@ export const metadata = { themeColor: "#fafafa", }; -function Meta() { - const metas = { - version: COMMIT_ID ?? "unknown", - access: ACCESS_CODES.size > 0 || IS_IN_DOCKER ? "enabled" : "disabled", - }; - - return ( - <> - {Object.entries(metas).map(([k, v]) => ( - - ))} - - ); -} - export default function RootLayout({ children, }: { @@ -58,7 +33,7 @@ export default function RootLayout({ content="#151515" media="(prefers-color-scheme: dark)" /> - + diff --git a/app/page.tsx b/app/page.tsx index 1d1da2227..484e2c205 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,12 +1,29 @@ import { Analytics } from "@vercel/analytics/react"; import { Home } from "./components/home"; +import { getServerSideConfig } from "./config/server"; +import { RUNTIME_CONFIG_DOM } from "./constant"; + +const serverConfig = getServerSideConfig(); + +// Danger! Don not write any secret value here! +// 警告!不要在这里写入任何敏感信息! +const DANGER_CONFIG = { + needCode: serverConfig?.needCode, +}; + +declare global { + type DangerConfig = typeof DANGER_CONFIG; +} export default function App() { return ( <> +
+ {JSON.stringify(DANGER_CONFIG)} +
- + {serverConfig?.isVercel && } ); } diff --git a/app/store/access.ts b/app/store/access.ts index 5d43e6327..9eafc3a45 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { queryMeta } from "../utils"; +import { getClientSideConfig } from "../config/client"; export interface AccessControlStore { accessCode: string; @@ -20,7 +20,7 @@ export const useAccessStore = create()( token: "", accessCode: "", enabledAccessControl() { - return queryMeta("access") === "enabled"; + return !!getClientSideConfig()?.needCode; }, updateCode(code: string) { set((state) => ({ accessCode: code })); @@ -30,7 +30,9 @@ export const useAccessStore = create()( }, isAuthorized() { // has token or has code or disabled access control - return !!get().token || !!get().accessCode || !get().enabledAccessControl(); + return ( + !!get().token || !!get().accessCode || !get().enabledAccessControl() + ); }, }), { diff --git a/app/store/update.ts b/app/store/update.ts index 97fb343c3..78001f91c 100644 --- a/app/store/update.ts +++ b/app/store/update.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; +import { getClientSideConfig } from "../config/client"; import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant"; -import { getCurrentVersion } from "../utils"; export interface UpdateStore { lastUpdate: number; @@ -22,7 +22,7 @@ export const useUpdateStore = create()( const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000; const shouldFetch = force || overTenMins; if (!shouldFetch) { - return getCurrentVersion(); + return getClientSideConfig()?.version ?? ""; } try { @@ -38,7 +38,7 @@ export const useUpdateStore = create()( return remoteId; } catch (error) { console.error("[Fetch Upstream Commit Id]", error); - return getCurrentVersion(); + return getClientSideConfig()?.version ?? ""; } }, }), diff --git a/app/utils.ts b/app/utils.ts index 9a792fd52..5c2b06975 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -69,31 +69,6 @@ export function selectOrCopy(el: HTMLElement, content: string) { return true; } -export function queryMeta(key: string, defaultValue?: string): string { - let ret: string; - if (document) { - const meta = document.head.querySelector( - `meta[name='${key}']`, - ) as HTMLMetaElement; - ret = meta?.content ?? ""; - } else { - ret = defaultValue ?? ""; - } - - return ret; -} - -let currentId: string; -export function getCurrentVersion() { - if (currentId) { - return currentId; - } - - currentId = queryMeta("version"); - - return currentId; -} - export function getEmojiUrl(unified: string, style: EmojiStyle) { return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`; } diff --git a/middleware.ts b/middleware.ts index 9338a2c6b..31a417a2f 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,21 +1,23 @@ import { NextRequest, NextResponse } from "next/server"; -import { ACCESS_CODES } from "./app/api/access"; +import { getServerSideConfig } from "./app/config/server"; import md5 from "spark-md5"; export const config = { matcher: ["/api/openai", "/api/chat-stream"], }; +const serverConfig = getServerSideConfig(); + export function middleware(req: NextRequest) { const accessCode = req.headers.get("access-code"); const token = req.headers.get("token"); const hashedCode = md5.hash(accessCode ?? "").trim(); - console.log("[Auth] allowed hashed codes: ", [...ACCESS_CODES]); + console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]); console.log("[Auth] got access code:", accessCode); console.log("[Auth] hashed access code:", hashedCode); - if (ACCESS_CODES.size > 0 && !ACCESS_CODES.has(hashedCode) && !token) { + if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) { return NextResponse.json( { error: true, diff --git a/next.config.js b/next.config.js index fc164db9c..f7d5ff086 100644 --- a/next.config.js +++ b/next.config.js @@ -8,14 +8,11 @@ const nextConfig = { config.module.rules.push({ test: /\.svg$/, use: ["@svgr/webpack"], - }); // 针对 SVG 的处理规则 + }); return config; - } + }, + output: "standalone", }; -if (process.env.DOCKER) { - nextConfig.output = 'standalone' -} - module.exports = nextConfig;