Browse Source

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

fix: change updater endpoint and better UX
Shreyas 1 week ago
parent
commit
a1e581632d

+ 2 - 8
packages/hoppscotch-desktop/src-tauri/src/lib.rs

@@ -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");

+ 34 - 10
packages/hoppscotch-desktop/src-tauri/src/updater.rs

@@ -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 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;
+            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))
         }
     }
 }

+ 1 - 1
packages/hoppscotch-desktop/src-tauri/tauri.conf.json

@@ -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"
     }

+ 0 - 6
packages/hoppscotch-desktop/src/App.vue

@@ -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>

+ 1 - 1
packages/hoppscotch-desktop/src/components.d.ts

@@ -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']

+ 55 - 25
packages/hoppscotch-desktop/src/views/Home.vue

@@ -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");
-    } catch (updateError) {
-      console.error("Update check error:", updateError);
-    }
 
-    const vendoredInstance: VendoredInstance = {
-      type: "vendored",
-      displayName: "Vendored",
-      version: "vendored"
-    };
+      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?
+      });
 
-    await saveConnectionState({
-      status: "connected",
-      instance: vendoredInstance
-    });
+      const result = await Promise.race([
+        invoke('check_updates_available').then(hasUpdates => ({ status: "completed", hasUpdates })),
+        timeoutPromise
+      ]);
 
-    console.log("Loading vendored app...");
-    const loadResp = await load({
-      bundleName: "Hoppscotch",
-      window: { title: "Hoppscotch" }
-    });
+      console.log("Update check result:", result);
+
+      if (result.status === "completed" && result.hasUpdates) {
+        console.log("Updates available, handling before loading app");
+        shouldProceedWithLoad = false;
 
-    if (!loadResp.success) {
-      throw new Error("Failed to load Hoppscotch Vendored");
+        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
     }
 
-    console.log("Vendored app loaded successfully");
+    if (shouldProceedWithLoad) {
+      const vendoredInstance: VendoredInstance = {
+        type: "vendored",
+        displayName: "Vendored",
+        version: "vendored"
+      };
+
+      await saveConnectionState({
+        status: "connected",
+        instance: vendoredInstance
+      });
+
+      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");
+      }
+
+      console.log("Vendored app loaded successfully");
+    }
   } catch (err) {
     const errorMessage = err instanceof Error ? err.message : String(err);
     console.error("Error loading vendored app:", errorMessage);