import browser from 'webextension-polyfill' import { isLikelyDownloadUrl, normalizeUrl } from '../lib/downloadIntent' const CAPTURE_TTL_MS = 8000 const captureInFlight = new Map() function pruneCaptureInFlight(): void { const now = Date.now() for (const [url, expiresAt] of captureInFlight.entries()) { if (expiresAt <= now) captureInFlight.delete(url) } } async function sendCapture(url: string, referer: string): Promise { const normalized = normalizeUrl(url, window.location.href) if (!normalized) return false pruneCaptureInFlight() if (captureInFlight.has(normalized)) return true captureInFlight.set(normalized, Date.now() + CAPTURE_TTL_MS) try { const result = (await browser.runtime.sendMessage({ type: 'capture-link-download', url: normalized, referer: referer || document.referrer || window.location.href, })) as { ok?: boolean } if (result?.ok) return true } catch { // ignored } captureInFlight.delete(normalized) return false } function findAnchor(target: EventTarget | null): HTMLAnchorElement | null { if (!target) return null if (target instanceof HTMLAnchorElement) return target if (target instanceof Element) return target.closest('a[href]') as HTMLAnchorElement | null return null } function shouldIgnoreHotkey(event: MouseEvent | KeyboardEvent): boolean { return !!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) } async function interceptAnchorEvent(event: MouseEvent): Promise { if (event.defaultPrevented) return if (shouldIgnoreHotkey(event)) return const anchor = findAnchor(event.target) if (!anchor) return const href = anchor.href || '' if (!href || !isLikelyDownloadUrl(href, window.location.href)) return event.preventDefault() event.stopImmediatePropagation() event.stopPropagation() await sendCapture(href, document.referrer || window.location.href) } function interceptMouseLike(event: MouseEvent): void { const anchor = findAnchor(event.target) if (!anchor) return const href = anchor.href || '' if (!href || !isLikelyDownloadUrl(href, window.location.href)) return if (shouldIgnoreHotkey(event)) return event.preventDefault() event.stopImmediatePropagation() event.stopPropagation() void sendCapture(href, document.referrer || window.location.href) } document.addEventListener('pointerdown', (event: PointerEvent) => { if (event.button !== 0) return interceptMouseLike(event) }, true) document.addEventListener('mousedown', (event: MouseEvent) => { if (event.button !== 0) return interceptMouseLike(event) }, true) document.addEventListener('click', (event: MouseEvent) => { if (event.button !== 0) return void interceptAnchorEvent(event) }, true) document.addEventListener('keydown', (event: KeyboardEvent) => { if (event.key !== 'Enter') return if (event.defaultPrevented) return if (shouldIgnoreHotkey(event)) return const anchor = findAnchor(event.target) if (!anchor) return const href = anchor.href || '' if (!href || !isLikelyDownloadUrl(href, window.location.href)) return event.preventDefault() event.stopImmediatePropagation() event.stopPropagation() void sendCapture(href, document.referrer || window.location.href) }, true) document.addEventListener('auxclick', (event: MouseEvent) => { if (event.button !== 1) return void interceptAnchorEvent(event) }, true) function installProgrammaticInterceptors(): void { try { const originalOpen = window.open.bind(window) window.open = function gomdownInterceptOpen(url?: string | URL, target?: string, features?: string): Window | null { const raw = String(url || '').trim() if (raw && isLikelyDownloadUrl(raw, window.location.href)) { void sendCapture(raw, window.location.href) return null } return originalOpen(url as string, target, features) } } catch { // ignored } try { const originalAnchorClick = HTMLAnchorElement.prototype.click HTMLAnchorElement.prototype.click = function gomdownInterceptAnchorClick(): void { const href = this.href || this.getAttribute('href') || '' if (href && isLikelyDownloadUrl(href, window.location.href)) { void sendCapture(href, document.referrer || window.location.href) return } originalAnchorClick.call(this) } } catch { // ignored } } installProgrammaticInterceptors()