Compare commits

...

3 Commits

Author SHA1 Message Date
Lucas Nogueira
1cb0d8e521 doesnt work :( 2024-03-27 21:36:00 -03:00
Lucas Nogueira
a1fae1baf3 fix async on linux 2024-03-27 21:35:39 -03:00
Lucas Nogueira
82567345d5 wip 2024-03-27 21:12:07 -03:00
11 changed files with 226 additions and 139 deletions

View File

@@ -306,8 +306,8 @@ fn body_async(
let span = tracing::debug_span!("ipc::request::run");
#resolver.respond_async_serialized(async move {
let result = $path(#(#args?),*);
let kind = (&result).async_kind();
kind.future(result).await
let kind = result.async_kind();
kind.future().await
}
.instrument(span));
return true;
@@ -317,8 +317,8 @@ fn body_async(
quote! {
#resolver.respond_async_serialized(async move {
let result = $path(#(#args?),*);
let kind = (&result).async_kind();
kind.future(result).await
let kind = result.async_kind();
kind.future().await
});
return true;
}
@@ -358,8 +358,8 @@ fn body_blocking(
Ok(quote! {
#maybe_span
let result = $path(#(match #args #match_body),*);
let kind = (&result).blocking_kind();
kind.block(result, #resolver);
let kind = result.blocking_kind();
kind.block(#resolver);
return true;
})
}

View File

@@ -20,7 +20,7 @@ use crate::{
Manager, Runtime, State, Webview,
};
use super::{CallbackFn, InvokeBody, InvokeError, IpcResponse, Request, Response};
use super::{CallbackFn, InvokeError, InvokeResponseBody, IpcResponse, Request, Response};
pub const IPC_PAYLOAD_PREFIX: &str = "__CHANNEL__:";
pub const CHANNEL_PLUGIN_NAME: &str = "__TAURI_CHANNEL__";
@@ -33,13 +33,13 @@ static CHANNEL_DATA_COUNTER: AtomicU32 = AtomicU32::new(0);
/// Maps a channel id to a pending data that must be send to the JavaScript side via the IPC.
#[derive(Default, Clone)]
pub struct ChannelDataIpcQueue(pub(crate) Arc<Mutex<HashMap<u32, InvokeBody>>>);
pub struct ChannelDataIpcQueue(pub(crate) Arc<Mutex<HashMap<u32, InvokeResponseBody>>>);
/// An IPC channel.
#[derive(Clone)]
pub struct Channel {
id: u32,
on_message: Arc<dyn Fn(InvokeBody) -> crate::Result<()> + Send + Sync>,
on_message: Arc<dyn Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync>,
}
impl Serialize for Channel {
@@ -130,13 +130,13 @@ impl<'de> Deserialize<'de> for JavaScriptChannelId {
impl Channel {
/// Creates a new channel with the given message handler.
pub fn new<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
pub fn new<F: Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync + 'static>(
on_message: F,
) -> Self {
Self::new_with_id(CHANNEL_COUNTER.fetch_add(1, Ordering::Relaxed), on_message)
}
fn new_with_id<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
fn new_with_id<F: Fn(InvokeResponseBody) -> crate::Result<()> + Send + Sync + 'static>(
id: u32,
on_message: F,
) -> Self {

View File

@@ -183,145 +183,189 @@ impl<'de, R: Runtime> Deserializer<'de> for CommandItem<'de, R> {
#[doc(hidden)]
pub mod private {
use crate::{
ipc::{InvokeBody, InvokeError, InvokeResolver, IpcResponse},
ipc::{InvokeError, InvokeResolver, InvokeResponse, InvokeResponseBody, IpcResponse},
Runtime,
};
use futures_util::{FutureExt, TryFutureExt};
use std::future::Future;
use std::{future::Future, pin::Pin};
#[cfg(feature = "tracing")]
pub use tracing;
// ===== impl IpcResponse =====
pub struct ResponseTag;
pub struct ResponseTag(InvokeResponse);
pub trait ResponseKind {
fn blocking_kind(self) -> ResponseTag;
fn async_kind(self) -> ResponseTag;
}
impl ResponseKind for Vec<u8> {
#[inline(always)]
fn blocking_kind(&self) -> ResponseTag {
ResponseTag
fn blocking_kind(self) -> ResponseTag {
ResponseTag(InvokeResponse::Ok(self.into()))
}
#[inline(always)]
fn async_kind(&self) -> ResponseTag {
ResponseTag
fn async_kind(self) -> ResponseTag {
ResponseTag(InvokeResponse::Ok(self.into()))
}
}
impl<T: IpcResponse> ResponseKind for &T {}
impl<T: IpcResponse + Clone> ResponseKind for &T {
#[inline(always)]
fn blocking_kind(self) -> ResponseTag {
ResponseTag(self.clone().body().map_err(InvokeError::from_error).into())
}
#[inline(always)]
fn async_kind(self) -> ResponseTag {
ResponseTag(self.clone().body().map_err(InvokeError::from_error).into())
}
}
impl ResponseTag {
#[inline(always)]
pub fn block<R, T>(self, value: T, resolver: InvokeResolver<R>)
pub fn block<R>(self, resolver: InvokeResolver<R>)
where
R: Runtime,
T: IpcResponse,
{
resolver.respond(Ok(value))
resolver.respond(self.0)
}
#[inline(always)]
pub fn future<T>(self, value: T) -> impl Future<Output = Result<InvokeBody, InvokeError>>
pub fn future<T>(self) -> impl Future<Output = Result<InvokeResponseBody, InvokeError>>
where
T: IpcResponse,
{
std::future::ready(value.body().map_err(InvokeError::from_error))
std::future::ready(match self.0 {
InvokeResponse::Ok(b) => Ok(b),
InvokeResponse::Err(e) => Err(e),
})
}
}
// ===== Result<impl Serialize, impl Into<InvokeError>> =====
// ===== Result<impl IpcResponse, impl Into<InvokeError>> =====
pub struct ResultTag;
pub struct ResultTag(InvokeResponse);
pub trait ResultKind {
#[inline(always)]
fn blocking_kind(&self) -> ResultTag {
ResultTag
}
fn blocking_kind(self) -> ResultTag;
#[inline(always)]
fn async_kind(&self) -> ResultTag {
ResultTag
}
fn async_kind(self) -> ResultTag;
}
impl<T: IpcResponse, E: Into<InvokeError>> ResultKind for Result<T, E> {}
impl<T: IpcResponse, E: Into<InvokeError>> ResultKind for Result<T, E> {
#[inline(always)]
fn blocking_kind(self) -> ResultTag {
ResultTag(
self
.map_err(Into::into)
.and_then(|r| r.body().map_err(InvokeError::from_error))
.into(),
)
}
#[inline(always)]
fn async_kind(self) -> ResultTag {
ResultTag(
self
.map_err(Into::into)
.and_then(|r| r.body().map_err(InvokeError::from_error))
.into(),
)
}
}
impl ResultTag {
#[inline(always)]
pub fn block<R, T, E>(self, value: Result<T, E>, resolver: InvokeResolver<R>)
where
R: Runtime,
T: IpcResponse,
E: Into<InvokeError>,
{
resolver.respond(value.map_err(Into::into))
pub fn block<R: Runtime>(self, resolver: InvokeResolver<R>) {
resolver.respond(self.0)
}
#[inline(always)]
pub fn future<T, E>(
self,
value: Result<T, E>,
) -> impl Future<Output = Result<InvokeBody, InvokeError>>
where
T: IpcResponse,
E: Into<InvokeError>,
{
std::future::ready(
value
.map_err(Into::into)
.and_then(|value| value.body().map_err(InvokeError::from_error)),
)
pub fn future(self) -> impl Future<Output = Result<InvokeResponseBody, InvokeError>> {
std::future::ready(match self.0 {
InvokeResponse::Ok(b) => Ok(b),
InvokeResponse::Err(e) => Err(e),
})
}
}
// ===== Future<Output = Vec<u8>> =====
pub struct BufferFutureTag<F: Future<Output = Vec<u8>>>(Pin<Box<F>>);
pub trait BufferFutureKind<F: Future<Output = Vec<u8>>> {
fn async_kind(self) -> BufferFutureTag<F>;
}
impl<F: Future<Output = Vec<u8>>> BufferFutureKind<F> for F {
#[inline(always)]
fn async_kind(self) -> BufferFutureTag<F> {
BufferFutureTag(Box::pin(self))
}
}
impl<F: Future<Output = Vec<u8>>> BufferFutureTag<F> {
#[inline(always)]
pub fn future(self) -> impl Future<Output = Result<InvokeResponseBody, InvokeError>> {
self.0.map(|value| Ok(InvokeResponseBody::Raw(value)))
}
}
// ===== Future<Output = impl IpcResponse> =====
pub struct FutureTag;
pub struct FutureTag<T: IpcResponse, F: Future<Output = T>>(Pin<Box<F>>);
pub trait FutureKind {
pub trait FutureKind<T: IpcResponse, F: Future<Output = T>> {
fn async_kind(self) -> FutureTag<T, F>;
}
impl<T: IpcResponse, F: Future<Output = T>> FutureKind<T, F> for F {
#[inline(always)]
fn async_kind(&self) -> FutureTag {
FutureTag
fn async_kind(self) -> FutureTag<T, F> {
FutureTag(Box::pin(self))
}
}
impl<T: IpcResponse, F: Future<Output = T>> FutureKind for &F {}
impl FutureTag {
impl<T: IpcResponse, F: Future<Output = T>> FutureTag<T, F> {
#[inline(always)]
pub fn future<T, F>(self, value: F) -> impl Future<Output = Result<InvokeBody, InvokeError>>
where
T: IpcResponse,
F: Future<Output = T> + Send + 'static,
{
value.map(|value| value.body().map_err(InvokeError::from_error))
pub fn future(self) -> impl Future<Output = Result<InvokeResponseBody, InvokeError>> {
self
.0
.map(|value| value.body().map_err(InvokeError::from_error))
}
}
// ===== Future<Output = Result<impl Serialize, impl Into<InvokeError>>> =====
pub struct ResultFutureTag;
pub struct ResultFutureTag<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>>(
Pin<Box<F>>,
);
pub trait ResultFutureKind {
pub trait ResultFutureKind<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>>
{
fn async_kind(self) -> ResultFutureTag<T, E, F>;
}
impl<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>>
ResultFutureKind<T, E, F> for F
{
#[inline(always)]
fn async_kind(&self) -> ResultFutureTag {
ResultFutureTag
fn async_kind(self) -> ResultFutureTag<T, E, F> {
ResultFutureTag(Box::pin(self))
}
}
impl<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>> ResultFutureKind
for F
impl<T: IpcResponse, E: Into<InvokeError>, F: Future<Output = Result<T, E>>>
ResultFutureTag<T, E, F>
{
}
impl ResultFutureTag {
#[inline(always)]
pub fn future<T, E, F>(self, value: F) -> impl Future<Output = Result<InvokeBody, InvokeError>>
where
T: IpcResponse,
E: Into<InvokeError>,
F: Future<Output = Result<T, E>> + Send,
{
value
pub fn future(self) -> impl Future<Output = Result<InvokeResponseBody, InvokeError>> {
self
.0
.err_into()
.map(|result| result.and_then(|value| value.body().map_err(InvokeError::from_error)))
}

View File

@@ -41,6 +41,33 @@ pub type InvokeResponder<R> =
pub type OwnedInvokeResponder<R> =
dyn FnOnce(Webview<R>, String, InvokeResponse, CallbackFn, CallbackFn) + Send + 'static;
/// Possible values of an IPC payload.
#[derive(Debug, Clone)]
pub enum InvokeResponseBody {
/// Json response.
Json(String),
/// Bytes response.
Raw(Vec<u8>),
}
impl From<String> for InvokeResponseBody {
fn from(value: String) -> Self {
Self::Json(value)
}
}
impl From<Vec<u8>> for InvokeResponseBody {
fn from(value: Vec<u8>) -> Self {
Self::Raw(value)
}
}
impl IpcResponse for InvokeResponseBody {
fn body(self) -> crate::Result<Self> {
Ok(self)
}
}
/// Possible values of an IPC payload.
#[derive(Debug, Clone)]
pub enum InvokeBody {
@@ -68,12 +95,6 @@ impl From<Vec<u8>> for InvokeBody {
}
}
impl IpcResponse for InvokeBody {
fn body(self) -> crate::Result<InvokeBody> {
Ok(self)
}
}
impl InvokeBody {
#[allow(dead_code)]
pub(crate) fn into_json(self) -> JsonValue {
@@ -126,12 +147,12 @@ impl<'a, R: Runtime> CommandArg<'a, R> for Request<'a> {
/// Marks a type as a response to an IPC call.
pub trait IpcResponse {
/// Resolve the IPC response body.
fn body(self) -> crate::Result<InvokeBody>;
fn body(self) -> crate::Result<InvokeResponseBody>;
}
impl<T: Serialize> IpcResponse for T {
fn body(self) -> crate::Result<InvokeBody> {
serde_json::to_value(self)
fn body(self) -> crate::Result<InvokeResponseBody> {
serde_json::to_string(&self)
.map(Into::into)
.map_err(Into::into)
}
@@ -139,18 +160,18 @@ impl<T: Serialize> IpcResponse for T {
/// The IPC request.
pub struct Response {
body: InvokeBody,
body: InvokeResponseBody,
}
impl IpcResponse for Response {
fn body(self) -> crate::Result<InvokeBody> {
fn body(self) -> crate::Result<InvokeResponseBody> {
Ok(self.body)
}
}
impl Response {
/// Defines a response with the given body.
pub fn new(body: impl Into<InvokeBody>) -> Self {
pub fn new(body: impl Into<InvokeResponseBody>) -> Self {
Self { body: body.into() }
}
}
@@ -206,19 +227,20 @@ impl From<crate::Error> for InvokeError {
#[derive(Debug)]
pub enum InvokeResponse {
/// Resolve the promise.
Ok(InvokeBody),
Ok(InvokeResponseBody),
/// Reject the promise.
Err(InvokeError),
}
// TODO
impl Serialize for InvokeResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Ok(InvokeBody::Json(j)) => j.serialize(serializer),
Self::Ok(InvokeBody::Raw(b)) => b.serialize(serializer),
Self::Ok(InvokeResponseBody::Json(j)) => j.serialize(serializer),
Self::Ok(InvokeResponseBody::Raw(b)) => b.serialize(serializer),
Self::Err(e) => e.0.serialize(serializer),
}
}
@@ -304,7 +326,7 @@ impl<R: Runtime> InvokeResolver<R> {
/// Reply to the invoke promise with an async task which is already serialized.
pub fn respond_async_serialized<F>(self, task: F)
where
F: Future<Output = Result<InvokeBody, InvokeError>> + Send + 'static,
F: Future<Output = Result<InvokeResponseBody, InvokeError>> + Send + 'static,
{
crate::async_runtime::spawn(async move {
let response = match task.await {
@@ -322,29 +344,24 @@ impl<R: Runtime> InvokeResolver<R> {
});
}
/// Reply to the invoke promise with a serializable value.
pub fn respond<T: IpcResponse>(self, value: Result<T, InvokeError>) {
/// Reply to the invoke promise with a response value.
pub fn respond(self, response: InvokeResponse) {
Self::return_result(
self.webview,
self.responder,
value.into(),
response,
self.cmd,
self.callback,
self.error,
)
}
/// Resolve the invoke promise with a value.
pub fn resolve<T: IpcResponse>(self, value: T) {
self.respond(Ok(value))
}
/// Reject the invoke promise with a value.
pub fn reject<T: Serialize>(self, value: T) {
Self::return_result(
self.webview,
self.responder,
Result::<(), _>::Err(value).into(),
Result::<&(), _>::Err(value).into(),
self.cmd,
self.callback,
self.error,

View File

@@ -14,7 +14,7 @@ use http::{
HeaderValue, Method, StatusCode,
};
use super::{CallbackFn, InvokeBody, InvokeResponse};
use super::{CallbackFn, InvokeResponse, InvokeResponseBody};
const TAURI_CALLBACK_HEADER_NAME: &str = "Tauri-Callback";
const TAURI_ERROR_HEADER_NAME: &str = "Tauri-Error";
@@ -48,6 +48,16 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
match *request.method() {
Method::POST => {
if let Some(webview) = manager.get_webview(&label) {
#[cfg(target_os = "linux")]
let respond = {
let webview_ = webview.clone();
move |response| {
let _ = webview_.run_on_main_thread(move || {
respond(response);
});
}
};
match parse_invoke_request(&manager, request) {
Ok(request) => {
#[cfg(feature = "tracing")]
@@ -80,11 +90,11 @@ pub fn get<R: Runtime>(manager: Arc<AppManager<R>>, label: String) -> UriSchemeP
.entered();
let (mut response, mime_type) = match response {
InvokeResponse::Ok(InvokeBody::Json(v)) => (
InvokeResponse::Ok(InvokeResponseBody::Json(v)) => (
http::Response::new(serde_json::to_vec(&v).unwrap().into()),
mime::APPLICATION_JSON,
),
InvokeResponse::Ok(InvokeBody::Raw(v)) => (
InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => (
http::Response::new(v.into()),
mime::APPLICATION_OCTET_STREAM,
),
@@ -271,7 +281,6 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
Channel,
};
use crate::sealed::ManagerBase;
use serde_json::Value as JsonValue;
#[cfg(feature = "tracing")]
let _respond_span = tracing::trace_span!(
@@ -303,20 +312,18 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
"ipc::request::response",
response = serde_json::to_string(&response).unwrap(),
mime_type = match &response {
InvokeResponse::Ok(InvokeBody::Json(_)) => mime::APPLICATION_JSON,
InvokeResponse::Ok(InvokeBody::Raw(_)) => mime::APPLICATION_OCTET_STREAM,
InvokeResponse::Ok(InvokeResponseBody::Json(_)) => mime::APPLICATION_JSON,
InvokeResponse::Ok(InvokeResponseBody::Raw(_)) => mime::APPLICATION_OCTET_STREAM,
InvokeResponse::Err(_) => mime::APPLICATION_JSON,
}
.essence_str()
)
.entered();
match &response {
InvokeResponse::Ok(InvokeBody::Json(v)) => {
if !(cfg!(target_os = "macos") || cfg!(target_os = "ios"))
&& matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
{
let _ = Channel::from_callback_fn(webview, callback).send(v);
match response {
InvokeResponse::Ok(InvokeResponseBody::Json(v)) => {
if !(cfg!(target_os = "macos") || cfg!(target_os = "ios")) && v.len() > 4000 {
let _ = Channel::from_callback_fn(webview, callback).send(&v);
} else {
responder_eval(
&webview,
@@ -325,7 +332,7 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
)
}
}
InvokeResponse::Ok(InvokeBody::Raw(v)) => {
InvokeResponse::Ok(InvokeResponseBody::Raw(v)) => {
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
responder_eval(
&webview,
@@ -333,8 +340,7 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
error,
);
} else {
let _ =
Channel::from_callback_fn(webview, callback).send(InvokeBody::Raw(v.clone()));
let _ = Channel::from_callback_fn(webview, callback).send(v);
}
}
InvokeResponse::Err(e) => responder_eval(

View File

@@ -1892,9 +1892,9 @@ dependencies = [
[[package]]
name = "muda"
version = "0.11.5"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c47e7625990fc1af2226ea4f34fb2412b03c12639fcb91868581eb3a6893453"
checksum = "a40c16e25abca53b401d2972e8ad344820e318cf7e00ea8a951a5ca265590295"
dependencies = [
"cocoa",
"crossbeam-channel",
@@ -3152,7 +3152,7 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "tauri"
version = "2.0.0-beta.11"
version = "2.0.0-beta.13"
dependencies = [
"anyhow",
"bytes",
@@ -3201,7 +3201,7 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"anyhow",
"cargo_toml",
@@ -3223,7 +3223,7 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"base64 0.22.0",
"brotli",
@@ -3248,7 +3248,7 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"heck",
"proc-macro2",
@@ -3260,7 +3260,7 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"anyhow",
"glob",
@@ -3286,7 +3286,7 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"gtk",
"http",
@@ -3302,7 +3302,7 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"cocoa",
"gtk",
@@ -3324,7 +3324,7 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-beta.9"
version = "2.0.0-beta.10"
dependencies = [
"aes-gcm",
"brotli",
@@ -3644,9 +3644,9 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.11.3"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4d9ddd4a7c0f3b6862af1c4911b529a49db4ee89310d3a258859c2f5053fdd"
checksum = "454035ff34b8430638c894e6197748578d6b4d449c6edaf8ea854d94e2dd862b"
dependencies = [
"cocoa",
"core-graphics",

View File

@@ -10,9 +10,11 @@ fn main() {
"app-menu",
tauri_build::InlinedPlugin::new().commands(&["toggle", "popup"]),
)
.app_manifest(
tauri_build::AppManifest::new().commands(&["log_operation", "perform_request"]),
),
.app_manifest(tauri_build::AppManifest::new().commands(&[
"download",
"log_operation",
"perform_request",
])),
)
.expect("failed to run tauri-build");
}

View File

@@ -16,6 +16,7 @@
]
},
"allow-perform-request",
"allow-download",
"app-menu:default",
"sample:allow-ping-scoped",
"sample:global-scope",

View File

@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-download"
description = "Enables the download command without any pre-configured scope."
commands.allow = ["download"]
[[permission]]
identifier = "deny-download"
description = "Denies the download command without any pre-configured scope."
commands.deny = ["download"]

View File

@@ -33,7 +33,7 @@ pub fn log_operation(
}
}
#[derive(Serialize)]
#[derive(Clone, Serialize)]
pub struct ApiResponse {
message: String,
}
@@ -45,3 +45,8 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
message: "message response".into(),
}
}
#[command]
pub async fn download() -> Vec<u8> {
vec![1, 2, 3]
}

View File

@@ -142,6 +142,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
.invoke_handler(tauri::generate_handler![
cmd::log_operation,
cmd::perform_request,
cmd::download
])
.build(tauri::tauri_build_context!())
.expect("error while building tauri application");