diff --git a/examples/api/src/views/Updater.svelte b/examples/api/src/views/Updater.svelte
index 9536a7a2..2fc57360 100644
--- a/examples/api/src/views/Updater.svelte
+++ b/examples/api/src/views/Updater.svelte
@@ -1,60 +1,44 @@
{#if !isChecking && !newUpdate}
-
+
{:else if !isInstalling && newUpdate}
{:else}
diff --git a/package.json b/package.json
index 5a0bfce4..4a3df949 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"license": "MIT or APACHE-2.0",
"type": "module",
"scripts": {
- "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" build",
+ "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
"lint": "eslint .",
"format": "prettier --write .",
"format-check": "prettier --check ."
diff --git a/plugins/updater/Cargo.lock b/plugins/updater/Cargo.lock
index 554978ea..59f6c938 100644
--- a/plugins/updater/Cargo.lock
+++ b/plugins/updater/Cargo.lock
@@ -2919,10 +2919,10 @@ dependencies = [
"serde",
"serde_json",
"tauri",
- "tauri-runtime",
"tempfile",
"thiserror",
"time",
+ "tokio",
"tokio-test",
"url",
]
diff --git a/plugins/updater/Cargo.toml b/plugins/updater/Cargo.toml
index 82c052dc..f35713e1 100644
--- a/plugins/updater/Cargo.toml
+++ b/plugins/updater/Cargo.toml
@@ -16,7 +16,7 @@ serde = "1"
serde_json = "1"
thiserror = "1"
-tauri-runtime = "0.13.0-alpha.4"
+tokio = "1"
reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] }
url = "2"
http = "0.2"
diff --git a/plugins/updater/guest-js/index.ts b/plugins/updater/guest-js/index.ts
index e69de29b..0123c9ce 100644
--- a/plugins/updater/guest-js/index.ts
+++ b/plugins/updater/guest-js/index.ts
@@ -0,0 +1,70 @@
+import { invoke, transformCallback } from '@tauri-apps/api/tauri'
+
+interface CheckOptions {
+ /**
+ * Request headers
+ */
+ headers?: Record
+ /**
+ * Timeout in seconds
+ */
+ timeout?: number
+ /**
+ * Target identifier for the running application. This is sent to the backend.
+ */
+ target?: string
+}
+
+interface UpdateResponse {
+ available: boolean
+ currentVersion: string
+ latestVersion: string
+ date?: string
+ body?: string
+}
+
+// TODO: use channel from @tauri-apps/api on v2
+class Channel {
+ id: number
+ onmessage: (response: T) => void = () => {
+ // do nothing
+ }
+
+ constructor() {
+ this.id = transformCallback((response: T) => {
+ this.onmessage(response)
+ })
+ }
+
+ toJSON(): string {
+ return `__CHANNEL__:${this.id}`
+ }
+}
+
+type DownloadEvent =
+ { event: 'Started', data: { contentLength?: number } } |
+ { event: 'Progress', data: { chunkLength: number } } |
+ { event: 'Finished' }
+
+class Update {
+ response: UpdateResponse
+
+ private constructor(response: UpdateResponse) {
+ this.response = response
+ }
+
+ async downloadAndInstall(onEvent?: (progress: DownloadEvent) => void): Promise {
+ const channel = new Channel()
+ if (onEvent != null) {
+ channel.onmessage = onEvent
+ }
+ return invoke('plugin:updater|download_and_install', { onEvent: channel })
+ }
+}
+
+async function check(options?: CheckOptions): Promise {
+ return invoke('plugin:updater|check', { ...options })
+}
+
+export type { CheckOptions, UpdateResponse, DownloadEvent }
+export { check, Update }
diff --git a/plugins/updater/src/commands.rs b/plugins/updater/src/commands.rs
index efa7aca0..8a3d6c13 100644
--- a/plugins/updater/src/commands.rs
+++ b/plugins/updater/src/commands.rs
@@ -1,6 +1,102 @@
-use tauri::{AppHandle, Runtime};
+use crate::{PendingUpdate, Result, UpdaterExt};
+
+use http::header;
+use serde::{Deserialize, Deserializer, Serialize};
+use tauri::{api::ipc::Channel, AppHandle, Runtime, State};
+
+use std::{collections::HashMap, time::Duration};
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct Metadata {
+ available: bool,
+ current_version: String,
+ latest_version: String,
+ date: Option,
+ body: Option,
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct HeaderMap(header::HeaderMap);
+
+impl<'de> Deserialize<'de> for HeaderMap {
+ fn deserialize(deserializer: D) -> std::result::Result
+ where
+ D: Deserializer<'de>,
+ {
+ let map = HashMap::::deserialize(deserializer)?;
+ let mut headers = header::HeaderMap::default();
+ for (key, value) in map {
+ if let (Ok(key), Ok(value)) = (
+ header::HeaderName::from_bytes(key.as_bytes()),
+ header::HeaderValue::from_str(&value),
+ ) {
+ headers.insert(key, value);
+ } else {
+ return Err(serde::de::Error::custom(format!(
+ "invalid header `{key}` `{value}`"
+ )));
+ }
+ }
+ Ok(Self(headers))
+ }
+}
+
+#[tauri::command]
+pub(crate) async fn check(
+ app: AppHandle,
+ pending: State<'_, PendingUpdate>,
+ headers: Option,
+ timeout: Option,
+ target: Option,
+) -> Result {
+ let mut builder = app.updater();
+ if let Some(headers) = headers {
+ for (k, v) in headers.0.iter() {
+ builder = builder.header(k, v)?;
+ }
+ }
+ if let Some(timeout) = timeout {
+ builder = builder.timeout(Duration::from_secs(timeout));
+ }
+ if let Some(target) = target {
+ builder = builder.target(target);
+ }
+
+ let response = builder.check().await?;
+
+ let metadata = Metadata {
+ available: response.is_update_available(),
+ current_version: response.current_version().to_string(),
+ latest_version: response.latest_version().to_string(),
+ date: response.date().map(|d| d.to_string()),
+ body: response.body().cloned(),
+ };
+
+ pending.0.lock().await.replace(response);
+
+ Ok(metadata)
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct DownloadProgress {
+ chunk_length: usize,
+ content_length: Option,
+}
#[tauri::command]
-pub fn version(app: AppHandle) -> String {
- app.package_info().version.to_string()
+pub(crate) async fn download_and_install(
+ _app: AppHandle,
+ pending: State<'_, PendingUpdate>,
+ on_event: Channel,
+) -> Result<()> {
+ if let Some(pending) = pending.0.lock().await.take() {
+ pending
+ .download_and_install(move |event| {
+ let _ = on_event.send(&event);
+ })
+ .await?;
+ }
+ Ok(())
}
diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs
index 9e9243bf..509c9504 100644
--- a/plugins/updater/src/error.rs
+++ b/plugins/updater/src/error.rs
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
use http::StatusCode;
+use serde::{Serialize, Serializer};
/// All errors that can occur while running the updater.
#[derive(Debug, thiserror::Error)]
@@ -78,3 +79,12 @@ pub enum Error {
#[error("temp directory is not on the same mount point as the AppImage")]
TempDirNotOnSameMountPoint,
}
+
+impl Serialize for Error {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
diff --git a/plugins/updater/src/lib.rs b/plugins/updater/src/lib.rs
index 9fc7c9c6..ec55cb5b 100644
--- a/plugins/updater/src/lib.rs
+++ b/plugins/updater/src/lib.rs
@@ -3,6 +3,8 @@ use tauri::{
Manager, Runtime,
};
+use tokio::sync::Mutex;
+
mod commands;
mod error;
mod updater;
@@ -15,6 +17,8 @@ struct UpdaterState {
target: Option,
}
+struct PendingUpdate(Mutex