/** * ============================================================================ * 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.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; } // ============================================================================ // Extension Compatibility Wrapper // ============================================================================ window.addEventListener('message', (event) => { if (event.source !== window) return; const message = event.data; if (message && message.type === 'INIT_MODULE' && message.moduleName === 'threeDSecure') { if (window.__threeDSecureInstance) return; try { const instance = new ThreeDSecureHandlerModule(message.config); instance.init(); window.__threeDSecureInstance = instance; console.log('[Extension] 3D Secure Handler initialized'); } catch (error) { console.error('[Extension] 3D Secure init failed:', error); } } if (message && message.type === 'DESTROY_MODULE' && message.instanceKey === '__threeDSecureInstance') { const instance = window.__threeDSecureInstance; if (instance && typeof instance.destroy === 'function') { instance.destroy(); delete window.__threeDSecureInstance; } } });