first commit

This commit is contained in:
2026-01-29 12:03:28 +08:00
commit 3ecfab1212
18 changed files with 34678 additions and 0 deletions

345
inject-script.js Normal file
View File

@@ -0,0 +1,345 @@
/**
* 独立的注入脚本 - 在页面上下文中最先执行
* 这个文件会被注入到页面上下文中,确保在任何页面脚本运行前拦截
*/
(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');
})();