summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/decky_loader/browser.py18
-rw-r--r--backend/decky_loader/customtypes.py6
-rw-r--r--backend/decky_loader/enums.py10
-rw-r--r--backend/decky_loader/helpers.py16
-rw-r--r--backend/decky_loader/loader.py18
-rw-r--r--backend/decky_loader/localplatform/localplatformlinux.py3
-rw-r--r--backend/decky_loader/localplatform/localplatformwin.py2
-rw-r--r--backend/decky_loader/main.py2
-rw-r--r--backend/decky_loader/plugin/plugin.py6
-rw-r--r--backend/decky_loader/plugin/sandboxed_plugin.py2
-rw-r--r--backend/decky_loader/settings.py2
-rw-r--r--backend/decky_loader/updater.py10
-rw-r--r--backend/decky_loader/wsrouter.py2
-rw-r--r--backend/locales/en-US.json10
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..."
+ }
}