better-xcloud/src/modules/remote-play-manager.ts
2025-02-14 09:04:33 +07:00

243 lines
6.8 KiB
TypeScript
Executable File

import { STATES } from "@utils/global";
import { Toast } from "@utils/toast";
import { BxEvent } from "@utils/bx-event";
import { t } from "@utils/translation";
import { localRedirect } from "@modules/ui/ui";
import { BxLogger } from "@utils/bx-logger";
import { HeaderSection } from "./ui/header";
import { GlobalPref } from "@/enums/pref-keys";
import { getGlobalPref, setGlobalPref } from "@/utils/pref-utils";
import { RemotePlayDialog } from "./ui/dialog/remote-play-dialog";
export const enum RemotePlayConsoleState {
ON = 'On',
OFF = 'Off',
STANDBY = 'ConnectedStandby',
UNKNOWN = 'Unknown',
}
type RemotePlayRegion = {
name: string;
baseUri: string;
isDefault: boolean;
};
type RemotePlayConsole = {
deviceName: string;
serverId: string;
powerState: RemotePlayConsoleState;
consoleType: string;
// playPath: string;
// outOfHomeWarning: string;
// wirelessWarning: string;
// isDevKit: string;
};
export class RemotePlayManager {
private static instance: RemotePlayManager | null | undefined;
public static getInstance(): typeof RemotePlayManager['instance'] {
if (typeof RemotePlayManager.instance === 'undefined') {
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
RemotePlayManager.instance = new RemotePlayManager();
} else {
RemotePlayManager.instance = null;
}
}
return RemotePlayManager.instance;
}
private readonly LOG_TAG = 'RemotePlayManager';
private isInitialized = false;
private XCLOUD_TOKEN!: string;
private XHOME_TOKEN!: string;
private consoles!: Array<RemotePlayConsole>;
private regions: Array<RemotePlayRegion> = [];
private constructor() {
BxLogger.info(this.LOG_TAG, 'constructor()');
}
initialize() {
if (this.isInitialized) {
return;
}
this.isInitialized = true;
this.requestXhomeToken(() => {
this.getConsolesList(() => {
BxLogger.info(this.LOG_TAG, 'Consoles', this.consoles);
STATES.supportedRegion && HeaderSection.getInstance().showRemotePlayButton();
BxEvent.dispatch(window, BxEvent.REMOTE_PLAY_READY);
});
});
}
getXcloudToken() {
return this.XCLOUD_TOKEN;
}
setXcloudToken(token: string) {
this.XCLOUD_TOKEN = token;
}
getXhomeToken() {
return this.XHOME_TOKEN;
}
getConsoles() {
return this.consoles;
}
private requestXhomeToken(callback: any) {
if (this.XHOME_TOKEN) {
callback();
return;
}
let GSSV_TOKEN;
try {
GSSV_TOKEN = JSON.parse(localStorage.getItem('xboxcom_xbl_user_info')!).tokens['http://gssv.xboxlive.com/'].token;
} catch (e) {
for (let i = 0; i < localStorage.length; i++){
const key = localStorage.key(i)!;
if (!key.startsWith('Auth.User.')) {
continue;
}
const json = JSON.parse(localStorage.getItem(key)!);
for (const token of json.tokens) {
if (!token.relyingParty.includes('gssv.xboxlive.com')) {
continue;
}
GSSV_TOKEN = token.tokenData.token;
break;
}
break;
}
}
const request = new Request('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', {
method: 'POST',
body: JSON.stringify({
offeringId: 'xhome',
token: GSSV_TOKEN,
}),
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});
fetch(request).then(resp => resp.json())
.then(json => {
this.regions = json.offeringSettings.regions;
this.XHOME_TOKEN = json.gsToken;
callback();
});
}
private async getConsolesList(callback: any) {
if (this.consoles) {
callback();
return;
}
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${this.XHOME_TOKEN}`,
},
};
// Test servers one by one
for (const region of this.regions) {
try {
const request = new Request(`${region.baseUri}/v6/servers/home?mr=50`, options);
const resp = await fetch(request);
const json = await resp.json();
if (json.results.length === 0) {
continue;
}
this.consoles = json.results;
// Store working server
STATES.remotePlay.server = region.baseUri;
break;
} catch (e) {}
}
// None of the servers worked
if (!STATES.remotePlay.server) {
this.consoles = [];
}
callback();
}
play(serverId: string, resolution?: string) {
if (resolution) {
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, resolution, 'ui');
}
STATES.remotePlay.config = {
serverId: serverId,
};
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play');
setTimeout(() => localRedirect('/consoles/launch/' + serverId), 100);
}
togglePopup(force = null) {
if (!this.isReady()) {
Toast.show(t('getting-consoles-list'));
return;
}
if (this.consoles.length === 0) {
Toast.show(t('no-consoles-found'), '', { instant: true });
return;
}
/*
// Show native dialog in Android app
if (AppInterface && AppInterface.showRemotePlayDialog) {
AppInterface.showRemotePlayDialog(JSON.stringify(this.consoles));
(document.activeElement as HTMLElement).blur();
return;
}
*/
RemotePlayDialog.getInstance().show();
}
static detect() {
if (!getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
return;
}
STATES.remotePlay.isPlaying = window.location.pathname.includes('/launch/') && window.location.hash.startsWith('#remote-play');
if (STATES.remotePlay?.isPlaying) {
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
// Remove /launch/... from URL
window.history.replaceState({origin: 'better-xcloud'}, '', 'https://www.xbox.com/' + location.pathname.substring(1, 6) + '/play');
} else {
window.BX_REMOTE_PLAY_CONFIG = null;
}
}
isReady() {
return this.consoles !== null;
}
}