// WebDrop Bridge - Intercept Version // Prevents browser drag for ALT+drag, hands off to Qt for file drag (function() { try { if (window.__webdrop_intercept_injected) return; window.__webdrop_intercept_injected = true; // Intercept mode enabled var INTERCEPT_ENABLED = true; console.log('%c[WebDrop Intercept] Script loaded - INTERCEPT_ENABLED=' + INTERCEPT_ENABLED, 'background: #2196F3; color: white; font-weight: bold; padding: 4px 8px;'); var currentDragUrls = []; // Array to support multiple URLs var angularDragHandlers = []; var originalAddEventListener = EventTarget.prototype.addEventListener; var listenerPatchActive = true; var dragHandlerInstalled = false; // Capture Authorization token from XHR requests (only if checkout is enabled) window.capturedAuthToken = null; if (window.webdropConfig && window.webdropConfig.enableCheckout) { console.log('[Intercept] Auth token capture enabled (checkout feature active)'); var originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.setRequestHeader = function(header, value) { if (header === 'Authorization' && value.startsWith('Bearer ')) { window.capturedAuthToken = value; console.log('[Intercept] Captured auth token'); } return originalXHRSetRequestHeader.apply(this, arguments); }; } else { console.log('[Intercept] Auth token capture disabled (checkout feature inactive)'); } // Only patch addEventListener for dragstart events // This minimizes impact on other event listeners (mouseover, mouseenter, etc.) EventTarget.prototype.addEventListener = function(type, listener, options) { if (listenerPatchActive && type === 'dragstart' && listener) { // Store Angular's dragstart handler instead of registering it console.log('[Intercept] Storing Angular dragstart listener for', this.tagName || this.constructor.name); angularDragHandlers.push({ target: this, listener: listener, options: options }); return; // Don't actually register it yet } // All other events (mouseover, mouseenter, mousedown, etc.): use original // This is critical to ensure mouseover/hover events work properly return originalAddEventListener.call(this, type, listener, options); }; // ============================================================================ // PART 2: Intercept DataTransfer.setData to capture URL // ============================================================================ var originalSetData = DataTransfer.prototype.setData; DataTransfer.prototype.setData = function(format, data) { if (format === 'text/plain' || format === 'text/uri-list') { // text/uri-list contains newline-separated URLs // text/plain may be single URL or multiple newline-separated URLs currentDragUrls = data.trim().split('\n').filter(function(url) { return url.trim().length > 0; }).map(function(url) { return url.trim(); }); console.log('%c[Intercept] Captured ' + currentDragUrls.length + ' URL(s)', 'color: #4CAF50; font-weight: bold;', currentDragUrls[0].substring(0, 60)); } return originalSetData.call(this, format, data); }; console.log('[Intercept] DataTransfer.setData patched ✓'); // ============================================================================ // PART 3: Install OUR dragstart handler in capture phase // ============================================================================ function installDragHandler() { console.log('[Intercept] Installing dragstart handler, have', angularDragHandlers.length, 'Angular handlers'); if (angularDragHandlers.length === 0) { console.warn('[Intercept] No Angular handlers found yet, will retry in 1s...'); setTimeout(installDragHandler, 1000); return; } // Only install once, even if called multiple times if (dragHandlerInstalled) { console.log('[Intercept] Handler already installed, skipping'); return; } dragHandlerInstalled = true; // NOTE: Keep listenerPatchActive = true to catch new Angular handlers registered later // This is important for page reloads where Angular might register handlers at different times // Register OUR handler in capture phase originalAddEventListener.call(document, 'dragstart', function(e) { currentDragUrls = []; // Reset // Call Angular's handlers first to let them set the data var handled = 0; for (var i = 0; i < angularDragHandlers.length; i++) { var h = angularDragHandlers[i]; if (h.target === document || h.target === e.target || (h.target.contains && h.target.contains(e.target))) { try { h.listener.call(e.target, e); handled++; } catch(err) { console.error('[Intercept] Error calling Angular handler:', err); } } } console.log('[Intercept] Called', handled, 'Angular handlers, URLs:', currentDragUrls.length, 'URL(s)', currentDragUrls.length > 0 ? currentDragUrls[0].substring(0, 60) : 'none'); // NOW check if we should intercept // Intercept any drag with URLs that match our configured mappings if (currentDragUrls.length > 0) { var shouldIntercept = false; // Check each URL against configured URL mappings // Intercept if ANY URL matches if (window.webdropConfig && window.webdropConfig.urlMappings) { for (var k = 0; k < currentDragUrls.length; k++) { var dragUrl = currentDragUrls[k]; for (var j = 0; j < window.webdropConfig.urlMappings.length; j++) { var mapping = window.webdropConfig.urlMappings[j]; if (dragUrl.toLowerCase().startsWith(mapping.url_prefix.toLowerCase())) { shouldIntercept = true; console.log('[Intercept] URL #' + (k+1) + ' matches mapping for:', mapping.local_path); break; } } if (shouldIntercept) break; } } else { // Fallback: Check for legacy Z: drive pattern if no config available for (var k = 0; k < currentDragUrls.length; k++) { if (/^z:/i.test(currentDragUrls[k])) { shouldIntercept = true; console.warn('[Intercept] Using fallback Z: drive pattern (no URL mappings configured)'); break; } } } if (shouldIntercept) { console.log('%c[Intercept] PREVENTING browser drag, using Qt for ' + currentDragUrls.length + ' file(s)', 'background: #F44336; color: white; font-weight: bold; padding: 4px 8px;'); e.preventDefault(); e.stopPropagation(); ensureChannel(function() { if (window.bridge && typeof window.bridge.start_file_drag === 'function') { console.log('%c[Intercept] → Qt: start_file_drag with ' + currentDragUrls.length + ' file(s)', 'color: #9C27B0; font-weight: bold;'); // Pass as JSON string to avoid Qt WebChannel array conversion issues window.bridge.start_file_drag(JSON.stringify(currentDragUrls)); } else { console.error('[Intercept] bridge.start_file_drag not available!'); } }); currentDragUrls = []; return false; } } console.log('[Intercept] Normal drag, allowing browser'); }, true); // Capture phase console.log('[Intercept] dragstart handler installed ✓'); } // Wait for Angular to register its listeners, then install our handler // Start checking after 3 seconds (give Angular time to load), then retry for up to 30 seconds var installRetries = 0; var maxRetries = 27; // 3 initial + 27 retries * 1s = 30s total function scheduleInstall() { if (dragHandlerInstalled) return; // Already done installRetries++; console.log('[Intercept] Install attempt', installRetries, '/', maxRetries + 3); installDragHandler(); if (!dragHandlerInstalled && installRetries < maxRetries) { setTimeout(scheduleInstall, 1000); } else if (!dragHandlerInstalled) { console.warn('[Intercept] Gave up waiting for Angular handlers after 30s'); } } setTimeout(scheduleInstall, 3000); // ============================================================================ // PART 3: QWebChannel connection // ============================================================================ function ensureChannel(callback) { if (window.bridge) { callback(); return; } if (window.QWebChannel && window.qt && window.qt.webChannelTransport) { new QWebChannel(window.qt.webChannelTransport, function(channel) { window.bridge = channel.objects.bridge; console.log('[WebDrop Intercept] QWebChannel connected ✓'); callback(); }); } else { console.error('[WebDrop Intercept] QWebChannel not available!'); } } // Initialize channel on load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { ensureChannel(function() { console.log('[WebDrop Intercept] Bridge ready ✓'); }); }); } else { ensureChannel(function() { console.log('[WebDrop Intercept] Bridge ready ✓'); }); } console.log('%c[WebDrop Intercept] Ready! URL-mapped drags will use Qt file drag.', 'background: #4CAF50; color: white; font-weight: bold; padding: 4px 8px;'); } catch(e) { console.error('[WebDrop Intercept] FATAL ERROR in bridge script:', e); console.error('[WebDrop Intercept] Stack:', e.stack); } })();