// 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 currentDragUrl = null; var angularDragHandlers = []; var originalAddEventListener = EventTarget.prototype.addEventListener; var listenerPatchActive = true; // Capture Authorization token from XHR requests window.capturedAuthToken = null; 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); }; // ============================================================================ // PART 1: Intercept Angular's dragstart listener registration // ============================================================================ 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: use original 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') { currentDragUrl = data; console.log('%c[Intercept] Captured URL:', 'color: #4CAF50; font-weight: bold;', data.substring(0, 80)); } 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; } // Stop intercepting addEventListener listenerPatchActive = false; // Register OUR handler in capture phase originalAddEventListener.call(document, 'dragstart', function(e) { currentDragUrl = null; // Reset // Check once per drag if native DnD is disabled via URL param (e.g. ?disablednd=true) var disabledNativeDnD = /[?&]disablednd=true/i.test(window.location.search); console.log('%c[Intercept] dragstart', 'background: #FF9800; color: white; padding: 2px 6px;', 'ALT:', e.altKey, '| disablednd:', disabledNativeDnD); // 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, URL:', currentDragUrl ? currentDragUrl.substring(0, 60) : 'none'); // NOW check if we should intercept // Intercept when: Alt key held (normal mode) OR native DnD disabled via URL param if ((e.altKey || disabledNativeDnD) && currentDragUrl) { var shouldIntercept = false; // Check against configured URL mappings if (window.webdropConfig && window.webdropConfig.urlMappings) { for (var j = 0; j < window.webdropConfig.urlMappings.length; j++) { var mapping = window.webdropConfig.urlMappings[j]; if (currentDragUrl.toLowerCase().startsWith(mapping.url_prefix.toLowerCase())) { shouldIntercept = true; console.log('[Intercept] URL matches mapping for:', mapping.local_path); break; } } } else { // Fallback: Check for legacy Z: drive pattern if no config available shouldIntercept = /^z:/i.test(currentDragUrl); if (shouldIntercept) { console.warn('[Intercept] Using fallback Z: drive pattern (no URL mappings configured)'); } } if (shouldIntercept) { console.log('%c[Intercept] PREVENTING browser drag, using Qt', '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', 'color: #9C27B0; font-weight: bold;'); window.bridge.start_file_drag(currentDragUrl); } else { console.error('[Intercept] bridge.start_file_drag not available!'); } }); currentDragUrl = null; 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 2 seconds (give Angular time to load on first page load) setTimeout(installDragHandler, 2000); // ============================================================================ // 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! ALT-drag or ?disablednd=true 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); } })();