2026-01-15 20:54:21 +08:00
|
|
|
|
export type MnoteRuntimeConfig = {
|
2026-01-17 10:12:53 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 是否启用 Convex(自部署)链路。
|
|
|
|
|
|
* 说明:该字段由服务端在运行期注入到 window.__MNOTE_RUNTIME_CONFIG__,用于客户端按需关闭 Supabase 相关能力。
|
|
|
|
|
|
*/
|
|
|
|
|
|
useConvex?: boolean;
|
2026-01-15 20:54:21 +08:00
|
|
|
|
supabaseUrl?: string;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 服务端/本机回源用的 Supabase 地址(通常是 HTTP),用于避免 FRP/自签证书导致 Node 侧 TLS 校验失败。
|
|
|
|
|
|
* 说明:该字段会出现在 public/mnote-env.json 中,但它主要给服务端读取使用。
|
|
|
|
|
|
*/
|
|
|
|
|
|
supabaseInternalUrl?: string;
|
|
|
|
|
|
supabaseAnonKey?: string;
|
|
|
|
|
|
backendUrl?: string;
|
|
|
|
|
|
onlyofficeBaseUrl?: string;
|
|
|
|
|
|
onlyofficeBaseUrlWeb?: string;
|
|
|
|
|
|
onlyofficeBaseUrlDesktop?: string;
|
|
|
|
|
|
onlyofficeStorageHostOverride?: string;
|
|
|
|
|
|
onlyofficeStorageHostOverrideWeb?: string;
|
|
|
|
|
|
onlyofficeStorageHostOverrideDesktop?: string;
|
|
|
|
|
|
cloudflareAppOrigin?: string;
|
|
|
|
|
|
onlyofficeProxyOriginWeb?: string;
|
|
|
|
|
|
onlyofficeCallbackOriginWeb?: string;
|
|
|
|
|
|
onlyofficeCallbackOriginDesktop?: string;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是否为桌面端(Electron)运行。
|
|
|
|
|
|
*/
|
|
|
|
|
|
isDesktop?: boolean;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ONLYOFFICE 文档服务器拉取 document.url 时,如需使用 /api/onlyoffice/proxy
|
|
|
|
|
|
* 规避 signedUrl 的 token 参数冲突,可把代理地址指向一个“文档服务器可访问”的公网 Origin。
|
|
|
|
|
|
*
|
|
|
|
|
|
* - 桌面端本地运行(window.location.origin):通常可不配置
|
|
|
|
|
|
* - 通过 Cloudflare Tunnel 使用远端 ONLYOFFICE:建议配置为 https://app.<你的域名>
|
|
|
|
|
|
*/
|
|
|
|
|
|
onlyofficeProxyOrigin?: string;
|
|
|
|
|
|
onlyofficeCallbackOrigin?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
|
interface Window {
|
|
|
|
|
|
__MNOTE_RUNTIME_CONFIG__?: MnoteRuntimeConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const readFromEnv = (): MnoteRuntimeConfig => ({
|
2026-01-17 10:12:53 +08:00
|
|
|
|
useConvex: process.env.USE_CONVEX === "1",
|
2026-01-15 20:54:21 +08:00
|
|
|
|
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
|
|
|
|
supabaseInternalUrl: process.env.SUPABASE_INTERNAL_URL,
|
|
|
|
|
|
supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
2026-01-17 10:12:53 +08:00
|
|
|
|
backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL ?? process.env.BACKEND_URL,
|
2026-01-15 20:54:21 +08:00
|
|
|
|
onlyofficeBaseUrl: process.env.NEXT_PUBLIC_ONLYOFFICE_BASE_URL,
|
|
|
|
|
|
onlyofficeStorageHostOverride: process.env.NEXT_PUBLIC_ONLYOFFICE_STORAGE_HOST_OVERRIDE,
|
|
|
|
|
|
onlyofficeProxyOrigin: process.env.NEXT_PUBLIC_ONLYOFFICE_PROXY_ORIGIN,
|
|
|
|
|
|
onlyofficeCallbackOrigin: process.env.NEXT_PUBLIC_ONLYOFFICE_CALLBACK_ORIGIN,
|
|
|
|
|
|
cloudflareAppOrigin: process.env.NEXT_PUBLIC_CLOUDFLARE_APP_ORIGIN,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const readFromPublicJson = (): Partial<MnoteRuntimeConfig> => {
|
|
|
|
|
|
if (typeof window !== "undefined") return {};
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 说明:桌面端与网页端共用 public/mnote-env.json 作为“公共环境文件”。
|
|
|
|
|
|
// 桌面端运行时 process.cwd() 会被 Electron 切到 desktop-next 根目录。
|
|
|
|
|
|
// 网页端运行时 process.cwd() 通常为 wolai-frontend。
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
|
|
|
|
const fs = require("fs") as typeof import("fs");
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
|
|
|
|
const path = require("path") as typeof import("path");
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:Next standalone 产物的 server.js 会执行 `process.chdir(__dirname)`,
|
|
|
|
|
|
// 导致 process.cwd() 变成 `.next/standalone`,此时 public/mnote-env.json 位于上层目录。
|
|
|
|
|
|
// 这里向上查找多级目录,确保能读取到真正的 public/mnote-env.json。
|
|
|
|
|
|
const candidates: string[] = [];
|
|
|
|
|
|
let dir = process.cwd();
|
|
|
|
|
|
for (let i = 0; i < 6; i += 1) {
|
|
|
|
|
|
candidates.push(path.join(dir, "public", "mnote-env.json"));
|
|
|
|
|
|
const next = path.dirname(dir);
|
|
|
|
|
|
if (next === dir) break;
|
|
|
|
|
|
dir = next;
|
|
|
|
|
|
}
|
|
|
|
|
|
const existing = candidates.filter((p) => fs.existsSync(p));
|
|
|
|
|
|
if (existing.length === 0) return {};
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:standalone 产物可能包含 `.next/standalone/public/mnote-env.json`,但它通常是构建时拷贝,
|
|
|
|
|
|
// 用户更希望修改“项目目录下”的 public/mnote-env.json 即可生效。
|
|
|
|
|
|
// 因此这里优先选择不在 `.next` 目录下的配置文件。
|
|
|
|
|
|
const isInNextDir = (p: string) => p.split(path.sep).includes(".next");
|
|
|
|
|
|
const filePath = existing.find((p) => !isInNextDir(p)) ?? existing[0];
|
|
|
|
|
|
|
|
|
|
|
|
const raw = fs.readFileSync(filePath, { encoding: "utf8" });
|
|
|
|
|
|
const parsed = JSON.parse(raw) as unknown;
|
|
|
|
|
|
if (!parsed || typeof parsed !== "object") return {};
|
|
|
|
|
|
return parsed as Partial<MnoteRuntimeConfig>;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeRuntimeConfig = (cfg: MnoteRuntimeConfig): MnoteRuntimeConfig => {
|
|
|
|
|
|
const isDesktop =
|
|
|
|
|
|
cfg.isDesktop ??
|
|
|
|
|
|
(typeof window === "undefined" ? process.env.MNOTE_DESKTOP === "1" : false);
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:`onlyofficeBaseUrl` / `onlyofficeStorageHostOverride` 属于“通用默认值”。
|
|
|
|
|
|
// 但我们在 `public/mnote-env.json` 里同时提供了 Web/Desktop 两套配置,
|
|
|
|
|
|
// 因此这里应当优先选择与平台匹配的字段,避免被环境变量中的默认值覆盖。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 典型场景:开发机环境变量仍是 `http://localhost:8081`,但网页端需要走
|
|
|
|
|
|
// `https://onlyoffice.<域名>`。若不调整优先级,远程浏览器会尝试访问它自己
|
|
|
|
|
|
// 的 localhost,从而导致 docx 打不开。
|
|
|
|
|
|
const onlyofficeBaseUrl = isDesktop
|
|
|
|
|
|
? cfg.onlyofficeBaseUrlDesktop ||
|
|
|
|
|
|
cfg.onlyofficeBaseUrl ||
|
|
|
|
|
|
cfg.onlyofficeBaseUrlWeb ||
|
|
|
|
|
|
""
|
|
|
|
|
|
: cfg.onlyofficeBaseUrlWeb ||
|
|
|
|
|
|
cfg.onlyofficeBaseUrl ||
|
|
|
|
|
|
cfg.onlyofficeBaseUrlDesktop ||
|
|
|
|
|
|
"";
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:这里需要用 `??` 而不是 `||`,允许通过配置显式传入空字符串来“关闭 override”。
|
|
|
|
|
|
// 否则 Web 端配置为 "" 时,会被环境变量里的默认值(例如 host.docker.internal)误覆盖,
|
|
|
|
|
|
// 进而导致 ONLYOFFICE 文档服务器无法访问真实的存储地址。
|
|
|
|
|
|
const onlyofficeStorageHostOverride = isDesktop
|
|
|
|
|
|
? (cfg.onlyofficeStorageHostOverrideDesktop ??
|
|
|
|
|
|
cfg.onlyofficeStorageHostOverride ??
|
|
|
|
|
|
cfg.onlyofficeStorageHostOverrideWeb ??
|
|
|
|
|
|
"")
|
|
|
|
|
|
: (cfg.onlyofficeStorageHostOverrideWeb ??
|
|
|
|
|
|
cfg.onlyofficeStorageHostOverride ??
|
|
|
|
|
|
cfg.onlyofficeStorageHostOverrideDesktop ??
|
|
|
|
|
|
"");
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:`onlyofficeProxyOrigin` 也属于“通用默认值”。在 Web 端需要优先使用
|
|
|
|
|
|
// onlyofficeProxyOriginWeb,避免被旧的通用值(例如 Cloudflare 域名)覆盖,
|
|
|
|
|
|
// 否则会导致 ONLYOFFICE 文档服务器回源到错误的公网入口。
|
|
|
|
|
|
const onlyofficeProxyOrigin = isDesktop
|
|
|
|
|
|
? cfg.onlyofficeProxyOrigin || ""
|
|
|
|
|
|
: cfg.onlyofficeProxyOriginWeb ||
|
|
|
|
|
|
cfg.onlyofficeProxyOrigin ||
|
|
|
|
|
|
"";
|
|
|
|
|
|
|
|
|
|
|
|
// 说明:ONLYOFFICE 回调(保存)必须是“文档服务器可访问”的地址。
|
|
|
|
|
|
// Web 端优先用 onlyofficeCallbackOriginWeb(通常是 http://host.docker.internal:3000)。
|
|
|
|
|
|
const onlyofficeCallbackOrigin = isDesktop
|
|
|
|
|
|
? cfg.onlyofficeCallbackOriginDesktop ||
|
|
|
|
|
|
cfg.onlyofficeCallbackOrigin ||
|
|
|
|
|
|
cfg.onlyofficeCallbackOriginWeb ||
|
|
|
|
|
|
""
|
|
|
|
|
|
: cfg.onlyofficeCallbackOriginWeb ||
|
|
|
|
|
|
cfg.onlyofficeCallbackOrigin ||
|
|
|
|
|
|
cfg.onlyofficeCallbackOriginDesktop ||
|
|
|
|
|
|
"";
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
...cfg,
|
|
|
|
|
|
isDesktop,
|
|
|
|
|
|
onlyofficeBaseUrl,
|
|
|
|
|
|
onlyofficeStorageHostOverride,
|
|
|
|
|
|
onlyofficeProxyOrigin,
|
|
|
|
|
|
onlyofficeCallbackOrigin,
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const getMnoteRuntimeConfig = (): MnoteRuntimeConfig => {
|
|
|
|
|
|
if (typeof window !== "undefined") {
|
|
|
|
|
|
return normalizeRuntimeConfig(window.__MNOTE_RUNTIME_CONFIG__ ?? readFromEnv());
|
|
|
|
|
|
}
|
|
|
|
|
|
const isDesktop = process.env.MNOTE_DESKTOP === "1";
|
|
|
|
|
|
// 说明:桌面端需要优先使用 public/mnote-env.json 来覆盖 build 时注入的 NEXT_PUBLIC_*。
|
|
|
|
|
|
// Web 端开发时则应优先使用环境变量(例如本机 http://127.0.0.1:18000),避免被
|
|
|
|
|
|
// public/mnote-env.json 中的远程/自签地址覆盖导致浏览器登录请求失败。
|
|
|
|
|
|
const merged: MnoteRuntimeConfig = isDesktop
|
|
|
|
|
|
? {
|
|
|
|
|
|
...readFromEnv(),
|
|
|
|
|
|
...readFromPublicJson(),
|
|
|
|
|
|
isDesktop,
|
|
|
|
|
|
}
|
|
|
|
|
|
: {
|
|
|
|
|
|
...readFromPublicJson(),
|
|
|
|
|
|
...readFromEnv(),
|
|
|
|
|
|
isDesktop,
|
|
|
|
|
|
};
|
|
|
|
|
|
return normalizeRuntimeConfig(merged);
|
|
|
|
|
|
};
|