fix(desktop): change updater endpoint and some UX improvements (#4794)

fix: change updater endpoint and better UX
This commit is contained in:
Shreyas 2025-02-28 14:59:01 +05:30 committed by GitHub
parent af604ab392
commit a1e581632d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 92 additions and 50 deletions

View file

@ -29,13 +29,6 @@ pub fn run() {
.plugin(tauri_plugin_updater::Builder::new().build())?;
Ok(())
})
.setup(|app| {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
updater::check_and_install_updates(&handle).await;
});
Ok(())
})
.setup(|app| {
let handle = app.handle().clone();
tracing::info!(app_name = %app.package_info().name, "Configuring deep link handler");
@ -78,7 +71,8 @@ pub fn run() {
.plugin(tauri_plugin_relay::init())
.invoke_handler(tauri::generate_handler![
hopp_auth_port,
updater::check_updates_and_wait
updater::check_updates_available,
updater::install_updates_and_restart
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View file

@ -2,19 +2,40 @@ use tauri_plugin_dialog::DialogExt;
use tauri_plugin_updater::UpdaterExt;
#[tauri::command]
pub async fn check_updates_and_wait(app: tauri::AppHandle) -> Result<String, String> {
check_and_install_updates(&app).await;
Ok("Update check completed".to_string())
}
pub async fn check_and_install_updates(app: &tauri::AppHandle) {
pub async fn check_updates_available(app: tauri::AppHandle) -> Result<bool, String> {
tracing::info!("Checking for updates...");
let updater = match app.updater() {
Ok(updater) => updater,
Err(e) => {
tracing::error!(error = %e, "Failed to initialize updater");
return;
return Ok(false);
}
};
match updater.check().await {
Ok(Some(_update)) => {
tracing::info!("Update available");
Ok(true)
}
Ok(None) => {
tracing::info!("No updates available");
Ok(false)
}
Err(e) => {
tracing::error!(error = %e, "Failed to check for updates");
Err(format!("Failed to check for updates: {}", e))
}
}
}
#[tauri::command]
pub async fn install_updates_and_restart(app: tauri::AppHandle) -> Result<(), String> {
tracing::info!("Installing updates...");
let updater = match app.updater() {
Ok(updater) => updater,
Err(e) => {
tracing::error!(error = %e, "Failed to initialize updater");
return Err(format!("Failed to initialize updater: {}", e));
}
};
@ -23,7 +44,7 @@ pub async fn check_and_install_updates(app: &tauri::AppHandle) {
tracing::info!(
current_version = app.package_info().version.to_string(),
update_version = update.version.to_string(),
"Update available"
"Installing update"
);
let dialog = app.dialog();
@ -40,32 +61,35 @@ pub async fn check_and_install_updates(app: &tauri::AppHandle) {
if should_update {
tracing::info!("User agreed to update, starting download...");
match update.download_and_install(|_, _| {}, || {}).await {
Ok(_) => {
tracing::info!("Update installed successfully, restarting app");
app.restart();
Err("Unreachable - app should have restarted".to_string())
}
Err(e) => {
tracing::error!(error = %e, "Failed to download or install update");
let _ = app
.dialog()
.message(format!("Failed to install update: {}", e))
.title("Update Error")
.kind(tauri_plugin_dialog::MessageDialogKind::Error)
.blocking_show();
Err(format!("Failed to download or install update: {}", e))
}
}
} else {
tracing::info!("User declined the update");
Ok(())
}
}
Ok(None) => {
tracing::info!("No updates available");
Ok(())
}
Err(e) => {
tracing::error!(error = %e, "Failed to check for updates");
Err(format!("Failed to check for updates: {}", e))
}
}
}

View file

@ -56,7 +56,7 @@
"plugins": {
"updater": {
"active": true,
"endpoints": ["https://releases.hoppscotch.com/hoppscotch-desktop.json"],
"endpoints": ["https://releases.hoppscotch.com/hoppscotch-selfhost-desktop.json"],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDYwOURFNEY4RDRGMDQxNTgKUldSWVFmRFUrT1NkWUc1RDM0Z2ZjTHE2dG52Q3ZlYzg3ZXVpZU9KaENPWTBMd3MwY0hWa1lreDcK"
}

View file

@ -1,6 +1,5 @@
<template>
<div class="flex h-screen overflow-hidden bg-primary text-secondary">
<LayoutSidebar v-model:expanded="sidebarExpanded" v-model:open="sidebarOpen" />
<div class="flex-1 flex flex-col overflow-hidden">
<main class="flex-1 overflow-y-auto bg-primary">
<div class="container mx-auto flex items-center justify-center">
@ -15,10 +14,5 @@
</template>
<script setup lang="ts">
import { ref } from "vue"
import { Toaster } from "@hoppscotch/ui"
import LayoutSidebar from "~/components/layout/LayoutSidebar.vue"
const sidebarOpen = ref(false)
const sidebarExpanded = ref(true)
</script>

View file

@ -8,7 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
LayoutHeader: typeof import('./components/layout/LayoutHeader.vue')['default']
LayoutSidebar: typeof import('./components/layout/LayoutSidebar.vue')['default']
Tippy: typeof import('vue-tippy')['Tippy']

View file

@ -9,8 +9,7 @@
</div>
<div v-if="isLoading" class="flex flex-col items-center space-y-4">
<div class="loading-spinner w-10 h-10 border-4 border-t-purple-500 rounded-full animate-spin"></div>
<p class="text-secondary">Loading Hoppscotch...</p>
<HoppSmartSpinner />
</div>
<div v-else-if="error" class="flex flex-col items-center space-y-4">
@ -153,13 +152,17 @@ const migrateFromLocalStorage = async () => {
console.log(`Migration complete. Migrated ${migratedCount} items.`);
};
interface UpdateCheckResult {
status: "completed" | "timeout";
hasUpdates?: boolean;
}
const loadVendored = async () => {
isLoading.value = true;
error.value = "";
try {
console.log("Initializing home_store and starting migration process");
await home_store.init();
await app_store.init();
@ -173,36 +176,63 @@ const loadVendored = async () => {
console.error("Migration error:", migrationError);
}
let shouldProceedWithLoad = true;
try {
console.log("Checking for updates before loading app...");
await invoke('check_updates_and_wait');
console.log("Update check completed");
const timeoutPromise: Promise<UpdateCheckResult> = new Promise((resolve) => {
setTimeout(() => {
console.log("Update check timeout reached, proceeding with app load");
resolve({ status: "timeout" });
}, 2000); // TODO: 2s shoud be good?
});
const result = await Promise.race([
invoke('check_updates_available').then(hasUpdates => ({ status: "completed", hasUpdates })),
timeoutPromise
]);
console.log("Update check result:", result);
if (result.status === "completed" && result.hasUpdates) {
console.log("Updates available, handling before loading app");
shouldProceedWithLoad = false;
await invoke('install_updates_and_restart');
// This point would only be reached if install_updates_and_restart
// doesn't actually restart the app
return;
}
} catch (updateError) {
console.error("Update check error:", updateError);
// Continue with loading the app despite update check errors
}
const vendoredInstance: VendoredInstance = {
type: "vendored",
displayName: "Vendored",
version: "vendored"
};
if (shouldProceedWithLoad) {
const vendoredInstance: VendoredInstance = {
type: "vendored",
displayName: "Vendored",
version: "vendored"
};
await saveConnectionState({
status: "connected",
instance: vendoredInstance
});
await saveConnectionState({
status: "connected",
instance: vendoredInstance
});
console.log("Loading vendored app...");
const loadResp = await load({
bundleName: "Hoppscotch",
window: { title: "Hoppscotch" }
});
console.log("Loading vendored app...");
const loadResp = await load({
bundleName: "Hoppscotch",
window: { title: "Hoppscotch" }
});
if (!loadResp.success) {
throw new Error("Failed to load Hoppscotch Vendored");
if (!loadResp.success) {
throw new Error("Failed to load Hoppscotch Vendored");
}
console.log("Vendored app loaded successfully");
}
console.log("Vendored app loaded successfully");
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error("Error loading vendored app:", errorMessage);