Files
gdown-helper/src/content/index.ts

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()