소스 검색

feat(core): allow defining a custom invoke system (#2899)

Lucas Fernandes Nogueira 3 년 전
부모
커밋
15164d930a

+ 5 - 0
.changes/custom-invoke-system.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added an API to use a custom invoke system to receive and respond to commands (`Builder#invoke_system`).

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
core/tauri/scripts/bundle.js


+ 29 - 98
core/tauri/scripts/core.js

@@ -2,7 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-;(function () {
+;
+(function () {
   function uid() {
     const length = new Int8Array(1)
     window.crypto.getRandomValues(length)
@@ -11,58 +12,6 @@
     return array.join('')
   }
 
-  function ownKeys(object, enumerableOnly) {
-    var keys = Object.keys(object)
-    if (Object.getOwnPropertySymbols) {
-      var symbols = Object.getOwnPropertySymbols(object)
-      if (enumerableOnly)
-        symbols = symbols.filter(function (sym) {
-          return Object.getOwnPropertyDescriptor(object, sym).enumerable
-        })
-      keys.push.apply(keys, symbols)
-    }
-    return keys
-  }
-
-  function _objectSpread(target) {
-    for (var i = 1; i < arguments.length; i++) {
-      var source = arguments[i] != null ? arguments[i] : {}
-      if (i % 2) {
-        ownKeys(source, true).forEach(function (key) {
-          _defineProperty(target, key, source[key])
-        })
-      } else if (Object.getOwnPropertyDescriptors) {
-        Object.defineProperties(
-          target,
-          Object.getOwnPropertyDescriptors(source)
-        )
-      } else {
-        ownKeys(source).forEach(function (key) {
-          Object.defineProperty(
-            target,
-            key,
-            Object.getOwnPropertyDescriptor(source, key)
-          )
-        })
-      }
-    }
-    return target
-  }
-
-  function _defineProperty(obj, key, value) {
-    if (key in obj) {
-      Object.defineProperty(obj, key, {
-        value: value,
-        enumerable: true,
-        configurable: true,
-        writable: true
-      })
-    } else {
-      obj[key] = value
-    }
-    return obj
-  }
-
   if (!window.__TAURI__) {
     window.__TAURI__ = {}
   }
@@ -103,30 +52,24 @@
         return reject(new Error('Invalid argument type.'))
       }
 
-      if (window.rpc) {
-        window.rpc.notify(
-          cmd,
-          _objectSpread(
-            {
-              callback: callback,
-              error: error,
-              __invokeKey: key || __TAURI_INVOKE_KEY__
-            },
-            args
-          )
+      if (window.__TAURI_POST_MESSAGE__) {
+        window.__TAURI_POST_MESSAGE__(
+          cmd, {
+            ...args,
+            callback: callback,
+            error: error,
+            __invokeKey: key || __TAURI_INVOKE_KEY__
+          }
         )
       } else {
         window.addEventListener('DOMContentLoaded', function () {
-          window.rpc.notify(
-            cmd,
-            _objectSpread(
-              {
-                callback: callback,
-                error: error,
-                __invokeKey: key || __TAURI_INVOKE_KEY__
-              },
-              args
-            )
+          window.__TAURI_POST_MESSAGE__(
+            cmd, {
+              ...args,
+              callback: callback,
+              error: error,
+              __invokeKey: key || __TAURI_INVOKE_KEY__
+            }
           )
         })
       }
@@ -147,8 +90,7 @@
               target.target === '_blank'
             ) {
               window.__TAURI_INVOKE__(
-                'tauri',
-                {
+                'tauri', {
                   __tauriModule: 'Shell',
                   message: {
                     cmd: 'open',
@@ -188,8 +130,7 @@
     if (e.target.hasAttribute('data-tauri-drag-region') && e.buttons === 1) {
       // start dragging if the element has a `tauri-drag-region` data attribute and maximize on double-clicking it
       window.__TAURI_INVOKE__(
-        'tauri',
-        {
+        'tauri', {
           __tauriModule: 'Window',
           message: {
             cmd: 'manage',
@@ -206,8 +147,7 @@
   })
 
   window.__TAURI_INVOKE__(
-    'tauri',
-    {
+    'tauri', {
       __tauriModule: 'Event',
       message: {
         cmd: 'listen',
@@ -233,8 +173,7 @@
       return Promise.resolve(window.Notification.permission === 'granted')
     }
     return window.__TAURI_INVOKE__(
-      'tauri',
-      {
+      'tauri', {
         __tauriModule: 'Notification',
         message: {
           cmd: 'isNotificationPermissionGranted'
@@ -253,8 +192,7 @@
   function requestPermission() {
     return window
       .__TAURI_INVOKE__(
-        'tauri',
-        {
+        'tauri', {
           __tauriModule: 'Notification',
           message: {
             cmd: 'requestNotificationPermission'
@@ -276,17 +214,13 @@
     isPermissionGranted().then(function (permission) {
       if (permission) {
         return window.__TAURI_INVOKE__(
-          'tauri',
-          {
+          'tauri', {
             __tauriModule: 'Notification',
             message: {
               cmd: 'notification',
-              options:
-                typeof options === 'string'
-                  ? {
-                      title: options
-                    }
-                  : options
+              options: typeof options === 'string' ? {
+                title: options
+              } : options
             }
           },
           _KEY_VALUE_
@@ -329,8 +263,7 @@
 
   window.alert = function (message) {
     window.__TAURI_INVOKE__(
-      'tauri',
-      {
+      'tauri', {
         __tauriModule: 'Dialog',
         message: {
           cmd: 'messageDialog',
@@ -343,8 +276,7 @@
 
   window.confirm = function (message) {
     return window.__TAURI_INVOKE__(
-      'tauri',
-      {
+      'tauri', {
         __tauriModule: 'Dialog',
         message: {
           cmd: 'confirmDialog',
@@ -359,8 +291,7 @@
   if (navigator.userAgent.includes('Mac')) {
     window.print = function () {
       return window.__TAURI_INVOKE__(
-        'tauri',
-        {
+        'tauri', {
           __tauriModule: 'Window',
           message: {
             cmd: 'manage',

+ 29 - 2
core/tauri/src/app.rs

@@ -7,7 +7,9 @@ pub(crate) mod tray;
 
 use crate::{
   command::{CommandArg, CommandItem},
-  hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook},
+  hooks::{
+    window_invoke_responder, InvokeHandler, InvokeResponder, OnPageLoad, PageLoadPayload, SetupHook,
+  },
   manager::{Asset, CustomProtocol, WindowManager},
   plugin::{Plugin, PluginStore},
   runtime::{
@@ -19,7 +21,7 @@ use crate::{
   sealed::{ManagerBase, RuntimeOrDispatch},
   utils::assets::Assets,
   utils::config::{Config, WindowUrl},
-  Context, Invoke, InvokeError, Manager, StateManager, Window,
+  Context, Invoke, InvokeError, InvokeResponse, Manager, StateManager, Window,
 };
 
 use tauri_macros::default_runtime;
@@ -582,6 +584,12 @@ pub struct Builder<R: Runtime> {
   /// The JS message handler.
   invoke_handler: Box<InvokeHandler<R>>,
 
+  /// The JS message responder.
+  invoke_responder: Arc<InvokeResponder<R>>,
+
+  /// The script that initializes the `window.__TAURI_POST_MESSAGE__` function.
+  invoke_initialization_script: String,
+
   /// The setup hook.
   setup: SetupHook<R>,
 
@@ -624,6 +632,9 @@ impl<R: Runtime> Builder<R> {
     Self {
       setup: Box::new(|_| Ok(())),
       invoke_handler: Box::new(|_| ()),
+      invoke_responder: Arc::new(window_invoke_responder),
+      invoke_initialization_script:
+        "Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (cmd, args) => window.rpc.notify(cmd, args) })".into(),
       on_page_load: Box::new(|_, _| ()),
       pending_windows: Default::default(),
       plugins: PluginStore::default(),
@@ -648,6 +659,21 @@ impl<R: Runtime> Builder<R> {
     self
   }
 
+  /// Defines a custom JS message system.
+  ///
+  /// The `responder` is a function that will be called when a command has been executed and must send a response to the JS layer.
+  ///
+  /// The `initialization_script` is a script that initializes `window.__TAURI_POST_MESSAGE__`.
+  /// That function must take the `command: string` and `args: object` types and send a message to the backend.
+  pub fn invoke_system<F>(mut self, initialization_script: String, responder: F) -> Self
+  where
+    F: Fn(Window<R>, InvokeResponse, String, String) + Send + Sync + 'static,
+  {
+    self.invoke_initialization_script = initialization_script;
+    self.invoke_responder = Arc::new(responder);
+    self
+  }
+
   /// Defines the setup hook.
   pub fn setup<F>(mut self, setup: F) -> Self
   where
@@ -905,6 +931,7 @@ impl<R: Runtime> Builder<R> {
       self.state,
       self.window_event_listeners,
       (self.menu, self.menu_event_listeners),
+      (self.invoke_responder, self.invoke_initialization_script),
     );
 
     // set up all the windows defined in the config

+ 36 - 51
core/tauri/src/hooks.rs

@@ -21,6 +21,10 @@ pub type SetupHook<R> =
 /// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
 pub type InvokeHandler<R> = dyn Fn(Invoke<R>) + Send + Sync + 'static;
 
+/// A closure that is responsible for respond a JS message.
+pub type InvokeResponder<R> =
+  dyn Fn(Window<R>, InvokeResponse, String, String) + Send + Sync + 'static;
+
 /// A closure that is run once every time a window is created and loaded.
 pub type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static;
 
@@ -140,7 +144,7 @@ impl<R: Runtime> InvokeResolver<R> {
     F: Future<Output = Result<T, InvokeError>> + Send + 'static,
   {
     crate::async_runtime::spawn(async move {
-      Self::return_task(self.window, task, self.callback, self.error).await;
+      self.return_task(task).await;
     });
   }
 
@@ -150,42 +154,28 @@ impl<R: Runtime> InvokeResolver<R> {
     F: Future<Output = Result<JsonValue, InvokeError>> + Send + 'static,
   {
     crate::async_runtime::spawn(async move {
-      Self::return_result(self.window, task.await.into(), self.callback, self.error);
+      self.return_result(task.await.into());
     });
   }
 
   /// Reply to the invoke promise with a serializable value.
   pub fn respond<T: Serialize>(self, value: Result<T, InvokeError>) {
-    Self::return_result(self.window, value.into(), self.callback, self.error)
-  }
-
-  /// Reply to the invoke promise running the given closure.
-  pub fn respond_closure<T, F>(self, f: F)
-  where
-    T: Serialize,
-    F: FnOnce() -> Result<T, InvokeError>,
-  {
-    Self::return_closure(self.window, f, self.callback, self.error)
+    self.return_result(value.into())
   }
 
   /// Resolve the invoke promise with a value.
   pub fn resolve<T: Serialize>(self, value: T) {
-    Self::return_result(self.window, Ok(value).into(), self.callback, self.error)
+    self.return_result(Ok(value).into())
   }
 
   /// Reject the invoke promise with a value.
   pub fn reject<T: Serialize>(self, value: T) {
-    Self::return_result(
-      self.window,
-      Result::<(), _>::Err(value.into()).into(),
-      self.callback,
-      self.error,
-    )
+    self.return_result(Result::<(), _>::Err(value.into()).into())
   }
 
   /// Reject the invoke promise with an [`InvokeError`].
   pub fn invoke_error(self, error: InvokeError) {
-    Self::return_result(self.window, error.into(), self.callback, self.error)
+    self.return_result(error.into())
   }
 
   /// Asynchronously executes the given task
@@ -193,48 +183,43 @@ impl<R: Runtime> InvokeResolver<R> {
   ///
   /// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
   /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
-  pub async fn return_task<T, F>(
-    window: Window<R>,
-    task: F,
-    success_callback: String,
-    error_callback: String,
-  ) where
+  pub async fn return_task<T, F>(self, task: F)
+  where
     T: Serialize,
     F: Future<Output = Result<T, InvokeError>> + Send + 'static,
   {
     let result = task.await;
-    Self::return_closure(window, || result, success_callback, error_callback)
+    self.return_closure(|| result)
   }
 
-  pub(crate) fn return_closure<T: Serialize, F: FnOnce() -> Result<T, InvokeError>>(
-    window: Window<R>,
-    f: F,
-    success_callback: String,
-    error_callback: String,
-  ) {
-    Self::return_result(window, f().into(), success_callback, error_callback)
+  pub(crate) fn return_closure<T: Serialize, F: FnOnce() -> Result<T, InvokeError>>(self, f: F) {
+    self.return_result(f().into())
   }
 
-  pub(crate) fn return_result(
-    window: Window<R>,
-    response: InvokeResponse,
-    success_callback: String,
-    error_callback: String,
-  ) {
-    let callback_string = match format_callback_result(
-      response.into_result(),
-      success_callback,
-      error_callback.clone(),
-    ) {
-      Ok(callback_string) => callback_string,
-      Err(e) => format_callback(error_callback, &e.to_string())
-        .expect("unable to serialize shortcut string to json"),
-    };
-
-    let _ = window.eval(&callback_string);
+  fn return_result(self, response: InvokeResponse) {
+    (self.window.invoke_responder())(self.window, response, self.callback, self.error);
   }
 }
 
+pub fn window_invoke_responder<R: Runtime>(
+  window: Window<R>,
+  response: InvokeResponse,
+  success_callback: String,
+  error_callback: String,
+) {
+  let callback_string = match format_callback_result(
+    response.into_result(),
+    success_callback,
+    error_callback.clone(),
+  ) {
+    Ok(callback_string) => callback_string,
+    Err(e) => format_callback(error_callback, &e.to_string())
+      .expect("unable to serialize shortcut string to json"),
+  };
+
+  let _ = window.eval(&callback_string);
+}
+
 /// An invoke message.
 #[default_runtime(crate::Wry, wry)]
 #[derive(Debug)]

+ 3 - 3
core/tauri/src/lib.rs

@@ -90,12 +90,12 @@ pub use {
     App, AppHandle, AssetResolver, Builder, CloseRequestApi, Event, GlobalWindowEvent, PathResolver,
   },
   self::hooks::{
-    Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
-    PageLoadPayload, SetupHook,
+    Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponder,
+    InvokeResponse, OnPageLoad, PageLoadPayload, SetupHook,
   },
   self::manager::Asset,
   self::runtime::{
-    webview::{WebviewAttributes, WindowBuilder},
+    webview::{InvokePayload, WebviewAttributes, WindowBuilder},
     window::{
       dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
       WindowEvent,

+ 33 - 18
core/tauri/src/manager.rs

@@ -5,7 +5,7 @@
 use crate::{
   app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
   event::{Event, EventHandler, Listeners},
-  hooks::{InvokeHandler, OnPageLoad, PageLoadPayload},
+  hooks::{InvokeHandler, InvokeResponder, OnPageLoad, PageLoadPayload},
   plugin::PluginStore,
   runtime::{
     http::{
@@ -80,6 +80,10 @@ pub struct InnerWindowManager<R: Runtime> {
   menu_event_listeners: Arc<Vec<GlobalMenuEventListener<R>>>,
   /// Window event listeners to all windows.
   window_event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
+  /// Responder for invoke calls.
+  invoke_responder: Arc<InvokeResponder<R>>,
+  /// The script that initializes the invoke system.
+  invoke_initialization_script: String,
 }
 
 impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
@@ -141,6 +145,7 @@ impl<R: Runtime> WindowManager<R> {
     state: StateManager,
     window_event_listeners: Vec<GlobalWindowEventListener<R>>,
     (menu, menu_event_listeners): (Option<Menu>, Vec<GlobalMenuEventListener<R>>),
+    (invoke_responder, invoke_initialization_script): (Arc<InvokeResponder<R>>, String),
   ) -> Self {
     Self {
       inner: Arc::new(InnerWindowManager {
@@ -158,6 +163,8 @@ impl<R: Runtime> WindowManager<R> {
         menu,
         menu_event_listeners: Arc::new(menu_event_listeners),
         window_event_listeners: Arc::new(window_event_listeners),
+        invoke_responder,
+        invoke_initialization_script,
       }),
       invoke_keys: Default::default(),
     }
@@ -173,6 +180,11 @@ impl<R: Runtime> WindowManager<R> {
     self.inner.state.clone()
   }
 
+  /// The invoke responder.
+  pub(crate) fn invoke_responder(&self) -> Arc<InvokeResponder<R>> {
+    self.inner.invoke_responder.clone()
+  }
+
   /// Get the base path to serve data from.
   ///
   /// * In dev mode, this will be based on the `devPath` configuration value.
@@ -226,16 +238,31 @@ impl<R: Runtime> WindowManager<R> {
       .initialization_script();
 
     let mut webview_attributes = pending.webview_attributes;
+    webview_attributes =
+      webview_attributes.initialization_script(&self.inner.invoke_initialization_script);
+    if is_init_global {
+      webview_attributes = webview_attributes.initialization_script(&format!(
+        "(function () {{
+        const __TAURI_INVOKE_KEY__ = {key};
+        {bundle_script}
+        }})()",
+        key = self.generate_invoke_key(),
+        bundle_script = include_str!("../scripts/bundle.js"),
+      ));
+    }
     webview_attributes = webview_attributes
-      .initialization_script(&self.initialization_script(&plugin_init, is_init_global))
       .initialization_script(&format!(
         r#"
+          if (!window.__TAURI__) {{
+            window.__TAURI__ = {{}}
+          }}
           window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
           window.__TAURI__.__currentWindow = {{ label: {current_window_label} }}
         "#,
         window_labels_array = serde_json::to_string(pending_labels)?,
         current_window_label = serde_json::to_string(&label)?,
-      ));
+      ))
+      .initialization_script(&self.initialization_script(&plugin_init));
 
     #[cfg(dev)]
     {
@@ -476,21 +503,13 @@ impl<R: Runtime> WindowManager<R> {
     })
   }
 
-  fn initialization_script(
-    &self,
-    plugin_initialization_script: &str,
-    with_global_tauri: bool,
-  ) -> String {
+  fn initialization_script(&self, plugin_initialization_script: &str) -> String {
     let key = self.generate_invoke_key();
     format!(
       r#"
-      (function () {{
-        const __TAURI_INVOKE_KEY__ = {key};
-        {bundle_script}
-      }})()
       {core_script}
       {event_initialization_script}
-      if (window.rpc) {{
+      if (window.__TAURI_INVOKE__) {{
         window.__TAURI_INVOKE__("__initialized", {{ url: window.location.href }}, {key})
       }} else {{
         window.addEventListener('DOMContentLoaded', function () {{
@@ -501,11 +520,6 @@ impl<R: Runtime> WindowManager<R> {
     "#,
       key = key,
       core_script = include_str!("../scripts/core.js").replace("_KEY_VALUE_", &key.to_string()),
-      bundle_script = if with_global_tauri {
-        include_str!("../scripts/bundle.js")
-      } else {
-        ""
-      },
       event_initialization_script = self.event_initialization_script(),
       plugin_initialization_script = plugin_initialization_script
     )
@@ -547,6 +561,7 @@ mod test {
       StateManager::new(),
       Default::default(),
       Default::default(),
+      (std::sync::Arc::new(|_, _, _, _| ()), "".into()),
     );
 
     #[cfg(custom_protocol)]

+ 10 - 2
core/tauri/src/window.rs

@@ -12,6 +12,7 @@ use crate::{
   app::AppHandle,
   command::{CommandArg, CommandItem},
   event::{Event, EventHandler},
+  hooks::InvokeResponder,
   manager::WindowManager,
   runtime::{
     monitor::Monitor as RuntimeMonitor,
@@ -32,7 +33,10 @@ use serde::Serialize;
 
 use tauri_macros::default_runtime;
 
-use std::hash::{Hash, Hasher};
+use std::{
+  hash::{Hash, Hasher},
+  sync::Arc,
+};
 
 /// Monitor descriptor.
 #[derive(Debug, Clone, Serialize)]
@@ -201,6 +205,10 @@ impl<R: Runtime> Window<R> {
     ))
   }
 
+  pub(crate) fn invoke_responder(&self) -> Arc<InvokeResponder<R>> {
+    self.manager.invoke_responder()
+  }
+
   /// The current window's dispatcher.
   pub(crate) fn dispatcher(&self) -> R::Dispatcher {
     self.window.dispatcher.clone()
@@ -216,7 +224,7 @@ impl<R: Runtime> Window<R> {
   }
 
   /// How to handle this window receiving an [`InvokeMessage`].
-  pub(crate) fn on_message(self, command: String, payload: InvokePayload) -> crate::Result<()> {
+  pub fn on_message(self, command: String, payload: InvokePayload) -> crate::Result<()> {
     let manager = self.manager.clone();
     match command.as_str() {
       "__initialized" => {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
examples/api/public/build/bundle.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
examples/api/public/build/bundle.js.map


+ 5 - 4
tooling/api/src/tauri.ts

@@ -13,9 +13,10 @@
 declare global {
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   interface Window {
-    rpc: {
-      notify: (command: string, args?: { [key: string]: unknown }) => void
-    }
+    __TAURI_POST_MESSAGE__: (
+      command: string,
+      args?: { [key: string]: unknown }
+    ) => void
   }
 }
 
@@ -82,7 +83,7 @@ async function invoke<T>(cmd: string, args: InvokeArgs = {}): Promise<T> {
       Reflect.deleteProperty(window, callback)
     }, true)
 
-    window.rpc.notify(cmd, {
+    window.__TAURI_POST_MESSAGE__(cmd, {
       __invokeKey: __TAURI_INVOKE_KEY__,
       callback,
       error,

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.