修复aiyifan-util.js中部分语法在ios fw中不适配的问题
This commit is contained in:
1 parent
c2eeeb164b
commit
c049d1c312
1 file changed
+140
-49
+140
-49
@@ -12,8 +12,34 @@ const DEFAULT_USER_AGENT = (
|
||||
|
||||
export const AIYIFAN_SIGNING_CONFIG_TTL_MS = 60 * 1000;
|
||||
|
||||
// 安全获取对象属性(替代可选链操作符)
|
||||
function safeGet(obj, path, defaultValue) {
|
||||
if (obj == null) return defaultValue;
|
||||
const keys = path.split('.');
|
||||
let result = obj;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (result == null) return defaultValue;
|
||||
// 处理数组索引,如 config[0]
|
||||
const key = keys[i];
|
||||
const arrayMatch = key.match(/^(.+)\[(\d+)\]$/);
|
||||
if (arrayMatch) {
|
||||
const arrKey = arrayMatch[1];
|
||||
const index = parseInt(arrayMatch[2], 10);
|
||||
result = result[arrKey];
|
||||
if (Array.isArray(result) && index < result.length) {
|
||||
result = result[index];
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
} else {
|
||||
result = result[key];
|
||||
}
|
||||
}
|
||||
return result !== undefined ? result : defaultValue;
|
||||
}
|
||||
|
||||
function extractAssignedObjectLiteral(html, variableName) {
|
||||
const assignmentPattern = new RegExp(`\\b(?:var|let|const)\\s+${variableName}\\s*=\\s*`);
|
||||
const assignmentPattern = new RegExp('\\b(?:var|let|const)\\s+' + variableName + '\\s*=\\s*');
|
||||
const match = assignmentPattern.exec(html);
|
||||
if (!match) {
|
||||
return null;
|
||||
@@ -74,8 +100,8 @@ function parseFallbackPConfig(html) {
|
||||
|
||||
let privateKeys = [];
|
||||
try {
|
||||
privateKeys = JSON.parse(`[${match[2]}]`);
|
||||
} catch {
|
||||
privateKeys = JSON.parse('[' + match[2] + ']');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -90,9 +116,14 @@ function parseFallbackPConfig(html) {
|
||||
}
|
||||
|
||||
export function extractPConfigFromInjectJson(injectJson) {
|
||||
const config = injectJson?.config?.[0]?.pConfig;
|
||||
const publicKey = config?.publicKey;
|
||||
const privateKey = Array.isArray(config?.privateKey) ? config.privateKey[0] : config?.privateKey;
|
||||
// 使用安全获取替代可选链
|
||||
const config = safeGet(injectJson, 'config[0]', null);
|
||||
const pConfig = config ? config.pConfig : null;
|
||||
|
||||
if (!pConfig) return null;
|
||||
|
||||
const publicKey = pConfig.publicKey;
|
||||
const privateKey = Array.isArray(pConfig.privateKey) ? pConfig.privateKey[0] : pConfig.privateKey;
|
||||
|
||||
if (!publicKey || !privateKey) {
|
||||
return null;
|
||||
@@ -111,7 +142,7 @@ export function extractPConfigFromHtml(html) {
|
||||
return signingConfig;
|
||||
}
|
||||
} catch (error) {
|
||||
log("warn", `[Aiyifan] 解析 injectJson 失败,回退到 pConfig 提取: ${error.message}`);
|
||||
log("warn", '[Aiyifan] 解析 injectJson 失败,回退到 pConfig 提取: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,12 +168,22 @@ function splitQueryString(queryString) {
|
||||
return queryString
|
||||
.split("&")
|
||||
.filter(Boolean)
|
||||
.map(pair => {
|
||||
.map(function(pair) {
|
||||
const equalsIndex = pair.indexOf("=");
|
||||
const rawKey = equalsIndex === -1 ? pair : pair.slice(0, equalsIndex);
|
||||
const rawValue = equalsIndex === -1 ? "" : pair.slice(equalsIndex + 1);
|
||||
const key = decodeURIComponent(rawKey.replace(/\+/g, "%20"));
|
||||
const value = decodeURIComponent(rawValue.replace(/\+/g, "%20"));
|
||||
|
||||
// 手动解码,替代 decodeURIComponent 的异常处理
|
||||
function safeDecode(str) {
|
||||
try {
|
||||
return decodeURIComponent(str.replace(/\+/g, "%20"));
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
const key = safeDecode(rawKey);
|
||||
const value = safeDecode(rawValue);
|
||||
return [key, value];
|
||||
});
|
||||
}
|
||||
@@ -163,32 +204,63 @@ function getQueryEntries(input) {
|
||||
if (queryIndex !== -1) {
|
||||
const hashIndex = trimmed.indexOf("#", queryIndex);
|
||||
queryString = trimmed.slice(queryIndex + 1, hashIndex === -1 ? undefined : hashIndex);
|
||||
} else if (trimmed.startsWith("?")) {
|
||||
} else if (trimmed.charAt(0) === "?") {
|
||||
queryString = trimmed.slice(1);
|
||||
}
|
||||
|
||||
return splitQueryString(queryString);
|
||||
}
|
||||
|
||||
if (input instanceof URLSearchParams) {
|
||||
return Array.from(input.entries());
|
||||
// 移除 URLSearchParams 检查,统一按普通对象处理
|
||||
if (typeof input === "object" && input !== null) {
|
||||
// 如果 input 有 entries 方法且返回数组,使用它
|
||||
if (typeof input.entries === "function") {
|
||||
try {
|
||||
var entries = input.entries();
|
||||
if (Array.isArray(entries)) {
|
||||
return entries;
|
||||
}
|
||||
// 处理迭代器情况
|
||||
var result = [];
|
||||
// 尝试作为 Map 或类似对象处理
|
||||
if (typeof input.forEach === "function") {
|
||||
input.forEach(function(value, key) {
|
||||
result.push([key, normalizeQueryValue(value)]);
|
||||
});
|
||||
return result.filter(function(item) {
|
||||
return item[1] !== null;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 失败则回退到 Object.entries
|
||||
}
|
||||
}
|
||||
|
||||
// 标准对象处理
|
||||
return Object.keys(input).map(function(key) {
|
||||
return [key, normalizeQueryValue(input[key])];
|
||||
}).filter(function(item) {
|
||||
return item[1] !== null;
|
||||
});
|
||||
}
|
||||
|
||||
return Object.entries(input)
|
||||
.map(([key, value]) => [key, normalizeQueryValue(value)])
|
||||
.filter(([, value]) => value !== null);
|
||||
return [];
|
||||
}
|
||||
|
||||
export function buildCanonicalQuery(input) {
|
||||
return getQueryEntries(input)
|
||||
.filter(([key]) => !isSigningParam(key))
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.filter(function(item) {
|
||||
return !isSigningParam(item[0]);
|
||||
})
|
||||
.map(function(item) {
|
||||
return item[0] + "=" + item[1];
|
||||
})
|
||||
.join("&");
|
||||
}
|
||||
|
||||
export function computeAiyifanVv(input, signingConfig) {
|
||||
const query = buildCanonicalQuery(input);
|
||||
const raw = `${signingConfig.publicKey}&${query.toLowerCase()}&${signingConfig.privateKey}`;
|
||||
const raw = signingConfig.publicKey + "&" + query.toLowerCase() + "&" + signingConfig.privateKey;
|
||||
return md5(raw);
|
||||
}
|
||||
|
||||
@@ -200,89 +272,108 @@ function normalizeJsonPayload(data) {
|
||||
}
|
||||
|
||||
function isSignedRequestSuccessful(payload) {
|
||||
return payload?.ret === 200 && payload?.data?.code === 0;
|
||||
// 使用安全获取替代可选链
|
||||
var ret = safeGet(payload, 'ret', null);
|
||||
var code = safeGet(payload, 'data.code', null);
|
||||
return ret === 200 && code === 0;
|
||||
}
|
||||
|
||||
function getFailureMessage(payload, status) {
|
||||
return payload?.data?.msg || payload?.msg || `HTTP ${status}`;
|
||||
// 使用安全获取替代可选链
|
||||
var msg = safeGet(payload, 'data.msg', null) || safeGet(payload, 'msg', null);
|
||||
return msg || ('HTTP ' + status);
|
||||
}
|
||||
|
||||
export class AiyifanSigningProvider {
|
||||
constructor(options = {}) {
|
||||
this.request = options.request || httpGet;
|
||||
this.proxyUrlBuilder = options.proxyUrlBuilder || (url => globals.makeProxyUrl(url));
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
this.proxyUrlBuilder = options.proxyUrlBuilder || function(url) {
|
||||
return globals.makeProxyUrl(url);
|
||||
};
|
||||
this.userAgent = options.userAgent || DEFAULT_USER_AGENT;
|
||||
this.configPageUrl = options.configPageUrl || DEFAULT_CONFIG_PAGE_URL;
|
||||
this.ttlMs = options.ttlMs || AIYIFAN_SIGNING_CONFIG_TTL_MS;
|
||||
this.now = options.now || (() => Date.now());
|
||||
this.now = options.now || function() { return Date.now(); };
|
||||
this.signingConfig = null;
|
||||
this.signingConfigFetchedAt = 0;
|
||||
}
|
||||
|
||||
async getSigningConfig(forceRefresh = false) {
|
||||
const now = this.now();
|
||||
const cacheValid = this.signingConfig && (now - this.signingConfigFetchedAt) < this.ttlMs;
|
||||
async getSigningConfig(forceRefresh) {
|
||||
forceRefresh = forceRefresh || false;
|
||||
var now = this.now();
|
||||
var cacheValid = this.signingConfig && (now - this.signingConfigFetchedAt) < this.ttlMs;
|
||||
|
||||
if (!forceRefresh && cacheValid) {
|
||||
return this.signingConfig;
|
||||
}
|
||||
|
||||
const response = await this.request(this.proxyUrlBuilder(this.configPageUrl), {
|
||||
var response = await httpGet(this.proxyUrlBuilder(this.configPageUrl), {
|
||||
headers: {
|
||||
"User-Agent": this.userAgent,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
||||
}
|
||||
});
|
||||
|
||||
const html = typeof response.data === "string" ? response.data : String(response.data ?? "");
|
||||
const signingConfig = extractPConfigFromHtml(html);
|
||||
var html = typeof response.data === "string" ? response.data : String(response.data || "");
|
||||
var signingConfig = extractPConfigFromHtml(html);
|
||||
if (!signingConfig) {
|
||||
throw new Error("未能从桌面站页面解析到 pConfig");
|
||||
}
|
||||
|
||||
this.signingConfig = signingConfig;
|
||||
this.signingConfigFetchedAt = now;
|
||||
log("info", `[Aiyifan] 已更新桌面站签名配置: ${signingConfig.publicKey.slice(0, 12)}...`);
|
||||
log("info", '[Aiyifan] 已更新桌面站签名配置: ' + signingConfig.publicKey.slice(0, 12) + '...');
|
||||
return signingConfig;
|
||||
}
|
||||
|
||||
buildSignedParams(baseParams, signingConfig) {
|
||||
return {
|
||||
...baseParams,
|
||||
vv: computeAiyifanVv(baseParams, signingConfig),
|
||||
pub: signingConfig.publicKey
|
||||
};
|
||||
var result = {};
|
||||
// 手动复制对象,替代展开运算符
|
||||
for (var key in baseParams) {
|
||||
if (baseParams.hasOwnProperty(key)) {
|
||||
result[key] = baseParams[key];
|
||||
}
|
||||
}
|
||||
result.vv = computeAiyifanVv(baseParams, signingConfig);
|
||||
result.pub = signingConfig.publicKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
async signedGetJson(api, baseParams, headers = {}, logPrefix = "Aiyifan", forceRefresh = false) {
|
||||
const signingConfig = await this.getSigningConfig(forceRefresh);
|
||||
const signedParams = this.buildSignedParams(baseParams, signingConfig);
|
||||
const requestUrl = updateQueryString(api, signedParams);
|
||||
const response = await this.request(this.proxyUrlBuilder(requestUrl), { headers });
|
||||
async signedGetJson(api, baseParams, headers, logPrefix, forceRefresh) {
|
||||
headers = headers || {};
|
||||
logPrefix = logPrefix || "Aiyifan";
|
||||
forceRefresh = forceRefresh || false;
|
||||
|
||||
var signingConfig = await this.getSigningConfig(forceRefresh);
|
||||
var signedParams = this.buildSignedParams(baseParams, signingConfig);
|
||||
var requestUrl = updateQueryString(api, signedParams);
|
||||
var response = await httpGet(this.proxyUrlBuilder(requestUrl), { headers: headers });
|
||||
|
||||
let payload;
|
||||
var payload;
|
||||
try {
|
||||
payload = normalizeJsonPayload(response.data);
|
||||
} catch (error) {
|
||||
if (!forceRefresh) {
|
||||
log("warn", `[${logPrefix}] 响应无法解析为 JSON,刷新签名配置后重试: ${error.message}`);
|
||||
log("warn", '[' + logPrefix + '] 响应无法解析为 JSON,刷新签名配置后重试: ' + (error.message || '未知错误'));
|
||||
return this.signedGetJson(api, baseParams, headers, logPrefix, true);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (response.status !== 200 || !isSignedRequestSuccessful(payload)) {
|
||||
// 安全获取状态码,如果不存在则默认为200
|
||||
var statusCode = response.status != null ? response.status : 200;
|
||||
if (statusCode !== 200 || !isSignedRequestSuccessful(payload)) {
|
||||
if (!forceRefresh) {
|
||||
log("warn", `[${logPrefix}] 当前签名请求失败,刷新 pConfig 后重试: ${getFailureMessage(payload, response.status)}`);
|
||||
log("warn", '[' + logPrefix + '] 当前签名请求失败,刷新 pConfig 后重试: ' + getFailureMessage(payload, statusCode));
|
||||
return this.signedGetJson(api, baseParams, headers, logPrefix, true);
|
||||
}
|
||||
throw new Error(getFailureMessage(payload, response.status));
|
||||
throw new Error(getFailureMessage(payload, statusCode));
|
||||
}
|
||||
|
||||
return {
|
||||
data: payload,
|
||||
vv: signedParams.vv,
|
||||
signingConfig
|
||||
signingConfig: signingConfig
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user