API Interceptor + DOM Inspector

Fixed version with better memory management and storage

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         API Interceptor + DOM Inspector
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Fixed version with better memory management and storage
// @author       gl4.manu
// @match        *://*/*
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_listValues
// @run-at       document-start
// @noframes
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const CONFIG = {
        enabled: true,
        maxLogEntries: 50, // Reduced for performance
        maxBodySize: 1000, // Smaller body size
        maxStorageSize: 500000, // ~500KB max storage
        autoSaveInterval: 5000,
        persistData: true,
        persistKey: 'devtools_pro_v4',
        logToConsole: false, // Disable console logging for performance
        compressData: true, // Compress stored data
        cleanupInterval: 60000, // Cleanup every minute
        maxTooltipAge: 3000 // Auto-hide tooltip after 3 seconds
    };

    // Storage
    let interceptedRequests = [];
    let requestId = 0;
    let expandedItems = new Set();
    let inspectorActive = false;
    let currentTooltip = null;
    let tooltipTimeout = null;
    let currentHoverElement = null;
    let originalFetch = null;
    let saveTimeout = null;
    let updateQueue = [];
    let updateTimer = null;

    // ==================== MEMORY MANAGEMENT ====================

    function cleanupOldData() {
        // Remove old entries beyond limit
        if (interceptedRequests.length > CONFIG.maxLogEntries) {
            const removed = interceptedRequests.splice(CONFIG.maxLogEntries);
            removed.forEach(r => expandedItems.delete(r.id));
        }

        // Clear old tooltips
        if (currentTooltip && currentTooltip.parentNode) {
            clearTooltip();
        }

        // Force garbage collection hint
        if (interceptedRequests.length > CONFIG.maxLogEntries * 0.8) {
            console.log('[Cleanup] Reduced to', interceptedRequests.length, 'entries');
        }
    }

    function compressData(data) {
        if (!CONFIG.compressData) return JSON.stringify(data);

        // Simple compression: remove unnecessary whitespace and truncate long strings
        const compressed = JSON.stringify(data, (key, value) => {
            if (typeof value === 'string' && value.length > 500) {
                return value.substring(0, 500) + '...[truncated]';
            }
            return value;
        });

        return compressed;
    }

    function decompressData(compressed) {
        try {
            return JSON.parse(compressed);
        } catch(e) {
            return null;
        }
    }

    function getStorageSize() {
        let total = 0;
        const keys = GM_listValues();
        for (let key of keys) {
            const value = GM_getValue(key, '');
            total += value.length;
        }
        return total;
    }

    function saveData() {
        if (!CONFIG.persistData) return;

        try {
            // Check storage size
            if (getStorageSize() > CONFIG.maxStorageSize) {
                console.warn('[Storage] Size limit reached, clearing old data');
                const oldData = GM_getValue(CONFIG.persistKey, '');
                if (oldData.length > CONFIG.maxStorageSize * 0.8) {
                    // Keep only last 25 entries
                    const trimmed = interceptedRequests.slice(0, 25);
                    const compressed = compressData({
                        version: '4.1.0',
                        timestamp: Date.now(),
                        requests: trimmed,
                        requestId: requestId
                    });
                    GM_setValue(CONFIG.persistKey, compressed);
                    return;
                }
            }

            const dataToSave = {
                version: '4.1.0',
                timestamp: Date.now(),
                requests: interceptedRequests.slice(0, CONFIG.maxLogEntries),
                requestId: requestId,
                expandedIds: Array.from(expandedItems)
            };

            const compressed = compressData(dataToSave);
            GM_setValue(CONFIG.persistKey, compressed);

        } catch(e) {
            console.error('[Storage] Save failed:', e);
        }
    }

    function loadData() {
        if (!CONFIG.persistData) return false;

        try {
            const saved = GM_getValue(CONFIG.persistKey, null);
            if (!saved) return false;

            const data = decompressData(saved);
            if (!data) return false;

            // Check age (max 2 hours)
            const maxAge = 7200000;
            if (Date.now() - data.timestamp > maxAge) {
                console.log('[Storage] Data too old, clearing');
                GM_deleteValue(CONFIG.persistKey);
                return false;
            }

            interceptedRequests = data.requests || [];
            requestId = data.requestId || 0;
            expandedItems = new Set(data.expandedIds || []);

            console.log(`[Storage] Loaded ${interceptedRequests.length} requests`);
            return true;
        } catch(e) {
            console.error('[Storage] Load failed:', e);
            return false;
        }
    }

    // ==================== OPTIMIZED UI UPDATES ====================

    function queueUpdate() {
        if (updateTimer) clearTimeout(updateTimer);
        updateTimer = setTimeout(() => {
            updateLogsDisplay();
            updateTimer = null;
        }, 300);
    }

    function updateLogsDisplay() {
        const container = document.getElementById('logs-container');
        if (!container) return;

        const searchTerm = document.querySelector('.search-box')?.value.toLowerCase() || '';
        const filtered = searchTerm ?
            interceptedRequests.filter(log =>
                log.url.toLowerCase().includes(searchTerm) ||
                log.method.toLowerCase().includes(searchTerm)
            ) : interceptedRequests;

        // Virtual scrolling: only render first 20 for performance
        const renderLimit = 20;
        const toRender = filtered.slice(0, renderLimit);
        const hasMore = filtered.length > renderLimit;

        container.innerHTML = toRender.map(log => {
            const isExpanded = expandedItems.has(log.id);
            const hasJWT = !!log.jwt;

            return `
                <div class="log-entry" data-id="${log.id}">
                    <div class="log-header" onclick="window.toggleLog(${log.id})">
                        <span class="expand-icon">${isExpanded ? '▼' : '▶'}</span>
                        <span class="log-method">${escapeHtml(log.method)}</span>
                        <span class="log-url" title="${escapeHtml(log.url)}">${escapeHtml(truncate(log.url, 50))}</span>
                        ${log.status ? `<span class="log-status">${log.status}</span>` : ''}
                        ${hasJWT ? '<span class="badge badge-jwt">🔑</span>' : ''}
                    </div>
                    <div class="log-details ${isExpanded ? 'expanded' : ''}">
                        ${renderDetails(log)}
                    </div>
                </div>
            `;
        }).join('');

        if (hasMore) {
            container.innerHTML += `<div style="text-align:center;padding:5px;color:#888;">+ ${filtered.length - renderLimit} more requests (search to see all)</div>`;
        }

        updateStats(filtered.length);
    }

    function renderDetails(log) {
        let html = '';

        if (log.jwt) {
            html += `<div class="detail-section">
                <div class="detail-title">🔑 JWT</div>
                <div class="detail-content jwt-token">${escapeHtml(truncate(log.jwt, 100))}</div>
            </div>`;
        }

        if (log.requestHeaders) {
            html += `<div class="detail-section">
                <div class="detail-title">📤 Headers</div>
                <div class="detail-content"><pre>${escapeHtml(truncate(JSON.stringify(log.requestHeaders, null, 2), 300))}</pre></div>
            </div>`;
        }

        if (log.responseBody) {
            html += `<div class="detail-section">
                <div class="detail-title">📥 Body</div>
                <div class="detail-content"><pre>${escapeHtml(truncate(log.responseBody, 300))}</pre></div>
            </div>`;
        }

        if (log.error) {
            html += `<div class="detail-section">
                <div class="detail-title">❌ Error</div>
                <div class="detail-content">${escapeHtml(log.error)}</div>
            </div>`;
        }

        html += `<div class="detail-section">
            <div class="detail-title">⏱️ ${new Date(log.timestamp).toLocaleTimeString()}</div>
        </div>`;

        return html;
    }

    function escapeHtml(str) {
        if (!str) return '';
        return str.replace(/[&<>]/g, function(m) {
            if (m === '&') return '&amp;';
            if (m === '<') return '&lt;';
            if (m === '>') return '&gt;';
            return m;
        });
    }

    function truncate(str, len) {
        if (!str) return '';
        return str.length > len ? str.substring(0, len) + '...' : str;
    }

    function updateStats(filteredCount) {
        const statsBar = document.getElementById('stats-bar');
        if (statsBar) {
            const countSpan = document.getElementById('request-count');
            if (countSpan) countSpan.textContent = interceptedRequests.length;
        }
    }

    // ==================== FIXED DOM INSPECTOR ====================

    function clearTooltip() {
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
        if (currentTooltip && currentTooltip.parentNode) {
            currentTooltip.parentNode.removeChild(currentTooltip);
        }
        currentTooltip = null;
    }

    function showTooltipFixed(element, x, y) {
        clearTooltip();

        const tooltip = document.createElement('div');
        tooltip.className = 'dom-inspector-tooltip';

        const tagName = element.tagName.toLowerCase();
        const id = element.id ? `#${element.id}` : '';
        const classes = element.className ? `.${element.className.split(' ')[0]}` : '';

        tooltip.innerHTML = `
            <div><strong>${tagName}${id}${classes}</strong></div>
            <div style="font-size:9px;margin-top:4px;">
                <button onclick="window.copySelector('${escapeHtml(getElementSelector(element))}')">Copy Selector</button>
                <button onclick="window.copyXPath('${escapeHtml(getElementXPath(element))}')">Copy XPath</button>
                <button onclick="window.editElement()">Edit</button>
            </div>
        `;

        document.body.appendChild(tooltip);
        currentTooltip = tooltip;

        // Position
        let left = x + 15;
        let top = y - 30;
        const rect = tooltip.getBoundingClientRect();
        if (left + rect.width > window.innerWidth) left = x - rect.width - 15;
        if (top + rect.height > window.innerHeight) top = y - rect.height - 15;
        if (top < 0) top = 10;
        if (left < 0) left = 10;

        tooltip.style.left = `${left}px`;
        tooltip.style.top = `${top}px`;

        // Auto-hide after 3 seconds
        tooltipTimeout = setTimeout(clearTooltip, CONFIG.maxTooltipAge);

        window.copySelector = (selector) => {
            navigator.clipboard.writeText(selector);
            showNotification('✅ Selector copied');
            clearTooltip();
        };

        window.copyXPath = (xpath) => {
            navigator.clipboard.writeText(xpath);
            showNotification('✅ XPath copied');
            clearTooltip();
        };

        window.editElement = () => {
            const newHTML = prompt('Edit HTML:', element.outerHTML);
            if (newHTML && newHTML !== element.outerHTML) {
                try {
                    const temp = document.createElement('div');
                    temp.innerHTML = newHTML;
                    element.parentNode.replaceChild(temp.firstChild, element);
                    showNotification('✅ Element updated');
                } catch(e) {
                    showNotification('❌ Invalid HTML');
                }
            }
            clearTooltip();
        };
    }

    function getElementSelector(element) {
        if (element.id) return `#${element.id}`;
        if (element.className && typeof element.className === 'string') {
            const cls = element.className.split(' ')[0];
            if (cls) return `${element.tagName.toLowerCase()}.${cls}`;
        }
        return element.tagName.toLowerCase();
    }

    function getElementXPath(element) {
        if (element.id) return `//*[@id="${element.id}"]`;
        if (element === document.body) return '/html/body';

        let ix = 0;
        const siblings = element.parentNode.childNodes;
        for (let i = 0; i < siblings.length; i++) {
            const sibling = siblings[i];
            if (sibling === element) {
                const parentPath = element.parentNode === document.body ? '/html/body' : getElementXPath(element.parentNode);
                return parentPath + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
            }
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) ix++;
        }
        return '';
    }

    function showNotification(msg) {
        const notif = document.createElement('div');
        notif.textContent = msg;
        notif.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            background: #00ff00;
            color: #000;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 11px;
            z-index: 1000000;
            animation: fadeOut 1.5s ease-out;
        `;
        document.body.appendChild(notif);
        setTimeout(() => notif.remove(), 1500);
    }

    // ==================== FIXED EVENT HANDLERS ====================

    function initDOMInspectorFixed() {
        let inspectorBtn = null;

        const addControls = () => {
            const container = document.getElementById('logs-container');
            if (container && !document.getElementById('inspector-toggle')) {
                const controls = document.createElement('div');
                controls.style.cssText = 'display:flex;gap:5px;margin-bottom:10px;';
                controls.innerHTML = `
                    <button id="inspector-toggle" style="background:#333;color:#0f0;border:1px solid #0f0;padding:3px 8px;border-radius:3px;cursor:pointer;">🔍 DOM Inspector OFF</button>
                    <button id="inspector-clear-highlight" style="background:#333;color:#0f0;border:1px solid #0f0;padding:3px 8px;border-radius:3px;cursor:pointer;">✨ Clear</button>
                `;

                const statsBar = document.getElementById('stats-bar');
                if (statsBar) {
                    statsBar.parentNode.insertBefore(controls, statsBar.nextSibling);
                }

                inspectorBtn = document.getElementById('inspector-toggle');
                const clearBtn = document.getElementById('inspector-clear-highlight');

                if (inspectorBtn) {
                    inspectorBtn.addEventListener('click', toggleInspector);
                }
                if (clearBtn) {
                    clearBtn.addEventListener('click', () => {
                        if (currentHoverElement) {
                            currentHoverElement.classList.remove('dom-inspector-highlight');
                            currentHoverElement = null;
                        }
                        clearTooltip();
                    });
                }
            } else {
                setTimeout(addControls, 200);
            }
        };

        function toggleInspector() {
            inspectorActive = !inspectorActive;

            if (inspectorActive) {
                inspectorBtn.textContent = '🔍 DOM Inspector ON';
                inspectorBtn.style.background = '#0f0';
                inspectorBtn.style.color = '#000';
                document.body.style.cursor = 'crosshair';
                document.addEventListener('mouseover', onMouseOver);
                document.addEventListener('click', onClick, true);
            } else {
                inspectorBtn.textContent = '🔍 DOM Inspector OFF';
                inspectorBtn.style.background = '#333';
                inspectorBtn.style.color = '#0f0';
                document.body.style.cursor = '';
                document.removeEventListener('mouseover', onMouseOver);
                document.removeEventListener('click', onClick, true);
                if (currentHoverElement) {
                    currentHoverElement.classList.remove('dom-inspector-highlight');
                    currentHoverElement = null;
                }
                clearTooltip();
            }
        }

        function onMouseOver(e) {
            if (!inspectorActive) return;

            // Ignore inspector buttons
            if (e.target.closest('#inspector-toggle') || e.target.closest('#inspector-clear-highlight')) {
                return;
            }

            if (currentHoverElement) {
                currentHoverElement.classList.remove('dom-inspector-highlight');
            }

            currentHoverElement = e.target;
            currentHoverElement.classList.add('dom-inspector-highlight');
            showTooltipFixed(currentHoverElement, e.clientX, e.clientY);
        }

        function onClick(e) {
            if (!inspectorActive) return;
            if (e.target.closest('#inspector-toggle') || e.target.closest('#inspector-clear-highlight')) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            const element = e.target;
            const action = prompt(
                'DOM Actions:\n' +
                '1 - Copy Selector\n' +
                '2 - Copy XPath\n' +
                '3 - Edit HTML\n' +
                '4 - Log to Console\n' +
                '5 - Hide Element'
            );

            switch(action) {
                case '1':
                    navigator.clipboard.writeText(getElementSelector(element));
                    showNotification('✅ Selector copied');
                    break;
                case '2':
                    navigator.clipboard.writeText(getElementXPath(element));
                    showNotification('✅ XPath copied');
                    break;
                case '3':
                    const newHTML = prompt('Edit HTML:', element.outerHTML);
                    if (newHTML && newHTML !== element.outerHTML) {
                        const temp = document.createElement('div');
                        temp.innerHTML = newHTML;
                        element.parentNode.replaceChild(temp.firstChild, element);
                        showNotification('✅ Updated');
                    }
                    break;
                case '4':
                    console.log('[DOM]', element);
                    showNotification('✅ Check console (F12)');
                    break;
                case '5':
                    element.style.display = 'none';
                    showNotification('✅ Hidden');
                    break;
            }

            clearTooltip();
            return false;
        }

        addControls();
    }

    // ==================== API INTERCEPTORS (Optimized) ====================

    function extractJWT(text) {
        if (!text || typeof text !== 'string') return null;
        const match = text.match(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/);
        return match ? match[0] : null;
    }

    function addLogEntry(entry) {
        if (!CONFIG.enabled) return;

        // Remove duplicate consecutive requests (optional)
        const lastEntry = interceptedRequests[0];
        if (lastEntry && lastEntry.url === entry.url && lastEntry.method === entry.method && Date.now() - lastEntry.timestamp < 100) {
            return; // Skip duplicate within 100ms
        }

        entry.id = requestId++;
        entry.timestamp = Date.now();

        interceptedRequests.unshift(entry);

        // Cleanup old entries
        if (interceptedRequests.length > CONFIG.maxLogEntries) {
            const removed = interceptedRequests.pop();
            expandedItems.delete(removed.id);
        }

        // Queue UI update
        queueUpdate();

        // Queue save
        if (saveTimeout) clearTimeout(saveTimeout);
        saveTimeout = setTimeout(() => saveData(), CONFIG.autoSaveInterval);
    }

    function interceptFetch() {
        if (!window.fetch) return;
        originalFetch = window.fetch;

        window.fetch = async function(...args) {
            const [resource, config = {}] = args;
            const url = resource instanceof Request ? resource.url : resource.toString();
            const method = config.method || (resource instanceof Request ? resource.method : 'GET');
            const startTime = Date.now();

            // Extract JWT from headers
            let headers = config.headers || {};
            if (resource instanceof Request) {
                headers = Object.fromEntries(resource.headers.entries());
            }
            const authHeader = headers['authorization'] || headers['Authorization'];
            const jwt = authHeader ? extractJWT(authHeader) : null;

            addLogEntry({ type: 'fetch', method, url, jwt });

            try {
                const response = await originalFetch.apply(this, args);
                addLogEntry({
                    type: 'fetch', method, url,
                    status: response.status,
                    duration: Date.now() - startTime
                });
                return response;
            } catch(error) {
                addLogEntry({ type: 'fetch', method, url, error: error.message });
                throw error;
            }
        };
    }

    function interceptXHR() {
        if (!window.XMLHttpRequest) return;
        const XHR = window.XMLHttpRequest;
        const originalOpen = XHR.prototype.open;
        const originalSend = XHR.prototype.send;

        XHR.prototype.open = function(method, url) {
            this._data = { method, url, startTime: Date.now() };
            return originalOpen.apply(this, arguments);
        };

        XHR.prototype.send = function(body) {
            if (this._data) {
                // Extract JWT from headers if any
                let jwt = null;
                if (this._data.requestHeaders) {
                    const authHeader = this._data.requestHeaders['Authorization'] || this._data.requestHeaders['authorization'];
                    if (authHeader) jwt = extractJWT(authHeader);
                }
                addLogEntry({ type: 'xhr', ...this._data, jwt });
            }

            this.addEventListener('loadend', function() {
                if (this._data) {
                    addLogEntry({
                        type: 'xhr',
                        method: this._data.method,
                        url: this._data.url,
                        status: this.status,
                        duration: Date.now() - this._data.startTime
                    });
                }
            });
            return originalSend.apply(this, arguments);
        };
    }

    // ==================== UI CREATION ====================

    function createFloatingPanel() {
        GM_addStyle(`
            #api-interceptor-panel {
                position: fixed;
                bottom: 10px;
                right: 10px;
                width: 500px;
                max-height: 450px;
                background: #1e1e1e;
                color: #e0e0e0;
                border-radius: 6px;
                z-index: 999999;
                font-family: monospace;
                font-size: 11px;
                overflow: hidden;
                box-shadow: 0 2px 10px rgba(0,0,0,0.5);
                display: none;
                flex-direction: column;
                border: 1px solid #0f0;
            }

            #api-interceptor-panel.active { display: flex; }

            .panel-header {
                padding: 6px 10px;
                background: #2a2a2a;
                border-bottom: 1px solid #0f0;
                cursor: move;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            .panel-header h3 {
                margin: 0;
                font-size: 11px;
                color: #0f0;
            }

            .panel-controls button {
                background: #333;
                color: #0f0;
                border: 1px solid #0f0;
                padding: 2px 5px;
                cursor: pointer;
                font-size: 9px;
                border-radius: 3px;
            }

            .panel-controls button:hover {
                background: #0f0;
                color: #000;
            }

            .panel-content {
                overflow-y: auto;
                padding: 8px;
                max-height: 400px;
            }

            .log-entry {
                background: #252525;
                border-left: 2px solid #0f0;
                margin-bottom: 4px;
                padding: 4px 6px;
                font-size: 10px;
            }

            .log-header {
                cursor: pointer;
                display: flex;
                gap: 6px;
                align-items: center;
                flex-wrap: wrap;
            }

            .log-method {
                color: #ff6b6b;
                font-weight: bold;
            }

            .log-url {
                color: #4ecdc4;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                flex: 1;
            }

            .log-status {
                color: #ffe66d;
            }

            .badge-jwt {
                background: #ffd700;
                color: #000;
                padding: 0 3px;
                border-radius: 2px;
            }

            .log-details {
                display: none;
                margin-top: 5px;
                padding-top: 5px;
                border-top: 1px solid #444;
            }

            .log-details.expanded { display: block; }

            .detail-section {
                margin-bottom: 5px;
            }

            .detail-title {
                color: #0f0;
                font-size: 9px;
                margin-bottom: 2px;
            }

            .detail-content {
                background: #1a1a1a;
                padding: 4px;
                border-radius: 3px;
                font-size: 9px;
                overflow-x: auto;
                max-height: 80px;
            }

            .jwt-token {
                color: #ffd700;
                word-break: break-all;
            }

            .dom-inspector-highlight {
                outline: 2px solid #0f0 !important;
                background-color: rgba(0,255,0,0.1) !important;
                cursor: crosshair !important;
            }

            .dom-inspector-tooltip {
                position: fixed;
                background: #1e1e1e;
                color: #0f0;
                padding: 6px 10px;
                border-radius: 4px;
                font-size: 10px;
                z-index: 1000000;
                border: 1px solid #0f0;
                pointer-events: auto;
            }

            .dom-inspector-tooltip button {
                background: #333;
                color: #0f0;
                border: 1px solid #0f0;
                padding: 2px 6px;
                margin: 0 2px;
                cursor: pointer;
                font-size: 9px;
                border-radius: 3px;
            }

            .toggle-panel-btn {
                position: fixed;
                bottom: 10px;
                right: 10px;
                width: 32px;
                height: 32px;
                background: #0f0;
                color: #000;
                border: none;
                border-radius: 50%;
                cursor: pointer;
                z-index: 999998;
                font-size: 16px;
                display: flex;
                align-items: center;
                justify-content: center;
            }

            .search-box {
                width: 100%;
                padding: 4px 6px;
                margin-bottom: 8px;
                background: #2a2a2a;
                border: 1px solid #0f0;
                color: #0f0;
                border-radius: 3px;
                font-size: 10px;
            }

            .stats-bar {
                background: #2a2a2a;
                padding: 3px 6px;
                font-size: 9px;
                margin-bottom: 8px;
                border-radius: 3px;
                display: flex;
                justify-content: space-between;
            }

            @keyframes fadeOut {
                0% { opacity: 1; }
                70% { opacity: 1; }
                100% { opacity: 0; }
            }
        `);

        // Toggle button
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'toggle-panel-btn';
        toggleBtn.innerHTML = '🛠️';
        document.body.appendChild(toggleBtn);

        // Panel
        const panel = document.createElement('div');
        panel.id = 'api-interceptor-panel';
        panel.innerHTML = `
            <div class="panel-header">
                <h3>🛠️ DevTools Pro</h3>
                <div class="panel-controls">
                    <button id="clear-logs">Clear</button>
                    <button id="export-logs">Export</button>
                    <button id="close-panel">×</button>
                </div>
            </div>
            <div class="panel-content">
                <input type="text" class="search-box" placeholder="Filter...">
                <div class="stats-bar">
                    <span>📡 <span id="request-count">0</span></span>
                    <span>💾 Auto-save</span>
                </div>
                <div id="logs-container"></div>
            </div>
        `;
        document.body.appendChild(panel);

        // Draggable
        let dragData = { active: false };
        const header = panel.querySelector('.panel-header');

        header.addEventListener('mousedown', (e) => {
            if (e.target.tagName === 'BUTTON') return;
            dragData = {
                active: true,
                startX: e.clientX,
                startY: e.clientY,
                startLeft: panel.offsetLeft,
                startTop: panel.offsetTop
            };
            panel.style.left = dragData.startLeft + 'px';
            panel.style.top = dragData.startTop + 'px';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!dragData.active) return;
            panel.style.left = (dragData.startLeft + e.clientX - dragData.startX) + 'px';
            panel.style.top = (dragData.startTop + e.clientY - dragData.startY) + 'px';
        });

        document.addEventListener('mouseup', () => {
            dragData.active = false;
        });

        // Event handlers
        toggleBtn.onclick = () => panel.classList.toggle('active');
        document.getElementById('close-panel').onclick = () => panel.classList.remove('active');
        document.getElementById('clear-logs').onclick = () => {
            if (confirm('Clear all?')) {
                interceptedRequests = [];
                expandedItems.clear();
                requestId = 0;
                updateLogsDisplay();
                saveData();
            }
        };
        document.getElementById('export-logs').onclick = () => {
            const data = JSON.stringify(interceptedRequests, null, 2);
            const blob = new Blob([data], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `devtools-${Date.now()}.json`;
            a.click();
            URL.revokeObjectURL(url);
        };

        document.querySelector('.search-box').addEventListener('input', () => updateLogsDisplay());

        return panel;
    }

    // ==================== INIT ====================

    function init() {
        console.log('[DevTools Pro] v4.1 - Optimized');

        // Load saved data
        loadData();

        // Setup interceptors
        interceptFetch();
        interceptXHR();

        // Create UI
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                createFloatingPanel();
                initDOMInspectorFixed();
                updateLogsDisplay();

                // Periodic cleanup
                setInterval(() => {
                    cleanupOldData();
                    if (CONFIG.persistData) saveData();
                }, CONFIG.cleanupInterval);
            });
        } else {
            createFloatingPanel();
            initDOMInspectorFixed();
            updateLogsDisplay();
            setInterval(() => {
                cleanupOldData();
                if (CONFIG.persistData) saveData();
            }, CONFIG.cleanupInterval);
        }

        // Save on unload
        window.addEventListener('beforeunload', () => saveData());
    }

    init();
})();