summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorAAGaming <aa@mail.catvibers.me>2022-10-24 19:14:56 -0400
committerGitHub <noreply@github.com>2022-10-24 16:14:56 -0700
commit84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10 (patch)
tree20b13066c6256cc6ca1beac085094c7964226a37 /backend
parent2e6b3834da357c7e81821ce60bad36f54dd9fa6e (diff)
downloaddecky-loader-84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10.tar.gz
decky-loader-84c3b039c385ad872bb0f22eba7a3d2cd4a5ea10.zip
preview 10/21/2022 fixes (#234)
* initial fixes: everything working except toasts and patch notes * tabshook changes, disable toaster for now * prettier * oops * implement custom toaster because I am tired of Valve's shit also fix QAM not injecting sometimes * remove extra logging * add findSP, fix patch notes, fix vscode screwup * fix patch notes * show error when plugin frontends fail to load * add get_tab_lambda * add css and has_element helpers to Tab * small modals fixup * Don't forceUpdate QuickAccess on stable * add routes prop used to get tabs component * add more dev utils to DFL global
Diffstat (limited to 'backend')
-rw-r--r--backend/browser.py6
-rw-r--r--backend/injector.py142
-rw-r--r--backend/loader.py5
-rw-r--r--backend/main.py54
-rw-r--r--backend/updater.py6
-rw-r--r--backend/utilities.py6
6 files changed, 169 insertions, 50 deletions
diff --git a/backend/browser.py b/backend/browser.py
index 3007ae18..69f82bdb 100644
--- a/backend/browser.py
+++ b/backend/browser.py
@@ -16,7 +16,7 @@ from zipfile import ZipFile
# Local modules
from helpers import get_ssl_context, get_user, get_user_group, download_remote_binary_to_path
-from injector import get_tab, inject_to_tab
+from injector import get_gamepadui_tab
logger = getLogger("Browser")
@@ -104,7 +104,7 @@ class PluginBrowser:
async def uninstall_plugin(self, name):
if self.loader.watcher:
self.loader.watcher.disabled = True
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
try:
logger.info("uninstalling " + name)
logger.info(" at dir " + self.find_plugin_folder(name))
@@ -172,7 +172,7 @@ class PluginBrowser:
async def request_plugin_install(self, artifact, name, version, hash):
request_id = str(time())
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
await tab.open_websocket()
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}')")
diff --git a/backend/injector.py b/backend/injector.py
index de914d17..c0d9cbad 100644
--- a/backend/injector.py
+++ b/backend/injector.py
@@ -3,6 +3,7 @@
from asyncio import sleep
from logging import getLogger
from traceback import format_exc
+from typing import List
from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
@@ -18,6 +19,7 @@ class Tab:
def __init__(self, res) -> None:
self.title = res["title"]
self.id = res["id"]
+ self.url = res["url"]
self.ws_url = res["webSocketDebuggerUrl"]
self.websocket = None
@@ -64,6 +66,26 @@ class Tab:
await self.close_websocket()
return res
+ async def has_global_var(self, var_name, manage_socket=True):
+ res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket)
+
+ if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
+ return False
+
+ return res["result"]["result"]["value"]
+
+ async def close(self, manage_socket=True):
+ if manage_socket:
+ await self.open_websocket()
+
+ res = await self._send_devtools_cmd({
+ "method": "Page.close",
+ }, False)
+
+ if manage_socket:
+ await self.close_websocket()
+ return res
+
async def enable(self):
"""
Enables page domain notifications.
@@ -80,6 +102,18 @@ class Tab:
"method": "Page.disable",
}, False)
+ async def refresh(self):
+ if manage_socket:
+ await self.open_websocket()
+
+ await self._send_devtools_cmd({
+ "method": "Page.reload",
+ }, False)
+
+ if manage_socket:
+ await self.close_websocket()
+
+ return
async def reload_and_evaluate(self, js, manage_socket=True):
"""
Reloads the current tab, with JS to run on load via debugger
@@ -227,6 +261,71 @@ class Tab:
if manage_socket:
await self.close_websocket()
+ async def has_element(self, element_name, manage_socket=True):
+ res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket)
+
+ if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
+ return False
+
+ return res["result"]["result"]["value"]
+
+ async def inject_css(self, style, manage_socket=True):
+ try:
+ css_id = str(uuid.uuid4())
+
+ result = await self.evaluate_js(
+ f"""
+ (function() {{
+ const style = document.createElement('style');
+ style.id = "{css_id}";
+ document.head.append(style);
+ style.textContent = `{style}`;
+ }})()
+ """, False, manage_socket)
+
+ if "exceptionDetails" in result["result"]:
+ return {
+ "success": False,
+ "result": result["result"]
+ }
+
+ return {
+ "success": True,
+ "result": css_id
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "result": e
+ }
+
+ async def remove_css(self, css_id, manage_socket=True):
+ try:
+ result = await self.evaluate_js(
+ f"""
+ (function() {{
+ let style = document.getElementById("{css_id}");
+
+ if (style.nodeName.toLowerCase() == 'style')
+ style.parentNode.removeChild(style);
+ }})()
+ """, False, manage_socket)
+
+ if "exceptionDetails" in result["result"]:
+ return {
+ "success": False,
+ "result": result
+ }
+
+ return {
+ "success": True
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "result": e
+ }
+
async def get_steam_resource(self, url):
res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True)
return res["result"]["result"]["value"]
@@ -235,7 +334,7 @@ class Tab:
return self.title
-async def get_tabs():
+async def get_tabs() -> List[Tab]:
async with ClientSession() as web:
res = {}
@@ -257,41 +356,28 @@ async def get_tabs():
raise Exception(f"/json did not return 200. {await res.text()}")
-async def get_tab(tab_name):
+async def get_tab(tab_name) -> Tab:
tabs = await get_tabs()
tab = next((i for i in tabs if i.title == tab_name), None)
if not tab:
raise ValueError(f"Tab {tab_name} not found")
return tab
+async def get_tab_lambda(test) -> Tab:
+ tabs = await get_tabs()
+ tab = next((i for i in tabs if test(i)), None)
+ if not tab:
+ raise ValueError(f"Tab not found by lambda")
+ return tab
+
+async def get_gamepadui_tab() -> Tab:
+ tabs = await get_tabs()
+ tab = next((i for i in tabs if ("https://steamloopback.host/routes/" in i.url and (i.title == "Steam" or i.title == "SP"))), None)
+ if not tab:
+ raise ValueError(f"GamepadUI Tab not found")
+ return tab
async def inject_to_tab(tab_name, js, run_async=False):
tab = await get_tab(tab_name)
return await tab.evaluate_js(js, run_async)
-
-
-async def tab_has_global_var(tab_name, var_name):
- try:
- tab = await get_tab(tab_name)
- except ValueError:
- return False
- res = await tab.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False)
-
- if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
- return False
-
- return res["result"]["result"]["value"]
-
-
-async def tab_has_element(tab_name, element_name):
- try:
- tab = await get_tab(tab_name)
- except ValueError:
- return False
- res = await tab.evaluate_js(f"document.getElementById('{element_name}') != null", False)
-
- if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
- return False
-
- return res["result"]["result"]["value"]
diff --git a/backend/loader.py b/backend/loader.py
index b4559180..8c48c7ae 100644
--- a/backend/loader.py
+++ b/backend/loader.py
@@ -15,7 +15,7 @@ try:
except UnsupportedLibc:
from watchdog.observers.fsevents import FSEventsObserver as Observer
-from injector import get_tab, inject_to_tab
+from injector import get_tab, get_gamepadui_tab
from plugin import PluginWrapper
@@ -141,7 +141,8 @@ class Loader:
print_exc()
async def dispatch_plugin(self, name, version):
- await inject_to_tab("SP", f"window.importDeckyPlugin('{name}', '{version}')")
+ gpui_tab = await get_gamepadui_tab()
+ await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')")
def import_plugins(self):
self.logger.info(f"import plugins from {self.plugin_path}")
diff --git a/backend/main.py b/backend/main.py
index c3f43078..563755ca 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -12,7 +12,7 @@ from traceback import format_exc
import aiohttp_cors
# Partial imports
-from aiohttp import ClientSession
+from aiohttp import ClientSession, client_exceptions
from aiohttp.web import Application, Response, get, run_app, static
from aiohttp_jinja2 import setup as jinja_setup
@@ -22,7 +22,7 @@ from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
get_home_path, get_homebrew_path, get_user,
get_user_group, set_user, set_user_group,
stop_systemd_unit)
-from injector import inject_to_tab, tab_has_global_var
+from injector import get_gamepadui_tab, Tab, get_tabs
from loader import Loader
from settings import SettingsManager
from updater import Updater
@@ -118,17 +118,49 @@ class PluginManager:
# await inject_to_tab("SP", "window.syncDeckyPlugins();")
async def loader_reinjector(self):
- await sleep(2)
- await self.inject_javascript()
while True:
- await sleep(5)
- if not await tab_has_global_var("SP", "deckyHasLoaded"):
- logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
- await self.inject_javascript()
-
- async def inject_javascript(self, request=None):
+ tab = None
+ while not tab:
+ try:
+ tab = await get_gamepadui_tab()
+ except client_exceptions.ClientConnectorError or client_exceptions.ServerDisconnectedError:
+ logger.debug("Couldn't connect to debugger, waiting 5 seconds.")
+ pass
+ except ValueError:
+ logger.debug("Couldn't find GamepadUI tab, waiting 5 seconds")
+ pass
+ if not tab:
+ await sleep(5)
+ await tab.open_websocket()
+ await tab.enable()
+ await self.inject_javascript(tab, True)
+ async for msg in tab.listen_for_message():
+ logger.debug("Page event: " + str(msg.get("method", None)))
+ if msg.get("method", None) == "Page.domContentEventFired":
+ if not await tab.has_global_var("deckyHasLoaded", False):
+ await self.inject_javascript(tab)
+ if msg.get("method", None) == "Inspector.detached":
+ logger.info("Steam is exiting...")
+ await tab.close_websocket()
+ break
+ # while True:
+ # await sleep(5)
+ # if not await tab.has_global_var("deckyHasLoaded", False):
+ # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
+ # await self.inject_javascript(tab)
+
+ async def inject_javascript(self, tab: Tab, first=False, request=None):
+ logger.info("Loading Decky frontend!")
try:
- await inject_to_tab("SP", "try{if (window.deckyHasLoaded) location.reload();window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", True)
+ if first:
+ if await tab.has_global_var("deckyHasLoaded", False):
+ tabs = await get_tabs()
+ for t in tabs:
+ if t.title != "Steam" and t.title != "SP":
+ logger.debug("Closing tab: " + getattr(t, "title", "Untitled"))
+ await t.close()
+ await sleep(0.5)
+ await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => location.reload(), 1000)}window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", False, False, False)
except:
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
pass
diff --git a/backend/updater.py b/backend/updater.py
index ed1520ab..a209f103 100644
--- a/backend/updater.py
+++ b/backend/updater.py
@@ -9,7 +9,7 @@ from subprocess import call
from aiohttp import ClientSession, web
import helpers
-from injector import get_tab, inject_to_tab
+from injector import get_gamepadui_tab, inject_to_tab
from settings import SettingsManager
logger = getLogger("Updater")
@@ -108,7 +108,7 @@ class Updater:
logger.error("release type: NOT FOUND")
raise ValueError("no valid branch found")
logger.info("Updated remote version information")
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False)
return await self.get_version()
@@ -125,7 +125,7 @@ class Updater:
version = self.remoteVer["tag_name"]
download_url = self.remoteVer["assets"][0]["browser_download_url"]
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
await tab.open_websocket()
async with ClientSession() as web:
async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res:
diff --git a/backend/utilities.py b/backend/utilities.py
index 8d899c0d..8351f3e0 100644
--- a/backend/utilities.py
+++ b/backend/utilities.py
@@ -7,7 +7,7 @@ from asyncio import sleep, start_server, gather, open_connection
from aiohttp import ClientSession, web
from logging import getLogger
-from injector import inject_to_tab, get_tab
+from injector import inject_to_tab, get_gamepadui_tab
import helpers
import subprocess
@@ -248,7 +248,7 @@ class Utilities:
self.start_rdt_proxy(ip, 8097)
script = "if(!window.deckyHasConnectedRDT){window.deckyHasConnectedRDT=true;\n" + await res.text() + "\n}"
self.logger.info("Connected to React DevTools, loading script")
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
# RDT needs to load before React itself to work.
result = await tab.reload_and_evaluate(script)
self.logger.info(result)
@@ -259,7 +259,7 @@ class Utilities:
async def disable_rdt(self):
self.logger.info("Disabling React DevTools")
- tab = await get_tab("SP")
+ tab = await get_gamepadui_tab()
self.rdt_script_id = None
await tab.evaluate_js("location.reload();", False, True, False)
self.logger.info("React DevTools disabled")