1use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::{Debug, Formatter};
6use std::path::Path;
7use std::rc::Rc;
8use std::{thread, time::Duration};
9
10use deno_core::anyhow::Context;
11use deno_core::v8::{Global, Value};
12use deno_core::{op2, serde_v8, v8, Extension, FastString, JsBuffer, JsRuntime, OpDecl, OpState};
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Serialize};
15
16use crate::{AnyError, CallArgs, JsError, JsValue};
17
18pub trait JsApi<'a> {
19 fn from_script(script: &'a mut Script) -> Self
21 where
22 Self: Sized;
23}
24
25pub struct Script {
30 runtime: JsRuntime,
31 last_rid: u32,
32 timeout: Option<Duration>,
33 added_namespaces: BTreeMap<String, Global<Value>>,
34}
35
36impl Debug for Script {
37 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("Script")
39 .field("runtime", &"...")
40 .field("last_rid", &self.last_rid)
41 .field("timeout", &self.timeout)
42 .finish()
43 }
44}
45
46#[derive(Debug, Clone, Deserialize)]
47#[serde(untagged)]
48enum CallResult<R> {
49 Error { error: String },
50 Result(R),
51}
52
53impl Script {
54 const DEFAULT_FILENAME: &'static str = "sandboxed.js";
55
56 pub fn from_string(js_code: &str) -> Result<Self, JsError> {
63 let all_code =
65 "const console = { log: function(expr) { Deno.core.print(expr + '\\n', false); } };"
66 .to_string() + js_code;
67
68 Self::create_script(all_code)
69 }
70
71 pub fn from_file(file: impl AsRef<Path>) -> Result<Self, JsError> {
78 match std::fs::read_to_string(file) {
79 Ok(js_code) => Self::create_script(js_code),
80 Err(e) => Err(JsError::Runtime(AnyError::from(e))),
81 }
82 }
83
84 pub fn new() -> Self {
85 const DECL: OpDecl = op_return();
86 let ext = Extension {
87 ops: Cow::Owned(vec![DECL]),
88 ..Default::default()
89 };
90
91 let runtime = JsRuntime::new(deno_core::RuntimeOptions {
92 module_loader: Some(Rc::new(deno_core::FsModuleLoader)),
93 extensions: vec![ext],
94 ..Default::default()
95 });
96
97 Script {
98 runtime,
99 last_rid: 0,
100 timeout: None,
101 added_namespaces: Default::default(),
102 }
103 }
104
105 pub fn add_script(
106 &mut self,
107 namespace: &str,
108 fn_name: &str,
109 js_code: &str,
110 ) -> Result<(), JsError> {
111 if self.added_namespaces.contains_key(namespace) {
112 return Ok(());
113 }
114
115 let js_code = format!(
116 "
117 var {namespace} = (function() {{
118 {js_code}
119
120 return {{
121 {fn_name}: function (input) {{
122 try {{
123 return {fn_name}(input)
124 }} catch (e) {{
125 return {{ error: `${{e}}` }}
126 }}
127 }}
128 }}
129 }})();
130 {namespace}.{fn_name}
131 "
132 );
133
134 let global = self
136 .runtime
137 .execute_script(Self::DEFAULT_FILENAME, js_code)?;
138
139 self.added_namespaces.insert(namespace.to_string(), global);
140
141 Ok(())
142 }
143
144 pub fn with_timeout(mut self, timeout: Duration) -> Self {
152 assert!(self.timeout.is_none());
153 assert!(timeout > Duration::ZERO);
154
155 self.timeout = Some(timeout);
156 self
157 }
158
159 pub fn call<A, R>(&mut self, fn_name: &str, args_tuple: A) -> Result<R, JsError>
170 where
171 A: CallArgs,
172 R: DeserializeOwned,
173 {
174 let json_args = args_tuple.into_arg_string()?;
175 let json_result = self.call_impl(None, fn_name, json_args)?;
176 let result: R = serde_json::from_value(json_result)?;
177
178 Ok(result)
179 }
180
181 pub fn call_namespace<A, R>(&mut self, namespace: &str, arg: A) -> Result<R, JsError>
182 where
183 A: Serialize,
184 R: DeserializeOwned,
185 {
186 deno_core::futures::executor::block_on(self.runtime.run_event_loop(Default::default()))?;
187
188 let Some(global) = self.added_namespaces.get(namespace) else {
189 return Err(JsError::Runtime(AnyError::msg(
190 "Failed to get namespace function",
191 )));
192 };
193 let scope = &mut self.runtime.handle_scope();
194 let scope = &mut v8::HandleScope::new(scope);
195 let input = serde_v8::to_v8(scope, arg).with_context(|| "Could not serialize arg")?;
196 let local = v8::Local::new(scope, global);
197 let func = v8::Local::<v8::Function>::try_from(local)
198 .with_context(|| "Could not create function out of local")?;
199 let Some(func_res) = func.call(scope, local, &[input]) else {
200 return Err(JsError::Runtime(AnyError::msg("Failed to call func")));
201 };
202 let deserialized_value = serde_v8::from_v8::<serde_json::Value>(scope, func_res)
203 .with_context(|| "Could not serialize func res")?;
204 let sanitized_value = Self::sanitize_number(deserialized_value)?;
205 let result: CallResult<R> = serde_json::from_value(sanitized_value)?;
206 match result {
207 CallResult::Error { error } => Err(JsError::Runtime(AnyError::msg(error))),
208 CallResult::Result(r) => Ok(r),
209 }
210 }
211
212 fn sanitize_number(value: serde_json::Value) -> Result<serde_json::Value, JsError> {
213 match value {
214 serde_json::Value::Number(number) => {
215 if number.is_f64() {
216 let f = number.as_f64().ok_or_else(|| {
217 JsError::Runtime(AnyError::msg("Failed to convert number to f64"))
218 })?;
219
220 if f.fract() == 0.0 {
221 return Ok(serde_json::Value::Number(serde_json::Number::from(
222 f as i64,
223 )));
224 }
225
226 Ok(serde_json::Value::Number(
227 serde_json::Number::from_f64(f).ok_or_else(|| {
228 JsError::Runtime(AnyError::msg("Failed to convert f64 to number"))
229 })?,
230 ))
231 } else if number.is_u64() {
232 Ok(serde_json::Value::Number(
233 number
234 .as_i64()
235 .ok_or_else(|| {
236 JsError::Runtime(AnyError::msg("Failed to convert number to i64"))
237 })?
238 .into(),
239 ))
240 } else if number.is_i64() {
241 Ok(serde_json::Value::Number(number))
242 } else {
243 Err(JsError::Runtime(AnyError::msg("Failed to convert number")))
244 }
245 }
246 serde_json::Value::Object(map) => {
247 let mut new_map = serde_json::Map::new();
248 for (key, value) in map {
249 new_map.insert(key, Self::sanitize_number(value)?);
250 }
251 Ok(serde_json::Value::Object(new_map))
252 }
253 serde_json::Value::Array(vec) => {
254 let mut new_vec = Vec::new();
255 for value in vec {
256 new_vec.push(Self::sanitize_number(value)?);
257 }
258 Ok(serde_json::Value::Array(new_vec))
259 }
260 _ => Ok(value),
261 }
262 }
263
264 pub fn bind_api<'a, A>(&'a mut self) -> A
265 where
266 A: JsApi<'a>,
267 {
268 A::from_script(self)
269 }
270
271 pub(crate) fn call_json(&mut self, fn_name: &str, args: &JsValue) -> Result<JsValue, JsError> {
272 self.call_impl(None, fn_name, args.to_string())
273 }
274
275 fn call_impl(
276 &mut self,
277 namespace: Option<&str>,
278 fn_name: &str,
279 json_args: String,
280 ) -> Result<JsValue, JsError> {
281 deno_core::futures::executor::block_on(self.call_impl_async(namespace, fn_name, json_args))
282 }
283
284 async fn call_impl_async(
285 &mut self,
286 namespace: Option<&str>,
287 fn_name: &str,
288 json_args: String,
289 ) -> Result<JsValue, JsError> {
290 let fn_name = if let Some(namespace) = namespace {
294 Cow::Owned(format!("{namespace}.{fn_name}"))
295 } else {
296 Cow::Borrowed(fn_name)
297 };
298
299 let js_code: String = format!(
301 "(async () => {{
302 let __rust_result = {fn_name}.constructor.name === 'AsyncFunction'
303 ? await {fn_name}({json_args})
304 : {fn_name}({json_args});
305
306 if (typeof __rust_result === 'undefined')
307 __rust_result = null;
308
309 Deno.core.ops.op_return(__rust_result);
310 }})()"
311 );
312
313 if let Some(timeout) = self.timeout {
314 let handle = self.runtime.v8_isolate().thread_safe_handle();
315
316 thread::spawn(move || {
317 thread::sleep(timeout);
318 handle.terminate_execution();
319 });
320 }
321
322 self.runtime
327 .execute_script(Self::DEFAULT_FILENAME, js_code)?;
328
329 self.runtime.run_event_loop(Default::default()).await?;
330
331 let state_rc = self.runtime.op_state();
332 let mut state = state_rc.borrow_mut();
333 let table = &mut state.resource_table;
334
335 let entry: Result<Rc<ResultResource>, deno_core::anyhow::Error> = table.take(self.last_rid);
337
338 match entry {
339 Ok(entry) => {
340 let extracted = Rc::try_unwrap(entry);
341
342 if extracted.is_err() {
343 return Err(JsError::Runtime(AnyError::msg(
344 "Failed to unwrap resource entry",
345 )));
346 }
347
348 let extracted = extracted.unwrap();
349
350 self.last_rid += 1;
351
352 Ok(extracted.json_value)
353 }
354 Err(e) => Err(JsError::Runtime(AnyError::from(e))),
355 }
356 }
357
358 fn create_script<S>(js_code: S) -> Result<Self, JsError>
359 where
360 S: Into<FastString>,
361 {
362 let mut script = Self::new();
363 script
364 .runtime
365 .execute_script(Self::DEFAULT_FILENAME, js_code.into())?;
366 Ok(script)
367 }
368}
369
370#[derive(Debug)]
371struct ResultResource {
372 json_value: JsValue,
373}
374
375impl deno_core::Resource for ResultResource {
377 fn name(&self) -> Cow<str> {
378 "__rust_Result".into()
379 }
380}
381
382#[op2]
383#[serde]
384fn op_return(
385 state: &mut OpState,
386 #[serde] args: JsValue,
387 #[buffer] _buf: Option<JsBuffer>,
388) -> Result<JsValue, deno_core::error::AnyError> {
389 let entry = ResultResource { json_value: args };
390 let resource_table = &mut state.resource_table;
391 let _rid = resource_table.add(entry);
392 Ok(serde_json::Value::Null)
393}