diff --git a/src/main/main.ts b/src/main/main.ts index 2271cd00..49b7e847 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -20,6 +20,8 @@ import { APP_NAME } from "./constants"; import { BSLauncherService } from "./services/bs-launcher.service"; import { IpcRequest } from "shared/models/ipc"; import { LivShortcut } from "./services/liv/liv-shortcut.service"; +import { BsOculusDownloaderService } from "./services/bs-oculus-downloader.service"; +import { BSVersion } from "shared/bs-version.interface"; const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true"; @@ -58,6 +60,8 @@ const createWindow = async (window: AppWindow = "launcher.html") => { await installExtensions(); } WindowManagerService.getInstance().openWindow(window); + + BsOculusDownloaderService.getInstance().downloadVersion({} as BSVersion).then(console.log).catch(console.error); }; const initServicesMustBeInitialized = () => { diff --git a/src/main/services/bs-oculus-downloader.service.ts b/src/main/services/bs-oculus-downloader.service.ts new file mode 100644 index 00000000..d44efc2e --- /dev/null +++ b/src/main/services/bs-oculus-downloader.service.ts @@ -0,0 +1,97 @@ +import { BSVersion } from "../../shared/bs-version.interface"; +import { WindowManagerService } from "./window-manager.service"; +import { minToMs } from "../../shared/helpers/time.helpers"; +import log from "electron-log"; + +export class BsOculusDownloaderService { + + private static instance: BsOculusDownloaderService; + + public static getInstance(): BsOculusDownloaderService { + if (!BsOculusDownloaderService.instance) { + BsOculusDownloaderService.instance = new BsOculusDownloaderService(); + } + + return BsOculusDownloaderService.instance; + } + + private readonly windows: WindowManagerService; + + private constructor() { + this.windows = WindowManagerService.getInstance(); + } + + private isUserTokenValid(token: string): boolean{ + + // Code take from "https://github.com/ComputerElite/QuestAppVersionSwitcher" (TokenTools.cs) + + log.info("Checking if Oculus user token is valid"); + + if(!token){ + log.info("Oculus user token is empty"); + return false; + } + if(token.includes("%")){ + log.info("Token contains %. Token most likely comes from an uri and won't work"); + return false; + } + if(!token.startsWith("OC")){ + log.info("Token does not start with OC."); + return false; + } + if(token.includes("|")){ + log.info("Token contains | which usually indicates an application token which is not valid for user tokens"); + return false; + } + if(token.match(/OC[0-9]{15}/)){ + log.info("Token matches /OC[0-9}{15}/ which usually indicates a changed oculus store token"); + return false; + } + + return true; + + } + + public async getUserToken(): Promise{ + const redirectUrl = "https://developer.oculus.com/manage/"; + const loginUrl = `https://auth.oculus.com/login/?redirect_uri=${encodeURIComponent(redirectUrl)}`; + const window = await this.windows.openWindow(loginUrl, { frame: true }); + + let timout: NodeJS.Timeout; + + const promise = new Promise((resolve, reject) => { + timout = setTimeout(() => { + reject(new Error("Trying to get Oculus user token timed out")); + window.close(); + }, minToMs(5)); + + window.webContents.on("did-navigate", async (_, url) => { + if(!url.startsWith(redirectUrl)){ return; } + + const token = (await window.webContents.session.cookies.get({ name: "oc_www_at" })).at(0)?.value; + + if(!this.isUserTokenValid(token)){ + return; + } + + resolve(token); + }); + + window.on("closed", () => { + reject(new Error("Oculus login window closed by user")); + }); + }).finally(() => { + if(!window.isDestroyed() && window.isClosable()){ + window.close(); + } + clearTimeout(timout); + }); + + return promise; + } + + public downloadVersion(version: BSVersion){ + return this.getUserToken(); + } + +} \ No newline at end of file diff --git a/src/main/services/window-manager.service.ts b/src/main/services/window-manager.service.ts index 5046f325..fd10ea8f 100644 --- a/src/main/services/window-manager.service.ts +++ b/src/main/services/window-manager.service.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, BrowserWindowConstructorOptions, shell } from "electron"; +import { app, BrowserWindow, BrowserWindowConstructorOptions, shell, WebContents } from "electron"; import { resolveHtmlPath } from "../util"; import { UtilsService } from "./utils.service"; import { AppWindow } from "shared/models/window-manager/app-window.model"; @@ -10,6 +10,7 @@ export class WindowManagerService { private static instance: WindowManagerService; private readonly PRELOAD_PATH = app.isPackaged ? path.join(__dirname, "preload.js") : path.join(__dirname, "../../../.erb/dll/preload.js"); + private readonly IS_DEBUG = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true" private readonly utilsService: UtilsService = UtilsService.getInstance(); @@ -28,10 +29,10 @@ export class WindowManagerService { show: false, frame: false, titleBarOverlay: false, - webPreferences: { preload: this.PRELOAD_PATH, webSecurity: false }, + webPreferences: { preload: this.PRELOAD_PATH, webSecurity: !this.IS_DEBUG }, }; - private readonly windows: Map = new Map(); + private readonly windows = new Map(); public static getInstance(): WindowManagerService { if (!WindowManagerService.instance) { @@ -42,8 +43,7 @@ export class WindowManagerService { private constructor() {} - public openWindow(windowType: AppWindow, options?: BrowserWindowConstructorOptions): Promise { - const window = new BrowserWindow({ ...this.appWindowsOptions[windowType], ...this.baseWindowOption, ...options }); + private handleNewWindow(url: AppWindow, window: BrowserWindow){ window.webContents.setWindowOpenHandler(({ url }) => { if(isValidUrl(url)){ @@ -51,12 +51,13 @@ export class WindowManagerService { } return { action: "deny"} - }) + }); - const promise = window.loadURL(resolveHtmlPath(windowType)); window.removeMenu(); window.setMenu(null); + const promise = window.loadURL(isValidUrl(url) ? url : resolveHtmlPath(url)); + window.once("ready-to-show", () => { if (!window) { throw new Error('"window" is not defined'); @@ -65,18 +66,24 @@ export class WindowManagerService { }); window.once("closed", () => { - this.windows.delete(windowType); + this.windows.delete(url); if (!this.windows.size) { app.quit(); } }); - this.windows.set(windowType, window); - this.utilsService.setMainWindows(this.windows); + this.windows.set(url, window); + this.utilsService.setMainWindows(this.windows as Map); // TODO : remove + return promise.then(() => window); } + public openWindow(windowType: AppWindow, options?: BrowserWindowConstructorOptions): Promise { + const window = new BrowserWindow({ ...(this.appWindowsOptions[windowType] ?? {}), ...this.baseWindowOption, ...options }); + return this.handleNewWindow(windowType, window); + } + public closeAllWindows(except?: AppWindow) { this.windows.forEach((window, key) => { if (key === except) { @@ -96,7 +103,7 @@ export class WindowManagerService { return this.windows.get(window); } - public getAppWindowFromWebContents(sender: Electron.WebContents): AppWindow { + public getAppWindowFromWebContents(sender: WebContents): AppWindow { return Array.from(this.windows.entries()).find(([, value]) => value.webContents.id === sender.id)[0]; } diff --git a/src/renderer/components/maps-mangement-components/filter-panel.component.tsx b/src/renderer/components/maps-mangement-components/filter-panel.component.tsx index e2973aa4..15845e1c 100644 --- a/src/renderer/components/maps-mangement-components/filter-panel.component.tsx +++ b/src/renderer/components/maps-mangement-components/filter-panel.component.tsx @@ -4,7 +4,7 @@ import { MutableRefObject, useEffect, useRef, useState } from "react"; import { MAP_TYPES } from "renderer/partials/maps/map-tags/map-types"; import { MAP_STYLES } from "renderer/partials/maps/map-tags/map-styles"; import { BsmCheckbox } from "../shared/bsm-checkbox.component"; -import { minToS } from "renderer/helpers/time-utils"; +import { minToS } from "shared/helpers/time.helpers"; import dateFormat from "dateformat"; import { BsmRange } from "../shared/bsm-range.component"; import { useTranslation } from "renderer/hooks/use-translation.hook"; diff --git a/src/renderer/helpers/time-utils.ts b/src/shared/helpers/time.helpers.ts similarity index 79% rename from src/renderer/helpers/time-utils.ts rename to src/shared/helpers/time.helpers.ts index 4d05b6e5..e0ca656b 100644 --- a/src/renderer/helpers/time-utils.ts +++ b/src/shared/helpers/time.helpers.ts @@ -5,6 +5,10 @@ export function minToS(minutes: number): number { return minutes * SECONDS_IN_MINUTE; } +export function minToMs(minutes: number): number { + return minToS(minutes) * 1000; +} + export function hourToMin(hours: number): number { return hours * MINUTES_IN_HOUR; } diff --git a/src/shared/models/window-manager/app-window.model.ts b/src/shared/models/window-manager/app-window.model.ts index 9e6cacda..94b06827 100644 --- a/src/shared/models/window-manager/app-window.model.ts +++ b/src/shared/models/window-manager/app-window.model.ts @@ -1 +1 @@ -export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html" | "shortcut-launch.html"; +export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html" | "shortcut-launch.html" | string;