diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/decky_loader/browser.py | 18 | ||||
| -rw-r--r-- | backend/decky_loader/customtypes.py | 6 | ||||
| -rw-r--r-- | backend/decky_loader/enums.py | 10 | ||||
| -rw-r--r-- | backend/decky_loader/helpers.py | 16 | ||||
| -rw-r--r-- | backend/decky_loader/loader.py | 18 | ||||
| -rw-r--r-- | backend/decky_loader/localplatform/localplatformlinux.py | 3 | ||||
| -rw-r--r-- | backend/decky_loader/localplatform/localplatformwin.py | 2 | ||||
| -rw-r--r-- | backend/decky_loader/main.py | 2 | ||||
| -rw-r--r-- | backend/decky_loader/plugin/plugin.py | 6 | ||||
| -rw-r--r-- | backend/decky_loader/plugin/sandboxed_plugin.py | 2 | ||||
| -rw-r--r-- | backend/decky_loader/settings.py | 2 | ||||
| -rw-r--r-- | backend/decky_loader/updater.py | 10 | ||||
| -rw-r--r-- | backend/decky_loader/wsrouter.py | 2 | ||||
| -rw-r--r-- | backend/locales/en-US.json | 10 |
14 files changed, 62 insertions, 45 deletions
diff --git a/backend/decky_loader/browser.py b/backend/decky_loader/browser.py index def81011..cb573b13 100644 --- a/backend/decky_loader/browser.py +++ b/backend/decky_loader/browser.py @@ -4,7 +4,7 @@ import json # from pprint import pformat # Partial imports -from aiohttp import ClientSession +from aiohttp import ClientSession, request from asyncio import sleep from hashlib import sha256 from io import BytesIO @@ -123,7 +123,6 @@ class PluginBrowser: async def uninstall_plugin(self, name: str): if self.loader.watcher: self.loader.watcher.disabled = True - tab = await get_gamepadui_tab() plugin_folder = self.find_plugin_folder(name) assert plugin_folder is not None plugin_dir = path.join(self.plugin_path, plugin_folder) @@ -131,8 +130,7 @@ class PluginBrowser: logger.info("uninstalling " + name) logger.info(" at dir " + plugin_dir) logger.debug("calling frontend unload for %s" % str(name)) - res = await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')") - logger.debug("result of unload from UI: %s", res) + await self.loader.ws.emit("loader/unload_plugin", name) # plugins_snapshot = self.plugins.copy() # snapshot_string = pformat(plugins_snapshot) # logger.debug("current plugins: %s", snapshot_string) @@ -258,20 +256,14 @@ class PluginBrowser: async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType): request_id = str(time()) self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) - tab = await get_gamepadui_tab() - await tab.open_websocket() - await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})") + + await self.loader.ws.emit("loader/add_plugin_install_prompt", name, version, request_id, hash, install_type) async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]): request_id = str(time()) self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests] - js_requests_parameter = ','.join([ - f"{{ name: '{req['name']}', version: '{req['version']}', hash: '{req['hash']}', install_type: {req['install_type']}}}" for req in requests - ]) - tab = await get_gamepadui_tab() - await tab.open_websocket() - await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])") + await self.loader.ws.emit("loader/add_multiple_plugins_install_prompt", request_id, requests) async def confirm_plugin_install(self, request_id: str): requestOrRequests = self.install_requests.pop(request_id) diff --git a/backend/decky_loader/customtypes.py b/backend/decky_loader/customtypes.py deleted file mode 100644 index 84ebc235..00000000 --- a/backend/decky_loader/customtypes.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - -class UserType(Enum): - HOST_USER = 1 - EFFECTIVE_USER = 2 - ROOT = 3
\ No newline at end of file diff --git a/backend/decky_loader/enums.py b/backend/decky_loader/enums.py new file mode 100644 index 00000000..e7fb4905 --- /dev/null +++ b/backend/decky_loader/enums.py @@ -0,0 +1,10 @@ +from enum import IntEnum + +class UserType(IntEnum): + HOST_USER = 1 + EFFECTIVE_USER = 2 + ROOT = 3 + +class PluginLoadType(IntEnum): + LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI + ESMODULE_V1 = 1 # esmodule loading with modern @decky/backend apis
\ No newline at end of file diff --git a/backend/decky_loader/helpers.py b/backend/decky_loader/helpers.py index 2d5eb6dd..f4005cc5 100644 --- a/backend/decky_loader/helpers.py +++ b/backend/decky_loader/helpers.py @@ -12,7 +12,7 @@ from aiohttp.web import Request, Response, middleware from aiohttp.typedefs import Handler from aiohttp import ClientSession from .localplatform import localplatform -from .customtypes import UserType +from .enums import UserType from logging import getLogger from packaging.version import Version @@ -23,6 +23,7 @@ csrf_token = str(uuid.uuid4()) ssl_ctx = ssl.create_default_context(cafile=certifi.where()) assets_regex = re.compile("^/plugins/.*/assets/.*") +dist_regex = re.compile("^/plugins/.*/dist/.*") frontend_regex = re.compile("^/frontend/.*") logger = getLogger("Main") @@ -34,7 +35,18 @@ def get_csrf_token(): @middleware async def csrf_middleware(request: Request, handler: Handler): - if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or str(request.rel_url.path) == "/ws" or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): + if str(request.method) == "OPTIONS" or \ + request.headers.get('Authentication') == csrf_token or \ + str(request.rel_url) == "/auth/token" or \ + str(request.rel_url).startswith("/plugins/load_main/") or \ + str(request.rel_url).startswith("/static/") or \ + str(request.rel_url).startswith("/steam_resource/") or \ + str(request.rel_url).startswith("/frontend/") or \ + str(request.rel_url.path) == "/ws" or \ + assets_regex.match(str(request.rel_url)) or \ + dist_regex.match(str(request.rel_url)) or \ + frontend_regex.match(str(request.rel_url)): + return await handler(request) return Response(text='Forbidden', status=403) diff --git a/backend/decky_loader/loader.py b/backend/decky_loader/loader.py index 550638a3..aad595e7 100644 --- a/backend/decky_loader/loader.py +++ b/backend/decky_loader/loader.py @@ -16,9 +16,9 @@ from typing import TYPE_CHECKING, List if TYPE_CHECKING: from .main import PluginManager -from .injector import get_gamepadui_tab from .plugin.plugin import PluginWrapper from .wsrouter import WSRouter +from .enums import PluginLoadType Plugins = dict[str, PluginWrapper] ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] @@ -96,6 +96,7 @@ class Loader: web.get("/frontend/{path:.*}", self.handle_frontend_assets), web.get("/locales/{path:.*}", self.handle_frontend_locales), web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), + web.get("/plugins/{plugin_name}/dist/{path:.*}", self.handle_plugin_dist), web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), ]) @@ -126,7 +127,13 @@ class Loader: async def get_plugins(self): plugins = list(self.plugins.values()) - return [{"name": str(i), "version": i.version} for i in plugins] + return [{"name": str(i), "version": i.version, "load_type": i.load_type} for i in plugins] + + async def handle_plugin_dist(self, request: web.Request): + plugin = self.plugins[request.match_info["plugin_name"]] + file = path.join(self.plugin_path, plugin.plugin_directory, "dist", request.match_info["path"]) + + return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) async def handle_plugin_frontend_assets(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] @@ -145,7 +152,7 @@ class Loader: async def plugin_emitted_event(event: str, data: Any): self.logger.debug(f"PLUGIN EMITTED EVENT: {str(event)} {data}") event_data = PluginEvent(plugin_name=plugin.name, event=event, data=data) - await self.ws.emit("plugin_event", event_data) + await self.ws.emit("loader/plugin_event", event_data) plugin = PluginWrapper(file, plugin_directory, self.plugin_path, plugin_emitted_event) if plugin.name in self.plugins: @@ -166,9 +173,8 @@ class Loader: self.logger.error(f"Could not load {file}. {e}") print_exc() - async def dispatch_plugin(self, name: str, version: str | None): - gpui_tab = await get_gamepadui_tab() - await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") + async def dispatch_plugin(self, name: str, version: str | None, load_type: int = PluginLoadType.ESMODULE_V1.value): + await self.ws.emit("loader/import_plugin", name, version, load_type) def import_plugins(self): self.logger.info(f"import plugins from {self.plugin_path}") diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 4eb112ee..2674e9bc 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -1,6 +1,6 @@ import os, pwd, grp, sys, logging from subprocess import call, run, DEVNULL, PIPE, STDOUT -from ..customtypes import UserType +from ..enums import UserType logger = logging.getLogger("localplatform") @@ -157,6 +157,7 @@ async def service_start(service_name : str) -> bool: return res.returncode == 0 async def restart_webhelper() -> bool: + logger.info("Restarting steamwebhelper") res = run(["killall", "-s", "SIGTERM", "steamwebhelper"], stdout=DEVNULL, stderr=DEVNULL) return res.returncode == 0 diff --git a/backend/decky_loader/localplatform/localplatformwin.py b/backend/decky_loader/localplatform/localplatformwin.py index f1a5be17..38e4b2b0 100644 --- a/backend/decky_loader/localplatform/localplatformwin.py +++ b/backend/decky_loader/localplatform/localplatformwin.py @@ -1,4 +1,4 @@ -from ..customtypes import UserType +from ..enums import UserType import os, sys def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: diff --git a/backend/decky_loader/main.py b/backend/decky_loader/main.py index 9095f711..64c76dc0 100644 --- a/backend/decky_loader/main.py +++ b/backend/decky_loader/main.py @@ -30,7 +30,7 @@ from .loader import Loader from .settings import SettingsManager from .updater import Updater from .utilities import Utilities -from .customtypes import UserType +from .enums import UserType from .wsrouter import WSRouter diff --git a/backend/decky_loader/plugin/plugin.py b/backend/decky_loader/plugin/plugin.py index 47d3d7b0..cad323f4 100644 --- a/backend/decky_loader/plugin/plugin.py +++ b/backend/decky_loader/plugin/plugin.py @@ -4,9 +4,9 @@ from logging import getLogger from os import path from multiprocessing import Process - from .sandboxed_plugin import SandboxedPlugin from .messages import MethodCallRequest, SocketMessageType +from ..enums import PluginLoadType from ..localplatform.localsocket import LocalSocket from typing import Any, Callable, Coroutine, Dict, List @@ -21,10 +21,14 @@ class PluginWrapper: self.version = None + self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value + json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8")) if path.isfile(path.join(plugin_path, plugin_directory, "package.json")): package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) self.version = package_json["version"] + if ("type" in package_json and package_json["type"] == "module"): + self.load_type = PluginLoadType.ESMODULE_V1.value self.name = json["name"] self.author = json["author"] diff --git a/backend/decky_loader/plugin/sandboxed_plugin.py b/backend/decky_loader/plugin/sandboxed_plugin.py index 3fd38e4f..b49dcf41 100644 --- a/backend/decky_loader/plugin/sandboxed_plugin.py +++ b/backend/decky_loader/plugin/sandboxed_plugin.py @@ -11,7 +11,7 @@ from asyncio import (get_event_loop, new_event_loop, from .messages import SocketResponseDict, SocketMessageType from ..localplatform.localsocket import LocalSocket from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path -from ..customtypes import UserType +from ..enums import UserType from .. import helpers from typing import List, TypeVar, Type diff --git a/backend/decky_loader/settings.py b/backend/decky_loader/settings.py index c0f2b90c..b5f034aa 100644 --- a/backend/decky_loader/settings.py +++ b/backend/decky_loader/settings.py @@ -2,7 +2,7 @@ from json import dump, load from os import mkdir, path, listdir, rename from typing import Any, Dict from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path -from .customtypes import UserType +from .enums import UserType from .helpers import get_homebrew_path diff --git a/backend/decky_loader/updater.py b/backend/decky_loader/updater.py index a28f0c11..6355fcc7 100644 --- a/backend/decky_loader/updater.py +++ b/backend/decky_loader/updater.py @@ -35,7 +35,6 @@ class TestingVersion(TypedDict): link: str head_sha: str - class Updater: def __init__(self, context: PluginManager) -> None: self.context = context @@ -103,7 +102,7 @@ class Updater: logger.debug("checking for updates") selectedBranch = self.get_branch(self.context.settings) async with ClientSession() as web: - async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res: + async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res: remoteVersions: List[RemoteVer] = await res.json() if selectedBranch == 0: logger.debug("release type: release") @@ -126,8 +125,7 @@ class Updater: logger.error("release type: NOT FOUND") raise ValueError("no valid branch found") logger.info("Updated remote version information") - tab = await get_gamepadui_tab() - await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False) + await self.context.ws.emit("loader/notify_updates") return await self.get_version_info() async def version_reloader(self): @@ -158,7 +156,7 @@ class Updater: raw += len(c) new_progress = round((raw / total) * 100) if progress != new_progress: - self.context.loop.create_task(self.context.ws.emit("frontend/update_download_percentage", new_progress)) + self.context.loop.create_task(self.context.ws.emit("updater/update_download_percentage", new_progress)) progress = new_progress with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out: @@ -182,7 +180,7 @@ class Updater: logger.info(f"Setting the executable flag with chcon returned {await process.wait()}") logger.info("Updated loader installation.") - await self.context.ws.emit("frontend/finish_download") + await self.context.ws.emit("updater/finish_download") await self.do_restart() await tab.close_websocket() diff --git a/backend/decky_loader/wsrouter.py b/backend/decky_loader/wsrouter.py index 4874e967..918b74bc 100644 --- a/backend/decky_loader/wsrouter.py +++ b/backend/decky_loader/wsrouter.py @@ -93,9 +93,7 @@ class WSRouter: async for msg in ws: msg = cast(WSMessageExtra, msg) - self.logger.debug(msg) if msg.type == WSMsgType.TEXT: - self.logger.debug(msg.data) if msg.data == 'close': # TODO DO NOT RELY ON THIS! break diff --git a/backend/locales/en-US.json b/backend/locales/en-US.json index ca18f7da..fe544dea 100644 --- a/backend/locales/en-US.json +++ b/backend/locales/en-US.json @@ -263,8 +263,10 @@ "reloading": "Reloading", "updating": "Updating" } - }, - "Testing": { - "download": "Download" - } + }, + "Testing": { + "download": "Download", + "header": "The following versions of Decky Loader are built from open third-party Pull Requests. The Decky Loader team has not verified their functionality or security, and they may be outdated.", + "loading": "Loading open Pull Requests..." + } } |
