diff options
| author | AAGaming <aagaming@riseup.net> | 2023-12-30 00:46:59 -0500 |
|---|---|---|
| committer | AAGaming <aagaming@riseup.net> | 2023-12-30 00:46:59 -0500 |
| commit | 6522ebf0cad1723a278144b6c5d8557cd47e52d6 (patch) | |
| tree | 8c048cfe75c73938d347f8e6cd7b8bb23269df2c /frontend | |
| parent | 6042ca56b85fffe6bac4cac5a2965ee87c4e1e32 (diff) | |
| download | decky-loader-6522ebf0cad1723a278144b6c5d8557cd47e52d6.tar.gz decky-loader-6522ebf0cad1723a278144b6c5d8557cd47e52d6.zip | |
Implement legacy & modern plugin method calls over WS
This version builds fine and runs all of the 14 plugins I have installed perfectly, so we're really close to having this done.
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/index.tsx | 12 | ||||
| -rw-r--r-- | frontend/src/logger.ts | 14 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 150 | ||||
| -rw-r--r-- | frontend/src/start.tsx | 18 | ||||
| -rw-r--r-- | frontend/src/wsrouter.ts | 4 |
5 files changed, 122 insertions, 76 deletions
diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx index 09d06d48..6475b40a 100644 --- a/frontend/src/components/settings/pages/plugin_list/index.tsx +++ b/frontend/src/components/settings/pages/plugin_list/index.tsx @@ -35,6 +35,8 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) { type PluginTableData = PluginData & { name: string; hidden: boolean; onHide(): void; onShow(): void }; +const reloadPluginBackend = window.DeckyBackend.callable<[pluginName: string], void>('loader/reload_plugin'); + function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }) { const { t } = useTranslation(); @@ -49,15 +51,9 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> } showContextMenu( <Menu label={t('PluginListIndex.plugin_actions')}> <MenuItem - onSelected={() => { + onSelected={async () => { try { - fetch(`http://127.0.0.1:1337/plugins/${name}/reload`, { - method: 'POST', - credentials: 'include', - headers: { - Authentication: window.deckyAuthToken, - }, - }); + await reloadPluginBackend(name); } catch (err) { console.error('Error Reloading Plugin Backend', err); } diff --git a/frontend/src/logger.ts b/frontend/src/logger.ts index 143bef16..80019fdb 100644 --- a/frontend/src/logger.ts +++ b/frontend/src/logger.ts @@ -18,6 +18,16 @@ export const debug = (name: string, ...args: any[]) => { ); }; +export const warn = (name: string, ...args: any[]) => { + console.warn( + `%c Decky %c ${name} %c`, + 'background: #16a085; color: black;', + 'background: #ffbb00; color: black;', + 'color: blue;', + ...args, + ); +}; + export const error = (name: string, ...args: any[]) => { console.error( `%c Decky %c ${name} %c`, @@ -41,6 +51,10 @@ class Logger { debug(this.name, ...args); } + warn(...args: any[]) { + warn(this.name, ...args); + } + error(...args: any[]) { error(this.name, ...args); } diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 3c9ba818..cba74a9e 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -5,6 +5,7 @@ import { Patch, QuickAccessTab, Router, + findSP, quickAccessMenuClasses, showModal, sleep, @@ -60,7 +61,6 @@ class PluginLoader extends Logger { constructor() { super(PluginLoader.name); this.tabsHook.init(); - this.log('Initialized'); const TabBadge = () => { const { updates, hasLoaderUpdate } = useDeckyState(); @@ -102,9 +102,32 @@ class PluginLoader extends Logger { initFilepickerPatches(); - this.getUserInfo(); + Promise.all([this.getUserInfo(), this.updateVersion()]) + .then(() => this.loadPlugins()) + .then(() => this.checkPluginUpdates()) + .then(() => this.log('Initialized')); + } + + private getPluginsFromBackend = window.DeckyBackend.callable<[], { name: string; version: string }[]>( + 'loader/get_plugins', + ); + + private async loadPlugins() { + // wait for SP window to exist before loading plugins + while (!findSP()) { + await sleep(100); + } + const plugins = await this.getPluginsFromBackend(); + const pluginLoadPromises = []; + const loadStart = performance.now(); + for (const plugin of plugins) { + if (!this.hasPlugin(plugin.name)) pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, false)); + } + await Promise.all(pluginLoadPromises); + const loadEnd = performance.now(); + this.log(`Loaded ${plugins.length} plugins in ${loadEnd - loadStart}ms`); - this.updateVersion(); + this.checkPluginUpdates(); } public async getUserInfo() { @@ -217,9 +240,9 @@ class PluginLoader extends Logger { if (val) import('./developer').then((developer) => developer.startup()); }); - //* Grab and set plugin order + // Grab and set plugin order getSetting<string[]>('pluginOrder', []).then((pluginOrder) => { - console.log(pluginOrder); + this.debug('pluginOrder: ', pluginOrder); this.deckyState.setPluginOrder(pluginOrder); }); @@ -236,15 +259,14 @@ class PluginLoader extends Logger { } public unloadPlugin(name: string) { - console.log('Plugin List: ', this.plugins); const plugin = this.plugins.find((plugin) => plugin.name === name); plugin?.onDismount?.(); this.plugins = this.plugins.filter((p) => p !== plugin); this.deckyState.setPlugins(this.plugins); } - public async importPlugin(name: string, version?: string | undefined) { - if (this.reloadLock) { + public async importPlugin(name: string, version?: string | undefined, useQueue: boolean = true) { + if (useQueue && this.reloadLock) { this.log('Reload currently in progress, adding to queue', name); this.pluginReloadQueue.push({ name, version: version }); return; @@ -255,17 +277,21 @@ class PluginLoader extends Logger { this.log(`Trying to load ${name}`); this.unloadPlugin(name); + const startTime = performance.now(); await this.importReactPlugin(name, version); + const endTime = performance.now(); this.deckyState.setPlugins(this.plugins); - this.log(`Loaded ${name}`); + this.log(`Loaded ${name} in ${endTime - startTime}ms`); } catch (e) { throw e; } finally { - this.reloadLock = false; - const nextPlugin = this.pluginReloadQueue.shift(); - if (nextPlugin) { - this.importPlugin(nextPlugin.name, nextPlugin.version); + if (useQueue) { + this.reloadLock = false; + const nextPlugin = this.pluginReloadQueue.shift(); + if (nextPlugin) { + this.importPlugin(nextPlugin.name, nextPlugin.version); + } } } } @@ -337,17 +363,14 @@ class PluginLoader extends Logger { } async callServerMethod(methodName: string, args = {}) { - const response = await fetch(`http://127.0.0.1:1337/methods/${methodName}`, { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - Authentication: window.deckyAuthToken, - }, - body: JSON.stringify(args), - }); - - return response.json(); + this.warn( + `Calling ${methodName} via callServerMethod, which is deprecated and will be removed in a future release. Please switch to the backend API.`, + ); + return await window.DeckyBackend.call<[methodName: string, kwargs: any], any>( + 'utilities/_call_legacy_utility', + methodName, + args, + ); } openFilePicker( @@ -355,7 +378,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'); + this.warn('openFilePicker is deprecated and will be removed. Please migrate to openFilePickerV2'); if (selectFiles) { return this.openFilePickerV2(FileSelectionType.FILE, startPath, true, true, regex); } else { @@ -405,45 +428,72 @@ class PluginLoader extends Logger { } createPluginAPI(pluginName: string) { - return { + const pluginAPI = { + backend: { + call<Args extends any[] = any[], Return = void>(method: string, ...args: Args): Promise<Return> { + return window.DeckyBackend.call<[pluginName: string, method: string, ...args: Args], Return>( + 'loader/call_plugin_method', + pluginName, + method, + ...args, + ); + }, + callable<Args extends any[] = any[], Return = void>(method: string): (...args: Args) => Promise<Return> { + return (...args) => pluginAPI.backend.call<Args, Return>(method, ...args); + }, + }, routerHook: this.routerHook, toaster: this.toaster, + // Legacy callServerMethod: this.callServerMethod, openFilePicker: this.openFilePicker, openFilePickerV2: this.openFilePickerV2, + // Legacy async callPluginMethod(methodName: string, args = {}) { - const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - Authentication: window.deckyAuthToken, - }, - body: JSON.stringify({ - args, - }), - }); - - return response.json(); + return window.DeckyBackend.call<[pluginName: string, methodName: string, kwargs: any], any>( + 'loader/call_legacy_plugin_method', + pluginName, + methodName, + args, + ); }, - fetchNoCors(url: string, request: any = {}) { - let args = { method: 'POST', headers: {} }; - const req = { ...args, ...request, url, data: request.body }; + /* TODO replace with the following flow (or similar) so we can reuse the JS Fetch API + frontend --request URL only--> backend (ws method) + backend --new temporary backend URL--> frontend (ws response) + frontend <--> backend <--> target URL (over http!) + */ + async fetchNoCors(url: string, request: any = {}) { + let method: string; + const req = { headers: {}, ...request, data: request.body }; req?.body && delete req.body; - return this.callServerMethod('http_request', req); - }, - executeInTab(tab: string, runAsync: boolean, code: string) { - return this.callServerMethod('execute_in_tab', { - tab, - run_async: runAsync, - code, - }); + if (!request.method) { + method = 'POST'; + } else { + method = request.method; + delete req.method; + } + // this is terrible but a. we're going to redo this entire method anyway and b. it was already terrible + try { + const ret = await window.DeckyBackend.call< + [method: string, url: string, extra_opts?: any], + { status: number; headers: { [key: string]: string }; body: string } + >('utilities/http_request', method, url, req); + return { success: true, result: ret }; + } catch (e) { + return { success: false, result: e?.toString() }; + } }, + executeInTab: window.DeckyBackend.callable< + [tab: String, runAsync: Boolean, code: string], + { success: boolean; result: any } + >('utilities/execute_in_tab'), 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'), }; + + return pluginAPI; } } diff --git a/frontend/src/start.tsx b/frontend/src/start.tsx index a125e3d4..cc2c02c2 100644 --- a/frontend/src/start.tsx +++ b/frontend/src/start.tsx @@ -10,7 +10,6 @@ declare global { DeckyPluginLoader: PluginLoader; DeckyUpdater?: DeckyUpdater; importDeckyPlugin: Function; - syncDeckyPlugins: Function; deckyHasLoaded: boolean; deckyHasConnectedRDT?: boolean; deckyAuthToken: string; @@ -53,23 +52,6 @@ declare global { window.importDeckyPlugin = function (name: string, version: string) { window.DeckyPluginLoader?.importPlugin(name, version); }; - - window.syncDeckyPlugins = async function () { - const plugins = await ( - await fetch('http://127.0.0.1:1337/plugins', { - credentials: 'include', - headers: { Authentication: window.deckyAuthToken }, - }) - ).json(); - for (const plugin of plugins) { - if (!window.DeckyPluginLoader.hasPlugin(plugin.name)) - window.DeckyPluginLoader?.importPlugin(plugin.name, plugin.version); - } - - window.DeckyPluginLoader.checkPluginUpdates(); - }; - - setTimeout(() => window.syncDeckyPlugins(), 5000); })(); export default i18n; diff --git a/frontend/src/wsrouter.ts b/frontend/src/wsrouter.ts index e1d766c3..d3bc45a0 100644 --- a/frontend/src/wsrouter.ts +++ b/frontend/src/wsrouter.ts @@ -1,3 +1,5 @@ +import { sleep } from 'decky-frontend-lib'; + import Logger from './logger'; declare global { @@ -161,6 +163,8 @@ export class WSRouter extends Logger { async onError(error: any) { this.error('WS DISCONNECTED', error); + // TODO queue up lost messages and send them once we connect again + await sleep(5000); await this.connect(); } } |
