diff --git a/.changes/generate_context-test.md b/.changes/generate_context-test.md new file mode 100644 index 000000000..a2f262907 --- /dev/null +++ b/.changes/generate_context-test.md @@ -0,0 +1,6 @@ +--- +"tauri-macros": "patch" +"tauri-codegen": "patch" +--- + +Add support for `test = true` in `generate_context!` macro to skip some code generation that could affect some tests, for now it only skips empedding a plist on macOS. diff --git a/.changes/isolation-raw-request.md b/.changes/isolation-raw-request.md new file mode 100644 index 000000000..05330e428 --- /dev/null +++ b/.changes/isolation-raw-request.md @@ -0,0 +1,5 @@ +--- +"tauri": "patch:bug" +--- + +Fix deserialization of raw invoke requests when using `isolation` pattern. diff --git a/.changes/utils-raw-isolation-payload.md b/.changes/utils-raw-isolation-payload.md new file mode 100644 index 000000000..9b31e05a5 --- /dev/null +++ b/.changes/utils-raw-isolation-payload.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": "patch:feat" +--- + +Add `RawIsolationPayload::content_type` method. diff --git a/core/tauri-build/src/codegen/context.rs b/core/tauri-build/src/codegen/context.rs index 695e8ed73..f003523ad 100644 --- a/core/tauri-build/src/codegen/context.rs +++ b/core/tauri-build/src/codegen/context.rs @@ -129,6 +129,7 @@ impl CodegenContext { root: quote::quote!(::tauri), capabilities: self.capabilities, assets: None, + test: false, })?; // get the full output file path diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 55b00692b..bd48c3c2e 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -43,6 +43,8 @@ pub struct ContextData { pub capabilities: Option>, /// The custom assets implementation pub assets: Option, + /// Skip runtime-only types generation for tests (e.g. embed-plist usage). + pub test: bool, } fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) { @@ -140,8 +142,12 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult { root, capabilities: additional_capabilities, assets, + test, } = data; + #[allow(unused_variables)] + let running_tests = test; + let target = std::env::var("TAURI_ENV_TARGET_TRIPLE") .as_deref() .map(Target::from_triple) @@ -291,7 +297,7 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult { }; #[cfg(target_os = "macos")] - let info_plist = if target == Target::MacOS && dev { + let info_plist = if target == Target::MacOS && dev && !running_tests { let info_plist_path = config_parent.join("Info.plist"); let mut info_plist = if info_plist_path.exists() { plist::Value::from_file(&info_plist_path) diff --git a/core/tauri-macros/src/context.rs b/core/tauri-macros/src/context.rs index 9b166919b..9469ea17f 100644 --- a/core/tauri-macros/src/context.rs +++ b/core/tauri-macros/src/context.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use syn::{ parse::{Parse, ParseBuffer}, punctuated::Punctuated, - Expr, ExprLit, Lit, LitStr, Meta, PathArguments, PathSegment, Token, + Expr, ExprLit, Lit, LitBool, LitStr, Meta, PathArguments, PathSegment, Token, }; use tauri_codegen::{context_codegen, get_config, ContextData}; use tauri_utils::{config::parse::does_supported_file_name_exist, platform::Target}; @@ -18,6 +18,7 @@ pub(crate) struct ContextItems { root: syn::Path, capabilities: Option>, assets: Option, + test: bool, } impl Parse for ContextItems { @@ -31,6 +32,7 @@ impl Parse for ContextItems { let mut root = None; let mut capabilities = None; let mut assets = None; + let mut test = false; let config_file = input.parse::().ok().map(|raw| { let _ = input.parse::(); let path = PathBuf::from(raw.value()); @@ -93,6 +95,17 @@ impl Parse for ContextItems { "assets" => { assets.replace(v.value); } + "test" => { + if let Expr::Lit(ExprLit { + lit: Lit::Bool(LitBool { value, .. }), + .. + }) = v.value + { + test = value; + } else { + return Err(syn::Error::new(input.span(), "unexpected value for test")); + } + } name => { return Err(syn::Error::new( input.span(), @@ -105,6 +118,8 @@ impl Parse for ContextItems { return Err(syn::Error::new(input.span(), "unexpected list input")); } } + + let _ = input.parse::(); } Ok(Self { @@ -128,6 +143,7 @@ impl Parse for ContextItems { }), capabilities, assets, + test, }) } } @@ -142,6 +158,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream { root: context.root.to_token_stream(), capabilities: context.capabilities, assets: context.assets, + test: context.test, }) .and_then(|data| context_codegen(data).map_err(|e| e.to_string())); diff --git a/core/tauri-plugin/src/build/mod.rs b/core/tauri-plugin/src/build/mod.rs index a9e498f27..e4c632ac9 100644 --- a/core/tauri-plugin/src/build/mod.rs +++ b/core/tauri-plugin/src/build/mod.rs @@ -124,7 +124,7 @@ impl<'a> Builder<'a> { acl::build::generate_docs( &permissions, &autogenerated, - &name.strip_prefix("tauri-plugin-").unwrap_or(&name), + name.strip_prefix("tauri-plugin-").unwrap_or(&name), )?; } diff --git a/core/tauri-utils/src/pattern/isolation.js b/core/tauri-utils/src/pattern/isolation.js index f1908bffc..880ceb212 100644 --- a/core/tauri-utils/src/pattern/isolation.js +++ b/core/tauri-utils/src/pattern/isolation.js @@ -37,20 +37,27 @@ * @param {object} data * @return {Promise<{nonce: number[], payload: number[]}>} */ - async function encrypt(data) { + async function encrypt(payload) { const algorithm = Object.create(null) algorithm.name = 'AES-GCM' algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12)) - const encoder = new TextEncoder() - const encoded = encoder.encode(__RAW_process_ipc_message_fn__(data).data) + const { contentType, data } = __RAW_process_ipc_message_fn__(payload) + + const message = + typeof data === 'string' + ? new TextEncoder().encode(data) + : ArrayBuffer.isView(data) || data instanceof ArrayBuffer + ? data + : new Uint8Array(data) return window.crypto.subtle - .encrypt(algorithm, aesGcmKey, encoded) + .encrypt(algorithm, aesGcmKey, message) .then((payload) => { const result = Object.create(null) result.nonce = Array.from(new Uint8Array(algorithm.iv)) result.payload = Array.from(new Uint8Array(payload)) + result.contentType = contentType return result }) } @@ -66,7 +73,9 @@ const keys = data.payload ? Object.keys(data.payload) : [] return ( keys.length > 0 && - keys.every((key) => key === 'nonce' || key === 'payload') + keys.every( + (key) => key === 'nonce' || key === 'payload' || key === 'contentType' + ) ) } return false diff --git a/core/tauri-utils/src/pattern/isolation.rs b/core/tauri-utils/src/pattern/isolation.rs index afffdf487..11351005b 100644 --- a/core/tauri-utils/src/pattern/isolation.rs +++ b/core/tauri-utils/src/pattern/isolation.rs @@ -73,6 +73,14 @@ impl AesGcmPair { pub fn key(&self) -> &Aes256Gcm { &self.key } + + #[doc(hidden)] + pub fn encrypt(&self, nonce: &[u8; 12], payload: &[u8]) -> Result, Error> { + self + .key + .encrypt(nonce.into(), payload) + .map_err(|_| self::Error::Aes) + } } /// All cryptographic keys required for Isolation encryption @@ -97,7 +105,7 @@ impl Keys { /// Decrypts a message using the generated keys. pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result, Error> { - let RawIsolationPayload { nonce, payload } = raw; + let RawIsolationPayload { nonce, payload, .. } = raw; let nonce: [u8; 12] = nonce.as_ref().try_into()?; self .aes_gcm @@ -109,9 +117,18 @@ impl Keys { /// Raw representation of #[derive(Debug, serde::Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RawIsolationPayload<'a> { nonce: Cow<'a, [u8]>, payload: Cow<'a, [u8]>, + content_type: Cow<'a, str>, +} + +impl<'a> RawIsolationPayload<'a> { + /// Content type of this payload. + pub fn content_type(&self) -> &Cow<'a, str> { + &self.content_type + } } impl<'a> TryFrom<&'a Vec> for RawIsolationPayload<'a> { diff --git a/core/tauri/scripts/ipc.js b/core/tauri/scripts/ipc.js index 77b21f5d3..28cf33150 100644 --- a/core/tauri/scripts/ipc.js +++ b/core/tauri/scripts/ipc.js @@ -39,7 +39,9 @@ const keys = Object.keys(event.data.payload || {}) return ( keys.length > 0 && - keys.every((key) => key === 'nonce' || key === 'payload') + keys.every( + (key) => key === 'contentType' || key === 'nonce' || key === 'payload' + ) ) } return false diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index 1c02986e3..cc05a08a8 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -46,6 +46,7 @@ pub type OwnedInvokeResponder = /// Possible values of an IPC payload. #[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] pub enum InvokeBody { /// Json payload. Json(JsonValue), diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index 3d06ecd44..ad67d70b2 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -388,6 +388,15 @@ fn parse_invoke_request( // so we must ignore it because some commands use the IPC for faster response let has_payload = !body.is_empty(); + #[allow(unused_mut)] + let mut content_type = parts + .headers + .get(http::header::CONTENT_TYPE) + .and_then(|h| h.to_str().ok()) + .map(|mime| mime.parse()) + .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM)) + .map_err(|_| "unknown content type")?; + #[cfg(feature = "isolation")] if let crate::Pattern::Isolation { crypto_keys, .. } = &*manager.pattern { // if the platform does not support request body, we ignore it @@ -395,8 +404,18 @@ fn parse_invoke_request( #[cfg(feature = "tracing")] let _span = tracing::trace_span!("ipc::request::decrypt_isolation_payload").entered(); - body = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) - .and_then(|raw| crypto_keys.decrypt(raw)) + (body, content_type) = crate::utils::pattern::isolation::RawIsolationPayload::try_from(&body) + .and_then(|raw| { + let content_type = raw.content_type().clone(); + crypto_keys.decrypt(raw).map(|decrypted| { + ( + decrypted, + content_type + .parse() + .unwrap_or(mime::APPLICATION_OCTET_STREAM), + ) + }) + }) .map_err(|e| e.to_string())?; } } @@ -440,14 +459,6 @@ fn parse_invoke_request( .map_err(|_| "Tauri error header value must be a numeric string")?, ); - let content_type = parts - .headers - .get(http::header::CONTENT_TYPE) - .and_then(|h| h.to_str().ok()) - .map(|mime| mime.parse()) - .unwrap_or(Ok(mime::APPLICATION_OCTET_STREAM)) - .map_err(|_| "unknown content type")?; - #[cfg(feature = "tracing")] let span = tracing::trace_span!("ipc::request::deserialize").entered(); @@ -481,3 +492,194 @@ fn parse_invoke_request( Ok(payload) } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + use crate::{manager::AppManager, plugin::PluginStore, StateManager, Wry}; + use http::header::*; + use serde_json::json; + use tauri_macros::generate_context; + + #[test] + fn parse_invoke_request() { + let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate, test = true); + let manager: AppManager = AppManager::with_handlers( + context, + PluginStore::default(), + Box::new(|_| false), + None, + Default::default(), + StateManager::new(), + Default::default(), + Default::default(), + Default::default(), + (None, "".into()), + crate::generate_invoke_key().unwrap(), + ); + + let cmd = "write_something"; + let url = "tauri://localhost"; + let invoke_key = "1234ahdsjkl123"; + let callback = 12378123; + let error = 6243; + let headers = HeaderMap::from_iter(vec![ + ( + CONTENT_TYPE, + HeaderValue::from_str(mime::APPLICATION_OCTET_STREAM.as_ref()).unwrap(), + ), + ( + HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(), + HeaderValue::from_str(invoke_key).unwrap(), + ), + ( + HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(), + HeaderValue::from_str(&callback.to_string()).unwrap(), + ), + ( + HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(), + HeaderValue::from_str(&error.to_string()).unwrap(), + ), + (ORIGIN, HeaderValue::from_str("tauri://localhost").unwrap()), + ]); + + let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}")); + *request.headers_mut().unwrap() = headers.clone(); + + let body = vec![123, 31, 45]; + let request = request.body(body.clone()).unwrap(); + let invoke_request = super::parse_invoke_request(&manager, request).unwrap(); + + assert_eq!(invoke_request.cmd, cmd); + assert_eq!(invoke_request.callback.0, callback); + assert_eq!(invoke_request.error.0, error); + assert_eq!(invoke_request.invoke_key, invoke_key); + assert_eq!(invoke_request.url, url.parse().unwrap()); + assert_eq!(invoke_request.headers, headers); + assert_eq!(invoke_request.body, InvokeBody::Raw(body)); + + let body = json!({ + "key": 1, + "anotherKey": "asda", + }); + + let mut headers = headers.clone(); + headers.insert( + CONTENT_TYPE, + HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(), + ); + + let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}")); + *request.headers_mut().unwrap() = headers.clone(); + + let request = request.body(serde_json::to_vec(&body).unwrap()).unwrap(); + let invoke_request = super::parse_invoke_request(&manager, request).unwrap(); + + assert_eq!(invoke_request.headers, headers); + assert_eq!(invoke_request.body, InvokeBody::Json(body)); + } + + #[test] + #[cfg(feature = "isolation")] + fn parse_invoke_request_isolation() { + let context = generate_context!( + "test/fixture/isolation/src-tauri/tauri.conf.json", + crate, + test = false + ); + + let crate::pattern::Pattern::Isolation { crypto_keys, .. } = &context.pattern else { + unreachable!() + }; + + let mut nonce = [0u8; 12]; + getrandom::getrandom(&mut nonce).unwrap(); + + let body_raw = vec![1, 41, 65, 12, 78]; + let body_bytes = crypto_keys.aes_gcm().encrypt(&nonce, &body_raw).unwrap(); + let isolation_payload_raw = json!({ + "nonce": nonce, + "payload": body_bytes, + "contentType": mime::APPLICATION_OCTET_STREAM.to_string(), + }); + + let body_json = json!({ + "key": 1, + "anotherKey": "string" + }); + let body_bytes = crypto_keys + .aes_gcm() + .encrypt(&nonce, &serde_json::to_vec(&body_json).unwrap()) + .unwrap(); + let isolation_payload_json = json!({ + "nonce": nonce, + "payload": body_bytes, + "contentType": mime::APPLICATION_JSON.to_string(), + }); + + let manager: AppManager = AppManager::with_handlers( + context, + PluginStore::default(), + Box::new(|_| false), + None, + Default::default(), + StateManager::new(), + Default::default(), + Default::default(), + Default::default(), + (None, "".into()), + crate::generate_invoke_key().unwrap(), + ); + + let cmd = "write_something"; + let url = "tauri://localhost"; + let invoke_key = "1234ahdsjkl123"; + let callback = 12378123; + let error = 6243; + + let headers = HeaderMap::from_iter(vec![ + ( + CONTENT_TYPE, + HeaderValue::from_str(mime::APPLICATION_JSON.as_ref()).unwrap(), + ), + ( + HeaderName::from_str(TAURI_INVOKE_KEY_HEADER_NAME).unwrap(), + HeaderValue::from_str(invoke_key).unwrap(), + ), + ( + HeaderName::from_str(TAURI_CALLBACK_HEADER_NAME).unwrap(), + HeaderValue::from_str(&callback.to_string()).unwrap(), + ), + ( + HeaderName::from_str(TAURI_ERROR_HEADER_NAME).unwrap(), + HeaderValue::from_str(&error.to_string()).unwrap(), + ), + (ORIGIN, HeaderValue::from_str("tauri://localhost").unwrap()), + ]); + + let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}")); + *request.headers_mut().unwrap() = headers.clone(); + let body = serde_json::to_vec(&isolation_payload_raw).unwrap(); + let request = request.body(body).unwrap(); + let invoke_request = super::parse_invoke_request(&manager, request).unwrap(); + + assert_eq!(invoke_request.cmd, cmd); + assert_eq!(invoke_request.callback.0, callback); + assert_eq!(invoke_request.error.0, error); + assert_eq!(invoke_request.invoke_key, invoke_key); + assert_eq!(invoke_request.url, url.parse().unwrap()); + assert_eq!(invoke_request.headers, headers); + assert_eq!(invoke_request.body, InvokeBody::Raw(body_raw)); + + let mut request = Request::builder().uri(format!("ipc://localhost/{cmd}")); + *request.headers_mut().unwrap() = headers.clone(); + let body = serde_json::to_vec(&isolation_payload_json).unwrap(); + let request = request.body(body).unwrap(); + let invoke_request = super::parse_invoke_request(&manager, request).unwrap(); + + assert_eq!(invoke_request.headers, headers); + assert_eq!(invoke_request.body, InvokeBody::Json(body_json)); + } +} diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index 8410e9e2b..f75eea079 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -692,7 +692,7 @@ mod test { #[test] fn check_get_url() { - let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate); + let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate, test = true); let manager: AppManager = AppManager::with_handlers( context, PluginStore::default(), diff --git a/core/tauri/src/vibrancy/macos.rs b/core/tauri/src/vibrancy/macos.rs index d1305718c..1af3f44ff 100644 --- a/core/tauri/src/vibrancy/macos.rs +++ b/core/tauri/src/vibrancy/macos.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -#![cfg(target_os = "macos")] #![allow(deprecated)] use crate::utils::config::WindowEffectsConfig; diff --git a/core/tauri/test/fixture/isolation/dist/index.html b/core/tauri/test/fixture/isolation/dist/index.html new file mode 100644 index 000000000..698a35779 --- /dev/null +++ b/core/tauri/test/fixture/isolation/dist/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/tauri/test/fixture/isolation/isolation-dist/index.html b/core/tauri/test/fixture/isolation/isolation-dist/index.html new file mode 100644 index 000000000..27c8d3f39 --- /dev/null +++ b/core/tauri/test/fixture/isolation/isolation-dist/index.html @@ -0,0 +1,10 @@ + + + + + Isolation Secure Script + + + + + diff --git a/core/tauri/test/fixture/isolation/isolation-dist/index.js b/core/tauri/test/fixture/isolation/isolation-dist/index.js new file mode 100644 index 000000000..6510a210e --- /dev/null +++ b/core/tauri/test/fixture/isolation/isolation-dist/index.js @@ -0,0 +1,8 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +window.__TAURI_ISOLATION_HOOK__ = (payload, options) => { + console.log('hook', payload, options) + return payload +} diff --git a/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico new file mode 100644 index 000000000..b3636e4b2 Binary files /dev/null and b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico differ diff --git a/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico~dev b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico~dev new file mode 100644 index 000000000..db7fd9820 Binary files /dev/null and b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.ico~dev differ diff --git a/core/tauri/test/fixture/isolation/src-tauri/icons/icon.png b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.png new file mode 100644 index 000000000..a437dd517 Binary files /dev/null and b/core/tauri/test/fixture/isolation/src-tauri/icons/icon.png differ diff --git a/core/tauri/test/fixture/isolation/src-tauri/tauri.conf.json b/core/tauri/test/fixture/isolation/src-tauri/tauri.conf.json new file mode 100644 index 000000000..5aae4629a --- /dev/null +++ b/core/tauri/test/fixture/isolation/src-tauri/tauri.conf.json @@ -0,0 +1,27 @@ +{ + "$schema": "../../../../../../core/tauri-config-schema/schema.json", + "identifier": "isolation.tauri.example", + "build": { + "frontendDist": "../dist", + "devUrl": "http://localhost:4000" + }, + "app": { + "windows": [ + { + "title": "Isolation Tauri App" + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost", + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist" + } + } + } + }, + "bundle": { + "active": true + } +} diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs index 40216f916..2b72c77a1 100644 --- a/examples/api/src-tauri/build.rs +++ b/examples/api/src-tauri/build.rs @@ -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(&[ + "log_operation", + "perform_request", + "echo", + ])), ) .expect("failed to run tauri-build"); } diff --git a/examples/api/src-tauri/capabilities/run-app.json b/examples/api/src-tauri/capabilities/run-app.json index 424fe5dd6..2aa2fcfb1 100644 --- a/examples/api/src-tauri/capabilities/run-app.json +++ b/examples/api/src-tauri/capabilities/run-app.json @@ -16,6 +16,7 @@ ] }, "allow-perform-request", + "allow-echo", "app-menu:default", "sample:allow-ping-scoped", "sample:global-scope", @@ -68,4 +69,4 @@ "webview:allow-create-webview-window", "webview:allow-print" ] -} +} \ No newline at end of file diff --git a/examples/api/src-tauri/permissions/autogenerated/echo.toml b/examples/api/src-tauri/permissions/autogenerated/echo.toml new file mode 100644 index 000000000..d8c458ee8 --- /dev/null +++ b/examples/api/src-tauri/permissions/autogenerated/echo.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-echo" +description = "Enables the echo command without any pre-configured scope." +commands.allow = ["echo"] + +[[permission]] +identifier = "deny-echo" +description = "Denies the echo command without any pre-configured scope." +commands.deny = ["echo"] diff --git a/examples/api/src-tauri/src/cmd.rs b/examples/api/src-tauri/src/cmd.rs index feeba329a..80ae03d10 100644 --- a/examples/api/src-tauri/src/cmd.rs +++ b/examples/api/src-tauri/src/cmd.rs @@ -45,3 +45,8 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse { message: "message response".into(), } } + +#[command] +pub fn echo(request: tauri::ipc::Request<'_>) -> tauri::ipc::Response { + tauri::ipc::Response::new(request.body().clone()) +} diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index ec676bbbe..ba1c41d9b 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -141,6 +141,7 @@ pub fn run_app) + Send + 'static>( .invoke_handler(tauri::generate_handler![ cmd::log_operation, cmd::perform_request, + cmd::echo ]) .build(tauri::tauri_build_context!()) .expect("error while building tauri application"); diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index 7051680ec..7aa6ceb92 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -83,12 +83,22 @@ let messages = writable([]) let consoleTextEl async function onMessage(value) { + const valueStr = + typeof value === 'string' + ? value + : JSON.stringify( + value instanceof ArrayBuffer + ? Array.from(new Uint8Array(value)) + : value, + null, + 1 + ) messages.update((r) => [ ...r, { html: `
[${new Date().toLocaleTimeString()}]: ` +
-          (typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
+          valueStr +
           '
' } ]) diff --git a/examples/api/src/views/Communication.svelte b/examples/api/src/views/Communication.svelte index ec6a8a673..cdee98ad4 100644 --- a/examples/api/src/views/Communication.svelte +++ b/examples/api/src/views/Communication.svelte @@ -36,6 +36,16 @@ .catch(onMessage) } + function echo() { + invoke('echo', { + message: 'Tauri JSON request!' + }) + .then(onMessage) + .catch(onMessage) + + invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage) + } + function emitEvent() { webviewWindow.emit('js-event', 'this is the payload string') } @@ -49,4 +59,5 @@ +