524 lines
16 KiB
JavaScript
524 lines
16 KiB
JavaScript
(function () {
|
||
'use strict';
|
||
|
||
var PROD_API_BASE = 'https://api.atuchat.com/';
|
||
var LOCAL_API_BASE = 'http://127.0.0.1:1100/';
|
||
var APP_CODE = 'aslan';
|
||
var DEFAULT_ORIGIN = 'ATYOU';
|
||
var readyPromise = null;
|
||
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=ATYOU;originChild=ATYOU',
|
||
'req-version': 'V2',
|
||
'req-zone': 'Asia/Shanghai',
|
||
};
|
||
|
||
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 === 'local' || env === 'test' || env === 'dev' ? env : '';
|
||
}
|
||
|
||
function baseURL() {
|
||
var override =
|
||
query('apiBase') ||
|
||
query('api_base') ||
|
||
query('aslan_api_base') ||
|
||
window.ASLAN_API_BASE_URL ||
|
||
(window.AslanCommonConfig && window.AslanCommonConfig.apiBase);
|
||
if (override) return normalizeBaseURL(override);
|
||
return resolveEnv() === 'local'
|
||
? normalizeBaseURL(LOCAL_API_BASE)
|
||
: normalizeBaseURL(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 splitHeaderPairs(value) {
|
||
var result = {};
|
||
String(value || '')
|
||
.split(';')
|
||
.forEach(function (item) {
|
||
var index = item.indexOf('=');
|
||
if (index > -1) {
|
||
result[item.slice(0, index).trim()] = item
|
||
.slice(index + 1)
|
||
.trim();
|
||
}
|
||
});
|
||
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 { 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',
|
||
appIntel['Req-Imei'] ||
|
||
appIntel.imei ||
|
||
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);
|
||
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') || DEFAULT_ORIGIN,
|
||
child:
|
||
query('sysOriginChild') ||
|
||
query('originChild') ||
|
||
DEFAULT_ORIGIN,
|
||
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 ensureAslanAppCode() {
|
||
document.documentElement.dataset.hyAppCode = APP_CODE;
|
||
var params = currentParams();
|
||
if (
|
||
params.get('app_code') ||
|
||
params.get('app') ||
|
||
params.get('appCode') ||
|
||
!window.history ||
|
||
!window.history.replaceState
|
||
) {
|
||
return;
|
||
}
|
||
var url = new URL(window.location.href);
|
||
url.searchParams.set('app_code', APP_CODE);
|
||
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;
|
||
}
|
||
|
||
// Aslan Flutter 在页面完成后注入 window.app.getAccessOrigin;这里轮询短时间等待,避免首屏请求早于注入而丢登录态。
|
||
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, 80);
|
||
};
|
||
poll();
|
||
timeoutId = window.setTimeout(function () {
|
||
finish(null);
|
||
}, 2400);
|
||
});
|
||
}
|
||
|
||
function connectToApp() {
|
||
if (readyPromise) return readyPromise;
|
||
ensureAslanAppCode();
|
||
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 ||
|
||
payload.errorMsg ||
|
||
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 ||
|
||
payload.error ||
|
||
'api_request_failed'
|
||
);
|
||
apiError.payload = payload;
|
||
apiError.code = payload.errorCode || payload.code;
|
||
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 stripAppEncryptedHeaders(target) {
|
||
// H5 WebView 只复用 App 登录态,不参与 App Dio 加密;带 Req-Atyou 会让网关按密文解码普通 H5 请求。
|
||
Object.keys(target || {}).forEach(function (key) {
|
||
if (String(key).toLowerCase() === 'req-atyou') delete target[key];
|
||
});
|
||
}
|
||
|
||
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 || {});
|
||
stripAppEncryptedHeaders(requestHeaders);
|
||
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 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.AslanCommon = {
|
||
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,
|
||
user: {
|
||
profile: function () {
|
||
return request('/user/user-profile/user-id', { method: 'GET' });
|
||
},
|
||
},
|
||
rechargeReward: {
|
||
status: function () {
|
||
return request(
|
||
'/activity/cumulative-recharge/recharge-reward',
|
||
{
|
||
method: 'GET',
|
||
}
|
||
);
|
||
},
|
||
claim: function (ruleId) {
|
||
return request('/activity/cumulative-recharge', {
|
||
method: 'POST',
|
||
body: { id: String(ruleId || '') },
|
||
});
|
||
},
|
||
},
|
||
};
|
||
})();
|