diff --git a/Cargo.lock b/Cargo.lock index 7d9d642..399db92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1485,6 +1485,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + [[package]] name = "miniz_oxide" version = "0.5.4" @@ -2861,6 +2867,7 @@ checksum = "ac135e45c2923bd91edbb95a0d656f8d025389697e34d6d79166952bfa79c61c" dependencies = [ "anyhow", "attohttpc", + "base64", "cocoa", "dirs-next", "embed_plist", @@ -2873,6 +2880,7 @@ dependencies = [ "heck 0.4.0", "http", "ignore", + "minisign-verify", "notify-rust", "objc", "once_cell", @@ -2898,12 +2906,14 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", + "time", "tokio", "url", "uuid 1.2.1", "webkit2gtk", "webview2-com", "windows 0.39.0", + "zip", ] [[package]] @@ -3051,13 +3061,11 @@ version = "0.0.0" dependencies = [ "anyhow", "console_error_panic_hook", - "futures-util", "log", "serde", "sycamore", "tauri-sys", "wasm-bindgen-futures", - "wasm-logger", ] [[package]] @@ -3528,17 +3536,6 @@ dependencies = [ "quote", ] -[[package]] -name = "wasm-logger" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" -dependencies = [ - "log", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.60" @@ -4009,3 +4006,14 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zip" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", +] diff --git a/Cargo.toml b/Cargo.toml index 593722e..475ba19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ tauri-sys = { path = ".", features = ["all"] } all-features = true [features] -all = ["app", "clipboard", "event", "mocks", "tauri", "window", "process", "dialog", "os", "notification", "path"] +all = ["app", "clipboard", "event", "mocks", "tauri", "window", "process", "dialog", "os", "notification", "path", "updater"] app = ["dep:semver"] clipboard = [] dialog = [] @@ -34,9 +34,10 @@ mocks = [] tauri = ["dep:url"] window = [] process = [] -os = ["dep:semver"] +os = [] notification = [] path = [] +updater = [] [workspace] members = ["examples/test", "examples/test/src-tauri"] diff --git a/examples/test/Cargo.toml b/examples/test/Cargo.toml index 9595dd7..2fa5c6f 100644 --- a/examples/test/Cargo.toml +++ b/examples/test/Cargo.toml @@ -10,9 +10,7 @@ sycamore = { git = "https://github.com/sycamore-rs/sycamore", rev = "abd556cbc02 anyhow = "1.0.66" console_error_panic_hook = "0.1.7" wasm-bindgen-futures = "0.4.32" -futures-util = "0.3.25" serde = { version = "1.0.147", features = ["derive"] } -wasm-logger = "0.2.0" log = { version = "0.4.17", features = ["serde"] } [features] diff --git a/examples/test/src-tauri/Cargo.toml b/examples/test/src-tauri/Cargo.toml index 4a2c62f..facabc7 100644 --- a/examples/test/src-tauri/Cargo.toml +++ b/examples/test/src-tauri/Cargo.toml @@ -16,8 +16,8 @@ tauri-build = { version = "1.2", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.2", features = ["api-all"] } tauri-plugin-log = {git = "https://github.com/tauri-apps/tauri-plugin-log", features = ["colored"] } +tauri = { version = "1.2", features = ["api-all", "updater"] } [features] # by default Tauri runs in production mode diff --git a/examples/test/src-tauri/tauri.conf.json b/examples/test/src-tauri/tauri.conf.json index 5d5cc52..660d3fd 100644 --- a/examples/test/src-tauri/tauri.conf.json +++ b/examples/test/src-tauri/tauri.conf.json @@ -51,7 +51,12 @@ "csp": null }, "updater": { - "active": false + "active": true, + "endpoints": [ + "https://releases.myapp.com/{{target}}/{{current_version}}" + ], + "dialog": true, + "pubkey": "YOUR_UPDATER_SIGNATURE_PUBKEY_HERE" }, "windows": [ { diff --git a/src/clipboard.rs b/src/clipboard.rs index 1715f18..27427db 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -24,8 +24,6 @@ pub async fn read_text() -> crate::Result { /// write_text("Tauri is awesome!").await; /// assert_eq!(read_text().await, "Tauri is awesome!"); /// ``` -/// -/// @returns A promise indicating the success or failure of the operation. #[inline(always)] pub async fn write_text(text: &str) -> crate::Result<()> { Ok(inner::writeText(text).await?) diff --git a/src/event.rs b/src/event.rs index 9ba40f1..48db53b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -67,7 +67,6 @@ pub async fn emit(event: &str, payload: &T) -> crate::Result<()> { /// /// @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. /// @param handler Event handler callback. -/// @returns A promise resolving to a function to unlisten to the event. /// /// Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. #[inline(always)] @@ -112,7 +111,6 @@ where /// ``` /// /// @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. -/// @returns A promise resolving to a function to unlisten to the event. /// /// Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. #[inline(always)] diff --git a/src/lib.rs b/src/lib.rs index 1fa38d4..caa58fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ pub mod mocks; pub mod process; #[cfg(feature = "tauri")] pub mod tauri; +#[cfg(feature = "updater")] +pub mod updater; #[cfg(feature = "window")] pub mod window; #[cfg(feature = "notification")] diff --git a/src/notification.rs b/src/notification.rs index 5cf0300..b65f958 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -1,41 +1,30 @@ use serde::{Deserialize, Serialize}; +#[inline(always)] pub async fn is_permission_granted() -> crate::Result { let raw = inner::isPermissionGranted().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } +#[inline(always)] pub async fn request_permission() -> crate::Result { let raw = inner::requestPermission().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Deserialize, Default, Clone, Copy, PartialEq, Eq)] pub enum Permission { #[default] + #[serde(rename = "default")] Default, + #[serde(rename = "granted")] Granted, + #[serde(rename = "denied")] Denied, } -impl<'de> Deserialize<'de> for Permission { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - match String::deserialize(deserializer)?.as_str() { - "default" => Ok(Permission::Default), - "granted" => Ok(Permission::Granted), - "denied" => Ok(Permission::Denied), - _ => Err(serde::de::Error::custom( - "expected one of default, granted, denied", - )), - } - } -} - #[derive(Debug, Default, Serialize)] pub struct Notification<'a> { body: Option<&'a str>, @@ -60,6 +49,7 @@ impl<'a> Notification<'a> { self.icon = Some(icon); } + #[inline(always)] pub fn show(&self) -> crate::Result<()> { inner::sendNotification(serde_wasm_bindgen::to_value(&self)?)?; diff --git a/src/os.rs b/src/os.rs index 261a3bd..ef6c1ef 100644 --- a/src/os.rs +++ b/src/os.rs @@ -1,6 +1,5 @@ -use std::path::{PathBuf}; -use semver::Version; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum Arch { @@ -25,7 +24,7 @@ pub enum Arch { #[serde(rename = "s390x")] S390x, #[serde(rename = "sparc64")] - Sparc64 + Sparc64, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] @@ -62,31 +61,41 @@ pub enum OsKind { WindowsNt, } +/// Returns the operating system CPU architecture for which the tauri app was compiled. +#[inline(always)] pub async fn arch() -> crate::Result { let raw = inner::arch().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } +/// Returns a string identifying the operating system platform. The value is set at compile time. +#[inline(always)] pub async fn platform() -> crate::Result { let raw = inner::platform().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } +/// Returns the operating system's default directory for temporary files. +#[inline(always)] pub async fn tempdir() -> crate::Result { let raw = inner::tempdir().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } +/// Returns 'OsKind::Linux' on Linux, 'OsKind::Darwin' on macOS, and 'OsKind::WindowsNT' on Windows. +#[inline(always)] pub async fn kind() -> crate::Result { let raw = inner::kind().await?; Ok(serde_wasm_bindgen::from_value(raw)?) } -pub async fn version() -> crate::Result { +/// Returns a string identifying the kernel version. +#[inline(always)] +pub async fn version() -> crate::Result { let raw = inner::version().await?; Ok(serde_wasm_bindgen::from_value(raw)?) diff --git a/src/path.rs b/src/path.rs index 93d9428..94e81df 100644 --- a/src/path.rs +++ b/src/path.rs @@ -14,6 +14,7 @@ use wasm_bindgen::JsValue; /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn app_config_dir() -> crate::Result { let raw = inner::appConfigDir().await?; @@ -33,6 +34,7 @@ pub async fn app_config_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn app_data_dir() -> crate::Result { let raw = inner::appDataDir().await?; @@ -52,6 +54,7 @@ pub async fn app_data_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn app_local_data_dir() -> crate::Result { let raw = inner::appLocalDataDir().await?; @@ -71,6 +74,7 @@ pub async fn app_local_data_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn app_cache_dir() -> crate::Result { let raw = inner::appCacheDir().await?; @@ -95,6 +99,7 @@ pub async fn app_cache_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn audio_dir() -> crate::Result { let raw = inner::audioDir().await?; @@ -119,6 +124,7 @@ pub async fn audio_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn cache_dir() -> crate::Result { let raw = inner::cacheDir().await?; @@ -143,6 +149,7 @@ pub async fn cache_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn config_dir() -> crate::Result { let raw = inner::configDir().await?; @@ -167,6 +174,7 @@ pub async fn config_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn data_dir() -> crate::Result { let raw = inner::dataDir().await?; @@ -191,6 +199,7 @@ pub async fn data_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn desktop_dir() -> crate::Result { let raw = inner::desktopDir().await?; @@ -215,6 +224,7 @@ pub async fn desktop_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn document_dir() -> crate::Result { let raw = inner::documentDir().await?; @@ -239,6 +249,7 @@ pub async fn document_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn download_dir() -> crate::Result { let raw = inner::downloadDir().await?; @@ -263,6 +274,7 @@ pub async fn download_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn executable_dir() -> crate::Result { let raw = inner::executableDir().await?; @@ -287,6 +299,7 @@ pub async fn executable_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn font_dir() -> crate::Result { let raw = inner::fontDir().await?; @@ -311,6 +324,7 @@ pub async fn font_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn home_dir() -> crate::Result { let raw = inner::homeDir().await?; @@ -335,6 +349,7 @@ pub async fn home_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn local_data_dir() -> crate::Result { let raw = inner::localDataDir().await?; @@ -359,6 +374,7 @@ pub async fn local_data_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn picture_dir() -> crate::Result { let raw = inner::pictureDir().await?; @@ -383,6 +399,7 @@ pub async fn picture_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn public_dir() -> crate::Result { let raw = inner::publicDir().await?; @@ -402,6 +419,7 @@ pub async fn public_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn resource_dir() -> crate::Result { let raw = inner::resourceDir().await?; @@ -422,6 +440,7 @@ pub async fn resource_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn resolve_resource(resource_path: &str) -> crate::Result { let raw = inner::resolveResource(JsValue::from_str(resource_path)).await?; @@ -446,6 +465,7 @@ pub async fn resolve_resource(resource_path: &str) -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn runtime_dir() -> crate::Result { let raw = inner::runtimeDir().await?; @@ -470,6 +490,7 @@ pub async fn runtime_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn template_dir() -> crate::Result { let raw = inner::templateDir().await?; @@ -494,6 +515,7 @@ pub async fn template_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn video_dir() -> crate::Result { let raw = inner::videoDir().await?; @@ -518,6 +540,7 @@ pub async fn video_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn app_log_dir() -> crate::Result { let raw = inner::appLogDir().await?; @@ -538,6 +561,7 @@ pub async fn app_log_dir() -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn resolve(paths: impl IntoIterator) -> crate::Result { let paths = paths.into_iter(); let raw = inner::resolve(serde_wasm_bindgen::to_value(&paths.collect::>())?).await?; @@ -559,6 +583,7 @@ pub async fn resolve(paths: impl IntoIterator) -> crate::Result crate::Result { let raw = inner::normalize(JsValue::from_str(path)).await?; @@ -579,6 +604,7 @@ pub async fn normalize(path: &str) -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn join(paths: impl IntoIterator) -> crate::Result { let paths = paths.into_iter(); let raw = inner::join(serde_wasm_bindgen::to_value(&paths.collect::>())?).await?; @@ -600,6 +626,7 @@ pub async fn join(paths: impl IntoIterator) -> crate::Result crate::Result { let raw = inner::dirname(JsValue::from_str(path)).await?; @@ -620,6 +647,7 @@ pub async fn dirname(path: &str) -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn extname(path: &str) -> crate::Result { let raw = inner::extname(JsValue::from_str(path)).await?; @@ -642,6 +670,7 @@ pub async fn extname(path: &str) -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn basename(path: &str, ext: Option<&str>) -> crate::Result { let raw = inner::basename( JsValue::from_str(path), @@ -664,6 +693,7 @@ pub async fn basename(path: &str, ext: Option<&str>) -> crate::Result { /// # Ok(()) /// # } /// ``` +#[inline(always)] pub async fn is_absolute(path: &str) -> crate::Result { let raw = inner::isAbsolute(JsValue::from_str(path)).await?; diff --git a/src/process.rs b/src/process.rs index 83ac88c..a038eb5 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,8 +1,10 @@ +#[inline(always)] pub async fn exit(exit_code: u32) -> ! { inner::exit(exit_code).await; unreachable!() } +#[inline(always)] pub fn relaunch() { inner::relaunch(); } diff --git a/src/updater.rs b/src/updater.rs new file mode 100644 index 0000000..7be5bff --- /dev/null +++ b/src/updater.rs @@ -0,0 +1,135 @@ +use serde::Deserialize; +use wasm_bindgen::{prelude::Closure, JsValue}; + +#[derive(Deserialize, Debug, Clone)] +pub struct UpdateManifest { + pub body: String, + pub date: String, + pub version: String +} + +#[derive(Deserialize, Debug, Clone)] +pub struct UpdateResult { + pub manifest: Option, + pub should_update: bool +} + +#[derive(Deserialize)] +struct UpdateStatusResult { + error: Option, + status: UpdateStatus +} + +#[derive(Deserialize)] +pub enum UpdateStatus { + #[serde(rename = "PENDING")] + Pending, + #[serde(rename = "DONE")] + Done, + #[serde(rename = "UPTODATE")] + UpToDate +} + +/// Checks if an update is available. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::updater::check_update; +/// +/// # async fn main() -> Result<(), Box> { +/// let update = check_update().await?; +/// // now run installUpdate() if needed +/// # Ok(()) +/// # } +/// ``` +#[inline(always)] +pub async fn check_update() -> crate::Result { + let raw = inner::checkUpdate().await?; + + Ok(serde_wasm_bindgen::from_value(raw)?) +} + +/// Install the update if there's one available. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::updater::{check_update, install_update}; +/// +/// # async fn main() -> Result<(), Box> { +/// let update = check_update().await?; +/// +/// if update.should_update { +/// log::info("Installing update {:?}", update.manifest); +/// install_update().await?; +/// } +/// # Ok(()) +/// # } +/// ``` +#[inline(always)] +pub async fn install_update() -> crate::Result<()> { + inner::installUpdate().await?; + Ok(()) +} + +/// Listen to an updater event. +/// +/// # Example +/// +/// ```rust,no_run +/// use tauri_sys::updater::on_updater_event; +/// +/// # async fn main() -> Result<(), Box> { +/// let unlisten = on_updater_event(|event| { +/// log::debug!("Updater event {:?}", event); +/// }).await?; +/// +/// // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted +/// unlisten(); +/// # Ok(()) +/// # } +/// ``` +/// Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. +#[inline(always)] +pub async fn on_updater_event(mut handler: H) -> crate::Result +where + H: FnMut(Result) + 'static, +{ + let closure = Closure::::new(move |raw| { + let raw: UpdateStatusResult = serde_wasm_bindgen::from_value(raw).unwrap(); + + let result = if let Some(error) = raw.error { + Err(error) + } else { + Ok(raw.status) + }; + + (handler)(result) + }); + + let unlisten = inner::onUpdaterEvent(&closure).await?; + + closure.forget(); + + let unlisten = js_sys::Function::from(unlisten); + Ok(move || { + unlisten.call0(&wasm_bindgen::JsValue::NULL).unwrap(); + }) +} + +mod inner { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "/src/updater.js")] + extern "C" { + #[wasm_bindgen(catch)] + pub async fn checkUpdate() -> Result; + #[wasm_bindgen(catch)] + pub async fn installUpdate() -> Result; + #[wasm_bindgen(catch)] + pub async fn onUpdaterEvent( + handler: &Closure, + ) -> Result; + } +}