diff --git a/src/content/content.ts b/src/content/content.ts index e660ae1..618d741 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -47,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]; @@ -102,7 +105,7 @@ function onPageMessage(event: MessageEvent) { return; } - const { message, responseType, responseMessageType } = event.data; + const { message } = event.data; if (!message) return; if (message.type === MessageType.GetStoreState) { @@ -150,6 +153,7 @@ function onPageMessage(event: MessageEvent) { isSubscribed, isWhitelisted, }); + const currentChannelName = findChannelFromTwitchTvUrl(location.href); if (store.state.whitelistChannelSubscriptions && channelName != null) { if (!wasSubscribed && isSubscribed) { store.state.activeChannelSubscriptions.push(channelName); @@ -158,7 +162,9 @@ function onPageMessage(event: MessageEvent) { console.log(`[TTV LOL PRO] Adding '${channelName}' to whitelist.`); store.state.whitelistedChannels.push(channelName); } - location.reload(); + if (channelName === currentChannelName) { + location.reload(); + } } else if (wasSubscribed && !isSubscribed) { store.state.activeChannelSubscriptions = store.state.activeChannelSubscriptions.filter( @@ -174,7 +180,9 @@ function onPageMessage(event: MessageEvent) { c => c.toLowerCase() !== channelName.toLowerCase() ); } - location.reload(); + if (channelName === currentChannelName) { + location.reload(); + } } } } @@ -201,6 +209,6 @@ function onPageMessage(event: MessageEvent) { } // --- else if (message.type === MessageType.ClearStats) { - clearStats(message.channelName); + clearStats(message.channelName, 2000); } } diff --git a/src/page/getFetch.ts b/src/page/getFetch.ts index cfb6bc6..b616ec6 100644 --- a/src/page/getFetch.ts +++ b/src/page/getFetch.ts @@ -71,9 +71,11 @@ 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 === channelNameLower) { + usherManifests[i].deleted = true; + } + } if (cachedPlaybackTokenRequestBody?.includes(channelNameLower)) { cachedPlaybackTokenRequestHeaders = null; cachedPlaybackTokenRequestBody = null; @@ -294,9 +296,9 @@ export function getFetch(pageState: PageState): typeof fetch { [...manifest.assignedMap.values()].includes(url) ); if (manifest == null) { - console.log( + console.warn( "[TTV LOL PRO] No associated Usher manifest found for Video Weaver request." - ); // This can happen just after a ClearStats message if Twitch decides to send another Video Weaver request. + ); } if (videoWeaverUrlsToNotProxy.has(url)) { if (IS_DEVELOPMENT) { @@ -442,6 +444,7 @@ export function getFetch(pageState: PageState): typeof fetch { } const isLivestream = !/^\d+$/.test(channelName); // VODs have numeric IDs. if (!isLivestream) break graphqlRes; + await waitForStore(pageState); const wasSubscribed = wasChannelSubscriber(channelName, pageState); const hasSubStatusChanged = (wasSubscribed && !isSubscribed) || (!wasSubscribed && isSubscribed); @@ -465,6 +468,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"') @@ -474,6 +478,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( @@ -486,9 +491,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() ?? [])]; @@ -534,14 +542,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, @@ -746,10 +751,6 @@ function cancelRequest(): never { throw new Error(); } -async function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - //#region Video Weaver URL replacement /** diff --git a/src/page/types.ts b/src/page/types.ts index a782b75..151ca7f 100644 --- a/src/page/types.ts +++ b/src/page/types.ts @@ -36,6 +36,7 @@ export interface UsherManifest { replacementMap: Map | 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 { diff --git a/src/types.ts b/src/types.ts index 43b0aa8..d642a20 100644 --- a/src/types.ts +++ b/src/types.ts @@ -79,13 +79,10 @@ export const enum MessageType { EnableFullMode = "TLP_EnableFullMode", EnableFullModeResponse = "TLP_EnableFullModeResponse", DisableFullMode = "TLP_DisableFullMode", - ChannelSubStatusQuery = "TLP_ChannelSubStatusQuery", - ChannelSubStatusQueryResponse = "TLP_ChannelSubStatusQueryResponse", - ChannelSubStatusChange = "TLP_ChannelSubStatusChange", - ChannelSubStatusChangeResponse = "TLP_ChannelSubStatusChangeResponse", UsherResponse = "TLP_UsherResponse", NewPlaybackAccessToken = "TLP_NewPlaybackAccessToken", NewPlaybackAccessTokenResponse = "TLP_NewPlaybackAccessTokenResponse", + ChannelSubStatusChange = "TLP_ChannelSubStatusChange", MultipleAdBlockersInUse = "TLP_MultipleAdBlockersInUse", ClearStats = "TLP_ClearStats", }