From 34501e34b74938f76ec9aec220b40ac8ccbaebb3 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Tue, 9 Sep 2025 15:37:22 -0400 Subject: [PATCH] dev(kernel): make extension variable more global-like The extension pseudo-global was only available at the root synchronous execution context of a module loaded as an extension, which meant it wasn't available for such things as handler functions for listeners even though it looks like it should be lexically scoped. This commit writes a `const` to the top of javascript files in the runtime copy of an extension so that it works as nearly to a real global as possible. A real global isn't possible here for two reasons. First, running in the same v8 VM as Puter means we can't have distinct global objects with the same name across different extensions. Second, we can't use node:vm because we would then be unable to support ES modules, which are experimental in node:vm and also there's no equivalent to createRequire for the linker even if we wanted to require the --experimental-vm-modules flag in our package.json. This seems to be the best we can do given the state of affairs of the runtime. --- src/backend/src/Kernel.js | 42 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/backend/src/Kernel.js b/src/backend/src/Kernel.js index 4977092a2..92959e95d 100644 --- a/src/backend/src/Kernel.js +++ b/src/backend/src/Kernel.js @@ -28,6 +28,9 @@ const { spawn } = require("node:child_process"); const fs = require('fs'); const path_ = require('path'); +const { prependToJSFiles } = require("./kernel/modutil"); + +const uuid = require('uuid'); const { quot } = libs.string; @@ -215,6 +218,14 @@ class Kernel extends AdvancedBase { } fs.mkdirSync('mod_packages'); + // Initialize some globals that external mods depend on + globalThis.__puter_extension_globals__ = { + extensionObjectRegistry: {}, + useapi: this.useapi, + }; + + // Install the mods... + const mod_install_root_context = Context.get(); const mod_installation_promises = []; @@ -292,12 +303,23 @@ class Kernel extends AdvancedBase { }); } + const extension_id = uuid.v4(); + + await prependToJSFiles(mod_package_dir, [ + `const { use, def } = globalThis.__puter_extension_globals__.useapi;`, + `const extension = globalThis.__puter_extension_globals__` + + `.extensionObjectRegistry[${JSON.stringify(extension_id)}];`, + ].join('\n') + '\n'); + const mod_require_dir = path_.join(process.cwd(), mod_package_dir); await this.run_npm_install(mod_require_dir); const mod = new ExtensionModule(); mod.extension = new Extension(); + + globalThis.__puter_extension_globals__.extensionObjectRegistry[extension_id] + = mod.extension; const mod_context = this._create_mod_context(mod_install_root_context, { name: mod_dirname, @@ -306,20 +328,12 @@ class Kernel extends AdvancedBase { mod_path, }); - // This is where the module gets the 'use' and 'def' globals - await this.useapi.awithuse(async () => { - // This is where the module gets the 'extension' global - await useapi.aglobalwith({ - extension: mod.extension, - }, async () => { - const maybe_promise = require(mod_require_dir); - if ( maybe_promise && maybe_promise instanceof Promise ) { - await maybe_promise; - } - // This is where the 'install' event gets triggered - await mod.install(mod_context); - }); - }); + const maybe_promise = require(mod_require_dir); + if ( maybe_promise && maybe_promise instanceof Promise ) { + await maybe_promise; + } + // This is where the 'install' event gets triggered + await mod.install(mod_context); }; _create_mod_context (parent, options) {