Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,454 @@
|
||||
/**
|
||||
* 认证相关前端封装:注册 / 登录 / 游客登录走本站 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);
|
||||
Reference in New Issue
Block a user