mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-12 18:39:58 +00:00
Add per-item toggles to site whitelist
This commit is contained in:
@@ -393,6 +393,14 @@
|
|||||||
"message": "If specified, a custom user agent string to use specifically for sites matching this pattern.",
|
"message": "If specified, a custom user agent string to use specifically for sites matching this pattern.",
|
||||||
"description": "Whitelist item user agent option label."
|
"description": "Whitelist item user agent option label."
|
||||||
},
|
},
|
||||||
|
"optionsSiteWhitelistItemEnabled": {
|
||||||
|
"message": "Enable whitelist item",
|
||||||
|
"description": "Whitelist item enabled checkbox title."
|
||||||
|
},
|
||||||
|
"optionsSiteWhitelistItemPattern": {
|
||||||
|
"message": "Match pattern",
|
||||||
|
"description": "Whitelist item pattern edit input title."
|
||||||
|
},
|
||||||
|
|
||||||
"optionsMirroringCategoryName": {
|
"optionsMirroringCategoryName": {
|
||||||
"message": "Screen/tab casting",
|
"message": "Screen/tab casting",
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ async function onMenuClicked(
|
|||||||
const whitelist = await options.get("siteWhitelist");
|
const whitelist = await options.get("siteWhitelist");
|
||||||
if (!whitelist.find(item => item.pattern === pattern)) {
|
if (!whitelist.find(item => item.pattern === pattern)) {
|
||||||
// Add to whitelist and update options
|
// Add to whitelist and update options
|
||||||
whitelist.push({ pattern });
|
whitelist.push({ pattern, isEnabled: true });
|
||||||
await options.set("siteWhitelist", whitelist);
|
await options.set("siteWhitelist", whitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ type OnBeforeRequestDetails = Parameters<
|
|||||||
|
|
||||||
export interface WhitelistItemData {
|
export interface WhitelistItemData {
|
||||||
pattern: string;
|
pattern: string;
|
||||||
|
isEnabled: boolean;
|
||||||
isUserAgentDisabled?: boolean;
|
isUserAgentDisabled?: boolean;
|
||||||
customUserAgent?: string;
|
customUserAgent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originUrlCache: string[] = [];
|
let matchPatterns: RemoteMatchPattern[] = [];
|
||||||
|
|
||||||
let platform: string;
|
let platform: string;
|
||||||
let chromeUserAgent: string | undefined;
|
let chromeUserAgent: string | undefined;
|
||||||
@@ -47,31 +48,25 @@ export async function initWhitelist() {
|
|||||||
platform = (await browser.runtime.getPlatformInfo()).os;
|
platform = (await browser.runtime.getPlatformInfo()).os;
|
||||||
chromeUserAgent = getChromeUserAgent(platform);
|
chromeUserAgent = getChromeUserAgent(platform);
|
||||||
chromeUserAgentHybrid = getChromeUserAgent(platform, true);
|
chromeUserAgentHybrid = getChromeUserAgent(platform, true);
|
||||||
|
|
||||||
customUserAgent = await options.get("siteWhitelistCustomUserAgent");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a UA string can't be obtained, don't bother continuing
|
|
||||||
* extension initialization
|
|
||||||
*/
|
|
||||||
if (!chromeUserAgent) {
|
if (!chromeUserAgent) {
|
||||||
throw logger.error("Failed to get Chrome UA string");
|
throw logger.error("Failed to get Chrome UA string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customUserAgent = await options.get("siteWhitelistCustomUserAgent");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register on first run
|
// Register on first run
|
||||||
await registerSiteWhitelist();
|
await registerSiteWhitelist();
|
||||||
|
|
||||||
// Re-register when options change
|
|
||||||
options.addEventListener("changed", async ev => {
|
options.addEventListener("changed", async ev => {
|
||||||
const alteredOpts = ev.detail;
|
// Update custom UA on change
|
||||||
|
if (ev.detail.includes("siteWhitelistCustomUserAgent")) {
|
||||||
if (alteredOpts.includes("siteWhitelistCustomUserAgent")) {
|
|
||||||
customUserAgent = await options.get("siteWhitelistCustomUserAgent");
|
customUserAgent = await options.get("siteWhitelistCustomUserAgent");
|
||||||
}
|
}
|
||||||
|
// Re-register whitelist on change
|
||||||
if (
|
if (
|
||||||
alteredOpts.includes("siteWhitelist") ||
|
ev.detail.includes("siteWhitelist") ||
|
||||||
alteredOpts.includes("siteWhitelistEnabled")
|
ev.detail.includes("siteWhitelistEnabled")
|
||||||
) {
|
) {
|
||||||
unregisterSiteWhitelist();
|
unregisterSiteWhitelist();
|
||||||
registerSiteWhitelist();
|
registerSiteWhitelist();
|
||||||
@@ -93,7 +88,7 @@ function getUserAgent(url: string, host?: string): Optional<string> {
|
|||||||
new RemoteMatchPattern(item.pattern).matches(url)
|
new RemoteMatchPattern(item.pattern).matches(url)
|
||||||
);
|
);
|
||||||
if (matchingItem) {
|
if (matchingItem) {
|
||||||
if (matchingItem.isUserAgentDisabled) return;
|
if (!matchingItem.isEnabled || matchingItem.isUserAgentDisabled) return;
|
||||||
return matchingItem.customUserAgent;
|
return matchingItem.customUserAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +99,9 @@ function getUserAgent(url: string, host?: string): Optional<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web apps usually only load the sender library and
|
* Override User-Agent header for requests to whitelisted URLs. Sites
|
||||||
* provide cast functionality if the browser is detected
|
* with Chromecast support will usually only load the Cast SDK if they
|
||||||
* as Chrome, so we should rewrite the User-Agent header
|
* detect a Chrome user agent string.
|
||||||
* to reflect this on whitelisted sites.
|
|
||||||
*/
|
*/
|
||||||
async function onWhitelistedBeforeSendHeaders(
|
async function onWhitelistedBeforeSendHeaders(
|
||||||
details: OnBeforeSendHeadersDetails
|
details: OnBeforeSendHeadersDetails
|
||||||
@@ -118,10 +112,6 @@ async function onWhitelistedBeforeSendHeaders(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.originUrl && !originUrlCache.includes(details.originUrl)) {
|
|
||||||
originUrlCache.push(details.originUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = details.requestHeaders.find(header => header.name === "Host");
|
const host = details.requestHeaders.find(header => header.name === "Host");
|
||||||
|
|
||||||
for (const header of details.requestHeaders) {
|
for (const header of details.requestHeaders) {
|
||||||
@@ -137,10 +127,9 @@ async function onWhitelistedBeforeSendHeaders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests from within child frames should also adopt
|
* Override User-Agent header for requests from child frames of
|
||||||
* the modified User-Agent header to support embedded
|
* whitelisted URLs to support embedded players on other origins (e.g.
|
||||||
* players on other origins (like CDN domains) when the
|
* CDN domains).
|
||||||
* main site is whitelisted.
|
|
||||||
*/
|
*/
|
||||||
function onWhitelistedChildBeforeSendHeaders(
|
function onWhitelistedChildBeforeSendHeaders(
|
||||||
details: OnBeforeSendHeadersDetails
|
details: OnBeforeSendHeadersDetails
|
||||||
@@ -150,55 +139,54 @@ function onWhitelistedChildBeforeSendHeaders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const ancestor of details.frameAncestors) {
|
for (const ancestor of details.frameAncestors) {
|
||||||
if (originUrlCache.includes(ancestor.url)) {
|
// If no matching patterns
|
||||||
const host = details.requestHeaders.find(
|
if (!matchPatterns.some(pattern => pattern.matches(ancestor.url))) {
|
||||||
header => header.name === "Host"
|
continue;
|
||||||
);
|
|
||||||
|
|
||||||
for (const header of details.requestHeaders) {
|
|
||||||
if (header.name === "User-Agent") {
|
|
||||||
header.value = getUserAgent(details.url, host?.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
requestHeaders: details.requestHeaders
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override User-Agent header
|
||||||
|
const requestHeaders = details.requestHeaders;
|
||||||
|
for (const header of requestHeaders) {
|
||||||
|
if (header.name === "User-Agent") {
|
||||||
|
const host = requestHeaders.find(
|
||||||
|
header => header.name === "Host"
|
||||||
|
);
|
||||||
|
header.value = getUserAgent(details.url, host?.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { requestHeaders };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sender applications load a cast_sender.js script that
|
* Handle requests to cast_sender.js SDK loader script and redirect to
|
||||||
* functions as a loader for the internal chrome-extension:
|
* the extension's implementation.
|
||||||
* hosted script.
|
|
||||||
*
|
|
||||||
* We can redirect this and inject our own script to setup
|
|
||||||
* the API.
|
|
||||||
*/
|
*/
|
||||||
async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
|
async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
|
||||||
if (!details.originUrl || details.tabId === -1) {
|
if (!details.originUrl || details.tabId === -1) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check against whitelist if enabled
|
// Test against whitelist if enabled
|
||||||
if (await options.get("siteWhitelistEnabled")) {
|
if (await options.get("siteWhitelistEnabled")) {
|
||||||
if (!details?.frameAncestors?.length) {
|
/**
|
||||||
if (!originUrlCache.includes(details.originUrl)) {
|
* Frame ancestor URLs (if present) or origin URL that the SDK
|
||||||
return {};
|
* is loaded from.
|
||||||
}
|
*/
|
||||||
} else {
|
const urls = [
|
||||||
let hasMatchingAncestor = false;
|
...(details.frameAncestors?.map(ancestor => ancestor.url) ?? []),
|
||||||
for (const ancestor of details.frameAncestors) {
|
details.originUrl
|
||||||
if (originUrlCache.includes(ancestor.url)) {
|
];
|
||||||
hasMatchingAncestor = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMatchingAncestor) {
|
// Allow request if no whitelist matches
|
||||||
return {};
|
if (
|
||||||
}
|
!urls.some(url =>
|
||||||
|
matchPatterns.some(pattern => pattern.matches(url))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,12 +216,18 @@ async function registerSiteWhitelist() {
|
|||||||
siteWhitelist = opts.siteWhitelist;
|
siteWhitelist = opts.siteWhitelist;
|
||||||
siteWhitelistEnabled = opts.siteWhitelistEnabled;
|
siteWhitelistEnabled = opts.siteWhitelistEnabled;
|
||||||
|
|
||||||
|
// Parse match patterns once
|
||||||
|
matchPatterns = siteWhitelist.map(
|
||||||
|
item => new RemoteMatchPattern(item.pattern)
|
||||||
|
);
|
||||||
|
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
onBeforeCastSDKRequest,
|
onBeforeCastSDKRequest,
|
||||||
{ urls: [CAST_LOADER_SCRIPT_URL, CAST_FRAMEWORK_LOADER_SCRIPT_URL] },
|
{ urls: [CAST_LOADER_SCRIPT_URL, CAST_FRAMEWORK_LOADER_SCRIPT_URL] },
|
||||||
["blocking"]
|
["blocking"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Skip whitelist request listeners if disabled or empty
|
||||||
if (!siteWhitelistEnabled || !siteWhitelist.length) {
|
if (!siteWhitelistEnabled || !siteWhitelist.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -243,7 +237,9 @@ async function registerSiteWhitelist() {
|
|||||||
{
|
{
|
||||||
// Filter for items with UA enabled
|
// Filter for items with UA enabled
|
||||||
urls: siteWhitelist.flatMap(item =>
|
urls: siteWhitelist.flatMap(item =>
|
||||||
!item.isUserAgentDisabled ? [item.pattern] : []
|
item.isEnabled && !item.isUserAgentDisabled
|
||||||
|
? [item.pattern]
|
||||||
|
: []
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
["blocking", "requestHeaders"]
|
["blocking", "requestHeaders"]
|
||||||
@@ -257,8 +253,6 @@ async function registerSiteWhitelist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function unregisterSiteWhitelist() {
|
function unregisterSiteWhitelist() {
|
||||||
originUrlCache.length = 0;
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders.removeListener(
|
browser.webRequest.onBeforeSendHeaders.removeListener(
|
||||||
onWhitelistedBeforeSendHeaders
|
onWhitelistedBeforeSendHeaders
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default {
|
|||||||
receiverSelectorCloseIfFocusLost: true,
|
receiverSelectorCloseIfFocusLost: true,
|
||||||
receiverSelectorWaitForConnection: true,
|
receiverSelectorWaitForConnection: true,
|
||||||
siteWhitelistEnabled: true,
|
siteWhitelistEnabled: true,
|
||||||
siteWhitelist: [{ pattern: "https://www.netflix.com/*" }],
|
siteWhitelist: [{ pattern: "https://www.netflix.com/*", isEnabled: true }],
|
||||||
siteWhitelistCustomUserAgent: "",
|
siteWhitelistCustomUserAgent: "",
|
||||||
showAdvancedOptions: false
|
showAdvancedOptions: false
|
||||||
} as Options;
|
} as Options;
|
||||||
|
|||||||
@@ -98,10 +98,13 @@
|
|||||||
if (isEditing) return;
|
if (isEditing) return;
|
||||||
|
|
||||||
if (knownAppToAdd?.matches) {
|
if (knownAppToAdd?.matches) {
|
||||||
items = [...items, { pattern: knownAppToAdd.matches }];
|
items = [
|
||||||
|
...items,
|
||||||
|
{ pattern: knownAppToAdd.matches, isEnabled: true }
|
||||||
|
];
|
||||||
knownAppToAdd = null;
|
knownAppToAdd = null;
|
||||||
} else {
|
} else {
|
||||||
items = [...items, { pattern: "" }];
|
items = [...items, { pattern: "", isEnabled: true }];
|
||||||
beginEditing(items.length - 1);
|
beginEditing(items.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +132,17 @@
|
|||||||
class="whitelist__item"
|
class="whitelist__item"
|
||||||
class:whitelist__item--selected={isEditingItem}
|
class:whitelist__item--selected={isEditingItem}
|
||||||
class:whitelist__item--expanded={isItemExpanded}
|
class:whitelist__item--expanded={isItemExpanded}
|
||||||
|
class:whitelist__item--disabled={!item.isEnabled}
|
||||||
|
class:whitelist__item--editing={isEditingItem}
|
||||||
>
|
>
|
||||||
|
{#if !isEditingItem}
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
title={_("optionsSiteWhitelistItemEnabled")}
|
||||||
|
bind:checked={item.isEnabled}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="whitelist__title"
|
class="whitelist__title"
|
||||||
on:dblclick={() => beginEditing(i)}
|
on:dblclick={() => beginEditing(i)}
|
||||||
@@ -140,6 +153,7 @@
|
|||||||
class="whitelist__input-pattern"
|
class="whitelist__input-pattern"
|
||||||
pattern={REMOTE_MATCH_PATTERN_REGEX.source}
|
pattern={REMOTE_MATCH_PATTERN_REGEX.source}
|
||||||
required
|
required
|
||||||
|
title={_("optionsSiteWhitelistItemPattern")}
|
||||||
bind:this={editingInput}
|
bind:this={editingInput}
|
||||||
bind:value={editingValue}
|
bind:value={editingValue}
|
||||||
on:input={onEditInput}
|
on:input={onEditInput}
|
||||||
|
|||||||
@@ -367,6 +367,9 @@ button.ghost:not(:hover) {
|
|||||||
.whitelist__item--selected {
|
.whitelist__item--selected {
|
||||||
background-color: var(--blue-50-a30) !important;
|
background-color: var(--blue-50-a30) !important;
|
||||||
}
|
}
|
||||||
|
.whitelist__item--disabled:not(.whitelist__item--editing) .whitelist__title {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
.whitelist__title {
|
.whitelist__title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
|
|
||||||
const whitelist = await options.get("siteWhitelist");
|
const whitelist = await options.get("siteWhitelist");
|
||||||
if (!whitelist.find(item => item.pattern === app.matches)) {
|
if (!whitelist.find(item => item.pattern === app.matches)) {
|
||||||
whitelist.push({ pattern: app.matches });
|
whitelist.push({ pattern: app.matches, isEnabled: true });
|
||||||
await options.set("siteWhitelist", whitelist);
|
await options.set("siteWhitelist", whitelist);
|
||||||
|
|
||||||
await browser.tabs.reload(pageInfo.tabId);
|
await browser.tabs.reload(pageInfo.tabId);
|
||||||
|
|||||||
Reference in New Issue
Block a user