143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
import browser from 'webextension-polyfill'
|
|
import { isLikelyDownloadUrl, normalizeUrl } from '../lib/downloadIntent'
|
|
|
|
const CAPTURE_TTL_MS = 8000
|
|
const captureInFlight = new Map<string, number>()
|
|
|
|
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<boolean> {
|
|
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<void> {
|
|
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()
|