From f7cfe41211ed561bb920ff63a035553072f38b9d Mon Sep 17 00:00:00 2001 From: bytedream Date: Fri, 10 Jul 2026 16:10:24 +0200 Subject: [PATCH] only listen to send headers and redirect request where it's necessary --- src/entrypoints/background/index.ts | 167 ++++++++++++++++++---------- src/entrypoints/player/player.ts | 20 +++- src/lib/communication.ts | 8 +- src/lib/settings.ts | 12 -- 4 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/entrypoints/background/index.ts b/src/entrypoints/background/index.ts index 9d37ded..829c8b4 100644 --- a/src/entrypoints/background/index.ts +++ b/src/entrypoints/background/index.ts @@ -1,12 +1,23 @@ import { BackgroundMessageData, BackgroundMessageType } from '@/lib/communication'; -import { getHost } from '@/lib/host'; -import { HostSettings, UrlReferer } from '@/lib/settings'; +import { getHost, hosts } from '@/lib/host'; +import { HostSettings } from '@/lib/settings'; + +const temporaryReferers: Record = {}; +const allHostDomains = hosts.flatMap((host) => host.domains); + +function domainToMatch(domain: string) { + return `*://${domain}/*`; +} + +function domainsToMatch(domains: string[]) { + return domains.map(domainToMatch); +} async function reInjectContentScript(domain: string) { const contentScript: Browser.scripting.RegisteredContentScript = { id: 'temporary-hosts', js: ['content-scripts/content.js'], - matches: [`*://${domain}/*`], + matches: [domainToMatch(domain)], persistAcrossSessions: false, allFrames: true, runAt: 'document_end' @@ -26,75 +37,115 @@ async function reInjectContentScript(domain: string) { } } -export default defineBackground(() => { - browser.runtime.onMessage.addListener(async (message, sender) => { - const type = message.type as BackgroundMessageType; - let data = message.data; +/* --- listeners --- */ +async function browserRuntimeOnMessageListener(message: any, sender: Browser.runtime.MessageSender) { + const type = message.type as BackgroundMessageType; + let data = message.data; - let response; + let response; - switch (type) { - case BackgroundMessageType.Ff2mpv: { - data = data as BackgroundMessageData; + switch (type) { + case BackgroundMessageType.Ff2mpv: { + data = data as BackgroundMessageData; - await browser.runtime.sendNativeMessage('ff2mpv', { url: data.url }); - break; - } - case BackgroundMessageType.RequestTabUrl: { - response = sender.tab?.url ?? null; - break; - } - case BackgroundMessageType.RegisterContentScript: { - // requires "dynamic" host permissions, which is only available in mv2 - if (import.meta.env.MANIFEST_VERSION === 3) break; - - data = data as BackgroundMessageData; - await reInjectContentScript(data.domain); - - break; - } - default: - break; + await browser.runtime.sendNativeMessage('ff2mpv', { url: data.url }); + break; } + case BackgroundMessageType.RequestTabUrl: { + response = sender.tab?.url ?? null; + break; + } + case BackgroundMessageType.RegisterContentScript: { + // requires "dynamic" host permissions, which is only available in mv2 + if (import.meta.env.MANIFEST_VERSION === 3) break; - return response; + data = data as BackgroundMessageData; + await reInjectContentScript(data.domain); + + break; + } + case BackgroundMessageType.RegisterTemporaryReferer: { + // requires "dynamic" host permissions, which is only available in mv2 + if (import.meta.env.MANIFEST_VERSION === 3) break; + + data = data as BackgroundMessageData; + if (data.domain in temporaryReferers) break; + temporaryReferers[data.domain] = data.referer; + + // remount before send headers listener which to also listen on the new temporary referer domain + registerBrowserWebRequestOnBeforeSendHeadersListener(); + + break; + } + default: + break; + } + + return response; +} +function registerBrowserRuntimeOnMessageListener() { + browser.runtime.onMessage.addListener(browserRuntimeOnMessageListener); +} + +function browserWebRequestOnBeforeSendHeadersListener( + details: Browser.webRequest.OnBeforeSendHeadersDetails +): Browser.webRequest.BlockingResponse | undefined { + const referer = temporaryReferers[new URL(details.url).host]; + if (!referer) return; + + details.requestHeaders!.push({ + name: 'Referer', + value: `https://${referer}/` }); - // the following listener is only available in mv2 + return { requestHeaders: details.requestHeaders }; +} +function registerBrowserWebRequestOnBeforeSendHeadersListener() { + // only available in mv2 if (import.meta.env.MANIFEST_VERSION === 3) return; + // background crashes when a listener with empty url is registered + else if (Object.keys(temporaryReferers).length === 0) return; + browser.webRequest.onBeforeSendHeaders.removeListener(browserWebRequestOnBeforeSendHeadersListener); browser.webRequest.onBeforeSendHeaders.addListener( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - async (details) => { - const referer = await UrlReferer.get(new URL(details.url).hostname); - if (!referer) return; - - details.requestHeaders!.push({ - name: 'Referer', - value: `https://${referer}/` - }); - - return { requestHeaders: details.requestHeaders }; + browserWebRequestOnBeforeSendHeadersListener, + { + urls: domainsToMatch(Object.keys(temporaryReferers)), + types: ['xmlhttprequest'] }, - { urls: [''], types: ['xmlhttprequest'] }, ['blocking', 'requestHeaders'] ); +} - browser.webRequest.onBeforeRedirect.addListener( - async (details) => { - const domain = new URL(details.url).host; - const host = await getHost(domain); - if (!host) return; +async function browserWebRequestOnBeforeRedirectListener(details: Browser.webRequest.OnBeforeRedirectDetails) { + const domain = new URL(details.url).host; + const host = await getHost(domain); + if (!host) return; - const newDomain = new URL(details.redirectUrl).host; + const newDomain = new URL(details.redirectUrl).host; - await Promise.all([ - HostSettings.addTemporaryHostDomain(host.id, newDomain, { registerContentScript: false }), - // must be called manually, a message sent from a background script is only received on non-background pages - reInjectContentScript(newDomain) - ]); - }, - { urls: [''], types: ['main_frame', 'sub_frame'] } - ); + await Promise.all([ + HostSettings.addTemporaryHostDomain(host.id, newDomain, { registerContentScript: false }), + // must be called manually, a message sent from a background script is only received on non-background pages + reInjectContentScript(newDomain) + ]); + + // add new domain to internal list of supported hosts and remount listener to also listen on the new domain + if (allHostDomains.indexOf(newDomain) === -1) allHostDomains.push(newDomain); + queueMicrotask(registerBrowserWebRequestOnBeforeRedirectListener); +} +function registerBrowserWebRequestOnBeforeRedirectListener() { + // only available in mv2 + if (import.meta.env.MANIFEST_VERSION === 3) return; + + browser.webRequest.onBeforeRedirect.removeListener(browserWebRequestOnBeforeRedirectListener); + browser.webRequest.onBeforeRedirect.addListener(browserWebRequestOnBeforeRedirectListener, { + urls: domainsToMatch(allHostDomains), + types: ['main_frame', 'sub_frame'] + }); +} + +export default defineBackground(() => { + registerBrowserRuntimeOnMessageListener(); + registerBrowserWebRequestOnBeforeRedirectListener(); }); diff --git a/src/entrypoints/player/player.ts b/src/entrypoints/player/player.ts index e6578b3..32f7965 100644 --- a/src/entrypoints/player/player.ts +++ b/src/entrypoints/player/player.ts @@ -1,11 +1,19 @@ import Hls from 'hls.js'; -import { listenTabMessages, sendTabBroadcast, TabMessageType } from '@/lib/communication'; +import { + BackgroundMessageType, + listenTabMessages, + sendBackgroundMessage, + sendTabBroadcast, + TabMessageType +} from '@/lib/communication'; import { HostMatchType, hosts } from '@/lib/host'; -import { UrlReferer } from '@/lib/settings'; async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) { // multiple hosts need to have a correct referer set - await UrlReferer.addTemporary(new URL(url).hostname, domain); + await sendBackgroundMessage(BackgroundMessageType.RegisterTemporaryReferer, { + domain: new URL(url).host, + referer: domain + }); videoElem.src = url; } @@ -18,7 +26,11 @@ async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) enableWorker: false, xhrSetup: async (xhr: XMLHttpRequest, url: string) => { // multiple hosts need to have a correct referer set - await UrlReferer.addTemporary(new URL(url).hostname, domain); + await sendBackgroundMessage(BackgroundMessageType.RegisterTemporaryReferer, { + domain: new URL(url).host, + referer: domain + }); + xhr.open('GET', url); } }); diff --git a/src/lib/communication.ts b/src/lib/communication.ts index 6daa955..45d7ca9 100644 --- a/src/lib/communication.ts +++ b/src/lib/communication.ts @@ -37,7 +37,8 @@ export function listenTabMessages(listener: (type: TabMessageType, data: any) => export enum BackgroundMessageType { Ff2mpv = 20, RequestTabUrl, - RegisterContentScript + RegisterContentScript, + RegisterTemporaryReferer } export type BackgroundMessageData = { @@ -48,12 +49,17 @@ export type BackgroundMessageData = { [BackgroundMessageType.RegisterContentScript]: { domain: string; }; + [BackgroundMessageType.RegisterTemporaryReferer]: { + domain: string; + referer: string; + }; }[T]; export type BackgroundMessageReply = { [BackgroundMessageType.Ff2mpv]: undefined; [BackgroundMessageType.RequestTabUrl]: string | null; [BackgroundMessageType.RegisterContentScript]: undefined; + [BackgroundMessageType.RegisterTemporaryReferer]: undefined; }[T]; export async function sendBackgroundMessage( diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 31e4501..9a596ec 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -67,18 +67,6 @@ export class FF2MPVSettings { static setEnabled = (enabled: boolean) => this.ff2mpvEnabled.set(enabled); } -export class UrlReferer { - private static temporaryUrlReferer = new Setting>('local:temporaryUrlReferer', {}); - - static addTemporary = (hostname: string, referer: string) => - this.temporaryUrlReferer.update((val) => { - val[hostname] = referer; - return val; - }); - - static get = (hostname: string) => this.temporaryUrlReferer.get().then((val) => val[hostname] ?? null); -} - export interface PerDomainConfig { allDisabled: boolean; disabledHostIds: HostId[];