Use a better method to show the Better xCloud button ASAP

This commit is contained in:
redphx
2025-02-06 21:23:56 +07:00
parent 63e5e90443
commit 2fd482bb7b
11 changed files with 121 additions and 131 deletions

View File

@@ -33,7 +33,6 @@ import { GameTile } from "./modules/ui/game-tile";
import { ProductDetailsPage } from "./modules/ui/product-details";
import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog";
import { GlobalPref, StreamPref } from "./enums/pref-keys";
import { SettingsDialog } from "./modules/ui/dialog/settings-dialog";
import { StreamUiHandler } from "./modules/stream/stream-ui";
import { UserAgent } from "./utils/user-agent";
import { XboxApi } from "./utils/xbox-api";
@@ -174,7 +173,7 @@ document.addEventListener('readystatechange', e => {
}
} else {
// Show Settings button in the header when not signed in
window.setTimeout(HeaderSection.watchHeader, 2000);
// window.setTimeout(HeaderSection.watchHeader, 2000);
}
// Hide "Play with Friends" skeleton section
@@ -198,20 +197,8 @@ window.addEventListener('popstate', onHistoryChanged);
window.history.pushState = patchHistoryMethod('pushState');
window.history.replaceState = patchHistoryMethod('replaceState');
BxEventBus.Script.once('xcloud.server.unavailable', () => {
STATES.supportedRegion = false;
window.setTimeout(HeaderSection.watchHeader, 2000);
// Open Settings dialog on Unsupported page
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
if ($unsupportedPage) {
SettingsDialog.getInstance().show();
}
});
BxEventBus.Script.on('xcloud.server.ready', () => {
STATES.isSignedIn = true;
window.setTimeout(HeaderSection.watchHeader, 2000);
BxEventBus.Script.on('header.rendered', () => {
HeaderSection.getInstance().checkHeader();
});
BxEventBus.Stream.on('state.loading', () => {

View File

@@ -1029,9 +1029,14 @@ ${subsVar} = subs;
return false;
}
const newCode = 'window.BX_EXPOSED.reactCreateElement=';
str = PatcherUtils.insertAt(str, index - 1, newCode);
str = PatcherUtils.insertAt(str, index - 1, 'window.BX_EXPOSED.reactCreateElement=');
index = PatcherUtils.indexOf(str, '.useEffect=', index);
if (index < 0) {
return false;
}
str = PatcherUtils.insertAt(str, index - 1, 'window.BX_EXPOSED.reactUseEffect=');
return str;
},
@@ -1130,7 +1135,24 @@ ${subsVar} = subs;
str = PatcherUtils.insertAt(str, index, `&q=${getGlobalPref(GlobalPref.UI_IMAGE_QUALITY)}`);
return str;
}
},
injectHeaderUseEffect(str: string) {
let index = str.indexOf('"EdgewaterHeader-module__spaceBetween');
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 300));
if (index < 0) {
return false;
}
const newCode = `
window.BX_EXPOSED.reactUseEffect(() => {
window.BxEventBus.Script.emit('header.rendered', {});
});
`;
str = PatcherUtils.insertAt(str, index, newCode);
return str;
},
};
let PATCH_ORDERS = PatcherUtils.filterPatches([
@@ -1140,6 +1162,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
] : []),
'exposeReactCreateComponent',
'injectHeaderUseEffect',
'gameCardCustomIcons',
// 'gameCardPassTitle',

View File

