diff options
| author | marios <marios8543@gmail.com> | 2022-05-26 04:00:18 +0300 |
|---|---|---|
| committer | marios <marios8543@gmail.com> | 2022-05-26 04:00:18 +0300 |
| commit | 4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919 (patch) | |
| tree | 3394a7e752b61bdfa16b1a7f50842c4e1dbc0972 /backend | |
| parent | 74438a31458af8bddd08d90eacc6d63677bab844 (diff) | |
| download | decky-loader-4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919.tar.gz decky-loader-4b923c1dc70eaa4a3ca58d9e9f3218785b2fe919.zip | |
display overhaul, compatibility with legacy plugins, fixes
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/browser.py | 2 | ||||
| -rw-r--r-- | backend/injector.py | 16 | ||||
| -rw-r--r-- | backend/loader.py | 93 | ||||
| -rw-r--r-- | backend/main.py | 27 | ||||
| -rw-r--r-- | backend/plugin.py | 15 | ||||
| -rw-r--r-- | backend/utilities.py | 41 |
6 files changed, 144 insertions, 50 deletions
diff --git a/backend/browser.py b/backend/browser.py index ffec26b3..323fb9c3 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -26,7 +26,7 @@ class PluginBrowser: server_instance.add_routes([ web.post("/browser/install_plugin", self.install_plugin), - web.get("/browser/iframe", self.redirect_to_store) + web.get("/browser/redirect", self.redirect_to_store) ]) def _unzip_to_plugin_dir(self, zip, name, hash): diff --git a/backend/injector.py b/backend/injector.py index 16ced852..9b4fe353 100644 --- a/backend/injector.py +++ b/backend/injector.py @@ -48,6 +48,10 @@ class Tab: await self.client.close() return res + 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"] + def __repr__(self): return self.title @@ -93,3 +97,15 @@ async def tab_has_global_var(tab_name, var_name): 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"]
\ No newline at end of file diff --git a/backend/loader.py b/backend/loader.py index e68842e2..60b1a901 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -2,16 +2,15 @@ from asyncio import Queue from logging import getLogger from os import listdir, path from pathlib import Path -from time import time from traceback import print_exc from aiohttp import web -from aiohttp_jinja2 import template from genericpath import exists +from json.decoder import JSONDecodeError from watchdog.events import FileSystemEventHandler from watchdog.observers.polling import PollingObserver as Observer -from injector import inject_to_tab +from injector import inject_to_tab, get_tab from plugin import PluginWrapper @@ -63,7 +62,6 @@ class Loader: self.plugin_path = plugin_path self.logger.info(f"plugin_path: {self.plugin_path}") self.plugins = {} - self.import_plugins() if live_reload: self.reload_queue = Queue() @@ -73,17 +71,21 @@ class Loader: self.loop.create_task(self.handle_reloads()) server_instance.add_routes([ - web.get("/plugins", self.handle_plugins), + web.get("/plugins", self.get_plugins), web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call), - web.post("/methods/{method_name}", self.handle_server_method_call) + + # The following is legacy plugin code. + web.get("/plugins/load_main/{name}", self.load_plugin_main_view), + web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route), + web.get("/steam_resource/{path:.+}", self.get_steam_resource) ]) - def handle_plugins(self, request): - plugins = list(map(lambda kv: dict([("name", kv[0])]), self.plugins.items())) - return web.json_response(plugins) + async def get_plugins(self, request): + plugins = list(self.plugins.values()) + return web.json_response([str(i) if not i.legacy else "$LEGACY_"+str(i) for i in plugins]) - def handle_frontend_bundle(self, request): + async def handle_frontend_bundle(self, request): plugin = self.plugins[request.match_info["plugin_name"]] with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.frontend_bundle), 'r') as bundle: @@ -103,14 +105,13 @@ class Loader: self.logger.info(f"Plugin {plugin.name} is passive") self.plugins[plugin.name] = plugin.start() self.logger.info(f"Loaded {plugin.name}") - if refresh: - self.loop.create_task(self.reload_frontend_plugin(plugin.name)) + #self.loop.create_task(self.dispatch_plugin(plugin.name)) except Exception as e: self.logger.error(f"Could not load {file}. {e}") print_exc() - async def reload_frontend_plugin(self, name): - await inject_to_tab("SP", f"window.DeckyPluginLoader?.loadPlugin('{name}')") + async def dispatch_plugin(self, name): + await inject_to_tab("SP", f"window.importDeckyPlugin('{name}')") def import_plugins(self): self.logger.info(f"import plugins from {self.plugin_path}") @@ -125,38 +126,58 @@ class Loader: args = await self.reload_queue.get() self.import_plugin(*args) - async def handle_server_method_call(self, request): - method_name = request.match_info["method_name"] - method_info = await request.json() - args = method_info["args"] - - res = {} - try: - r = await self.utilities.util_methods[method_name](**args) - res["result"] = r - res["success"] = True - except Exception as e: - res["result"] = str(e) - res["success"] = False - - return web.json_response(res) - async def handle_plugin_method_call(self, request): res = {} plugin = self.plugins[request.match_info["plugin_name"]] method_name = request.match_info["method_name"] - - method_info = await request.json() - args = method_info["args"] - + try: + method_info = await request.json() + args = method_info["args"] + except JSONDecodeError: + args = {} try: if method_name.startswith("_"): raise RuntimeError("Tried to call private method") - res["result"] = await plugin.execute_method(method_name, args) res["success"] = True except Exception as e: res["result"] = str(e) res["success"] = False - return web.json_response(res) + + """ + The following methods are used to load legacy plugins, which are considered deprecated. + I made the choice to re-add them so that the first iteration/version of the react loader + can work as a drop-in replacement for the stable branch of the PluginLoader, so that we + can introduce it more smoothly and give people the chance to sample the new features even + without plugin support. They will be removed once legacy plugins are no longer relevant. + """ + async def load_plugin_main_view(self, request): + plugin = self.plugins[request.match_info["name"]] + with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), 'r') as template: + template_data = template.read() + ret = f""" + <script src="/static/legacy-library.js"></script> + <script>const plugin_name = '{plugin.name}' </script> + <base href="http://127.0.0.1:1337/plugins/plugin_resource/{plugin.name}/"> + {template_data} + """ + return web.Response(text=ret, content_type="text/html") + + async def handle_sub_route(self, request): + plugin = self.plugins[request.match_info["name"]] + route_path = request.match_info["path"] + self.logger.info(path) + ret = "" + file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path) + with open(file_path, 'r') as resource_data: + ret = resource_data.read() + + return web.Response(text=ret) + + async def get_steam_resource(self, request): + tab = await get_tab("QuickAccess") + try: + return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") + except Exception as e: + return web.Response(text=str(e), status=400)
\ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 7e10165e..0bf0a49d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,6 +1,8 @@ from logging import DEBUG, INFO, basicConfig, getLogger from os import getenv +from aiohttp import ClientSession + CONFIG = { "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"), "chown_plugin_path": getenv("CHOWN_PLUGIN_PATH", "1") == "1", @@ -8,7 +10,7 @@ CONFIG = { "server_port": int(getenv("SERVER_PORT", "1337")), "live_reload": getenv("LIVE_RELOAD", "1") == "1", "log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")], - "store_url": getenv("STORE_URL", "https://sdh.tzatzi.me/browse") + "store_url": getenv("STORE_URL", "https://beta.deckbrew.xyz") } basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s") @@ -21,10 +23,9 @@ from subprocess import Popen import aiohttp_cors from aiohttp.web import Application, run_app, static from aiohttp_jinja2 import setup as jinja_setup -from jinja2 import FileSystemLoader from browser import PluginBrowser -from injector import get_tab, inject_to_tab, tab_has_global_var +from injector import inject_to_tab, tab_has_global_var from loader import Loader from utilities import Utilities @@ -48,22 +49,34 @@ class PluginManager: jinja_setup(self.web_app) self.web_app.on_startup.append(self.inject_javascript) - if CONFIG["chown_plugin_path"] == True: self.web_app.on_startup.append(chown_plugin_dir) - self.loop.create_task(self.loader_reinjector()) - + self.loop.create_task(self.load_plugins()) self.loop.set_exception_handler(self.exception_handler) - for route in list(self.web_app.router.routes()): self.cors.add(route) + self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) def exception_handler(self, loop, context): if context["message"] == "Unclosed connection": return loop.default_exception_handler(context) + async def wait_for_server(self): + async with ClientSession() as web: + while True: + try: + await web.get(f"http://{CONFIG['server_host']}:{CONFIG['server_port']}") + return + except Exception as e: + await sleep(0.1) + + async def load_plugins(self): + await self.wait_for_server() + self.plugin_loader.import_plugins() + #await inject_to_tab("SP", "window.syncDeckyPlugins();") + async def loader_reinjector(self): while True: await sleep(1) diff --git a/backend/plugin.py b/backend/plugin.py index db335225..502b35bf 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -10,9 +10,6 @@ from signal import SIGINT, signal from sys import exit from time import time -from injector import inject_to_tab - - class PluginWrapper: def __init__(self, file, plugin_directory, plugin_path) -> None: self.file = file @@ -24,13 +21,23 @@ class PluginWrapper: json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r")) + if "frontend_bundle" in json: + self.frontend_bundle = json["frontend_bundle"] + self.legacy = False + else: + self.main_view_html = json["main_view_html"] + self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else "" + self.legacy = True + self.name = json["name"] self.author = json["author"] - self.frontend_bundle = json["frontend_bundle"] self.flags = json["flags"] self.passive = not path.isfile(self.file) + def __str__(self) -> str: + return self.name + def _init(self): signal(SIGINT, lambda s, f: exit(0)) diff --git a/backend/utilities.py b/backend/utilities.py index 39f9ca55..983bb790 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,5 +1,6 @@ -from aiohttp import ClientSession +from aiohttp import ClientSession, web from injector import inject_to_tab +from json.decoder import JSONDecodeError import uuid class Utilities: @@ -11,9 +12,32 @@ class Utilities: "confirm_plugin_install": self.confirm_plugin_install, "execute_in_tab": self.execute_in_tab, "inject_css_into_tab": self.inject_css_into_tab, - "remove_css_from_tab": self.remove_css_from_tab + "remove_css_from_tab": self.remove_css_from_tab, + "open_plugin_store": self.open_plugin_store } + if context: + context.web_app.add_routes([ + web.post("/methods/{method_name}", self._handle_server_method_call) + ]) + + async def _handle_server_method_call(self, request): + method_name = request.match_info["method_name"] + try: + method_info = await request.json() + args = method_info["args"] + except JSONDecodeError: + args = {} + res = {} + try: + r = await self.util_methods[method_name](**args) + res["result"] = r + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + return web.json_response(res) + async def confirm_plugin_install(self, request_id): return await self.context.plugin_browser.confirm_plugin_install(request_id) @@ -104,3 +128,16 @@ class Utilities: "success": False, "result": e } + + async def open_plugin_store(self): + await inject_to_tab("SP", """ + (function() { + wpRequire = webpackJsonp.push([[], { get_require: (mod, _exports, wpRequire) => mod.exports = wpRequire }, [["get_require"]]]); + const all = () => Object.keys(wpRequire.c).map((x) => wpRequire.c[x].exports).filter((x) => x); + router = all().map(m => { + if (typeof m !== "object") return undefined; + for (let prop in m) { if (m[prop]?.Navigate) return m[prop]} + }).find(x => x) + router.NavigateToExternalWeb("http://127.0.0.1:1337/browser/redirect") + })(); + """)
\ No newline at end of file |
