// Create axios instance import { APP_GAME_APK, APP_LOGIN, APP_LOG, BASE_API } from "./config"; import axios from "axios"; import { getHttpMsg, getUrlParam, isDebugEnv, localStorageGet, localStorageSet } from "@utils/helpers"; import { LOCAL_ACTIVITY_C, LOCAL_IS_LOGIN, LOCAL_SIGN, LOCAL_WHEEL_INVITE_CODE } from "@constants/localkey"; import { toast } from "react-toastify"; import LZString from "lz-string"; import { isStandalone, isTWA } from "@utils/UAUtils"; import { normalizeClientError, reportClientLog, sanitizeUrlForLog } from "@utils/clientLog"; const SENSITIVE_LOG_KEY = /(pass(word)?|sign|token|secret|authorization|cookie|phone|email|cpf|cashapp|paypal)/i; const stringifyForDebug = (value) => { try { return JSON.stringify(value, (key, item) => SENSITIVE_LOG_KEY.test(key) ? "[REDACTED]" : item); } catch (e) { return "[unserializable]"; } }; const getSign = () => { const sign = localStorageGet(LOCAL_SIGN, ""); return sign; } const getC = () => { let c = localStorageGet(LOCAL_ACTIVITY_C, ""); //容错为空时候再检测下url if (!c){ let ccode = getUrlParam("c"); if (ccode != null) { localStorageSet(LOCAL_ACTIVITY_C, ccode); c=ccode; } } return c; } const getComParams = () => { let params = {}; if (global.BFP != null) params.bfp = global.BFP; if (global.FF != null) params.ff = global.FF; if(window.themeConfig)params.regionid=window.themeConfig.product; else params.regionid = window.location.hostname.split('.')[window.location.hostname.startsWith("www")?1:0]; let sign = getSign(); if (!!sign) params.sign = sign; let c = getC(); if (!!c) params.c = c; params._d = global.device; params._t = Date.now() + "_" + global.width + "_" + global.height; if (isStandalone() || isTWA()) { params.pwa = 1; } let act = localStorageGet(LOCAL_WHEEL_INVITE_CODE); if (act != null && act !== "") { params.act = act; } return params; } const checkResponse = (data) => { if (data) { //登录失效 if (data.code === 404) { let msg = getHttpMsg(data); toast.error(msg, { onClose: () => { window.location.replace(window.location.origin) }, toastId: "checkResponse" }); } else if (data.code === 777) { let action = { type: "main/updateOpenPay", payload: 1, } setTimeout(() => { if (global.dispatch) global.dispatch(action); }, 1000) } } } const isLogRequest = (url = "") => { return String(url).includes(APP_LOG); } const service = axios.create({ baseURL: BASE_API, withCredentials: true, timeout: 10000, // Request timeout retry: 2,//重复5次 retryDelay: 1000, compress: !isDebugEnv() }); //出错重试 retry const checkRetryRequest = (error) => { let config = error.config; if (!config || !config.retry) return Promise.reject(error); config.__retryCount = config.__retryCount || 0; if (config.__retryCount >= config.retry) { // Reject with the error return Promise.reject(error); } config.__retryCount += 1; // Create new promise to handle exponential backoff let backoff = new Promise(function (resolve) { setTimeout(function () { resolve(); }, config.retryDelay || 1); }); // Return the promise in which recalls axios to retry the request return backoff.then(function () { return service(config); }); } // Request intercepter service.interceptors.request.use( config => { if (config.compress) { let data = config.ori_data; if (data == null) data = config.data; if (data) { let compressData = LZString.compressToBase64(JSON.stringify(data)); config.data = { lz: compressData } config.ori_data = data; } let params = config.ori_params; if (params == null) params = config.params; if (params) { let compressParams = LZString.compressToBase64(JSON.stringify(params)); config.params = { lz: compressParams } config.ori_params = params; } } return config; }, error => { // Do something with request error return Promise.reject(error); } ); // response pre-processing service.interceptors.response.use( response => { let data = response.data; if (typeof data === "string") { const decompressed = LZString.decompressFromBase64(data); const candidates = [decompressed, data]; for (const candidate of candidates) { if (typeof candidate !== "string" || candidate === "") continue; try { response.data = JSON.parse(candidate); data = response.data; break; } catch (error) { // Try the next representation; preserve the original string if neither parses. } } } if (data && typeof data === "object" && typeof data.code === "string") { const code = data.code.trim(); if (/^-?\d+$/.test(code)) data.code = Number(code); } if (isDebugEnv()) { const responseUrl = response.request?.responseURL || response.config?.url || ""; console.debug(sanitizeUrlForLog(responseUrl), stringifyForDebug(response.data)); } const requestUrl = response.config && response.config.url; if (!isLogRequest(requestUrl) && data && data.code != null && data.code !== 200) { reportClientLog("api_response_error", { url: requestUrl, method_type: response.config && response.config.method, code: data.code, msg: data.msg || data.message, response: data }, { throttleKey: `api_response_error:${requestUrl}:${data.code}:${data.msg || data.message || ""}`, throttleMs: 5000 }); } checkResponse(response.data); return response.data; }, error => { const config = error.config || {}; const retry = config.retry || 0; const retryCount = config.__retryCount || 0; const willRetry = retry && retryCount < retry; if (!willRetry && !isLogRequest(config.url)) { reportClientLog("api_request_error", { url: config.url, method_type: config.method, status: error.response && error.response.status, message: error.message, error: normalizeClientError(error), response: error.response && error.response.data }, { throttleKey: `api_request_error:${config.url}:${error.message || ""}`, throttleMs: 5000 }); } return checkRetryRequest(error); } ); let _guestLoginPromise = null; const ensureSign = async (requestUrl) => { if (getSign()) return; const isLogin = localStorageGet(LOCAL_IS_LOGIN, "") === "1" || !!global.user; if (!isLogin) return; const isLoginUrl = requestUrl && (requestUrl.includes("/login") || requestUrl.includes("/register") || requestUrl.includes("/routes")); if (isLoginUrl) return; if (!_guestLoginPromise) { _guestLoginPromise = (async () => { try { const comParams = getComParams(); const loginData = { type: "guest", ...comParams }; const resp = await service({ method: "post", url: APP_LOGIN, data: loginData }); if (resp && resp.code === 200 && resp.data) { const sign = resp.data.sign; if (sign) { localStorageSet(LOCAL_SIGN, sign); global.user = resp.data; } } } catch (e) { console.error("ensureSign guest login error", e); } finally { _guestLoginPromise = null; } })(); } await _guestLoginPromise; }; const get = async (params = {}) => { await ensureSign(params.url); const requestParams = { ...params, params: { ...(params.params || {}), ...getComParams() } }; return service({ method: 'get', ...requestParams }); } const post = async (params = {}) => { await ensureSign(params.url); const requestParams = { ...params, data: { ...(params.data || {}), ...getComParams() } }; if (isDebugEnv()) console.debug(`post params: ${stringifyForDebug(requestParams.data)} url:${params.url}`); return service({ method: 'post', ...requestParams }); } const put = async (params = {}) => { await ensureSign(params.url); const requestParams = { ...params, data: { ...(params.data || {}), ...getComParams() } }; return service({ method: 'put', ...requestParams }); } const adelete = async (params = {}) => { await ensureSign(params.url); const requestParams = { ...params, data: { ...(params.data || {}), ...getComParams() } }; return service({ method: 'delete', ...requestParams }); } const buildQuery = (param) => { if (param == null || typeof param === "string") return param; const query = new URLSearchParams(); for (const key in param) { let value = param[key]; if (value != null && typeof value === "object") value = JSON.stringify(value); query.append(key, value == null ? "" : String(value)); } return query.toString(); } const getAPKUrl = (apkUrl = null) => { let params = getComParams(); let url = apkUrl == null ? BASE_API + APP_GAME_APK : apkUrl; const separator = url.includes("?") ? "&" : "?"; if (!isDebugEnv()) { url += separator + "lz=" + encodeURIComponent(LZString.compressToBase64(JSON.stringify(params))); } else { url += separator + buildQuery(params); } return url; } /** 解析 quickSave 生成的 path:/s_k_c 或 /s_k_c/xxx => { s_k, c },用于 routes 恢复环境与 basename */ export const parseQuickSavePath = () => { const pathname = window.location.pathname || ""; const m = pathname.match(/^\/([a-zA-Z0-9]+)_(\d+)(?:\/.*)?$/); return m ? { s_k: m[1], c: m[2] } : null; }; /** 当前 pathname(去掉 s_k_c 前缀),用于与 "/"、ROUTE_PATH 等比较时与首页一视同仁 */ export const getLogicalPathname = () => { const pathname = window.location.pathname || ""; const q = parseQuickSavePath(); if (!q) return pathname; const prefix = `/${q.s_k}_${q.c}`; if (pathname === prefix || pathname === prefix + "/") return "/"; if (pathname.startsWith(prefix + "/")) return pathname.slice(prefix.length) || "/"; return pathname; }; const Http = { get, post, put, delete: adelete, getAPKUrl } export default Http;