feat(core): add state management, closes #1655 (#1665)

* feat(core): add state management, closes #1655

* fix(tests): ignore doc example

* use a trait to manage #[command] parameters

* add docs [skip ci]

* finish command before moving into respond_async

* Revert "finish command before moving into respond_async"

This reverts commit 4651bed5bf.

* refactor: split InvokeMessage into InvokeResolver, add InvokeResponse

* feat: add managed state to the plugin interface

* feat: add commands example

* add change file [skip ci]

* cleanup clones

Co-authored-by: chip reed <chip@chip.sh>
This commit is contained in:
Lucas Fernandes Nogueira 2021-05-02 15:34:15 -03:00 committed by GitHub
parent d92fde7505
commit 8b6f3de0ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1111 additions and 263 deletions

5
.changes/app-state.md Normal file
View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Adds `manage` API to the `Builder` struct, which manages app state.

View File

@ -0,0 +1,5 @@
---
"tauri-macros": patch
---
Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`.

View File

@ -0,0 +1,7 @@
---
"tauri": patch
---
Refactored the `Plugin` trait `initialize` and `extend_api` signatures.
`initialize` now takes the `App` as first argument, and `extend_api` takes a `InvokeResolver` as last argument.
This adds support to managed state on plugins.

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait.

View File

@ -11,6 +11,7 @@ members = [
"examples/api/src-tauri",
"examples/helloworld/src-tauri",
"examples/multiwindow/src-tauri",
"examples/commands/src-tauri",
# used to build updater artifacts
"examples/updater/src-tauri",
]

View File

@ -3,28 +3,35 @@
// SPDX-License-Identifier: MIT
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use quote::{format_ident, quote, TokenStreamExt};
use syn::{
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
ReturnType, Token, Type,
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, Path, ReturnType, Token, Type,
Visibility,
};
pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
// Check if "with_window" attr was passed to macro
let with_window = attrs.iter().any(|a| {
if let NestedMeta::Meta(Meta::Path(path)) = a {
path
.get_ident()
.map(|i| *i == "with_window")
.unwrap_or(false)
} else {
false
}
});
fn fn_wrapper(function: &ItemFn) -> (&Visibility, Ident) {
(
&function.vis,
format_ident!("{}_wrapper", function.sig.ident),
)
}
fn err(function: ItemFn, error_message: &str) -> TokenStream {
let (vis, wrap) = fn_wrapper(&function);
quote! {
#function
#vis fn #wrap<P: ::tauri::Params>(_message: ::tauri::InvokeMessage<P>) {
compile_error!(#error_message);
unimplemented!()
}
}
}
pub fn generate_command(function: ItemFn) -> TokenStream {
let fn_name = function.sig.ident.clone();
let fn_name_str = fn_name.to_string();
let fn_wrapper = format_ident!("{}_wrapper", fn_name);
let (vis, fn_wrapper) = fn_wrapper(&function);
let returns_result = match function.sig.output {
ReturnType::Type(_, ref ty) => match &**ty {
Type::Path(type_path) => {
@ -40,40 +47,45 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
ReturnType::Default => false,
};
// Split function args into names and types
let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
.sig
.inputs
.iter()
.map(|param| {
let mut arg_name = None;
let mut arg_type = None;
if let FnArg::Typed(arg) = param {
if let Pat::Ident(ident) = arg.pat.as_ref() {
arg_name = Some(ident.ident.clone());
}
if let Type::Path(path) = arg.ty.as_ref() {
arg_type = Some(path.path.clone());
}
}
(
arg_name.clone().unwrap(),
arg_type.unwrap_or_else(|| panic!("Invalid type for arg \"{}\"", arg_name.unwrap())),
)
})
.unzip();
let mut invoke_arg_names: Vec<Ident> = Default::default();
let mut invoke_arg_types: Vec<Path> = Default::default();
let mut invoke_args: TokenStream = Default::default();
let window_arg_maybe = match types.first() {
Some(_) if with_window => {
// Remove window arg from list so it isn't expected as arg from JS
types.drain(0..1);
names.drain(0..1);
// Tell wrapper to pass `window` to original function
quote!(_window,)
for param in &function.sig.inputs {
let mut arg_name = None;
let mut arg_type = None;
if let FnArg::Typed(arg) = param {
if let Pat::Ident(ident) = arg.pat.as_ref() {
arg_name = Some(ident.ident.clone());
}
if let Type::Path(path) = arg.ty.as_ref() {
arg_type = Some(path.path.clone());
}
}
// Tell wrapper not to pass `window` to original function
_ => quote!(),
};
let arg_name_ = arg_name.clone().unwrap();
let arg_name_s = arg_name_.to_string();
let arg_type = match arg_type {
Some(arg_type) => arg_type,
None => {
return err(
function.clone(),
&format!("invalid type for arg: {}", arg_name_),
)
}
};
invoke_args.append_all(quote! {
let #arg_name_ = match <#arg_type>::from_command(#fn_name_str, #arg_name_s, &message) {
Ok(value) => value,
Err(e) => return tauri::InvokeResponse::error(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string())
};
});
invoke_arg_names.push(arg_name_.clone());
invoke_arg_types.push(arg_type);
}
let await_maybe = if function.sig.asyncness.is_some() {
quote!(.await)
} else {
@ -86,30 +98,23 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
// note that all types must implement `serde::Serialize`.
let return_value = if returns_result {
quote! {
match #fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe {
Ok(value) => ::core::result::Result::Ok(value),
Err(e) => ::core::result::Result::Err(e),
match #fn_name(#(#invoke_arg_names),*)#await_maybe {
Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(),
Err(e) => ::core::result::Result::<(), _>::Err(e).into(),
}
}
} else {
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe) }
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() }
};
quote! {
#function
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
#[derive(::serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct ParsedArgs {
#(#names: #types),*
}
let _window = message.window();
match ::serde_json::from_value::<ParsedArgs>(message.payload()) {
Ok(parsed_args) => message.respond_async(async move {
#return_value
}),
Err(e) => message.reject(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string()),
}
#vis fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
use ::tauri::command::FromCommand;
resolver.respond_async(async move {
#invoke_args
#return_value
})
}
}
}
@ -134,12 +139,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
});
quote! {
move |message| {
move |message, resolver| {
let cmd = message.command().to_string();
match cmd.as_str() {
#(stringify!(#fn_names) => #fn_wrappers(message),)*
#(stringify!(#fn_names) => #fn_wrappers(message, resolver),)*
_ => {
message.reject(format!("command {} not found", cmd))
resolver.reject(format!("command {} not found", cmd))
},
}
}

View File

@ -5,7 +5,7 @@
extern crate proc_macro;
use crate::context::ContextItems;
use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, ItemFn};
use syn::{parse_macro_input, ItemFn};
mod command;
@ -13,10 +13,9 @@ mod command;
mod context;
#[proc_macro_attribute]
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
pub fn command(_attrs: TokenStream, item: TokenStream) -> TokenStream {
let function = parse_macro_input!(item as ItemFn);
let attrs = parse_macro_input!(attrs as AttributeArgs);
let gen = command::generate_command(attrs, function);
let gen = command::generate_command(function);
gen.into()
}

View File

@ -48,6 +48,7 @@ shared_child = "0.3"
os_pipe = "0.9"
minisign-verify = "0.1.8"
image = "0.23"
state = "0.4"
[build-dependencies]
cfg_aliases = "0.1.1"

300
core/tauri/src/command.rs Normal file
View File

@ -0,0 +1,300 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Useful items for custom commands.
use crate::{InvokeMessage, Params};
use serde::de::Visitor;
use serde::Deserializer;
/// A [`Deserializer`] wrapper around [`Value::get`].
///
/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting
/// an optional item. If the key does exist, the value will be called with [`Value`]'s
/// [`Deserializer`] implementation.
struct KeyedValue<'de> {
command: &'de str,
key: &'de str,
value: &'de serde_json::Value,
}
macro_rules! kv_value {
($s:ident) => {{
use serde::de::Error;
match $s.value.get($s.key) {
Some(value) => value,
None => {
return Err(serde_json::Error::custom(format!(
"command {} missing required key `{}`",
$s.command, $s.key
)))
}
}
}};
}
impl<'de> Deserializer<'de> for KeyedValue<'de> {
type Error = serde_json::Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_any(visitor)
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_bool(visitor)
}
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i8(visitor)
}
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i16(visitor)
}
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i32(visitor)
}
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i64(visitor)
}
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u8(visitor)
}
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u16(visitor)
}
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u32(visitor)
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u64(visitor)
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_f32(visitor)
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_f64(visitor)
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_char(visitor)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_str(visitor)
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_string(visitor)
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_bytes(visitor)
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_byte_buf(visitor)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value.get(self.key) {
Some(value) => value.deserialize_option(visitor),
None => visitor.visit_none(),
}
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_unit(visitor)
}
fn deserialize_unit_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_unit_struct(name, visitor)
}
fn deserialize_newtype_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_newtype_struct(name, visitor)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_seq(visitor)
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_tuple(len, visitor)
}
fn deserialize_tuple_struct<V>(
self,
name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_tuple_struct(name, len, visitor)
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_map(visitor)
}
fn deserialize_struct<V>(
self,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_struct(name, fields, visitor)
}
fn deserialize_enum<V>(
self,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_enum(name, variants, visitor)
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_identifier(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_ignored_any(visitor)
}
}
/// Trait implemented by command arguments to derive a value from a [`InvokeMessage`].
/// [`tauri::Window`], [`tauri::State`] and types that implements [`Deserialize`] automatically implements this trait.
pub trait FromCommand<'de, P: Params>: Sized {
/// Derives an instance of `Self` from the [`InvokeMessage`].
/// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].
///
/// # Arguments
/// - `command`: the command value passed to invoke, e.g. `initialize` on `invoke('initialize', {})`.
/// - `key`: The name of the variable in the command handler, e.g. `value` on `#[command] fn handler(value: u64)`
/// - `message`: The [`InvokeMessage`] instance.
fn from_command(
command: &'de str,
key: &'de str,
message: &'de InvokeMessage<P>,
) -> ::core::result::Result<Self, serde_json::Error>;
}
impl<'de, D: serde::Deserialize<'de>, P: Params> FromCommand<'de, P> for D {
fn from_command(
command: &'de str,
key: &'de str,
message: &'de InvokeMessage<P>,
) -> ::core::result::Result<Self, serde_json::Error> {
D::deserialize(KeyedValue {
command,
key,
value: &message.payload,
})
}
}

