tauri_plugin_updater/
lib.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! In-app updates for Tauri applications.
6//!
7//! - Supported platforms: Windows, Linux and macOS.crypted database and secure runtime.
8
9#![doc(
10    html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
11    html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
12)]
13
14use std::{ffi::OsString, sync::Arc};
15
16use http::{HeaderMap, HeaderName, HeaderValue};
17use semver::Version;
18use tauri::{
19    plugin::{Builder as PluginBuilder, TauriPlugin},
20    Manager, Runtime,
21};
22
23mod commands;
24mod config;
25mod error;
26mod updater;
27
28pub use config::Config;
29pub use error::{Error, Result};
30pub use updater::*;
31
32/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the updater APIs.
33pub trait UpdaterExt<R: Runtime> {
34    /// Gets the updater builder to build and updater
35    /// that can manually check if an update is available.
36    ///
37    /// # Examples
38    ///
39    /// ```no_run
40    /// use tauri_plugin_updater::UpdaterExt;
41    /// tauri::Builder::default()
42    ///   .setup(|app| {
43    ///     let handle = app.handle().clone();
44    ///     tauri::async_runtime::spawn(async move {
45    ///         let response = handle.updater_builder().build().unwrap().check().await;
46    ///     });
47    ///     Ok(())
48    ///   });
49    /// ```
50    fn updater_builder(&self) -> UpdaterBuilder;
51
52    /// Gets the updater to manually check if an update is available.
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// use tauri_plugin_updater::UpdaterExt;
58    /// tauri::Builder::default()
59    ///   .setup(|app| {
60    ///     let handle = app.handle().clone();
61    ///     tauri::async_runtime::spawn(async move {
62    ///         let response = handle.updater().unwrap().check().await;
63    ///     });
64    ///     Ok(())
65    ///   });
66    /// ```
67    fn updater(&self) -> Result<Updater>;
68}
69
70impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
71    fn updater_builder(&self) -> UpdaterBuilder {
72        let app = self.app_handle();
73        let UpdaterState {
74            config,
75            target,
76            version_comparator,
77            headers,
78        } = self.state::<UpdaterState>().inner();
79
80        let mut builder = UpdaterBuilder::new(app, config.clone()).headers(headers.clone());
81
82        if let Some(target) = target {
83            builder = builder.target(target);
84        }
85
86        let args = self.env().args_os;
87        if !args.is_empty() {
88            builder = builder.current_exe_args(args);
89        }
90
91        builder.version_comparator = version_comparator.clone();
92
93        #[cfg(any(
94            target_os = "linux",
95            target_os = "dragonfly",
96            target_os = "freebsd",
97            target_os = "netbsd",
98            target_os = "openbsd"
99        ))]
100        {
101            let env = app.env();
102            if let Some(appimage) = env.appimage {
103                builder = builder.executable_path(appimage);
104            }
105        }
106
107        let app_handle = app.app_handle().clone();
108        builder = builder.on_before_exit(move || {
109            app_handle.cleanup_before_exit();
110        });
111
112        builder
113    }
114
115    fn updater(&self) -> Result<Updater> {
116        self.updater_builder().build()
117    }
118}
119
120struct UpdaterState {
121    target: Option<String>,
122    config: Config,
123    version_comparator: Option<VersionComparator>,
124    headers: HeaderMap,
125}
126
127#[derive(Default)]
128pub struct Builder {
129    target: Option<String>,
130    pubkey: Option<String>,
131    installer_args: Vec<OsString>,
132    headers: HeaderMap,
133    default_version_comparator: Option<VersionComparator>,
134}
135
136impl Builder {
137    pub fn new() -> Self {
138        Self::default()
139    }
140
141    pub fn target(mut self, target: impl Into<String>) -> Self {
142        self.target.replace(target.into());
143        self
144    }
145
146    pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
147        self.pubkey.replace(pubkey.into());
148        self
149    }
150
151    pub fn installer_args<I, S>(mut self, args: I) -> Self
152    where
153        I: IntoIterator<Item = S>,
154        S: Into<OsString>,
155    {
156        self.installer_args.extend(args.into_iter().map(Into::into));
157        self
158    }
159
160    pub fn installer_arg<S>(mut self, arg: S) -> Self
161    where
162        S: Into<OsString>,
163    {
164        self.installer_args.push(arg.into());
165        self
166    }
167
168    pub fn clear_installer_args(mut self) -> Self {
169        self.installer_args.clear();
170        self
171    }
172
173    pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
174    where
175        HeaderName: TryFrom<K>,
176        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
177        HeaderValue: TryFrom<V>,
178        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
179    {
180        let key: std::result::Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
181        let value: std::result::Result<HeaderValue, http::Error> =
182            value.try_into().map_err(Into::into);
183        self.headers.insert(key?, value?);
184
185        Ok(self)
186    }
187
188    pub fn headers(mut self, headers: HeaderMap) -> Self {
189        self.headers = headers;
190        self
191    }
192
193    pub fn default_version_comparator<
194        F: Fn(Version, RemoteRelease) -> bool + Send + Sync + 'static,
195    >(
196        mut self,
197        f: F,
198    ) -> Self {
199        self.default_version_comparator.replace(Arc::new(f));
200        self
201    }
202
203    pub fn build<R: Runtime>(self) -> TauriPlugin<R, Config> {
204        let pubkey = self.pubkey;
205        let target = self.target;
206        let version_comparator = self.default_version_comparator;
207        let installer_args = self.installer_args;
208        let headers = self.headers;
209        PluginBuilder::<R, Config>::new("updater")
210            .setup(move |app, api| {
211                let mut config = api.config().clone();
212                if let Some(pubkey) = pubkey {
213                    config.pubkey = pubkey;
214                }
215                if let Some(windows) = &mut config.windows {
216                    windows.installer_args.extend(installer_args);
217                }
218                app.manage(UpdaterState {
219                    target,
220                    config,
221                    version_comparator,
222                    headers,
223                });
224                Ok(())
225            })
226            .invoke_handler(tauri::generate_handler![
227                commands::check,
228                commands::download,
229                commands::install,
230                commands::download_and_install,
231            ])
232            .build()
233    }
234}