Files
web_huangguo_web/app/static/js/common/auth.js
T
fish03andClaude Opus 5.5 bef9f8b4ed Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 14:43:16 +07:00

455 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 认证相关前端封装:注册 / 登录 / 游客登录走本站 BFF,token 等写入 cookie。
* 未注册用户自动游客登录(POST /api/auth/guest → 上游 /api/app/auth/guest)。
*/
(function (global) {
var UUID_KEY = "uuid";
var DEV_ID_KEY = "guest_dev_id";
var CHANNEL_KEY = "channel";
var GUEST_LOGOUT_FLAG_KEY = "guest_logout_manual";
var guestLoginPromise = null;
var volatileDevID = "";
/** 上游约定:token 失效需重新登录 */
var TOKEN_EXPIRED_CODE = 1000;
var tokenRecoveryPromise = null;
function toast(message, type) {
if (typeof global.showToast === "function") {
global.showToast(message, type || "info");
return;
}
}
function getInviteCode() {
try {
var params = new URLSearchParams(global.location.search || "");
return (
params.get("inviteCode") ||
params.get("invite") ||
params.get("code") ||
""
).trim();
} catch (e) {
return "";
}
}
function hasSessionToken() {
return !!(global.CookieUtil && global.CookieUtil.getToken && global.CookieUtil.getToken());
}
// 与 91porn-h5-client generateUid 保持一致:16 位十六进制随机串 + 毫秒时间戳。
function generateUUID() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var uuid = [];
for (var i = 0; i < 16; i++) {
uuid[i] = chars.charAt(Math.floor(Math.random() * 16));
}
return uuid.join("") + Date.now();
}
function syncDevID(devID) {
if (!devID) return;
try {
localStorage.setItem(UUID_KEY, devID);
// 保留旧 key,兼容已上线页面和服务端游客 cookie。
localStorage.setItem(DEV_ID_KEY, devID);
} catch (e) {}
try {
if (global.CookieUtil && typeof global.CookieUtil.set === "function") {
global.CookieUtil.set(DEV_ID_KEY, devID, { days: 365 });
}
} catch (e2) {}
}
function getOrCreateDevID() {
var devID = "";
try {
devID = (localStorage.getItem(UUID_KEY) || "").trim();
} catch (e) {}
if (devID) {
syncDevID(devID);
return devID;
}
// 首屏 SSR 可能已生成同规则 UUID 并写入 cookie,优先复用以保持游客身份一致。
var fromCookie =
global.CookieUtil && typeof global.CookieUtil.get === "function"
? global.CookieUtil.get(DEV_ID_KEY)
: "";
if (fromCookie) {
syncDevID(fromCookie);
return fromCookie;
}
if (!volatileDevID) {
volatileDevID = generateUUID();
}
syncDevID(volatileDevID);
return volatileDevID;
}
function getChannel() {
var channel = "";
try {
channel = (localStorage.getItem(CHANNEL_KEY) || "").trim();
if (channel) {
if (global.CookieUtil && typeof global.CookieUtil.set === "function") {
global.CookieUtil.set(CHANNEL_KEY, channel, { days: 365 });
}
return channel;
}
} catch (e) {}
try {
var params = new URLSearchParams(global.location.search || "");
channel = (params.get("dc") || params.get("ch") || params.get("pc") || "").trim();
if (channel) {
localStorage.setItem(CHANNEL_KEY, channel);
if (global.CookieUtil && typeof global.CookieUtil.set === "function") {
global.CookieUtil.set(CHANNEL_KEY, channel, { days: 365 });
}
}
} catch (e2) {}
return channel;
}
function clearGuestLogoutFlag() {
try {
sessionStorage.removeItem(GUEST_LOGOUT_FLAG_KEY);
} catch (e) {}
try {
if (global.CookieUtil && typeof global.CookieUtil.remove === "function") {
global.CookieUtil.remove("guest_skip_auto");
}
} catch (e2) {}
}
function markGuestLogout() {
try {
sessionStorage.setItem(GUEST_LOGOUT_FLAG_KEY, "1");
} catch (e) {}
try {
if (global.CookieUtil && typeof global.CookieUtil.set === "function") {
// 不设 days → session cookie,配合 SSR 跳过本次自动游客
global.CookieUtil.set("guest_skip_auto", "1");
}
} catch (e2) {}
}
function isGuestLogoutMarked() {
try {
if (sessionStorage.getItem(GUEST_LOGOUT_FLAG_KEY) === "1") return true;
} catch (e) {}
try {
if (
global.CookieUtil &&
typeof global.CookieUtil.get === "function" &&
global.CookieUtil.get("guest_skip_auto") === "1"
) {
return true;
}
} catch (e2) {}
return false;
}
function syncTrackerUid(user) {
if (!user || !global.tracker || typeof global.tracker.setUid !== "function") return;
var uid = user.userId != null ? user.userId : user.uid != null ? user.uid : user.id;
if (uid == null || String(uid) === "") return;
global.tracker.setUid(String(uid));
}
function applyAuthSession(res, failTip) {
if (!res || res.code !== 200 || !res.data) {
var err = new Error((res && (res.tip || res.msg)) || failTip || "请求失败");
err.code = res && res.code;
err.data = res;
throw err;
}
if (global.CookieUtil && typeof global.CookieUtil.setAuthSession === "function") {
global.CookieUtil.setAuthSession(res.data);
}
clearGuestLogoutFlag();
syncTrackerUid(res.data);
try {
document.dispatchEvent(
new CustomEvent("auth:login", { detail: { user: res.data } })
);
} catch (e) {}
return res;
}
function register(payload) {
var channel = getChannel();
var body = {
accountNo: (payload.accountNo || "").trim(),
password: payload.password || "",
devID: getOrCreateDevID(),
channel: channel,
};
var invite = (payload.agentInviteCode || getInviteCode() || "").trim();
if (invite) body.agentInviteCode = invite;
// 游客升级注册:携带当前游客 token,上游按 Authorization 绑定原游客账号。
var options = {};
var token =
global.CookieUtil && typeof global.CookieUtil.getToken === "function"
? global.CookieUtil.getToken()
: "";
var registered =
global.CookieUtil && typeof global.CookieUtil.isRegistered === "function"
? global.CookieUtil.isRegistered()
: false;
if (token && !registered) {
options.headers = { Authorization: token };
}
return global.HttpRequest.post("/api/auth/register", body, options).then(function (res) {
return applyAuthSession(res, "注册失败");
});
}
function login(payload) {
var body = {
account: (payload.account || "").trim(),
password: payload.password || "",
devID: getOrCreateDevID(),
channel: getChannel(),
};
return global.HttpRequest.post("/api/auth/login", body).then(function (res) {
return applyAuthSession(res, "登录失败");
});
}
function resetPassword(payload) {
return global.HttpRequest.post("/api/auth/resetPassword", {
accountNo: (payload.accountNo || "").trim(),
email: (payload.email || "").trim(),
newPassword: payload.newPassword || "",
});
}
function guestLogin() {
var devId = getOrCreateDevID();
return global.HttpRequest.post("/api/auth/guest", {
devID: devId,
deviceId: devId,
channel: getChannel(),
mac: "",
}).then(function (res) {
return applyAuthSession(res, "游客登录失败");
});
}
function isAuthEndpoint(url) {
return /\/api\/auth\/(guest|login|register|resetPassword)(\?|$)/.test(String(url || ""));
}
function isSafeRedirect(path) {
return typeof path === "string" && path.charAt(0) === "/" && path.charAt(1) !== "/";
}
/** 注册账号:打开登录弹窗;无弹窗时跳首页登录入口 */
function promptRegisteredRelogin() {
toast("登录已过期,请重新登录", "warning");
var redirect = "";
try {
redirect = (global.location.pathname || "/") + (global.location.search || "");
} catch (e2) {
redirect = "/";
}
var url = "/login/";
if (isSafeRedirect(redirect) && redirect !== "/" && redirect.indexOf("/?auth=") !== 0) {
url = "/login/return" + (global.location.pathname || "/");
}
if (typeof global.openLoginDialog === "function") {
try {
var opened = global.openLoginDialog();
// dialogLoader 桩函数返回 Promise:加载失败时再走跳转兜底
if (opened && typeof opened.then === "function") {
opened.catch(function () {
global.location.href = url;
});
return;
}
return;
} catch (e) {}
}
global.location.href = url;
}
/**
* 接口返回 code=1000(token 失效)时的统一处理。
* - 游客:清会话后走游客登录,成功后可重试原请求
* - 注册账号:清会话并打开登录弹窗 / 跳转登录页
* @returns {Promise<{ok:boolean, guest?:boolean, needLogin?:boolean, res?:any}>}
*/
function handleTokenExpired() {
if (tokenRecoveryPromise) {
return tokenRecoveryPromise;
}
var wasRegistered =
global.CookieUtil && typeof global.CookieUtil.isRegistered === "function"
? global.CookieUtil.isRegistered()
: false;
if (global.CookieUtil && typeof global.CookieUtil.clearAuth === "function") {
global.CookieUtil.clearAuth();
}
try {
document.dispatchEvent(
new CustomEvent("auth:logout", { detail: { reason: "token-expired" } })
);
} catch (e) {}
if (wasRegistered) {
var registeredJob = Promise.resolve().then(function () {
promptRegisteredRelogin();
return { ok: false, needLogin: true };
});
tokenRecoveryPromise = registeredJob;
registeredJob.finally(function () {
setTimeout(function () {
if (tokenRecoveryPromise === registeredJob) {
tokenRecoveryPromise = null;
}
}, 1200);
});
return registeredJob;
}
clearGuestLogoutFlag();
var guestJob = guestLogin()
.then(function (res) {
return { ok: true, guest: true, res: res };
})
.catch(function (err) {
toast("会话已失效,请刷新页面重试", "error");
throw err;
})
.finally(function () {
if (tokenRecoveryPromise === guestJob) {
tokenRecoveryPromise = null;
}
});
tokenRecoveryPromise = guestJob;
return guestJob;
}
/**
* 无 token(未自己登录)时自动游客登录。
* SSR 也会在首屏补游客;此处作客户端兜底。
* 手动退出后跳过本次;清掉标记后,下次进页再自动游客。
* BFF: POST /api/auth/guest → 上游 POST /api/app/auth/guest
*/
function ensureGuestSession() {
if (hasSessionToken()) {
return Promise.resolve({ ok: true, skipped: true });
}
if (isGuestLogoutMarked()) {
// 本次因手动退出跳过;清标记,下次进页恢复自动游客
clearGuestLogoutFlag();
return Promise.resolve({ ok: true, skipped: true, reason: "manual-logout" });
}
if (!global.HttpRequest) {
return Promise.resolve({ ok: false, skipped: true, reason: "no-http" });
}
if (!guestLoginPromise) {
guestLoginPromise = guestLogin()
.then(function (res) {
return { ok: true, res: res };
})
.catch(function (err) {
return { ok: false, error: err };
})
.finally(function () {
guestLoginPromise = null;
});
}
return guestLoginPromise;
}
function uploadImage(file) {
var fd = new FormData();
fd.append("file", file);
return global.HttpRequest.post("/api/upload/image", fd).then(function (res) {
if (!res || res.code !== 200 || !res.data || !res.data.url) {
var err = new Error((res && (res.tip || res.msg)) || "上传失败");
err.code = res && res.code;
err.data = res;
throw err;
}
return res.data;
});
}
function updateProfile(payload) {
var body = {};
if (payload.nickName != null && String(payload.nickName).trim() !== "") {
body.nickName = String(payload.nickName).trim();
}
if (payload.introduction != null) {
body.introduction = String(payload.introduction).trim();
}
if (payload.avatar) body.avatar = payload.avatar;
if (payload.oldPassword) body.oldPassword = payload.oldPassword;
if (payload.newPassword) body.newPassword = payload.newPassword;
if (typeof payload.privacyMode === "boolean") body.privacyMode = payload.privacyMode;
return global.HttpRequest.post("/api/auth/profile", body).then(function (res) {
if (!res || res.code !== 200) {
var err = new Error((res && (res.tip || res.msg)) || "保存失败");
err.code = res && res.code;
err.data = res;
throw err;
}
if (res.data && global.CookieUtil && typeof global.CookieUtil.setAuthSession === "function") {
global.CookieUtil.setAuthSession(res.data);
}
if (res.data) syncTrackerUid(res.data);
return res;
});
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || "").trim());
}
/** 登录凭证:账号 4–16 位,或合法邮箱 */
function isValidLoginAccount(account) {
var v = String(account || "").trim();
if (!v) return false;
if (v.indexOf("@") >= 0) return isValidEmail(v);
return v.length >= 4 && v.length <= 16;
}
global.AuthAPI = {
TOKEN_EXPIRED_CODE: TOKEN_EXPIRED_CODE,
register: register,
login: login,
resetPassword: resetPassword,
guestLogin: guestLogin,
ensureGuestSession: ensureGuestSession,
handleTokenExpired: handleTokenExpired,
isAuthEndpoint: isAuthEndpoint,
markGuestLogout: markGuestLogout,
hasSessionToken: hasSessionToken,
uploadImage: uploadImage,
updateProfile: updateProfile,
getInviteCode: getInviteCode,
getOrCreateDevID: getOrCreateDevID,
getChannel: getChannel,
isValidEmail: isValidEmail,
isValidLoginAccount: isValidLoginAccount,
toast: toast,
};
// 每次页面初始化都准备好持久设备 UUID,并捕获首次渠道。
getOrCreateDevID();
getChannel();
// 首屏无 token → 自动游客登录
ensureGuestSession();
})(typeof window !== "undefined" ? window : this);