use std::{future::Future, sync::Arc};
use millennium_macros::default_runtime;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use serialize_to_javascript::{default_template, Template};
use crate::{
api::ipc::{format_callback, format_callback_result, CallbackFn},
app::App,
Runtime, StateManager, Window
};
pub type SetupHook<R> = Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>;
pub type InvokeHandler<R> = dyn Fn(Invoke<R>) + Send + Sync + 'static;
pub type InvokeResponder<R> = dyn Fn(Window<R>, InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static;
pub type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static;
#[derive(Template)]
#[default_template("../scripts/ipc.js")]
pub(crate) struct IpcJavascript<'a> {
pub(crate) isolation_origin: &'a str
}
#[cfg(feature = "isolation")]
#[derive(Template)]
#[default_template("../scripts/isolation.js")]
pub(crate) struct IsolationJavascript<'a> {
pub(crate) origin: String,
pub(crate) isolation_src: &'a str,
pub(crate) style: &'a str
}
#[derive(Debug, Clone, Deserialize)]
pub struct PageLoadPayload {
url: String
}
impl PageLoadPayload {
pub fn url(&self) -> &str {
&self.url
}
}
#[derive(Debug, Deserialize)]
pub struct InvokePayload {
pub cmd: String,
#[serde(rename = "__millenniumModule")]
#[doc(hidden)]
pub millennium_module: Option<String>,
pub callback: CallbackFn,
pub error: CallbackFn,
#[serde(flatten)]
pub inner: JsonValue
}
#[default_runtime(crate::MillenniumWebview, millennium_webview)]
#[derive(Debug)]
pub struct Invoke<R: Runtime> {
pub message: InvokeMessage<R>,
pub resolver: InvokeResolver<R>
}
#[derive(Debug)]
pub struct InvokeError(JsonValue);
impl InvokeError {
#[inline(always)]
pub fn from_serde_json(error: serde_json::Error) -> Self {
Self(JsonValue::String(error.to_string()))
}
#[inline(always)]
pub fn from_anyhow(error: anyhow::Error) -> Self {
Self(JsonValue::String(format!("{:#}", error)))
}
}
impl<T: Serialize> From<T> for InvokeError {
#[inline]
fn from(value: T) -> Self {
serde_json::to_value(value).map(Self).unwrap_or_else(Self::from_serde_json)
}
}
impl From<crate::Error> for InvokeError {
#[inline(always)]
fn from(error: crate::Error) -> Self {
Self(JsonValue::String(error.to_string()))
}
}
#[derive(Debug)]
pub enum InvokeResponse {
Ok(JsonValue),
Err(InvokeError)
}
impl InvokeResponse {
#[inline(always)]
pub fn into_result(self) -> Result<JsonValue, JsonValue> {
match self {
Self::Ok(v) => Ok(v),
Self::Err(e) => Err(e.0)
}
}
}
impl<T: Serialize> From<Result<T, InvokeError>> for InvokeResponse {
#[inline]
fn from(result: Result<T, InvokeError>) -> Self {
match result {
Ok(ok) => match serde_json::to_value(ok) {
Ok(value) => Self::Ok(value),
Err(err) => Self::Err(InvokeError::from_serde_json(err))
},
Err(err) => Self::Err(err)
}
}
}
impl From<InvokeError> for InvokeResponse {
fn from(error: InvokeError) -> Self {
Self::Err(error)
}
}
#[default_runtime(crate::MillenniumWebview, millennium_webview)]
#[derive(Debug)]
pub struct InvokeResolver<R: Runtime> {
window: Window<R>,
pub(crate) callback: CallbackFn,
pub(crate) error: CallbackFn
}
impl<R: Runtime> InvokeResolver<R> {
pub(crate) fn new(window: Window<R>, callback: CallbackFn, error: CallbackFn) -> Self {
Self { window, callback, error }
}
pub fn respond_async<T, F>(self, task: F)
where
T: Serialize,
F: Future<Output = Result<T, InvokeError>> + Send + 'static
{
crate::async_runtime::spawn(async move {
Self::return_task(self.window, task, self.callback, self.error).await;
});
}
pub fn respond_async_serialized<F>(self, task: F)
where
F: Future<Output = Result<JsonValue, InvokeError>> + Send + 'static
{
crate::async_runtime::spawn(async move { Self::return_result(self.window, task.await.into(), self.callback, self.error) });
}
pub fn respond<T: Serialize>(self, value: Result<T, InvokeError>) {
Self::return_result(self.window, value.into(), self.callback, self.error)
}
pub fn resolve<T: Serialize>(self, value: T) {
Self::return_result(self.window, Ok(value).into(), self.callback, self.error)
}
pub fn reject<T: Serialize>(self, value: T) {
Self::return_result(self.window, Result::<(), _>::Err(value.into()).into(), self.callback, self.error)
}
pub fn invoke_error(self, error: InvokeError) {
Self::return_result(self.window, error.into(), self.callback, self.error)
}
pub async fn return_task<T, F>(window: Window<R>, task: F, success_callback: CallbackFn, error_callback: CallbackFn)
where
T: Serialize,
F: Future<Output = Result<T, InvokeError>> + Send + 'static
{
let result = task.await;
Self::return_closure(window, || result, success_callback, error_callback)
}
pub(crate) fn return_closure<T: Serialize, F: FnOnce() -> Result<T, InvokeError>>(
window: Window<R>,
f: F,
success_callback: CallbackFn,
error_callback: CallbackFn
) {
Self::return_result(window, f().into(), success_callback, error_callback)
}
pub(crate) fn return_result(window: Window<R>, response: InvokeResponse, success_callback: CallbackFn, error_callback: CallbackFn) {
(window.invoke_responder())(window, response, success_callback, error_callback);
}
}
pub fn window_invoke_responder<R: Runtime>(window: Window<R>, response: InvokeResponse, success_callback: CallbackFn, error_callback: CallbackFn) {
let callback_string = match format_callback_result(response.into_result(), success_callback, error_callback) {
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, &e.to_string()).expect("unable to serialize response string to json")
};
let _ = window.eval(&callback_string);
}
#[default_runtime(crate::MillenniumWebview, millennium_webview)]
#[derive(Debug)]
pub struct InvokeMessage<R: Runtime> {
pub(crate) window: Window<R>,
pub(crate) state: Arc<StateManager>,
pub(crate) command: String,
pub(crate) payload: JsonValue
}
impl<R: Runtime> InvokeMessage<R> {
pub(crate) fn new(window: Window<R>, state: Arc<StateManager>, command: String, payload: JsonValue) -> Self {
Self { window, state, command, payload }
}
#[inline(always)]
pub fn command(&self) -> &str {
&self.command
}
#[inline(always)]
pub fn window(&self) -> Window<R> {
self.window.clone()
}
#[inline(always)]
pub fn window_ref(&self) -> &Window<R> {
&self.window
}
#[inline(always)]
pub fn payload(&self) -> &JsonValue {
&self.payload
}
#[inline(always)]
pub fn state(&self) -> Arc<StateManager> {
self.state.clone()
}
#[inline(always)]
pub fn state_ref(&self) -> &StateManager {
&self.state
}
}