642 lines
21 KiB
JavaScript
642 lines
21 KiB
JavaScript
(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,
|
||
},
|
||
};
|
||
})();
|