only listen to send headers and redirect request where it's necessary

This commit is contained in:
2026-07-10 16:10:24 +02:00
parent d7e1cbfb48
commit f7cfe41211
4 changed files with 132 additions and 75 deletions
+109 -58
View File
@@ -1,12 +1,23 @@
import { BackgroundMessageData, BackgroundMessageType } from '@/lib/communication'; import { BackgroundMessageData, BackgroundMessageType } from '@/lib/communication';
import { getHost } from '@/lib/host'; import { getHost, hosts } from '@/lib/host';
import { HostSettings, UrlReferer } from '@/lib/settings'; import { HostSettings } from '@/lib/settings';
const temporaryReferers: Record<string, string> = {};
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) { async function reInjectContentScript(domain: string) {
const contentScript: Browser.scripting.RegisteredContentScript = { const contentScript: Browser.scripting.RegisteredContentScript = {
id: 'temporary-hosts', id: 'temporary-hosts',
js: ['content-scripts/content.js'], js: ['content-scripts/content.js'],
matches: [`*://${domain}/*`], matches: [domainToMatch(domain)],
persistAcrossSessions: false, persistAcrossSessions: false,
allFrames: true, allFrames: true,
runAt: 'document_end' runAt: 'document_end'
@@ -26,75 +37,115 @@ async function reInjectContentScript(domain: string) {
} }
} }
export default defineBackground(() => { /* --- listeners --- */
browser.runtime.onMessage.addListener(async (message, sender) => { async function browserRuntimeOnMessageListener(message: any, sender: Browser.runtime.MessageSender) {
const type = message.type as BackgroundMessageType; const type = message.type as BackgroundMessageType;
let data = message.data; let data = message.data;
let response; let response;
switch (type) { switch (type) {
case BackgroundMessageType.Ff2mpv: { case BackgroundMessageType.Ff2mpv: {
data = data as BackgroundMessageData<typeof type>; data = data as BackgroundMessageData<typeof type>;
await browser.runtime.sendNativeMessage('ff2mpv', { url: data.url }); await browser.runtime.sendNativeMessage('ff2mpv', { url: data.url });
break; 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<typeof type>;
await reInjectContentScript(data.domain);
break;
}
default:
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<typeof type>;
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<typeof type>;
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; 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( browser.webRequest.onBeforeSendHeaders.addListener(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment browserWebRequestOnBeforeSendHeadersListener,
// @ts-ignore {
async (details) => { urls: domainsToMatch(Object.keys(temporaryReferers)),
const referer = await UrlReferer.get(new URL(details.url).hostname); types: ['xmlhttprequest']
if (!referer) return;
details.requestHeaders!.push({
name: 'Referer',
value: `https://${referer}/`
});
return { requestHeaders: details.requestHeaders };
}, },
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
['blocking', 'requestHeaders'] ['blocking', 'requestHeaders']
); );
}
browser.webRequest.onBeforeRedirect.addListener( async function browserWebRequestOnBeforeRedirectListener(details: Browser.webRequest.OnBeforeRedirectDetails) {
async (details) => { const domain = new URL(details.url).host;
const domain = new URL(details.url).host; const host = await getHost(domain);
const host = await getHost(domain); if (!host) return;
if (!host) return;
const newDomain = new URL(details.redirectUrl).host; const newDomain = new URL(details.redirectUrl).host;
await Promise.all([ await Promise.all([
HostSettings.addTemporaryHostDomain(host.id, newDomain, { registerContentScript: false }), 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 // must be called manually, a message sent from a background script is only received on non-background pages
reInjectContentScript(newDomain) reInjectContentScript(newDomain)
]); ]);
},
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] } // 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();
}); });
+16 -4
View File
@@ -1,11 +1,19 @@
import Hls from 'hls.js'; 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 { HostMatchType, hosts } from '@/lib/host';
import { UrlReferer } from '@/lib/settings';
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) { async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
// multiple hosts need to have a correct referer set // 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; videoElem.src = url;
} }
@@ -18,7 +26,11 @@ async function playHls(url: string, domain: string, videoElem: HTMLVideoElement)
enableWorker: false, enableWorker: false,
xhrSetup: async (xhr: XMLHttpRequest, url: string) => { xhrSetup: async (xhr: XMLHttpRequest, url: string) => {
// multiple hosts need to have a correct referer set // 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); xhr.open('GET', url);
} }
}); });
+7 -1
View File
@@ -37,7 +37,8 @@ export function listenTabMessages(listener: (type: TabMessageType, data: any) =>
export enum BackgroundMessageType { export enum BackgroundMessageType {
Ff2mpv = 20, Ff2mpv = 20,
RequestTabUrl, RequestTabUrl,
RegisterContentScript RegisterContentScript,
RegisterTemporaryReferer
} }
export type BackgroundMessageData<T extends BackgroundMessageType> = { export type BackgroundMessageData<T extends BackgroundMessageType> = {
@@ -48,12 +49,17 @@ export type BackgroundMessageData<T extends BackgroundMessageType> = {
[BackgroundMessageType.RegisterContentScript]: { [BackgroundMessageType.RegisterContentScript]: {
domain: string; domain: string;
}; };
[BackgroundMessageType.RegisterTemporaryReferer]: {
domain: string;
referer: string;
};
}[T]; }[T];
export type BackgroundMessageReply<T extends BackgroundMessageType> = { export type BackgroundMessageReply<T extends BackgroundMessageType> = {
[BackgroundMessageType.Ff2mpv]: undefined; [BackgroundMessageType.Ff2mpv]: undefined;
[BackgroundMessageType.RequestTabUrl]: string | null; [BackgroundMessageType.RequestTabUrl]: string | null;
[BackgroundMessageType.RegisterContentScript]: undefined; [BackgroundMessageType.RegisterContentScript]: undefined;
[BackgroundMessageType.RegisterTemporaryReferer]: undefined;
}[T]; }[T];
export async function sendBackgroundMessage<T extends BackgroundMessageType>( export async function sendBackgroundMessage<T extends BackgroundMessageType>(
-12
View File
@@ -67,18 +67,6 @@ export class FF2MPVSettings {
static setEnabled = (enabled: boolean) => this.ff2mpvEnabled.set(enabled); static setEnabled = (enabled: boolean) => this.ff2mpvEnabled.set(enabled);
} }
export class UrlReferer {
private static temporaryUrlReferer = new Setting<Record<string, string>>('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 { export interface PerDomainConfig {
allDisabled: boolean; allDisabled: boolean;
disabledHostIds: HostId[]; disabledHostIds: HostId[];