From 34d1a34b10f4386865f3c241c5ae4026d2bfd8bd Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 5 Aug 2023 01:11:43 -0400 Subject: Migrate most of frontend callServerMethod usage over to websocket --- backend/decky_loader/utilities.py | 98 ++++++++++------------ backend/decky_loader/wsrouter.py | 5 +- .../src/components/modals/PluginUninstallModal.tsx | 4 +- .../src/components/modals/filepicker/index.tsx | 90 +++++++++----------- .../components/settings/pages/developer/index.tsx | 13 +-- .../settings/pages/general/RemoteDebugging.tsx | 4 +- frontend/src/developer.tsx | 4 +- frontend/src/index.ts | 6 ++ frontend/src/plugin-loader.tsx | 36 +++----- frontend/src/plugin.ts | 24 ++++++ frontend/src/start.tsx | 2 - frontend/src/store.tsx | 21 ++--- frontend/src/utils/settings.ts | 4 +- frontend/src/wsrouter.ts | 13 ++- 14 files changed, 163 insertions(+), 161 deletions(-) diff --git a/backend/decky_loader/utilities.py b/backend/decky_loader/utilities.py index 20280c24..2eea63ea 100644 --- a/backend/decky_loader/utilities.py +++ b/backend/decky_loader/utilities.py @@ -66,6 +66,21 @@ class Utilities: context.ws.add_route("utilities/ping", self.ping) context.ws.add_route("utilities/settings/get", self.get_setting) context.ws.add_route("utilities/settings/set", self.set_setting) + context.ws.add_route("utilities/install_plugin", self.install_plugin) + context.ws.add_route("utilities/install_plugins", self.install_plugins) + context.ws.add_route("utilities/cancel_plugin_install", self.cancel_plugin_install) + context.ws.add_route("utilities/confirm_plugin_install", self.confirm_plugin_install) + context.ws.add_route("utilities/uninstall_plugin", self.uninstall_plugin) + context.ws.add_route("utilities/execute_in_tab", self.execute_in_tab) + context.ws.add_route("utilities/inject_css_into_tab", self.inject_css_into_tab) + context.ws.add_route("utilities/remove_css_from_tab", self.remove_css_from_tab) + context.ws.add_route("utilities/allow_remote_debugging", self.allow_remote_debugging) + context.ws.add_route("utilities/disallow_remote_debugging", self.disallow_remote_debugging) + context.ws.add_route("utilities/filepicker_ls", self.filepicker_ls) + context.ws.add_route("utilities/disable_rdt", self.disable_rdt) + context.ws.add_route("utilities/enable_rdt", self.enable_rdt) + context.ws.add_route("utilities/get_tab_id", self.get_tab_id) + context.ws.add_route("utilities/get_user_info", self.get_user_info) async def _handle_server_method_call(self, request): method_name = request.match_info["method_name"] @@ -139,62 +154,39 @@ class Utilities: "result": e } - async def inject_css_into_tab(self, tab: str, style: str): - try: - css_id = str(uuid.uuid4()) - - result = await inject_to_tab(tab, - f""" - (function() {{ - const style = document.createElement('style'); - style.id = "{css_id}"; - document.head.append(style); - style.textContent = `{style}`; - }})() - """, False) - - if result and "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result["result"] - } + async def inject_css_into_tab(self, tab: str, style: str) -> str: + css_id = str(uuid.uuid4()) - return { - "success": True, - "result": css_id - } - except Exception as e: - return { - "success": False, - "result": e - } + result = await inject_to_tab(tab, + f""" + (function() {{ + const style = document.createElement('style'); + style.id = "{css_id}"; + document.head.append(style); + style.textContent = `{style}`; + }})() + """, False) + + if "exceptionDetails" in result["result"]: + raise result["result"]["exceptionDetails"] + + return css_id async def remove_css_from_tab(self, tab: str, css_id: str): - try: - result = await inject_to_tab(tab, - f""" - (function() {{ - let style = document.getElementById("{css_id}"); + result = await inject_to_tab(tab, + f""" + (function() {{ + let style = document.getElementById("{css_id}"); - if (style.nodeName.toLowerCase() == 'style') - style.parentNode.removeChild(style); - }})() - """, False) + if (style.nodeName.toLowerCase() == 'style') + style.parentNode.removeChild(style); + }})() + """, False) - if result and "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result - } + if "exceptionDetails" in result["result"]: + raise result["result"]["exceptionDetails"] - return { - "success": True - } - except Exception as e: - return { - "success": False, - "result": e - } + return async def get_setting(self, key: str, default: Any): return self.context.settings.getSetting(key, default) @@ -211,12 +203,12 @@ class Utilities: return True async def filepicker_ls(self, - path : str | None = None, + path: str | None = None, include_files: bool = True, include_folders: bool = True, - include_ext: list[str] = [], + include_ext: list[str] | None = None, include_hidden: bool = False, - order_by: str = "name_asc", + order_by: str = "name_desc", filter_for: str | None = None, page: int = 1, max: int = 1000): diff --git a/backend/decky_loader/wsrouter.py b/backend/decky_loader/wsrouter.py index 2b4c3a3b..7a7b59c9 100644 --- a/backend/decky_loader/wsrouter.py +++ b/backend/decky_loader/wsrouter.py @@ -79,7 +79,7 @@ class WSRouter: match data["type"]: case MessageType.CALL.value: # do stuff with the message - if self.routes[data["route"]]: + if data["route"] in self.routes: try: res = await self.routes[data["route"]](*data["args"]) await self.write({"type": MessageType.REPLY.value, "id": data["id"], "result": res}) @@ -87,7 +87,8 @@ class WSRouter: except: await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": format_exc()}) else: - await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": "Route does not exist."}) + # Dunno why but fstring doesnt work here + await self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": "Route " + data["route"] + " does not exist."}) case MessageType.REPLY.value: if self.running_calls[data["id"]]: self.running_calls[data["id"]].set_result(data["result"]) diff --git a/frontend/src/components/modals/PluginUninstallModal.tsx b/frontend/src/components/modals/PluginUninstallModal.tsx index e7ecbc99..e9c243b6 100644 --- a/frontend/src/components/modals/PluginUninstallModal.tsx +++ b/frontend/src/components/modals/PluginUninstallModal.tsx @@ -1,6 +1,8 @@ import { ConfirmModal } from 'decky-frontend-lib'; import { FC } from 'react'; +import { uninstallPlugin } from '../../plugin'; + interface PluginUninstallModalProps { name: string; title: string; @@ -14,7 +16,7 @@ const PluginUninstallModal: FC = ({ name, title, butt { - await window.DeckyPluginLoader.callServerMethod('uninstall_plugin', { name }); + await uninstallPlugin(name); // uninstalling a plugin resets the hidden setting for it server-side // we invalidate here so if you re-install it, you won't have an out-of-date hidden filter await window.DeckyPluginLoader.hiddenPluginsService.invalidate(); diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx index c4e72d95..2dfa3ccd 100644 --- a/frontend/src/components/modals/filepicker/index.tsx +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -95,29 +95,20 @@ const sortOptions = [ }, ]; -function getList( - path: string, - includeFiles: boolean, - includeFolders: boolean = true, - includeExt: string[] | null = null, - includeHidden: boolean = false, - orderBy: SortOptions = SortOptions.name_desc, - filterFor: RegExp | ((file: File) => boolean) | null = null, - pageNumber: number = 1, - max: number = 1000, -): Promise<{ result: FileListing | string; success: boolean }> { - return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { - path, - include_files: includeFiles, - include_folders: includeFolders, - include_ext: includeExt ? includeExt : [], - include_hidden: includeHidden, - order_by: orderBy, - filter_for: filterFor, - page: pageNumber, - max: max, - }); -} +const getList = window.DeckyBackend.callable< + [ + path: string, + includeFiles?: boolean, + includeFolders?: boolean, + includeExt?: string[] | null, + includeHidden?: boolean, + orderBy?: SortOptions, + filterFor?: RegExp | ((file: File) => boolean) | null, + pageNumber?: number, + max?: number, + ], + FileListing +>('utilities/filepicker_ls'); const iconStyles = { paddingRight: '10px', @@ -126,20 +117,20 @@ const iconStyles = { const FilePicker: FunctionComponent = ({ startPath, - //What are we allowing to show in the file picker + // What are we allowing to show in the file picker includeFiles = true, includeFolders = true, - //Parameter for specifying a specific filename match + // Parameter for specifying a specific filename match filter = undefined, - //Filter for specific extensions as an array + // Filter for specific extensions as an array validFileExtensions = undefined, - //Allow to override the fixed extension above + // Allow to override the fixed extension above allowAllFiles = true, - //If we need to show hidden files and folders (both Win and Linux should work) + // If we need to show hidden files and folders (both Win and Linux should work) defaultHidden = false, // false by default makes sense for most users - //How much files per page to show, default 1000 + // How many files per page to show, default 1000 max = 1000, - //Which picking option to select by default + // Which picking option to select by default fileSelType = FileSelectionType.FOLDER, onSubmit, closeModal, @@ -190,21 +181,27 @@ const FilePicker: FunctionComponent = ({ useEffect(() => { (async () => { setLoading(true); - const listing = await getList( - path, - includeFiles, - includeFolders, - selectedExts, - showHidden, - sort, - filter, - page, - max, - ); - if (!listing.success) { + try { + const listing = await getList( + path, + includeFiles, + includeFolders, + selectedExts, + showHidden, + sort, + filter, + page, + max, + ); + setRawError(null); + setError(FileErrorTypes.None); + setFiles(listing.files); + setLoading(false); + setListing(listing); + logger.log('reloaded', path, listing); + } catch (theError: any) { setListing({ files: [], realpath: path, total: 0 }); setLoading(false); - const theError = listing.result as string; switch (theError) { case theError.match(/\[Errno\s2.*/i)?.input: case theError.match(/\[WinError\s3.*/i)?.input: @@ -220,14 +217,7 @@ const FilePicker: FunctionComponent = ({ } logger.debug(theError); return; - } else { - setRawError(null); - setError(FileErrorTypes.None); - setFiles((listing.result as FileListing).files); } - setLoading(false); - setListing(listing.result as FileListing); - logger.log('reloaded', path, listing); })(); }, [error, path, includeFiles, includeFolders, showHidden, sort, selectedExts, page]); diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx index 5ed76515..36d3b5c0 100644 --- a/frontend/src/components/settings/pages/developer/index.tsx +++ b/frontend/src/components/settings/pages/developer/index.tsx @@ -91,13 +91,16 @@ export default function DeveloperSettings() { > { - let res = await window.DeckyPluginLoader.callServerMethod('get_tab_id', { name: 'SharedJSContext' }); - if (res.success) { + try { + let tabId = await window.DeckyBackend.call<[name: string], string>( + 'utilities/get_tab_id', + 'SharedJSContext', + ); Navigation.NavigateToExternalWeb( - 'localhost:8080/devtools/inspector.html?ws=localhost:8080/devtools/page/' + res.result, + 'localhost:8080/devtools/inspector.html?ws=localhost:8080/devtools/page/' + tabId, ); - } else { - console.error('Unable to find ID for SharedJSContext tab ', res.result); + } catch (e) { + console.error('Unable to find ID for SharedJSContext tab ', e); Navigation.NavigateToExternalWeb('localhost:8080'); } }} diff --git a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx index 60d57d91..60e0e3c1 100644 --- a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx +++ b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx @@ -18,8 +18,8 @@ export default function RemoteDebuggingSettings() { value={allowRemoteDebugging || false} onChange={(toggleValue) => { setAllowRemoteDebugging(toggleValue); - if (toggleValue) window.DeckyPluginLoader.callServerMethod('allow_remote_debugging'); - else window.DeckyPluginLoader.callServerMethod('disallow_remote_debugging'); + if (toggleValue) window.DeckyBackend.call('allow_remote_debugging'); + else window.DeckyBackend.call('disallow_remote_debugging'); }} /> diff --git a/frontend/src/developer.tsx b/frontend/src/developer.tsx index 43f550d7..8bd09812 100644 --- a/frontend/src/developer.tsx +++ b/frontend/src/developer.tsx @@ -50,9 +50,7 @@ export async function setShouldConnectToReactDevTools(enable: boolean) { icon: , }); await sleep(5000); - return enable - ? window.DeckyPluginLoader.callServerMethod('enable_rdt') - : window.DeckyPluginLoader.callServerMethod('disable_rdt'); + return enable ? window.DeckyBackend.call('utilities/enable_rdt') : window.DeckyBackend.call('utilities/disable_rdt'); } export async function startup() { diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 6588cb5c..3a0c4880 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -2,5 +2,11 @@ (async () => { console.debug('Setting up decky-frontend-lib...'); window.DFL = await import('decky-frontend-lib'); + console.debug('Authenticating to Decky backend...'); + window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text()); + console.debug('Connecting to Decky backend...'); + window.DeckyBackend = new (await import('./wsrouter')).WSRouter(); + await window.DeckyBackend.connect(); + console.debug('Starting Decky!'); await import('./start'); })(); diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 86592016..3c9ba818 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -34,7 +34,6 @@ import Toaster from './toaster'; import { VerInfo, callUpdaterMethod } from './updater'; import { getSetting, setSetting } from './utils/settings'; import TranslationHelper, { TranslationClass } from './utils/TranslationHelper'; -import { WSRouter } from './wsrouter'; const StorePage = lazy(() => import('./components/store/Store')); const SettingsPage = lazy(() => import('./components/settings')); @@ -49,8 +48,6 @@ class PluginLoader extends Logger { public toaster: Toaster = new Toaster(); private deckyState: DeckyState = new DeckyState(); - public ws: WSRouter = new WSRouter(); - public hiddenPluginsService = new HiddenPluginsService(this.deckyState); public notificationService = new NotificationService(this.deckyState); @@ -105,15 +102,13 @@ class PluginLoader extends Logger { initFilepickerPatches(); - this.ws.connect().then(() => { - this.getUserInfo(); + this.getUserInfo(); - this.updateVersion(); - }); + this.updateVersion(); } public async getUserInfo() { - const userInfo = (await this.callServerMethod('get_user_info')).result as UserInfo; + const userInfo = await window.DeckyBackend.call<[], UserInfo>('utilities/get_user_info'); setSetting('user_info.user_name', userInfo.username); setSetting('user_info.user_home', userInfo.path); } @@ -183,8 +178,8 @@ class PluginLoader extends Logger { version={version} hash={hash} installType={install_type} - onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })} - onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })} + onOK={() => window.DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)} + onCancel={() => window.DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)} />, ); } @@ -196,8 +191,8 @@ class PluginLoader extends Logger { showModal( this.callServerMethod('confirm_plugin_install', { request_id })} - onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })} + onOK={() => window.DeckyBackend.call<[string]>('utilities/confirm_plugin_install', request_id)} + onCancel={() => window.DeckyBackend.call<[string]>('utilities/cancel_plugin_install', request_id)} />, ); } @@ -360,6 +355,7 @@ class PluginLoader extends Logger { selectFiles?: boolean, regex?: RegExp, ): Promise<{ path: string; realpath: string }> { + console.warn('openFilePicker is deprecated and will be removed. Please migrate to openFilePickerV2'); if (selectFiles) { return this.openFilePickerV2(FileSelectionType.FILE, startPath, true, true, regex); } else { @@ -443,18 +439,10 @@ class PluginLoader extends Logger { code, }); }, - injectCssIntoTab(tab: string, style: string) { - return this.callServerMethod('inject_css_into_tab', { - tab, - style, - }); - }, - removeCssFromTab(tab: string, cssId: any) { - return this.callServerMethod('remove_css_from_tab', { - tab, - css_id: cssId, - }); - }, + injectCssIntoTab: window.DeckyBackend.callable<[tab: string, style: string], string>( + 'utilities/inject_css_into_tab', + ), + removeCssFromTab: window.DeckyBackend.callable<[tab: string, cssId: string]>('utilities/remove_css_from_tab'), }; } } diff --git a/frontend/src/plugin.ts b/frontend/src/plugin.ts index fa1bb28a..21d45654 100644 --- a/frontend/src/plugin.ts +++ b/frontend/src/plugin.ts @@ -13,3 +13,27 @@ export enum InstallType { REINSTALL, UPDATE, } + +type installPluginArgs = [ + artifact: string, + name?: string, + version?: string, + hash?: string | boolean, + installType?: InstallType, +]; + +export let installPlugin = window.DeckyBackend.callable('utilities/install_plugin'); + +type installPluginsArgs = [ + requests: { + artifact: string; + name?: string; + version?: string; + hash?: string | boolean; + installType?: InstallType; + }[], +]; + +export let installPlugins = window.DeckyBackend.callable('utilities/install_plugins'); + +export let uninstallPlugin = window.DeckyBackend.callable<[name: string]>('utilities/uninstall_plugin'); diff --git a/frontend/src/start.tsx b/frontend/src/start.tsx index 94b22ffe..a125e3d4 100644 --- a/frontend/src/start.tsx +++ b/frontend/src/start.tsx @@ -19,8 +19,6 @@ declare global { } (async () => { - window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text()); - i18n .use(Backend) .use(initReactI18next) diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index bc419430..5ae98ec4 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -1,4 +1,4 @@ -import { InstallType, Plugin } from './plugin'; +import { InstallType, Plugin, installPlugin, installPlugins } from './plugin'; import { getSetting, setSetting } from './utils/settings'; export enum Store { @@ -92,33 +92,24 @@ export async function getPluginList(): Promise { export async function installFromURL(url: string) { const splitURL = url.split('/'); - await window.DeckyPluginLoader.callServerMethod('install_plugin', { - name: splitURL[splitURL.length - 1].replace('.zip', ''), - artifact: url, - }); + await installPlugin(url, splitURL[splitURL.length - 1].replace('.zip', '')); } export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion, installType: InstallType) { const artifactUrl = selectedVer.artifact ?? pluginUrl(selectedVer.hash); - await window.DeckyPluginLoader.callServerMethod('install_plugin', { - name: plugin, - artifact: artifactUrl, - version: selectedVer.name, - hash: selectedVer.hash, - install_type: installType, - }); + await installPlugin(artifactUrl, plugin, selectedVer.name, selectedVer.hash, installType); } export async function requestMultiplePluginInstalls(requests: PluginInstallRequest[]) { - await window.DeckyPluginLoader.callServerMethod('install_plugins', { - requests: requests.map(({ plugin, installType, selectedVer }) => ({ + await installPlugins( + requests.map(({ plugin, installType, selectedVer }) => ({ name: plugin, artifact: selectedVer.artifact ?? pluginUrl(selectedVer.hash), version: selectedVer.name, hash: selectedVer.hash, install_type: installType, })), - }); + ); } export async function checkForUpdates(plugins: Plugin[]): Promise { diff --git a/frontend/src/utils/settings.ts b/frontend/src/utils/settings.ts index d390d7ba..cba4887f 100644 --- a/frontend/src/utils/settings.ts +++ b/frontend/src/utils/settings.ts @@ -1,8 +1,8 @@ export async function getSetting(key: string, def: T): Promise { - const res = await window.DeckyPluginLoader.ws.call<[string, T], T>('utilities/settings/get', key, def); + const res = await window.DeckyBackend.call<[string, T], T>('utilities/settings/get', key, def); return res; } export async function setSetting(key: string, value: T): Promise { - await window.DeckyPluginLoader.ws.call<[string, T], void>('utilities/settings/set', key, value); + await window.DeckyBackend.call<[string, T], void>('utilities/settings/set', key, value); } diff --git a/frontend/src/wsrouter.ts b/frontend/src/wsrouter.ts index 3a36b5b0..e50b06a7 100644 --- a/frontend/src/wsrouter.ts +++ b/frontend/src/wsrouter.ts @@ -1,5 +1,11 @@ import Logger from './logger'; +declare global { + interface Window { + DeckyBackend: WSRouter; + } +} + enum MessageType { // Call-reply CALL, @@ -94,7 +100,6 @@ export class WSRouter extends Logger { } async onMessage(msg: MessageEvent) { - this.debug('WS Message', msg); try { const data = JSON.parse(msg.data) as Message; switch (data.type) { @@ -108,7 +113,7 @@ export class WSRouter extends Logger { await this.write({ type: MessageType.ERROR, id: data.id, error: (e as Error)?.stack || e }); } } else { - await this.write({ type: MessageType.ERROR, id: data.id, error: 'Route does not exist.' }); + await this.write({ type: MessageType.ERROR, id: data.id, error: `Route ${data.route} does not exist.` }); } break; @@ -152,6 +157,10 @@ export class WSRouter extends Logger { return resolver.promise; } + callable(route: string): (...args: Args) => Promise { + return (...args) => this.call(route, ...args); + } + async onError(error: any) { this.error('WS DISCONNECTED', error); await this.connect(); -- cgit v1.2.3