346 lines
13 KiB
JavaScript
346 lines
13 KiB
JavaScript
/**
|
|
* 独立的注入脚本 - 在页面上下文中最先执行
|
|
* 这个文件会被注入到页面上下文中,确保在任何页面脚本运行前拦截
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
// 立即拦截,不等待任何其他代码
|
|
console.log('🚀 [页面上下文] 拦截脚本开始执行 - 时间:', new Date().toISOString());
|
|
|
|
// 在页面上下文中存储解密记录
|
|
if (!window.__XHR_DECRYPT_DATA__) {
|
|
window.__XHR_DECRYPT_DATA__ = {
|
|
requests: [],
|
|
addRequest: function (record) {
|
|
this.requests.push(record);
|
|
if (this.requests.length > 100) {
|
|
this.requests.shift();
|
|
}
|
|
// 通知 content script
|
|
window.dispatchEvent(new CustomEvent('__XHR_DECRYPTED__', { detail: record }));
|
|
},
|
|
getRequests: function () {
|
|
return this.requests;
|
|
},
|
|
clear: function () {
|
|
this.requests = [];
|
|
}
|
|
};
|
|
}
|
|
|
|
const decryptedData = window.__XHR_DECRYPT_DATA__;
|
|
const requestHeadersMap = new Map();
|
|
const skipDecryptPaths = ['v1/picture/upload'];
|
|
|
|
// 获取解密工具
|
|
function getDecryptTools() {
|
|
if (window.__DECRYPT_TOOLS__) {
|
|
return window.__DECRYPT_TOOLS__;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 计算密钥
|
|
function calculateKey(timestamp, traceId) {
|
|
if (!timestamp || !traceId) return null;
|
|
return String(timestamp + traceId).slice(0, 16);
|
|
}
|
|
|
|
// 获取解密函数
|
|
function getDecryptFunction() {
|
|
const tools = getDecryptTools();
|
|
if (tools && tools.Decrypt) {
|
|
return tools.Decrypt;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 解密请求数据
|
|
function decryptRequestData(encryptedData, timestampKey) {
|
|
try {
|
|
const Decrypt = getDecryptFunction();
|
|
if (!Decrypt) {
|
|
console.warn('[页面上下文] 无法获取解密函数');
|
|
return encryptedData;
|
|
}
|
|
if (encryptedData && encryptedData.data && typeof encryptedData.data === 'string') {
|
|
const decrypted = Decrypt(encryptedData.data, timestampKey);
|
|
return JSON.parse(decrypted);
|
|
}
|
|
return encryptedData;
|
|
} catch (error) {
|
|
console.error('[页面上下文] 解密请求数据失败:', error);
|
|
return encryptedData;
|
|
}
|
|
}
|
|
|
|
// 解密响应数据
|
|
function decryptResponseData(encryptedData, timestampKey) {
|
|
try {
|
|
const Decrypt = getDecryptFunction();
|
|
if (!Decrypt) {
|
|
console.warn('[页面上下文] 无法获取解密函数');
|
|
return encryptedData;
|
|
}
|
|
if (typeof encryptedData === 'string') {
|
|
const decrypted = Decrypt(encryptedData, timestampKey);
|
|
try {
|
|
return JSON.parse(decrypted);
|
|
} catch (e) {
|
|
if (window.JSON5) {
|
|
return window.JSON5.parse(decrypted);
|
|
}
|
|
return decrypted;
|
|
}
|
|
}
|
|
return encryptedData;
|
|
} catch (error) {
|
|
console.error('[页面上下文] 解密响应数据失败:', error);
|
|
return encryptedData;
|
|
}
|
|
}
|
|
|
|
// 获取密钥
|
|
function getTimestampKey(url, uuid, requestHeaders) {
|
|
const tools = getDecryptTools();
|
|
if (tools && tools.getKey) {
|
|
const key = tools.getKey(url, uuid);
|
|
if (key) return key;
|
|
}
|
|
const timestamp = requestHeaders?.time || requestHeaders?.Time || '';
|
|
const traceId = requestHeaders?.TraceId || requestHeaders?.['trace-id'] || '';
|
|
if (timestamp && traceId) {
|
|
return calculateKey(timestamp, traceId);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ========== 拦截 Fetch (优先,因为 umi-request 使用 fetch) ==========
|
|
if (typeof fetch !== 'undefined' && window.fetch) {
|
|
const originalFetch = window.fetch;
|
|
|
|
window.fetch = function (...args) {
|
|
const [url, options = {}] = args;
|
|
const method = options.method || 'GET';
|
|
const requestId = Date.now() + Math.random();
|
|
const shouldSkip = skipDecryptPaths.some(path => url.includes(path));
|
|
const originalBody = options.body;
|
|
|
|
console.log('🔍 [页面上下文] 拦截 Fetch:', method, url);
|
|
|
|
return originalFetch.apply(this, args).then(async (response) => {
|
|
if (shouldSkip) return response;
|
|
|
|
try {
|
|
const clonedResponse = response.clone();
|
|
const uuid = clonedResponse.headers.get('uuid');
|
|
|
|
if (!uuid) {
|
|
return response;
|
|
}
|
|
|
|
const headers = options.headers || {};
|
|
const timestamp = headers.time || headers.Time || '';
|
|
const traceId = headers.TraceId || headers['trace-id'] || '';
|
|
const timestampKey = getTimestampKey(url, uuid, { time: timestamp, TraceId: traceId });
|
|
|
|
if (!timestampKey) {
|
|
console.warn('[页面上下文] 无法获取密钥 (Fetch)', { url, uuid });
|
|
return response;
|
|
}
|
|
|
|
// 解密请求数据
|
|
let requestBody = null;
|
|
if (originalBody) {
|
|
try {
|
|
if (typeof originalBody === 'string') {
|
|
const parsed = JSON.parse(originalBody);
|
|
requestBody = decryptRequestData(parsed, timestampKey);
|
|
} else {
|
|
requestBody = originalBody;
|
|
}
|
|
} catch (e) {
|
|
requestBody = originalBody;
|
|
}
|
|
}
|
|
|
|
// 解密响应数据
|
|
let responseData = null;
|
|
try {
|
|
const text = await clonedResponse.text();
|
|
if (text) {
|
|
try {
|
|
responseData = JSON.parse(text);
|
|
} catch (e) {
|
|
responseData = text;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('[页面上下文] 读取响应数据失败:', e);
|
|
}
|
|
|
|
const decryptedResponse = decryptResponseData(responseData, timestampKey);
|
|
|
|
// 记录解密结果
|
|
const decryptedRecord = {
|
|
id: requestId,
|
|
url: url,
|
|
method: method,
|
|
uuid: uuid,
|
|
timestampKey: timestampKey,
|
|
request: requestBody,
|
|
response: decryptedResponse,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// 存储到页面上下文
|
|
decryptedData.addRequest(decryptedRecord);
|
|
|
|
// 输出到控制台
|
|
console.group('🔓 Fetch 解密 [' + method + '] ' + url);
|
|
console.log('🔑 密钥:', timestampKey);
|
|
console.log('📤 请求:', requestBody);
|
|
console.log('📥 响应:', decryptedResponse);
|
|
console.log('📍 UUID:', uuid);
|
|
console.groupEnd();
|
|
|
|
} catch (error) {
|
|
console.error('[页面上下文] Fetch 解密过程出错:', error);
|
|
}
|
|
|
|
return response;
|
|
}).catch((error) => {
|
|
console.error('[页面上下文] Fetch 请求失败:', error);
|
|
return Promise.reject(error);
|
|
});
|
|
};
|
|
|
|
console.log('✅ [页面上下文] Fetch 拦截已设置');
|
|
}
|
|
|
|
// ========== 拦截 XMLHttpRequest ==========
|
|
if (typeof XMLHttpRequest !== 'undefined') {
|
|
const originalOpen = XMLHttpRequest.prototype.open;
|
|
const originalSend = XMLHttpRequest.prototype.send;
|
|
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
|
|
XMLHttpRequest.prototype.open = function (method, url, ...args) {
|
|
this._method = method;
|
|
this._url = url;
|
|
this._requestHeaders = {};
|
|
requestHeadersMap.set(this, {});
|
|
console.log('🔍 [页面上下文] 拦截 XHR open:', method, url);
|
|
return originalOpen.apply(this, [method, url, ...args]);
|
|
};
|
|
|
|
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
|
if (!this._requestHeaders) {
|
|
this._requestHeaders = {};
|
|
}
|
|
this._requestHeaders[name] = value;
|
|
const headers = requestHeadersMap.get(this) || {};
|
|
headers[name] = value;
|
|
requestHeadersMap.set(this, headers);
|
|
return originalSetRequestHeader.apply(this, [name, value]);
|
|
};
|
|
|
|
XMLHttpRequest.prototype.send = function (body) {
|
|
const url = this._url;
|
|
const method = this._method;
|
|
const requestId = Date.now() + Math.random();
|
|
const shouldSkip = skipDecryptPaths.some(path => url.includes(path));
|
|
|
|
// 监听响应
|
|
this.addEventListener('loadend', function () {
|
|
if (shouldSkip) return;
|
|
|
|
try {
|
|
const headers = requestHeadersMap.get(this) || {};
|
|
const uuid = this.getResponseHeader('uuid');
|
|
|
|
if (!uuid) {
|
|
return;
|
|
}
|
|
|
|
const timestampKey = getTimestampKey(url, uuid, headers);
|
|
if (!timestampKey) {
|
|
console.warn('[页面上下文] 无法获取密钥', { url, uuid, headers });
|
|
return;
|
|
}
|
|
|
|
// 解密请求数据
|
|
let requestBody = null;
|
|
if (body) {
|
|
try {
|
|
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
requestBody = decryptRequestData(parsed, timestampKey);
|
|
} catch (e) {
|
|
requestBody = body;
|
|
}
|
|
}
|
|
|
|
// 解密响应数据
|
|
let responseData = null;
|
|
try {
|
|
if (this.responseText) {
|
|
responseData = JSON.parse(this.responseText);
|
|
}
|
|
} catch (e) {
|
|
responseData = this.responseText || this.response;
|
|
}
|
|
|
|
const decryptedResponse = decryptResponseData(responseData, timestampKey);
|
|
|
|
// 记录解密结果
|
|
const decryptedRecord = {
|
|
id: requestId,
|
|
url: url,
|
|
method: method,
|
|
uuid: uuid,
|
|
timestampKey: timestampKey,
|
|
request: requestBody,
|
|
response: decryptedResponse,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// 存储到页面上下文
|
|
decryptedData.addRequest(decryptedRecord);
|
|
|
|
// 输出到控制台
|
|
console.group('🔓 XHR 解密 [' + method + '] ' + url);
|
|
console.log('🔑 密钥:', timestampKey);
|
|
console.log('📤 请求:', requestBody);
|
|
console.log('📥 响应:', decryptedResponse);
|
|
console.log('📍 UUID:', uuid);
|
|
console.groupEnd();
|
|
|
|
} catch (error) {
|
|
console.error('[页面上下文] 解密过程出错:', error);
|
|
}
|
|
});
|
|
|
|
return originalSend.apply(this, [body]);
|
|
};
|
|
|
|
console.log('✅ [页面上下文] XHR 拦截已设置');
|
|
}
|
|
|
|
// 暴露 API 供 DevTools 面板使用
|
|
window.__XHR_DECRYPT_EXTENSION__ = {
|
|
getDecryptedRequests: function () {
|
|
return decryptedData.getRequests();
|
|
},
|
|
clearDecryptedRequests: function () {
|
|
decryptedData.clear();
|
|
console.log('✅ [页面上下文] 已清空解密记录');
|
|
}
|
|
};
|
|
|
|
console.log('✅ [页面上下文] 拦截脚本初始化完成');
|
|
console.log('✅ [页面上下文] 已拦截 Fetch:', typeof window.fetch !== 'undefined');
|
|
console.log('✅ [页面上下文] 已拦截 XHR:', typeof XMLHttpRequest !== 'undefined');
|
|
})();
|
|
|