874 lines
42 KiB
JavaScript
874 lines
42 KiB
JavaScript
/**
|
||
* Content Script - 使用 DevTools Network API 获取 XHR 请求
|
||
*
|
||
* 注意:这个脚本只在 DevTools 面板中使用
|
||
* 实际的请求捕获通过 chrome.devtools.network.onRequestFinished 实现
|
||
*/
|
||
|
||
// 这个文件现在已经不需要了,因为请求捕获在 DevTools 面板中完成
|
||
// 但保留文件以避免错误
|
||
|
||
// 将完整的拦截脚本注入到页面上下文
|
||
const script = document.createElement('script');
|
||
script.textContent = `
|
||
(function() {
|
||
'use strict';
|
||
|
||
console.log('🔧 [页面上下文] 拦截脚本已执行');
|
||
console.log('🔧 [页面上下文] XMLHttpRequest 类型:', typeof XMLHttpRequest);
|
||
console.log('🔧 [页面上下文] fetch 类型:', typeof fetch);
|
||
|
||
// 在页面上下文中存储解密记录
|
||
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'];
|
||
|
||
// ========== 密钥获取策略管理器 ==========
|
||
const KeyGetStrategies = {
|
||
strategies: [],
|
||
|
||
// 注册密钥获取策略
|
||
register: function(strategy) {
|
||
if (strategy && typeof strategy.getName === 'function' && typeof strategy.getKey === 'function') {
|
||
this.strategies.push({
|
||
name: strategy.getName(),
|
||
priority: strategy.priority || 100,
|
||
getKey: strategy.getKey,
|
||
canHandle: strategy.canHandle || (() => true)
|
||
});
|
||
// 按优先级排序(数字越小优先级越高)
|
||
this.strategies.sort((a, b) => a.priority - b.priority);
|
||
}
|
||
},
|
||
|
||
// 获取密钥(按优先级尝试所有策略)
|
||
getKey: function(url, uuid, requestHeaders) {
|
||
for (const strategy of this.strategies) {
|
||
try {
|
||
if (strategy.canHandle(url, uuid, requestHeaders)) {
|
||
const key = strategy.getKey(url, uuid, requestHeaders);
|
||
if (key) {
|
||
console.log('🔑 [密钥策略] 使用策略:', strategy.name, '获取密钥成功');
|
||
return { key: key, strategy: strategy.name };
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('🔑 [密钥策略] 策略', strategy.name, '执行失败:', error);
|
||
continue;
|
||
}
|
||
}
|
||
console.warn('🔑 [密钥策略] 所有策略都无法获取密钥');
|
||
return null;
|
||
},
|
||
|
||
// 初始化默认策略
|
||
init: function() {
|
||
// 策略1: 从页面工具获取密钥(优先级最高)
|
||
this.register({
|
||
name: '页面工具获取',
|
||
priority: 10,
|
||
canHandle: function(url, uuid, requestHeaders) {
|
||
return window.__DECRYPT_TOOLS__ && window.__DECRYPT_TOOLS__.getKey;
|
||
},
|
||
getKey: function(url, uuid, requestHeaders) {
|
||
const tools = window.__DECRYPT_TOOLS__;
|
||
if (tools && tools.getKey) {
|
||
return tools.getKey(url, uuid);
|
||
}
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 策略2: 从请求头计算密钥(timestamp + TraceId)
|
||
this.register({
|
||
name: '请求头计算',
|
||
priority: 20,
|
||
canHandle: function(url, uuid, requestHeaders) {
|
||
const timestamp = requestHeaders?.time || requestHeaders?.Time || '';
|
||
const traceId = requestHeaders?.TraceId || requestHeaders?.['trace-id'] || '';
|
||
return !!(timestamp && traceId);
|
||
},
|
||
getKey: function(url, uuid, requestHeaders) {
|
||
const timestamp = requestHeaders?.time || requestHeaders?.Time || '';
|
||
const traceId = requestHeaders?.TraceId || requestHeaders?.['trace-id'] || '';
|
||
if (timestamp && traceId) {
|
||
return String(timestamp + traceId).slice(0, 16);
|
||
}
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 可以在这里添加更多密钥获取策略
|
||
// 例如:从 localStorage 获取、从 URL 参数获取、从 Cookie 获取等
|
||
}
|
||
};
|
||
|
||
// ========== 解密策略管理器 ==========
|
||
const DecryptStrategies = {
|
||
strategies: [],
|
||
|
||
// 注册解密策略
|
||
register: function(strategy) {
|
||
if (strategy && typeof strategy.getName === 'function' && typeof strategy.decrypt === 'function') {
|
||
this.strategies.push({
|
||
name: strategy.getName(),
|
||
priority: strategy.priority || 100,
|
||
decrypt: strategy.decrypt,
|
||
canHandle: strategy.canHandle || (() => true)
|
||
});
|
||
// 按优先级排序
|
||
this.strategies.sort((a, b) => a.priority - b.priority);
|
||
}
|
||
},
|
||
|
||
// 解密数据(按优先级尝试所有策略)
|
||
decrypt: function(encryptedData, key, isRequest = false) {
|
||
for (const strategy of this.strategies) {
|
||
try {
|
||
if (strategy.canHandle(encryptedData, key, isRequest)) {
|
||
const decrypted = strategy.decrypt(encryptedData, key, isRequest);
|
||
if (decrypted !== null && decrypted !== undefined && decrypted !== encryptedData) {
|
||
console.log('🔓 [解密策略] 使用策略:', strategy.name, '解密成功');
|
||
return { data: decrypted, strategy: strategy.name };
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('🔓 [解密策略] 策略', strategy.name, '执行失败:', error);
|
||
continue;
|
||
}
|
||
}
|
||
console.warn('🔓 [解密策略] 所有策略都无法解密数据');
|
||
return { data: encryptedData, strategy: null };
|
||
},
|
||
|
||
// 初始化默认策略
|
||
init: function() {
|
||
// 策略1: 使用页面工具解密(优先级最高)
|
||
this.register({
|
||
name: '页面工具解密',
|
||
priority: 10,
|
||
canHandle: function(encryptedData, key, isRequest) {
|
||
return window.__DECRYPT_TOOLS__ && window.__DECRYPT_TOOLS__.Decrypt;
|
||
},
|
||
decrypt: function(encryptedData, key, isRequest) {
|
||
const tools = window.__DECRYPT_TOOLS__;
|
||
if (!tools || !tools.Decrypt) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
if (isRequest) {
|
||
// 请求数据格式:{ data: "base64String" }
|
||
if (encryptedData && encryptedData.data && typeof encryptedData.data === 'string') {
|
||
const decrypted = tools.Decrypt(encryptedData.data, key);
|
||
if (decrypted) {
|
||
return JSON.parse(decrypted);
|
||
}
|
||
}
|
||
} else {
|
||
// 响应数据格式:字符串或对象
|
||
if (typeof encryptedData === 'string') {
|
||
const decrypted = tools.Decrypt(encryptedData, key);
|
||
if (decrypted) {
|
||
try {
|
||
return JSON.parse(decrypted);
|
||
} catch (e) {
|
||
if (window.JSON5) {
|
||
return window.JSON5.parse(decrypted);
|
||
}
|
||
return decrypted;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('解密失败:', error);
|
||
}
|
||
return null;
|
||
}
|
||
});
|
||
|
||
// 策略2: 使用 CryptoJS 直接解密(如果可用)
|
||
this.register({
|
||
name: 'CryptoJS 解密',
|
||
priority: 20,
|
||
canHandle: function(encryptedData, key, isRequest) {
|
||
return typeof CryptoJS !== 'undefined' && key;
|
||
},
|
||
decrypt: function(encryptedData, key, isRequest) {
|
||
try {
|
||
if (typeof CryptoJS === 'undefined') {
|
||
return null;
|
||
}
|
||
|
||
let dataToDecrypt = null;
|
||
if (isRequest) {
|
||
if (encryptedData && encryptedData.data && typeof encryptedData.data === 'string') {
|
||
dataToDecrypt = encryptedData.data;
|
||
} else {
|
||
return null;
|
||
}
|
||
} else {
|
||
if (typeof encryptedData === 'string') {
|
||
dataToDecrypt = encryptedData;
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
if (!dataToDecrypt) {
|
||
return null;
|
||
}
|
||
|
||
// 清理数据
|
||
const cleanData = dataToDecrypt.replace(/\s/g, '').replace(/"/g, '');
|
||
const cleanKey = key.replace(/\s/g, '').replace(/"/g, '');
|
||
|
||
// AES 解密
|
||
const decrypt = CryptoJS.AES.decrypt(
|
||
cleanData,
|
||
CryptoJS.enc.Utf8.parse(cleanKey),
|
||
{
|
||
mode: CryptoJS.mode.ECB,
|
||
padding: CryptoJS.pad.Pkcs7
|
||
}
|
||
);
|
||
|
||
const decrypted = decrypt.toString(CryptoJS.enc.Utf8);
|
||
if (!decrypted) {
|
||
return null;
|
||
}
|
||
|
||
// 尝试解析 JSON
|
||
try {
|
||
return JSON.parse(decrypted);
|
||
} catch (e) {
|
||
if (window.JSON5) {
|
||
return window.JSON5.parse(decrypted);
|
||
}
|
||
return decrypted;
|
||
}
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 可以在这里添加更多解密策略
|
||
// 例如:其他加密算法、不同的填充方式等
|
||
}
|
||
};
|
||
|
||
// 初始化策略管理器
|
||
KeyGetStrategies.init();
|
||
DecryptStrategies.init();
|
||
|
||
// ========== 便捷函数 ==========
|
||
|
||
// 获取密钥
|
||
function getTimestampKey(url, uuid, requestHeaders) {
|
||
const result = KeyGetStrategies.getKey(url, uuid, requestHeaders);
|
||
return result ? result.key : null;
|
||
}
|
||
|
||
// 解密请求数据
|
||
function decryptRequestData(encryptedData, timestampKey) {
|
||
if (!timestampKey) {
|
||
return encryptedData;
|
||
}
|
||
const result = DecryptStrategies.decrypt(encryptedData, timestampKey, true);
|
||
return result.data;
|
||
}
|
||
|
||
// 解密响应数据
|
||
function decryptResponseData(encryptedData, timestampKey) {
|
||
if (!timestampKey) {
|
||
return encryptedData;
|
||
}
|
||
const result = DecryptStrategies.decrypt(encryptedData, timestampKey, false);
|
||
return result.data;
|
||
}
|
||
|
||
// 暴露策略管理器,允许外部注册新策略
|
||
window.__XHR_DECRYPT_STRATEGIES__ = {
|
||
keyGetStrategies: KeyGetStrategies,
|
||
decryptStrategies: DecryptStrategies
|
||
};
|
||
|
||
// ========== 拦截 XMLHttpRequest ==========
|
||
// 必须在页面脚本运行前拦截,否则会被覆盖
|
||
(function() {
|
||
if (typeof XMLHttpRequest === 'undefined') {
|
||
console.warn('⚠️ [页面上下文] XMLHttpRequest 未定义');
|
||
return;
|
||
}
|
||
|
||
// 保存原始函数(必须在任何其他脚本运行前保存)
|
||
const originalXHR = window.XMLHttpRequest;
|
||
const originalOpen = XMLHttpRequest.prototype.open;
|
||
const originalSend = XMLHttpRequest.prototype.send;
|
||
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
||
|
||
// 检查是否已经被拦截过
|
||
const openStr = originalOpen.toString();
|
||
if (openStr.includes('_method') || openStr.includes('__XHR_DECRYPT')) {
|
||
console.log('⚠️ [页面上下文] XHR 可能已被其他脚本拦截');
|
||
}
|
||
|
||
XMLHttpRequest.prototype.open = function(method, url, ...args) {
|
||
this._method = method;
|
||
this._url = url;
|
||
this._requestHeaders = {};
|
||
requestHeadersMap.set(this, {});
|
||
console.log('🔍 [页面上下文] 拦截 XHR open:', method, url);
|
||
// 确保返回正确的结果
|
||
const result = originalOpen.apply(this, [method, url, ...args]);
|
||
return result;
|
||
};
|
||
|
||
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 keyResult = KeyGetStrategies.getKey(url, uuid, headers);
|
||
if (!keyResult || !keyResult.key) {
|
||
console.warn('无法获取密钥', { url, uuid, headers });
|
||
return;
|
||
}
|
||
const timestampKey = keyResult.key;
|
||
const keyStrategy = keyResult.strategy;
|
||
|
||
// 解密请求数据(包含策略信息)
|
||
let requestBody = null;
|
||
let requestDecryptStrategy = null;
|
||
if (body) {
|
||
try {
|
||
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
||
const decryptResult = DecryptStrategies.decrypt(parsed, timestampKey, true);
|
||
requestBody = decryptResult.data;
|
||
requestDecryptStrategy = decryptResult.strategy;
|
||
} catch (e) {
|
||
requestBody = body;
|
||
}
|
||
}
|
||
|
||
// 解密响应数据(包含策略信息)
|
||
let responseData = null;
|
||
try {
|
||
if (this.responseText) {
|
||
responseData = JSON.parse(this.responseText);
|
||
}
|
||
} catch (e) {
|
||
responseData = this.responseText || this.response;
|
||
}
|
||
|
||
const decryptResult = DecryptStrategies.decrypt(responseData, timestampKey, false);
|
||
const decryptedResponse = decryptResult.data;
|
||
const responseDecryptStrategy = decryptResult.strategy;
|
||
|
||
// 记录解密结果
|
||
const decryptedRecord = {
|
||
id: requestId,
|
||
url: url,
|
||
method: method,
|
||
uuid: uuid,
|
||
timestampKey: timestampKey,
|
||
keyStrategy: keyStrategy,
|
||
request: requestBody,
|
||
requestDecryptStrategy: requestDecryptStrategy,
|
||
response: decryptedResponse,
|
||
responseDecryptStrategy: responseDecryptStrategy,
|
||
timestamp: new Date().toISOString()
|
||
};
|
||
|
||
// 存储到页面上下文
|
||
decryptedData.addRequest(decryptedRecord);
|
||
|
||
// 输出到控制台
|
||
console.group('🔓 XHR 解密 [' + method + '] ' + url);
|
||
console.log('🔑 密钥:', timestampKey, '(策略: ' + keyStrategy + ')');
|
||
if (requestDecryptStrategy) {
|
||
console.log('📤 请求:', requestBody, '(解密策略: ' + requestDecryptStrategy + ')');
|
||
} else {
|
||
console.log('📤 请求:', requestBody);
|
||
}
|
||
if (responseDecryptStrategy) {
|
||
console.log('📥 响应:', decryptedResponse, '(解密策略: ' + responseDecryptStrategy + ')');
|
||
} else {
|
||
console.log('📥 响应:', decryptedResponse);
|
||
}
|
||
console.log('📍 UUID:', uuid);
|
||
console.groupEnd();
|
||
|
||
} catch (error) {
|
||
console.error('❌ 解密过程出错:', error);
|
||
}
|
||
});
|
||
|
||
return originalSend.apply(this, [body]);
|
||
};
|
||
|
||
console.log('✅ [页面上下文] XHR 拦截已设置');
|
||
console.log('✅ [页面上下文] originalOpen 类型:', typeof originalOpen);
|
||
console.log('✅ [页面上下文] XMLHttpRequest.prototype.open:', typeof XMLHttpRequest.prototype.open);
|
||
|
||
// 验证拦截是否真的生效
|
||
setTimeout(function() {
|
||
const testXhr = new XMLHttpRequest();
|
||
const testOpenStr = testXhr.open.toString();
|
||
if (testOpenStr.includes('_method')) {
|
||
console.log('✅ [页面上下文] XHR 拦截验证成功');
|
||
} else {
|
||
console.error('❌ [页面上下文] XHR 拦截验证失败,可能被覆盖');
|
||
console.error(' 当前 open 函数:', testOpenStr.substring(0, 100));
|
||
}
|
||
}, 500);
|
||
})();
|
||
|
||
// ========== 拦截 Fetch ==========
|
||
// 必须在页面脚本运行前拦截
|
||
(function() {
|
||
if (typeof fetch === 'undefined' || !window.fetch) {
|
||
console.warn('⚠️ [页面上下文] fetch 未定义');
|
||
return;
|
||
}
|
||
|
||
// 保存原始函数
|
||
const originalFetch = window.fetch;
|
||
|
||
// 检查是否已经被拦截过
|
||
const fetchStr = originalFetch.toString();
|
||
if (fetchStr.includes('clonedResponse') || fetchStr.includes('__XHR_DECRYPT')) {
|
||
console.log('⚠️ [页面上下文] 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 keyResult = KeyGetStrategies.getKey(url, uuid, { time: timestamp, TraceId: traceId });
|
||
if (!keyResult || !keyResult.key) {
|
||
console.warn('无法获取密钥 (Fetch)', { url, uuid });
|
||
return response;
|
||
}
|
||
const timestampKey = keyResult.key;
|
||
const keyStrategy = keyResult.strategy;
|
||
|
||
// 解密请求数据(包含策略信息)
|
||
let requestBody = null;
|
||
let requestDecryptStrategy = null;
|
||
if (originalBody) {
|
||
try {
|
||
if (typeof originalBody === 'string') {
|
||
const parsed = JSON.parse(originalBody);
|
||
const decryptResult = DecryptStrategies.decrypt(parsed, timestampKey, true);
|
||
requestBody = decryptResult.data;
|
||
requestDecryptStrategy = decryptResult.strategy;
|
||
} 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 decryptResult = DecryptStrategies.decrypt(responseData, timestampKey, false);
|
||
const decryptedResponse = decryptResult.data;
|
||
const responseDecryptStrategy = decryptResult.strategy;
|
||
|
||
// 记录解密结果
|
||
const decryptedRecord = {
|
||
id: requestId,
|
||
url: url,
|
||
method: method,
|
||
uuid: uuid,
|
||
timestampKey: timestampKey,
|
||
keyStrategy: keyStrategy,
|
||
request: requestBody,
|
||
requestDecryptStrategy: requestDecryptStrategy,
|
||
response: decryptedResponse,
|
||
responseDecryptStrategy: responseDecryptStrategy,
|
||
timestamp: new Date().toISOString()
|
||
};
|
||
|
||
// 存储到页面上下文
|
||
decryptedData.addRequest(decryptedRecord);
|
||
|
||
// 输出到控制台
|
||
console.group('🔓 Fetch 解密 [' + method + '] ' + url);
|
||
console.log('🔑 密钥:', timestampKey, '(策略: ' + keyStrategy + ')');
|
||
if (requestDecryptStrategy) {
|
||
console.log('📤 请求:', requestBody, '(解密策略: ' + requestDecryptStrategy + ')');
|
||
} else {
|
||
console.log('📤 请求:', requestBody);
|
||
}
|
||
if (responseDecryptStrategy) {
|
||
console.log('📥 响应:', decryptedResponse, '(解密策略: ' + responseDecryptStrategy + ')');
|
||
} else {
|
||
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 拦截已设置');
|
||
console.log('✅ [页面上下文] originalFetch 类型:', typeof originalFetch);
|
||
console.log('✅ [页面上下文] window.fetch:', typeof window.fetch);
|
||
|
||
// 验证拦截是否真的生效
|
||
setTimeout(function() {
|
||
const fetchStr = window.fetch.toString();
|
||
if (fetchStr.includes('clonedResponse') || fetchStr.includes('requestId')) {
|
||
console.log('✅ [页面上下文] Fetch 拦截验证成功');
|
||
} else {
|
||
console.error('❌ [页面上下文] Fetch 拦截验证失败,可能被覆盖');
|
||
console.error(' 当前 fetch 函数:', fetchStr.substring(0, 100));
|
||
}
|
||
}, 500);
|
||
})();
|
||
|
||
// 暴露 API 供 DevTools 面板使用
|
||
window.__XHR_DECRYPT_EXTENSION__ = {
|
||
getDecryptedRequests: function() {
|
||
return decryptedData.getRequests();
|
||
},
|
||
clearDecryptedRequests: function() {
|
||
decryptedData.clear();
|
||
console.log('✅ 已清空解密记录');
|
||
}
|
||
};
|
||
|
||
console.log('✅ [页面上下文] 拦截脚本初始化完成');
|
||
console.log('✅ [页面上下文] 已拦截 XHR:', typeof XMLHttpRequest !== 'undefined');
|
||
console.log('✅ [页面上下文] 已拦截 Fetch:', typeof fetch !== 'undefined');
|
||
|
||
// 测试一下拦截是否真的工作
|
||
setTimeout(function() {
|
||
console.log('🧪 [页面上下文] 测试:检查拦截状态');
|
||
console.log('🧪 [页面上下文] XMLHttpRequest.prototype.open:', typeof XMLHttpRequest.prototype.open);
|
||
console.log('🧪 [页面上下文] window.fetch:', typeof window.fetch);
|
||
|
||
// 尝试创建一个测试请求(仅用于验证)
|
||
try {
|
||
const testXhr = new XMLHttpRequest();
|
||
console.log('🧪 [页面上下文] 测试:可以创建 XMLHttpRequest 实例');
|
||
} catch (e) {
|
||
console.error('🧪 [页面上下文] 测试:无法创建 XMLHttpRequest:', e);
|
||
}
|
||
}, 1000);
|
||
})();
|
||
`;
|
||
|
||
// 注入到页面上下文
|
||
// 必须在页面脚本运行之前注入,否则无法拦截
|
||
function injectToPage() {
|
||
try {
|
||
// 方式1:尝试注入到 head
|
||
if (document.head) {
|
||
document.head.appendChild(script.cloneNode(true));
|
||
console.log('✅ 拦截脚本已注入到 head');
|
||
return;
|
||
}
|
||
|
||
// 方式2:注入到 documentElement
|
||
if (document.documentElement) {
|
||
document.documentElement.appendChild(script.cloneNode(true));
|
||
console.log('✅ 拦截脚本已注入到 documentElement');
|
||
return;
|
||
}
|
||
|
||
// 方式3:如果都不行,等待 DOM 加载
|
||
console.warn('⚠️ DOM 未准备好,等待加载...');
|
||
const checkInterval = setInterval(function () {
|
||
if (document.head || document.documentElement) {
|
||
clearInterval(checkInterval);
|
||
injectToPage();
|
||
}
|
||
}, 50);
|
||
|
||
// 超时保护
|
||
setTimeout(function () {
|
||
clearInterval(checkInterval);
|
||
console.error('❌ 注入超时,DOM 可能无法加载');
|
||
}, 5000);
|
||
|
||
} catch (e) {
|
||
console.error('❌ 注入脚本失败:', e);
|
||
}
|
||
}
|
||
|
||
// 立即尝试注入(不等待)
|
||
try {
|
||
// 如果 DOM 已准备好,立即注入
|
||
if (document.head || document.documentElement) {
|
||
injectToPage();
|
||
} else {
|
||
// 如果还没准备好,使用多种方式等待
|
||
console.log('⏳ DOM 未准备好,准备注入...');
|
||
|
||
// 方式1:等待 DOMContentLoaded
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
console.log('📄 DOMContentLoaded 触发,开始注入');
|
||
injectToPage();
|
||
}, { once: true });
|
||
}
|
||
|
||
// 方式2:也尝试立即注入(某些浏览器允许)
|
||
setTimeout(function () {
|
||
if (document.head || document.documentElement) {
|
||
console.log('⏱️ 延迟注入尝试');
|
||
injectToPage();
|
||
}
|
||
}, 0);
|
||
|
||
// 方式3:监听 readystatechange
|
||
document.addEventListener('readystatechange', function () {
|
||
if (document.readyState !== 'loading') {
|
||
console.log('📄 readystatechange 触发,readyState:', document.readyState);
|
||
injectToPage();
|
||
}
|
||
}, { once: true });
|
||
}
|
||
} catch (e) {
|
||
console.error('❌ 注入过程出错:', e);
|
||
}
|
||
|
||
// 在 Content Script 中监听来自页面上下文的事件
|
||
window.addEventListener('__XHR_DECRYPTED__', function (event) {
|
||
if (event.detail) {
|
||
decryptedRequests.push(event.detail);
|
||
if (decryptedRequests.length > 100) {
|
||
decryptedRequests.shift();
|
||
}
|
||
|
||
// 发送到 background
|
||
if (typeof chrome !== 'undefined' && chrome.runtime) {
|
||
chrome.runtime.sendMessage({
|
||
type: 'DECRYPTED_REQUEST',
|
||
data: event.detail
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
// 监听来自 popup 的消息
|
||
if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) {
|
||
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
|
||
if (message.action === 'getDecryptedRequests') {
|
||
// 直接返回 content script 中存储的数据
|
||
// content script 会从页面事件中接收更新
|
||
sendResponse({ data: decryptedRequests });
|
||
return true;
|
||
} else if (message.action === 'clearDecryptedRequests') {
|
||
decryptedRequests = [];
|
||
// 清除页面上下文的记录
|
||
const clearScript = document.createElement('script');
|
||
clearScript.textContent = `
|
||
(function() {
|
||
if (window.__XHR_DECRYPT_EXTENSION__) {
|
||
window.__XHR_DECRYPT_EXTENSION__.clearDecryptedRequests();
|
||
}
|
||
})();
|
||
`;
|
||
document.documentElement.appendChild(clearScript);
|
||
clearScript.remove();
|
||
sendResponse({ success: true });
|
||
}
|
||
});
|
||
}
|
||
|
||
// 暴露 API 供 popup 使用(Content Script 上下文)
|
||
window.__XHR_DECRYPT_EXTENSION__ = {
|
||
getDecryptedRequests: function () {
|
||
// 返回 content script 中存储的数据
|
||
return decryptedRequests;
|
||
},
|
||
clearDecryptedRequests: function () {
|
||
decryptedRequests = [];
|
||
// 清除页面上下文的记录
|
||
const clearScript = document.createElement('script');
|
||
clearScript.textContent = `
|
||
(function() {
|
||
if (window.__XHR_DECRYPT_EXTENSION__) {
|
||
window.__XHR_DECRYPT_EXTENSION__.clearDecryptedRequests();
|
||
}
|
||
})();
|
||
`;
|
||
document.documentElement.appendChild(clearScript);
|
||
clearScript.remove();
|
||
}
|
||
};
|
||
|
||
console.log('✅ Content Script 已启动');
|
||
console.log('📊 Content Script 当前记录数:', decryptedRequests.length);
|
||
console.log('📊 Content Script document.readyState:', document.readyState);
|
||
console.log('📊 Content Script 准备注入拦截脚本...');
|
||
console.log('📊 Content Script window.location:', window.location.href);
|
||
|
||
// 立即检查是否已经注入成功(某些情况下可能已经注入)
|
||
try {
|
||
const testScript = document.createElement('script');
|
||
testScript.textContent = `
|
||
(function() {
|
||
if (window.__XHR_DECRYPT_EXTENSION__) {
|
||
console.log('✅ [页面上下文] 拦截对象已存在');
|
||
} else {
|
||
console.warn('⚠️ [页面上下文] 拦截对象不存在');
|
||
}
|
||
|
||
if (window.__XHR_DECRYPT_DATA__) {
|
||
console.log('✅ [页面上下文] 数据对象已存在');
|
||
} else {
|
||
console.warn('⚠️ [页面上下文] 数据对象不存在');
|
||
}
|
||
})();
|
||
`;
|
||
if (document.head || document.documentElement) {
|
||
(document.head || document.documentElement).appendChild(testScript);
|
||
testScript.remove();
|
||
}
|
||
} catch (e) {
|
||
console.error('❌ 检查注入状态失败:', e);
|
||
}
|
||
|
||
// 延迟检查注入是否成功
|
||
setTimeout(function () {
|
||
// 通过 DevTools API 检查
|
||
if (typeof chrome !== 'undefined' && chrome.devtools && chrome.devtools.inspectedWindow) {
|
||
chrome.devtools.inspectedWindow.eval('typeof window.__XHR_DECRYPT_EXTENSION__', function (result, isException) {
|
||
if (isException) {
|
||
console.warn('⚠️ 无法访问页面上下文,可能注入失败。错误:', result);
|
||
} else {
|
||
if (result === 'object') {
|
||
console.log('✅ 拦截脚本注入成功,页面上下文对象存在');
|
||
} else {
|
||
console.warn('⚠️ 拦截脚本可能未注入成功,页面上下文对象类型:', result);
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
// 如果没有 DevTools,使用其他方式检查
|
||
const checkScript = document.createElement('script');
|
||
checkScript.textContent = `
|
||
(function() {
|
||
console.log('🔍 [页面上下文] 检查拦截状态...');
|
||
console.log('🔍 [页面上下文] __XHR_DECRYPT_EXTENSION__:', typeof window.__XHR_DECRYPT_EXTENSION__);
|
||
console.log('🔍 [页面上下文] __XHR_DECRYPT_DATA__:', typeof window.__XHR_DECRYPT_DATA__);
|
||
console.log('🔍 [页面上下文] XMLHttpRequest:', typeof XMLHttpRequest);
|
||
console.log('🔍 [页面上下文] fetch:', typeof fetch);
|
||
|
||
// 检查拦截是否真的工作
|
||
if (XMLHttpRequest.prototype.open.toString().includes('_method')) {
|
||
console.log('✅ [页面上下文] XHR 拦截已生效');
|
||
} else {
|
||
console.warn('⚠️ [页面上下文] XHR 拦截可能未生效');
|
||
}
|
||
|
||
if (window.fetch.toString().includes('clonedResponse') || window.fetch.toString().includes('requestId')) {
|
||
console.log('✅ [页面上下文] Fetch 拦截已生效');
|
||
} else {
|
||
console.warn('⚠️ [页面上下文] Fetch 拦截可能未生效');
|
||
}
|
||
})();
|
||
`;
|
||
if (document.head || document.documentElement) {
|
||
(document.head || document.documentElement).appendChild(checkScript);
|
||
checkScript.remove();
|
||
}
|
||
}
|
||
}, 3000);
|
||
}) ();
|