* progress: reworked don't run this, it'll kill your web browser * feat: use_eval but with comms * revision: async recv & recv_sync * revision: use_eval * revision: standard eval interface * revision: use serde_json::Value instead of JsValue * revision: docs * revision: error message * create: desktop eval (wip) * fix: desktop eval * revision: wrap use_eval in Rc<RefCell<_>> * fix: fmt, clippy * fix: desktop tests * revision: change to channel system - fixes clippy errors - fixes playwright tests * fix: tests * fix: eval example * fix: fmt * fix: tests, desktop stuff * fix: tests * feat: drop handler * fix: tests * fix: rustfmt * revision: web promise/callback system * fix: recv error * revision: IntoFuture, functionation * fix: ci * revision: playwright web * remove: unescessary code * remove dioxus-html from public examples * prototype-patch * fix web eval * fix: rustfmt * fix: CI * make use_eval more efficient * implement eval for liveview as well * fix playwright tests * fix clippy * more clippy fixes * fix clippy * fix stack overflow * fix desktop mock * fix clippy --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
97 lines
3.2 KiB
Rust
97 lines
3.2 KiB
Rust
#![allow(clippy::await_holding_refcell_ref)]
|
|
|
|
use async_trait::async_trait;
|
|
use dioxus_core::ScopeState;
|
|
use std::future::{Future, IntoFuture};
|
|
use std::pin::Pin;
|
|
use std::rc::Rc;
|
|
|
|
/// 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) -> Result<Rc<dyn Evaluator>, EvalError>;
|
|
}
|
|
|
|
/// The platform's evaluator.
|
|
#[async_trait(?Send)]
|
|
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.
|
|
async fn recv(&self) -> Result<serde_json::Value, EvalError>;
|
|
/// Gets the return value of the JavaScript
|
|
async fn join(&self) -> Result<serde_json::Value, EvalError>;
|
|
}
|
|
|
|
type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
|
|
|
|
/// 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.**
|
|
pub fn use_eval(cx: &ScopeState) -> &EvalCreator {
|
|
&*cx.use_hook(|| {
|
|
let eval_provider = cx
|
|
.consume_context::<Rc<dyn EvalProvider>>()
|
|
.expect("evaluator not provided");
|
|
|
|
Rc::new(move |script: &str| {
|
|
eval_provider
|
|
.new_evaluator(script.to_string())
|
|
.map(|evaluator| UseEval::new(evaluator))
|
|
}) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>
|
|
})
|
|
}
|
|
|
|
/// A wrapper around the target platform's evaluator.
|
|
#[derive(Clone)]
|
|
pub struct UseEval {
|
|
evaluator: Rc<dyn Evaluator + 'static>,
|
|
}
|
|
|
|
impl UseEval {
|
|
/// Creates a new UseEval
|
|
pub fn new(evaluator: Rc<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.send(data)
|
|
}
|
|
|
|
/// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript.
|
|
pub async fn recv(&self) -> Result<serde_json::Value, EvalError> {
|
|
self.evaluator.recv().await
|
|
}
|
|
|
|
/// Gets the return value of the evaluated JavaScript.
|
|
pub async fn join(self) -> Result<serde_json::Value, EvalError> {
|
|
self.evaluator.join().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 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),
|
|
}
|