use async_trait::async_trait;
use deno_core::*;
use deno_runtime::deno_permissions::PermissionsContainer;
use deno_runtime::worker::{MainWorker, WorkerOptions};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::{thread, time::Duration};
use crate::args::Args;
pub type AnyError = error::AnyError;
pub type Permissions = deno_runtime::deno_permissions::Permissions;
pub type PermissionsOptions = deno_runtime::deno_permissions::PermissionsOptions;
#[async_trait]
pub trait Callback {
async fn callback(&mut self, value: serde_json::Value) -> Result<serde_json::Value, AnyError>;
}
pub struct Script {
code: String,
callback: CallbackFunctions,
timeout: Duration,
permissions: Permissions,
worker: Option<MainWorker>,
}
#[derive(Clone)]
struct CallbackFunctions {
functions: HashMap<String, Arc<Mutex<dyn Callback>>>,
}
impl Resource for CallbackFunctions {
fn name(&self) -> std::borrow::Cow<str> {
"CallbackFunctions".into()
}
}
struct ReturnValue {
value: serde_json::Value,
}
impl Resource for ReturnValue {
fn name(&self) -> std::borrow::Cow<str> {
"ReturnValue".into()
}
}
#[op2(async)]
#[serde]
async fn op_callback(
state: Rc<RefCell<OpState>>,
rid: u32,
#[string] name: String,
#[serde] value: serde_json::Value,
) -> Result<serde_json::Value, AnyError> {
let callbacks = match state.borrow().resource_table.get::<CallbackFunctions>(rid) {
Ok(v) => v,
Err(..) => return Err(error::generic_error("No callbacks found in resource table")),
};
let callback = match callbacks.functions.get(&name) {
Some(v) => v,
None => return Err(error::generic_error("No callback function found")),
};
let mut x = callback.lock().unwrap();
x.callback(value).await
}
#[op2]
fn op_return(state: Rc<RefCell<OpState>>, #[serde] args: serde_json::Value) {
state
.borrow_mut()
.resource_table
.add(ReturnValue { value: args });
}
deno_core::extension!(script_runtime, ops = [op_return, op_callback]);
impl Script {
pub fn from_string(code: &str) -> Self {
Script {
code: code.into(),
callback: CallbackFunctions {
functions: HashMap::new(),
},
timeout: Duration::ZERO,
permissions: Permissions::none_without_prompt(),
worker: None,
}
}
pub fn function(mut self, name: String, function: impl Callback + 'static) -> Self {
self.callback
.functions
.insert(name, Arc::new(Mutex::new(function)));
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn permissions(mut self, permissions: Permissions) -> Self {
self.permissions = permissions;
self
}
pub fn build(mut self) -> Result<Self, error::AnyError> {
self.worker = Some(MainWorker::bootstrap_from_options(
url::Url::parse("data:text/plain,").unwrap(),
PermissionsContainer::new(self.permissions.clone()),
WorkerOptions {
extensions: vec![script_runtime::init_ops()],
..Default::default()
},
));
let worker = self.worker.as_mut().unwrap();
worker.execute_script(
"ext:<anon>",
format!(
r#"
//var op_callback, op_return;
import("ext:core/ops").then((imported) => {{
globalThis.op_callback = imported.op_callback;
globalThis.op_return = imported.op_return;
}});
"#
)
.into(),
)?;
let state_rc = worker.js_runtime.op_state();
let mut state = state_rc.borrow_mut();
let callback_rid = state.resource_table.add(self.callback.clone());
for function in &self.callback.functions {
let func_name = function.0;
worker.execute_script(
"ext:<anon>",
format!(
r#"
function {func_name} (value) {{
return /*Deno[Deno.internal].core.ops.*/op_callback({callback_rid}, '{func_name}', value);
}}
"#
)
.into(),
)?;
}
worker.execute_script("ext:<anon>", self.code.clone().into())?;
Ok(self)
}
pub async fn call<T, R>(&mut self, func: &str, args: T) -> Result<R, error::AnyError>
where
T: Args,
R: serde::de::DeserializeOwned,
{
if self.worker.is_none() {
return Err(error::AnyError::msg(
"Script is not properly initialized, did you call build()?",
));
}
let worker = self.worker.as_mut().unwrap();
worker
.js_runtime
.run_event_loop(PollEventLoopOptions::default())
.await?;
let a = args.to_string()?;
if self.timeout > Duration::ZERO {
let handle = worker.js_runtime.v8_isolate().thread_safe_handle();
let timeout = self.timeout;
thread::spawn(move || {
thread::sleep(timeout);
handle.terminate_execution();
});
}
worker.execute_script(
"ext:<anon>",
format!(
r#"
(async () => {{
/*Deno[Deno.internal].core.ops.*/op_return(
{func}.constructor.name === 'AsyncFunction' ? await {func}({a}) : {func}({a})
);
}})();
"#
).into(),
)?;
worker
.js_runtime
.run_event_loop(PollEventLoopOptions::default())
.await?;
let state_rc = worker.js_runtime.op_state();
let mut state = state_rc.borrow_mut();
let result: std::rc::Rc<ReturnValue>;
let mut rid: u32 = 0;
for val in state.resource_table.names() {
if val.1 == "ReturnValue" {
rid = val.0;
}
}
result = state.resource_table.take::<ReturnValue>(rid).unwrap();
Ok(serde_json::from_value(result.value.clone()).unwrap())
}
}
pub async fn eval<R>(expr: &str) -> Result<R, error::AnyError>
where
R: serde::de::DeserializeOwned,
{
let mut script = Script::from_string(&format!(
r#"
function expr() {{
return {expr};
}}
"#
))
.build()?;
script.call("expr", ()).await
}