mirror of
https://github.com/bytedream/stream-bypass.git
synced 2026-08-04 21:10:40 +02:00
only listen to send headers and redirect request where it's necessary
This commit is contained in:
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
Reference in New Issue
Block a user