/* eslint-disable */ /** * Network Capture Helper * * This script helps replay network requests with the original cookies and headers. */ // Prevent duplicate initialization if (window.__NETWORK_CAPTURE_HELPER_INITIALIZED__) { // Already initialized, skip } else { window.__NETWORK_CAPTURE_HELPER_INITIALIZED__ = true; /** * Replay a network request * @param {string} url - The URL to send the request to * @param {string} method - The HTTP method to use * @param {Object} headers - The headers to include in the request * @param {any} body - The body of the request * @param {number} timeout - Timeout in milliseconds (default: 30000) * @returns {Promise} - The response data */ async function replayNetworkRequest( url, method, headers, body, timeout = 30000, formDataDescriptor = null, ) { try { // Create fetch options const options = { method: method, headers: headers || {}, credentials: 'include', // Include cookies mode: 'cors', cache: 'no-cache', }; // Helper: convert base64 to Blob const base64ToBlob = (base64, contentType = 'application/octet-stream') => { try { const decodedString = atob(base64); const len = decodedString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) bytes[i] = decodedString.charCodeAt(i); return new Blob([bytes], { type: contentType }); } catch (e) { return new Blob([]); } }; // Helper: request native to read filePath into base64 const readFileBase64 = (path) => new Promise((resolve) => { const requestId = `net-helper-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const timeoutId = setTimeout(() => { cleanup(); resolve(null); }, 30000); function onMessage(msg) { if ( msg && msg.type === 'file_operation_response' && msg.responseToRequestId === requestId ) { cleanup(); const p = msg.payload || {}; if (p.success && p.base64Data) resolve({ base64: p.base64Data, fileName: p.fileName }); else resolve(null); } } function cleanup() { clearTimeout(timeoutId); chrome.runtime.onMessage.removeListener(onMessage); } chrome.runtime.onMessage.addListener(onMessage); chrome.runtime .sendMessage({ type: 'forward_to_native', message: { type: 'file_operation', requestId, payload: { action: 'readBase64File', filePath: path }, }, }) .catch(() => { cleanup(); resolve(null); }); }); // Build multipart/form-data if descriptor is provided if (method !== 'GET' && method !== 'HEAD' && formDataDescriptor) { const fd = new FormData(); try { if (Array.isArray(formDataDescriptor)) { for (const item of formDataDescriptor) { if (!Array.isArray(item) || item.length < 2) continue; const name = String(item[0] || 'file'); const spec = String(item[1] || ''); const filenameHint = item[2] ? String(item[2]) : undefined; if (/^(https?:\/\/|url:)/i.test(spec)) { const url = spec.replace(/^url:/i, ''); const resp = await fetch(url); const blob = await resp.blob(); const fn = filenameHint || url.split('?')[0].split('#')[0].split('/').pop() || 'file'; fd.append(name, blob, fn); } else if (/^base64:/i.test(spec)) { const b64 = spec.replace(/^base64:/i, ''); const blob = base64ToBlob(b64); fd.append(name, blob, filenameHint || 'file'); } else if (/^file:/i.test(spec)) { const p = spec.replace(/^file:/i, ''); const res = await readFileBase64(p); if (res && res.base64) { const blob = base64ToBlob(res.base64); fd.append(name, blob, filenameHint || res.fileName || 'file'); } } else { // treat as string field fd.append(name, spec); } } } else if (typeof formDataDescriptor === 'object') { const fds = formDataDescriptor; const fields = fds.fields || {}; const files = Array.isArray(fds.files) ? fds.files : []; for (const [k, v] of Object.entries(fields)) fd.append(String(k), String(v)); for (const file of files) { const name = String(file.name || 'file'); if (file.fileUrl) { const resp = await fetch(String(file.fileUrl)); const blob = await resp.blob(); const fn = file.filename || String(file.fileUrl).split('?')[0].split('#')[0].split('/').pop() || 'file'; fd.append(name, blob, fn); } else if (file.base64Data) { const blob = base64ToBlob( String(file.base64Data), String(file.contentType || 'application/octet-stream'), ); fd.append(name, blob, file.filename || 'file'); } else if (file.filePath) { const res = await readFileBase64(String(file.filePath)); if (res && res.base64) { const blob = base64ToBlob( res.base64, String(file.contentType || 'application/octet-stream'), ); fd.append(name, blob, file.filename || res.fileName || 'file'); } } } } } catch (e) { console.warn('Failed to construct FormData:', e); } // Let browser set the correct multipart boundary try { if (options.headers) { delete options.headers['content-type']; delete options.headers['Content-Type']; } } catch {} options.body = fd; } else if (method !== 'GET' && method !== 'HEAD' && body !== undefined) { // Fallback to raw body options.body = body; } // 创建一个带超时的 fetch const fetchWithTimeout = async (url, options, timeout) => { const controller = new AbortController(); const signal = controller.signal; // 设置超时 const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } }; // 发送带超时的请求 const response = await fetchWithTimeout(url, options, timeout); // Process response const responseData = { status: response.status, statusText: response.statusText, headers: {}, }; // Get response headers response.headers.forEach((value, key) => { responseData.headers[key] = value; }); // Try to get response body based on content type const contentType = response.headers.get('content-type') || ''; try { if (contentType.includes('application/json')) { responseData.body = await response.json(); } else if ( contentType.includes('text/') || contentType.includes('application/xml') || contentType.includes('application/javascript') ) { responseData.body = await response.text(); } else { // For binary data, just indicate it was received but not parsed responseData.body = '[Binary data not displayed]'; } } catch (error) { responseData.body = `[Error parsing response body: ${error.message}]`; } return { success: true, response: responseData, }; } catch (error) { console.error('Error replaying request:', error); return { success: false, error: `Error replaying request: ${error.message}`, }; } } // Listen for messages from the extension chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { // Respond to ping message if (request.action === 'chrome_network_request_ping') { sendResponse({ status: 'pong' }); return false; // Synchronous response } else if (request.action === 'sendPureNetworkRequest') { replayNetworkRequest( request.url, request.method, request.headers, request.body, request.timeout, request.formData, ) .then(sendResponse) .catch((error) => { sendResponse({ success: false, error: `Unexpected error: ${error.message}`, }); }); return true; // Indicates async response } }); }