pub extern crate serde;
pub extern crate specta;
pub extern crate specta_macros;
use specta::datatype::Function;
use specta::TypeCollection;
pub use specta_typescript::Typescript;
use std::collections::{BTreeMap, HashMap};
use std::{fmt::Debug, sync::Arc};
use tokio::sync::broadcast::Sender;
use serde::Serialize;
use tauri::ipc::{Invoke, InvokeError};
use tauri::{AppHandle, Emitter, EventTarget, Runtime};
pub use taurpc_macros::{ipc_type, procedures, resolvers};
mod export;
use export::export_types;
pub trait TauRpcHandler<R: Runtime>: Sized {
const TRAIT_NAME: &'static str;
const PATH_PREFIX: &'static str;
const EXPORT_PATH: Option<&'static str>;
fn handle_incoming_request(self, invoke: Invoke<R>);
fn spawn(self) -> Sender<Arc<Invoke<R>>>;
fn args_map() -> String;
fn collect_fn_types(type_map: &mut TypeCollection) -> Vec<Function>;
}
pub fn create_ipc_handler<H, R: Runtime>(
procedures: H,
) -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static
where
H: TauRpcHandler<R> + Send + Sync + 'static + Clone,
{
let args_map = BTreeMap::from([(H::PATH_PREFIX.to_string(), H::args_map())]);
let mut type_map = TypeCollection::default();
let functions = BTreeMap::from([(
H::PATH_PREFIX.to_string(),
H::collect_fn_types(&mut type_map),
)]);
if tauri::is_dev() {
if let Some(export_path) = H::EXPORT_PATH {
export_types(
export_path,
args_map,
specta_typescript::Typescript::default(),
functions,
type_map,
)
.unwrap();
}
}
move |invoke: Invoke<R>| {
procedures.clone().handle_incoming_request(invoke);
true
}
}
#[derive(Serialize, Clone)]
struct Event<S> {
event: S,
event_name: String,
}
#[derive(Debug)]
pub struct EventTrigger<RT: Runtime> {
app_handle: AppHandle<RT>,
path_prefix: String,
target: EventTarget,
}
impl<RT: Runtime> Clone for EventTrigger<RT> {
fn clone(&self) -> Self {
Self {
app_handle: self.app_handle.clone(),
path_prefix: self.path_prefix.clone(),
target: self.target.clone(),
}
}
}
impl<RT: Runtime> EventTrigger<RT> {
pub fn new(app_handle: AppHandle<RT>, path_prefix: String) -> Self {
Self {
app_handle,
path_prefix,
target: EventTarget::Any,
}
}
pub fn new_scoped<I: Into<EventTarget>>(
app_handle: AppHandle<RT>,
path_prefix: String,
target: I,
) -> Self {
Self {
app_handle,
path_prefix,
target: target.into(),
}
}
pub fn new_scoped_from_trigger(trigger: Self, target: EventTarget) -> Self {
Self {
app_handle: trigger.app_handle,
path_prefix: trigger.path_prefix,
target,
}
}
pub fn call<S: Serialize + Clone>(&self, proc_name: &str, event: S) -> tauri::Result<()> {
let event_name = if self.path_prefix.is_empty() {
proc_name.to_string()
} else {
format!("{}.{}", self.path_prefix, proc_name)
};
let event = Event { event_name, event };
let _ = self
.app_handle
.emit_to(self.target.clone(), "TauRpc_event", event);
Ok(())
}
}
#[derive(Default)]
pub struct Router<R: Runtime> {
types: TypeCollection,
handlers: HashMap<String, Sender<Arc<Invoke<R>>>>,
export_path: Option<&'static str>,
args_map_json: BTreeMap<String, String>,
fns_map: BTreeMap<String, Vec<Function>>,
export_config: specta_typescript::Typescript,
}
impl<R: Runtime> Router<R> {
pub fn new() -> Self {
Self {
types: TypeCollection::default(),
handlers: HashMap::new(),
fns_map: BTreeMap::new(),
export_path: None,
args_map_json: BTreeMap::new(),
export_config: specta_typescript::Typescript::default(),
}
}
pub fn export_config(mut self, config: specta_typescript::Typescript) -> Self {
self.export_config = config;
self
}
pub fn merge<H: TauRpcHandler<R>>(mut self, handler: H) -> Self {
if H::EXPORT_PATH.is_some() {
self.export_path = H::EXPORT_PATH;
}
self.args_map_json
.insert(H::PATH_PREFIX.to_string(), H::args_map());
self.fns_map.insert(
H::PATH_PREFIX.to_string(),
H::collect_fn_types(&mut self.types),
);
self.handlers
.insert(H::PATH_PREFIX.to_string(), handler.spawn());
self
}
pub fn into_handler(self) -> impl Fn(Invoke<R>) -> bool {
if tauri::is_dev() {
if let Some(export_path) = self.export_path {
export_types(
export_path,
self.args_map_json.clone(),
self.export_config.clone(),
self.fns_map.clone(),
self.types.clone(),
)
.unwrap();
}
}
move |invoke: Invoke<R>| self.on_command(invoke)
}
fn on_command(&self, invoke: Invoke<R>) -> bool {
let cmd = invoke.message.command();
if !cmd.starts_with("TauRPC__") {
return false;
}
let prefix = cmd[8..].to_string();
let mut prefix = prefix.split('.').collect::<Vec<_>>();
prefix.pop().unwrap();
match self.handlers.get(&prefix.join(".")) {
Some(handler) => {
let _ = handler.send(Arc::new(invoke));
}
None => invoke
.resolver
.invoke_error(InvokeError::from(format!("`{cmd}` not found"))),
};
true
}
}