diff --git a/src/background/handlers/onBeforeVideoWeaverRequest.ts b/src/background/handlers/onBeforeVideoWeaverRequest.ts index deec2f2..9eba3c7 100644 --- a/src/background/handlers/onBeforeVideoWeaverRequest.ts +++ b/src/background/handlers/onBeforeVideoWeaverRequest.ts @@ -38,7 +38,7 @@ export default function onBeforeVideoWeaverRequest( textLower.includes("https://example.com") && textLower.includes("https://help.twitch.tv/"); const proxy = - details.proxyInfo && details.proxyInfo.type !== "DIRECT" + details.proxyInfo && details.proxyInfo.type !== "direct" ? getUrlFromProxyInfo(details.proxyInfo) : null; diff --git a/src/background/handlers/onProxyRequest.ts b/src/background/handlers/onProxyRequest.ts index a27e60d..5dbaf5f 100644 --- a/src/background/handlers/onProxyRequest.ts +++ b/src/background/handlers/onProxyRequest.ts @@ -32,7 +32,7 @@ export default async function onProxyRequest( } const host = getHostFromUrl(details.url); - if (!host) return { type: "DIRECT" }; + if (!host) return { type: "direct" }; const documentHost = details.documentUrl ? getHostFromUrl(details.documentUrl) @@ -43,7 +43,7 @@ export default async function onProxyRequest( !passportHostRegex.test(documentHost) && // Passport requests have a `passport.twitch.tv` document URL. !twitchTvHostRegex.test(documentHost) ) { - return { type: "DIRECT" }; + return { type: "direct" }; } const proxies = store.state.optimizedProxiesEnabled @@ -92,12 +92,12 @@ export default async function onProxyRequest( if (proxyUsherRequest && usherHostRegex.test(host)) { if (details.url.includes("/vod/")) { console.log(`✋ '${details.url}' is a VOD manifest.`); - return { type: "DIRECT" }; + return { type: "direct" }; } const channelName = findChannelFromUsherUrl(details.url); if (isChannelWhitelisted(channelName)) { console.log(`✋ Channel '${channelName}' is whitelisted.`); - return { type: "DIRECT" }; + return { type: "direct" }; } console.log( `⌛ Proxying ${details.url} (${ @@ -119,7 +119,7 @@ export default async function onProxyRequest( findChannelFromTwitchTvUrl(tabUrl); if (isChannelWhitelisted(channelName)) { console.log(`✋ Channel '${channelName}' is whitelisted.`); - return { type: "DIRECT" }; + return { type: "direct" }; } console.log( `⌛ Proxying ${details.url} (${ @@ -149,12 +149,12 @@ export default async function onProxyRequest( return proxyInfoArray; } - return { type: "DIRECT" }; + return { type: "direct" }; } function getProxyInfoArrayFromUrls(urls: string[]): ProxyInfo[] { return [ ...urls.map(getProxyInfoFromUrl), - { type: "DIRECT" } as ProxyInfo, // Fallback to direct connection if all proxies fail. + { type: "direct" } as ProxyInfo, // Fallback to direct connection if all proxies fail. ]; } diff --git a/src/background/handlers/onResponseStarted.ts b/src/background/handlers/onResponseStarted.ts index e188386..a02b436 100644 --- a/src/background/handlers/onResponseStarted.ts +++ b/src/background/handlers/onResponseStarted.ts @@ -171,7 +171,7 @@ function getProxyFromDetails( return getUrlFromProxyInfo(possibleProxies[0]); } else { const proxyInfo = details.proxyInfo; // Firefox only. - if (!proxyInfo || proxyInfo.type === "DIRECT") return null; + if (!proxyInfo || proxyInfo.type === "direct") return null; return getUrlFromProxyInfo(proxyInfo); } } diff --git a/src/common/ts/proxyInfo.ts b/src/common/ts/proxyInfo.ts index 7f42552..1e46349 100644 --- a/src/common/ts/proxyInfo.ts +++ b/src/common/ts/proxyInfo.ts @@ -1,36 +1,24 @@ import { Address6 } from "ip-address"; -import type { ProxyInfo } from "../../types"; +import type { ProxyInfo, ProxyType } from "../../types"; -export const proxySchemes = { - direct: "DIRECT", - http: "PROXY", - https: "HTTPS", - socks: "SOCKS", - socks4: "SOCKS4", - socks5: "SOCKS5", - quic: "QUIC", -} as const; - -type Protocol = keyof typeof proxySchemes; - -const defaultPorts: Partial<{ - [key in keyof typeof proxySchemes]: number; -}> = { +const DEFAULT_PORTS: Readonly> = Object.freeze({ + direct: 0, http: 80, https: 443, socks: 1080, socks4: 1080, - socks5: 1080, - quic: 443, -}; +}); -export function getProxyInfoFromUrl(url: string) { - let protocol = ""; +export function getProxyInfoFromUrl( + url: string +): ProxyInfo & { host: string; port: number } { + let type: ProxyType | undefined = undefined; if (url.includes("://")) { - const [_protocol, urlWithoutProtocol] = url.split("://"); - protocol = _protocol; + const [protocol, urlWithoutProtocol] = url.split("://", 2); + type = protocol as ProxyType; url = urlWithoutProtocol; } + const lastIndexOfAt = url.lastIndexOf("@"); const hostname = url.substring(lastIndexOfAt + 1, url.length); const lastIndexOfColon = getLastIndexOfColon(hostname); @@ -39,9 +27,7 @@ export function getProxyInfoFromUrl(url: string) { let port: number | undefined = undefined; if (lastIndexOfColon === -1) { host = hostname; - if (!protocol) { - port = 3128; // Default port - } + port = !type ? 3128 : DEFAULT_PORTS[type]; // Default port } else { host = hostname.substring(0, lastIndexOfColon); port = Number(hostname.substring(lastIndexOfColon + 1, hostname.length)); @@ -59,11 +45,8 @@ export function getProxyInfoFromUrl(url: string) { password = credentials.substring(indexOfColon + 1, credentials.length); } - port = port ? port : defaultPorts[protocol as Protocol]; - return { - type: proxySchemes[protocol as Protocol] ?? "PROXY", - protocol, + type: type ?? "http", host, port, username, diff --git a/src/common/ts/proxySettings.ts b/src/common/ts/proxySettings.ts index d493f5f..39f3de5 100644 --- a/src/common/ts/proxySettings.ts +++ b/src/common/ts/proxySettings.ts @@ -1,5 +1,5 @@ import store from "../../store"; -import { ProxyRequestType } from "../../types"; +import { ProxyRequestType, ProxyType } from "../../types"; import isRequestTypeProxied from "./isRequestTypeProxied"; import { getProxyInfoFromUrl, getUrlFromProxyInfo } from "./proxyInfo"; import { @@ -11,6 +11,14 @@ import { } from "./regexes"; import updateDnsResponses from "./updateDnsResponses"; +const PROXY_TYPE_MAP: Readonly> = Object.freeze({ + direct: "DIRECT", + http: "PROXY", + https: "HTTPS", + socks4: "SOCKS4", + socks: "SOCKS5", +}); + export function updateProxySettings(requestFilter?: ProxyRequestType[]) { const { optimizedProxiesEnabled, passportLevel } = store.state; @@ -92,7 +100,7 @@ function getProxyInfoStringFromUrls(urls: string[]): string { return [ ...urls.map(url => { const proxyInfo = getProxyInfoFromUrl(url); - return `${proxyInfo.type} ${getUrlFromProxyInfo({ + return `${PROXY_TYPE_MAP[proxyInfo.type]} ${getUrlFromProxyInfo({ ...proxyInfo, // Don't include username/password in PAC script. username: undefined, diff --git a/src/options/options.ts b/src/options/options.ts index c78a016..75e4a38 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -85,6 +85,9 @@ const optimizedProxiesListElement = $( ) as HTMLOListElement; const normalProxiesInputElement = $("#normal") as HTMLInputElement; const normalProxiesListElement = $("#normal-proxies-list") as HTMLOListElement; +const otherProtocolsCheckboxElement = $( + "#other-protocols-checkbox" +) as HTMLInputElement; // Ad log const adLogEnabledCheckboxElement = $( "#ad-log-enabled-checkbox" @@ -219,6 +222,12 @@ function main() { hidePromptMarker: true, insertMode: "both", }); + otherProtocolsCheckboxElement.checked = store.state.allowOtherProxyProtocols; + otherProtocolsCheckboxElement.addEventListener("change", () => { + store.state.allowOtherProxyProtocols = + otherProtocolsCheckboxElement.checked; + location.reload(); + }); // Ad log adLogEnabledCheckboxElement.checked = store.state.adLogEnabled; adLogEnabledCheckboxElement.addEventListener("change", () => { @@ -336,16 +345,18 @@ function isOptimizedProxyUrlAllowed(url: string): AllowedResult { return [false, "TTV LOL PRO v1 proxies are not compatible"]; } - const proxyInfo = getProxyInfoFromUrl(url); - if (proxyInfo.host.includes("/")) { - return [false, "Proxy URLs must not contain a path (e.g. '/path')"]; + if (url.includes("://")) { + const [protocol, urlWithoutProtocol] = url.split("://", 2); + if (!store.state.allowOtherProxyProtocols) { + return [false, "Proxy URLs must not contain a protocol (e.g. 'http://')"]; + } else if (!["http", "https", "socks", "socks4"].includes(protocol)) { + return [false, `'${protocol}' is not a supported protocol`]; + } + url = urlWithoutProtocol; } - try { - const host = url.substring(url.lastIndexOf("@") + 1, url.length); - new URL(`http://${host}`); // Throws if the host is invalid. - } catch { - return [false, `'${url}' is not a valid proxy URL`]; + if (url.includes("/")) { + return [false, "Proxy URLs must not contain a path (e.g. '/path')"]; } try { diff --git a/src/options/page.html b/src/options/page.html index d11724c..44f6773 100644 --- a/src/options/page.html +++ b/src/options/page.html @@ -200,6 +200,36 @@ >List of other proxies" discussion on TTV LOL PRO's GitHub repository. +

+ diff --git a/src/store/getDefaultState.ts b/src/store/getDefaultState.ts index ea99dc2..3831f45 100644 --- a/src/store/getDefaultState.ts +++ b/src/store/getDefaultState.ts @@ -7,6 +7,7 @@ export default function getDefaultState() { adLog: [], adLogEnabled: true, adLogLastSent: 0, + allowOtherProxyProtocols: false, anonymousMode: true, chromiumProxyActive: false, dnsResponses: [], diff --git a/src/store/types.ts b/src/store/types.ts index f244c2d..666554b 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -10,6 +10,7 @@ export interface State { adLog: AdLogEntry[]; adLogEnabled: boolean; adLogLastSent: number; + allowOtherProxyProtocols: boolean; anonymousMode: boolean; chromiumProxyActive: boolean; dnsResponses: DnsResponse[]; diff --git a/src/types.ts b/src/types.ts index 86853f9..b8b45ea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,19 +3,11 @@ export type KeyOfType = keyof { [P in keyof T as T[P] extends V ? P : never]: any; }; -// From https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-server-schemes -export type ProxyScheme = - | "DIRECT" - | "PROXY" - | "HTTPS" - | "SOCKS" - | "SOCKS4" - | "SOCKS5" - | "QUIC"; +export type ProxyType = "direct" | "http" | "https" | "socks" | "socks4"; // From https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo export interface ProxyInfo { - type: ProxyScheme; + type: ProxyType; host?: string; port?: number; username?: string;