|
@@ -47,39 +47,73 @@ function resolveApiBaseUrlValue(raw: string | undefined): string {
|
|
|
return s;
|
|
return s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** 本地 / 局域网访问时保留当前 origin(含端口),避免误跳到 :80/:443 上无服务。 */
|
|
|
|
|
+function shouldPreserveCurrentOriginPort(hostname: string): boolean {
|
|
|
|
|
+ if (
|
|
|
|
|
+ hostname === "localhost" ||
|
|
|
|
|
+ hostname === "127.0.0.1" ||
|
|
|
|
|
+ hostname === "[::1]"
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ const parts = hostname.split(".");
|
|
|
|
|
+ if (
|
|
|
|
|
+ parts.length === 4 &&
|
|
|
|
|
+ parts.every((p) => /^\d+$/.test(p)) &&
|
|
|
|
|
+ parts[0] !== undefined &&
|
|
|
|
|
+ parts[1] !== undefined
|
|
|
|
|
+ ) {
|
|
|
|
|
+ const a = Number(parts[0]);
|
|
|
|
|
+ const b = Number(parts[1]);
|
|
|
|
|
+ if (a === 10) return true;
|
|
|
|
|
+ if (a === 172 && b >= 16 && b <= 31) return true;
|
|
|
|
|
+ if (a === 192 && b === 168) return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 登录页跳转地址(仅浏览器)。
|
|
* 登录页跳转地址(仅浏览器)。
|
|
|
- * 相对路径 `/auth/login` 会保留当前 URL 的端口;若用户通过 `jinclab.com:4000` 访问而
|
|
|
|
|
- * `NEXT_PUBLIC_SITE_URL` 为无端口的公网地址,则用后者 origin,避免地址栏出现 `:4000`。
|
|
|
|
|
- * 本地开发未配置 SITE_URL 时仍用相对路径。
|
|
|
|
|
|
|
+ * - `window.location.assign("/auth/login")` 会保留 `:4000` 等当前端口。
|
|
|
|
|
+ * - 若已配置 `NEXT_PUBLIC_SITE_URL` 且当前 host 与站点一致,优先用该 canonical origin。
|
|
|
|
|
+ * - 否则对公网域名用「当前协议 + 主机名(不含端口)」拼路径,这样即使构建时未打入
|
|
|
|
|
+ * SITE_URL,从 `jinclab.com:4000` 也会跳到 `https://jinclab.com/auth/login`(由中间件补 `/zh`)。
|
|
|
|
|
+ * - localhost / 局域网 IP 仍用相对路径,保留端口。
|
|
|
*/
|
|
*/
|
|
|
export function resolveAuthLoginHref(): string {
|
|
export function resolveAuthLoginHref(): string {
|
|
|
const path = "/auth/login";
|
|
const path = "/auth/login";
|
|
|
if (typeof window === "undefined") return path;
|
|
if (typeof window === "undefined") return path;
|
|
|
|
|
|
|
|
|
|
+ const { protocol, hostname } = window.location;
|
|
|
|
|
+
|
|
|
const site = process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "");
|
|
const site = process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "");
|
|
|
- if (!site) return path;
|
|
|
|
|
|
|
+ if (site) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const siteUrl = new URL(site);
|
|
|
|
|
+ const canonical = siteUrl.hostname;
|
|
|
|
|
+ const allowedHosts = new Set([canonical, `www.${canonical}`]);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const current = new URL(window.location.href);
|
|
|
|
|
+ if (allowedHosts.has(current.hostname)) {
|
|
|
|
|
+ return `${siteUrl.origin}${path}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* fall through */
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* invalid NEXT_PUBLIC_SITE_URL */
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- let siteUrl: URL;
|
|
|
|
|
- try {
|
|
|
|
|
- siteUrl = new URL(site);
|
|
|
|
|
- } catch {
|
|
|
|
|
|
|
+ if (shouldPreserveCurrentOriginPort(hostname)) {
|
|
|
return path;
|
|
return path;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const canonical = siteUrl.hostname;
|
|
|
|
|
- const allowedHosts = new Set([canonical, `www.${canonical}`]);
|
|
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- const current = new URL(window.location.href);
|
|
|
|
|
- if (allowedHosts.has(current.hostname)) {
|
|
|
|
|
- return `${siteUrl.origin}${path}`;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return new URL(path, `${protocol}//${hostname}/`).href;
|
|
|
} catch {
|
|
} catch {
|
|
|
return path;
|
|
return path;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return path;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export function resolveAppEnv(): AppEnv {
|
|
export function resolveAppEnv(): AppEnv {
|