Browse Source

feat(core): add channel interceptor API (#11362)

Lucas Fernandes Nogueira 9 tháng trước cách đây
mục cha
commit
e3b09be7f0

+ 5 - 0
.changes/channel-interceptor.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:enhance
+---
+
+Added `Builder::channel_interceptor` to intercept messages to be sent to the frontend, complemeting the `Builder::invoke_system` interface.

+ 25 - 3
crates/tauri/src/app.rs

@@ -5,7 +5,8 @@
 use crate::{
   image::Image,
   ipc::{
-    channel::ChannelDataIpcQueue, CommandArg, CommandItem, Invoke, InvokeError, InvokeHandler,
+    channel::ChannelDataIpcQueue, CallbackFn, CommandArg, CommandItem, Invoke, InvokeError,
+    InvokeHandler, InvokeResponseBody,
   },
   manager::{webview::UriSchemeProtocol, AppManager, Asset},
   plugin::{Plugin, PluginStore},
@@ -15,8 +16,7 @@ use crate::{
     ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
   },
   sealed::{ManagerBase, RuntimeOrDispatch},
-  utils::config::Config,
-  utils::Env,
+  utils::{config::Config, Env},
   webview::PageLoadPayload,
   Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result,
   Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
@@ -66,6 +66,8 @@ pub type SetupHook<R> =
   Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;
 /// A closure that is run every time a page starts or finishes loading.
 pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
+pub type ChannelInterceptor<R> =
+  Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;
 
 /// The exit code on [`RunEvent::ExitRequested`] when [`AppHandle#method.restart`] is called.
 pub const RESTART_EXIT_CODE: i32 = i32::MAX;
@@ -1205,6 +1207,8 @@ pub struct Builder<R: Runtime> {
   /// The script that initializes the `window.__TAURI_INTERNALS__.postMessage` function.
   pub(crate) invoke_initialization_script: String,
 
+  channel_interceptor: Option<ChannelInterceptor<R>>,
+
   /// The setup hook.
   setup: SetupHook<R>,
 
@@ -1291,6 +1295,7 @@ impl<R: Runtime> Builder<R> {
       .render_default(&Default::default())
       .unwrap()
       .into_string(),
+      channel_interceptor: None,
       on_page_load: None,
       plugins: PluginStore::default(),
       uri_scheme_protocols: Default::default(),
@@ -1370,6 +1375,22 @@ impl<R: Runtime> Builder<R> {
     self
   }
 
+  /// Registers a channel interceptor that can overwrite the default channel implementation.
+  ///
+  /// If the event has been consumed, it must return `true`.
+  ///
+  /// The channel automatically orders the messages, so the third closure argument represents the message number.
+  /// The payload expected by the channel receiver is in the form of `{ id: usize, message: T }`.
+  pub fn channel_interceptor<
+    F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,
+  >(
+    mut self,
+    interceptor: F,
+  ) -> Self {
+    self.channel_interceptor.replace(Box::new(interceptor));
+    self
+  }
+
   /// Append a custom initialization script.
   ///
   /// Allow to append custom initialization script instend of replacing entire invoke system.
@@ -1856,6 +1877,7 @@ tauri::Builder::default()
       #[cfg(desktop)]
       HashMap::new(),
       self.invoke_initialization_script,
+      self.channel_interceptor,
       self.invoke_key,
     ));
 

+ 8 - 3
crates/tauri/src/ipc/channel.rs

@@ -24,7 +24,6 @@ use super::{CallbackFn, InvokeError, InvokeResponseBody, IpcResponse, Request, R
 
 pub const IPC_PAYLOAD_PREFIX: &str = "__CHANNEL__:";
 pub const CHANNEL_PLUGIN_NAME: &str = "__TAURI_CHANNEL__";
-// TODO: ideally this const references CHANNEL_PLUGIN_NAME
 pub const FETCH_CHANNEL_DATA_COMMAND: &str = "plugin:__TAURI_CHANNEL__|fetch";
 pub(crate) const CHANNEL_ID_HEADER_NAME: &str = "Tauri-Channel-Id";
 
@@ -101,6 +100,14 @@ impl JavaScriptChannelId {
     let counter = AtomicUsize::new(0);
 
     Channel::new_with_id(callback_id.0, move |body| {
+      let i = counter.fetch_add(1, Ordering::Relaxed);
+
+      if let Some(interceptor) = &webview.manager.channel_interceptor {
+        if interceptor(&webview, callback_id, i, &body) {
+          return Ok(());
+        }
+      }
+
       let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);
 
       webview
@@ -110,8 +117,6 @@ impl JavaScriptChannelId {
         .unwrap()
         .insert(data_id, body);
 
-      let i = counter.fetch_add(1, Ordering::Relaxed);
-
       webview.eval(&format!(
         "window.__TAURI_INTERNALS__.invoke('{FETCH_CHANNEL_DATA_COMMAND}', null, {{ headers: {{ '{CHANNEL_ID_HEADER_NAME}': '{data_id}' }} }}).then((response) => window['_' + {}]({{ message: response, id: {i} }})).catch(console.error)",
         callback_id.0

+ 2 - 0
crates/tauri/src/ipc/protocol.rs

@@ -590,6 +590,7 @@ mod tests {
       Default::default(),
       Default::default(),
       "".into(),
+      None,
       crate::generate_invoke_key().unwrap(),
     );
 
@@ -704,6 +705,7 @@ mod tests {
       Default::default(),
       Default::default(),
       "".into(),
+      None,
       crate::generate_invoke_key().unwrap(),
     );
 

+ 9 - 1
crates/tauri/src/manager/mod.rs

@@ -20,7 +20,10 @@ use tauri_utils::{
 };
 
 use crate::{
-  app::{AppHandle, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad},
+  app::{
+    AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener,
+    OnPageLoad,
+  },
   event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
   ipc::{Invoke, InvokeHandler, RuntimeAuthority},
   plugin::PluginStore,
@@ -216,6 +219,8 @@ pub struct AppManager<R: Runtime> {
 
   /// Runtime-generated invoke key.
   pub(crate) invoke_key: String,
+
+  pub(crate) channel_interceptor: Option<ChannelInterceptor<R>>,
 }
 
 impl<R: Runtime> fmt::Debug for AppManager<R> {
@@ -256,6 +261,7 @@ impl<R: Runtime> AppManager<R> {
       crate::app::GlobalMenuEventListener<Window<R>>,
     >,
     invoke_initialization_script: String,
+    channel_interceptor: Option<ChannelInterceptor<R>>,
     invoke_key: String,
   ) -> Self {
     // generate a random isolation key at runtime
@@ -307,6 +313,7 @@ impl<R: Runtime> AppManager<R> {
       plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
       resources_table: Arc::default(),
       invoke_key,
+      channel_interceptor,
     }
   }
 
@@ -733,6 +740,7 @@ mod test {
       Default::default(),
       Default::default(),
       "".into(),
+      None,
       crate::generate_invoke_key().unwrap(),
     );