eval.rs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #![allow(clippy::await_holding_refcell_ref)]
  2. use async_trait::async_trait;
  3. use dioxus_core::ScopeState;
  4. use std::future::{Future, IntoFuture};
  5. use std::pin::Pin;
  6. use std::rc::Rc;
  7. /// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
  8. /// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
  9. pub trait EvalProvider {
  10. fn new_evaluator(&self, js: String) -> Result<Rc<dyn Evaluator>, EvalError>;
  11. }
  12. /// The platform's evaluator.
  13. #[async_trait(?Send)]
  14. pub trait Evaluator {
  15. /// Sends a message to the evaluated JavaScript.
  16. fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
  17. /// Receive any queued messages from the evaluated JavaScript.
  18. async fn recv(&self) -> Result<serde_json::Value, EvalError>;
  19. /// Gets the return value of the JavaScript
  20. async fn join(&self) -> Result<serde_json::Value, EvalError>;
  21. }
  22. type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
  23. /// Get a struct that can execute any JavaScript.
  24. ///
  25. /// # Safety
  26. ///
  27. /// Please be very careful with this function. A script with too many dynamic
  28. /// parts is practically asking for a hacker to find an XSS vulnerability in
  29. /// it. **This applies especially to web targets, where the JavaScript context
  30. /// has access to most, if not all of your application data.**
  31. pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
  32. &*cx.use_hook(|| {
  33. let eval_provider = cx
  34. .consume_context::<Rc<dyn EvalProvider>>()
  35. .expect("evaluator not provided");
  36. Rc::new(move |script: &str| {
  37. eval_provider
  38. .new_evaluator(script.to_string())
  39. .map(UseEval::new)
  40. }) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
  41. })
  42. }
  43. /// A wrapper around the target platform's evaluator.
  44. #[derive(Clone)]
  45. pub struct UseEval {
  46. evaluator: Rc<dyn Evaluator + 'static>,
  47. }
  48. impl UseEval {
  49. /// Creates a new UseEval
  50. pub fn new(evaluator: Rc<dyn Evaluator + 'static>) -> Self {
  51. Self { evaluator }
  52. }
  53. /// Sends a [`serde_json::Value`] to the evaluated JavaScript.
  54. pub fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
  55. self.evaluator.send(data)
  56. }
  57. /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
  58. pub async fn recv(&self) -> Result<serde_json::Value, EvalError> {
  59. self.evaluator.recv().await
  60. }
  61. /// Gets the return value of the evaluated JavaScript.
  62. pub async fn join(self) -> Result<serde_json::Value, EvalError> {
  63. self.evaluator.join().await
  64. }
  65. }
  66. impl IntoFuture for UseEval {
  67. type Output = Result<serde_json::Value, EvalError>;
  68. type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
  69. fn into_future(self) -> Self::IntoFuture {
  70. Box::pin(self.join())
  71. }
  72. }
  73. /// Represents an error when evaluating JavaScript
  74. #[derive(Debug)]
  75. pub enum EvalError {
  76. /// The provided JavaScript has already been ran.
  77. Finished,
  78. /// The provided JavaScript is not valid and can't be ran.
  79. InvalidJs(String),
  80. /// Represents an error communicating between JavaScript and Rust.
  81. Communication(String),
  82. }