1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#![allow(clippy::await_holding_refcell_ref)]

use dioxus_core::prelude::*;
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
use std::future::{poll_fn, Future, IntoFuture};
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};

/// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
/// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
pub trait EvalProvider {
    fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>>;
}

/// The platform's evaluator.
pub trait Evaluator {
    /// Sends a message to the evaluated JavaScript.
    fn send(&self, data: serde_json::Value) -> Result<(), EvalError>;
    /// Receive any queued messages from the evaluated JavaScript.
    fn poll_recv(
        &mut self,
        context: &mut Context<'_>,
    ) -> Poll<Result<serde_json::Value, EvalError>>;
    /// Gets the return value of the JavaScript
    fn poll_join(
        &mut self,
        context: &mut Context<'_>,
    ) -> Poll<Result<serde_json::Value, EvalError>>;
}

type EvalCreator = Rc<dyn Fn(&str) -> UseEval>;

/// Get a struct that can execute any JavaScript.
///
/// # Safety
///
/// Please be very careful with this function. A script with too many dynamic
/// parts is practically asking for a hacker to find an XSS vulnerability in
/// it. **This applies especially to web targets, where the JavaScript context
/// has access to most, if not all of your application data.**
#[must_use]
pub fn eval_provider() -> EvalCreator {
    let eval_provider = consume_context::<Rc<dyn EvalProvider>>();

    Rc::new(move |script: &str| UseEval::new(eval_provider.new_evaluator(script.to_string())))
        as Rc<dyn Fn(&str) -> UseEval>
}

pub fn eval(script: &str) -> UseEval {
    let eval_provider = dioxus_core::prelude::try_consume_context::<Rc<dyn EvalProvider>>()
        // Create a dummy provider that always hiccups when trying to evaluate
        // That way, we can still compile and run the code without a real provider
        .unwrap_or_else(|| {
            struct DummyProvider;
            impl EvalProvider for DummyProvider {
                fn new_evaluator(&self, _js: String) -> GenerationalBox<Box<dyn Evaluator>> {
                    UnsyncStorage::owner().insert(Box::new(DummyEvaluator))
                }
            }

            struct DummyEvaluator;
            impl Evaluator for DummyEvaluator {
                fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
                    Err(EvalError::Unsupported)
                }
                fn poll_recv(
                    &mut self,
                    _context: &mut Context<'_>,
                ) -> Poll<Result<serde_json::Value, EvalError>> {
                    Poll::Ready(Err(EvalError::Unsupported))
                }
                fn poll_join(
                    &mut self,
                    _context: &mut Context<'_>,
                ) -> Poll<Result<serde_json::Value, EvalError>> {
                    Poll::Ready(Err(EvalError::Unsupported))
                }
            }

            Rc::new(DummyProvider) as Rc<dyn EvalProvider>
        });

    UseEval::new(eval_provider.new_evaluator(script.to_string()))
}

/// A wrapper around the target platform's evaluator.
#[derive(Clone, Copy)]
pub struct UseEval {
    evaluator: GenerationalBox<Box<dyn Evaluator>>,
}

impl UseEval {
    /// Creates a new UseEval
    pub fn new(evaluator: GenerationalBox<Box<dyn Evaluator + 'static>>) -> Self {
        Self { evaluator }
    }

    /// Sends a [`serde_json::Value`] to the evaluated JavaScript.
    pub fn send(&self, data: serde_json::Value) -> Result<(), EvalError> {
        self.evaluator.read().send(data)
    }

    /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
    pub async fn recv(&mut self) -> Result<serde_json::Value, EvalError> {
        poll_fn(|cx| match self.evaluator.try_write() {
            Ok(mut evaluator) => evaluator.poll_recv(cx),
            Err(_) => Poll::Ready(Err(EvalError::Finished)),
        })
        .await
    }

    /// Gets the return value of the evaluated JavaScript.
    pub async fn join(self) -> Result<serde_json::Value, EvalError> {
        poll_fn(|cx| match self.evaluator.try_write() {
            Ok(mut evaluator) => evaluator.poll_join(cx),
            Err(_) => Poll::Ready(Err(EvalError::Finished)),
        })
        .await
    }
}

impl IntoFuture for UseEval {
    type Output = Result<serde_json::Value, EvalError>;
    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;

    fn into_future(self) -> Self::IntoFuture {
        Box::pin(self.join())
    }
}

/// Represents an error when evaluating JavaScript
#[derive(Debug)]
pub enum EvalError {
    /// The platform does not support evaluating JavaScript.
    Unsupported,

    /// The provided JavaScript has already been ran.
    Finished,

    /// The provided JavaScript is not valid and can't be ran.
    InvalidJs(String),

    /// Represents an error communicating between JavaScript and Rust.
    Communication(String),
}