Run plugins in a QuickJS sandbox in the browser

Adds packages/plugin-sandbox: QuickJS-ng compiled to wasm, running in a
dedicated worker, with a runtime shell inside it that loads a plugin bundle
and answers the same InternalEventPayload events the Node runtime answers.
Plugins are unmodified.

Wires the browser host's template function, authentication, cURL import and
template render commands to it, and relaxes TemplateCallback's Send bound on
wasm32 so the engine's renderer can call back out to a plugin.
This commit is contained in:
Gregory Schier
2026-08-18 15:22:49 -07:00
parent 115615d994
commit f8d6dfbdaa
37 changed files with 3251 additions and 138 deletions
+18 -1
View File
@@ -8,12 +8,29 @@ use std::future::Future;
const MAX_DEPTH: usize = 50;
/// `Send`, except where nothing can be.
///
/// Rendering a template function is a host call, and on a host with one thread
/// it is a call into JavaScript: the future holds a `JsFuture` and the callback
/// holds the `Rc` connection pool, neither of which is `Send` nor can be made
/// so. Every other host spawns rendering onto a thread pool and needs the bound.
/// So the bound belongs to the targets that can keep it, rather than to the
/// trait every host must implement.
#[cfg(not(target_arch = "wasm32"))]
pub trait MaybeSend: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send> MaybeSend for T {}
#[cfg(target_arch = "wasm32")]
pub trait MaybeSend {}
#[cfg(target_arch = "wasm32")]
impl<T> MaybeSend for T {}
pub trait TemplateCallback {
fn run(
&self,
fn_name: &str,
args: HashMap<String, serde_json::Value>,
) -> impl Future<Output = Result<String>> + Send;
) -> impl Future<Output = Result<String>> + MaybeSend;
fn transform_arg(&self, fn_name: &str, arg_name: &str, arg_value: &str) -> Result<String>;
}