import CryptoJS from "crypto-js"; import { defineStore } from "pinia"; import { navigateTo, useRuntimeConfig } from "#app"; import { computed, ref } from "vue"; var ce = [ [/产品类型错误.{0,30}安全组[^\n]*/g, `该商品未接入安全组`], [/商品未关联.{0,20}模块接口/g, `该商品暂不可用`], [/未关联.{0,12}模块接口/g, `该商品暂不可用`], [/尚未接入.{0,12}模块/g, `该商品暂不可用`], ]; var le = [ [/快快云-弹性云|弹性云/g, ``], [/快快云|快快/g, ``], [/魔方云|魔方财务|智简魔方|智简/g, ``], [/美得云|美德云|meidecloud/gi, ``], [/dwvps[a-z0-9_]*/gi, ``], [/miloce[a-z0-9_]*/gi, ``], [/jinghanyun[a-z0-9_]*/gi, ``], [/kkidc[a-z0-9_]*/gi, ``], [/kky[a-z0-9_]*/gi, ``], [/idcsmart[a-z0-9_]*/gi, ``], [/mf[_-]?cloud[a-z0-9_]*/gi, ``], [/mf[_-]?dcim[a-z0-9_]*/gi, ``], [/common[_-]?cloud[a-z0-9_]*/gi, ``], [/pve[_-]?cloud[a-z0-9_]*/gi, ``], ]; var ue = /模块接口|自定义配置|模块未关联|模块标识|上游|第三方云|[a-z]{2,}[_-][a-z0-9_]+|\bDCIM\b|whmcs|reserver|https?:\/\//i; function W(e) { let t = typeof e == `string` ? e : ``; if (!t) return ``; let n = t; for (let [e, t] of ce) n = n.replace(e, t); for (let [e, t] of le) n = n.replace(e, t); return ( (n = n .replace(/\s{2,}/g, ` `) .replace(/[,,、]{2,}/g, `,`) .replace(/^[\s,,、::;;-]+|[\s,,、::;;-]+$/g, ``) .trim()), n.length < 2 || ue.test(n) ? `操作失败,请稍后重试或联系客服` : n ); } function de(e) { let t = typeof e == `string` ? e : ``; return /^\s*上游熔断解除[::]/u.test(t) ? `服务连接已恢复正常` : /^\s*上游熔断[::]/u.test(t) ? `服务连接暂时异常,请稍后重试` : t; } var fe = 32; var pe = 200; var me = 6e4; var G = new Map(); var he = Symbol(`uninitialized`); var ge = 0; function _e() { (ge++, G.clear()); } function ve() { return ge; } function ye(e, t, n = ``) { return JSON.stringify([e, n, t], (e, t) => t && typeof t == `object` && !Array.isArray(t) ? Object.fromEntries( Object.keys(t) .sort() .map((e) => [e, t[e]]), ) : t, ); } function be(e) { return JSON.parse(JSON.stringify(e)); } function xe(e, t, n = Date.now()) { he !== e && (_e(), (he = e)); for (let [e, t] of G) t.expires <= n && G.delete(e); let r = G.get(t); if (r) return (G.delete(t), G.set(t, r), be(r.data)); } function Se(e, t, n, r, i = Date.now()) { if (e === he && r === ge) { if (!Array.isArray(n.list) || n.list.length > pe) { G.delete(t); return; } try { let e = be(n); (G.delete(t), G.size >= fe && G.delete(G.keys().next().value), G.set(t, { data: e, expires: i + me, })); } catch { G.delete(t); } } } var Ce = 2147483647; function K(e, t) { let n = typeof e == `number` ? e : typeof e == `string` && /^\d+$/.test(e) ? Number(e) : NaN; if (!Number.isSafeInteger(n) || n < t || n > Ce) throw Error(`购买信息无效,请重新确认后重试`); return n; } function we(e) { if (!e || typeof e != `object` || Array.isArray(e)) throw Error(`购买信息无效,请重新确认后重试`); return e; } function Te(e) { let t = we(e); return { product_id: K(t.product_id, 1), qty: K(t.qty, 1), }; } function Ee(e, t, n) { if (e === `POST` && t === `/product/settle`) return { mode: `single`, ...Te(n), }; if (e === `POST` && t === `/cart`) { let e = we(n); if (e.products !== void 0) { if ( !Array.isArray(e.products) || !e.products.length || e.products.length > 1e3 ) throw Error(`购买信息无效,请重新确认后重试`); return { mode: `cart_add`, products: e.products.map(Te), }; } return { mode: `cart_add`, ...Te(e), }; } if (e === `POST` && t === `/cart/settle`) { let e = we(n); if (!Array.isArray(e.positions) || !e.positions.length) throw Error(`请选择要结算的商品`); return { mode: `cart_settle`, positions: [...new Set(e.positions.map((e) => K(e, 0)))], }; } let r = e === `PUT` ? /^\/cart\/(\d+)(\/qty)?$/.exec(t) : null; if (r) { let e = we(n); return { mode: `cart_update`, position: K(r[1], 0), qty: K(e.qty, 1), ...(r[2] ? {} : { product_id: K(e.product_id, 1), }), }; } return null; } function De(e) { if (!e || typeof e != `object` || Array.isArray(e)) return !1; let t = e; return ( typeof t.allowed == `boolean` && typeof t.message == `string` && (t.allowed || !!t.message.trim()) ); } class ApiError extends Error { status; data; constructor(status, message, data) { super(message); this.status = status; this.data = data; this.name = "ApiError"; } } const DEFAULT_REQUEST_TIMEOUT_MS = 30_000; const FILE_TRANSFER_TIMEOUT_MS = 120_000; function requestTimeoutError() { return new ApiError(408, "请求超时,请稍后重试"); } async function withRequestDeadline( operation, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, controller = new AbortController(), ) { const duration = Number.isFinite(timeoutMs) && timeoutMs > 0 && timeoutMs <= 2_147_483_647 ? timeoutMs : DEFAULT_REQUEST_TIMEOUT_MS; let timer; const deadline = new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(requestTimeoutError()); }, duration); }); try { // The deadline also covers response-body decoding and any purchase preflight. return await Promise.race([ Promise.resolve().then(() => operation(controller.signal)), deadline, ]); } finally { clearTimeout(timer); } } function assertRequestActive(signal) { if (signal.aborted) throw requestTimeoutError(); } function normalizeRequestError(error) { return error instanceof ApiError ? error : new ApiError(0, "网络异常,请检查网络后重试"); } function useApi() { const config = useRuntimeConfig(); const auth = useAuth(); function clearUnauthorizedSession(requestToken) { if ( !requestToken || localStorage.getItem("jwt") !== requestToken || auth.token !== requestToken ) return; auth.clear(); if (window.location.pathname === "/login") return; const redirect = window.location.pathname + window.location.search; try { void Promise.resolve( navigateTo({ path: "/login", query: redirect === "/" ? undefined : { redirect }, }), ).catch(() => {}); } catch {} } function responseObject(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; } function responseError(response, payload, requestToken, fallbackMessage) { const envelope = responseObject(payload); const status = response.status === 401 ? 401 : typeof envelope.status === "number" && envelope.status !== 200 ? envelope.status : response.status; if (status === 401) clearUnauthorizedSession(requestToken); return new ApiError( status, W(envelope.msg || envelope.message) || fallbackMessage, envelope.data, ); } async function decodeResponse( response, requestToken, fallbackMessage, signal, allowStatuslessDataArray = false, ) { const payload = await response.json().catch(() => null); assertRequestActive(signal); const envelope = responseObject(payload); if (response.ok && envelope.status === 200) return payload; if ( response.ok && allowStatuslessDataArray && !("status" in envelope) && Array.isArray(envelope.data) ) { return { status: 200, msg: "", data: envelope.data }; } throw responseError(response, payload, requestToken, fallbackMessage); } function apiUrl(path, query, keepEmpty = false) { let url = `${String(config.public.apiBase ?? "")}/console/v1${path}`; const search = new URLSearchParams(); for (const [key, value] of Object.entries(query ?? {})) { if (value == null || (value === "" && !keepEmpty)) continue; if (Array.isArray(value)) { for (const item of value) { if (item != null && item !== "") search.append(`${key}[]`, String(item)); } } else { search.append(key, String(value)); } } if (search.size) url += `?${search}`; return url; } async function request(method, path, options = {}) { // Capture identity before the first await, including when logout clears it immediately. const requestToken = auth.token; const headers = { Accept: "application/json" }; if (requestToken) headers.Authorization = `Bearer ${requestToken}`; if (options.body !== undefined) headers["Content-Type"] = "application/json"; try { const serializedBody = options.body === undefined ? undefined : JSON.stringify(options.body); return await withRequestDeadline(async (signal) => { if (requestToken) { let purchase; try { purchase = Ee(method, path, options.body); } catch (error) { throw new ApiError( 422, error instanceof Error ? error.message : "购买信息无效,请重新确认后重试", ); } if (purchase) { const authToken = auth.token; const permission = await request("POST", "/purchase_limit/check", { body: purchase, timeoutMs: options.timeoutMs ?? 15_000, }); assertRequestActive(signal); if ( localStorage.getItem("jwt") !== requestToken || auth.token !== authToken ) { throw new ApiError(409, "登录状态已变化,请重新确认后重试"); } if (JSON.stringify(options.body) !== serializedBody) { throw new ApiError(409, "购买信息已变化,请重新确认后重试"); } if (!De(permission)) throw new ApiError(502, "限购验证暂不可用,请稍后重试"); if (!permission.allowed) throw new ApiError(422, permission.message); } } assertRequestActive(signal); const response = await fetch( apiUrl(path, options.query, options.keepEmpty), { method, headers, body: serializedBody, signal, }, ); assertRequestActive(signal); const envelope = await decodeResponse( response, requestToken, response.ok ? "响应格式异常,请稍后重试" : `请求失败(HTTP ${response.status})`, signal, options.allowStatuslessDataArray, ); assertRequestActive(signal); if ( method !== "GET" && path !== "/purchase_limit/check" && !/^\/product\/\d+\/config_option$/.test(path) ) { _e(); } return options.raw ? envelope : envelope.data; }, options.timeoutMs); } catch (error) { throw normalizeRequestError(error); } } async function upload(file) { const body = new FormData(); body.append("file", file); const headers = { Accept: "application/json" }; const requestToken = auth.token; if (requestToken) headers.Authorization = `Bearer ${requestToken}`; try { return await withRequestDeadline(async (signal) => { const response = await fetch(apiUrl("/upload"), { method: "POST", headers, body, signal, }); assertRequestActive(signal); const envelope = await decodeResponse( response, requestToken, "上传失败", signal, ); assertRequestActive(signal); return envelope.data; }, FILE_TRANSFER_TIMEOUT_MS); } catch (error) { throw normalizeRequestError(error); } } async function download(path, data, filename = "export.xlsx", options = {}) { const method = options.method ?? "GET"; const headers = { Accept: "*/*" }; const requestToken = auth.token; if (requestToken) headers.Authorization = `Bearer ${requestToken}`; if (method === "POST") headers["Content-Type"] = "application/json"; try { await withRequestDeadline(async (signal) => { const response = await fetch( apiUrl(path, method === "GET" ? data : undefined), { method, headers, ...(method === "POST" ? { body: JSON.stringify(data ?? {}) } : {}), signal, }, ); assertRequestActive(signal); const isJson = /\bapplication\/(?:[\w.-]+\+)?json\b/i.test( response.headers.get("content-type") || "", ); if (!response.ok || isJson) { const payload = await response .clone() .json() .catch(() => null); assertRequestActive(signal); const envelope = responseObject(payload); if ( !response.ok || (typeof envelope.status === "number" && envelope.status !== 200) ) { throw responseError( response, payload, requestToken, `下载失败(HTTP ${response.status})`, ); } } const disposition = response.headers.get("content-disposition") || ""; const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition); if (match?.[1]) { try { filename = decodeURIComponent(match[1]); } catch { filename = match[1]; } } const blob = await response.blob(); assertRequestActive(signal); const objectUrl = URL.createObjectURL(blob); const link = document.createElement("a"); try { link.href = objectUrl; link.download = filename; document.body.appendChild(link); link.click(); } finally { link.remove(); URL.revokeObjectURL(objectUrl); } }, options.timeoutMs ?? FILE_TRANSFER_TIMEOUT_MS); } catch (error) { throw normalizeRequestError(error); } } return { identity: () => auth.token, request, upload, download, get: (path, query, keepEmpty = false, options = {}) => request("GET", path, { ...options, query, keepEmpty, }), getRaw: (path, query, options = {}) => request("GET", path, { ...options, query, raw: true, }), post: (path, body, raw = false, options = {}) => request("POST", path, { ...options, body, raw, }), put: (path, body, query) => request("PUT", path, { body, query }), del: (path, query, body) => request("DELETE", path, { query, body }), }; } var ot = CryptoJS.enc.Utf8.parse(`idcsmart.finance`); var st = CryptoJS.enc.Utf8.parse(`9311019310287172`); function ct(e) { return CryptoJS.AES.encrypt(e, ot, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: st, }).toString(); } function lt(e, t = `登录失败,请稍后重试`) { let n = e && typeof e == `object` ? e : {}, r = typeof n.message == `string` ? n.message.trim() : ``, i = n.data && typeof n.data == `object` ? n.data : {}; return i.need_security_verify || i.ip_exception_verify ? `本次登录需要额外的安全验证,请联系客服协助完成验证。` : n.status === 429 ? `登录尝试过于频繁,请稍后再试` : n.status === 408 || /超时|timeout/i.test(r) ? `登录请求超时,请检查网络后重试` : n.status === 0 || /网络异常|Failed to fetch|NetworkError|Load failed/i.test(r) ? `网络连接失败,请检查网络后重试` : (n.status ?? 0) >= 500 || /页面错误|响应格式异常|Internal Server Error|Bad Gateway|Service Unavailable/i.test( r, ) ? `登录服务暂时异常,请稍后重试;若持续失败,请联系客服。` : !r || /<[^>]+>|SQLSTATE|\b(?:TypeError|ReferenceError|Exception)\b/.test( r, ) ? t : r; } function ut(e) { return ( typeof e == `string` && e.length > 0 && e.length <= 8192 && !/\s/.test(e) ); } function dt(e) { if (!e || typeof e != `object`) return null; let t = e, n = [t, t.data, t.error, t.response]; for (let e of n) { if (!e || typeof e != `object`) continue; let t = e, n = t.data && typeof t.data == `object` ? t.data : t; if ( n.need_security_verify === !0 || n.need_security_verify === 1 || n.need_security_verify === `1` || (Array.isArray(n.available_methods) && n.available_methods.length > 0) ) return n; } return null; } function ft(e) { let t = dt(e); if (t) throw Object.assign(Error(`安全验证尚未完成,请重试`), { data: t, }); } var pt = { operate_password: `操作密码`, email_code: `邮箱验证码`, phone_code: `手机验证码`, certification: `实名认证`, }; function mt(e) { return ( Array.isArray(e.available_methods) ? e.available_methods : [] ).flatMap((t) => { let n = typeof t == `string` ? { value: t, } : t && typeof t == `object` ? t : {}, r = String(n.value ?? ``); return pt[r] ? [ { value: r, label: pt[r], tip: String(n.tip ?? ``), account: String(n.account ?? ``), phone_code: String(n.phone_code ?? ``), security_verify_token: String( n.security_verify_token ?? e.security_verify_token ?? ``, ), }, ] : []; }); } const useAuth = defineStore("auth", () => { const token = ref(localStorage.getItem("jwt")); const overview = ref(null); const loading = ref(false); const legacyLoginPending = ref(false); const isLoggedIn = computed(() => !!token.value); let overviewPromise = null; let sessionVersion = 0; let legacySession = null; function loginWithJwt(jwt) { if (!ut(jwt)) throw Error("登录响应无效,请重新登录;若持续失败,请联系客服。"); resetSessionState(); token.value = jwt; localStorage.setItem("jwt", jwt); } function prepareLegacyLogin(jwt = localStorage.getItem("jwt")) { clear(); legacySession = { jwt: ut(jwt) ? jwt : null, version: sessionVersion, controller: null, promise: null, }; legacyLoginPending.value = true; } function cancelLegacyLogin() { legacySession?.controller?.abort(); legacySession = null; legacyLoginPending.value = false; } function verifyLegacyLogin() { const session = legacySession; if (!session?.jwt) { return Promise.reject( Error("缺少有效登录凭证,请返回原平台重新进入,或使用账号登录。"), ); } if (session.promise) return session.promise; const apiBase = String(useRuntimeConfig().public.apiBase ?? ""); const controller = new AbortController(); session.controller = controller; const isCurrentSession = () => legacySession === session && sessionVersion === session.version; const promise = withRequestDeadline( async (signal) => { let response; try { response = await fetch(`${apiBase}/console/v1/index`, { headers: { Accept: "application/json", Authorization: `Bearer ${session.jwt}`, }, credentials: "omit", cache: "no-store", referrerPolicy: "no-referrer", signal, }); } catch { throw Error("网络连接失败或验证超时,请检查网络后重试。"); } const envelope = await response.json().catch(() => null); assertRequestActive(signal); if (!isCurrentSession()) throw Error("登录状态已变化,请重新进入。"); if (response.status === 401 || envelope?.status === 401) { throw Error("登录凭证已失效,请返回原平台重新进入,或使用账号登录。"); } const account = envelope?.data?.account; if ( !response.ok || envelope?.status !== 200 || !account || typeof account !== "object" || !Number.isSafeInteger(account.id) || account.id <= 0 ) throw Error("暂时无法验证登录身份,请稍后重试。"); loginWithJwt(session.jwt); overview.value = account; }, 15_000, controller, ) .catch((error) => { if (error instanceof ApiError && error.status === 408) { throw Error("网络连接失败或验证超时,请检查网络后重试。"); } throw error; }) .finally(() => { if (isCurrentSession()) { session.controller = null; session.promise = null; } }); session.promise = promise; return promise; } async function login( account, password, captcha = "", captchaToken = "", type = "password", code = "", phoneCode = "86", verification, ) { if (type === "code" && account.includes("@")) throw Error("验证码登录仅支持手机号"); const api = useApi(); const version = sessionVersion; loading.value = true; try { const result = await api.post( "/login", { type, account, ...(account.includes("@") ? {} : { phone_code: phoneCode }), code, password: type === "password" ? ct(password) : "", remember_password: "0", captcha, token: captchaToken, ...(verification || {}), }, false, { timeoutMs: 15_000 }, ); if (version !== sessionVersion) throw Error("登录状态已变化,请重试"); ft(result); if (!ut(result?.jwt)) throw Error("登录响应无效,请重新登录;若持续失败,请联系客服。"); loginWithJwt(result.jwt); } finally { loading.value = false; } } function loadOverview() { if (!overviewPromise) { const version = sessionVersion; const promise = useApi() .get("/index", undefined, false, { timeoutMs: 10_000 }) .then((result) => { if (version !== sessionVersion) throw Error("登录状态已变化,请重试"); overview.value = result.account; return result.account; }) .finally(() => { if (overviewPromise === promise) overviewPromise = null; }); overviewPromise = promise; } return overviewPromise; } async function fetchOverview() { return overview.value ?? loadOverview(); } async function refreshOverview() { return loadOverview(); } function logout() { // request() captures the original Bearer synchronously; remote revocation never owns local logout. const revocation = useApi().post("/logout"); clear(); void revocation.catch(() => {}); } function resetSessionState() { _e(); cancelLegacyLogin(); sessionVersion += 1; overview.value = null; overviewPromise = null; } function clear() { resetSessionState(); token.value = null; localStorage.removeItem("jwt"); } return { token, overview, loading, isLoggedIn, login, loginWithJwt, fetchOverview, refreshOverview, logout, clear, legacyLoginPending, prepareLegacyLogin, verifyLegacyLogin, cancelLegacyLogin, }; }); export { lt as loginErrorMessage, ApiError, ye as apiCacheKey, ve as apiCacheVersion, W as sanitizeMessage, de as operationMessage, mt as securityMethods, useApi, Se as writeApiCache, ft as assertSecurityComplete, ut as isValidJwt, xe as readApiCache, dt as securityChallenge, useAuth, _e as clearApiCache, };