use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use serde_json::Value;
use tauri::Manager;
use tauri::ipc::{Invoke, InvokeBody, InvokeResolver, InvokeResponseBody, JavaScriptChannelId};
use crate::{Context, DispatchError};
#[derive(Debug, Clone, PartialEq)]
pub enum Outcome {
Resolve(Value),
Reject(Value),
}
pub enum Dispatch {
Sync(Result<Outcome, DispatchError>),
Async(Pin<Box<dyn Future<Output = Result<Outcome, DispatchError>> + Send>>),
}
type DispatchFn = dyn Fn(&Context<'_>, &str, Value) -> Dispatch + Send + Sync;
#[must_use]
pub struct Procedures {
names: Box<[&'static str]>,
dispatch: Box<DispatchFn>,
}
impl Procedures {
#[doc(hidden)]
pub fn new(
names: &'static [&'static str],
dispatch: impl Fn(&Context<'_>, &str, Value) -> Dispatch + Send + Sync + 'static,
) -> Self {
Self {
names: names.into(),
dispatch: Box::new(dispatch),
}
}
#[must_use]
pub fn names(&self) -> &[&'static str] {
&self.names
}
pub fn merge(self, other: Procedures) -> Procedures {
let Procedures {
names: a_names,
dispatch: a,
} = self;
let Procedures {
names: b_names,
dispatch: b,
} = other;
for name in b_names.iter() {
assert!(
!a_names.contains(name),
"tauri_typed_ipc: procedure {name:?} is registered by more than one merged set",
);
}
let names = a_names.iter().chain(b_names.iter()).copied().collect();
let dispatch = move |ctx: &Context<'_>, procedure: &str, args: Value| {
if a_names.contains(&procedure) {
a(ctx, procedure, args)
} else {
b(ctx, procedure, args)
}
};
Procedures {
names,
dispatch: Box::new(dispatch),
}
}
pub fn dispatch(&self, ctx: &Context<'_>, procedure: &str, args: Value) -> Dispatch {
(self.dispatch)(ctx, procedure, args)
}
}
pub fn handler<R: tauri::Runtime>(
procedures: Procedures,
) -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static {
handler_with_fallback(procedures, |_| false)
}
pub fn handler_with_fallback<R, F>(
procedures: Procedures,
fallback: F,
) -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static
where
R: tauri::Runtime,
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
{
move |invoke| {
if !procedures.names().contains(&invoke.message.command()) {
return fallback(invoke);
}
let Invoke {
message, resolver, ..
} = invoke;
let args = match message.payload() {
InvokeBody::Json(value) => value.clone(),
InvokeBody::Raw(_) => {
resolver.reject(DispatchError::RawBody.to_string());
return true;
}
};
let webview = message.webview();
let injectable: [&(dyn Any + Send + Sync); 1] = [webview.app_handle()];
let make_channel = {
let webview = webview.clone();
move |id: JavaScriptChannelId| id.channel_on::<R, InvokeResponseBody>(webview.clone())
};
let ctx = Context::new(&injectable)
.with_state(message.state())
.with_channels(&make_channel);
match procedures.dispatch(&ctx, message.command(), args) {
Dispatch::Sync(result) => settle(resolver, result),
Dispatch::Async(future) => {
tauri::async_runtime::spawn(async move {
settle(resolver, future.await);
});
}
}
true
}
}
#[cfg(feature = "validate")]
pub fn handler_validated<R: tauri::Runtime>(
procedures: Procedures,
validator: crate::Validator,
) -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static {
handler_validated_with_fallback(procedures, validator, |_| false)
}
#[cfg(feature = "validate")]
pub fn handler_validated_with_fallback<R, F>(
procedures: Procedures,
validator: crate::Validator,
fallback: F,
) -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static
where
R: tauri::Runtime,
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
{
let inner = handler_with_fallback(procedures, fallback);
move |invoke| {
let failure = match invoke.message.payload() {
InvokeBody::Json(value) => validator.validate(invoke.message.command(), value).err(),
InvokeBody::Raw(_) => None,
};
if let Some(error) = failure {
invoke
.resolver
.reject(DispatchError::from(error).to_string());
return true;
}
inner(invoke)
}
}
fn settle<R: tauri::Runtime>(resolver: InvokeResolver<R>, result: Result<Outcome, DispatchError>) {
match result {
Ok(Outcome::Resolve(value)) => resolver.resolve(value),
Ok(Outcome::Reject(value)) => resolver.reject(value),
Err(err) => resolver.reject(err.to_string()),
}
}