mirror of
https://github.com/younesaassila/ttv-lol-pro.git
synced 2025-04-29 14:04:26 +02:00
Merge pull request #369 from younesaassila/v2.4.0
Release version 2.4.0
This commit is contained in:
commit
fa1c74e23e
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -6,15 +6,17 @@ on:
|
||||
tags-ignore: ["**"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
concurrency: build-${{ github.ref }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Run linter
|
||||
|
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@ -18,6 +18,7 @@ on:
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ["v2"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
schedule:
|
||||
- cron: "32 9 * * 6"
|
||||
|
||||
@ -25,6 +26,7 @@ jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
concurrency: codeql-${{ github.ref }}
|
||||
permissions:
|
||||
actions: read
|
||||
@ -42,11 +44,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@ -59,7 +61,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@ -72,6 +74,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
8
.github/workflows/dependency-review.yml
vendored
8
.github/workflows/dependency-review.yml
vendored
@ -5,14 +5,20 @@
|
||||
# Source repository: https://github.com/actions/dependency-review-action
|
||||
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
|
||||
name: "Dependency Review"
|
||||
on: [pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
concurrency: dependencies-${{ github.ref }}
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v4
|
||||
|
2109
package-lock.json
generated
2109
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ttv-lol-pro",
|
||||
"version": "2.3.10",
|
||||
"version": "2.4.0",
|
||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||
"@parcel/bundler-default": {
|
||||
"minBundles": 10000000,
|
||||
@ -43,24 +43,22 @@
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"bowser": "^2.11.0",
|
||||
"ip-address": "^9.0.5",
|
||||
"ip-address": "^10.0.1",
|
||||
"m3u8-parser": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/config-webextension": "^2.12.0",
|
||||
"@types/chrome": "^0.0.271",
|
||||
"@types/jsbn": "^1.2.33",
|
||||
"@types/node": "^20.16.5",
|
||||
"@parcel/config-webextension": "^2.13.3",
|
||||
"@types/chrome": "^0.0.304",
|
||||
"@types/node": "^22.13.2",
|
||||
"@types/webextension-polyfill": "^0.12.1",
|
||||
"buffer": "^6.0.3",
|
||||
"os-browserify": "^0.3.0",
|
||||
"parcel": "^2.12.0",
|
||||
"postcss": "^8.4.47",
|
||||
"parcel": "^2.13.3",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-css-order": "^1.3.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.6.2",
|
||||
"svgo": "^3.3.2",
|
||||
"typescript": "^5.7.3",
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
},
|
||||
"private": true
|
||||
|
@ -4,8 +4,8 @@ export default function isChannelWhitelisted(
|
||||
channelName: string | null
|
||||
): boolean {
|
||||
if (!channelName) return false;
|
||||
const whitelistedChannelsLower = store.state.whitelistedChannels.map(
|
||||
channel => channel.toLowerCase()
|
||||
const channelNameLower = channelName.toLowerCase();
|
||||
return store.state.whitelistedChannels.some(
|
||||
channel => channel.toLowerCase() === channelNameLower
|
||||
);
|
||||
return whitelistedChannelsLower.includes(channelName.toLowerCase());
|
||||
}
|
||||
|
@ -1,9 +1,24 @@
|
||||
import { Address6 } from "ip-address";
|
||||
import type { ProxyInfo } from "../../types";
|
||||
import type { ProxyInfo, ProxyType } from "../../types";
|
||||
|
||||
const DEFAULT_PORTS: Readonly<Record<ProxyType, number>> = Object.freeze({
|
||||
direct: 0,
|
||||
http: 80,
|
||||
https: 443,
|
||||
socks: 1080,
|
||||
socks4: 1080,
|
||||
});
|
||||
|
||||
export function getProxyInfoFromUrl(
|
||||
url: string
|
||||
): ProxyInfo & { type: "http"; host: string; port: number } {
|
||||
): ProxyInfo & { host: string; port: number } {
|
||||
let type: ProxyType | undefined = undefined;
|
||||
if (url.includes("://")) {
|
||||
const [protocol] = url.split("://", 1);
|
||||
type = protocol as ProxyType;
|
||||
url = url.substring(protocol.length + 3, url.length);
|
||||
}
|
||||
|
||||
const lastIndexOfAt = url.lastIndexOf("@");
|
||||
const hostname = url.substring(lastIndexOfAt + 1, url.length);
|
||||
const lastIndexOfColon = getLastIndexOfColon(hostname);
|
||||
@ -12,7 +27,7 @@ export function getProxyInfoFromUrl(
|
||||
let port: number | undefined = undefined;
|
||||
if (lastIndexOfColon === -1) {
|
||||
host = hostname;
|
||||
port = 3128; // Default port
|
||||
port = type ? DEFAULT_PORTS[type] : 3128; // Default port
|
||||
} else {
|
||||
host = hostname.substring(0, lastIndexOfColon);
|
||||
port = Number(hostname.substring(lastIndexOfColon + 1, hostname.length));
|
||||
@ -31,7 +46,7 @@ export function getProxyInfoFromUrl(
|
||||
}
|
||||
|
||||
return {
|
||||
type: "http",
|
||||
type: type ?? "http",
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
|
@ -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<Record<ProxyType, string>> = Object.freeze({
|
||||
direct: "DIRECT",
|
||||
http: "PROXY",
|
||||
https: "HTTPS",
|
||||
socks: "SOCKS5",
|
||||
socks4: "SOCKS4",
|
||||
});
|
||||
|
||||
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 `PROXY ${getUrlFromProxyInfo({
|
||||
return `${PROXY_TYPE_MAP[proxyInfo.type]} ${getUrlFromProxyInfo({
|
||||
...proxyInfo,
|
||||
// Don't include username/password in PAC script.
|
||||
username: undefined,
|
||||
|
@ -1,7 +1,7 @@
|
||||
export const passportHostRegex = /^passport\.twitch\.tv$/i;
|
||||
export const twitchApiChannelNameRegex = /\/hls\/(.+)\.m3u8/i;
|
||||
export const twitchChannelNameRegex =
|
||||
/^https?:\/\/(?:www|m)\.twitch\.tv\/(?:videos\/|popout\/|moderator\/)?((?!(?:directory|jobs|p|privacy|store|turbo)\b)\w+)/i;
|
||||
/^https?:\/\/(?:www|m)\.twitch\.tv\/(?:videos\/|popout\/|moderator\/)?((?!(?:directory|downloads|jobs|p|privacy|search|settings|store|turbo)\b)\w+)/i;
|
||||
export const twitchGqlHostRegex = /^gql\.twitch\.tv$/i;
|
||||
export const twitchTvHostRegex = /^(?:www|m)\.twitch\.tv$/i;
|
||||
export const usherHostRegex = /^usher\.ttvnw\.net$/i;
|
||||
|
@ -2,6 +2,7 @@ import pageScriptURL from "url:../page/page.ts";
|
||||
import workerScriptURL from "url:../page/worker.ts";
|
||||
import browser, { Storage } from "webextension-polyfill";
|
||||
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
||||
import isChannelWhitelisted from "../common/ts/isChannelWhitelisted";
|
||||
import isChromium from "../common/ts/isChromium";
|
||||
import { getStreamStatus, setStreamStatus } from "../common/ts/streamStatus";
|
||||
import store from "../store";
|
||||
@ -46,10 +47,13 @@ function onStoreLoad() {
|
||||
|
||||
/**
|
||||
* Clear stats for stream on page load/reload.
|
||||
* @param channelName
|
||||
* @param delayMs
|
||||
* @returns
|
||||
*/
|
||||
function clearStats(channelName: string | null) {
|
||||
async function clearStats(channelName: string | null, delayMs?: number) {
|
||||
if (!channelName) return;
|
||||
if (delayMs) await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
const channelNameLower = channelName.toLowerCase();
|
||||
if (store.state.streamStatuses.hasOwnProperty(channelNameLower)) {
|
||||
delete store.state.streamStatuses[channelNameLower];
|
||||
@ -97,66 +101,118 @@ function onBackgroundMessage(message: any): undefined {
|
||||
}
|
||||
|
||||
function onPageMessage(event: MessageEvent) {
|
||||
if (event.data?.type !== MessageType.ContentScriptMessage) return;
|
||||
if (!event.data || event.data.type !== MessageType.ContentScriptMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = event.data?.message;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
|
||||
switch (message.type) {
|
||||
case MessageType.GetStoreState:
|
||||
const sendStoreState = () => {
|
||||
window.postMessage({
|
||||
type: MessageType.PageScriptMessage,
|
||||
message: {
|
||||
type: MessageType.GetStoreStateResponse,
|
||||
state: JSON.parse(JSON.stringify(store.state)),
|
||||
},
|
||||
});
|
||||
};
|
||||
if (store.readyState === "complete") sendStoreState();
|
||||
else store.addEventListener("load", sendStoreState);
|
||||
break;
|
||||
case MessageType.EnableFullMode:
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send EnableFullMode message",
|
||||
error
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MessageType.DisableFullMode:
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send DisableFullMode message",
|
||||
error
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MessageType.UsherResponse:
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send UsherResponse message",
|
||||
error
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MessageType.MultipleAdBlockersInUse:
|
||||
const channelName = findChannelFromTwitchTvUrl(location.href);
|
||||
if (!channelName) break;
|
||||
const streamStatus = getStreamStatus(channelName);
|
||||
setStreamStatus(channelName, {
|
||||
...(streamStatus ?? { proxied: false }),
|
||||
reason: "Another Twitch ad blocker is in use",
|
||||
if (message.type === MessageType.GetStoreState) {
|
||||
const sendStoreState = () => {
|
||||
window.postMessage({
|
||||
type: MessageType.PageScriptMessage,
|
||||
message: {
|
||||
type: MessageType.GetStoreStateResponse,
|
||||
state: JSON.parse(JSON.stringify(store.state)),
|
||||
},
|
||||
});
|
||||
break;
|
||||
case MessageType.ClearStats:
|
||||
clearStats(message.channelName);
|
||||
break;
|
||||
};
|
||||
if (store.readyState === "complete") sendStoreState();
|
||||
else store.addEventListener("load", sendStoreState);
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.EnableFullMode) {
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send EnableFullMode message",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.DisableFullMode) {
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send DisableFullMode message",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.ChannelSubStatusChange) {
|
||||
const { channelNameLower, wasSubscribed, isSubscribed } = message;
|
||||
const isWhitelisted = isChannelWhitelisted(channelNameLower);
|
||||
console.log("[TTV LOL PRO] ChannelSubStatusChange", {
|
||||
channelNameLower,
|
||||
wasSubscribed,
|
||||
isSubscribed,
|
||||
isWhitelisted,
|
||||
});
|
||||
const currentChannelNameLower = findChannelFromTwitchTvUrl(
|
||||
location.href
|
||||
)?.toLowerCase();
|
||||
if (store.state.whitelistChannelSubscriptions && channelNameLower != null) {
|
||||
if (!wasSubscribed && isSubscribed) {
|
||||
store.state.activeChannelSubscriptions.push(channelNameLower);
|
||||
// Add to whitelist.
|
||||
if (!isWhitelisted) {
|
||||
console.log(
|
||||
`[TTV LOL PRO] Adding '${channelNameLower}' to whitelist.`
|
||||
);
|
||||
store.state.whitelistedChannels.push(channelNameLower);
|
||||
if (channelNameLower === currentChannelNameLower) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
} else if (wasSubscribed && !isSubscribed) {
|
||||
store.state.activeChannelSubscriptions =
|
||||
store.state.activeChannelSubscriptions.filter(
|
||||
channel => channel.toLowerCase() !== channelNameLower
|
||||
);
|
||||
// Remove from whitelist.
|
||||
if (isWhitelisted) {
|
||||
console.log(
|
||||
`[TTV LOL PRO] Removing '${channelNameLower}' from whitelist.`
|
||||
);
|
||||
store.state.whitelistedChannels =
|
||||
store.state.whitelistedChannels.filter(
|
||||
channel => channel.toLowerCase() !== channelNameLower
|
||||
);
|
||||
if (channelNameLower === currentChannelNameLower) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.UsherResponse) {
|
||||
try {
|
||||
browser.runtime.sendMessage(message);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TTV LOL PRO] Failed to send UsherResponse message",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.MultipleAdBlockersInUse) {
|
||||
const channelName = findChannelFromTwitchTvUrl(location.href);
|
||||
if (!channelName) return;
|
||||
const streamStatus = getStreamStatus(channelName);
|
||||
setStreamStatus(channelName, {
|
||||
...(streamStatus ?? { proxied: false }),
|
||||
reason: "Another Twitch ad blocker is in use",
|
||||
});
|
||||
}
|
||||
// ---
|
||||
else if (message.type === MessageType.ClearStats) {
|
||||
clearStats(message.channelName, 2000);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "TTV LOL PRO",
|
||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
||||
"version": "2.3.10",
|
||||
"version": "2.4.0",
|
||||
"background": {
|
||||
"service_worker": "background/background.ts",
|
||||
"type": "module"
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "TTV LOL PRO",
|
||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
|
||||
"version": "2.3.10",
|
||||
"version": "2.4.0",
|
||||
"background": {
|
||||
"scripts": ["background/background.ts"],
|
||||
"persistent": false
|
||||
|
@ -28,7 +28,7 @@ type ListOptions = {
|
||||
getPromptPlaceholder(insertMode: InsertMode): string;
|
||||
isAddAllowed(text: string): AllowedResult;
|
||||
isEditAllowed(text: string): AllowedResult;
|
||||
onEdit?(text: string): void;
|
||||
onChange?(oldText: string | undefined, newText: string): void;
|
||||
focusPrompt: boolean;
|
||||
hidePromptMarker: boolean;
|
||||
insertMode: InsertMode;
|
||||
@ -75,6 +75,9 @@ const passportLevelProxyUsageWwwElement = $(
|
||||
const whitelistedChannelsListElement = $(
|
||||
"#whitelisted-channels-list"
|
||||
) as HTMLUListElement;
|
||||
const whitelistSubscriptionsCheckboxElement = $(
|
||||
"#whitelist-subscriptions-checkbox"
|
||||
) as HTMLInputElement;
|
||||
// Proxies
|
||||
const optimizedProxiesInputElement = $("#optimized") as HTMLInputElement;
|
||||
const optimizedProxiesListElement = $(
|
||||
@ -82,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"
|
||||
@ -156,55 +162,36 @@ function main() {
|
||||
}
|
||||
return [true];
|
||||
},
|
||||
isEditAllowed(text) {
|
||||
if (!/^[a-z0-9_]+$/i.test(text)) {
|
||||
return [false, `'${text}' is not a valid channel name`];
|
||||
}
|
||||
return [true];
|
||||
},
|
||||
});
|
||||
whitelistSubscriptionsCheckboxElement.checked =
|
||||
store.state.whitelistChannelSubscriptions;
|
||||
whitelistSubscriptionsCheckboxElement.addEventListener("change", () => {
|
||||
const { checked } = whitelistSubscriptionsCheckboxElement;
|
||||
store.state.whitelistChannelSubscriptions = checked;
|
||||
if (!checked) {
|
||||
// Clear active channel subscriptions to free up storage space.
|
||||
store.state.activeChannelSubscriptions = [];
|
||||
}
|
||||
});
|
||||
// Proxies
|
||||
if (store.state.optimizedProxiesEnabled)
|
||||
optimizedProxiesInputElement.checked = true;
|
||||
else normalProxiesInputElement.checked = true;
|
||||
const onProxyTypeChange = () => {
|
||||
const onProxyModeChange = () => {
|
||||
store.state.optimizedProxiesEnabled = optimizedProxiesInputElement.checked;
|
||||
if (isChromium && store.state.chromiumProxyActive) {
|
||||
updateProxySettings();
|
||||
}
|
||||
updateProxyUsage();
|
||||
};
|
||||
optimizedProxiesInputElement.addEventListener("change", onProxyTypeChange);
|
||||
normalProxiesInputElement.addEventListener("change", onProxyTypeChange);
|
||||
listInit(optimizedProxiesListElement, "optimizedProxies", {
|
||||
getPromptPlaceholder: insertMode => {
|
||||
if (insertMode == "prepend") return "Enter a proxy URL… (Primary)";
|
||||
return "Enter a proxy URL… (Fallback)";
|
||||
},
|
||||
isAddAllowed: isOptimizedProxyUrlAllowed,
|
||||
isEditAllowed: isOptimizedProxyUrlAllowed,
|
||||
onEdit() {
|
||||
if (isChromium && store.state.chromiumProxyActive) {
|
||||
updateProxySettings();
|
||||
}
|
||||
},
|
||||
hidePromptMarker: true,
|
||||
insertMode: "both",
|
||||
});
|
||||
listInit(normalProxiesListElement, "normalProxies", {
|
||||
getPromptPlaceholder: insertMode => {
|
||||
if (insertMode == "prepend") return "Enter a proxy URL… (Primary)";
|
||||
return "Enter a proxy URL… (Fallback)";
|
||||
},
|
||||
isAddAllowed: isNormalProxyUrlAllowed,
|
||||
isEditAllowed: isNormalProxyUrlAllowed,
|
||||
onEdit() {
|
||||
if (isChromium && store.state.chromiumProxyActive) {
|
||||
updateProxySettings();
|
||||
}
|
||||
},
|
||||
hidePromptMarker: true,
|
||||
insertMode: "both",
|
||||
optimizedProxiesInputElement.addEventListener("change", onProxyModeChange);
|
||||
normalProxiesInputElement.addEventListener("change", onProxyModeChange);
|
||||
loadProxiesLists();
|
||||
otherProtocolsCheckboxElement.checked = store.state.allowOtherProxyProtocols;
|
||||
otherProtocolsCheckboxElement.addEventListener("change", () => {
|
||||
store.state.allowOtherProxyProtocols =
|
||||
otherProtocolsCheckboxElement.checked;
|
||||
loadProxiesLists();
|
||||
});
|
||||
// Ad log
|
||||
adLogEnabledCheckboxElement.checked = store.state.adLogEnabled;
|
||||
@ -291,6 +278,37 @@ function updateProxyUsage() {
|
||||
}
|
||||
}
|
||||
|
||||
function loadProxiesLists() {
|
||||
listInit(optimizedProxiesListElement, "optimizedProxies", {
|
||||
getPromptPlaceholder: insertMode => {
|
||||
if (insertMode == "prepend") return "Enter a proxy URL… (Primary)";
|
||||
return "Enter a proxy URL… (Fallback)";
|
||||
},
|
||||
isAddAllowed: isOptimizedProxyUrlAllowed,
|
||||
onChange() {
|
||||
if (isChromium && store.state.chromiumProxyActive) {
|
||||
updateProxySettings();
|
||||
}
|
||||
},
|
||||
hidePromptMarker: true,
|
||||
insertMode: "both",
|
||||
});
|
||||
listInit(normalProxiesListElement, "normalProxies", {
|
||||
getPromptPlaceholder: insertMode => {
|
||||
if (insertMode == "prepend") return "Enter a proxy URL… (Primary)";
|
||||
return "Enter a proxy URL… (Fallback)";
|
||||
},
|
||||
isAddAllowed: isNormalProxyUrlAllowed,
|
||||
onChange() {
|
||||
if (isChromium && store.state.chromiumProxyActive) {
|
||||
updateProxySettings();
|
||||
}
|
||||
},
|
||||
hidePromptMarker: true,
|
||||
insertMode: "both",
|
||||
});
|
||||
}
|
||||
|
||||
function isOptimizedProxyUrlAllowed(url: string): AllowedResult {
|
||||
const urlLower = url.toLowerCase();
|
||||
|
||||
@ -323,8 +341,17 @@ function isOptimizedProxyUrlAllowed(url: string): AllowedResult {
|
||||
return [false, "TTV LOL PRO v1 proxies are not compatible"];
|
||||
}
|
||||
|
||||
if (/^https?:\/\//i.test(url)) {
|
||||
return [false, "Proxy URLs must not contain a protocol (e.g. 'http://')"];
|
||||
if (url.includes("://")) {
|
||||
const [protocol] = url.split("://", 1);
|
||||
if (!store.state.allowOtherProxyProtocols) {
|
||||
return [
|
||||
false,
|
||||
"Proxy URLs are not allowed to contain a protocol (e.g. 'http://')",
|
||||
];
|
||||
} else if (!["http", "https", "socks", "socks4"].includes(protocol)) {
|
||||
return [false, `'${protocol}' is not a supported protocol`];
|
||||
}
|
||||
url = url.substring(protocol.length + 3, url.length);
|
||||
}
|
||||
|
||||
if (url.includes("/")) {
|
||||
@ -388,6 +415,7 @@ function listInit(
|
||||
storeKey: StoreStringArrayKey,
|
||||
options: Partial<ListOptions> = {}
|
||||
) {
|
||||
listElement.innerHTML = ""; // Reset list element.
|
||||
const listOptions: ListOptions = { ...DEFAULT_LIST_OPTIONS, ...options };
|
||||
for (const text of store.state[storeKey]) {
|
||||
_listAppend(listElement, storeKey, text, {
|
||||
@ -447,18 +475,19 @@ function _listAppend(
|
||||
return console.error(`Item '${text}' not found in '${storeKey}' array`);
|
||||
|
||||
const textInput = e.target as HTMLInputElement;
|
||||
const oldText = text;
|
||||
const newText = textInput.value.trim();
|
||||
// Remove item if text is empty.
|
||||
if (newText === "") {
|
||||
store.state[storeKey].splice(itemIndex, 1);
|
||||
listItem.remove();
|
||||
if (options.onEdit) options.onEdit(newText);
|
||||
if (options.onChange) options.onChange(oldText, newText);
|
||||
return;
|
||||
}
|
||||
// Check if text is valid.
|
||||
const [allowed, error] = options.isEditAllowed(newText);
|
||||
const [allowed, error] = options.isAddAllowed(newText);
|
||||
if (!allowed) {
|
||||
alert(error || "You cannot edit this item");
|
||||
alert(error || "You cannot add this item");
|
||||
textInput.value = text;
|
||||
return;
|
||||
}
|
||||
@ -467,7 +496,7 @@ function _listAppend(
|
||||
textInput.placeholder = options.getItemPlaceholder(newText);
|
||||
textInput.value = newText; // Update text in case it was trimmed.
|
||||
text = newText; // Update current text variable.
|
||||
if (options.onEdit) options.onEdit(newText);
|
||||
if (options.onChange) options.onChange(oldText, newText);
|
||||
});
|
||||
|
||||
listItem.append(textInput);
|
||||
@ -522,7 +551,7 @@ function _listPrompt(
|
||||
if (options.insertMode === "prepend") newArray.unshift(text);
|
||||
else newArray.push(text);
|
||||
store.state[storeKey] = newArray;
|
||||
if (options.onEdit) options.onEdit(text);
|
||||
if (options.onChange) options.onChange(undefined, text);
|
||||
|
||||
listItem.remove();
|
||||
_listAppend(listElement, storeKey, text, options);
|
||||
@ -543,11 +572,13 @@ function _listPrompt(
|
||||
exportButtonElement.addEventListener("click", () => {
|
||||
const state: Partial<State> = {
|
||||
adLogEnabled: store.state.adLogEnabled,
|
||||
allowOtherProxyProtocols: store.state.allowOtherProxyProtocols,
|
||||
anonymousMode: store.state.anonymousMode,
|
||||
normalProxies: store.state.normalProxies,
|
||||
optimizedProxies: store.state.optimizedProxies,
|
||||
optimizedProxiesEnabled: store.state.optimizedProxiesEnabled,
|
||||
passportLevel: store.state.passportLevel,
|
||||
whitelistChannelSubscriptions: store.state.whitelistChannelSubscriptions,
|
||||
whitelistedChannels: store.state.whitelistedChannels,
|
||||
};
|
||||
saveFile(
|
||||
|
@ -138,6 +138,23 @@
|
||||
Twitch tabs are whitelisted channels.
|
||||
</small>
|
||||
<ul id="whitelisted-channels-list" class="store-list"></ul>
|
||||
<ul class="options-list">
|
||||
<li>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="whitelist-subscriptions-checkbox"
|
||||
id="whitelist-subscriptions-checkbox"
|
||||
/>
|
||||
<label for="whitelist-subscriptions-checkbox">
|
||||
Automatically whitelist channels you're subscribed to
|
||||
</label>
|
||||
<br />
|
||||
<small>
|
||||
This option will automatically add or remove channels from the
|
||||
whitelist based on your subscriptions.
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Proxies -->
|
||||
@ -183,6 +200,34 @@
|
||||
>List of other proxies</a
|
||||
>" discussion on TTV LOL PRO's GitHub repository.
|
||||
</small>
|
||||
<br /><br />
|
||||
<ul class="options-list">
|
||||
<li>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="other-protocols-checkbox"
|
||||
id="other-protocols-checkbox"
|
||||
/>
|
||||
<label for="other-protocols-checkbox">
|
||||
Allow the use of other protocols
|
||||
</label>
|
||||
<span class="tag">For advanced users only</span>
|
||||
<br />
|
||||
<small>
|
||||
This option allows you to use protocols other than HTTP.
|
||||
</small>
|
||||
<br />
|
||||
<small>
|
||||
Supported protocols are HTTPS (<code>https://</code>), SOCKS4
|
||||
(<code>socks4://</code>), and SOCKS5 (<code>socks://</code>).
|
||||
</small>
|
||||
<br />
|
||||
<small>
|
||||
To provide a protocol, use the format
|
||||
<code>protocol://hostname:port</code>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Ad log -->
|
||||
|
@ -28,9 +28,11 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
// Listen for NewPlaybackAccessToken messages from the worker script.
|
||||
if (pageState.scope === "page") {
|
||||
self.addEventListener("message", async event => {
|
||||
if (event.data?.type !== MessageType.PageScriptMessage) return;
|
||||
if (!event.data || event.data.type !== MessageType.PageScriptMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = event.data?.message;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
|
||||
switch (message.type) {
|
||||
@ -42,14 +44,10 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
cachedPlaybackTokenRequestHeaders,
|
||||
cachedPlaybackTokenRequestBody
|
||||
);
|
||||
const message = {
|
||||
pageState.sendMessageToWorkerScripts(pageState.twitchWorkers, {
|
||||
type: MessageType.NewPlaybackAccessTokenResponse,
|
||||
newPlaybackAccessToken,
|
||||
};
|
||||
pageState.sendMessageToWorkerScripts(
|
||||
pageState.twitchWorkers,
|
||||
message
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
@ -58,13 +56,14 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
// Listen for ClearStats messages from the page script.
|
||||
self.addEventListener("message", event => {
|
||||
if (
|
||||
event.data?.type !== MessageType.PageScriptMessage &&
|
||||
event.data?.type !== MessageType.WorkerScriptMessage
|
||||
!event.data ||
|
||||
(event.data.type !== MessageType.PageScriptMessage &&
|
||||
event.data.type !== MessageType.WorkerScriptMessage)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = event.data?.message;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
|
||||
switch (message.type) {
|
||||
@ -72,9 +71,13 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
console.log("[TTV LOL PRO] Cleared stats (getFetch).");
|
||||
if (!message.channelName) break;
|
||||
const channelNameLower = message.channelName.toLowerCase();
|
||||
usherManifests = usherManifests.filter(
|
||||
manifest => manifest.channelName !== channelNameLower
|
||||
);
|
||||
for (let i = 0; i < usherManifests.length; i++) {
|
||||
if (
|
||||
usherManifests[i].channelName?.toLowerCase() === channelNameLower
|
||||
) {
|
||||
usherManifests[i].deleted = true;
|
||||
}
|
||||
}
|
||||
if (cachedPlaybackTokenRequestBody?.includes(channelNameLower)) {
|
||||
cachedPlaybackTokenRequestHeaders = null;
|
||||
cachedPlaybackTokenRequestBody = null;
|
||||
@ -196,7 +199,7 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
pageState.state?.anonymousMode === true ||
|
||||
(shouldFlagRequest && willFailIntegrityCheckIfProxied);
|
||||
if (shouldOverrideRequest) {
|
||||
const newRequest = await getDefaultPlaybackAccessTokenRequest(
|
||||
const newRequest = getDefaultPlaybackAccessTokenRequest(
|
||||
channelName,
|
||||
pageState.state?.anonymousMode === true
|
||||
);
|
||||
@ -400,6 +403,68 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
|
||||
//#region Responses
|
||||
|
||||
graphqlRes: if (
|
||||
host != null &&
|
||||
twitchGqlHostRegex.test(host) &&
|
||||
response.status < 400
|
||||
) {
|
||||
await waitForStore(pageState);
|
||||
if (!pageState.state?.whitelistChannelSubscriptions) break graphqlRes;
|
||||
responseBody ??= await readResponseBody();
|
||||
// Preliminary check to avoid parsing the response body if possible.
|
||||
if (
|
||||
!responseBody.includes('"UserSelfConnection"') ||
|
||||
!responseBody.includes('"subscriptionBenefit"') ||
|
||||
!responseBody.includes('"login"')
|
||||
) {
|
||||
break graphqlRes;
|
||||
}
|
||||
try {
|
||||
let channelName: string;
|
||||
let isSubscribed: boolean;
|
||||
const body = JSON.parse(responseBody);
|
||||
if (Array.isArray(body)) {
|
||||
const match = body.find(
|
||||
(obj: any) =>
|
||||
obj.data &&
|
||||
obj.data.user &&
|
||||
obj.data.user.login != null &&
|
||||
obj.data.user.self &&
|
||||
"subscriptionBenefit" in obj.data.user.self
|
||||
);
|
||||
if (match == null) break graphqlRes;
|
||||
channelName = match.data.user.login;
|
||||
isSubscribed = match.data.user.self.subscriptionBenefit != null;
|
||||
} else {
|
||||
const isMatch =
|
||||
body.data &&
|
||||
body.data.user &&
|
||||
body.data.user.login != null &&
|
||||
body.data.user.self &&
|
||||
"subscriptionBenefit" in body.data.user.self;
|
||||
if (!isMatch) break graphqlRes;
|
||||
channelName = body.data.user.login;
|
||||
isSubscribed = body.data.user.self.subscriptionBenefit != null;
|
||||
}
|
||||
if (!channelName) break graphqlRes;
|
||||
const isLivestream = !/^\d+$/.test(channelName); // VODs have numeric IDs.
|
||||
if (!isLivestream) break graphqlRes;
|
||||
const wasSubscribed = wasChannelSubscriber(channelName, pageState);
|
||||
const hasSubStatusChanged =
|
||||
(wasSubscribed && !isSubscribed) || (!wasSubscribed && isSubscribed);
|
||||
if (hasSubStatusChanged) {
|
||||
pageState.sendMessageToContentScript({
|
||||
type: MessageType.ChannelSubStatusChange,
|
||||
channelNameLower: channelName.toLowerCase(),
|
||||
wasSubscribed,
|
||||
isSubscribed,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[TTV LOL PRO] Failed to parse GraphQL response:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Twitch Usher responses.
|
||||
usherRes: if (
|
||||
host != null &&
|
||||
@ -407,6 +472,7 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
response.status < 400
|
||||
) {
|
||||
//#region Usher responses.
|
||||
// No need to wait for store here because all Usher requests have already waited for it.
|
||||
const isLivestream = !url.includes("/vod/");
|
||||
const isFrontpage = url.includes(
|
||||
encodeURIComponent('"player_type":"frontpage"')
|
||||
@ -416,6 +482,7 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
if (!isLivestream) break usherRes;
|
||||
|
||||
responseBody ??= await readResponseBody();
|
||||
usherManifests = usherManifests.filter(manifest => !manifest.deleted); // Clean up deleted manifests.
|
||||
const assignedMap = parseUsherManifest(responseBody);
|
||||
if (assignedMap != null) {
|
||||
console.debug(
|
||||
@ -428,9 +495,12 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
replacementMap: null,
|
||||
consecutiveMidrollResponses: 0,
|
||||
consecutiveMidrollCooldown: 0,
|
||||
deleted: false,
|
||||
});
|
||||
} else {
|
||||
console.debug("[TTV LOL PRO] Received Usher response.");
|
||||
console.error(
|
||||
"[TTV LOL PRO] Received Usher response but failed to parse it."
|
||||
);
|
||||
}
|
||||
// Send Video Weaver URLs to content script.
|
||||
const videoWeaverUrls = [...(assignedMap?.values() ?? [])];
|
||||
@ -476,14 +546,11 @@ export function getFetch(pageState: PageState): typeof fetch {
|
||||
console.log("[TTV LOL PRO] Midroll ad detected.");
|
||||
manifest.consecutiveMidrollResponses += 1;
|
||||
manifest.consecutiveMidrollCooldown = 15;
|
||||
const isWhitelisted = isChannelWhitelisted(
|
||||
manifest.channelName,
|
||||
pageState
|
||||
);
|
||||
await waitForStore(pageState);
|
||||
const shouldUpdateReplacementMap =
|
||||
pageState.state?.optimizedProxiesEnabled === true &&
|
||||
manifest.consecutiveMidrollResponses <= 2 && // Avoid infinite loop.
|
||||
!isWhitelisted;
|
||||
!videoWeaverUrlsToNotProxy.has(url);
|
||||
if (shouldUpdateReplacementMap) {
|
||||
const success = await updateVideoWeaverReplacementMap(
|
||||
pageState,
|
||||
@ -616,11 +683,25 @@ function isChannelWhitelisted(
|
||||
pageState: PageState
|
||||
): boolean {
|
||||
if (!channelName) return false;
|
||||
const whitelistedChannelsLower =
|
||||
pageState.state?.whitelistedChannels.map(channel =>
|
||||
channel.toLowerCase()
|
||||
) ?? [];
|
||||
return whitelistedChannelsLower.includes(channelName.toLowerCase());
|
||||
const channelNameLower = channelName.toLowerCase();
|
||||
return (
|
||||
pageState.state?.whitelistedChannels.some(
|
||||
channel => channel.toLowerCase() === channelNameLower
|
||||
) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
function wasChannelSubscriber(
|
||||
channelName: string | null | undefined,
|
||||
pageState: PageState
|
||||
): boolean {
|
||||
if (!channelName) return false;
|
||||
const channelNameLower = channelName.toLowerCase();
|
||||
return (
|
||||
pageState.state?.activeChannelSubscriptions.some(
|
||||
channel => channel.toLowerCase() === channelNameLower
|
||||
) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async function flagRequest(
|
||||
@ -674,10 +755,6 @@ function cancelRequest(): never {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
//#region Video Weaver URL replacement
|
||||
|
||||
/**
|
||||
@ -686,10 +763,10 @@ async function sleep(ms: number): Promise<void> {
|
||||
* @param anonymousMode
|
||||
* @returns
|
||||
*/
|
||||
async function getDefaultPlaybackAccessTokenRequest(
|
||||
function getDefaultPlaybackAccessTokenRequest(
|
||||
channel: string | null = null,
|
||||
anonymousMode: boolean = false
|
||||
): Promise<Request | null> {
|
||||
): Request | null {
|
||||
// We can use `location.href` because we're in the page script.
|
||||
const channelName = channel ?? findChannelFromTwitchTvUrl(location.href);
|
||||
if (!channelName) return null;
|
||||
@ -745,7 +822,7 @@ async function fetchReplacementPlaybackAccessToken(
|
||||
): Promise<PlaybackAccessToken | null> {
|
||||
// Not using the cached request because we'd need to check if integrity requests are proxied.
|
||||
try {
|
||||
let request = await getDefaultPlaybackAccessTokenRequest(
|
||||
let request = getDefaultPlaybackAccessTokenRequest(
|
||||
null,
|
||||
pageState.state?.anonymousMode === true
|
||||
);
|
||||
|
@ -39,6 +39,7 @@ const pageState: PageState = {
|
||||
sendMessageToWorkerScriptsAndWaitForResponse,
|
||||
};
|
||||
|
||||
const NATIVE_FETCH = window.fetch;
|
||||
window.fetch = getFetch(pageState);
|
||||
|
||||
const NATIVE_WORKER = window.Worker;
|
||||
@ -130,9 +131,9 @@ window.addEventListener("message", event => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data?.type !== MessageType.PageScriptMessage) return;
|
||||
if (!event.data || event.data.type !== MessageType.PageScriptMessage) return;
|
||||
|
||||
const message = event.data?.message;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
|
||||
switch (message.type) {
|
||||
|
@ -13,7 +13,9 @@ function sendMessage(
|
||||
type: MessageType,
|
||||
message: any
|
||||
): void {
|
||||
if (!recipient) return;
|
||||
if (!recipient) {
|
||||
return console.error("[TTV LOL PRO] Message recipient is undefined.");
|
||||
}
|
||||
recipient.postMessage({
|
||||
type,
|
||||
message,
|
||||
@ -30,14 +32,12 @@ async function sendMessageAndWaitForResponse(
|
||||
): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!recipient) {
|
||||
console.warn("[TTV LOL PRO] Recipient is undefined.");
|
||||
resolve(undefined);
|
||||
return;
|
||||
return reject(new Error("Message recipient is undefined."));
|
||||
}
|
||||
|
||||
const listener = (event: MessageEvent) => {
|
||||
if (event.data?.type !== responseType) return;
|
||||
const message = event.data?.message;
|
||||
if (!event.data || event.data.type !== responseType) return;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
if (message.type === responseMessageType) {
|
||||
self.removeEventListener("message", listener);
|
||||
@ -46,7 +46,12 @@ async function sendMessageAndWaitForResponse(
|
||||
};
|
||||
|
||||
self.addEventListener("message", listener);
|
||||
sendMessage(recipient, type, message);
|
||||
recipient.postMessage({
|
||||
type,
|
||||
message,
|
||||
responseType,
|
||||
responseMessageType,
|
||||
});
|
||||
setTimeout(() => {
|
||||
self.removeEventListener("message", listener);
|
||||
reject(new Error("Timed out waiting for message response."));
|
||||
|
@ -36,6 +36,7 @@ export interface UsherManifest {
|
||||
replacementMap: Map<string, string> | null; // Same as above, but with new URLs.
|
||||
consecutiveMidrollResponses: number; // Used to avoid infinite loops.
|
||||
consecutiveMidrollCooldown: number; // Used to avoid infinite loops.
|
||||
deleted: boolean; // Deletion flag for cleanup.
|
||||
}
|
||||
|
||||
export interface PlaybackAccessToken {
|
||||
|
@ -47,9 +47,11 @@ const pageState: PageState = {
|
||||
self.fetch = getFetch(pageState);
|
||||
|
||||
self.addEventListener("message", event => {
|
||||
if (event.data?.type !== MessageType.WorkerScriptMessage) return;
|
||||
if (!event.data || event.data.type !== MessageType.WorkerScriptMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = event.data?.message;
|
||||
const { message } = event.data;
|
||||
if (!message) return;
|
||||
|
||||
switch (message.type) {
|
||||
|
@ -3,9 +3,11 @@ import type { State } from "./types";
|
||||
|
||||
export default function getDefaultState() {
|
||||
const state: State = {
|
||||
activeChannelSubscriptions: [],
|
||||
adLog: [],
|
||||
adLogEnabled: true,
|
||||
adLogLastSent: 0,
|
||||
allowOtherProxyProtocols: false,
|
||||
anonymousMode: true,
|
||||
chromiumProxyActive: false,
|
||||
dnsResponses: [],
|
||||
@ -18,6 +20,7 @@ export default function getDefaultState() {
|
||||
passportLevel: 0,
|
||||
streamStatuses: {},
|
||||
videoWeaverUrlsByChannel: {},
|
||||
whitelistChannelSubscriptions: true,
|
||||
whitelistedChannels: [],
|
||||
};
|
||||
return state;
|
||||
|
@ -6,9 +6,11 @@ export type ReadyState = "loading" | "complete";
|
||||
export type StorageAreaName = "local" | "managed" | "sync";
|
||||
|
||||
export interface State {
|
||||
activeChannelSubscriptions: string[];
|
||||
adLog: AdLogEntry[];
|
||||
adLogEnabled: boolean;
|
||||
adLogLastSent: number;
|
||||
allowOtherProxyProtocols: boolean;
|
||||
anonymousMode: boolean;
|
||||
chromiumProxyActive: boolean;
|
||||
dnsResponses: DnsResponse[];
|
||||
@ -19,6 +21,7 @@ export interface State {
|
||||
passportLevel: number;
|
||||
streamStatuses: Record<string, StreamStatus>;
|
||||
videoWeaverUrlsByChannel: Record<string, string[]>;
|
||||
whitelistChannelSubscriptions: boolean;
|
||||
whitelistedChannels: string[];
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,11 @@ export type KeyOfType<T, V> = keyof {
|
||||
[P in keyof T as T[P] extends V ? P : never]: any;
|
||||
};
|
||||
|
||||
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: "direct" | "http" | "https" | "socks" | "socks4";
|
||||
type: ProxyType;
|
||||
host?: string;
|
||||
port?: number;
|
||||
username?: string;
|
||||
@ -82,6 +84,7 @@ export const enum MessageType {
|
||||
UsherResponse = "TLP_UsherResponse",
|
||||
NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken",
|
||||
NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",
|
||||
ChannelSubStatusChange = "TLP_ChannelSubStatusChange",
|
||||
MultipleAdBlockersInUse = "TLP_MultipleAdBlockersInUse",
|
||||
ClearStats = "TLP_ClearStats",
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user