Bläddra i källkod

feat(api): Expose application metadata and functions to JS api - fix #1387 (#1445)

david 4 år sedan
förälder
incheckning
e511d39910

+ 6 - 0
.changes/js-app-metadata.md

@@ -0,0 +1,6 @@
+---
+"tauri-api": minor
+"tauri": minor
+---
+
+Added new Javascript API to extract `name`, `version`, `tauri version` from the running application. We exposed `relaunch` and `exit` as well to control your application state.

+ 1 - 0
api/package.json

@@ -5,6 +5,7 @@
   "main": "./dist/index.js",
   "exports": {
     ".": "./dist/index.js",
+    "./app": "./dist/app.js",
     "./cli": "./dist/cli.js",
     "./dialog": "./dist/dialog.js",
     "./event": "./dist/event.js",

+ 1 - 0
api/rollup.config.js

@@ -10,6 +10,7 @@ import pkg from './package.json'
 export default [
   {
     input: {
+      app: './src/app.ts',
       fs: './src/fs.ts',
       path: './src/path.ts',
       dialog: './src/dialog.ts',

+ 80 - 0
api/src/app.ts

@@ -0,0 +1,80 @@
+import { invokeTauriCommand } from './helpers/tauri'
+
+/**
+ * @name getVersion
+ * @description Get application version
+ * @returns {Promise<string>} Promise resolving to application version
+ */
+async function getVersion(): Promise<string> {
+  return invokeTauriCommand<string>({
+    __tauriModule: 'App',
+    mainThread: true,
+    message: {
+      cmd: 'getAppVersion'
+    }
+  })
+}
+
+/**
+ * @name getName
+ * @description Get application name
+ * @returns {Promise<string>} Promise resolving to application name
+ */
+async function getName(): Promise<string> {
+  return invokeTauriCommand<string>({
+    __tauriModule: 'App',
+    mainThread: true,
+    message: {
+      cmd: 'getAppName'
+    }
+  })
+}
+
+/**
+ * @name getTauriVersion
+ * @description Get tauri version
+ * @returns {Promise<string>} Promise resolving to tauri version
+ */
+async function getTauriVersion(): Promise<string> {
+  return invokeTauriCommand<string>({
+    __tauriModule: 'App',
+    mainThread: true,
+    message: {
+      cmd: 'getTauriVersion'
+    }
+  })
+}
+
+/**
+ * @name exit
+ * @description Exits immediately with exitCode.
+ * @param [exitCode] defaults to 0.
+ * @returns {Promise<void>} Application is closing, nothing is returned
+ */
+async function exit(exitCode: Number = 0): Promise<void> {
+  return invokeTauriCommand<string>({
+    __tauriModule: 'App',
+    mainThread: true,
+    message: {
+      cmd: 'exit',
+      exitCode
+    }
+  })
+}
+
+/**
+ * @name relaunch
+ * @description Relaunches the app when current instance exits.
+ * @returns {Promise<void>} Application is restarting, nothing is returned
+ */
+async function relaunch(): Promise<void> {
+  return invokeTauriCommand<string>({
+    __tauriModule: 'App',
+    mainThread: true,
+    message: {
+      cmd: 'relaunch'
+    }
+  })
+}
+
+export { getName, getVersion, getTauriVersion, relaunch, exit }

+ 2 - 0
api/src/bundle.ts

@@ -1,4 +1,5 @@
 import 'regenerator-runtime/runtime'
+import * as app from './app'
 import * as cli from './cli'
 import * as dialog from './dialog'
 import * as event from './event'
@@ -13,6 +14,7 @@ import * as notification from './notification'
 import * as globalShortcut from './globalShortcut'
 
 export {
+  app,
   cli,
   dialog,
   event,

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/api/public/build/bundle.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
examples/api/public/build/bundle.js.map


+ 15 - 2
examples/api/src/components/Updater.svelte

@@ -1,13 +1,25 @@
 <script>
-
+  import { onMount, onDestroy } from "svelte";
+  
   // This example show how updater events work when dialog is disabled.
   // This allow you to use custom dialog for the updater.
   // This is your responsability to restart the application after you receive the STATUS: DONE.
 
   import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
+  import { listen } from "@tauri-apps/api/event";
+  import { relaunch } from "@tauri-apps/api/app";
 
   export let onMessage;
-
+  let unlisten;
+
+  onMount(async () => {
+    unlisten = await listen("tauri://update-status", onMessage)
+  })
+  onDestroy(() => {
+    if (unlisten) {
+      unlisten()
+    }
+  })
 
   async function check() {
     try {
@@ -31,6 +43,7 @@
 
       await installUpdate();
       onMessage("Installation complete, restart required.");
+      await relaunch();
 
     } catch(e) {
       onMessage(e);

+ 25 - 0
examples/api/src/components/Welcome.svelte

@@ -1,6 +1,31 @@
+<script>
+  import { getName, getVersion, getTauriVersion, relaunch, exit } from "@tauri-apps/api/app";
+  let version = 0.0;
+  let tauriVersion = 0.0;
+  let appName = 'Unknown';
+
+  getName().then(n => {appName = n});
+  getVersion().then(v => {version = v});
+  getTauriVersion().then(v => {tauriVersion = v});
+
+  async function closeApp() {
+    await exit();
+  }
+
+  async function relaunchApp() {
+    await relaunch();
+  }
+</script>
 <h1>Welcome</h1>
 <p>
   Tauri's API capabilities using the ` @tauri-apps/api ` package. It's used as
   the main validation app, serving as the testbed of our development process. In
   the future, this app will be used on Tauri's integration tests.
 </p>
+
+<p>Current App version: {version}</p>
+<p>Current Tauri version: {tauriVersion}</p>
+<p>Current App name: {appName}</p>
+
+<button class="button" on:click={closeApp}>Close application</button>
+<button class="button" on:click={relaunchApp}>Relaunch application</button>

+ 43 - 0
tauri-api/src/app.rs

@@ -0,0 +1,43 @@
+use std::{
+  env,
+  path::PathBuf,
+  process::{exit, Command},
+};
+
+/// Get the current binary
+pub fn current_binary() -> Option<PathBuf> {
+  let mut current_binary = None;
+
+  // if we are running with an APP Image, we should return the app image path
+  #[cfg(target_os = "linux")]
+  if let Some(app_image_path) = env::var_os("APPIMAGE") {
+    current_binary = Some(PathBuf::from(app_image_path));
+  }
+
+  // if we didn't extracted binary in previous step,
+  // let use the current_exe from current environment
+  if current_binary.is_none() {
+    if let Ok(current_process) = env::current_exe() {
+      current_binary = Some(current_process);
+    }
+  }
+
+  current_binary
+}
+
+/// Restart application
+pub fn restart_application(binary_to_start: Option<PathBuf>) {
+  let mut binary_path = binary_to_start;
+  // spawn new process
+  if binary_path.is_none() {
+    binary_path = current_binary();
+  }
+
+  if let Some(path) = binary_path {
+    Command::new(path)
+      .spawn()
+      .expect("application failed to start");
+  }
+
+  exit(0);
+}

+ 2 - 0
tauri-api/src/lib.rs

@@ -1,6 +1,8 @@
 //! The Tauri API interface.
 #![warn(missing_docs, rust_2018_idioms)]
 
+/// The App API module allows you to manage application processes.
+pub mod app;
 /// The Command API module allows you to manage child processes.
 pub mod command;
 /// The Dialog API module allows you to show messages and prompt for file paths.

+ 0 - 22
tauri-updater/src/lib.rs

@@ -239,23 +239,6 @@ impl<'a> UpdateBuilder<'a> {
     // Get the extract_path from the provided executable_path
     let extract_path = extract_path_from_executable(&executable_path);
 
-    // current binary used to restart the application
-    #[cfg(target_os = "windows")]
-    let current_binary = None;
-
-    #[cfg(not(target_os = "windows"))]
-    let mut current_binary = None;
-
-    #[cfg(target_os = "linux")]
-    if let Some(app_image_path) = env::var_os("APPIMAGE") {
-      current_binary = Some(PathBuf::from(app_image_path));
-    }
-
-    #[cfg(target_os = "macos")]
-    if let Ok(current_process) = std::env::current_exe() {
-      current_binary = Some(current_process);
-    }
-
     // Set SSL certs for linux if they aren't available.
     // We do not require to recheck in the download_and_install as we use
     // ENV variables, we can expect them to be set for the second call.
@@ -351,7 +334,6 @@ impl<'a> UpdateBuilder<'a> {
       download_url: final_release.download_url,
       body: final_release.body,
       signature: final_release.signature,
-      current_binary,
     })
   }
 }
@@ -372,10 +354,6 @@ pub struct Update {
   pub current_version: String,
   /// Update publish date
   pub date: String,
-  /// If announced, this is the process to start once the
-  /// update is completed. On linux it return the APPImage Path and macos
-  /// the current executable path. On windows we exit before this step.
-  pub current_binary: Option<PathBuf>,
   /// Target
   target: String,
   /// Extract path

+ 21 - 5
tauri/src/endpoints.rs

@@ -1,7 +1,11 @@
-use crate::{api::config::Config, hooks::InvokeMessage, runtime::Params};
+use crate::{
+  api::{config::Config, PackageInfo},
+  hooks::InvokeMessage,
+  runtime::Params,
+};
 use serde::{Deserialize, Serialize};
 use serde_json::Value as JsonValue;
-
+mod app;
 mod cli;
 mod dialog;
 mod event;
@@ -30,6 +34,7 @@ impl<T: Serialize> From<T> for InvokeResponse {
 #[derive(Deserialize)]
 #[serde(tag = "module", content = "message")]
 enum Module {
+  App(app::Cmd),
   Fs(file_system::Cmd),
   Window(Box<window::Cmd>),
   Shell(shell::Cmd),
@@ -43,9 +48,15 @@ enum Module {
 }
 
 impl Module {
-  fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config) {
+  fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config, package_info: PackageInfo) {
     let window = message.window();
     match self {
+      Self::App(cmd) => message.respond_async(async move {
+        cmd
+          .run(package_info)
+          .and_then(|r| r.json)
+          .map_err(|e| e.to_string())
+      }),
       Self::Fs(cmd) => message
         .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
       Self::Window(cmd) => message.respond_async(async move {
@@ -111,13 +122,18 @@ impl Module {
   }
 }
 
-pub(crate) fn handle<M: Params>(module: String, message: InvokeMessage<M>, config: &Config) {
+pub(crate) fn handle<M: Params>(
+  module: String,
+  message: InvokeMessage<M>,
+  config: &Config,
+  package_info: &PackageInfo,
+) {
   let mut payload = message.payload();
   if let JsonValue::Object(ref mut obj) = payload {
     obj.insert("module".to_string(), JsonValue::String(module));
   }
   match serde_json::from_value::<Module>(payload) {
-    Ok(module) => module.run(message, config),
+    Ok(module) => module.run(message, config, package_info.clone()),
     Err(e) => message.reject(e.to_string()),
   }
 }

+ 42 - 0
tauri/src/endpoints/app.rs

@@ -0,0 +1,42 @@
+use std::process::exit;
+
+use super::InvokeResponse;
+use crate::api::{app::restart_application, PackageInfo};
+use serde::Deserialize;
+
+/// The API descriptor.
+#[derive(Deserialize)]
+#[serde(tag = "cmd", rename_all = "camelCase")]
+pub enum Cmd {
+  /// Get Application Version
+  GetAppVersion,
+  /// Get Application Name
+  GetAppName,
+  /// Get Tauri Version
+  GetTauriVersion,
+  /// Relaunch application
+  Relaunch,
+  /// Close application with provided exit_code
+  #[serde(rename_all = "camelCase")]
+  Exit { exit_code: i32 },
+}
+
+impl Cmd {
+  pub fn run(self, package_info: PackageInfo) -> crate::Result<InvokeResponse> {
+    match self {
+      Self::GetAppVersion => Ok(package_info.version.into()),
+      Self::GetAppName => Ok(package_info.name.into()),
+      Self::GetTauriVersion => Ok(env!("CARGO_PKG_VERSION").into()),
+      Self::Relaunch => Ok({
+        restart_application(None);
+        ().into()
+      }),
+      Self::Exit { exit_code } => {
+        // would be great if we can have a handler inside tauri
+        // who close all window and emit an event that user can catch
+        // if they want to process something before closing the app
+        exit(exit_code);
+      }
+    }
+  }
+}

+ 2 - 20
tauri/src/runtime/updater.rs

@@ -1,14 +1,11 @@
 use crate::{
   api::{
+    app::restart_application,
     config::UpdaterConfig,
     dialog::{ask, AskResponse},
   },
   runtime::{window::Window, Params},
 };
-use std::{
-  path::PathBuf,
-  process::{exit, Command},
-};
 
 // Check for new updates
 pub const EVENT_CHECK_UPDATE: &str = "tauri://update";
@@ -181,8 +178,6 @@ pub(crate) fn listener<M: Params>(
                       );
                     } else {
                       // emit {"status": "DONE"}
-                      // todo(lemarier): maybe we should emit the
-                      // path of the current EXE so they can restart it
                       send_status_update(window.clone(), EVENT_STATUS_SUCCESS, None);
                     }
                   })
@@ -253,7 +248,7 @@ Release Notes:
       );
       match should_exit {
         AskResponse::Yes => {
-          restart_application(updater.current_binary.as_ref());
+          restart_application(None);
           // safely exit even if the process
           // should be killed
           return Ok(());
@@ -270,16 +265,3 @@ Release Notes:
 
   Ok(())
 }
-
-// Tested on macOS and Linux. Windows will not trigger the dialog
-// as it'll exit before, to launch the MSI installation.
-fn restart_application(binary_to_start: Option<&PathBuf>) {
-  // spawn new process
-  if let Some(path) = binary_to_start {
-    Command::new(path)
-      .spawn()
-      .expect("application failed to start");
-  }
-
-  exit(0);
-}

+ 1 - 1
tauri/src/runtime/window.rs

@@ -151,7 +151,7 @@ impl<M: Params> Window<M> {
       let message = InvokeMessage::new(self, command.to_string(), payload);
       if let Some(module) = &message.payload.tauri_module {
         let module = module.to_string();
-        crate::endpoints::handle(module, message, manager.config());
+        crate::endpoints::handle(module, message, manager.config(), manager.package_info());
       } else if command.starts_with("plugin:") {
         manager.extend_api(command, message);
       } else {

Vissa filer visades inte eftersom för många filer har ändrats