Merge pull request #340 from younesaassila/v2.3.8

Release version 2.3.8
This commit is contained in:
Younes Aassila 2024-08-23 10:38:28 +02:00 committed by GitHub
commit 4cf96c311c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 476 additions and 1438 deletions

View File

@ -1,8 +1,17 @@
name: Build and Test name: Build and Test
on: [push, pull_request] on:
push:
branches:
- "**"
tags-ignore:
- "**"
pull_request:
branches:
- "**"
jobs: jobs:
build: build:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -32,7 +32,7 @@
</div> </div>
<div align="center"> <div align="center">
<a href="https://discord.gg/AmtFTPwsyH"> <a href="https://discord.ttvlolpro.com/">
<img <img
alt="Discord server" alt="Discord server"
src="https://dcbadge.vercel.app/api/server/AmtFTPwsyH" 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. 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 ## Screenshots

1665
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "ttv-lol-pro", "name": "ttv-lol-pro",
"version": "2.3.7", "version": "2.3.8",
"description": "TTV LOL PRO removes most livestream ads from Twitch.", "description": "TTV LOL PRO removes most livestream ads from Twitch.",
"@parcel/bundler-default": { "@parcel/bundler-default": {
"minBundles": 10000000, "minBundles": 10000000,
@ -43,24 +43,25 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"bowser": "^2.11.0", "bowser": "^2.11.0",
"ip": "^2.0.1", "ip-address": "^9.0.5",
"m3u8-parser": "^7.1.0" "m3u8-parser": "^7.1.0"
}, },
"devDependencies": { "devDependencies": {
"@parcel/config-webextension": "^2.12.0", "@parcel/config-webextension": "^2.12.0",
"@types/chrome": "^0.0.267", "@types/chrome": "^0.0.270",
"@types/ip": "^1.1.3", "@types/jsbn": "^1.2.33",
"@types/webextension-polyfill": "^0.10.7", "@types/node": "^20.16.1",
"@types/webextension-polyfill": "^0.12.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"parcel": "^2.12.0", "parcel": "^2.12.0",
"postcss": "^8.4.38", "postcss": "^8.4.41",
"prettier": "2.8.8", "prettier": "2.8.8",
"prettier-plugin-css-order": "^1.3.1", "prettier-plugin-css-order": "^1.3.1",
"prettier-plugin-organize-imports": "^3.2.4", "prettier-plugin-organize-imports": "^3.2.4",
"shx": "^0.3.4", "shx": "^0.3.4",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"webextension-polyfill": "^0.11.0" "webextension-polyfill": "^0.12.0"
}, },
"private": true "private": true
} }

View File

