feat: 更新SproutGate前后端代码

This commit is contained in:
2026-04-01 22:04:01 +08:00
parent 90590c7cb0
commit 650e1c7707
49 changed files with 3609 additions and 768 deletions

View File

@@ -12,6 +12,32 @@ export const API_BASE = (() => {
/** `public/logo192.png`,含 Vite `base` 前缀,避免子路径部署时顶栏/开屏裂图 */
export const LOGO_192_SRC = `${import.meta.env.BASE_URL}logo192.png`;
/** 全站随机背景(`GET /api/random?format=json`,见 https://randbg.api.smyhub.com */
export const RAND_BG_API_ORIGIN = "https://randbg.api.smyhub.com";
const SELF_SERVICE_ACCOUNT_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
/** 与自助注册规则一致:随机小写字母与数字(默认长度 10 */
export function randomSelfServiceAccount(length = 10) {
const n = Math.max(3, Math.min(32, Number(length) || 10));
const buf = new Uint8Array(n);
crypto.getRandomValues(buf);
let s = "";
for (let i = 0; i < n; i++) {
s += SELF_SERVICE_ACCOUNT_ALPHABET[buf[i] % SELF_SERVICE_ACCOUNT_ALPHABET.length];
}
return s;
}
/** 输入时规范为自助注册允许的字符(小写、数字),超长截断 */
export function normalizeSelfServiceAccountInput(raw, maxLen = 32) {
const m = Math.max(3, Math.min(32, maxLen));
return String(raw || "")
.toLowerCase()
.replace(/[^a-z0-9]/g, "")
.slice(0, m);
}
/** 浏览器侧 IP/地理(与后端写入「最后访问」字段配合使用) */
export const CLIENT_GEO_LOOKUP_URL = "https://cf-ip-geo.smyhub.com/api";
@@ -130,15 +156,35 @@ export function getPublicAccountFromPathname(pathname) {
export function getAuthFlowFromSearch(search) {
const params = new URLSearchParams(search);
const redirectUri = (params.get("redirect_uri") || params.get("return_url") || "").trim();
const scopeRaw = (params.get("scope") || "").trim();
const scopes = scopeRaw
? scopeRaw.split(/[\s+]+/).map((s) => s.trim()).filter(Boolean)
: [];
return {
redirectUri,
state: (params.get("state") || "").trim(),
prompt: (params.get("prompt") || "").trim(),
clientId: (params.get("client_id") || "").trim(),
clientName: (params.get("client_name") || "").trim()
clientName: (params.get("client_name") || "").trim(),
scopes,
scopeRaw
};
}
/** 授权页展示用:回跳地址可读标签(主机 + 路径) */
export function formatOAuthRedirectLabel(redirectUri) {
if (!redirectUri || typeof redirectUri !== "string") return "—";
const t = redirectUri.trim();
if (!t) return "—";
try {
const u = new URL(t, typeof window !== "undefined" ? window.location.href : "https://example.com");
const path = u.pathname === "/" ? "" : u.pathname;
return `${u.host}${path}` || u.host;
} catch {
return t.length > 64 ? `${t.slice(0, 61)}` : t;
}
}
const AUTH_CLIENT_ID_KEY = "sproutgate_auth_client_id";
const AUTH_CLIENT_NAME_KEY = "sproutgate_auth_client_name";
@@ -185,3 +231,15 @@ export function buildAuthCallbackUrl(redirectUri, payload) {
url.hash = hashParams.toString();
return url.toString();
}
/** 用户拒绝授权:回跳到应用地址,在 URL hash 中携带 OAuth 风格 error与成功回跳一致使用 fragment */
export function buildAuthDenyCallbackUrl(redirectUri, state = "") {
if (!redirectUri || typeof redirectUri !== "string") return "";
const url = new URL(redirectUri, window.location.href);
const hashParams = new URLSearchParams();
hashParams.set("error", "access_denied");
hashParams.set("error_description", "resource_owner_denied");
if (state) hashParams.set("state", String(state));
url.hash = hashParams.toString();
return url.toString();
}