mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(desktop): restore Tauri auto-updater support (#393)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"@tiptap/core": "^3.22.3",
|
||||
"@tiptap/extension-link": "^3.22.3",
|
||||
"@tiptap/extension-placeholder": "^3.22.3",
|
||||
|
||||
Generated
+10
@@ -68,6 +68,9 @@ importers:
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
'@tauri-apps/plugin-updater':
|
||||
specifier: ^2.10.0
|
||||
version: 2.10.1
|
||||
'@tiptap/core':
|
||||
specifier: ^3.22.3
|
||||
version: 3.22.3(@tiptap/pm@3.22.3)
|
||||
@@ -1359,6 +1362,9 @@ packages:
|
||||
'@tauri-apps/plugin-process@2.3.1':
|
||||
resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==}
|
||||
|
||||
'@tauri-apps/plugin-updater@2.10.1':
|
||||
resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==}
|
||||
|
||||
'@tiptap/core@3.22.3':
|
||||
resolution: {integrity: sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==}
|
||||
peerDependencies:
|
||||
@@ -3731,6 +3737,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@tauri-apps/plugin-updater@2.10.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@tiptap/core@3.22.3(@tiptap/pm@3.22.3)':
|
||||
dependencies:
|
||||
'@tiptap/pm': 3.22.3
|
||||
|
||||
Generated
+79
-1
@@ -2937,6 +2937,12 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign-verify"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -3374,6 +3380,18 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@@ -3529,6 +3547,20 @@ dependencies = [
|
||||
"ureq 3.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osakit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -5278,6 +5310,7 @@ dependencies = [
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-websocket",
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
@@ -5288,7 +5321,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"zeroize",
|
||||
"zip",
|
||||
"zip 2.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5931,6 +5964,39 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http",
|
||||
"infer",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest 0.13.2",
|
||||
"rustls",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.60.2",
|
||||
"zip 4.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-websocket"
|
||||
version = "2.4.2"
|
||||
@@ -8052,6 +8118,18 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"indexmap 2.14.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
@@ -31,6 +31,7 @@ tauri-plugin-opener = "2"
|
||||
tauri-plugin-window-state = "2"
|
||||
tauri-plugin-websocket = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-process = "2"
|
||||
infer = "0.19"
|
||||
hex = "0.4"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_RELAY_URL");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_RELAY_HTTP");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_PUBLIC_KEY");
|
||||
println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_ENDPOINT");
|
||||
println!("cargo:rustc-check-cfg=cfg(sprout_updater_enabled)");
|
||||
|
||||
if let Ok(relay_url) = std::env::var("SPROUT_RELAY_URL") {
|
||||
println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_URL={relay_url}");
|
||||
@@ -10,5 +13,18 @@ fn main() {
|
||||
println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_RELAY_HTTP={relay_http}");
|
||||
}
|
||||
|
||||
let updater_public_key = std::env::var("SPROUT_UPDATER_PUBLIC_KEY")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
let updater_endpoint = std::env::var("SPROUT_UPDATER_ENDPOINT")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
|
||||
if updater_public_key.is_some() && updater_endpoint.is_some() {
|
||||
println!("cargo:rustc-cfg=sprout_updater_enabled");
|
||||
}
|
||||
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"websocket:default",
|
||||
"window-state:default",
|
||||
"dialog:default",
|
||||
"updater:allow-check",
|
||||
"updater:allow-download-and-install",
|
||||
"process:allow-restart",
|
||||
"global-shortcut:allow-register",
|
||||
"global-shortcut:allow-unregister",
|
||||
|
||||
@@ -252,6 +252,19 @@ pub fn run() {
|
||||
.build()
|
||||
});
|
||||
|
||||
// Only register the updater in release builds that were compiled with a
|
||||
// real updater configuration. Local unsigned builds omit that config and
|
||||
// should still launch for debugging.
|
||||
#[cfg(sprout_updater_enabled)]
|
||||
let builder = if cfg!(debug_assertions) {
|
||||
builder
|
||||
} else {
|
||||
builder.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
};
|
||||
|
||||
#[cfg(not(sprout_updater_enabled))]
|
||||
let builder = builder;
|
||||
|
||||
let shutdown_started = Arc::new(AtomicBool::new(false));
|
||||
let restore_shutdown_started = Arc::clone(&shutdown_started);
|
||||
let app = builder
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"endpoints": []
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import { check, type Update } from "@tauri-apps/plugin-updater";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
|
||||
type UpdateStatus =
|
||||
| { state: "idle" }
|
||||
| { state: "checking" }
|
||||
| { state: "up-to-date" }
|
||||
| { state: "available"; version: string }
|
||||
| { state: "downloading" }
|
||||
| { state: "installing" }
|
||||
| { state: "ready" }
|
||||
| { state: "error"; message: string };
|
||||
|
||||
export function UpdateChecker() {
|
||||
const [status, setStatus] = useState<UpdateStatus>({ state: "idle" });
|
||||
const updateRef = useRef<Update | null>(null);
|
||||
|
||||
const closeUpdate = useCallback(async () => {
|
||||
if (updateRef.current) {
|
||||
await updateRef.current.close();
|
||||
updateRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function checkForUpdate() {
|
||||
try {
|
||||
await closeUpdate();
|
||||
setStatus({ state: "checking" });
|
||||
const update = await check();
|
||||
|
||||
if (update) {
|
||||
updateRef.current = update;
|
||||
setStatus({ state: "available", version: update.version });
|
||||
} else {
|
||||
setStatus({ state: "up-to-date" });
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
state: "error",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndInstall() {
|
||||
try {
|
||||
const update = updateRef.current;
|
||||
if (!update) {
|
||||
setStatus({ state: "up-to-date" });
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus({ state: "downloading" });
|
||||
|
||||
await update.downloadAndInstall((event) => {
|
||||
if (event.event === "Finished") {
|
||||
setStatus({ state: "installing" });
|
||||
}
|
||||
});
|
||||
|
||||
setStatus({ state: "ready" });
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
state: "error",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRelaunch() {
|
||||
await relaunch();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-zinc-900 p-4">
|
||||
<h3 className="mb-3 text-sm font-medium text-zinc-200">
|
||||
Software Updates
|
||||
</h3>
|
||||
|
||||
{status.state === "idle" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-zinc-400">
|
||||
Check if a new version is available.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={checkForUpdate}
|
||||
className="rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
>
|
||||
Check for Updates
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status.state === "checking" && (
|
||||
<p className="text-sm text-zinc-400">Checking for updates...</p>
|
||||
)}
|
||||
|
||||
{status.state === "up-to-date" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-zinc-300">You're on the latest version.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={checkForUpdate}
|
||||
className="rounded-lg bg-zinc-800 px-3 py-1.5 text-sm font-medium text-zinc-300 hover:bg-zinc-700"
|
||||
>
|
||||
Check Again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status.state === "available" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-zinc-300">
|
||||
Version <span className="font-semibold">{status.version}</span> is
|
||||
available.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={downloadAndInstall}
|
||||
className="rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
>
|
||||
Download & Install
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status.state === "downloading" && (
|
||||
<p className="text-sm text-zinc-400">Downloading update...</p>
|
||||
)}
|
||||
|
||||
{status.state === "installing" && (
|
||||
<p className="text-sm text-zinc-400">Installing update...</p>
|
||||
)}
|
||||
|
||||
{status.state === "ready" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-zinc-300">
|
||||
Update installed. Restart to apply.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRelaunch}
|
||||
className="rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
>
|
||||
Restart Now
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status.state === "error" && (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-red-400">
|
||||
Update failed: {status.message}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={checkForUpdate}
|
||||
className="rounded-lg bg-zinc-800 px-3 py-1.5 text-sm font-medium text-zinc-300 hover:bg-zinc-700"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState, useMemo, useRef } from "react";
|
||||
import {
|
||||
BellRing,
|
||||
Check,
|
||||
Download,
|
||||
Keyboard,
|
||||
KeyRound,
|
||||
MonitorCog,
|
||||
@@ -26,6 +27,7 @@ import { KeyboardShortcutsCard } from "./KeyboardShortcutsCard";
|
||||
import { MobilePairingCard } from "./MobilePairingCard";
|
||||
import { NotificationSettingsCard } from "./NotificationSettingsCard";
|
||||
import { ProfileSettingsCard } from "./ProfileSettingsCard";
|
||||
import { UpdateChecker } from "../UpdateChecker";
|
||||
|
||||
export type SettingsSection =
|
||||
| "profile"
|
||||
@@ -34,6 +36,7 @@ export type SettingsSection =
|
||||
| "shortcuts"
|
||||
| "tokens"
|
||||
| "mobile"
|
||||
| "updates"
|
||||
| "doctor";
|
||||
|
||||
export const DEFAULT_SETTINGS_SECTION: SettingsSection = "profile";
|
||||
@@ -88,6 +91,11 @@ export const settingsSections: SettingsSectionDescriptor[] = [
|
||||
label: "Mobile",
|
||||
icon: Smartphone,
|
||||
},
|
||||
{
|
||||
value: "updates",
|
||||
label: "Updates",
|
||||
icon: Download,
|
||||
},
|
||||
{
|
||||
value: "doctor",
|
||||
label: "Doctor",
|
||||
@@ -248,6 +256,8 @@ export function renderSettingsSection(
|
||||
return <TokenSettingsCard currentPubkey={props.currentPubkey} />;
|
||||
case "mobile":
|
||||
return <MobilePairingCard currentPubkey={props.currentPubkey} />;
|
||||
case "updates":
|
||||
return <UpdateChecker />;
|
||||
case "doctor":
|
||||
return <DoctorSettingsPanel />;
|
||||
default: {
|
||||
|
||||
Reference in New Issue
Block a user