mirror of
https://github.com/younesaassila/ttv-lol-pro.git
synced 2025-04-29 14:04:26 +02:00
Merge pull request #340 from younesaassila/v2.3.8
Release version 2.3.8
This commit is contained in:
commit
4cf96c311c
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@ -1,8 +1,17 @@
|
||||
name: Build and Test
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "**"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
concurrency: ci-${{ github.ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://discord.gg/AmtFTPwsyH">
|
||||
<a href="https://discord.ttvlolpro.com/">
|
||||
<img
|
||||
alt="Discord server"
|
||||
src="https://dcbadge.vercel.app/api/server/AmtFTPwsyH"
|
||||
@ -78,7 +78,7 @@ TTV LOL PRO is a fork of TTV LOL that:
|
||||
|
||||
TTV LOL PRO does not remove banner ads, nor does it remove ads from VODs. For the best experience, we recommend using [uBlock Origin](https://ublockorigin.com/) alongside TTV LOL PRO.
|
||||
|
||||
Any questions? Please read the [wiki](https://wiki.cdn-perfprod.com/) first.
|
||||
Any questions? Please read the [wiki](https://wiki.ttvlolpro.com/) first.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
1665
package-lock.json
generated
1665
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ttv-lol-pro",
|
||||
"version": "2.3.7",
|
||||
"version": "2.3.8",
|
||||
"description": "TTV LOL PRO removes most livestream ads from Twitch.",
|
||||
"@parcel/bundler-default": {
|
||||
"minBundles": 10000000,
|
||||
@ -43,24 +43,25 @@
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"bowser": "^2.11.0",
|
||||
"ip": "^2.0.1",
|
||||
"ip-address": "^9.0.5",
|
||||
"m3u8-parser": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/config-webextension": "^2.12.0",
|
||||
"@types/chrome": "^0.0.267",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/webextension-polyfill": "^0.10.7",
|
||||
"@types/chrome": "^0.0.270",
|
||||
"@types/jsbn": "^1.2.33",
|
||||
"@types/node": "^20.16.1",
|
||||
"@types/webextension-polyfill": "^0.12.0",
|
||||
"buffer": "^6.0.3",
|
||||
"os-browserify": "^0.3.0",
|
||||
"parcel": "^2.12.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "2.8.8",
|
||||
"prettier-plugin-css-order": "^1.3.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.4.5",
|
||||
"webextension-polyfill": "^0.11.0"
|
||||
"typescript": "^5.5.4",
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ const pendingRequests: string[] = [];
|
||||
|
||||
export default function onAuthRequired(
|
||||
details: WebRequest.OnAuthRequiredDetailsType
|
||||
): void | WebRequest.BlockingResponseOrPromise {
|
||||
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||
if (!details.isProxy) return;
|
||||
|
||||
if (pendingRequests.includes(details.requestId)) {
|
||||
|
@ -8,7 +8,7 @@ import { twitchTvHostRegex } from "../../common/ts/regexes";
|
||||
|
||||
export default function onBeforeTwitchTvSendHeaders(
|
||||
details: WebRequest.OnBeforeSendHeadersDetailsType
|
||||
): void | WebRequest.BlockingResponseOrPromise {
|
||||
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||
const host = getHostFromUrl(details.url);
|
||||
if (!host || !twitchTvHostRegex.test(host)) return;
|
||||
|
||||
|
@ -11,7 +11,7 @@ export default function onBeforeVideoWeaverRequest(
|
||||
details: WebRequest.OnBeforeRequestDetailsType & {
|
||||
proxyInfo?: ProxyInfo;
|
||||
}
|
||||
): void | WebRequest.BlockingResponseOrPromise {
|
||||
): WebRequest.BlockingResponseOrPromise | undefined {
|
||||
// Filter to video-weaver responses.
|
||||
const host = getHostFromUrl(details.url);
|
||||
if (!host || !videoWeaverHostRegex.test(host)) return;
|
||||
@ -34,9 +34,7 @@ export default function onBeforeVideoWeaverRequest(
|
||||
if (isDuplicate) return text;
|
||||
|
||||
const channelName = findChannelFromVideoWeaverUrl(details.url);
|
||||
const isPurpleScreen = textLower.includes(
|
||||
"https://help.twitch.tv/s/article/ad-experience-on-twitch"
|
||||
);
|
||||
const isPurpleScreen = textLower.includes("https://help.twitch.tv/");
|
||||
const proxy =
|
||||
details.proxyInfo && details.proxyInfo.type !== "direct"
|
||||
? getUrlFromProxyInfo(details.proxyInfo)
|
||||
|
@ -14,8 +14,8 @@ const fetchTimeoutMsOverride: Map<ProxyRequestType, number> = new Map([
|
||||
export default function onContentScriptMessage(
|
||||
message: any,
|
||||
sender: Runtime.MessageSender,
|
||||
sendResponse: () => void
|
||||
): true | void | Promise<any> {
|
||||
sendResponse: (message: any) => void
|
||||
): Promise<any> | true | undefined {
|
||||
if (message.type === MessageType.EnableFullMode) {
|
||||
if (!sender.tab?.id) return;
|
||||
|
||||
|
@ -27,7 +27,13 @@ export default async function onResponseStarted(
|
||||
const host = getHostFromUrl(details.url);
|
||||
if (!host) return;
|
||||
|
||||
const proxy = getProxyFromDetails(details);
|
||||
let proxy: string | null = null;
|
||||
let errorMessage: string | null = null;
|
||||
try {
|
||||
proxy = getProxyFromDetails(details);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error ? error.message : `${error}`;
|
||||
}
|
||||
|
||||
const requestParams = {
|
||||
isChromium: isChromium,
|
||||
@ -79,13 +85,26 @@ export default async function onResponseStarted(
|
||||
findChannelFromTwitchTvUrl(tabUrl);
|
||||
const streamStatus = getStreamStatus(channelName);
|
||||
const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 };
|
||||
|
||||
if (!proxy) {
|
||||
stats.notProxied++;
|
||||
let reason = errorMessage ?? streamStatus?.reason ?? "";
|
||||
try {
|
||||
const proxySettings = await browser.proxy.settings.get({});
|
||||
switch (proxySettings.levelOfControl) {
|
||||
case "controlled_by_other_extensions":
|
||||
reason = "Proxy settings controlled by other extension";
|
||||
break;
|
||||
case "not_controllable":
|
||||
reason = "Proxy settings not controllable";
|
||||
break;
|
||||
}
|
||||
} catch {}
|
||||
setStreamStatus(channelName, {
|
||||
proxied: false,
|
||||
proxyHost: streamStatus?.proxyHost ? streamStatus.proxyHost : undefined,
|
||||
proxyCountry: streamStatus?.proxyCountry,
|
||||
reason: streamStatus?.reason ?? "",
|
||||
reason,
|
||||
stats,
|
||||
});
|
||||
console.log(
|
||||
@ -93,6 +112,7 @@ export default async function onResponseStarted(
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
stats.proxied++;
|
||||
setStreamStatus(channelName, {
|
||||
proxied: true,
|
||||
@ -126,24 +146,29 @@ function getProxyFromDetails(
|
||||
}
|
||||
): string | null {
|
||||
if (isChromium) {
|
||||
const proxies = [
|
||||
...store.state.optimizedProxies,
|
||||
...store.state.normalProxies,
|
||||
];
|
||||
const isDnsError =
|
||||
proxies.length !== 0 && store.state.dnsResponses.length === 0;
|
||||
if (isDnsError) {
|
||||
throw new Error(
|
||||
"Cannot detect if requests are being proxied due to a DNS error"
|
||||
);
|
||||
}
|
||||
const ip = details.ip;
|
||||
if (!ip) return null;
|
||||
const dnsResponse = store.state.dnsResponses.find(
|
||||
dnsResponse => dnsResponse.ips.indexOf(ip) !== -1
|
||||
);
|
||||
if (!dnsResponse) return null;
|
||||
const proxies = [
|
||||
...store.state.optimizedProxies,
|
||||
...store.state.normalProxies,
|
||||
];
|
||||
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
||||
const possibleProxies = proxyInfoArray.filter(
|
||||
proxy => proxy.host === dnsResponse.host
|
||||
);
|
||||
if (possibleProxies.length === 1)
|
||||
return getUrlFromProxyInfo(possibleProxies[0]);
|
||||
// TODO: Set reason to some error message about DNS.
|
||||
return dnsResponse.host;
|
||||
if (possibleProxies.length === 0) return dnsResponse.host;
|
||||
return getUrlFromProxyInfo(possibleProxies[0]);
|
||||
} else {
|
||||
const proxyInfo = details.proxyInfo; // Firefox only.
|
||||
if (!proxyInfo || proxyInfo.type === "direct") return null;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ip from "ip";
|
||||
import { Address4, Address6 } from "ip-address";
|
||||
import isPrivateIp from "./isPrivateIp";
|
||||
import { getProxyInfoFromUrl } from "./proxyInfo";
|
||||
|
||||
/**
|
||||
@ -12,16 +13,26 @@ export function anonymizeIpAddress(url: string): string {
|
||||
|
||||
let proxyHost = proxyInfo.host;
|
||||
|
||||
const isIPv4 = ip.isV4Format(proxyHost);
|
||||
const isIPv6 = ip.isV6Format(proxyHost);
|
||||
const isIPv4 = Address4.isValid(proxyHost);
|
||||
const isIPv6 = Address6.isValid(proxyHost);
|
||||
const isIP = isIPv4 || isIPv6;
|
||||
const isPublicIP = isIP && !ip.isPrivate(proxyHost);
|
||||
const isPublicIP = isIP && !isPrivateIp(proxyHost);
|
||||
|
||||
if (isPublicIP) {
|
||||
if (isIPv4) {
|
||||
proxyHost = ip.mask(proxyHost, "255.255.0.0").replace(/\.0\.0$/, ".*.*");
|
||||
proxyHost = new Address4(proxyHost)
|
||||
.correctForm()
|
||||
.split(".")
|
||||
.map((byte, index) => (index < 2 ? byte : "xxx"))
|
||||
.join(".");
|
||||
} else if (isIPv6) {
|
||||
proxyHost = ip.mask(proxyHost, "ffff:ffff:ffff:ffff:0000:0000:0000:0000");
|
||||
const bytes = new Address6(proxyHost).toByteArray();
|
||||
const anonymizedBytes = bytes.map((byte, index) =>
|
||||
index < 6 ? byte : 0x0
|
||||
);
|
||||
proxyHost = Address6.fromByteArray(anonymizedBytes)
|
||||
.correctForm()
|
||||
.replace(/::$/, "::xxxx");
|
||||
}
|
||||
}
|
||||
|
||||
|
27
src/common/ts/isPrivateIp.ts
Normal file
27
src/common/ts/isPrivateIp.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Address4, Address6 } from "ip-address";
|
||||
|
||||
const ip4LinkLocalSubnet = new Address4("169.254.0.0/16");
|
||||
const ip4LoopbackSubnet = new Address4("127.0.0.0/8");
|
||||
const ip4PrivateASubnet = new Address4("10.0.0.0/8");
|
||||
const ip4PrivateBSubnet = new Address4("172.16.0.0/12");
|
||||
const ip4PrivateCSubnet = new Address4("192.168.0.0/16");
|
||||
|
||||
export default function isPrivateIp(address: string): boolean {
|
||||
try {
|
||||
const ip4 = new Address4(address);
|
||||
return (
|
||||
ip4.isInSubnet(ip4LinkLocalSubnet) ||
|
||||
ip4.isInSubnet(ip4LoopbackSubnet) ||
|
||||
ip4.isInSubnet(ip4PrivateASubnet) ||
|
||||
ip4.isInSubnet(ip4PrivateBSubnet) ||
|
||||
ip4.isInSubnet(ip4PrivateCSubnet)
|
||||
);
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
const ip6 = new Address6(address);
|
||||
return ip6.isLinkLocal() || ip6.isLoopback();
|
||||
} catch (error) {}
|
||||
|
||||
return false;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import ip from "ip";
|
||||
import { Address6 } from "ip-address";
|
||||
import type { ProxyInfo } from "../../types";
|
||||
|
||||
export function getProxyInfoFromUrl(
|
||||
@ -71,14 +71,14 @@ export function getUrlFromProxyInfo(proxyInfo: ProxyInfo): string {
|
||||
} else if (username) {
|
||||
url = `${username}@`;
|
||||
}
|
||||
const isIPv4 = ip.isV4Format(host);
|
||||
const isIPv6 = ip.isV6Format(host);
|
||||
// isV6Format() returns true for IPv4 addresses, so we need to exclude those.
|
||||
if (isIPv6 && !isIPv4) {
|
||||
const isIPv6 = Address6.isValid(host);
|
||||
if (isIPv6) {
|
||||
url += `[${host}]`;
|
||||
} else {
|
||||
url += host;
|
||||
}
|
||||
if (port) url += `:${port}`;
|
||||
if (port) {
|
||||
url += `:${port}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import ip from "ip";
|
||||
import { Address4, Address6 } from "ip-address";
|
||||
import store from "../../store";
|
||||
import type { DnsResponse, DnsResponseJson } from "../../types";
|
||||
import { getProxyInfoFromUrl } from "./proxyInfo";
|
||||
|
||||
export default async function updateDnsResponses() {
|
||||
const proxies = store.state.optimizedProxiesEnabled
|
||||
? store.state.optimizedProxies
|
||||
: store.state.normalProxies;
|
||||
const proxies = [
|
||||
...store.state.optimizedProxies,
|
||||
...store.state.normalProxies,
|
||||
];
|
||||
const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
|
||||
|
||||
for (const proxyInfo of proxyInfoArray) {
|
||||
@ -25,18 +26,19 @@ export default async function updateDnsResponses() {
|
||||
}
|
||||
|
||||
// If the host is an IP address, we don't need to make a DNS request.
|
||||
const isIp = ip.isV4Format(host) || ip.isV6Format(host);
|
||||
const isIp = Address4.isValid(host) || Address6.isValid(host);
|
||||
if (isIp) {
|
||||
if (dnsResponseIndex !== -1) {
|
||||
store.state.dnsResponses.splice(dnsResponseIndex, 1);
|
||||
}
|
||||
const dnsResponse: DnsResponse = {
|
||||
host,
|
||||
ips: [host],
|
||||
timestamp: Date.now(),
|
||||
ttl: Infinity,
|
||||
};
|
||||
store.state.dnsResponses.push(dnsResponse);
|
||||
if (dnsResponseIndex !== -1) {
|
||||
store.state.dnsResponses.splice(dnsResponseIndex, 1, dnsResponse);
|
||||
} else {
|
||||
store.state.dnsResponses.push(dnsResponse);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -59,16 +61,17 @@ export default async function updateDnsResponses() {
|
||||
}
|
||||
const { Answer } = data;
|
||||
|
||||
if (dnsResponseIndex !== -1) {
|
||||
store.state.dnsResponses.splice(dnsResponseIndex, 1);
|
||||
}
|
||||
const dnsResponse: DnsResponse = {
|
||||
host,
|
||||
ips: Answer.map(answer => answer.data),
|
||||
timestamp: Date.now(),
|
||||
ttl: Math.max(Math.max(...Answer.map(answer => answer.TTL)), 300),
|
||||
};
|
||||
store.state.dnsResponses.push(dnsResponse);
|
||||
if (dnsResponseIndex !== -1) {
|
||||
store.state.dnsResponses.splice(dnsResponseIndex, 1, dnsResponse);
|
||||
} else {
|
||||
store.state.dnsResponses.push(dnsResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import workerScriptURL from "url:../page/worker.ts";
|
||||
import browser, { Storage } from "webextension-polyfill";
|
||||
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
||||
import isChromium from "../common/ts/isChromium";
|
||||
import { getStreamStatus, setStreamStatus } from "../common/ts/streamStatus";
|
||||
import store from "../store";
|
||||
import type { State } from "../store/types";
|
||||
import { MessageType } from "../types";
|
||||
@ -80,7 +81,7 @@ function onStoreChange(changes: Record<string, Storage.StorageChange>) {
|
||||
});
|
||||
}
|
||||
|
||||
function onBackgroundMessage(message: any) {
|
||||
function onBackgroundMessage(message: any): undefined {
|
||||
switch (message.type) {
|
||||
case MessageType.EnableFullModeResponse:
|
||||
window.postMessage({
|
||||
@ -145,6 +146,15 @@ function onPageMessage(event: MessageEvent) {
|
||||
);
|
||||
}
|
||||
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",
|
||||
});
|
||||
break;
|
||||
case MessageType.ClearStats:
|
||||
clearStats(message.channelName);
|
||||
break;
|
||||
|
@ -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.7",
|
||||
"version": "2.3.8",
|
||||
"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.7",
|
||||
"version": "2.3.8",
|
||||
"background": {
|
||||
"scripts": ["background/background.ts"],
|
||||
"persistent": false
|
||||
|
@ -113,9 +113,14 @@
|
||||
<label for="anonymous-mode-checkbox">Anonymous mode</label>
|
||||
<br />
|
||||
<small>
|
||||
Watch streams as if you were logged out. This option might help
|
||||
Watch streams as if you were logged out. This option may help
|
||||
reduce the number of "Commercial break in progress" ads.
|
||||
</small>
|
||||
<br />
|
||||
<small>
|
||||
This option might prevent Drops from working. Only disable it
|
||||
<b>if you are having issues</b>.
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -41,7 +41,8 @@ const pageState: PageState = {
|
||||
|
||||
window.fetch = getFetch(pageState);
|
||||
|
||||
window.Worker = class Worker extends window.Worker {
|
||||
const NATIVE_WORKER = window.Worker;
|
||||
window.Worker = class Worker extends NATIVE_WORKER {
|
||||
constructor(scriptURL: string | URL, options?: WorkerOptions) {
|
||||
const fullUrl = toAbsoluteUrl(scriptURL.toString());
|
||||
const isTwitchWorker = fullUrl.includes(".twitch.tv");
|
||||
@ -49,6 +50,16 @@ window.Worker = class Worker extends window.Worker {
|
||||
super(scriptURL, options);
|
||||
return;
|
||||
}
|
||||
// Required for VAFT (>=12.0.0) compatibility.
|
||||
const isAlreadyHooked = NATIVE_WORKER.toString().includes("twitch");
|
||||
if (isAlreadyHooked) {
|
||||
console.info("[TTV LOL PRO] Another Twitch ad blocker is in use.");
|
||||
sendMessageToContentScript({
|
||||
type: MessageType.MultipleAdBlockersInUse,
|
||||
});
|
||||
super(scriptURL, options);
|
||||
return;
|
||||
}
|
||||
let script = "";
|
||||
// Fetch Twitch's script, since Firefox Nightly errors out when trying to
|
||||
// import a blob URL directly.
|
||||
@ -80,7 +91,7 @@ window.Worker = class Worker extends window.Worker {
|
||||
const newScriptURL = URL.createObjectURL(
|
||||
new Blob([newScript], { type: "text/javascript" })
|
||||
);
|
||||
// Required for VAFT compatibility.
|
||||
// Required for VAFT (<9.0.0) compatibility.
|
||||
const wrapperScript = `
|
||||
try {
|
||||
importScripts('${newScriptURL}');
|
||||
|
@ -173,7 +173,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://discord.gg/AmtFTPwsyH"
|
||||
href="https://discord.ttvlolpro.com/"
|
||||
target="_blank"
|
||||
class="list-item"
|
||||
>
|
||||
@ -239,7 +239,7 @@
|
||||
|
||||
<small class="question-info">
|
||||
Any questions? Please read the
|
||||
<a href="https://wiki.cdn-perfprod.com/" target="_blank">wiki</a>
|
||||
<a href="https://wiki.ttvlolpro.com/" target="_blank">wiki</a>
|
||||
first.
|
||||
</small>
|
||||
</main>
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
import { alpha2 } from "../common/ts/countryCodes";
|
||||
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
|
||||
import isChannelWhitelisted from "../common/ts/isChannelWhitelisted";
|
||||
import isChromium from "../common/ts/isChromium";
|
||||
import store from "../store";
|
||||
import type { StreamStatus } from "../types";
|
||||
|
||||
@ -116,7 +115,7 @@ function setProxyStatus(
|
||||
reasonElement.textContent = status.reason;
|
||||
reasonElement.style.display = "";
|
||||
} else if (status.stats) {
|
||||
reasonElement.textContent = `Proxied: ${status.stats.proxied} | Not proxied: ${status.stats.notProxied}`;
|
||||
reasonElement.textContent = getProxyStatusStatsMessage(status.stats);
|
||||
reasonElement.style.display = "";
|
||||
} else {
|
||||
reasonElement.style.display = "none";
|
||||
@ -133,8 +132,20 @@ function setProxyStatus(
|
||||
}
|
||||
}
|
||||
|
||||
function getProxyStatusStatsMessage(
|
||||
stats: NonNullable<StreamStatus["stats"]>
|
||||
): string {
|
||||
const formatter = new Intl.NumberFormat("en-US");
|
||||
return `Proxied: ${formatter.format(
|
||||
stats.proxied
|
||||
)} | Not proxied: ${formatter.format(stats.notProxied)}`;
|
||||
}
|
||||
|
||||
function getProxyStatusMessages(status: StreamStatus): string[] {
|
||||
const messages = [];
|
||||
if (status.reason && status.stats) {
|
||||
messages.push(getProxyStatusStatsMessage(status.stats));
|
||||
}
|
||||
if (status.proxyHost) {
|
||||
messages.push(`Proxy: ${anonymizeIpAddress(status.proxyHost)}`);
|
||||
}
|
||||
@ -219,6 +230,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
|
||||
`Stream status:\n`,
|
||||
status != null
|
||||
? [
|
||||
`- Reason: ${status.reason ?? "N/A"}\n`,
|
||||
`- Proxied: ${status.stats?.proxied ?? "N/A"}, Not proxied: ${
|
||||
status.stats?.notProxied ?? "N/A"
|
||||
}\n`,
|
||||
@ -230,9 +242,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
|
||||
`- Country: ${status.proxyCountry ?? "N/A"}\n`,
|
||||
].join("")
|
||||
: "",
|
||||
isChromium
|
||||
? `Proxy level of control: ${proxySettings.levelOfControl}\n`
|
||||
: "",
|
||||
`Proxy level of control: ${proxySettings.levelOfControl}\n`,
|
||||
].join("")
|
||||
: "",
|
||||
store.state.adLog.length > 0
|
||||
|
@ -33,7 +33,7 @@ class Store<T extends Record<string | symbol, any>> {
|
||||
if (area !== this._areaName) return;
|
||||
for (const [key, { newValue }] of Object.entries(changes)) {
|
||||
if (newValue === undefined) continue; // Ignore deletions.
|
||||
this._state[key as keyof T] = newValue;
|
||||
this._state[key as keyof T] = newValue as T[keyof T];
|
||||
}
|
||||
this.dispatchEvent("change", changes);
|
||||
});
|
||||
@ -46,7 +46,7 @@ class Store<T extends Record<string | symbol, any>> {
|
||||
|
||||
this._state = this._getDefaultState();
|
||||
for (const [key, value] of Object.entries(storage)) {
|
||||
this._state[key as keyof T] = value;
|
||||
this._state[key as keyof T] = value as T[keyof T];
|
||||
}
|
||||
const stateHandler = getStateHandler(this._areaName, this._state);
|
||||
const stateProxy = new Proxy(this._state, stateHandler);
|
||||
|
@ -82,6 +82,7 @@ export const enum MessageType {
|
||||
UsherResponse = "TLP_UsherResponse",
|
||||
NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken",
|
||||
NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",
|
||||
MultipleAdBlockersInUse = "TLP_MultipleAdBlockersInUse",
|
||||
ClearStats = "TLP_ClearStats",
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user