View File

@ -4,8 +4,8 @@
use crate::{
api::{config::Config, PackageInfo},
hooks::InvokeMessage,
Params,
hooks::{InvokeMessage, InvokeResolver},
Params, Window,
};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
@ -54,77 +54,106 @@ enum Module {
}
impl Module {
fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config, package_info: PackageInfo) {
let window = message.window();
fn run<M: Params>(
self,
window: Window<M>,
resolver: InvokeResolver<M>,
config: &Config,
package_info: PackageInfo,
) {
match self {
Self::App(cmd) => message.respond_async(async move {
Self::App(cmd) => resolver.respond_async(async move {
cmd
.run(package_info)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Process(cmd) => message
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
Self::Fs(cmd) => message
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
Self::Window(cmd) => message.respond_async(async move {
Self::Process(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Fs(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Window(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.await
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Shell(cmd) => message.respond_async(async move {
Self::Shell(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Event(cmd) => message.respond_async(async move {
Self::Event(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Internal(cmd) => message.respond_async(async move {
Self::Internal(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Dialog(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Dialog(cmd) => message
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
Self::Cli(cmd) => {
if let Some(cli_config) = config.tauri.cli.clone() {
message.respond_async(async move {
resolver.respond_async(async move {
cmd
.run(&cli_config)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
})
}
}
Self::Notification(cmd) => {
let identifier = config.tauri.bundle.identifier.clone();
message.respond_async(async move {
resolver.respond_async(async move {
cmd
.run(identifier)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
})
}
Self::Http(cmd) => message.respond_async(async move {
Self::Http(cmd) => resolver.respond_async(async move {
cmd
.run()
.await
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::GlobalShortcut(cmd) => message.respond_async(async move {
Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
}
}
@ -133,15 +162,17 @@ impl Module {
pub(crate) fn handle<M: Params>(
module: String,
message: InvokeMessage<M>,
resolver: InvokeResolver<M>,
config: &Config,
package_info: &PackageInfo,
) {
let mut payload = message.payload();
let mut payload = message.payload;
if let JsonValue::Object(ref mut obj) = payload {
obj.insert("module".to_string(), JsonValue::String(module));
}
let window = message.window;
match serde_json::from_value::<Module>(payload) {
Ok(module) => module.run(message, config, package_info.clone()),
Err(e) => message.reject(e.to_string()),
Ok(module) => module.run(window, resolver, config, package_info.clone()),
Err(e) => resolver.reject(e.to_string()),
}
}

View File

@ -5,16 +5,17 @@
use crate::{
api::rpc::{format_callback, format_callback_result},
runtime::app::App,
Params, Window,
Params, StateManager, Window,
};
use serde::{Deserialize, Serialize};
use std::future::Future;
use serde_json::Value as JsonValue;
use std::{future::Future, sync::Arc};
/// A closure that is run when the Tauri application is setting up.
pub type SetupHook<M> = Box<dyn Fn(&mut App<M>) -> Result<(), Box<dyn std::error::Error>> + Send>;
/// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>) + Send + Sync + 'static;
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>, InvokeResolver<M>) + Send + Sync + 'static;
/// A closure that is run once every time a window is created and loaded.
pub type OnPageLoad<M> = dyn Fn(Window<M>, PageLoadPayload) + Send + Sync + 'static;
@ -32,33 +33,174 @@ impl PageLoadPayload {
}
}
/// Payload from an invoke call.
#[derive(Debug, Deserialize)]
pub(crate) struct InvokePayload {
#[serde(rename = "__tauriModule")]
pub(crate) tauri_module: Option<String>,
/// Response from a [`InvokeMessage`] passed to the [`InvokeResolver`].
#[derive(Debug)]
pub enum InvokeResponse {
/// Resolve the promise.
Ok(JsonValue),
/// Reject the promise.
Err(JsonValue),
}
impl<T: Serialize, E: Serialize> From<Result<T, E>> for InvokeResponse {
fn from(result: Result<T, E>) -> Self {
match result {
Result::Ok(t) => match serde_json::to_value(t) {
Ok(v) => Self::Ok(v),
Err(e) => Self::Err(JsonValue::String(e.to_string())),
},
Result::Err(e) => Self::error(e),
}
}
}
impl InvokeResponse {
#[doc(hidden)]
pub fn error<T: Serialize>(value: T) -> Self {
match serde_json::to_value(value) {
Ok(v) => Self::Err(v),
Err(e) => Self::Err(JsonValue::String(e.to_string())),
}
}
}
/// Resolver of a invoke message.
pub struct InvokeResolver<M: Params> {
window: Window<M>,
pub(crate) main_thread: bool,
pub(crate) callback: String,
pub(crate) error: String,
#[serde(rename = "mainThread", default)]
pub(crate) main_thread: bool,
#[serde(flatten)]
pub(crate) inner: serde_json::Value,
}
/*impl<P: Params> Clone for InvokeResolver<P> {
fn clone(&self) -> Self {
Self {
window: self.window.clone(),
main_thread: self.main_thread,
callback: self.callback.clone(),
error: self.error.clone(),
}
}
}*/
impl<M: Params> InvokeResolver<M> {
pub(crate) fn new(window: Window<M>, main_thread: bool, callback: String, error: String) -> Self {
Self {
window,
main_thread,
callback,
error,
}
}
/// Reply to the invoke promise with an async task.
pub fn respond_async<F: Future<Output = InvokeResponse> + Send + 'static>(self, task: F) {
if self.main_thread {
crate::async_runtime::block_on(async move {
Self::return_task(self.window, task, self.callback, self.error).await;
});
} else {
crate::async_runtime::spawn(async move {
Self::return_task(self.window, task, self.callback, self.error).await;
});
}
}
/// Reply to the invoke promise running the given closure.
pub fn respond_closure<F: FnOnce() -> InvokeResponse>(self, f: F) {
Self::return_closure(self.window, f, self.callback, self.error)
}
/// Resolve the invoke promise with a value.
pub fn resolve<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<S, ()>::Ok(value).into(),
self.callback,
self.error,
)
}
/// Reject the invoke promise with a value.
pub fn reject<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<(), S>::Err(value).into(),
self.callback,
self.error,
)
}
/// Asynchronously executes the given task
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
///
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
pub async fn return_task<F: std::future::Future<Output = InvokeResponse> + Send + 'static>(
window: Window<M>,
task: F,
success_callback: String,
error_callback: String,
) {
let result = task.await;
Self::return_closure(window, || result, success_callback, error_callback)
}
pub(crate) fn return_closure<F: FnOnce() -> InvokeResponse>(
window: Window<M>,
f: F,
success_callback: String,
error_callback: String,
) {
Self::return_result(window, f(), success_callback, error_callback)
}
pub(crate) fn return_result(
window: Window<M>,
response: InvokeResponse,
success_callback: String,
error_callback: String,
) {
let callback_string = match format_callback_result(
match response {
InvokeResponse::Ok(t) => std::result::Result::Ok(t),
InvokeResponse::Err(e) => std::result::Result::Err(e),
},
success_callback,
error_callback.clone(),
) {
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, &e.to_string())
.expect("unable to serialize shortcut string to json"),
};
let _ = window.eval(&callback_string);
}
}
/// An invoke message.
pub struct InvokeMessage<M: Params> {
window: Window<M>,
/// The window that received the invoke message.
pub(crate) window: Window<M>,
/// Application managed state.
pub(crate) state: Arc<StateManager>,
/// The RPC command.
pub(crate) command: String,
/// Allow our crate to access the payload without cloning it.
pub(crate) payload: InvokePayload,
/// The JSON argument passed on the invoke message.
pub(crate) payload: JsonValue,
}
impl<M: Params> InvokeMessage<M> {
/// Create an new [`InvokeMessage`] from a payload send to a window.
pub(crate) fn new(window: Window<M>, command: String, payload: InvokePayload) -> Self {
pub(crate) fn new(
window: Window<M>,
state: Arc<StateManager>,
command: String,
payload: JsonValue,
) -> Self {
Self {
window,
state,
command,
payload,
}
@ -69,102 +211,8 @@ impl<M: Params> InvokeMessage<M> {
&self.command
}
/// The invoke payload.
pub fn payload(&self) -> serde_json::Value {
self.payload.inner.clone()
}
/// The window that received the invoke.
pub fn window(&self) -> Window<M> {
self.window.clone()
}
/// Reply to the invoke promise with an async task.
pub fn respond_async<
T: Serialize,
Err: Serialize,
F: Future<Output = Result<T, Err>> + Send + 'static,
>(
self,
task: F,
) {
if self.payload.main_thread {
crate::async_runtime::block_on(async move {
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
});
} else {
crate::async_runtime::spawn(async move {
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
});
}
}
/// Reply to the invoke promise running the given closure.
pub fn respond_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(self, f: F) {
Self::return_closure(self.window, f, self.payload.callback, self.payload.error)
}
/// Resolve the invoke promise with a value.
pub fn resolve<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<S, ()>::Ok(value),
self.payload.callback,
self.payload.error,
)
}
/// Reject the invoke promise with a value.
pub fn reject<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<(), S>::Err(value),
self.payload.callback,
self.payload.error,
)
}
/// Asynchronously executes the given task
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
///
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
pub async fn return_task<
T: Serialize,
Err: Serialize,
F: std::future::Future<Output = Result<T, Err>> + Send + 'static,
>(
window: Window<M>,
task: F,
success_callback: String,
error_callback: String,
) {
let result = task.await;
Self::return_closure(window, || result, success_callback, error_callback)
}
pub(crate) fn return_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(
window: Window<M>,
f: F,
success_callback: String,
error_callback: String,
) {
Self::return_result(window, f(), success_callback, error_callback)
}
pub(crate) fn return_result<T: Serialize, Err: Serialize>(
window: Window<M>,
result: Result<T, Err>,
success_callback: String,
error_callback: String,
) {
let callback_string =
match format_callback_result(result, success_callback, error_callback.clone()) {
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, &e.to_string())
.expect("unable to serialize shortcut string to json"),
};
let _ = window.eval(&callback_string);
}
}

View File

@ -18,6 +18,7 @@ pub use tauri_macros::{command, generate_handler};
pub mod api;
/// Async runtime.
pub mod async_runtime;
pub mod command;
/// The Tauri API endpoints.
mod endpoints;
mod error;
@ -27,6 +28,7 @@ pub mod plugin;
pub mod runtime;
/// The Tauri-specific settings for your runtime e.g. notification permission status.
pub mod settings;
mod state;
#[cfg(feature = "updater")]
pub mod updater;
@ -36,27 +38,32 @@ pub type Result<T> = std::result::Result<T, Error>;
/// A task to run on the main thread.
pub type SyncTask = Box<dyn FnOnce() + Send>;
use crate::api::assets::Assets;
use crate::api::config::Config;
use crate::event::{Event, EventHandler};
use crate::runtime::tag::{Tag, TagRef};
use crate::runtime::window::PendingWindow;
use crate::runtime::{Dispatch, Runtime};
use crate::{
api::{assets::Assets, config::Config},
event::{Event, EventHandler},
runtime::{
tag::{Tag, TagRef},
window::PendingWindow,
Dispatch, Runtime,
},
};
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
use std::{borrow::Borrow, collections::HashMap, path::PathBuf};
// Export types likely to be used by the application.
pub use {
api::config::WindowUrl,
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
runtime::app::{App, Builder},
runtime::flavors::wry::Wry,
runtime::webview::{WebviewAttributes, WindowBuilder},
runtime::window::export::Window,
self::api::config::WindowUrl,
self::hooks::{
InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload,
SetupHook,
},
self::runtime::app::{App, Builder},
self::runtime::flavors::wry::Wry,
self::runtime::webview::{WebviewAttributes, WindowBuilder},
self::runtime::window::export::Window,
self::state::{State, StateManager},
};
use std::borrow::Borrow;
/// Reads the config file at compile time and generates a [`Context`] based on its content.
///
/// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of
@ -223,6 +230,23 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
fn windows(&self) -> HashMap<P::Label, Window<P>> {
self.manager().windows()
}
/// Add `state` to the state managed by the application.
/// See [`tauri::Builder#manage`] for instructions.
fn manage<T>(&self, state: T)
where
T: Send + Sync + 'static,
{
self.manager().state().set(state);
}
/// Gets the managed state for the type `T`.
fn state<T>(&self) -> State<'_, T>
where
T: Send + Sync + 'static,
{
self.manager().inner.state.get()
}
}
/// Prevent implementation details from leaking out of the [`Manager`] and [`Params`] traits.

View File

@ -6,8 +6,8 @@
use crate::{
api::config::PluginConfig,
hooks::{InvokeMessage, PageLoadPayload},
Params, Window,
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
App, Params, Window,
};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
@ -22,7 +22,7 @@ pub trait Plugin<M: Params>: Send {
/// Initialize the plugin.
#[allow(unused_variables)]
fn initialize(&mut self, config: JsonValue) -> Result<()> {
fn initialize(&mut self, app: &App<M>, config: JsonValue) -> Result<()> {
Ok(())
}
@ -45,7 +45,7 @@ pub trait Plugin<M: Params>: Send {
/// Add invoke_handler API extension commands.
#[allow(unused_variables)]
fn extend_api(&mut self, message: InvokeMessage<M>) {}
fn extend_api(&mut self, message: InvokeMessage<M>, resolver: InvokeResolver<M>) {}
}
/// Plugin collection type.
@ -70,10 +70,13 @@ impl<M: Params> PluginStore<M> {
}
/// Initializes all plugins in the store.
pub(crate) fn initialize(&mut self, config: &PluginConfig) -> crate::Result<()> {
pub(crate) fn initialize(&mut self, app: &App<M>, config: &PluginConfig) -> crate::Result<()> {
self.store.values_mut().try_for_each(|plugin| {
plugin
.initialize(config.0.get(plugin.name()).cloned().unwrap_or_default())
.initialize(
&app,
config.0.get(plugin.name()).cloned().unwrap_or_default(),
)
.map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
})
}
@ -105,7 +108,7 @@ impl<M: Params> PluginStore<M> {
.for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
}
pub(crate) fn extend_api(&mut self, mut message: InvokeMessage<M>) {
pub(crate) fn extend_api(&mut self, mut message: InvokeMessage<M>, resolver: InvokeResolver<M>) {
let command = message.command.replace("plugin:", "");
let mut tokens = command.split('|');
// safe to unwrap: split always has a least one item
@ -116,9 +119,9 @@ impl<M: Params> PluginStore<M> {
.next()
.map(|c| c.to_string())
.unwrap_or_else(String::new);
plugin.extend_api(message);
plugin.extend_api(message, resolver);
} else {
message.reject(format!("plugin {} not found", target));
resolver.reject(format!("plugin {} not found", target));
}
}
}

View File

@ -4,7 +4,7 @@
use crate::{
api::{assets::Assets, config::WindowUrl},
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload, SetupHook},
plugin::{Plugin, PluginStore},
runtime::{
flavors::wry::Wry,
@ -15,7 +15,7 @@ use crate::{
Dispatch, Runtime,
},
sealed::{ManagerBase, RuntimeOrDispatch},
Context, Manager, Params, Window,
Context, Manager, Params, StateManager, Window,
};
use std::{collections::HashMap, sync::Arc};
@ -126,6 +126,9 @@ where
/// The webview protocols available to all windows.
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
/// App state.
state: StateManager,
}
impl<E, L, A, R> Builder<E, L, A, R>
@ -139,18 +142,20 @@ where
pub fn new() -> Self {
Self {
setup: Box::new(|_| Ok(())),
invoke_handler: Box::new(|_| ()),
invoke_handler: Box::new(|_, _| ()),
on_page_load: Box::new(|_, _| ()),
pending_windows: Default::default(),
plugins: PluginStore::default(),
uri_scheme_protocols: Default::default(),
state: StateManager::new(),
}
}
/// Defines the JS message handler callback.
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F: Fn(InvokeMessage<Args<E, L, A, R>>) + Send + Sync + 'static,
F:
Fn(InvokeMessage<Args<E, L, A, R>>, InvokeResolver<Args<E, L, A, R>>) + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self
@ -180,6 +185,58 @@ where
self
}
/// Add `state` to the state managed by the application.
///
/// This method can be called any number of times as long as each call
/// refers to a different `T`.
///
/// Managed state can be retrieved by any request handler via the
/// [`State`](crate::State) request guard. In particular, if a value of type `T`
/// is managed by Tauri, adding `State<T>` to the list of arguments in a
/// request handler instructs Tauri to retrieve the managed value.
///
/// # Panics
///
/// Panics if state of type `T` is already being managed.
///
/// # Example
///
/// ```rust,ignore
/// use tauri::State;
///
/// struct MyInt(isize);
/// struct MyString(String);
///
/// #[tauri::command]
/// fn int_command(state: State<'_, MyInt>) -> String {
/// format!("The stateful int is: {}", state.0)
/// }
///
/// #[tauri::command]
/// fn string_command<'r>(state: State<'r, MyString>) {
/// println!("state: {}", state.inner().0);
/// }
///
/// fn main() {
/// tauri::Builder::default()
/// .manage(MyInt(10))
/// .manage(MyString("Hello, managed state!".to_string()))
/// .run(tauri::generate_context!())
/// .expect("error while running tauri application");
/// }
/// ```
pub fn manage<T>(self, state: T) -> Self
where
T: Send + Sync + 'static,
{
let type_name = std::any::type_name::<T>();
if !self.state.set(state) {
panic!("state for type '{}' is already being managed", type_name);
}
self
}
/// Creates a new webview.
pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
where
@ -237,6 +294,7 @@ where
self.invoke_handler,
self.on_page_load,
self.uri_scheme_protocols,
self.state,
);
// set up all the windows defined in the config
@ -254,13 +312,13 @@ where
));
}
manager.initialize_plugins()?;
let mut app = App {
runtime: R::new()?,
manager,
};
app.manager.initialize_plugins(&app)?;
let pending_labels = self
.pending_windows
.iter()
@ -284,6 +342,7 @@ where
app.run_updater(main_window);
(self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.to_string()))?;
app.runtime.run();
Ok(())
}

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::runtime::tag::TagRef;
use crate::{
api::{
assets::Assets,
@ -11,16 +10,19 @@ use crate::{
PackageInfo,
},
event::{Event, EventHandler, Listeners},
hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload},
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload},
plugin::PluginStore,
runtime::{
tag::{tags_to_javascript_array, Tag, ToJsString},
webview::{CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler, WindowBuilder},
tag::{tags_to_javascript_array, Tag, TagRef, ToJsString},
webview::{
CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler,
WindowBuilder,
},
window::{DetachedWindow, PendingWindow},
Icon, Runtime,
},
sealed::ParamsBase,
Context, Params, Window,
App, Context, Params, StateManager, Window,
};
use serde::Serialize;
use serde_json::Value as JsonValue;
@ -52,6 +54,7 @@ pub struct InnerWindowManager<P: Params> {
windows: Mutex<HashMap<P::Label, Window<P>>>,
plugins: Mutex<PluginStore<P>>,
listeners: Listeners<P::Event, P::Label>,
pub(crate) state: Arc<StateManager>,
/// The JS message handler.
invoke_handler: Box<InvokeHandler<P>>,
@ -67,7 +70,7 @@ pub struct InnerWindowManager<P: Params> {
salts: Mutex<HashSet<Uuid>>,
package_info: PackageInfo,
/// The webview protocols protocols available to all windows.
uri_scheme_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
}
/// A [Zero Sized Type] marker representing a full [`Params`].
@ -119,13 +122,15 @@ impl<P: Params> WindowManager<P> {
plugins: PluginStore<P>,
invoke_handler: Box<InvokeHandler<P>>,
on_page_load: Box<OnPageLoad<P>>,
uri_scheme_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
state: StateManager,
) -> Self {
Self {
inner: Arc::new(InnerWindowManager {
windows: Mutex::default(),
plugins: Mutex::new(plugins),
listeners: Listeners::default(),
state: Arc::new(state),
invoke_handler,
on_page_load,
config: context.config,
@ -144,6 +149,11 @@ impl<P: Params> WindowManager<P> {
self.inner.windows.lock().expect("poisoned window manager")
}
/// State managed by the application.
pub(crate) fn state(&self) -> Arc<StateManager> {
self.inner.state.clone()
}
// setup content for dev-server
#[cfg(dev)]
fn get_url(&self) -> String {
@ -383,7 +393,7 @@ impl<P: Params> WindowManager<P> {
#[cfg(test)]
mod test {
use super::{Args, WindowManager};
use crate::{generate_context, plugin::PluginStore, runtime::flavors::wry::Wry};
use crate::{generate_context, plugin::PluginStore, runtime::flavors::wry::Wry, StateManager};
#[test]
fn check_get_url() {
@ -391,9 +401,10 @@ mod test {
let manager: WindowManager<Args<String, String, _, Wry>> = WindowManager::with_handlers(
context,
PluginStore::default(),
Box::new(|_| ()),
Box::new(|_, _| ()),
Box::new(|_, _| ()),
Default::default(),
StateManager::new(),
);
#[cfg(custom_protocol)]
@ -405,9 +416,10 @@ mod test {
}
impl<P: Params> WindowManager<P> {
pub fn run_invoke_handler(&self, message: InvokeMessage<P>) {
(self.inner.invoke_handler)(message);
pub fn run_invoke_handler(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
(self.inner.invoke_handler)(message, resolver);
}
pub fn run_on_page_load(&self, window: Window<P>, payload: PageLoadPayload) {
(self.inner.on_page_load)(window.clone(), payload.clone());
self
@ -417,21 +429,23 @@ impl<P: Params> WindowManager<P> {
.expect("poisoned plugin store")
.on_page_load(window, payload);
}
pub fn extend_api(&self, message: InvokeMessage<P>) {
pub fn extend_api(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.extend_api(message);
.extend_api(message, resolver);
}
pub fn initialize_plugins(&self) -> crate::Result<()> {
pub fn initialize_plugins(&self, app: &App<P>) -> crate::Result<()> {
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.initialize(&self.inner.config.plugins)
.initialize(&app, &self.inner.config.plugins)
}
pub fn prepare_window(

View File

@ -9,6 +9,7 @@ use crate::{
api::config::{WindowConfig, WindowUrl},
runtime::window::DetachedWindow,
};
use serde::Deserialize;
use serde_json::Value as JsonValue;
use std::{collections::HashMap, path::PathBuf};
@ -166,3 +167,15 @@ pub(crate) type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, RpcRequest)
/// File drop handler callback
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
pub(crate) type FileDropHandler<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;
#[derive(Deserialize)]
pub(crate) struct InvokePayload {
#[serde(rename = "__tauriModule")]
pub(crate) tauri_module: Option<String>,
pub(crate) callback: String,
pub(crate) error: String,
#[serde(rename = "mainThread", default)]
pub(crate) main_thread: bool,
#[serde(flatten)]
pub(crate) inner: JsonValue,
}

View File

@ -7,10 +7,10 @@
use crate::{
api::config::WindowConfig,
event::{Event, EventHandler},
hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
runtime::{
tag::ToJsString,
webview::{FileDropHandler, WebviewAttributes, WebviewRpcHandler},
webview::{FileDropHandler, InvokePayload, WebviewAttributes, WebviewRpcHandler},
Dispatch, Runtime,
},
sealed::{ManagerBase, RuntimeOrDispatch},
@ -114,6 +114,7 @@ impl<M: Params> PartialEq for DetachedWindow<M> {
/// We want to export the runtime related window at the crate root, but not look like a re-export.
pub(crate) mod export {
use super::*;
use crate::command::FromCommand;
use crate::runtime::{manager::WindowManager, tag::TagRef};
use std::borrow::Borrow;
@ -166,6 +167,16 @@ pub(crate) mod export {
}
}
impl<'de, P: Params> FromCommand<'de, P> for Window<P> {
fn from_command(
_: &'de str,
_: &'de str,
message: &'de InvokeMessage<P>,
) -> Result<Self, serde_json::Error> {
Ok(message.window())
}
}
impl<P: Params> Window<P> {
/// Create a new window that is attached to the manager.
pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
@ -186,14 +197,27 @@ pub(crate) mod export {
manager.run_on_page_load(self, payload);
}
_ => {
let message = InvokeMessage::new(self, command.to_string(), payload);
if let Some(module) = &message.payload.tauri_module {
let message = InvokeMessage::new(
self.clone(),
manager.state(),
command.to_string(),
payload.inner,
);
let resolver =
InvokeResolver::new(self, payload.main_thread, payload.callback, payload.error);
if let Some(module) = &payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(module, message, manager.config(), manager.package_info());
crate::endpoints::handle(
module,
message,
resolver,
manager.config(),
manager.package_info(),
);
} else if command.starts_with("plugin:") {
manager.extend_api(message);
manager.extend_api(message, resolver);
} else {
manager.run_invoke_handler(message);
manager.run_invoke_handler(message, resolver);
}
}
}

63
core/tauri/src/state.rs Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::command::FromCommand;
use crate::{InvokeMessage, Params};
use state::Container;
/// A guard for a state value.
pub struct State<'r, T: Send + Sync + 'static>(&'r T);
impl<'r, T: Send + Sync + 'static> State<'r, T> {
/// Retrieve a borrow to the underlying value with a lifetime of `'r`.
/// Using this method is typically unnecessary as `State` implements
/// [`Deref`] with a [`Deref::Target`] of `T`.
#[inline(always)]
pub fn inner(&self) -> &'r T {
self.0
}
}
impl<T: Send + Sync + 'static> std::ops::Deref for State<'_, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
self.0
}
}
impl<T: Send + Sync + 'static> Clone for State<'_, T> {
fn clone(&self) -> Self {
State(self.0)
}
}
impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> FromCommand<'de, P> for State<'r, T> {
fn from_command(
_: &'de str,
_: &'de str,
message: &'de InvokeMessage<P>,
) -> Result<Self, serde_json::Error> {
Ok(message.state.get())
}
}
/// The Tauri state manager.
pub struct StateManager(pub(crate) Container);
impl StateManager {
pub(crate) fn new() -> Self {
Self(Container::new())
}
pub(crate) fn set<T: Send + Sync + 'static>(&self, state: T) -> bool {
self.0.set(state)
}
/// Gets the state associated with the specified type.
pub fn get<T: Send + Sync + 'static>(&self) -> State<'_, T> {
State(self.0.get())
}
}

View File

@ -11,12 +11,8 @@ pub struct RequestBody {
name: String,
}
#[command(with_window)]
pub fn log_operation<M: tauri::Params>(
_window: tauri::Window<M>,
event: String,
payload: Option<String>,
) {
#[command]
pub fn log_operation(event: String, payload: Option<String>) {
println!("{} {:?}", event, payload);
}

View File

@ -0,0 +1,7 @@
{
"name": "commands",
"version": "1.0.0",
"scripts": {
"tauri": "node ../../tooling/cli.js/bin/tauri"
}
}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri</title>
</head>
<body>
<h1>Tauri Commands</h1>
<div id="response">Response:</div>
<div id="container"></div>
<script>
const responseDiv = document.querySelector('#response')
function runCommand(commandName, args) {
window.__TAURI__.invoke(commandName, args).then(response => {
responseDiv.innerHTML = `Response: Ok(${response})`
}).catch(error => {
responseDiv.innerHTML = `Response: Err(${error})`
})
}
const container = document.querySelector('#container')
const commands = [
{ name: 'simple_command', required: true },
{ name: 'stateful_command', required: false },
{ name: 'async_simple_command', required: true },
{ name: 'async_stateful_command', required: false },
{ name: 'simple_command_with_result', required: true },
{ name: 'stateful_command_with_result', required: false },
{ name: 'async_simple_command_with_result', required: true },
{ name: 'async_stateful_command_with_result', required: false },
]
for (command of commands) {
const { name, required } = command
const button = document.createElement('button')
button.innerHTML = `Run ${name}`;
button.addEventListener("click", function () {
runCommand(name, { argument: 'value' })
if (!required) {
setTimeout(() => {
runCommand(name, {})
}, 1000)
}
});
container.appendChild(button);
}
</script>
</body>
</html>

10
examples/commands/src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
# These are backup files generated by rustfmt
**/*.rs.bk
config.json
bundle.json

View File

@ -0,0 +1 @@
../../../.license_template

View File

@ -0,0 +1,17 @@
[package]
name = "commands"
version = "0.1.0"
description = "A simple Tauri Application showcasing the commands API"
edition = "2018"
[build-dependencies]
tauri-build = { path = "../../../core/tauri-build", features = [ "codegen" ]}
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { path = "../../../core/tauri", features =["api-all"]}
[features]
default = [ "custom-protocol" ]
custom-protocol = [ "tauri/custom-protocol" ]

View File

@ -0,0 +1,7 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
fn main() {
tauri_build::build();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -0,0 +1,92 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
#[derive(Debug)]
struct MyState {
value: u64,
label: String,
}
#[tauri::command]
fn simple_command(argument: String) {
println!("{}", argument);
}
#[tauri::command]
fn stateful_command(argument: Option<String>, state: tauri::State<'_, MyState>) {
println!("{:?} {:?}", argument, state.inner());
}
// Async commands
#[tauri::command]
async fn async_simple_command(argument: String) {
println!("{}", argument);
}
#[tauri::command]
async fn async_stateful_command(argument: Option<String>, state: tauri::State<'_, MyState>) {
println!("{:?} {:?}", argument, state.inner());
}
// ------------------------ Commands returning Result ------------------------
type Result<T> = std::result::Result<T, ()>;
#[tauri::command]
fn simple_command_with_result(argument: String) -> Result<String> {
println!("{}", argument);
Ok(argument)
}
#[tauri::command]
fn stateful_command_with_result(
argument: Option<String>,
state: tauri::State<'_, MyState>,
) -> Result<String> {
println!("{:?} {:?}", argument, state.inner());
Ok(argument.unwrap_or_else(|| "".to_string()))
}
// Async commands
#[tauri::command]
async fn async_simple_command_with_result(argument: String) -> Result<String> {
println!("{}", argument);
Ok(argument)
}
#[tauri::command]
async fn async_stateful_command_with_result(
argument: Option<String>,
state: tauri::State<'_, MyState>,
) -> Result<String> {
println!("{:?} {:?}", argument, state.inner());
Ok(argument.unwrap_or_else(|| "".to_string()))
}
fn main() {
tauri::Builder::default()
.manage(MyState {
value: 0,
label: "Tauri!".into(),
})
.invoke_handler(tauri::generate_handler![
simple_command,
stateful_command,
async_simple_command,
async_stateful_command,
simple_command_with_result,
stateful_command_with_result,
async_simple_command_with_result,
async_stateful_command_with_result
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -0,0 +1,56 @@
{
"build": {
"distDir": "../public",
"devPath": "../public",
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool",
"shortDescription": "",
"longDescription": "",
"deb": {
"depends": [],
"useBootstrapper": false
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "",
"useBootstrapper": false,
"exceptionDomain": ""
}
},
"allowlist": {
"all": true
},
"windows": [
{
"title": "Welcome to Tauri!",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
},
"updater": {
"active": false
}
}
}