(function () { 'use strict'; var PROD_API_BASE = 'https://jvapi.haiyihy.com'; var TEST_API_BASE = 'https://jvapi-test.haiyihy.com'; var LOCAL_API_BASE = 'http://127.0.0.1:13000'; var APP_CODE = 'yumi'; var RELATION_TYPE_BY_TAB = { cp: 'CP', brother: 'BROTHER', sister: 'SISTERS', }; var headers = { authorization: '', 'req-app-intel': 'version=1.0.0;build=1;model=H5;sysVersion=web;channel=Web', 'req-client': 'H5', 'req-imei': 'H5-STATIC', 'req-lang': 'en', 'req-sys-origin': 'origin=YUMI;originChild=YUMI', 'req-version': 'V2', 'req-zone': 'Asia/Shanghai', }; var readyPromise = null; function currentParams() { var params = new URLSearchParams(window.location.search || ''); var hash = window.location.hash || ''; if (hash.indexOf('?') >= 0) { new URLSearchParams(hash.split('?').slice(1).join('?')).forEach( function (value, key) { if (!params.has(key)) params.set(key, value); } ); } return params; } function query(name) { return currentParams().get(name); } function normalizeBaseURL(value) { return String(value || '').replace(/\/+$/, ''); } function resolveEnv() { var env = String(query('env') || '') .trim() .toLowerCase(); return env === 'test' || env === 'local' || env === 'dev' ? env : ''; } function baseURL() { var config = window.YumiCommonConfig || {}; var override = query('apiBase') || query('api_base') || query('yumi_api_base') || window.YUMI_API_BASE_URL || config.apiBase; if (override) return normalizeBaseURL(override); var env = resolveEnv(); if (env === 'local') return normalizeBaseURL(config.localApi || LOCAL_API_BASE); if (env === 'test' || env === 'dev') return normalizeBaseURL(config.testApi || TEST_API_BASE); return normalizeBaseURL(config.prodApi || PROD_API_BASE); } function buildURL(path, queryData) { var url = new URL( String(path || '').replace(/^\/+/, ''), baseURL() + '/' ); Object.keys(queryData || {}).forEach(function (key) { var value = queryData[key]; if (value === undefined || value === null || value === '') return; url.searchParams.set(key, value); }); return url.toString(); } function normalizeAuthorization(value) { var text = String(value || '').trim(); if (!text) return ''; return /^Bearer\s+/i.test(text) ? text : 'Bearer ' + text; } function tokenForHyAppAPI(value) { return String(value || '').replace(/^Bearer\s+/i, ''); } function splitHeaderPairs(value) { var result = {}; String(value || '') .split(';') .forEach(function (item) { var index = item.indexOf('='); if (index > -1) result[item.slice(0, index)] = item.slice(index + 1); }); return result; } function putHeader(target, key, value) { if ( value === undefined || value === null || value === '' || target[key] ) return; target[key] = String(value); } function normalizeHeaderCandidate(value, depth) { depth = depth || 0; if (depth > 4 || value === null || value === undefined) return {}; if (typeof value === 'string') { var trimmed = value.trim(); if (!trimmed) return {}; if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') { try { return normalizeHeaderCandidate( JSON.parse(trimmed), depth + 1 ); } catch (_) { return {}; } } return trimmed ? { authorization: trimmed } : {}; } if (Array.isArray(value)) { return value.reduce(function (result, item) { return Object.assign( result, normalizeHeaderCandidate(item, depth + 1) ); }, {}); } if (typeof value !== 'object') return {}; var appIntel = splitHeaderPairs( value['Req-App-Intel'] || value['req-app-intel'] ); var sysOrigin = splitHeaderPairs( value['Req-Sys-Origin'] || value['req-sys-origin'] ); var normalized = {}; putHeader( normalized, 'authorization', value.Authorization || value.authorization || value.token || value.accessToken ); putHeader( normalized, 'reqLang', value['Req-Lang'] || value['req-lang'] || value.reqLang || value.lang ); putHeader( normalized, 'appVersion', appIntel.version || value.appVersion || value['App-Version'] ); putHeader( normalized, 'buildVersion', appIntel.build || value.buildVersion ); putHeader(normalized, 'model', appIntel.model || value.model); putHeader(normalized, 'channel', appIntel.channel || value.channel); putHeader( normalized, 'reqImei', value['Req-Imei'] || value['req-imei'] || value.reqImei ); putHeader( normalized, 'origin', sysOrigin.origin || value.sysOrigin || value.origin ); putHeader( normalized, 'child', sysOrigin.originChild || sysOrigin.child || value.sysOriginChild || value.child ); ['headerInfo', 'headers', 'auth', 'data', 'state'].forEach( function (key) { Object.assign( normalized, normalizeHeaderCandidate(value[key], depth + 1) ); } ); return normalized; } function setHeadersFromApp(rawOrObject) { var data = normalizeHeaderCandidate(rawOrObject || {}); var next = {}; if (data.authorization) next.authorization = normalizeAuthorization(data.authorization); if (data.reqLang) next['req-lang'] = data.reqLang; if ( data.appVersion || data.buildVersion || data.model || data.channel ) { next['req-app-intel'] = [ data.appVersion ? 'version=' + data.appVersion : '', data.buildVersion ? 'build=' + data.buildVersion : '', data.model ? 'model=' + data.model : '', data.channel ? 'channel=' + data.channel : '', ] .filter(Boolean) .join(';'); } if (data.reqImei) next['req-imei'] = data.reqImei; if (data.origin) { next['req-sys-origin'] = [ 'origin=' + data.origin, 'originChild=' + (data.child || data.origin), ].join(';'); } headers = Object.assign(headers, next); if (window.HyAppAPI) { if (next.authorization) window.HyAppAPI.setAccessToken( tokenForHyAppAPI(next.authorization) ); window.HyAppAPI.setAppCode(APP_CODE); } return headers; } function discoverQueryHeaders() { return { authorization: query('authorization') || query('Authorization') || query('token') || query('accessToken') || query('access_token'), reqLang: query('lang') || query('language') || query('locale'), origin: query('sysOrigin') || query('origin') || 'YUMI', child: query('sysOriginChild') || query('originChild') || 'YUMI', channel: query('channel') || query('appChannel'), }; } function readStorageHeaders(storage) { var collected = {}; if (!storage) return collected; [ 'authorization', 'Authorization', 'token', 'accessToken', 'headerInfo', 'headersFromApp', 'appHeaders', 'user', 'userInfo', ].forEach(function (key) { try { Object.assign( collected, normalizeHeaderCandidate(storage.getItem(key)) ); } catch (_) {} }); return collected; } function applyDiscoveredHeaders() { setHeadersFromApp(discoverQueryHeaders()); setHeadersFromApp(window.__APP_HEADERS__); setHeadersFromApp(window.__HEADER_INFO__); setHeadersFromApp(window.headerInfo); try { setHeadersFromApp(readStorageHeaders(window.localStorage)); setHeadersFromApp(readStorageHeaders(window.sessionStorage)); } catch (_) {} } function ensureYumiAppCode() { if (window.HyAppAPI) window.HyAppAPI.setAppCode(APP_CODE); var params = currentParams(); if ( params.get('app_code') || params.get('app') || params.get('appCode') ) return; if (!window.history || !window.history.replaceState) return; var url = new URL(window.location.href); url.searchParams.set('app_code', APP_CODE); // 目标仓 common/api.js 没有 app_code 会默认走 lalu;Yumi 页入口必须显式落到 Yumi 租户。 window.history.replaceState(null, '', url.toString()); } function isAppEnvironment() { return Boolean( window.app || window.FlutterApp || window.FlutterPageControl || window.webkit ); } function requestAccessOrigin() { return new Promise(function (resolve) { var settled = false; var timeoutId = null; function finish(raw) { if (raw) setHeadersFromApp(raw); if (settled) return; settled = true; if (timeoutId) window.clearTimeout(timeoutId); resolve(raw || null); } window.renderData = finish; window.getIosAccessOriginParam = finish; if (!isAppEnvironment()) { finish(null); return; } // Android 普通 WebView 会注入 window.app.getAccessOrigin;iOS/旧页可能只通过回调异步回填。 if ( window.app && typeof window.app.getAccessOrigin === 'function' ) { try { finish(window.app.getAccessOrigin()); return; } catch (_) {} } if ( window.FlutterApp && typeof window.FlutterApp.postMessage === 'function' ) { try { window.FlutterApp.postMessage('requestAccessOrigin'); } catch (_) {} } var attempt = 0; var poll = function () { attempt += 1; if ( window.app && typeof window.app.getAccessOrigin === 'function' ) { try { finish(window.app.getAccessOrigin()); return; } catch (_) {} } if (!settled && attempt < 30) window.setTimeout(poll, 60); }; poll(); timeoutId = window.setTimeout(function () { finish(null); }, 1800); }); } function connectToApp() { if (readyPromise) return readyPromise; ensureYumiAppCode(); applyDiscoveredHeaders(); readyPromise = requestAccessOrigin().then(function () { return { baseURL: baseURL(), headers: Object.assign({}, headers), authenticated: Boolean(headers.authorization), }; }); return readyPromise; } function parseResponse(response) { return response.text().then(function (text) { var payload = text ? JSON.parse(text) : {}; if (!response.ok) { var httpError = new Error( payload.message || response.statusText || 'request_failed' ); httpError.status = response.status; httpError.payload = payload; throw httpError; } if (payload && payload.status === false) { var apiError = new Error( payload.message || payload.errorMsg || 'api_request_failed' ); apiError.payload = payload; throw apiError; } if ( payload && Object.prototype.hasOwnProperty.call(payload, 'body') ) return payload.body; if ( payload && Object.prototype.hasOwnProperty.call(payload, 'data') ) return payload.data; return payload; }); } function request(path, options) { var opts = options || {}; var body = opts.body; if (body && typeof body === 'object' && !(body instanceof FormData)) { body = JSON.stringify(body); } return connectToApp().then(function () { var requestHeaders = Object.assign({}, headers, opts.headers || {}); if (!requestHeaders.authorization) delete requestHeaders.authorization; if (body) requestHeaders['content-type'] = requestHeaders['content-type'] || 'application/json'; return window .fetch(buildURL(path, opts.query), { method: opts.method || 'GET', headers: requestHeaders, body: body, credentials: 'omit', cache: 'no-store', }) .then(parseResponse); }); } function readNumber(value, fallback) { var number = Number(value); return Number.isFinite(number) ? number : fallback || 0; } function relationType(tabOrType) { var key = String(tabOrType || 'cp') .trim() .toLowerCase(); return ( RELATION_TYPE_BY_TAB[key] || String(tabOrType || 'CP').toUpperCase() ); } function userAvatar(profile) { profile = profile || {}; return ( profile.userAvatar || profile.avatar || profile.headImg || profile.headImage || '' ); } function userId(profile) { profile = profile || {}; return profile.id || profile.userId || profile.uid || ''; } function choosePair(list, type) { var rows = Array.isArray(list) ? list : list ? [list] : []; for (var i = 0; i < rows.length; i += 1) { if (relationType(rows[i].relationType) === type) return rows[i]; } return rows[0] || null; } function sortedLevels(levels) { return (Array.isArray(levels) ? levels : []) .slice() .sort(function (a, b) { return readNumber(a.level, 0) - readNumber(b.level, 0); }); } function levelProgress(rights) { var intimacy = readNumber(rights && rights.intimacyValue, 0); var levels = sortedLevels(rights && rights.levels); var current = levels[0] || { level: 0, requiredIntimacyValue: 0 }; var next = levels[1] || current; levels.forEach(function (level, index) { if (readNumber(level.requiredIntimacyValue, 0) <= intimacy) { current = level; next = levels[index + 1] || level; } }); var currentThreshold = readNumber(current.requiredIntimacyValue, 0); var nextThreshold = Math.max( readNumber(next.requiredIntimacyValue, currentThreshold), currentThreshold ); var currentExp = Math.max(0, intimacy - currentThreshold); var nextExp = Math.max(0, nextThreshold - currentThreshold); var progressPercent = nextExp ? (currentExp / nextExp) * 100 : 100; return { currentLevel: readNumber(current.level, 0), nextLevel: readNumber(next.level, current.level), currentExp: currentExp, nextExp: nextExp || intimacy, neededExp: Math.max(0, nextThreshold - intimacy), nextExpLevel: readNumber(next.level, current.level), progressPercent: Math.max(0, Math.min(100, progressPercent)), }; } function normalizeRelation(pair, rights) { pair = pair || {}; rights = rights || {}; var progress = levelProgress(rights); var me = pair.meUserProfile || pair.me || {}; var partner = pair.cpUserProfile || pair.partner || {}; return Object.assign(progress, { relationType: relationType( pair.relationType || rights.relationType ), status: rights.relationshipStatus || pair.status || 'NONE', cpUserId: rights.cpUserId || userId(partner), days: readNumber(pair.days, 0), togetherDays: readNumber(pair.days, 0), me: { avatar: userAvatar(me), id: userId(me) }, partner: { avatar: userAvatar(partner), id: userId(partner) }, raw: { pair: pair, rights: rights }, }); } function pair(options) { var opts = options || {}; return request('/user/cp-relationship/pair', { query: { relationType: relationType(opts.relationType || opts.tab), userId: opts.userId, }, }); } function rights(options) { var opts = options || {}; return request('/user/cp/rights/config', { query: { relationType: relationType(opts.relationType || opts.tab), cpUserId: opts.cpUserId, }, }); } function loadRelation(options) { var opts = options || {}; var type = relationType(opts.relationType || opts.tab); var selectedPair = null; var firstError = null; return pair({ relationType: type, userId: opts.userId }) .then(function (pairList) { selectedPair = choosePair(pairList, type); var cpUserId = opts.cpUserId || (selectedPair && userId(selectedPair.cpUserProfile)) || ''; return rights({ relationType: type, cpUserId: cpUserId }); }) .catch(function (error) { firstError = error; return rights({ relationType: type, cpUserId: opts.cpUserId }); }) .then(function (rightsData) { return normalizeRelation(selectedPair, rightsData); }) .catch(function (error) { throw firstError || error; }); } function back() { if ( window.FlutterPageControl && window.FlutterPageControl.postMessage ) { window.FlutterPageControl.postMessage('close_page'); return; } if (window.HyAppBridge && window.HyAppBridge.back) { window.HyAppBridge.back(); return; } window.history.back(); } window.YumiCommon = { baseURL: baseURL, buildURL: buildURL, headers: function () { return Object.assign({}, headers); }, connectToApp: connectToApp, setHeadersFromApp: setHeadersFromApp, request: request, get: function (path, queryData) { return request(path, { method: 'GET', query: queryData }); }, post: function (path, body, queryData) { return request(path, { method: 'POST', body: body, query: queryData, }); }, back: back, cpSpace: { relationType: relationType, pair: pair, rights: rights, loadRelation: loadRelation, normalizeRelation: normalizeRelation, }, }; })();