|
@@ -5,7 +5,7 @@ import axios, {
|
|
|
} from "axios";
|
|
} from "axios";
|
|
|
|
|
|
|
|
import { clearUser } from "@/lib/auth-types";
|
|
import { clearUser } from "@/lib/auth-types";
|
|
|
-import { getApiBaseUrl, resolveAuthLoginHref } from "@/lib/env";
|
|
|
|
|
|
|
+import { getApiBaseUrl } from "@/lib/env";
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 业务后端接口约定:
|
|
* 业务后端接口约定:
|
|
@@ -72,12 +72,71 @@ export class ApiError extends Error {
|
|
|
|
|
|
|
|
let authTimeoutDialogShown = false;
|
|
let authTimeoutDialogShown = false;
|
|
|
|
|
|
|
|
|
|
+/** 局域网 IP(无 NEXT_PUBLIC_SITE_URL 时仍用相对路径,避免误跳到 :80)。 */
|
|
|
|
|
+function isPrivateLanIpv4Hostname(hostname: string): boolean {
|
|
|
|
|
+ const parts = hostname.split(".");
|
|
|
|
|
+ if (
|
|
|
|
|
+ parts.length !== 4 ||
|
|
|
|
|
+ !parts.every((p) => /^\d+$/.test(p)) ||
|
|
|
|
|
+ parts[0] === undefined ||
|
|
|
|
|
+ parts[1] === undefined
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ 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;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 会话失效后整页跳转登录(必须用绝对 URL,禁止相对 `/auth/login`):
|
|
|
|
|
+ * 相对路径会保留当前 origin 的 `:4000`,国际化再补 `/zh` 就会变成 `host:4000/zh/...`。
|
|
|
|
|
+ * - 配置了 `NEXT_PUBLIC_SITE_URL` 时一律用它(不再做 hostname 白名单,避免 www/裸域/直连端口不一致时误回退相对路径)。
|
|
|
|
|
+ * - 本机回环、内网 IP 且无 SITE_URL:仍用相对路径。
|
|
|
|
|
+ * - 其它:用 `协议//主机名`(不含端口)为基址拼 `/auth/login`。
|
|
|
|
|
+ */
|
|
|
|
|
+function loginHrefAfterSessionExpired(): string {
|
|
|
|
|
+ const path = "/auth/login";
|
|
|
|
|
+ if (typeof window === "undefined") return path;
|
|
|
|
|
+
|
|
|
|
|
+ const { hostname, protocol } = window.location;
|
|
|
|
|
+ if (
|
|
|
|
|
+ hostname === "localhost" ||
|
|
|
|
|
+ hostname === "127.0.0.1" ||
|
|
|
|
|
+ hostname === "[::1]"
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const site = process.env.NEXT_PUBLIC_SITE_URL?.trim().replace(/\/$/, "");
|
|
|
|
|
+ if (site) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return new URL(path, `${site}/`).href;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* fall through */
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isPrivateLanIpv4Hostname(hostname)) {
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ return new URL(path, `${protocol}//${hostname}/`).href;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function notifySessionTimeoutAndRedirect() {
|
|
function notifySessionTimeoutAndRedirect() {
|
|
|
if (typeof window === "undefined") return;
|
|
if (typeof window === "undefined") return;
|
|
|
if (authTimeoutDialogShown) return;
|
|
if (authTimeoutDialogShown) return;
|
|
|
authTimeoutDialogShown = true;
|
|
authTimeoutDialogShown = true;
|
|
|
window.alert("登录超时,请重新登录");
|
|
window.alert("登录超时,请重新登录");
|
|
|
- window.location.assign(resolveAuthLoginHref());
|
|
|
|
|
|
|
+ window.location.replace(loginHrefAfterSessionExpired());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
function isPlainObject(v: unknown): v is Record<string, unknown> {
|