@@ -1,7 +1,7 @@
import { isFullVersion } from "@macros/build" with { type: "macro" };
import { SCRIPT_VERSION } from "@utils/global";
import { createButton, ButtonStyle, CE, isElementVisible } from "@utils/html";
import { SCRIPT_VERSION, STATES } from "@utils/global";
import { createButton, ButtonStyle, CE } from "@utils/html";
import { BxIcon } from "@utils/bx-icon";
import { getPreferredServerRegion } from "@utils/region";
import { RemotePlayManager } from "@/modules/remote-play-manager";
@@ -10,6 +10,7 @@ import { SettingsDialog } from "./dialog/settings-dialog";
import { GlobalPref } from "@/enums/pref-keys";
import { getGlobalPref } from "@/utils/pref-utils";
import { BxLogger } from "@/utils/bx-logger";
import { BxEventBus } from "@/utils/bx-event-bus";
export class HeaderSection {
private static instance: HeaderSection;
@@ -20,9 +21,6 @@ export class HeaderSection {
private $btnSettings: HTMLElement;
private $buttonsWrapper: HTMLElement;
private observer?: MutationObserver;
private timeoutId?: number | null;
constructor() {
BxLogger.info(this.LOG_TAG, 'constructor()');
@@ -38,9 +36,9 @@ export class HeaderSection {
this.$btnRemotePlay = null;
}
this.$btnSettings = createButton({
classes: ['bx-header-settings-button'],
label: '???',
let $btnSettings = this.$btnSettings = createButton({
classes: ['bx-header-settings-button', 'bx-gone'],
label: t('better-xcloud'),
style: ButtonStyle.FROSTED | ButtonStyle.DROP_SHADOW | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
onClick: e => SettingsDialog.getInstance().show(),
});
@@ -49,65 +47,47 @@ export class HeaderSection {
getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? this.$btnRemotePlay : null,
this.$btnSettings,
);
BxEventBus.Script.on('xcloud.server', ({status}) => {
if (status === 'ready') {
STATES.isSignedIn = true;
// Show server name
$btnSettings.querySelector('span')!.textContent = getPreferredServerRegion(true) || t('better-xcloud');
const PREF_LATEST_VERSION = getGlobalPref(GlobalPref.VERSION_LATEST);
// Show new update status
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
$btnSettings.setAttribute('data-update-available', 'true');
}
} else if (status === 'unavailable') {
STATES.supportedRegion = false;
// Open Settings dialog on Unsupported page
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
if ($unsupportedPage) {
SettingsDialog.getInstance().show();
}
}
$btnSettings.classList.remove('bx-gone');
});
}
private injectSettingsButton($parent?: HTMLElement) {
if (!$parent) {
return;
}
const PREF_LATEST_VERSION = getGlobalPref(GlobalPref.VERSION_LATEST);
// Setup Settings button
const $btnSettings = this.$btnSettings;
if (isElementVisible(this.$buttonsWrapper)) {
return;
}
$btnSettings.querySelector('span')!.textContent = getPreferredServerRegion(true) || t('better-xcloud');
// Show new update status
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
$btnSettings.setAttribute('data-update-available', 'true');
}
// Add the Settings button to the web page
$parent.appendChild(this.$buttonsWrapper);
}
private checkHeader = () => {
checkHeader = () => {
let $target = document.querySelector('#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]');
if (!$target) {
$target = document.querySelector('div[class^=UnsupportedMarketPage-module__buttons]');
}
$target && this.injectSettingsButton($target as HTMLElement);
}
// Add the Settings button to the web page
$target?.appendChild(this.$buttonsWrapper);
private watchHeader() {
const $root = document.querySelector('#PageContent header') || document.querySelector('#root');
if (!$root) {
return;
if (!STATES.isSignedIn) {
BxEventBus.Script.emit('xcloud.server', { status: 'signed-out' });
}
this.timeoutId && clearTimeout(this.timeoutId);
this.timeoutId = null;
this.observer && this.observer.disconnect();
this.observer = new MutationObserver(mutationList => {
this.timeoutId && clearTimeout(this.timeoutId);
this.timeoutId = window.setTimeout(this.checkHeader, 2000);
});
this.observer.observe($root, { subtree: true, childList: true });
this.checkHeader();
}
showRemotePlayButton() {
this.$btnRemotePlay?.classList.remove('bx-gone');
}
static watchHeader() {
HeaderSection.getInstance().watchHeader();
}
}

View File

@@ -8,8 +8,9 @@ import type { SpeakerState } from "@/modules/shortcuts/sound-shortcut";
type EventCallback<T = any> = (payload: T) => void;
type ScriptEvents = {
'xcloud.server.ready': {};
'xcloud.server.unavailable': {};
'xcloud.server': {
status: 'ready' | 'unavailable' | 'signed-out',
};
'dialog.shown': {};
'dialog.dismissed': {};
@@ -34,6 +35,8 @@ type ScriptEvents = {
};
'webgpu.ready': {},
'header.rendered': {},
};
type StreamEvents = {

View File

@@ -14,6 +14,7 @@ export namespace BxEvent {
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
// Inside app
// TODO: Use EventBus
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';

View File

@@ -242,6 +242,7 @@ export const BxExposed = {
localCoOpManager: isFullVersion() ? LocalCoOpManager.getInstance() : null,
reactCreateElement: function(...args: any[]) {},
reactUseEffect: function(...args: any[]) {},
createReactLocalCoOpIcon: isFullVersion() ? (attrs: any): any => {
const reactCE = window.BX_EXPOSED.reactCreateElement;

View File

@@ -3,7 +3,6 @@ import { isFullVersion } from "@macros/build" with { type: "macro" };
import { BxEvent } from "@utils/bx-event";
import { LoadingScreen } from "@modules/loading-screen";
import { RemotePlayManager } from "@/modules/remote-play-manager";
import { HeaderSection } from "@/modules/ui/header";
import { BxEventBus } from "./bx-event-bus";
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
@@ -35,7 +34,5 @@ export function onHistoryChanged(e: PopStateEvent) {
NavigationDialogManager.getInstance().hide();
LoadingScreen.reset();
window.setTimeout(HeaderSection.watchHeader, 2000);
BxEventBus.Stream.emit('state.stopped', {});
}

View File

@@ -14,7 +14,6 @@ import { ControllerCustomizationDefaultPresetId } from "../local-db/controller-c
import { ControllerShortcutDefaultId } from "../local-db/controller-shortcuts-table";
import { BxEventBus } from "../bx-event-bus";
import { WebGPUPlayer } from "@/modules/player/webgpu/webgpu-player";
import { BX_FLAGS } from "../bx-flags";
export class StreamSettingsStorage extends BaseSettingsStorage<StreamPref> {

View File

@@ -52,7 +52,7 @@ export class XcloudInterceptor {
const response = await NATIVE_FETCH(request, init);
if (response.status !== 200) {
// Unsupported region
BxEventBus.Script.emit('xcloud.server.unavailable', {});
BxEventBus.Script.emit('xcloud.server', { status: 'unavailable' });
return response;
}
@@ -92,8 +92,6 @@ export class XcloudInterceptor {
STATES.serverRegions[region.name] = Object.assign({}, region);
}
BxEventBus.Script.emit('xcloud.server.ready', {});
const preferredRegion = getPreferredServerRegion();
if (preferredRegion && preferredRegion in STATES.serverRegions) {
const tmp = Object.assign({}, STATES.serverRegions[preferredRegion]);
@@ -104,6 +102,7 @@ export class XcloudInterceptor {
}
STATES.gsToken = obj.gsToken;
BxEventBus.Script.emit('xcloud.server', { status: 'ready' });
response.json = () => Promise.resolve(obj);
return response;