mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
refactor(core): move updater to a plugin (#6919)
This commit is contained in:
committed by
GitHub
parent
60cf9ed2fc
commit
b072daa3bd
7
.changes/move-updater.md
Normal file
7
.changes/move-updater.md
Normal 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.
|
||||
107
.github/workflows/artifacts-updater.yml
vendored
107
.github/workflows/artifacts-updater.yml
vendored
@@ -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.*
|
||||
@@ -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[@]}"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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>;
|
||||
@@ -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));
|
||||
}
|
||||
0
core/tests/app-updater/.gitignore
vendored
0
core/tests/app-updater/.gitignore
vendored
@@ -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"]
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,7 @@ features = [
|
||||
"icon-png",
|
||||
"isolation",
|
||||
"macos-private-api",
|
||||
"system-tray",
|
||||
"updater"
|
||||
"system-tray"
|
||||
]
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
|
||||
@@ -81,13 +81,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
|
||||
"endpoints": [
|
||||
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
|
||||
]
|
||||
},
|
||||
"allowlist": {
|
||||
"all": true,
|
||||
"fs": {
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "updater",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"tauri": "node ../../tooling/cli/node/tauri.js"
|
||||
}
|
||||
}
|
||||
3
examples/updater/src-tauri/.gitignore
vendored
3
examples/updater/src-tauri/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
3436
examples/updater/src-tauri/Cargo.lock
generated
3436
examples/updater/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
@@ -49,9 +49,6 @@
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
@@ -62,4 +59,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
@@ -47,9 +47,6 @@
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,6 @@
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,6 @@
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"allowlist": {
|
||||
"all": false
|
||||
},
|
||||
@@ -62,4 +59,4 @@
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user