/** * 认证相关前端封装:注册 / 登录 / 游客登录走本站 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; } // 调试兜底:无 toast 组件时用简易提示 try { var tip = String(message || "").trim(); if (!tip) return; var el = document.getElementById("auth-debug-toast"); if (!el) { el = document.createElement("div"); el.id = "auth-debug-toast"; el.setAttribute("role", "status"); el.style.cssText = "position:fixed;left:50%;bottom:48px;transform:translateX(-50%);z-index:99999;" + "max-width:86vw;padding:10px 16px;border-radius:8px;font-size:14px;line-height:1.4;" + "color:#fff;background:rgba(20,20,20,.88);box-shadow:0 8px 24px rgba(0,0,0,.25);"; document.body.appendChild(el); } el.textContent = tip; el.style.background = type === "error" ? "rgba(180,40,40,.92)" : type === "success" ? "rgba(30,120,70,.92)" : "rgba(20,20,20,.88)"; el.style.display = "block"; clearTimeout(el._hideTimer); el._hideTimer = setTimeout(function () { el.style.display = "none"; }, 2600); } catch (e) {} } 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() { if ( global.ChannelAttribution && typeof global.ChannelAttribution.get === "function" ) { return global.ChannelAttribution.get(); } 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 { channel = global.CookieUtil && typeof global.CookieUtil.get === "function" ? String(global.CookieUtil.get(CHANNEL_KEY) || "").trim() : ""; if (channel) { try { localStorage.setItem(CHANNEL_KEY, channel); } catch (e2) {} return channel; } } catch (e3) {} 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 (e4) {} 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) !== "/"; } /** PC 打开登录弹窗;H5 跳转登录页 */ function promptRegisteredRelogin() { toast("登录已过期,请重新登录", "warning"); var isMobile = global.matchMedia && global.matchMedia("(max-width: 768px)").matches; if (!isMobile && typeof global.openLoginPop === "function") { try { global.openLoginPop(); return; } catch (e) {} } if (typeof global.openLoginDialog === "function") { try { var opened = global.openLoginDialog(); if (opened && typeof opened.then === "function") { opened.catch(function () { global.location.href = "/login"; }); return; } return; } catch (e2) {} } global.location.href = "/login"; } /** * 接口返回 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; } function logout() { markGuestLogout(); if (global.CookieUtil && typeof global.CookieUtil.clearAuth === "function") { global.CookieUtil.clearAuth(); } try { document.dispatchEvent(new CustomEvent("auth:logout", { detail: { reason: "manual" } })); } catch (e) {} if (!global.HttpRequest) { return Promise.resolve({ code: 200 }); } return global.HttpRequest.post("/api/auth/logout", {}).catch(function () { return { code: 200 }; }); } global.AuthAPI = { TOKEN_EXPIRED_CODE: TOKEN_EXPIRED_CODE, register: register, login: login, logout: logout, 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);