more major websocket progress

This commit is contained in:
AAGaming
2024-02-21 01:08:25 -05:00
parent 61cf80f8a2
commit 6d2e9365c0
26 changed files with 358 additions and 240 deletions
+5 -1
View File
@@ -7,7 +7,7 @@ settings:
dependencies:
decky-frontend-lib:
specifier: 3.24.5
version: link:../../lib
version: 3.24.5
filesize:
specifier: ^10.0.7
version: 10.0.7
@@ -1482,6 +1482,10 @@ packages:
dependencies:
ms: 2.1.2
/decky-frontend-lib@3.24.5:
resolution: {integrity: sha512-eYlbKDOOcIBPI0b76Rqvlryq2ym/QNiry4xf2pFrXmBa1f95dflqbQAb2gTq9uHEa5gFmeV4lUcMPGJ3M14Xqw==}
dev: false
/decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
dependencies:
@@ -1,6 +1,7 @@
import { Patch, findModuleChild, replacePatch, sleep } from 'decky-frontend-lib';
import Logger from '../../../../logger';
import { FileSelectionType } from '..';
const logger = new Logger('LibraryPatch');
@@ -13,7 +14,12 @@ function rePatch() {
const details = window.appDetailsStore.GetAppDetails(appid);
logger.debug('game details', details);
// strShortcutStartDir
const file = await DeckyPluginLoader.openFilePicker(details?.strShortcutStartDir.replaceAll('"', '') || '/');
const file = await DeckyPluginLoader.openFilePicker(
FileSelectionType.FILE,
details?.strShortcutStartDir.replaceAll('"', '') || '/',
true,
true,
);
logger.debug('user selected', file);
window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path));
const pathArr = file.path.split('/');
@@ -28,7 +28,7 @@ const installFromZip = async () => {
logger.error('The default path has not been found!');
return;
}
DeckyPluginLoader.openFilePickerV2(FileSelectionType.FILE, path, true, true, undefined, ['zip'], false, false).then(
DeckyPluginLoader.openFilePicker(FileSelectionType.FILE, path, true, true, undefined, ['zip'], false, false).then(
(val) => {
const url = `file://${val.path}`;
console.log(`Installing plugin locally from ${url}`);
@@ -37,6 +37,8 @@ const installFromZip = async () => {
);
};
const getTabID = DeckyBackend.callable<[name: string], string>('utilities/get_tab_id');
export default function DeveloperSettings() {
const [enableValveInternal, setEnableValveInternal] = useSetting<boolean>('developer.valve_internal', false);
const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false);
@@ -85,7 +87,7 @@ export default function DeveloperSettings() {
<DialogButton
onClick={async () => {
try {
let tabId = await DeckyBackend.call<[name: string], string>('utilities/get_tab_id', 'SharedJSContext');
let tabId = await getTabID('SharedJSContext');
Navigation.NavigateToExternalWeb(
'localhost:8080/devtools/inspector.html?ws=localhost:8080/devtools/page/' + tabId,
);
@@ -75,12 +75,12 @@ export default function UpdaterSettings() {
const { t } = useTranslation();
useEffect(() => {
const a = DeckyBackend.addEventListener('frontend/update_download_percentage', (percentage) => {
const a = DeckyBackend.addEventListener('updater/update_download_percentage', (percentage) => {
setUpdateProgress(percentage);
setIsLoaderUpdating(true);
});
const b = DeckyBackend.addEventListener('frontend/finish_download', () => {
const b = DeckyBackend.addEventListener('updater/finish_download', () => {
setUpdateProgress(0);
setReloading(true);
});
@@ -1,4 +1,12 @@
import { DialogBody, DialogButton, DialogControlsSection, Focusable, Navigation } from 'decky-frontend-lib';
import {
DialogBody,
DialogButton,
DialogControlsSection,
Field,
Focusable,
Navigation,
SteamSpinner,
} from 'decky-frontend-lib';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaDownload, FaInfo } from 'react-icons/fa';
@@ -19,13 +27,23 @@ const downloadTestingVersion = DeckyBackend.callable<[pr_id: number, sha: string
export default function TestingVersionList() {
const { t } = useTranslation();
const [testingVersions, setTestingVersions] = useState<TestingVersion[]>([]);
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
(async () => {
setTestingVersions(await getTestingVersions());
setLoading(false);
})();
}, []);
if (loading) {
return (
<>
<SteamSpinner>{t('Testing.loading')}</SteamSpinner>
</>
);
}
if (testingVersions.length === 0) {
return (
<div>
@@ -37,48 +55,54 @@ export default function TestingVersionList() {
return (
<DialogBody>
<DialogControlsSection>
<h4>{t('Testing.header')}</h4>
<ul style={{ listStyleType: 'none', padding: '0' }}>
{testingVersions.map((version) => {
return (
<li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', paddingBottom: '10px' }}>
<span>
{version.name} <span style={{ opacity: '50%' }}>{'#' + version.id}</span>
</span>
<Focusable style={{ height: '40px', marginLeft: 'auto', display: 'flex' }}>
<DialogButton
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
onClick={() => {
downloadTestingVersion(version.id, version.head_sha);
setSetting('branch', UpdateBranch.Testing);
}}
>
<div
style={{
display: 'flex',
minWidth: '150px',
justifyContent: 'space-between',
alignItems: 'center',
<li>
<Field
label={
<>
{version.name} <span style={{ opacity: '50%' }}>{'#' + version.id}</span>
</>
}
>
<Focusable style={{ height: '40px', marginLeft: 'auto', display: 'flex' }}>
<DialogButton
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
onClick={() => {
downloadTestingVersion(version.id, version.head_sha);
setSetting('branch', UpdateBranch.Testing);
}}
>
{t('Testing.download')}
<FaDownload style={{ paddingLeft: '1rem' }} />
</div>
</DialogButton>
<DialogButton
style={{
height: '40px',
width: '40px',
padding: '10px 12px',
minWidth: '40px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
onClick={() => Navigation.NavigateToExternalWeb(version.link)}
>
<FaInfo />
</DialogButton>
</Focusable>
<div
style={{
display: 'flex',
minWidth: '150px',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{t('Testing.download')}
<FaDownload style={{ paddingLeft: '1rem' }} />
</div>
</DialogButton>
<DialogButton
style={{
height: '40px',
width: '40px',
padding: '10px 12px',
minWidth: '40px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
onClick={() => Navigation.NavigateToExternalWeb(version.link)}
>
<FaInfo />
</DialogButton>
</Focusable>
</Field>
</li>
);
})}
+2 -2
View File
@@ -89,7 +89,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
useEffect(() => {
(async () => {
const res = await getPluginList(selectedSort[0], selectedSort[1]);
logger.log('got data!', res);
logger.debug('got data!', res);
setPluginList(res);
setPluginCount(res.length);
})();
@@ -98,7 +98,7 @@ const BrowseTab: FC<{ setPluginCount: Dispatch<SetStateAction<number | null>> }>
useEffect(() => {
(async () => {
const storeRes = await getStore();
logger.log(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
logger.debug(`store is ${storeRes}, isTesting is ${storeRes === Store.Testing}`);
setIsTesting(storeRes === Store.Testing);
})();
}, []);
+202 -117
View File
@@ -2,7 +2,6 @@ import {
ModalRoot,
PanelSection,
PanelSectionRow,
Patch,
QuickAccessTab,
Router,
findSP,
@@ -26,7 +25,7 @@ import { FrozenPluginService } from './frozen-plugins-service';
import { HiddenPluginsService } from './hidden-plugins-service';
import Logger from './logger';
import { NotificationService } from './notification-service';
import { InstallType, Plugin } from './plugin';
import { InstallType, Plugin, PluginLoadType } from './plugin';
import RouterHook from './router-hook';
import { deinitSteamFixes, initSteamFixes } from './steamfixes';
import { checkForPluginUpdates } from './store';
@@ -41,6 +40,18 @@ const SettingsPage = lazy(() => import('./components/settings'));
const FilePicker = lazy(() => import('./components/modals/filepicker'));
declare global {
interface Window {
__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyPluginBackendAPIInit?: {
connect: (version: number, key: string) => any; // Returns the backend API used above, no real point adding types to this.
};
}
}
const callPluginMethod = DeckyBackend.callable<[pluginName: string, method: string, ...args: any], any>(
'loader/call_plugin_method',
);
class PluginLoader extends Logger {
private plugins: Plugin[] = [];
private tabsHook: TabsHook = new TabsHook();
@@ -55,11 +66,21 @@ class PluginLoader extends Logger {
private reloadLock: boolean = false;
// stores a list of plugin names which requested to be reloaded
private pluginReloadQueue: { name: string; version?: string }[] = [];
private focusWorkaroundPatch?: Patch;
private apiKeys: Map<string, string> = new Map();
constructor() {
super(PluginLoader.name);
console.log(import.meta.url);
DeckyBackend.addEventListener('loader/notify_updates', this.notifyUpdates.bind(this));
DeckyBackend.addEventListener('loader/import_plugin', this.importPlugin.bind(this));
DeckyBackend.addEventListener('loader/unload_plugin', this.unloadPlugin.bind(this));
DeckyBackend.addEventListener('loader/add_plugin_install_prompt', this.addPluginInstallPrompt.bind(this));
DeckyBackend.addEventListener(
'loader/add_multiple_plugins_install_prompt',
this.addMultiplePluginsInstallPrompt.bind(this),
);
this.tabsHook.init();
const TabBadge = () => {
@@ -108,7 +129,10 @@ class PluginLoader extends Logger {
.then(() => this.log('Initialized'));
}
private getPluginsFromBackend = DeckyBackend.callable<[], { name: string; version: string }[]>('loader/get_plugins');
private getPluginsFromBackend = DeckyBackend.callable<
[],
{ name: string; version: string; load_type: PluginLoadType }[]
>('loader/get_plugins');
private async loadPlugins() {
// wait for SP window to exist before loading plugins
@@ -119,7 +143,8 @@ class PluginLoader extends Logger {
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));
if (!this.hasPlugin(plugin.name))
pluginLoadPromises.push(this.importPlugin(plugin.name, plugin.version, plugin.load_type, false));
}
await Promise.all(pluginLoadPromises);
const loadEnd = performance.now();
@@ -256,7 +281,6 @@ class PluginLoader extends Logger {
this.routerHook.removeRoute('/decky/settings');
deinitSteamFixes();
deinitFilepickerPatches();
this.focusWorkaroundPatch?.unpatch();
}
public unloadPlugin(name: string) {
@@ -266,7 +290,12 @@ class PluginLoader extends Logger {
this.deckyState.setPlugins(this.plugins);
}
public async importPlugin(name: string, version?: string | undefined, useQueue: boolean = true) {
public async importPlugin(
name: string,
version?: string | undefined,
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
useQueue: boolean = true,
) {
if (useQueue && this.reloadLock) {
this.log('Reload currently in progress, adding to queue', name);
this.pluginReloadQueue.push({ name, version: version });
@@ -279,7 +308,7 @@ class PluginLoader extends Logger {
this.unloadPlugin(name);
const startTime = performance.now();
await this.importReactPlugin(name, version);
await this.importReactPlugin(name, version, loadType);
const endTime = performance.now();
this.deckyState.setPlugins(this.plugins);
@@ -297,70 +326,94 @@ class PluginLoader extends Logger {
}
}
private async importReactPlugin(name: string, version?: string) {
let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
credentials: 'include',
headers: {
Authentication: deckyAuthToken,
},
});
private async importReactPlugin(
name: string,
version?: string,
loadType: PluginLoadType = PluginLoadType.ESMODULE_V1,
) {
try {
switch (loadType) {
case PluginLoadType.ESMODULE_V1:
const uuid = this.initPluginBackendAPIConnection(name);
let plugin_export: () => Plugin;
try {
plugin_export = await import(`http://127.0.0.1:1337/plugins/${name}/dist/index.js#apiKey=${uuid}`);
} finally {
this.destroyPluginBackendAPIConnection(uuid);
}
let plugin = plugin_export();
if (res.ok) {
try {
let plugin_export = await eval(await res.text());
let plugin = plugin_export(this.createPluginAPI(name));
this.plugins.push({
...plugin,
name: name,
version: version,
});
} catch (e) {
this.error('Error loading plugin ' + name, e);
const TheError: FC<{}> = () => (
<PanelSection>
<PanelSectionRow>
<div
className={quickAccessMenuClasses.FriendsTitle}
style={{ display: 'flex', justifyContent: 'center' }}
>
<TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="error" />
</div>
</PanelSectionRow>
<PanelSectionRow>
<pre style={{ overflowX: 'scroll' }}>
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
</pre>
</PanelSectionRow>
<PanelSectionRow>
<div className={quickAccessMenuClasses.Text}>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_error_uninstall"
i18n_args={{ name: name }}
/>
</div>
</PanelSectionRow>
</PanelSection>
);
this.plugins.push({
name: name,
version: version,
content: <TheError />,
icon: <FaExclamationCircle />,
});
this.toaster.toast({
title: (
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_load_error.toast"
i18n_args={{ name: name }}
/>
),
body: '' + e,
icon: <FaExclamationCircle />,
});
this.plugins.push({
...plugin,
name: name,
version: version,
});
break;
case PluginLoadType.LEGACY_EVAL_IIFE:
let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
credentials: 'include',
headers: {
Authentication: deckyAuthToken,
},
});
if (res.ok) {
let plugin_export: (serverAPI: any) => Plugin = await eval(await res.text());
let plugin = plugin_export(this.createLegacyPluginAPI(name));
this.plugins.push({
...plugin,
name: name,
version: version,
});
} else throw new Error(`${name} frontend_bundle not OK`);
break;
default:
throw new Error(`${name} has no defined loadType.`);
}
} else throw new Error(`${name} frontend_bundle not OK`);
} catch (e) {
this.error('Error loading plugin ' + name, e);
const TheError: FC<{}> = () => (
<PanelSection>
<PanelSectionRow>
<div className={quickAccessMenuClasses.FriendsTitle} style={{ display: 'flex', justifyContent: 'center' }}>
<TranslationHelper trans_class={TranslationClass.PLUGIN_LOADER} trans_text="error" />
</div>
</PanelSectionRow>
<PanelSectionRow>
<pre style={{ overflowX: 'scroll' }}>
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
</pre>
</PanelSectionRow>
<PanelSectionRow>
<div className={quickAccessMenuClasses.Text}>
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_error_uninstall"
i18n_args={{ name: name }}
/>
</div>
</PanelSectionRow>
</PanelSection>
);
this.plugins.push({
name: name,
version: version,
content: <TheError />,
icon: <FaExclamationCircle />,
});
this.toaster.toast({
title: (
<TranslationHelper
trans_class={TranslationClass.PLUGIN_LOADER}
trans_text="plugin_load_error.toast"
i18n_args={{ name: name }}
/>
),
body: '' + e,
icon: <FaExclamationCircle />,
});
}
}
async callServerMethod(methodName: string, args = {}) {
@@ -374,20 +427,20 @@ class PluginLoader extends Logger {
);
}
openFilePicker(
openFilePickerLegacy(
startPath: string,
selectFiles?: boolean,
regex?: RegExp,
): Promise<{ path: string; realpath: string }> {
this.warn('openFilePicker is deprecated and will be removed. Please migrate to openFilePickerV2');
if (selectFiles) {
return this.openFilePickerV2(FileSelectionType.FILE, startPath, true, true, regex);
return this.openFilePicker(FileSelectionType.FILE, startPath, true, true, regex);
} else {
return this.openFilePickerV2(FileSelectionType.FOLDER, startPath, false, true, regex);
return this.openFilePicker(FileSelectionType.FOLDER, startPath, false, true, regex);
}
}
openFilePickerV2(
openFilePicker(
select: FileSelectionType,
startPath: string,
includeFiles?: boolean,
@@ -428,27 +481,84 @@ class PluginLoader extends Logger {
});
}
createPluginAPI(pluginName: string) {
const pluginAPI = {
backend: {
call<Args extends any[] = any[], Return = void>(method: string, ...args: Args): Promise<Return> {
return 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);
},
/* 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;
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 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() };
}
}
destroyPluginBackendAPIConnection(uuid: string) {
if (this.apiKeys.delete(uuid)) {
this.debug(`backend api connection init data destroyed for ${uuid}`);
}
}
initPluginBackendAPI() {
// Things will break *very* badly if plugin code touches this outside of @decky/backend, so lets make that clear.
window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyPluginBackendAPIInit = {
connect: (version: number, key: string) => {
if (!this.apiKeys.has(key)) {
throw new Error(`Backend API key ${key} is invalid.`);
}
const pluginName = this.apiKeys.get(key)!;
if (version <= 0) {
this.destroyPluginBackendAPIConnection(key);
throw new Error(`UUID ${key} requested invalid backend api version ${version}.`);
}
const backendAPI = {
call: (methodName: string, ...args: any) => {
return callPluginMethod(pluginName, methodName, ...args);
},
callable: (methodName: string) => {
return (...args: any) => callPluginMethod(pluginName, methodName, ...args);
},
};
this.destroyPluginBackendAPIConnection(key);
return backendAPI;
},
};
}
initPluginBackendAPIConnection(pluginName: string) {
const key = crypto.randomUUID();
this.apiKeys.set(key, pluginName);
return key;
}
createLegacyPluginAPI(pluginName: string) {
const pluginAPI = {
routerHook: this.routerHook,
toaster: this.toaster,
// Legacy
callServerMethod: this.callServerMethod,
openFilePicker: this.openFilePicker,
openFilePickerV2: this.openFilePickerV2,
openFilePicker: this.openFilePickerLegacy,
openFilePickerV2: this.openFilePicker,
// Legacy
async callPluginMethod(methodName: string, args = {}) {
return DeckyBackend.call<[pluginName: string, methodName: string, kwargs: any], any>(
@@ -458,32 +568,7 @@ class PluginLoader extends Logger {
args,
);
},
/* 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;
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 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() };
}
},
fetchNoCors: this.fetchNoCors,
executeInTab: DeckyBackend.callable<
[tab: String, runAsync: Boolean, code: string],
{ success: boolean; result: any }
+5
View File
@@ -1,3 +1,8 @@
export enum PluginLoadType {
LEGACY_EVAL_IIFE = 0, // legacy, uses legacy serverAPI
ESMODULE_V1 = 1, // esmodule loading with modern @decky/backend apis
}
export interface Plugin {
name: string;
version?: string;
+1 -4
View File
@@ -6,7 +6,6 @@ import PluginLoader from './plugin-loader';
declare global {
export var DeckyPluginLoader: PluginLoader;
export var importDeckyPlugin: Function;
export var deckyHasLoaded: boolean;
export var deckyHasConnectedRDT: boolean | undefined;
export var deckyAuthToken: string;
@@ -45,9 +44,7 @@ declare global {
window?.DeckyPluginLoader?.deinit();
window.DeckyPluginLoader = new PluginLoader();
DeckyPluginLoader.init();
window.importDeckyPlugin = function (name: string, version: string) {
DeckyPluginLoader?.importPlugin(name, version);
};
console.log(import.meta.url);
})();
export default i18n;
+4 -4
View File
@@ -1,5 +1,5 @@
import reloadFix from './reload';
import restartFix from './restart';
// import reloadFix from './reload';
// import restartFix from './restart';
let fixes: Function[] = [];
export function deinitSteamFixes() {
@@ -7,6 +7,6 @@ export function deinitSteamFixes() {
}
export async function initSteamFixes() {
fixes.push(await reloadFix());
fixes.push(await restartFix());
// fixes.push(await reloadFix());
// fixes.push(await restartFix());
}
+1 -1
View File
@@ -28,6 +28,6 @@ export const doRestart = DeckyBackend.callable('updater/do_restart');
export const getVersionInfo = DeckyBackend.callable<[], VerInfo>('updater/get_version_info');
export const checkForUpdates = DeckyBackend.callable<[], VerInfo>('updater/check_for_updates');
DeckyBackend.addEventListener('frontend/finish_download', async () => {
DeckyBackend.addEventListener('updater/finish_download', async () => {
await doRestart();
});
+1 -23
View File
@@ -50,7 +50,6 @@ interface PromiseResolver<T> {
}
export class WSRouter extends Logger {
routes: Map<string, (...args: any) => any> = new Map();
runningCalls: Map<number, PromiseResolver<any>> = new Map();
eventListeners: Map<string, Set<(...args: any) => any>> = new Map();
ws?: WebSocket;
@@ -92,14 +91,6 @@ export class WSRouter extends Logger {
this.ws?.send(JSON.stringify(data));
}
addRoute(name: string, route: (...args: any) => any) {
this.routes.set(name, route);
}
removeRoute(name: string) {
this.routes.delete(name);
}
addEventListener(event: string, listener: (...args: any) => any) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set([listener]));
@@ -123,20 +114,6 @@ export class WSRouter extends Logger {
try {
const data = JSON.parse(msg.data) as Message;
switch (data.type) {
case MessageType.CALL:
if (this.routes.has(data.route)) {
try {
const res = await this.routes.get(data.route)!(...data.args);
this.write({ type: MessageType.REPLY, id: data.id, result: res });
this.debug(`Started JS call ${data.route} ID ${data.id}`);
} catch (e) {
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 ${data.route} does not exist.` });
}
break;
case MessageType.REPLY:
if (this.runningCalls.has(data.id)) {
this.runningCalls.get(data.id)!.resolve(data.result);
@@ -154,6 +131,7 @@ export class WSRouter extends Logger {
break;
case MessageType.EVENT:
this.debug(`Recieved event ${data.event} with args`, data.args);
if (this.eventListeners.has(data.event)) {
for (const listener of this.eventListeners.get(data.event)!) {
(async () => {