From 43c80713e9a774c933bd9a5355cdfe69a24ff2c5 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Wed, 12 Mar 2025 13:34:39 -0400 Subject: [PATCH] 4.77.1 * Fixed: Add support for a new React router Twitch is testing in an A/B experiment. --- package.json | 2 +- src/sites/clips/index.jsx | 8 +- src/sites/player/index.jsx | 3 + src/sites/twitch-twilight/index.js | 5 +- src/sites/twitch-twilight/modules/channel.jsx | 2 +- .../modules/directory/index.jsx | 4 +- src/utilities/compat/fine-router.ts | 106 ++++++++++++++++-- src/utilities/vue.ts | 6 +- 8 files changed, 114 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 43449302..6041ea15 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.77.0", + "version": "4.77.1", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/sites/clips/index.jsx b/src/sites/clips/index.jsx index 4c949906..75af702a 100644 --- a/src/sites/clips/index.jsx +++ b/src/sites/clips/index.jsx @@ -140,10 +140,10 @@ export default class ClipsSite extends BaseSite { updateContext() { try { const state = this.store.getState(), - history = this.router && this.router.history; + location = this.router?.reactLocation; this.settings.updateContext({ - location: history?.location, + location, ui: state?.ui, session: state?.session }); @@ -223,4 +223,8 @@ ClipsSite.CLIP_ROUTES = { 'clip-page': '/:slug' }; +ClipsSite.CHAT_ROUTES = [ + 'clip-page' +]; + ClipsSite.DIALOG_SELECTOR = '#root > div'; diff --git a/src/sites/player/index.jsx b/src/sites/player/index.jsx index 1a8a654c..ac449cd0 100644 --- a/src/sites/player/index.jsx +++ b/src/sites/player/index.jsx @@ -211,3 +211,6 @@ export default class PlayerSite extends BaseSite { ver.textContent = this.resolve('core').constructor.version_info.toString(); } } + + +PlayerSite.CHAT_ROUTES = []; diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index 1c19d51d..9e78962f 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -165,14 +165,15 @@ export default class Twilight extends BaseSite { updateContext() { try { const state = this.store.getState(), - history = this.router && this.router.history; + location = this.router?.reactLocation; this.settings.updateContext({ - location: history && history.location, + location, ui: state && state.ui, session: state && state.session, chat: state && state.chat }); + } catch(err) { this.log.error('Error updating context.', err); } diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index 2568a062..d3151861 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -247,7 +247,7 @@ export default class Channel extends Module { if ( this.router.old_location === this.router.location ) return; - this.router.history.replace(this.router.location, {channelView: 'Watch'}); + this.router.replace(this.router.location, {channelView: 'Watch'}); } updateLinks() { diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index ba7271d8..0e8e6ab1 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -621,7 +621,7 @@ export default class Directory extends Module { link.props.onClick(); // And follow the generated link. - this.router.history.push(link.props.linkTo); + this.router.push(link.props.linkTo); } updateGameCard(el) { @@ -1029,7 +1029,7 @@ export default class Directory extends Module { } if ( url ) - this.router.history.push(url); + this.router.push(url); } diff --git a/src/utilities/compat/fine-router.ts b/src/utilities/compat/fine-router.ts index 0c95dedc..4865db30 100644 --- a/src/utilities/compat/fine-router.ts +++ b/src/utilities/compat/fine-router.ts @@ -9,6 +9,7 @@ import Module, { GenericModule } from 'utilities/module'; import {has, deep_equals, sleep} from 'utilities/object'; import type Fine from './fine'; import type { OptionalPromise } from 'utilities/types'; +import { ReactNode, ReactStateNode } from './react-types'; declare module 'utilities/types' { interface ModuleEventMap { @@ -34,6 +35,36 @@ export type RouteInfo = { }; +type ReactLocation = Location & { + state: unknown; +} + + +type HistoryObject = { + listen(fn: (location: ReactLocation) => void): void; + push(url: string, state: unknown): void; + replace(url: string, state: unknown): void; + location: ReactLocation; +}; + +type RouterState = { + historyAction: string; + location: ReactLocation; +}; + +type RouterObject = { + subscribe(fn: (state: RouterState) => void): void; + router: { + state: RouterState + } +}; + +type NavigationObject = { + push(url: string, state: unknown): void; + replace(url: string, state: unknown): void; +} + + export default class FineRouter extends Module<'site.router', FineRouterEvents> { // Dependencies @@ -51,6 +82,10 @@ export default class FineRouter extends Module<'site.router', FineRouterEvents> match: unknown | null; location: unknown | null; + // Things + history?: HistoryObject | null; + router?: RouterObject | null; + navigator?: NavigationObject | null; constructor(name?: string, parent?: GenericModule) { @@ -69,26 +104,75 @@ export default class FineRouter extends Module<'site.router', FineRouterEvents> } /** @internal */ - onEnable(): OptionalPromise { - const thing = this.fine.searchTree(null, n => n.props && n.props.history), - history = this.history = thing && thing.props && thing.props.history; + onEnable(tries = 0): OptionalPromise { + const thing = this.fine.searchTree>(null, n => n?.props?.history); + this.history = thing?.props?.history; - if ( ! history ) - return sleep(50).then(() => this.onEnable()); + if ( this.history ) { + this.history.listen(location => { + if ( this.enabled ) + this._navigateTo(location); + }); - history.listen(location => { - if ( this.enabled ) - this._navigateTo(location); + this._navigateTo(this.history.location); + return; + } + + + const other = this.fine.searchNode(null, n => n?.pendingProps?.router?.subscribe); + this.router = other?.pendingProps?.router; + + const nav = this.fine.searchNode(null, n => n?.pendingProps?.navigator?.push); + this.navigator = nav?.pendingProps?.navigator; + + if ( ! this.router || ! this.navigator ) { + if (tries > 100) { + this.log.warn('Finding React\'s router is taking a long time.'); + tries = -500; + } + + return sleep(50).then(() => this.onEnable(tries + 1)); + } + + this.router.subscribe(evt => { + if ( this.enabled && evt?.location ) + this._navigateTo(evt.location); }); - this._navigateTo(history.location); + this._navigateTo(this.router.router.state.location); } navigate(route, data, opts, state) { - this.history.push(this.getURL(route, data, opts), state); + const url = this.getURL(route, data, opts); + this.push(url, state); } - private _navigateTo(location) { + get reactLocation() { + if (this.history) + return this.history.location; + else if (this.router) + return this.router.router.state.location; + } + + push(url: string, state: unknown) { + if (this.history) + this.history.push(url, state); + else if (this.navigator) + this.navigator.push(url, state); + else + throw new Error('unable to push new route'); + } + + replace(url: string, state: unknown) { + if (this.history) + this.history.replace(url, state); + else if (this.navigator) + this.navigator.replace(url, state); + else + throw new Error('unable to replace route'); + } + + private _navigateTo(location: ReactLocation) { this.log.debug('New Location', location); const host = window.location.host, path = location.pathname, diff --git a/src/utilities/vue.ts b/src/utilities/vue.ts index e6134f70..1db6e194 100644 --- a/src/utilities/vue.ts +++ b/src/utilities/vue.ts @@ -218,16 +218,16 @@ export class VueModule extends Module<'vue'> { methods: { reactNavigate(url, event, state) { const router = t.resolve('site.router'); - if ( router && router.history ) { + if ( router ) { if ( event ) { event.preventDefault(); event.stopPropagation(); } - router.history.push(url, state ?? undefined); + router.push(url, state ?? undefined); } }, getReactURL(route, data, opts, ...args) { - const router = t.resolve('site.router'); + const router = t.resolve('site.router')!; return router.getURL(route, data, opts, ...args); }, getI18n() {