import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' import { getScreenPublicKey, screenAutoLogin } from '@/api/login' import { getToken, setToken } from '@/utils/auth' /** URL 密文登录失败时展示给用户的提示 */ export const SCREEN_LOGIN_CIPHER_ERROR = '公钥不对' /** URL uuid 免密登录失败时展示给用户的提示 */ export const SCREEN_LOGIN_UUID_ERROR = '免密失败,请联系管理员获取免登ID' /** 同一会话内仅尝试一次免密登录,避免反复请求 */ let autoLoginState = null let autoLoginPromise = null let autoLoginUuid = null export function isScreenAutoLoginEnabled() { return import.meta.env.VITE_SCREEN_AUTO_LOGIN === 'true' } export function resetScreenAutoLoginState() { autoLoginState = null autoLoginPromise = null autoLoginUuid = null } /** 从路由 query 读取 RSA 密文(?token=xxxx) */ export function getUrlScreenLoginCipher(query) { const raw = query?.token if (typeof raw !== 'string' || !raw.trim()) { return '' } try { return decodeURIComponent(raw.trim()) } catch { return raw.trim() } } /** 从路由 query 读取免登 UUID(?uuid=xxxx) */ export function getUrlScreenLoginUuid(query) { const raw = query?.uuid if (typeof raw !== 'string' || !raw.trim()) { return '' } try { return decodeURIComponent(raw.trim()) } catch { return raw.trim() } } /** 去掉 URL 中的 token,避免密文残留在地址栏 */ export function stripScreenLoginTokenQuery(query = {}) { const next = { ...query } delete next.token return next } /** 去掉 URL 中的 uuid */ export function stripScreenLoginUuidQuery(query = {}) { const next = { ...query } delete next.uuid return next } /** * 使用 URL 传入的 RSA 密文换取 JWT * @param {string} cipher * @returns {Promise} JWT */ export async function tryScreenLoginWithCipher(cipher) { if (!cipher) { throw new Error(SCREEN_LOGIN_CIPHER_ERROR) } try { const loginRes = await screenAutoLogin(cipher) if (!loginRes?.token) { throw new Error(SCREEN_LOGIN_CIPHER_ERROR) } setToken(loginRes.token) autoLoginState = 'ok' return loginRes.token } catch { throw new Error(SCREEN_LOGIN_CIPHER_ERROR) } } /** * 获取公钥 → RSA 加密 URL 中的 UUID → 换取 JWT * @param {string} uuid * @returns {Promise} JWT */ export async function tryScreenAutoLogin(uuid) { if (!uuid) { throw new Error(SCREEN_LOGIN_UUID_ERROR) } try { const keyRes = await getScreenPublicKey() const publicKey = keyRes.data?.publicKey if (!publicKey) { throw new Error(SCREEN_LOGIN_UUID_ERROR) } const encryptor = new JSEncrypt() encryptor.setPublicKey(publicKey) const cipher = encryptor.encrypt(uuid) if (!cipher) { throw new Error(SCREEN_LOGIN_UUID_ERROR) } const loginRes = await screenAutoLogin(cipher) if (!loginRes?.token) { throw new Error(SCREEN_LOGIN_UUID_ERROR) } setToken(loginRes.token) return loginRes.token } catch { throw new Error(SCREEN_LOGIN_UUID_ERROR) } } /** * 路由守卫用:URL 含 uuid 时尝试免密登录 * @returns {Promise<{ attempted: boolean, ok: boolean }>} */ export async function ensureScreenAutoLogin(query) { if (getToken()) { return { attempted: false, ok: true } } if (!isScreenAutoLoginEnabled()) { return { attempted: false, ok: false } } const uuid = getUrlScreenLoginUuid(query) if (!uuid) { return { attempted: false, ok: false } } if (autoLoginState === 'fail' && autoLoginUuid === uuid) { return { attempted: true, ok: false } } if (autoLoginState === 'ok' && getToken()) { return { attempted: false, ok: true } } if (autoLoginState === 'pending' && autoLoginPromise && autoLoginUuid === uuid) { const ok = await autoLoginPromise return { attempted: true, ok } } autoLoginUuid = uuid autoLoginState = 'pending' autoLoginPromise = tryScreenAutoLogin(uuid) .then(() => { autoLoginState = 'ok' return true }) .catch(() => { autoLoginState = 'fail' return false }) const ok = await autoLoginPromise return { attempted: true, ok } }