1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Fixed: Add support for a new React router Twitch is testing in an A/B experiment.
This commit is contained in:
SirStendec 2025-03-12 13:34:39 -04:00
parent 850c4d53fd
commit 43c80713e9
8 changed files with 114 additions and 22 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.77.0", "version": "4.77.1",
"description": "FrankerFaceZ is a Twitch enhancement suite.", "description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",

View file

@ -140,10 +140,10 @@ export default class ClipsSite extends BaseSite {
updateContext() { updateContext() {
try { try {
const state = this.store.getState(), const state = this.store.getState(),
history = this.router && this.router.history; location = this.router?.reactLocation;
this.settings.updateContext({ this.settings.updateContext({
location: history?.location, location,
ui: state?.ui, ui: state?.ui,
session: state?.session session: state?.session
}); });
@ -223,4 +223,8 @@ ClipsSite.CLIP_ROUTES = {
'clip-page': '/:slug' 'clip-page': '/:slug'
}; };
ClipsSite.CHAT_ROUTES = [
'clip-page'
];
ClipsSite.DIALOG_SELECTOR = '#root > div'; ClipsSite.DIALOG_SELECTOR = '#root > div';

View file

@ -211,3 +211,6 @@ export default class PlayerSite extends BaseSite {
ver.textContent = this.resolve('core').constructor.version_info.toString(); ver.textContent = this.resolve('core').constructor.version_info.toString();
} }
} }
PlayerSite.CHAT_ROUTES = [];

View file

@ -165,14 +165,15 @@ export default class Twilight extends BaseSite {
updateContext() { updateContext() {
try { try {
const state = this.store.getState(), const state = this.store.getState(),
history = this.router && this.router.history; location = this.router?.reactLocation;
this.settings.updateContext({ this.settings.updateContext({
location: history && history.location, location,
ui: state && state.ui, ui: state && state.ui,
session: state && state.session, session: state && state.session,
chat: state && state.chat chat: state && state.chat
}); });
} catch(err) { } catch(err) {
this.log.error('Error updating context.', err); this.log.error('Error updating context.', err);
} }

View file

@ -247,7 +247,7 @@ export default class Channel extends Module {
if ( this.router.old_location === this.router.location ) if ( this.router.old_location === this.router.location )
return; return;
this.router.history.replace(this.router.location, {channelView: 'Watch'}); this.router.replace(this.router.location, {channelView: 'Watch'});
} }
updateLinks() { updateLinks() {

View file

@ -621,7 +621,7 @@ export default class Directory extends Module {
link.props.onClick(); link.props.onClick();
// And follow the generated link. // And follow the generated link.
this.router.history.push(link.props.linkTo); this.router.push(link.props.linkTo);
} }
updateGameCard(el) { updateGameCard(el) {
@ -1029,7 +1029,7 @@ export default class Directory extends Module {
} }
if ( url ) if ( url )
this.router.history.push(url); this.router.push(url);
} }

View file

@ -9,6 +9,7 @@ import Module, { GenericModule } from 'utilities/module';
import {has, deep_equals, sleep} from 'utilities/object'; import {has, deep_equals, sleep} from 'utilities/object';
import type Fine from './fine'; import type Fine from './fine';
import type { OptionalPromise } from 'utilities/types'; import type { OptionalPromise } from 'utilities/types';
import { ReactNode, ReactStateNode } from './react-types';
declare module 'utilities/types' { declare module 'utilities/types' {
interface ModuleEventMap { 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> { export default class FineRouter extends Module<'site.router', FineRouterEvents> {
// Dependencies // Dependencies
@ -51,6 +82,10 @@ export default class FineRouter extends Module<'site.router', FineRouterEvents>
match: unknown | null; match: unknown | null;
location: unknown | null; location: unknown | null;
// Things
history?: HistoryObject | null;
router?: RouterObject | null;
navigator?: NavigationObject | null;
constructor(name?: string, parent?: GenericModule) { constructor(name?: string, parent?: GenericModule) {
@ -69,26 +104,75 @@ export default class FineRouter extends Module<'site.router', FineRouterEvents>
} }
/** @internal */ /** @internal */
onEnable(): OptionalPromise<void> { onEnable(tries = 0): OptionalPromise<void> {
const thing = this.fine.searchTree(null, n => n.props && n.props.history), const thing = this.fine.searchTree<ReactStateNode<{history: HistoryObject}>>(null, n => n?.props?.history);
history = this.history = thing && thing.props && thing.props.history; this.history = thing?.props?.history;
if ( ! history ) if ( this.history ) {
return sleep(50).then(() => this.onEnable()); this.history.listen(location => {
history.listen(location => {
if ( this.enabled ) if ( this.enabled )
this._navigateTo(location); this._navigateTo(location);
}); });
this._navigateTo(history.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(this.router.router.state.location);
} }
navigate(route, data, opts, state) { 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); this.log.debug('New Location', location);
const host = window.location.host, const host = window.location.host,
path = location.pathname, path = location.pathname,

View file

@ -218,16 +218,16 @@ export class VueModule extends Module<'vue'> {
methods: { methods: {
reactNavigate(url, event, state) { reactNavigate(url, event, state) {
const router = t.resolve('site.router'); const router = t.resolve('site.router');
if ( router && router.history ) { if ( router ) {
if ( event ) { if ( event ) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
router.history.push(url, state ?? undefined); router.push(url, state ?? undefined);
} }
}, },
getReactURL(route, data, opts, ...args) { getReactURL(route, data, opts, ...args) {
const router = t.resolve('site.router'); const router = t.resolve('site.router')!;
return router.getURL(route, data, opts, ...args); return router.getURL(route, data, opts, ...args);
}, },
getI18n() { getI18n() {