var test_api = 'https://api-test.global-interaction.com/'; var local_api = 'http://127.0.0.1:13000/'; var default_api = 'https://api.global-interaction.com/'; (function () { var API_ENV_KEY = 'hyapp_h5_env'; var ACCESS_TOKEN_KEY = 'hyapp_access_token'; var APP_CODE_KEY = 'hyapp_app_code'; var RENEWED_ACCESS_TOKEN_HEADER = 'X-Hyapp-Access-Token'; var memoryEnv = ''; var memoryAccessToken = ''; var memoryAppCode = ''; function readQuery(name) { return new URLSearchParams(window.location.search).get(name); } function normalizeToken(value) { var token = String(value || '').trim(); if (!token) return ''; if (/^Bearer\s+/i.test(token)) token = token.replace(/^Bearer\s+/i, ''); var match = token.match( /[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/ ); return match ? match[0] : token; } function decodeBase64URLJSON(value) { try { var normalized = String(value || '') .replace(/-/g, '+') .replace(/_/g, '/'); while (normalized.length % 4) normalized += '='; return JSON.parse(window.atob(normalized)); } catch (_) { return null; } } function isExpiredToken(token) { var parts = String(token || '').split('.'); if (parts.length < 2) return false; var payload = decodeBase64URLJSON(parts[1]); if (!payload || !payload.exp) return false; // JWT exp 是秒级时间戳;本地 H5 不能只凭 query 有 token 就进入 token 模式,过期 token 会被 gateway 当成未登录。 // 这里预留 5 秒偏移,避免页面发请求时刚好跨过过期点,导致 H5 隐藏账号输入但服务端收到空 display_user_id。 return Number(payload.exp) * 1000 <= Date.now() + 5000; } function normalizeBaseURL(value) { return String(value || '').replace(/\/+$/, '') + '/'; } function isLocalDevOrigin() { var host = window.location.hostname; return host === '127.0.0.1' || host === 'localhost' || host === '::1'; } function shouldPersist() { return !isLocalDevOrigin(); } function clearLocalDevCache() { if (shouldPersist()) return; window.localStorage.removeItem(API_ENV_KEY); window.localStorage.removeItem(ACCESS_TOKEN_KEY); window.localStorage.removeItem(APP_CODE_KEY); } function devProxyBase(path) { return normalizeBaseURL(window.location.origin + path); } function resolveEnv() { var env = readQuery('env'); if (env === 'test' || env === 'local') { memoryEnv = env; if (shouldPersist()) window.localStorage.setItem(API_ENV_KEY, env); return env; } if (memoryEnv) return memoryEnv; if (!shouldPersist()) return ''; return window.localStorage.getItem(API_ENV_KEY) || ''; } function resolveBaseURL() { var env = resolveEnv(); if (env === 'test') return isLocalDevOrigin() ? devProxyBase('/__api_test__/') : normalizeBaseURL(test_api); if (env === 'local') return normalizeBaseURL(local_api); return normalizeBaseURL(default_api); } function buildURL(path, query) { var url = new URL(path.replace(/^\/+/, ''), resolveBaseURL()); if (query) { Object.keys(query).forEach(function (key) { var value = query[key]; if (value === undefined || value === null || value === '') return; url.searchParams.set(key, value); }); } return url.toString(); } function isSameOriginURL(url) { try { return ( new URL(url, window.location.href).origin === window.location.origin ); } catch (_) { return false; } } function getAccessToken() { if (memoryAccessToken && !isExpiredToken(memoryAccessToken)) return memoryAccessToken; if (memoryAccessToken && isExpiredToken(memoryAccessToken)) { memoryAccessToken = ''; if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY); } var queryToken = normalizeToken(readQuery('token')) || normalizeToken(readQuery('access_token')) || normalizeToken(readQuery('accessToken')); if (queryToken) { if (isExpiredToken(queryToken)) { if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY); return ''; } memoryAccessToken = queryToken; if (shouldPersist()) window.localStorage.setItem(ACCESS_TOKEN_KEY, queryToken); return queryToken; } if (!shouldPersist()) return ''; var storedToken = normalizeToken( window.localStorage.getItem(ACCESS_TOKEN_KEY) || '' ); if (storedToken && isExpiredToken(storedToken)) { window.localStorage.removeItem(ACCESS_TOKEN_KEY); return ''; } return storedToken; } function getAppCode() { var queryAppCode = readQuery('app_code') || readQuery('appCode'); if (queryAppCode) { memoryAppCode = queryAppCode; if (shouldPersist()) window.localStorage.setItem(APP_CODE_KEY, queryAppCode); return queryAppCode; } if (memoryAppCode) return memoryAppCode; if (!shouldPersist()) return 'lalu'; return window.localStorage.getItem(APP_CODE_KEY) || 'lalu'; } function setAccessToken(token) { token = normalizeToken(token); if (token && isExpiredToken(token)) token = ''; memoryAccessToken = token || ''; if (token) { if (shouldPersist()) window.localStorage.setItem(ACCESS_TOKEN_KEY, token); return; } if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY); } function setAppCode(appCode) { memoryAppCode = appCode || ''; if (appCode) { if (shouldPersist()) window.localStorage.setItem(APP_CODE_KEY, appCode); return; } if (shouldPersist()) window.localStorage.removeItem(APP_CODE_KEY); } function errorMessageForResponse(response, payload) { if (response.status === 409) return 'Duplicate request.'; return payload.message || response.statusText || 'request_failed'; } function notifyHTTPError(response, message) { if (response.status !== 409 || !message) return; if (window.HyAppToast && window.HyAppToast.show) { window.HyAppToast.show(message); return; } window.dispatchEvent( new CustomEvent('hyapp:toast', { detail: { message: message }, }) ); } function parseResponse(response) { return response.text().then(function (text) { var payload = text ? JSON.parse(text) : {}; if (!response.ok) { var message = errorMessageForResponse(response, payload); notifyHTTPError(response, message); var httpError = new Error(message); httpError.status = response.status; httpError.code = payload.code; httpError.request_id = payload.request_id; httpError.payload = payload; throw httpError; } var code = payload && payload.code; var isErrorCode = (typeof code === 'number' && code !== 0) || (typeof code === 'string' && code !== '' && code !== 'OK'); if (isErrorCode) { var apiError = new Error( payload.message || 'api_request_failed' ); apiError.code = payload.code; apiError.request_id = payload.request_id; apiError.payload = payload; throw apiError; } return payload && Object.prototype.hasOwnProperty.call(payload, 'data') ? payload.data : payload; }); } function persistRenewedAccessToken(response) { var renewedToken = response && response.headers ? response.headers.get(RENEWED_ACCESS_TOKEN_HEADER) : ''; if (renewedToken) setAccessToken(renewedToken); } function request(path, options) { var opts = options || {}; var headers = Object.assign({}, opts.headers || {}); var token = getAccessToken(); var appCode = getAppCode(); var url = buildURL(path, opts.query); if (token) headers.Authorization = 'Bearer ' + token; if (appCode) headers['X-App-Code'] = appCode; if (!shouldPersist() && isSameOriginURL(url)) headers['Cache-Control'] = 'no-cache'; var body = opts.body; if (body && typeof body === 'object' && !(body instanceof FormData)) { headers['Content-Type'] = headers['Content-Type'] || 'application/json'; body = JSON.stringify(body); } return window .fetch(url, { method: opts.method || 'GET', headers: headers, body: body, cache: shouldPersist() ? 'default' : 'no-store', credentials: opts.credentials || 'omit', }) .then(function (response) { persistRenewedAccessToken(response); return parseResponse(response); }); } var vipAPI = { me: function () { return request('/api/v1/vip/me', { method: 'GET' }); }, packages: function () { return request('/api/v1/vip/packages', { method: 'GET' }); }, purchase: function (payload) { return request('/api/v1/vip/purchase', { method: 'POST', body: payload || {}, }); }, }; var userAPI = { me: function () { return request('/api/v1/users/me', { method: 'GET' }); }, overview: function () { return request('/api/v1/users/me/overview', { method: 'GET' }); }, appearance: function () { return request('/api/v1/users/me/appearance', { method: 'GET' }); }, hostIdentity: function () { return request('/api/v1/users/me/host-identity', { method: 'GET', }); }, roleSummary: function () { return request('/api/v1/users/me/role-summary', { method: 'GET', }); }, resolveDisplayUserID: function (displayUserID) { return request( '/api/v1/users/by-display-user-id/' + encodeURIComponent(displayUserID || ''), { method: 'GET', } ); }, }; var walletAPI = { balances: function (assetTypes) { var value = Array.isArray(assetTypes) ? assetTypes.join(',') : assetTypes; return request('/api/v1/wallet/me/balances', { method: 'GET', query: { asset_type: value }, }); }, transactions: function (params) { return request('/api/v1/wallet/transactions', { method: 'GET', query: params || {}, }); }, transferCoinFromSeller: function (payload) { return request('/api/v1/wallet/coin-seller/transfer', { method: 'POST', body: payload || {}, }); }, }; // H5 充值 API 只做路径和参数名封装;token、app_code、env、错误 envelope 仍由 request 统一处理。 // 金额、金币数、普通/币商钱包、MiFaPay 参数和 USDT 链上校验都以后端订单快照为准。 var rechargeAPI = { context: function (displayUserID) { return request('/api/v1/recharge/h5/context', { method: 'GET', query: { display_user_id: displayUserID || '' }, }); }, options: function (displayUserID) { return request('/api/v1/recharge/h5/options', { method: 'GET', query: { display_user_id: displayUserID || '' }, }); }, createOrder: function (payload) { return request('/api/v1/recharge/h5/orders', { method: 'POST', body: payload || {}, }); }, submitTx: function (orderID, payload) { return request( '/api/v1/recharge/h5/orders/' + encodeURIComponent(orderID || '') + '/tx', { method: 'POST', body: payload || {}, } ); }, getOrder: function (orderID, displayUserID) { return request( '/api/v1/recharge/h5/orders/' + encodeURIComponent(orderID || ''), { method: 'GET', query: { display_user_id: displayUserID || '' }, } ); }, }; var salaryWalletAPI = { overview: function (identity) { return request('/api/v1/salary-wallet/overview', { method: 'GET', query: { identity: identity }, }); }, searchCoinSeller: function (displayUserID, identity) { return request('/api/v1/salary-wallet/coin-sellers/search', { method: 'GET', query: { display_user_id: displayUserID, identity: identity, }, }); }, exchange: function (payload) { return request('/api/v1/salary-wallet/exchange', { method: 'POST', body: payload || {}, }); }, transferToCoinSeller: function (payload) { return request('/api/v1/salary-wallet/transfer-to-coin-seller', { method: 'POST', body: payload || {}, }); }, }; var hostAPI = { searchAgencies: function (shortId, pageSize) { return request('/api/v1/host/agencies/search', { method: 'GET', query: { short_id: shortId, page_size: pageSize || 20 }, }); }, applyToAgency: function (payload) { return request('/api/v1/host/agency-applications', { method: 'POST', body: payload || {}, }); }, }; var agencyCenterAPI = { overview: function () { return request('/api/v1/agency-center/overview', { method: 'GET', }); }, platformPolicy: function () { return request('/api/v1/agency-center/platform-policy', { method: 'GET', }); }, hosts: function () { return request('/api/v1/agency-center/hosts', { method: 'GET', }); }, applications: function (status) { return request('/api/v1/agency-center/applications', { method: 'GET', query: { status: status || 'pending' }, }); }, reviewApplication: function (applicationId, payload) { return request( '/api/v1/agency-center/applications/' + encodeURIComponent(applicationId || '') + '/review', { method: 'POST', body: payload || {}, } ); }, removeHost: function (hostUserId, payload) { return request( '/api/v1/agency-center/hosts/' + encodeURIComponent(hostUserId || '') + '/remove', { method: 'POST', body: payload || {}, } ); }, inviteHost: function (payload) { return request('/api/v1/agency-center/invitations/host', { method: 'POST', body: payload || {}, }); }, }; var hostCenterAPI = { agency: function () { return request('/api/v1/host-center/agency', { method: 'GET', }); }, platformPolicy: function () { return request('/api/v1/host-center/platform-policy', { method: 'GET', }); }, }; var bdLeaderCenterAPI = { overview: function () { return request('/api/v1/bd-leader-center/overview', { method: 'GET', }); }, bds: function (pageSize) { return request('/api/v1/bd-leader-center/bds', { method: 'GET', query: { page_size: pageSize || 20 }, }); }, agencies: function (pageSize) { return request('/api/v1/bd-leader-center/agencies', { method: 'GET', query: { page_size: pageSize || 20 }, }); }, inviteBD: function (payload) { return request('/api/v1/bd-leader/invitations/bd', { method: 'POST', body: payload || {}, }); }, inviteAgency: function (payload) { return request('/api/v1/bd-leader/invitations/agency', { method: 'POST', body: payload || {}, }); }, }; var bdCenterAPI = { overview: function () { return request('/api/v1/bd-center/overview', { method: 'GET', }); }, agencies: function (pageSize) { return request('/api/v1/bd-center/agencies', { method: 'GET', query: { page_size: pageSize || 20 }, }); }, inviteAgency: function (payload) { return request('/api/v1/bd/invitations/agency', { method: 'POST', body: payload || {}, }); }, }; var roleInvitationAPI = { list: function (status, pageSize) { return request('/api/v1/role-invitations', { method: 'GET', query: { status: status || 'pending', page_size: pageSize || 50 }, }); }, process: function (invitationID, payload) { return request( '/api/v1/role-invitations/' + encodeURIComponent(invitationID || '') + '/process', { method: 'POST', body: payload || {}, } ); }, }; var managerCenterAPI = { overview: function () { return request('/api/v1/manager-center/overview', { method: 'GET', }); }, searchUsers: function (keyword, pageSize) { return request('/api/v1/manager-center/users/search', { method: 'GET', query: { keyword: keyword, page_size: pageSize || 20, }, }); }, listResources: function (resourceType, pageSize) { return request('/api/v1/manager-center/resource-grants/resources', { method: 'GET', query: { resource_type: resourceType, page_size: pageSize || 20, }, }); }, grantResource: function (payload) { return request('/api/v1/manager-center/resource-grants', { method: 'POST', body: payload || {}, }); }, grantVip: function (payload) { return request('/api/v1/manager-center/vip-grants', { method: 'POST', body: payload || {}, }); }, listBlocks: function (status, pageSize) { return request('/api/v1/manager-center/blocks', { method: 'GET', query: { status: status || 'active', page_size: pageSize || 50, }, }); }, blockUser: function (payload) { return request('/api/v1/manager-center/blocks', { method: 'POST', body: payload || {}, }); }, unblockUser: function (blockId, payload) { return request( '/api/v1/manager-center/blocks/' + encodeURIComponent(blockId || '') + '/unblock', { method: 'POST', body: payload || {}, } ); }, updateLevel: function (payload) { return request('/api/v1/manager-center/levels', { method: 'POST', body: payload || {}, }); }, }; var superadminCenterAPI = bdLeaderCenterAPI; var levelAPI = { overview: function () { return request('/api/v1/levels/me/overview', { method: 'GET' }); }, track: function (track) { return request( '/api/v1/levels/tracks/' + encodeURIComponent(track || ''), { method: 'GET', } ); }, rewards: function (query) { return request('/api/v1/levels/rewards', { method: 'GET', query: query || {}, }); }, }; // 任务页 API 只封装 H5 需要的两个 App 合约;登录态、app_code、env 和错误 envelope 仍由 request 统一处理。 // 领取奖励的 command_id 必须来自点击动作本身,页面脚本负责生成并在同一次重试中复用,避免重复点击造成多次发奖。 var taskAPI = { tabs: function () { return request('/api/v1/tasks/tabs', { method: 'GET' }); }, claim: function (payload) { return request('/api/v1/tasks/claim', { method: 'POST', body: payload || {}, }); }, }; var weeklyStarAPI = { current: function () { return request('/api/v1/activities/weekly-star/current', { method: 'GET', }); }, leaderboard: function (params) { return request('/api/v1/activities/weekly-star/leaderboard', { method: 'GET', query: params || {}, }); }, history: function (limit) { return request('/api/v1/activities/weekly-star/history', { method: 'GET', query: { limit: limit || 10 }, }); }, }; // 邀请活动 H5 只关心当前用户本月状态和领取动作;有效人数、累计充值和最高档差额都以后端聚合事实为准。 // 领取时页面必须传入本次点击生成的 command_id,服务端按用户、周期、档位和 command_id 做幂等发奖。 var inviteActivityRewardAPI = { status: function () { return request('/api/v1/activities/invite-reward', { method: 'GET', }); }, leaderboard: function (params) { return request('/api/v1/activities/invite-reward/leaderboard', { method: 'GET', query: params || {}, }); }, claim: function (payload) { return request('/api/v1/activities/invite-reward/claim', { method: 'POST', body: payload || {}, }); }, }; var diceGameAPI = { config: function (gameID) { return request('/api/v1/games/dice/config', { method: 'GET', query: { game_id: gameID || 'dice' }, }); }, match: function (payload) { return request('/api/v1/games/dice/match', { method: 'POST', body: payload || {}, }); }, getMatch: function (matchID) { return request( '/api/v1/games/dice/matches/' + encodeURIComponent(matchID || ''), { method: 'GET' } ); }, roll: function (matchID) { return request( '/api/v1/games/dice/matches/' + encodeURIComponent(matchID || '') + '/roll', { method: 'POST', body: {} } ); }, cancel: function (matchID) { return request( '/api/v1/games/dice/matches/' + encodeURIComponent(matchID || '') + '/cancel', { method: 'POST', body: {} } ); }, }; var rpsGameAPI = { config: function (gameID) { return request('/api/v1/games/rps/config', { method: 'GET', query: { game_id: gameID || 'rock' }, }); }, match: function (payload) { return request('/api/v1/games/rps/match', { method: 'POST', body: payload || {}, }); }, create: function (payload) { return request('/api/v1/games/rps/matches', { method: 'POST', body: payload || {}, }); }, join: function (matchID, payload) { return request( '/api/v1/games/rps/matches/' + encodeURIComponent(matchID || '') + '/join', { method: 'POST', body: payload || {} } ); }, getMatch: function (matchID) { return request( '/api/v1/games/rps/matches/' + encodeURIComponent(matchID || ''), { method: 'GET' } ); }, roll: function (matchID, payload) { return request( '/api/v1/games/rps/matches/' + encodeURIComponent(matchID || '') + '/roll', { method: 'POST', body: payload || {} } ); }, cancel: function (matchID) { return request( '/api/v1/games/rps/matches/' + encodeURIComponent(matchID || '') + '/cancel', { method: 'POST', body: {} } ); }, }; function analyticsEventID(prefix) { var random = ''; if (window.crypto && window.crypto.getRandomValues) { var bytes = new Uint32Array(2); window.crypto.getRandomValues(bytes); random = bytes[0].toString(36) + bytes[1].toString(36); } else { random = Math.random().toString(36).slice(2); } return ( String(prefix || 'self_game') + '_' + Date.now().toString(36) + '_' + random ); } function reportSelfGameEvent(payload) { var body = Object.assign({}, payload || {}); body.event_id = body.event_id || analyticsEventID(body.event_name); body.occurred_at_ms = body.occurred_at_ms || Date.now(); return request('/api/v1/games/self/report-events', { method: 'POST', body: body, }); } var gameAPI = { dice: diceGameAPI, rps: rpsGameAPI, reportEvent: reportSelfGameEvent, }; window.HyAppAPI = { baseURL: resolveBaseURL, buildURL: buildURL, getAccessToken: getAccessToken, getAppCode: getAppCode, setAccessToken: setAccessToken, setAppCode: setAppCode, request: request, get: function (path, query) { return request(path, { method: 'GET', query: query }); }, post: function (path, body, query) { return request(path, { method: 'POST', body: body, query: query }); }, put: function (path, body, query) { return request(path, { method: 'PUT', body: body, query: query }); }, patch: function (path, body, query) { return request(path, { method: 'PATCH', body: body, query: query }); }, delete: function (path, query) { return request(path, { method: 'DELETE', query: query }); }, vip: vipAPI, user: userAPI, wallet: walletAPI, recharge: rechargeAPI, salaryWallet: salaryWalletAPI, host: hostAPI, hostCenter: hostCenterAPI, agencyCenter: agencyCenterAPI, bdCenter: bdCenterAPI, bdLeaderCenter: bdLeaderCenterAPI, roleInvitations: roleInvitationAPI, managerCenter: managerCenterAPI, superadminCenter: superadminCenterAPI, level: levelAPI, task: taskAPI, weeklyStar: weeklyStarAPI, inviteActivityReward: inviteActivityRewardAPI, game: gameAPI, }; clearLocalDevCache(); })();