/** * 前端请求封装。 * - 登录态走 cookie(credentials: include) * - 浏览器无法改写 User-Agent;X-Real-IP 由服务端反代上游时注入 * - 直连上游时若需自定义头,请走本站 Go 代理接口 */ (function (global) { var DEFAULT_TIMEOUT_MS = 30000; function buildQuery(params) { if (!params) return ""; var pairs = []; Object.keys(params).forEach(function (key) { var value = params[key]; if (value == null) return; pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(value))); }); return pairs.length ? "?" + pairs.join("&") : ""; } /** * @param {string} url * @param {{ * method?: string, * data?: any, * params?: Record, * headers?: Record, * timeout?: number, * }} [options] */ function request(url, options) { options = options || {}; var method = String(options.method || "GET").toUpperCase(); var headers = Object.assign( { Accept: "application/json" }, options.headers || {} ); var finalUrl = url + buildQuery(options.params); var body; var controller = typeof AbortController !== "undefined" ? new AbortController() : null; var timer; if (options.data != null && method !== "GET" && method !== "HEAD") { if (typeof FormData !== "undefined" && options.data instanceof FormData) { body = options.data; } else { headers["Content-Type"] = headers["Content-Type"] || "application/json"; body = headers["Content-Type"].indexOf("application/json") >= 0 ? JSON.stringify(options.data) : options.data; } } var fetchOpts = { method: method, headers: headers, credentials: "include", signal: controller ? controller.signal : undefined, }; if (body != null) fetchOpts.body = body; var timeoutMs = typeof options.timeout === "number" ? options.timeout : DEFAULT_TIMEOUT_MS; if (controller && timeoutMs > 0) { timer = setTimeout(function () { controller.abort(); }, timeoutMs); } return fetch(finalUrl, fetchOpts) .then(function (res) { var contentType = res.headers.get("content-type") || ""; var parse = contentType.indexOf("application/json") >= 0 ? res.json() : res.text(); return parse.then(function (data) { if (!res.ok) { var err = new Error( (data && data.msg) || (data && data.message) || res.statusText || "request failed" ); err.status = res.status; err.code = data && data.code; err.data = data; throw err; } return data; }); }) .then(function (data) { return handleTokenExpiredResponse(url, options, data); }) .finally(function () { if (timer) clearTimeout(timer); }); } /** * 业务码 1000:token 失效。 * 游客 → 重新游客登录并重试一次;注册账号 → 打开登录弹窗/跳转登录页。 */ function handleTokenExpiredResponse(url, options, data) { options = options || {}; var expiredCode = (global.AuthAPI && global.AuthAPI.TOKEN_EXPIRED_CODE) || 1000; if (!data || Number(data.code) !== Number(expiredCode)) { return data; } if (options._skipTokenRecovery || options._tokenRetried) { return data; } if ( global.AuthAPI && typeof global.AuthAPI.isAuthEndpoint === "function" && global.AuthAPI.isAuthEndpoint(url) ) { return data; } if (!global.AuthAPI || typeof global.AuthAPI.handleTokenExpired !== "function") { return data; } return global.AuthAPI.handleTokenExpired().then(function (result) { if (result && result.ok && result.guest) { return request( url, Object.assign({}, options, { _tokenRetried: true }) ); } return data; }); } global.HttpRequest = { request: request, get: function (url, options) { return request(url, Object.assign({}, options, { method: "GET" })); }, post: function (url, data, options) { return request(url, Object.assign({}, options, { method: "POST", data: data })); }, }; })(typeof window !== "undefined" ? window : this);