summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2023-12-30 00:46:59 -0500
committerAAGaming <aagaming@riseup.net>2023-12-30 00:46:59 -0500
commit6522ebf0cad1723a278144b6c5d8557cd47e52d6 (patch)
tree8c048cfe75c73938d347f8e6cd7b8bb23269df2c /frontend
parent6042ca56b85fffe6bac4cac5a2965ee87c4e1e32 (diff)
downloaddecky-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.tsx12
-rw-r--r--frontend/src/logger.ts14
-rw-r--r--frontend/src/plugin-loader.tsx150
-rw-r--r--frontend/src/start.tsx18
-rw-r--r--frontend/src/wsrouter.ts4
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();
}
}