refactor(core): move updater to a plugin (#6919)

This commit is contained in:
Lucas Fernandes Nogueira
2023-05-09 16:43:31 -07:00
committed by GitHub
parent 60cf9ed2fc
commit b072daa3bd
42 changed files with 34 additions and 6805 deletions

7
.changes/move-updater.md Normal file
View File

@@ -0,0 +1,7 @@
---
"api": patch
"tauri": patch
"tauri-utils": patch
---
Moved the `updater` feature to its own plugin in the plugins-workspace repository.

View File

@@ -1,107 +0,0 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: updater test artifacts
on:
schedule:
- cron: '0 0 * * *'
pull_request:
paths:
- '.github/workflows/artifacts-updater.yml'
- 'examples/updater/**'
workflow_dispatch:
env:
RUST_BACKTRACE: 1
CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-artifacts:
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
core -> ../target
tooling/cli
- name: build and install cli.rs
run: cargo install --path tooling/cli --force
- name: Check whether code signing should be enabled
id: enablecodesigning
env:
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
run: |
echo "Enable code signing: ${{ env.ENABLE_CODE_SIGNING != '' }}"
echo "::set-output name=enabled::${{ env.ENABLE_CODE_SIGNING != '' }}"
# run only on tauri-apps/tauri repo (require secrets)
- name: build sample artifacts + code signing (updater)
if: steps.enablecodesigning.outputs.enabled == 'true'
working-directory: ./examples/updater
run: |
yarn install
cargo tauri build --verbose
env:
# Notarization (disabled)
# FIXME: enable only on `dev` push maybe? as it take some times...
#
# APPLE_ID: ${{ secrets.APPLE_ID }}
# APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
# Apple code signing testing
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
# Updater signature is exposed here to make sure it works in PR's
TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==
TAURI_KEY_PASSWORD:
# run on PRs and forks
- name: build sample artifacts (updater)
if: steps.enablecodesigning.outputs.enabled != 'true'
working-directory: ./examples/updater
run: |
yarn install
cargo tauri build --verbose
env:
TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==
TAURI_KEY_PASSWORD:
# upload assets
- uses: actions/upload-artifact@v2
if: matrix.platform == 'ubuntu-latest'
with:
name: linux-updater-artifacts
path: ./examples/updater/src-tauri/target/release/bundle/appimage/updater-example_*.AppImage.*
- uses: actions/upload-artifact@v2
if: matrix.platform == 'windows-latest'
with:
name: windows-updater-artifacts
path: ./examples/updater/src-tauri/target/release/bundle/msi/*
- uses: actions/upload-artifact@v2
if: matrix.platform == 'macos-latest'
with:
name: macos-updater-artifacts
path: ./examples/updater/src-tauri/target/release/bundle/macos/updater-example.app.tar.*

View File

@@ -3,7 +3,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
declare -a examples=("api" "sidecar" "updater" "resources" "tauri-dynamic-lib" "workspace")
declare -a examples=("api" "sidecar" "resources" "tauri-dynamic-lib" "workspace")
declare -a tooling=("bench" "cli" "webdriver")
for example in "${examples[@]}"

View File

@@ -12,13 +12,11 @@ members = [
# integration tests
"core/tests/restart",
"core/tests/app-updater",
]
exclude = [
# examples that can be compiled with the tauri CLI
"examples/api/src-tauri",
"examples/updater/src-tauri",
"examples/resources/src-tauri",
"examples/sidecar/src-tauri",
"examples/web/core",

View File

@@ -2206,12 +2206,7 @@ impl TauriConfig {
#[allow(dead_code)]
pub fn all_features() -> Vec<&'static str> {
let mut features = AllowlistConfig::all_features();
features.extend(vec![
"updater",
"system-tray",
"macos-private-api",
"isolation",
]);
features.extend(vec!["system-tray", "macos-private-api", "isolation"]);
features
}
@@ -2219,9 +2214,6 @@ impl TauriConfig {
#[allow(dead_code)]
pub fn features(&self) -> Vec<&str> {
let mut features = self.allowlist.to_features();
if self.updater.active {
features.push("updater");
}
if self.system_tray.is_some() {
features.push("system-tray");
}

View File

@@ -60,12 +60,10 @@ flate2 = "1.0"
http = "0.2"
dirs-next = "2.0"
percent-encoding = "2.2"
base64 = { version = "0.21", optional = true }
reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] }
bytes = { version = "1", features = [ "serde" ] }
raw-window-handle = "0.5"
minisign-verify = { version = "0.2", optional = true }
time = { version = "0.3", features = [ "parsing", "formatting" ], optional = true }
time = { version = "0.3", optional = true }
glob = "0.3"
data-url = { version = "0.2", optional = true }
serialize-to-javascript = "=0.1.1"
@@ -110,14 +108,12 @@ once_cell = "1"
tauri-build = { path = "../tauri-build/", version = "2.0.0-alpha.1" }
[dev-dependencies]
mockito = "0.31"
proptest = "1.0.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
tauri = { path = ".", default-features = false, features = [ "wry" ] }
tokio-test = "0.4.2"
tokio = { version = "1", features = [ "full" ] }
cargo_toml = "0.11"
winnow = "=0.4.1"
@@ -130,13 +126,7 @@ objc-exception = [ "tauri-runtime-wry/objc-exception" ]
linux-protocol-headers = [ "tauri-runtime-wry/linux-headers", "webkit2gtk/v2_36" ]
isolation = [ "tauri-utils/isolation", "tauri-macros/isolation" ]
custom-protocol = [ "tauri-macros/custom-protocol" ]
updater = [
"minisign-verify",
"time",
"base64",
"dialog-ask",
"fs-extract-api"
]
updater = [ "time" ]
fs-extract-api = [ "zip" ]
native-tls = [ "reqwest/native-tls" ]
native-tls-vendored = [ "reqwest/native-tls-vendored" ]

File diff suppressed because one or more lines are too long

View File

@@ -45,9 +45,6 @@ use crate::runtime::menu::{Menu, MenuId, MenuIdRef};
use crate::runtime::RuntimeHandle;
#[cfg(updater)]
use crate::updater;
#[cfg(target_os = "macos")]
use crate::ActivationPolicy;
@@ -232,12 +229,6 @@ impl<R: Runtime> GlobalWindowEvent<R> {
}
}
#[cfg(updater)]
#[derive(Debug, Clone, Default)]
pub(crate) struct UpdaterSettings {
pub(crate) target: Option<String>,
}
/// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface.
#[derive(Debug, Clone)]
pub struct AssetResolver<R: Runtime> {
@@ -259,15 +250,11 @@ impl<R: Runtime> AssetResolver<R> {
pub struct AppHandle<R: Runtime> {
pub(crate) runtime_handle: R::Handle,
pub(crate) manager: WindowManager<R>,
/// The updater configuration.
#[cfg(updater)]
pub(crate) updater_settings: UpdaterSettings,
}
impl<R: Runtime> AppHandle<R> {
// currently only used on the updater
#[allow(dead_code)]
pub(crate) fn create_proxy(&self) -> R::EventLoopProxy {
/// Creates a proxy to send events through the event loop.
pub fn create_proxy(&self) -> R::EventLoopProxy {
self.runtime_handle.create_proxy()
}
}
@@ -306,8 +293,6 @@ impl<R: Runtime> Clone for AppHandle<R> {
Self {
runtime_handle: self.runtime_handle.clone(),
manager: self.manager.clone(),
#[cfg(updater)]
updater_settings: self.updater_settings.clone(),
}
}
}
@@ -511,29 +496,6 @@ impl App<crate::Wry> {
macro_rules! shared_app_impl {
($app: ty) => {
impl<R: Runtime> $app {
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
/// Gets the updater builder to manually check if an update is available.
///
/// # Examples
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
#[cfg_attr(
feature = "updater",
doc = r#" let response = handle.updater().check().await;"#
)]
/// });
/// Ok(())
/// });
/// ```
pub fn updater(&self) -> updater::UpdateBuilder<R> {
updater::builder(self.app_handle())
}
/// Gets a handle to the first system tray.
///
/// Prefer [`Self::tray_handle_by_id`] when multiple system trays are created.
@@ -788,21 +750,6 @@ impl<R: Runtime> App<R> {
}
}
#[cfg(updater)]
impl<R: Runtime> App<R> {
fn run_updater(&self) {
// check if updater is active or not
if self.manager.config().tauri.updater.active {
// we only listen for `tauri://update`
// once we receive the call, we check if an update is available or not
// if there is a new update we emit `tauri://update-available` with details
// this is the user responsibilities to display dialog and ask if user want to install
// to install the update you need to invoke the Event `tauri://update-install`
updater::listener(self.handle());
}
}
}
/// Builds a Tauri application.
///
/// # Examples
@@ -866,10 +813,6 @@ pub struct Builder<R: Runtime> {
#[cfg(all(desktop, feature = "system-tray"))]
system_tray_event_listeners: Vec<SystemTrayEventListener<R>>,
/// The updater configuration.
#[cfg(updater)]
updater_settings: UpdaterSettings,
/// The device event filter.
device_event_filter: DeviceEventFilter,
}
@@ -898,8 +841,6 @@ impl<R: Runtime> Builder<R> {
system_tray: None,
#[cfg(all(desktop, feature = "system-tray"))]
system_tray_event_listeners: Vec::new(),
#[cfg(updater)]
updater_settings: Default::default(),
device_event_filter: Default::default(),
}
}
@@ -1307,42 +1248,6 @@ impl<R: Runtime> Builder<R> {
self
}
/// Sets the current platform's target name for the updater.
///
/// See [`UpdateBuilder::target`](crate::updater::UpdateBuilder#method.target) for more information.
///
/// # Examples
///
/// - Use a macOS Universal binary target name:
///
/// ```
/// let mut builder = tauri::Builder::default();
/// #[cfg(target_os = "macos")]
/// {
/// builder = builder.updater_target("darwin-universal");
/// }
/// ```
///
/// - Append debug information to the target:
///
/// ```
/// let kind = if cfg!(debug_assertions) { "debug" } else { "release" };
/// tauri::Builder::default()
/// .updater_target(format!("{}-{kind}", tauri::updater::target().unwrap()));
/// ```
///
/// - Use the platform's target triple:
///
/// ```
/// tauri::Builder::default()
/// .updater_target(tauri::utils::platform::target_triple().unwrap());
/// ```
#[cfg(updater)]
pub fn updater_target<T: Into<String>>(mut self, target: T) -> Self {
self.updater_settings.target.replace(target.into());
self
}
/// Change the device event filter mode.
///
/// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]
@@ -1431,8 +1336,6 @@ impl<R: Runtime> Builder<R> {
handle: AppHandle {
runtime_handle,
manager,
#[cfg(updater)]
updater_settings: self.updater_settings,
},
};
@@ -1550,8 +1453,6 @@ fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
(setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
}
#[cfg(updater)]
app.run_updater();
Ok(())
}

View File

@@ -61,10 +61,6 @@ pub enum Error {
/// IO error.
#[error("{0}")]
Io(#[from] std::io::Error),
/// Failed to decode base64.
#[cfg(feature = "updater")]
#[error("Failed to decode base64 string: {0}")]
Base64Decode(#[from] base64::DecodeError),
/// Failed to load window icon.
#[error("invalid icon: {0}")]
InvalidIcon(std::io::Error),
@@ -77,11 +73,6 @@ pub enum Error {
/// Encountered an error in the setup hook,
#[error("error encountered during setup hook: {0}")]
Setup(SetupError),
/// Tauri updater error.
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
#[error("Updater: {0}")]
TauriUpdater(#[from] crate::updater::Error),
/// Error initializing plugin.
#[error("failed to initialize plugin `{0}`: {1}")]
PluginInitialization(String, String),

View File

@@ -199,9 +199,6 @@ pub mod process;
/// The allowlist scopes.
pub mod scope;
mod state;
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
pub mod updater;
pub use tauri_utils as utils;
@@ -377,20 +374,6 @@ pub enum UpdaterEvent {
Error(String),
}
#[cfg(updater)]
impl UpdaterEvent {
pub(crate) fn status_message(self) -> &'static str {
match self {
Self::Pending => updater::EVENT_STATUS_PENDING,
Self::Downloaded => updater::EVENT_STATUS_DOWNLOADED,
Self::Updated => updater::EVENT_STATUS_SUCCESS,
Self::AlreadyUpToDate => updater::EVENT_STATUS_UPTODATE,
Self::Error(_) => updater::EVENT_STATUS_ERROR,
_ => unreachable!(),
}
}
}
/// The user event type.
#[derive(Debug, Clone)]
pub enum EventLoopMessage {

File diff suppressed because it is too large Load Diff

View File

@@ -1,81 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use http::StatusCode;
use thiserror::Error;
/// All errors that can occur while running the updater.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
/// IO Errors.
#[error("`{0}`")]
Io(#[from] std::io::Error),
/// Semver Errors.
#[error("Unable to compare version: {0}")]
Semver(#[from] semver::Error),
/// JSON (Serde) Errors.
#[error("JSON error: {0}")]
SerdeJson(#[from] serde_json::Error),
/// Minisign is used for signature validation.
#[error("Verify signature error: {0}")]
Minisign(#[from] minisign_verify::Error),
/// Error with Minisign base64 decoding.
#[error("Signature decoding error: {0}")]
Base64(#[from] base64::DecodeError),
/// UTF8 Errors in signature.
#[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
SignatureUtf8(String),
/// Tauri utils, mainly extract and file move.
#[error("Tauri API error: {0}")]
TauriApi(#[from] crate::api::Error),
/// Network error.
#[error("Download request failed with status: {0}")]
DownloadFailed(StatusCode),
/// Network error.
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
/// Failed to serialize header value as string.
#[error(transparent)]
Utf8(#[from] std::string::FromUtf8Error),
/// Could not fetch a valid response from the server.
#[error("Could not fetch a valid release JSON from the remote")]
ReleaseNotFound,
/// Error building updater.
#[error("Unable to prepare the updater: {0}")]
Builder(String),
/// Error building updater.
#[error("Unable to extract the new version: {0}")]
Extract(String),
/// Updater cannot be executed on this Linux package. Currently the updater is enabled only on AppImages.
#[error("Cannot run updater on this Linux package. Currently only an AppImage can be updated.")]
UnsupportedLinuxPackage,
/// Operating system is not supported.
#[error("unsupported OS, expected one of `linux`, `darwin` or `windows`.")]
UnsupportedOs,
/// Unsupported app architecture.
#[error(
"Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`."
)]
UnsupportedArch,
/// The platform was not found on the updater JSON response.
#[error("the platform `{0}` was not found on the response `platforms` object")]
TargetNotFound(String),
/// Triggered when there is NO error and the two versions are equals.
/// On client side, it's important to catch this error.
#[error("No updates available")]
UpToDate,
/// The updater responded with an invalid signature type.
#[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")]
InvalidResponseType(&'static str, &'static str, serde_json::Value),
/// HTTP error.
#[error(transparent)]
Http(#[from] http::Error),
/// Temp dir is not on same mount mount. This prevents our updater to rename the AppImage to a temp file.
#[cfg(target_os = "linux")]
#[error("temp directory is not on the same mount point as the AppImage")]
TempDirNotOnSameMountPoint,
}
pub type Result<T = ()> = std::result::Result<T, Error>;

View File

@@ -1,499 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! The Tauri updater.
//!
//! The updater is focused on making Tauri's application updates **as safe and transparent as updates to a website**.
//!
//! For a full guide on setting up the updater, see <https://tauri.app/v1/guides/distribution/updater>.
//!
//! Check [`UpdateBuilder`] to see how to manually trigger and customize the updater at runtime.
//!
//! ## Events
//!
//! To listen to the updater events, for example to check for error messages, you need to use [`RunEvent::Updater`](crate::RunEvent) in [`App::run`](crate::App#method.run).
//!
//! ```no_run
//! let app = tauri::Builder::default()
//! // on an actual app, remove the string argument
//! .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
//! .expect("error while building tauri application");
//! app.run(|_app_handle, event| match event {
//! tauri::RunEvent::Updater(updater_event) => {
//! match updater_event {
//! tauri::UpdaterEvent::UpdateAvailable { body, date, version } => {
//! println!("update available {} {:?} {}", body, date, version);
//! }
//! // Emitted when the download is about to be started.
//! tauri::UpdaterEvent::Pending => {
//! println!("update is pending!");
//! }
//! tauri::UpdaterEvent::DownloadProgress { chunk_length, content_length } => {
//! println!("downloaded {} of {:?}", chunk_length, content_length);
//! }
//! // Emitted when the download has finished and the update is about to be installed.
//! tauri::UpdaterEvent::Downloaded => {
//! println!("update has been downloaded!");
//! }
//! // Emitted when the update was installed. You can then ask to restart the app.
//! tauri::UpdaterEvent::Updated => {
//! println!("app has been updated");
//! }
//! // Emitted when the app already has the latest version installed and an update is not needed.
//! tauri::UpdaterEvent::AlreadyUpToDate => {
//! println!("app is already up to date");
//! }
//! // Emitted when there is an error with the updater. We suggest to listen to this event even if the default dialog is enabled.
//! tauri::UpdaterEvent::Error(error) => {
//! println!("failed to update: {}", error);
//! }
//! _ => (),
//! }
//! }
//! _ => {}
//! });
//! ```
mod core;
mod error;
use std::time::Duration;
use http::header::{HeaderName, HeaderValue};
use semver::Version;
use time::OffsetDateTime;
pub use self::{core::RemoteRelease, error::Error};
/// Alias for [`std::result::Result`] using our own [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
use crate::{runtime::EventLoopProxy, AppHandle, EventLoopMessage, Manager, Runtime, UpdaterEvent};
/// Check for new updates
pub const EVENT_CHECK_UPDATE: &str = "tauri://update";
/// New update available
pub const EVENT_UPDATE_AVAILABLE: &str = "tauri://update-available";
/// Used to initialize an update *should run check-update first (once you received the update available event)*
pub const EVENT_INSTALL_UPDATE: &str = "tauri://update-install";
/// Send updater status or error even if dialog is enabled, you should
/// always listen for this event. It'll send you the install progress
/// and any error triggered during update check and install
pub const EVENT_STATUS_UPDATE: &str = "tauri://update-status";
/// The name of the event that is emitted on download progress.
pub const EVENT_DOWNLOAD_PROGRESS: &str = "tauri://update-download-progress";
/// this is the status emitted when the download start
pub const EVENT_STATUS_PENDING: &str = "PENDING";
/// When you got this status, something went wrong
/// you can find the error message inside the `error` field.
pub const EVENT_STATUS_ERROR: &str = "ERROR";
/// The update has been downloaded.
pub const EVENT_STATUS_DOWNLOADED: &str = "DOWNLOADED";
/// When you receive this status, you should ask the user to restart
pub const EVENT_STATUS_SUCCESS: &str = "DONE";
/// When you receive this status, this is because the application is running last version
pub const EVENT_STATUS_UPTODATE: &str = "UPTODATE";
/// Gets the target string used on the updater.
pub fn target() -> Option<String> {
if let (Some(target), Some(arch)) = (core::get_updater_target(), core::get_updater_arch()) {
Some(format!("{target}-{arch}"))
} else {
None
}
}
#[derive(Clone, serde::Serialize)]
struct StatusEvent {
status: String,
error: Option<String>,
}
#[derive(Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct DownloadProgressEvent {
chunk_length: usize,
content_length: Option<u64>,
}
#[derive(Clone, serde::Serialize)]
struct UpdateManifest {
version: String,
date: Option<String>,
body: String,
}
/// An update check builder.
#[derive(Debug)]
pub struct UpdateBuilder<R: Runtime> {
inner: core::UpdateBuilder<R>,
events: bool,
}
impl<R: Runtime> UpdateBuilder<R> {
/// Do not use the event system to emit information or listen to install the update.
pub fn skip_events(mut self) -> Self {
self.events = false;
self
}
/// Sets the current platform's target name for the updater.
///
/// The target is injected in the endpoint URL by replacing `{{target}}`.
/// Note that this does not affect the `{{arch}}` variable.
///
/// If the updater response JSON includes the `platforms` field,
/// that object must contain a value for the target key.
///
/// By default Tauri uses `$OS_NAME` as the replacement for `{{target}}`
/// and `$OS_NAME-$ARCH` as the key in the `platforms` object,
/// where `$OS_NAME` is the current operating system name "linux", "windows" or "darwin")
/// and `$ARCH` is one of the supported architectures ("i686", "x86_64", "armv7" or "aarch64").
///
/// See [`Builder::updater_target`](crate::Builder#method.updater_target) for a way to set the target globally.
///
/// # Examples
///
/// ## Use a macOS Universal binary target name
///
/// In this example, we set the updater target only on macOS.
/// On other platforms, we set the default target.
/// Note that `{{target}}` will be replaced with `darwin-universal`,
/// but `{{arch}}` is still the running platform's architecture.
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
/// let builder = tauri::updater::builder(handle).target(if cfg!(target_os = "macos") {
/// "darwin-universal".to_string()
/// } else {
/// tauri::updater::target().unwrap()
/// });
/// match builder.check().await {
/// Ok(update) => {}
/// Err(error) => {}
/// }
/// });
/// Ok(())
/// });
/// ```
///
/// ## Append debug information to the target
///
/// This allows you to provide updates for both debug and release applications.
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
/// let kind = if cfg!(debug_assertions) { "debug" } else { "release" };
/// let builder = tauri::updater::builder(handle).target(format!("{}-{kind}", tauri::updater::target().unwrap()));
/// match builder.check().await {
/// Ok(update) => {}
/// Err(error) => {}
/// }
/// });
/// Ok(())
/// });
/// ```
///
/// ## Use the platform's target triple
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
/// let builder = tauri::updater::builder(handle).target(tauri::utils::platform::target_triple().unwrap());
/// match builder.check().await {
/// Ok(update) => {}
/// Err(error) => {}
/// }
/// });
/// Ok(())
/// });
/// ```
pub fn target(mut self, target: impl Into<String>) -> Self {
self.inner = self.inner.target(target);
self
}
/// Sets a closure that is invoked to compare the current version and the latest version returned by the updater server.
/// The first argument is the current version, and the second one is the latest version.
///
/// The closure must return `true` if the update should be installed.
///
/// # Examples
///
/// - Always install the version returned by the server:
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// tauri::updater::builder(app.handle()).should_install(|_current, _latest| true);
/// Ok(())
/// });
/// ```
pub fn should_install<F: FnOnce(&Version, &RemoteRelease) -> bool + Send + 'static>(
mut self,
f: F,
) -> Self {
self.inner = self.inner.should_install(f);
self
}
/// Sets the timeout for the requests to the updater endpoints.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.inner = self.inner.timeout(timeout);
self
}
/// Add a `Header` to the request.
pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.inner = self.inner.header(key, value)?;
Ok(self)
}
/// Check if an update is available.
///
/// # Examples
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
/// match tauri::updater::builder(handle).check().await {
/// Ok(update) => {
/// if update.is_update_available() {
/// update.download_and_install().await.unwrap();
/// }
/// }
/// Err(e) => {
/// println!("failed to get update: {}", e);
/// }
/// }
/// });
/// Ok(())
/// });
/// ```
pub async fn check(self) -> Result<UpdateResponse<R>> {
let handle = self.inner.app.clone();
let events = self.events;
// check updates
match self.inner.build().await {
Ok(update) => {
if events {
// send notification if we need to update
if update.should_update {
let body = update.body.clone().unwrap_or_else(|| String::from(""));
// Emit `tauri://update-available`
let _ = handle.emit_all(
EVENT_UPDATE_AVAILABLE,
UpdateManifest {
body: body.clone(),
date: update.date.map(|d| d.to_string()),
version: update.version.clone(),
},
);
let _ = handle.create_proxy().send_event(EventLoopMessage::Updater(
UpdaterEvent::UpdateAvailable {
body,
date: update.date,
version: update.version.clone(),
},
));
// Listen for `tauri://update-install`
let update_ = update.clone();
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
crate::async_runtime::spawn(async move {
let _ = download_and_install(update_).await;
});
});
} else {
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
}
}
Ok(UpdateResponse { update })
}
Err(e) => {
if self.events {
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
}
Err(e)
}
}
}
}
/// The response of an updater check.
pub struct UpdateResponse<R: Runtime> {
update: core::Update<R>,
}
impl<R: Runtime> Clone for UpdateResponse<R> {
fn clone(&self) -> Self {
Self {
update: self.update.clone(),
}
}
}
impl<R: Runtime> UpdateResponse<R> {
/// Whether the updater found a newer release or not.
pub fn is_update_available(&self) -> bool {
self.update.should_update
}
/// The current version of the application as read by the updater.
pub fn current_version(&self) -> &Version {
&self.update.current_version
}
/// The latest version of the application found by the updater.
pub fn latest_version(&self) -> &str {
&self.update.version
}
/// The update date.
pub fn date(&self) -> Option<&OffsetDateTime> {
self.update.date.as_ref()
}
/// The update description.
pub fn body(&self) -> Option<&String> {
self.update.body.as_ref()
}
/// Downloads and installs the update.
pub async fn download_and_install(self) -> Result<()> {
download_and_install(self.update).await
}
}
/// Updater listener
/// This function should be run on the main thread once.
pub(crate) fn listener<R: Runtime>(handle: AppHandle<R>) {
// Wait to receive the event `"tauri://update"`
let handle_ = handle.clone();
handle.listen_global(EVENT_CHECK_UPDATE, move |_msg| {
let handle_ = handle_.clone();
crate::async_runtime::spawn(async move {
let _ = builder(handle_.clone()).check().await;
});
});
}
pub(crate) async fn download_and_install<R: Runtime>(update: core::Update<R>) -> Result<()> {
// Start installation
// emit {"status": "PENDING"}
send_status_update(&update.app, UpdaterEvent::Pending);
let handle = update.app.clone();
let handle_ = handle.clone();
// Launch updater download process
// macOS we display the `Ready to restart dialog` asking to restart
// Windows is closing the current App and launch the downloaded MSI when ready (the process stop here)
// Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here)
let update_result = update
.download_and_install(
update.app.config().tauri.updater.pubkey.clone(),
move |chunk_length, content_length| {
send_download_progress_event(&handle, chunk_length, content_length);
},
move || {
send_status_update(&handle_, UpdaterEvent::Downloaded);
},
)
.await;
if let Err(err) = &update_result {
// emit {"status": "ERROR", "error": "The error message"}
send_status_update(&update.app, UpdaterEvent::Error(err.to_string()));
} else {
// emit {"status": "DONE"}
send_status_update(&update.app, UpdaterEvent::Updated);
}
update_result
}
/// Initializes the [`UpdateBuilder`] using the app configuration.
pub fn builder<R: Runtime>(handle: AppHandle<R>) -> UpdateBuilder<R> {
let updater_config = &handle.config().tauri.updater;
let package_info = handle.package_info().clone();
// prepare our endpoints
let endpoints = updater_config
.endpoints
.as_ref()
.expect("Something wrong with endpoints")
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>();
let mut builder = self::core::builder(handle.clone())
.urls(&endpoints[..])
.current_version(package_info.version);
if let Some(target) = &handle.updater_settings.target {
builder = builder.target(target);
}
UpdateBuilder {
inner: builder,
events: true,
}
}
// Send a status update via `tauri://update-download-progress` event.
fn send_download_progress_event<R: Runtime>(
handle: &AppHandle<R>,
chunk_length: usize,
content_length: Option<u64>,
) {
let _ = handle.emit_all(
EVENT_DOWNLOAD_PROGRESS,
DownloadProgressEvent {
chunk_length,
content_length,
},
);
let _ =
handle
.create_proxy()
.send_event(EventLoopMessage::Updater(UpdaterEvent::DownloadProgress {
chunk_length,
content_length,
}));
}
// Send a status update via `tauri://update-status` event.
fn send_status_update<R: Runtime>(handle: &AppHandle<R>, message: UpdaterEvent) {
let _ = handle.emit_all(
EVENT_STATUS_UPDATE,
if let UpdaterEvent::Error(error) = &message {
StatusEvent {
error: Some(error.clone()),
status: message.clone().status_message().into(),
}
} else {
StatusEvent {
error: None,
status: message.clone().status_message().into(),
}
},
);
let _ = handle
.create_proxy()
.send_event(EventLoopMessage::Updater(message));
}

View File

View File

@@ -1,18 +0,0 @@
[package]
name = "app-updater"
version = "0.1.0"
edition = "2021"
[build-dependencies]
tauri-build = { path = "../../tauri-build", features = [] }
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tiny_http = "0.11"
tauri = { path = "../../tauri", features = ["updater"] }
time = { version = "0.3", features = ["formatting"] }
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]

View File

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

View File

@@ -1,46 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
let mut context = tauri::generate_context!();
if std::env::var("TARGET").unwrap_or_default() == "nsis" {
// /D sets the default installation directory ($INSTDIR),
// overriding InstallDir and InstallDirRegKey.
// It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces.
// Only absolute paths are supported.
// NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder
context.config_mut().tauri.updater.windows.installer_args = vec![format!(
"/D={}",
tauri::utils::platform::current_exe()
.unwrap()
.parent()
.unwrap()
.display()
)];
}
tauri::Builder::default()
.setup(|app| {
let handle = app.handle();
tauri::async_runtime::spawn(async move {
match handle.updater().check().await {
Ok(update) => {
if let Err(e) = update.download_and_install().await {
println!("{e}");
std::process::exit(1);
}
std::process::exit(0);
}
Err(e) => {
println!("{e}");
std::process::exit(1);
}
}
});
Ok(())
})
.run(context)
.expect("error while running tauri application");
}

View File

@@ -1,40 +0,0 @@
{
"$schema": "../../../core/tauri-config-schema/schema.json",
"build": {
"distDir": [],
"devPath": []
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.updater",
"icon": [
"../../../examples/.icons/32x32.png",
"../../../examples/.icons/128x128.png",
"../../../examples/.icons/128x128@2x.png",
"../../../examples/.icons/icon.icns",
"../../../examples/.icons/icon.ico"
],
"category": "DeveloperTool",
"windows": {
"wix": {
"skipWebviewInstall": true
}
}
},
"allowlist": {
"all": false
},
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"endpoints": [
"http://localhost:3007"
],
"windows": {
"installMode": "quiet"
}
}
}
}

View File

@@ -1,322 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(dead_code, unused_imports)]
use std::{
collections::HashMap,
fs::File,
path::{Path, PathBuf},
process::Command,
};
use serde::Serialize;
const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==";
#[derive(Serialize)]
struct PackageConfig {
version: &'static str,
}
#[derive(Serialize)]
struct Config {
package: PackageConfig,
}
#[derive(Serialize)]
struct PlatformUpdate {
signature: String,
url: &'static str,
with_elevated_task: bool,
}
#[derive(Serialize)]
struct Update {
version: &'static str,
date: String,
platforms: HashMap<String, PlatformUpdate>,
}
fn get_cli_bin_path(cli_dir: &Path, debug: bool) -> Option<PathBuf> {
let mut cli_bin_path = cli_dir.join(format!(
"target/{}/cargo-tauri",
if debug { "debug" } else { "release" }
));
if cfg!(windows) {
cli_bin_path.set_extension("exe");
}
if cli_bin_path.exists() {
Some(cli_bin_path)
} else {
None
}
}
fn build_app(
cli_bin_path: &Path,
cwd: &Path,
config: &Config,
bundle_updater: bool,
target: BundleTarget,
) {
let mut command = Command::new(cli_bin_path);
command
.args(["build", "--debug", "--verbose"])
.arg("--config")
.arg(serde_json::to_string(config).unwrap())
.current_dir(cwd);
#[cfg(target_os = "linux")]
command.args(["--bundles", target.name()]);
#[cfg(target_os = "macos")]
command.args(["--bundles", target.name()]);
if bundle_updater {
#[cfg(windows)]
command.args(["--bundles", "msi", "nsis"]);
command
.env("TAURI_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
.env("TAURI_KEY_PASSWORD", "")
.args(["--bundles", "updater"]);
} else {
#[cfg(windows)]
command.args(["--bundles", target.name()]);
}
let status = command
.status()
.expect("failed to run Tauri CLI to bundle app");
if !status.code().map(|c| c == 0).unwrap_or(true) {
panic!("failed to bundle app {:?}", status.code());
}
}
#[derive(Copy, Clone)]
enum BundleTarget {
AppImage,
App,
Msi,
Nsis,
}
impl BundleTarget {
fn name(self) -> &'static str {
match self {
Self::AppImage => "appimage",
Self::App => "app",
Self::Msi => "msi",
Self::Nsis => "nsis",
}
}
}
impl Default for BundleTarget {
fn default() -> Self {
#[cfg(any(target_os = "macos", target_os = "ios"))]
return Self::App;
#[cfg(target_os = "linux")]
return Self::App;
#[cfg(windows)]
return Self::Nsis;
}
}
#[cfg(target_os = "linux")]
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::AppImage,
root_dir.join(format!(
"target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage"
)),
)]
}
#[cfg(target_os = "macos")]
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::App,
root_dir.join("target/debug/bundle/macos/app-updater.app"),
)]
}
#[cfg(target_os = "ios")]
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![(
BundleTarget::App,
root_dir.join("target/debug/bundle/ios/app-updater.ipa"),
)]
}
#[cfg(target_os = "android")]
fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
root_dir.join("target/debug/bundle/android/app-updater.apk")
}
#[cfg(windows)]
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
vec![
(
BundleTarget::Nsis,
root_dir.join(format!(
"target/debug/bundle/nsis/app-updater_{version}_x64-setup.exe"
)),
),
(
BundleTarget::Msi,
root_dir.join(format!(
"target/debug/bundle/msi/app-updater_{version}_x64_en-US.msi"
)),
),
]
}
#[test]
#[ignore]
fn update_app() {
let target = tauri::updater::target().expect("running updater test in an unsupported platform");
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root_dir = manifest_dir.join("../../..");
let cli_dir = root_dir.join("tooling/cli");
let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) {
p
} else if let Some(p) = get_cli_bin_path(&cli_dir, true) {
p
} else {
let status = Command::new("cargo")
.arg("build")
.current_dir(&cli_dir)
.status()
.expect("failed to run cargo");
if !status.success() {
panic!("failed to build CLI");
}
get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI")
};
let mut config = Config {
package: PackageConfig { version: "1.0.0" },
};
// bundle app update
build_app(
&cli_bin_path,
&manifest_dir,
&config,
true,
Default::default(),
);
let updater_zip_ext = if cfg!(windows) { "zip" } else { "tar.gz" };
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
let bundle_updater_ext = out_bundle_path
.extension()
.unwrap()
.to_str()
.unwrap()
.replace("exe", "nsis");
let signature_path =
out_bundle_path.with_extension(format!("{bundle_updater_ext}.{updater_zip_ext}.sig"));
let signature = std::fs::read_to_string(&signature_path)
.unwrap_or_else(|_| panic!("failed to read signature file {}", signature_path.display()));
let out_updater_path =
out_bundle_path.with_extension(format!("{}.{}", bundle_updater_ext, updater_zip_ext));
let updater_path = root_dir.join(format!(
"target/debug/{}",
out_updater_path.file_name().unwrap().to_str().unwrap()
));
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
let target = target.clone();
std::thread::spawn(move || {
// start the updater server
let server =
tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
loop {
if let Ok(request) = server.recv() {
match request.url() {
"/" => {
let mut platforms = HashMap::new();
platforms.insert(
target.clone(),
PlatformUpdate {
signature: signature.clone(),
url: "http://localhost:3007/download",
with_elevated_task: false,
},
);
let body = serde_json::to_vec(&Update {
version: "1.0.0",
date: time::OffsetDateTime::now_utc()
.format(&time::format_description::well_known::Rfc3339)
.unwrap(),
platforms,
})
.unwrap();
let len = body.len();
let response = tiny_http::Response::new(
tiny_http::StatusCode(200),
Vec::new(),
std::io::Cursor::new(body),
Some(len),
None,
);
let _ = request.respond(response);
}
"/download" => {
let _ = request.respond(tiny_http::Response::from_file(
File::open(&updater_path).unwrap_or_else(|_| {
panic!("failed to open updater bundle {}", updater_path.display())
}),
));
// close server
return;
}
_ => (),
}
}
}
});
config.package.version = "0.1.0";
// bundle initial app version
build_app(&cli_bin_path, &manifest_dir, &config, false, bundle_target);
let mut binary_cmd = if cfg!(windows) {
Command::new(root_dir.join("target/debug/app-updater.exe"))
} else if cfg!(target_os = "macos") {
Command::new(
bundle_paths(&root_dir, "0.1.0")
.first()
.unwrap()
.1
.join("Contents/MacOS/app-updater"),
)
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
let mut c = Command::new("xvfb-run");
c.arg("--auto-servernum")
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
c
} else {
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
};
binary_cmd.env("TARGET", bundle_target.name());
let status = binary_cmd.status().expect("failed to run app");
if !status.success() {
panic!("failed to run app");
}
}
}

View File

@@ -39,8 +39,7 @@ features = [
"icon-png",
"isolation",
"macos-private-api",
"system-tray",
"updater"
"system-tray"
]
[target."cfg(target_os = \"windows\")".dependencies]

View File

@@ -81,13 +81,6 @@
}
}
},
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"endpoints": [
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
]
},
"allowlist": {
"all": true,
"fs": {

View File

@@ -1,29 +0,0 @@
# Updater Example
This example showcases the App Updater feature.
## Running the example
- Compile Tauri
go to root of the Tauri repo and run:
Linux / Mac:
```
# choose to install node cli (1)
bash .scripts/setup.sh
```
Windows:
```
./.scripts/setup.ps1
```
- Run the app in development mode (Run inside of this folder `examples/updater/`)
```bash
$ cargo tauri dev
```
- Build an run the release app (Run inside of this folder `examples/updater/`)
```bash
$ cargo tauri build
$ ./src-tauri/target/release/app
```

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key><true/>
</dict>
</plist>

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Tauri!</title>
</head>
<body>
<h1>Welcome to Tauri!</h1>
</body>
</html>

View File

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

View File

@@ -1,3 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
[package]
name = "updater-example"
version = "0.1.0"
description = "A very simple Tauri Application"
edition = "2021"
rust-version = "1.65"
license = "Apache-2.0 OR MIT"
[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 = ["updater"] }
[features]
custom-protocol = [ "tauri/custom-protocol" ]
[[bin]]
name = "updater-example"
path = "src/main.rs"
# default to small, optimized release binaries
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
incremental = false
opt-level = "s"

View File

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

View File

@@ -1,17 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[tauri::command]
fn my_custom_command(argument: String) {
println!("{}", argument);
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,72 +0,0 @@
{
"$schema": "../../../core/tauri-config-schema/schema.json",
"build": {
"distDir": ["../index.html"],
"devPath": ["../index.html"],
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"package": {
"productName": "Updater",
"version": "0.1.0"
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.updater",
"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": []
},
"macOS": {
"signingIdentity": null,
"entitlements": "../entitlements.plist",
"frameworks": [],
"exceptionDomain": ""
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": null,
"timestampUrl": null,
"wix": {
"enableElevatedUpdateTask": false
}
}
},
"allowlist": {
"all": false
},
"windows": [
{
"title": "Welcome to Tauri!",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": "default-src 'self'"
},
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"endpoints": [
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
]
}
}
}

View File

@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -49,9 +49,6 @@
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
@@ -62,4 +59,4 @@
}
]
}
}
}

View File

@@ -1,7 +1,11 @@
{
"build": {
"distDir": ["../index.html"],
"devPath": ["../index.html"]
"distDir": [
"../index.html"
],
"devPath": [
"../index.html"
]
},
"package": {
"productName": "workspace"
@@ -46,9 +50,6 @@
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
@@ -59,4 +60,4 @@
}
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -30,12 +30,7 @@ export enum TauriEvent {
WINDOW_FILE_DROP = 'tauri://file-drop',
WINDOW_FILE_DROP_HOVER = 'tauri://file-drop-hover',
WINDOW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled',
MENU = 'tauri://menu',
CHECK_UPDATE = 'tauri://update',
UPDATE_AVAILABLE = 'tauri://update-available',
INSTALL_UPDATE = 'tauri://update-install',
STATUS_UPDATE = 'tauri://update-status',
DOWNLOAD_PROGRESS = 'tauri://update-download-progress'
MENU = 'tauri://menu'
}
/**

View File

@@ -16,10 +16,9 @@
import * as event from './event'
import * as path from './path'
import * as tauri from './tauri'
import * as updater from './updater'
import * as window from './window'
/** @ignore */
const invoke = tauri.invoke
export { invoke, event, path, tauri, updater, window }
export { invoke, event, path, tauri, window }

View File

@@ -1,211 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/**
* Customize the auto updater flow.
*
* This package is also accessible with `window.__TAURI__.updater` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
* @module
*/
import { once, listen, emit, TauriEvent } from './event'
import { type UnlistenFn } from './helpers/event'
/**
* @since 1.0.0
*/
type UpdateStatus = 'PENDING' | 'ERROR' | 'DONE' | 'UPTODATE'
/**
* @since 1.0.0
*/
interface UpdateStatusResult {
error?: string
status: UpdateStatus
}
/**
* @since 1.0.0
*/
interface UpdateManifest {
version: string
date: string
body: string
}
/**
* @since 1.0.0
*/
interface UpdateResult {
manifest?: UpdateManifest
shouldUpdate: boolean
}
/**
* Listen to an updater event.
* @example
* ```typescript
* import { onUpdaterEvent } from "@tauri-apps/api/updater";
* const unlisten = await onUpdaterEvent(({ error, status }) => {
* console.log('Updater event', error, status);
* });
*
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
* unlisten();
* ```
*
* @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.
*
* @since 1.0.2
*/
async function onUpdaterEvent(
handler: (status: UpdateStatusResult) => void
): Promise<UnlistenFn> {
return listen(TauriEvent.STATUS_UPDATE, (data: { payload: any }) => {
handler(data?.payload as UpdateStatusResult)
})
}
/**
* Install the update if there's one available.
* @example
* ```typescript
* import { checkUpdate, installUpdate } from '@tauri-apps/api/updater';
* const update = await checkUpdate();
* if (update.shouldUpdate) {
* console.log(`Installing update ${update.manifest?.version}, ${update.manifest?.date}, ${update.manifest.body}`);
* await installUpdate();
* }
* ```
*
* @return A promise indicating the success or failure of the operation.
*
* @since 1.0.0
*/
async function installUpdate(): Promise<void> {
let unlistenerFn: UnlistenFn | undefined
function cleanListener(): void {
if (unlistenerFn) {
unlistenerFn()
}
unlistenerFn = undefined
}
return new Promise((resolve, reject) => {
function onStatusChange(statusResult: UpdateStatusResult): void {
if (statusResult.error) {
cleanListener()
reject(statusResult.error)
return
}
// install complete
if (statusResult.status === 'DONE') {
cleanListener()
resolve()
}
}
// listen status change
onUpdaterEvent(onStatusChange)
.then((fn) => {
unlistenerFn = fn
})
.catch((e) => {
cleanListener()
// dispatch the error to our checkUpdate
throw e
})
// start the process we dont require much security as it's
// handled by rust
emit(TauriEvent.INSTALL_UPDATE).catch((e) => {
cleanListener()
// dispatch the error to our checkUpdate
throw e
})
})
}
/**
* Checks if an update is available.
* @example
* ```typescript
* import { checkUpdate } from '@tauri-apps/api/updater';
* const update = await checkUpdate();
* // now run installUpdate() if needed
* ```
*
* @return Promise resolving to the update status.
*
* @since 1.0.0
*/
async function checkUpdate(): Promise<UpdateResult> {
let unlistenerFn: UnlistenFn | undefined
function cleanListener(): void {
if (unlistenerFn) {
unlistenerFn()
}
unlistenerFn = undefined
}
return new Promise((resolve, reject) => {
function onUpdateAvailable(manifest: UpdateManifest): void {
cleanListener()
resolve({
manifest,
shouldUpdate: true
})
}
function onStatusChange(statusResult: UpdateStatusResult): void {
if (statusResult.error) {
cleanListener()
reject(statusResult.error)
return
}
if (statusResult.status === 'UPTODATE') {
cleanListener()
resolve({
shouldUpdate: false
})
}
}
// wait to receive the latest update
once(TauriEvent.UPDATE_AVAILABLE, (data: { payload: any }) => {
onUpdateAvailable(data?.payload as UpdateManifest)
}).catch((e) => {
cleanListener()
// dispatch the error to our checkUpdate
throw e
})
// listen status change
onUpdaterEvent(onStatusChange)
.then((fn) => {
unlistenerFn = fn
})
.catch((e) => {
cleanListener()
// dispatch the error to our checkUpdate
throw e
})
// start the process
emit(TauriEvent.CHECK_UPDATE).catch((e) => {
cleanListener()
// dispatch the error to our checkUpdate
throw e
})
})
}
export type { UpdateStatus, UpdateStatusResult, UpdateManifest, UpdateResult }
export { onUpdaterEvent, installUpdate, checkUpdate }

View File

@@ -47,9 +47,6 @@
],
"security": {
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'"
},
"updater": {
"active": false
}
}
}
}

View File

@@ -37,7 +37,9 @@
"all": false,
"fs": {
"readFile": true,
"scope": ["$HOME/.tauri_3mb.json"]
"scope": [
"$HOME/.tauri_3mb.json"
]
}
},
"windows": [
@@ -51,9 +53,6 @@
],
"security": {
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'"
},
"updater": {
"active": false
}
}
}
}

View File

@@ -47,9 +47,6 @@
],
"security": {
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'"
},
"updater": {
"active": false
}
}
}
}

View File

@@ -43,9 +43,6 @@
"timestampUrl": ""
}
},
"updater": {
"active": false
},
"allowlist": {
"all": false
},
@@ -62,4 +59,4 @@
"csp": null
}
}
}
}