@ -6,7 +6,7 @@ const pendingRequests: string[] = [];
export default function onAuthRequired( export default function onAuthRequired(
details: WebRequest.OnAuthRequiredDetailsType details: WebRequest.OnAuthRequiredDetailsType
): void | WebRequest.BlockingResponseOrPromise { ): WebRequest.BlockingResponseOrPromise | undefined {
if (!details.isProxy) return; if (!details.isProxy) return;
if (pendingRequests.includes(details.requestId)) { if (pendingRequests.includes(details.requestId)) {

View File

@ -8,7 +8,7 @@ import { twitchTvHostRegex } from "../../common/ts/regexes";
export default function onBeforeTwitchTvSendHeaders( export default function onBeforeTwitchTvSendHeaders(
details: WebRequest.OnBeforeSendHeadersDetailsType details: WebRequest.OnBeforeSendHeadersDetailsType
): void | WebRequest.BlockingResponseOrPromise { ): WebRequest.BlockingResponseOrPromise | undefined {
const host = getHostFromUrl(details.url); const host = getHostFromUrl(details.url);
if (!host || !twitchTvHostRegex.test(host)) return; if (!host || !twitchTvHostRegex.test(host)) return;

View File

@ -11,7 +11,7 @@ export default function onBeforeVideoWeaverRequest(
details: WebRequest.OnBeforeRequestDetailsType & { details: WebRequest.OnBeforeRequestDetailsType & {
proxyInfo?: ProxyInfo; proxyInfo?: ProxyInfo;
} }
): void | WebRequest.BlockingResponseOrPromise { ): WebRequest.BlockingResponseOrPromise | undefined {
// Filter to video-weaver responses. // Filter to video-weaver responses.
const host = getHostFromUrl(details.url); const host = getHostFromUrl(details.url);
if (!host || !videoWeaverHostRegex.test(host)) return; if (!host || !videoWeaverHostRegex.test(host)) return;
@ -34,9 +34,7 @@ export default function onBeforeVideoWeaverRequest(
if (isDuplicate) return text; if (isDuplicate) return text;
const channelName = findChannelFromVideoWeaverUrl(details.url); const channelName = findChannelFromVideoWeaverUrl(details.url);
const isPurpleScreen = textLower.includes( const isPurpleScreen = textLower.includes("https://help.twitch.tv/");
"https://help.twitch.tv/s/article/ad-experience-on-twitch"
);
const proxy = const proxy =
details.proxyInfo && details.proxyInfo.type !== "direct" details.proxyInfo && details.proxyInfo.type !== "direct"
? getUrlFromProxyInfo(details.proxyInfo) ? getUrlFromProxyInfo(details.proxyInfo)

View File

@ -14,8 +14,8 @@ const fetchTimeoutMsOverride: Map<ProxyRequestType, number> = new Map([
export default function onContentScriptMessage( export default function onContentScriptMessage(
message: any, message: any,
sender: Runtime.MessageSender, sender: Runtime.MessageSender,
sendResponse: () => void sendResponse: (message: any) => void
): true | void | Promise<any> { ): Promise<any> | true | undefined {
if (message.type === MessageType.EnableFullMode) { if (message.type === MessageType.EnableFullMode) {
if (!sender.tab?.id) return; if (!sender.tab?.id) return;

View File

@ -27,7 +27,13 @@ export default async function onResponseStarted(
const host = getHostFromUrl(details.url); const host = getHostFromUrl(details.url);
if (!host) return; 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 = { const requestParams = {
isChromium: isChromium, isChromium: isChromium,
@ -79,13 +85,26 @@ export default async function onResponseStarted(
findChannelFromTwitchTvUrl(tabUrl); findChannelFromTwitchTvUrl(tabUrl);
const streamStatus = getStreamStatus(channelName); const streamStatus = getStreamStatus(channelName);
const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 }; const stats = streamStatus?.stats ?? { proxied: 0, notProxied: 0 };
if (!proxy) { if (!proxy) {
stats.notProxied++; 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, { setStreamStatus(channelName, {
proxied: false, proxied: false,
proxyHost: streamStatus?.proxyHost ? streamStatus.proxyHost : undefined, proxyHost: streamStatus?.proxyHost ? streamStatus.proxyHost : undefined,
proxyCountry: streamStatus?.proxyCountry, proxyCountry: streamStatus?.proxyCountry,
reason: streamStatus?.reason ?? "", reason,
stats, stats,
}); });
console.log( console.log(
@ -93,6 +112,7 @@ export default async function onResponseStarted(
); );
return; return;
} }
stats.proxied++; stats.proxied++;
setStreamStatus(channelName, { setStreamStatus(channelName, {
proxied: true, proxied: true,
@ -126,24 +146,29 @@ function getProxyFromDetails(
} }
): string | null { ): string | null {
if (isChromium) { 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; const ip = details.ip;
if (!ip) return null; if (!ip) return null;
const dnsResponse = store.state.dnsResponses.find( const dnsResponse = store.state.dnsResponses.find(
dnsResponse => dnsResponse.ips.indexOf(ip) !== -1 dnsResponse => dnsResponse.ips.indexOf(ip) !== -1
); );
if (!dnsResponse) return null; if (!dnsResponse) return null;
const proxies = [
...store.state.optimizedProxies,
...store.state.normalProxies,
];
const proxyInfoArray = proxies.map(getProxyInfoFromUrl); const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
const possibleProxies = proxyInfoArray.filter( const possibleProxies = proxyInfoArray.filter(
proxy => proxy.host === dnsResponse.host proxy => proxy.host === dnsResponse.host
); );
if (possibleProxies.length === 1) if (possibleProxies.length === 0) return dnsResponse.host;
return getUrlFromProxyInfo(possibleProxies[0]); return getUrlFromProxyInfo(possibleProxies[0]);
// TODO: Set reason to some error message about DNS.
return dnsResponse.host;
} else { } else {
const proxyInfo = details.proxyInfo; // Firefox only. const proxyInfo = details.proxyInfo; // Firefox only.
if (!proxyInfo || proxyInfo.type === "direct") return null; if (!proxyInfo || proxyInfo.type === "direct") return null;

View File

@ -1,4 +1,5 @@
import ip from "ip"; import { Address4, Address6 } from "ip-address";
import isPrivateIp from "./isPrivateIp";
import { getProxyInfoFromUrl } from "./proxyInfo"; import { getProxyInfoFromUrl } from "./proxyInfo";
/** /**
@ -12,16 +13,26 @@ export function anonymizeIpAddress(url: string): string {
let proxyHost = proxyInfo.host; let proxyHost = proxyInfo.host;
const isIPv4 = ip.isV4Format(proxyHost); const isIPv4 = Address4.isValid(proxyHost);
const isIPv6 = ip.isV6Format(proxyHost); const isIPv6 = Address6.isValid(proxyHost);
const isIP = isIPv4 || isIPv6; const isIP = isIPv4 || isIPv6;
const isPublicIP = isIP && !ip.isPrivate(proxyHost); const isPublicIP = isIP && !isPrivateIp(proxyHost);
if (isPublicIP) { if (isPublicIP) {
if (isIPv4) { 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) { } 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");
} }
} }

View 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;
}

View File

@ -1,4 +1,4 @@
import ip from "ip"; import { Address6 } from "ip-address";
import type { ProxyInfo } from "../../types"; import type { ProxyInfo } from "../../types";
export function getProxyInfoFromUrl( export function getProxyInfoFromUrl(
@ -71,14 +71,14 @@ export function getUrlFromProxyInfo(proxyInfo: ProxyInfo): string {
} else if (username) { } else if (username) {
url = `${username}@`; url = `${username}@`;
} }
const isIPv4 = ip.isV4Format(host); const isIPv6 = Address6.isValid(host);
const isIPv6 = ip.isV6Format(host); if (isIPv6) {
// isV6Format() returns true for IPv4 addresses, so we need to exclude those.
if (isIPv6 && !isIPv4) {
url += `[${host}]`; url += `[${host}]`;
} else { } else {
url += host; url += host;
} }
if (port) url += `:${port}`; if (port) {
url += `:${port}`;
}
return url; return url;
} }

View File

@ -1,12 +1,13 @@
import ip from "ip"; import { Address4, Address6 } from "ip-address";
import store from "../../store"; import store from "../../store";
import type { DnsResponse, DnsResponseJson } from "../../types"; import type { DnsResponse, DnsResponseJson } from "../../types";
import { getProxyInfoFromUrl } from "./proxyInfo"; import { getProxyInfoFromUrl } from "./proxyInfo";
export default async function updateDnsResponses() { export default async function updateDnsResponses() {
const proxies = store.state.optimizedProxiesEnabled const proxies = [
? store.state.optimizedProxies ...store.state.optimizedProxies,
: store.state.normalProxies; ...store.state.normalProxies,
];
const proxyInfoArray = proxies.map(getProxyInfoFromUrl); const proxyInfoArray = proxies.map(getProxyInfoFromUrl);
for (const proxyInfo of proxyInfoArray) { 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. // 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 (isIp) {
if (dnsResponseIndex !== -1) {
store.state.dnsResponses.splice(dnsResponseIndex, 1);
}
const dnsResponse: DnsResponse = { const dnsResponse: DnsResponse = {
host, host,
ips: [host], ips: [host],
timestamp: Date.now(), timestamp: Date.now(),
ttl: Infinity, ttl: Infinity,
}; };
store.state.dnsResponses.push(dnsResponse); if (dnsResponseIndex !== -1) {
store.state.dnsResponses.splice(dnsResponseIndex, 1, dnsResponse);
} else {
store.state.dnsResponses.push(dnsResponse);
}
continue; continue;
} }
@ -59,16 +61,17 @@ export default async function updateDnsResponses() {
} }
const { Answer } = data; const { Answer } = data;
if (dnsResponseIndex !== -1) {
store.state.dnsResponses.splice(dnsResponseIndex, 1);
}
const dnsResponse: DnsResponse = { const dnsResponse: DnsResponse = {
host, host,
ips: Answer.map(answer => answer.data), ips: Answer.map(answer => answer.data),
timestamp: Date.now(), timestamp: Date.now(),
ttl: Math.max(Math.max(...Answer.map(answer => answer.TTL)), 300), 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) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -3,6 +3,7 @@ import workerScriptURL from "url:../page/worker.ts";
import browser, { Storage } from "webextension-polyfill"; import browser, { Storage } from "webextension-polyfill";
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl"; import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
import isChromium from "../common/ts/isChromium"; import isChromium from "../common/ts/isChromium";
import { getStreamStatus, setStreamStatus } from "../common/ts/streamStatus";
import store from "../store"; import store from "../store";
import type { State } from "../store/types"; import type { State } from "../store/types";
import { MessageType } from "../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) { switch (message.type) {
case MessageType.EnableFullModeResponse: case MessageType.EnableFullModeResponse:
window.postMessage({ window.postMessage({
@ -145,6 +146,15 @@ function onPageMessage(event: MessageEvent) {
); );
} }
break; 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: case MessageType.ClearStats:
clearStats(message.channelName); clearStats(message.channelName);
break; break;

View File

@ -3,7 +3,7 @@
"name": "TTV LOL PRO", "name": "TTV LOL PRO",
"description": "TTV LOL PRO removes most livestream ads from Twitch.", "description": "TTV LOL PRO removes most livestream ads from Twitch.",
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro", "homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
"version": "2.3.7", "version": "2.3.8",
"background": { "background": {
"service_worker": "background/background.ts", "service_worker": "background/background.ts",
"type": "module" "type": "module"

View File

@ -3,7 +3,7 @@
"name": "TTV LOL PRO", "name": "TTV LOL PRO",
"description": "TTV LOL PRO removes most livestream ads from Twitch.", "description": "TTV LOL PRO removes most livestream ads from Twitch.",
"homepage_url": "https://github.com/younesaassila/ttv-lol-pro", "homepage_url": "https://github.com/younesaassila/ttv-lol-pro",
"version": "2.3.7", "version": "2.3.8",
"background": { "background": {
"scripts": ["background/background.ts"], "scripts": ["background/background.ts"],
"persistent": false "persistent": false

View File

@ -113,9 +113,14 @@
<label for="anonymous-mode-checkbox">Anonymous mode</label> <label for="anonymous-mode-checkbox">Anonymous mode</label>
<br /> <br />
<small> <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. reduce the number of "Commercial break in progress" ads.
</small> </small>
<br />
<small>
This option might prevent Drops from working. Only disable it
<b>if you are having issues</b>.
</small>
</li> </li>
</ul> </ul>
</section> </section>

View File

@ -41,7 +41,8 @@ const pageState: PageState = {
window.fetch = getFetch(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) { constructor(scriptURL: string | URL, options?: WorkerOptions) {
const fullUrl = toAbsoluteUrl(scriptURL.toString()); const fullUrl = toAbsoluteUrl(scriptURL.toString());
const isTwitchWorker = fullUrl.includes(".twitch.tv"); const isTwitchWorker = fullUrl.includes(".twitch.tv");
@ -49,6 +50,16 @@ window.Worker = class Worker extends window.Worker {
super(scriptURL, options); super(scriptURL, options);
return; 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 = ""; let script = "";
// Fetch Twitch's script, since Firefox Nightly errors out when trying to // Fetch Twitch's script, since Firefox Nightly errors out when trying to
// import a blob URL directly. // import a blob URL directly.
@ -80,7 +91,7 @@ window.Worker = class Worker extends window.Worker {
const newScriptURL = URL.createObjectURL( const newScriptURL = URL.createObjectURL(
new Blob([newScript], { type: "text/javascript" }) new Blob([newScript], { type: "text/javascript" })
); );
// Required for VAFT compatibility. // Required for VAFT (<9.0.0) compatibility.
const wrapperScript = ` const wrapperScript = `
try { try {
importScripts('${newScriptURL}'); importScripts('${newScriptURL}');

View File

@ -173,7 +173,7 @@
</li> </li>
<li> <li>
<a <a
href="https://discord.gg/AmtFTPwsyH" href="https://discord.ttvlolpro.com/"
target="_blank" target="_blank"
class="list-item" class="list-item"
> >
@ -239,7 +239,7 @@
<small class="question-info"> <small class="question-info">
Any questions? Please read the 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. first.
</small> </small>
</main> </main>

View File

@ -8,7 +8,6 @@ import {
import { alpha2 } from "../common/ts/countryCodes"; import { alpha2 } from "../common/ts/countryCodes";
import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl"; import findChannelFromTwitchTvUrl from "../common/ts/findChannelFromTwitchTvUrl";
import isChannelWhitelisted from "../common/ts/isChannelWhitelisted"; import isChannelWhitelisted from "../common/ts/isChannelWhitelisted";
import isChromium from "../common/ts/isChromium";
import store from "../store"; import store from "../store";
import type { StreamStatus } from "../types"; import type { StreamStatus } from "../types";
@ -116,7 +115,7 @@ function setProxyStatus(
reasonElement.textContent = status.reason; reasonElement.textContent = status.reason;
reasonElement.style.display = ""; reasonElement.style.display = "";
} else if (status.stats) { } else if (status.stats) {
reasonElement.textContent = `Proxied: ${status.stats.proxied} | Not proxied: ${status.stats.notProxied}`; reasonElement.textContent = getProxyStatusStatsMessage(status.stats);
reasonElement.style.display = ""; reasonElement.style.display = "";
} else { } else {
reasonElement.style.display = "none"; 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[] { function getProxyStatusMessages(status: StreamStatus): string[] {
const messages = []; const messages = [];
if (status.reason && status.stats) {
messages.push(getProxyStatusStatsMessage(status.stats));
}
if (status.proxyHost) { if (status.proxyHost) {
messages.push(`Proxy: ${anonymizeIpAddress(status.proxyHost)}`); messages.push(`Proxy: ${anonymizeIpAddress(status.proxyHost)}`);
} }
@ -219,6 +230,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
`Stream status:\n`, `Stream status:\n`,
status != null status != null
? [ ? [
`- Reason: ${status.reason ?? "N/A"}\n`,
`- Proxied: ${status.stats?.proxied ?? "N/A"}, Not proxied: ${ `- Proxied: ${status.stats?.proxied ?? "N/A"}, Not proxied: ${
status.stats?.notProxied ?? "N/A" status.stats?.notProxied ?? "N/A"
}\n`, }\n`,
@ -230,9 +242,7 @@ copyDebugInfoButtonElement.addEventListener("click", async e => {
`- Country: ${status.proxyCountry ?? "N/A"}\n`, `- Country: ${status.proxyCountry ?? "N/A"}\n`,
].join("") ].join("")
: "", : "",
isChromium `Proxy level of control: ${proxySettings.levelOfControl}\n`,
? `Proxy level of control: ${proxySettings.levelOfControl}\n`
: "",
].join("") ].join("")
: "", : "",
store.state.adLog.length > 0 store.state.adLog.length > 0

View File

@ -33,7 +33,7 @@ class Store<T extends Record<string | symbol, any>> {
if (area !== this._areaName) return; if (area !== this._areaName) return;
for (const [key, { newValue }] of Object.entries(changes)) { for (const [key, { newValue }] of Object.entries(changes)) {
if (newValue === undefined) continue; // Ignore deletions. 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); this.dispatchEvent("change", changes);
}); });
@ -46,7 +46,7 @@ class Store<T extends Record<string | symbol, any>> {
this._state = this._getDefaultState(); this._state = this._getDefaultState();
for (const [key, value] of Object.entries(storage)) { 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 stateHandler = getStateHandler(this._areaName, this._state);
const stateProxy = new Proxy(this._state, stateHandler); const stateProxy = new Proxy(this._state, stateHandler);

View File

@ -82,6 +82,7 @@ export const enum MessageType {
UsherResponse = "TLP_UsherResponse", UsherResponse = "TLP_UsherResponse",
NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken", NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken",
NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse", NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse",
MultipleAdBlockersInUse = "TLP_MultipleAdBlockersInUse",
ClearStats = "TLP_ClearStats", ClearStats = "TLP_ClearStats",
} }