From c7428dcb082c084cd085c051b62b231d806d2ef0 Mon Sep 17 00:00:00 2001 From: nahkd123 Date: Thu, 29 Jun 2023 21:59:48 +0700 Subject: [PATCH 1/2] External addon loader --- src/Workspace.ts | 10 +++++++++- src/addons/ExternalAddonLoader.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/addons/ExternalAddonLoader.ts diff --git a/src/Workspace.ts b/src/Workspace.ts index f22fba5..477d4e7 100644 --- a/src/Workspace.ts +++ b/src/Workspace.ts @@ -1,9 +1,17 @@ import { LiveNote } from "./midi/Note.js"; +import * as engine from "./index.js"; +import * as stateMachine from "@mixery/state-machine"; + export class Workspace { + addonRequires = new Map(); + public constructor( public readonly audioContext: BaseAudioContext - ) {} + ) { + this.addonRequires.set("@mixery/engine", engine); + this.addonRequires.set("@mixery/state-machine", stateMachine); + } private _lastNoteId = 0; diff --git a/src/addons/ExternalAddonLoader.ts b/src/addons/ExternalAddonLoader.ts new file mode 100644 index 0000000..b1ac294 --- /dev/null +++ b/src/addons/ExternalAddonLoader.ts @@ -0,0 +1,26 @@ +import { Addon } from "../addons/Addon.js"; +import { Workspace } from "../Workspace.js"; + +export class ExternalAddonLoader { + resources: any[] = []; + addon: Addon; + + constructor(public readonly workspace: Workspace) {} + + require(name: string) { + let module = this.workspace.addonRequires.get(name); + if (!module) throw new Error(`Can't find module ${name} (Have you registered your module using Workspace#addonRequires.set() yet?).`); + return module; + } + + runScript(script: string) { + let func = new Function("require", "use", script); + func( + (name: string) => this.require(name), + (addon: Addon) => { + if (this.addon) throw new Error("You can only register 1 addon for each package"); + this.addon = addon; + } + ); + } +} \ No newline at end of file From 6151721db03038ec678c97f0f53478c005ab2842 Mon Sep 17 00:00:00 2001 From: nahkd123 Date: Fri, 30 Jun 2023 23:06:42 +0700 Subject: [PATCH 2/2] Load addons from URL --- src/Workspace.ts | 12 +++++++++-- src/addons/Addon.ts | 5 +++++ src/addons/ExternalAddonLoader.ts | 36 ++++++++++++++++++++++++------- src/test.workspace.ts | 5 ++++- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/Workspace.ts b/src/Workspace.ts index 477d4e7..3148a47 100644 --- a/src/Workspace.ts +++ b/src/Workspace.ts @@ -1,10 +1,12 @@ -import { LiveNote } from "./midi/Note.js"; - import * as engine from "./index.js"; import * as stateMachine from "@mixery/state-machine"; +import { LiveNote } from "./midi/Note.js"; +import { Addon } from "./addons/Addon.js"; +import { ExternalAddonLoader } from "./addons/ExternalAddonLoader.js"; export class Workspace { addonRequires = new Map(); + addons: Addon[] = []; public constructor( public readonly audioContext: BaseAudioContext @@ -18,4 +20,10 @@ export class Workspace { public autoNoteId(note: LiveNote) { note.id = this._lastNoteId++; } + + async loadAddonFromUrl(url: string) { + let addon = await ExternalAddonLoader.loadFromUrl(this, url); + this.addons.push(addon); + return addon; + } } \ No newline at end of file diff --git a/src/addons/Addon.ts b/src/addons/Addon.ts index 8ad3fa5..2e597f3 100644 --- a/src/addons/Addon.ts +++ b/src/addons/Addon.ts @@ -8,6 +8,11 @@ export interface AddonInfo { description?: string; } +export interface AddonMetadata extends AddonInfo { + id: string; + main: string; +} + export type AddonFactory = (addon: Addon) => any; export class Addon { diff --git a/src/addons/ExternalAddonLoader.ts b/src/addons/ExternalAddonLoader.ts index b1ac294..c5d7de0 100644 --- a/src/addons/ExternalAddonLoader.ts +++ b/src/addons/ExternalAddonLoader.ts @@ -1,11 +1,28 @@ -import { Addon } from "../addons/Addon.js"; +import { Addon, AddonMetadata } from "../addons/Addon.js"; import { Workspace } from "../Workspace.js"; export class ExternalAddonLoader { - resources: any[] = []; + metadata: AddonMetadata; addon: Addon; - constructor(public readonly workspace: Workspace) {} + constructor( + public readonly workspace: Workspace + ) {} + + static async loadFromUrl(workspace: Workspace, url: string) { + // Security risk: Loading addons from URLs allows scripts to have access to your + // data (projects, presets, etc) that is stored inside Mixery. + + if (url.endsWith("/")) url = url.substring(0, url.length - 1); + const metadata: AddonMetadata = await (await fetch(`${url}/addon.metadata.json`)).json(); + const script = await (await fetch(`${url}/${metadata.main}`)).text(); + const loader = new ExternalAddonLoader(workspace); + + loader.metadata = metadata; + loader.createAddonInstance(); + loader.runScript(script); + return loader.addon; + } require(name: string) { let module = this.workspace.addonRequires.get(name); @@ -13,14 +30,17 @@ export class ExternalAddonLoader { return module; } + createAddonInstance() { + this.addon = new Addon(this.metadata.id, this.metadata); + } + runScript(script: string) { - let func = new Function("require", "use", script); + let func = new Function("require", "id", "metadata", "addon", script); func( (name: string) => this.require(name), - (addon: Addon) => { - if (this.addon) throw new Error("You can only register 1 addon for each package"); - this.addon = addon; - } + this.metadata.id, + this.metadata, + this.addon ); } } \ No newline at end of file diff --git a/src/test.workspace.ts b/src/test.workspace.ts index 0dd41e9..cc91d8b 100644 --- a/src/test.workspace.ts +++ b/src/test.workspace.ts @@ -78,4 +78,7 @@ function render() { ctx.stroke(); ctx.closePath(); } -window.requestAnimationFrame(render); \ No newline at end of file +window.requestAnimationFrame(render); + +// Expose some methods +globalThis.loadAddonFromUrl = (url: string) => workspace.loadAddonFromUrl(url); \ No newline at end of file