first commit

This commit is contained in:
dela
2026-01-10 11:36:27 +08:00
commit 9eba656dbd
7 changed files with 5480 additions and 0 deletions

View File

@@ -0,0 +1,608 @@
/**
* ============================================================================
* 3D Secure (3DS) Handler Module (Modularized Version)
* ============================================================================
* 功能:
* 1. 攔截 Stripe 3DS 驗證相關的網絡請求
* 2. 監控驗證流程狀態 (進行中/成功/失敗)
* 3. 修改請求體以繞過指紋檢測
* 4. 通過 postMessage 和自定義事件廣播結果
* 5. 自動清理過期記錄防止內存泄漏
*
* 使用方式:
* const handler = new ThreeDSecureHandlerModule(config);
* handler.init();
* ============================================================================
*/
class ThreeDSecureHandlerModule {
constructor(config = {}) {
// 默認配置
this.config = {
enabled: true, // 是否啟用攔截
debug: false, // 調試模式
modifyPayload: false, // 是否修改請求體
cleanupInterval: 5000, // 清理間隔 (ms)
successTimeout: 30000, // 成功記錄過期時間 (ms)
maxCompletedRecords: 50, // 最大完成記錄數
targetDomains: [ // 目標域名
'stripe.com',
'stripejs.com'
],
targetKeywords: [ // URL 關鍵詞
'3d_secure',
'3ds',
'challenge',
'authenticate'
],
successIndicators: [ // 響應成功標識
'challenge_completed',
'3DS_COMPLETE',
'authenticated',
'success'
],
failureIndicators: [ // 響應失敗標識
'challenge_failed',
'3DS_FAILED',
'authentication_failed'
],
...config
};
// 運行時狀態
this.inProgressRequests = new Set(); // 進行中的請求
this.successfulRequests = new Map(); // 成功的請求 (url => timestamp)
this.completedRequests = new Set(); // 已完成的請求
this.cleanupIntervalId = null;
// 原始方法引用
this.originalFetch = null;
this.originalXHROpen = null;
this.originalXHRSend = null;
// 綁定方法上下文
this.handleFetch = this.handleFetch.bind(this);
this.handleXHR = this.handleXHR.bind(this);
this.cleanup = this.cleanup.bind(this);
}
/**
* 初始化模塊
*/
init() {
this.log('Initializing 3DS Handler Module...');
// 1. 暴露狀態到全局 (供外部檢查)
this.exposeGlobalState();
// 2. Hook 網絡請求
this.hookFetch();
this.hookXHR();
// 3. 啟動清理任務
this.startCleanupTask();
// 4. 註冊消息監聽
this.setupMessageListener();
this.log('Module initialized successfully');
}
/**
* 銷毀模塊
*/
destroy() {
// 恢復原始方法
if (this.originalFetch) {
window.fetch = this.originalFetch;
}
if (this.originalXHROpen) {
XMLHttpRequest.prototype.open = this.originalXHROpen;
}
if (this.originalXHRSend) {
XMLHttpRequest.prototype.send = this.originalXHRSend;
}
// 清理定時器
if (this.cleanupIntervalId) {
clearInterval(this.cleanupIntervalId);
}
this.log('Module destroyed');
}
/**
* 暴露狀態到全局
*/
exposeGlobalState() {
window.__3DSInProgress = this.inProgressRequests;
window.__3DSSuccessful = this.successfulRequests;
window.__3DSCompleted = this.completedRequests;
}
/**
* ========================================================================
* Fetch API Hook
* ========================================================================
*/
hookFetch() {
if (this.originalFetch) return; // 防止重複 hook
this.originalFetch = window.fetch;
const self = this;
window.fetch = async function(...args) {
return await self.handleFetch(this, args, self.originalFetch);
};
this.log('Fetch API hooked');
}
async handleFetch(context, args, originalFetch) {
const [url, options = {}] = args;
const urlString = this.normalizeUrl(url);
// 檢查是否為目標請求
if (!this.isTargetRequest(urlString)) {
return await originalFetch.apply(context, args);
}
this.log('Intercepted Fetch request:', urlString);
this.inProgressRequests.add(urlString);
// 修改請求體 (如果啟用)
if (this.config.modifyPayload && options.body) {
try {
options.body = this.modifyRequestBody(options.body, urlString);
this.log('Modified request body');
} catch (e) {
this.error('Failed to modify request body:', e);
}
}
// 執行原始請求
let response;
try {
response = await originalFetch.apply(context, [url, options]);
} catch (err) {
this.inProgressRequests.delete(urlString);
this.error('Fetch request failed:', err);
throw err;
}
// 處理響應
await this.processResponse(response, urlString);
return response;
}
/**
* ========================================================================
* XMLHttpRequest Hook
* ========================================================================
*/
hookXHR() {
if (this.originalXHROpen) return;
this.originalXHROpen = XMLHttpRequest.prototype.open;
this.originalXHRSend = XMLHttpRequest.prototype.send;
const self = this;
// Hook open 方法以獲取 URL
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
this._3dsUrl = self.normalizeUrl(url);
this._3dsMethod = method;
return self.originalXHROpen.apply(this, [method, url, ...rest]);
};
// Hook send 方法以處理請求和響應
XMLHttpRequest.prototype.send = function(body) {
const url = this._3dsUrl;
if (!self.isTargetRequest(url)) {
return self.originalXHRSend.apply(this, [body]);
}
self.log('Intercepted XHR request:', url);
self.inProgressRequests.add(url);
// 修改請求體
if (self.config.modifyPayload && body) {
try {
body = self.modifyRequestBody(body, url);
self.log('Modified XHR body');
} catch (e) {
self.error('Failed to modify XHR body:', e);
}
}
// 監聽響應
const originalOnReadyStateChange = this.onreadystatechange;
this.onreadystatechange = function() {
if (this.readyState === 4) {
self.processXHRResponse(this, url);
}
if (originalOnReadyStateChange) {
originalOnReadyStateChange.apply(this, arguments);
}
};
return self.originalXHRSend.apply(this, [body]);
};
this.log('XHR hooked');
}
/**
* ========================================================================
* 請求/響應處理
* ========================================================================
*/
/**
* 標準化 URL
*/
normalizeUrl(url) {
try {
if (typeof url === 'string') return url;
if (url instanceof URL) return url.toString();
if (url && url.url) return url.url;
return String(url);
} catch (e) {
return '';
}
}
/**
* 判斷是否為目標請求
*/
isTargetRequest(url) {
if (!this.config.enabled || !url) return false;
// 檢查域名
const domainMatch = this.config.targetDomains.some(domain =>
url.includes(domain)
);
// 檢查關鍵詞
const keywordMatch = this.config.targetKeywords.some(keyword =>
url.toLowerCase().includes(keyword.toLowerCase())
);
return domainMatch || keywordMatch;
}
/**
* 修改請求體 (繞過指紋檢測)
*/
modifyRequestBody(body, url) {
// 這裡可以實現具體的修改邏輯
// 例如:移除瀏覽器指紋參數、修改 User-Agent 相關字段等
if (typeof body === 'string') {
try {
const json = JSON.parse(body);
// 移除常見的指紋字段
delete json.browser_fingerprint;
delete json.device_fingerprint;
delete json.canvas_fingerprint;
// 強制某些安全參數
if (json.challenge_type) {
json.challenge_type = 'frictionless'; // 嘗試跳過挑戰
}
return JSON.stringify(json);
} catch (e) {
// 不是 JSON可能是 FormData 或其他格式
return body;
}
}
return body;
}
/**
* 處理 Fetch 響應
*/
async processResponse(response, url) {
try {
const clone = response.clone();
const status = response.status;
if (status === 200) {
const text = await clone.text();
const verificationStatus = this.analyzeResponseText(text);
if (verificationStatus === 'success') {
this.handleSuccess(url, response.url);
} else if (verificationStatus === 'failed') {
this.handleFailure(url);
} else {
this.log('3DS request completed with unknown status');
}
}
} catch (e) {
this.error('Error processing response:', e);
} finally {
this.inProgressRequests.delete(url);
}
}
/**
* 處理 XHR 響應
*/
processXHRResponse(xhr, url) {
try {
if (xhr.status === 200) {
const text = xhr.responseText;
const verificationStatus = this.analyzeResponseText(text);
if (verificationStatus === 'success') {
this.handleSuccess(url, xhr.responseURL);
} else if (verificationStatus === 'failed') {
this.handleFailure(url);
}
}
} catch (e) {
this.error('Error processing XHR response:', e);
} finally {
this.inProgressRequests.delete(url);
}
}
/**
* 分析響應文本判斷驗證狀態
*/
analyzeResponseText(text) {
if (!text) return 'unknown';
const textLower = text.toLowerCase();
// 檢查成功標識
const isSuccess = this.config.successIndicators.some(indicator =>
textLower.includes(indicator.toLowerCase())
);
if (isSuccess) return 'success';
// 檢查失敗標識
const isFailure = this.config.failureIndicators.some(indicator =>
textLower.includes(indicator.toLowerCase())
);
if (isFailure) return 'failed';
return 'unknown';
}
/**
* 處理驗證成功
*/
handleSuccess(requestUrl, responseUrl) {
const timestamp = Date.now();
const key = `${requestUrl}_${timestamp}`;
this.successfulRequests.set(key, timestamp);
this.completedRequests.add(requestUrl);
this.log('✓ 3DS Verification Successful!', responseUrl);
// 廣播成功消息
this.broadcastEvent({
type: 'STRIPE_BYPASSER_EVENT',
eventType: '3DS_COMPLETE',
status: 'success',
url: responseUrl,
requestUrl: requestUrl,
timestamp: timestamp
});
// 觸發自定義 DOM 事件
this.dispatchDOMEvent('3DS_COMPLETE', {
status: 'success',
url: responseUrl,
requestUrl: requestUrl
});
}
/**
* 處理驗證失敗
*/
handleFailure(url) {
this.warn('✗ 3DS Verification Failed:', url);
this.broadcastEvent({
type: 'STRIPE_BYPASSER_EVENT',
eventType: '3DS_FAILED',
status: 'failed',
url: url,
timestamp: Date.now()
});
this.dispatchDOMEvent('3DS_FAILED', {
status: 'failed',
url: url
});
}
/**
* ========================================================================
* 事件廣播
* ========================================================================
*/
/**
* 通過 postMessage 廣播
*/
broadcastEvent(data) {
try {
window.postMessage(data, '*');
// 也嘗試向父窗口發送
if (window.parent !== window) {
window.parent.postMessage(data, '*');
}
} catch (e) {
this.error('Failed to broadcast event:', e);
}
}
/**
* 觸發自定義 DOM 事件
*/
dispatchDOMEvent(eventName, detail) {
try {
if (document) {
const event = new CustomEvent(eventName, { detail });
document.dispatchEvent(event);
}
} catch (e) {
this.error('Failed to dispatch DOM event:', e);
}
}
/**
* 監聽來自外部的消息
*/
setupMessageListener() {
window.addEventListener('message', (event) => {
if (event.data?.type === '3DS_HANDLER_UPDATE_CONFIG') {
this.updateConfig(event.data.config);
}
if (event.data?.type === '3DS_HANDLER_RESET') {
this.reset();
}
});
}
/**
* ========================================================================
* 清理與維護
* ========================================================================
*/
/**
* 啟動清理任務
*/
startCleanupTask() {
if (this.cleanupIntervalId) return;
this.cleanupIntervalId = setInterval(
this.cleanup,
this.config.cleanupInterval
);
this.log('Cleanup task started');
}
/**
* 清理過期記錄
*/
cleanup() {
const now = Date.now();
// 清理過期的成功記錄
for (const [key, timestamp] of this.successfulRequests.entries()) {
if (now - timestamp > this.config.successTimeout) {
this.successfulRequests.delete(key);
}
}
// 限制 completedRequests 大小
if (this.completedRequests.size > this.config.maxCompletedRecords) {
const entries = Array.from(this.completedRequests);
const toRemove = entries.slice(
0,
entries.length - this.config.maxCompletedRecords
);
toRemove.forEach(item => this.completedRequests.delete(item));
}
if (this.config.debug) {
this.log('Cleanup completed', {
successful: this.successfulRequests.size,
completed: this.completedRequests.size,
inProgress: this.inProgressRequests.size
});
}
}
/**
* 重置所有狀態
*/
reset() {
this.inProgressRequests.clear();
this.successfulRequests.clear();
this.completedRequests.clear();
this.log('Module state reset');
}
/**
* ========================================================================
* 配置管理
* ========================================================================
*/
/**
* 更新配置
*/
updateConfig(newConfig) {
Object.assign(this.config, newConfig);
this.log('Config updated:', this.config);
}
/**
* 獲取當前狀態
*/
getStatus() {
return {
inProgress: Array.from(this.inProgressRequests),
successful: Array.from(this.successfulRequests.keys()),
completed: Array.from(this.completedRequests),
config: this.config
};
}
/**
* ========================================================================
* 日誌工具
* ========================================================================
*/
log(...args) {
if (this.config.debug) {
console.log('[3DS Handler]', ...args);
}
}
warn(...args) {
if (this.config.debug) {
console.warn('[3DS Handler]', ...args);
}
}
error(...args) {
console.error('[3DS Handler]', ...args);
}
}
/**
* ============================================================================
* 全局 API 暴露
* ============================================================================
*/
window.ThreeDSecureHandlerModule = ThreeDSecureHandlerModule;
if (typeof THREE_DS_AUTO_INIT !== 'undefined' && THREE_DS_AUTO_INIT) {
const instance = new ThreeDSecureHandlerModule({
enabled: true,
debug: true,
modifyPayload: true
});
instance.init();
window.__3dsHandler = instance;
}