Skip to content

Commit e3b09be

Browse files
authored
feat(core): add channel interceptor API (#11362)
1 parent bcf2792 commit e3b09be

File tree

5 files changed

+49
-7
lines changed

5 files changed

+49
-7
lines changed

.changes/channel-interceptor.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:enhance
3+
---
4+
5+
Added `Builder::channel_interceptor` to intercept messages to be sent to the frontend, complemeting the `Builder::invoke_system` interface.

crates/tauri/src/app.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
use crate::{
66
image::Image,
77
ipc::{
8-
channel::ChannelDataIpcQueue, CommandArg, CommandItem, Invoke, InvokeError, InvokeHandler,
8+
channel::ChannelDataIpcQueue, CallbackFn, CommandArg, CommandItem, Invoke, InvokeError,
9+
InvokeHandler, InvokeResponseBody,
910
},
1011
manager::{webview::UriSchemeProtocol, AppManager, Asset},
1112
plugin::{Plugin, PluginStore},
@@ -15,8 +16,7 @@ use crate::{
1516
ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
1617
},
1718
sealed::{ManagerBase, RuntimeOrDispatch},
18-
utils::config::Config,
19-
utils::Env,
19+
utils::{config::Config, Env},
2020
webview::PageLoadPayload,
2121
Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result,
2222
Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
@@ -66,6 +66,8 @@ pub type SetupHook<R> =
6666
Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;
6767
/// A closure that is run every time a page starts or finishes loading.
6868
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
69+
pub type ChannelInterceptor<R> =
70+
Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;
6971

7072
/// The exit code on [`RunEvent::ExitRequested`] when [`AppHandle#method.restart`] is called.
7173
pub const RESTART_EXIT_CODE: i32 = i32::MAX;
@@ -1205,6 +1207,8 @@ pub struct Builder<R: Runtime> {
12051207
/// The script that initializes the `window.__TAURI_INTERNALS__.postMessage` function.
12061208
pub(crate) invoke_initialization_script: String,
12071209

1210+
channel_interceptor: Option<ChannelInterceptor<R>>,
1211+
12081212
/// The setup hook.
12091213
setup: SetupHook<R>,
12101214

@@ -1291,6 +1295,7 @@ impl<R: Runtime> Builder<R> {
12911295
.render_default(&Default::default())
12921296
.unwrap()
12931297
.into_string(),
1298+
channel_interceptor: None,
12941299
on_page_load: None,
12951300
plugins: PluginStore::default(),
12961301
uri_scheme_protocols: Default::default(),
@@ -1370,6 +1375,22 @@ impl<R: Runtime> Builder<R> {
13701375
self
13711376
}
13721377

1378+
/// Registers a channel interceptor that can overwrite the default channel implementation.
1379+
///
1380+
/// If the event has been consumed, it must return `true`.
1381+
///
1382+
/// The channel automatically orders the messages, so the third closure argument represents the message number.
1383+
/// The payload expected by the channel receiver is in the form of `{ id: usize, message: T }`.
1384+
pub fn channel_interceptor<
1385+
F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,
1386+
>(
1387+
mut self,
1388+
interceptor: F,
1389+
) -> Self {
1390+
self.channel_interceptor.replace(Box::new(interceptor));
1391+
self
1392+
}
1393+
13731394
/// Append a custom initialization script.
13741395
///
13751396
/// Allow to append custom initialization script instend of replacing entire invoke system.
@@ -1856,6 +1877,7 @@ tauri::Builder::default()
18561877
#[cfg(desktop)]
18571878
HashMap::new(),
18581879
self.invoke_initialization_script,
1880+
self.channel_interceptor,
18591881
self.invoke_key,
18601882
));
18611883

crates/tauri/src/ipc/channel.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use super::{CallbackFn, InvokeError, InvokeResponseBody, IpcResponse, Request, R
2424

2525
pub const IPC_PAYLOAD_PREFIX: &str = "__CHANNEL__:";
2626
pub const CHANNEL_PLUGIN_NAME: &str = "__TAURI_CHANNEL__";
27-
// TODO: ideally this const references CHANNEL_PLUGIN_NAME
2827
pub const FETCH_CHANNEL_DATA_COMMAND: &str = "plugin:__TAURI_CHANNEL__|fetch";
2928
pub(crate) const CHANNEL_ID_HEADER_NAME: &str = "Tauri-Channel-Id";
3029

@@ -101,6 +100,14 @@ impl JavaScriptChannelId {
101100
let counter = AtomicUsize::new(0);
102101

103102
Channel::new_with_id(callback_id.0, move |body| {
103+
let i = counter.fetch_add(1, Ordering::Relaxed);
104+
105+
if let Some(interceptor) = &webview.manager.channel_interceptor {
106+
if interceptor(&webview, callback_id, i, &body) {
107+
return Ok(());
108+
}
109+
}
110+
104111
let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);
105112

106113
webview
@@ -110,8 +117,6 @@ impl JavaScriptChannelId {
110117
.unwrap()
111118
.insert(data_id, body);
112119

113-
let i = counter.fetch_add(1, Ordering::Relaxed);
114-
115120
webview.eval(&format!(
116121
"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)",
117122
callback_id.0

crates/tauri/src/ipc/protocol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ mod tests {
590590
Default::default(),
591591
Default::default(),
592592
"".into(),
593+
None,
593594
crate::generate_invoke_key().unwrap(),
594595
);
595596

@@ -704,6 +705,7 @@ mod tests {
704705
Default::default(),
705706
Default::default(),
706707
"".into(),
708+
None,
707709
crate::generate_invoke_key().unwrap(),
708710
);
709711

crates/tauri/src/manager/mod.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use tauri_utils::{
2020
};
2121

2222
use crate::{
23-
app::{AppHandle, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad},
23+
app::{
24+
AppHandle, ChannelInterceptor, GlobalWebviewEventListener, GlobalWindowEventListener,
25+
OnPageLoad,
26+
},
2427
event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
2528
ipc::{Invoke, InvokeHandler, RuntimeAuthority},
2629
plugin::PluginStore,
@@ -216,6 +219,8 @@ pub struct AppManager<R: Runtime> {
216219

217220
/// Runtime-generated invoke key.
218221
pub(crate) invoke_key: String,
222+
223+
pub(crate) channel_interceptor: Option<ChannelInterceptor<R>>,
219224
}
220225

221226
impl<R: Runtime> fmt::Debug for AppManager<R> {
@@ -256,6 +261,7 @@ impl<R: Runtime> AppManager<R> {
256261
crate::app::GlobalMenuEventListener<Window<R>>,
257262
>,
258263
invoke_initialization_script: String,
264+
channel_interceptor: Option<ChannelInterceptor<R>>,
259265
invoke_key: String,
260266
) -> Self {
261267
// generate a random isolation key at runtime
@@ -307,6 +313,7 @@ impl<R: Runtime> AppManager<R> {
307313
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
308314
resources_table: Arc::default(),
309315
invoke_key,
316+
channel_interceptor,
310317
}
311318
}
312319

@@ -733,6 +740,7 @@ mod test {
733740
Default::default(),
734741
Default::default(),
735742
"".into(),
743+
None,
736744
crate::generate_invoke_key().unwrap(),
737745
);
738746

0 commit comments

Comments
 (0)